Add Ukrainian localization (uk) (#1196)
Ukrainian UI catalog; untranslated keys fall back to English.
This commit is contained in:
1 parent
9935133b56
commit
861ab027b6
7 files changed
+1063
-16
No files matched your search
@@ -96,6 +96,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/locales/ja.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/locales/ko.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/locales/ru.json | Alex Pro (del000) | Russian translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
|
||||
| assets/locales/uk.json | Igor Bronovskyi (BrunIF) | Ukrainian translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
|
||||
| assets/locales/zh.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/materials/library.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/platforms/linux/ai.storyteller.solidcraft.desktop | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -37,8 +37,9 @@ static GERMAN: Catalog = Catalog::new("de", "Deutsch", include_str!(concat!(env!
|
||||
static FRENCH: Catalog = Catalog::new("fr", "Français", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/fr.json")));
|
||||
static KOREAN: Catalog = Catalog::new("ko", "한국어", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/ko.json")));
|
||||
static RUSSIAN: Catalog = Catalog::new("ru", "Русский", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/ru.json")));
|
||||
static UKRAINIAN: Catalog = Catalog::new("uk", "Українська", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/uk.json")));
|
||||
static CHINESE: Catalog = Catalog::new("zh", "中文", include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/locales/zh.json")));
|
||||
pub const LOCALES: &[&Catalog] = &[&ENGLISH, &JAPANESE, &GERMAN, &FRENCH, &KOREAN, &RUSSIAN, &CHINESE];
|
||||
pub const LOCALES: &[&Catalog] = &[&ENGLISH, &JAPANESE, &GERMAN, &FRENCH, &KOREAN, &RUSSIAN, &UKRAINIAN, &CHINESE];
|
||||
|
||||
pub fn is_supported(id: &str) -> bool {
|
||||
LOCALES.iter().any(|catalog| catalog.id == id)
|
||||
@@ -189,11 +190,11 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Catalogs delivered complete. English is the source catalog; Japanese was completed
|
||||
/// upstream and Chinese lands complete here. German, Korean, Russian and French are partial
|
||||
/// (German and Korean predate the coverage guards; Russian and French are kept partial so a
|
||||
/// new label doesn't need more translations), so their gaps fall back to English and are reported by
|
||||
/// `cargo xtask i18n` without failing the build, while nothing in the complete catalogs may
|
||||
/// regress.
|
||||
/// upstream and Chinese lands complete here. German, Korean, Russian, French and Ukrainian
|
||||
/// are partial (German and Korean predate the coverage guards; Russian, French and Ukrainian
|
||||
/// are kept partial so a new label doesn't need more translations), so their gaps fall back
|
||||
/// to English and are reported by `cargo xtask i18n` without failing the build, while
|
||||
/// nothing in the complete catalogs may regress.
|
||||
const COMPLETE: &[&str] = &["ja", "zh"];
|
||||
|
||||
/// `text()` falls back to English silently, so a message a complete locale never received is
|
||||
@@ -262,6 +263,7 @@ mod tests {
|
||||
("ko", "플랜지", "얼룩말줄무늬 분석", "운동 연구"),
|
||||
("ru", "Фланец", "Зебра-анализ", "Исследование движения"),
|
||||
("fr", "Bord tombé", "Analyse zébrée", "Étude de mouvement"),
|
||||
("uk", "Фланець", "Зебра-аналіз", "Дослідження руху"),
|
||||
];
|
||||
for (lang, flange, zebra, motion) in cases {
|
||||
let locale = Locale::for_language(lang);
|
||||
@@ -343,6 +345,7 @@ mod tests {
|
||||
("ko", "프로파일", "점"),
|
||||
("ru", "Профиль", "Точки"),
|
||||
("fr", "Profil", "Points"),
|
||||
("uk", "Профіль", "Точки"),
|
||||
] {
|
||||
let locale = Locale::for_language(lang);
|
||||
assert_eq!(locale.label("ui.palette_profile"), profile, "{lang}");
|
||||
@@ -367,6 +370,7 @@ mod tests {
|
||||
("ko", "구성 형상"),
|
||||
("ru", "Вспомогательная геометрия"),
|
||||
("fr", "Géométries de construction"),
|
||||
("uk", "Конструкційна геометрія"),
|
||||
];
|
||||
for (lang, want) in rows {
|
||||
let locale = Locale::for_language(lang);
|
||||
@@ -463,6 +467,27 @@ mod tests {
|
||||
assert_eq!(fr.dynamic("Sketch of bracket"), "Sketch of bracket");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ukrainian_catalog_translates_keys_labels_and_browser_patterns() {
|
||||
let uk = Locale::for_language("uk");
|
||||
assert_eq!(uk.name(), "Українська");
|
||||
assert!(is_supported("uk"));
|
||||
assert_eq!(uk.text("ui.preferences"), "Налаштування");
|
||||
assert_eq!(uk.label("Extrude"), "Видавлювання");
|
||||
assert_eq!(uk.label("CREATE SKETCH"), "СТВОРИТИ ЕСКІЗ");
|
||||
assert_eq!(uk.label("NonExistentKey"), "NonExistentKey");
|
||||
assert_eq!(uk.format("document.save_changes", &[("name", "My {design}")]), "Зберегти зміни в My {design}?");
|
||||
assert_eq!(uk.format("dialog.selected_count", &[("count", "2")]), "↖ Вибрано: 2");
|
||||
assert_eq!(uk.dynamic("Sketch1"), "Ескіз1");
|
||||
assert_eq!(uk.dynamic("Body2"), "Тіло2");
|
||||
assert_eq!(uk.dynamic("Component12"), "Компонент12");
|
||||
assert_eq!(uk.dynamic("Units: mm"), "Одиниці: mm");
|
||||
assert_eq!(uk.dynamic("Precision: 3"), "Точність: 3");
|
||||
assert_eq!(uk.dynamic("Parameters (5)"), "Параметри (5)");
|
||||
assert_eq!(uk.dynamic("4 degrees of freedom"), "4 ступенів вільності");
|
||||
assert_eq!(uk.dynamic("Sketch of bracket"), "Sketch of bracket");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_scopes_restore_the_previous_language() {
|
||||
let _english = scope("en");
|
||||
|
||||
@@ -28,7 +28,8 @@ fn sketch_palette_profile_and_points_rows_show_their_own_translation() {
|
||||
// i18n::tests::id_labels_resolve_to_their_own_translation_not_an_english_fallback for the
|
||||
// lookup itself). A broken id falls back to English silently rather than failing loudly, so
|
||||
// check the word actually on screen in each locale, not just that something renders.
|
||||
for (lang, profile, points) in [("de", "Profil", "Punkte"), ("ja", "プロファイル", "点"), ("ko", "프로파일", "점")] {
|
||||
for (lang, profile, points) in [("de", "Profil", "Punkte"), ("ja", "プロファイル", "点"), ("ko", "프로파일", "점"), ("uk", "Профіль", "Точки")]
|
||||
{
|
||||
let mut h = Harness::new();
|
||||
h.app.preferences.language = lang.into();
|
||||
h.app.home.open = false;
|
||||
@@ -66,6 +67,7 @@ fn window_chrome_renders_in_the_active_language() {
|
||||
("ko", "브라우저", "선택 필터", "면"),
|
||||
("ru", "БРАУЗЕР", "Фильтр выделения", "Грани"),
|
||||
("fr", "NAVIGATEUR", "Filtre de sélection", "Faces"),
|
||||
("uk", "БРАУЗЕР", "Фільтр виділення", "Грані"),
|
||||
] {
|
||||
let mut h = Harness::new();
|
||||
h.app.preferences.language = lang.into();
|
||||
|
||||
@@ -62,9 +62,12 @@ fn editing_a_thin_extrude_keeps_its_wall() {
|
||||
|
||||
#[test]
|
||||
fn the_thin_rows_are_translated() {
|
||||
for (lang, thin, location, outside) in
|
||||
[("ja", "薄肉押し出し", "壁の位置", "外側の厚さ"), ("de", "Dünnwandig extrudieren", "Wandlage", "Dicke außen")]
|
||||
{
|
||||
let cases = [
|
||||
("ja", "薄肉押し出し", "壁の位置", "外側の厚さ"),
|
||||
("de", "Dünnwandig extrudieren", "Wandlage", "Dicke außen"),
|
||||
("uk", "Тонке видавлювання", "Розташування стінки", "Товщина назовні"),
|
||||
];
|
||||
for (lang, thin, location, outside) in cases {
|
||||
let mut h = Harness::new();
|
||||
h.app.preferences.language = lang.into();
|
||||
rectangle(&mut h);
|
||||
|
||||
@@ -131,7 +131,7 @@ unit regardless of the design, pass an expression with one: `"value": "50 mm"` o
|
||||
| `ui.home` | `open?`: bool; `sample?`: index | the start page: show or hide it, open a built-in sample (once built); returns `open` and the recent designs |
|
||||
| `ui.shortcut` | `command`, `key?`, `replace?` | a command's key, or rebinds it (`""` clears; a key another command uses is refused unless `replace`) |
|
||||
| `ui.help` | `item?`: `about`, `shortcuts` (read only), `report` (opens a report draft for review), `crashes` (the Crash Reports window) | the version, commit, build date and diagnostic text |
|
||||
| `ui.prefs` | any of `default_units`, `nav` (`fusion`, `solidworks`, `inventor`, `alias`, `tinkercad`, `openscad`), `zoom_reverse`, `zoom_to_cursor`, `smooth_wheel_zoom` (default true), `trackpad_pan` (default true: two-finger scroll pans, pinch zooms), `orbit_cursor`, `look_at_anim`, `look_at_speed`, `middle_double_click` (`home` (default), `fit`, `off`), `msaa`, `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system`), `language` (`en`, `ja`, `de`, `fr`, `ko`, `ru`, `zh`; see [localization](localization.md)), `ui_scale`, `up_axis` (`z`, `y`), `offer_stand_up`, `color_vision` (`off` (default), `protan`, `deutan`, `tritan`: colour vision modes, #778), `check_updates` (default true; [updates](book/src/guide/updates.md)); `open` (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file | sets preferences (kept between runs), returns them all plus `dialog` (the open page or null), `dark` and `stand_up_offer` |
|
||||
| `ui.prefs` | any of `default_units`, `nav` (`fusion`, `solidworks`, `inventor`, `alias`, `tinkercad`, `openscad`), `zoom_reverse`, `zoom_to_cursor`, `smooth_wheel_zoom` (default true), `trackpad_pan` (default true: two-finger scroll pans, pinch zooms), `orbit_cursor`, `look_at_anim`, `look_at_speed`, `middle_double_click` (`home` (default), `fit`, `off`), `msaa`, `length_decimals`, `angle_decimals`, `theme` (`dark`, `light`, `system`), `language` (`en`, `ja`, `de`, `fr`, `ko`, `ru`, `uk`, `zh`; see [localization](localization.md)), `ui_scale`, `up_axis` (`z`, `y`), `offer_stand_up`, `color_vision` (`off` (default), `protan`, `deutan`, `tritan`: colour vision modes, #778), `check_updates` (default true; [updates](book/src/guide/updates.md)); `open` (a page: General, Navigation, Keyboard Shortcuts, Design, Graphics, Sketch, Selection) opens the Preferences dialog there; `button`: `ok`, `cancel`, `apply`, `defaults`; `stand_up: true` stands up a just-opened Y-up file | sets preferences (kept between runs), returns them all plus `dialog` (the open page or null), `dark` and `stand_up_offer` |
|
||||
| `ui.capture` | `action?`: `capture`, `revert`, `cancel` | answers the Capture Position question (asked when a command starts or the design is saved while components were moved without capturing); without `action`, what waits |
|
||||
| `ui.window` | `action`: minimize, maximize, restore, toggle, close | what the title bar's caption buttons do (on Windows and Linux the application bar is the title bar); close asks about unsaved designs first |
|
||||
| `ui.agent_cursor` | `show` (bool), `speed`: a number from 1 to 5 (times the normal speed) or `"instant"` (no animation: the command runs at once and the pointer jumps to where it acted), `follow_camera` (bool); all optional | the settings. While the cursor is shown, `engine.execute` first glides to the command's toolbar button and to each pick, then runs and replies; later requests wait their turn. `"animate": false` on a call skips it |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# UI localization
|
||||
|
||||
SolidCraft embeds its translations and works offline. English, Japanese, German, French, Korean, Russian
|
||||
and Chinese are currently registered in `crates/ui-egui/src/i18n.rs`. Adding a language requires an
|
||||
SolidCraft embeds its translations and works offline. English, Japanese, German, French, Korean,
|
||||
Russian, Ukrainian and Chinese are currently registered in `crates/ui-egui/src/i18n.rs`. Adding a language requires an
|
||||
embedded catalog and one registration there; the preferences selector and validation use the same
|
||||
registry.
|
||||
|
||||
@@ -31,9 +31,10 @@ that looks untranslated. Two guards catch that in the build:
|
||||
|
||||
- `every_complete_catalog_carries_every_english_message` — adding a key to `en.json`
|
||||
without giving it to a complete catalog fails the tests. English is the source catalog,
|
||||
Japanese and Chinese are delivered complete; German, Korean, Russian and French are partial — their
|
||||
gaps fall back to English and are reported by `cargo xtask i18n` without failing the build. A
|
||||
new label needs en, ja, zh, de and ko; Russian and French may follow later.
|
||||
Japanese and Chinese are delivered complete; German, Korean, Russian, French and Ukrainian are
|
||||
partial — their gaps fall back to English and are reported by `cargo xtask i18n` without
|
||||
failing the build. A new label needs en, ja, zh, de and ko; Russian, French and Ukrainian may
|
||||
follow later.
|
||||
- `every_toolbar_tab_and_panel_is_in_the_english_catalog` — tabs and panels reach the UI as bare
|
||||
source strings, resolved only by the English-label adapter, so a heading missing from `en.json`
|
||||
can never translate.
|
||||
|
||||
Reference in new issue
Block a user