M14: Telugu (తెలుగు) interface language (#349)
* M14: Telugu interface font - The interface takes `Telu` faces from craft-fonts after the CJK and Arabic ones, on the web too - Test: Telugu text has glyphs in every family and its conjuncts are shaped Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * M14: Telugu interface language - Complete te catalog: 1,984 entries plus whole-phrase Undo/Redo; te and te_IN locales use it - Undo and Redo labels use a catalog phrase when the language has one (Telugu puts the verb last) - Tests, docs, attribution, parity and ROADMAP Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * Add my contributor name (chiranjeevi-max) Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * M14: catalog contexts for labels with two meanings - tl_ctx! gives the signature pad's Type, the Action Wizard's Start and the comment menu's Edit their own context; Telugu translates them, other languages keep their current translation - Preferences' All tools goes through the catalog in every language Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * M14: Telugu for the look-alike web address warnings Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * M14: Telugu for the image signature notices Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> * Telugu for Open Recent (#323) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: Chiranjeevi U <244287281+chiranjeevi-max@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
868bd727d1
commit
05c8ab3b3e
|
|
@ -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 (249)
|
||||
## In this repository (250)
|
||||
|
||||
| Asset | Title | Author | Licence | Source | Used for |
|
||||
|---|---|---|---|---|---|
|
||||
|
|
@ -14,6 +14,7 @@ Every asset PdfCraft includes, bundles or uses to build its published material,
|
|||
| `crates/ui-egui/src/i18n/pt-br.tsv` | Brazilian Portuguese 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 | Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/es.tsv` | Spanish 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 | Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/fr.tsv` | French 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 | French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/te.tsv` | Telugu 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 | Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs |
|
||||
| `crates/ui-egui/src/i18n/ja.tsv` | Japanese 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 | Japanese 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 |
|
||||
|
|
|
|||
|
|
@ -76,6 +76,17 @@ kind = "translation"
|
|||
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "b131025cb6c14135440968f08ab6334d70dd3b20271539471878d3825ecc9629"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/te.tsv"
|
||||
title = "Telugu 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 = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "1c8ce042942ac9169458e79261fc1d0647d4a3734e548799bf19b98eb314b9a2"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ja.tsv"
|
||||
title = "Japanese interface translation catalog"
|
||||
|
|
|
|||
|
|
@ -132,6 +132,8 @@ M0 → M1 → M2 → M3 → M4 must happen in order. After M4, M5–M12 can run
|
|||
|
||||
Newest first. One line per session: the date, what moved, and the new overall percentage.
|
||||
|
||||
- **2026-10-09 (community, M14, Telugu):** Telugu (తెలుగు, `te`) as a complete catalog of 1,984 entries covering every command, All tools entry, dialog, panel and notice; `te` and `te_IN` locales follow it. Telugu text is drawn with a craft-fonts `Telu` face (Noto Sans Telugu, storytold/craft-fonts#10), kept in the web build too, after the CJK and Arabic faces; egui shapes its conjuncts. Commands use the plain imperative and sentences the polite form. The craft-fonts revision pinned for releases has no Telugu face yet, so release builds show Telugu as replacement boxes until the pin moves. Overall estimate unchanged (about 30–35%).
|
||||
|
||||
- **2026-10-09 (M14, Simplified Chinese About):** The About tabs, contributor controls and statistics, and model table now have Simplified Chinese translations, along with multiline link-privacy and cut-and-stack guidance. Coverage checks include direct `i18n::t` calls, multiline literals and dynamic credit labels; UI control checks exercise the translated About tabs and tables. Chinese font delivery remains incomplete; overall ≈ 30–35% (unchanged).
|
||||
|
||||
- **2026-10-09 (M14, #305):** Windows MSI rejects per-user installation overrides while allowing removal of legacy per-user installs and keeps its progress message visible. README documents unattended deployment without a desktop shortcut. Compiled x64 MSI checks pass; ARM64 install regressions added for CI. Overall estimate unchanged (about 30–35%).
|
||||
|
|
|
|||
|
|
@ -46,3 +46,8 @@ sync_github_name = false
|
|||
real_name = "Marlon Konrad"
|
||||
display_name = "Marlon Konrad"
|
||||
sync_github_name = false
|
||||
|
||||
[people.chiranjeevi-max]
|
||||
real_name = "Chiranjeevi Uddanti"
|
||||
display_name = "Chiranjeevi U"
|
||||
sync_github_name = false
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ set). Without that variable `CRAFT_FONTS` is empty and everything below copes:
|
|||
in Chinese mode, so one line never mixes faces with different vertical metrics).
|
||||
- `ui_arabic_fonts`: the `Arab` faces for Arabic-script interface text (file names, document
|
||||
titles), in manifest order.
|
||||
- `ui_telugu_fonts`: the `Telu` faces for Telugu interface text (the Telugu catalog, file names,
|
||||
document titles), in manifest order.
|
||||
- `document_japanese_font` / `japanese_glyph`: the face (Shippori Mincho, then BIZ UDMincho) whose
|
||||
outlines become the Type 3 fallback font for Japanese text written into PDFs. Without it,
|
||||
`japanese_glyph` returns `GlyphError::NoFont` and the editor reports a clear error.
|
||||
- `SHIPPORI_MINCHO`: Shippori Mincho's bytes, or `None`.
|
||||
|
||||
wasm32 builds embed only BIZ UDPGothic Regular and any `Arab` face, to keep the web build small: the web build
|
||||
wasm32 builds embed only BIZ UDPGothic Regular and any `Arab` or `Telu` face, to keep the web build small: the web build
|
||||
currently has no Chinese face, so Chinese there still shows the replacement glyph.
|
||||
Font files are never
|
||||
committed here (`AGENTS.md` §1.4; team members: [craftrules `standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md), internal).
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
//! Embeds the fonts of the optional craft-fonts build input (`CRAFT_FONTS_DIR`) as `CRAFT_FONTS`.
|
||||
//! The recipe is craft-fonts' `docs/integration.md`; unset, `CRAFT_FONTS` is empty. It only reads
|
||||
//! the local checkout (no network). On wasm32 it embeds only the UI faces (BIZ UDPGothic Regular
|
||||
//! and any `Arab` face), to keep the web build within hosting limits (Cloudflare Pages: 25 MiB
|
||||
//! per file).
|
||||
//! and any `Arab` or `Telu` face), to keep the web build within hosting limits (Cloudflare Pages:
|
||||
//! 25 MiB per file).
|
||||
|
||||
use std::fmt::Write as _;
|
||||
use std::path::PathBuf;
|
||||
|
|
@ -41,7 +41,10 @@ fn craft_fonts(dir: &std::path::Path) -> Result<String, String> {
|
|||
};
|
||||
// Arabic interface faces are small, so the web build keeps them too.
|
||||
let arabic = scripts.split(',').any(|s| s.trim() == "Arab");
|
||||
if wasm && !(*family == "BIZ UDPGothic" && *style == "Regular") && !arabic {
|
||||
// Telugu faces too, for the Telugu interface.
|
||||
let telugu = scripts.split(',').any(|s| s.trim() == "Telu");
|
||||
let web_face = (*family == "BIZ UDPGothic" && *style == "Regular") || arabic || telugu;
|
||||
if wasm && !web_face {
|
||||
continue;
|
||||
}
|
||||
let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ fn arabic<'a>(faces: impl IntoIterator<Item = &'a CraftFont>) -> Vec<&'a CraftFo
|
|||
faces.into_iter().filter(|f| f.covers("Arab")).collect()
|
||||
}
|
||||
|
||||
/// The `Telu` craft-fonts faces for Telugu interface text (the Telugu catalog, file names,
|
||||
/// document titles), in manifest order. Empty when built without craft-fonts or when it has no
|
||||
/// Telugu face.
|
||||
pub fn ui_telugu_fonts() -> Vec<&'static CraftFont> {
|
||||
CRAFT_FONTS.iter().filter(|f| f.covers("Telu")).collect()
|
||||
}
|
||||
|
||||
/// Interface CJK faces in fallback order for the UI language: Simplified Chinese first when
|
||||
/// `prefer_hans`, otherwise Japanese first (the historical default).
|
||||
///
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub mod pdf;
|
|||
mod script;
|
||||
pub use craft::{
|
||||
CRAFT_FONTS, CraftFont, SHIPPORI_MINCHO, document_japanese_font, ui_arabic_fonts, ui_chinese_fonts, ui_cjk_fonts, ui_japanese_fonts,
|
||||
ui_telugu_fonts,
|
||||
};
|
||||
pub use script::{GlyphError, GlyphOutline, MAX_SIGNATURE_CHARS, ScriptOutline, japanese_glyph, script_outline};
|
||||
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> bool
|
|||
}
|
||||
}
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
if widgets::pill_button(ui, tl!("Start"), true).clicked() {
|
||||
if widgets::pill_button(ui, tl_ctx!("action wizard", "Start"), true).clicked() {
|
||||
close = true;
|
||||
app.start_selected_action();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ fn card(
|
|||
.layout(Layout::top_down_justified(Align::Min))
|
||||
.show(|ui| {
|
||||
ui.set_min_width(160.0);
|
||||
if ui.button(tl!("Edit")).clicked() {
|
||||
if ui.button(tl_ctx!("comment menu", "Edit")).clicked() {
|
||||
view.comments.editing = Some((a.page, a.index, a.contents.clone().unwrap_or_default()));
|
||||
ui.close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -500,7 +500,7 @@ pub(crate) fn signature_pad(
|
|||
let title = if d.editing { tl!("Change {what}") } else { tl!("Create {what}") };
|
||||
ui.label(egui::RichText::new(crate::i18n::fmt(title, &[("what", what)])).font(crate::theme::semibold(18.0)));
|
||||
ui.horizontal(|ui| {
|
||||
if crate::widgets::pill_button(ui, tl!("Type"), !d.drawing && !d.image_mode).clicked() {
|
||||
if crate::widgets::pill_button(ui, tl_ctx!("signature pad", "Type"), !d.drawing && !d.image_mode).clicked() {
|
||||
d.drawing = false;
|
||||
d.image_mode = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
//!
|
||||
//! # Looking strings up
|
||||
//! - [`tl!`](crate::tl) / [`t`]: a plain string in the current language. [`tr`]: in a given one.
|
||||
//! - [`tr_ctx`]: when one English word needs different translations.
|
||||
//! - [`tr_ctx`] (`tl_ctx!` in UI code): when one English word needs different translations.
|
||||
//! - [`tr_id`]: a command-id keyed string with the English label as fallback (menu items), so a
|
||||
//! translation survives rewording of the English text and can differ per command.
|
||||
//! - [`trn`]: plural-aware (`{n}` is filled in). [`fmt`]: fill `{name}` placeholders after [`tr`];
|
||||
|
|
@ -75,7 +75,7 @@ fn plural_fr(n: u64) -> usize {
|
|||
}
|
||||
|
||||
/// The registry. English first: it is the fallback and the source language.
|
||||
pub static LANGUAGES: [LangInfo; 8] = [
|
||||
pub static LANGUAGES: [LangInfo; 9] = [
|
||||
LangInfo { code: "en", name: "English", source: "", plural: plural_one_other, catalog: OnceLock::new() },
|
||||
LangInfo { code: "ja", name: "日本語", source: include_str!("ja.tsv"), plural: plural_none, catalog: OnceLock::new() },
|
||||
// Simplified Chinese; `zh`, `zh-CN`, `zh-SG` and `zh-Hans-*` locales resolve here (see `candidates`).
|
||||
|
|
@ -90,6 +90,8 @@ pub static LANGUAGES: [LangInfo; 8] = [
|
|||
LangInfo { code: "es", name: "Español", source: include_str!("es.tsv"), plural: plural_one_other, catalog: OnceLock::new() },
|
||||
// French; every `fr-*` locale (`fr-FR`, `fr-CA`, `fr-BE` ...) resolves here.
|
||||
LangInfo { code: "fr", name: "Français", source: include_str!("fr.tsv"), plural: plural_fr, catalog: OnceLock::new() },
|
||||
// Telugu; every `te-*` locale (`te-IN`) resolves here.
|
||||
LangInfo { code: "te", name: "తెలుగు", source: include_str!("te.tsv"), plural: plural_one_other, catalog: OnceLock::new() },
|
||||
];
|
||||
|
||||
impl LangInfo {
|
||||
|
|
@ -343,11 +345,17 @@ pub fn action_label(text: &str) -> String {
|
|||
t(text).to_owned()
|
||||
}
|
||||
|
||||
/// A command label such as "Undo Insert pages from a.pdf" in the current language.
|
||||
/// A command label such as "Undo Insert pages from a.pdf" in the current language. A catalog
|
||||
/// whose word order doesn't put the verb first translates "Undo {action}" and "Redo {action}"
|
||||
/// as whole phrases; otherwise the translated verb goes before the action.
|
||||
pub fn command_label(text: &str) -> String {
|
||||
for prefix in ["Undo", "Redo"] {
|
||||
for (prefix, template) in [("Undo", "Undo {action}"), ("Redo", "Redo {action}")] {
|
||||
if let Some(action) = text.strip_prefix(prefix).and_then(|tail| tail.strip_prefix(' ')) {
|
||||
return format!("{} {}", t(prefix), action_label(action));
|
||||
let action = action_label(action);
|
||||
if has(current(), template) {
|
||||
return fmt(t(template), &[("action", &action)]);
|
||||
}
|
||||
return format!("{} {action}", t(prefix));
|
||||
}
|
||||
}
|
||||
t(text).to_owned()
|
||||
|
|
@ -712,6 +720,97 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telugu_is_registered() {
|
||||
let te = Lang::from_code("te").expect("te registered");
|
||||
assert_eq!(te.name(), "తెలుగు");
|
||||
assert_eq!(normalize_pref("TE"), Some("te"));
|
||||
assert_eq!(lang_from_tag("te_IN.UTF-8"), Some(te));
|
||||
assert_eq!(first_supported("te-IN\r\nen-US"), Some(te));
|
||||
assert_eq!(tr(te, "File"), "ఫైల్");
|
||||
assert_eq!(tr(te, "Save as…"), "వేరే పేరుతో సేవ్ చేయి…");
|
||||
assert_eq!(tr(te, "నివేదిక.pdf"), "నివేదిక.pdf");
|
||||
assert_eq!(trn(te, 1, "{n} page", "{n} pages"), "1 పేజీ");
|
||||
assert_eq!(trn(te, 3, "{n} page", "{n} pages"), "3 పేజీలు");
|
||||
let mut app = crate::PdfCraftApp::default();
|
||||
app.set_option("language", "te").unwrap();
|
||||
assert_eq!(app.language, "te");
|
||||
let mut restored = crate::PdfCraftApp::default();
|
||||
restored.restore(&app.persist());
|
||||
assert_eq!(restored.language, "te");
|
||||
}
|
||||
|
||||
/// Telugu puts the verb last, so it translates "Undo {action}" as a whole phrase; a catalog
|
||||
/// without that entry (Spanish) keeps the verb in front of the action.
|
||||
#[test]
|
||||
fn undo_and_redo_labels_use_the_catalog_phrase_when_there_is_one() {
|
||||
let te = Lang::from_code("te").expect("te registered");
|
||||
set_current(te);
|
||||
assert_eq!(command_label("Undo"), "రద్దు చేయి");
|
||||
assert_eq!(command_label("Undo Untranslated custom action"), "రద్దు చేయి: Untranslated custom action");
|
||||
assert_eq!(command_label("Redo Untranslated custom action"), "మళ్లీ చేయి: Untranslated custom action");
|
||||
let es = Lang::from_code("es").expect("es registered");
|
||||
set_current(es);
|
||||
assert_eq!(command_label("Undo Untranslated custom action"), format!("{} Untranslated custom action", tr(es, "Undo")));
|
||||
set_current(Lang::EN);
|
||||
assert_eq!(command_label("Undo Untranslated custom action"), "Undo Untranslated custom action");
|
||||
}
|
||||
|
||||
/// A label that reads differently by use has its own contextual entry where a language needs
|
||||
/// one; a language without it falls back to the plain translation.
|
||||
#[test]
|
||||
fn contextual_labels_fall_back_to_the_plain_translation() {
|
||||
let te = Lang::from_code("te").expect("te registered");
|
||||
assert_eq!(tr_ctx(te, "signature pad", "Type"), "టైప్ చేయి");
|
||||
assert_eq!(tr(te, "Type"), "రకం");
|
||||
let es = Lang::from_code("es").expect("es registered");
|
||||
assert_eq!(tr_ctx(es, "signature pad", "Type"), tr(es, "Type"));
|
||||
}
|
||||
|
||||
/// Telugu translates every registered command and every All tools group, section and item.
|
||||
#[test]
|
||||
fn telugu_covers_commands_and_catalogue() {
|
||||
let te = Lang::from_code("te").expect("te registered");
|
||||
for command in pdfcraft_engine::commands::COMMANDS {
|
||||
assert!(has(te, command.label), "missing command: {}", command.label);
|
||||
}
|
||||
for group in pdfcraft_engine::catalog::TOOL_GROUPS {
|
||||
assert!(has(te, group.label), "missing group: {}", group.label);
|
||||
for section in group.sections {
|
||||
assert!(has(te, section.title), "missing section: {}", section.title);
|
||||
for item in section.items {
|
||||
assert!(has(te, item.label), "missing item: {}", item.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// With a craft-fonts Telugu face, every Telugu translation has glyphs: with all interface
|
||||
/// faces (desktop) and with the Telugu face as the only craft-fonts face (the web build keeps
|
||||
/// it; Telugu labels must not need a Japanese or Arabic face).
|
||||
#[test]
|
||||
fn telugu_labels_have_glyphs() {
|
||||
let telugu: Vec<String> = pdfcraft_fonts::ui_telugu_fonts().iter().map(|f| f.name()).collect();
|
||||
if telugu.is_empty() {
|
||||
eprintln!("skipping Telugu glyph checks: build with CRAFT_FONTS_DIR and a Telugu face to run them");
|
||||
return;
|
||||
}
|
||||
let te = Lang::from_code("te").expect("te registered");
|
||||
let (entries, _) = parse_entries(te.0.source, te.0.plural_forms());
|
||||
let labels: String = entries.iter().flat_map(|e| e.translation.chars()).chain(te.name().chars()).filter(|c| !c.is_control()).collect();
|
||||
let mut telugu_only = crate::theme::font_definitions();
|
||||
for family in telugu_only.families.values_mut() {
|
||||
family.retain(|name| !pdfcraft_fonts::CRAFT_FONTS.iter().any(|face| face.name() == *name) || telugu.contains(name));
|
||||
}
|
||||
use egui::epaint::text::{Fonts, TextOptions};
|
||||
for (build, defs) in [("all faces", crate::theme::font_definitions()), ("Telugu face only", telugu_only)] {
|
||||
let mut fonts = Fonts::new(TextOptions::default(), defs);
|
||||
for id in [egui::FontId::proportional(13.0), egui::FontId::monospace(13.0), crate::theme::medium(13.0), crate::theme::semibold(17.0)] {
|
||||
assert!(fonts.has_glyphs(&id, &labels), "{build}: {id:?} lacks a Telugu label glyph");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Czech covers every registered menu title and menu command label (#68), keeps ellipses, and
|
||||
/// only leaves untranslated what is deliberately the same in Czech.
|
||||
#[test]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -257,7 +257,7 @@ pub(crate) fn preferences_body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tok
|
|||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Default workspace mode"));
|
||||
for (mode, label) in [
|
||||
(crate::Mode::AllTools, "All Tools"),
|
||||
(crate::Mode::AllTools, "All tools"),
|
||||
(crate::Mode::Read, "Read"),
|
||||
(crate::Mode::Edit, "Edit"),
|
||||
(crate::Mode::Convert, "Convert"),
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ macro_rules! tl {
|
|||
};
|
||||
}
|
||||
|
||||
/// [`tl!`] for a label whose meaning depends on where it appears ("Type" is a column and a
|
||||
/// button): a catalog can translate it under `context`, otherwise the plain translation is used.
|
||||
macro_rules! tl_ctx {
|
||||
($context:expr, $s:expr) => {
|
||||
$crate::i18n::tr_ctx($crate::i18n::current(), $context, $s)
|
||||
};
|
||||
}
|
||||
|
||||
mod a11y_ui;
|
||||
mod actions_ui;
|
||||
pub mod canvas;
|
||||
|
|
|
|||
|
|
@ -159,8 +159,8 @@ pub fn installed_font_definitions(prefer_hans: bool) -> FontDefinitions {
|
|||
}
|
||||
|
||||
/// The interface fonts: Inter (and JetBrains Mono for code) first, then egui's defaults, then
|
||||
/// the CJK and Arabic faces of the optional craft-fonts build input as the last fallback in
|
||||
/// every family. Without craft-fonts there is no Japanese, Chinese or Arabic face here.
|
||||
/// the CJK, Arabic and Telugu faces of the optional craft-fonts build input as the last fallback
|
||||
/// in every family. Without craft-fonts there is no Japanese, Chinese, Arabic or Telugu face here.
|
||||
pub fn font_definitions() -> FontDefinitions {
|
||||
font_definitions_for(false)
|
||||
}
|
||||
|
|
@ -191,8 +191,9 @@ pub fn font_definitions_for(prefer_hans: bool) -> FontDefinitions {
|
|||
fonts.families.entry(family).or_default().push(name.clone());
|
||||
}
|
||||
}
|
||||
// Arabic-script faces (file names, document titles) after the CJK ones; the ranges don't overlap.
|
||||
for face in pdfcraft_fonts::ui_arabic_fonts() {
|
||||
// Arabic-script faces (file names, document titles) and Telugu faces (the Telugu interface,
|
||||
// file names) after the CJK ones; the ranges don't overlap.
|
||||
for face in pdfcraft_fonts::ui_arabic_fonts().into_iter().chain(pdfcraft_fonts::ui_telugu_fonts()) {
|
||||
let name = face.name();
|
||||
if !fonts.font_data.contains_key(&name) {
|
||||
add(&mut fonts, &name, face.bytes);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use pdfcraft_ui_egui::theme;
|
|||
const JAPANESE: &str = "日本語の文字";
|
||||
const CHINESE: &str = "简体中文欢迎";
|
||||
const ARABIC: &str = "واحد اثنين";
|
||||
const TELUGU: &str = "తెలుగు ఫైల్";
|
||||
|
||||
fn families() -> Vec<FontId> {
|
||||
vec![FontId::proportional(13.0), FontId::monospace(13.0), theme::medium(13.0), theme::semibold(17.0)]
|
||||
|
|
@ -95,6 +96,34 @@ fn arabic_ui_text_uses_craft_fonts() {
|
|||
}
|
||||
}
|
||||
|
||||
/// Built with a craft-fonts Telugu face, Telugu text has real glyphs in every family, from a
|
||||
/// face placed after the app's own fonts, and is shaped: the conjunct క్ష is narrower than క్
|
||||
/// and ష held apart by a zero-width non-joiner.
|
||||
#[test]
|
||||
fn telugu_ui_text_uses_craft_fonts() {
|
||||
let telugu: Vec<String> = pdfcraft_fonts::CRAFT_FONTS.iter().filter(|f| f.covers("Telu")).map(|f| f.name()).collect();
|
||||
if telugu.is_empty() {
|
||||
eprintln!("skipping telugu_ui_text_uses_craft_fonts: no Telu face bundled (set CRAFT_FONTS_DIR with a Telugu face to run it)");
|
||||
return;
|
||||
}
|
||||
let defs = theme::font_definitions();
|
||||
for family in [FontFamily::Proportional, FontFamily::Monospace] {
|
||||
let stack = &defs.families[&family];
|
||||
let first_te = stack.iter().position(|n| telugu.contains(n)).expect("a Telu face is a fallback");
|
||||
let own = stack.iter().position(|n| n == "Inter" || n == "JetBrainsMono").expect("the app's own font");
|
||||
assert!(own < first_te, "{family:?}: {stack:?}");
|
||||
}
|
||||
let mut fonts = Fonts::new(TextOptions::default(), defs);
|
||||
for id in families() {
|
||||
assert!(fonts.has_glyphs(&id, TELUGU), "{id:?} lacks {TELUGU}");
|
||||
}
|
||||
let joined = layout_widths(&mut fonts, "క్ష");
|
||||
let apart = layout_widths(&mut fonts, "క్\u{200c}ష");
|
||||
for (j, a) in joined.iter().zip(&apart) {
|
||||
assert!(j < a, "క్ష is one conjunct, narrower than క్ష: {j} vs {a}");
|
||||
}
|
||||
}
|
||||
|
||||
/// On a machine with a suitable installed font, the installed definitions end every family
|
||||
/// with it and Arabic text has glyphs; the embedded-only definitions never name it.
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Interface language
|
||||
|
||||
Choose **Menu > Edit > Preferences…** (Command-comma on macOS, Ctrl-comma elsewhere) **> Interface language** and select **Auto**, **English**, **日本語**, **简体中文**, **繁體中文**, **Čeština**, **Português (Brasil)**, **Español** or **Français**. 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)**, **Español**, **Français** or **తెలుగు**. The change applies immediately and persists between launches. Command ids, document contents and file names are unchanged.
|
||||
|
||||
**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, 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. 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. 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, 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. 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. 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`:
|
||||
|
||||
|
|
@ -10,11 +10,11 @@ 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`, `es`, `fr`), in any case. `ui.state` reports `language` as written in the preferences (`auto`, `en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `es` or `fr`). 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`, `es`, `fr`, `te`), in any case. `ui.state` reports `language` as written in the preferences (`auto`, `en`, `ja`, `zh-hans`, `zh-hant`, `cs`, `pt-br`, `es`, `fr` or `te`). 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, Spanish and French translate them (about 1,800 entries each); Czech and Brazilian Portuguese cover the menus so far. 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.
|
||||
Dialogs, panels, menus, notices and history labels go through the catalog (`tl!`). Japanese, Traditional Chinese, Simplified Chinese, Spanish, French and Telugu translate them (about 1,800 entries each); Czech and Brazilian Portuguese cover the menus so far. 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.
|
||||
|
||||
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). Czech, Brazilian Portuguese, Spanish and French need only the bundled Latin faces (tested in `tests/fonts.rs`).
|
||||
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). Czech, Brazilian Portuguese, Spanish and French need only the bundled Latin faces (tested in `tests/fonts.rs`). 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.
|
||||
|
||||
There is no right-to-left interface language yet. File names and document titles in Arabic or Hebrew are drawn with a craft-fonts `Arab` face when the build input has one; on desktop, a character no embedded face has is drawn with one font already installed on the machine (`PDFCRAFT_SYSTEM_FONTS=0` turns this off). The tab strip, the recent-files list and Properties show such names in display order; other places (bookmarks, comments, search results) have the glyphs but may order mixed-direction text wrongly.
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ Simplified Chinese and French coverage tests, following PhotoCraft's complete-ca
|
|||
PdfCraft uses the same system as PhotoCraft (`crates/ui-egui/src/i18n/`):
|
||||
|
||||
- Strings in the code stay in English and serve as lookup keys. At draw time, `tl!("Save")` returns the text in the current language, and falls back to English when there is no entry.
|
||||
- Each language has a catalog, `crates/ui-egui/src/i18n/<code>.tsv`, with one entry per line: `context<TAB>English<TAB>translation`. An empty context marks a plain string. `@id` keys an entry by command id (for example `file.saveAs`), so one menu item can read differently from another with the same English label. `@plural` holds plural forms. Any other context disambiguates an English word with several meanings (`tr_ctx`). The header of `ja.tsv` documents the format and escapes.
|
||||
- Each language has a catalog, `crates/ui-egui/src/i18n/<code>.tsv`, with one entry per line: `context<TAB>English<TAB>translation`. An empty context marks a plain string. `@id` keys an entry by command id (for example `file.saveAs`), so one menu item can read differently from another with the same English label. `@plural` holds plural forms. Any other context disambiguates an English word with several meanings (`tl_ctx!` in UI code, for example the signature pad's "Type" button against the "Type" column). The header of `ja.tsv` documents the format and escapes.
|
||||
- Placeholders (`{name}`) must appear in both columns. A trailing `…` must be kept. `i18n::fmt` fills placeholders in one pass, so a value such as a file name containing `{n}` is inserted as written.
|
||||
- Catalogs are validated strictly: unknown escapes, unknown `@` contexts, placeholder or ellipsis mismatches, wrong plural form counts and duplicates are errors. The tests require the bundled catalogs to have none; at run time a bad line is skipped (and logged) and that string shows in English.
|
||||
- Catalogs are assets: each `.tsv` has an `ATTRIBUTION.toml` entry with `kind = "translation"` and its SHA-256, checked by `cargo xtask assets`.
|
||||
|
|
|
|||
|
|
@ -9806,8 +9806,8 @@ milestone = "M14"
|
|||
status = "partial"
|
||||
commands = ["app.preferences"]
|
||||
tools = []
|
||||
evidence = ["crates/ui-egui/src/i18n/mod.rs::windows_display_language_resolves_chinese_scripts", "crates/ui-egui/src/i18n/mod.rs::windows_display_language_query_returns_a_locale_tag", "crates/ui-egui/src/i18n/mod.rs::language_persists_and_invalid_input_keeps_current_language", "crates/ui-egui/src/i18n/mod.rs::bundled_catalogs_are_consistent", "crates/ui-egui/src/i18n/mod.rs::japanese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_history_and_diagnostics_preserve_user_values", "crates/ui-egui/tests/control.rs::simplified_chinese_signature_prompts_and_errors_keep_document_state", "crates/ui-egui/tests/control.rs::language_switch_preserves_document_and_command_ids", "crates/ui-egui/tests/control.rs::japanese_controls_and_search_keep_command_ids", "crates/ui-egui/tests/control.rs::japanese_dialogs_errors_and_custom_action_names", "crates/ui-egui/src/i18n/mod.rs::french_is_registered", "crates/ui-egui/src/i18n/mod.rs::french_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::french_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::french_history_and_diagnostics_preserve_user_values"]
|
||||
notes = "Preferences switches Auto (system language)/English/Japanese/Simplified Chinese/Traditional Chinese/Czech/Brazilian Portuguese/Spanish/French and persists; ui.set language auto|en|ja|zh-hans|zh-hant|cs|pt-br|es|fr and ui.state expose it. TSV catalogs per language (same system as PhotoCraft); every dialog, panel and notice goes through tl!. Japanese, Traditional Chinese, Simplified Chinese, Spanish and French cover commands, dialogs, panels, shortcuts and history labels; Czech and Brazilian Portuguese the menus. French locales (fr, fr_FR, fr_CA, fr_BE) select the French catalog. Engine/OS error details stay untranslated. Auto detects the Windows display language after locale environment overrides, with Chinese script/region mapping and English fallback. Missing: Czech and Brazilian Portuguese dialogs, Chinese font delivery, a full localization audit."
|
||||
evidence = ["crates/ui-egui/src/i18n/mod.rs::windows_display_language_resolves_chinese_scripts", "crates/ui-egui/src/i18n/mod.rs::windows_display_language_query_returns_a_locale_tag", "crates/ui-egui/src/i18n/mod.rs::language_persists_and_invalid_input_keeps_current_language", "crates/ui-egui/src/i18n/mod.rs::bundled_catalogs_are_consistent", "crates/ui-egui/src/i18n/mod.rs::japanese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::simplified_chinese_history_and_diagnostics_preserve_user_values", "crates/ui-egui/tests/control.rs::simplified_chinese_signature_prompts_and_errors_keep_document_state", "crates/ui-egui/tests/control.rs::language_switch_preserves_document_and_command_ids", "crates/ui-egui/tests/control.rs::japanese_controls_and_search_keep_command_ids", "crates/ui-egui/tests/control.rs::japanese_dialogs_errors_and_custom_action_names", "crates/ui-egui/src/i18n/mod.rs::french_is_registered", "crates/ui-egui/src/i18n/mod.rs::french_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::french_covers_ui_literals", "crates/ui-egui/src/i18n/mod.rs::french_history_and_diagnostics_preserve_user_values", "crates/ui-egui/src/i18n/mod.rs::telugu_covers_commands_and_catalogue", "crates/ui-egui/src/i18n/mod.rs::telugu_labels_have_glyphs"]
|
||||
notes = "Preferences switches Auto (system language)/English/Japanese/Simplified Chinese/Traditional Chinese/Czech/Brazilian Portuguese/Spanish/French/Telugu and persists; ui.set language auto|en|ja|zh-hans|zh-hant|cs|pt-br|es|fr|te and ui.state expose it. TSV catalogs per language (same system as PhotoCraft); every dialog, panel and notice goes through tl!. Japanese, Traditional Chinese, Simplified Chinese, Spanish and French cover commands, dialogs, panels, shortcuts and history labels; Czech and Brazilian Portuguese the menus. French locales (fr, fr_FR, fr_CA, fr_BE) select the French catalog. Engine/OS error details stay untranslated. Auto detects the Windows display language after locale environment overrides, with Chinese script/region mapping and English fallback. Missing: Czech and Brazilian Portuguese dialogs, Chinese and Telugu font delivery in releases, a full localization audit."
|
||||
|
||||
[[feature]]
|
||||
id = "misc.prefs-reading"
|
||||
|
|
|
|||
Loading…
Reference in New Issue