mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-02 22:20:50 -06:00
Strings in code stay English and are the lookup keys; `crates/ui-egui/src/i18n` maps them to display text at render time from one `*.tsv` catalog per language (`LANGUAGES` registry, `tl!`/`t`/`tr`/`tr_ctx`/`tr_id`/`tn`/`fmt`). Command ids, menu paths, `ui.menu.list`, the control channel, the CLI, MCP, status messages and undo labels never see translated text, so agents, scripts and the existing tests are unaffected (under `cargo test` the UI is always English). Wired: in-window menus and the macOS native menu (rebuilt on a language change), dynamic labels (Undo X, Reset X, Last Effect: X as templates), the command palette (matches English and translated text), dock tabs, panel tooltips, the toolbar (category names now cut by characters, not bytes), the dialog frame, the shared widgets, the Preferences dialog, the shortcut editor, the hint and status bars, the Home screen, and the literals of every panel and dialog. Preference `interfaceLanguage` (User Interface › Language): `auto` or a code. `auto` follows `VECTORCRAFT_LOCALE`, `LC_ALL`/`LC_MESSAGES`/`LANG`/`LANGUAGE`, the macOS preferred languages and, on Windows (which sets no `LANG`), the user locale from `HKCU\Control Panel\International`. `zh-TW`/`zh-HK`/`zh-MO`/ `zh-Hant-*` resolve to `zh-hant`; Simplified locales stay English until a `zh-hans` catalog exists. `zh-hant.tsv` (~2,850 rows) is clean-room, in the vocabulary used in Taiwan; terms shared with PhotoCraft reuse its wording, vector terms use everyday vector-drawing vocabulary. Tests: catalog well-formedness (placeholders, ellipses, command ids, no Simplified characters), locale resolution, and two coverage tests that fail when a menu string, panel/tool/preference label, dialog button, hint-bar fragment or `tl!` literal has no row in a language marked `complete_menus`. `VECTORCRAFT_I18N_DUMP=file cargo test -p vectorcraft-ui-egui dump_source_strings` lists the strings for translators. Also: two clippy findings under rustc 1.97 fixed in passing. Integrated with main (squashed onto it, 42 commits on): the Czech interface (#213) moves from its Rust table to `i18n/cs.tsv` on the registry (all 556 rows, Czech plural rule, its menu-coverage and glyph tests kept); Find Font's resolution notes and the canvas context menu's labels are translated and covered by the catalog tests (`menus::CONTEXT_LABELS`); the glyph test checks CJK text only when the craft-fonts build input is embedded (#220 moved the Japanese fonts out of this repository); a language saved by an older version carries over to `interfaceLanguage`; Windows' locale lookup runs reg.exe by its full path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
137 lines
6.2 KiB
Rust
137 lines
6.2 KiB
Rust
//! EPS Options (Export As → EPS): the preview (none, TIFF black and white or colour, transparent
|
|
//! or opaque), overprints and the flattener preset transparency is flattened with, fonts, the
|
|
//! options (linked files, thumbnails, CMYK PostScript, compatible gradients) and the PostScript
|
|
//! language level.
|
|
//!
|
|
//! Fields: `document.export` params (`format: eps`, `path?`, the artboard choice) and the EPS
|
|
//! options of `document.exportEps`. OK remembers the options for next time and writes the file
|
|
//! (asking where when there is no path); bad options keep the dialog open.
|
|
|
|
use serde_json::{Map, Value, json};
|
|
use vectorcraft_engine::cmd::{FlattenOptions, fileio};
|
|
use vectorcraft_eps::{Level, Overprint, Preview};
|
|
|
|
use super::DialogSpec;
|
|
use super::document_setup::{LABEL, check, choice};
|
|
use crate::state::Dialog;
|
|
use crate::{VectorcraftApp, io, widgets};
|
|
|
|
pub(super) const KIND: &str = "epsOptions";
|
|
|
|
pub(super) const SPEC: DialogSpec = DialogSpec {
|
|
heading: |_| tl!("EPS Options").into(),
|
|
body,
|
|
confirm,
|
|
ok: Some("Export"),
|
|
min_width: 420.0,
|
|
max_width: Some(460.0),
|
|
..DialogSpec::FORM
|
|
};
|
|
|
|
/// Fields that are not remembered between exports (they belong to one export).
|
|
const PER_EXPORT: [&str; 7] = ["format", "path", "useArtboards", "range", "artboard", "artboards", "selectedOnly"];
|
|
|
|
/// The options a first EPS Options dialog starts from (the export's defaults).
|
|
fn defaults() -> Map<String, Value> {
|
|
let medium = FlattenOptions::preset_label(FlattenOptions::PRESETS[1]).unwrap_or_default();
|
|
let mut m = Map::new();
|
|
for (k, v) in [
|
|
("previewFormat", json!(Preview::default().id())),
|
|
("transparentPreview", json!(true)),
|
|
("overprints", json!(Overprint::default().id())),
|
|
("flattenerPreset", json!(medium)),
|
|
("includeLinkedFiles", json!(false)),
|
|
("thumbnails", json!(true)),
|
|
("cmykPostScript", json!(true)),
|
|
("compatibleGradients", json!(false)),
|
|
("level", json!(Level::default().id())),
|
|
] {
|
|
m.insert(k.into(), v);
|
|
}
|
|
m
|
|
}
|
|
|
|
/// Open EPS Options for an export whose params (path, artboard choice, any EPS option) are
|
|
/// `params`, over the options used last.
|
|
pub fn open(app: &mut VectorcraftApp, params: &Value) {
|
|
let mut fields = defaults();
|
|
for given in [&app.ui.eps_options, params] {
|
|
if let Some(o) = given.as_object() {
|
|
fields.extend(o.iter().map(|(k, v)| (k.clone(), v.clone())));
|
|
}
|
|
}
|
|
fields.insert("format".into(), json!("eps"));
|
|
// A built-in preset named by id (`medium`) shows as its name.
|
|
if let Some(label) = fields.get("flattenerPreset").and_then(Value::as_str).and_then(|p| FlattenOptions::preset_label(&p.to_ascii_lowercase())) {
|
|
fields.insert("flattenerPreset".into(), json!(label));
|
|
}
|
|
app.ui.dialog = Some(Dialog::new(KIND, Value::Object(fields)));
|
|
}
|
|
|
|
fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
|
widgets::subheader(ui, tl!("Preview"));
|
|
let previews: Vec<(&str, &str)> = Preview::ALL.iter().map(|p| (p.id(), p.label())).collect();
|
|
choice(ui, d, "previewFormat", tl!("Format:"), &previews);
|
|
// Only the colour preview can keep transparency.
|
|
let color = Preview::from_id(&d.str("previewFormat")) == Some(Preview::TiffColor);
|
|
let transparent = d.bool("transparentPreview");
|
|
ui.horizontal(|ui| {
|
|
ui.add_space(LABEL + 8.0);
|
|
for (label, on) in [(tl!("Transparent"), true), (tl!("Opaque"), false)] {
|
|
if widgets::radio(ui, label, color && transparent == on, color) {
|
|
d.fields.insert("transparentPreview".into(), json!(on));
|
|
}
|
|
ui.add_space(12.0);
|
|
}
|
|
});
|
|
ui.add_space(8.0);
|
|
widgets::subheader(ui, tl!("Transparency"));
|
|
let overprints: Vec<(&str, &str)> = Overprint::ALL.iter().map(|o| (o.id(), o.label())).collect();
|
|
choice(ui, d, "overprints", tl!("Overprints:"), &overprints);
|
|
let presets = app.session.flattener_presets();
|
|
let names: Vec<(&str, &str)> = presets.iter().map(|p| (p.name.as_str(), p.name.as_str())).collect();
|
|
choice(ui, d, "flattenerPreset", tl!("Preset:"), &names);
|
|
ui.add_space(8.0);
|
|
widgets::subheader(ui, tl!("Fonts"));
|
|
// Type is written as glyph outlines, which need no fonts.
|
|
widgets::check(ui, tl!("Embed Fonts (for other applications)"), true, false);
|
|
ui.add_space(8.0);
|
|
widgets::subheader(ui, tl!("Options"));
|
|
check(ui, d, "includeLinkedFiles", tl!("Include Linked Files"));
|
|
check(ui, d, "thumbnails", tl!("Include Document Thumbnails"));
|
|
check(ui, d, "cmykPostScript", tl!("Include CMYK PostScript in RGB Files"));
|
|
check(ui, d, "compatibleGradients", tl!("Compatible Gradient and Gradient Mesh Printing"));
|
|
ui.add_space(4.0);
|
|
let levels: Vec<(&str, &str)> = Level::ALL.iter().map(|l| (l.id(), l.label())).collect();
|
|
// A level set as a number (`level: 2`) reads as its id.
|
|
if let Some(Value::Number(n)) = d.fields.get("level") {
|
|
let id = n.to_string();
|
|
d.fields.insert("level".into(), json!(id));
|
|
}
|
|
choice(ui, d, "level", tl!("PostScript:"), &levels);
|
|
ui.add_space(8.0);
|
|
super::form::caption(
|
|
ui,
|
|
if Level::from_id(&d.str("level")) == Some(Level::Two) || d.bool("compatibleGradients") {
|
|
tl!("Transparency is flattened with the preset. Type is written as outlines; gradients as bands of colour.")
|
|
} else {
|
|
tl!("Transparency is flattened with the preset. Type is written as outlines; gradients stay smooth.")
|
|
},
|
|
);
|
|
false
|
|
}
|
|
|
|
/// Check the options, remember them, and write the file(s).
|
|
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
|
let params = fileio::eps::resolve(&super::form::params(d), &app.session.prefs.flattener_presets)?;
|
|
// Nothing selected keeps the dialog open too.
|
|
if let Some(st) = app.session.active() {
|
|
fileio::export_source(st, ¶ms).map_err(|e| e.to_string())?;
|
|
}
|
|
app.ui.eps_options =
|
|
Value::Object(d.fields.iter().filter(|(k, _)| !PER_EXPORT.contains(&k.as_str())).map(|(k, v)| (k.clone(), v.clone())).collect());
|
|
app.ui.dialog = None;
|
|
let path = d.fields.get("path").and_then(Value::as_str).map(str::to_string);
|
|
io::export(app, Some("eps"), path, ¶ms)
|
|
}
|