mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
M14: complete the Czech (cs) interface catalog (#1066)
* M14: complete the Czech (cs) interface catalog
Czech covered the menus and a few later additions (167 keys). The catalog
now has every key the code uses (2,194): Home, All tools, panels, dialogs,
notices, diagnostics and history labels. Translated clean-room from the
meaning of the English labels; the 167 existing entries are unchanged.
- The @plural messages use the three Czech forms (one | few | other);
other counted messages use count-neutral wording ("Vybráno stránek: {n}").
- "calendar date" months are genitive ("9. října 2026"); "Undo {action}"
and "Redo {action}" are whole phrases ("Zpět: {action}").
- Labels the code joins with a leading or trailing space keep it.
- The key-name entries (context "key") are kept as they were.
Tests that relied on the incomplete catalog:
- czech_covers_every_menu_label keeps a short list of strings spelled the
same in Czech (product and format names, Text, Index, Metadata, Model,
templates such as "{0}: {1}"), compares leading and trailing spaces with
the source instead of forbidding them, and requires a catalog without
parse errors.
- date_format.rs expected the English fallback "Same as interface
(Čeština)" under the Czech interface; that label is now translated.
ATTRIBUTION.toml: new SHA-256 of cs.tsv.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M14: check the Czech catalog like the other complete catalogs
Czech now has the same coverage gates as the complete catalogs:
- czech_covers_commands_and_catalogue: every registered command, menu
title and All tools group, section and item;
- czech_covers_ui_literals: every tl!("…") literal in the UI source;
- czech_plurals_dates_and_history_labels: 1/3/5 pages and 0 fields, the
genitive date ("9. října 2026"), and Undo/Redo/Change history labels
that keep captured names.
cs also joins the lists in missing_ocr_models_messages_are_translated and
bulk_field_properties_labels_are_in_every_full_catalog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n (cs): terminology review fixes
An independent review of the Czech catalog found 5 entries with a wrong
or misleading meaning and 44 that read unnaturally or inconsistently.
Meaning:
- US formats say so: "Čísla sociálního zabezpečení (USA)" (not the Czech
birth number) and "Telefonní číslo (USA)" for the field format.
- Mouse Exit/Enter triggers: "Odjetí kurzoru" / "Najetí kurzoru".
- Rich text in form fields is not RTF; Form XObjects are not forms.
Wording and consistency:
- Home: pinning folders refers to "úvodní obrazovka" (also in the existing
"Pin folder to Home…"); "úvodní stránka" reads as the first page.
- Callout is "Popisek se šipkou"; Self-signed is "Samopodepsaný" (fixes
the agreement in "Samopodepsaná ID").
- Reset (forms), Apply redactions, guided actions, the radio button and
checkbox styles, line endings (also the existing arrow entries), border
styles, "Omezit na … znaků", "Rozlišovat velikost písmen", grammar in
several messages, and the About window wording shared with the other
Crafting Apps.
History labels for the Creator and Producer document information keys
use the Document Properties wording: "Aplikace" and "Producent PDF".
ATTRIBUTION.toml: new SHA-256 of cs.tsv.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* contributors: add my own people.toml entry
Own entry only, as AGENTS.md describes: credited by the name on my public
GitHub profile (sync_github_name), otherwise by @username.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n (cs): translate "Offset from centre" (watermark offset, #1076)
New upstream string added after the catalog was last synced.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* i18n (cs): follow the reworded Combine files strings (#1032)
Upstream generalised the Combine files dialog (images and text files, a
prompt for files that can't be read) and replaced six strings. The old
Czech entries are dropped and the ten new ones translated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* i18n (cs): translate the new Enhance page / camera image strings (#1027)
Four strings added upstream after the catalog was last synced.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* i18n (cs): margin labels Top/Bottom read Nahoře/Dole
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: translate the Digital ID errors, the Signatures panel and key usages
The digital-ID dialog printed its validation errors, the Signatures panel
headings ("Rev. n: Signed by ...", "Unsigned signature field"), the
certificate issuer line of the signed-document bar and the nine key-usage
names in English. Translate them at the point where they are drawn, the way
title() and notify_fmt() already do, so engine and OS error texts that
share those paths pass through unchanged. Czech catalog entries added; other
languages fall back to English.
The signature appearance preview is left alone on purpose: it mirrors the
Helvetica text the engine bakes into the PDF ("Digitally signed by", "Reason:"),
which is document content, not interface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: translate form action, print, panel layout, rendering and OCR language texts
Route the remaining plain-English messages through the translation lookup
where they are drawn: the Add-action validation hints, the Cut and stack
print error (preview and notice), the two panel-layout notices, the
"Rendering..." placeholders on unrendered pages, the page number under the
Page Boxes preview (reuses "Page {label}") and the OCR language name.
The messages of the docking validator and the engine and OS errors stay
English: they report internal invariants or text this code does not own.
"My stamps" stays too: it is stored as the category of the user's stamps, so
translating it would split a library when the language changes. The dynamic
stamp chip text mirrors the English text baked into the stamp and is left as is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n (cs): translate the Appearance preferences and the wheel scroll speed
The strings added upstream with the Appearance section and the mouse wheel
scroll speed setting were missing from the Czech catalog, so
czech_covers_ui_literals failed after the merge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: use plural forms for counts built in code
The saved notice after Optimize ("3 images optimized", "2 items discarded"),
the pixel sizes in Export all images, the exact size in bytes (human_size and
the document properties) and the history label of a multi-field delete were
assembled from English words and a trailing "s". Use trn() for them, with
@plural entries in the Czech catalog (1 | 2-4 | 5+), and translate the
"Delete N fields" history label in action_label(). English output is unchanged;
other languages without a plural entry keep the English wording.
The Czech plural test now also checks bytes, optimized images and the
Delete N fields label.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: translate the text-box and organize toolbar tooltips
"Done adding text" and "Discard this text (Esc)" (the buttons under a new
text box) and the Rotate tooltips of the organize toolbar were passed to
icons::button as plain English. Translate them at the call site, as the
other icon buttons do.
The two text-box tooltips are tl!() literals, so every complete catalog
(ar, bg, cs, de, fr, hu, it, ja, pt-br, ru, uk, zh-hans) has to carry them;
the entries for the languages other than Czech are AI drafts that want a
native speaker's look. The Rotate tooltips already had translations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Losminek
Brandon Thomas
parent
0f7ff1ce30
commit
de8e0176ed
+12
-12
@@ -19,7 +19,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 = "b12eb7c9c8e540b2c1294966f4cb8c885d32763204f8fdb4d0963d275c282a10"
|
||||
sha256 = "1863437c886142b2bccce3f44a538ae80d0ceea81074b84dfdfe8d9a4a4227ab"
|
||||
|
||||
[[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 = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "18f725656c3b560b3b84b908d017a8ccc1b01670cfeea4655d21fd3b41bc59a4"
|
||||
sha256 = "5ae2e315a9b99b4c0dfaf7be019c4d430ace17b4fbbfea5a2779b3e972846a21"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pt-br.tsv"
|
||||
@@ -52,7 +52,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 = "c3bc62f928891ee565f2b53a29e4efe6d7f4f83f44cd62b74227a17171b8d82c"
|
||||
sha256 = "8b9e22fa25339e4aedb0542b8bf6f68dfd97bc3634105199e1951475e30eed53"
|
||||
|
||||
[[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 = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "8956e77078a2dd55ee7fb9fe74d178753154bd4ea2ef98fecba8ed7553411c73"
|
||||
sha256 = "fb9c91a98c6975bc24249e8ae750fd46b9245ac5d2d7381f1e21319b9adbcaac"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/de.tsv"
|
||||
@@ -85,7 +85,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 = "4aa8784d99113ff58876e34469d6d2edadb6f226c748b153a11c97fac0eb5742"
|
||||
sha256 = "f36aefae1a1ba6d576a4647fbfdd65b9b5086bfb266ea260e513244adc7af05a"
|
||||
|
||||
[[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 = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "5c6da9eb74460a7ba67fa53866760644f258054a05cef7df4aedd60cd51928c5"
|
||||
sha256 = "c4d477cae250ff2cd16f5d1203b4fa4c7c6962efa158a4e501c4553d3709439a"
|
||||
|
||||
[[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 = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "25fd7dc03149033b02c4e655da84380dd0d90c41df556183c98285f056db4c82"
|
||||
sha256 = "bada9b6f0b3775e4dab1213d9759070f7551d8a6142de10159268c88a04d3f0c"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hu.tsv"
|
||||
@@ -140,7 +140,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 = "4045450381525f0acb0a3895879cd369f0c4de7a502790e8a4609c3333408931"
|
||||
sha256 = "efb75d8575df9e1dd4610602f20545b3a14a3a0814060efc27eb7cab591ccccb"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/bg.tsv"
|
||||
@@ -151,7 +151,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 = "1c747fb3a4cd1e66dd2868349d8df02385c1b825928bfca01b5059c7de9bdaba"
|
||||
sha256 = "ca305b188abc8d9c261901dde10f7cd8518520983fdf88d2358374b352ad3d5a"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ja.tsv"
|
||||
@@ -162,7 +162,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 = "40919a7582812d7231cd2c0cc712b57f066b8e1af6fc43281ec8ce6df4f60cca"
|
||||
sha256 = "f004b06f4e7eb8f4a99e22e1abfd97c95795b2215080e4008574522e54eda74c"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/uk.tsv"
|
||||
@@ -173,7 +173,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 = "f61852f58a28ae3d8a3a59219b91ef79842c7bc7b01ea451aca6110f54939755"
|
||||
sha256 = "cd9ecaf661533cbd3b055cfd3994602f6739c83061d1e225ba43ee6f229068cf"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ar.tsv"
|
||||
@@ -184,7 +184,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 = "0c0a492b757d3a5c488b2ef4e48386bf7528083c85805bcfd0e7e06fe4e9092f"
|
||||
sha256 = "a8a66390b2d80b4708c4f854318b923eef7f0f5932439fb5ef25f2651980f24e"
|
||||
|
||||
[[asset]]
|
||||
path = "assets/icons/accessibility.svg"
|
||||
|
||||
@@ -100,3 +100,6 @@ display_name = "Mustafa Hadidi"
|
||||
real_name = "Lucas"
|
||||
display_name = "Lucas"
|
||||
sync_github_name = true # use the name on your public GitHub profile as display name
|
||||
|
||||
[people.Losminek]
|
||||
sync_github_name = true
|
||||
|
||||
@@ -2360,7 +2360,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
let now = ui.input(|inp| inp.time);
|
||||
let since = *view.waiting_since.entry(i).or_insert(now);
|
||||
let msg = if now - since > 6.0 { "Still rendering — this page is unusually complex…" } else { "Rendering…" };
|
||||
painter.text(r.center(), Align2::CENTER_CENTER, msg, theme::regular(12.0), t.text_faint);
|
||||
painter.text(r.center(), Align2::CENTER_CENTER, tl!(msg), theme::regular(12.0), t.text_faint);
|
||||
}
|
||||
}
|
||||
if tiled && r.intersects(visible) {
|
||||
|
||||
@@ -364,8 +364,8 @@ pub(crate) fn editor(ctx: &egui::Context, view: &mut DocView, info: &DocInfo, ad
|
||||
.show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing = egui::vec2(2.0, 2.0);
|
||||
ui.horizontal(|ui| {
|
||||
done = crate::icons::button(ui, "check", 24.0, false, "Done adding text").clicked();
|
||||
cancel = crate::icons::button(ui, "x", 24.0, false, "Discard this text (Esc)").clicked();
|
||||
done = crate::icons::button(ui, "check", 24.0, false, tl!("Done adding text")).clicked();
|
||||
cancel = crate::icons::button(ui, "x", 24.0, false, tl!("Discard this text (Esc)")).clicked();
|
||||
});
|
||||
})
|
||||
.response
|
||||
|
||||
@@ -321,7 +321,7 @@ fn dialogs(app: &mut PdfCraftApp, ctx: &egui::Context) {
|
||||
PropsTab::Advanced => {
|
||||
row(ui, "PDF version", i.pdf_version.clone());
|
||||
row(ui, "Location", crate::bidi::visual(doc.path.as_deref().unwrap_or_default()).into_owned());
|
||||
row(ui, "File size", format!("{} ({} bytes)", human_size(i.file_size), i.file_size));
|
||||
row(ui, "File size", format!("{} ({})", human_size(i.file_size), crate::panels::byte_count(i.file_size)));
|
||||
let p = &i.pages[0];
|
||||
row(ui, "Page size", units.size(f64::from(p.width), f64::from(p.height)));
|
||||
row(ui, "Number of pages", i.pages.len().to_string());
|
||||
|
||||
@@ -348,7 +348,7 @@ pub fn show(app: &mut crate::PdfCraftApp, ui: &mut egui::Ui) {
|
||||
let t = crate::theme::Tokens::get(ui.ctx());
|
||||
let mut workspace = std::mem::take(&mut app.docking);
|
||||
if let Err(error) = workspace.validate().and_then(|()| sync(&mut workspace, app)) {
|
||||
app.notify(format!("Panel layout could not be restored: {error}"));
|
||||
app.notify_fmt("Panel layout could not be restored: {error}", &[("error", &error)]);
|
||||
workspace = Workspace::default();
|
||||
let _ = sync(&mut workspace, app);
|
||||
}
|
||||
@@ -411,7 +411,7 @@ pub fn show(app: &mut crate::PdfCraftApp, ui: &mut egui::Ui) {
|
||||
if let Err(error) =
|
||||
serde_json::to_value(action).map_err(|error| error.to_string()).and_then(|action| command(app, false, &json!({"action": action})))
|
||||
{
|
||||
app.notify(format!("Panel move was not applied: {error}"));
|
||||
app.notify_fmt("Panel move was not applied: {error}", &[("error", &error)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,9 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens, kind: E
|
||||
} else if kind == ExportKind::AllImages {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Exclude images smaller than"));
|
||||
let label = move |n: u32| if n == 0 { tl!("No limit").to_string() } else { format!("{n} pixels") };
|
||||
let label = move |n: u32| {
|
||||
if n == 0 { tl!("No limit").to_string() } else { crate::i18n::trn(crate::i18n::current(), u64::from(n), "{n} pixel", "{n} pixels") }
|
||||
};
|
||||
egui::ComboBox::from_id_salt("export-min").selected_text(label(d.min_side)).show_ui(ui, |ui| {
|
||||
for n in [0, 16, 32, 64, 128, 256] {
|
||||
ui.selectable_value(&mut d.min_side, n, label(n));
|
||||
|
||||
@@ -2261,6 +2261,8 @@ scroll speed Fast سريعة
|
||||
Enhance camera image تحسين صورة الكاميرا
|
||||
Drag the corners onto the document's edges, then choose Enhance page. اسحب الزوايا إلى حواف المستند، ثم اختر تحسين الصفحة.
|
||||
Enhance page تحسين الصفحة
|
||||
Done adding text إنهاء إضافة النص
|
||||
Discard this text (Esc) تجاهل هذا النص (Esc)
|
||||
Clean-up تنظيف
|
||||
Original الأصل
|
||||
Greyscale تدرج رمادي
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2203,6 +2203,8 @@ scroll speed Fast Висока
|
||||
Enhance camera image Подобри снимка от камера
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Плъзнете ъглите до краищата на документа, след това изберете „Подобри страницата“.
|
||||
Enhance page Подобри страницата
|
||||
Done adding text Готово с добавянето на текст
|
||||
Discard this text (Esc) Отхвърли този текст (Esc)
|
||||
Clean-up Почистване
|
||||
Original Оригинал
|
||||
Greyscale Нива на сивото
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
+2141
-9
File diff suppressed because it is too large
Load Diff
@@ -2241,6 +2241,8 @@ scroll speed Fast Schnell
|
||||
Enhance camera image Kamerafoto verbessern
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Ziehen Sie die Ecken auf die Kanten des Dokuments und wählen Sie dann Seite verbessern.
|
||||
Enhance page Seite verbessern
|
||||
Done adding text Texteingabe abschließen
|
||||
Discard this text (Esc) Diesen Text verwerfen (Esc)
|
||||
Clean-up Bereinigung
|
||||
Original Original
|
||||
Greyscale Graustufen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
@@ -2240,6 +2240,8 @@ scroll speed Fast Rapide
|
||||
Enhance camera image Améliorer la photo d'un document
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Faites glisser les coins sur les bords du document, puis choisissez Améliorer la page.
|
||||
Enhance page Améliorer la page
|
||||
Done adding text Terminer l'ajout de texte
|
||||
Discard this text (Esc) Abandonner ce texte (Échap)
|
||||
Clean-up Nettoyage
|
||||
Original Original
|
||||
Greyscale Niveaux de gris
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
@@ -2239,6 +2239,8 @@ scroll speed Fast Gyors
|
||||
Enhance camera image Kamerakép javítása
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Húzza a sarkokat a dokumentum széleire, majd válassza az Oldal javítása lehetőséget.
|
||||
Enhance page Oldal javítása
|
||||
Done adding text Szöveg hozzáadásának befejezése
|
||||
Discard this text (Esc) Szöveg elvetése (Esc)
|
||||
Clean-up Tisztítás
|
||||
Original Eredeti
|
||||
Greyscale Szürkeárnyalatos
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
@@ -2248,6 +2248,8 @@ scroll speed Fast Veloce
|
||||
Enhance camera image Migliora foto della fotocamera
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Trascina gli angoli sui bordi del documento, poi scegli Migliora la pagina.
|
||||
Enhance page Migliora la pagina
|
||||
Done adding text Termina l'aggiunta del testo
|
||||
Discard this text (Esc) Scarta questo testo (Esc)
|
||||
Clean-up Pulizia
|
||||
Original Originale
|
||||
Greyscale Scala di grigi
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2240,6 +2240,8 @@ scroll speed Fast 速い
|
||||
Enhance camera image カメラ画像を補正
|
||||
Drag the corners onto the document's edges, then choose Enhance page. 四隅を文書の端までドラッグしてから、「ページを補正」を選択します。
|
||||
Enhance page ページを補正
|
||||
Done adding text テキストの追加を完了
|
||||
Discard this text (Esc) このテキストを破棄(Esc)
|
||||
Clean-up クリーンアップ
|
||||
Original オリジナル
|
||||
Greyscale グレースケール
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -472,6 +472,9 @@ pub fn action_label(text: &str) -> String {
|
||||
{
|
||||
return fmt(t("Change {key}"), &[("key", t(key))]);
|
||||
}
|
||||
if let Some(n) = text.strip_prefix("Delete ").and_then(|rest| rest.strip_suffix(" fields")).and_then(|n| n.parse::<u64>().ok()) {
|
||||
return trn(current(), n, "Delete {n} field", "Delete {n} fields");
|
||||
}
|
||||
t(text).to_owned()
|
||||
}
|
||||
|
||||
@@ -760,7 +763,7 @@ mod tests {
|
||||
let error = pdfcraft_engine::ocr::OcrError::NoModels.to_string();
|
||||
let notice = "Text recognition isn't installed: its model files are missing. Reinstall PdfCraft, or set PDFCRAFT_MODELS to the folder that holds them.";
|
||||
assert!(include_str!("../ocr_ui.rs").contains(notice), "keep in step with ocr_ui.rs");
|
||||
for code in ["bg", "de", "es", "fr", "hu", "ja", "ru", "te", "uk", "zh-hans", "zh-hant"] {
|
||||
for code in ["bg", "cs", "de", "es", "fr", "hu", "ja", "ru", "te", "uk", "zh-hans", "zh-hant"] {
|
||||
let lang = Lang::from_code(code).expect("registered");
|
||||
assert!(has(lang, notice) && has(lang, &error), "{code}");
|
||||
}
|
||||
@@ -823,7 +826,7 @@ mod tests {
|
||||
labels.insert(label.to_string());
|
||||
}
|
||||
assert!(labels.len() > 20 && labels.contains("Comb of characters"), "the scan found the dialog's labels: {labels:?}");
|
||||
for code in ["bg", "de", "es", "fr", "hu", "it", "ja", "pt-br", "ru", "te", "uk", "zh-hans", "zh-hant"] {
|
||||
for code in ["bg", "cs", "de", "es", "fr", "hu", "it", "ja", "pt-br", "ru", "te", "uk", "zh-hans", "zh-hant"] {
|
||||
let lang = Lang::from_code(code).expect("registered");
|
||||
let missing: Vec<_> = labels.iter().filter(|l| !has(lang, l)).collect();
|
||||
assert!(missing.is_empty(), "{code} lacks Shared Field Properties labels: {missing:#?}");
|
||||
@@ -1275,13 +1278,38 @@ mod tests {
|
||||
assert!(has(cs, menu), "Czech lacks the menu title {menu:?}");
|
||||
assert!(has(cs, spec.label), "Czech lacks the {menu} menu label {:?} ({})", spec.label, spec.id);
|
||||
}
|
||||
const KEEP_AS_IS: &[&str] = &["OK"];
|
||||
let (entries, _) = parse_entries(cs.0.source, cs.0.plural_forms());
|
||||
// Spelled the same in Czech: product and format names, abbreviations, key caps, words
|
||||
// Czech shares with English, and templates with no English words.
|
||||
const KEEP_AS_IS: &[&str] = &[
|
||||
"OK",
|
||||
"PDF/A…",
|
||||
"Microsoft Word (.docx)",
|
||||
"PostScript / EPS",
|
||||
"Home / End",
|
||||
"JavaScript",
|
||||
"ZIP",
|
||||
"Text",
|
||||
"Index",
|
||||
"Metadata",
|
||||
"Model",
|
||||
"{field}: {e}",
|
||||
"{0}: {1}",
|
||||
"{month} {y}",
|
||||
"+LOC",
|
||||
"−LOC",
|
||||
"ΔLOC",
|
||||
"+Bin",
|
||||
"−Bin",
|
||||
];
|
||||
let (entries, errors) = parse_entries(cs.0.source, cs.0.plural_forms());
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
// The code joins some labels with a space at either end: keep exactly the source's.
|
||||
let edges = |s: &str| (s.len() - s.trim_start().len(), s.len() - s.trim_end().len());
|
||||
for e in &entries {
|
||||
let key_cap = e.context == "key" && KEY_CAP_NAMES.contains(&e.source.as_str());
|
||||
assert!(e.source != e.translation || KEEP_AS_IS.contains(&e.source.as_str()) || key_cap, "{:?} is untranslated", e.source);
|
||||
assert!(!e.translation.contains("..."), "use … rather than three dots: {:?}", e.translation);
|
||||
assert_eq!(e.translation.trim(), e.translation, "stray whitespace: {:?}", e.translation);
|
||||
assert_eq!(edges(&e.translation), edges(&e.source), "stray whitespace: {:?}", e.translation);
|
||||
}
|
||||
let mut app = crate::PdfCraftApp::default();
|
||||
app.set_option("language", "cs").unwrap();
|
||||
@@ -1292,6 +1320,62 @@ mod tests {
|
||||
assert_eq!(restored.language, "cs");
|
||||
}
|
||||
|
||||
/// Czech translates every registered command, menu and All tools label.
|
||||
#[test]
|
||||
fn czech_covers_commands_and_catalogue() {
|
||||
let cs = Lang::from_code("cs").expect("cs registered");
|
||||
for command in pdfcraft_engine::commands::COMMANDS {
|
||||
assert!(has(cs, command.label), "missing command: {}", command.label);
|
||||
if let Some(menu) = command.menu {
|
||||
assert!(has(cs, menu), "missing menu: {menu}");
|
||||
}
|
||||
}
|
||||
for group in pdfcraft_engine::catalog::TOOL_GROUPS {
|
||||
assert!(has(cs, group.label), "missing group: {}", group.label);
|
||||
for section in group.sections {
|
||||
assert!(has(cs, section.title), "missing section: {}", section.title);
|
||||
for item in section.items {
|
||||
assert!(has(cs, item.label), "missing item: {}", item.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Czech translates every `tl!("…")` literal in the UI source.
|
||||
#[test]
|
||||
fn czech_covers_ui_literals() {
|
||||
let cs = Lang::from_code("cs").expect("cs registered");
|
||||
let literals = ui_literals();
|
||||
assert!(literals.len() > 900, "source scan found only {} literals", literals.len());
|
||||
let missing: Vec<_> = literals.iter().filter(|label| !has(cs, label)).collect();
|
||||
assert!(missing.is_empty(), "untranslated Czech UI literals: {missing:#?}");
|
||||
}
|
||||
|
||||
/// Czech counts take three forms (1; 2–4; 0 and 5+), dates use the genitive month after the
|
||||
/// day, and history labels keep captured names.
|
||||
#[test]
|
||||
fn czech_plurals_dates_and_history_labels() {
|
||||
let cs = Lang::from_code("cs").expect("cs registered");
|
||||
assert_eq!(trn(cs, 1, "{n} page", "{n} pages"), "1 stránka");
|
||||
assert_eq!(trn(cs, 3, "{n} page", "{n} pages"), "3 stránky");
|
||||
assert_eq!(trn(cs, 5, "{n} page", "{n} pages"), "5 stránek");
|
||||
assert_eq!(trn(cs, 0, "{n} field", "{n} fields"), "0 polí");
|
||||
assert_eq!(trn(cs, 1, "{n} byte", "{n} bytes"), "1 bajt");
|
||||
assert_eq!(trn(cs, 512, "{n} byte", "{n} bytes"), "512 bajtů");
|
||||
assert_eq!(trn(cs, 3, "{n} image optimized", "{n} images optimized"), "3 obrázky optimalizovány");
|
||||
assert_eq!(tr(cs, "October"), "Říjen");
|
||||
let month = tr_ctx(cs, "calendar date", "October");
|
||||
assert_eq!(fmt(tr(cs, "{month} {day}, {y}"), &[("month", month), ("day", "9"), ("y", "2026")]), "9. října 2026");
|
||||
set_current(cs);
|
||||
assert_eq!(command_label("Undo Insert pages from Zpráva {n}.pdf"), "Zpět: Vložit stránky ze souboru Zpráva {n}.pdf");
|
||||
assert_eq!(command_label("Redo Fill in Kontakt {key}"), "Znovu: Vyplnit Kontakt {key}");
|
||||
assert_eq!(action_label("Change Title"), "Změnit: Název");
|
||||
assert_eq!(action_label("Delete 3 fields"), "Odstranit 3 pole");
|
||||
assert_eq!(action_label("Delete 12 fields"), "Odstranit 12 polí");
|
||||
assert_eq!(fmt(t("{n} pages selected"), &[("n", "3")]), "Vybráno stránek: 3");
|
||||
set_current(Lang::EN);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simplified_chinese_is_registered() {
|
||||
let zh = Lang::from_code("zh-hans").expect("zh-hans registered");
|
||||
|
||||
@@ -2254,6 +2254,8 @@ scroll speed Fast Rápida
|
||||
Enhance camera image Aprimorar foto da câmera
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Arraste os cantos até as bordas do documento e escolha Aprimorar página.
|
||||
Enhance page Aprimorar página
|
||||
Done adding text Concluir a adição de texto
|
||||
Discard this text (Esc) Descartar este texto (Esc)
|
||||
Clean-up Limpeza
|
||||
Original Original
|
||||
Greyscale Escala de cinza
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2153,6 +2153,8 @@ scroll speed Fast Высокая
|
||||
Enhance camera image Улучшить снимок с камеры
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Перетащите углы к краям документа, затем выберите «Улучшить страницу».
|
||||
Enhance page Улучшить страницу
|
||||
Done adding text Завершить добавление текста
|
||||
Discard this text (Esc) Отклонить этот текст (Esc)
|
||||
Clean-up Очистка
|
||||
Original Оригинал
|
||||
Greyscale Оттенки серого
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2262,6 +2262,8 @@ scroll speed Fast Швидка
|
||||
Enhance camera image Поліпшити знімок з камери
|
||||
Drag the corners onto the document's edges, then choose Enhance page. Перетягніть кути до країв документа, потім виберіть «Поліпшити сторінку».
|
||||
Enhance page Поліпшити сторінку
|
||||
Done adding text Завершити додавання тексту
|
||||
Discard this text (Esc) Відкинути цей текст (Esc)
|
||||
Clean-up Очищення
|
||||
Original Оригінал
|
||||
Greyscale Відтінки сірого
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2204,6 +2204,8 @@ scroll speed Fast 快
|
||||
Enhance camera image 增强相机图像
|
||||
Drag the corners onto the document's edges, then choose Enhance page. 将四个角拖到文档边缘,然后选择“增强页面”。
|
||||
Enhance page 增强页面
|
||||
Done adding text 完成添加文本
|
||||
Discard this text (Esc) 放弃此文本 (Esc)
|
||||
Clean-up 清理
|
||||
Original 原始
|
||||
Greyscale 灰度
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -89,9 +89,9 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
||||
egui::Grid::new("ocr-settings").num_columns(2).spacing([12.0, 8.0]).show(ui, |ui| {
|
||||
ui.label(tl!("Document language"));
|
||||
let name = LANGUAGES.iter().find(|l| l.0 == d.language).map_or("English", |l| l.1);
|
||||
egui::ComboBox::from_id_salt("ocr-language").selected_text(name).width(220.0).show_ui(ui, |ui| {
|
||||
egui::ComboBox::from_id_salt("ocr-language").selected_text(tl!(name)).width(220.0).show_ui(ui, |ui| {
|
||||
for (code, name) in LANGUAGES {
|
||||
ui.selectable_value(&mut d.language, (*code).to_string(), *name);
|
||||
ui.selectable_value(&mut d.language, (*code).to_string(), tl!(*name));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
@@ -91,17 +91,15 @@ fn stage_label(stage: OptimizeStage) -> String {
|
||||
/// What the saved notice adds after the size: images optimized and items discarded.
|
||||
fn summary(r: &OptimizeReport) -> String {
|
||||
let o = &r.optimize;
|
||||
let lang = crate::i18n::current();
|
||||
let mut parts = Vec::new();
|
||||
if o.images_resampled + o.images_recompressed > 0 {
|
||||
parts.push(format!(
|
||||
"{} image{} optimized",
|
||||
o.images_resampled + o.images_recompressed,
|
||||
if o.images_resampled + o.images_recompressed == 1 { "" } else { "s" }
|
||||
));
|
||||
let images = o.images_resampled + o.images_recompressed;
|
||||
if images > 0 {
|
||||
parts.push(crate::i18n::trn(lang, images as u64, "{n} image optimized", "{n} images optimized"));
|
||||
}
|
||||
let discarded: usize = r.discarded.iter().map(|(_, n)| n).sum();
|
||||
if discarded > 0 {
|
||||
parts.push(format!("{discarded} item{} discarded", if discarded == 1 { "" } else { "s" }));
|
||||
parts.push(crate::i18n::trn(lang, discarded as u64, "{n} item discarded", "{n} items discarded"));
|
||||
}
|
||||
if parts.is_empty() { String::new() } else { format!("; {}", parts.join(", ")) }
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ pub(crate) fn body(ui: &mut egui::Ui, app: &mut PdfCraftApp, t: &Tokens) -> (boo
|
||||
ui.painter().text(
|
||||
page.center_bottom() + vec2(0.0, 6.0),
|
||||
egui::Align2::CENTER_TOP,
|
||||
format!("Page {}", current + 1),
|
||||
crate::i18n::fmt(tl!("Page {label}"), &[("label", &(current + 1).to_string())]),
|
||||
theme::regular(11.0),
|
||||
t.text_faint,
|
||||
);
|
||||
|
||||
@@ -1282,10 +1282,15 @@ pub fn human_size(n: usize) -> String {
|
||||
match n {
|
||||
n if n >= 1 << 20 => format!("{:.1} MB", n as f64 / (1u64 << 20) as f64),
|
||||
n if n >= 1 << 10 => format!("{:.1} KB", n as f64 / (1u64 << 10) as f64),
|
||||
n => format!("{n} bytes"),
|
||||
n => byte_count(n),
|
||||
}
|
||||
}
|
||||
|
||||
/// An exact size in bytes, with the count's own plural form.
|
||||
pub fn byte_count(n: usize) -> String {
|
||||
crate::i18n::trn(crate::i18n::current(), n as u64, "{n} byte", "{n} bytes")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod page_moves {
|
||||
use super::{moved_index, panel_gap};
|
||||
|
||||
@@ -1481,7 +1481,7 @@ fn actions_tab(ui: &mut egui::Ui, d: &mut FieldDraft, t: &crate::theme::Tokens)
|
||||
}
|
||||
}
|
||||
if let Some(e) = error {
|
||||
ui.label(egui::RichText::new(e).small().color(t.text_muted));
|
||||
ui.label(egui::RichText::new(tl!(&e)).small().color(t.text_muted));
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
ui.label(egui::RichText::new(tl!("Actions")).font(theme::semibold(13.0)));
|
||||
|
||||
@@ -318,7 +318,7 @@ impl PdfCraftApp {
|
||||
let settings = match self.print_draft.settings(doc.info.pages.len(), &labels) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
self.notify_error(e);
|
||||
self.notify_tr(&e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -786,7 +786,7 @@ pub(crate) fn body(
|
||||
ui.painter().rect_filled(area, 6.0, t.hover);
|
||||
match (&settings, sheets.get(d.sheet)) {
|
||||
(Err(e), _) => {
|
||||
ui.put(area.shrink(16.0), egui::Label::new(egui::RichText::new(e).color(t.text_muted)).wrap());
|
||||
ui.put(area.shrink(16.0), egui::Label::new(egui::RichText::new(tl!(e)).color(t.text_muted)).wrap());
|
||||
}
|
||||
(Ok(_), Some(sheet)) => {
|
||||
let k = (PREVIEW_BOX.0 / sheet.size.0 as f32).min(PREVIEW_BOX.1 / sheet.size.1 as f32);
|
||||
|
||||
@@ -551,7 +551,7 @@ fn title(ui: &mut egui::Ui, text: &str) {
|
||||
|
||||
fn error(ui: &mut egui::Ui, err: &Option<String>) {
|
||||
if let Some(e) = err.as_ref().filter(|e| !e.is_empty()) {
|
||||
ui.colored_label(Color32::from_rgb(0xD7, 0x37, 0x3F), e);
|
||||
ui.colored_label(Color32::from_rgb(0xD7, 0x37, 0x3F), tl!(e));
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
}
|
||||
@@ -955,7 +955,12 @@ pub(crate) fn banner(sigs: &[SignatureInfo]) -> Option<(&'static str, Color32, &
|
||||
{
|
||||
let by = c.certificate.as_ref().map(|x| {
|
||||
let org = x.subject.organization().map(|o| format!(", {o}")).unwrap_or_default();
|
||||
format!("{}{org}, certificate issued by {}.", x.display_name(), x.issuer.common_name().unwrap_or("an unknown issuer"))
|
||||
let who = format!("{}{org}", x.display_name());
|
||||
let (template, issuer) = match x.issuer.common_name() {
|
||||
Some(issuer) => ("{who}, certificate issued by {issuer}.", issuer),
|
||||
None => ("{who}, certificate issued by an unknown issuer.", ""),
|
||||
};
|
||||
crate::i18n::fmt(tl!(template), &[("who", &who), ("issuer", issuer)])
|
||||
});
|
||||
return Some(("badge-check", Color32::from_rgb(0x14, 0x73, 0xE6), "Certified by {by}", by.unwrap_or_default()));
|
||||
}
|
||||
@@ -1016,16 +1021,14 @@ pub(crate) fn panel(ui: &mut egui::Ui, t: &Tokens, sigs: &[SignatureInfo], expan
|
||||
for s in ordered {
|
||||
let open = expanded.contains(&s.field);
|
||||
let (icon, color) = status_icon(s);
|
||||
let head = if s.signed {
|
||||
format!(
|
||||
"Rev. {}: {} by {}",
|
||||
s.revision,
|
||||
if s.certify.is_some() { "Certified" } else { "Signed" },
|
||||
s.signer.as_deref().unwrap_or("an unknown signer")
|
||||
)
|
||||
} else {
|
||||
format!("Unsigned signature field: {}", s.field)
|
||||
let (template, name) = match (s.signed, s.certify.is_some(), s.signer.as_deref()) {
|
||||
(false, ..) => ("Unsigned signature field: {name}", s.field.as_str()),
|
||||
(true, true, Some(signer)) => ("Rev. {rev}: Certified by {name}", signer),
|
||||
(true, true, None) => ("Rev. {rev}: Certified by an unknown signer", ""),
|
||||
(true, false, Some(signer)) => ("Rev. {rev}: Signed by {name}", signer),
|
||||
(true, false, None) => ("Rev. {rev}: Signed by an unknown signer", ""),
|
||||
};
|
||||
let head = crate::i18n::fmt(tl!(template), &[("rev", &s.revision.to_string()), ("name", name)]);
|
||||
let resp = ui
|
||||
.horizontal(|ui| {
|
||||
let chevron = if open { "chevron-down" } else { "chevron-right" };
|
||||
@@ -1172,8 +1175,8 @@ fn key_usage(bits: u16) -> String {
|
||||
"Encipher Only",
|
||||
"Decipher Only",
|
||||
];
|
||||
let used: Vec<&str> = NAMES.iter().enumerate().filter(|(i, _)| bits & (1 << i) != 0).map(|(_, n)| *n).collect();
|
||||
if used.is_empty() { "None".into() } else { used.join(", ") }
|
||||
let used: Vec<&str> = NAMES.iter().enumerate().filter(|(i, _)| bits & (1 << i) != 0).map(|(_, n)| tl!(*n)).collect();
|
||||
if used.is_empty() { tl!("None").into() } else { used.join(", ") }
|
||||
}
|
||||
|
||||
/// What the viewer asks for.
|
||||
|
||||
@@ -110,7 +110,7 @@ fn month_names_follow_the_date_language_or_else_the_interface_language() {
|
||||
let text = |h: &Harness<'static, PdfCraftApp>| h.state().date_text(None).unwrap();
|
||||
assert_eq!(text(&h), h.state().session.today_text(None, Some("cs")).unwrap(), "follows the interface (Czech)");
|
||||
// The picker offers following the interface first, then the interface languages.
|
||||
assert!(shows(&h, "Same as interface (Čeština)"));
|
||||
assert!(shows(&h, "Stejný jako rozhraní (Čeština)"));
|
||||
h.state_mut().set_option("date-language", "es").unwrap();
|
||||
h.run_steps(2);
|
||||
assert_eq!(text(&h), h.state().session.today_text(None, Some("es")).unwrap());
|
||||
|
||||
Reference in New Issue
Block a user