diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 20d01d7b..bf9caacb 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -426,6 +426,9 @@ mod tests { for tag in ["de", "de-DE", "de_AT.UTF-8", "de-CH"] { assert_eq!(Locale::parse_tag(tag), Some(Locale::De), "{tag}"); } + for tag in ["ru", "ru-RU", "ru_RU.UTF-8", "ru-UA"] { + assert_eq!(Locale::parse_tag(tag), Some(Locale::Ru), "{tag}"); + } assert_eq!(Locale::parse_tag("xx"), None); } @@ -584,14 +587,12 @@ mod tests { } } + /// Every command, control, rule and rename label, and every line of What's New, is a message a + /// catalog can translate. What a language lacks is reported, not failed (like + /// `catalogs_agree_on_placeholders_and_report_gaps`): a feature PR doesn't have to ship every + /// language, and the translations catch up at their own pace. #[test] - fn german_catalog_covers_catalogs_commands_controls_and_rule_labels() { - let catalog = Locale::De.catalog(); - for language in Locale::ALL { - for key in language.catalog().keys() { - assert!(catalog.contains_key(key), "German lacks {key:?}"); - } - } + fn display_label_gaps_are_reported() { let mut labels: Vec<&str> = lightcraft_engine::command_specs().iter().map(|spec| spec.label).collect(); labels.extend(crate::menus::ui_commands().map(|command| command.1).filter(|label| !Locale::ALL.iter().any(|locale| locale.name() == *label))); labels.extend(lightcraft_develop::CONTROLS.iter().map(|control| control.label)); @@ -601,6 +602,7 @@ mod tests { } labels.extend(lightcraft_engine::rename::TOKENS.iter().map(|token| token.meaning)); labels.extend(lightcraft_engine::rename::TEMPLATE_NOTES); + let ui_labels = labels.len(); for line in crate::panels::dialogs::WHATS_NEW.lines().map(str::trim) { if line.is_empty() || line.starts_with("# ") { continue; @@ -608,8 +610,19 @@ mod tests { let label = line.strip_prefix("### ").or_else(|| line.strip_prefix("## ")).or_else(|| line.strip_prefix("- ")).unwrap_or(line); labels.push(label); } - for label in labels { - assert!(catalog.contains_key(label), "German lacks display label {label:?}"); + for language in Locale::ALL.iter().filter(|language| **language != Locale::En) { + let catalog = language.catalog(); + let missing: Vec<&&str> = labels[..ui_labels].iter().filter(|label| !catalog.contains_key(**label)).collect(); + let notes = labels[ui_labels..].iter().filter(|label| !catalog.contains_key(**label)).count(); + if !missing.is_empty() || notes > 0 { + eprintln!("{} lacks {} display label(s) (shown in English): {missing:?}; and {notes} What's New line(s)", language.code(), missing.len()); + } + } + // the German catalog, which set out to cover them all, still covers every other catalog + let german = Locale::De.catalog(); + let gaps: Vec<&String> = Locale::ALL.iter().flat_map(|language| language.catalog().keys()).filter(|key| !german.contains_key(key.as_str())).collect(); + if !gaps.is_empty() { + eprintln!("de lacks {} message(s) another catalog has: {gaps:?}", gaps.len()); } } diff --git a/docs/localization-ru.md b/docs/localization-ru.md new file mode 100644 index 00000000..5ffbeffe --- /dev/null +++ b/docs/localization-ru.md @@ -0,0 +1,23 @@ +# LightCraft на русском + +Выберите **Правка → Язык → Русский** или **Настройки → Общие → Язык**. Выбор применяется сразу +и сохраняется для следующего запуска (`language: "ru"` в `ui.json`). + +Русский каталог охватывает меню, сочетания клавиш, библиотеку, обработку, кадрирование, маски, +импорт и экспорт, настройки, встроенные стили и профили, историю, даты и сообщения о ходе работы. +Технические ошибки из нижних уровней, как и в других языках, остаются на английском; строки, которых +ещё нет в каталоге, показываются по-английски. + +Имена файлов, идентификаторы команд, шаблоны и названные вами альбомы, стили и метаданные не +переводятся. Шрифт интерфейса Inter содержит кириллицу, дополнительные шрифты не нужны. + +## Сопровождение и проверка + +Постоянные тексты: `crates/ui-egui/locales/ru.json`. Сообщения со значениями: +`crates/ui-egui/locales/ru-formats.json`. Английские ключи не меняются; заполнители проверяются +компилятором Rust при сборке. Английские окончания множественного числа скрываются через `{:.0}`. + +```sh +cargo test -p lightcraft-ui-egui i18n::tests -- --nocapture +LIGHTCRAFT_LANGUAGE=ru lightcraft-cli snapshot --demo -o russian.png --size 1600x1000 +``` diff --git a/docs/localization.md b/docs/localization.md index e8d28092..9965dec8 100644 --- a/docs/localization.md +++ b/docs/localization.md @@ -2,10 +2,10 @@ The interface ships in the language it is written in (English) plus every language in the table in `crates/ui-egui/src/i18n.rs`. Today that is English, Simplified Chinese (`zh-hans`), Traditional -Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`) and German (`de`). This file is the reference for **adding or maintaining a language**; the per-language notes +Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), German (`de`) and Russian (`ru`). This file is the reference for **adding or maintaining a language**; the per-language notes are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-hant.md`](localization-zh-hant.md), -[`localization-ja.md`](localization-ja.md), [`localization-pt-br.md`](localization-pt-br.md) and -[`localization-de.md`](localization-de.md). +[`localization-ja.md`](localization-ja.md), [`localization-pt-br.md`](localization-pt-br.md), +[`localization-de.md`](localization-de.md) and [`localization-ru.md`](localization-ru.md). ## Adding a language @@ -14,7 +14,8 @@ are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-h English source strings and must stay byte-for-byte identical. The plain catalog may grow at its own pace: a message it lacks shows in English, and `cargo test -p lightcraft-ui-egui i18n::tests -- --nocapture` lists the gaps (it fails on an empty entry or mismatched placeholders). The - formats catalog must carry every message (the build fails otherwise), including the date + formats catalog must carry every message (the build fails otherwise; a message copied in English, + value equal to its key, is an untranslated placeholder until someone translates it), including the date patterns (`{year}`, `{month} {year}`, `{weekday}, {day} {month} {year}`…) that date headings and capture times use in every language but English; weekday names go in the plain catalog. 2. **Add the table entry** in `crates/ui-egui/src/i18n.rs`: @@ -27,6 +28,7 @@ are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-h Ja, "ja", "日本語", "Jpan", include_str!("../locales/ja.json"); PtBr, "pt-br", "Português (Brasil)", "Latn", include_str!("../locales/pt-br.json"); De, "de", "Deutsch", "Latn", include_str!("../locales/de.json"); + Ru, "ru", "Русский", "Cyrl", include_str!("../locales/ru.json"); } ``` @@ -87,3 +89,11 @@ The settings file stores the BCP-47 code, never the Rust variant name, so a lang in code without invalidating anyone's saved preference. `LIGHTCRAFT_LANGUAGE` accepts what a system locale looks like (`zh_Hans`, `zh-CN`, `en_US`, `ja_JP.UTF-8`) and falls back to the base language when a region has no dedicated entry. + +## Release notes + +Help ▸ What's New shows `docs/whats-new.md`, and each of its lines (a heading, or one wrapped line of a +bullet, without the `- ` or `### `) is looked up like any message, so a catalog can translate the notes +line by line (German does). A line that is re-wrapped or edited no longer matches its translation and +shows in English until the catalog is updated; untranslated lines are reported by +`display_label_gaps_are_reported`, never failed. diff --git a/docs/parity.md b/docs/parity.md index 6bc9ab6f..11d42afb 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -84,8 +84,6 @@ Take the first one nobody is working on. Settings → Performance → Preview size (2,560 px by default). Render visible regions at native resolution so 100% can reliably show original-pixel detail on larger photos. -German UI is available (M16, LR-BEHAV-LOCALIZE); German also translates release notes; remaining localisation gaps are technical errors and other locales. - ## Shortcuts: conflicts and missing bindings Compared `plan/lightroom/06-shortcuts.md` (desktop part) with our bindings: command specs in `crates/engine/src/cmd/`, @@ -518,8 +516,8 @@ under the UI command that wraps it (a no-op error); the UI command now wins. | LR-BEHAV-EMPTY-STATES | Empty states | P1 | ✅ | `crates/ui-egui/src/panels/mod.rs` (`empty_message`) | | | LR-BEHAV-TOOLTIPS | Tooltips with shortcuts | P0 | ✅ | `crates/ui-egui/src/panels/bottombar.rs` | | | LR-BEHAV-ACCESS | Accessibility | P2 | 🟡 | `crates/ui-egui/src/widgets.rs`, `crates/ui-egui/src/panels/grid.rs` | AccessKit (VoiceOver / Narrator / AT-SPI): sliders announce control and value, buttons / icon buttons / dropdowns / section headers / sources their labels and state, grid thumbnails file name, rating, flag and label; the canvas tools (crop, masks) are pointer-only; not audited with a screen reader | -| LR-BEHAV-LOCALE | Language options | P2 | ✅ | `cmd:app.language.english`, `cmd:app.language.simplifiedChinese`, `cmd:app.language.traditionalChinese`, `cmd:app.language.japanese`, `cmd:app.language.portuguese`, `cmd:app.language.german` | Edit > Language lists every language in the table and the active one is checked; shared with Settings > General (docs/localization.md) | -| LR-BEHAV-LOCALIZE | Localisation | P2 | 🟡 | `crates/ui-egui/src/i18n.rs`, `crates/ui-egui/locales/`, `docs/localization.md`, `docs/localization-zh-hans.md`, `docs/localization-zh-hant.md`, `docs/localization-ja.md`, `docs/localization-pt-br.md`, `docs/localization-de.md` | One table entry per language (code, endonym, ISO 15924 script, catalog): English, Simplified Chinese, Traditional Chinese (Taiwan), Japanese, Brazilian Portuguese and German UI with a persisted language preference and craft-fonts regular/bold faces per script; menus, edit, crop/masks, settings, import/export and primary progress messages. Adding a language is a table entry plus two catalogs. Stock presets/profiles, history steps, library headings, date headings and capture times are localised (user-named items stay verbatim); the German catalog covers every existing catalog key, command/control label and rule-editor label; the other catalogs fall back to English for messages they lack. Technical errors remain English; release notes are translated in German; Traditional Chinese borrows the Simplified Chinese face (no TC face in craft-fonts yet), and the web build embeds only the Japanese face. | +| LR-BEHAV-LOCALE | Language options | P2 | ✅ | `cmd:app.language.english`, `cmd:app.language.simplifiedChinese`, `cmd:app.language.traditionalChinese`, `cmd:app.language.japanese`, `cmd:app.language.portuguese`, `cmd:app.language.german`, `cmd:app.language.russian` | Edit > Language lists every language in the table and the active one is checked; shared with Settings > General (docs/localization.md) | +| LR-BEHAV-LOCALIZE | Localisation | P2 | 🟡 | `crates/ui-egui/src/i18n.rs`, `crates/ui-egui/locales/`, `docs/localization.md`, `docs/localization-zh-hans.md`, `docs/localization-zh-hant.md`, `docs/localization-ja.md`, `docs/localization-pt-br.md`, `docs/localization-de.md`, `docs/localization-ru.md` | One table entry per language (code, endonym, ISO 15924 script, catalog): English, Simplified Chinese, Traditional Chinese (Taiwan), Japanese, Brazilian Portuguese, German and Russian UI with a persisted language preference and craft-fonts regular/bold faces per script; menus, edit, crop/masks, settings, import/export and primary progress messages. Adding a language is a table entry plus two catalogs. Stock presets/profiles, history steps, library headings, date headings and capture times are localised (user-named items stay verbatim); the German catalog covers every existing catalog key, command/control label and rule-editor label; the other catalogs fall back to English for messages they lack. Technical errors remain English; release notes are translated in German; Traditional Chinese borrows the Simplified Chinese face (no TC face in craft-fonts yet), and the web build embeds only the Japanese face. | | LR-BEHAV-LEARN | Tutorials | OOS | 🚫 | | | | LR-BEHAV-WHATSNEW | What's new | P2 | ✅ | `cmd:app.whatsNew`, `docs/whats-new.md` | Help ▸ What's New: release highlights | | LR-BEHAV-AI-EA | Early-access badges | P2 | ⬜ | | | diff --git a/docs/whats-new.md b/docs/whats-new.md index 62615b95..9ad976b5 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -46,6 +46,10 @@ - The Import Photos review opens bigger and can be resized; its photo grid fills it (issue #337). Shift-click checks or unchecks a range of photos (issue #338). +### Languages +- The interface is available in German and Russian (Edit ▸ Language), alongside English, Chinese (Simplified and + Traditional), Japanese and Brazilian Portuguese. + ### Reliability - On macOS, single-key shortcuts that appear in the menu bar now work: E, C, H, M, ⇧P, I, K, D, ratings 0–5, labels 6–9, P / U and the rest did nothing, because macOS only passes ⌘ / ⌃ combinations and function keys