fix(i18n): 复审 r1 —— 两句近义软件渲染说明合一,生效层级码翻成译文(M180)

P2-1(一屏两句重复):
- 留面板那一句(就在被禁用的滑块旁边,正好回答「为什么拖不动」),措辞同时点名面板与玻璃
- 删 `SettingsAppearanceSoftwareRendererNote`(catalog 一条 + 金表一行,两边同时删)
- 删 `bg_image.rs` 的 `backend_note` 渲染块与 `State::backend_note()`;state.rs 391 → 382 行

P2-2(配色:preset 的机器码):
- `palette::level_key(code)` 把 `preset`/`wallpaper`/其余 映射到已有的三条档位 key,不新增平行词表
- `bg_image.rs` 用 `tr(PaletteLevel, {level})` 传译文,中文界面显示「配色:内置色卡」
- 补 `every_level_code_maps_to_its_own_sentence`:三个码值一一对应 + 三条译文互不相同

顺手(复审 L-1/L-2/L-4,全在本 scope 内):
- `appearance_texts` 加「渲染后不许残留 `{`」断言(参数名写错当场红)
- `section/mod.rs` 文档:取词口 t/tr、填充措辞、清掉孤立 `//!`、把「没有一句英文」改成
  写明两处例外(`{reason}` 原文串、专名)
This commit is contained in:
AskaEth committed 2026-11-08 21:42:12 +08:00
1 parent c4c3c15285
commit 1d240ff1f2
7 files changed
+88 -32

No files matched your search

@@ -130,6 +130,24 @@ pub fn note(provenance: &Provenance) -> Option<Key> {
}) })
} }
/// 生效层级**码值** → 已有的那三条档位 key(M180 复审 P2-2:中文界面里不许留机器码)。
///
/// 码值的全部来源只有两处,取值集合相同:`Level::code()`(`background-image` / `wallpaper` /
/// `preset`)与 `PaletteSource::as_str()`(同三个词)。所以三条 key 就够,**不新增平行词表**——
/// 卡片底部「配色来源」用它们表示**用户选的**档,这一行用它们表示**回落之后真正生效**的档。
///
/// 未识别的码值落到 `background-image` 那条(宁可显示一个偏保守的档名,也不许把机器码漏进
/// 中文界面——这条兜底本身由 `tests` 里的 `every_level_code_maps_to_its_own_sentence` 钉住)。
/// `state.rs` 已贴 400 行线,所以这个映射住在本文件。
#[must_use]
pub fn level_key(code: &str) -> Key {
match code {
"preset" => Key::SettingsAppearancePalettePreset,
"wallpaper" => Key::SettingsAppearancePaletteWallpaper,
_ => Key::SettingsAppearancePaletteBackground,
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{note, resolve, Source}; use super::{note, resolve, Source};
@@ -273,4 +291,40 @@ mod tests {
None None
); );
} }
/// **M180 复审 P2-2 的守门**:生效层级的**每一个码值**都必须映射到它自己那条中文句子。
///
/// 抓得住:① 三个码值被映射到同一条 key(界面上分不出到底生效的是哪一级);
/// ② 某条映射被删掉 / 写错(`preset` 落到 background 那条 ⇒ 中文界面说「取自背景图」);
/// ③ 有人再加一级却忘了扩映射。
/// 抓不住:译文本身的措辞(那是 `catalog/settings/appearance.rs` 的译文守卫的事)。
///
/// 变异证明:把 `"preset"` 那行删掉(落进兜底)⇒ 本条红,且 `assert_ne!` 直接点名码值。
///
/// 为什么这条必须有:`the_appearance_card_speaks_chinese` 断的是**整句**非 ASCII,
/// 而「配色:preset」里有汉字 ⇒ 整句非 ASCII ⇒ 那个守卫放过它。这个口子只能在这里堵。
#[test]
fn every_level_code_maps_to_its_own_sentence() {
use super::level_key;
let codes = ["background-image", "wallpaper", "preset"];
let keys: Vec<Key> = codes.iter().copied().map(level_key).collect();
assert_eq!(
keys,
vec![
Key::SettingsAppearancePaletteBackground,
Key::SettingsAppearancePaletteWallpaper,
Key::SettingsAppearancePalettePreset,
],
"码值与档位 key 必须一一对应"
);
// 三条中文句子互不相同(否则「映射对了」但界面上仍分不出层级)。
let zh = unlucky_i18n::Lang::ZhCn.catalog();
let mut texts: Vec<&str> = keys.iter().map(|key| zh.text(*key)).collect();
let total = texts.len();
texts.sort_unstable();
texts.dedup();
assert_eq!(texts.len(), total, "三条档位译文有重复:{texts:?}");
}
} }
@@ -17,6 +17,7 @@ use crate::pages::Message;
use crate::theme::Ui; use crate::theme::Ui;
use super::super::background::{self, BG_DIM_MAX}; use super::super::background::{self, BG_DIM_MAX};
use super::super::palette;
use super::super::state::State; use super::super::state::State;
use super::super::Change; use super::super::Change;
use super::{choice, choice_row, fixed2, label, note_line, slider_row, t, tr, tr_note}; use super::{choice, choice_row, fixed2, label, note_line, slider_row, t, tr, tr_note};
@@ -66,19 +67,15 @@ pub(super) fn push<'a>(
.into(), .into(),
); );
// ⑥ 诊断(读盘失败 / CPU 降级 / 配色回落)——**不许静默**,且必须在**首屏可见处** // ⑥ 诊断(读盘失败 / 配色回落)——**不许静默**,且必须在**首屏可见处**
// (复审 r1 P2-3:文案落在折叠线以下等于没提示)。位置就在背景图那两行的紧下方。 // (复审 r1 P2-3:文案落在折叠线以下等于没提示)。位置就在背景图那两行的紧下方。
//
// CPU 档的「面板/玻璃被强制」那句**不在这里**:它是张卡级状态,挂在被禁用的面板滑块旁边
// (`panel.rs` 的 `PanelOpacityForcedHint`),一屏只出现一次(M180 复审 P2-1)。
for note in state.diagnostics() { for note in state.diagnostics() {
let text = tr_note(lang, note); let text = tr_note(lang, note);
rows.push(note_line(ui, &text, ui.color(Role::Error))); rows.push(note_line(ui, &text, ui.color(Role::Error)));
} }
if let Some(key) = state.backend_note() {
rows.push(note_line(
ui,
t(key, lang),
ui.color(Role::OnSurfaceVariant),
));
}
if let Some(note) = state if let Some(note) = state
.image() .image()
.and_then(|image| background::small_tile_note(image, state.bg_fill)) .and_then(|image| background::small_tile_note(image, state.bg_fill))
@@ -93,10 +90,12 @@ pub(super) fn push<'a>(
ui.color(Role::OnSurfaceVariant), ui.color(Role::OnSurfaceVariant),
)); ));
} }
// 生效的配色层级:显示的是**回落之后真正生效**的那一级(与底部「配色来源」里用户选的那一档
// 不一定相同)。码值 → 已有的三条档位 key(不新增平行词表),所以中文界面也读得懂。
let level = tr( let level = tr(
lang, lang,
Key::SettingsAppearancePaletteLevel, Key::SettingsAppearancePaletteLevel,
&[("level", state.palette_level())], &[("level", t(palette::level_key(state.palette_level()), lang))],
); );
rows.push(note_line(ui, &level, ui.color(Role::OnSurfaceVariant))); rows.push(note_line(ui, &level, ui.color(Role::OnSurfaceVariant)));
@@ -9,7 +9,7 @@
//! | 面板透明度滑块(下限 0.4,CPU 档禁用) | `appearance.panel_alpha` | [`Change::PanelAlpha`] | //! | 面板透明度滑块(下限 0.4,CPU 档禁用) | `appearance.panel_alpha` | [`Change::PanelAlpha`] |
//! | 背景图开关 | `appearance.bg_image_enabled` | [`Change::BgEnabled`] | //! | 背景图开关 | `appearance.bg_image_enabled` | [`Change::BgEnabled`] |
//! | 背景图路径(文本框) | `appearance.bg_image` | [`Change::BgImage`] | //! | 背景图路径(文本框) | `appearance.bg_image` | [`Change::BgImage`] |
//! | 填充(封面 / 拉伸 / 平铺) | `appearance.bg_fill` | [`Change::BgFill`] | //! | 填充(铺满 / 完整显示 / 平铺) | `appearance.bg_fill` | [`Change::BgFill`] |
//! | 暗化强度(0~0.95) | `appearance.bg_dim` | [`Change::BgDim`] | //! | 暗化强度(0~0.95) | `appearance.bg_dim` | [`Change::BgDim`] |
//! | 配色来源(三档) | `appearance.palette_source` | [`Change::PaletteSource`] | //! | 配色来源(三档) | `appearance.palette_source` | [`Change::PaletteSource`] |
//! //!
@@ -20,7 +20,7 @@
//! //!
//! | 文件 | 是什么 | //! | 文件 | 是什么 |
//! |---|---| //! |---|---|
//! | 本文件(`section/mod.rs`) | **壳**:[`section`] 按顺序拼各段 + [`card`] 卡片 + 行控件([`label`] / [`slider_row`] / [`choice_row`] / [`choice`] / [`note_line`])+ 取词口 [`t`] | //! | 本文件(`section/mod.rs`) | **壳**:[`section`] 按顺序拼各段 + [`card`] 卡片 + 行控件([`label`] / [`slider_row`] / [`choice_row`] / [`choice`] / [`note_line`])+ 两个取词口 [`t`] / [`tr`] |
//! | [`window`] | ① 整窗透明度 + ② 窗口圆角(含「因整窗 α 不生效」的 notice 与说明段) | //! | [`window`] | ① 整窗透明度 + ② 窗口圆角(含「因整窗 α 不生效」的 notice 与说明段) |
//! | [`panel`] | ③ 面板透明度(CPU 档强制 1.0,值 / 生效值分开显示) | //! | [`panel`] | ③ 面板透明度(CPU 档强制 1.0,值 / 生效值分开显示) |
//! | [`bg_image`] | ④ 背景图开关 + ⑤ 路径 + ⑥ 诊断 + ⑦ 填充 + ⑧ 暗化 | //! | [`bg_image`] | ④ 背景图开关 + ⑤ 路径 + ⑥ 诊断 + ⑦ 填充 + ⑧ 暗化 |
@@ -41,8 +41,12 @@
//! `lang` 从 `settings::view` 的 `Language.effective` → `Group.body` → [`section`] 一路递进来。 //! `lang` 从 `settings::view` 的 `Language.effective` → `Group.body` → [`section`] 一路递进来。
//! M179 只把管子接通(当时本组 27 条文案里真正本地化的只有圆角那 5 条);M180 `I18NAPPEAR` 把 //! M179 只把管子接通(当时本组 27 条文案里真正本地化的只有圆角那 5 条);M180 `I18NAPPEAR` 把
//! 剩下 22 条字面量与 `state.rs` / `background.rs` / `palette.rs` 的运行时诊断全部搬进 //! 剩下 22 条字面量与 `state.rs` / `background.rs` / `palette.rs` 的运行时诊断全部搬进
//! `catalog/settings/appearance.rs`,于是中文界面上这一页**没有一句是英文**。 //! `catalog/settings/appearance.rs`,于是中文界面上这一页的**卡片文案**没有一句是英文。
//!
//! 两处例外(M180 复审 L-1 记下的口径):① 写进 `{reason}` 的**原因串**里,来自本仓库自写的
//! 两条(`"no path configured"`、`"unknown image format …"`)与 OS / 解码库给出的文本仍是英文——
//! 它们是不可翻译的第三方或诊断原文;② `Windows 11` / `macOS` / `Linux` / `PNG` / `JPEG` / `BMP`
//! 这些专名。除此之外这一页不出现英文。
//! //!
//! ## 为什么「值」与「生效值」分开显示 //! ## 为什么「值」与「生效值」分开显示
//! //!
@@ -203,15 +203,6 @@ impl State {
&self.diagnostics &self.diagnostics
} }
/// CPU 档降级的说明 key(`None` = 没有降级)。
///
/// M180:返回 key 而不是英文句子——本文件已 390 行、贴着 400 行硬线,**不放文案表**。
#[must_use]
pub fn backend_note(&self) -> Option<Key> {
self.panel_is_forced()
.then_some(Key::SettingsAppearanceSoftwareRendererNote)
}
/// 配色来源的提示 key(`None` = 一级成功)。 /// 配色来源的提示 key(`None` = 一级成功)。
#[must_use] #[must_use]
pub fn palette_note(&self) -> Option<Key> { pub fn palette_note(&self) -> Option<Key> {
+11 -1
View File
@@ -119,6 +119,17 @@ fn appearance_texts(lang: Lang, case: Case) -> Vec<(Key, String)> {
.map(|(key, text)| (key, text.to_owned())) .map(|(key, text)| (key, text.to_owned()))
.collect(); .collect();
texts.extend(probe::take_rendered()); texts.extend(probe::take_rendered());
// 复审 L-2:`{name}` 换完还留着花括号 ⇒ 某处参数名与模板不一致(`("floor", …)` 写成
// `("flor", …)` 时句子会原样显示「下限 {floor}」,而「整句非 ASCII」的守卫看不出来)。
// 三档状态合起来覆盖本页全部 `tr()` 调用,所以这条断言是全覆盖的。
for (key, text) in &texts {
assert!(
!text.contains('{'),
"{case:?}:{} 渲染后还留着占位符「{text}」——`tr()` 的参数名与模板对不上",
key.name()
);
}
texts texts
} }
@@ -150,7 +161,6 @@ const CARD_KEYS: &[Key] = &[
Key::SettingsAppearancePanelOpacityHint, Key::SettingsAppearancePanelOpacityHint,
Key::SettingsAppearancePanelOpacityForcedValue, Key::SettingsAppearancePanelOpacityForcedValue,
Key::SettingsAppearancePanelOpacityForcedHint, Key::SettingsAppearancePanelOpacityForcedHint,
Key::SettingsAppearanceSoftwareRendererNote,
Key::SettingsAppearanceBackgroundImage, Key::SettingsAppearanceBackgroundImage,
Key::SettingsAppearanceOff, Key::SettingsAppearanceOff,
Key::SettingsAppearanceOn, Key::SettingsAppearanceOn,
+1 -2
View File
@@ -38,7 +38,7 @@ pub(super) const ROWS: &[(&str, &str, &str)] = &[
("SettingsAppearancePanelOpacity", "Panel transparency", "面板透明度"), ("SettingsAppearancePanelOpacity", "Panel transparency", "面板透明度"),
("SettingsAppearancePanelOpacityHint", "Only the panel/card layer. Floor {floor}; independent of window transparency.", "只作用于面板 / 卡片这一层。下限 {floor};与整窗透明度互不影响。"), ("SettingsAppearancePanelOpacityHint", "Only the panel/card layer. Floor {floor}; independent of window transparency.", "只作用于面板 / 卡片这一层。下限 {floor};与整窗透明度互不影响。"),
("SettingsAppearancePanelOpacityForcedValue", "100% (forced from {requested})", "100%(强制,原请求值 {requested})"), ("SettingsAppearancePanelOpacityForcedValue", "100% (forced from {requested})", "100%(强制,原请求值 {requested})"),
("SettingsAppearancePanelOpacityForcedHint", "Software renderer: panels stay fully opaque (window transparency still works).", "软件渲染:面板恒为不透明(整窗透明度仍然有效)。"), ("SettingsAppearancePanelOpacityForcedHint", "Software renderer: panels and glass stay fully opaque (window transparency still works).", "软件渲染:面板与玻璃效果恒为不透明(整窗透明度仍然有效)。"),
("SettingsAppearanceBackgroundImage", "Background image", "背景图"), ("SettingsAppearanceBackgroundImage", "Background image", "背景图"),
("SettingsAppearanceOff", "Off", "关"), ("SettingsAppearanceOff", "Off", "关"),
("SettingsAppearanceOn", "On", "开"), ("SettingsAppearanceOn", "On", "开"),
@@ -54,7 +54,6 @@ pub(super) const ROWS: &[(&str, &str, &str)] = &[
("SettingsAppearancePaletteBackground", "Background", "取自背景图"), ("SettingsAppearancePaletteBackground", "Background", "取自背景图"),
("SettingsAppearancePaletteWallpaper", "Wallpaper", "取自壁纸"), ("SettingsAppearancePaletteWallpaper", "Wallpaper", "取自壁纸"),
("SettingsAppearancePalettePreset", "Preset", "内置色卡"), ("SettingsAppearancePalettePreset", "Preset", "内置色卡"),
("SettingsAppearanceSoftwareRendererNote", "Software renderer: panel transparency and glass stay fully opaque (window transparency still works).", "软件渲染:面板透明度与玻璃效果恒为不透明(整窗透明度仍然有效)。"),
("SettingsAppearanceBgReadFailed", "Background image could not be read: {reason}", "背景图读不出来:{reason}"), ("SettingsAppearanceBgReadFailed", "Background image could not be read: {reason}", "背景图读不出来:{reason}"),
("SettingsAppearanceBgDecodeFailed", "Background image could not be decoded ({reason}; this build decodes PNG, JPEG and BMP)", "背景图解不开({reason};这个构建只支持 PNG、JPEG 与 BMP)"), ("SettingsAppearanceBgDecodeFailed", "Background image could not be decoded ({reason}; this build decodes PNG, JPEG and BMP)", "背景图解不开({reason};这个构建只支持 PNG、JPEG 与 BMP)"),
("SettingsAppearanceBgTooLarge", "Background image is too large to decode: {width}×{height} (limit {limit} px per edge)", "背景图太大,解不开:{width}×{height}(每边上限 {limit} 像素)"), ("SettingsAppearanceBgTooLarge", "Background image is too large to decode: {width}×{height} (limit {limit} px per edge)", "背景图太大,解不开:{width}×{height}(每边上限 {limit} 像素)"),
@@ -70,8 +70,8 @@ macro_rules! settings_appearance_entries {
"100%(强制,原请求值 {requested})" "100%(强制,原请求值 {requested})"
), ),
SettingsAppearancePanelOpacityForcedHint => ( SettingsAppearancePanelOpacityForcedHint => (
"Software renderer: panels stay fully opaque (window transparency still works).", "Software renderer: panels and glass stay fully opaque (window transparency still works).",
"软件渲染:面板恒为不透明(整窗透明度仍然有效)。" "软件渲染:面板与玻璃效果恒为不透明(整窗透明度仍然有效)。"
), ),
SettingsAppearanceBackgroundImage => ("Background image", "背景图"), SettingsAppearanceBackgroundImage => ("Background image", "背景图"),
SettingsAppearanceOff => ("Off", "关"), SettingsAppearanceOff => ("Off", "关"),
@@ -91,11 +91,10 @@ macro_rules! settings_appearance_entries {
SettingsAppearancePaletteBackground => ("Background", "取自背景图"), SettingsAppearancePaletteBackground => ("Background", "取自背景图"),
SettingsAppearancePaletteWallpaper => ("Wallpaper", "取自壁纸"), SettingsAppearancePaletteWallpaper => ("Wallpaper", "取自壁纸"),
SettingsAppearancePalettePreset => ("Preset", "内置色卡"), SettingsAppearancePalettePreset => ("Preset", "内置色卡"),
// ---- M180:运行时诊断(`state.rs` 的 backend_note + `background.rs` 的三种读盘失败 + 小图平铺) // ---- M180:运行时诊断(`background.rs` 的三种读盘失败 + 小图平铺)。
SettingsAppearanceSoftwareRendererNote => ( //
"Software renderer: panel transparency and glass stay fully opaque (window transparency still works).", // M180 r1 复审 P2-1:软件渲染那句**只有一条**,就挂在面板那一行(`PanelOpacityForcedHint`)
"软件渲染:面板透明度与玻璃效果恒为不透明(整窗透明度仍然有效)。" // 上——它挨着被禁用的滑块,正好回答「为什么拖不动」;这里不再有第二句。
),
SettingsAppearanceBgReadFailed => ( SettingsAppearanceBgReadFailed => (
"Background image could not be read: {reason}", "Background image could not be read: {reason}",
"背景图读不出来:{reason}" "背景图读不出来:{reason}"