
【免费下载链接】cadcraftCADCraft: computer-aided design and drafting — an open-source, clean-room AutoCAD-style app in pure Rust项目地址https://gitcode.com/gh_mirrors/ca/cadcraft点击查看免费下载CADCraft 把界面文案与绘图数据彻底解耦英文源码字符串始终是查找键翻译只在“显示边界”发生因此切换语言既不会改动图纸内容也不会改变命令 ID 与自动化协议。本文以 docs/localization.md 为主线结合 crates/ui-egui/src/i18n.rs 的真实实现讲清语言选择、Auto 解析、CADCRAFT_LOCALE覆盖、控制通道调用、偏好持久化、字体与字形回退以及如何新增一门语言目录。语言清单与入口菜单路径固定为Edit → Interface Language下拉框列出以下 17 个偏好值外加一个 “auto” 语义项显示名偏好代码说明Auto (system language)auto跟随操作系统或浏览器语言Englishen源语言缺翻译时的兜底Ελληνικάel希腊语Українськаuk乌克兰语Português (Brasil)pt-BR巴西葡萄牙语Deutschde德语Españoles西班牙语Češtinacs捷克语Русскийru俄语Françaisfr法语简体中文zh中文需要 CJK 字体Italianoit意大利语한국어 / Koreanko韩语无 Hangul 字体时显示英文名Svenskasv瑞典语العربيةar阿拉伯语从右到左书写Magyarhu匈牙利语Türkçetr土耳其语这些取值是“偏好”而不是“实际显示语言”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的排序键是turkcecrates/ui-egui/src/i18n.rs#L425-L441。对话框实现非常薄选中项直接调用 UI 命令ui.languagecrates/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#L317Webapps/cadcraft-web/src/main.rs#L155-L156解析函数只有寥寥数行但覆盖了 BCP 47、POSIX 与.UTF-8/euro修饰符crates/ui-egui/src/i18n.rs#L239-L244pub fn locale_language(tag: str) - Optionstatic 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_UAeuro、 uk-UA uk取主语言并按大小写不敏感比较de_AT.UTF-8、DE-atde同上es-MX、ES-ares同上pt_BR.UTF-8、pt-PT、PT-brpt-BR任意pt-*都落到唯一的葡萄牙语目录cs_CZ.UTF-8、cs_CZeurocs同上it_CH.UTF-8、ko_KR.UTF-8、sv_FI、hu_HU.UTF-8、tr-CYit/ko/sv/hu/tr只看主语言el-GR、EL-cyel同上zh-CNzh任意zh-*ua、ukulele、cz、csharp、kr、se、swenska、magyar、elvish无匹配 →en不做模糊/近邻猜测注意最后一行的细节se是北萨米语、kr不是韩语、cz不是捷克语实现刻意不做“看起来像”的匹配避免把用户带到错误的语言上。Auto.resolve()的语义是“列表里第一个能解析的”例如[pl-PL, pt-BR]→pt-BR列表为空则回落到encrates/ui-egui/src/i18n.rs#L156-L176。启动期覆盖CADCRAFT_LOCALE若只想临时以某语言启动桌面版CADCRAFT_LOCALEcs-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_languagecrates/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 偏好中回显interfaceLanguagecrates/ui-egui/src/options.rs#L210-L222可用于自动化脚本确认最终状态。非法值同样被拒绝错误格式为interfaceLanguage must be codes。另外ui.resetpalettesWindow → Reset Palettescrates/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-L323Web 端使用浏览器 local storage内容形如{theme:..., animations:..., interfaceLanguage:auto, saveFormat:..., saveVersion:...}由prefs_json()序列化crates/ui-egui/src/lib.rs#L427-L447。容错策略值得单独说明load_prefs对 JSON 解析失败、字段缺失、值未知都只是“忽略”不会崩Preference::restore在保存值无法解析时回落到Autocrates/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行复数形式数备注捷克语cs1,20243四个菜单标题与对象标签区分Zobrazení / Pohled、Kreslení / Kreslit、Kótování / Kóta、Modifikace / Upravit韩语ko1,23541原创 AI 辅助韩语 CAD 术语도면층、치수、모깎기、모따기、간격띄우기、객체 스냅巴西葡萄牙语pt-BR1,23102复数规则one|other0 和 1 都算one缺口为零匈牙利语hu1,32641仅Méretezés / Méret一词在菜单标题与对象标签间不同土耳其语tr1,33041Ölçülendirme菜单/Ölçü对象无可回落字面量希腊语el1,330421,324 条普通标签 4 条菜单标题 2 条整数计数模板乌克兰语uk—03参考目录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用线程局部Cellstatic str在一帧渲染内设置当前语言并用Drop守卫还原crates/ui-egui/src/i18n.rs#L246-L260pub fn with_languageR(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-L668pub 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面 → 系统字体WindowsMalgun Gothic/GulimmacOSApple SD Gothic Neo/AppleGothicLinux/FreeBSDNanum 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-L385pub 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_en2n ! 1→ other英、德、西、意、瑞、希腊、中文复用plural_uk3(1, 11) 与 (2–4, 12–14) 特殊处理1/21 → one2–4 → few其余 many俄语复用plural_cs31 → one2–4 → few其余含 0、11、21、22→ otherplural_pt2n 1→ other0 与 1 都是 one法语复用符合 CLDRfrplural_ko1始终第 0 形式韩、匈、土复用数词后名词保持单数plural_arabic6zero / one / two / few(3–10, 103–110) / many(11–99) / otherAPI 接收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数量。目录在首次使用时由OnceLockparse懒加载一次。加偏好变体在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-L184cargo test --workspace --lib -q -- --ignored --exact i18n::tests::translation_gaps --nocapture它把每种语言“仍显示英文”的字符串打印出来。缺翻译不算失败报告失败也只打印一条 note不阻断流水线。目录解析的容错与测试约束parse会跳过空行与#注释把非法行收集为错误而不是 paniccrates/ui-egui/src/i18n.rs#L262-L283match 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-L405dialogs_render_in_every_language_and_keep_their_source_ids10 个对话框标题在各语言下可渲染且app.ui.dialog的 ID 不变crates/ui-egui/src/i18n/tests.rs#L407-L425interface_fonts_cover_every_letter_in_each_catalog把catalog第 3 列所有非 ASCII 字母收集起来在 Proportional 与 Monospace 两个家族里查字符表要求零缺失crates/ui-egui/src/i18n/tests.rs#L437-L478chinese_needs_a_cjk_font/korean_without_a_hangul_font_stays_english_and_says_why缺字体时shown en偏好保留状态栏给出原因crates/ui-egui/src/i18n/tests.rs#L479-L505korean_ui_fonts_cover_the_language_picker_in_both_families韩语选择器标签한국어在两个字体家族都可见否则英文界面显示Koreancrates/ui-egui/src/i18n/tests.rs#L507-L535字体覆盖测试里有一段值得注意的注释在 egui 0.36 中has_glyph对与替换字符同面的字形可能误报缺失因此测试改用view.characters(family)的真实字符表判断。这是排查“文字显示为方块却查不到原因”时的重要线索。排障速查现象可能原因处理选了韩语仍是英文无任何字体覆盖目录中全部 HangulWeb 构建固定如此安装 Malgun Gothic / Nanum Gothic / Noto CJK或用CRAFT_FONTS_DIR提供Hang/Kore面控制通道返回的shown会说明选了中文仍是英文无 CJK 字体且构建未嵌入安装 Noto Sans CJK 等状态栏提示No Chinese font is installedCADCRAFT_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 了解与目标应用的差距清单。赞分享【免费下载链接】cadcraftCADCraft: computer-aided design and drafting — an open-source, clean-room AutoCAD-style app in pure Rust项目地址https://gitcode.com/gh_mirrors/ca/cadcraft点击查看免费下载相关推荐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应用程序。该项目提供了完善的多语言支持系统让全球用户都能使用熟悉的虚拟化上一篇解放双手CopyQ语音控制剪贴板全攻略用声音管理你的复制内容下一篇paraphrase-multilingual-MiniLM-L12-v2模型安全加固输入过滤与输出验证策略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考