mirror of
https://github.com/storytold/filmcraft.git
synced 2026-11-02 18:09:21 +00:00
i18n: add Polish interface language
Adds a Polish catalog (pl.tsv) covering every Spanish entry plus the macOS-only menu rows, registers Polski in the language menu, settings and the native macOS menu, lists app.language.polish in the localization docs, and adds a test that keeps the catalog complete and checks Polish letters render with the bundled fonts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DukHNvUY45rjRnvtz91x33
This commit is contained in:
@@ -13,6 +13,7 @@ iconography, images or other Adobe assets; open licences only; every asset attri
|
||||
| `crates/ui-egui/src/i18n/id.tsv` | FilmCraft contributors | Original work: clean-room Indonesian interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/ko.tsv` | FilmCraft contributors | Original work: clean-room Korean interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/fr.tsv` | FilmCraft contributors | Original work: clean-room French interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/pl.tsv` | FilmCraft contributors | Original work: clean-room Polish interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/pt-br.tsv` | FilmCraft contributors | Original work: clean-room Brazilian Portuguese interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/ru.tsv` | FilmCraft contributors | Original work: clean-room Russian interface translations | MIT OR Apache-2.0 |
|
||||
| `crates/ui-egui/src/i18n/uk.tsv` | FilmCraft contributors | Original work: clean-room Ukrainian interface translations | MIT OR Apache-2.0 |
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ Premiere ships 10 interface languages. Measured from our catalogs (3,498 strings
|
||||
| German | partial (whole interface; menus native-reviewed, the rest welcome) | ~100% |
|
||||
| Vietnamese | none | 0% |
|
||||
|
||||
Also shipped: Ukrainian (menus only, 10%). Detail: [docs/localization-parity.md](docs/localization-parity.md).
|
||||
Also shipped: Ukrainian (menus only, 10%), Polish (whole interface; AI-drafted, native review welcome). Detail: [docs/localization-parity.md](docs/localization-parity.md).
|
||||
|
||||
## Upcoming
|
||||
|
||||
|
||||
@@ -147,6 +147,8 @@ pub fn install(app: &FilmcraftApp, ctx: egui::Context) -> (Receiver<String>, Men
|
||||
filmcraft_ui_egui::i18n::Language::It
|
||||
} else if checked("app.language.korean") {
|
||||
filmcraft_ui_egui::i18n::Language::Ko
|
||||
} else if checked("app.language.polish") {
|
||||
filmcraft_ui_egui::i18n::Language::Pl
|
||||
} else {
|
||||
filmcraft_ui_egui::i18n::Language::En
|
||||
};
|
||||
|
||||
@@ -224,6 +224,7 @@ pub(super) static CATEGORIES: &[Category] = &[
|
||||
("id", "Bahasa Indonesia"),
|
||||
("it", "Italiano"),
|
||||
("ko", "한국어"),
|
||||
("pl", "Polski"),
|
||||
]),
|
||||
true,
|
||||
),
|
||||
|
||||
@@ -50,6 +50,8 @@ fn interface_language_persists_and_rejects_hostile_values() {
|
||||
}
|
||||
set(&mut s, "general.interfaceLanguage", json!("it"));
|
||||
assert_eq!(Preferences::load(&path).general.interface_language, "it");
|
||||
set(&mut s, "general.interfaceLanguage", json!("pl"));
|
||||
assert_eq!(Preferences::load(&path).general.interface_language, "pl");
|
||||
// back to following the operating system (#218)
|
||||
set(&mut s, "general.interfaceLanguage", json!("system"));
|
||||
assert_eq!(Preferences::load(&path).general.interface_language, "system");
|
||||
|
||||
@@ -49,6 +49,8 @@ pub enum Language {
|
||||
It,
|
||||
/// Korean; Hangul needs an installed system font ([`korean_font_available`]).
|
||||
Ko,
|
||||
/// Polish.
|
||||
Pl,
|
||||
}
|
||||
|
||||
static JAPANESE: OnceLock<Catalog> = OnceLock::new();
|
||||
@@ -60,11 +62,13 @@ static GERMAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static RUSSIAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static FRENCH: OnceLock<Catalog> = OnceLock::new();
|
||||
static KOREAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static POLISH: OnceLock<Catalog> = OnceLock::new();
|
||||
static INDONESIAN: OnceLock<Catalog> = OnceLock::new();
|
||||
static ITALIAN: OnceLock<Catalog> = OnceLock::new();
|
||||
|
||||
impl Language {
|
||||
pub const ALL: [Self; 12] = [Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk, Self::ZhCn, Self::De, Self::Ru, Self::Fr, Self::Id, Self::Ko, Self::It];
|
||||
pub const ALL: [Self; 13] =
|
||||
[Self::En, Self::Ja, Self::Es, Self::PtBr, Self::Uk, Self::ZhCn, Self::De, Self::Ru, Self::Fr, Self::Id, Self::Ko, Self::It, Self::Pl];
|
||||
|
||||
/// BCP 47 tag for AccessKit and the web IME's hidden input. Keep the persisted spelling
|
||||
/// (`pt-br`, `zh-cn`) out of the protocol-facing value.
|
||||
@@ -82,6 +86,7 @@ impl Language {
|
||||
Self::Id => "id",
|
||||
Self::It => "it",
|
||||
Self::Ko => "ko",
|
||||
Self::Pl => "pl",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +104,7 @@ impl Language {
|
||||
Self::Id => "Bahasa Indonesia",
|
||||
Self::It => "Italiano",
|
||||
Self::Ko => "한국어",
|
||||
Self::Pl => "Polski",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +122,7 @@ impl Language {
|
||||
"id" => Some(Self::Id),
|
||||
"it" => Some(Self::It),
|
||||
"ko" => Some(Self::Ko),
|
||||
"pl" => Some(Self::Pl),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -130,7 +137,7 @@ impl Language {
|
||||
/// only for Simplified locales (`zh`, `zh-CN`, `zh-SG`, `zh-Hans…`); Traditional ones (`zh-TW`,
|
||||
/// `zh-HK`, `zh-MO`, `zh-Hant…`) are skipped.
|
||||
pub fn from_locales(tags: &[String]) -> Self {
|
||||
const PRIMARY: [(&str, Language); 12] = [
|
||||
const PRIMARY: [(&str, Language); 13] = [
|
||||
("en", Language::En),
|
||||
("ja", Language::Ja),
|
||||
("es", Language::Es),
|
||||
@@ -143,6 +150,7 @@ impl Language {
|
||||
("in", Language::Id),
|
||||
("it", Language::It),
|
||||
("ko", Language::Ko),
|
||||
("pl", Language::Pl),
|
||||
];
|
||||
tags.iter()
|
||||
.find_map(|tag| {
|
||||
@@ -175,6 +183,7 @@ impl Language {
|
||||
Self::Id => "id",
|
||||
Self::It => "it",
|
||||
Self::Ko => "ko",
|
||||
Self::Pl => "pl",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +202,7 @@ impl Language {
|
||||
Self::Id => Some(INDONESIAN.get_or_init(|| Catalog::parse(include_str!("i18n/id.tsv")))),
|
||||
Self::It => Some(ITALIAN.get_or_init(|| Catalog::parse(include_str!("i18n/it.tsv")))),
|
||||
Self::Ko => Some(KOREAN.get_or_init(|| Catalog::parse(include_str!("i18n/ko.tsv")))),
|
||||
Self::Pl => Some(POLISH.get_or_init(|| Catalog::parse(include_str!("i18n/pl.tsv")))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
asset: pl.tsv
|
||||
title: Polish interface translation catalog
|
||||
author: FilmCraft contributors
|
||||
source: original work
|
||||
license: MIT OR Apache-2.0
|
||||
license-file: LICENSE-MIT and LICENSE-APACHE
|
||||
added: 2026-10-13 by lewandowskit
|
||||
notes: Original English-to-Polish interface translations using clean-room NLE vocabulary (AI-assisted draft).
|
||||
@@ -15,6 +15,7 @@ fn catalog_text(l: Language) -> Option<&'static str> {
|
||||
Language::Id => Some(include_str!("id.tsv")),
|
||||
Language::It => Some(include_str!("it.tsv")),
|
||||
Language::Ko => Some(include_str!("ko.tsv")),
|
||||
Language::Pl => Some(include_str!("pl.tsv")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +64,7 @@ fn unknown_strings_pass_through_and_languages_round_trip() {
|
||||
(Language::Id, "Bahasa Indonesia"),
|
||||
(Language::It, "Italiano"),
|
||||
(Language::Ko, "한국어"),
|
||||
(Language::Pl, "Polski"),
|
||||
] {
|
||||
assert_eq!(l.name(), name);
|
||||
}
|
||||
@@ -513,6 +515,7 @@ fn language_commands_switch_and_persist_without_a_document() {
|
||||
("french", Language::Fr, "fr", "Français", Some(("app.language.german", "Deutsch"))),
|
||||
("indonesian", Language::Id, "id", "Bahasa Indonesia", Some(("app.language.french", "Français"))),
|
||||
("italian", Language::It, "it", "Italiano", Some(("app.language.indonesian", "Bahasa Indonesia"))),
|
||||
("polish", Language::Pl, "pl", "Polski", Some(("app.language.italian", "Italiano"))),
|
||||
] {
|
||||
switch_and_check(&mut app, &ctx, name, lang, code, own, earlier);
|
||||
}
|
||||
@@ -749,3 +752,24 @@ fn japanese_needs_an_installed_font() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn polish_is_complete_and_renders_with_bundled_fonts() {
|
||||
let (entries, errors) = catalog::parse_entries(include_str!("pl.tsv"));
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
let (spanish, _) = catalog::parse_entries(include_str!("es.tsv"));
|
||||
let missing: Vec<&String> = spanish.iter().map(|(_, en, _)| en).filter(|en| !Language::Pl.has(en)).collect();
|
||||
assert!(missing.is_empty(), "untranslated Polish entries: {missing:#?}");
|
||||
for (en, pl) in [("File", "Plik"), ("Save As…", "Zapisz jako…"), ("Timeline", "Oś czasu"), ("Gaussian Blur", "Rozmycie gaussowskie")] {
|
||||
assert_eq!(Language::Pl.tr(en), pl);
|
||||
}
|
||||
assert_eq!(Language::Pl.tr("Wakacje w górach.mp4"), "Wakacje w górach.mp4");
|
||||
assert_eq!(Language::parse("pl"), Some(Language::Pl));
|
||||
assert_eq!(locales(&["pl-PL"]), Language::Pl);
|
||||
assert_eq!(locales(&["pl_PL.UTF-8", "en"]), Language::Pl);
|
||||
// every Polish letter, even ones no entry uses yet, and every letter, curly quote, dash and
|
||||
// ellipsis the entries use
|
||||
let alphabet = "ĄĆĘŁŃÓŚŹŻąćęłńóśźż„”";
|
||||
let letters = entries.iter().flat_map(|(_, _, pl)| pl.chars()).filter(|ch| ch.is_alphabetic() || "„”“‘’–—…".contains(*ch));
|
||||
assert_bundled_fonts_cover(alphabet.chars().chain(Language::Pl.name().chars()).chain(letters));
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ pub const UI_COMMANDS: &[UiCommand] = &[
|
||||
uic!("app.language.indonesian", "Bahasa Indonesia", ["Edit", "Language"], None),
|
||||
uic!("app.language.italian", "Italiano", ["Edit", "Language"], None),
|
||||
uic!("app.language.korean", "한국어", ["Edit", "Language"], None),
|
||||
uic!("app.language.polish", "Polski", ["Edit", "Language"], None),
|
||||
uic!("source.playback.toggle", "Source Play/Stop", [], None),
|
||||
uic!("source.playback.play", "Play Source", [], None),
|
||||
uic!("source.playback.stop", "Stop Source", [], None),
|
||||
@@ -203,6 +204,7 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params:
|
||||
| "app.language.indonesian"
|
||||
| "app.language.italian"
|
||||
| "app.language.korean"
|
||||
| "app.language.polish"
|
||||
) {
|
||||
// Japanese needs the craft-fonts (built with CRAFT_FONTS_DIR) or a font installed on the system
|
||||
if id == "app.language.japanese" && !crate::i18n::install_japanese_font(ctx) {
|
||||
@@ -228,6 +230,7 @@ pub fn invoke(app: &mut FilmcraftApp, ctx: &egui::Context, id: &str, mut params:
|
||||
"app.language.indonesian" => crate::i18n::Language::Id,
|
||||
"app.language.italian" => crate::i18n::Language::It,
|
||||
"app.language.korean" => crate::i18n::Language::Ko,
|
||||
"app.language.polish" => crate::i18n::Language::Pl,
|
||||
_ => crate::i18n::Language::En,
|
||||
};
|
||||
// The preference is updated in memory before it is written, so a failed write (read-only
|
||||
@@ -818,6 +821,7 @@ pub fn menu_items(app: &FilmcraftApp) -> Vec<MenuItem> {
|
||||
"app.language.indonesian" => it.checked = Some(app.ui.language == crate::i18n::Language::Id),
|
||||
"app.language.italian" => it.checked = Some(app.ui.language == crate::i18n::Language::It),
|
||||
"app.language.korean" => it.checked = Some(app.ui.language == crate::i18n::Language::Ko),
|
||||
"app.language.polish" => it.checked = Some(app.ui.language == crate::i18n::Language::Pl),
|
||||
_ => {}
|
||||
}
|
||||
if it.id.starts_with("view.") {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (German covers the whole interface) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (Polish catalog added) · **Target:** Adobe Premiere Pro 2026 (26.5.2)
|
||||
|
||||
Per-language status of FilmCraft's interface. Premiere 26.5.2 ships 10 interface languages (the
|
||||
installed bundle's `.lproj` folders): German, English, Spanish, French, Italian, Japanese, Korean,
|
||||
@@ -10,7 +10,7 @@ Portuguese (Brazil), Russian, Simplified Chinese.
|
||||
|
||||
Edit ▸ Language (also Settings ▸ General, `general.interfaceLanguage`) switches the interface and
|
||||
persists across restarts; System Language (the default) follows the OS's or browser's preferred
|
||||
languages and falls back to English. Commands `app.language.<english|japanese|spanish|portuguese|ukrainian|chinese|german|russian|french|indonesian|italian>`
|
||||
languages and falls back to English. Commands `app.language.<english|japanese|spanish|portuguese|ukrainian|chinese|german|russian|french|indonesian|italian|polish>`
|
||||
are reachable over the control channel. One catalog per language,
|
||||
`crates/ui-egui/src/i18n/<code>.tsv` (format in the header of `es.tsv`); UI code wraps strings in
|
||||
`tl!("…")` / `tlf!("…{name}", name)`, and registry names (effects and their parameters, settings,
|
||||
@@ -54,6 +54,7 @@ whole interface, one PR) at ~4–8 h per language for the catalog plus tests.
|
||||
| Korean | ko | 3,573 (same entries as Spanish, plus the macOS Settings item) | as Spanish | installed system Hangul font (Malgun Gothic, Apple SD Gothic Neo, Noto Sans CJK KR…); refused without one | not recorded; welcome | partial (#508) | review; Hangul in craft-fonts for the web |
|
||||
| Vietnamese | vi | 0 (0%) | — | Latin with stacked diacritics (font coverage to check) | — | none | 6–12 h + review |
|
||||
| *Other shipped:* Italian | it | 3,573 (same entries as Spanish, plus the macOS Settings item) | as Spanish | Latin (bundled fonts) | not recorded; welcome | partial | review |
|
||||
| *Other shipped:* Polish | pl | every Spanish entry plus the native macOS menu items; counts written count-neutral | as Spanish | Latin (bundled fonts) | not recorded; AI-drafted, review welcome | partial | review |
|
||||
| *Other shipped:* Ukrainian | uk | 351 (10.0%) | menus only | Cyrillic (bundled) | not recorded | menus only | 5–9 h |
|
||||
|
||||
UI strings are catalog entries (the non-comment lines of `src/i18n/<code>.tsv`; English counts the
|
||||
|
||||
Reference in New Issue
Block a user