i18n: add Georgian (ka) interface language

Add a Georgian catalog (ka.tsv, 1,383 rows): the union of the existing
catalogs, four menu headings (ზომების დატანა for the Dimension menu, ზომა
for the object) and single-form counts, since a Georgian noun stays
singular after a numeral. No marked UI literal falls back to English.
Prompts read "მიუთითეთ …" for Specify and "შეიყვანეთ …" for Enter;
clickable keywords keep the spelling the engine accepts. No Mtavruli.

Register the language (Preference::Georgian, `ka`, ქართული) and add
locale, preference, count, menu-heading, live-switch, dialog and
no-Mtavruli tests. Docs, README and ATTRIBUTION list it.

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-23 04:39:13 +04:00
co-authored by Claude Opus 5.5
parent dc4bbe86f6
commit 6c72999361
9 changed files with 1554 additions and 9 deletions
+1
View File
@@ -71,3 +71,4 @@ Generated-in-code assets are original and have no file to list:
| `crates/ui-egui/src/i18n/hu.tsv` | @Skaywalker | Original Hungarian translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied |
| `crates/ui-egui/src/i18n/tr.tsv` | @ogzhnklvz (AI-assisted) | Original Turkish translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied; written from the English strings, no other catalog used as a source; the running app reviewed by a native speaker |
| `crates/ui-egui/src/i18n/el.tsv` | @Camthalion77 with ChatGPT (AI-assisted) | Original Greek translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied; independent native proofreading pending |
| `crates/ui-egui/src/i18n/ka.tsv` | @devius with Claude (AI-assisted) | Original Georgian translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied; written from the English strings, no other catalog used as a source; no font assets added (the Georgian face comes from craft-fonts) |
+1 -1
View File
@@ -118,7 +118,7 @@ numbers):
| Layouts & plotting | Paper-space layouts, viewports (scale, lock, per-viewport layer freeze), MSPACE/PSPACE through viewports, page setups, PLOT and EXPORTPDF |
| Parametric | Geometric and dimensional constraints, AUTOCONSTRAIN, PARAMETERS with expressions, conflict detection; constraints re-solve after every edit |
| Grips | Hot grips with Space to cycle Stretch, Move, Rotate, Scale and Mirror |
| Interface languages | English, Ukrainian (Українська), Brazilian Portuguese (Português (Brasil)), German (Deutsch), Spanish (Español), Czech (Čeština), Russian (Русский), French (Français), Simplified Chinese (简体中文, with an installed CJK font), Italian (Italiano), Korean (한국어, with an installed Hangul font), Swedish (Svenska), Arabic (العربية, with an installed Arabic font), Hungarian (Magyar) and Turkish (Türkçe), live switching and a remembered Auto/system-language choice; [details](docs/localization.md) |
| Interface languages | English, Ukrainian (Українська), Brazilian Portuguese (Português (Brasil)), German (Deutsch), Spanish (Español), Czech (Čeština), Russian (Русский), French (Français), Simplified Chinese (简体中文, with an installed CJK font), Italian (Italiano), Korean (한국어, with an installed Hangul font), Swedish (Svenska), Arabic (العربية, with an installed Arabic font), Hungarian (Magyar), Turkish (Türkçe) and Georgian (ქართული), live switching and a remembered Auto/system-language choice; [details](docs/localization.md) |
| Automation | MCP server, JSON control channel, `cadcraft-cli` (info, convert, run, commands, mcp) |
## Quick start
+1
View File
@@ -95,6 +95,7 @@ Ukrainian and German. Detail: [localization-parity.md](docs/localization-parity.
| Indonesian | none | 0% |
| Japanese | none | 0% |
| Greek | complete current catalog | 1,330 current source keys; independent proofreading pending |
| Georgian | complete current catalog | 1,383 rows; native proofreading pending |
| German | partial | ≈ 100% of the catalog (989 rows) |
| Korean | none | 0% |
| Vietnamese | none | 0% |
File diff suppressed because it is too large Load Diff
+21 -2
View File
@@ -45,10 +45,12 @@ pub enum Preference {
Hungarian,
#[serde(rename = "tr")]
Turkish,
#[serde(rename = "ka")]
Georgian,
}
impl Preference {
pub const ALL: [Self; 17] = [
pub const ALL: [Self; 18] = [
Self::Auto,
Self::English,
Self::Greek,
@@ -66,6 +68,7 @@ impl Preference {
Self::Arabic,
Self::Hungarian,
Self::Turkish,
Self::Georgian,
];
/// The codes a preference can be given as, for messages ("auto, en or uk").
@@ -96,6 +99,7 @@ impl Preference {
Self::Arabic => "ar",
Self::Hungarian => "hu",
Self::Turkish => "tr",
Self::Georgian => "ka",
}
}
@@ -124,6 +128,15 @@ impl Preference {
Self::Arabic => "العربية",
Self::Hungarian => "Magyar",
Self::Turkish => "Türkçe",
// Georgian letters come from the craft-fonts Georgian face; a build without it shows
// the English name (as Korean does) and keeps the interface in English.
Self::Georgian => {
if crate::theme::can_show("ka") {
"ქართული"
} else {
"Georgian"
}
}
}
}
@@ -154,6 +167,7 @@ impl Preference {
Self::Arabic => "ar",
Self::Hungarian => "hu",
Self::Turkish => "tr",
Self::Georgian => "ka",
Self::Auto => locales.iter().find_map(|s| locale_language(s)).unwrap_or("en"),
}
}
@@ -170,7 +184,7 @@ pub struct Language {
catalog: OnceLock<Catalog<'static>>,
}
pub static LANGUAGES: [Language; 16] = [
pub static LANGUAGES: [Language; 17] = [
Language { code: "en", name: "English", source: "", plural: plural_en, plural_forms: 2, catalog: OnceLock::new() },
// Greek: one for exactly 1; other for zero and all other integer counts.
Language { code: "el", name: "Ελληνικά", source: include_str!("el.tsv"), plural: plural_en, plural_forms: 2, catalog: OnceLock::new() },
@@ -211,6 +225,11 @@ pub static LANGUAGES: [Language; 16] = [
Language { code: "hu", name: "Magyar", source: include_str!("hu.tsv"), plural: plural_ko, plural_forms: 1, catalog: OnceLock::new() },
// Turkish keeps a noun singular after a number ("3 commit"), so like Korean it has one form.
Language { code: "tr", name: "Türkçe", source: include_str!("tr.tsv"), plural: plural_ko, plural_forms: 1, catalog: OnceLock::new() },
// Georgian keeps a noun singular after a numeral ("5 კომიტი"), so one form, the Korean rule.
// Its letters come from the craft-fonts `Geor` face (`theme::can_show`).
Language {
code: "ka", name: "ქართული", source: include_str!("ka.tsv"), plural: plural_ko, plural_forms: 1, catalog: OnceLock::new()
},
];
/// BCP 47 and POSIX locale tags, including `uk-UA`, `uk_UA.UTF-8` and `pt_BR.UTF-8`.
+110 -1
View File
@@ -39,7 +39,7 @@ fn catalog_is_valid_and_preserves_placeholders_and_ellipses() {
/// to English for what they don't cover yet; their gaps are reported, not enforced, so adding a
/// command doesn't require a row in every language.
const REQUIRED: &[&str] = &["uk"];
const REPORTED: &[&str] = &["pt-BR", "de", "es", "cs", "ru", "fr", "zh", "it", "ko", "sv", "ar", "hu", "tr", "el"];
const REPORTED: &[&str] = &["pt-BR", "de", "es", "cs", "ru", "fr", "zh", "it", "ko", "sv", "ar", "hu", "tr", "el", "ka"];
fn check(language: &str, what: &str, missing: Vec<String>) {
if REQUIRED.contains(&language) {
@@ -1172,3 +1172,112 @@ fn default_fonts_cover_all_greek_letters_in_the_catalog() {
}
});
}
#[test]
fn georgian_locales_preferences_and_single_count_form() {
for tag in ["ka", "ka-GE", "ka_GE", "ka_GE.UTF-8", "KA-ge", " ka-GE "] {
assert_eq!(locale_language(tag), Some("ka"), "{tag}");
}
// `kat`/`geo` are the three-letter codes and `kab` is Kabyle; none picks Georgian.
for tag in ["kat", "geo", "kab", "kartuli"] {
assert_eq!(locale_language(tag), None, "{tag}");
}
assert_eq!(Preference::parse("ka"), Some(Preference::Georgian));
assert_eq!(Preference::Georgian.code(), "ka");
assert_eq!(Preference::Georgian.name(), if crate::theme::can_show("ka") { "ქართული" } else { "Georgian" });
assert_eq!(Preference::Georgian.resolve(&["en-US".into()]), "ka");
assert_eq!(Preference::Auto.resolve(&["pl-PL".into(), "ka-GE".into(), "en-US".into()]), "ka");
let saved = serde_json::to_string(&Preference::Georgian).unwrap();
assert_eq!(saved, "\"ka\"");
let mut app = CadApp::new(Session::new(), Services::default());
app.ui.interface_language = Preference::Georgian;
let mut restored = CadApp::new(Session::new(), Services::default());
restored.load_prefs(&app.prefs_json());
assert_eq!(restored.ui.interface_language, Preference::Georgian);
with_language("ka", || {
// A Georgian noun stays singular after a numeral, 1 included.
for n in [0, 1, 2, 5, 21, u64::MAX] {
assert_eq!(count(n, "{n} contributor", "{n} contributors"), format!("{n} მონაწილე"));
assert_eq!(count(n, "{n} commit", "{n} commits"), format!("{n} კომიტი"));
}
assert_eq!(t("Open"), "გახსნა");
assert_eq!(menu("Dimension"), "ზომების დატანა");
assert_eq!(t("Dimension"), "ზომა");
assert_eq!(menu("Draw"), "ხაზვა");
assert_eq!(menu("File"), "ფაილი");
});
assert_eq!(t("Open"), "Open", "language must not leak outside a render scope");
}
#[test]
fn georgian_catalog_covers_marked_ui_literals_without_mtavruli() {
assert!(missing_marked_ui_literals("ka").is_empty(), "{:?}", missing_marked_ui_literals("ka"));
assert!(missing_commands_menus_tools_and_snap_labels("ka").is_empty(), "{:?}", missing_commands_menus_tools_and_snap_labels("ka"));
// Mtavruli (U+1C90–U+1CBF) is a display style, not upper-case Georgian: never in the catalog.
let mtavruli: Vec<char> = include_str!("ka.tsv").chars().filter(|c| ('\u{1c90}'..='\u{1cbf}').contains(c)).collect();
assert!(mtavruli.is_empty(), "{mtavruli:?}");
}
#[test]
fn georgian_live_switch_keeps_drawing_and_falls_back_without_font() {
let mut app = CadApp::new(Session::new(), Services::default());
app.ui.in_window_menu = true;
app.session.state_mut().unwrap().title = "Open".into();
let ctx = egui::Context::default();
app.logic(&ctx);
assert!(render(&mut app, &ctx).contains("Properties"));
app.start("line");
let prompt = crate::control::cmdline_state(&app);
let commands = crate::control::all_commands(&app);
let result = app.run("ui.language", json!({"lang": "ka"})).unwrap();
let supported = crate::theme::can_show("ka");
assert_eq!(result["shown"], if supported { "ka" } else { "en" });
assert_eq!(app.ui.interface_language, Preference::Georgian);
app.logic(&ctx);
assert_eq!(app.fonts_language, Some(app.language()));
let text = render(&mut app, &ctx);
if supported {
for label in ["თვისებები", "ფაილი", "ხედი", "ხაზვა", "ზომების დატანა", "მიუთითეთ პირველი წერტილი"]
{
assert!(text.contains(label), "missing {label}: {text}");
}
} else {
assert!(text.contains("Properties"));
}
assert_eq!(app.session.state().unwrap().title, "Open");
assert_eq!(crate::control::cmdline_state(&app), prompt);
assert_eq!(crate::control::all_commands(&app), commands);
app.run("ui.language", json!({"lang": "en"})).unwrap();
app.logic(&ctx);
assert!(render(&mut app, &ctx).contains("Properties"));
}
#[test]
fn georgian_dialogs_render_with_stable_ids() {
if !crate::theme::can_show("ka") {
return; // The fallback path is covered above; do not claim glyph rendering here.
}
for (dialog, source) in [
("dsettings", "Drafting Settings"),
("about", "About CADCraft"),
("commands", "Command Reference"),
("blocks", "Blocks"),
("layers", "Layer Properties Manager"),
("qselect", "Quick Select"),
("parameters", "Parameters Manager"),
("language", "Interface Language"),
("options:preferences", "User Preferences"),
] {
let mut app = CadApp::new(Session::new(), Services::default());
app.ui.interface_language = Preference::Georgian;
app.ui.dialog = Some(dialog.into());
let ctx = egui::Context::default();
app.logic(&ctx);
let text = render(&mut app, &ctx);
assert!(text.contains(tr("ka", source)), "{dialog}: {text}");
if dialog == "language" {
assert!(text.contains("ქართული"), "{dialog}: {text}");
}
assert_eq!(app.ui.dialog.as_deref(), Some(dialog));
}
}
+1 -1
View File
@@ -10,7 +10,7 @@
//! |---|---|
//! | `theme` | `"system"`, `"light"` or `"dark"` |
//! | `animations` | `"system"`, `"on"` or `"off"`: dialog and menu fades and smoothed scrolling |
//! | `interfaceLanguage` | `"auto"`, `"en"`, `"uk"`, `"pt-BR"`, `"de"`, `"es"`, `"cs"`, `"ru"`, `"fr"`, `"zh"`, `"it"`, `"ko"`, `"sv"`, `"ar"`, `"hu"`, `"tr"` or `"el"` |
//! | `interfaceLanguage` | `"auto"`, `"en"`, `"uk"`, `"pt-BR"`, `"de"`, `"es"`, `"cs"`, `"ru"`, `"fr"`, `"zh"`, `"it"`, `"ko"`, `"sv"`, `"ar"`, `"hu"`, `"tr"`, `"el"` or `"ka"` |
//! | `saveFormat` | `"dxf"` or `"dwg"`: the format Save As suggests for new drawings |
//! | `saveVersion` | `"2000"` … `"2018"`: the version drawings with no file yet are saved in |
//! | `SAVETIME` | minutes between automatic saves, 0–600 (0: off) |
+3 -1
View File
@@ -70,9 +70,10 @@ plus native review by a human; extending the catalog to the remaining English-on
| Swedish | sv | 1,330 catalog rows; reference catalog, marked UI literals and menu headings covered (no gaps reported) | dialogs and tooltips yes; contextual menu headings; command reference searchable in Swedish | Latin: yes (bundled egui proportional and monospace fonts; å ä ö é and Swedish quotes tested) | AI-assisted (Claude Code) by @holmbom; terminology and the running app reviewed by a native speaker | complete current catalog; English-only interface gaps remain | line-by-line proofreading + shared English-only UI work |
| Italian | it | 1,227 catalog rows; reference catalog and marked UI literals covered | dialogs and tooltips yes; contextual menu headings (Visualizza / Vista, Edita / Modifica); command reference searchable in Italian | Latin: yes (bundled egui fonts; accented letters tested) | AI-assisted by Emanuele Bottelli (Claude), written from the English strings with Italian desktop-CAD vocabulary; no native proofreading pass yet | complete current catalog; English-only interface gaps remain | native review + shared English-only UI work |
| Hungarian | hu | 1,326 catalog rows; reference catalog, marked UI literals and menu headings covered (no gaps reported) | dialogs and tooltips yes; contextual menu headings; command reference searchable in Hungarian | Latin: yes (bundled egui proportional and monospace fonts; ő ű Ő Ű and the other accented letters tested) | translated by @Skaywalker from the English strings; independent native proofreading pending | complete current catalog; English-only interface gaps remain | native proofreading + shared English-only UI work |
| Georgian | ka | 1,383 catalog rows (1,377 ordinary, 4 menu, 2 count); the existing catalog union, marked UI literals and menu headings covered (no gaps reported) | dialogs and tooltips yes; contextual menu headings (ზომების დატანა / ზომა); command reference searchable in Georgian | Georgian (Mkhedruli): the craft-fonts Noto Sans Georgian (`Geor`) face, embedded in release and web builds; English fallback without it; Mtavruli never used (tested) | AI-assisted (Claude), written from the English strings with Georgian technical-drawing vocabulary; native proofreading pending | complete current catalog; English-only interface gaps remain | native proofreading + shared English-only UI work |
| Turkish | tr | 1,330 catalog rows; reference catalog, marked UI literals and menu headings covered (no gaps reported) | dialogs and tooltips yes; contextual menu headings (Ölçülendirme / Ölçü); command reference searchable in Turkish | Latin: yes (bundled egui proportional and monospace fonts; Ç Ğ İ Ö Ş Ü ç ğ ı ö ş ü tested) | AI-assisted by @ogzhnklvz, written from the English strings with Turkish desktop-CAD vocabulary; the running app reviewed by a native speaker | complete current catalog; English-only interface gaps remain | line-by-line proofreading + shared English-only UI work |
Other languages shipped: Ukrainian, Brazilian Portuguese, German, Czech, Russian, French, Italian, Korean, Swedish, Hungarian and Turkish.
Other languages shipped: Ukrainian, Brazilian Portuguese, German, Czech, Russian, French, Italian, Korean, Swedish, Hungarian, Turkish, Greek and Georgian.
Czech, Swedish, Hungarian and Brazilian Portuguese cover the reference catalog and every marked UI literal; engine diagnostics and uncatalogued text
remain English. AutoCAD for Mac ships 7
non-English languages (zh-Hans, es, fr, ja, de, ko, it); of those, CADCraft now ships German and Korean,
@@ -86,6 +87,7 @@ each language, which needs humans. Translations parallelise fully once the catal
| Date | Change | Summary |
|---|---|---|
| 2026-10-23 | minor | Georgian (`ka`) added: 1,383 rows (the existing catalog union), single count form, four menu headings, prompt keywords kept in English; the craft-fonts `Geor` face in release and web builds (pin a71c20e3); locale/preferences/menu/live-switch/dialog/font/no-Mtavruli tests; native proofreading pending |
| 2026-10-15 | minor | Greek (`el`) catalog: 1,330 rows, context/plural/locale/font tests; review pending |
| 2026-10-14 | minor | Swedish completed: 95 rows for strings added since (Hatch and Gradient dialog, Block Editor, REFEDIT, multileader editing, Options selection settings, MTEXT editor, recent files); 1,330 rows, every key of the Ukrainian reference catalog |
| 2026-10-18 | minor | Turkish added: 1,330 rows (the existing catalog union), single count form, four menu headings, prompt keywords kept in English; locale/preferences/menu/live-switch/dialog/font tests; no gaps in the marked UI literals; AI-assisted, the running app reviewed by a native speaker |
+17 -3
View File
@@ -1,7 +1,7 @@
# Interface languages
Choose **Edit → Interface Language** and select **Auto (system language)**, **English**,
**Українська**, **Português (Brasil)**, **Deutsch**, **Español**, **Čeština** (Czech), **Русский**, **Français**, **简体中文**, **Italiano**, **한국어** (Korean), **Svenska** (Swedish), **العربية** (Arabic), **Magyar** (Hungarian), **Türkçe** (Turkish), or **Ελληνικά** (Greek).
**Українська**, **Português (Brasil)**, **Deutsch**, **Español**, **Čeština** (Czech), **Русский**, **Français**, **简体中文**, **Italiano**, **한국어** (Korean), **Svenska** (Swedish), **العربية** (Arabic), **Magyar** (Hungarian), **Türkçe** (Turkish), **Ελληνικά** (Greek), or **ქართული** (Georgian).
The interface updates without restarting, including the native macOS menus.
The choice is saved with the other interface preferences (`cadcraft.prefs`, beside the theme):
eframe storage on the desktop, browser local storage on the web. Installations without a saved
@@ -11,7 +11,7 @@ Auto uses the first supported language in the operating system's preferred local
browser's `navigator.languages` on the web. BCP 47 and POSIX tags select their primary language:
`uk-UA` → `uk`, `de_AT.UTF-8` → `de`, `es-MX` → `es`, `pt_BR.UTF-8` → `pt-BR`,
`cs-CZ` and `cs_CZ.UTF-8` → `cs`, `it-IT` and `it_CH.UTF-8` → `it`, `ko-KR` and `ko_KR.UTF-8` → `ko`,
`sv-SE`, `sv-FI` and `sv_SE.UTF-8` → `sv`, `hu-HU` and `hu_HU.UTF-8` → `hu`, `tr-TR` and `tr_TR.UTF-8` → `tr`. Unsupported languages fall back to English.
`sv-SE`, `sv-FI` and `sv_SE.UTF-8` → `sv`, `hu-HU` and `hu_HU.UTF-8` → `hu`, `tr-TR` and `tr_TR.UTF-8` → `tr`, `ka-GE` and `ka_GE.UTF-8` → `ka`. Unsupported languages fall back to English.
Set `CADCRAFT_LOCALE=cs-CZ` to override the desktop's locale for a launch.
An explicit saved language choice takes precedence over that override.
@@ -22,7 +22,7 @@ Automation can select the language through the existing control channel:
```
The `ui.set` method also accepts `{"interfaceLanguage":"cs"}`. Supported values are `auto`,
`en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr`, `zh`, `it`, `ko`, `sv`, `ar`, `hu`, `tr`, and `el`; invalid values are rejected without changing the current
`en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr`, `zh`, `it`, `ko`, `sv`, `ar`, `hu`, `tr`, `el`, and `ka`; invalid values are rejected without changing the current
selection. Resetting palettes keeps the language choice.
## Translation coverage
@@ -115,6 +115,20 @@ original AI-assisted work contributed by @Camthalion77 with language review; it
from Autodesk localization files. Independent native proofreading and visual checks remain
follow-up work.
## Georgian (`ka`) interface
Georgian has **1,383 rows**: 1,377 ordinary labels, four contextual menu headings (**ზომების
დატანა** for the Dimension menu, **ზომა** for the object; **ხაზვა** for Draw) and two single-form
count templates, since a Georgian noun stays singular after a numeral ("5 კომიტი"). It covers the
union of the existing catalogs, and no marked UI literal falls back to English. Commands are verbal
nouns (**შენახვა**, **შემობრუნება**); prompts read "მიუთითეთ …" for Specify, "შეიყვანეთ …" for
Enter and "მონიშნეთ …" for Select, and clickable keywords keep the spelling the engine accepts.
Georgian has no letter case, so nothing is capitalised, and Mtavruli (a display style, not upper
case) never appears; a test checks that. The translation is original, AI-assisted work written from
the English strings with Georgian technical-drawing vocabulary (შრე, დაშტრიხვა, მომრგვალება, ფასკა,
წანაცვლება, ობიექტზე მიბმა, ხედვის არე); no other product's localization was consulted. Native
proofreading of the running app is still to do.
## Adding translations
The implementation follows the display-time catalog pattern used by