From 4bb3acb913e1740526179a1eaa701085aa9210fc Mon Sep 17 00:00:00 2001 From: mayloglog <100988988+mayloglog@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:28:17 +0800 Subject: [PATCH] Add Chinese language support to preferences (#684) Squash of PR #684 by @mayloglog. Co-Authored-By: Claude Opus 5.5 --- ATTRIBUTION.md | 1 + README.md | 2 +- crates/ui-egui/src/i18n/mod.rs | 13 +- crates/ui-egui/src/i18n/tests.rs | 19 +- crates/ui-egui/src/i18n/zh.tsv | 1014 +++++++++++++++++++++++++++++ crates/ui-egui/src/lib.rs | 17 +- crates/ui-egui/src/menus.rs | 8 +- crates/ui-egui/src/options/mod.rs | 2 +- crates/ui-egui/src/theme.rs | 64 +- docs/control-protocol.md | 2 +- 10 files changed, 1127 insertions(+), 15 deletions(-) create mode 100644 crates/ui-egui/src/i18n/zh.tsv diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 0ab9905..4a1b7c1 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -63,3 +63,4 @@ Generated-in-code assets are original and have no file to list: | `crates/ui-egui/src/i18n/cs.tsv` | @Losminek (AI-assisted) | Original Czech translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied | | `crates/ui-egui/src/i18n/ru.tsv` | @pechnikov2026-star | Original Russian translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied | | `crates/ui-egui/src/i18n/fr.tsv` | @pechnikov2026-star | Original French translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied | +| `crates/ui-egui/src/i18n/zh.tsv` | @mayloglog | Original Simplified Chinese translations of CADCraft English strings | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | No proprietary localization resources copied | diff --git a/README.md b/README.md index 00ab7ad..3c5720b 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ numbers): | Layouts & plotting | Paper-space layouts, viewports (scale, lock, per-viewport layer freeze), MSPACE/PSPACE through viewports, page setups, PLOT and EXPORTPDF | | Parametric | Geometric and dimensional constraints, AUTOCONSTRAIN, PARAMETERS with expressions, conflict detection; constraints re-solve after every edit | | Grips | Hot grips with Space to cycle Stretch, Move, Rotate, Scale and Mirror | -| Interface languages | English, Ukrainian (Українська), Brazilian Portuguese (Português (Brasil)), German (Deutsch), Spanish (Español), Czech (Čeština), Russian (Русский) and French (Français), live switching and a remembered Auto/system-language choice; [details](docs/localization.md) | +| Interface languages | English, Ukrainian (Українська), Brazilian Portuguese (Português (Brasil)), German (Deutsch), Spanish (Español), Czech (Čeština), Russian (Русский), French (Français) and Simplified Chinese (简体中文, with an installed CJK font), live switching and a remembered Auto/system-language choice; [details](docs/localization.md) | | Automation | MCP server, JSON control channel, `cadcraft-cli` (info, convert, run, commands, mcp) | ## Quick start diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index 9f8da8f..9590990 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -29,10 +29,12 @@ pub enum Preference { Russian, #[serde(rename = "fr")] French, + #[serde(rename = "zh")] + Chinese, } impl Preference { - pub const ALL: [Self; 9] = [ + pub const ALL: [Self; 10] = [ Self::Auto, Self::English, Self::Ukrainian, @@ -42,6 +44,7 @@ impl Preference { Self::Czech, Self::Russian, Self::French, + Self::Chinese, ]; /// The codes a preference can be given as, for messages ("auto, en or uk"). @@ -64,6 +67,7 @@ impl Preference { Self::Czech => "cs", Self::Russian => "ru", Self::French => "fr", + Self::Chinese => "zh", } } @@ -78,6 +82,7 @@ impl Preference { Self::Czech => "Čeština", Self::Russian => "Русский", Self::French => "Français", + Self::Chinese => "简体中文", } } @@ -100,6 +105,7 @@ impl Preference { Self::Czech => "cs", Self::Russian => "ru", Self::French => "fr", + Self::Chinese => "zh", Self::Auto => locales.iter().find_map(|s| locale_language(s)).unwrap_or("en"), } } @@ -116,7 +122,7 @@ pub struct Language { catalog: OnceLock>, } -pub static LANGUAGES: [Language; 8] = [ +pub static LANGUAGES: [Language; 9] = [ Language { code: "en", name: "English", source: "", plural: plural_en, plural_forms: 2, catalog: OnceLock::new() }, Language { code: "uk", name: "Українська", source: include_str!("uk.tsv"), plural: plural_uk, plural_forms: 3, catalog: OnceLock::new() @@ -138,6 +144,9 @@ pub static LANGUAGES: [Language; 8] = [ Language { code: "ru", name: "Русский", source: include_str!("ru.tsv"), plural: plural_uk, plural_forms: 3, catalog: OnceLock::new() }, // French integer plurals (CLDR `fr`): one (0, 1), other, the Portuguese rule. Language { code: "fr", name: "Français", source: include_str!("fr.tsv"), plural: plural_pt, plural_forms: 2, catalog: OnceLock::new() }, + // Chinese has no grammatical plural: both `@plural` forms read the same. Its glyphs come from + // an installed CJK font (`theme::cjk_font`); without one the interface stays English. + Language { code: "zh", name: "简体中文", source: include_str!("zh.tsv"), plural: plural_en, plural_forms: 2, catalog: OnceLock::new() }, ]; /// BCP 47 and POSIX locale tags, including `uk-UA`, `uk_UA.UTF-8` and `pt_BR.UTF-8`. diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index a187758..6bd9804 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -39,7 +39,7 @@ fn catalog_is_valid_and_preserves_placeholders_and_ellipses() { /// to English for what they don't cover yet; their gaps are reported, not enforced, so adding a /// command doesn't require a row in every language. const REQUIRED: &[&str] = &["uk"]; -const REPORTED: &[&str] = &["pt-BR", "de", "es", "cs", "ru", "fr"]; +const REPORTED: &[&str] = &["pt-BR", "de", "es", "cs", "ru", "fr", "zh"]; fn check(language: &str, what: &str, missing: Vec) { if REQUIRED.contains(&language) { @@ -524,3 +524,20 @@ fn russian_and_french_plurals() { } }); } + +/// Chinese shows only with an installed CJK font (none is bundled); without one the interface +/// stays English instead of drawing boxes, and choosing it says why. +#[test] +fn chinese_needs_an_installed_cjk_font() { + assert_eq!(locale_language("zh-CN"), Some("zh")); + let mut app = CadApp::new(Session::new(), Services::default()); + let r = app.run("ui.language", json!({"lang": "zh"})).unwrap(); + assert_eq!(r["interfaceLanguage"], "zh"); + let font = crate::theme::cjk_font(); + assert_eq!(app.language(), if font.is_some() { "zh" } else { "en" }); + assert_eq!(r["shown"], app.language()); + assert!(crate::theme::can_show("uk") && crate::theme::can_show("en")); + if font.is_none() { + assert!(app.status.as_ref().is_some_and(|(s, _)| s.contains("No Chinese font"))); + } +} diff --git a/crates/ui-egui/src/i18n/zh.tsv b/crates/ui-egui/src/i18n/zh.tsv new file mode 100644 index 0000000..2fd92e5 --- /dev/null +++ b/crates/ui-egui/src/i18n/zh.tsv @@ -0,0 +1,1014 @@ +# CADCraft Simplified Chinese interface catalog — original translations. +# License: MIT OR Apache-2.0 (the repository LICENSE-MIT and LICENSE-APACHE). +# Written from CADCraft's English strings; no proprietary translations were copied. +# UTF-8: contextEnglish sourceChinese translation. +# Empty context = ordinary label; @plural = one|other forms with {n}. +# Command IDs, typed keywords, technology/product names and user data stay unchanged. + Cancel 取消 + OK 确定 + or [ 或 [ + % of all commits % 的所有提交 + * Wildcard Match * 通配符匹配 + *VARIES* *多值* + + | Top | 2D Wireframe + | 俯视 | 二维线框 + +Bin +Bin + +LOC +LOC + 1 Viewport 1 个视口 + 2 Points 2 点 + 2 Viewports 2 个视口 + 2D Solid 二维实体 + 3 Points 3 点 + 3 Viewports 3 个视口 + 3D Face 三维面 + 3D Operations 三维操作 + 3D Polyline 三维多段线 + 4 Viewports 4 个视口 + < Less than < 小于 + <> Not Equal <> 不等于 + = Equals = 等于 + > Greater than > 大于 + About 关于 + About CADCraft 关于 CADCraft + Absolute 绝对 + Add Leader 添加引线 + Align 对齐 + Align Text 对齐文字 + Aligned 对齐 + All 全部 + All Used Layers 所有已使用的图层 + All files 所有文件 + Angle 角度 + Angular 角度 + Annotation Visibility 注释可见性 + Annotation scale 注释比例 + Apparent Intersection 外观交点 + Append to current selection set 添加到当前选择集 + Apply to: 应用于: + Apply true color 应用真彩色 + Arc 圆弧 + Arc Length 弧长 + Area 面积 + Arrange Panel 排列面板 + Array 阵列 + Attach 附着 + Attribute 属性 + Attribute Definition 属性定义 + Attribute Display 属性显示 + Audit 检查 + Auto (system language) 自动(系统语言) + Auto Constrain 自动约束 + AutoConstrain 自动约束 + Axis, End 轴端点 + Base 基准 + Baseline 基线 + Batch Publish 批量发布 + Binary assets added 已添加二进制资产 + Binary assets removed 已移除二进制资产 + Blend 过渡 + Block 块 + Block Attribute Manager... 块属性管理器... + Block Editor 块编辑器 + Block Reference 块参照 + Block... 块... + Blocks 块 + Blue 蓝色 + Bold 粗体 + Boolean 布尔运算 + Boundary 边界 + Boundary... 边界... + Box 长方体 + Break 打断 + Break At Point 于点打断 + Bring to Front 置于顶层 + ByBlock ByBlock + ByColor ByColor + ByLayer ByLayer + CADCraft Help CADCraft 帮助 + Cancel 取消 + Cartesian (x, y) 笛卡尔坐标 (x, y) + Center 中心 + Center Line 中心线 + Center Mark 中心标记 + Center X 中心 X + Center Y 中心 Y + Center Z 中心 Z + Center, Diameter 中心点、直径 + Center, Radius 中心点、半径 + Center, Start, Angle 中心点、起点、角度 + Center, Start, End 中心点、起点、端点 + Center, Start, Length 中心点、起点、长度 + Chamfer 倒角 + Change Space 修改空间 + Change to Current Layer 改至当前图层 + Circle 圆 + Circumference 圆周 + Clear 清除 + Clipboard 剪贴板 + Close 关闭 + Close All 全部关闭 + Closed 已关闭 + Coincident 重合 + Collapse Palettes 折叠选项板 + Collapse Tool Sets 折叠工具集 + Collinear 共线 + Color 颜色 + Color 8 颜色 8 + Color 9 颜色 9 + Color Index 颜色索引 + Color... 颜色... + Command History 命令行历史 + Command Line 命令行 + Command Line History Lines 命令行历史行数 + Command Reference 命令参考 + Commits 提交 + Company 公司 + Computer-aided design and drafting: an open-source, clean-room CAD application written in pure Rust. 计算机辅助设计与绘图:一个用纯 Rust 编写的开源、自主实现的 CAD 应用程序。 + Concentric 同心 + Cone 圆锥体 + Constraint Bars 约束栏 + Constraint Settings 约束设置 + Construction Line 构造线 + Contents 内容 + Continue 继续 + Contributors 贡献者 + Control Vertices 控制点 + Copy 复制 + Copy Layout 复制布局 + Copy Nested Objects 复制嵌套对象 + Copy Selection 复制所选对象 + Copy with Base Point 带基点复制 + Count 计数 + Create Block 创建块 + Creates a user parameter 创建一个用户参数 + Ctrl+Enter to finish Ctrl+Enter 完成 + Current layer 当前图层 + Current selection 当前选择 + Current: {color} 当前: {color} + Cut 剪切 + Cyan 青色 + Cylinder 圆柱体 + DWG DWG + DXF DXF + Dark 深色 + Default 默认 + Default Save Format 默认保存格式 + Define Attributes 定义属性 + Define Attributes... 定义属性... + Defined width 指定宽度 + Degree 度 + Delete 删除 + Delete Column 删除列 + Delete Constraints 删除约束 + Delete Dimension Style 删除标注样式 + Delete Duplicate Objects 删除重复对象 + Delete Layer 删除图层 + Delete Layer State 删除图层状态 + Delete Layout 删除布局 + Delete Row 删除行 + Delete Text Style 删除文字样式 + Delta X 增量 X + Delta Y 增量 Y + Description 描述 + Deselect All 取消全选 + Diameter 直径 + Dim style 标注样式 + Dimension 标注 + Dimension Space 标注间距 + Dimension Style Override 标注样式替代 + Dimension Style... 标注样式... + Dimension Text Edit 编辑标注文字 + Dimension style 标注样式 + Dimensional Constraint 尺寸约束 + Dimensional Constraint Parameters 尺寸约束参数 + Dimensional Constraints 尺寸约束 + Disassociate Dimensions 取消标注关联 + Display name 显示名称 + Distance 距离 + Divide 定数等分 + Do not plot (click: plot) 不打印(点击:打印) + Donut 圆环 + Double-click to edit the description 双击编辑描述 + Down 向下 + Drafting 绘图 + Drafting Settings 草图设置 + Drafting Settings... 草图设置... + Draw 绘图 + Draw Order 绘图次序 + Drawing (DWG) 图形 (DWG) + Drawing (DXF) 图形 (DXF) + Drawing Limits 图形界限 + Drawing Utilities 图形实用工具 + Drawings (DWG, DXF) 图形 (DWG, DXF) + Dynamic Dimensions 动态标注 + Dynamic Input 动态输入 + Dynamic Input (F12) 动态输入 (F12) + E E + Edit 编辑 + Edit Text... 编辑文字... + Elevation 标高 + Ellipse 椭圆 + Elliptical Arc 椭圆弧 + Enable Dynamic Input (F12) 启用动态输入 (F12) + End Width 端点宽度 + End X 终点 X + End Y 终点 Y + End angle 端点角度 + Endpoint 端点 + Enter Top, Zero or Bottom. 输入 Top、Zero 或 Bottom。 + Enter a fit data option 输入拟合数据选项 + Enter a pattern name 输入图案名称 + Enter a vertex editing option 输入顶点编辑选项 + Enter an option 输入选项 + Enter angle of xline 输入构造线角度 + Enter angle of xline (0) or 输入构造线角度 (0) 或 + Enter area of rectangle in current units 在当前单位中输入矩形面积 + Enter attribute prompt 输入属性提示 + Enter attribute tag name 输入属性标记名称 + Enter attribute value 输入属性值 + Enter block name 输入块名 + Enter constraint type 输入约束类型 + Enter continuity 输入连续性 + Enter default attribute value 输入默认属性值 + Enter degree of spline 输入样条曲线次数 + Enter delta angle 输入增量角度 + Enter delta length or 输入增量长度或 + Enter dimension text 输入标注文字 + Enter dimension text (<> = measurement) 输入标注文字 (<> = 测量值) + Enter dimension value or [name=expression] 输入标注值或 [名称=表达式] + Enter dimension variable name to override 输入要替代的标注变量名 + Enter justification type 输入对齐类型 + Enter leader text 输入引线文字 + Enter mline scale 输入多线比例 + Enter mode 输入模式 + Enter name of block to insert 输入要插入的块名 + Enter new text 输入新文字 + Enter next point 指定下一点 + Enter number of columns 输入列数 + Enter number of data rows 输入数据行数 + Enter number of items 输入项目数 + Enter number of items along path 输入路径上的项目数 + Enter number of sides 输入边数 + Enter number of turns 输入圈数 + Enter percentage length 输入百分比长度 + Enter rectangle length 输入矩形长度 + Enter rectangle width 输入矩形宽度 + Enter scale factor 输入比例因子 + Enter text 输入文字 + Enter text (use \P for new paragraphs) 输入文字(使用 \P 创建新段落) + Enter the number of segments 输入段数 + Enter twist direction of helix 输入螺旋的扭曲方向 + Entire drawing 整个图形 + Equal 相等 + Erase 擦除 + Exclude from new selection set 从新选择集中排除 + Explode 分解 + Export 导出 + Export to PDF... 导出为 PDF... + Expression 表达式 + Extend 延伸 + Extension 延伸线 + Extents 范围 + Extrude 拉伸 + File 文件 + File Tab 文件选项卡 + Fillet 圆角 + Filter 筛选器 + Filters 筛选器 + Find and Replace 查找和替换 + Find... 查找... + First 第一 + First commit 首次提交 + Fit Points 拟合点 + Fix 固定 + Flatten Objects 展平对象 + Format 格式 + Freehand 徒手画 + Freeze 冻结 + Freeze Layer 冻结图层 + Freeze in new viewports 在新视口中冻结 + Freeze/Thaw 冻结/解冻 + Freeze/Thaw in all viewports 在所有视口中冻结/解冻 + Freeze/Thaw in the current viewport 在当前视口中冻结/解冻 + From 自 + General 常规 + Geometric Center 几何中心 + Geometric Constraint 几何约束 + Geometric Constraints 几何约束 + Geometry 几何图形 + Get Variable 获取变量 + Get View 获取视图 + Global 全局 + Global width 全局宽度 + Grab bag 杂项 + Gradient 渐变色 + Gradient... 渐变色... + Green 绿色 + Grid (F7) 栅格 (F7) + Grid Display 栅格显示 + Grid On (F7) 开启栅格 (F7) + Grid spacing 栅格间距 + Grip Mirror 夹点镜像 + Grip Rotate 夹点旋转 + Grip Scale 夹点缩放 + Grip Stretch 夹点拉伸 + Group 编组 + Group Edit 编组编辑 + Hatch 图案填充 + Hatch Edit 编辑图案填充 + Hatch... 图案填充... + Height 高度 + Height: {height} 高度: {height} + Helix 螺旋 + Help 帮助 + Hide All 全部隐藏 + Hide All Constraint Bars 隐藏所有约束栏 + Hide All Dynamic Dimensions 隐藏所有动态标注 + Hide Layer List 隐藏图层列表 + Hide Palettes 隐藏选项板 + Home 主页 + Horizontal 水平 + HorizontalDistance 水平距离 + How to apply: 应用方式: + ID Point 点坐标 + Import 导入 + In 放大 + In-window Menu Bar 窗口内菜单栏 + Include in new selection set 包含在新选择集中 + Increment angle 增量角度 + Index Color 索引颜色 + Index color: {index} 索引颜色: {index} + Inquiry 查询 + Insert 插入 + Insert Block 插入块 + Insert Column 插入列 + Insert Row 插入行 + Insert this block 插入此块 + Insertion 插入 + Insertion X 插入 X + Insertion Y 插入 Y + Insertion Z 插入 Z + Inspect Constraints 检查约束 + Inspect Drawing 检查图形 + Interface Language 界面语言 + Interface Theme 界面主题 + Intersect 相交 + Intersection 交点 + Isolate 隔离 + Isolate Layer 隔离图层 + Isometric Drafting 等轴测制图 + Join 合并 + Join the ArtCraft Discord… 加入 ArtCraft Discord… + Join us on Discord 在 Discord 上加入我们 + Language 语言 + Last 最后 + Last commit 最后提交 + Layer 图层 + Layer Delete 删除图层 + Layer Match 匹配图层 + Layer Merge 合并图层 + Layer Off 关闭图层 + Layer Properties 图层特性 + Layer Properties Manager 图层特性管理器 + Layer Properties Manager — current viewport 图层特性管理器 — 当前视口 + Layer State 图层状态 + Layer States Manager... 图层状态管理器... + Layer Tools 图层工具 + Layer contains objects (double-click to make current) 图层包含对象(双击将其置为当前) + Layer is empty (double-click to make current) 图层为空(双击将其置为当前) + Layers 图层 + Layers Palette 图层选项板 + Layers displayed: {shown} of {total}. Current layer: {layer} 显示的图层: {shown} / {total}。当前图层: {layer} + Layout 布局 + Layout list 布局列表 + Leader 引线 + Left 左 + Length 长度 + Length X 长度 X + Length Y 长度 Y + Lengthen 拉长 + Light 浅色 + Line 直线 + Line delta 直线增量 + Linear 线性 + Lines +/− 直线 +/− + Lines added 添加的行数 + Lines deleted 删除的行数 + Linetype 线型 + Linetype Scale 线型比例 + Linetype scale 线型比例 + Ltscale Ltscale + Linetype... 线型... + Lineweight 线宽 + Lineweight... 线宽... + List 列表 + List Blocks 列出块 + List Dimension Styles 列出标注样式 + List Groups 列出编组 + List Layer States 列出图层状态 + List Layouts 列出布局 + List System Variables 列出系统变量 + List Text Styles 列出文字样式 + Load all 全部加载 + Load... 加载... + Loaded linetypes 已加载的线型 + Lock 锁定 + Lock Layer 锁定图层 + Lock/Unlock 锁定/解锁 + Loft 放样 + MIT OR Apache-2.0. Not affiliated with Autodesk, Inc. MIT 或 Apache-2.0。与 Autodesk, Inc. 无关。 + MODEL 模型 + MText 多行文字 + Magenta 品红 + Major line every 主网格线间隔 + Make Current 置为当前 + Make Object's Layer Current 将对象的图层置为当前 + Make... 制作... + Match Layer 匹配图层 + Match Properties 特性匹配 + Measure 度量 + Measure Distance 测量距离 + Measure Geometry 测量几何图形 + Merge Cells 合并单元格 + Merged PRs 已合并的 PR + Mid Between 2 Points 两点之间的中点 + Midpoint 中点 + Mirror 镜像 + Model 模型 + Model Space (in viewport) 模型空间(在视口中) + Modeling 建模 + Models 模型 + Modify 修改 + Move 移动 + Multileader 多重引线 + Multileader Style... 多重引线样式... + Multileader style 多重引线样式 + Multiline 多线 + Multiline Text 多行文字 + Multiline Text... 多行文字... + Multiple 多个 + Multiple Point 多点 + My 我的 + My properties: not available yet 我的属性:暂不可用 + N N + Name 名称 + Name (A–Z) 名称 (A–Z) + Nearest 最近点 + New 新建 + New (metric) 新建(公制) + New Drawing 新图形 + New Drawing (⌘N) 新图形 (⌘N) + New Drawing... 新建图形... + New Layer 新图层 + New Layout 新布局 + New VP Freeze 新视口冻结 + New Viewports... 新视口... + New layout 新布局 + New state name 新状态名称 + No 否 + No blocks defined in this drawing. 此图形中未定义块。 + No contributor data was built into this copy. 此副本中未内置贡献者数据。 + No model credits were built into this copy. 此副本中未内置模型鸣谢数据。 + No saved layer states 没有保存的图层状态 + No selection 未选择 + Node 节点 + None 无 + Not available 不可用 + Not available yet 暂不可用 + OK 确定 + Object 对象 + Object Snap 对象捕捉 + Object Snap (F3) 对象捕捉 (F3) + Object Snap On (F3) 开启对象捕捉 (F3) + Object Snap Tracking 对象捕捉追踪 + Object Snap Tracking (F11) 对象捕捉追踪 (F11) + Object type: 对象类型: + Off 关闭 + Offset 偏移 + On 开启 + On/Off 开/关 + One of the Crafting Apps by the ArtCraft team and community. 由 ArtCraft 团队和社区打造的 Crafting 系列应用之一。 + Open (⌘O) 打开 (⌘O) + Open Sample Drawing 打开示例图形 + Open sample drawing 打开示例图形 + Open... 打开... + Operator: 运算符: + Orbit 三维动态观察 + Ordinate 坐标标注 + Ortho (F8) 正交 (F8) + Ortho Mode 正交模式 + Ortho Mode (F8) 正交模式 (F8) + Osnap Settings... 捕捉设置... + Out 缩小 + Override 替代 + PAPER 图纸 + Percent 百分比 + Polar (distance < angle) 极轴 (距离 < 角度) + PRs PR + Page Setup Manager 页面设置管理器 + Page Setup Manager... 页面设置管理器... + Pan 平移 + Pan Down 向下平移 + Pan Left 向左平移 + Pan Right 向右平移 + Pan Up 向上平移 + Paper Space 图纸空间 + Parallel 平行 + Parameters Manager 参数管理器 + Parametric 参数化 + Paste 粘贴 + Paste as Block 粘贴为块 + Paste to Original Coordinates 粘贴到原坐标 + Path Array 路径阵列 + Pattern 图案 + Perpendicular 垂直 + Plot 打印 + Plot (click: do not plot) 打印(点击:不打印) + Plot Preview 打印预览 + Plot style 打印样式 + Plot style attached to 附加到...的打印样式 + Plot style table 打印样式表 + Plot Style... 打印样式... + Plot Style Manager 打印样式管理器 + Plot Style Manager... 打印样式管理器... + Plot Styles... 打印样式... + New CTB 新 CTB + New STB 新 STB + New style 新样式 + Reset Panel Layout 重置面板布局 + Use for this layout 用于此布局 + Display plot styles 显示打印样式 + No plot style tables. 没有打印样式表。 + A built-in table: editing it keeps an edited copy in the drawing. 内置表:对其进行编辑会在图形中保留一个编辑过的副本。 + Keep an editable copy of this table in the drawing 在此图形中保留此表的编辑副本 + Layout {name}: {table} 布局 {name}: {table} + Grayscale 灰度 + Screening 网目调 + Use object linetype 使用对象线型 + Use object lineweight 使用对象线宽 + Remove 移除 + Add 添加 + Open a drawing first. 请先打开一个图形。 + Plot table type 打印样式表类型 + Color-dependent 颜色相关 + Named 命名 + Plus/minus 正/负 + Point 点 + Point Filters 点过滤器 + Point Style... 点样式... + Polar Array 环形阵列 + Polar Tracking 极轴追踪 + Polar Tracking (F10) 极轴追踪 (F10) + Polar Tracking On (F10) 开启极轴追踪 (F10) + Polar: {distance} < {angle}° 极轴: {distance} < {angle}° + Polygon 多边形 + Polygonal 多边形 + Polyline 多段线 + Position X 位置 X + Position Y 位置 Y + Position Z 位置 Z + Previous 上一个 + Previous Layer 上一图层 + Primitives 图元 + Print 打印 + Print... 打印... + Properties 特性 + Properties Inspector 特性检视器 + Properties: 特性: + Proxy 代理 + Purge 清理 + Pyramid 棱锥体 + Quadrant 象限点 + Query Entities 查询实体 + Quick 快速 + Quick Dimension 快速标注 + Quick Properties 快速特性 + Quick Select 快速选择 + Quick Select Properties 快速选择特性 + Quick Select... 快速选择... + QuickCalc 快速计算器 + Quit CADCraft 退出 CADCraft + Radius 半径 + Raster Image 光栅图像 + Ray 射线 + Read-only 只读 + Real name 真实姓名 + Realtime 实时 + Reassociate Dimensions 重新关联标注 + Recent 最近 + Recover... 修复... + Rectangle 矩形 + Rectangular 矩形 + Rectangular Array 矩形阵列 + Red 红色 + Redo 重做 + Redo (⇧⌘Z) 重做 (⇧⌘Z) + Redraw 重绘 + Regen 重生成 + Regen All 全部重生成 + Region 面域 + Region/Mass Properties 面域/质量特性 + Relative 相对 + Remove Leader 删除引线 + Remove the viewport override 移除视口替代 + Rename Dimension Style 重命名标注样式 + Rename Layer 重命名图层 + Rename Layer State 重命名图层状态 + Rename Layout 重命名布局 + Rename Text Style 重命名文字样式 + Rename... 重命名... + Repeat {command} 重复 {command} + Requires a point (x,y, @dx,dy or @d 指定第二点或 <使用第一点作为位移> + Specify second source point 指定第二个源点 + Specify side to offset 指定偏移侧 + Specify start angle 指定起始角度 + Specify start point 指定起点 + Specify start point of arc 指定圆弧起点 + Specify start point of polyline 指定多段线起点 + Specify start point of text 指定文字起点 + Specify starting width 指定起端宽度 + Specify starting width for next segment 指定下一段的起端宽度 + Specify tangent direction for the start point of arc 指定圆弧起点的切线方向 + Specify the elevation for rectangles 指定矩形标高 + Specify thickness for rectangles 指定矩形厚度 + Specify third point on circle 指定圆上的第三点 + Specify third point on circle: _tan to 指定圆上的第三点: _tan 到 + Specify third source point or 指定第三个源点或 <继续> + Specify through point 指定通过点 + Specify top diameter 指定顶面直径 + Specify top radius or Specify top radius or + Specify total angle 指定总角度 + Specify total length or Specify total length or + Specify width for rectangles 指定矩形宽度 + Sphere 球体 + Spline 样条曲线 + Stack (type 1/2 then select) 堆叠(输入 1/2 然后选择) + Standard 标准 + Start 起点 + Start Width 起点宽度 + Start X 起点 X + Start Y 起点 Y + Start angle 起点角度 + Start, Center, Angle 起点、中心点、角度 + Start, Center, End 起点、中心点、端点 + Start, Center, Length 起点、中心点、长度 + Start, End, Angle 起点、端点、角度 + Start, End, Direction 起点、端点、方向 + Start, End, Radius 起点、端点、半径 + Status 状态 + Status Bar 状态栏 + Stretch 拉伸 + Style: Standard 样式: Standard + Subtract 差集 + Sweep 扫掠 + Switch Drawing 切换图形 + Switch Layout 切换布局 + Switch between model space in a viewport and paper space 在视口中的模型空间与图纸空间之间切换 + Symmetric 对称 + Synchronize Attributes 同步属性 + Toggle PICKADD (off: each pick replaces the selection) 切换 PICKADD(关闭:每次点击替换当前选择) + Toggle PICKADD (on: picks add to the selection) 切换 PICKADD(开启:点击会添加到选择集) + TOP 俯视 + Table 表格 + Table Style... 表格样式... + Table style 表格样式 + Table... 表格... + Tan, Tan, Radius 相切、相切、半径 + Tan, Tan, Tan 相切、相切、相切 + Tangent 相切 + Text 文字 + Text Editor 文字编辑器 + Text Height 文字高度 + Text Style... 文字样式... + Text height 文字高度 + Text override 文字替代 + Text style 文字样式 + Thaw All Layers 解冻所有图层 + There is nothing here yet. 这里什么也没有。 + Thickness 厚度 + Time 时间 + To get started, create or open a drawing. 若要开始,请创建或打开一个图形。 + Tool Bar 工具栏 + Tool Sets 工具集 + Tools 工具 + Torus 圆环体 + Trace 追踪 + Transparency 透明度 + Trim 修剪 + True Color 真彩色 + Turn All Layers On 打开所有图层 + Turn Off Layer 关闭图层 + Turn Quick Properties off (QPMODE) 关闭快速特性 (QPMODE) + Type 类型 + Type a command 输入命令 + Type text; Enter starts a new paragraph 输入文字;回车开始新段落 + UCS Icon UCS 图标 + Undo 放弃 + Undo (⌘Z) 放弃 (⌘Z) + Ungroup 取消编组 + Union 并集 + Unisolate Layer 取消图层隔离 + Units... 单位... + Unlock Layer 解锁图层 + Unmerge Cells 取消合并单元格 + Unsaved Layer State 未保存的图层状态 + Up 向上 + Update 更新 + Use System Setting 使用系统设置 + User 用户 + User Parameters 用户参数 + Username 用户名 + VP Color 视口颜色 + VP Freeze 视口冻结 + Value 值 + Value: 值: + Version 版本 + Version {version} 版本 {version} + Vertical 垂直 + VerticalDistance 垂直距离 + View 视图 + ViewCube 视图立方 + Viewport 视口 + Viewport Layer Freeze 视口图层冻结 + Viewport Properties 视口特性 + Viewport {id} 视口 {id} + Viewports 视口 + W W + WCS ⌄ WCS ⌄ + White 白色 + Width factor 宽度因子 + Window 窗口 + Wipeout 区域覆盖 + Workspace Switching 工作空间切换 + Write Block 写块 + Xline 构造线 + Yellow 黄色 + Yes 是 + Zoom 缩放 + Zoom All 全部缩放 + Zoom Extents 范围缩放 + Zoom In 放大 + Zoom Object 对象缩放 + Zoom Out 缩小 + Zoom Previous 上一个缩放 + Zoom Window 窗口缩放 + error 错误 + {contributors} · {commits} {contributors} · {commits} + {kind} (constraint {id}) {kind} (约束 {id}) + {kind} constraint {name} = {expression} (double-click to edit) {kind} 约束 {name} = {expression} (双击编辑) + {name}: {distance} < {angle}° {name}: {distance} < {angle}° + ƒx New User Parameter ƒx 新建用户参数 + ΔLOC ΔLOC + −Bin −Bin + −LOC −LOC + ⌘↩ to finish ⌘↩ 完成 +@plural {n} commit {n} 次提交|{n} 次提交 +@plural {n} contributor {n} 位贡献者|{n} 位贡献者 + Drawing as bytes 字节形式的图形 + Services 服务 + Hide CADCraft 隐藏 CADCraft + Hide Others 隐藏其他人 + Accessibility 辅助功能 + Show System Cursor 显示系统光标 + 3D Mouse... 3D 鼠标... + 3D Mouse 3D 鼠标 + Calibrate 3D Mouse 校准 3D 鼠标 + 3D Mouse Navigation 3D 鼠标导航 + 3D mouse input is unavailable on this platform 此平台不可用 3D 鼠标输入 + No supported 3D mouse found 未找到支持的 3D 鼠标 + Release the 3D mouse to calibrate its resting position 释放 3D 鼠标以校准其静止位置 + 3D mouse connected 3D 鼠标已连接 + 3D mouse busy, possibly 3DxWare or missing device permissions 3D 鼠标忙,可能是因为 3DxWare 或缺少设备权限 + 3D mouse disconnected 3D 鼠标已断开连接 + 3D mouse input could not be started 无法启动 3D 鼠标输入 + Enable 3D mouse navigation 启用 3D 鼠标导航 + Pan sensitivity 平移灵敏度 + Zoom sensitivity 缩放灵敏度 + Reverse zoom direction 反转缩放方向 + Calibrate 校准 + Move sideways to pan; push/pull or twist to zoom. 向侧面移动以平移;推拉或扭转以缩放。 + Left device button: zoom extents. Right device button: calibrate. 左侧设备按钮:范围缩放。右侧设备按钮:校准。 \ No newline at end of file diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 34ecc5e..199c52d 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -195,7 +195,8 @@ pub struct CadApp { /// Caps the frame rate when presenting without vsync (see [`gpu::FrameCap`]). #[cfg(not(target_arch = "wasm32"))] pub frame_cap: Option, - styled: bool, + /// Interface fonts installed, and whether with a CJK font (`None` until the first frame). + fonts_cjk: Option, /// The theme last installed into egui (`None` until the first frame). shown_theme: Option, /// Whether the last applied theme was handed to egui as the System preference. @@ -237,7 +238,7 @@ impl CadApp { synthetic: Vec::new(), #[cfg(not(target_arch = "wasm32"))] frame_cap: None, - styled: false, + fonts_cjk: None, shown_theme: None, shown_follows_system: false, frame_ms: 0.0, @@ -251,8 +252,11 @@ impl CadApp { } } + /// The interface language shown: the chosen one, or English when this computer has no font + /// for it (Chinese without an installed CJK font would be boxes). pub fn language(&self) -> &'static str { - self.ui.interface_language.resolve(&self.system_languages) + let l = self.ui.interface_language.resolve(&self.system_languages); + if theme::can_show(l) { l } else { "en" } } /// Draw the canvas on the GPU with the app's wgpu render state (eframe's @@ -410,9 +414,10 @@ impl CadApp { if ctx.input(|i| i.viewport().close_requested()) && self.request_quit() { ctx.send_viewport_cmd(egui::ViewportCommand::CancelClose); } - if !self.styled { - theme::install_fonts(ctx); - self.styled = true; + let cjk = self.language().starts_with("zh"); + if self.fonts_cjk != Some(cjk) { + theme::install_fonts(ctx, cjk); + self.fonts_cjk = Some(cjk); } // Resolved every frame, so a System choice follows the OS appearance live (winit and the // browser report changes as input); the saved choice itself never changes here. diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 59c377a..e3539ef 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -114,7 +114,13 @@ fn run_ui_command_inner(app: &mut CadApp, id: &str, params: &Value) -> Option { let picked = app.services.pick_open.as_ref().and_then(|f| f()); diff --git a/crates/ui-egui/src/options/mod.rs b/crates/ui-egui/src/options/mod.rs index 4bff00c..e64f480 100644 --- a/crates/ui-egui/src/options/mod.rs +++ b/crates/ui-egui/src/options/mod.rs @@ -9,7 +9,7 @@ //! | name | value | //! |---|---| //! | `theme` | `"system"`, `"light"` or `"dark"` | -//! | `interfaceLanguage` | `"auto"`, `"en"`, `"uk"`, `"pt-BR"`, `"de"`, `"es"`, `"cs"`, `"ru"` or `"fr"` | +//! | `interfaceLanguage` | `"auto"`, `"en"`, `"uk"`, `"pt-BR"`, `"de"`, `"es"`, `"cs"`, `"ru"`, `"fr"` or `"zh"` | //! | `saveFormat` | `"dxf"` or `"dwg"`: the format Save As suggests for new drawings | //! | `saveVersion` | `"2000"` … `"2018"`: the version drawings with no file yet are saved in | //! | `SAVETIME` | minutes between automatic saves, 0–600 (0: off) | diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 2d04c0a..53e4094 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -302,7 +302,7 @@ pub fn mono() -> FontId { } /// Load a system UI font at run time when one is installed (not bundled), else keep egui's. -pub fn install_fonts(ctx: &egui::Context) { +pub fn install_fonts(ctx: &egui::Context, cjk: bool) { #[cfg(not(target_arch = "wasm32"))] { let candidates: &[&str] = if cfg!(target_os = "macos") { @@ -327,10 +327,70 @@ pub fn install_fonts(ctx: &egui::Context) { break; } } + // Chinese: an installed CJK font after the Latin ones, so Latin text keeps its look. + if let Some(bytes) = cjk.then(cjk_font).flatten().and_then(|p| read_capped(p, MAX_UI_FONT_BYTES)) { + fonts.font_data.insert("system-cjk".into(), std::sync::Arc::new(egui::FontData::from_owned(bytes))); + for family in [FontFamily::Proportional, FontFamily::Monospace] { + if let Some(f) = fonts.families.get_mut(&family) { + f.push("system-cjk".into()); + } + } + } ctx.set_fonts(fonts); } #[cfg(target_arch = "wasm32")] - let _ = ctx; + let _ = (ctx, cjk); +} + +/// Largest interface font file read (CJK collections are 15–60 MB). +#[cfg(not(target_arch = "wasm32"))] +const MAX_UI_FONT_BYTES: u64 = 96 * 1024 * 1024; + +#[cfg(not(target_arch = "wasm32"))] +fn read_capped(path: &std::path::Path, max: u64) -> Option> { + let len = std::fs::metadata(path).ok()?.len(); + if len == 0 || len > max { + return None; + } + std::fs::read(path).ok() +} + +/// An installed font with Chinese characters, for the Chinese interface; fonts are never bundled +/// (only looked for where the OS or common packages put them). Checked once. +pub fn cjk_font() -> Option<&'static std::path::Path> { + #[cfg(not(target_arch = "wasm32"))] + { + static FOUND: std::sync::OnceLock> = std::sync::OnceLock::new(); + FOUND + .get_or_init(|| { + let candidates: &[&str] = if cfg!(target_os = "macos") { + &["/System/Library/Fonts/PingFang.ttc", "/System/Library/Fonts/STHeiti Light.ttc", "/System/Library/Fonts/Hiragino Sans GB.ttc"] + } else if cfg!(windows) { + &["C:\\Windows\\Fonts\\msyh.ttc", "C:\\Windows\\Fonts\\msyh.ttf", "C:\\Windows\\Fonts\\simsun.ttc"] + } else { + &[ + "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf", + "/usr/local/share/fonts/noto/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", + "/usr/share/fonts/wqy-microhei/wqy-microhei.ttc", + "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", + "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", + ] + }; + candidates.iter().map(std::path::PathBuf::from).find(|p| p.is_file()) + }) + .as_deref() + } + #[cfg(target_arch = "wasm32")] + None +} + +/// Whether the interface can show `language`: Chinese needs an installed CJK font. +pub fn can_show(language: &str) -> bool { + !language.starts_with("zh") || cjk_font().is_some() } /// egui's visuals for `theme`, from its tokens. diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 50ed2d7..1f4d938 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -115,7 +115,7 @@ Select a language with `engine.execute` and the UI command `ui.language`: {"id":1,"method":"engine.execute","params":{"command":"ui.language","params":{"lang":"uk"}}} ``` -Supported values are `auto`, `en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru` and `fr`. `ui.set` also accepts +Supported values are `auto`, `en`, `uk`, `pt-BR`, `de`, `es`, `cs`, `ru`, `fr` and `zh` (Chinese needs an installed CJK font; without one the result's `shown` is `en`). `ui.set` also accepts `{"interfaceLanguage":"uk"}`; `ui.inspect` reports the saved choice in its UI state. Invalid choices leave the current language unchanged. Menus, command labels and prompt text returned by the control channel retain their English source text. See [localization.md](localization.md).