i18n: German and Russian follow-ups (#265, #264)

- #265's German coverage test failed whenever any catalog key, command or
  control label, or line of What's New lacked German, so every feature PR
  would have had to ship German. It reports those gaps for every language
  now (display_label_gaps_are_reported), as the project's other gap test
  does; docs/localization.md says how release-note lines are matched and
  that an English copy in a formats catalog marks an untranslated message.
- Russian: docs/localization-ru.md, the language lists in
  docs/localization.md and docs/parity.md, ru tag tests.
- What's new: German and Russian UI. Removes a status sentence #265 put
  in parity.md's Top gaps list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01912WaK6Rmew3wxZuPyX7E8
This commit is contained in:
Claude 2026-10-08 18:26:56 +00:00
parent 9b5e771fd8
commit 0634782fbc
No known key found for this signature in database
5 changed files with 65 additions and 17 deletions

View File

@ -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());
}
}

23
docs/localization-ru.md Normal file
View File

@ -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
```

View File

@ -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.

View File

@ -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 | ⬜ | | |

View File

@ -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