mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 10:27:00 -06:00
i18n: a missing translation fails no check; cargo xtask i18n-report lists them (#1228)
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
//! The catalog checks: every key the code looks up is in the English catalog, no catalog has keys
|
||||
//! the code doesn't use, placeholders match English, and complete languages translate everything.
|
||||
//! the code doesn't use, and placeholders match English. A missing translation reads English and
|
||||
//! fails nothing: [`missing_translations_report`] lists them (`cargo xtask i18n-report`).
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Languages that must translate every UI string and command label.
|
||||
const COMPLETE: &[&str] = &["es"];
|
||||
|
||||
/// Keys looked up from data rather than written as `tr("…")` in the code: the main menu's
|
||||
/// submenu names (the engine's menu catalog) and labels of engine and document enums.
|
||||
fn dynamic_keys() -> BTreeSet<String> {
|
||||
@@ -274,14 +272,19 @@ fn english_text_is_its_key_except_plurals() {
|
||||
assert!(english().commands.is_empty() && english().menu.is_empty(), "English command labels come from the engine");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catalogs_have_known_keys_and_matching_placeholders() {
|
||||
let en = english();
|
||||
let ids: BTreeSet<&str> = figcraft_engine::all_commands()
|
||||
/// The ids `[commands]` entries translate: every command and every main-menu item.
|
||||
fn command_ids() -> BTreeSet<&'static str> {
|
||||
figcraft_engine::all_commands()
|
||||
.iter()
|
||||
.map(|c| c.id)
|
||||
.chain(figcraft_engine::catalog::MENU.iter().map(|m| m.id).filter(|id| !id.is_empty()))
|
||||
.collect();
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catalogs_have_known_keys_and_matching_placeholders() {
|
||||
let en = english();
|
||||
let ids = command_ids();
|
||||
for lang in LANGS {
|
||||
assert!(!lang.name.is_empty(), "{}: [meta] name is missing", lang.code);
|
||||
for (k, t) in lang.ui {
|
||||
@@ -302,13 +305,29 @@ fn catalogs_have_known_keys_and_matching_placeholders() {
|
||||
for (id, _) in lang.menu {
|
||||
assert!(menu_worded_differently().contains(id), "{}: [menu] {id:?} reads like the command; [commands] covers it", lang.code);
|
||||
}
|
||||
if COMPLETE.contains(&lang.code) {
|
||||
let missing: Vec<_> = en.ui.iter().filter(|(k, _)| find(lang.ui, k).is_none()).map(|(k, _)| k).collect();
|
||||
assert!(missing.is_empty(), "{}: untranslated: {missing:?}", lang.code);
|
||||
let missing: Vec<_> = ids.iter().filter(|id| find(lang.commands, id).is_none()).collect();
|
||||
assert!(missing.is_empty(), "{}: untranslated commands: {missing:?}", lang.code);
|
||||
let missing: Vec<_> = menu_worded_differently().into_iter().filter(|id| find(lang.menu, id).is_none()).collect();
|
||||
assert!(missing.is_empty(), "{}: menu items worded unlike their command, missing from [menu]: {missing:?}", lang.code);
|
||||
}
|
||||
}
|
||||
|
||||
/// What each language lacks: UI strings, command labels and menu wordings that read as in English
|
||||
/// because neither its catalog nor the one it overlays has them. It prints them and always passes.
|
||||
#[test]
|
||||
fn missing_translations_report() {
|
||||
let ids = command_ids();
|
||||
let worded = menu_worded_differently();
|
||||
for (i, lang) in LANGS.iter().enumerate().filter(|(_, l)| l.code != "en") {
|
||||
let own = || chain(i).filter(|l| l.code != "en");
|
||||
let ui: Vec<_> = english().ui.iter().map(|(k, _)| *k).filter(|k| own().all(|l| find(l.ui, k).is_none())).collect();
|
||||
let commands: Vec<_> = ids.iter().filter(|id| own().all(|l| find(l.commands, id).is_none())).collect();
|
||||
let menu: Vec<_> = worded.iter().filter(|id| own().all(|l| find(l.menu, id).is_none())).collect();
|
||||
println!("{}: {} UI strings, {} command labels, {} menu wordings missing", lang.code, ui.len(), commands.len(), menu.len());
|
||||
for k in ui {
|
||||
println!(" {k:?}");
|
||||
}
|
||||
for id in commands {
|
||||
println!(" [commands] {id}");
|
||||
}
|
||||
for id in menu {
|
||||
println!(" [menu] {id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,8 +363,6 @@ fn system_languages_pick_the_first_one_with_a_catalog() {
|
||||
assert_eq!(code("system", Some("cs_CZ.UTF-8")), "cs");
|
||||
assert_eq!(code("system", Some("cs-CZ,en-US")), "cs");
|
||||
assert_eq!(code("cs", None), "cs");
|
||||
assert_eq!(LANGS[resolve("cs", None)].name, "Čeština");
|
||||
assert!(LANGS[resolve("cs", None)].draft);
|
||||
assert_eq!(code("system", Some("pt-BR")), "pt-BR");
|
||||
assert_eq!(code("system", Some("tr_TR.UTF-8")), "tr");
|
||||
assert_eq!(code("system", Some("id_ID.UTF-8")), "id");
|
||||
@@ -368,8 +385,6 @@ fn swedish_is_picked_for_swedish_locales() {
|
||||
assert_eq!(code("system", Some(tag)), "sv", "{tag}");
|
||||
}
|
||||
let sv = LANGS.iter().position(|l| l.code == "sv").unwrap();
|
||||
assert_eq!(LANGS[sv].name, "Svenska");
|
||||
assert!(!LANGS[sv].draft);
|
||||
assert!(!plural_one(sv, 0) && plural_one(sv, 1) && !plural_one(sv, 2));
|
||||
}
|
||||
|
||||
@@ -387,10 +402,11 @@ fn latin_american_spanish_overlays_spanish() {
|
||||
assert_eq!(LANGS[la].parent, es);
|
||||
assert!(es < la, "the picker lists Español first");
|
||||
// Its own wording, else Spanish's, else English's.
|
||||
assert!(matches!(lookup(la, "Add fill"), Some(Text::Plain("Agregar relleno"))));
|
||||
assert!(matches!(lookup(la, "Fill"), Some(Text::Plain("Relleno"))));
|
||||
for (k, _) in LANGS[es].ui.iter().filter(|(k, _)| find(LANGS[la].ui, k).is_none()) {
|
||||
assert!(lookup(la, k).is_some_and(|t| std::ptr::eq(t, find(LANGS[es].ui, k).unwrap())), "{k:?}");
|
||||
}
|
||||
let undo = |i: usize| chain(i).find_map(|l| find(l.commands, "edit.undo")).map(forms);
|
||||
assert!(undo(es).is_some() && undo(la) == undo(es), "command labels fall back too");
|
||||
assert_eq!(undo(la), undo(es), "command labels fall back too");
|
||||
// An overlay lists only what differs from the catalog under it.
|
||||
for lang in LANGS.iter().filter(|l| l.parent != 0) {
|
||||
let under = &LANGS[lang.parent];
|
||||
@@ -431,19 +447,11 @@ fn japanese_korean_and_chinese_have_no_plural_forms() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn georgian_is_a_draft_with_count_neutral_plurals() {
|
||||
fn georgian_is_picked_for_georgian_locales() {
|
||||
let code = |pref: &str, system: Option<&str>| LANGS[resolve(pref, system)].code;
|
||||
for tag in ["ka", "ka-GE", "ka_GE.UTF-8"] {
|
||||
assert_eq!(code("system", Some(tag)), "ka", "{tag}");
|
||||
}
|
||||
let ka = LANGS.iter().position(|l| l.code == "ka").unwrap();
|
||||
assert!(LANGS[ka].draft && LANGS[ka].name == "ქართული");
|
||||
// A noun stays singular after a numeral: both forms read the same where English counts in both.
|
||||
for (k, t) in LANGS[ka].ui {
|
||||
if let (Text::Plural { one: Some(one), other, .. }, Some(Text::Plural { one: Some(en_one), .. })) = (t, find(english().ui, k)) {
|
||||
assert!(!en_one.contains("{n}") || one == other, "ka: {k:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Release builds embed craft-fonts' Noto Sans Georgian (`theme::install_fonts`' built-in faces),
|
||||
@@ -477,7 +485,6 @@ fn chinese_reads_simplified_and_traditional_has_no_catalog() {
|
||||
assert_eq!(code(tag, None), "en", "{tag}");
|
||||
}
|
||||
assert_eq!(code("system", Some("zh-TW,ja-JP")), "ja");
|
||||
assert_eq!(LANGS[resolve("zh-Hans", None)].name, "简体中文");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -491,28 +498,18 @@ fn the_key_scanner_reads_rust_literals() {
|
||||
|
||||
#[test]
|
||||
fn czech_integer_plurals() {
|
||||
let cs = LANGS.iter().position(|l| l.code == "cs").unwrap();
|
||||
assert_eq!(plural("cs", 1), Plural::One);
|
||||
assert_eq!(fill(counted(cs, "{n} layers", 1), &[("n", &1)]), "1 vrstva");
|
||||
for n in [2, 3, 4] {
|
||||
assert_eq!(plural("cs", n), Plural::Few, "{n}");
|
||||
assert_eq!(fill(counted(cs, "{n} layers", n), &[("n", &n)]), format!("{n} vrstvy"));
|
||||
}
|
||||
for n in [0, 5, 11, 12, 21, 22, 100, 1_000_000] {
|
||||
assert_eq!(plural("cs", n), Plural::Other, "{n}");
|
||||
assert_eq!(fill(counted(cs, "{n} layers", n), &[("n", &n)]), format!("{n} vrstev"));
|
||||
}
|
||||
for (key, text) in LANGS[cs].ui {
|
||||
if let Text::Plural { one, few, .. } = text {
|
||||
assert!(one.is_some() && few.is_some(), "{key}: missing Czech plural forms");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn russian_language_tags_and_integer_plurals() {
|
||||
let ru = LANGS.iter().position(|l| l.code == "ru").unwrap();
|
||||
assert_eq!(LANGS[ru].name, "Русский");
|
||||
for tag in ["ru", "ru-RU", "ru_RU.UTF-8", "RU", "ru-BY", "ru-Cyrl-RU"] {
|
||||
assert_eq!(resolve(tag, None), ru, "{tag}");
|
||||
assert_eq!(resolve("system", Some(tag)), ru, "{tag}");
|
||||
@@ -520,49 +517,34 @@ fn russian_language_tags_and_integer_plurals() {
|
||||
assert_eq!(resolve("system", Some("hi,ru-RU,en-US")), ru);
|
||||
for n in [1, 21, 31, 101, 1_001] {
|
||||
assert_eq!(plural("ru", n), Plural::One, "{n}");
|
||||
assert_eq!(fill(counted(ru, "{n} layers", n), &[("n", &n)]), format!("{n} слой"));
|
||||
}
|
||||
for n in [2, 3, 4, 22, 23, 24, 102, 104, 1_002] {
|
||||
assert_eq!(plural("ru", n), Plural::Few, "{n}");
|
||||
assert_eq!(fill(counted(ru, "{n} layers", n), &[("n", &n)]), format!("{n} слоя"));
|
||||
}
|
||||
for n in [0, 5, 10, 11, 12, 13, 14, 19, 20, 25, 100, 111, 112, 114, 1_000_000] {
|
||||
assert_eq!(plural("ru", n), Plural::Many, "{n}");
|
||||
assert_eq!(fill(counted(ru, "{n} layers", n), &[("n", &n)]), format!("{n} слоёв"));
|
||||
}
|
||||
for (key, text) in LANGS[ru].ui {
|
||||
if let Text::Plural { one, few, many, .. } = text {
|
||||
assert!(one.is_some() && few.is_some() && many.is_some(), "{key}: missing Russian plural forms");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn polish_integer_plurals() {
|
||||
let pl = LANGS.iter().position(|l| l.code == "pl").unwrap();
|
||||
assert_eq!(LANGS[pl].name, "Polski");
|
||||
assert_eq!(plural("pl", 1), Plural::One);
|
||||
assert_eq!(fill(counted(pl, "{n} layers", 1), &[("n", &1)]), "1 warstwa");
|
||||
for n in [2, 3, 4, 22, 23, 24, 102, 104, 1_002] {
|
||||
assert_eq!(plural("pl", n), Plural::Few, "{n}");
|
||||
assert_eq!(fill(counted(pl, "{n} layers", n), &[("n", &n)]), format!("{n} warstwy"));
|
||||
}
|
||||
for n in [0, 5, 10, 11, 12, 13, 14, 19, 20, 21, 25, 31, 100, 101, 111, 112, 114, 1_000_000] {
|
||||
assert_eq!(plural("pl", n), Plural::Many, "{n}");
|
||||
assert_eq!(fill(counted(pl, "{n} layers", n), &[("n", &n)]), format!("{n} warstw"));
|
||||
}
|
||||
for (key, text) in LANGS[pl].ui {
|
||||
if let Text::Plural { one, few, many, .. } = text {
|
||||
assert!(one.is_some() && few.is_some() && many.is_some(), "{key}: missing Polish plural forms");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plural_fallback_uses_the_language_of_its_catalog() {
|
||||
// A plural es-419 doesn't overlay reads Spanish's entry under Spanish's rule.
|
||||
let es = LANGS.iter().position(|l| l.code == "es").unwrap();
|
||||
let la = LANGS.iter().position(|l| l.code == "es-419").unwrap();
|
||||
assert_eq!(counted(la, "{n} layers", 1), "{n} capa");
|
||||
assert_eq!(counted(la, "{n} layers", 21), "{n} capas");
|
||||
for n in [1, 21] {
|
||||
assert_eq!(counted(la, "{n} layers", n), counted(es, "{n} layers", n), "{n}");
|
||||
}
|
||||
// Unsupported optional categories use other; existing one/other languages are unchanged.
|
||||
for code in ["en", "es", "de"] {
|
||||
for n in [0, 2, 11, 21, 1_000_000] {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** minor (Russian and Turkish catalogs reviewed by native speakers, Russian one/few/many plurals; Czech and Indonesian drafts; the Japanese draft completed; counts re-measured; Windows UI language from the first preferred language; draft catalogs catch up: every draft translates every string again; Simplified Chinese draft catalog; macOS finds its downloadable CJK system fonts (#406); string counts re-measured: 2,272 entries, the drafts at 95–99%; Swedish command labels and menu wordings; Russian, Swedish, Turkish, Czech and Indonesian listed; a duplicated paragraph removed; draft catalogs catch up: every draft translates every string again; Simplified Chinese draft catalog) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-23 · **Change:** minor (a missing translation fails no check: `cargo xtask i18n-report` lists them; Russian and Turkish catalogs reviewed by native speakers, Russian one/few/many plurals; Czech and Indonesian drafts; the Japanese draft completed; counts re-measured; Windows UI language from the first preferred language; draft catalogs catch up: every draft translates every string again; Simplified Chinese draft catalog; macOS finds its downloadable CJK system fonts (#406); string counts re-measured: 2,272 entries, the drafts at 95–99%; Swedish command labels and menu wordings; Russian, Swedish, Turkish, Czech and Indonesian listed; a duplicated paragraph removed; draft catalogs catch up: every draft translates every string again; Simplified Chinese draft catalog) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
|
||||
FigCraft's interface has a localization layer with an English catalog, a Russian catalog reviewed
|
||||
by a native speaker, and Spanish (with a Latin American overlay), French, German, Brazilian
|
||||
@@ -46,11 +46,11 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
|
||||
(`es-419.toml` over `es.toml`); its UI strings, command labels and menu wordings read the
|
||||
overlay, then the base language, then English.
|
||||
- **Check:** `cargo test -p figcraft-ui-egui i18n` fails on keys the code uses that `en.toml`
|
||||
lacks, keys no code uses, unknown command ids, placeholders that differ from English, overlay
|
||||
entries that read as in their base language and, for complete languages (Spanish), anything
|
||||
untranslated. The other catalogs are not held to it: a string added later shows English there
|
||||
until someone translates it. Every draft translated every string on 2026-10-12; by 2026-10-17
|
||||
the strings added since left them at 95–99% (table below).
|
||||
lacks, keys no code uses, unknown command ids, placeholders that differ from English and overlay
|
||||
entries that read as in their base language. No language is held to full coverage: a string
|
||||
added later shows English until someone translates it, and `cargo xtask i18n-report` (part of
|
||||
`cargo xtask ci`) lists what each language lacks. Every draft translated every string on
|
||||
2026-10-12; by 2026-10-17 the strings added since left them at 95–99% (table below).
|
||||
- **Not yet localized:** messages the engine writes (errors, a few toasts, Dev Mode property values
|
||||
such as "inside"), number formatting (decimal points stay `.`), and decimal plural rules
|
||||
(`trn` takes integers: Czech uses one/few/other and Russian one/few/many; French and Brazilian
|
||||
@@ -122,6 +122,7 @@ Hindi, Vietnamese and Arabic. Traditional Chinese is not planned (no catalog; it
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-23 | minor | A missing translation fails no check: Spanish is no longer held to full coverage, and the tests of one language's wording, names and plural forms are gone (the plural rules and language-tag tests stay). `cargo xtask i18n-report`, run by `cargo xtask ci`, lists each language's missing UI strings, command labels and menu wordings. Grade unchanged |
|
||||
| 2026-10-15 | minor | Community catalogs (#257, #263, #469, #477, #288): Russian (`ru`, Русский, reviewed and approved by VanTan) and Turkish (`tr`, Türkçe, reviewed by a native speaker), Czech (`cs`, Čeština) and Indonesian (`id`, Bahasa Indonesia) drafts, and the Japanese draft caught up; Preferences ▸ Language lists them and System maps their regional tags. Catalogs may give few and many plural forms; Russian picks one, few or many by its integer rule (11–14 take many), the catalog supplying the text choosing the rule. Only Spanish is held to completeness. Counts re-measured: 1,587 UI keys (38 plural), 559 command labels and 31 menu wordings, 2,177 entries; Spanish 100%, Japanese 2,176, Czech and Indonesian 2,166, Russian and Turkish 2,165, the five older drafts 2,088–2,089 (95%). Localization 39% and 29–51 h unchanged at this rounding |
|
||||
| 2026-10-17 | minor | Chinese showed as boxes on macOS (#406): the font scan also walks the system's downloaded font assets (`/System/Library/AssetsV2` and the older `/System/Library/Assets`, only their `com_apple_MobileAsset_Font*` folders), where current versions keep PingFang SC, and Hiragino Sans GB and Heiti SC join the Chinese UI faces and the text fallbacks. Tested with a folder laid out the same way; not yet run on a Mac |
|
||||
| 2026-10-17 | minor | String counts re-measured from the catalogs: 1,680 UI keys, 561 command labels and 31 menu wordings (2,272 entries); Spanish complete, the other drafts at 95.3–99.2% (strings added since 2026-10-12 show English), es-419 101 entries. Indonesian has a draft (98.8%; it was listed as none); Russian (98.8%, reviewed), Swedish (99.6%, command labels and menu wordings from #599), Turkish (98.7%) and Czech (98.8%) listed with counts; the language picker's list updated; a paragraph repeated by a merge removed. Grade and hours unchanged (Indonesian's draft is offset by the drafts' new gaps) |
|
||||
|
||||
+8
-12
@@ -91,13 +91,9 @@ picker leaves Georgian out. A new CJK or other non-Latin language also needs its
|
||||
three forms. Czech's CLDR `many` applies only to decimals; `trn` takes integer counts.
|
||||
Polish uses `one` for 1 only, `few` like Russian (2–4, 22–24… except 12–14) and `many` for
|
||||
the other integers, including 0 and 21 (e.g. `{n} warstwa`, `{n} warstwy`, `{n} warstw`).
|
||||
4. To require full coverage, add the code to `COMPLETE` in
|
||||
[`crates/ui-egui/src/i18n_tests.rs`](../crates/ui-egui/src/i18n_tests.rs). Every later PR that
|
||||
adds a string then has to translate it into each complete language, so only Spanish is there:
|
||||
French, German, Brazilian Portuguese, Japanese, Korean, Simplified Chinese, Czech, Indonesian and
|
||||
Georgian are registered as drafts, Russian and Turkish are reviewed but not required, and a string added
|
||||
later shows English in them until it is translated. To list what a draft lacks,
|
||||
add its code to `COMPLETE` locally and run the check.
|
||||
4. A string added later shows English in every language until someone translates it; a missing
|
||||
translation never fails a check or a build. `cargo xtask i18n-report` lists what each language
|
||||
lacks (UI strings, command labels, menu wordings), and `cargo xtask ci` prints the same list.
|
||||
5. Run the check and look at the panels in the app (longer words must fit the right panel):
|
||||
|
||||
cargo test -p figcraft-ui-egui i18n
|
||||
@@ -106,9 +102,9 @@ picker leaves Georgian out. A new CJK or other non-Latin language also needs its
|
||||
with "…" and show it in full on hover; elsewhere, look for clipped or wrapped text.
|
||||
|
||||
The check fails on keys the code uses that `en.toml` lacks (it prints the lines to paste), keys no
|
||||
code uses, unknown command ids, placeholders that don't match English, and, for complete languages,
|
||||
anything untranslated. Machine-drafted catalogs stay marked as drafts until a native speaker has
|
||||
reviewed them; a reviewed catalog stays a draft until a maintainer approves it (its `status` says which).
|
||||
code uses, unknown command ids and placeholders that don't match English. Machine-drafted catalogs
|
||||
stay marked as drafts until a native speaker has reviewed them; a reviewed catalog stays a draft
|
||||
until a maintainer approves it (its `status` says which).
|
||||
|
||||
## Adding strings in code
|
||||
|
||||
@@ -116,11 +112,11 @@ Wrap every string a user sees or a screen reader hears: `tr` for plain text, `tr
|
||||
placeholders, `trn` for counts, `tr_noop` for literals in `const` tables that are looked up later
|
||||
with `tr(label)`. The key must be a string literal as the first argument (the check reads the
|
||||
source). Don't wrap ids, command ids, JSON, log messages, file extensions or document content. Then
|
||||
add the key to `en.toml` and to each complete language (Spanish and Russian); the drafts may follow later.
|
||||
add the key to `en.toml`; translations may follow later.
|
||||
|
||||
Labels from the engine and document crates (tool names, blend modes, easing curves…) are looked up
|
||||
at display with `tr(x.label())`; they are listed in `dynamic_labels` in `i18n_tests.rs`.
|
||||
|
||||
The importers' fixed notes (the string literals passed to `warn` in `crates/format/src/fig` and
|
||||
`crates/format/src/rest`) are keys too: the import report shows them in the UI language, so a new
|
||||
note goes into `en.toml` and each complete language. Notes built with `format!` show in English.
|
||||
note goes into `en.toml`. Notes built with `format!` show in English.
|
||||
|
||||
+12
-2
@@ -1,7 +1,9 @@
|
||||
//! Project tasks: `cargo xtask <task>`.
|
||||
//!
|
||||
//! - `ci` fmt check, clippy -D warnings, tests, layers, assets, wasm check
|
||||
//! - `ci` fmt check, clippy -D warnings, tests, the translation report, layers, assets, wasm check
|
||||
//! - `test` the workspace tests (nextest when installed) and doctests
|
||||
//! - `i18n-report` the UI strings, command labels and menu wordings each language lacks (never fails
|
||||
//! on them: a missing translation reads English)
|
||||
//! - `layers` crate dependency layering check
|
||||
//! - `assets` every file under assets/ and docs/ has a row in ATTRIBUTION.md
|
||||
//! - `parity` menu-catalog coverage → docs/parity-checklist.md
|
||||
@@ -38,6 +40,7 @@ fn main() -> ExitCode {
|
||||
let ok = match task {
|
||||
"ci" => ci(),
|
||||
"test" => test(),
|
||||
"i18n-report" => i18n_report(),
|
||||
"layers" => layers::check(&root()),
|
||||
"assets" => assets(),
|
||||
"parity" => parity(),
|
||||
@@ -47,7 +50,7 @@ fn main() -> ExitCode {
|
||||
"plugins" => plugins(),
|
||||
"bench" => bench(args.get(1).map_or("1000,10000,50000", String::as_str)),
|
||||
_ => {
|
||||
eprintln!("tasks: ci, test, layers, assets, parity, version [set X.Y.Z], icon, wasm, plugins, bench [SIZES]");
|
||||
eprintln!("tasks: ci, test, i18n-report, layers, assets, parity, version [set X.Y.Z], icon, wasm, plugins, bench [SIZES]");
|
||||
task == "help"
|
||||
}
|
||||
};
|
||||
@@ -58,6 +61,7 @@ fn ci() -> bool {
|
||||
run(cargo().args(["fmt", "--all", "--check"]))
|
||||
&& run(cargo().args(["clippy", "--workspace", "--all-targets", "--keep-going", "--", "-D", "warnings"]))
|
||||
&& test()
|
||||
&& i18n_report()
|
||||
&& layers::check(&root())
|
||||
&& assets()
|
||||
&& wasm()
|
||||
@@ -77,6 +81,12 @@ fn test() -> bool {
|
||||
units && run(cargo().args(["test", "--workspace", "--doc"]))
|
||||
}
|
||||
|
||||
/// Prints what each language's catalog lacks, from the test that lists it. `--workspace --lib`
|
||||
/// reuses the binaries `test` built.
|
||||
fn i18n_report() -> bool {
|
||||
run(cargo().args(["test", "-q", "--workspace", "--lib", "--", "--exact", "i18n::tests::missing_translations_report", "--nocapture"]))
|
||||
}
|
||||
|
||||
fn wasm() -> bool {
|
||||
run(cargo().args([
|
||||
"check",
|
||||
|
||||
Reference in New Issue
Block a user