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:
1 parent
777d02e7cb
commit
89dcb6bdf3
25 files changed
+1456
-92
No files matched your search
@@ -1,24 +1,33 @@
|
||||
//! 「General」分组自己的断言(M100 **新增**:`settings/tests.rs` 那 7 条全是跨 section 的,
|
||||
//! 一条 section 断言都没有;这 3 条是新增,不是搬移)。
|
||||
//! 「General」分组自己的断言(M106 `I18NFOLLOW` 重写:语言行换成真控件)。
|
||||
//!
|
||||
//! **归 language+appearance 线**。接真控件那天要改**两个**文件(M101 r2 删掉了原先的第 3 处跨
|
||||
//! section 绊线):`settings/general.rs` + 本文件 `the_general_group_is_still_demo_rows`。
|
||||
//!
|
||||
//! M106(`I18NFOLLOW`)已经接了这一组的语言行。用户报的 bug 有两半:**界面全英文**(探测对了
|
||||
//! 但没人用)与**设置页写着 `English (System)`**(一行硬编码假值)。本文件的断言守后半:那行
|
||||
//! 假值不许回来,语言行的三档必须真的能在两套目录里取到词。
|
||||
//!
|
||||
//! 接线形状:语言行的正文由 [`general::body`] 画(入口是 `pages::settings` 的 `GENERAL_GROUP`
|
||||
//! 按下标分发),`GROUP.body` 仍是 `None`——那个槽位的签名是 `fn(&Ui, States) -> Option<Element>`,
|
||||
//! `States` 只有 `appearance` + `groups` 两格,装不下「当前语言档位」。所以 M101 r2 的
|
||||
//! 「本组接真控件只需改两个文件」这条口径,在语言行上**仍然是两个文件**。
|
||||
|
||||
use super::super::general;
|
||||
use unlucky_design::icons::IconKind;
|
||||
use unlucky_i18n::LangChoice;
|
||||
use unlucky_i18n::{Key, Lang};
|
||||
|
||||
/// 这一组的行集合(标签 + 值)。改文案就是改观感,所以逐条钉住,而不是「非空就行」。
|
||||
/// 这一组的**静态演示行**:语言那一行已经换真控件,所以只剩三行。
|
||||
///
|
||||
/// 抓得住:① 四条线并行合并时把别人的行粘到本文件(四份 `GROUP` 长得几乎一样,最容易整块贴错);
|
||||
/// ② 漏掉/多出一条行;③ 标签与值错位(`("Theme", "Follow system")` 被写成 `("Theme", "Instances")`)。
|
||||
/// 抓不住:值对不对(`"Follow system"` 是不是当前系统主题的真相)——那是接线后真数据源的事。
|
||||
/// 抓得住:①四条线并行合并时把别人的行粘到本文件;②语言行被「顺手」加回静态表——
|
||||
/// 那等于把用户看到的假值又写回去(它的值必须跟着语言变,静态表做不到)。
|
||||
/// 抓不住:真控件画出来长什么样(那是截图验收的事,见 `DevDocs/artifacts/m106/`)。
|
||||
#[test]
|
||||
fn the_general_group_shows_language_theme_startup_and_close() {
|
||||
fn the_general_group_keeps_theme_startup_and_close_as_static_rows() {
|
||||
let rows: Vec<(&str, &str)> = general::GROUP.rows.to_vec();
|
||||
assert_eq!(
|
||||
rows,
|
||||
vec![
|
||||
("Language", "English (System)"),
|
||||
("Theme", "Follow system"),
|
||||
("Startup page", "Instances"),
|
||||
("Close button", "Exit to tray"),
|
||||
@@ -32,17 +41,82 @@ fn the_general_group_keeps_its_icon() {
|
||||
assert_eq!(general::GROUP.icon, IconKind::Settings);
|
||||
}
|
||||
|
||||
/// **绊线**:这一组还没接真控件,画的是演示行。
|
||||
/// **回归绊线(用户原话)**:`"English (System)"` 那条硬编码字面量不许回来。
|
||||
///
|
||||
/// 抓得住:language+appearance 线接上了 `body` 却没更新这里的期望(那会让本文件静默变成假绿)。
|
||||
/// 抓不住:界面上真控件长什么样。
|
||||
/// 抓得住:①原来那条「这一组还没接真控件」在接上之后没更新(静默变假绿);
|
||||
/// ②语言行**退回静态演示行**(哪怕换个说法,只要还写着「English」就是同一个 bug 的复盘)。
|
||||
/// 抓不住:界面上真控件长什么样、三档选项名对不对(下一条与截图各管一段)。
|
||||
///
|
||||
/// 接真控件时:把 `is_none()` 改成 `is_some()`,**只改这一行**(M101 r2 起不必再去
|
||||
/// `tests/mod.rs` 报到:那条跨 section 聚合绊线已删)。
|
||||
/// 现状(M106):`body` 仍是 `None`(语言行走 `GENERAL_GROUP` 的按下标分发),但这一组**已经**
|
||||
/// 有真控件了——所以这里断言的**不再是「还没接」**,而是「不许回到静态行」。
|
||||
#[test]
|
||||
fn the_general_group_is_still_demo_rows() {
|
||||
fn the_language_row_is_not_a_static_string() {
|
||||
for (label, value) in general::GROUP.rows {
|
||||
assert_ne!(
|
||||
*label, "Language",
|
||||
"语言行必须走真控件(pages::Language),不许回到静态演示行"
|
||||
);
|
||||
assert!(
|
||||
!value.contains("English"),
|
||||
"静态行里不许再出现写死的语言名:{value}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 语言行**三档**,且在**两套目录**里都有词(中文界面不该出现 `⟪missing⟫`)。
|
||||
///
|
||||
/// 抓得住:①少一档(用户就没法切回去了);②标签 key 只加进了中文表(英文界面上一档变成
|
||||
/// `⟪missing⟫`);③回显那句的占位符名字与调用点对不上(会原样印出 `{language}`)。
|
||||
/// 抓不住:译文翻得对不对(金表 + `chinese_catalog_is_actually_chinese` 管那一层)。
|
||||
#[test]
|
||||
fn the_language_row_offers_three_choices_in_both_languages() {
|
||||
assert_eq!(
|
||||
LangChoice::ALL.len(),
|
||||
3,
|
||||
"跟随系统 / 简体中文 / 英文,少一档就切不回来"
|
||||
);
|
||||
|
||||
for lang in Lang::ALL {
|
||||
let catalog = lang.catalog();
|
||||
for choice in LangChoice::ALL {
|
||||
assert!(
|
||||
catalog.get(choice.label_key()).is_some(),
|
||||
"{} 在 {} 目录里没词",
|
||||
choice.label_key().name(),
|
||||
lang.tag()
|
||||
);
|
||||
}
|
||||
|
||||
// 「界面当前使用:⟨语言名⟩」——中文系统下这句必须真的出现「简体中文」。
|
||||
let effective = catalog.text(LangChoice::Fixed(*lang).label_key());
|
||||
let line = catalog.render(Key::SettingsLanguageEffective, &[("language", effective)]);
|
||||
assert!(line.contains(effective), "回显没带上语言名:{line}");
|
||||
assert!(!line.contains('{'), "占位符没被替换:{line}");
|
||||
}
|
||||
|
||||
// 中文系统 + 跟随系统 ⇒ 回显是「简体中文」(用户报的 bug 的直接判据)。
|
||||
let zh = Lang::ZhCn.catalog();
|
||||
assert_eq!(
|
||||
zh.render(
|
||||
Key::SettingsLanguageEffective,
|
||||
&[(
|
||||
"language",
|
||||
zh.text(LangChoice::Fixed(Lang::ZhCn).label_key())
|
||||
)]
|
||||
),
|
||||
"界面当前使用:简体中文"
|
||||
);
|
||||
}
|
||||
|
||||
/// **绊线**:这一组仍不走 `Group.body`。
|
||||
///
|
||||
/// 为什么不是「接了真控件」:语言行的入口是 `pages::Language`(`GENERAL_GROUP` 那条分发),
|
||||
/// 而 `Group.body` 那个槽位的签名是 `fn(&Ui, &pages::appearance::State)`——装不下「当前语言档位」。
|
||||
/// 谁把语言行塞进 `body`,就得先给它换签名(那会改到另外三组的文件),这条断言逼他先想一遍。
|
||||
#[test]
|
||||
fn the_general_group_does_not_use_the_appearance_body_slot() {
|
||||
assert!(
|
||||
general::GROUP.body.is_none(),
|
||||
"General 还没接真控件:接了就改本断言"
|
||||
"General 的真控件走 pages::Language 入口(见 settings/mod.rs 的 GENERAL_GROUP),不是 body"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user