i18n: add Swedish (sv) interface translation (#882)

* Show Thai file names and text in the interface with an installed Thai font

A drawing whose file name is Thai showed boxes in its file tab and in the
window title: the interface loaded one Latin system font (plus a CJK font for
the Chinese interface), and neither has Thai.

install_fonts now also adds an installed Thai font (Thonburi, Leelawadee UI,
Noto Sans Thai, Loma...) after the Latin one, in any interface language, since
file names and drawing text are Thai whatever the interface is in. No font is
bundled; without one installed nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add Korean interface localization and Hangul font support

* i18n: start the Swedish (sv) interface catalog

Header only, not registered yet: the catalog is filled and the language registered in this
branch, translated from the meaning of CADCraft's English strings and reviewed by a native
Swedish speaker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: add Swedish (sv) interface translation

A complete 1,235-entry Swedish catalog: every key of the Spanish catalog, Run Script... and the
four menu headings (Visa, Rita, Måttsättning, Ändra). Written from the meaning of CADCraft's
English strings; no other product's localisation and no other catalog was used. AI-assisted; the
terminology and the running app were reviewed by a native speaker. Command-line keywords, system
variables and osnap tokens stay English.

- Preference::Swedish (sv); sv-SE, sv-FI and sv_SE.UTF-8 select it; one|other plurals (the
  English rule).
- sv in REPORTED; tests for locales, saved preference and plurals, menu headings, a live switch,
  dialogs and the Swedish letters in the bundled fonts. No gaps are reported for Swedish.
- ATTRIBUTION, README, docs/localization.md, docs/localization-parity.md,
  docs/control-protocol.md, the options doc comment and a contributors/people.toml entry for
  @holmbom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Chanthawat Kiriyadee <enwuft@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: huk082 <89136256+huk082@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-13 06:53:31 -04:00
1 parent aed2ae9615
commit 7fc29b056e
10 files changed
+1381 -12

No files matched your search

+1
View File
@@ -64,5 +64,6 @@ Generated-in-code assets are original and have no file to list:
| `crates/ui-egui/src/i18n/ru.tsv` | @pechnikov2026-star | Original Russian translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied |
| `crates/ui-egui/src/i18n/fr.tsv` | @pechnikov2026-star | Original French translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied |
| `crates/ui-egui/src/i18n/zh.tsv` | @mayloglog | Original Simplified Chinese translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied |
| `crates/ui-egui/src/i18n/sv.tsv` | Erik Holmbom (@holmbom) with Claude Code (AI-assisted) | Original Swedish translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied; terminology and the running app reviewed by a native speaker |
| `crates/ui-egui/src/i18n/it.tsv` | Emanuele Bottelli (AI-assisted) | Original Italian translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied |
| `crates/ui-egui/src/i18n/ko.tsv` | @huk082 (AI-assisted) | Original Korean translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied; no font assets added |
+1 -1
View File
@@ -111,7 +111,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) and Korean (한국어, with an installed Hangul font), 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) and Swedish (Svenska), 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
+5
View File
@@ -52,6 +52,11 @@ sync_github_name = true
[people.GJoe2]
sync_github_name = true
[people.holmbom]
real_name = "Erik Holmbom"
display_name = "Erik Holmbom"
sync_github_name = false
[people.cyfung1031]
real_name = "Chester Fung"
display_name = "Chester Fung"
+10 -2
View File
@@ -35,10 +35,12 @@ pub enum Preference {
Italian,
#[serde(rename = "ko")]
Korean,
#[serde(rename = "sv")]
Swedish,
}
impl Preference {
pub const ALL: [Self; 12] = [
pub const ALL: [Self; 13] = [
Self::Auto,
Self::English,
Self::Ukrainian,
@@ -51,6 +53,7 @@ impl Preference {
Self::Chinese,
Self::Italian,
Self::Korean,
Self::Swedish,
];
/// The codes a preference can be given as, for messages ("auto, en or uk").
@@ -76,6 +79,7 @@ impl Preference {
Self::Chinese => "zh",
Self::Italian => "it",
Self::Korean => "ko",
Self::Swedish => "sv",
}
}
@@ -99,6 +103,7 @@ impl Preference {
"Korean"
}
}
Self::Swedish => "Svenska",
}
}
@@ -124,6 +129,7 @@ impl Preference {
Self::Chinese => "zh",
Self::Italian => "it",
Self::Korean => "ko",
Self::Swedish => "sv",
Self::Auto => locales.iter().find_map(|s| locale_language(s)).unwrap_or("en"),
}
}
@@ -140,7 +146,7 @@ pub struct Language {
catalog: OnceLock<Catalog<'static>>,
}
pub static LANGUAGES: [Language; 11] = [
pub static LANGUAGES: [Language; 12] = [
Language { code: "en", name: "English", source: "", plural: plural_en, plural_forms: 2, catalog: OnceLock::new() },
Language {
code: "uk", name: "Українська", source: include_str!("uk.tsv"), plural: plural_uk, plural_forms: 3, catalog: OnceLock::new()
@@ -169,6 +175,8 @@ pub static LANGUAGES: [Language; 11] = [
Language { code: "it", name: "Italiano", source: include_str!("it.tsv"), plural: plural_en, plural_forms: 2, catalog: OnceLock::new() },
// Korean has one integer plural form and needs a Hangul-capable interface font.
Language { code: "ko", name: "한국어", source: include_str!("ko.tsv"), plural: plural_ko, plural_forms: 1, catalog: OnceLock::new() },
// Swedish plurals follow the English rule: one (1), other.
Language { code: "sv", name: "Svenska", source: include_str!("sv.tsv"), plural: plural_en, plural_forms: 2, catalog: OnceLock::new() },
];
/// BCP 47 and POSIX locale tags, including `uk-UA`, `uk_UA.UTF-8` and `pt_BR.UTF-8`.
File diff suppressed because it is too large. Load diff
+106 -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"];
const REPORTED: &[&str] = &["pt-BR", "de", "es", "cs", "ru", "fr", "zh", "it", "ko", "sv"];
fn check(language: &str, what: &str, missing: Vec<String>) {
if REQUIRED.contains(&language) {
@@ -779,3 +779,108 @@ fn korean_ui_fonts_cover_catalog_and_language_picker_in_both_families() {
});
assert!(missing.is_empty(), "missing Korean UI glyphs: {}", missing.join(", "));
}
#[test]
fn swedish_locales_preferences_and_plural_forms() {
for tag in ["sv", "sv-SE", "sv_SE.UTF-8", "SV-se", " sv-SE ", "sv-FI"] {
assert_eq!(locale_language(tag), Some("sv"), "{tag}");
}
// `se` is Northern Sami and `swe` the three-letter code; neither picks Swedish.
for tag in ["se", "swe", "svenska"] {
assert_eq!(locale_language(tag), None, "{tag}");
}
assert_eq!(Preference::parse("sv"), Some(Preference::Swedish));
assert_eq!(Preference::Swedish.name(), "Svenska");
assert_eq!(Preference::Swedish.resolve(&["en-US".into()]), "sv");
assert_eq!(Preference::Auto.resolve(&["pl-PL".into(), "sv-SE".into(), "en-US".into()]), "sv");
let mut app = CadApp::new(Session::new(), Services::default());
app.ui.interface_language = Preference::Swedish;
let mut restored = CadApp::new(Session::new(), Services::default());
restored.load_prefs(&app.prefs_json());
assert_eq!(restored.ui.interface_language, Preference::Swedish);
with_language("sv", || {
for n in [0, 1, 2, 5, 21, u64::MAX] {
// `bidragsgivare` is the same in the singular and the plural; `commit` takes an -s.
assert_eq!(count(n, "{n} contributor", "{n} contributors"), format!("{n} bidragsgivare"));
let commit = if n == 1 { "commit" } else { "commits" };
assert_eq!(count(n, "{n} commit", "{n} commits"), format!("{n} {commit}"));
}
assert_eq!(format("Current: {color}", &[("color", "{n} Open".into())]), "Aktuell: {n} Open");
});
}
#[test]
fn swedish_menu_headings_keep_object_labels() {
with_language("sv", || {
assert_eq!(menu("Dimension"), "Måttsättning");
assert_eq!(t("Dimension"), "Mått");
assert_eq!(menu("View"), "Visa");
assert_eq!(menu("Draw"), "Rita");
assert_eq!(menu("Modify"), "Ändra");
assert_eq!(menu("Edit"), "Redigera");
assert_eq!(menu("File"), "Arkiv");
});
}
#[test]
fn live_swedish_switch_preserves_drawing_command_and_saved_choice() {
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);
app.run("ui.language", json!({"lang":"sv"})).unwrap();
let text = render(&mut app, &ctx);
for label in ["Egenskaper", "Arkiv", "Visa", "Rita", "Måttsättning", "Ange första punkten"] {
assert!(text.contains(label), "missing {label}: {text}");
}
assert_eq!(app.language(), "sv");
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();
assert!(render(&mut app, &ctx).contains("Properties"));
}
#[test]
fn swedish_dialogs_render_with_stable_ids() {
for (dialog, heading) in [
("dsettings", "Ritinställningar"),
("about", "Om CADCraft"),
("blocks", "Block"),
("layers", "Hantera lageregenskaper"),
("qselect", "Snabbmarkering"),
("parameters", "Hantera parametrar"),
("language", "Svenska"),
("options:preferences", "Användarinställningar"),
] {
let mut app = CadApp::new(Session::new(), Services::default());
app.ui.interface_language = Preference::Swedish;
app.ui.dialog = Some(dialog.into());
let ctx = egui::Context::default();
app.logic(&ctx);
let text = render(&mut app, &ctx);
assert!(text.contains(heading), "{dialog}: {text}");
assert_eq!(app.ui.dialog.as_deref(), Some(dialog));
}
}
#[test]
fn default_fonts_cover_swedish_letters_and_quotes() {
let ctx = egui::Context::default();
let mut output = ctx.run_ui(Default::default(), |_| {});
output.textures_delta.clear();
let letters: BTreeSet<_> = "ÅÄÖÉåäöé”–".chars().collect();
ctx.fonts_mut(|view| {
for family in [egui::FontFamily::Proportional, egui::FontFamily::Monospace] {
let mut font = view.fonts.font(&family);
for c in &letters {
assert!(font.characters().contains_key(c), "missing {c} in {family:?}");
}
}
});
}
+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"` or `"ko"` |
//! | `interfaceLanguage` | `"auto"`, `"en"`, `"uk"`, `"pt-BR"`, `"de"`, `"es"`, `"cs"`, `"ru"`, `"fr"`, `"zh"`, `"it"`, `"ko"` or `"sv"` |
//! | `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) |
+1 -1
View File
@@ -152,7 +152,7 @@ Select a language with `engine.execute` and the UI command `ui.language`:
{"id":1,"method":"engine.execute","params":{"command":"ui.language","params":{"lang":"uk"}}}
```
Supported values are `auto`, `en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr`, `zh`, `it` and `ko`.
Supported values are `auto`, `en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr`, `zh`, `it`, `ko` and `sv`.
Chinese needs an installed CJK font; Korean needs a font covering the Korean interface from the
optional craft-fonts input or the operating system. Without a suitable font, the saved choice is
retained and the result's `shown` is `en`. `ui.set` also accepts
+5 -3
View File
@@ -1,6 +1,6 @@
# Localization parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (complete current Korean catalog; locale/preferences/live-switch support and Hangul font coverage) · **Target:** Autodesk AutoCAD 2027 for Mac (26.0)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Swedish catalog added: complete catalog, menu headings, native-speaker terminology review; previously: complete current Korean catalog with Hangul font coverage) · **Target:** Autodesk AutoCAD 2027 for Mac (26.0)
How far CADCraft's interface and drawing text are from AutoCAD's languages. Summarised in
[ROADMAP.md](../ROADMAP.md#languages); work items in [gaps.md](gaps.md#localization).
@@ -66,10 +66,11 @@ plus native review by a human; extending the catalog to the remaining English-on
| Vietnamese | vi | 0 (0%) | 0% | Latin with stacked diacritics: renders per code point, untested | no | none | 3–5 h |
| Ukrainian | uk | 989 catalog rows (≈ 100% of the catalog; ≈ 80% of all UI strings, estimated: engine diagnostics and uncatalogued text stay English) | dialogs and tooltips yes; command reference searchable in Ukrainian | Cyrillic: yes (bundled egui fonts) | translated by a contributor; no second review | partial | 2–4 h |
| Czech | cs | 1,202 catalog rows; reference catalog and marked UI literals covered | dialogs and tooltips yes; contextual menu headings; command reference searchable in Czech | Latin: yes (bundled egui proportional and monospace fonts; Czech letters and quotes tested) | AI-assisted by @Losminek; independent review pass; not yet proofread by a native speaker | complete current catalog; English-only interface gaps remain | native review + shared English-only UI work |
| Swedish | sv | 1,235 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 |
Other languages shipped: Ukrainian, Brazilian Portuguese, German, Czech, Russian, French, Italian and Korean. Czech and
Brazilian Portuguese cover the reference catalog and every marked UI literal; engine diagnostics and uncatalogued text
Other languages shipped: Ukrainian, Brazilian Portuguese, German, Czech, Russian, French, Italian, Korean and Swedish.
Czech, Swedish 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,
and additionally Ukrainian, which AutoCAD for Mac doesn't ship.
@@ -82,6 +83,7 @@ each language, which needs humans. Translations parallelise fully once the catal
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | minor | Swedish added: 1,235 rows, one/other plurals, menu headings, locale/preferences/menu/font/render tests; no gaps in the marked UI literals; terminology and the running app reviewed by a native speaker |
| 2026-10-13 | minor | Korean added: full 1,235-row existing catalog union, single count form, locale/persistence/live-switch tests, and validated Hangul UI fonts; native review and platform verification pending |
| 2026-10-12 | minor | Italian added: 1,227 rows, one/other plurals, two `menu` headings, locale/preference/plural/font tests; AI-assisted, native proofreading pending |
| 2026-10-12 | minor | Brazilian Portuguese catalog completed by @RicSchonfelder: 238 rows added (1,231 total), now covering the reference catalog and every marked UI literal, gaps 0; bundled-font glyph test for the catalog's accented letters |
+4 -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**, or **한국어** (Korean).
**Українська**, **Português (Brasil)**, **Deutsch**, **Español**, **Čeština** (Czech), **Русский**, **Français**, **简体中文**, **Italiano**, **한국어** (Korean), or **Svenska** (Swedish).
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
@@ -10,7 +10,8 @@ choice use Auto.
Auto uses the first supported language in the operating system's preferred locales, or the
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`. Unsupported languages fall back to English.
`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`. 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.
@@ -21,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`, and `ko`; invalid values are rejected without changing the current
`en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr`, `zh`, `it`, `ko`, and `sv`; invalid values are rejected without changing the current
selection. Resetting palettes keeps the language choice.
## Translation coverage