feat(i18n): 外观页 40 条文案进词表(M180 I18NAPPEAR)

- 卡片内 22 处硬编码英文(整窗/面板透明度、背景图开关+路径、填充、暗化、配色来源)全部改走 Key
- 运行时诊断改成 key + 参数:`background::Note`(读盘/解码/过大/小图平铺)、`state::backend_note`、
  `palette::note` 全部返回 Key;`state.rs` 只留 key、不放文案表(390 → 391 行)
- 上限/下限常量走占位符 `{floor}` / `{max}` / `{limit}` / `{requested}`;`{:.2}` 在 Rust 侧格式化好
  再套模板(`Catalog::render` 只认 `{name}`)
- 新增 40 条 key(中英各一条),按装配链锚点插在 `SettingsAppearanceCornerSuspended` 之后,
  金表 `golden/tail.rs` 同位置插入 40 行(`rows() == len() == Key::ALL.len()`)
- 守卫三条:中文渲染全非 ASCII、卡片 key 全覆盖(改回写死英文即红)、外观 key 逐条真翻过
- probe 加第二条取证路(`record_rendered`),`take()` 签名不动(M179 的守卫在 scope 外)
This commit is contained in:
AskaEth committed 2026-11-08 21:42:11 +08:00
1 parent 65e7ccc3bf
commit c4c3c15285
14 files changed
+858 -184

No files matched your search

+25 -3
View File
@@ -10,8 +10,9 @@
//! - 想走 `Widget::operate` 得自己造 `Tree` + 跑布局 + 传 `Renderer`,而单测里**没有渲染器**
//! (`pages/download/tests/options_screen.rs` 记的正是这条边界)。
//!
//! 所以取证点退到「字进树之前」那一层:`section::t` 是本分组**唯一**的取词口,它把每条译文
//! 顺手记一份,测试再断言这批字里有没有汉字。这不是「看源码字符串」那种假守卫——它要求真的把
//! 所以取证点退到「字进树之前」那一层:`section::t` / `section::tr` 是本分组**唯一**两个取词口,
//! 它们把每条译文顺手记一份(M180 起分成两份:`t()` 的 `&'static str` 与 `tr()` 替换完占位符的
//! 整句),测试再断言这批字里有没有汉字。这不是「看源码字符串」那种假守卫——它要求真的把
//! [`crate::pages::appearance::section`] 跑一遍(并且走 `settings::view` 的语言分发),只是不画像素。
//!
//! ## 为什么是 thread-local
@@ -25,7 +26,7 @@ use std::cell::RefCell;
use unlucky_i18n::catalog::Key;
// 自上次 `take` 以来记录下的 `(key, 译文)`,顺序 = 取词顺序。
// 自上次 `take` 以来记录下的 `(key, 译文)`,顺序 = 取词顺序(`t()` 取的 `&'static str`)。
thread_local! {
// clippy 1.99.1(= 本仓 CI 的 stable)对这条 lint **误报**:初始化器已经写成 `const { … }`
// 它还是报「can be made const」。最小复现(clippy-driver 原地可验,无本仓任何符号):
@@ -35,12 +36,33 @@ thread_local! {
static TEXTS: RefCell<Vec<(Key, &'static str)>> = const { RefCell::new(Vec::new()) };
}
// 自上次 `take_rendered` 以来记录下的 `(key, 整句)`:`tr()` 那条带占位符的路径(M180 加)。
//
// 为什么单独一份而不是把上面那份改成 `String`:M179 的守卫
// (`pages/settings/tests/appearance_section.rs::the_appearance_group_speaks_chinese_when_the_interface_does`)
// 断言的是 `&'static str`,那个文件不在 M180 的 scope 里、不能跟着改签名。两份记录各自的
// `take*` 也各自清空,互不干扰。
thread_local! {
#[allow(clippy::missing_const_for_thread_local)]
static RENDERED: RefCell<Vec<(Key, String)>> = const { RefCell::new(Vec::new()) };
}
/// 记一条(由 [`super::section::t`] 调用;不对外)。
pub(crate) fn record(key: Key, text: &'static str) {
TEXTS.with(|texts| texts.borrow_mut().push((key, text)));
}
/// 记一条**替换完占位符**的整句(由 [`super::section::tr`] 调用;不对外)。
pub(crate) fn record_rendered(key: Key, text: String) {
RENDERED.with(|texts| texts.borrow_mut().push((key, text)));
}
/// 取走并清空本线程记录下的取词(测试用)。
pub(crate) fn take() -> Vec<(Key, &'static str)> {
TEXTS.with(|texts| std::mem::take(&mut *texts.borrow_mut()))
}
/// 取走并清空本线程记录下的整句(测试用)。
pub(crate) fn take_rendered() -> Vec<(Key, String)> {
RENDERED.with(|texts| std::mem::take(&mut *texts.borrow_mut()))
}