Complete application localization across all supported languages

This commit is contained in:
Hakan Seven committed 2026-09-07 01:05:02 +03:00
1 parent 96c794c072
commit d66e89ca19
78 files changed
+26043 -877

No files matched your search

+8 -8
View File
@@ -555,11 +555,11 @@ impl OpenCADStudio {
_ => return None,
};
if add.is_empty() {
self.command_line.push_info(match kw.as_str() {
self.command_line.push_info(crate::t!(match kw.as_str() {
"P" | "PREVIOUS" => "No previous selection set.",
"ALL" => "Nothing to select.",
_ => "No last object.",
});
}).as_ref());
return Some(Task::none());
}
let count = add.len();
@@ -1103,7 +1103,7 @@ impl OpenCADStudio {
});
if !valid {
self.command_line.push_error(
"MVIEW Object: select a closed paper-space circle, ellipse, or polyline.",
crate::t!("MVIEW Object: select a closed paper-space circle, ellipse, or polyline.").as_ref(),
);
if let Some(prompt) =
self.tabs[i].active_cmd.as_ref().map(|command| command.prompt())
@@ -2140,11 +2140,11 @@ impl OpenCADStudio {
if self.tabs[i].scene.reassociate_center_mark(target, source, point) {
self.tabs[i].dirty = true;
self.command_line.push_output(
"CENTERREASSOCIATE: center mark associated.",
crate::t!("CENTERREASSOCIATE: center mark associated.").as_ref(),
);
} else {
self.command_line.push_error(
"CENTERREASSOCIATE: the selected source is not circular.",
crate::t!("CENTERREASSOCIATE: the selected source is not circular.").as_ref(),
);
}
self.tabs[i].active_cmd = None;
@@ -2414,11 +2414,11 @@ impl OpenCADStudio {
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.restore_pre_cmd_tangent();
self.command_line.push_info(if space_changed {
self.command_line.push_info(crate::t!(if space_changed {
"Command cancelled because the active drawing space changed."
} else {
"Command cancelled."
});
}).as_ref());
}
CmdResult::SelectByPath {
path,
@@ -3200,7 +3200,7 @@ impl OpenCADStudio {
self.tabs[i].scene.clear_preview_wire();
self.restore_pre_cmd_tangent();
self.command_line.push_error(
"JOIN: objects don't form a single connected chain, or contain an unsupported type / tilted arc.",
crate::t!("JOIN: objects don't form a single connected chain, or contain an unsupported type / tilted arc.").as_ref(),
);
}
}
+1 -1
View File
@@ -529,7 +529,7 @@ impl OpenCADStudio {
let sel: Vec<_> = self.tabs[i].scene.selected.iter().copied().collect();
if sel.is_empty() {
self.command_line.push_error(
"WBLOCK Select entities first, or: WBLOCK <block name> or WBLOCK *",
crate::t!("WBLOCK Select entities first, or: WBLOCK <block name> or WBLOCK *").as_ref(),
);
} else {
return Some(Task::done(Message::WblockSave("*".to_string())));
+3 -3
View File
@@ -130,7 +130,7 @@ impl OpenCADStudio {
}
if opt.is_empty() {
self.command_line.push_info(
"Usage: JUSTIFYTEXT <Left|Center|Right|Middle|TL|TC|TR|ML|MC|MR|BL|BC|BR>",
crate::t!("Usage: JUSTIFYTEXT <Left|Center|Right|Middle|TL|TC|TR|ML|MC|MR|BL|BC|BR>").as_ref(),
);
return Some(Task::none());
}
@@ -401,7 +401,7 @@ impl OpenCADStudio {
Some(v) if v > 0.0 => v,
_ => {
self.command_line.push_info(
"Usage: TEXTFIT <target width> (fits selected text to that width)",
crate::t!("Usage: TEXTFIT <target width> (fits selected text to that width)").as_ref(),
);
return Some(Task::none());
}
@@ -1311,7 +1311,7 @@ impl OpenCADStudio {
let text = cmd.strip_prefix("ARCTEXT").unwrap_or("").trim().to_string();
if text.is_empty() {
self.command_line.push_info(
"Usage: ARCTEXT <text> (select an arc first; the text follows it)",
crate::t!("Usage: ARCTEXT <text> (select an arc first; the text follows it)").as_ref(),
);
return None;
}
+16 -16
View File
@@ -429,11 +429,11 @@ impl OpenCADStudio {
self.tabs[i].scene.set_drawing_limit_check(enabled);
self.tabs[i].dirty = true;
}
self.command_line.push_output(if enabled {
self.command_line.push_output(crate::t!(if enabled {
"Limits checking ON."
} else {
"Limits checking OFF."
});
}).as_ref());
}
cmd if cmd.starts_with("LIMITS SET ") => {
let tokens: Vec<&str> = cmd["LIMITS SET ".len()..].split_whitespace().collect();
@@ -492,14 +492,14 @@ impl OpenCADStudio {
"REDRAW" => {
use crate::scene::ViewportRefreshScope;
self.tabs[i].scene.request_refresh(ViewportRefreshScope::Active);
self.command_line.push_output("REDRAW: viewport refreshed.");
self.command_line.push_output(crate::t!("REDRAW: viewport refreshed.").as_ref());
return Some(Task::none());
}
// REDRAWALL — force re-rasterize of every generated viewport.
"REDRAWALL" => {
use crate::scene::ViewportRefreshScope;
self.tabs[i].scene.request_refresh(ViewportRefreshScope::All);
self.command_line.push_output("REDRAWALL: viewports refreshed.");
self.command_line.push_output(crate::t!("REDRAWALL: viewports refreshed.").as_ref());
return Some(Task::none());
}
// REGEN — full model regeneration (bump_geometry: geometry_epoch AND
@@ -509,7 +509,7 @@ impl OpenCADStudio {
// REGENALL is functionally identical (C5).
"REGEN" | "REGENALL" => {
self.tabs[i].scene.bump_geometry();
self.command_line.push_output("REGEN: regenerated model.");
self.command_line.push_output(crate::t!("REGEN: regenerated model.").as_ref());
return Some(Task::none());
}
@@ -877,8 +877,8 @@ impl OpenCADStudio {
let active = self
.active_plot_style
.as_ref()
.map(|t| format!("Active: {}", t.name))
.unwrap_or_else(|| "No plot style loaded.".into());
.map(|t| crate::tf!("Active: {}", t.name).into_owned())
.unwrap_or_else(|| crate::t!("No plot style loaded.").into_owned());
self.command_line.push_info(&active);
return Some(Task::done(Message::PlotStyleLoad));
}
@@ -887,13 +887,13 @@ impl OpenCADStudio {
.active_plot_style
.as_ref()
.map(|t| {
format!(
crate::tf!(
"Plot style: {} ({} color overrides)",
t.name,
t.aci_entries.iter().filter(|e| e.color.is_some()).count()
)
).into_owned()
})
.unwrap_or_else(|| "No plot style table loaded.".into());
.unwrap_or_else(|| crate::t!("No plot style table loaded.").into_owned());
self.command_line.push_output(&msg);
}
_ => {
@@ -1215,7 +1215,7 @@ impl OpenCADStudio {
.push_output(crate::tf!("ADJUST: {action} = {v} on {changed} image(s).").as_ref());
} else {
self.command_line.push_error(
"ADJUST: no raster images selected, or unknown property (use BRIGHTNESS|CONTRAST|FADE).",
crate::t!("ADJUST: no raster images selected, or unknown property (use BRIGHTNESS|CONTRAST|FADE).").as_ref(),
);
}
} else {
@@ -1274,7 +1274,7 @@ impl OpenCADStudio {
match value.parse::<i8>() {
Ok(mode @ -4..=4) => self.annotation_auto_scale = mode,
_ => self.command_line.push_error(
"ANNOAUTOSCALE: enter an integer from -4 through 4.",
crate::t!("ANNOAUTOSCALE: enter an integer from -4 through 4.").as_ref(),
),
}
}
@@ -1461,7 +1461,7 @@ impl OpenCADStudio {
let path = cmd.trim_start_matches("DATALINK").trim();
if path.is_empty() {
self.command_line.push_info(
"Usage: DATALINK <path-to-.csv>",
crate::t!("Usage: DATALINK <path-to-.csv>").as_ref(),
);
return Some(Task::none());
}
@@ -1574,7 +1574,7 @@ impl OpenCADStudio {
}
if jobs.is_empty() {
self.command_line
.push_error("DATALINKUPDATE: no linked tables found.");
.push_error(crate::t!("DATALINKUPDATE: no linked tables found.").as_ref());
return Some(Task::none());
}
if write_back {
@@ -1606,7 +1606,7 @@ impl OpenCADStudio {
.collect();
if updates.is_empty() {
self.command_line
.push_error("DATALINKUPDATE: linked sources could not be read.");
.push_error(crate::t!("DATALINKUPDATE: linked sources could not be read.").as_ref());
return Some(Task::none());
}
self.push_undo_snapshot(i, "DATALINKUPDATE");
@@ -1675,7 +1675,7 @@ impl OpenCADStudio {
let path = cmd.trim_start_matches("LANDXMLIMPORT").trim();
if path.is_empty() {
self.command_line.push_info(
"Usage: LANDXMLIMPORT <path-to-.xml> (imports CgPoint survey points)",
crate::t!("Usage: LANDXMLIMPORT <path-to-.xml> (imports CgPoint survey points)").as_ref(),
);
return Some(Task::none());
}
+8 -8
View File
@@ -526,7 +526,7 @@ impl OpenCADStudio {
self.tabs[i].dirty = true;
}
self.command_line
.push_output(&format!("CENTERRESET: {count} center object(s) updated."));
.push_output(&crate::tf!("CENTERRESET: {count} center object(s) updated."));
}
"CENTERREASSOCIATE" => {
@@ -549,7 +549,7 @@ impl OpenCADStudio {
self.tabs[i].dirty = true;
}
self.command_line
.push_output(&format!("CENTERREASSOCIATE: {count} center object(s) associated."));
.push_output(&crate::tf!("CENTERREASSOCIATE: {count} center object(s) associated."));
}
"CENTERDISASSOCIATE" => {
@@ -561,7 +561,7 @@ impl OpenCADStudio {
self.tabs[i].dirty = true;
}
self.command_line
.push_output(&format!("CENTERDISASSOCIATE: {count} center object(s) detached."));
.push_output(&crate::tf!("CENTERDISASSOCIATE: {count} center object(s) detached."));
}
"DIMCENTER" => {
@@ -1159,7 +1159,7 @@ impl OpenCADStudio {
match parts.get(val_idx).and_then(|s| s.parse::<f64>().ok()) {
Some(v) => return Some(self.solid_section(axis, v)),
None => self.command_line.push_info(
"Usage: SECTION [X|Y|Z] <value> (cross-sections the selected solid)",
crate::t!("Usage: SECTION [X|Y|Z] <value> (cross-sections the selected solid)").as_ref(),
),
}
}
@@ -1185,7 +1185,7 @@ impl OpenCADStudio {
return Some(self.solid_align3d(src, dst));
}
self.command_line.push_info(
"Usage: 3DALIGN <sx1 sy1 sz1 … sx3 sy3 sz3 dx1 dy1 dz1 … dx3 dy3 dz3> (18 numbers: 3 source then 3 destination points)",
crate::t!("Usage: 3DALIGN <sx1 sy1 sz1 … sx3 sy3 sz3 dx1 dy1 dz1 … dx3 dy3 dz3> (18 numbers: 3 source then 3 destination points)").as_ref(),
);
}
@@ -1214,7 +1214,7 @@ impl OpenCADStudio {
Some("Z") => 2,
_ => {
self.command_line.push_info(
"Usage: 3DMIRROR [X|Y|Z] (mirrors the selected solid across that plane)",
crate::t!("Usage: 3DMIRROR [X|Y|Z] (mirrors the selected solid across that plane)").as_ref(),
);
return None;
}
@@ -1254,7 +1254,7 @@ impl OpenCADStudio {
match angle {
Some(a) => return Some(self.solid_rotate3d(axis, a)),
None => self.command_line.push_info(
"Usage: 3DROTATE [X|Y|Z] <angle> (rotates the selected solid)",
crate::t!("Usage: 3DROTATE [X|Y|Z] <angle> (rotates the selected solid)").as_ref(),
),
}
}
@@ -1294,7 +1294,7 @@ impl OpenCADStudio {
match value {
Some(v) => return Some(self.solid_slice(axis, v, keep_low)),
None => self.command_line.push_info(
"Usage: SLICE [X|Y|Z] <value> [TOP|BOTTOM] (cuts the selected solid)",
crate::t!("Usage: SLICE [X|Y|Z] <value> [TOP|BOTTOM] (cuts the selected solid)").as_ref(),
),
}
}
+7 -7
View File
@@ -54,7 +54,7 @@ impl OpenCADStudio {
self.command_line.push_output(crate::tf!(
"SAVEALL: saved {saved} drawing(s){}.",
if skipped > 0 {
format!("; {skipped} need SAVEAS (no file path yet)")
crate::tf!("; {skipped} need SAVEAS (no file path yet)").into_owned()
} else {
String::new()
}
@@ -130,10 +130,10 @@ impl OpenCADStudio {
let style = cmd.split_whitespace().nth(1).unwrap_or("");
let Some(mode) = visual_style::mode_for_keyword(style) else {
self.command_line
.push_error(visual_style::keyword_prompt());
.push_error(crate::t!(visual_style::keyword_prompt()).as_ref());
return Some(Task::none());
};
let label = visual_style::label_for(mode);
let label = crate::t!(visual_style::label_for(mode));
self.command_line
.push_output(crate::tf!("Visual style: {label}.").as_ref());
return Some(Task::done(Message::SetRenderMode(mode)));
@@ -220,11 +220,11 @@ impl OpenCADStudio {
"PERF" => {
self.perf_hud = !self.perf_hud;
crate::perf::set_ui_enabled(self.perf_hud);
self.command_line.push_info(if self.perf_hud {
self.command_line.push_info(crate::t!(if self.perf_hud {
"PERF panel on — tracing render and interaction costs"
} else {
"PERF panel off"
});
}).as_ref());
return Some(Task::none());
}
@@ -299,7 +299,7 @@ impl OpenCADStudio {
rgb[2]
).as_ref());
} else {
self.command_line.push_info("Usage: BACKGROUND DESK <r> <g> <b> | DEFAULT");
self.command_line.push_info(crate::t!("Usage: BACKGROUND DESK <r> <g> <b> | DEFAULT").as_ref());
}
} else if let Some(rgba) = parse_background_color(&args) {
let [r, g, b, _] = rgba;
@@ -322,7 +322,7 @@ impl OpenCADStudio {
).as_ref());
} else {
self.command_line.push_info(
"Usage: BACKGROUND <r> <g> <b> (0–255) | THEME|CLASSIC|DEFAULT|DESK|BLACK|DARKGRAY|GRAY|LIGHTGRAY|WHITE",
crate::t!("Usage: BACKGROUND <r> <g> <b> (0–255) | THEME|CLASSIC|DEFAULT|DESK|BLACK|DARKGRAY|GRAY|LIGHTGRAY|WHITE").as_ref(),
);
}
}
+37 -37
View File
@@ -119,9 +119,9 @@ impl OpenCADStudio {
} else {
use std::collections::BTreeMap;
let space = if scene.current_layout == "Model" {
"Model space".to_string()
crate::t!("Model space").into_owned()
} else {
format!("Paper space '{}'", scene.current_layout)
crate::tf!("Paper space '{}'", scene.current_layout).into_owned()
};
let mut handles: Vec<u64> = Vec::with_capacity(selected.len());
let mut type_counts: BTreeMap<&'static str, usize> = BTreeMap::new();
@@ -139,18 +139,18 @@ impl OpenCADStudio {
let types: Vec<String> =
type_counts.iter().map(|(t, n)| format!("{t}×{n}")).collect();
let list: Vec<String> = handles.iter().map(|h| format!("{:X}", h)).collect();
let mut msg = format!(
let mut msg = crate::tf!(
"SELHANDLES: {} selected in {}\n Types: {}",
handles.len(),
space,
types.join(", ")
);
).into_owned();
if !block_counts.is_empty() {
let blocks: Vec<String> =
block_counts.iter().map(|(b, n)| format!("{b}×{n}")).collect();
msg.push_str(&format!("\n Blocks: {}", blocks.join(", ")));
msg.push_str(&crate::tf!("\n Blocks: {}", blocks.join(", ")).into_owned());
}
msg.push_str(&format!("\n Handles: {}", list.join(",")));
msg.push_str(&crate::tf!("\n Handles: {}", list.join(",")).into_owned());
self.command_line.push_output(&msg);
}
}
@@ -180,10 +180,10 @@ impl OpenCADStudio {
};
let details = entity_list_details(entity);
format!(
"{type_name} Handle:{:X} Layer:{} Color:{} LT:{}{}",
common.handle.value(),
common.layer,
color_str,
"{type_name} {}:{:X} {}:{} {}:{} LT:{}{}",
crate::t!("Handle"), common.handle.value(),
crate::t!("Layer"), common.layer,
crate::t!("Color"), color_str,
linetype,
if details.is_empty() {
String::new()
@@ -1360,7 +1360,7 @@ impl OpenCADStudio {
// Use the first selected object as the template.
let Some(handle) = self.tabs[i].scene.selected.iter().next().copied() else {
self.command_line.push_info(
"ADDSELECTED: select an object first, then run ADDSELECTED to draw a new one like it.",
crate::t!("ADDSELECTED: select an object first, then run ADDSELECTED to draw a new one like it.").as_ref(),
);
return Task::none();
};
@@ -1558,7 +1558,7 @@ fn add_selected_verb(entity: &acadrust::EntityType) -> Option<&'static str> {
fn entity_list_details(entity: &acadrust::EntityType) -> String {
use std::f64::consts::PI;
match entity {
acadrust::EntityType::Line(l) => format!(
acadrust::EntityType::Line(l) => crate::tf!(
"from ({:.4},{:.4},{:.4}) to ({:.4},{:.4},{:.4}) len={:.4}",
l.start.x,
l.start.y,
@@ -1570,16 +1570,16 @@ fn entity_list_details(entity: &acadrust::EntityType) -> String {
+ (l.end.y - l.start.y).powi(2)
+ (l.end.z - l.start.z).powi(2))
.sqrt()
),
acadrust::EntityType::Circle(c) => format!(
).into_owned(),
acadrust::EntityType::Circle(c) => crate::tf!(
"center ({:.4},{:.4},{:.4}) r={:.4} area={:.4}",
c.center.x,
c.center.y,
c.center.z,
c.radius,
PI * c.radius * c.radius
),
acadrust::EntityType::Arc(a) => format!(
).into_owned(),
acadrust::EntityType::Arc(a) => crate::tf!(
"center ({:.4},{:.4},{:.4}) r={:.4} start={:.2}° end={:.2}°",
a.center.x,
a.center.y,
@@ -1587,25 +1587,25 @@ fn entity_list_details(entity: &acadrust::EntityType) -> String {
a.radius,
a.start_angle.to_degrees(),
a.end_angle.to_degrees()
),
acadrust::EntityType::LwPolyline(p) => format!(
).into_owned(),
acadrust::EntityType::LwPolyline(p) => crate::tf!(
"{} vertices closed={} elevation={:.4}",
p.vertices.len(),
p.is_closed,
p.elevation
),
acadrust::EntityType::Text(t) => format!(
).into_owned(),
acadrust::EntityType::Text(t) => crate::tf!(
"\"{}\" h={:.4} at ({:.4},{:.4})",
t.value, t.height, t.insertion_point.x, t.insertion_point.y
),
acadrust::EntityType::MText(t) => format!(
).into_owned(),
acadrust::EntityType::MText(t) => crate::tf!(
"\"{}\" h={:.4} at ({:.4},{:.4})",
t.value.chars().take(40).collect::<String>(),
t.height,
t.insertion_point.x,
t.insertion_point.y
),
acadrust::EntityType::Insert(ins) => format!(
).into_owned(),
acadrust::EntityType::Insert(ins) => crate::tf!(
"block=\"{}\" at ({:.4},{:.4},{:.4}) scale=({:.4},{:.4},{:.4}) rot={:.2}°",
ins.block_name,
ins.insert_point.x,
@@ -1615,20 +1615,20 @@ fn entity_list_details(entity: &acadrust::EntityType) -> String {
ins.y_scale(),
ins.z_scale(),
ins.rotation.to_degrees()
),
acadrust::EntityType::Spline(s) => format!(
).into_owned(),
acadrust::EntityType::Spline(s) => crate::tf!(
"{} ctrl pts degree={} closed={}",
s.control_points.len(),
s.degree,
s.flags.closed
),
acadrust::EntityType::Ellipse(e) => format!(
).into_owned(),
acadrust::EntityType::Ellipse(e) => crate::tf!(
"center ({:.4},{:.4}) major_len={:.4} ratio={:.4}",
e.center.x,
e.center.y,
e.major_axis_length(),
e.minor_axis_ratio
),
).into_owned(),
_ => String::new(),
}
}
@@ -1967,13 +1967,13 @@ fn entity_extra_info(entity: &acadrust::EntityType) -> String {
"BLK={} @({:.3},{:.3})",
e.block_name, e.insert_point.x, e.insert_point.y
),
EntityType::LwPolyline(e) => format!("{} vertices", e.vertices.len()),
EntityType::Polyline(e) => format!("{} vertices", e.vertices.len()),
EntityType::Polyline2D(e) => format!("{} vertices", e.vertices.len()),
EntityType::Polyline3D(e) => format!("{} vertices", e.vertices.len()),
EntityType::LwPolyline(e) => crate::tf!("{} vertices", e.vertices.len()).into_owned(),
EntityType::Polyline(e) => crate::tf!("{} vertices", e.vertices.len()).into_owned(),
EntityType::Polyline2D(e) => crate::tf!("{} vertices", e.vertices.len()).into_owned(),
EntityType::Polyline3D(e) => crate::tf!("{} vertices", e.vertices.len()).into_owned(),
EntityType::Hatch(e) => format!("PAT={}", e.pattern.name),
EntityType::Dimension(e) => format!("{:.3}", e.base().actual_measurement),
EntityType::Spline(e) => format!("{} ctrl pts", e.control_points.len()),
EntityType::Spline(e) => crate::tf!("{} ctrl pts", e.control_points.len()).into_owned(),
_ => String::new(),
}
}
@@ -1997,7 +1997,7 @@ fn arith_eval(expr: &str) -> Result<f64, String> {
};
let v = p.expr()?;
if p.pos != p.chars.len() {
return Err(format!("unexpected '{}'", p.chars[p.pos]));
return Err(crate::tf!("unexpected '{}'", p.chars[p.pos]).into_owned());
}
Ok(v)
}
@@ -2101,7 +2101,7 @@ impl ArithParser {
Ok(v)
}
Some(c) if c.is_ascii_digit() || c == '.' => self.number(),
Some(c) => Err(format!("unexpected '{c}'")),
Some(c) => Err(crate::tf!("unexpected '{c}'").into_owned()),
None => Err("unexpected end of expression".into()),
}
}
@@ -2116,7 +2116,7 @@ impl ArithParser {
}
}
let s: String = self.chars[start..self.pos].iter().collect();
s.parse::<f64>().map_err(|_| format!("bad number '{s}'"))
s.parse::<f64>().map_err(|_| crate::tf!("bad number '{s}'").into_owned())
}
}
+8 -8
View File
@@ -706,10 +706,10 @@ impl OpenCADStudio {
}
})
.map(|s| {
format!(
crate::tf!(
"{} (h_margin:{:.2} v_margin:{:.2})",
s.name, s.horizontal_margin, s.vertical_margin
)
).into_owned()
})
.collect();
if styles.is_empty() {
@@ -909,7 +909,7 @@ impl OpenCADStudio {
.document
.dim_styles
.iter()
.map(|s| format!("{}(txt:{:.2} asz:{:.2})", s.name, s.dimtxt, s.dimasz))
.map(|s| crate::tf!("{}(txt:{:.2} asz:{:.2})", s.name, s.dimtxt, s.dimasz).into_owned())
.collect();
if styles.is_empty() {
self.command_line.push_output(crate::t!("No dim styles defined.").as_ref());
@@ -1019,7 +1019,7 @@ impl OpenCADStudio {
}
_ => {
self.command_line.push_info(
"Usage: DIMSTYLE LIST | NEW <name> | SET <name> <prop> <val>",
crate::t!("Usage: DIMSTYLE LIST | NEW <name> | SET <name> <prop> <val>").as_ref(),
);
}
}
@@ -1043,10 +1043,10 @@ impl OpenCADStudio {
.values()
.filter_map(|o| {
if let ObjectType::MultiLeaderStyle(s) = o {
Some(format!(
Some(crate::tf!(
"{}(txt:{:.2} asz:{:.2})",
s.name, s.text_height, s.arrowhead_size
))
).into_owned())
} else {
None
}
@@ -1206,13 +1206,13 @@ impl OpenCADStudio {
.text_styles
.iter()
.map(|s| {
format!(
crate::tf!(
"{} (font: {}, w: {:.2}, oblique: {:.1}°)",
s.name,
s.font_file,
s.width_factor,
s.oblique_angle.to_degrees()
)
).into_owned()
})
.collect();
if styles.is_empty() {
+5 -16
View File
@@ -91,21 +91,10 @@ impl OpenCADStudio {
.filter(|&(code, _)| code != 0)
.map(|(code, label)| (label, units::short(code), None))
.collect();
// Built once: the table it comes from never changes, and
// `KeywordCommand` wants a `&'static str`.
static PROMPT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
let prompt = PROMPT.get_or_init(|| {
let listed = units::all()
.filter(|&(code, _)| code != 0)
.map(|(code, label)| format!("{label}({})", units::short(code)))
.collect::<Vec<_>>()
.join(" / ");
format!("DWGUNITS convert to [{listed}]:")
});
let current = units::label(self.tabs[i].scene.document.header.insertion_units);
let current = crate::t!(units::label(self.tabs[i].scene.document.header.insertion_units));
self.command_line
.push_info(crate::tf!("DWGUNITS current unit: {current}").as_ref());
let c = KeywordCommand::new("DWGUNITS", prompt.as_str(), choices);
let c = KeywordCommand::new("DWGUNITS", "DWGUNITS Convert drawing to:", choices);
self.command_line.push_info(&c.prompt());
self.tabs[i].active_cmd = Some(Box::new(c));
}
@@ -157,7 +146,7 @@ impl OpenCADStudio {
}
if layer.eq_ignore_ascii_case(&self.tabs[i].active_layer) {
self.command_line.push_error(
"LAYDEL: cannot delete the current layer. Make another layer current first.",
crate::t!("LAYDEL: cannot delete the current layer. Make another layer current first.").as_ref(),
);
return Some(Task::none());
}
@@ -234,7 +223,7 @@ impl OpenCADStudio {
}
if src.eq_ignore_ascii_case(&self.tabs[i].active_layer) {
self.command_line.push_error(
"LAYMRG: cannot merge the current layer. Make another layer current first.",
crate::t!("LAYMRG: cannot merge the current layer. Make another layer current first.").as_ref(),
);
return Some(Task::none());
}
@@ -280,7 +269,7 @@ impl OpenCADStudio {
let states = self.tabs[i].scene.document.layer_states();
if states.is_empty() {
self.command_line.push_info(
"LAYERSTATE: no saved states. Use LAYERSTATE SAVE <name>.",
crate::t!("LAYERSTATE: no saved states. Use LAYERSTATE SAVE <name>.").as_ref(),
);
} else {
let mut names: Vec<&str> =
+23 -23
View File
@@ -29,7 +29,7 @@ impl OpenCADStudio {
let describe = |c: &Color| match c {
Color::ByLayer => "ByLayer".to_string(),
Color::ByBlock => "ByBlock".to_string(),
Color::Index(n) => format!("index {n}"),
Color::Index(n) => crate::tf!("index {n}").into_owned(),
_ => "(custom)".to_string(),
};
let arg = if cmd == "BYLAYER" {
@@ -65,7 +65,7 @@ impl OpenCADStudio {
}
None => {
self.command_line.push_error(
"Usage: COLOR <ByLayer|ByBlock|1-255|red|yellow|green|cyan|blue|magenta|white>",
crate::t!("Usage: COLOR <ByLayer|ByBlock|1-255|red|yellow|green|cyan|blue|magenta|white>").as_ref(),
);
}
}
@@ -486,19 +486,19 @@ impl OpenCADStudio {
// Per-type breakdown so the user sees exactly what went.
let mut parts: Vec<String> = Vec::new();
if n_layers > 0 {
parts.push(format!("{n_layers} layer(s)"));
parts.push(crate::tf!("{n_layers} layer(s)").into_owned());
}
if n_styles > 0 {
parts.push(format!("{n_styles} text style(s)"));
parts.push(crate::tf!("{n_styles} text style(s)").into_owned());
}
if n_lts > 0 {
parts.push(format!("{n_lts} linetype(s)"));
parts.push(crate::tf!("{n_lts} linetype(s)").into_owned());
}
if n_blocks > 0 {
parts.push(format!("{n_blocks} block(s)"));
parts.push(crate::tf!("{n_blocks} block(s)").into_owned());
}
if n_sortents > 0 {
parts.push(format!("{n_sortents} stale draw-order table(s)"));
parts.push(crate::tf!("{n_sortents} stale draw-order table(s)").into_owned());
}
self.command_line.push_output(crate::tf!(
"PURGE: {} item(s) removed — {}.",
@@ -539,7 +539,7 @@ impl OpenCADStudio {
if prop.is_empty() {
self.command_line.push_info(
"Usage: CHPROP <prop> <val> (props: LAYER COLOR LINETYPE LTSCALE)",
crate::t!("Usage: CHPROP <prop> <val> (props: LAYER COLOR LINETYPE LTSCALE)").as_ref(),
);
} else {
let handles: Vec<_> = self.tabs[i]
@@ -940,7 +940,7 @@ impl OpenCADStudio {
let value = it.next().map(|s| s.trim().to_string());
if name.is_empty() || name == "?" {
self.command_line.push_info(
"SETVAR: 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 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 | CLAYER CELTYPE TEXTSTYLE (read-only)",
crate::t!("SETVAR: 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 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 | CLAYER CELTYPE TEXTSTYLE (read-only)").as_ref(),
);
} else {
if matches!(name.as_str(), "SHOWHIST" | "SOLIDHIST") {
@@ -964,12 +964,12 @@ impl OpenCADStudio {
self.tabs[i].dirty = true;
self.refresh_properties();
}
self.command_line.push_output(&format!("{name} = {mode}"));
self.command_line.push_output(&crate::tf!("{name} = {mode}"));
}
None => self.command_line.push_error(&format!("{name}: expected an integer from 0 to {maximum}.")),
None => self.command_line.push_error(&crate::tf!("{name}: expected an integer from 0 to {maximum}.")),
}
} else {
self.command_line.push_output(&format!("Enter new value for {name} <{current}>:"));
self.command_line.push_output(&crate::tf!("Enter new value for {name} <{current}>:"));
self.pending_setvar = Some(name.clone());
}
return Some(self.finish_dispatch(cmd));
@@ -1033,7 +1033,7 @@ impl OpenCADStudio {
self.tabs[i].dirty = true;
}
self.command_line
.push_output(&format!("{name} = {mode}"));
.push_output(&crate::tf!("{name} = {mode}"));
}
_ => self.command_line.push_error(
crate::tf!("SETVAR: {name} requires 0, 1, or 2.").as_ref(),
@@ -1095,10 +1095,10 @@ impl OpenCADStudio {
self.dimension_continue_mode = mode;
self.persist_settings_if_changed();
self.command_line
.push_output(&format!("DIMCONTINUEMODE = {mode}"));
.push_output(&crate::tf!("DIMCONTINUEMODE = {mode}"));
}
_ => self.command_line.push_error(
"SETVAR: DIMCONTINUEMODE requires 0 or 1.",
crate::t!("SETVAR: DIMCONTINUEMODE requires 0 or 1.").as_ref(),
),
}
} else {
@@ -1136,13 +1136,13 @@ impl OpenCADStudio {
crate::modules::draw::defaults::set_donut_outer_diameter(number);
}
self.command_line
.push_output(&format!("{name} = {number}"));
.push_output(&crate::tf!("{name} = {number}"));
} else {
self.command_line.push_error(if name == "DONUTID" {
self.command_line.push_error(crate::t!(if name == "DONUTID" {
"SETVAR: DONUTID requires a finite value greater than or equal to zero."
} else {
"SETVAR: DONUTOD requires a finite value greater than zero."
});
}).as_ref());
}
} else {
self.command_line.push_output(crate::tf!(
@@ -2097,7 +2097,7 @@ impl OpenCADStudio {
false,
)),
},
_ => Err(format!("SETVAR: unknown variable \"{name}\".")),
_ => Err(crate::tf!("SETVAR: unknown variable \"{name}\".").into_owned()),
}
};
match outcome {
@@ -2501,7 +2501,7 @@ impl OpenCADStudio {
self.command_line.push_output(crate::tf!("PDMODE set to {v}").as_ref());
} else {
self.command_line.push_error(
"Usage: PDMODE [value] (0=dot 1=none 2=+ 3=x 4=tick; +32 circle, +64 square)",
crate::t!("Usage: PDMODE [value] (0=dot 1=none 2=+ 3=x 4=tick; +32 circle, +64 square)").as_ref(),
);
}
}
@@ -2569,7 +2569,7 @@ impl OpenCADStudio {
self.persist_settings_if_changed();
match crate::io::file_association::register_as_handler() {
Ok(()) => self.command_line.push_output(
"FILEASSOC set to 1 — registered as a .dwg/.dxf/.bak handler",
crate::t!("FILEASSOC set to 1 — registered as a .dwg/.dxf/.bak handler").as_ref(),
),
Err(e) => self
.command_line
@@ -2614,7 +2614,7 @@ impl OpenCADStudio {
let msg = if v == 0 {
"SAVETIME set to 0 (autosave off)".to_string()
} else {
format!("SAVETIME set to {v} minute(s)")
crate::tf!("SAVETIME set to {v} minute(s)").into_owned()
};
self.command_line.push_output(&msg);
}
@@ -2646,7 +2646,7 @@ impl OpenCADStudio {
.push_output(crate::tf!("PDSIZE set to {v:.4}").as_ref());
} else {
self.command_line.push_error(
"Usage: PDSIZE [value] (>0 absolute size, <0 percent of viewport, 0 default)",
crate::t!("Usage: PDSIZE [value] (>0 absolute size, <0 percent of viewport, 0 default)").as_ref(),
);
}
}
+14 -14
View File
@@ -257,7 +257,7 @@ impl OpenCADStudio {
let path = cmd.trim_start_matches("CUIEXPORT").trim();
if path.is_empty() {
self.command_line.push_info(
"Usage: CUIEXPORT <path> — save the keyboard shortcuts to a file.",
crate::t!("Usage: CUIEXPORT <path> — save the keyboard shortcuts to a file.").as_ref(),
);
return Some(Task::none());
}
@@ -290,7 +290,7 @@ impl OpenCADStudio {
.trim();
if path.is_empty() {
self.command_line.push_info(
"Usage: CUIIMPORT <path> — load keyboard shortcuts from a file.",
crate::t!("Usage: CUIIMPORT <path> — load keyboard shortcuts from a file.").as_ref(),
);
return Some(Task::none());
}
@@ -381,12 +381,12 @@ impl OpenCADStudio {
"COLORSCHEME",
"COLORSCHEME Enter theme name (or ? to list):",
);
self.command_line.push_output(
"Available themes: DARK LIGHT DRACULA NORD SOLARIZED_LIGHT SOLARIZED_DARK \
self.command_line.push_output(&crate::tf!("Available themes: {}",
"DARK LIGHT DRACULA NORD SOLARIZED_LIGHT SOLARIZED_DARK \
GRUVBOX_LIGHT GRUVBOX_DARK TOKYONIGHT TOKYONIGHTSTORM TOKYONIGHTLIGHT \
KANAGAWAWAVE KANAGAWADRAGON KANAGAWALOTUS MOONFLY NIGHTFLY OXOCARBON FERRA",
);
self.command_line.push_info(&format!(
));
self.command_line.push_info(&crate::tf!(
"Current color scheme: <{}>",
self.ui_theme.name
));
@@ -398,23 +398,23 @@ impl OpenCADStudio {
let sub = cmd.split_once(' ').map(|(_, r)| r.trim()).unwrap_or("");
let upper_sub = sub.to_ascii_uppercase();
if upper_sub.is_empty() {
self.command_line.push_output(&format!(
self.command_line.push_output(&crate::tf!(
"Current color scheme: <{}>",
self.ui_theme.name
));
return Some(Task::none());
}
if upper_sub == "LIST" || upper_sub == "?" {
self.command_line.push_output(
"Available themes: DARK LIGHT DRACULA NORD SOLARIZED_LIGHT SOLARIZED_DARK \
self.command_line.push_output(&crate::tf!("Available themes: {}",
"DARK LIGHT DRACULA NORD SOLARIZED_LIGHT SOLARIZED_DARK \
GRUVBOX_LIGHT GRUVBOX_DARK TOKYONIGHT TOKYONIGHTSTORM TOKYONIGHTLIGHT \
KANAGAWAWAVE KANAGAWADRAGON KANAGAWALOTUS MOONFLY NIGHTFLY OXOCARBON FERRA",
);
));
return Some(Task::none());
}
if sub == "0" || sub == "1" {
self.command_line.push_error(
"COLORSCHEME: 0 and 1 are COLORTHEME options. Enter a theme name (e.g. DARK, LIGHT, DRACULA) or use COLORTHEME.",
crate::t!("COLORSCHEME: 0 and 1 are COLORTHEME options. Enter a theme name (e.g. DARK, LIGHT, DRACULA) or use COLORTHEME.").as_ref(),
);
return Some(Task::none());
}
@@ -630,7 +630,7 @@ impl OpenCADStudio {
}
_ => {
self.command_line.push_error(
"VPORTS: unknown option. Use VPORTS 2H | 2V | 4 | SINGLE",
crate::t!("VPORTS: unknown option. Use VPORTS 2H | 2V | 4 | SINGLE").as_ref(),
);
vec![]
}
@@ -1161,7 +1161,7 @@ impl OpenCADStudio {
.collect();
if vps.len() < 2 {
self.command_line.push_error(
"SYNCPVIEWPORTS: select two or more viewports (the first is the master).",
crate::t!("SYNCPVIEWPORTS: select two or more viewports (the first is the master).").as_ref(),
);
return Some(Task::none());
}
@@ -1215,7 +1215,7 @@ impl OpenCADStudio {
// that work.
None => self
.command_line
.push_info(visual_style::keyword_prompt()),
.push_info(crate::t!(visual_style::keyword_prompt()).as_ref()),
}
}
+1 -1
View File
@@ -836,7 +836,7 @@ impl OpenCADStudio {
if let Some(doc) = target {
let Some(i) = self.tabs.iter().position(|t| t.id == doc) else {
self.command_line
.push_error("Automation target document closed");
.push_error(crate::t!("Automation target document closed").as_ref());
return Task::none();
};
self.active_tab = i;
+13 -13
View File
@@ -60,12 +60,12 @@ impl OpenCADStudio {
}
self.find_replace.current_match = Some(target);
self.find_replace.status = format!(
self.find_replace.status = crate::tf!(
"{} of {} — {}",
index + 1,
matches.len(),
match_label(target)
);
).into_owned();
self.tabs[i].scene.deselect_all();
self.tabs[i]
.scene
@@ -99,7 +99,7 @@ impl OpenCADStudio {
let search = self.find_replace.search.clone();
let replacement = self.find_replace.replacement.clone();
if match_is_locked(&self.tabs[i].scene, target) {
self.find_replace.status = "The matching object is on a locked layer.".to_string();
self.find_replace.status = crate::t!("The matching object is on a locked layer.").into_owned();
return;
}
self.push_undo_snapshot(i, "FIND/REPLACE");
@@ -131,10 +131,10 @@ impl OpenCADStudio {
self.tabs[i].dirty = true;
self.find_replace.current_match = None;
let remaining = self.find_text_matches(i).len();
self.find_replace.status = format!(
self.find_replace.status = crate::tf!(
"Replaced 1 occurrence in {}; {remaining} matching object(s) remain.",
match_label(target)
);
).into_owned();
self.command_line.push_output(crate::tf!(
"FIND/REPLACE: replaced 1 occurrence of \"{search}\"."
).as_ref());
@@ -192,10 +192,10 @@ impl OpenCADStudio {
}
self.tabs[i].dirty = true;
self.find_replace.current_match = None;
self.find_replace.status = format!(
self.find_replace.status = crate::tf!(
"Replaced {replaced} occurrence(s) in {} object(s).",
changed.len()
);
).into_owned();
self.command_line.push_output(crate::tf!(
"FIND/REPLACE: replaced {replaced} occurrence(s) of \"{search}\"."
).as_ref());
@@ -286,9 +286,9 @@ impl OpenCADStudio {
fn no_match_status(search: &str) -> String {
if search.trim().is_empty() {
"Enter text to find.".to_string()
crate::t!("Enter text to find.").into_owned()
} else {
format!("\"{search}\" was not found.")
crate::tf!("\"{search}\" was not found.").into_owned()
}
}
@@ -372,16 +372,16 @@ fn match_is_locked(scene: &crate::scene::Scene, target: FindMatchKey) -> bool {
fn match_label(target: FindMatchKey) -> String {
match target {
FindMatchKey::Entity(handle) => format!("handle {:X}", handle.value()),
FindMatchKey::Entity(handle) => crate::tf!("handle {:X}", handle.value()).into_owned(),
FindMatchKey::BlockEntityInInsert { entity, insert } => {
format!(
crate::tf!(
"block {:X}, text {:X}",
insert.value(),
entity.value()
)
).into_owned()
}
FindMatchKey::InsertAttribute { insert, index } => {
format!("block {:X}, attribute {}", insert.value(), index + 1)
crate::tf!("block {:X}, attribute {}", insert.value(), index + 1).into_owned()
}
}
}
+1 -1
View File
@@ -160,7 +160,7 @@ impl std::fmt::Display for QSelectOp {
QSelectOp::Gt => "> Greater than",
QSelectOp::Lt => "< Less than",
};
f.write_str(s)
f.write_str(crate::t!(s).as_ref())
}
}
+6 -6
View File
@@ -161,7 +161,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
crate::tf!("Unknown visual style \"{name}\".").as_ref(),
);
self.command_line
.push_info(visual_style::keyword_prompt());
.push_info(crate::t!(visual_style::keyword_prompt()).as_ref());
}
}
}
@@ -181,7 +181,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
let path = crate::sys::file_dialog()
.set_title(title)
.add_filter(filter_name, &exts)
.add_filter("All Files", &["*"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.pick_file()
.await
.map(|h| crate::sys::handle_path(&h));
@@ -520,8 +520,8 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
BlockPaletteMsg::PickFile => iced::Task::perform(
async {
let handle = rfd::AsyncFileDialog::new()
.set_title("Select Drawing to Insert as Block")
.add_filter("DWG/DXF Files", &["dwg", "dxf", "DWG", "DXF"])
.set_title(crate::t!("Select Drawing to Insert as Block").as_ref())
.add_filter(crate::t!("DWG/DXF Files").as_ref(), &["dwg", "dxf", "DWG", "DXF"])
.pick_file()
.await;
match handle {
@@ -535,12 +535,12 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
match self.import_file_as_block(path) {
Ok(name) => {
self.command_line
.push_output(&format!("Inserting \"{name}\" from file."));
.push_output(&crate::tf!("Inserting \"{name}\" from file."));
self.refresh_block_palette();
self.start_block_placement(&name);
}
Err(e) if e != "Cancelled" => {
self.command_line.push_error(&format!("INSERT FILE: {e}"));
self.command_line.push_error(&crate::tf!("INSERT FILE: {e}"));
}
Err(_) => {}
}
+23 -23
View File
@@ -2576,7 +2576,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
let pick = Task::perform(
async move {
let mut dlg = crate::sys::file_dialog()
.set_title("Save Drawing As")
.set_title(crate::t!("Save Drawing As").as_ref())
.set_file_name(default_name)
.add_filter(filter_label, &[filter_ext]);
if let Some(dir) = seed_dir {
@@ -2934,8 +2934,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
plot_style.as_ref(),
render_options,
)
.map(|_| format!("Exported: {}", worker_path.display()))
.map_err(|e| format!("Export failed: {e}"))
.map(|_| crate::tf!("Exported: {}", worker_path.display()).into_owned())
.map_err(|e| crate::tf!("Export failed: {e}").into_owned())
};
self.run_plot_work(self.plot_dialog.background, false, work)
}
@@ -2978,12 +2978,12 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
plot_style.as_ref(),
render_options,
)
.map(|_| {format!(
.map(|_| {crate::tf!(
"Plotted window to {}",
worker_path
.file_name().unwrap_or_default().to_string_lossy()
)
}).map_err(|e| format!("Plot failed: {e}"))
).into_owned()
}).map_err(|e| crate::tf!("Plot failed: {e}").into_owned())
};
self.run_plot_work(self.plot_dialog.background, false, work)
}
@@ -3071,7 +3071,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
let page_setup = self.tabs[i]
.scene
.plot_settings_for(&name)
.ok_or_else(|| format!("Layout '{name}' has no page setup."))?;
.ok_or_else(|| crate::tf!("Layout '{name}' has no page setup.").into_owned())?;
{
let scene = &mut self.tabs[i].scene;
scene.current_layout = name.clone();
@@ -3092,10 +3092,10 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
self.tabs[i].scene.restore_saved_camera();
}
if dialog.style_missing && dialog.apply_plot_styles {
return Err(format!(
return Err(crate::tf!(
"Layout '{name}' plot style table '{}' is not loaded.",
dialog.style_name
));
).into_owned());
}
let plot_style = self.dialog_plot_style(&dialog);
let params = match dialog.area.as_str() {
@@ -3135,7 +3135,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
rotation_deg,
scale,
clip,
) = params.ok_or_else(|| format!("Layout '{name}' plot area is empty."))?;
) = params.ok_or_else(|| crate::tf!("Layout '{name}' plot area is empty.").into_owned())?;
(
std::sync::Arc::new(wires),
hatches,
@@ -3219,8 +3219,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
&worker_path,
None,
)
.map(|_| format!("Exported {} layouts to {}", pages.len(), worker_path.display()))
.map_err(|error| format!("Export failed: {error}"))
.map(|_| crate::tf!("Exported {} layouts to {}", pages.len(), worker_path.display()).into_owned())
.map_err(|error| crate::tf!("Export failed: {error}").into_owned())
};
self.run_print_all_work(dialog.background, work)
}
@@ -3269,8 +3269,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
.and_then(|_| {
crate::io::print_to_printer::print_existing_pdf(&temp_path, &options)
})
.map(|printer| format!("Sent {} layouts to printer: {printer}", pages.len()))
.map_err(|error| format!("Print failed: {error}"))
.map(|printer| crate::tf!("Sent {} layouts to printer: {printer}", pages.len()).into_owned())
.map_err(|error| crate::tf!("Print failed: {error}").into_owned())
};
self.run_print_all_work(true, work)
}
@@ -4480,7 +4480,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
plot_style.as_ref(),
render_options,
).and_then(|_| crate::io::print_to_printer::open_in_viewer(&tmp))
.map(|_| "Opened plot preview.".to_string()).map_err(|e| format!("Preview failed: {e}"))
.map(|_| crate::t!("Opened plot preview.").into_owned()).map_err(|e| crate::tf!("Preview failed: {e}").into_owned())
};
return self.run_plot_work(d.background, true, work);
}
@@ -4497,8 +4497,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
plot_style.as_ref(),
render_options,
).and_then(|_| crate::io::print_to_printer::print_existing_pdf(&tmp, &opts))
.map(|printer| format!("Sent to printer: {printer}"))
.map_err(|e| format!("Print failed: {e}"))
.map(|printer| crate::tf!("Sent to printer: {printer}").into_owned())
.map_err(|e| crate::tf!("Print failed: {e}").into_owned())
};
return self.run_plot_work(true, false, work);
}
@@ -4525,7 +4525,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
plot_style.as_ref(),
render_options,
).and_then(|_| crate::io::print_to_printer::open_in_viewer(&tmp))
.map(|_| "Opened plot preview.".to_string()).map_err(|e| format!("Preview failed: {e}"))
.map(|_| crate::t!("Opened plot preview.").into_owned()).map_err(|e| crate::tf!("Preview failed: {e}").into_owned())
};
return self.run_plot_work(d.background, true, work);
}
@@ -4543,8 +4543,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
wires, hatches, wipeouts, page_w, page_h, ox, oy, rotation, scale, clip,
plot_style, opts,
))
.map(|printer| format!("Sent to printer: {printer}"))
.map_err(|error| format!("Print failed: {error}"))
.map(|printer| crate::tf!("Sent to printer: {printer}").into_owned())
.map_err(|error| crate::tf!("Print failed: {error}").into_owned())
};
self.run_plot_work(true, false, work)
}
@@ -4860,10 +4860,10 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
Task::perform(
async move {
let dialog = crate::sys::file_dialog()
.set_title("Save Plot Style Table")
.set_title(crate::t!("Save Plot Style Table").as_ref())
.set_file_name(&default_name)
.add_filter("Plot Style Files", &["ctb", "CTB"])
.add_filter("All Files", &["*"]);
.add_filter(crate::t!("Plot Style Files").as_ref(), &["ctb", "CTB"])
.add_filter(crate::t!("All Files").as_ref(), &["*"]);
#[cfg(not(target_arch = "wasm32"))]
let dialog = match crate::io::plot_style::ensure_plot_styles_dir() {
Ok(dir) => dialog.set_directory(dir),
+38 -38
View File
@@ -907,8 +907,8 @@ impl OpenCADStudio {
Message::PdfAttachPick => Task::perform(
async {
let handle = crate::sys::file_dialog()
.set_title("Select PDF Underlay")
.add_filter("PDF Files", &["pdf", "PDF"])
.set_title(crate::t!("Select PDF Underlay").as_ref())
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf", "PDF"])
.pick_file()
.await;
@@ -961,11 +961,11 @@ impl OpenCADStudio {
Message::XAttachPick => Task::perform(
async {
let handle = crate::sys::file_dialog()
.set_title("Select External Reference File")
.add_filter("CAD Files", &["dwg", "dxf", "bak", "DWG", "DXF", "BAK"])
.add_filter("DWG Files", &["dwg", "DWG"])
.add_filter("DXF Files", &["dxf", "DXF"])
.add_filter("Backup Files", &["bak", "BAK"])
.set_title(crate::t!("Select External Reference File").as_ref())
.add_filter(crate::t!("CAD Files").as_ref(), &["dwg", "dxf", "bak", "DWG", "DXF", "BAK"])
.add_filter(crate::t!("DWG Files").as_ref(), &["dwg", "DWG"])
.add_filter(crate::t!("DXF Files").as_ref(), &["dxf", "DXF"])
.add_filter(crate::t!("Backup Files").as_ref(), &["bak", "BAK"])
.pick_file()
.await;
match handle {
@@ -999,9 +999,9 @@ impl OpenCADStudio {
Task::perform(
async move {
let path = crate::sys::file_dialog()
.set_title("Save Block As")
.set_title(crate::t!("Save Block As").as_ref())
.set_file_name("block.dwg")
.add_filter("DWG Files", &["dwg"])
.add_filter(crate::t!("DWG Files").as_ref(), &["dwg"])
.save_file()
.await
.map(|h| crate::sys::handle_path(&h));
@@ -1035,10 +1035,10 @@ impl OpenCADStudio {
Task::perform(
async move {
let path = crate::sys::file_dialog()
.set_title("Save Data Extraction")
.set_title(crate::t!("Save Data Extraction").as_ref())
.set_file_name("extraction.csv")
.add_filter("CSV", &["csv"])
.add_filter("All Files", &["*"])
.add_filter(crate::t!("CSV").as_ref(), &["csv"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.save_file()
.await
.map(|h| crate::sys::handle_path(&h));
@@ -1078,10 +1078,10 @@ impl OpenCADStudio {
Task::perform(
async {
crate::sys::file_dialog()
.set_title("Export STL")
.set_title(crate::t!("Export STL").as_ref())
.set_file_name("export.stl")
.add_filter("STL Files", &["stl"])
.add_filter("All Files", &["*"])
.add_filter(crate::t!("STL Files").as_ref(), &["stl"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.save_file()
.await
.map(|h| crate::sys::handle_path(&h))
@@ -1115,10 +1115,10 @@ impl OpenCADStudio {
Task::perform(
async {
crate::sys::file_dialog()
.set_title("Export STEP AP203")
.set_title(crate::t!("Export STEP AP203").as_ref())
.set_file_name("export.step")
.add_filter("STEP Files", &["step", "stp"])
.add_filter("All Files", &["*"])
.add_filter(crate::t!("STEP Files").as_ref(), &["step", "stp"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.save_file()
.await
.map(|h| crate::sys::handle_path(&h))
@@ -1145,9 +1145,9 @@ impl OpenCADStudio {
Message::ObjImport => Task::perform(
async {
crate::sys::file_dialog()
.set_title("Import OBJ Mesh")
.add_filter("Wavefront OBJ", &["obj", "OBJ"])
.add_filter("All Files", &["*"])
.set_title(crate::t!("Import OBJ Mesh").as_ref())
.add_filter(crate::t!("Wavefront OBJ").as_ref(), &["obj", "OBJ"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.pick_file()
.await
.map(|h| crate::sys::handle_path(&h))
@@ -1286,11 +1286,11 @@ impl OpenCADStudio {
.scene
.set_projection_preserving_frame(proj);
self.ribbon.set_ortho(ortho);
self.command_line.push_output(if ortho {
self.command_line.push_output(crate::t!(if ortho {
"Projection: Orthographic"
} else {
"Projection: Perspective"
});
}).as_ref());
Task::none()
}
@@ -6613,13 +6613,13 @@ impl OpenCADStudio {
crate::plugin::external::normalize_repository(&self.plugin_repo_input)
else {
self.marketplace_status =
"Enter a GitHub URL or repository in owner/repo format.".to_string();
crate::t!("Enter a GitHub URL or repository in owner/repo format.").into_owned();
return Task::none();
};
if self.plugin_repos.contains(&repo)
|| self.plugin_registry.iter().any(|entry| entry.repo == repo)
{
self.marketplace_status = format!("{repo} is already in the catalog.");
self.marketplace_status = crate::tf!("{repo} is already in the catalog.").into_owned();
self.selected_plugin_repo = Some(repo.clone());
if !self.plugin_readmes.contains_key(&repo)
&& self.plugin_readme_loading.insert(repo.clone())
@@ -6633,7 +6633,7 @@ impl OpenCADStudio {
.iter()
.any(|plugin| plugin.repository.as_deref() == Some(repo.as_str()))
{
self.marketplace_status = format!("{repo} is already installed.");
self.marketplace_status = crate::tf!("{repo} is already installed.").into_owned();
self.selected_plugin_repo = Some(repo.clone());
if !self.plugin_readmes.contains_key(&repo)
&& self.plugin_readme_loading.insert(repo.clone())
@@ -6645,7 +6645,7 @@ impl OpenCADStudio {
self.plugin_repos.push(repo.clone());
self.plugin_repo_input.clear();
self.persist_settings_if_changed();
self.marketplace_status = format!("Fetching releases for {repo}…");
self.marketplace_status = crate::tf!("Fetching releases for {repo}…").into_owned();
self.selected_plugin_repo = Some(repo.clone());
self.plugin_readmes.remove(&repo);
self.plugin_readme_loading.insert(repo.clone());
@@ -6791,12 +6791,12 @@ impl OpenCADStudio {
.entry(repo.clone())
.or_insert_with(|| first.tag.clone());
}
if self.marketplace_status == format!("Fetching releases for {repo}…") {
if self.marketplace_status == crate::tf!("Fetching releases for {repo}…").into_owned() {
self.marketplace_status =
format!(
crate::tf!(
"Repository added. {} installable release(s) found.",
releases.len()
);
).into_owned();
}
self.repo_release_tags.insert(repo, releases);
Task::none()
@@ -6834,15 +6834,15 @@ impl OpenCADStudio {
let Some(tag) = self.repo_selected_tag.get(&repo).cloned() else {
return Task::none();
};
self.marketplace_status = format!("Installing {repo} {tag}…");
self.marketplace_status = crate::tf!("Installing {repo} {tag}…").into_owned();
self.install_task(repo, tag)
}
Message::PluginUpdate(repo, tag) => {
self.marketplace_status = format!("Updating {repo} to {tag}…");
self.marketplace_status = crate::tf!("Updating {repo} to {tag}…").into_owned();
self.install_task(repo, tag)
}
Message::PluginInstalled(Ok(id)) => {
self.marketplace_status = format!("Installed '{id}'. Restart to load it.");
self.marketplace_status = crate::tf!("Installed '{id}'. Restart to load it.").into_owned();
self.plugin_load_errors.remove(&id);
#[cfg(not(target_arch = "wasm32"))]
{
@@ -6851,7 +6851,7 @@ impl OpenCADStudio {
Task::none()
}
Message::PluginInstalled(Err(e)) => {
self.marketplace_status = format!("Install failed: {e}");
self.marketplace_status = crate::tf!("Install failed: {e}").into_owned();
Task::none()
}
Message::PluginUninstall(id) => {
@@ -6861,20 +6861,20 @@ impl OpenCADStudio {
// and allows the package directory to be deleted.
if !crate::plugin::external::remove_plugin(&id) {
self.marketplace_status =
format!("Uninstall failed: plugin '{id}' did not stop in time");
crate::tf!("Uninstall failed: plugin '{id}' did not stop in time").into_owned();
return Task::none();
}
match crate::plugin::external::uninstall(&id) {
Ok(()) => {
self.marketplace_status =
format!("Uninstalled '{id}'.");
crate::tf!("Uninstalled '{id}'.").into_owned();
self.plugin_load_errors.remove(&id);
self.loaded_plugin_ids.remove(&id);
self.rebuild_ribbon_modules();
self.external_plugins = crate::plugin::external::discover();
}
Err(e) => {
self.marketplace_status = format!("Uninstall failed: {e}");
self.marketplace_status = crate::tf!("Uninstall failed: {e}").into_owned();
}
}
}
@@ -7344,7 +7344,7 @@ impl OpenCADStudio {
Message::PlotStyleLoaded(Some(table)) => {
if table.is_stb {
self.command_line.push_error(
"Named plot style tables are not supported by the vector plotter.",
crate::t!("Named plot style tables are not supported by the vector plotter.").as_ref(),
);
return Task::none();
}
+1 -1
View File
@@ -5122,7 +5122,7 @@ impl OpenCADStudio {
}
self.tabs[i].scene.camera_generation += 1;
self.command_line
.push_output(crate::tf!("View: {}", region.label()).as_ref());
.push_output(crate::tf!("View: {}", crate::t!(region.label())).as_ref());
Task::none()
}
+5 -5
View File
@@ -1485,7 +1485,7 @@ bg={bg_ms:.1}ms n={view_count}"
);
let trace = crate::perf::snapshot_tail_text(80);
let trace = if trace.is_empty() {
"No samples yet".to_string()
t!("No samples yet").into_owned()
} else {
trace
};
@@ -2204,7 +2204,7 @@ bg={bg_ms:.1}ms n={view_count}"
crate::ui::modal::modal(
base,
"Select Color",
t!("Select Color"),
content,
Message::CloseColorPicker,
self.modal_offset,
@@ -2713,7 +2713,7 @@ fn doc_tab_context_menu(
let item = |label: &'static str, msg: Option<Message>| -> Element<'static, Message> {
let enabled = msg.is_some();
let content = container(text(label).size(12))
let content = container(text(t!(label)).size(12))
.padding([4, 12])
.width(Fill)
.style(move |theme: &Theme| {
@@ -3233,7 +3233,7 @@ fn start_page_content<'a>(
{
// Filled with the active theme's primary colour.
secondary_items.push(
button(text("OCS Web").size(14))
button(text(crate::t!("OCS Web")).size(14))
.on_press(Message::RibbonToolClick {
tool_id: "WEBVERSION".to_string(),
event: crate::modules::ModuleEvent::Command("WEBVERSION".to_string()),
@@ -3245,7 +3245,7 @@ fn start_page_content<'a>(
}
#[cfg(target_arch = "wasm32")]
secondary_items.push(
button(text("OCS Desktop").size(14))
button(text(crate::t!("OCS Desktop")).size(14))
.on_press(Message::OpenUrl(
"https://github.com/HakanSeven12/OpenCADStudio/releases/latest".to_string(),
))
+9 -9
View File
@@ -69,7 +69,7 @@ pub(super) fn text_inline_overlay(
position_canvas_overlay(anchor, panel.into())
}
// Stroke-font families the renderer ships (LibreCAD LFF; see scene/lff.rs).
// Stroke-font families bundled with the renderer; see scene/lff.rs.
const MTEXT_FONTS: [&str; 10] = [
"[Style default]",
"Standard",
@@ -465,7 +465,7 @@ fn mtext_editor_content<'a>(
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>(),
|value| value.to_string(),
|value| t!(value).into_owned(),
)
.on_select(Message::MTextFont)
.text_size(11)
@@ -626,7 +626,7 @@ fn mtext_editor_content<'a>(
.into_iter()
.map(str::to_string)
.collect::<Vec<_>>(),
|value| value.to_string(),
|value| t!(value).into_owned(),
)
.on_select(Message::MTextColumnMode)
.text_size(11)
@@ -703,7 +703,7 @@ fn mtext_editor_content<'a>(
.width(width);
let row4 = row![
lbl("Find"),
text_input("Find", &ed.find_text)
text_input(&t!("Find"), &ed.find_text)
.on_input(Message::MTextFindText)
.width(iced::Length::Fixed(140.0))
.padding(3)
@@ -713,7 +713,7 @@ fn mtext_editor_content<'a>(
.padding(3)
.style(btn_style),
lbl("Replace"),
text_input("Replace", &ed.replace_text)
text_input(&t!("Replace"), &ed.replace_text)
.on_input(Message::MTextReplaceText)
.width(iced::Length::Fixed(140.0))
.padding(3)
@@ -1409,7 +1409,7 @@ fn qselect_content<'a>(
let mut prop_options: Vec<crate::app::QSelectPropertyChoice> =
vec![crate::app::QSelectPropertyChoice {
field: String::new(),
label: QSELECT_ANY_PROP.to_string(),
label: t!(QSELECT_ANY_PROP).into_owned(),
editor: crate::app::QSelectValueEditor::Text,
}];
prop_options.extend(properties.iter().cloned());
@@ -1436,7 +1436,7 @@ fn qselect_content<'a>(
.clone()
.unwrap_or(crate::app::QSelectPropertyChoice {
field: String::new(),
label: QSELECT_ANY_PROP.to_string(),
label: t!(QSELECT_ANY_PROP).into_owned(),
editor: crate::app::QSelectValueEditor::Text,
});
@@ -1552,7 +1552,7 @@ fn qselect_content<'a>(
.spacing(8)
.width(sizing.width),
Space::new().height(4),
text(format!("{} candidate object(s)", candidate_count))
text(crate::tf!("{} candidate object(s)", candidate_count))
.size(11)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.65)),
@@ -1567,7 +1567,7 @@ fn qselect_content<'a>(
iced::widget::pick_list(
Some(type_sel),
type_options,
|value| value.to_string(),
|value| t!(value).into_owned(),
)
.on_select(|s: String| {
if s == QSELECT_ANY_TYPE {
+14 -1
View File
@@ -21,63 +21,76 @@ use clap::Parser;
#[command(
name = "OpenCADStudio",
version,
about = "Open CAD Studio — 2D/3D CAD editor",
about = crate::t!("Open CAD Studio — 2D/3D CAD editor").into_owned(),
long_about = None,
)]
pub struct Cli {
/// CAD files to open at startup (.dwg / .dxf). Also how the OS file
/// association launches us when drawings are double-clicked — selecting
/// several hands them all to one launch, so this takes a list.
#[arg(help = crate::t!("CAD files to open at startup (.dwg / .dxf).").into_owned(), long_help = None)]
pub files: Vec<PathBuf>,
/// Start with a new empty drawing, ignoring any file argument.
#[arg(long)]
#[arg(help = crate::t!("Start with a new empty drawing.").into_owned(), long_help = None)]
pub new: bool,
/// Always start a new editor process, even when one is already running.
/// Without this, opening a drawing hands it to the running editor as a tab.
#[arg(long)]
#[arg(help = crate::t!("Start a new editor process.").into_owned(), long_help = None)]
pub new_instance: bool,
/// Open read-only: editing is allowed but saving is disabled.
#[arg(long)]
#[arg(help = crate::t!("Open read-only: saving is disabled.").into_owned(), long_help = None)]
pub read_only: bool,
/// Restrict the GPU backend (e.g. dx12, vulkan, gl, metal). Sets WGPU_BACKEND.
#[arg(long, value_name = "BACKEND")]
#[arg(help = crate::t!("GPU backend (dx12, vulkan, gl, metal). Sets WGPU_BACKEND.").into_owned(), long_help = None)]
pub backend: Option<String>,
/// Safe mode: force the GL backend, for flaky/hybrid GPU drivers.
#[arg(long, visible_alias = "no-gpu")]
#[arg(help = crate::t!("Safe mode: use the GL backend.").into_owned(), long_help = None)]
pub safe_mode: bool,
/// Force the packed renderer path that avoids shader storage buffers.
/// Normally selected automatically for adapters with insufficient limits.
#[arg(long)]
#[arg(help = crate::t!("Use the renderer for GPUs without shader storage buffers.").into_owned(), long_help = None)]
pub compat_renderer: bool,
/// Run the headless JSON automation server (stdin/stdout, or --port).
#[arg(long)]
#[arg(help = crate::t!("Run the headless JSON automation server (stdin/stdout, or --port).").into_owned(), long_help = None)]
pub serve: bool,
/// Expose the running desktop editor to AI clients over MCP stdio.
#[arg(long)]
#[arg(help = crate::t!("Connect AI clients to the running editor over MCP stdio.").into_owned(), long_help = None)]
pub mcp: bool,
/// TCP port for --serve (defaults to stdin/stdout).
#[arg(long, value_name = "PORT")]
#[arg(help = crate::t!("TCP port for --serve (defaults to stdin/stdout).").into_owned(), long_help = None)]
pub port: Option<u16>,
/// Headless convert: read IN, write OUT (format from OUT's extension), exit.
#[arg(long, num_args = 2, value_names = ["IN", "OUT"])]
#[arg(help = crate::t!("Convert IN to OUT using the output file extension, then exit.").into_owned(), long_help = None)]
pub export: Option<Vec<PathBuf>>,
/// Run a command script at startup: one command line per line of FILE.
#[arg(long, value_name = "FILE")]
#[arg(help = crate::t!("Run a command script at startup, one command per line.").into_owned(), long_help = None)]
pub script: Option<PathBuf>,
/// Log level (error|warn|info|debug|trace). Also honours RUST_LOG.
#[arg(long, value_name = "LEVEL")]
#[arg(help = crate::t!("Log level (error|warn|info|debug|trace). Also reads RUST_LOG.").into_owned(), long_help = None)]
pub log: Option<String>,
/// Internal: run as the plugin runner child process.
+105 -21
View File
@@ -1,9 +1,9 @@
//! Application language selection and embedded Fluent resources.
use i18n_embed::fluent::{fluent_language_loader, FluentLanguageLoader};
use i18n_embed::LanguageLoader;
#[cfg(not(target_arch = "wasm32"))]
use i18n_embed::DesktopLanguageRequester;
use i18n_embed::LanguageLoader;
#[cfg(target_arch = "wasm32")]
use i18n_embed::WebLanguageRequester;
use rust_embed::RustEmbed;
@@ -251,12 +251,8 @@ pub fn translate(source: impl AsRef<str>) -> Cow<'static, str> {
}
/// Translate a catalog message and replace the named values used by legacy
/// command prompts. Catalog generation protects these markers from machine
/// translation and Fluent parsing.
pub fn translate_args(
source: impl AsRef<str>,
args: &[(&str, String)],
) -> Cow<'static, str> {
/// command prompts. Markers preserve values through Fluent parsing.
pub fn translate_args(source: impl AsRef<str>, args: &[(&str, String)]) -> Cow<'static, str> {
let source = source.as_ref();
let mut translated = locale_catalog::message_attribute(source)
.map(|(message_id, attribute_id)| loader().get_attr(message_id, attribute_id))
@@ -416,17 +412,18 @@ mod tests {
{
let loader = FluentLanguageLoader::new("opencadstudio", "en-US".parse().unwrap());
load_language(&loader, language).expect("Fluent resources must parse");
let attributes: BTreeSet<String> = loader.with_message_iter(
&language.requested()[0],
|messages| {
let attributes: BTreeSet<String> =
loader.with_message_iter(&language.requested()[0], |messages| {
messages
.filter(|message| message.id.name == "donation")
.flat_map(|message| {
message.attributes.iter().map(|attr| attr.id.name.to_owned())
message
.attributes
.iter()
.map(|attr| attr.id.name.to_owned())
})
.collect()
},
);
});
assert_eq!(
attributes,
BTreeSet::from(["title", "heading", "body", "decline"].map(String::from))
@@ -438,12 +435,12 @@ mod tests {
}
#[test]
fn greek_catalog_loads_and_covers_the_source_catalog() {
fn every_catalog_covers_and_formats_the_source_catalog() {
let loader = FluentLanguageLoader::new("opencadstudio", "en-US".parse().unwrap());
load_language(&loader, Language::ElGr).expect("Greek Fluent resources must parse");
let keys = |language: &str| -> BTreeSet<(String, String)> {
loader.with_message_iter(&language.parse().unwrap(), |messages| {
messages
load_language(&loader, Language::EnUs).expect("English Fluent resources must parse");
let keys = |loader: &FluentLanguageLoader, language: Language| {
loader.with_message_iter(&language.requested()[0], |messages| {
let entries: Vec<_> = messages
.flat_map(|message| {
std::iter::once((message.id.name.to_owned(), String::new())).chain(
message.attributes.iter().map(|attribute| {
@@ -451,12 +448,99 @@ mod tests {
}),
)
})
.collect()
.collect();
let unique: BTreeSet<_> = entries.iter().cloned().collect();
assert_eq!(
entries.len(),
unique.len(),
"duplicate keys in {language:?}"
);
unique
})
};
let source = keys("en-US");
let source = keys(&loader, Language::EnUs);
assert!(!source.is_empty());
assert_eq!(keys("el-GR"), source);
let english = Localizations::get("en-US/opencadstudio.ftl").unwrap();
let english = std::str::from_utf8(&english.data).unwrap();
let variables: Vec<_> = english
.split('$')
.skip(1)
.map(|tail| {
tail.split(|ch: char| !ch.is_ascii_alphanumeric() && ch != '_' && ch != '-')
.next()
.unwrap()
})
.collect();
let args = variables.into_iter().map(|name| (name, 2)).collect();
let render = |loader: &FluentLanguageLoader, message: &str, attribute: &str| {
loader
.with_fluent_message_and_bundle(message, |fluent_message, bundle| {
let pattern = if attribute.is_empty() {
fluent_message.value()
} else {
fluent_message
.get_attribute(attribute)
.map(|attr| attr.value())
};
pattern
.map(|pattern| {
let mut errors = Vec::new();
let text = bundle.format_pattern(pattern, Some(&args), &mut errors);
assert!(errors.is_empty(), "{message}.{attribute}: {errors:?}");
text.into_owned()
})
.unwrap_or_default()
})
.unwrap()
};
let markers = |text: &str| -> BTreeSet<String> {
text.split("__ocs_")
.skip(1)
.filter_map(|tail| tail.split_once("__").map(|(marker, _)| marker.to_owned()))
.collect()
};
for language in Language::ALL.into_iter().filter(|l| *l != Language::System) {
let resource =
Localizations::get(&format!("{}/opencadstudio.ftl", language.requested()[0]))
.unwrap();
let text = std::str::from_utf8(&resource.data).unwrap();
if let Err((_, errors)) = fluent_syntax::parser::parse(text) {
panic!("Invalid Fluent syntax in {language:?}: {errors:?}");
}
let localized = FluentLanguageLoader::new("opencadstudio", "en-US".parse().unwrap());
load_language(&localized, language).expect("Fluent resources must parse");
let actual = keys(&localized, language);
let extra: Vec<_> = actual.difference(&source).collect();
assert!(extra.is_empty(), "extra keys in {language:?}: {extra:?}");
let missing: Vec<_> = source.difference(&actual).collect();
assert!(
missing.is_empty(),
"{} missing keys in {language:?}: {:?}",
missing.len(),
&missing[..missing.len().min(10)]
);
for (message, attribute) in &source {
let expected = render(&loader, message, attribute);
let translated = render(&localized, message, attribute);
assert_eq!(
markers(&translated),
markers(&expected),
"{language:?}: {message}.{attribute}"
);
assert_eq!(
translated.is_empty(),
expected.is_empty(),
"{language:?}: {message}.{attribute}"
);
}
}
}
#[test]
fn greek_language_setting_round_trips() {
let loader = FluentLanguageLoader::new("opencadstudio", "en-US".parse().unwrap());
load_language(&loader, Language::ElGr).expect("Greek Fluent resources must parse");
assert_eq!(loader.get_attr("language", "greek"), "Ελληνικά");
assert_eq!(serde_json::to_string(&Language::ElGr).unwrap(), "\"el-GR\"");
}
+25 -25
View File
@@ -163,12 +163,12 @@ impl From<&str> for OpenLoadError {
#[cfg(not(target_arch = "wasm32"))]
pub async fn pick_open_path() -> Option<(PathBuf, u64)> {
let handle = crate::sys::file_dialog()
.set_title("Open CAD file")
.add_filter("CAD Files", &["dwg", "dxf", "bak", "sv$", "DWG", "DXF", "BAK"])
.add_filter("DWG Files", &["dwg", "DWG"])
.add_filter("DXF Files", &["dxf", "DXF"])
.add_filter("Backup / Autosave", &["bak", "sv$", "BAK"])
.add_filter("All Files", &["*"])
.set_title(crate::t!("Open CAD file").as_ref())
.add_filter(crate::t!("CAD Files").as_ref(), &["dwg", "dxf", "bak", "sv$", "DWG", "DXF", "BAK"])
.add_filter(crate::t!("DWG Files").as_ref(), &["dwg", "DWG"])
.add_filter(crate::t!("DXF Files").as_ref(), &["dxf", "DXF"])
.add_filter(crate::t!("Backup / Autosave").as_ref(), &["bak", "sv$", "BAK"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.pick_file()
.await?;
let path = crate::sys::handle_path(&handle);
@@ -181,12 +181,12 @@ pub async fn pick_open_path() -> Option<(PathBuf, u64)> {
/// rather than needing anything of their own. (#624)
pub async fn pick_layer_standard_path() -> Option<PathBuf> {
let handle = crate::sys::file_dialog()
.set_title("Load layer standard")
.set_title(crate::t!("Load layer standard").as_ref())
.add_filter(
"Drawings and standards",
crate::t!("Drawings and standards").as_ref(),
&["dwg", "dws", "dwt", "dxf", "DWG", "DWS", "DWT", "DXF"],
)
.add_filter("All Files", &["*"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.pick_file()
.await?;
Some(crate::sys::handle_path(&handle))
@@ -195,13 +195,13 @@ pub async fn pick_layer_standard_path() -> Option<PathBuf> {
/// Pick where a set of layer mappings is written, or read back from.
pub async fn pick_layer_mapping_path(save: bool) -> Option<PathBuf> {
let dialog = crate::sys::file_dialog()
.set_title(if save {
.set_title(crate::t!(if save {
"Save layer mappings"
} else {
"Load layer mappings"
})
.add_filter("Layer mappings", &["ocslmap"])
.add_filter("All Files", &["*"]);
}).as_ref())
.add_filter(crate::t!("Layer mappings").as_ref(), &["ocslmap"])
.add_filter(crate::t!("All Files").as_ref(), &["*"]);
let handle = if save {
dialog.set_file_name("layers.ocslmap").save_file().await?
} else {
@@ -504,9 +504,9 @@ pub async fn pick_and_load_web(
progress: Arc<OpenProgressState>,
) -> WebOpenOutcome {
let Some(handle) = crate::sys::file_dialog()
.set_title("Open CAD file")
.add_filter("CAD Files", &["dwg", "dxf", "DWG", "DXF"])
.add_filter("All Files", &["*"])
.set_title(crate::t!("Open CAD file").as_ref())
.add_filter(crate::t!("CAD Files").as_ref(), &["dwg", "dxf", "DWG", "DXF"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.pick_file()
.await
else {
@@ -1506,10 +1506,10 @@ mod save_failure_tests {
/// Show a file-open dialog and load the selected CTB or STB file.
pub async fn pick_plot_style() -> Option<plot_style::PlotStyleTable> {
let dialog = crate::sys::file_dialog()
.set_title("Load Plot Style Table")
.add_filter("Plot Style Tables", &["ctb", "CTB"])
.add_filter("CTB Files", &["ctb", "CTB"])
.add_filter("All Files", &["*"]);
.set_title(crate::t!("Load Plot Style Table").as_ref())
.add_filter(crate::t!("Plot Style Tables").as_ref(), &["ctb", "CTB"])
.add_filter(crate::t!("CTB Files").as_ref(), &["ctb", "CTB"])
.add_filter(crate::t!("All Files").as_ref(), &["*"]);
#[cfg(not(target_arch = "wasm32"))]
let dialog = match plot_style::ensure_plot_styles_dir() {
Ok(dir) => dialog.set_directory(dir),
@@ -1525,11 +1525,11 @@ pub async fn pick_plot_style() -> Option<plot_style::PlotStyleTable> {
/// Returns `(path, pixel_width, pixel_height)` or an error string.
pub async fn pick_image_file() -> Result<(PathBuf, u32, u32), String> {
let handle = crate::sys::file_dialog()
.set_title("Select Image File")
.add_filter("Images", &["png", "jpg", "jpeg", "bmp", "tiff", "tif"])
.add_filter("PNG", &["png"])
.add_filter("JPEG", &["jpg", "jpeg"])
.add_filter("All Files", &["*"])
.set_title(crate::t!("Select Image File").as_ref())
.add_filter(crate::t!("Images").as_ref(), &["png", "jpg", "jpeg", "bmp", "tiff", "tif"])
.add_filter(crate::t!("PNG").as_ref(), &["png"])
.add_filter(crate::t!("JPEG").as_ref(), &["jpg", "jpeg"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.pick_file()
.await
.ok_or_else(|| "Cancelled".to_string())?;
+3 -3
View File
@@ -206,10 +206,10 @@ pub fn pick_pdf_path_owned(
) -> Option<std::path::PathBuf> {
let path = crate::sys::blocking_file_dialog()
.set_parent(parent)
.set_title("Export as PDF")
.set_title(crate::t!("Export as PDF").as_ref())
.set_file_name(&format!("{stem}.pdf"))
.add_filter("PDF Files", &["pdf"])
.add_filter("All Files", &["*"])
.add_filter(crate::t!("PDF Files").as_ref(), &["pdf"])
.add_filter(crate::t!("All Files").as_ref(), &["*"])
.save_file()
?;
crate::config::remember_dialog_dir(&path);
File diff suppressed because it is too large. Load diff
+7 -7
View File
@@ -280,14 +280,14 @@ impl CadCommand for ArcLengthDimensionCommand {
fn prompt(&self) -> String {
if self.awaiting_text {
return "DIMARC Enter dimension text (blank = measured value):".to_string();
return crate::t!("DIMARC Enter dimension text (blank = measured value):").into_owned();
}
if self.awaiting_angle {
return "DIMARC Specify text angle (degrees):".to_string();
return crate::t!("DIMARC Specify text angle (degrees):").into_owned();
}
match self.step {
Step::SelectObject => {
"DIMARC Select arc or polyline arc segment:".to_string()
crate::t!("DIMARC Select arc or polyline arc segment:").into_owned()
}
Step::DimLine(selection) => {
let leader_option = if selection.sweep() > std::f64::consts::FRAC_PI_2 {
@@ -295,15 +295,15 @@ impl CadCommand for ArcLengthDimensionCommand {
} else {
""
};
format!(
crate::tf!(
"DIMARC Specify arc length dimension location [Mtext/Text/Angle/Partial{leader_option}]:"
)
).into_owned()
}
Step::PartialFirst(_) => {
"DIMARC Specify first point of partial arc:".to_string()
crate::t!("DIMARC Specify first point of partial arc:").into_owned()
}
Step::PartialSecond { .. } => {
"DIMARC Specify second point of partial arc:".to_string()
crate::t!("DIMARC Specify second point of partial arc:").into_owned()
}
}
}
+3 -4
View File
@@ -385,16 +385,15 @@ impl CadCommand for DimBaselineCommand {
fn prompt(&self) -> String {
if self.base.is_none() {
"DIMBASELINE Select base dimension:".to_string()
crate::t!("DIMBASELINE Select base dimension:").into_owned()
} else if self
.base
.as_ref()
.is_some_and(|base| matches!(&base.kind, BaselineKind::Ordinate { .. }))
{
"DIMBASELINE Specify feature location [Undo/Select] <Select>:".to_string()
crate::t!("DIMBASELINE Specify feature location [Undo/Select] <Select>:").into_owned()
} else {
"DIMBASELINE Specify second extension line origin [Select/Undo] <Select>:"
.to_string()
crate::t!("DIMBASELINE Specify second extension line origin [Select/Undo] <Select>:").into_owned()
}
}
+3 -4
View File
@@ -322,16 +322,15 @@ impl CadCommand for DimContinueCommand {
fn prompt(&self) -> String {
if self.base.is_none() {
"DIMCONTINUE Select continued dimension:".to_string()
crate::t!("DIMCONTINUE Select continued dimension:").into_owned()
} else if self
.base
.as_ref()
.is_some_and(|base| matches!(&base.kind, ContinueKind::Ordinate { .. }))
{
"DIMCONTINUE Specify feature location [Undo/Select] <Select>:".to_string()
crate::t!("DIMCONTINUE Specify feature location [Undo/Select] <Select>:").into_owned()
} else {
"DIMCONTINUE Specify second extension line origin [Select/Undo] <Select>:"
.to_string()
crate::t!("DIMCONTINUE Specify second extension line origin [Select/Undo] <Select>:").into_owned()
}
}
+1 -1
View File
@@ -249,7 +249,7 @@ impl CadCommand for TextCommand {
"TEXT Current style: {}, Height: {}, Annotative: {}",
self.style_name,
self.height,
if self.annotative { "Yes" } else { "No" }
if self.annotative { t!("Yes") } else { t!("No") }
),
t!("TEXT Specify start point or [Justify/Style]:")
),
+2 -2
View File
@@ -573,7 +573,7 @@ impl CadCommand for HatchCommand {
"D",
),
CmdOption::new(
&format!("Island style: {}", self.island_style_label()),
&crate::tf!("Island style: {}", self.island_style_label()),
"Y",
),
];
@@ -603,7 +603,7 @@ impl CadCommand for HatchCommand {
"D",
),
CmdOption::new(
&format!("Island style: {}", self.island_style_label()),
&crate::tf!("Island style: {}", self.island_style_label()),
"Y",
),
];
+8 -8
View File
@@ -53,14 +53,14 @@ impl Report {
self.translated.iter().map(|(_, _, n)| n).sum()
}
/// The transaction log AutoCAD writes beside the drawing, as text.
/// Layer translation log stored beside the drawing.
pub fn to_log(&self) -> String {
let mut out = String::new();
for (from, to, moved) in &self.translated {
out.push_str(&format!("{from} -> {to} ({moved} object(s))\n"));
out.push_str(&crate::tf!("{from} -> {to} ({moved} object(s))\n").into_owned());
}
for (layer, reason) in &self.skipped {
out.push_str(&format!("{layer}: skipped — {reason}\n"));
out.push_str(&crate::tf!("{layer}: skipped — {reason}\n").into_owned());
}
out
}
@@ -85,7 +85,7 @@ pub fn load_targets(path: &Path) -> Result<Vec<TargetLayer>, String> {
.collect();
targets.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
if targets.is_empty() {
return Err("no layers found in that file".into());
return Err(crate::t!("no layers found in that file").into_owned());
}
Ok(targets)
}
@@ -143,20 +143,20 @@ pub fn translate(
if mapping.from == "0" {
report
.skipped
.push((mapping.from.clone(), "layer \"0\" cannot be translated".into()));
.push((mapping.from.clone(), crate::t!("layer \"0\" cannot be translated").into_owned()));
continue;
}
if mapping.from.eq_ignore_ascii_case(current_layer) {
report.skipped.push((
mapping.from.clone(),
"it is the current layer".into(),
crate::t!("it is the current layer").into_owned(),
));
continue;
}
if !scene.document.layers.contains(&mapping.from) {
report
.skipped
.push((mapping.from.clone(), "no such layer in this drawing".into()));
.push((mapping.from.clone(), crate::t!("no such layer in this drawing").into_owned()));
continue;
}
// Bring the target in with the standard's own properties. An existing
@@ -170,7 +170,7 @@ pub fn translate(
else {
report
.skipped
.push((mapping.from.clone(), "target layer is not in the loaded set".into()));
.push((mapping.from.clone(), crate::t!("target layer is not in the loaded set").into_owned()));
continue;
};
let mut layer = target.layer.clone();
+12 -12
View File
@@ -383,27 +383,27 @@ impl CadCommand for PolysolidCommand {
fn prompt(&self) -> String {
match self.step {
Step::Start => format!(
Step::Start => crate::tf!(
"POLYSOLID Height = {:.4}, Width = {:.4}, Justification = {}\n{}",
self.height,
self.width,
match self.justification {
PolysolidJustification::Left => "Left",
PolysolidJustification::Center => "Center",
PolysolidJustification::Right => "Right",
PolysolidJustification::Left => crate::t!("Left"),
PolysolidJustification::Center => crate::t!("Center"),
PolysolidJustification::Right => crate::t!("Right"),
},
t!("Specify start point or [Object/Height/Width/Justify] <Object>:")
),
Step::Height => format!("Specify height <{:.4}>:", self.height),
Step::Width => format!("Specify width <{:.4}>:", self.width),
Step::Justify => format!(
).into_owned(),
Step::Height => crate::tf!("Specify height <{:.4}>:", self.height).into_owned(),
Step::Width => crate::tf!("Specify width <{:.4}>:", self.width).into_owned(),
Step::Justify => crate::tf!(
"Enter justification [Left/Center/Right] <{}>:",
match self.justification {
PolysolidJustification::Left => "Left",
PolysolidJustification::Center => "Center",
PolysolidJustification::Right => "Right",
PolysolidJustification::Left => crate::t!("Left"),
PolysolidJustification::Center => crate::t!("Center"),
PolysolidJustification::Right => crate::t!("Right"),
}
),
).into_owned(),
Step::Object => t!("Select object:").into_owned(),
Step::Line if self.vertices.len() >= 3 => {
t!("Specify next point or [Arc/Close/Undo]:").into_owned()
+2 -11
View File
@@ -8,8 +8,6 @@
// command at all). Naming each style once, next to the command that applies it,
// is what keeps the three in step. (#621)
use std::sync::OnceLock;
use acadrust::entities::ViewportRenderMode as Mode;
use crate::modules::IconKind;
@@ -95,16 +93,9 @@ pub fn keyword_choices() -> Vec<(&'static str, &'static str, Option<&'static str
.collect()
}
/// The prompt those choices are announced with. Built once from the table so
/// the line the user reads cannot list something the picker does not offer.
/// Stable source used by the shared command prompt translator.
pub fn keyword_prompt() -> &'static str {
static PROMPT: OnceLock<String> = OnceLock::new();
PROMPT
.get_or_init(|| {
let listed: Vec<&str> = VISUAL_STYLES.iter().map(|style| style.label).collect();
format!("Visual style [{}]:", listed.join(" / "))
})
.as_str()
"Visual style [Wireframe 2D/Wireframe 3D/Hidden Line/Flat Shaded/Gouraud Shaded/Flat Shaded + Edges/Gouraud Shaded + Edges]:"
}
pub fn label_for(mode: Mode) -> &'static str {
+13 -9
View File
@@ -442,20 +442,24 @@ pub mod web_diag {
padding:8px 12px;max-height:40vh;overflow:auto;\
user-select:text;cursor:text;",
);
// Inline `onclick` keeps this dependency-free (no JS closures):
// Copy puts the full error text on the clipboard; Dismiss
// removes the overlay so the app stays usable underneath.
overlay.set_inner_html(
"<div><b>OpenCADStudio renderer error</b> — please copy \
this into a bug report: \
<button style=\"margin-left:8px\" onclick=\"navigator.clipboard.writeText(\
document.getElementById('ocs-err-text').innerText)\">Copy</button> \
<button onclick=\"document.getElementById('ocs-err').remove()\">\
Dismiss</button></div>\
"<div><b id=\"ocs-err-title\"></b> \
<button id=\"ocs-err-copy\" style=\"margin-left:8px\" onclick=\"navigator.clipboard.writeText(\
document.getElementById('ocs-err-text').innerText)\"></button> \
<button id=\"ocs-err-dismiss\" onclick=\"document.getElementById('ocs-err').remove()\"></button></div>\
<pre id=\"ocs-err-text\" style=\"margin:6px 0 0;\
white-space:pre-wrap;user-select:text;\"></pre>",
);
let _ = body.append_child(&overlay);
for (id, label) in [
("ocs-err-title", crate::t!("OpenCADStudio renderer error — copy this into a bug report:")),
("ocs-err-copy", crate::t!("Copy")),
("ocs-err-dismiss", crate::t!("Dismiss")),
] {
if let Some(element) = doc.get_element_by_id(id) {
element.set_text_content(Some(label.as_ref()));
}
}
match doc.get_element_by_id("ocs-err-text") {
Some(pre) => pre,
None => return,
+15 -15
View File
@@ -169,7 +169,7 @@ pub fn color_selector_varies<'a>(
on_toggle: Message,
on_more: Message,
) -> Element<'a, Message> {
let indicator = container(text("?").size(10))
let indicator = container(text(crate::t!("?")).size(10))
.style(|theme: &Theme| {
let palette = theme.palette();
container::Style {
@@ -434,7 +434,7 @@ pub fn index_color_page<'a>(
}
let recent: Element<'a, Message> = if recent_colors.is_empty() {
text("No recent colors yet")
text(crate::t!("No recent colors yet"))
.size(10)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(
@@ -464,18 +464,18 @@ pub fn index_color_page<'a>(
format!("ACI {i} RGB {r}, {g}, {b}")
}
AcadColor::Rgb { r, g, b } => {
format!("True Color RGB {r}, {g}, {b}")
crate::tf!("True Color RGB {r}, {g}, {b}").into_owned()
}
AcadColor::ByLayer => "ByLayer".to_string(),
AcadColor::ByBlock => "ByBlock".to_string(),
AcadColor::None => "None".to_string(),
AcadColor::ByLayer => t!("ByLayer").into_owned(),
AcadColor::ByBlock => t!("ByBlock").into_owned(),
AcadColor::None => t!("None").into_owned(),
};
let tabs = row![
button(text("Index Color").size(12))
button(text(crate::t!("Index Color")).size(12))
.style(button::primary)
.padding([5, 14]),
button(text("True Color").size(12))
button(text(crate::t!("True Color")).size(12))
.on_press(Message::ColorPickerTabChanged(
crate::app::ColorPickerTab::TrueColor
))
@@ -486,11 +486,11 @@ pub fn index_color_page<'a>(
let actions = container(
row![
button(text("Cancel").size(11))
button(text(crate::t!("Cancel")).size(11))
.on_press(Message::CloseColorPicker)
.style(button::secondary)
.padding([5, 14]),
button(text("OK").size(11))
button(text(crate::t!("OK")).size(11))
.on_press(Message::ColorWindowPick(current))
.style(button::primary)
.padding([5, 18]),
@@ -503,11 +503,11 @@ pub fn index_color_page<'a>(
container(
column![
tabs,
text("Standard colors").size(11),
text(crate::t!("Standard colors")).size(11),
standard,
text("CAD Color Index (ACI) 10–249").size(11),
text(crate::t!("CAD Color Index (ACI) 10–249")).size(11),
text("Color family → · Shade / intensity ↓")
text(crate::t!("Color family → · Shade / intensity ↓"))
.size(9)
.style(|theme: &Theme| iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.65)),
@@ -527,11 +527,11 @@ pub fn index_color_page<'a>(
..Default::default()
}),
text("Grayscale 250–255").size(11),
text(crate::t!("Grayscale 250–255")).size(11),
grayscale,
text("Recently used").size(11),
text(crate::t!("Recently used")).size(11),
recent,
+1 -1
View File
@@ -769,7 +769,7 @@ impl CommandLine {
style
})
.padding([2, 6]);
let mcp_tip = container(text(mcp_tooltip).size(11))
let mcp_tip = container(text(t!(mcp_tooltip)).size(11))
.padding([3, 6])
.style(container::bordered_box);
let mcp_btn = container(tooltip(mcp_btn, mcp_tip, tooltip::Position::Top).gap(4))
+6 -6
View File
@@ -323,8 +323,8 @@ let mut layer_usage = vec![Vec::<String>::new(); 256];
} else {
crate::io::plot_style::LW_TABLE
.get(e.lineweight as usize)
.map(|lw| format!("{lw:.2}mm (idx {})", e.lineweight))
.unwrap_or_else(|| format!("idx {}", e.lineweight))
.map(|lw| crate::tf!("{lw:.2}mm (idx {})", e.lineweight).into_owned())
.unwrap_or_else(|| crate::tf!("idx {}", e.lineweight).into_owned())
}
})
.unwrap_or_else(|| "—".into());
@@ -491,7 +491,7 @@ let mut layer_usage = vec![Vec::<String>::new(); 256];
container(
column![
row![
text(format!("Layers using ACI {selected_aci}"))
text(crate::tf!("Layers using ACI {selected_aci}"))
.size(11),
Space::new().width(iced::Length::Fill),
@@ -500,9 +500,9 @@ let mut layer_usage = vec![Vec::<String>::new(); 256];
"{} {}",
selected_layers.len(),
if selected_layers.len() == 1 {
"layer"
t!("layer")
} else {
"layers"
t!("layers")
}
))
.size(10)
@@ -512,7 +512,7 @@ let mut layer_usage = vec![Vec::<String>::new(); 256];
if selected_layers.is_empty() {
column![
text("No layers use this ACI in the current drawing.")
text(t!("No layers use this ACI in the current drawing."))
.size(10)
.style(muted_style)
]
+3 -3
View File
@@ -254,11 +254,11 @@ impl canvas::Program<Message> for TablePreviewCanvas {
let active = &self.rows[self.active_row.min(2)];
frame.fill_text(canvas::Text {
content: format!(
"{} · {} · type {} / unit {}{}{}",
"{} · {} · {} {} / {} {}{}{}",
active.text_style,
if self.annotative { t!("Annotative") } else { t!("Drawing units") },
active.data_type,
active.unit_type,
t!("Type"), active.data_type,
t!("Unit"), active.unit_type,
if self.description.trim().is_empty() { "" } else { " · " },
self.description,
),
+1 -1
View File
@@ -118,7 +118,7 @@ pub fn color_label(c: AcadColor) -> String {
AcadColor::Index(5) => "Blue".into(),
AcadColor::Index(6) => "Magenta".into(),
AcadColor::Index(7) => "White".into(),
AcadColor::Index(n) => format!("Color {n}"),
AcadColor::Index(n) => crate::tf!("Color {n}").into_owned(),
AcadColor::Rgb { r, g, b } => format!("{r},{g},{b}"),
}
}
+5 -5
View File
@@ -210,18 +210,18 @@ pub fn view(palette: &BlockPalette, width: f32, auto_collapse: bool) -> Element<
style
})
.padding([3, 5]);
let pin = tooltip(pin, text("Auto").size(10), tooltip::Position::Bottom).gap(4);
let pin = tooltip(pin, text(crate::t!("Auto")).size(10), tooltip::Position::Bottom).gap(4);
let close = button(crate::ui::icons::themed_secondary(crate::ui::icons::CLOSE, 12.0))
.on_press(Message::Dock(DockMsg::Close(PanelId::BlockPalette)))
.style(button::subtle)
.padding([3, 5]);
let close = tooltip(close, text("Close").size(10), tooltip::Position::Bottom).gap(4);
let close = tooltip(close, text(crate::t!("Close")).size(10), tooltip::Position::Bottom).gap(4);
let title_bar = mouse_area(
container(
row![
text("Block Palette").size(12),
text(crate::t!("Block Palette")).size(12),
iced::widget::Space::new().width(Fill),
pin,
close,
@@ -239,7 +239,7 @@ pub fn view(palette: &BlockPalette, width: f32, auto_collapse: bool) -> Element<
.on_press(Message::Dock(DockMsg::DockGrab(PanelId::BlockPalette)))
.interaction(iced::mouse::Interaction::Grab);
let search_input = text_input("Search blocks…", &palette.search)
let search_input = text_input(&crate::t!("Search blocks…"), &palette.search)
.on_input(|v| Message::BlockPalette(BlockPaletteMsg::Search(v)))
.padding([4, 8])
.size(12);
@@ -264,7 +264,7 @@ pub fn view(palette: &BlockPalette, width: f32, auto_collapse: bool) -> Element<
} else {
"No matches"
};
container(text(msg).size(12).color(iced::Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 }))
container(text(crate::t!(msg)).size(12).color(iced::Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 }))
.center_x(Fill)
.center_y(Fill)
.width(Fill)
+1 -1
View File
@@ -288,7 +288,7 @@ pub fn view_window<'a>(
.into_iter()
.map(|(value, label)| Labelled {
value,
label: label.to_string(),
label: crate::t!(label).into_owned(),
})
.collect::<Vec<_>>();
+20 -2
View File
@@ -47,13 +47,31 @@ impl PlotChoice {
impl fmt::Display for PlotChoice {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.localized {
formatter.write_str(crate::i18n::translate(&self.raw).as_ref())
if let Some(name) = self.raw.strip_prefix("View: ") {
formatter.write_str(crate::tf!("View: {name}").as_ref())
} else {
formatter.write_str(crate::i18n::translate(&self.raw).as_ref())
}
} else {
formatter.write_str(&self.raw)
}
}
}
#[cfg(test)]
mod tests {
use super::PlotChoice;
#[test]
fn named_view_display_preserves_the_saved_plot_area() {
let name = "Plan: north";
let choice = PlotChoice::localized(format!("View: {name}"));
assert_eq!(choice.to_string(), crate::tf!("View: {name}").as_ref());
assert_eq!(choice.raw.strip_prefix("View: "), Some(name));
assert_eq!(PlotChoice::raw("View: PDF printer").to_string(), "View: PDF printer");
}
}
/// One of the many boolean plot options (folded into a single message so the
/// dialog needn't carry a variant per checkbox).
#[derive(Debug, Clone, Copy)]
@@ -716,7 +734,7 @@ pub fn view_window(
area_options.extend(
s.plot_views
.iter()
.map(|name| PlotChoice::raw(format!("View: {name}"))),
.map(|name| PlotChoice::localized(format!("View: {name}"))),
);
}
let mut area_row = row![