Georgian: a symbol Noto Sans Georgian cannot paint

`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.
This commit is contained in:
Hamza
2026-10-18 18:12:18 +05:00
parent 4f0b110bcb
commit 0c3c500ea4
2 changed files with 11 additions and 3 deletions
+2 -2
View File
@@ -2575,7 +2575,7 @@
"on the CPU, are not waited for).": "CPU-ზე, არ ელოდება).", "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 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 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.": "როცა დააპატარავებთ ან სხვაზე გადახვალთ.", "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-ზე თითოეული ფაილის სინქრონიზაცია დიდ", "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.": "მხოლოდ კამერის მამრავლებსა და კელვინებს შორის გარდაქმნის; ფერის იერი ისეთივეა, როგორიც იყო.", "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": "რენდერები და ეკრანის ანაბეჭდები კვლავ დროებით ფაილში იწერება და ადგილზე გადაირქმევა, მაგრამ დისკზე იძულებით აღარ ჩაიწერება", "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`-ს აბრუნებს.", "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 ფოტო“. წაშლილი ფოტო, რომლის ფაილიც გადაიტანეს,", "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.": "შენახვა შესრულდება.", "save goes through.": "შენახვა შესრულდება.",
"saved in": "შენახულია:", "saved in": "შენახულია:",
"saved in an earlier version now shifts the other way; set it again (or re-run Auto / the white-balance picker).": "შენახული მორგებული ტონირება ახლა საპირისპიროდ იხრება; კვლავ დააყენეთ (ან ხელახლა გაუშვით ავტომატური / თეთრის ბალანსის პიპეტი).", "saved in an earlier version now shifts the other way; set it again (or re-run Auto / the white-balance picker).": "შენახული მორგებული ტონირება ახლა საპირისპიროდ იხრება; კვლავ დააყენეთ (ან ხელახლა გაუშვით ავტომატური / თეთრის ბალანსის პიპეტი).",
+9 -1
View File
@@ -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] #[test]
fn translated_catalogs_do_not_use_missing_menu_triangles() { fn translated_catalogs_do_not_use_missing_menu_triangles() {
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("locales"); let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("locales");
@@ -759,7 +765,9 @@ mod tests {
} }
let messages: BTreeMap<String, String> = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap(); let messages: BTreeMap<String, String> = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
for (source, text) in messages { 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());
}
} }
} }
} }