text: Georgian document language; no initial capitals for Georgian

Georgian is a Properties text language (IDML "Georgian" maps to ka). Georgian has no initial capitals: Mtavruli is only for all-caps text, so Change Case title and sentence case and index section heads leave Mkhedruli as it is (doc::capitalizes); upper case still gives Mtavruli.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
This commit is contained in:
Davit Matchakhelidze
2026-10-14 20:55:04 +04:00
co-authored by Claude Opus 5.5
parent 19bacaadd7
commit 5d4ed2a663
5 changed files with 44 additions and 3 deletions
+9
View File
@@ -190,6 +190,13 @@ impl Digits {
}
}
/// Does `c` belong to a script that capitalizes the first letter of sentences, words and headings?
/// Georgian doesn't: Unicode maps Mkhedruli to Mtavruli capitals, but those are only for all-caps
/// text, never for a sentence's, a title's or an index heading's first letter.
pub fn capitalizes(c: char) -> bool {
!matches!(c, '\u{10A0}'..='\u{10FF}' | '\u{1C90}'..='\u{1CBF}' | '\u{2D00}'..='\u{2D2F}')
}
/// The BCP 47 tag of a language name as InDesign and IDML write it ("English: USA", "Chinese:
/// Simplified", or a locale code such as "de_DE_2006"), for the shaper's localized forms (`locl`)
/// and everything that depends on the language (quotes, hyphenation and spelling, line breaking,
@@ -272,6 +279,7 @@ pub fn language_tag(language: &str) -> Option<&'static str> {
"persian" | "farsi" => "fa",
"urdu" => "ur",
"hebrew" => "he",
"georgian" => "ka",
_ => return None,
})
}
@@ -1004,6 +1012,7 @@ mod tests {
assert_eq!(language_tag("English: USA"), Some("en-US"));
assert_eq!(language_tag("English: Australian"), Some("en"));
assert_eq!(language_tag("Turkish"), Some("tr"));
assert_eq!(language_tag("Georgian"), Some("ka"));
assert_eq!(language_tag("Japanese"), Some("ja"));
assert_eq!(language_tag("Korean"), Some("ko"));
assert_eq!(language_tag("Chinese"), Some("zh"));
+14 -1
View File
@@ -137,7 +137,12 @@ pub fn build(spec: &IndexSpec, refs: &[Resolved], page_name: &dyn Fn(usize) -> S
let mut heading: Option<char> = None;
for (key, n) in &root {
if spec.section_headings {
let c = key.chars().next().map(|c| if c.is_alphabetic() { c.to_uppercase().next().unwrap_or(c) } else { '#' });
// Georgian headings stay Mkhedruli (no capitals; Mtavruli is only for all-caps text).
let c = key.chars().next().map(|c| match c {
c if !c.is_alphabetic() => '#',
c if crate::capitalizes(c) => c.to_uppercase().next().unwrap_or(c),
c => c,
});
if c != heading {
heading = c;
if let Some(c) = c {
@@ -243,4 +248,12 @@ mod tests {
let run_in = build(&IndexSpec { run_in: true, section_headings: false, title: String::new(), ..spec }, &refs[..2], &|p| (p + 1).to_string());
assert_eq!(run_in, [("Index Level 1".to_string(), "Typography 2\u{2013}4: kerning 5".to_string())]);
}
#[test]
fn georgian_section_heads_stay_mkhedruli() {
let refs = vec![r(&["ასო"], IndexRange::CurrentPage, Some((0, 0))), r(&["ბადე"], IndexRange::CurrentPage, Some((1, 1)))];
let out = build(&IndexSpec::default(), &refs, &|p| (p + 1).to_string());
let heads: Vec<&str> = out.iter().filter(|(s, _)| s == "Index Section Head").map(|(_, t)| t.as_str()).collect();
assert_eq!(heads, ["ა", "ბ"]);
assert!(crate::capitalizes('a') && !crate::capitalizes('ა') && !crate::capitalizes('Ა'));
}
}
+16
View File
@@ -649,6 +649,22 @@ mod text_clipboard_tests {
assert_eq!(s.doc().unwrap().selection.text.unwrap().range(), 0..st.len());
st.check().unwrap();
}
#[test]
fn change_case_leaves_georgian_without_initial_capitals() {
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
let a = frame(&mut s, "ᲒᲐᲛᲐᲠᲯᲝᲑᲐ ᲛᲡᲝᲤᲚᲘᲝ. ᲐᲮᲐᲚᲘ word");
let len = s.doc().unwrap().doc.story(a).unwrap().len();
s.execute("text.select", &json!({"story": a.0, "anchor": 0, "focus": len})).unwrap();
// Georgian has no title or sentence capitals: Mtavruli is only for all-caps text.
s.execute("type.changeCase", &json!({"case": "title"})).unwrap();
assert_eq!(s.doc().unwrap().doc.story(a).unwrap().text, "გამარჯობა მსოფლიო. ახალი Word");
s.execute("type.changeCase", &json!({"case": "sentence"})).unwrap();
assert_eq!(s.doc().unwrap().doc.story(a).unwrap().text, "გამარჯობა მსოფლიო. ახალი word");
s.execute("type.changeCase", &json!({"case": "upper"})).unwrap();
assert_eq!(s.doc().unwrap().doc.story(a).unwrap().text, "ᲒᲐᲛᲐᲠᲯᲝᲑᲐ ᲛᲡᲝᲤᲚᲘᲝ. ᲐᲮᲐᲚᲘ WORD");
}
}
fn paste_into(s: &mut Session, p: &Value) -> Result<Value> {
+4 -2
View File
@@ -1436,19 +1436,21 @@ pub(crate) fn case_map(src: &str, case: &str) -> Vec<String> {
let word_start = !(prev.is_alphanumeric() || matches!(prev, '\'' | '\u{2019}'));
let up = |c: char| c.to_uppercase().collect::<String>();
let low = |c: char| c.to_lowercase().collect::<String>();
// Title and sentence case capitalize only scripts with initial capitals (not Georgian).
let cap = |c: char| if designcraft_doc::capitalizes(c) { up(c) } else { low(c) };
let m = match case {
"upper" => up(c),
"lower" => low(c),
"title" => {
if word_start {
up(c)
cap(c)
} else {
low(c)
}
}
_ => {
if sentence_start && c.is_alphanumeric() {
up(c)
cap(c)
} else {
low(c)
}
+1
View File
@@ -1257,6 +1257,7 @@ pub(crate) const LANGUAGES: &[&str] = &[
"Greek",
"Russian",
"Ukrainian",
"Georgian",
"Turkish",
"Japanese",
"Chinese",