From 8901877e40e7d7f2bb804947b2d484e1251ed0c1 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 05:50:46 -0800 Subject: [PATCH 1/4] Color management: commands reject a param of the wrong type The color management commands read their params with helpers that fell back to the default for a value of the wrong type, so "bpc": "no" kept the old setting, "on": "true" toggled the view and "convert": "no" converted the colors. They now go through switch_or and text_param: such a value is an error and nothing changes. Omitting a param or giving null keeps its default. - edit.colorSettings: rgb, cmyk, intent, bpc - file.documentColorMode (and its alias): convert, intent, grays, checked before the early return for the current mode; PDF import's grays reads the same way - color.convert: to, intent - color.loadProfile: path; color.removeProfile: name - view.proofSetup: target, intent, simulatePaper, proof - view.proofColors and view.overprintPreview: on - view.separationsPreview: on, only, toggle, and plates, which must be an array of plate names - swatch.setSpot: name, spot --- crates/engine/src/cmd/colormgmt.rs | 67 +++++++++++++-------- crates/engine/src/tests/colormgmt.rs | 89 ++++++++++++++++++++++++++++ docs/mcp.md | 4 ++ 3 files changed, 135 insertions(+), 25 deletions(-) diff --git a/crates/engine/src/cmd/colormgmt.rs b/crates/engine/src/cmd/colormgmt.rs index 9842cfbe9..ccc5d4ece 100644 --- a/crates/engine/src/cmd/colormgmt.rs +++ b/crates/engine/src/cmd/colormgmt.rs @@ -19,7 +19,7 @@ use vectorcraft_render::proof; #[cfg(not(target_arch = "wasm32"))] use super::EngineError; -use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, color_value, has_doc, str_param, targets}; +use super::{CommandSpec, Result, Session, always, bad, cmd, color_value, has_doc, switch_or, targets, text_param}; /// `Document.unknown` key under which files before format v3 kept the assigned profiles (migrated /// to `Document::color_profiles` on load). @@ -100,7 +100,7 @@ pub fn specs() -> Vec { proof_setup ), cmd!(query "view.proofColors", "Proof Colors", ["View"], None, "{on?: bool (default: toggle)} → proof state", always, |s, p| { - toggle_view(s, p, |v, on| v.proof_colors = on.unwrap_or(!v.proof_colors)) + toggle_view(s, p, "view.proofColors", |v, on| v.proof_colors = on.unwrap_or(!v.proof_colors)) }), cmd!( query "view.overprintPreview", @@ -109,7 +109,7 @@ pub fn specs() -> Vec { Some("Cmd+Alt+Shift+Y"), "{on?: bool (default: toggle)} → proof state", always, - |s, p| toggle_view(s, p, |v, on| v.overprint = on.unwrap_or(!v.overprint)) + |s, p| toggle_view(s, p, "view.overprintPreview", |v, on| v.overprint = on.unwrap_or(!v.overprint)) ), cmd!( query "view.separationsPreview", @@ -143,12 +143,21 @@ pub fn specs() -> Vec { } fn intent_param(p: &Value, cmd: &str) -> Result> { - match str_param(p, "intent") { + match text_param(p, "intent", cmd)? { None => Ok(None), Some(i) => Intent::parse(i).map(Some).ok_or_else(|| bad(cmd, format!("unknown intent `{i}`"))), } } +/// The true or false `p` gives for `key`, or `None` when `p` leaves it out or gives null. Any other +/// value is an error, as [`switch_or`] reads it. +fn on_param(p: &Value, key: &str, cmd: &str) -> Result> { + if p.get(key).is_none_or(Value::is_null) { + return Ok(None); + } + switch_or(p, key, false, cmd).map(Some) +} + fn settings_json(st: &ColorSettings) -> Value { json!({ "rgb": st.rgb, @@ -171,16 +180,16 @@ fn color_settings(s: &mut Session, p: &Value) -> Result { const C: &str = "edit.colorSettings"; let mut st = cms::active_settings(); let before = st.clone(); - if let Some(v) = str_param(p, "rgb") { + if let Some(v) = text_param(p, "rgb", C)? { st.rgb = cms::canonical_name(v).to_string(); } - if let Some(v) = str_param(p, "cmyk") { + if let Some(v) = text_param(p, "cmyk", C)? { st.cmyk = cms::canonical_name(v).to_string(); } if let Some(i) = intent_param(p, C)? { st.intent = i; } - st.bpc = bool_or(p, "bpc", st.bpc); + st.bpc = switch_or(p, "bpc", st.bpc, C)?; if st != before { cms::set_active(&st).map_err(|e| bad(C, e.to_string()))?; touch_all(s); @@ -191,7 +200,7 @@ fn color_settings(s: &mut Session, p: &Value) -> Result { #[cfg(not(target_arch = "wasm32"))] fn load_profile(s: &mut Session, p: &Value) -> Result { const C: &str = "color.loadProfile"; - let path = str_param(p, "path").ok_or_else(|| bad(C, "missing `path`"))?; + let path = text_param(p, "path", C)?.ok_or_else(|| bad(C, "missing `path`"))?; crate::file_access::check_read(path).map_err(EngineError::Other)?; let bytes = crate::store::read_capped(std::path::Path::new(path), crate::store::MAX_PROFILE_FILE).map_err(|e| bad(C, format!("{path}: {e}")))?; let info = cms::register_icc(&bytes, None).map_err(|e| bad(C, e.to_string()))?; @@ -209,7 +218,7 @@ fn load_profile(s: &mut Session, p: &Value) -> Result { /// Forget a profile `color.loadProfile` loaded, and the copy that is kept of it. fn remove_profile(s: &mut Session, p: &Value) -> Result { const C: &str = "color.removeProfile"; - let name = str_param(p, "name").ok_or_else(|| bad(C, "missing `name`"))?; + let name = text_param(p, "name", C)?.ok_or_else(|| bad(C, "missing `name`"))?; let info = cms::profiles().into_iter().find(|i| i.name == name).ok_or_else(|| bad(C, format!("no profile named `{name}`")))?; if info.builtin { return Err(bad(C, format!("`{name}` is a built-in profile and stays"))); @@ -289,7 +298,7 @@ pub enum Grays { impl Grays { /// The `grays` param of `cmd`: `"profile"` (default) or `"black"`. pub(crate) fn param(cmd: &str, p: &Value) -> Result { - match str_param(p, "grays").map(str::to_ascii_lowercase).as_deref() { + match text_param(p, "grays", cmd)?.map(str::to_ascii_lowercase).as_deref() { None | Some("profile") => Ok(Grays::Profile), Some("black") => Ok(Grays::Black), Some(g) => Err(bad(cmd, format!("grays must be \"profile\" or \"black\", not `{g}`"))), @@ -302,18 +311,18 @@ impl Grays { /// colour through the colour settings. pub(crate) fn convert_mode(s: &mut Session, p: &Value) -> Result { const C: &str = "file.documentColorMode"; - let mode = match str_param(p, "mode").map(str::to_ascii_lowercase).as_deref() { + let mode = match p.get("mode").and_then(Value::as_str).map(str::to_ascii_lowercase).as_deref() { Some("cmyk") => ColorMode::Cmyk, Some("rgb") => ColorMode::Rgb, _ => return Err(bad(C, "mode must be \"cmyk\" or \"rgb\"")), }; let intent = intent_param(p, C)?; let grays = Grays::param(C, p)?; + let convert = switch_or(p, "convert", true, C)?; // Choosing the mode the document already has (the checked menu item) is not an edit. if s.doc()?.doc.color_mode == mode { return Ok(json!({ "changed": 0 })); } - let convert = bool_or(p, "convert", true); let mut changed = 0usize; s.edit("Document Color Mode", |d, _| { changed = set_color_mode(d, mode, convert, intent, grays); @@ -654,7 +663,7 @@ fn convert_color(_: &mut Session, p: &Value) -> Result { let c = cms::active(); let intent = intent_param(p, C)?.unwrap_or(c.settings().intent); let lab = c.lab(&col); - let (model, vals) = match str_param(p, "to").unwrap_or("rgb") { + let (model, vals) = match text_param(p, "to", C)?.unwrap_or("rgb") { "rgb" => ("rgb", values(&c.convert(&col, Model::Rgb, intent))), "cmyk" => ("cmyk", values(&c.convert(&col, Model::Cmyk, intent))), "gray" | "grayscale" => ("gray", values(&c.convert(&col, Model::Gray, intent))), @@ -728,7 +737,7 @@ fn view_json(v: &proof::ProofView) -> Value { fn proof_setup(s: &mut Session, p: &Value) -> Result { const C: &str = "view.proofSetup"; let mut v = proof::view(); - if let Some(t) = str_param(p, "target") { + if let Some(t) = text_param(p, "target", C)? { let t = ProofTarget::parse(t).ok_or_else(|| bad(C, format!("unknown proof target `{t}`")))?; if let ProofTarget::Cmyk(name) = &t && !cms::profile(name).is_some_and(|k| k.kind == cms::ProfileKind::Cmyk) @@ -740,8 +749,8 @@ fn proof_setup(s: &mut Session, p: &Value) -> Result { if let Some(i) = intent_param(p, C)? { v.setup.intent = i; } - v.setup.simulate_paper = bool_or(p, "simulatePaper", v.setup.simulate_paper); - if let Some(on) = p.get("proof").and_then(Value::as_bool) { + v.setup.simulate_paper = switch_or(p, "simulatePaper", v.setup.simulate_paper, C)?; + if let Some(on) = on_param(p, "proof", C)? { v.proof_colors = on; } proof::set_view(v.clone()); @@ -749,9 +758,10 @@ fn proof_setup(s: &mut Session, p: &Value) -> Result { Ok(view_json(&v)) } -fn toggle_view(s: &mut Session, p: &Value, f: impl FnOnce(&mut proof::ProofView, Option)) -> Result { +fn toggle_view(s: &mut Session, p: &Value, cmd: &str, f: impl FnOnce(&mut proof::ProofView, Option)) -> Result { + let on = on_param(p, "on", cmd)?; let mut v = proof::view(); - f(&mut v, p.get("on").and_then(Value::as_bool)); + f(&mut v, on); proof::set_view(v.clone()); touch_all(s); Ok(view_json(&v)) @@ -779,16 +789,23 @@ fn separations(s: &mut Session, p: &Value) -> Result { .map(|d| proof::plates(&d.doc).into_iter().map(|p| p.name).collect()) .unwrap_or_else(|| cms::PROCESS_PLATES.iter().map(|s| s.to_string()).collect()); let check = |n: &str| if all.iter().any(|a| a == n) { Ok(n.to_string()) } else { Err(bad(C, format!("unknown plate `{n}`"))) }; - if let Some(on) = p.get("on").and_then(Value::as_bool) { + if let Some(on) = on_param(p, "on", C)? { v.separations = if on { Some(v.separations.take().unwrap_or_else(|| all.clone())) } else { None }; } - if let Some(a) = p.get("plates").and_then(Value::as_array) { - v.separations = Some(a.iter().filter_map(Value::as_str).map(check).collect::>>()?); + match p.get("plates") { + None | Some(Value::Null) => {} + Some(Value::Array(a)) if a.iter().all(Value::is_string) => { + v.separations = Some(a.iter().filter_map(Value::as_str).map(check).collect::>>()?); + } + Some(_) => { + let key = "plates"; + return Err(bad(C, format!("`{key}` must be an array of these names: {}", all.join(", ")))); + } } - if let Some(n) = str_param(p, "only") { + if let Some(n) = text_param(p, "only", C)? { v.separations = Some(vec![check(n)?]); } - if let Some(n) = str_param(p, "toggle") { + if let Some(n) = text_param(p, "toggle", C)? { let n = check(n)?; let vis = v.separations.get_or_insert_with(|| all.clone()); if let Some(i) = vis.iter().position(|x| *x == n) { @@ -804,8 +821,8 @@ fn separations(s: &mut Session, p: &Value) -> Result { fn set_spot(s: &mut Session, p: &Value) -> Result { const C: &str = "swatch.setSpot"; - let name = str_param(p, "name").ok_or_else(|| bad(C, "missing `name`"))?.to_string(); - let spot = bool_or(p, "spot", true); + let name = text_param(p, "name", C)?.ok_or_else(|| bad(C, "missing `name`"))?.to_string(); + let spot = switch_or(p, "spot", true, C)?; let sw = s.doc()?.doc.swatch(&name).ok_or_else(|| bad(C, format!("no swatch `{name}`")))?; if !matches!(sw.paint, Paint::Solid { .. }) { return Err(bad(C, "only solid-colour swatches can be spot colours")); diff --git a/crates/engine/src/tests/colormgmt.rs b/crates/engine/src/tests/colormgmt.rs index cb6ca47c5..1ca956794 100644 --- a/crates/engine/src/tests/colormgmt.rs +++ b/crates/engine/src/tests/colormgmt.rs @@ -542,3 +542,92 @@ fn intents_reach_the_document_conversion() { assert_ne!(first(&a), first(&b)); let _ = Intent::Perceptual; } + +/// The error `cmd` gives for `params`: it must name `msg`. +fn refused(s: &mut Session, cmd: &str, params: Value, msg: &str) { + let e = match s.execute(cmd, ¶ms) { + Ok(r) => panic!("{cmd} {params} gave {r}"), + Err(e) => e.to_string(), + }; + assert!(e.contains(msg), "{cmd} {params}: {e}"); +} + +#[test] +fn color_settings_refuse_a_param_of_the_wrong_type() { + let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner()); + let mut s = session(); + let before = cms::active_settings(); + let rev = s.doc().unwrap().revision; + let c = "edit.colorSettings"; + refused(&mut s, c, json!({"bpc": "no"}), "`bpc` must be true or false"); + refused(&mut s, c, json!({"rgb": 5}), "`rgb` must be a string"); + refused(&mut s, c, json!({"cmyk": true}), "`cmyk` must be a string"); + refused(&mut s, c, json!({"intent": 2}), "`intent` must be a string"); + assert_eq!(cms::active_settings(), before, "the settings are unchanged"); + assert_eq!(s.doc().unwrap().revision, rev, "nothing redraws"); + // Omitted and null params keep the settings. + s.execute(c, &json!({"bpc": null, "rgb": null})).unwrap(); + assert_eq!(cms::active_settings(), before); + refused(&mut s, "color.convert", json!({"color": "#808080", "to": 1}), "`to` must be a string"); + refused(&mut s, "color.convert", json!({"color": "#808080", "intent": ["relative"]}), "`intent` must be a string"); + assert_eq!(s.execute("color.convert", &json!({"color": "#808080", "to": null})).unwrap()["model"], "rgb"); + refused(&mut s, "color.loadProfile", json!({"path": 7}), "`path` must be a string"); + refused(&mut s, "color.removeProfile", json!({"name": 7}), "`name` must be a string"); +} + +#[test] +fn document_color_mode_refuses_a_param_of_the_wrong_type() { + let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner()); + let mut s = session(); + let a = rect(&mut s, 0.0, 0.0, 10.0, 10.0, json!("#ff0000")); + let fill = fill_of(&s, a); + let undo = s.doc().unwrap().history.undo.len(); + for c in ["file.documentColorMode", "object.convertDocumentColorMode"] { + refused(&mut s, c, json!({"mode": "cmyk", "convert": "no"}), "`convert` must be true or false"); + refused(&mut s, c, json!({"mode": "rgb", "convert": 0}), "`convert` must be true or false"); + refused(&mut s, c, json!({"mode": "cmyk", "grays": 1}), "`grays` must be a string"); + refused(&mut s, c, json!({"mode": "cmyk", "intent": false}), "`intent` must be a string"); + } + let st = s.doc().unwrap(); + assert_eq!(st.doc.color_mode, ColorMode::Rgb); + assert_eq!(st.history.undo.len(), undo, "no undo step"); + assert_eq!(fill_of(&s, a), fill, "the colors are unchanged"); +} + +#[test] +fn proof_views_refuse_a_param_of_the_wrong_type() { + let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner()); + let mut s = session(); + let view = vectorcraft_render::proof::view(); + let rev = s.doc().unwrap().revision; + refused(&mut s, "view.proofSetup", json!({"simulatePaper": "yes"}), "`simulatePaper` must be true or false"); + refused(&mut s, "view.proofSetup", json!({"proof": 1}), "`proof` must be true or false"); + refused(&mut s, "view.proofSetup", json!({"target": 3}), "`target` must be a string"); + refused(&mut s, "view.proofColors", json!({"on": "true"}), "`on` must be true or false"); + refused(&mut s, "view.overprintPreview", json!({"on": 1}), "`on` must be true or false"); + refused(&mut s, "view.separationsPreview", json!({"on": "false"}), "`on` must be true or false"); + refused(&mut s, "view.separationsPreview", json!({"plates": "Cyan"}), "`plates` must be an array of these names: Cyan, Magenta"); + refused(&mut s, "view.separationsPreview", json!({"plates": ["Cyan", 2]}), "`plates` must be an array of these names"); + refused(&mut s, "view.separationsPreview", json!({"only": 0}), "`only` must be a string"); + refused(&mut s, "view.separationsPreview", json!({"toggle": ["Cyan"]}), "`toggle` must be a string"); + assert_eq!(vectorcraft_render::proof::view(), view, "the proof view is unchanged"); + assert_eq!(s.doc().unwrap().revision, rev, "nothing redraws"); + // A null `on` toggles, as an omitted one does. + let r = s.execute("view.proofColors", &json!({"on": null})).unwrap(); + assert_eq!(r["proofColors"], !view.proof_colors); + s.execute("view.proofColors", &json!({"on": view.proof_colors})).unwrap(); +} + +#[test] +fn set_spot_refuses_a_param_of_the_wrong_type() { + let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner()); + let mut s = session(); + s.execute("swatch.new", &json!({"name": "Ink", "color": {"c": 0, "m": 60, "y": 100, "k": 0}, "global": true})).unwrap(); + let undo = s.doc().unwrap().history.undo.len(); + refused(&mut s, "swatch.setSpot", json!({"name": "Ink", "spot": "true"}), "`spot` must be true or false"); + refused(&mut s, "swatch.setSpot", json!({"name": 1}), "`name` must be a string"); + assert!(!s.doc().unwrap().doc.swatch("Ink").unwrap().spot, "the swatch stays a process color"); + assert_eq!(s.doc().unwrap().history.undo.len(), undo, "no undo step"); + s.execute("swatch.setSpot", &json!({"name": "Ink", "spot": null})).unwrap(); + assert!(s.doc().unwrap().doc.swatch("Ink").unwrap().spot, "a null `spot` is the default, true"); +} diff --git a/docs/mcp.md b/docs/mcp.md index db1d43ae8..c978252f4 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -662,6 +662,10 @@ them. `grays: "black"` puts RGB greys with R = G = B on the black plate instead, Convert to Grayscale before the switch would give); other colors convert as usual. The reply's `changed` counts the colors that changed in the art, symbols, graph designs and pattern tiles; it does not count the colors of graphic styles and brushes. +`edit.colorSettings`, `file.documentColorMode`, `color.convert`, `color.loadProfile`, `color.removeProfile`, +`view.proofSetup`, `view.proofColors`, `view.overprintPreview`, `view.separationsPreview` and `swatch.setSpot` reject a +param of the wrong type, such as `"bpc": "no"`, `"on": 1` or `"to": 5`, and change nothing. Omitting a param or giving +null keeps its default. SVG import keeps what the canvas edits live. `` becomes a pattern swatch. A `` with `` becomes a symbol (named after its `data-name`, else its id) with an instance per ``; a `` that shows it differently From 56ad84266dabe5b2b6db9ccc634e361b8d4dac0c Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 06:15:28 -0800 Subject: [PATCH 2/4] Color management: color.gamutCheck rejects a colors param that is not a list A colors value that is not an array, such as "#fff" or 5, was ignored and the command checked the selection or the whole document instead. It is now an error. A null colors still checks the selection or document, as an omitted one does. --- crates/engine/src/cmd/colormgmt.rs | 6 +++++- crates/engine/src/tests/colormgmt.rs | 15 +++++++++++++++ docs/mcp.md | 8 ++++---- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/cmd/colormgmt.rs b/crates/engine/src/cmd/colormgmt.rs index ccc5d4ece..37ccc3004 100644 --- a/crates/engine/src/cmd/colormgmt.rs +++ b/crates/engine/src/cmd/colormgmt.rs @@ -683,7 +683,11 @@ fn convert_color(_: &mut Session, p: &Value) -> Result { fn gamut_check(s: &mut Session, p: &Value) -> Result { let mut cols: Vec = vec![]; - if let Some(a) = p.get("colors").and_then(Value::as_array) { + let colors = p.get("colors").filter(|v| !v.is_null()); + if let Some(v) = colors.filter(|v| !v.is_array()) { + return Err(bad("color.gamutCheck", format!("bad color {v}"))); + } + if let Some(a) = colors.and_then(Value::as_array) { for v in a { cols.push(color_value(v).ok_or_else(|| bad("color.gamutCheck", format!("bad color {v}")))?); } diff --git a/crates/engine/src/tests/colormgmt.rs b/crates/engine/src/tests/colormgmt.rs index 1ca956794..4ce1ba466 100644 --- a/crates/engine/src/tests/colormgmt.rs +++ b/crates/engine/src/tests/colormgmt.rs @@ -631,3 +631,18 @@ fn set_spot_refuses_a_param_of_the_wrong_type() { s.execute("swatch.setSpot", &json!({"name": "Ink", "spot": null})).unwrap(); assert!(s.doc().unwrap().doc.swatch("Ink").unwrap().spot, "a null `spot` is the default, true"); } + +#[test] +fn gamut_check_refuses_colors_that_are_not_a_list() { + let _g = GLOBAL.lock().unwrap_or_else(|e| e.into_inner()); + let mut s = session(); + rect(&mut s, 0.0, 0.0, 10.0, 10.0, json!("#0000ff")); + s.execute("select.set", &json!({"ids": []})).unwrap(); + refused(&mut s, "color.gamutCheck", json!({"colors": "#00ff00"}), "bad color \"#00ff00\""); + refused(&mut s, "color.gamutCheck", json!({"colors": 5}), "bad color 5"); + refused(&mut s, "color.gamutCheck", json!({"colors": {"c": 100, "m": 0, "y": 0, "k": 0}}), "bad color"); + // A null list checks the document, as an omitted one does. + let r = s.execute("color.gamutCheck", &json!({"colors": null})).unwrap(); + assert_eq!(r, s.execute("color.gamutCheck", &json!({})).unwrap()); + assert!(r["outOfGamut"].as_array().unwrap().iter().any(|v| v["hex"] == "#0000ff"), "{r}"); +} diff --git a/docs/mcp.md b/docs/mcp.md index c978252f4..d953a310d 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -662,10 +662,10 @@ them. `grays: "black"` puts RGB greys with R = G = B on the black plate instead, Convert to Grayscale before the switch would give); other colors convert as usual. The reply's `changed` counts the colors that changed in the art, symbols, graph designs and pattern tiles; it does not count the colors of graphic styles and brushes. -`edit.colorSettings`, `file.documentColorMode`, `color.convert`, `color.loadProfile`, `color.removeProfile`, -`view.proofSetup`, `view.proofColors`, `view.overprintPreview`, `view.separationsPreview` and `swatch.setSpot` reject a -param of the wrong type, such as `"bpc": "no"`, `"on": 1` or `"to": 5`, and change nothing. Omitting a param or giving -null keeps its default. +`edit.colorSettings`, `file.documentColorMode`, `color.convert`, `color.gamutCheck`, `color.loadProfile`, +`color.removeProfile`, `view.proofSetup`, `view.proofColors`, `view.overprintPreview`, `view.separationsPreview` and +`swatch.setSpot` reject a param of the wrong type, such as `"bpc": "no"`, `"on": 1`, `"to": 5` or `"colors": "#fff"`, +and change nothing. Omitting a param or giving null keeps its default. SVG import keeps what the canvas edits live. `` becomes a pattern swatch. A `` with `` becomes a symbol (named after its `data-name`, else its id) with an instance per ``; a `` that shows it differently From badda0a16ea420d82758cd464c602086c41f6256 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 7 Nov 2026 07:49:42 -0800 Subject: [PATCH 3/4] Color management: color.gamutCheck names `colors` when it is not a list A non-null `colors` that is not an array now gives "`colors` must be an array of colors, not ..." instead of the generic "bad color ...", with a row in every catalog. The loadProfile and removeProfile refusal tests also check that no profile is loaded or removed and the settings are unchanged. --- crates/engine/src/cmd/colormgmt.rs | 2 +- crates/engine/src/tests/colormgmt.rs | 11 ++++++++--- crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hi.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 4 ++++ crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + 29 files changed, 39 insertions(+), 4 deletions(-) diff --git a/crates/engine/src/cmd/colormgmt.rs b/crates/engine/src/cmd/colormgmt.rs index 37ccc3004..7bdcfad58 100644 --- a/crates/engine/src/cmd/colormgmt.rs +++ b/crates/engine/src/cmd/colormgmt.rs @@ -685,7 +685,7 @@ fn gamut_check(s: &mut Session, p: &Value) -> Result { let mut cols: Vec = vec![]; let colors = p.get("colors").filter(|v| !v.is_null()); if let Some(v) = colors.filter(|v| !v.is_array()) { - return Err(bad("color.gamutCheck", format!("bad color {v}"))); + return Err(bad("color.gamutCheck", format!("`colors` must be an array of colors, not {v}"))); } if let Some(a) = colors.and_then(Value::as_array) { for v in a { diff --git a/crates/engine/src/tests/colormgmt.rs b/crates/engine/src/tests/colormgmt.rs index 4ce1ba466..f841efedc 100644 --- a/crates/engine/src/tests/colormgmt.rs +++ b/crates/engine/src/tests/colormgmt.rs @@ -571,8 +571,13 @@ fn color_settings_refuse_a_param_of_the_wrong_type() { refused(&mut s, "color.convert", json!({"color": "#808080", "to": 1}), "`to` must be a string"); refused(&mut s, "color.convert", json!({"color": "#808080", "intent": ["relative"]}), "`intent` must be a string"); assert_eq!(s.execute("color.convert", &json!({"color": "#808080", "to": null})).unwrap()["model"], "rgb"); + let profiles = || cms::profiles().into_iter().map(|i| i.name).collect::>(); + let listed = profiles(); refused(&mut s, "color.loadProfile", json!({"path": 7}), "`path` must be a string"); refused(&mut s, "color.removeProfile", json!({"name": 7}), "`name` must be a string"); + assert_eq!(profiles(), listed, "no profile is loaded or removed"); + assert_eq!(cms::active_settings(), before, "the settings are unchanged"); + assert_eq!(s.doc().unwrap().revision, rev, "nothing redraws"); } #[test] @@ -638,9 +643,9 @@ fn gamut_check_refuses_colors_that_are_not_a_list() { let mut s = session(); rect(&mut s, 0.0, 0.0, 10.0, 10.0, json!("#0000ff")); s.execute("select.set", &json!({"ids": []})).unwrap(); - refused(&mut s, "color.gamutCheck", json!({"colors": "#00ff00"}), "bad color \"#00ff00\""); - refused(&mut s, "color.gamutCheck", json!({"colors": 5}), "bad color 5"); - refused(&mut s, "color.gamutCheck", json!({"colors": {"c": 100, "m": 0, "y": 0, "k": 0}}), "bad color"); + refused(&mut s, "color.gamutCheck", json!({"colors": "#00ff00"}), "`colors` must be an array of colors, not \"#00ff00\""); + refused(&mut s, "color.gamutCheck", json!({"colors": 5}), "`colors` must be an array of colors, not 5"); + refused(&mut s, "color.gamutCheck", json!({"colors": {"c": 100, "m": 0, "y": 0, "k": 0}}), "`colors` must be an array of colors"); // A null list checks the document, as an omitted one does. let r = s.execute("color.gamutCheck", &json!({"colors": null})).unwrap(); assert_eq!(r, s.execute("color.gamutCheck", &json!({})).unwrap()); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 62e63236c..0347066f8 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -4616,6 +4616,7 @@ axis Both И двете ICF Box Касетка ICF @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` е референцията за стъпка {r}, която се разрешава само от `run` и `command_batch` @msg `ids` must be an array of object ids, not {v} `ids` трябва да бъде масив от идентификатори на обекти, а не {v} +@msg `colors` must be an array of colors, not {v} `colors` трябва да бъде масив от цветове, а не {v} @msg `corners` must be an array of anchor indices, not {v} `corners` трябва да бъде масив от индекси на опорни точки, а не {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: резултатът от стъпка {i} няма такава стойност (напишете $$ за литерален знак $) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: няма стъпка {i} преди тази (стъпките се броят от 0; напишете $$ за литерален знак $) diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index f9440a3ac..f2697d1c5 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -4974,6 +4974,7 @@ imageTrace Outline Obris @msg No linked files to look for Nema povezanih datoteka za traženje @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` je referenca na korak {r}, koju razrješavaju samo `run` i `command_batch` @msg `ids` must be an array of object ids, not {v} `ids` mora biti niz ID-ova objekata, a ne {v} +@msg `colors` must be an array of colors, not {v} `colors` mora biti niz boja, a ne {v} @msg `corners` must be an array of anchor indices, not {v} `corners` mora biti niz indeksa sidara, a ne {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: rezultat koraka {i} nema takvu vrijednost (napišite $$ za doslovni $) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: prije ovog nema koraka {i} (koraci se broje od 0; napišite $$ za doslovni $) diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 4b7620122..7341359e0 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -4870,6 +4870,7 @@ digits Malayalam Malaiàlam @msg `groupColors` must be an array `groupColors` ha de ser una matriu @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` és la referència del pas {r}, que només resolen `run` i `command_batch` @msg `ids` must be an array of object ids, not {v} `ids` ha de ser una matriu d’identificadors d’objecte, no {v} +@msg `colors` must be an array of colors, not {v} `colors` ha de ser una matriu de colors, no {v} @msg `library` is a library id or name (see {list}) `library` és un identificador o un nom de biblioteca (vegeu {list}) @msg `method` must be classic or perceptual `method` ha de ser classic o perceptual @msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` i `maxAnchors` han de ser nombres enters diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 1d97c368b..cf9f54705 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -5032,6 +5032,7 @@ imageTrace Outline Obrys @msg No linked files to look for Žádné propojené soubory k vyhledání @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` je odkaz na krok {r}, který vyhodnocují jen `run` a `command_batch` @msg `ids` must be an array of object ids, not {v} `ids` musí být pole ID objektů, ne {v} +@msg `colors` must be an array of colors, not {v} `colors` musí být pole barev, ne {v} @msg `corners` must be an array of anchor indices, not {v} `corners` musí být pole indexů kotevních bodů, ne {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: výsledek kroku {i} takovou hodnotu nemá (pro samotný znak $ napište $$) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: před tímto krokem není krok {i} (kroky se číslují od 0; pro samotný znak $ napište $$) diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index f8a359ab6..edf9cce8c 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -4660,6 +4660,7 @@ imageTrace Outline Kontur @msg No linked files to look for Ingen linkede filer at lede efter @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` er trinreferencen {r}, som kun `run` og `command_batch` fortolker @msg `ids` must be an array of object ids, not {v} `ids` skal være et array af objekt-id'er, ikke {v} +@msg `colors` must be an array of colors, not {v} `colors` skal være et array af farver, ikke {v} @msg `corners` must be an array of anchor indices, not {v} `corners` skal være et array af ankerindekser, ikke {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: resultatet af trin {i} har ingen sådan værdi (skriv $$ for at få et bogstaveligt $) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: der findes intet trin {i} før dette (trin nummereres fra 0; skriv $$ for at få et bogstaveligt $) diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 1546be1ae..a34527815 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4689,6 +4689,7 @@ theme Appearance Erscheinungsbild ICF Box ICF-Rahmen @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` ist der Schrittverweis {r}, den nur `run` und `command_batch` auflösen @msg `ids` must be an array of object ids, not {v} `ids` muss ein Array von Objekt-IDs sein, nicht {v} +@msg `colors` must be an array of colors, not {v} `colors` muss ein Array von Farben sein, nicht {v} @msg `corners` must be an array of anchor indices, not {v} `corners` muss ein Array von Ankerpunkt-Indizes sein, nicht {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: Das Ergebnis von Schritt {i} hat keinen solchen Wert (schreiben Sie $$ für ein literales $) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: Vor diesem Schritt gibt es keinen Schritt {i} (Schritte zählen ab 0; schreiben Sie $$ für ein literales $) diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 2d7ffc5b2..25017dd4f 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4713,6 +4713,7 @@ digits Malayalam Μαλαγιάλαμ ICF Box Πλαίσιο ICF @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve το `id` είναι η αναφορά βήματος {r}, την οποία αναλύουν μόνο τα `run` και `command_batch` @msg `ids` must be an array of object ids, not {v} το `ids` πρέπει να είναι πίνακας αναγνωριστικών αντικειμένων, όχι {v} +@msg `colors` must be an array of colors, not {v} το `colors` πρέπει να είναι πίνακας χρωμάτων, όχι {v} @msg `corners` must be an array of anchor indices, not {v} το `corners` πρέπει να είναι πίνακας δεικτών αγκύρωσης, όχι {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: το αποτέλεσμα του βήματος {i} δεν έχει τέτοια τιμή (γράψτε $$ για κυριολεκτικό $) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: δεν υπάρχει βήμα {i} πριν από αυτό (η αρίθμηση αρχίζει από 0· γράψτε $$ για κυριολεκτικό $) diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index e5c6f889d..b35270874 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4752,6 +4752,7 @@ axis Both Ambos Perspective Distort Distorsión de perspectiva @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` es la referencia a un paso {r}, que solo `run` y `command_batch` resuelven @msg `ids` must be an array of object ids, not {v} `ids` debe ser una lista de identificadores de objeto, no {v} +@msg `colors` must be an array of colors, not {v} `colors` debe ser una lista de colores, no {v} @msg `corners` must be an array of anchor indices, not {v} `corners` debe ser una lista de índices de puntos de ancla, no {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: el resultado del paso {i} no tiene ese valor (escriba $$ para un $ literal) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: no hay ningún paso {i} antes de este (los pasos se cuentan desde 0; escriba $$ para un $ literal) diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 86a651070..953f1ad09 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -4934,6 +4934,7 @@ imageTrace Outline Ääriviiva @msg No linked files to look for Ei etsittäviä linkitettyjä tiedostoja @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` on vaiheviittaus {r}, jonka vain `run` ja `command_batch` ratkaisevat @msg `ids` must be an array of object ids, not {v} `ids` on oltava objektitunnusten taulukko, ei {v} +@msg `colors` must be an array of colors, not {v} `colors` on oltava värien taulukko, ei {v} @msg `corners` must be an array of anchor indices, not {v} `corners` on oltava ankkuri-indeksien taulukko, ei {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: vaiheen {i} tuloksessa ei ole tällaista arvoa (kirjoita $$, jos tarkoitat merkkiä $) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: ennen tätä ei ole vaihetta {i} (vaiheet numeroidaan 0:sta; kirjoita $$, jos tarkoitat merkkiä $) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 3fa64c5b2..be05a7b72 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4760,6 +4760,7 @@ axis Both Les deux ICF Bottom/Left ICF bas/gauche @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` est la référence d’étape {r}, que seuls `run` et `command_batch` résolvent @msg `ids` must be an array of object ids, not {v} `ids` doit être une liste d’identifiants d’objet, pas {v} +@msg `colors` must be an array of colors, not {v} `colors` doit être une liste de couleurs, pas {v} @msg `corners` must be an array of anchor indices, not {v} `corners` doit être une liste d’indices de points d’ancrage, pas {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}` : le résultat de l’étape {i} n’a pas cette valeur (écrivez $$ pour un $ littéral) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}` : il n’y a pas d’étape {i} avant celle-ci (les étapes sont comptées à partir de 0 ; écrivez $$ pour un $ littéral) diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 7e766cf6e..d5d0db2f5 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -5548,6 +5548,7 @@ @msg `id` must be a node id `id` एक नोड आईडी होना चाहिए @msg `id` must be an object id, not {v} `id` एक ऑब्जेक्ट आईडी होना चाहिए, नहीं {v} @msg `ids` must be an array of object ids, not {v} `ids` को ऑब्जेक्ट आईडी की ऐरे होना चाहिए, {v} नहीं +@msg `colors` must be an array of colors, not {v} `colors` को रंगों की ऐरे होना चाहिए, {v} नहीं @msg `ids` must name Objects on Path groups or objects in them `ids` को ऑब्जेक्ट्स ऑन पाथ समूह या उनके ऑब्जेक्ट बताने चाहिए @msg `ids` takes at most {MAX_INSPECT_IDS} ids `ids` में अधिकतम {MAX_INSPECT_IDS} आईडी हो सकती हैं @msg `imageMap` must be none, rectangle or polygon `imageMap` होना चाहिए कोई नहीं, आयत या बहुभुज diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 26cad4650..813a912a5 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4595,6 +4595,7 @@ imageTrace Outline Körvonal @msg `groupColors` must be an array `groupColors`: tömb kell @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve az `id` lépéshivatkozás ({r}), ezt csak a `run` és a `command_batch` oldja fel @msg `ids` must be an array of object ids, not {v} `ids`: objektumazonosítók tömbje kell, ez nem az: {v} +@msg `colors` must be an array of colors, not {v} `colors`: színek tömbje kell, ez nem az: {v} @msg `library` is a library id or name (see {list}) a `library` könyvtárazonosító vagy -név lehet (lásd: {list}) @msg `mode` applies to freeform gradients a `mode` a szabad formájú színátmenetekre vonatkozik @msg `preserve` must be an object `preserve`: objektum kell diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index c7677a71f..bc6fb945f 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -4660,6 +4660,7 @@ imageTrace Outline Kontur @msg No linked files to look for Tidak ada berkas tertaut untuk dicari @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` adalah referensi langkah {r}, yang hanya diselesaikan oleh `run` dan `command_batch` @msg `ids` must be an array of object ids, not {v} `ids` harus berupa array id objek, bukan {v} +@msg `colors` must be an array of colors, not {v} `colors` harus berupa array warna, bukan {v} @msg `corners` must be an array of anchor indices, not {v} `corners` harus berupa array indeks jangkar, bukan {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: hasil langkah {i} tidak memiliki nilai seperti itu (tulis $$ untuk $ literal) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: tidak ada langkah {i} sebelum langkah ini (langkah dihitung mulai dari 0; tulis $$ untuk $ literal) diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index a6ac70bb4..7cb43bf3d 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4794,6 +4794,7 @@ axis Both Entrambi Perspective Distort Distorsione prospettica @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` è il riferimento al passaggio {r}, che solo `run` e `command_batch` risolvono @msg `ids` must be an array of object ids, not {v} `ids` deve essere un elenco di ID oggetto, non {v} +@msg `colors` must be an array of colors, not {v} `colors` deve essere un elenco di colori, non {v} @msg `corners` must be an array of anchor indices, not {v} `corners` deve essere un elenco di indici di punti di ancoraggio, non {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: il risultato del passaggio {i} non ha questo valore (scrivi $$ per un $ letterale) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: non c’è un passaggio {i} prima di questo (i passaggi si contano da 0; scrivi $$ per un $ letterale) diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 3884362fc..34782839e 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4835,6 +4835,7 @@ swatch Bright Violet ビビッドバイオレット @msg No linked files to look for 検索するリンクファイルがありません @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` はステップ参照 {r} です。ステップ参照を解決できるのは `run` と `command_batch` だけです @msg `ids` must be an array of object ids, not {v} `ids` はオブジェクト ID の配列にしてください ({v} は使えません) +@msg `colors` must be an array of colors, not {v} `colors` は色の配列にしてください ({v} は使えません) @msg `corners` must be an array of anchor indices, not {v} `corners` はアンカーポイントのインデックスの配列にしてください ({v} は使えません) @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: ステップ {i} の結果にその値はありません ($ そのものを書くには $$ と入力してください) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: この前にステップ {i} はありません (ステップは 0 から数えます。$ そのものを書くには $$ と入力してください) diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 42ab753ae..165da5c4c 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -4452,6 +4452,7 @@ font-technology Variable ცვლადი @msg every numbered name for it is taken in the Fonts folder შრიფტების საქაღალდეში მისთვის ყველა დანომრილი სახელი დაკავებულია @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` არის ნაბიჯის მიმართვა {r}, რომელსაც მხოლოდ `run` და `command_batch` ხსნის @msg `ids` must be an array of object ids, not {v} `ids` უნდა იყოს ობიექტების id-ების მასივი და არა {v} +@msg `colors` must be an array of colors, not {v} `colors` უნდა იყოს ფერების მასივი და არა {v} @msg `corners` must be an array of anchor indices, not {v} `corners` უნდა იყოს საყრდენი წერტილების ინდექსების მასივი და არა {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: ნაბიჯ {i}-ის შედეგს ასეთი მნიშვნელობა არ აქვს (ლიტერალური $-ისთვის დაწერეთ $$) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: ამის წინ ნაბიჯი {i} არ არის (ნაბიჯები 0-დან ითვლება; ლიტერალური $-ისთვის დაწერეთ $$) diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 97d8eaaef..d093336de 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4762,6 +4762,7 @@ theme Appearance 외관 @msg No linked files to look for 찾을 링크된 파일이 없습니다 @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id`는 단계 참조 {r}이며, `run`과 `command_batch`만 이를 해석합니다 @msg `ids` must be an array of object ids, not {v} `ids`는 {v}이(가) 아니라 개체 id 배열이어야 합니다 +@msg `colors` must be an array of colors, not {v} `colors`는 {v}이(가) 아니라 색상 배열이어야 합니다 @msg `corners` must be an array of anchor indices, not {v} `corners`는 {v}이(가) 아니라 앵커 인덱스 배열이어야 합니다 @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: 단계 {i}의 결과에 해당 값이 없습니다(문자 그대로의 $는 $$로 쓰세요) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: 이 단계 앞에 단계 {i}이(가) 없습니다(단계는 0부터 셉니다. 문자 그대로의 $는 $$로 쓰세요) diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 986489e61..3054ac0f7 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -4672,6 +4672,7 @@ imageTrace Outline Contour @msg No linked files to look for Geen gekoppelde bestanden om naar te zoeken @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` is de stapverwijzing {r}, die alleen `run` en `command_batch` oplossen @msg `ids` must be an array of object ids, not {v} `ids` moet een array van object-id's zijn, niet {v} +@msg `colors` must be an array of colors, not {v} `colors` moet een array van kleuren zijn, niet {v} @msg `corners` must be an array of anchor indices, not {v} `corners` moet een array van ankerindices zijn, niet {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: het resultaat van stap {i} heeft geen dergelijke waarde (schrijf $$ voor een letterlijke $) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: er is geen stap {i} vóór deze stap (stappen tellen vanaf 0; schrijf $$ voor een letterlijke $) diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index a0b0ca47e..8b8258956 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4858,6 +4858,7 @@ imageTrace Outline Kontur @msg No linked files to look for Brak połączonych plików do wyszukania @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` to odwołanie do kroku {r}, które rozwiązują tylko `run` i `command_batch` @msg `ids` must be an array of object ids, not {v} `ids` musi być tablicą identyfikatorów obiektów, nie {v} +@msg `colors` must be an array of colors, not {v} `colors` musi być tablicą kolorów, nie {v} @msg `corners` must be an array of anchor indices, not {v} `corners` musi być tablicą indeksów punktów, nie {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: wynik kroku {i} nie ma takiej wartości (wpisz $$, aby uzyskać znak $) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: przed tym krokiem nie ma kroku {i} (kroki liczone od 0; wpisz $$, aby uzyskać znak $) diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index fe87b21ea..8260f0142 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -4296,6 +4296,7 @@ axis Both Ambos Artboard Prancheta @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` é a referência de etapa {r}, que somente `run` e `command_batch` resolvem @msg `ids` must be an array of object ids, not {v} `ids` deve ser uma matriz de ids de objeto, não {v} +@msg `colors` must be an array of colors, not {v} `colors` deve ser uma matriz de cores, não {v} @msg `corners` must be an array of anchor indices, not {v} `corners` deve ser uma matriz de índices de âncora, não {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: o resultado da etapa {i} não tem esse valor (escreva $$ para um $ literal) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: não há etapa {i} antes desta (as etapas contam a partir de 0; escreva $$ para um $ literal) diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1f6bab7e7..075adabfd 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4791,6 +4791,7 @@ axis Both Оба Exit Выйти @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` — это ссылка на шаг {r}, которую разрешают только `run` и `command_batch` @msg `ids` must be an array of object ids, not {v} `ids` должен быть массивом идентификаторов объектов, а не {v} +@msg `colors` must be an array of colors, not {v} `colors` должен быть массивом цветов, а не {v} @msg `corners` must be an array of anchor indices, not {v} `corners` должен быть массивом индексов опорных точек, а не {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: в результате шага {i} нет такого значения (чтобы написать сам $, введите $$) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: перед этим шагом нет шага {i} (шаги считаются с 0; чтобы написать сам $, введите $$) diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index d1e716e64..e68195185 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -4669,6 +4669,7 @@ axis Both Båda ICF Box ICF-ruta @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` är stegreferensen {r}, som bara `run` och `command_batch` löser upp @msg `ids` must be an array of object ids, not {v} `ids` måste vara en array med objekt-id:n, inte {v} +@msg `colors` must be an array of colors, not {v} `colors` måste vara en array med färger, inte {v} @msg `corners` must be an array of anchor indices, not {v} `corners` måste vara en array med index för ankarpunkter, inte {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: resultatet från steg {i} har inget sådant värde (skriv $$ för ett bokstavligt $) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: det finns inget steg {i} före det här (stegen räknas från 0; skriv $$ för ett bokstavligt $) diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 59ae480bc..3aee4db8c 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -4501,6 +4501,10 @@ axis Both Her İkisi Em Box Em Kutusu x-Height x-Yüksekliği ICF Box ICF Kutusu +@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve "id", yalnızca "run" ve "command_batch" çözümleyen {r} adım referansıdır +@msg `ids` must be an array of object ids, not {v} "kimlikler" {v} değil, bir nesne kimlikleri dizisi olmalıdır +@msg `colors` must be an array of colors, not {v} `colors` {v} değil, bir renk dizisi olmalıdır +@msg `corners` must be an array of anchor indices, not {v} "köşeler" {v} değil, bir bağlantı indeksleri dizisi olmalıdır @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id`, yalnızca `run` ve `command_batch` komutlarının çözümlediği {r} adım referansıdır @msg `ids` must be an array of object ids, not {v} `ids` bir nesne kimlikleri dizisi olmalıdır, {v} değil @msg `corners` must be an array of anchor indices, not {v} `corners` bir bağlantı noktası indeksleri dizisi olmalıdır, {v} değil diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index ec1b07ec3..7e3b49e47 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4778,6 +4778,7 @@ axis Both Обидві Exit Вийти @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` — це посилання на крок {r}, яке розв’язують лише `run` і `command_batch` @msg `ids` must be an array of object ids, not {v} `ids` має бути масивом ідентифікаторів об’єктів, а не {v} +@msg `colors` must be an array of colors, not {v} `colors` має бути масивом кольорів, а не {v} @msg `corners` must be an array of anchor indices, not {v} `corners` має бути масивом індексів опорних точок, а не {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`: у результаті кроку {i} немає такого значення (щоб написати сам $, введіть $$) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`: перед цим кроком немає кроку {i} (кроки рахуються з 0; щоб написати сам $, введіть $$) diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 0ed0816b6..4faf2b168 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4247,6 +4247,7 @@ theme Appearance Giao diện @msg `groupColors` must be an array `groupColors` phải là một mảng @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` là tham chiếu bước {r}, chỉ `run` và `command_batch` giải quyết @msg `ids` must be an array of object ids, not {v} `ids` phải là mảng id đối tượng, không phải {v} +@msg `colors` must be an array of colors, not {v} `colors` phải là mảng màu, không phải {v} @msg `library` is a library id or name (see {list}) `library` là id hoặc tên thư viện (xem {list}) @msg `mode` applies to freeform gradients `mode` áp dụng cho chuyển sắc tự do @msg `preserve` must be an object `preserve` phải là một đối tượng diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 71975b2b8..9d18bb5c5 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4545,6 +4545,7 @@ imageTrace Outline 轮廓 @msg No linked files to look for 没有要查找的链接文件 @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` 是步骤引用 {r},只有 `run` 和 `command_batch` 能解析它 @msg `ids` must be an array of object ids, not {v} `ids` 必须是由对象 id 组成的数组,而不是 {v} +@msg `colors` must be an array of colors, not {v} `colors` 必须是由颜色组成的数组,而不是 {v} @msg `corners` must be an array of anchor indices, not {v} `corners` 必须是由锚点索引组成的数组,而不是 {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`:步骤 {i} 的结果中没有此值 (要表示 $ 本身,请写 $$) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`:此步骤之前没有步骤 {i} (步骤从 0 开始计数;要表示 $ 本身,请写 $$) diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 6dbbd1e20..26ceb22b5 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2419,6 +2419,7 @@ file-filter Templates 範本 @msg No linked files to look for 沒有要尋找的連結檔案 @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` 是步驟參照 {r},只有 `run` 和 `command_batch` 能解析它 @msg `ids` must be an array of object ids, not {v} `ids` 必須是由物件 id 組成的陣列,而非 {v} +@msg `colors` must be an array of colors, not {v} `colors` 必須是由顏色組成的陣列,而非 {v} @msg `corners` must be an array of anchor indices, not {v} `corners` 必須是由錨點索引組成的陣列,而非 {v} @msg `{text}`: step {i}'s result has no such value (write $$ for a literal $) `{text}`:步驟 {i} 的結果中沒有此值 (若要表示 $ 本身,請寫 $$) @msg `{text}`: there is no step {i} before this one (steps count from 0; write $$ for a literal $) `{text}`:此步驟之前沒有步驟 {i} (步驟從 0 起算;若要表示 $ 本身,請寫 $$) From 612682970fe81fde392bc91c648e496e167ee367 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 8 Nov 2026 12:59:32 +0700 Subject: [PATCH 4/4] i18n: keep one Turkish row for each message 6129 touches --- crates/ui-egui/src/i18n/tr.tsv | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 3aee4db8c..ad3ed7518 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -4501,10 +4501,7 @@ axis Both Her İkisi Em Box Em Kutusu x-Height x-Yüksekliği ICF Box ICF Kutusu -@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve "id", yalnızca "run" ve "command_batch" çözümleyen {r} adım referansıdır -@msg `ids` must be an array of object ids, not {v} "kimlikler" {v} değil, bir nesne kimlikleri dizisi olmalıdır @msg `colors` must be an array of colors, not {v} `colors` {v} değil, bir renk dizisi olmalıdır -@msg `corners` must be an array of anchor indices, not {v} "köşeler" {v} değil, bir bağlantı indeksleri dizisi olmalıdır @msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id`, yalnızca `run` ve `command_batch` komutlarının çözümlediği {r} adım referansıdır @msg `ids` must be an array of object ids, not {v} `ids` bir nesne kimlikleri dizisi olmalıdır, {v} değil @msg `corners` must be an array of anchor indices, not {v} `corners` bir bağlantı noktası indeksleri dizisi olmalıdır, {v} değil