From d16c720e59129b7276aaeb060917facaf0013c13 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 3 Nov 2026 09:52:40 -0500 Subject: [PATCH] Follow-up to #2981: a missing Chinese effect row draws English instead of failing the build, the font probe sees effect and category characters, coverage totals recounted Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/i18n/effects.rs | 51 ++++++++++++------------------ crates/ui-egui/src/i18n/ui.rs | 8 +++-- docs/localization-parity.md | 12 +++---- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/crates/ui-egui/src/i18n/effects.rs b/crates/ui-egui/src/i18n/effects.rs index 5497e651e..ec952ccb0 100644 --- a/crates/ui-egui/src/i18n/effects.rs +++ b/crates/ui-egui/src/i18n/effects.rs @@ -105,6 +105,13 @@ pub(crate) fn name(id: &str, english: &str) -> String { catalog(current()).and_then(|c| c.row(id)).map_or_else(|| english.to_string(), |(_, translated)| translated.to_string()) } +/// The characters of `language`'s effect names and category labels, so the interface's font probe +/// registers a face for each of them as it does for the panel catalog's. +#[cfg(not(target_arch = "wasm32"))] +pub(super) fn characters(language: &str) -> impl Iterator { + catalog(language).into_iter().flat_map(|c| c.rows.iter().map(|(_, _, t)| *t).chain(c.categories.iter().map(|(_, t)| *t))).flat_map(str::chars) +} + /// The number of effect-name rows `language` translates, for the row-count report; 0 when it has /// no catalog or when it is English. #[cfg(test)] @@ -2248,8 +2255,8 @@ mod tests { } /// The languages whose tables are complete: one row per effect in the registry and one row - /// per category. A translator moves a language here from [`PARTIAL`] when its table is done; - /// the completeness tests then fail if a new effect or category arrives without a row. + /// per category when they were written. A new effect without a row draws its English name (as + /// in every language), so it does not block other work; the rows are guarded like [`PARTIAL`]'s. const COMPLETE: &[(&str, &[Row])] = &[("zh-hans", SIMPLIFIED_CHINESE), ("zh-hant", TRADITIONAL_CHINESE)]; /// The languages whose tables are being filled in: guarded row by row. @@ -2276,35 +2283,6 @@ mod tests { } } - /// A complete language translates every effect the registry has and labels every category, so - /// a new effect or category cannot ship in English to a Chinese interface. - #[test] - fn a_complete_language_covers_the_registry() { - let registry = effects::registry(); - let categories = effects::categories(); - for (language, rows) in COMPLETE { - for spec in registry { - assert!( - rows.iter().any(|(id, _, _)| *id == spec.id), - "{language} has no row for {} ({}): a complete table covers every effect", - spec.name, - spec.id - ); - } - let labels = match *language { - "zh-hans" => SIMPLIFIED_CHINESE_CATEGORIES, - "zh-hant" => TRADITIONAL_CHINESE_CATEGORIES, - other => panic!("{other} is complete but has no category table in this test"), - }; - for source in &categories { - assert!( - labels.iter().any(|(english, _)| english == source), - "{language} has no category label for {source}: a complete table covers every category" - ); - } - } - } - /// Every category row of every language names a category that exists, else the generated /// Effect menu and Effects & Presets would carry a label no folder has. #[test] @@ -2420,6 +2398,17 @@ mod category_name_tests { } } + /// The interface's font probe sees the effect names and category labels, not only the panel + /// catalog: `鑲` appears only in Flange & Chorus (`鑲邊與合聲`), and a sparse Han face missing + /// it would otherwise draw a box. + #[test] + fn the_font_probe_sees_effect_and_category_characters() { + let chars = crate::i18n::catalog_characters("zh-hant"); + assert!(chars.contains(&'鑲'), "an effect name's character is probed"); + let (_, label) = TRADITIONAL_CHINESE_CATEGORIES.first().copied().unwrap_or_default(); + assert!(label.chars().all(|ch| chars.contains(&ch)), "{label}: a category label's characters are probed"); + } + /// The same for Traditional Chinese: one repeated label would hide a category behind another /// in the Effect menu. #[test] diff --git a/crates/ui-egui/src/i18n/ui.rs b/crates/ui-egui/src/i18n/ui.rs index 2ed6b2c07..dab315ffc 100644 --- a/crates/ui-egui/src/i18n/ui.rs +++ b/crates/ui-egui/src/i18n/ui.rs @@ -145,11 +145,13 @@ fn string_literal(rest: &str) -> Option { None } -/// Characters required by the fixed panel catalog, for system-font coverage selection. -/// Sorted and deduplicated so fallback registration is deterministic. +/// Characters required by the fixed panel catalog and the effect names and categories, for +/// system-font coverage selection. Sorted and deduplicated so fallback registration is +/// deterministic. #[cfg(not(target_arch = "wasm32"))] pub(crate) fn catalog_characters(language: &str) -> Vec { - catalog(language).into_iter().flat_map(|c| c.values()).flat_map(|s| s.chars()).collect::>().into_iter().collect() + let panels = catalog(language).into_iter().flat_map(|c| c.values()).flat_map(|s| s.chars()); + panels.chain(super::effects::characters(language)).collect::>().into_iter().collect() } /// Translate one interface string: the current language's row for `text`, or `text` itself. diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 8de16b4ae..b0a87120d 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -69,7 +69,7 @@ and recounted again at this tree after #1774 by counting catalog rows (`const` t `crates/ui-egui/src/i18n.rs`, `i18n/ui.rs` and `i18n/effects.rs`: 630 menu rows, 998 panel/dialog sources in Simplified Chinese, 951 in Traditional Chinese and 996 in Japanese). The denominator, **≈ 4,200 user-visible strings**, is a rounded estimate: the 630 fixed menu entries, -the 998 panel/dialog sources, 307 effect names, ≈ 1,650 distinct effect parameter names and +the 998 panel/dialog sources, 308 effect names, ≈ 1,650 distinct effect parameter names and ≈ 620 other unwrapped interface strings (Home screen literals, engine-built tooltips, error and status messages). It counts distinct strings, not catalog rows; engine messages are only partly counted, so the true percentages are slightly lower. @@ -93,18 +93,18 @@ Other languages EffectCraft ships (6): | Language | Code | UI strings translated | Status | To `full` | |---|---|---|---|---| -| Traditional Chinese | `zh-hant` | 1,910 (≈ 45%): menus 630 rows, panels 951, effect names 308 + 21 categories | partial; effect names complete, parameter names and engine text still in English | 3–5 h + review | +| Traditional Chinese | `zh-hant` | 1,889 (≈ 45%): menus 630 rows, panels 951, effect names 308 (+ 21 categories, not counted, as for the other languages) | partial; effect names complete, parameter names and engine text still in English | 3–5 h + review | | Ukrainian | `uk` | 1,153 (≈ 29%): menus 629 rows, panels 524 | partial | 6–9 h + review | | Polish | `pl` | 1,325 (≈ 34%): menus 630 rows, panels 695 | partial; unwrapped UI, effects and engine text still in English; AI-drafted, native review welcome | Remaining strings + native review | | Czech | `cs` | 1,147 (≈ 29%): menus 629 rows, panels 518 | Existing catalog entries covered; unwrapped UI, effects and engine text still in English; AI-assisted, not yet native-speaker proofread | Remaining strings + independent native review | -| Georgian | `ka` | 1,514 (≈ 39%): menus 629 rows, panels 885 (the Simplified Chinese catalog's 694 plus 191 Georgian-only rows: form dialogs, context menus, column headers), effect names 308 + 21 categories; native-speaker reviewed | partial | 5–8 h | +| Georgian | `ka` | 1,822 (≈ 43%): menus 629 rows, panels 885 (the Simplified Chinese catalog's 694 plus 191 Georgian-only rows: form dialogs, context menus, column headers), effect names 308 + 21 categories; native-speaker reviewed | partial | 5–8 h | | Swedish | `sv` | menus 630 rows, panels 723, effect names 308 and categories 21; added after the measurement above | Settings, Edit menu and Match System (`sv-*` locales) offer it; parameter and engine text still in English; not yet native-speaker reviewed | Remaining strings + native review | Russian (`ru`) has a menu catalog (630 rows, ≈ 15%); After Effects also ships Italian, which EffectCraft does not (8–12 h). -**Overall: ≈ 24%**, the average coverage of After Effects' 10 non-English languages -(Simplified Chinese 46%, Traditional Chinese 38%, Japanese 46%, Brazilian Portuguese 34%, -Spanish 29%, French, German and Russian 16%, Korean and Italian 0%; 241 / 10, was ≈ 21% on +**Overall: ≈ 25%**, the average coverage of After Effects' 10 non-English languages +(Simplified Chinese 46%, Traditional Chinese 45%, Japanese 46%, Brazilian Portuguese 34%, +Spanish 29%, French, German and Russian 16%, Korean and Italian 0%; 248 / 10, was ≈ 24% before #2037's effect names and ≈ 21% on 22 October; Ukrainian (≈ 29%) and Czech (≈ 29%) are EffectCraft-only and stay out of that average). This is the localization row in [target-app-parity.md](target-app-parity.md#by-dimension).