i18n: catalogue entries for the context menu, PLINE and modify options

Every new UI string (menu rows, Options → Right-click Customization, the
PLINE sub-prompts and keyword labels, MOVE/COPY/OFFSET prompts) gets its
Fluent attribute in all 21 locales plus the source → attribute rows in
locale_catalog.rs; the SETVAR help line lists SHORTCUTMENU and
SHORTCUTMENUDURATION. Tests that compared prompt text now check command
state so they pass under any UI language.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PanosandClaude Opus 5 committed 2026-09-17 12:05:15 +03:00
1 parent a5fd040745
commit 426af2d3a5
24 files changed
+954 -31

No files matched your search

+2 -1
View File
@@ -743,7 +743,8 @@ mod tests {
assert!(app.tabs[0].active_grip.is_none());
assert_eq!(active(&app), Some("MOVE"));
let prompt = app.tabs[0].active_cmd.as_ref().map(|c| c.prompt()).unwrap_or_default();
assert!(!prompt.contains("base point"), "base already consumed: {prompt}");
let base_prompt = crate::tr!("command-move", "base", count = 1i64);
assert_ne!(prompt, base_prompt, "base already consumed");
assert_eq!(app.last_point, Some(DVec3::ZERO));
});
}
+43 -1
View File
@@ -4953,7 +4953,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"SECTIONPLANE Specify through point:" => Some(("common", "sectionplane-specify-through-point")),
"SETBYLAYER: no properties required changes." => Some(("common", "setbylayer-no-properties-required-changes")),
"SETBYLAYER: reset {} entity/entities to ByLayer." => Some(("common", "setbylayer-reset-entity-entities-to-bylayer")),
"SETVAR: CETRANSPARENCY LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE CONSTRAINTSOLVEMODE CONSTRAINTINFER CONSTRAINTBARDISPLAY ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER GRIPOBJLIMIT | CLAYER CELTYPE TEXTSTYLE (read-only)" => Some(("common", "setvar-cetransparency-ltscale-celtscale-pdmode-pdsize-textsize-orthomode-fillmode-mirrtext-frame-imageframe-pdfframe-wipeoutframe-xclipframe-pointcloudclipframe-zoomwheel-zoomfactor-cursorsize-pickbox-cursortype-snapang-textfill-clipromptlines-commandlinefadetime-attreq-attdia-dimassoc-dimcontinuemode-constraintsolvemode-constraintinfer-constraintbardisplay-angbase-angdir-sketchinc-skpoly-sktolerance-donutid-donutod-centerexe-centerlayer-centerltype-centerltscale-centerltypefile-centercrosssize-centercrossgap-centermarkexe-colortheme-selectionarea-selectionareaopacity-selectioneffect-selectioneffectcolor-windowsareacolor-crossingareacolor-selectionpreview-gripsize-gripcolor-griphot-griphover-gripobjlimit-clayer-celtype-textstyle-read-only")),
"SETVAR: CETRANSPARENCY LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR SHORTCUTMENU SHORTCUTMENUDURATION CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES COMMANDLINEFADETIME ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE CONSTRAINTSOLVEMODE CONSTRAINTINFER CONSTRAINTBARDISPLAY ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER GRIPOBJLIMIT | CLAYER CELTYPE TEXTSTYLE (read-only)" => Some(("common", "setvar-cetransparency-ltscale-celtscale-pdmode-pdsize-textsize-orthomode-fillmode-mirrtext-frame-imageframe-pdfframe-wipeoutframe-xclipframe-pointcloudclipframe-zoomwheel-zoomfactor-cursorsize-pickbox-cursortype-snapang-textfill-clipromptlines-commandlinefadetime-attreq-attdia-dimassoc-dimcontinuemode-constraintsolvemode-constraintinfer-constraintbardisplay-angbase-angdir-sketchinc-skpoly-sktolerance-donutid-donutod-centerexe-centerlayer-centerltype-centerltscale-centerltypefile-centercrosssize-centercrossgap-centermarkexe-colortheme-selectionarea-selectionareaopacity-selectioneffect-selectioneffectcolor-windowsareacolor-crossingareacolor-selectionpreview-gripsize-gripcolor-griphot-griphover-gripobjlimit-clayer-celtype-textstyle-read-only")),
"SHELL supports rectangular boxes, circular cylinders, and spheres." => Some(("common", "shell-supports-rectangular-boxes-circular-cylinders-and-spheres")),
"SHELL: solid updated." => Some(("common", "shell-solid-updated")),
"SPLINEDIT Fit data [Add/Delete/Move/Purge/Tangents/eXit] <eXit>:" => Some(("common", "splinedit-fit-data-add-delete-move-purge-tangents-exit-exit")),
@@ -5093,6 +5093,48 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"{} row(s) skipped: duplicate parameter name." => Some(("common", "row-s-skipped-duplicate-parameter-name")),
"{} surface(s) cannot be thickened because the offset intersects itself." => Some(("common", "surface-s-cannot-be-thickened-because-the-offset-intersects-itself")),
"⚠ {parametric_conflicts} conflicting" => Some(("common", "conflicting")),
"PLINE Specify starting width <%{w}>:" => Some(("draw", "pline-specify-starting-width")),
"PLINE Specify ending width <%{w}>:" => Some(("draw", "pline-specify-ending-width")),
"PLINE Specify starting half-width <%{w}>:" => Some(("draw", "pline-specify-starting-half-width")),
"PLINE Specify ending half-width <%{w}>:" => Some(("draw", "pline-specify-ending-half-width")),
"PLINE (Line) Specify next point:" => Some(("draw", "pline-line-specify-next-point")),
"PLINE (Arc) Specify endpoint of arc:" => Some(("draw", "pline-arc-specify-endpoint-of-arc")),
"PLINE Specify length of line:" => Some(("draw", "pline-specify-length-of-line")),
"PLINE Specify included angle:" => Some(("draw", "pline-specify-included-angle")),
"PLINE Specify endpoint of arc:" => Some(("draw", "pline-specify-endpoint-of-arc")),
"PLINE Specify center point of arc:" => Some(("draw", "pline-specify-center-point-of-arc")),
"PLINE Specify radius of arc:" => Some(("draw", "pline-specify-radius-of-arc")),
"PLINE Specify direction of chord for arc:" => Some(("draw", "pline-specify-direction-of-chord-for-arc")),
"PLINE Specify length of chord:" => Some(("draw", "pline-specify-length-of-chord")),
"PLINE Specify tangent direction for the start point of arc:" => Some(("draw", "pline-specify-tangent-direction-for-the-start-point-of-arc")),
"PLINE Specify second point on arc:" => Some(("draw", "pline-specify-second-point-on-arc")),
"Halfwidth" => Some(("draw", "halfwidth")),
"CEnter" => Some(("draw", "center-keyword")),
"CLose" => Some(("draw", "close-keyword")),
"Second pt" => Some(("draw", "second-pt")),
"ARea" => Some(("modify", "area-keyword")),
"Displacement" => Some(("modify", "displacement")),
"mOde" => Some(("modify", "mode-keyword")),
"Array" => Some(("modify", "array-keyword")),
"Use first point as displacement" => Some(("modify", "use-first-point-as-displacement")),
"OFFSET Erase source object after offsetting? [Yes/No] <No>:" => Some(("modify", "offset-erase-source-object-after-offsetting")),
"OFFSET Enter layer option for offset objects [Current/Source] <Source>:" => Some(("modify", "offset-enter-layer-option-for-offset-objects")),
"Recent Input" => Some(("ui", "recent-input")),
"Copy with Base Point" => Some(("ui", "copy-with-base-point")),
"Undo %{label}" => Some(("ui", "undo-label")),
"Redo %{label}" => Some(("ui", "redo-label")),
"Copy Selection" => Some(("ui", "copy-selection")),
"Isolate" => Some(("ui", "isolate")),
"Deselect All" => Some(("ui", "deselect-all")),
"Options..." => Some(("ui", "options-ellipsis")),
"Base Point" => Some(("ui", "base-point")),
"Right-click Customization" => Some(("options", "right-click-customization")),
"Right-click in drawing area" => Some(("options", "right-click-in-drawing-area")),
"Hold duration" => Some(("options", "hold-duration")),
"Shortcut menu: right-click always opens the menu (AutoCAD default). Time-sensitive: a quick click is Enter, holding longer opens the menu (SHORTCUTMENUDURATION)." => Some(("options", "right-click-mode-help")),
"Shortcut menu" => Some(("options", "right-click-mode-shortcut-menu")),
"Time-sensitive (quick click = Enter)" => Some(("options", "right-click-mode-time-sensitive")),
"Enter first, second click = menu" => Some(("options", "right-click-mode-enter-first")),
_ => None,
}
}
+6 -8
View File
@@ -437,9 +437,7 @@ impl PlineCommand {
if !value.is_finite() {
return None;
}
let Some((a, z)) = self.last_local() else {
return None;
};
let (a, z) = self.last_local()?;
match self.sub {
Sub::WidthStart { half } => {
if value < 0.0 {
@@ -1296,9 +1294,9 @@ mod tests {
fn width_option_sets_segment_widths_in_entity() {
let mut cmd = pl(&[(0.0, 0.0)]);
assert!(matches!(cmd.on_text_input("W"), Some(CmdResult::NeedPoint)));
assert!(cmd.prompt().contains("starting width"));
assert!(matches!(cmd.sub, Sub::WidthStart { half: false }));
assert!(matches!(cmd.on_text_input("1"), Some(CmdResult::NeedPoint)));
assert!(cmd.prompt().contains("ending width"));
assert!(matches!(cmd.sub, Sub::WidthEnd { half: false, .. }));
assert!(matches!(cmd.on_text_input("2"), Some(CmdResult::NeedPoint)));
assert!(matches!(cmd.sub, Sub::None));
let e = entity_of(cmd.on_point(DVec3::new(10.0, 0.0, 0.0)));
@@ -1314,7 +1312,7 @@ mod tests {
fn halfwidth_doubles_value_and_enter_keeps_default() {
let mut cmd = pl(&[(0.0, 0.0)]);
cmd.on_text_input("H");
assert!(cmd.prompt().contains("half-width"));
assert!(matches!(cmd.sub, Sub::WidthStart { half: true }));
cmd.on_text_input("0.5");
// Enter at the ending prompt keeps the starting value.
assert!(matches!(cmd.on_enter(), CmdResult::NeedPoint));
@@ -1326,7 +1324,7 @@ mod tests {
fn length_extends_along_last_tangent() {
let mut cmd = pl(&[(0.0, 0.0), (10.0, 0.0)]);
cmd.on_text_input("L");
assert!(cmd.prompt().contains("length"));
assert!(matches!(cmd.sub, Sub::Length));
let e = entity_of(cmd.on_text_input("5").expect("length consumed"));
assert_eq!(e.vertices.len(), 3);
assert!((e.vertices[2].location.x - 15.0).abs() < 1e-9);
@@ -1378,7 +1376,7 @@ mod tests {
let mut cmd = pl(&[(0.0, 0.0), (10.0, 0.0)]);
cmd.on_text_input("A");
cmd.on_text_input("CE");
assert!(cmd.prompt().contains("center"));
assert!(matches!(cmd.sub, Sub::ArcCenter));
cmd.on_point(DVec3::new(10.0, 5.0, 0.0));
assert_eq!(keywords(&cmd), ["A", "L"]);
let e = entity_of(cmd.on_point(DVec3::new(20.0, 5.0, 0.0)));