#859: complete the Brazilian Portuguese (pt-br) interface translation
Problem: the Portuguese (Brazil) catalog, one of Premiere's interface languages and one of the 12 key languages in docs/gaps.md G14, covered only the menus (348 of ~3,540 strings); panels, dialogs, Settings pages, effect names, the Keyboard Shortcuts dialog and status messages showed in English. Fix: pt-br.tsv now has every entry Spanish has plus the native macOS Settings item, written from the plain meaning of the English text with ordinary Brazilian video-editing vocabulary (infinitive commands, polite imperative instructions, sentence case); the header records the conventions and a term glossary. Like Japanese and French, tests require only the menus (now every menu label, not just the core set), so later strings show in English until translated; a Portuguese entry without a Spanish one fails, and a new test checks the bundled fonts cover every Portuguese letter. The core-menu set that Ukrainian, German, Russian and French must cover moves from pt-br.tsv (no longer menus-only) to the menus-only Ukrainian catalog, unchanged in content for those tests. Attribution sidecar and ATTRIBUTION.md entry added. Docs: G14, localization-parity, ROADMAP language row, ui-egui README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2ff3f5e266
commit
c6d47e20f0
|
|
@ -10,6 +10,7 @@ iconography, images or other Adobe assets; open licences only; every asset attri
|
|||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `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/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 |
|
||||
| `assets/fonts/Inter-Regular.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ Premiere ships 10 interface languages. Measured from our catalogs (3,498 strings
|
|||
| Hindi | none (no Devanagari shaping in the interface) | 0% |
|
||||
| Arabic | none (no RTL interface) | 0% |
|
||||
| French | partial (whole interface; native review welcome) | ~100% |
|
||||
| Portuguese (Brazil) | menus only | 9.9% |
|
||||
| Portuguese (Brazil) | partial (whole interface; native review welcome) | ~100% |
|
||||
| Indonesian | none | 0% |
|
||||
| Japanese | partial (whole interface) | 99.7% |
|
||||
| German | none | 0% |
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ fills translated templates, and `i18n::t` translates names from registries. Plac
|
|||
(including user filenames containing braces) are inserted literally. Catalog translations are
|
||||
original work using ordinary language, without proprietary localisation resources.
|
||||
|
||||
Spanish, Japanese, Simplified Chinese and French cover menus, panels, dialogs, settings and registry labels. Searches accept both the
|
||||
Spanish, Japanese, Simplified Chinese, French and Brazilian Portuguese cover menus, panels, dialogs, settings and registry labels. Searches accept both the
|
||||
translated label and its English source, including Unicode capitals. Project content, command ids
|
||||
and preference values retain their original values. Engine errors, CLI and MCP messages remain
|
||||
English. Brazilian Portuguese and Ukrainian currently cover the core menus, German every menu; they
|
||||
fall back to English elsewhere.
|
||||
English. Ukrainian and Russian currently cover the core menus, German every menu; they fall back
|
||||
to English elsewhere.
|
||||
|
||||
Verification: `cargo test -p filmcraft-ui-egui` checks catalog syntax, duplicate keys, placeholders,
|
||||
literal/menu/registry coverage and UI behaviour; `cargo xtask ci` runs the workspace gates. Visual
|
||||
|
|
|
|||
|
|
@ -425,19 +425,62 @@ mod tests {
|
|||
assert!(missing.is_empty(), "untranslated menu labels: {missing:#?}");
|
||||
}
|
||||
|
||||
/// The Brazilian Portuguese catalog covers the menus; every entry must still be a menu label
|
||||
/// (a renamed command would otherwise leave a dead entry and an untranslated menu item).
|
||||
/// The core menus every other catalog must cover: the entries of the menus-only Ukrainian
|
||||
/// catalog, which `ukrainian_entries_cover_the_original_menu_catalog` keeps to menu labels (and
|
||||
/// the native macOS Settings menu). Brazilian Portuguese played this role while it was
|
||||
/// menus-only.
|
||||
fn core_menu_labels() -> Vec<String> {
|
||||
let (entries, _) = catalog::parse_entries(include_str!("uk.tsv"));
|
||||
entries.into_iter().map(|(_, en, _)| en).collect()
|
||||
}
|
||||
|
||||
/// Brazilian Portuguese covers the whole interface (the Spanish catalog's entries and the native
|
||||
/// macOS Settings menu), but, like Japanese and French, the tests require only the menus, so
|
||||
/// strings added later show in English until they are translated. Every entry must still have a
|
||||
/// Spanish counterpart: a renamed or removed source would otherwise leave a dead entry.
|
||||
#[test]
|
||||
fn portuguese_entries_are_menu_labels() {
|
||||
let app = crate::FilmcraftApp::new(filmcraft_engine::Session::default());
|
||||
let items = crate::menus::menu_items(&app);
|
||||
let known = |text: &str| crate::menus::MENUS.contains(&text) || items.iter().any(|it| it.label == text || it.path.iter().any(|p| p == text));
|
||||
let (entries, _) = catalog::parse_entries(include_str!("pt-br.tsv"));
|
||||
assert!(!entries.is_empty());
|
||||
for (_, en, _) in entries {
|
||||
assert!(known(&en), "not a menu label: {en}");
|
||||
fn portuguese_covers_the_menus_and_has_no_dead_entries() {
|
||||
let (entries, errors) = catalog::parse_entries(include_str!("pt-br.tsv"));
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
for (ctx, en, _) in &entries {
|
||||
assert!(ctx.is_empty() && (Language::Es.has(en) || en == "Settings"), "Portuguese entry without a Spanish one: {ctx:?} {en:?}");
|
||||
}
|
||||
assert_eq!(Language::PtBr.tr("File"), "Arquivo");
|
||||
let app = crate::FilmcraftApp::new(filmcraft_engine::Session::default());
|
||||
let mut missing: Vec<String> = Vec::new();
|
||||
let mut need = |text: &str| {
|
||||
if translatable(text) && Language::Es.has(text) && !Language::PtBr.has(text) && !missing.iter().any(|m| m == text) {
|
||||
missing.push(text.to_string());
|
||||
}
|
||||
};
|
||||
for top in crate::menus::MENUS.into_iter().chain(["Audio", "Settings"]) {
|
||||
need(top);
|
||||
}
|
||||
for it in crate::menus::menu_items(&app) {
|
||||
need(&it.label);
|
||||
for p in &it.path {
|
||||
need(p);
|
||||
}
|
||||
}
|
||||
for en in core_menu_labels() {
|
||||
need(&en);
|
||||
}
|
||||
missing.sort();
|
||||
assert!(missing.is_empty(), "untranslated Portuguese menu labels: {missing:#?}");
|
||||
// beyond the menus: panels, effects and their parameters, settings, status messages
|
||||
for (en, pt) in [
|
||||
("File", "Arquivo"),
|
||||
("Settings", "Preferências"),
|
||||
("Gaussian Blur", "Desfoque gaussiano"),
|
||||
("Audio Track Mixer", "Misturador de faixas de áudio"),
|
||||
("Keyboard Shortcuts", "Atalhos de teclado"),
|
||||
("Playhead", "Cursor de reprodução"),
|
||||
] {
|
||||
assert_eq!(Language::PtBr.tr(en), pt);
|
||||
}
|
||||
set_current(Language::PtBr);
|
||||
assert!(matches_query("Gaussian Blur", "desfoque"));
|
||||
assert!(matches_query("Gaussian Blur", "gaussian"));
|
||||
set_current(Language::En);
|
||||
}
|
||||
|
||||
/// Every `tl!`/`tlf!` literal outside test modules has a Spanish translation.
|
||||
|
|
@ -921,17 +964,13 @@ mod tests {
|
|||
for (_, en, _) in entries {
|
||||
assert!(known(&en) || en == "Settings", "not a menu label: {en}");
|
||||
}
|
||||
let (portuguese, _) = catalog::parse_entries(include_str!("pt-br.tsv"));
|
||||
for (_, en, _) in portuguese {
|
||||
assert!(Language::Uk.has(&en), "missing Ukrainian menu label: {en}");
|
||||
}
|
||||
for en in crate::menus::MENUS.into_iter().chain(["Audio", "Settings"]) {
|
||||
assert_ne!(Language::Uk.tr(en), en, "untranslated Ukrainian menu: {en}");
|
||||
}
|
||||
}
|
||||
|
||||
/// German covers at least the core menus (the Brazilian Portuguese entries, the top-level menus
|
||||
/// and the native macOS Settings menu); every entry must still be a menu label.
|
||||
/// German covers at least the core menus (`core_menu_labels`, the top-level menus and the native
|
||||
/// macOS Settings menu); every entry must still be a menu label.
|
||||
#[test]
|
||||
fn german_entries_cover_the_original_menu_catalog() {
|
||||
let app = crate::FilmcraftApp::new(filmcraft_engine::Session::default());
|
||||
|
|
@ -941,8 +980,7 @@ mod tests {
|
|||
for (_, en, _) in entries {
|
||||
assert!(known(&en) || en == "Settings", "not a menu label: {en}");
|
||||
}
|
||||
let (portuguese, _) = catalog::parse_entries(include_str!("pt-br.tsv"));
|
||||
for (_, en, _) in portuguese {
|
||||
for en in core_menu_labels() {
|
||||
assert!(Language::De.has(&en), "missing German menu label: {en}");
|
||||
}
|
||||
// "Clip" and "Audio" are the same word in German, so check for an entry, not a change
|
||||
|
|
@ -963,8 +1001,7 @@ mod tests {
|
|||
for (ctx, en, _) in &entries {
|
||||
assert!(ctx.is_empty() && (Language::Es.has(en) || en == "Settings"), "French entry without a Spanish one: {ctx:?} {en:?}");
|
||||
}
|
||||
let (portuguese, _) = catalog::parse_entries(include_str!("pt-br.tsv"));
|
||||
for (_, en, _) in portuguese {
|
||||
for en in core_menu_labels() {
|
||||
assert!(Language::Fr.has(&en), "missing French menu label: {en}");
|
||||
}
|
||||
for en in crate::menus::MENUS.into_iter().chain(["Audio", "Settings"]) {
|
||||
|
|
@ -991,8 +1028,7 @@ mod tests {
|
|||
for (_, en, _) in entries {
|
||||
assert!(known(&en) || en == "Settings", "not a menu label: {en}");
|
||||
}
|
||||
let (portuguese, _) = catalog::parse_entries(include_str!("pt-br.tsv"));
|
||||
for (_, en, _) in portuguese {
|
||||
for en in core_menu_labels() {
|
||||
assert!(Language::Ru.has(&en), "missing Russian menu label: {en}");
|
||||
}
|
||||
for en in crate::menus::MENUS.into_iter().chain(["Audio", "Settings"]) {
|
||||
|
|
@ -1033,6 +1069,15 @@ mod tests {
|
|||
assert_bundled_fonts_cover(alphabet.chars().chain(Language::Uk.name().chars()).chain(entries.iter().flat_map(|(_, _, uk)| uk.chars())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portuguese_renders_with_bundled_fonts() {
|
||||
let (entries, _) = catalog::parse_entries(include_str!("pt-br.tsv"));
|
||||
// every Portuguese letter, even ones no entry uses yet, and every letter the entries use
|
||||
let alphabet = "ÁÂÃÀÇÉÊÍÓÔÕÚÜáâãàçéêíóôõúü";
|
||||
let letters = entries.iter().flat_map(|(_, _, pt)| pt.chars()).filter(|ch| ch.is_alphabetic());
|
||||
assert_bundled_fonts_cover(alphabet.chars().chain(Language::PtBr.name().chars()).chain(letters));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn french_renders_with_bundled_fonts() {
|
||||
let (entries, _) = catalog::parse_entries(include_str!("fr.tsv"));
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,8 @@
|
|||
asset: pt-br.tsv
|
||||
title: Brazilian Portuguese 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-11 by vangel76
|
||||
notes: Original English-to-Brazilian-Portuguese interface translations using clean-room NLE vocabulary.
|
||||
|
|
@ -201,11 +201,11 @@ platforms** G4, G9, G17; **performance** G16; **ecosystem** G11; **measurement**
|
|||
|
||||
## G14. Localization: 8 of the 12 key languages missing
|
||||
|
||||
- **Have:** Spanish, Japanese, Simplified Chinese and French (#498) catalogs cover ~99.6–100% of
|
||||
the ~3,500 cataloged strings; Portuguese (Brazil) and Ukrainian ~10% (menus).
|
||||
- **Have:** Spanish, Japanese, Simplified Chinese, French (#498) and Portuguese (Brazil) (#859)
|
||||
catalogs cover ~99.6–100% of the ~3,500 cataloged strings; Ukrainian ~10% (menus).
|
||||
- **Missing:** Hindi, Arabic (no RTL interface layout; Arabic titles reported broken, #395),
|
||||
Indonesian, German beyond menus (#490), Korean (#508; needs Hangul fonts in craft-fonts),
|
||||
Vietnamese, Portuguese beyond menus; Premiere also ships Italian and Russian. Engine errors,
|
||||
Vietnamese; Premiere also ships Italian and Russian. Engine errors,
|
||||
CLI and MCP stay English. No language has a recorded native-speaker review.
|
||||
- **Estimate:** 150–230 h. Doc: [localization-parity.md](localization-parity.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,9 @@ are reachable over the control channel. One catalog per language,
|
|||
commands, panels, workspaces, presets) are translated where they are drawn. Searches match the
|
||||
displayed labels as well as English sources. Tests fail when a `tl!` literal, a menu label or a
|
||||
registry name has no Spanish entry; `zh-cn.tsv` must also cover every `tl!` literal and menu
|
||||
label; `ja.tsv` and `fr.tsv` have the same entries but are not yet required by tests (French must
|
||||
cover the core menus and keep no entry without a Spanish one). Engine error messages, the
|
||||
label; `ja.tsv`, `fr.tsv` and `pt-br.tsv` have the same entries but are not yet required by tests
|
||||
(French must cover the core menus, Brazilian Portuguese every menu label, and neither may keep an
|
||||
entry without a Spanish one). Engine error messages, the
|
||||
control channel, the CLI and MCP stay English.
|
||||
|
||||
Fonts come from [craft-fonts](https://github.com/storytold/craft-fonts) (`CRAFT_FONTS_DIR`, all
|
||||
|
|
@ -43,7 +44,7 @@ whole interface, one PR) at ~4–8 h per language for the catalog plus tests.
|
|||
| Hindi | hi | 0 (0%) | — | **no Devanagari shaping in the interface** | — | none | 30–50 h (shaping in UI + catalog + fonts) |
|
||||
| Arabic | ar | 0 (0%) | — | **no RTL layout, no shaping in the interface**; titles broken (#395) | — | none | 40–70 h |
|
||||
| French | fr | 3,526 (same entries as Spanish, plus the macOS Settings item) | as Spanish | Latin (bundled fonts) | not recorded; welcome | partial (#498) | review |
|
||||
| Portuguese (Brazil) | pt-br | 348 (9.9%) | menus only | Latin | not recorded | menus only | 5–9 h + review |
|
||||
| Portuguese (Brazil) | pt-br | 3,541 (same entries as Spanish, plus the macOS Settings item) | as Spanish | Latin (bundled fonts) | not recorded; welcome | partial (#859) | review |
|
||||
| Indonesian | id | 0 (0%) | — | Latin | — | none | 6–10 h + review |
|
||||
| Japanese | ja | 3,487 (99.7%) | as zh-cn; system-font kanji may pick the Chinese fallback (#547) | CJK fonts, vertical title text | not recorded | partial | 4–8 h + review |
|
||||
| German | de | 0 (0%) | — | Latin | — | none (requested #490) | 6–10 h + review |
|
||||
|
|
@ -53,9 +54,9 @@ whole interface, one PR) at ~4–8 h per language for the catalog plus tests.
|
|||
|
||||
Premiere also ships Italian and Russian, which we don't (6–10 h each).
|
||||
|
||||
**Localization dimension:** 5 of the 12 key languages near-complete (English, Spanish, Japanese,
|
||||
Simplified Chinese, French), 1 menus-only, 6 none; against Premiere's 10 languages we cover 5 well
|
||||
and 1 partly. **~35%, 150–230 h** for all twelve to `full` plus Italian and Russian, of which the script
|
||||
**Localization dimension:** 6 of the 12 key languages near-complete (English, Spanish, Japanese,
|
||||
Simplified Chinese, French, Portuguese (Brazil)), 6 none; against Premiere's 10 languages we cover 6
|
||||
well. **~35%, 150–230 h** for all twelve to `full` plus Italian and Russian, of which the script
|
||||
work (RTL and complex shaping in the egui interface) is the largest and least parallel part.
|
||||
Every language needs native-speaker review (human).
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue