CADCraft 界面本地化实战:17 种语言、显示时目录、字体回退与自动化切换
【免费下载链接】cadcraft
CADCraft: computer-aided design and drafting — an open-source, clean-room AutoCAD-style app in pure Rust
CADCraft 把界面文案与绘图数据彻底解耦:英文源码字符串始终是查找键,翻译只在“显示边界”发生,因此切换语言既不会改动图纸内容,也不会改变命令 ID 与自动化协议。本文以 docs/localization.md 为主线,结合 crates/ui-egui/src/i18n.rs 的真实实现,讲清语言选择、Auto 解析、CADCRAFT_LOCALE覆盖、控制通道调用、偏好持久化、字体与字形回退,以及如何新增一门语言目录。
语言清单与入口
菜单路径固定为Edit → Interface Language,下拉框列出以下 17 个偏好值(外加一个 “auto” 语义项):
| 显示名 | 偏好代码 | 说明 |
|---|---|---|
| Auto (system language) | auto | 跟随操作系统或浏览器语言 |
| English | en | 源语言,缺翻译时的兜底 |
| Ελληνικά | el | 希腊语 |
| Українська | uk | 乌克兰语 |
| Português (Brasil) | pt-BR | 巴西葡萄牙语 |
| Deutsch | de | 德语 |
| Español | es | 西班牙语 |
| Čeština | cs | 捷克语 |
| Русский | ru | 俄语 |
| Français | fr | 法语 |
| 简体中文 | zh | 中文(需要 CJK 字体) |
| Italiano | it | 意大利语 |
| 한국어 / Korean | ko | 韩语(无 Hangul 字体时显示英文名) |
| Svenska | sv | 瑞典语 |
| العربية | ar | 阿拉伯语(从右到左书写) |
| Magyar | hu | 匈牙利语 |
| Türkçe | tr | 土耳其语 |
这些取值是“偏好”而不是“实际显示语言”:Preference枚举(crates/ui-egui/src/i18n.rs#L11-L48)通过#[serde(rename = ...)]与字符串一一对应,Preference::code()给出序列化/命令行代码,Preference::name()给出界面显示名。选择结果立即生效,无需重启,包括 macOS 的原生菜单——原生菜单在语言变化时逐项重写标题(apps/cadcraft/src/native_menu.rs#L114-L140)。
下拉框本身的排序并非数组顺序,而是Preference::for_picker()的结果(crates/ui-egui/src/i18n.rs#L71-L86):先Auto与English,其余按“该语言自己的名字”做去重音折叠后排序,因此 Čeština 落在 C 下、Magyar 落在 M 下;非拉丁文字(希腊文、西里尔文、阿拉伯文、CJK)排在拉丁名之后。测试picker_lists_auto_and_english_first_then_names_alphabetically固化了这一顺序,并特别断言Türkçe的排序键是turkce(crates/ui-egui/src/i18n.rs#L425-L441)。
对话框实现非常薄:选中项直接调用 UI 命令ui.language(crates/ui-egui/src/dialogs.rs#L431-L443)。
let _ = app.run("ui.language", serde_json::json!({"lang": preference.code()}));也就是说,界面操作、命令行调用、控制通道调用走的是同一条代码路径,不存在“只有菜单能用”的隐藏分支。
Auto 模式:BCP 47 与 POSIX 标签如何收敛到主语言
Auto 的语义是:在系统偏好语言列表中,取第一个受支持的语言。桌面端读取sys_locale::get_locales(),Web 端读取navigator.languages:
- 桌面:apps/cadcraft/src/main.rs#L317
- Web:apps/cadcraft-web/src/main.rs#L155-L156
解析函数只有寥寥数行,但覆盖了 BCP 47、POSIX 与.UTF-8/@euro修饰符(crates/ui-egui/src/i18n.rs#L239-L244):
pub fn locale_language(tag: &str) -> Option<&'static str> { let primary = tag.trim().split(['-', '_', '.', '@']).next()?; LANGUAGES.iter() .find(|l| l.code.split('-').next().is_some_and(|p| p.eq_ignore_ascii_case(primary))) .map(|l| l.code) }关键行为与文档一致,并被测试逐一锁定(crates/ui-egui/src/i18n/tests.rs#L160-L206):
| 输入标签 | 解析结果 | 原因 |
|---|---|---|
uk-UA、uk_UA.UTF-8、uk_UA@euro、" uk-UA " | uk | 取主语言并按大小写不敏感比较 |
de_AT.UTF-8、DE-at | de | 同上 |
es-MX、ES-ar | es | 同上 |
pt_BR.UTF-8、pt-PT、PT-br | pt-BR | 任意pt-*都落到唯一的葡萄牙语目录 |
cs_CZ.UTF-8、cs_CZ@euro | cs | 同上 |
it_CH.UTF-8、ko_KR.UTF-8、sv_FI、hu_HU.UTF-8、tr-CY | it/ko/sv/hu/tr | 只看主语言 |
el-GR、EL-cy | el | 同上 |
zh-CN | zh | 任意zh-* |
ua、ukulele、cz、csharp、kr、se、swenska、magyar、elvish | 无匹配 →en | 不做模糊/近邻猜测 |
注意最后一行的细节:se是北萨米语、kr不是韩语、cz不是捷克语,实现刻意不做“看起来像”的匹配,避免把用户带到错误的语言上。Auto.resolve()的语义是“列表里第一个能解析的”,例如["pl-PL", "pt-BR"]→pt-BR,列表为空则回落到en(crates/ui-egui/src/i18n.rs#L156-L176)。
启动期覆盖:CADCRAFT_LOCALE
若只想临时以某语言启动桌面版:
CADCRAFT_LOCALE=cs-CZ cadcraft该变量在 apps/cadcraft/src/env.rs#L12 定义,并在启动时被包装成只含一个元素的“系统语言列表”:
app.system_languages = vars.locale.map(|s| vec![s]).unwrap_or_else(|| sys_locale::get_locales().collect());配套规则来自实现与文档:
- 变量值为空白(
""或全空格)时视为未设置,Env::from_vars用filter(|v| !v.trim().is_empty())处理,env.rs的测试直接断言这一点。 - 该变量只影响本次启动的“系统语言”输入,不写入偏好。
- 已保存的显式语言选择优先于该覆盖。原因是
load_prefs在启动流程中随后执行,会把interfaceLanguage写回app.ui.interface_language(crates/ui-egui/src/lib.rs#L455-L462)。因此要把CADCRAFT_LOCALE真正“生效”,要么此前保持 Auto,要么先在界面里选回 Auto。
自动化切换:控制通道的两种写法
文档给出的 JSON-RPC 风格请求:
{"id":1,"method":"engine.execute","params":{"command":"ui.language","params":{"lang":"cs"}}}它由run_language_command处理(crates/ui-egui/src/menus.rs#L258-L275),要点有三:
- 参数校验严格:
lang必须能Preference::parse成功,否则整个请求返回Err,错误信息为language must be auto, en, el, uk, …,当前选择保持不变。 - 返回值带
shown字段:{"interfaceLanguage": "cs", "shown": "cs"}。shown是“实际显示语言”,可能因缺字体而回落为en。 - 回落时给出可读状态提示:韩语缺 Hangul 字体与中文缺 CJK 字体分别有专门文案,直接写进状态栏。
通用偏好通道ui.set也接受同一键名(见 docs/control-protocol.md 与 crates/ui-egui/src/options.rs#L163-L172):
{"id":1,"method":"ui.set","params":{"interfaceLanguage":"cs"}}options命令会在返回的 UI 偏好中回显interfaceLanguage(crates/ui-egui/src/options.rs#L210-L222),可用于自动化脚本确认最终状态。非法值同样被拒绝,错误格式为interfaceLanguage must be <codes>。
另外ui.resetpalettes(Window → Reset Palettes,crates/ui-egui/src/menus.rs#L404)只重置面板布局,不会清掉语言选择;测试live_switch_translates_the_shell_without_mutating_drawing_or_automation在每种语言下都调用了该命令并断言语言不变。
偏好持久化:cadcraft.prefs
语言选择与其他界面偏好一起存在键cadcraft.prefs下(crates/ui-egui/src/lib.rs#L69):
- 桌面端由 eframe storage 承载,写入点在 apps/cadcraft/src/main.rs#L133,读取点在 apps/cadcraft/src/main.rs#L322-L323;
- Web 端使用浏览器 local storage;
- 内容形如
{"theme":..., "animations":..., "interfaceLanguage":"auto", "saveFormat":..., "saveVersion":...},由prefs_json()序列化(crates/ui-egui/src/lib.rs#L427-L447)。
容错策略值得单独说明:load_prefs对 JSON 解析失败、字段缺失、值未知都只是“忽略”,不会崩;Preference::restore在保存值无法解析时回落到Auto(crates/ui-egui/src/i18n.rs#L147-L154)。测试里直接写入了{"interfaceLanguage":"klingon"},断言原偏好保持不变(crates/ui-egui/src/i18n/tests.rs#L238-L247)。没有保存过选择的安装即使用 Auto。
翻译覆盖范围
按文档,目录覆盖命令与菜单标签、工具组与工具提示、Start 页面、文件与模型空间标签、状态栏控件、对象捕捉、绘图提示、属性标签、对话框以及 About 窗口的控件。命令参考窗口(Command Reference,默认 F1)的过滤器同时匹配英文命令 ID、英文标签与翻译标签:
if !f.is_empty() && !c.id.contains(&f) && !c.label.to_ascii_lowercase().contains(&f) && !crate::i18n::t(c.label).to_lowercase().contains(&f) { continue; }(crates/ui-egui/src/dialogs.rs#L358-L396)
各语言的规模与质量状态(数字取自 docs/localization.md,目录仍在增长——例如cs.tsv当前 1229 行,含 5 行注释):
| 语言 | 目录行数 | 菜单类目(menu行) | 复数形式数 | 备注 |
|---|---|---|---|---|
捷克语cs | 1,202 | 4 | 3 | 四个菜单标题与对象标签区分:Zobrazení / Pohled、Kreslení / Kreslit、Kótování / Kóta、Modifikace / Upravit |
韩语ko | 1,235 | 4 | 1 | 原创 AI 辅助韩语 CAD 术语(도면층、치수、모깎기、모따기、간격띄우기、객체 스냅) |
巴西葡萄牙语pt-BR | 1,231 | 0 | 2 | 复数规则one|other(0 和 1 都算one),缺口为零 |
匈牙利语hu | 1,326 | 4 | 1 | 仅Méretezés / Méret一词在菜单标题与对象标签间不同 |
土耳其语tr | 1,330 | 4 | 1 | Ölçülendirme(菜单)/Ölçü(对象);无可回落字面量 |
希腊语el | 1,330 | 4 | 2 | 1,324 条普通标签 + 4 条菜单标题 + 2 条整数计数模板 |
乌克兰语uk | — | 0 | 3 | 参考目录(reference catalog) |
希腊语还给出了一个值得借鉴的术语策略:专业词汇保留英文括注,便于熟悉 CAD 命令的用户对上号,例如Επίπεδο (Layer)、Πολυγραμμή (Polyline)、Διαγράμμιση (Hatch);而 Open、Save 这类常用动作直接用希腊语。土耳其语则把提示语固定为 “… girin”(Enter)与 “… belirtin”(Specify),并让可点击关键字(Top、Zero、Bottom、_tan to、nX/nXP)保持引擎可接受的拼写。
所有目录都是基于 CADCraft 英文原文的原创翻译,许可为
MIT OR Apache-2.0,作者与状态登记在 ATTRIBUTION.md。文档明确提醒:不得复制专有本地化资源。
显示边界:什么会被翻译,什么不会
i18n::with_language用线程局部Cell<&'static str>在一帧渲染内设置当前语言,并用Drop守卫还原(crates/ui-egui/src/i18n.rs#L246-L260):
pub fn with_language<R>(language: &'static str, draw: impl FnOnce() -> R) -> R { struct Restore(&'static str); impl Drop for Restore { fn drop(&mut self) { CURRENT.set(self.0); } } let _restore = Restore(CURRENT.replace(language)); draw() }应用绘制入口就是i18n::with_language(self.language(), || self.draw(ui))(crates/ui-egui/src/lib.rs#L749)。这条边界带来几个直接后果:
- 无条件翻译:
tl!("Properties")、i18n::t(label)、tf!("Version {version}", version = v)只在渲染期生效;同一进程中的无头调用、并行测试默认英文。 - 不翻译用户数据:图纸标题、图层/布局/块/样式名、参数名与表达式、图纸中的文字、命令行输入都保持原样。测试里把图纸标题设为
"Open",然后在每种语言下断言界面出现翻译后的Open、而标题仍是"Open"。 - 不翻译自动化面:CLI/MCP/控制通道响应、引擎诊断、命令历史保持英文;
ui.inspect与菜单树返回的英文标签与 ID 不变。 - 未收录文本回落英文,且永远不会让构建失败。
菜单标题单独走一条路径:i18n::menu/i18n::tr_menu先查("menu", source)键,查不到再退回普通翻译(crates/ui-egui/src/i18n.rs#L297-L305)。这就是捷克语能同时拥有 “Kótování”(菜单)与 “Kóta”(对象)的原因。窗口与工具组仍以英文源字符串作为 ID/键,所以切换语言不会丢状态——文档中“Windows retain explicit IDs based on their English source”说的正是这一点,dialogs_render_in_every_language_and_keep_their_source_ids覆盖了 10 个对话框。
命名占位符由format单遍扫描实现,替换进去的值不会再次被当作模板解析,因此用户名里含{n}也不会被误替换(crates/ui-egui/src/i18n.rs#L307-L331)。
字体、字形与脚本回退
翻译不新增字体资产,全部依赖 egui 内置字体 + 已安装字体 + 可选构建输入。can_show决定某语言能否真正显示(crates/ui-egui/src/theme.rs#L655-L668):
pub fn can_show(language: &str) -> bool { match language { "ko" => shows_hangul("ko"), "ar" => arabic_font().is_some() || embedded_script("Arab"), l if l.starts_with("zh") => cjk_font().is_some() || embedded_script("Hans") || embedded_script("Hant"), _ => true, } }App::language()因此是“偏好 + 字体可用性”的合成结果:拿不到字体就显示英文,而不是画一堆豆腐块(crates/ui-egui/src/lib.rs#L350-L356)。
| 语言 | 字形来源 | 缺失时行为 |
|---|---|---|
| 乌克兰语 | 内置 egui 字体覆盖Ґґ Єє Іі Її | 无需额外字体 |
| 德语 / 捷克语 / 匈牙利语 | 内置字体覆盖ÄÖÜ ß、ÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ(含„…“)、ÁÉÍÓÖŐÚÜŰ | 无需额外字体 |
中文zh | 已安装 CJK 字体(Noto Sans CJK、Microsoft YaHei、PingFang),或构建期嵌入的 CJK 字体 | 保持zh偏好,界面显示英文,状态栏提示安装 CJK 字体 |
韩语ko | 任一语言的 CJK 字体若已覆盖 Hangul 则直接可用;否则仅在选择韩语时加载 Hangul 面 | 保持ko偏好,界面英文,语言选择器显示可读标签Korean |
阿拉伯语ar | 已安装阿拉伯字体,或嵌入的 Arab 字体 | 未覆盖时回落英文 |
韩语的字体查找顺序(文档明确列出):CRAFT_FONTS_DIR输入的Hang/Kore面 → 系统字体(Windows:Malgun Gothic/Gulim;macOS:Apple SD Gothic Neo/AppleGothic;Linux/FreeBSD:Nanum Gothic 或 Noto CJK)。集合字体(.ttc)逐个 face 检查,文件读取与 face 扫描都有上限(MAX_UI_FONT_BYTES、read_capped);不重分发任何系统字体或新字体资产。
Web 构建不能读取系统字体,也不内嵌韩语字体面(受托管体积限制),因此网页端选韩语时界面保持英文;这条限制在文档中被显式写出,测试korean_without_a_hangul_font_stays_english_and_says_why断言shown == "en"且状态栏文案含No font covers the Korean interface。阿拉伯语在i18n.rs的注释里说明:字形由已安装字体提供,但外壳保持 egui 的从左到右 run 顺序(crates/ui-egui/src/i18n.rs#L227-L232)。
字体的安装是“帧级关注点”:draw中检测到显示语言变化才调用theme::install_fonts,并把fonts_language记账(crates/ui-egui/src/lib.rs#L766-L775)。同一处还会ctx.set_language(...),让 AccessKit 根节点带上界面实际语言。
复数规则与计数模板
计数不用{}拼接,而由@plural行 + 语言级规则决定。count()的实现(crates/ui-egui/src/i18n.rs#L379-L385):
pub fn count(n: u64, one: &str, other: &str) -> String { let language = CURRENT.get(); let source = catalog(language).and_then(|c| c.get(&("@plural", one))).copied(); let plural = LANGUAGES.iter().find(|l| l.code == language).map_or(plural_en as fn(u64) -> usize, |l| l.plural); let form = source.and_then(|s| s.split('|').nth(plural(n))).unwrap_or(if n == 1 { one } else { other }); format(form, &[("n", n.to_string())]) }调用点例如贡献者统计(crates/ui-egui/src/credits.rs#L249-L250)。目录里的实际行:
@plural {n} commit {n} commit|{n} commity|{n} commitů @plural {n} contributor {n} přispěvatel|{n} přispěvatelé|{n} přispěvatelů| 规则函数 | 形式数 | 判定 |
|---|---|---|
plural_en | 2 | n != 1→ other(英、德、西、意、瑞、希腊、中文复用) |
plural_uk | 3 | (1, 11) 与 (2–4, 12–14) 特殊处理;1/21 → one,2–4 → few,其余 many(俄语复用) |
plural_cs | 3 | 1 → one;2–4 → few;其余(含 0、11、21、22)→ other |
plural_pt | 2 | n > 1→ other;0 与 1 都是 one(法语复用,符合 CLDRfr) |
plural_ko | 1 | 始终第 0 形式(韩、匈、土复用:数词后名词保持单数) |
plural_arabic | 6 | zero / one / two / few(3–10, 103–110) / many(11–99) / other |
API 接收u64,不接受小数计数。测试plural_rules_pick_the_cldr_form用0, 1, 2, 4, 5, 11, 12, 14, 21, 22, 25, 101, 111, 114, u64::MAX等边界值逐一校验(crates/ui-egui/src/i18n/tests.rs#L267-L280)。
新增一门语言:完整操作清单
TSV 目录位于 crates/ui-egui/src/i18n/,UTF-8、三列制表符分隔:context、英文源、译文。普通标签的 context 为空。
Open Otevřít menu View Zobrazení @plural {n} commit {n} commit|{n} commity|{n} commitů按文档与实现,步骤是:
- 写目录文件
crates/ui-egui/src/i18n/<code>.tsv。注释行以#开头;解析器只认三种 context:空、menu、@plural,其它 context 会被测试拒绝。 - 注册目录:在
LANGUAGES数组加一行(crates/ui-egui/src/i18n.rs#L190-L237),填写code、name(该语言的自称)、source: include_str!(...)、plural函数与plural_forms数量。目录在首次使用时由OnceLock+parse懒加载一次。 - 加偏好变体:在
Preference枚举中加变体与serde(rename)(crates/ui-egui/src/i18n.rs#L11-L48),同步更新ALL、code()、name()、resolve()三处 match——Rust 编译器会强制覆盖。 - 标出 UI 字面量:静态文案用
crate::tl!("English label"),已知的动态 UI 标签用i18n::t(label),带命名占位符用crate::tf!("Version {version}", version = value)。只在显示边界翻译,不要对用户名或标识符做查表。 - 文案归属:在 ATTRIBUTION.md 增加一行;文档同时要求更新语言列表与复数规则。
- 跑门禁:
cargo test -p cadcraft-ui-egui i18n cargo xtask i18n cargo xtask assets cargo xtask ci其中cargo xtask i18n是 CI 的一个步骤,内部执行被#[ignore]的i18n::tests::translation_gaps报告任务(xtask/src/main.rs#L172-L184):
cargo test --workspace --lib -q -- --ignored --exact i18n::tests::translation_gaps --nocapture它把每种语言“仍显示英文”的字符串打印出来。缺翻译不算失败,报告失败也只打印一条 note,不阻断流水线。
目录解析的容错与测试约束
parse会跳过空行与#注释,把非法行收集为错误而不是 panic(crates/ui-egui/src/i18n.rs#L262-L283):
match columns.as_slice() { [context, key, value] if !key.is_empty() && !value.is_empty() => { if entries.insert((*context, *key), *value).is_some() { errors.push(format!("line {}: duplicate key {key}", i + 1)); } } _ => errors.push(format!("line {}: expected context, source, translation", i + 1)), }运行期忽略坏行、测试期报错,这种“双轨”设计保证了应用永不因翻译数据崩溃。测试catalog_is_valid_and_preserves_placeholders_and_ellipses对每种语言校验(crates/ui-egui/src/i18n/tests.rs#L18-L45):
- 行格式与 context 合法性;
@plural行的形式数必须等于该Language::plural_forms;- 占位符集合与英文源完全一致(
{n}不能写成{m}); - 末尾省略号必须同形(源是
...译文也得是...,源是…译文也得是…); - 未收录键返回原文;
- 一份故意写坏的三行目录能产生 3 条错误但仍解析出 1 条有效记录。
渲染级测试
除了数据校验,还有一组真正跑 egui 渲染的测试:
| 测试 | 验证内容 | 位置 |
|---|---|---|
live_switch_translates_the_shell_without_mutating_drawing_or_automation | 逐语言切换后:Properties/File被翻译、图纸标题不变、命令行 prompt 不变、all_commands不变、菜单树不变、ui.language非法值报错、ui.resetpalettes不重置语言、ui.set同样生效 | crates/ui-egui/src/i18n/tests.rs#L357-L405 |
dialogs_render_in_every_language_and_keep_their_source_ids | 10 个对话框标题在各语言下可渲染,且app.ui.dialog的 ID 不变 | crates/ui-egui/src/i18n/tests.rs#L407-L425 |
interface_fonts_cover_every_letter_in_each_catalog | 把catalog第 3 列所有非 ASCII 字母收集起来,在 Proportional 与 Monospace 两个家族里查字符表,要求零缺失 | crates/ui-egui/src/i18n/tests.rs#L437-L478 |
chinese_needs_a_cjk_font/korean_without_a_hangul_font_stays_english_and_says_why | 缺字体时shown == "en",偏好保留,状态栏给出原因 | crates/ui-egui/src/i18n/tests.rs#L479-L505 |
korean_ui_fonts_cover_the_language_picker_in_both_families | 韩语选择器标签한국어在两个字体家族都可见,否则英文界面显示Korean | crates/ui-egui/src/i18n/tests.rs#L507-L535 |
字体覆盖测试里有一段值得注意的注释:在 egui 0.36 中,has_glyph对与替换字符同面的字形可能误报缺失,因此测试改用view.characters(family)的真实字符表判断。这是排查“文字显示为方块却查不到原因”时的重要线索。
排障速查
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 选了韩语仍是英文 | 无任何字体覆盖目录中全部 Hangul;Web 构建固定如此 | 安装 Malgun Gothic / Nanum Gothic / Noto CJK,或用CRAFT_FONTS_DIR提供Hang/Kore面;控制通道返回的shown会说明 |
| 选了中文仍是英文 | 无 CJK 字体且构建未嵌入 | 安装 Noto Sans CJK 等;状态栏提示No Chinese font is installed |
CADCRAFT_LOCALE不生效 | 存在已保存的显式语言偏好 | 先切回 Auto,或删除/改写cadcraft.prefs中的interfaceLanguage |
| 菜单与对象文字不一致 | 该语言提供了menu行,菜单标题与对象标签本就有别 | 属预期,例如捷克语 Kótování / Kóta |
| 自动化返回英文标签 | 控制通道响应、命令历史、菜单树为英文契约的一部分 | 属预期,按 ID 而非标签做断言 |
| 某个新字符串显示英文 | 未收录,或标签没包tl!/t | 跑cargo xtask i18n看缺口清单 |
译文里出现{x}原样输出 | 占位符名与英文源不一致 | 修正 TSV,测试会因占位符集合不等而失败 |
小结
CADCraft 的本地化是一条清晰的单向管线:英文源码字符串 → 显示时目录查找 → 帧级语言作用域 → 字体可用性裁决 → 实际显示语言。偏好只记录“用户想要什么”,shown才记录“这帧实际显示什么”,两者分离使得缺字体时既不丢用户选择、也不至于画出乱码。目录数据的所有约束(占位符、省略号、@plural形式数)都由测试守住,运行期只做容错跳过;而ui.language与ui.set让界面点击、脚本与 MCP 自动化共用同一条路径。若要继续深入,可以从 crates/ui-egui/src/i18n.rs、crates/ui-egui/src/theme.rs 与 crates/ui-egui/src/i18n/tests.rs 三处接着读,配合 docs/localization-parity.md 了解与目标应用的差距清单。
【免费下载链接】cadcraft
CADCraft: computer-aided design and drafting — an open-source, clean-room AutoCAD-style app in pure Rust
相关推荐
Mobox多语言支持:10种界面语言切换与本地化配置
Mobox多语言支持:10种界面语言切换与本地化配置 Mobox作为一款旨在通过Box64和Wine在Termux中运行Windows x86应用程序的工具,提
虚拟化Yaak多语言切换终极指南:实时界面本地化实战
Yaak多语言切换终极指南:实时界面本地化实战 Yaak是一款功能强大的桌面API客户端,支持REST、GraphQL、WebSockets、Server Se
开发工具接口测试Mobox多语言界面配置:10种语言轻松切换与本地化优化指南
Mobox是一款功能强大的跨平台工具,通过Box64和Wine在移动设备上运行Windows应用程序。该项目提供了完善的多语言支持系统,让全球用户都能使用熟悉的
虚拟化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考