mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 02:58:32 -06:00
i18n: add Hindi and Korean interfaces
Two complete catalogs, hi and ko, 2,578 entries each (every (context, English) key the bundled catalogs hold), registered in LANGUAGES (24 -> 26) and in the date languages (hi, ko). Both use a single @plural form. Korean has no Hangul face in craft-fonts, so desktop builds draw it with one installed face (Malgun Gothic, Apple SD Gothic Neo, Nanum; never Noto CJK / Source Han), added after the embedded faces like the Devanagari fallback. PDFCRAFT_SYSTEM_FONTS=0 turns it off. Tests: catalog registration and persistence, command / All tools / tl!() literal coverage for both, date names, Hangul fallback and candidate list. cargo xtask assets, fmt and clippy are clean. Also refreshes the ru.tsv SHA-256 in ATTRIBUTION.toml and the docs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
1ff8fe4800
commit
644348dd0f
+5
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with SHA-256 hashes, is [ATTRIBUTION.toml](ATTRIBUTION.toml). Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).
|
||||
|
||||
## In this repository (270)
|
||||
## In this repository (273)
|
||||
|
||||
| Asset | Title | Author | Licence | Source | Used for |
|
||||
|---|---|---|---|---|---|
|
||||
@@ -28,6 +28,10 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
|
||||
| `crates/ui-egui/src/i18n/ar.tsv` | Arabic interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources | Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/tr.tsv` | Turkish interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources | Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/lt.tsv` | Lithuanian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels and user-facing messages; reviewed by a native Lithuanian-speaking contributor | Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/id.tsv` | Indonesian interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources | Indonesian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/vi.tsv` | Vietnamese interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources | Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/hi.tsv` | Hindi interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources | Hindi UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/ko.tsv` | Korean interface translation catalog | PdfCraft contributors | MIT OR Apache-2.0 | Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources | Korean UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `assets/icons/accessibility.svg` | Lucide icon "accessibility" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/accessibility.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/align-left.svg` | Lucide icon "align-left" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/align-left.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
| `assets/icons/align-center.svg` | Lucide icon "align-center" | Lucide Contributors (portions Cole Bemis, Feather) | ISC | https://github.com/lucide-icons/lucide (npm lucide-static 1.49.0, icons/align-center.svg) | UI icon, embedded via crates/ui-egui/src/icon_data.rs |
|
||||
@@ -222,7 +226,6 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
|
||||
| `vendor/hayro-interpret/assets/FoxitSerifItalic.pfb` | FoxitSerifItalic (standard-14 substitute font) | Foxit Software Inc. / PDFium Authors | BSD-3-Clause | https://pdfium.googlesource.com/pdfium (via hayro-interpret 0.7.0 assets/) | Renders non-embedded standard-14 fonts (vendored hayro-interpret) |
|
||||
| `vendor/hayro-interpret/assets/FoxitSymbol.pfb` | FoxitSymbol (standard-14 substitute font) | Foxit Software Inc. / PDFium Authors | BSD-3-Clause | https://pdfium.googlesource.com/pdfium (via hayro-interpret 0.7.0 assets/) | Renders non-embedded standard-14 fonts (vendored hayro-interpret) |
|
||||
| `vendor/hayro-interpret/assets/CGATS001Compat-v2-micro.icc` | CGATS001Compat v2 micro ICC profile | Clinton Ingram (saucecontrol/Compact-ICC-Profiles) | CC0-1.0 | https://github.com/saucecontrol/Compact-ICC-Profiles | Default CMYK colour conversion (vendored hayro-interpret) |
|
||||
| `crates/fonts/src/std14.rs` | Standard-14 font advance widths and encodings (as Rust tables) | Adobe (Core14 AFM metrics, PDF specification encodings); tables generated by the PdfCraft contributors | MIT OR Apache-2.0 | ISO 32000-2 Annex D and the Adobe Core14 AFM metrics. Generated by joining the published WinAnsi, Symbol and ZapfDingbats encodings (code to glyph name) with the per-glyph advance widths, then spot-checked against the specification by hand in the tests of crates/fonts/src/std14.rs. | Advance widths for the fourteen standard fonts, which a PDF may name without embedding. Non-visual data, not a typeface: the item AGENTS.md 1.1 permits, held first-party so generated appearance streams and the renderer agree. |
|
||||
| `docs/brand/artcraft-logo-white.png` | ArtCraft brand: artcraft-logo-white | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
| `docs/brand/artcraft-logo-white.svg` | ArtCraft brand: artcraft-logo-white | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
| `docs/brand/artcraft-logo.png` | ArtCraft brand: artcraft-logo | ArtCraft Team | LicenseRef-Storyteller-Trademark | ArtCraft Team brand assets (provided by the project owner, 2026-10-01; identical copies in storytold/photocraft) | ArtCraft branding next to the community links (About dialog, Help menu, home screen) and in docs. ArtCraft Team trademark, not open source; may be used only unmodified within PdfCraft; forks and modified versions must remove it. |
|
||||
|
||||
+43
-33
@@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "d0a69a35403e70b94c3adfef88b41ec719f4f6ab700d351c8f82a78c53b63ef6"
|
||||
sha256 = "e04ab5246125efae2bad439e62c150fc10649c4b6796b30565615f5bd7509ec6"
|
||||
|
||||
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
|
||||
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
|
||||
@@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "caa23201b7f25dba9f8dc579771e13435e5e8c72a7efb9770e917a2a33003a39"
|
||||
sha256 = "7f600eaa47f23fac4f9f0fece1454e59756c8dc57c7c697502cb02ee21410ac0"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
|
||||
@@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "5ead9fccdca17810a3a98de3fa499d8a2469a1ff015c3d0e11880ca629295ea1"
|
||||
sha256 = "37733adc79d7d32a057811b4dc5eb31251ef8ec91f1dac430253c69faac1c33f"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/cs.tsv"
|
||||
@@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "d48f9d68711850cae48af53f7691facee4209f7846e3eb2c064d97dad2ae2426"
|
||||
sha256 = "285b689b5224a66079738ab39d5a27e5f9ba72e1ab42e148f263b7ea320c4729"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pt-br.tsv"
|
||||
@@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "6bc2edec99534d0df9b5e4a1c2e18dc32ad84c32e404c74dd437d86119b1d6ea"
|
||||
sha256 = "e8154a154129ca158396de84d2d432ce1c587477795a6a14985f7834db255744"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/es.tsv"
|
||||
@@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "cb6c04bd36ba6ce0ebc808da2e47b65797e14d59466e93802adec4d4f5056e3b"
|
||||
sha256 = "50d4e66209e8b56707bcd148d4644eb0ad9f49dfc195287cfbd851ffbe5c3830"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/fr.tsv"
|
||||
@@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "0368944155991c2ea304788418fb326f30e45649539a0cf49539194b026ca104"
|
||||
sha256 = "13e677d44d5224e60af28fcc89d8f8af552368ca3c11f0a871498c60fefa7ee2"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/de.tsv"
|
||||
@@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "f4969c353f1424c6b9b5b110820c69dc59907590d013221511eae3f86c29024e"
|
||||
sha256 = "e81112d69acc2e1bbb44c5252f8f323b1d637059b5ac5f4c9fcd3eb985a53442"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/te.tsv"
|
||||
@@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "4bb6fa4f3b6e236a4a353df79acd0c5528ea8354058eeaeb66c28bc0f7e02939"
|
||||
sha256 = "df3d3650af984251372bf22b859ba6aca47f26eeff790e834d57a5a4071a1b48"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/it.tsv"
|
||||
@@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "adcbdfc683589b067e062a6200084bf9192935b297f5b616f8ed39db9c71db9b"
|
||||
sha256 = "c9e453382d9d5c83f7b0f986befc6113a62b4736a6b94a6462805cead406753b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pl.tsv"
|
||||
@@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "aebe63cd91df2d2f9d2bdc1da43b017823dbc8a32135e999b9ac7041277be0c8"
|
||||
sha256 = "2a205868b13f719dbc91fdc50f1133f5f317aa2245143bfeaa048190bc9c7d8c"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ru.tsv"
|
||||
@@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "3c8e3a28a5b751c6d6384809d4fd81c14c2ff5f561aaee5b58d24fbfa2ca4dc1"
|
||||
sha256 = "59ff8e2d10c825711e25af41e3b12366a24568be725ba07998f2c6be30e56187"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hu.tsv"
|
||||
@@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "637fcfdd0bf1898b413af7ccbc2eb4228bf656273de9f3ef9241bd128940aeab"
|
||||
sha256 = "882b3937802bbbd5afe0087264ee4118161351e8828cb02b608488ccfe39bfe9"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/bg.tsv"
|
||||
@@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "5285f8987bd8c265f73ff443906c1832a0886037592d334974cf539b9f972a3f"
|
||||
sha256 = "8cca054e167995ca6d95bc45fc0f75385a15a78852e4b173bd4c5b7b651c5a9d"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/kk.tsv"
|
||||
@@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "23779abc08be40ddfb305fd638fe280c854ac33473b2822006ba8bad399c0cad"
|
||||
sha256 = "2f4a6429c0c290a53268733dac08da221085992036a4d304d7663d7947d806d3"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ja.tsv"
|
||||
@@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "c5129c40b202d8467d8d4cf0a189da8c2ba2af711998edb73f67e1b291289a04"
|
||||
sha256 = "2792f38b1699269b5886fa99a17c7773c3934df95dc4e2db3ba6a475dd652ba0"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/uk.tsv"
|
||||
@@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "11ed2aa56dc5f8f6656eba717500c74bb8c8f931c08f9f9b210b5b33dd9c2d5a"
|
||||
sha256 = "937ac7eac4230c9a1d26d6bd7ef4f08f8d8c9d391a091682e8758fba3ca9678e"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ar.tsv"
|
||||
@@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "a3863e2fed5ef584a87f65083a6b9fedf26d18ecebd574efce22bd7a01e3281c"
|
||||
sha256 = "7ced08c3485c5eddf92767e6deb009d72351d70cefe60be654cfdac441f40f4b"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/tr.tsv"
|
||||
@@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "cade94846289c2822b408a2ea23e6c8f8f321708f4f27fd563695a3c4fade6a1"
|
||||
sha256 = "4e70c6db374db37bf70a17e251675b94128fb11b4d6d20ef669b36fd4ae14ff1"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/lt.tsv"
|
||||
@@ -228,7 +228,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "35b6b03dae01b9a2e98f6d1f682d0699d77772e6efec7e28e1f26039c57b151f"
|
||||
sha256 = "d2e6be7cf74525310a203bed7434e7f69d2288d337ee696de2882df8da31a746"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/id.tsv"
|
||||
@@ -252,6 +252,28 @@ kind = "translation"
|
||||
usage = "Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "21215a2a3489a03050d884b51a512e5c8b58b690dd36e182a4917ddd87b7b922"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hi.tsv"
|
||||
title = "Hindi interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Hindi UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "0dff6ebbd918a71f3e1e4dab7f439088bedf91469fc6bf56c3e1a0e114e99149"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ko.tsv"
|
||||
title = "Korean interface translation catalog"
|
||||
author = "PdfCraft contributors"
|
||||
source = "Contributor-original: clean-room translations of PdfCraft's English UI labels, no proprietary localisation resources"
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Korean UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "0254b088b5ef93c88a2f9cfc0a27cdf12eaf38fcad6f0a619adb34a618175cc5"
|
||||
|
||||
[[asset]]
|
||||
path = "assets/icons/accessibility.svg"
|
||||
title = "Lucide icon \"accessibility\""
|
||||
@@ -2386,18 +2408,6 @@ kind = "data"
|
||||
usage = "Default CMYK colour conversion (vendored hayro-interpret)"
|
||||
sha256 = "73e1ba37d2bad5bab2a964f40a9eed96209666efc067c3322626214bbef234a0"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/fonts/src/std14.rs"
|
||||
title = "Standard-14 font advance widths and encodings (as Rust tables)"
|
||||
author = "Adobe (Core14 AFM metrics, PDF specification encodings); tables generated by the PdfCraft contributors"
|
||||
source = "ISO 32000-2 Annex D and the Adobe Core14 AFM metrics. Generated by joining the published WinAnsi, Symbol and ZapfDingbats encodings (code to glyph name) with the per-glyph advance widths, then spot-checked against the specification by hand in the tests of crates/fonts/src/std14.rs."
|
||||
licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "data"
|
||||
adobe_data = true
|
||||
usage = "Advance widths for the fourteen standard fonts, which a PDF may name without embedding. Non-visual data, not a typeface: the item AGENTS.md 1.1 permits, held first-party so generated appearance streams and the renderer agree."
|
||||
sha256 = "4ce47db7e65467d4ea1d1642ff76dfcaeb9669c1811a93819d7e00e63fa9fcf8"
|
||||
|
||||
[[bundled]]
|
||||
crate = "epaint_default_fonts"
|
||||
version = "0.36.2"
|
||||
@@ -3373,4 +3383,4 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "2685fbb694cbdeb7632ecb5e7396e1209047c83319e28ceea032b3ec9fb2fc0e"
|
||||
sha256 = "6c01a439220415b83acab79c014286e5871ba7946a9c3b9a4776be797d55a086"
|
||||
|
||||
+2
-2
@@ -119,14 +119,14 @@ Measured from `crates/ui-egui/src/i18n/*.tsv` (2,578 keys). Detail and script no
|
||||
| English | `en` | 100% | full |
|
||||
| Simplified Chinese | `zh-hans` | 97.4% | partial (no Hans UI face in releases, #826) |
|
||||
| Spanish | `es` | 95.5% | partial |
|
||||
| Hindi | `hi` | 0% | none |
|
||||
| Hindi | `hi` | 100% | partial |
|
||||
| Arabic | `ar` | 100% | partial (layout not mirrored) |
|
||||
| French | `fr` | 99.3% | partial |
|
||||
| Portuguese (Brazil) | `pt-br` | 99.4% | partial |
|
||||
| Indonesian | `id` | 100% | partial |
|
||||
| Japanese | `ja` | 99.1% | partial |
|
||||
| German | `de` | 99.3% | partial |
|
||||
| Korean | `ko` | 0% | none |
|
||||
| Korean | `ko` | 100% | partial (installed Hangul face; none in craft-fonts yet) |
|
||||
| Vietnamese | `vi` | 100% | partial |
|
||||
|
||||
Also shipped: Traditional Chinese, Russian, Bulgarian, Telugu, Hungarian, Ukrainian, Italian
|
||||
|
||||
@@ -56,7 +56,7 @@ const AR_DAYS: [&str; 7] = ["الأحد", "الاثنين", "الثلاثاء",
|
||||
|
||||
/// The languages month and weekday names can be written in (Preferences ▸ Date format ▸ Language):
|
||||
/// the interface languages, in the same order and with the same names.
|
||||
pub static DATE_LANGUAGES: [DateLanguage; 24] = [
|
||||
pub static DATE_LANGUAGES: [DateLanguage; 26] = [
|
||||
DateLanguage {
|
||||
code: "en",
|
||||
name: "English",
|
||||
@@ -408,6 +408,26 @@ pub static DATE_LANGUAGES: [DateLanguage; 24] = [
|
||||
days: ["Chủ Nhật", "Thứ Hai", "Thứ Ba", "Thứ Tư", "Thứ Năm", "Thứ Sáu", "Thứ Bảy"],
|
||||
days_short: ["CN", "T2", "T3", "T4", "T5", "T6", "T7"],
|
||||
},
|
||||
// Hindi (Devanagari). Month and weekday names are the same with and after a day number.
|
||||
DateLanguage {
|
||||
code: "hi",
|
||||
name: "हिन्दी",
|
||||
months: ["जनवरी", "फ़रवरी", "मार्च", "अप्रैल", "मई", "जून", "जुलाई", "अगस्त", "सितंबर", "अक्तूबर", "नवंबर", "दिसंबर"],
|
||||
months_with_day: None,
|
||||
months_short: ["जन॰", "फ़र॰", "मार्च", "अप्रैल", "मई", "जून", "जुला॰", "अग॰", "सित॰", "अक्तू॰", "नव॰", "दिस॰"],
|
||||
days: ["रविवार", "सोमवार", "मंगलवार", "बुधवार", "गुरुवार", "शुक्रवार", "शनिवार"],
|
||||
days_short: ["रवि", "सोम", "मंगल", "बुध", "गुरु", "शुक्र", "शनि"],
|
||||
},
|
||||
// Korean: months are numbered (`3월`) and weekdays end in 요일; no names change after a day.
|
||||
DateLanguage {
|
||||
code: "ko",
|
||||
name: "한국어",
|
||||
months: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
|
||||
months_with_day: None,
|
||||
months_short: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
|
||||
days: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"],
|
||||
days_short: ["일", "월", "화", "수", "목", "금", "토"],
|
||||
},
|
||||
];
|
||||
|
||||
/// The date language for `code` (any case), or `None` when there's none.
|
||||
@@ -727,6 +747,22 @@ mod tests {
|
||||
assert_eq!(s.today_text_for_pdf(Some("d mmmm yyyy"), None).as_deref(), Ok("14 November 2023"));
|
||||
}
|
||||
|
||||
/// Hindi and Korean names are the same with and after a day number. Neither is Western
|
||||
/// European, so the date-text path refuses them with what to use instead.
|
||||
#[test]
|
||||
fn hindi_and_korean_month_and_weekday_names() {
|
||||
assert_eq!(in_lang("d mmmm yyyy", "hi"), "7 मार्च 2026");
|
||||
assert_eq!(in_lang("dddd, mmmm", "hi"), "शनिवार, मार्च");
|
||||
assert_eq!(in_lang("ddd, d mmm", "hi"), "शनि, 7 मार्च");
|
||||
assert_eq!(in_lang("yyyy\\년 mmmm d\\일 dddd", "ko"), "2026년 3월 7일 토요일");
|
||||
assert_eq!(in_lang("ddd", "ko"), "토");
|
||||
let mut s = Session::new().with_clock(|| 1_700_000_000);
|
||||
for code in ["hi", "ko"] {
|
||||
s.set_date_language(Some(code)).unwrap();
|
||||
assert!(s.today_text_for_pdf(Some("d mmmm yyyy"), None).unwrap_err().contains("dd.mm.yyyy"), "{code}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_session_writes_names_in_its_date_language() {
|
||||
let mut s = Session::new().with_clock(|| 1_700_000_000);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -147,7 +147,7 @@ fn plural_sl(n: u64) -> usize {
|
||||
}
|
||||
|
||||
/// The registry. English first: it is the fallback and the source language.
|
||||
pub static LANGUAGES: [LangInfo; 24] = [
|
||||
pub static LANGUAGES: [LangInfo; 26] = [
|
||||
LangInfo { code: "en", name: "English", source: "", plural: plural_one_other, rtl: false, catalog: OnceLock::new() },
|
||||
LangInfo { code: "ja", name: "日本語", source: include_str!("ja.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() },
|
||||
// Simplified Chinese; `zh`, `zh-CN`, `zh-SG` and `zh-Hans-*` locales resolve here (see `candidates`).
|
||||
@@ -213,6 +213,13 @@ pub static LANGUAGES: [LangInfo; 24] = [
|
||||
// Vietnamese; every `vi-*` locale (`vi-VN`, `vi_VN.UTF-8`) resolves here. CLDR `vi` counts have
|
||||
// one form ("other"), so `@plural` entries list a single form.
|
||||
LangInfo { code: "vi", name: "Tiếng Việt", source: include_str!("vi.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() },
|
||||
// Hindi; every `hi-*` locale (`hi-IN`, `hi_IN.UTF-8`) resolves here. CLDR `hi` has "one"
|
||||
// (0 and 1) and "other", but the catalog's nouns are the same in both ("{n} पृष्ठ"), so
|
||||
// `@plural` entries list a single form.
|
||||
LangInfo { code: "hi", name: "हिन्दी", source: include_str!("hi.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() },
|
||||
// Korean; every `ko-*` locale (`ko-KR`, `ko_KR.UTF-8`) resolves here. CLDR `ko` counts have
|
||||
// one form ("other"), so `@plural` entries list a single form.
|
||||
LangInfo { code: "ko", name: "한국어", source: include_str!("ko.tsv"), plural: plural_none, rtl: false, catalog: OnceLock::new() },
|
||||
];
|
||||
|
||||
impl LangInfo {
|
||||
@@ -2503,6 +2510,122 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hindi_and_korean_are_registered_and_persist() {
|
||||
let hi = Lang::from_code("hi").expect("hi registered");
|
||||
let ko = Lang::from_code("ko").expect("ko registered");
|
||||
assert_eq!(hi.name(), "हिन्दी");
|
||||
assert_eq!(ko.name(), "한국어");
|
||||
assert_eq!(normalize_pref("HI"), Some("hi"));
|
||||
assert_eq!(normalize_pref("Ko"), Some("ko"));
|
||||
for tag in ["hi", "hi-IN", "hi_IN.UTF-8", "HI-in"] {
|
||||
assert_eq!(lang_from_tag(tag), Some(hi), "{tag}");
|
||||
}
|
||||
for tag in ["ko", "ko-KR", "ko_KR.UTF-8", "KO-kr"] {
|
||||
assert_eq!(lang_from_tag(tag), Some(ko), "{tag}");
|
||||
}
|
||||
assert_eq!(first_supported("hi-IN\r\nen-US"), Some(hi));
|
||||
assert_eq!(first_supported("ko-KR\r\nen-US"), Some(ko));
|
||||
assert_eq!(tr(hi, "Save"), "सहेजें");
|
||||
assert_eq!(tr(hi, "Save as…"), "इस रूप में सहेजें…");
|
||||
assert_eq!(tr(hi, "Bookmarks"), "बुकमार्क");
|
||||
assert_eq!(tr(hi, "Home"), "होम");
|
||||
assert_eq!(tr(hi, "Interface language"), "इंटरफ़ेस भाषा");
|
||||
assert_eq!(tr(ko, "Save"), "저장");
|
||||
assert_eq!(tr(ko, "Save as…"), "다른 이름으로 저장…");
|
||||
assert_eq!(tr(ko, "Bookmarks"), "책갈피");
|
||||
assert_eq!(tr(ko, "Home"), "홈");
|
||||
assert_eq!(tr(ko, "Interface language"), "인터페이스 언어");
|
||||
// One plural form per catalog: the message reads the same for every count.
|
||||
assert_eq!((0..=3).map(|n| (hi.0.plural)(n)).collect::<Vec<_>>(), [0, 0, 0, 0]);
|
||||
assert_eq!((0..=3).map(|n| (ko.0.plural)(n)).collect::<Vec<_>>(), [0, 0, 0, 0]);
|
||||
for n in [0, 1, 2, 5] {
|
||||
assert_eq!(trn(hi, n, "{n} page", "{n} pages"), format!("{n} पृष्ठ"));
|
||||
assert_eq!(trn(ko, n, "{n} page", "{n} pages"), format!("{n}페이지"));
|
||||
assert_eq!(trn(ko, n, "{n} field", "{n} fields"), format!("필드 {n}개"));
|
||||
}
|
||||
// The broken-line regression: a `\n` escape stays one catalog line.
|
||||
assert_eq!(tr(ko, "This page couldn't be displayed.\n{e}"), "이 페이지를 표시할 수 없습니다.\n{e}");
|
||||
for code in ["hi", "ko"] {
|
||||
let mut app = crate::PdfCraftApp::default();
|
||||
app.set_option("language", &code.to_uppercase()).unwrap();
|
||||
assert_eq!(app.language, code);
|
||||
let mut restored = crate::PdfCraftApp::default();
|
||||
restored.restore(&app.persist());
|
||||
assert_eq!(restored.language, code);
|
||||
assert!(restored.set_option("language", "xx").is_err());
|
||||
assert_eq!(restored.language, code);
|
||||
}
|
||||
}
|
||||
|
||||
/// Hindi and Korean translate every registered command and every All tools group, section
|
||||
/// and item.
|
||||
#[test]
|
||||
fn hindi_and_korean_cover_commands_and_catalogue() {
|
||||
for lang in [Lang::from_code("hi").expect("hi registered"), Lang::from_code("ko").expect("ko registered")] {
|
||||
for command in pdfcraft_engine::commands::COMMANDS {
|
||||
assert!(has(lang, command.label), "{} missing command: {}", lang.code(), command.label);
|
||||
if let Some(menu) = command.menu {
|
||||
assert!(has(lang, menu), "{} missing menu: {menu}", lang.code());
|
||||
}
|
||||
}
|
||||
for group in pdfcraft_engine::catalog::TOOL_GROUPS {
|
||||
assert!(has(lang, group.label), "{} missing group: {}", lang.code(), group.label);
|
||||
for section in group.sections {
|
||||
assert!(has(lang, section.title), "{} missing section: {}", lang.code(), section.title);
|
||||
for item in section.items {
|
||||
assert!(has(lang, item.label), "{} missing item: {}", lang.code(), item.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Every `tl!("…")` literal in the UI source is translated in Hindi and Korean, so a new
|
||||
/// label cannot silently fall back to English in either.
|
||||
#[test]
|
||||
fn hindi_and_korean_cover_ui_literals() {
|
||||
let mut stack = vec![std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src")];
|
||||
let mut literals = std::collections::BTreeSet::new();
|
||||
while let Some(dir) = stack.pop() {
|
||||
for entry in std::fs::read_dir(dir).expect("UI source directory") {
|
||||
let path = entry.expect("UI source entry").path();
|
||||
if path.is_dir() {
|
||||
if path.file_name().is_some_and(|name| name != "i18n") {
|
||||
stack.push(path);
|
||||
}
|
||||
} else if path.extension().is_some_and(|ext| ext == "rs") {
|
||||
let source = std::fs::read_to_string(path).expect("UI source file").replace("\r\n", "\n");
|
||||
let mut rest = source.split("#[cfg(test)]\nmod ").next().unwrap_or_default();
|
||||
while let Some((_, after)) = rest.split_once("tl!(\"") {
|
||||
let mut escaped = false;
|
||||
let end = after
|
||||
.char_indices()
|
||||
.find_map(|(i, c)| {
|
||||
if c == '"' && !escaped {
|
||||
return Some(i);
|
||||
}
|
||||
escaped = c == '\\' && !escaped;
|
||||
None
|
||||
})
|
||||
.expect("closed tl! literal");
|
||||
let (raw, tail) = after.split_at(end);
|
||||
if tail.starts_with("\")") {
|
||||
let label: String = serde_json::from_str(&format!("\"{raw}\"")).expect("UI literal escapes");
|
||||
literals.insert(label);
|
||||
}
|
||||
rest = tail.strip_prefix('"').expect("closing quote");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(literals.len() > 900, "source scan found only {} literals", literals.len());
|
||||
for lang in [Lang::from_code("hi").expect("hi registered"), Lang::from_code("ko").expect("ko registered")] {
|
||||
let missing: Vec<_> = literals.iter().filter(|label| !has(lang, label)).collect();
|
||||
assert!(missing.is_empty(), "untranslated {} UI literals: {missing:#?}", lang.code());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bulgarian_is_registered() {
|
||||
let bg = Lang::from_code("bg").expect("bg registered");
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
//! The embedded faces (Inter, egui's defaults, craft-fonts) come first in every family; this one
|
||||
//! only draws characters none of them has, such as an Arabic file name in a build without
|
||||
//! craft-fonts, or the Simplified Chinese interface in a build without a `Hans` face (#826). A
|
||||
//! second and a third installed face, [`devanagari_fallback`] and [`telugu_fallback`], draw Hindi
|
||||
//! and Telugu when one is installed. It is
|
||||
//! second installed face, [`devanagari_fallback`], draws Hindi when one is installed. It is
|
||||
//! read at runtime and never embedded or shipped (AGENTS.md §1.4), and `PDFCRAFT_SYSTEM_FONTS=0`
|
||||
//! turns it off (published screenshots do).
|
||||
|
||||
@@ -23,8 +22,8 @@ const MAX_FACES: u32 = 16;
|
||||
const PROBE: char = '\u{0627}';
|
||||
/// Devanagari letter ka: the face must have it to cover Hindi.
|
||||
const DEVANAGARI_PROBE: char = '\u{0915}';
|
||||
/// Telugu letter ka: the face must have it to cover Telugu.
|
||||
const TELUGU_PROBE: char = '\u{0C15}';
|
||||
/// Hangul syllable han (한, U+D55C): the face must have it to cover Korean.
|
||||
const HANGUL_PROBE: char = '\u{D55C}';
|
||||
/// 欢 (U+6B22): Simplified only, absent from the Japanese craft-fonts faces.
|
||||
const HANS_PROBE: char = '\u{6B22}';
|
||||
/// 說 (U+8AAA): Traditional only, absent from the Japanese craft-fonts faces.
|
||||
@@ -72,21 +71,53 @@ fn load(need: Need) -> Option<Arc<FontData>> {
|
||||
/// or no candidate is installed; the interface then draws what the embedded faces can.
|
||||
pub fn devanagari_fallback() -> Option<Arc<FontData>> {
|
||||
static CACHE: OnceLock<Option<Arc<FontData>>> = OnceLock::new();
|
||||
CACHE.get_or_init(|| script_fallback(&devanagari_candidates(), DEVANAGARI_PROBE)).clone()
|
||||
CACHE
|
||||
.get_or_init(|| {
|
||||
if std::env::var_os("PDFCRAFT_SYSTEM_FONTS").is_some_and(|v| v == "0") {
|
||||
return None;
|
||||
}
|
||||
devanagari_candidates().iter().find_map(|path| read(path, DEVANAGARI_PROBE, MAX_BYTES))
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// The installed fallback face for Telugu, read once. `None` when it is turned off or no
|
||||
/// candidate is installed; the interface then draws what the embedded faces can.
|
||||
pub fn telugu_fallback() -> Option<Arc<FontData>> {
|
||||
/// The installed fallback face for Hangul (Korean), read once. `None` when it is turned off or no
|
||||
/// candidate is installed; the interface then draws what the embedded faces can (no embedded face
|
||||
/// has Hangul, so Korean text shows replacement boxes).
|
||||
pub fn hangul_fallback() -> Option<Arc<FontData>> {
|
||||
static CACHE: OnceLock<Option<Arc<FontData>>> = OnceLock::new();
|
||||
CACHE.get_or_init(|| script_fallback(&telugu_candidates(), TELUGU_PROBE)).clone()
|
||||
CACHE
|
||||
.get_or_init(|| {
|
||||
if std::env::var_os("PDFCRAFT_SYSTEM_FONTS").is_some_and(|v| v == "0") {
|
||||
return None;
|
||||
}
|
||||
hangul_candidates().iter().find_map(|path| read(path, HANGUL_PROBE, MAX_HAN_BYTES))
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn script_fallback(candidates: &[PathBuf], probe: char) -> Option<Arc<FontData>> {
|
||||
if std::env::var_os("PDFCRAFT_SYSTEM_FONTS").is_some_and(|v| v == "0") {
|
||||
return None;
|
||||
/// Well-known locations of Korean system faces, best first. Noto CJK and Source Han are left out
|
||||
/// on purpose (AGENTS.md §1.1).
|
||||
fn hangul_candidates() -> Vec<PathBuf> {
|
||||
if cfg!(windows) {
|
||||
["malgun.ttf", "gulim.ttc", "batang.ttc", "malgunsl.ttf"].iter().map(|f| windows_fonts().join(f)).collect()
|
||||
} else if cfg!(target_os = "macos") {
|
||||
["/System/Library/Fonts/AppleSDGothicNeo.ttc", "/System/Library/Fonts/Supplemental/AppleGothic.ttf", "/Library/Fonts/Arial Unicode.ttf"]
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
.collect()
|
||||
} else {
|
||||
unix_fonts(&[
|
||||
"truetype/nanum/NanumGothic.ttf",
|
||||
"nanum/NanumGothic.ttf",
|
||||
"naver-nanum/NanumGothic.ttf",
|
||||
"truetype/unfonts-core/UnDotum.ttf",
|
||||
"baekmuk-ttf/gulim.ttf",
|
||||
"truetype/baekmuk/gulim.ttf",
|
||||
"truetype/droid/DroidSansFallbackFull.ttf",
|
||||
"google-droid-sans-fonts/DroidSansFallbackFull.ttf",
|
||||
])
|
||||
}
|
||||
candidates.iter().find_map(|path| read(path, probe, MAX_BYTES))
|
||||
}
|
||||
|
||||
/// Well-known locations of Devanagari (Hindi) faces, best first.
|
||||
@@ -124,37 +155,6 @@ fn devanagari_candidates() -> Vec<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Well-known locations of Telugu faces, best first.
|
||||
fn telugu_candidates() -> Vec<PathBuf> {
|
||||
if cfg!(windows) {
|
||||
["Nirmala.ttc", "Nirmala.ttf", "gautami.ttf"].iter().map(|f| windows_fonts().join(f)).collect()
|
||||
} else if cfg!(target_os = "macos") {
|
||||
[
|
||||
"/System/Library/Fonts/KohinoorTelugu.ttc",
|
||||
"/System/Library/Fonts/Supplemental/Telugu Sangam MN.ttc",
|
||||
"/System/Library/Fonts/Supplemental/Telugu MN.ttc",
|
||||
"/Library/Fonts/Arial Unicode.ttf",
|
||||
]
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
.collect()
|
||||
} else {
|
||||
unix_fonts(&[
|
||||
"google-noto/NotoSansTeluguUI-Regular.ttf",
|
||||
"google-noto/NotoSansTelugu-Regular.ttf",
|
||||
"google-noto-vf/NotoSansTelugu[wght].ttf",
|
||||
"google-noto-vf/NotoSansTelugu-UI[wght].ttf",
|
||||
"truetype/noto/NotoSansTelugu-Regular.ttf",
|
||||
"truetype/noto/NotoSansTeluguUI-Regular.ttf",
|
||||
"noto/NotoSansTelugu-Regular.ttf",
|
||||
"noto/NotoSansTeluguUI-Regular.ttf",
|
||||
"lohit-telugu-fonts/Lohit-Telugu.ttf",
|
||||
"truetype/lohit-telugu/Lohit-Telugu.ttf",
|
||||
"truetype/fonts-telu-extra/Pothana2000.ttf",
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/// Well-known locations of faces with broad script coverage, best first.
|
||||
fn candidates(need: Need) -> Vec<PathBuf> {
|
||||
match need {
|
||||
@@ -286,21 +286,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_script_fallback_is_the_first_candidate_with_the_probe() {
|
||||
let inter = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/fonts/Inter-Regular.ttf"));
|
||||
let candidates = [PathBuf::from("definitely/not/here.ttf"), inter];
|
||||
assert!(script_fallback(&candidates, 'A').is_some());
|
||||
assert!(script_fallback(&candidates, TELUGU_PROBE).is_none());
|
||||
assert!(script_fallback(&candidates, DEVANAGARI_PROBE).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_candidates_are_absolute_font_files() {
|
||||
for list in [devanagari_candidates(), telugu_candidates()] {
|
||||
assert!(!list.is_empty());
|
||||
assert!(list.iter().all(|p| p.is_absolute() || cfg!(windows)), "{list:?}");
|
||||
assert!(list.iter().all(|p| p.extension().is_some_and(|e| e == "ttf" || e == "ttc")), "{list:?}");
|
||||
fn hangul_candidates_are_font_files_without_adobe_designs() {
|
||||
let list = hangul_candidates();
|
||||
assert!(!list.is_empty());
|
||||
for path in list {
|
||||
assert!(path.extension().is_some_and(|e| e == "ttf" || e == "ttc"), "{path:?}");
|
||||
let name = path.to_string_lossy().to_lowercase();
|
||||
assert!(!["notosanscjk", "notoserifcjk", "sourcehan", "source-han"].iter().any(|b| name.contains(b)), "{name}");
|
||||
}
|
||||
// Inter has no Hangul.
|
||||
let inter = include_bytes!("../../../assets/fonts/Inter-Regular.ttf");
|
||||
assert_eq!(face_with(inter, HANGUL_PROBE), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+15
-11
@@ -218,8 +218,8 @@ pub fn install_fonts_for(ctx: &egui::Context, cjk: CjkPreference) {
|
||||
pub const SYSTEM_FALLBACK: &str = "system-fallback";
|
||||
/// The name of the installed Devanagari (Hindi) face [`installed_font_definitions`] may add.
|
||||
pub const SYSTEM_DEVANAGARI_FALLBACK: &str = "system-devanagari-fallback";
|
||||
/// The name of the installed Telugu face [`installed_font_definitions`] may add.
|
||||
pub const SYSTEM_TELUGU_FALLBACK: &str = "system-telugu-fallback";
|
||||
/// The name of the installed Hangul (Korean) face [`installed_font_definitions`] may add.
|
||||
pub const SYSTEM_HANGUL_FALLBACK: &str = "system-hangul-fallback";
|
||||
|
||||
/// What [`install_fonts_for`] installs: [`font_definitions_for`], then, on desktop, one face
|
||||
/// already installed on this machine. It only draws characters no embedded face has.
|
||||
@@ -232,8 +232,9 @@ pub const SYSTEM_TELUGU_FALLBACK: &str = "system-telugu-fallback";
|
||||
/// - Otherwise it is the last fallback of every family (an Arabic file name in a build without
|
||||
/// craft-fonts).
|
||||
///
|
||||
/// An installed Devanagari face and an installed Telugu face, when there are, end every family
|
||||
/// just before that one, so Hindi and Telugu text draw in a build without such faces (#1531).
|
||||
/// An installed Devanagari face, when there is one, ends every family just before that one, so
|
||||
/// Hindi text draws in a build without a Devanagari face. An installed Hangul face does the same
|
||||
/// for Korean: no craft-fonts face has Hangul yet.
|
||||
///
|
||||
/// `PDFCRAFT_SYSTEM_FONTS=0` leaves them out.
|
||||
pub fn installed_font_definitions(cjk: CjkPreference) -> FontDefinitions {
|
||||
@@ -243,14 +244,17 @@ pub fn installed_font_definitions(cjk: CjkPreference) -> FontDefinitions {
|
||||
{
|
||||
use crate::system_fonts::{Need, fallback};
|
||||
// Before the general installed face, which stays the last fallback outside Chinese.
|
||||
for (name, data) in [
|
||||
(SYSTEM_DEVANAGARI_FALLBACK, crate::system_fonts::devanagari_fallback()),
|
||||
(SYSTEM_TELUGU_FALLBACK, crate::system_fonts::telugu_fallback()),
|
||||
] {
|
||||
let Some(data) = data else { continue };
|
||||
fonts.font_data.insert(name.to_owned(), data);
|
||||
if let Some(data) = crate::system_fonts::devanagari_fallback() {
|
||||
fonts.font_data.insert(SYSTEM_DEVANAGARI_FALLBACK.to_owned(), data);
|
||||
for stack in fonts.families.values_mut() {
|
||||
stack.push(name.to_owned());
|
||||
stack.push(SYSTEM_DEVANAGARI_FALLBACK.to_owned());
|
||||
}
|
||||
}
|
||||
// No embedded face has Hangul, so Korean text relies on an installed one.
|
||||
if let Some(data) = crate::system_fonts::hangul_fallback() {
|
||||
fonts.font_data.insert(SYSTEM_HANGUL_FALLBACK.to_owned(), data);
|
||||
for stack in fonts.families.values_mut() {
|
||||
stack.push(SYSTEM_HANGUL_FALLBACK.to_owned());
|
||||
}
|
||||
}
|
||||
let need = match cjk {
|
||||
|
||||
@@ -170,6 +170,28 @@ fn system_fallback_covers_devanagari() {
|
||||
assert!(layout_widths(&mut fonts, DEVANAGARI).iter().all(|w| w.is_finite() && *w > 0.0));
|
||||
}
|
||||
|
||||
/// On a machine with a suitable installed Korean font, the installed definitions include it and
|
||||
/// Korean text has glyphs; the embedded-only definitions never name it.
|
||||
#[test]
|
||||
fn system_fallback_covers_hangul() {
|
||||
const HANGUL: &str = "한국어 파일 편집";
|
||||
assert!(!theme::font_definitions().font_data.contains_key(theme::SYSTEM_HANGUL_FALLBACK));
|
||||
let defs = theme::installed_font_definitions(theme::CjkPreference::Default);
|
||||
if !defs.font_data.contains_key(theme::SYSTEM_HANGUL_FALLBACK) {
|
||||
eprintln!("skipping system_fallback_covers_hangul: no installed Hangul fallback font (or PDFCRAFT_SYSTEM_FONTS=0)");
|
||||
assert!(defs.families.values().all(|stack| !stack.iter().any(|n| n == theme::SYSTEM_HANGUL_FALLBACK)));
|
||||
return;
|
||||
}
|
||||
for (family, stack) in &defs.families {
|
||||
assert_eq!(stack.iter().filter(|n| *n == theme::SYSTEM_HANGUL_FALLBACK).count(), 1, "{family:?}: {stack:?}");
|
||||
}
|
||||
let mut fonts = Fonts::new(TextOptions::default(), defs);
|
||||
for id in families() {
|
||||
assert!(fonts.has_glyphs(&id, HANGUL), "{id:?} lacks {HANGUL}");
|
||||
}
|
||||
assert!(layout_widths(&mut fonts, HANGUL).iter().all(|w| w.is_finite() && *w > 0.0));
|
||||
}
|
||||
|
||||
/// Built with craft-fonts, Hindi text renders with real glyphs (no tofu) in every family: a
|
||||
/// `Deva` face is a fallback after the app's own fonts. Without craft-fonts it is the installed
|
||||
/// Devanagari fallback's job (see `system_fallback_covers_devanagari`), and the web build has
|
||||
@@ -183,7 +205,8 @@ fn devanagari_ui_text_uses_craft_fonts() {
|
||||
let defs = theme::font_definitions();
|
||||
for family in [FontFamily::Proportional, FontFamily::Monospace] {
|
||||
let stack = &defs.families[&family];
|
||||
let first = stack.iter().position(|n| pdfcraft_fonts::ui_devanagari_fonts().iter().any(|f| f.name() == *n)).expect("a Deva face is a fallback");
|
||||
let first =
|
||||
stack.iter().position(|n| pdfcraft_fonts::ui_devanagari_fonts().iter().any(|f| f.name() == *n)).expect("a Deva face is a fallback");
|
||||
let own = stack.iter().position(|n| n == "Inter" || n == "JetBrainsMono").expect("the app's own font");
|
||||
assert!(own < first, "{family:?}: {stack:?}");
|
||||
}
|
||||
|
||||
+7
-6
@@ -38,7 +38,7 @@ update the numbers.
|
||||
| 11 | Accessibility: checker only, no tagging tools | feature | 40–70 | [target-app-parity.md](target-app-parity.md) |
|
||||
| 12 | No measured fidelity harness against Acrobat | quality | (in 2) | [target-app-parity.md](target-app-parity.md) |
|
||||
| 13 | Performance budgets and lazy loading of very large files | performance | 25–45 | this file |
|
||||
| 14 | Arabic not mirrored; no Hindi, Korean | localization | 30–50 | [localization-parity.md](localization-parity.md) |
|
||||
| 14 | Arabic not mirrored; Korean has no craft-fonts face | localization | 25–45 | [localization-parity.md](localization-parity.md) |
|
||||
| 15 | Forms: JavaScript object model, actions, submit, XFA depth | feature | 40–75 | [pdf-spec-parity.md](pdf-spec-parity.md) |
|
||||
| 16 | UI precision gaps (multi-select move, rulers, accelerators, windows) | UI/UX | 30–60 | [ui-parity.md](ui-parity.md) |
|
||||
| 17 | Certificate security and other protect gaps | feature, spec | 15–30 | [pdf-spec-parity.md](pdf-spec-parity.md) |
|
||||
@@ -236,14 +236,15 @@ update the numbers.
|
||||
to edit.
|
||||
- **Estimate:** 25–45 h. **Doc:** this file.
|
||||
|
||||
### 14. Arabic not mirrored; no Hindi, Korean
|
||||
### 14. Arabic not mirrored; Korean has no craft-fonts face
|
||||
|
||||
- **Missing:** a right-to-left layout (panels, menus, alignment), bidirectional text in the text
|
||||
layout itself (wrapping, typed text); catalogs for Hindi and Korean, each with the script face it
|
||||
needs (Acrobat ships Korean; it ships Arabic in its Middle East edition; it ships neither Hindi
|
||||
nor Indonesian nor Vietnamese). Indonesian and Vietnamese ship as of 2026-10-13.
|
||||
layout itself (wrapping, typed text); a Hangul face in craft-fonts (Korean now draws with one
|
||||
installed system face, so the web build and machines without one show boxes). Acrobat ships
|
||||
Korean; it ships Arabic in its Middle East edition; it ships neither Hindi nor Indonesian nor
|
||||
Vietnamese. Indonesian, Vietnamese, Hindi and Korean catalogs ship as of 2026-10-13.
|
||||
- **Evidence:** [localization.md](localization.md) §Arabic; `misc.rtl-ui` planned.
|
||||
- **Estimate:** 30–50 h (Arabic RTL 20–35, two catalogs 10–15 plus review). **Doc:**
|
||||
- **Estimate:** 25–45 h (Arabic RTL 20–35, a Hangul face and native review 5–10). **Doc:**
|
||||
[localization-parity.md](localization-parity.md).
|
||||
|
||||
### 15. Forms: JavaScript object model, actions, submit, XFA depth
|
||||
|
||||
+12
-10
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Indonesian and Vietnamese ship as full 2,578-key catalogs; the key union is now 2,578; the localization dimension re-measures at ≈ 76%) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Hindi and Korean ship as full 2,578-key catalogs, after Indonesian and Vietnamese; all twelve key languages now have one; the localization dimension is estimated at ≈ 93%) · **Target:** Adobe Acrobat Pro (Acrobat DC, continuous track 26.002.21931, macOS)
|
||||
|
||||
Per-language status of PdfCraft's interface against Acrobat Pro's. How translations work and how to
|
||||
add a language: [localization.md](localization.md). Summary in [ROADMAP.md](../ROADMAP.md).
|
||||
@@ -11,8 +11,9 @@ add a language: [localization.md](localization.md). Summary in [ROADMAP.md](../R
|
||||
as distinct `(context, English)` keys with a non-empty translation. The denominator is the union
|
||||
of keys across all catalogs, **2,578** (it was 2,143 until the Lithuanian catalog arrived; the
|
||||
percentages in the table below for the languages measured on 2026-10-10 are that older
|
||||
snapshot's). Cross-check: of the `tl!("…")` literals in `crates/ui-egui/src`, Indonesian and
|
||||
Vietnamese translate every one of them (required by `indonesian_and_vietnamese_cover_ui_literals`),
|
||||
snapshot's). Cross-check: of the `tl!("…")` literals in `crates/ui-egui/src`, Indonesian,
|
||||
Vietnamese, Hindi and Korean translate every one of them (required by
|
||||
`indonesian_and_vietnamese_cover_ui_literals` and `hindi_and_korean_cover_ui_literals`),
|
||||
and two literals are in no catalog yet.
|
||||
English is built in.
|
||||
- **Not counted:** error details from the engine and the operating system, which are shown in
|
||||
@@ -33,21 +34,21 @@ add a language: [localization.md](localization.md). Summary in [ROADMAP.md](../R
|
||||
| English | `en` | built in (100%) | all; README and docs | Latin | yes | **full** | yes | 0 |
|
||||
| Simplified Chinese | `zh-hans` | 2,088 (97.4%) | dialogs and tooltips through `tl!`; no help | CJK text and IME work, but **release builds have no Hans face**: many characters show as boxes (#826, #688, #728, #758; Flatpak #689) | no | partial | yes | 6–12 |
|
||||
| Spanish | `es` | 2,046 (95.5%) | dialogs and tooltips; no help | Latin | no | partial | yes | 2–4 |
|
||||
| Hindi | `hi` | 0 | none | Devanagari not tested (egui shapes Telugu conjuncts, so shaping is likely workable); no Devanagari face in craft-fonts | no | none | no | 10–18 |
|
||||
| Hindi | `hi` | 2,578 (100%) | dialogs and tooltips through `tl!`; no help | Devanagari drawn by the craft-fonts Noto Sans Devanagari face (`Deva`, kept in the web build); desktop builds without it fall back to one installed face (`SYSTEM_DEVANAGARI_FALLBACK`); tested in `crates/ui-egui/tests/fonts.rs` | no | partial | no | 2–4 |
|
||||
| Arabic | `ar` | 2,143 (100%) | dialogs and tooltips; no help | Letters joined and lines reordered at load, but **layout not mirrored**, wrapped lines break at the wrong end, typed text runs left to right (`localization.md` §Arabic) | no | partial | yes (Middle East edition) | 25–45 |
|
||||
| French | `fr` | 2,128 (99.3%) | dialogs and tooltips; no help | Latin | no | partial | yes | 1–2 |
|
||||
| Portuguese (Brazil) | `pt-br` | 2,130 (99.4%) | dialogs and tooltips; no help | Latin | community (#288) | partial | yes | 1–2 |
|
||||
| Indonesian | `id` | 2,578 (100%) | dialogs and tooltips through `tl!`; no help | Latin, drawn by the bundled Inter and JetBrains Mono (tested in `crates/ui-egui/tests/fonts.rs`) | no | partial | no | 1–2 |
|
||||
| Japanese | `ja` | 2,123 (99.1%) | dialogs and tooltips; no help | BIZ UDPGothic from craft-fonts; IME patched on macOS, Windows, X11, Wayland; vertical text renders but can't be edited | no | partial | yes | 4–8 |
|
||||
| German | `de` | 2,128 (99.3%) | dialogs and tooltips; no help | Latin | community, in progress (#828: terminology, clipping) | partial | yes | 2–4 |
|
||||
| Korean | `ko` | 0 | none | Hangul IME patched (Microsoft and Apple Korean, `vendor/README.md` winit 3, 6, 9, 10); no Hangul UI face in craft-fonts | no | none | yes | 6–10 |
|
||||
| Korean | `ko` | 2,578 (100%) | dialogs and tooltips through `tl!`; no help | Hangul IME patched (Microsoft and Apple Korean, `vendor/README.md` winit 3, 6, 9, 10); **no Hangul face in craft-fonts**, so desktop builds draw Korean with one installed face (`SYSTEM_HANGUL_FALLBACK`: Malgun Gothic, Apple SD Gothic Neo, Nanum) and the web build and machines without one show boxes | no | partial | yes | 4–8 |
|
||||
| Vietnamese | `vi` | 2,578 (100%) | dialogs and tooltips through `tl!`; no help | Latin with stacked diacritics; the bundled Inter covers them, tested in `crates/ui-egui/tests/fonts.rs` | no | partial | no | 1–2 |
|
||||
|
||||
**Localization dimension ≈ 76%**: the mean over the twelve, counting a catalog's percentage but only
|
||||
60% for Chinese and Arabic until their script problems are fixed, and 0 for the two languages still
|
||||
missing (Hindi, Korean). **≈ 55–90 h** to bring all twelve to `full`, of which ≈ 35–60 h is
|
||||
engineering (Hans face, RTL layout, bidi text) and the rest the two remaining catalogs and fixes;
|
||||
native-speaker review needs humans.
|
||||
**Localization dimension ≈ 93%** (estimated, not re-measured): the mean over the twelve, counting a
|
||||
catalog's percentage but only 60% for Chinese and Arabic until their script problems are fixed. All
|
||||
twelve now have a catalog. **≈ 45–75 h** to bring all twelve to `full`, of which ≈ 40–65 h is
|
||||
engineering (Hans face, a Hangul craft-fonts face, RTL layout, bidi text); native-speaker review
|
||||
needs humans.
|
||||
|
||||
No language is `full` except English: every catalog leaves engine error details in English, and
|
||||
none has translated help.
|
||||
@@ -79,6 +80,7 @@ Acrobat languages PdfCraft lacks: Danish, Dutch, Finnish, Norwegian, Swedish, He
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-13 | minor | Hindi and Korean ship as full 2,578-key catalogs with month and weekday names; Korean draws with an installed Hangul face (no craft-fonts face yet); the dimension is now estimated at ≈ 93% and all twelve key languages have a catalog |
|
||||
| 2026-10-13 | minor | Indonesian and Vietnamese ship as full 2,578-key catalogs with month and weekday names; the key union is 2,578 and the dimension re-measures at ≈ 76% (Hindi and Korean are the two languages still missing) |
|
||||
| 2026-10-12 | minor | Added current counts for Kazakh, Polish, Slovenian, Greek, Turkish and Lithuanian; retained existing language assessments and the original percentage snapshot |
|
||||
| 2026-10-10 | major | Created. Catalog counts measured from `crates/ui-egui/src/i18n/*.tsv` against the 2,143-key union; Acrobat's localizations from its bundle's `.lproj` and Sequences folder names |
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Interface language
|
||||
|
||||
Choose **Menu > Edit > Preferences…** (Command-comma on macOS, Ctrl-comma elsewhere) **> Interface language** and select **Auto**, **English**, **日本語**, **简体中文**, **繁體中文**, **Čeština**, **Português (Brasil)**, **Deutsch**, **Español**, **Français**, **Русский**, **Қазақша**, **Български**, **తెలుగు**, **Magyar**, **Українська**, **Italiano**, **العربية**, **Polski**, **Slovenščina**, **Ελληνικά**, **Türkçe**, **Lietuvių**, **Bahasa Indonesia** or **Tiếng Việt**. The change applies immediately and persists between launches. Command ids, document contents and file names are unchanged.
|
||||
Choose **Menu > Edit > Preferences…** (Command-comma on macOS, Ctrl-comma elsewhere) **> Interface language** and select **Auto**, **English**, **日本語**, **简体中文**, **繁體中文**, **Čeština**, **Português (Brasil)**, **Deutsch**, **Español**, **Français**, **Русский**, **Қазақша**, **Български**, **తెలుగు**, **Magyar**, **Українська**, **Italiano**, **العربية**, **Polski**, **Slovenščina**, **Ελληνικά**, **Türkçe**, **Lietuvių**, **Bahasa Indonesia**, **Tiếng Việt**, **हिन्दी** or **한국어**. The change applies immediately and persists between launches. Command ids, document contents and file names are unchanged.
|
||||
|
||||
Displayed keyboard shortcuts use the same active interface language. For example, German shows `Strg+K` and `Strg+Umschalt+S` in the toolbar, menus, command palette, tooltips and Keyboard shortcuts dialog; macOS keeps its modifier symbols (`⌘`, `⇧`, `⌃`). Key names have their own `key` catalog context, so the Delete key can read `Entf` while the command remains `Löschen`. A missing key-name entry falls back to the English key name. Actual bindings and machine-facing command ids remain stable.
|
||||
|
||||
**Auto** (the default) follows the system language: `LC_ALL`, `LC_MESSAGES` or `LANG`, then the preferred-languages list on macOS. Any Portuguese locale (`pt_BR`, `pt_PT`) uses the Brazilian catalog, any French locale (`fr`, `fr_FR`, `fr_CA`, `fr_BE`) the French one, any German locale (`de`, `de_DE`, `de_AT`, `de_CH`) the German one, and Chinese locales for Taiwan, Hong Kong and Macau (`zh_TW`, `zh_HK`, `zh_MO`, `zh-Hant`) the Traditional Chinese one; other Chinese locales (`zh`, `zh_CN`, `zh_SG`, `zh-Hans`) the Simplified Chinese one. Telugu locales (`te`, `te_IN`) use the Telugu catalog, Bulgarian locales (`bg`, `bg_BG`) the Bulgarian one, and Hungarian locales (`hu`, `hu_HU`) the Hungarian one, and Ukrainian locales (`uk`, `uk_UA`) the Ukrainian one, and Italian locales (`it`, `it_IT`, `it_CH`) the Italian one, and Arabic locales (`ar`, `ar_MA`, `ar_EG`, `ar_SA`, …) the Arabic one, and Turkish locales (`tr`, `tr_TR`) the Turkish one, and Indonesian locales (`id`, `id_ID`) the Indonesian one, and Vietnamese locales (`vi`, `vi_VN`) the Vietnamese one. A system language without a catalog shows English. On Windows, **Auto** uses the Windows display languages, in preference order, when no supported locale is selected by those environment variables. Neutral `C`/`POSIX` locale values (including `C.UTF-8`) inherited from a launcher do not override the Windows display language; explicitly named supported locales such as `fr_FR.UTF-8` still do. The query asks Windows directly (`GetUserPreferredUILanguages`, through the `sys-locale` crate) without starting a process, and is cached for the process. If detection fails or the display language has no catalog, PdfCraft shows English.
|
||||
**Auto** (the default) follows the system language: `LC_ALL`, `LC_MESSAGES` or `LANG`, then the preferred-languages list on macOS. Any Portuguese locale (`pt_BR`, `pt_PT`) uses the Brazilian catalog, any French locale (`fr`, `fr_FR`, `fr_CA`, `fr_BE`) the French one, any German locale (`de`, `de_DE`, `de_AT`, `de_CH`) the German one, and Chinese locales for Taiwan, Hong Kong and Macau (`zh_TW`, `zh_HK`, `zh_MO`, `zh-Hant`) the Traditional Chinese one; other Chinese locales (`zh`, `zh_CN`, `zh_SG`, `zh-Hans`) the Simplified Chinese one. Telugu locales (`te`, `te_IN`) use the Telugu catalog, Bulgarian locales (`bg`, `bg_BG`) the Bulgarian one, and Hungarian locales (`hu`, `hu_HU`) the Hungarian one, and Ukrainian locales (`uk`, `uk_UA`) the Ukrainian one, and Italian locales (`it`, `it_IT`, `it_CH`) the Italian one, and Arabic locales (`ar`, `ar_MA`, `ar_EG`, `ar_SA`, …) the Arabic one, and Slovenian locales (`sl`, `sl_SI`) the Slovenian one, and Indonesian locales (`id`, `id_ID`) the Indonesian one, and Vietnamese locales (`vi`, `vi_VN`) the Vietnamese one. A system language without a catalog shows English. On Windows, **Auto** uses the Windows display languages, in preference order, when no supported locale is selected by those environment variables. Neutral `C`/`POSIX` locale values (including `C.UTF-8`) inherited from a launcher do not override the Windows display language; explicitly named supported locales such as `fr_FR.UTF-8` still do. The query asks Windows directly (`GetUserPreferredUILanguages`, through the `sys-locale` crate) without starting a process, and is cached for the process. If detection fails or the display language has no catalog, PdfCraft shows English.
|
||||
**Auto** (the default) follows the system language: `LC_ALL`, `LC_MESSAGES` or `LANG`, then the preferred-languages list on macOS. Any Portuguese locale (`pt_BR`, `pt_PT`) uses the Brazilian catalog, any French locale (`fr`, `fr_FR`, `fr_CA`, `fr_BE`) the French one, any German locale (`de`, `de_DE`, `de_AT`, `de_CH`) the German one, and Chinese locales for Taiwan, Hong Kong and Macau (`zh_TW`, `zh_HK`, `zh_MO`, `zh-Hant`) the Traditional Chinese one; other Chinese locales (`zh`, `zh_CN`, `zh_SG`, `zh-Hans`) the Simplified Chinese one. Telugu locales (`te`, `te_IN`) use the Telugu catalog, Bulgarian locales (`bg`, `bg_BG`) the Bulgarian one, and Hungarian locales (`hu`, `hu_HU`) the Hungarian one, and Ukrainian locales (`uk`, `uk_UA`) the Ukrainian one, and Italian locales (`it`, `it_IT`, `it_CH`) the Italian one, and Arabic locales (`ar`, `ar_MA`, `ar_EG`, `ar_SA`, …) the Arabic one, and Turkish locales (`tr`, `tr_TR`) the Turkish one, and Indonesian locales (`id`, `id_ID`) the Indonesian one, Vietnamese locales (`vi`, `vi_VN`) the Vietnamese one, Hindi locales (`hi`, `hi_IN`) the Hindi one, and Korean locales (`ko`, `ko_KR`) the Korean one. A system language without a catalog shows English. On Windows, **Auto** uses the Windows display languages, in preference order, when no supported locale is selected by those environment variables. Neutral `C`/`POSIX` locale values (including `C.UTF-8`) inherited from a launcher do not override the Windows display language; explicitly named supported locales such as `fr_FR.UTF-8` still do. The query asks Windows directly (`GetUserPreferredUILanguages`, through the `sys-locale` crate) without starting a process, and is cached for the process. If detection fails or the display language has no catalog, PdfCraft shows English.
|
||||
**Auto** (the default) follows the system language: `LC_ALL`, `LC_MESSAGES` or `LANG`, then the preferred-languages list on macOS. Any Portuguese locale (`pt_BR`, `pt_PT`) uses the Brazilian catalog, any French locale (`fr`, `fr_FR`, `fr_CA`, `fr_BE`) the French one, any German locale (`de`, `de_DE`, `de_AT`, `de_CH`) the German one, and Chinese locales for Taiwan, Hong Kong and Macau (`zh_TW`, `zh_HK`, `zh_MO`, `zh-Hant`) the Traditional Chinese one; other Chinese locales (`zh`, `zh_CN`, `zh_SG`, `zh-Hans`) the Simplified Chinese one. Telugu locales (`te`, `te_IN`) use the Telugu catalog, Bulgarian locales (`bg`, `bg_BG`) the Bulgarian one, and Hungarian locales (`hu`, `hu_HU`) the Hungarian one, and Ukrainian locales (`uk`, `uk_UA`) the Ukrainian one, and Italian locales (`it`, `it_IT`, `it_CH`) the Italian one, and Arabic locales (`ar`, `ar_MA`, `ar_EG`, `ar_SA`, …) the Arabic one, and Slovenian locales (`sl`, `sl_SI`) the Slovenian one, and Indonesian locales (`id`, `id_ID`) the Indonesian one, Vietnamese locales (`vi`, `vi_VN`) the Vietnamese one, Hindi locales (`hi`, `hi_IN`) the Hindi one, and Korean locales (`ko`, `ko_KR`) the Korean one. A system language without a catalog shows English. On Windows, **Auto** uses the Windows display languages, in preference order, when no supported locale is selected by those environment variables. Neutral `C`/`POSIX` locale values (including `C.UTF-8`) inherited from a launcher do not override the Windows display language; explicitly named supported locales such as `fr_FR.UTF-8` still do. The query asks Windows directly (`GetUserPreferredUILanguages`, through the `sys-locale` crate) without starting a process, and is cached for the process. If detection fails or the display language has no catalog, PdfCraft shows English.
|
||||
|
||||
The control channel exposes the setting through `ui.set`:
|
||||
|
||||
@@ -13,7 +13,7 @@ The control channel exposes the setting through `ui.set`:
|
||||
{"method":"ui.set","params":{"key":"language","value":"ja"}}
|
||||
```
|
||||
|
||||
The value is `auto` or a language code (`en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `de`, `es`, `fr`, `ru`, `kk`, `bg`, `te`, `hu`, `uk`, `it`, `ar`, `pl`, `sl`, `el`, `tr`, `lt`, `id`, `vi`), in any case. `ui.state` reports `language` as written in the preferences (`auto`, `en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `de`, `es`, `fr`, `ru`, `kk`, `bg`, `te`, `hu`, `uk`, `it`, `ar`, `pl`, `sl`, `el`, `tr`, `lt`, `id` or `vi`). Unknown values return an error without changing the current setting. Preferences saved before this setting existed follow the system language.
|
||||
The value is `auto` or a language code (`en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `de`, `es`, `fr`, `ru`, `kk`, `bg`, `te`, `hu`, `uk`, `it`, `ar`, `pl`, `sl`, `el`, `tr`, `lt`, `id`, `vi`, `hi`, `ko`), in any case. `ui.state` reports `language` as written in the preferences (`auto`, `en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `de`, `es`, `fr`, `ru`, `kk`, `bg`, `te`, `hu`, `uk`, `it`, `ar`, `pl`, `sl`, `el`, `tr`, `lt`, `id`, `vi`, `hi` or `ko`). Unknown values return an error without changing the current setting. Preferences saved before this setting existed follow the system language.
|
||||
|
||||
Dialogs, panels, menus, notices and history labels go through the catalog (`tl!`). Japanese, Traditional Chinese, Simplified Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese and Arabic translate them (about 2,000–2,140 entries each); Czech and Turkish cover the menus so far. Indonesian and Vietnamese translate every key in the union (2,578 entries each, including every registered command, All tools label and `tl!` literal). Untranslated labels use English. Error details that come from the engine or the operating system are shown as they are, inside a translated frame ("操作「…」に失敗しました: …"). The command palette matches the translated label, the English label and the command id. Vertical Japanese PDF rendering is an existing viewer feature; this change does not add vertical text editing. Per-language coverage, script support and what each language still needs are in [localization-parity.md](localization-parity.md).
|
||||
Dialogs, panels, menus, notices and history labels go through the catalog (`tl!`). Japanese, Traditional Chinese, Simplified Chinese, Russian, Bulgarian, German, Spanish, French, Telugu, Hungarian, Ukrainian, Italian, Brazilian Portuguese, Arabic and Slovenian translate them (about 2,000–2,190 entries each); Czech covers the menus so far. Indonesian and Vietnamese translate every key in the union (2,578 entries each). Untranslated labels use English. Error details that come from the engine or the operating system are shown as they are, inside a translated frame ("操作「…」に失敗しました: …"). The command palette matches the translated label, the English label and the command id. Vertical Japanese PDF rendering is an existing viewer feature; this change does not add vertical text editing. Per-language coverage, script support and what each language still needs are in [localization-parity.md](localization-parity.md).
|
||||
@@ -26,6 +26,8 @@ The Lithuanian catalog has 2,578 entries and three integer plural forms. Calenda
|
||||
|
||||
The Indonesian and Vietnamese catalogs have 2,578 entries each: every `(context, English)` key the union of the bundled catalogs holds, written from the meaning of the English labels with Indonesian computing vocabulary and standard Vietnamese terms. Neither language inflects nouns for number ([CLDR](https://www.unicode.org/cldr/charts/48/supplemental/language_plural_rules.html) `id` and `vi` have one form, `other`), so their `@plural` entries list a single form that reads the same for every count. Catalog regressions require every registered command, every All tools group, section and item, and every `tl!` literal in both, and the font test requires the bundled Latin faces to cover both catalogs, stacked Vietnamese diacritics included. Month and weekday names for stamps are in the same two languages: Indonesian writes them as they stand, Vietnamese lower-cases `tháng` after a day number (`7 tháng Ba`) and keeps it capitalised standing alone (`Tháng Ba`).
|
||||
|
||||
The Hindi and Korean catalogs also have 2,578 entries each, with the same coverage tests (commands, All tools, every `tl!` literal) and one `@plural` form: Korean has no number inflection, and the Hindi nouns in the catalog read the same for every count (`{n} पृष्ठ`). Month and weekday names for stamps are in both languages (Hindi `7 मार्च`, Korean `3월`, `토요일`); like Vietnamese, neither is Western European, so the date-text path refuses them for PDF text with what to use instead. Hindi is drawn with the craft-fonts Noto Sans Devanagari face (`Deva`, kept in the web build) or, on desktop without it, one installed Devanagari face. No craft-fonts face has Hangul yet: desktop builds draw Korean with one installed face (Malgun Gothic, Apple SD Gothic Neo or Nanum, never Noto CJK or Source Han; `PDFCRAFT_SYSTEM_FONTS=0` turns it off), and the web build and machines without such a face show replacement boxes until craft-fonts gets a Hangul face.
|
||||
|
||||
Edit text reads Hebrew lines in typing order and writes them back in drawing order (`crates/edit/src/bidi.rs`); letters the page's own font lacks are drawn with the craft-fonts `Hebr` face (Noto Sans Hebrew) as Type 3 glyphs, and a build without one says so. Hebrew points (niqqud) can't be written yet. The craft-fonts revision pinned for CI and releases has no `Hebr` face yet, so until one is added releases report that error instead of drawing the missing letters.
|
||||
|
||||
Japanese interface text uses BIZ UDPGothic from [craft-fonts](https://github.com/storytold/craft-fonts), an optional build input that every release includes (`CRAFT_FONTS_DIR`; see the README). A build made without it has no Japanese face, so Japanese labels show replacement boxes. Chinese is drawn with the same craft-fonts CJK faces (in Simplified Chinese the `Hans` faces come first, so a line never mixes faces with different baselines), so a character none of them has shows a replacement box (and every Chinese character does in a build without craft-fonts). Russian, Bulgarian, Czech, Brazilian Portuguese, German, Spanish, French, Hungarian, Italian, Turkish, Lithuanian, Indonesian and Vietnamese need only the bundled Latin faces (tested in `tests/fonts.rs`). Hungarian uses the same one/other plural rule as English: 1 is singular, and every other count — including 0 — is the other form. After a numeral the noun stays singular (`1 oldal`, `2 oldal`). Telugu is drawn with a craft-fonts `Telu` face (Noto Sans Telugu, kept in the web build too), whose conjuncts egui shapes; a build without one shows Telugu labels as replacement boxes, and the release-pinned craft-fonts input has no Telugu face yet.
|
||||
|
||||
@@ -181,7 +181,7 @@ features total and are shown for orientation; the others add to it.
|
||||
| File formats and conversion | ≈ 40% | 12% | 120–220 | within features | PDF read/write strong, encrypted output included (#774 fixed); export Word is layout-poor (#773); no Excel/PowerPoint export, no Office/HTML/PostScript import | [file-format-parity.md](file-format-parity.md) |
|
||||
| PDF specification and standards | ≈ 60% | 8% | 150–260 | within features | ISO 32000 syntax, encryption, annotations and AcroForm strong; rendering and fonts borrowed from `hayro`; PDF 2.0 extras, linearization, public-key security absent; PDF/A partial, PDF/X/UA none | [pdf-spec-parity.md](pdf-spec-parity.md) |
|
||||
| Hardware | ≈ 35% | 4% | 25–50 | additional | Page raster is CPU only (Acrobat has GPU rendering); Windows CNG and macOS Keychain keys sign, no PKCS #11; no scanners; web printing goes through the browser's print dialog as pictures, not vectors | [hardware-parity.md](hardware-parity.md) |
|
||||
| Localization | ≈ 76% over the 12 key languages | 5% | 25–50 | additional | 17 catalogs; Chinese UI shows tofu in releases (#826, #688); Arabic not mirrored; no Hindi or Korean | [localization-parity.md](localization-parity.md) |
|
||||
| Localization | ≈ 93% over the 12 key languages | 5% | 25–50 | additional | 25 catalogs; Chinese UI shows tofu in releases (#826, #688); Arabic not mirrored; Korean has no craft-fonts face | [localization-parity.md](localization-parity.md) |
|
||||
| Performance | ≈ 40% | 6% | 25–45 | additional | 500-page smooth scrolling and large-file memory work (#307) done; no performance budgets (`misc.performance-budgets`), no lazy loading of GB files; blurry text at 1080p/1440p (#730) | [gaps.md](gaps.md) |
|
||||
| Stability | ≈ 50% | 8% | 45–80 | additional | Never-crash rules and nightly fuzzing; 245 open issues on 2026-10-10, many of them `fix(...)` reports of wrong results in shipped features (the #799–#821 batch is fixed) | [gaps.md](gaps.md) |
|
||||
| Platforms | ≈ 70% | 5% | 15–30 | additional | macOS (signed, notarized), Windows x64/x86/ARM64 (signed), Linux (AppImage, deb, rpm, Flatpak), FreeBSD, web (WASM). Acrobat has no Linux, FreeBSD or full web editor; we lack web OCR and web recovery | [hardware-parity.md](hardware-parity.md) |
|
||||
|
||||
Reference in New Issue
Block a user