feat(app-iced,i18n): M106 —— 界面跟随系统语言 + 设置页真实语言行

用户原话:「我系统语言是中文 为什么打开软件是英文 并且设置页里面显示的是 English(system)」。
两处根源:①M92 的探测链一直是对的,但 `pages::Model.lang` 在 `app/view.rs` 里写死成
`Lang::En`(`pages::render` 还把下载页写死成英文),探测结果没人用;②设置页语言行是硬编码
字面量 `("Language", "English (System)")`。

新增/改动(按层):
- i18n:新域 `catalog/general.rs`(24 条中英双份 key,装配链尾追加)+ `catalog/golden.rs`
  只追加不重排;`choice.rs`(`LangChoice`:跟随系统/手选 + D28 四级优先级真值表 + 字体优先级真值表)。
- app-iced 语言入口 `src/ui.rs` + `src/ui/lang.rs`:`LangState` = 用户档位 + 探测结论;
  启动判定 `ULL_LANG` > 配置 `ui.language` > 系统 locale > 英文;两行日志(探测/生效 + 中文策略)。
- `app/state.rs`:`App` 持有 `LangState`,切语言 = 换目录 + 落盘 + 重建视图模型;
  `app/view.rs` 的 `model()` 收 `lang`,不再写死英文。
- 设置页:语言行换真控件(跟随系统 / 简体中文 / 英文,回显「界面当前使用:简体中文」);
  页头、左列分组名、逐实例空态、导航轨标签全部走 catalog。
- presenter:`settings_bridge_lang.rs`(`load_ui_language` / `persist_ui_language`)。
  「跟随系统」= **删键**(schema 允许值只有 zh_cn/en_us),读的是 `doc().get()` 而不是
  `effective_str`(后者会把默认值 `zh_cn` 当成用户手选,英文机器一启动就被切成中文)。

验证:408 条 app-iced lib 测试 + 28 条 i18n 测试 + 全仓 `cargo test --workspace --exclude
unlucky-app` 全绿;fmt / clippy `-D warnings` 干净;出图 6 张(en/zh × 浅/深 + 下载页对照)
在 `DevDocs/artifacts/m106/`(该目录被 .gitignore 忽略,按仓内惯例作为本地验收产物)。
This commit is contained in:
AskaEth committed 2026-11-04 22:50:09 +08:00
1 parent 777d02e7cb
commit 89dcb6bdf3
25 files changed
+1456 -92

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
//! 设置页 General 分组**真控件**的文案(M106 `I18NFOLLOW`):语言选择器 + 其余三行。
//!
//! ## 与 `catalog/settings/general.rs` 的分工
//!
//! `catalog/settings/general.rs`(M99 建的)只有两条**标题**([`SettingsLanguage`] /
//! [`SettingsLanguageHint`]),是设置页五域装配的一部分;本文件是 M106 接真控件时新增的
//! **选择器文案**:三档选项名、选中后的「界面当前使用」回显,以及 General 分组里另外三行
//! (主题 / 启动页 / 关闭按钮)。
//!
//! 为什么要分两个文件:`catalog.rs` 的装配链是**追加**式的,新域挂在链尾就不会动到既有
//! `Key` 的顺序(= `catalog/golden.rs` 那张逐字节金表)。两个文件同属 General 分组,读代码时
//! 按「标题 vs 选项」区分即可;真要合并也是一个文件搬十行的活,不急着在接线的 mission 里做。
//!
//! ## 选项名为什么不是「语言自称」
//!
//! 常见做法是三档都写 `简体中文` / `English` 这样的自称。这里没有:`catalog/tests.rs` 有一条
//! `chinese_catalog_is_actually_chinese`(英文表与中文表逐 key 必须不同),自称会让「中文表里
//! 写着 English」被判成漏译。所以选项名**跟着界面语言走**:中文界面「跟随系统 / 简体中文 / 英文」,
//! 英文界面 "Follow system / Chinese (Simplified) / English"——这也是多数启动器的做法。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节。
macro_rules! general_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// ── General 分组(语言选择器 + 其余三行)
SettingsLanguageFollowSystem => ("Follow system", "跟随系统"),
SettingsLanguageZhCn => ("Chinese (Simplified)", "简体中文"),
SettingsLanguageEn => ("English", "英文"),
SettingsLanguageEffective => ("Interface right now: {language}", "界面当前使用:{language}"),
SettingsTheme => ("Theme", "主题"),
SettingsThemeFollowSystem => ("Follow system", "跟随系统"),
SettingsStartupPage => ("Startup page", "启动页"),
SettingsStartupInstances => ("Instances", "实例"),
SettingsCloseButton => ("Close button", "关闭按钮"),
SettingsCloseToTray => ("Exit to tray", "退出到托盘"),
// 设置页页头(M106):标题复用 `NavSettings`,这三条是副标题与两个动作。
SettingsSubtitle => ("Launcher behaviour, Java runtimes and download sources", "启动器行为、Java 运行时与下载源"),
SettingsResetSection => ("Reset section", "重置本组"),
SettingsSave => ("Save", "保存"),
// 第五条导航项(M106 给导航轨补的最后一条)。放在本域而不是 `catalog/nav.rs`:
// 金表按**位置**逐字节比,插进 nav 域会把既有 4 条导航行整体平移一位。
NavAi => ("Assistant", "助手"),
// 设置页左列的分组名(M106)。`Group.name` 是**id**(测试按它认组),显示名走这里。
SettingsGroupGeneral => ("General", "通用"),
SettingsGroupAppearance => ("Appearance", "外观"),
SettingsGroupJava => ("Java", "Java 运行时"),
SettingsGroupDownloads => ("Downloads", "下载"),
// 逐实例覆盖的空态(M106)
SettingsOverridesEmptyTitle => ("No per-instance overrides", "没有逐实例覆盖"),
SettingsOverridesEmptyBody => ("Overrides let one instance keep its own Java runtime, memory or JVM flags while the rest of the launcher keeps the defaults above.", "逐实例覆盖让某个实例保留自己的 Java 运行时、内存或 JVM 参数,其余实例沿用上面的默认值。"),
SettingsOverridesAdd => ("Add override", "新增覆盖"),
// `Route::Agent` 的回退卡正文(M106:`app/view.rs` 里最后一条硬编码英文)
AgentFallbackBody => ("The assistant page is not built yet. Chat lives in the docked panel once the AI mission lands; this screen is the M68 skeleton fallback.", "助手页还没做。AI 接线落地后对话会出现在右侧停靠面板;这一屏是 M68 的骨架回退。"),
} }
};
}
pub(super) use general_entries;
+24
View File
@@ -124,4 +124,28 @@ pub(super) const GOLDEN: &[(&str, &str, &str)] = &[
("AgentRoleYou", "You", "你"),
("AgentRoleAssistant", "Assistant", "助手"),
("AgentRoleNotice", "Notice", "提示"),
// ── M106 `I18NFOLLOW`:General 分组真控件(语言选择器 + 其余三行)。**只追加在链尾**,
// 上一行到 `LogsEmptyAction` 的既有顺序一字未动(金表要证明的正是这件事)。
("SettingsLanguageFollowSystem", "Follow system", "跟随系统"),
("SettingsLanguageZhCn", "Chinese (Simplified)", "简体中文"),
("SettingsLanguageEn", "English", "英文"),
("SettingsLanguageEffective", "Interface right now: {language}", "界面当前使用:{language}"),
("SettingsTheme", "Theme", "主题"),
("SettingsThemeFollowSystem", "Follow system", "跟随系统"),
("SettingsStartupPage", "Startup page", "启动页"),
("SettingsStartupInstances", "Instances", "实例"),
("SettingsCloseButton", "Close button", "关闭按钮"),
("SettingsCloseToTray", "Exit to tray", "退出到托盘"),
("SettingsSubtitle", "Launcher behaviour, Java runtimes and download sources", "启动器行为、Java 运行时与下载源"),
("SettingsResetSection", "Reset section", "重置本组"),
("SettingsSave", "Save", "保存"),
("NavAi", "Assistant", "助手"),
("SettingsGroupGeneral", "General", "通用"),
("SettingsGroupAppearance", "Appearance", "外观"),
("SettingsGroupJava", "Java", "Java 运行时"),
("SettingsGroupDownloads", "Downloads", "下载"),
("SettingsOverridesEmptyTitle", "No per-instance overrides", "没有逐实例覆盖"),
("SettingsOverridesEmptyBody", "Overrides let one instance keep its own Java runtime, memory or JVM flags while the rest of the launcher keeps the defaults above.", "逐实例覆盖让某个实例保留自己的 Java 运行时、内存或 JVM 参数,其余实例沿用上面的默认值。"),
("SettingsOverridesAdd", "Add override", "新增覆盖"),
("AgentFallbackBody", "The assistant page is not built yet. Chat lives in the docked panel once the AI mission lands; this screen is the M68 skeleton fallback.", "助手页还没做。AI 接线落地后对话会出现在右侧停靠面板;这一屏是 M68 的骨架回退。"),
];