From 8987d42c19eb92c92adde93f599181005bc16ecf Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 20:33:16 -0800 Subject: [PATCH] i18n: messages with escaped JSON braces match their catalog rows Catalog rows now read `{{` and `}}` as a literal `{` and `}`, in the English source and in the translation, as in a Rust format string. A `{` with no `}` after it is literal text instead of dropping the row. - The plugin.run errors for an importer, an exporter and a live effect (`document.open {"importer": ...}` and the like) now show translated with the plug-in's id and JSON kept, in the 22 catalogs that list them. Their rows were keyed as `{{"importer"}"}}`, which never matched. - The test that keys a format string keeps `{{`/`}}` and numbers only real placeholders, so its expected keys match the runtime text. Two junk rows it produced for the RTF error (hi, sv) are removed. --- crates/ui-egui/src/i18n.rs | 14 ++++- crates/ui-egui/src/i18n/ca.tsv | 4 +- crates/ui-egui/src/i18n/catalog.rs | 41 +++++++++++--- crates/ui-egui/src/i18n/cs.tsv | 4 +- crates/ui-egui/src/i18n/de.tsv | 4 +- crates/ui-egui/src/i18n/el.tsv | 4 +- crates/ui-egui/src/i18n/es.tsv | 4 +- crates/ui-egui/src/i18n/fi.tsv | 4 +- crates/ui-egui/src/i18n/fr.tsv | 4 +- crates/ui-egui/src/i18n/hi.tsv | 7 +-- crates/ui-egui/src/i18n/hu.tsv | 4 +- crates/ui-egui/src/i18n/it.tsv | 4 +- crates/ui-egui/src/i18n/ja.tsv | 4 +- crates/ui-egui/src/i18n/ka.tsv | 4 +- crates/ui-egui/src/i18n/ko.tsv | 4 +- crates/ui-egui/src/i18n/pl.tsv | 4 +- crates/ui-egui/src/i18n/pt-br.tsv | 4 +- crates/ui-egui/src/i18n/ru.tsv | 4 +- crates/ui-egui/src/i18n/sv.tsv | 7 +-- crates/ui-egui/src/i18n/tests.rs | 88 ++++++++++++++++++++++++++++- crates/ui-egui/src/i18n/tr.tsv | 4 +- crates/ui-egui/src/i18n/uk.tsv | 4 +- crates/ui-egui/src/i18n/vi.tsv | 4 +- crates/ui-egui/src/i18n/zh-hans.tsv | 4 +- crates/ui-egui/src/i18n/zh-hant.tsv | 4 +- 25 files changed, 196 insertions(+), 41 deletions(-) diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 7ee09ee14e..eeb87a3318 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -677,13 +677,23 @@ fn message_at(lang: Lang, s: &str, depth: usize) -> String { } /// Fill `{name}` placeholders in one pass: a value that holds `{name}` text stays as it is. -/// Unknown placeholders are left as written. +/// Unknown placeholders are left as written. `{{` and `}}` are a literal `{` and `}`. fn fill_once(template: &str, args: &[(&str, &str)]) -> String { let mut rest = template; let mut out = String::with_capacity(rest.len()); - while let Some(open) = rest.find('{') { + while let Some(open) = rest.find(['{', '}']) { out.push_str(&rest[..open]); let after = &rest[open + 1..]; + if rest[open..].starts_with('}') { + out.push('}'); + rest = after.strip_prefix('}').unwrap_or(after); + continue; + } + if let Some(escaped) = after.strip_prefix('{') { + out.push('{'); + rest = escaped; + continue; + } let filled = after.find('}').and_then(|close| args.iter().find(|(k, _)| *k == &after[..close]).map(|(_, v)| (*v, close))); match filled { Some((value, close)) => { diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 0399e6c04a..4e17e5bc34 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -7135,7 +7135,9 @@ breakText Glyphs Glifs @msg “{shown}” opens {_1} files, not `{_2}` «{shown}» obre fitxers {_1}, no `{_2}` @msg `{_1}` is a plug-in of kind `{_2}`, not an importer `{_1}` és un complement del tipus `{_2}`, no un importador @msg `{_1}` is a plug-in of kind `{_2}`, not an exporter `{_1}` és un complement del tipus `{_2}`, no un exportador -@msg `{_1}` is an importer: open a file with document.open {{"importer"}"}} `{_1}` és un importador: obriu un fitxer amb document.open {{"importer"}"}} +@msg `{_1}` is an importer: open a file with document.open {{"importer": "{_2}"}} `{_1}` és un importador: obriu un fitxer amb document.open {{"importer": "{_2}"}} +@msg `{_1}` is an exporter: export with document.export {{"format": "{_2}"}} `{_1}` és un exportador: exporteu amb document.export {{"format": "{_2}"}} +@msg `{_1}` is a live effect: apply it with effect.apply {{"effect": "{_2}"}} `{_1}` és un efecte dinàmic: apliqueu-lo amb effect.apply {{"effect": "{_2}"}} @msg the importer returned no objects l’importador no ha retornat cap objecte @msg an importer returns `objects` and `warnings`, not `{key}` un importador retorna `objects` i `warnings`, no `{key}` @msg the file is larger than the {MAX_INPUT_BYTES} bytes an importer reads el fitxer supera els {MAX_INPUT_BYTES} bytes que llegeix un importador diff --git a/crates/ui-egui/src/i18n/catalog.rs b/crates/ui-egui/src/i18n/catalog.rs index 138299643d..ac659ac4b2 100644 --- a/crates/ui-egui/src/i18n/catalog.rs +++ b/crates/ui-egui/src/i18n/catalog.rs @@ -47,23 +47,42 @@ const MAX_MESSAGE_LEN: usize = 2000; impl Template { /// `None` for a template that can't be matched reliably: no literal text, or two placeholders - /// in a row (where one ends and the next begins is unknowable). + /// in a row (where one ends and the next begins is unknowable). `{{` and `}}` are a literal `{` + /// and `}`, as in a Rust format string, and so is a `{` with no `}` after it. fn parse(source: &str, forms: Vec, counted: bool) -> Option