i18n: complete Simplified Chinese catalog and localize brush sections (#359)

* i18n: complete Simplified Chinese UI catalog and brush labels

* i18n(zh-hans): translate labels added on main since this PR (opening/cancel, layer navigation)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* i18n(zh-hans): keep main's Mixer Brush entries, translate the rendering-mode strings added since (#434)

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-10-07 07:46:52 -04:00
1 parent b5eed81a25
commit b45dfde498
6 files changed
+242 -53

No files matched your search

+2 -2
View File
@@ -263,7 +263,7 @@ fn section_list(app: &mut PhotocraftApp, ui: &mut egui::Ui, b: &mut BrushSetting
}
let color = if sel { t.text } else { t.text_dim };
let font = if i == 0 { theme::semibold(12.0) } else { theme::medium(12.0) };
ui.painter().text(egui::pos2(x, r.center().y), egui::Align2::LEFT_CENTER, *name, font, color);
ui.painter().text(egui::pos2(x, r.center().y), egui::Align2::LEFT_CENTER, tl!(name), font, color);
let mut lock_clicked = false;
if let Some(lock) = section_lock(b, i) {
let lr = egui::Rect::from_center_size(egui::pos2(r.right() - 11.0, r.center().y), vec2(18.0, 18.0));
@@ -300,7 +300,7 @@ fn settings_tab(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
widgets::vline(ui, 482.0);
ui.vertical(|ui| {
ui.set_width(WIDTH - 190.0);
ui.label(RichText::new(SECTIONS[section].0).font(theme::semibold(12.5)).color(t.text));
ui.label(RichText::new(tl!(SECTIONS[section].0)).font(theme::semibold(12.5)).color(t.text));
ui.add_space(6.0);
let on = section_flag(&mut b, section).is_none_or(|f| *f);
egui::ScrollArea::vertical().id_salt(("brush-section", section)).max_height(450.0).auto_shrink([false, false]).show(ui, |ui| {
+38 -1
View File
@@ -446,17 +446,24 @@ fn brushes_panel_groups_collapse_and_filter() {
/// A fresh session with the Brush tool: shortcuts, the options bar and the Brush Settings window,
/// in the default theme (#258).
fn app_harness() -> Harness<'static, PhotocraftApp> {
app_harness_with_language(crate::i18n::Lang::EN)
}
fn app_harness_with_language(lang: crate::i18n::Lang) -> Harness<'static, PhotocraftApp> {
let mut app = app();
app.ui.tool = crate::state::Tool::Brush;
let mut h = Harness::builder().with_size(vec2(1400.0, 900.0)).build_ui_state(
|ui, app: &mut PhotocraftApp| {
move |ui, app: &mut PhotocraftApp| {
let ctx = ui.ctx().clone();
if !ctx.fonts(|f| f.families().contains(&egui::FontFamily::Name("medium".into()))) {
return;
}
let previous = crate::i18n::current();
crate::i18n::set_current(lang);
crate::shortcuts::handle(app, &ctx);
crate::panels::options_bar(app, ui);
window(app, &ctx);
crate::i18n::set_current(previous);
},
app,
);
@@ -465,6 +472,36 @@ fn app_harness() -> Harness<'static, PhotocraftApp> {
h
}
#[test]
fn brush_sections_paint_simplified_chinese_labels_and_heading() {
fn collect_text(shape: &egui::Shape, out: &mut Vec<String>) {
match shape {
egui::Shape::Text(text) => out.push(text.galley.job.text.clone()),
egui::Shape::Vec(shapes) => {
for shape in shapes {
collect_text(shape, out);
}
}
_ => {}
}
}
let zh = crate::i18n::Lang::from_code("zh-hans").unwrap();
let mut h = app_harness_with_language(zh);
h.key_press(egui::Key::F5);
h.run_steps(3);
let mut painted = Vec::new();
for shape in &h.output().shapes {
collect_text(&shape.shape, &mut painted);
}
for (name, _) in SECTIONS {
let translated = crate::i18n::tr(zh, name);
assert!(painted.iter().any(|s| s == translated), "{name} must paint as {translated}");
assert!(!painted.iter().any(|s| s == name), "{name} must not paint in English");
}
let heading = crate::i18n::tr(zh, "Brush Tip Shape");
assert_eq!(painted.iter().filter(|s| s.as_str() == heading).count(), 2, "both the section row and heading are translated");
}
#[test]
fn f5_edits_the_default_brush_with_every_section() {
use egui_kittest::kittest::Queryable;
+27 -1
View File
@@ -74,7 +74,7 @@ pub static LANGUAGES: [LangInfo; 7] = [
LangInfo { code: "en", name: "English", source: "", plural: plural_one_other, complete_menus: false, catalog: OnceLock::new() },
LangInfo { code: "ja", name: "日本語", source: include_str!("ja.tsv"), plural: plural_none, complete_menus: true, catalog: OnceLock::new() },
LangInfo {
code: "zh-hans", name: "简体中文", source: include_str!("zh-hans.tsv"), plural: plural_none, complete_menus: false, catalog: OnceLock::new()
code: "zh-hans", name: "简体中文", source: include_str!("zh-hans.tsv"), plural: plural_none, complete_menus: true, catalog: OnceLock::new()
},
// Traditional Chinese in the vocabulary used in Taiwan; `zh-TW`, `zh-HK`, `zh-MO` and `zh-Hant-*`
// locales all resolve here (see `candidates`).
@@ -341,6 +341,22 @@ mod tests {
assert_eq!(Lang::from_pref("xx-unknown"), Lang::EN);
}
#[test]
fn simplified_chinese_covers_dynamic_shortcuts_and_layer_counts() {
let zh = Lang::from_code("zh-hans").expect("zh-hans registered");
assert!(zh.complete_menus(), "Simplified Chinese must participate in the coverage gates");
assert_eq!(Lang::from_pref("ZH-Hans"), zh);
assert_eq!(tr(zh, "Pixel Layer"), "像素图层");
assert_eq!(tr(zh, "System Info"), "系统信息");
for key in ["⌥", "Alt"] {
assert_eq!(fmt(tr(zh, "Add a mask (from the selection; {key} inverts)"), &[("key", key)]), format!("添加蒙版 (基于选区;{key} 反相)"));
}
for n in [0, 1, 3] {
assert_eq!(trn(zh, n, "{n} layer", "{n} layers"), format!("{n} 个图层"));
}
assert_eq!(tr(zh, "no such label"), "no such label");
}
#[test]
fn spanish_resolves_and_pluralises() {
let es = Lang::from_code("es").expect("es registered");
@@ -529,6 +545,16 @@ mod tests {
}
}
/// Section names are dynamic labels, so the literal scanner cannot cover them.
#[test]
fn brush_section_names_are_translated() {
for lang in Lang::all().filter(|l| l.complete_menus()) {
for (name, _) in crate::brush_panel::SECTIONS {
assert!(lang.0.catalog().plain(name).is_some(), "{} missing brush section: {name}", lang.code());
}
}
}
/// Blend mode names come from the colour crate; each must be translated.
#[test]
fn blend_mode_names_are_translated() {
+133 -6
View File
@@ -5,9 +5,10 @@
# with a single Chinese form. Other contexts disambiguate the English meaning.
# Escapes inside columns: \n, \t, \\. Keep {name} placeholders and trailing ….
#
# Original translations from the meaning of the English source keys in PhotoCraft
# PR #169 (c1fb8909c4b3b176eead12d66b04338a103cf63d). No proprietary translation
# resources were consulted or extracted. Licensed MIT OR Apache-2.0, as the code.
# Original translations from the meaning of PhotoCraft's English source keys,
# including the current menus, panels and platform-specific shortcut templates.
# No proprietary translation resources were consulted or extracted.
# Licensed MIT OR Apache-2.0, as the code.
# Product and technology names retain their spelling. See docs/localization-zh-hans.md.
File 文件
@@ -1143,8 +1144,6 @@
Midtones 中间调
Midtones from: 中间调下限:
Midtones to: 中间调上限:
Mix 混合
Mixer Brush 混合笔刷
Mixer Brush Tool 混合画笔工具
Minimum Depth 最小深度
Minimum Diameter 最小直径
@@ -1676,7 +1675,6 @@
Web Minimum Web 最小尺寸
Web Most Common Web 常用尺寸
WebP (lossless) WebP(无损)
Wet 湿度
Wet Edges 湿边
Yellows 黄色
Zoom Tool 缩放工具
@@ -1938,3 +1936,132 @@ cameraRaw Blending 混合
Patch Tool 修补工具
Patch: 修补:
Lasso around an area, then drag the selection 用套索圈出区域,然后拖动选区
# Current shell labels and shortcut templates. Keep {key} platform-dependent.
Add a mask (from the selection; {key} inverts) 添加蒙版 (基于选区;{key} 反相)
Add to selection ({key}) 添加到选区 ({key})
Click the first point or press {key} to close · Esc cancels 单击起点或按 {key} 闭合 · Esc 取消
Click to add a point · drag off or {key}-click or Delete to remove 单击添加点 · 拖离、按 {key} 单击或按 Delete 移除
Click to apply, {key}-click to clear overrides, double-click for Style Options 单击应用,按 {key} 单击清除覆盖设置,双击打开样式选项
Click to zoom in · {key}-click to zoom out · drag right/left to zoom in/out 单击放大 · 按 {key} 单击缩小 · 向右或向左拖动以放大或缩小
Click: corner · Drag: smooth · Click first point: close · {key} finish · Esc cancel 单击:角点 · 拖动:平滑点 · 单击起点:闭合 · {key} 完成 · Esc 取消
Commit Perspective Warp ({key}) 应用透视变形({key})
Commit Puppet Warp ({key}) 应用操控变形({key})
Commit any current edits ({key}) 应用当前所有编辑({key})
Commit current crop operation ({key}) 应用当前裁剪操作 ({key})
Commit transform ({key}) 应用变换({key})
Commit warp ({key}) 应用变形({key})
Create a new layer 创建图层
Intersect with selection ({key}) 与选区相交 ({key})
New document… 新建文档…
Search commands 搜索命令
Show Guides 显示参考线
Subtract from selection ({key}) 从选区中减去 ({key})
↑↓ navigate {key} run esc close ↑↓ 导航 {key} 运行 esc 关闭
@plural {n} layer|{n} layers {n} 个图层
Collapse group ({key}-click: all groups) 折叠组 (按 {key} 单击:所有组)
Expand group ({key}-click: all groups) 展开组 (按 {key} 单击:所有组)
Hide the layer's effects ({key}-click: all layers) 隐藏图层效果 (按 {key} 单击:所有图层)
Show the layer's effects ({key}-click: all layers) 显示图层效果 (按 {key} 单击:所有图层)
System Info… 系统信息…
System Info 系统信息
More panels 更多面板
These settings aren't available in PhotoCraft yet. PhotoCraft 暂不支持这些设置。
Graphics 图形
Reset GPU Backend 重置 GPU 后端
Applies at next launch. 下次启动时生效。
Click to sample the foreground colour · {key}-click for background 单击拾取前景色 · 按 {key} 单击拾取背景色
{key}-click to set the source 按 {key} 单击设置源点
{key} to subtract 按 {key} 从选区中减去
Clone source {n} ({key}-click with Clone Stamp to set) 仿制源 {n}(使用仿制图章时按 {key} 单击设置)
Source: not set ({key}-click with Clone Stamp) 源点:未设置(使用仿制图章时按 {key} 单击)
Pixel Layer 像素图层
Type Layer 文字图层
Shape Layer 形状图层
Group Layer 图层组
Fill Layer 填充图层
Smart Object Layer 智能对象图层
Create Warped Text 创建变形文字
Rasterize Smart Object 栅格化智能对象
Rasterize Fill Content 栅格化填充内容
Unlock: picking a preset replaces these settings 解锁:选择预设时替换这些设置
Lock: keep these settings when picking another preset 锁定:选择其他预设时保留这些设置
Diameter: 直径:
Hardness: 硬度:
No brush presets 没有画笔预设
Toggle the Brush Settings panel (F5) 切换画笔设置面板 (F5)
Create new brush preset from the current settings 根据当前设置创建画笔预设
Brush Settings… (F5): every option of this brush 画笔设置… (F5):此画笔的全部选项
Rename Brush… 重命名画笔…
Delete Brush 删除画笔
Group name 组名称
Brush name 画笔名称
Grid view 网格视图
List view 列表视图
Off: the speed of the pointer sets the spacing 关闭:由指针移动速度决定间距
Tilt Scale 倾斜缩放
Brush Projection 画笔投影
Apply the pen's tilt and rotation to the tip shape 将笔的倾斜和旋转应用于笔尖形状
Mixer Brush 混合器画笔
Wet 湿润
Mix 混合
Wetness Jitter 湿润度抖动
Mix Jitter 混合抖动
Show the layer mask as an overlay 以叠加方式显示图层蒙版
Toggle visibility 切换可见性
Unlink the mask from the layer 取消蒙版与图层的链接
Link the mask to the layer 将蒙版与图层链接
Gradient 渐变
Gradient Options 渐变选项
Align with layer 与图层对齐
Reset Alignment 重置对齐
Centre the gradient (offset 0, 0) 将渐变居中(偏移量 0, 0)
The current gradient (pick one in Window › Gradients) 当前渐变(在“窗口 › 渐变”中选择)
Select a type layer to edit its character and paragraph settings. 选择文字图层以编辑字符和段落设置。
Text color 文字颜色
Type Options 文字选项
Anti-aliasing method 消除锯齿方式
Horizontal type 横排文字
Vertical type 直排文字
Fill path with foreground color 以前景色填充路径
Stroke path with brush 用画笔描边路径
Load path as a selection 将路径载入选区
Make work path from selection 从选区生成工作路径
Add vector mask 添加矢量蒙版
Create new path 创建路径
Delete path 删除路径
Delete vector mask 删除矢量蒙版
CPU (no GPU acceleration) CPU(无 GPU 加速)
DirectX 12 DirectX 12
GPU backend GPU 后端
Metal Metal
OpenGL OpenGL
Use tablet pressure 使用数位板压感
Vulkan Vulkan
No path to make a selection from: draw one with the Pen tool or select one in the Paths panel. 没有可用于生成选区的路径:请用钢笔工具绘制路径,或在路径面板中选择路径。
# Layer navigation, copy transforms and cancellable opening (labels added on main since this catalog).
Cancel opening 取消打开
Cancelled opening {name} 已取消打开 {name}
Cancelled {label} 已取消 {label}
Esc to cancel 按 Esc 取消
Opening {name} 正在打开 {name}
Opening {name}… 正在打开 {name}…
Opening… 正在打开…
Reading… 正在读取…
Working… 正在处理…
# Rendering mode and recovery
Rendering Mode 渲染模式
Automatic (recommended) 自动(推荐)
CPU / Compatibility CPU / 兼容模式
Automatic uses GPU acceleration when available and falls back to CPU rendering on errors. 自动模式会在可用时使用 GPU 加速,出错时改用 CPU 渲染。
Switched to CPU rendering 已切换到 CPU 渲染
GPU acceleration could not continue. Your documents are unchanged. GPU 加速无法继续。您的文档未更改。
PhotoCraft is using the CPU image compositor. The window may still use your graphics adapter. PhotoCraft 正在使用 CPU 合成图像。窗口仍可能使用您的显卡。
Retry GPU requires a restart. Save your work first. 重试 GPU 需要重新启动。请先保存您的工作。
Keep Using CPU 继续使用 CPU
Retry GPU 重试 GPU
Advanced 高级
GPU Backend GPU 后端
Details 详细信息
Rendering mode 渲染模式
1 # Simplified Chinese (zh-hans) catalog. UTF-8, TAB-separated columns:
5 # with a single Chinese form. Other contexts disambiguate the English meaning.
6 # Escapes inside columns: \n, \t, \\. Keep {name} placeholders and trailing ….
7 #
8 # Original translations from the meaning of the English source keys in PhotoCraft # Original translations from the meaning of PhotoCraft's English source keys,
9 # PR #169 (c1fb8909c4b3b176eead12d66b04338a103cf63d). No proprietary translation # including the current menus, panels and platform-specific shortcut templates.
10 # resources were consulted or extracted. Licensed MIT OR Apache-2.0, as the code. # No proprietary translation resources were consulted or extracted.
11 # Licensed MIT OR Apache-2.0, as the code.
12 # Product and technology names retain their spelling. See docs/localization-zh-hans.md.
13
14
1144
1145
1146
1147
1148
1149
1675
1676
1677
1678
1679
1680
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956 @plural
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037 # Layer navigation, copy transforms and cancellable opening (labels added on main since this catalog).
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047 # Rendering mode and recovery
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
+40 -41
View File
@@ -1,40 +1,45 @@
# Simplified Chinese catalog
The `zh-hans` catalog targets the i18n interface proposed in
[PhotoCraft PR #169](https://github.com/storytold/photocraft/pull/169), commit
`c1fb8909c4b3b176eead12d66b04338a103cf63d`. It translates the English source keys
from that revision's catalog. The Chinese wording is original and uses ordinary
image-editing terminology; no proprietary translation resources were extracted
or copied. Contributions use the repository's MIT OR Apache-2.0 license.
The `zh-hans` catalog is registered as **简体中文** in the shared i18n foundation
introduced by [PhotoCraft PR #169](https://github.com/storytold/photocraft/pull/169).
Simplified Chinese support landed in
[PR #282](https://github.com/storytold/photocraft/pull/282), after the v0.2.0 release.
That release does not contain the language selector or this catalog; use a build
that includes the localisation changes.
## Draft status
The Chinese wording is original and uses ordinary image-editing terminology;
no proprietary translation resources were extracted or copied. Contributions
use the repository's MIT OR Apache-2.0 license.
This contribution contains catalog data and documentation for translation review.
The catalog is not loaded by current main, so it does not yet enable a Chinese UI.
The integration below has been validated locally against the referenced #169
revision. Language registration and its tests will follow the shared i18n
foundation adopted upstream; the translations can be retained if that design
changes.
## Selecting Simplified Chinese
Select **Preferences > Interface > Language > 简体中文**, or set
`interface.language` to `zh-hans` through the existing `prefs.set` command.
The native build's **Auto** setting reads `LANG`/`LC_*`, the macOS preferred
languages and the Windows user locale. `zh`, `zh-CN`, `zh-SG` and `zh-Hans`
variants resolve to this catalog. Traditional Chinese locales (`zh-TW`, `zh-HK`,
`zh-MO`, `zh-Hant`) resolve to the separate `zh-hant` catalog.
The web build does not detect the browser language automatically; choose the
language manually there.
## Files and integration
- `crates/ui-egui/src/i18n/zh-hans.tsv` contains the translations, independent of
the lookup implementation. Its UTF-8 columns are `context<TAB>source<TAB>translation`.
- With #169's foundation, register `zh-hans` in `crates/ui-egui/src/i18n/mod.rs`,
displayed as `简体中文`, with one plural form and the complete-catalog checks.
- Once registered, select **Preferences > Interface > Language > 简体中文**, or set
`interface.language` to `zh-hans` through the existing `prefs.set` command.
- `crates/ui-egui/src/i18n/mod.rs` registers `zh-hans` with one plural form and
`complete_menus: true`. Shared tests enforce coverage of every menu string,
`tl!` literal, blend mode, brush section and generated preference label.
- Shortcut templates retain `{key}` so the shell supplies the correct key for
the current platform. Layer-count messages use one Chinese plural form.
Keep English source keys, contexts, command IDs, placeholders and escapes intact.
Retain the trailing `…` on commands that open a dialog. Missing translations use
the framework's English fallback. User-supplied names and document data are not
translated. Product names and technology names such as PhotoCraft, ArtCraft,
OpenType, RGB, CMYK and Lab retain their spelling.
The #169 locale resolver recognizes `zh`, `zh-CN`, `zh-SG` and `zh-Hans`
variants. Traditional Chinese locales (`zh-TW`, `zh-HK`, `zh-MO`, `zh-Hant`) do
not select this catalog. Automatic OS/browser detection remains the responsibility
of the i18n foundation; manual language selection works independently of it.
OpenType, RGB, CMYK and Lab retain their spelling. Status messages, errors and
automation output remain English as described in [UI design](ui-design.md#localisation);
complete catalog coverage does not claim those strings are translated.
## Terminology
@@ -57,24 +62,18 @@ of the i18n foundation; manual language selection works independently of it.
Run `cargo test -p photocraft-ui-egui`, the touched-crate all-target clippy check,
`cargo xtask layers` and `cargo xtask wasm`. The shared catalog tests validate
duplicate keys, placeholders, ellipses, menu coverage, `tl!` literals and blend
modes. Chinese-specific tests cover locale selection, fallback, plural messages
and formatted labels. Render and inspect the menus, Preferences and representative
duplicate keys, placeholders, ellipses, menu coverage, `tl!` literals, brush
sections and blend modes. Chinese-specific tests cover locale selection, fallback,
plural messages, formatted labels and the painted brush section rows and heading. Render and inspect the menus, Preferences and representative
dialogs with the existing offscreen `snapshot` example.
Font delivery is separate from the translation data. This contribution adds no
font assets. The PR #169 revision predates the newer system CJK fallback changes;
test against the current upstream font implementation before publishing a
release, and verify Web glyph coverage separately.
Font delivery is separate from the translation data. The catalog adds no font
assets; the shell's CJK fallback loads fonts separately. Verify missing glyphs,
label layout and platform-specific shortcuts visually on each target platform,
and verify web glyph coverage separately.
Offscreen review on Windows confirmed that the PR #169 baseline's Japanese-first
font fallback leaves some Simplified Chinese glyphs missing. New Document and
Interface Preferences were also reviewed using the locally installed Microsoft
YaHei font in a temporary harness; their Chinese labels fit at 1280 × 800. That
font override is only a review aid and is not part of the production change.
If #169's implementation changes, retain the catalog's English source and
context keys, adapt the registration/loader, and rerun the catalog checks. If a
different resource format is adopted, convert these three columns mechanically
instead of retranslating the text. Review additions and changed source meanings
against the new English UI.
When English labels change, update their source keys and translations together,
preserving contexts and placeholders. The complete-catalog tests catch missing
keys, and the Simplified Chinese regression test checks dynamic shortcut labels,
layer counts and English fallback. Review changed source meanings against the
current English UI.
+2 -2
View File
@@ -84,7 +84,7 @@ the tests enforce it):
- English (`en`), the source language.
- Japanese (`ja`), complete.
- Simplified Chinese (`zh-hans`; see [`localization-zh-hans.md`](localization-zh-hans.md)), partial.
- Simplified Chinese (`zh-hans`; see [`localization-zh-hans.md`](localization-zh-hans.md)), complete.
- Traditional Chinese (`zh-hant`), complete, in the vocabulary used in Taiwan; `zh-TW`, `zh-HK`,
`zh-MO` and `zh-Hant-*` locales all resolve to it. The resolver distinguishes the two Chinese
scripts, so neither catalog is shown to the other script's locales.
@@ -107,7 +107,7 @@ the tests enforce it):
Localised so far: menus, the command palette, dialogs and panels (literals wrapped in `tl!("…")`;
widgets such as `checkbox`, `slider_row`, `dropdown` and the buttons translate their labels
themselves). A test fails when a `tl!` literal, a menu string, a blend mode name or a generated
themselves). A test fails when a `tl!` literal, a menu string, a blend mode name, brush section name or a generated
preference label has no entry in a language marked `complete_menus`. Not translated: status-bar
messages and errors (they stay English, also for agents), names that are user data (layers, styles,
documents), strings assembled with `format!` that were not converted to `fmt`/`trn`. Not done yet: