From 0c3c500ea408de17c7a792f3ae340afa96cc62ed Mon Sep 17 00:00:00 2001 From: Hamza Date: Sun, 18 Oct 2026 18:12:18 +0500 Subject: [PATCH] Georgian: a symbol Noto Sans Georgian cannot paint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cargo xtask ci` fails with the craft-fonts input supplied, which CLAUDE.md says releases always build with: ka in Proportional (faces: ["Noto Sans Georgian"]) lack glyphs: ✕ Two strings in ka.json use ✕ (U+2715). Noto Sans Georgian has no glyph for it and egui's bundled fonts do not either, so a Georgian reader gets a blank where the text describes the activity stack's stop control. Without the fonts input those twenty tests skip, so a plain `cargo xtask ci` is green and this stayed invisible. The two Georgian values use × (U+00D7) instead: it reads the same at text size and every face has it. The English keys are untouched — they are the source text from docs/whats-new.md, and the test checks values, which is the right scope. An English reader's font covers ✕; the problem is a translation carrying it into a script whose face does not. `translated_catalogs_do_not_use_missing_menu_triangles` already forbids ▸ and ▾ in locale JSON for the same reason, so ✕ joins them in one list. That matters because ✕ is in the English source of those two strings: every language that translates them next would hit the same failure, and a cheap test now catches it instead of the font-dependent one. --- crates/ui-egui/locales/ka.json | 4 ++-- crates/ui-egui/src/i18n.rs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/locales/ka.json b/crates/ui-egui/locales/ka.json index bafd7827c..f6530929d 100644 --- a/crates/ui-egui/locales/ka.json +++ b/crates/ui-egui/locales/ka.json @@ -2575,7 +2575,7 @@ "on the CPU, are not waited for).": "CPU-ზე, არ ელოდება).", "on the DLL search path without its `dxil.dll` (issue #471): DirectX 12 shaders now always compile with the": "DLL-ების საძიებო გზაზეა მისი `dxil.dll`-ის გარეშე (ხარვეზი #471): DirectX 12 შეიდერები ახლა ყოველთვის კომპილირდება", "on the card after Review Import is reported instead of being imported with stale details. Checking an export path": "ბარათზე იმპორტის გადახედვის შემდეგ, აღირიცხება იმის ნაცვლად, რომ მოძველებული მონაცემებით იმპორტირდეს. ექსპორტის მისამართის შემოწმება", - "on; ✕ stops the tasks that can stop. Three rows show, then \"+N more\". The separate export panel and the progress": "მუშაობს; ✕ აჩერებს შეჩერებად ამოცანებს. ჩანს სამი სტრიქონი, შემდეგ „+N კიდევ“. ცალკე ექსპორტის პანელი, პროგრესის", + "on; ✕ stops the tasks that can stop. Three rows show, then \"+N more\". The separate export panel and the progress": "მუშაობს; × აჩერებს შეჩერებად ამოცანებს. ჩანს სამი სტრიქონი, შემდეგ „+N კიდევ“. ცალკე ექსპორტის პანელი, პროგრესის", "once you zoom out or move on.": "როცა დააპატარავებთ ან სხვაზე გადახვალთ.", "one by one — they can always be exported again, and on a USB drive or a NAS that per-file sync dominated a large": "სათითაოდ — მათი ხელახლა ექსპორტი ყოველთვის შეიძლება, ხოლო USB დისკზე ან NAS-ზე თითოეული ფაილის სინქრონიზაცია დიდ", "only convert between the camera's multipliers and Kelvin; the colour look is what it was.": "მხოლოდ კამერის მამრავლებსა და კელვინებს შორის გარდაქმნის; ფერის იერი ისეთივეა, როგორიც იყო.", @@ -2615,7 +2615,7 @@ "renders and screenshots are still written to a temp file and renamed into place, but no longer forced to disk": "რენდერები და ეკრანის ანაბეჭდები კვლავ დროებით ფაილში იწერება და ადგილზე გადაირქმევა, მაგრამ დისკზე იძულებით აღარ ჩაიწერება", "reports each row's `label`.": "თითოეული სტრიქონის `label`-ს აბრუნებს.", "restore the photo too and say \"Restored 1 photo from Recently Deleted\". A deleted photo whose file was moved is": "ფოტოს ასევე აღადგენს და ამბობს „ახლახან წაშლილიდან აღდგა 1 ფოტო“. წაშლილი ფოტო, რომლის ფაილიც გადაიტანეს,", - "runs in the background with a row in the activity stack (✕ stops it); everything it does is one undo step, and no file on disk": "ფონურად მიმდინარეობს აქტივობების დასტაში საკუთარი სტრიქონით (✕ აჩერებს); ყველაფერი, რასაც აკეთებს, ერთი დაბრუნების ნაბიჯია, და დისკზე არცერთ ფაილს", + "runs in the background with a row in the activity stack (✕ stops it); everything it does is one undo step, and no file on disk": "ფონურად მიმდინარეობს აქტივობების დასტაში საკუთარი სტრიქონით (× აჩერებს); ყველაფერი, რასაც აკეთებს, ერთი დაბრუნების ნაბიჯია, და დისკზე არცერთ ფაილს", "save goes through.": "შენახვა შესრულდება.", "saved in": "შენახულია:", "saved in an earlier version now shifts the other way; set it again (or re-run Auto / the white-balance picker).": "შენახული მორგებული ტონირება ახლა საპირისპიროდ იხრება; კვლავ დააყენეთ (ან ხელახლა გაუშვით ავტომატური / თეთრის ბალანსის პიპეტი).", diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 9ba6c5462..ed7ad602e 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -749,6 +749,12 @@ mod tests { }); } + /// Symbols a translation must not carry, because the craft-fonts faces of at least one shipped + /// script have no glyph for them and egui's bundled fonts do not either: the menu triangles, + /// and the heavy multiplication X (U+2715) that Noto Sans Georgian lacks — `×` (U+00D7) reads + /// the same and every face has it. + const UNPAINTABLE: [char; 3] = ['▸', '▾', '✕']; + #[test] fn translated_catalogs_do_not_use_missing_menu_triangles() { let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("locales"); @@ -759,7 +765,9 @@ mod tests { } let messages: BTreeMap = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap(); for (source, text) in messages { - assert!(!text.contains(['▸', '▾']), "{}: {source:?} uses an unpaintable menu triangle", path.display()); + for bad in UNPAINTABLE { + assert!(!text.contains(bad), "{}: {source:?} uses the unpaintable {bad:?}", path.display()); + } } } }