#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:
vangel76 2026-10-11 02:17:58 +02:00
parent 2ff3f5e266
commit c6d47e20f0
8 changed files with 3304 additions and 42 deletions

View File

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

View File

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

View File

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

View File

@ -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).
#[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}");
/// 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()
}
assert_eq!(Language::PtBr.tr("File"), "Arquivo");
/// 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_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:?}");
}
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

View File

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

View File

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

View File

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