refactor(pages,i18n): M99 纯搬移 —— 导航表抽 pages/nav.rs、设置页拆目录、catalog 按域拆开

- pages/mod.rs 400 -> 356 行:NAV 表与组件版 rail 搬进 pages/nav.rs(NAV 可见性变 pub(crate),
  只为让 pages/tests.rs 的三条断言原样保留)
- pages/settings.rs -> pages/settings/{mod,frame,general,appearance,java,download,ai,tests}.rs:
  mod.rs 只留「分组注册表 + 选中分组 + 内容分发」,一组一个文件,为四条 section 线各留位置
- i18n catalog.rs 310 -> 273 行:条目表按域拆到 catalog/<域>.rs,catalog.rs 留 catalog! 宏、
  装配链、Catalog 与对外 API;新增 catalog/golden.rs(拆分前 key->译文 全量快照)守门
- 行为/文案/样式零变化:pages_demo 的 instances/settings × 浅/深 四张图逐字节相同
This commit is contained in:
AskaEth committed 2026-11-03 13:19:17 +08:00
1 parent 4b1471a60d
commit 330f7216fd
29 files changed
+1173 -541

No files matched your search

+85 -122
View File
@@ -23,6 +23,44 @@
//! 表格是手写的,缺条目的风险回来了;
//! 2. **回归守门**:万一有人把这张表改回「两套各写一遍」,[`Catalog::missing`] 的守卫测试立刻红。
//!
//! ## 按域拆开(M99 `SETTINGSFRAME`)
//!
//! 条目表原先整张写在本文件里,而**设置页正文接下来由四条 mission 并行接**(java / download /
//! language+appearance / ai+about),每条都要往这张表里加一批 key——同一张表就是四条线的冲突点。
//! 现在一个域一个文件(`catalog/<域>.rs`),本文件只留:`catalog!` 宏、装配链、[`Catalog`] 与对外 API。
//!
//! | 文件 | 域 | 谁会长它 |
//! |---|---|---|
//! | [`nav`] / [`instances`] / [`loader`] / [`launch`] / [`font`] / [`core_text`] | 页面与运行时文案 | 各自页面 |
//! | [`download`] / [`logs`] | 下载页 / 日志页(M94 / M95) | 那两页 |
//! | `settings/`(`general` / `appearance` / `java` / `download` / `ai`) | 设置页,**一个分组一个文件** | 设置页四条线 |
//!
//! ### 域文件的形状(加 key 时照抄)
//!
//! ```ignore
//! macro_rules! <域>_entries {
//! ($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
//! $next! { $($rest,)* {
//! $($acc)*
//! NavInstances => ("Instances", "实例"),
//! } }
//! };
//! }
//! pub(super) use <域>_entries;
//! ```
//!
//! 它把本域条目**追加**到累积表 `$acc` 之后,再把「还没装配的域」原样交给 `$next`——最后交给
//! [`finish`],由它把整张表喂给 [`catalog!`]。**为什么非得这么绕**:`macro_rules!` 不能在
//! 宏参数里展开另一个宏(`catalog! { nav_entries!() }` 只会把 `nav_entries!()` 当成普通 token),
//! 也不能用元变量当宏名(`macro_rules! $name` 非法),所以只能「回调链」式累积。代价是每个域文件
//! 都要重抄上面那 6 行签名——这是 `macro_rules!` 的固有成本,不是可以省掉的样板。
//!
//! ### 拆开之后仍然只有一个真相源
//!
//! 装配顺序写在下面那条调用里,**它等于 `Key` 的声明顺序**;[`golden`] 把拆分前那份
//! `key -> (en, zh-CN)` 全量逐字节钉住(连顺序一起钉),所以「搬丢一条 / 两条互抄 / 顺序被调换」
//! 都会红。
//!
//! ## 缺译时给什么
//!
//! **不回落**(绝不拿另一种语言顶替,用户会看到「一半英文一半中文」的界面而不自知):
@@ -66,132 +104,59 @@ macro_rules! catalog {
};
}
catalog! {
// ── 导航轨(`pages::Route` 的四项)
NavInstances => ("Instances", "实例"),
NavDownload => ("Downloads", "下载"),
NavLogs => ("Logs", "日志"),
NavSettings => ("Settings", "设置"),
// --------------------------------------------------------------- 域装配
// ── 实例页
InstancesNew => ("New instance", "新建实例"),
InstancesImport => ("Import", "导入"),
InstancesBack => ("Back to instances", "返回实例列表"),
InstancesPlay => ("Play", "启动"),
InstancesLastPlayed => ("Last played {when}", "上次游玩 {when}"),
InstancesEmptyTitle => ("No instance selected", "未选择实例"),
InstancesEmptyBody => ("Pick an instance to see its details.", "选择一个实例查看详情。"),
/// 链的终点:把累积起来的所有条目交给 [`catalog!`],生成 `Key` 与两张表。
macro_rules! finish {
({ $($acc:tt)* }) => { catalog! { $($acc)* } };
}
// ── 加载器(品牌名不翻译:Fabric / Forge / Quilt 一律原样,只有「原版」是词)
LoaderVanilla => ("Vanilla", "原版"),
mod core_text;
mod download;
mod font;
mod instances;
mod launch;
mod loader;
mod logs;
mod nav;
mod settings;
// ── 启动结局(原来 UI 把 "Launch failed" 与 core 的「静默失败」拼成一句话,现在一句话 = 一个 key)
LaunchOutcomeNormal => ("Finished normally", "正常结束"),
LaunchOutcomeCrash => ("The game crashed", "游戏崩溃"),
LaunchOutcomeSilent => (
"Suspected silent failure: exit code 0, but the game never really started",
"疑似静默失败:退出码 0,但游戏没真正跑起来"
),
LaunchFailedWithCode => ("Launch failed (exit {code}): {reason}", "启动失败(退出码 {code}):{reason}"),
#[cfg(test)]
mod golden;
// ── 中文字体(D28:中文环境缺字体时的下载提示 / 失败回退)
FontCjkNeedsDownload => (
"No Chinese font found on this system. Download an open-source (OFL) Chinese font?",
"系统里没找到中文字体。要下载一套开源(OFL)中文字体吗?"
),
FontCjkDownloading => ("Downloading Chinese font… {percent}%", "正在下载中文字体… {percent}%"),
FontCjkDownloadFailed => (
"Chinese font download failed; the interface stays in English.",
"中文字体下载失败,界面暂时保持英文。"
),
FontCjkReady => ("Chinese font ready: {family}", "中文字体已就绪:{family}"),
#[cfg(test)]
mod tests;
// ── 设置
SettingsLanguage => ("Language", "界面语言"),
SettingsLanguageHint => (
"Switches the interface language. Chinese text needs a Chinese font.",
"切换界面语言。显示中文需要中文字体。"
),
use core_text::core_text_entries;
use download::download_entries;
use font::font_entries;
use instances::instances_entries;
use launch::launch_entries;
use loader::loader_entries;
use logs::logs_entries;
use nav::nav_entries;
use settings::{
settings_ai_entries, settings_appearance_entries, settings_download_entries,
settings_general_entries, settings_java_entries,
};
// ── core / presenter 只给了中文串、UI 又不许画原文时的兜底(见 `bridge`)
CoreTextUntranslated => ("[system text not translated]", "[未翻译的系统文案]"),
// --- download page (M94) ---
// 下载页(`crates/app-iced/src/pages/download`)。一句话一个 key:带变量的句子用 `{name}`,
// 页面只做 `Catalog::render`,不拼片段(`bridge` 的规矩 1)。
//
// 文案口径(M94 r1 / P2-2):**只说这个页面真能做到的动作**(切分类、刷新、看日志),
// 不写「稍后会自动重试」这类 UI 没有的能力,也不拿文案去补功能。
DownloadTitle => ("Downloads", "下载"),
DownloadSubtitle => ("Versions you can install", "可以安装的版本"),
DownloadRefresh => ("Refresh", "刷新"),
DownloadInstall => ("Install", "安装"),
DownloadCancel => ("Cancel install", "取消安装"),
DownloadInstalled => ("Installed", "已安装"),
DownloadCount => ("{count} versions", "{count} 个版本"),
DownloadRowSupporting => ("{kind} · released {date}", "{kind} · 发布于 {date}"),
DownloadSelectHint => ("Pick a version, then install", "选一个版本再安装"),
DownloadLoading => ("Loading versions…", "正在加载版本列表…"),
DownloadLoadingHint => (
"If this stays here, press Refresh in the top right.",
"如果一直停在这里,点右上角的刷新。"
),
DownloadEmptyTitle => ("No versions to show", "没有可显示的版本"),
DownloadEmptyBody => (
"Pick another category, or refresh the list.",
"换一个分类,或刷新一次列表。"
),
DownloadCategoryAll => ("All", "全部"),
DownloadCategoryRelease => ("Release", "正式版"),
DownloadCategorySnapshot => ("Snapshot", "预览版"),
DownloadCategoryAncient => ("Ancient", "远古版"),
DownloadStagePlanning => ("Planning", "解析版本"),
DownloadStageDownloading => ("Downloading files", "下载文件"),
DownloadStageInstalling => ("Building the version", "生成版本"),
DownloadStageRegistering => ("Registering the instance", "登记实例"),
DownloadStageDone => ("Done", "已完成"),
DownloadStageFailed => ("Install failed", "安装失败"),
DownloadStageCancelled => ("Install cancelled", "安装已取消"),
DownloadProgress => (
"{done}/{total} files",
"{done}/{total} 文件"
),
DownloadProgressUnknown => ("Waiting for the first file…", "等第一个文件…"),
// 速率与剩余时间是两个独立短语,各占一个 key(`DownloadRate` 只负责把它们排成一行)
DownloadRate => ("{speed} · {eta}", "{speed},{eta}"),
DownloadEtaSecs => ("{secs}s left", "剩 {secs}s"),
DownloadEtaMins => ("{mins}m{secs}s left", "剩 {mins}m{secs}s"),
DownloadFailedHint => (
"The Logs page has the full reason.",
"完整原因在日志页。"
),
DownloadOpenLogs => ("Open logs", "打开日志"),
DownloadDismiss => ("Dismiss", "关闭"),
// --- logs page (M95) ---
// 日志页的**界面文案**(标题 / 按钮 / 分组名 / 空态)。诊断结论的正文不在这里:
// 那是规则表 `rules.d/*.yaml` 的输出(数据),见 `pages/logs/diagnostic.rs` 的模块文档。
// `LogsStatus` 是**模板**:中英差别落在分隔符上(中文用全角竖线——`·` 是外来符号,
// 中文正文里偏挤;这也让「中文目录必须与英文不同」这条守卫成立,不是为过测试硬造差异)。
LogsSubtitleIdle => ("Nothing is running right now", "当前没有运行中的实例"),
LogsStatus => ("{instance} · {when} · {outcome}", "{instance}|{when}|{outcome}"),
LogsOpenFolder => ("Open folder", "打开目录"),
LogsClear => ("Clear", "清空"),
LogsOutputTitle => ("OUTPUT", "输出"),
LogsDiagnosisTitle => ("DIAGNOSIS", "诊断"),
LogsConfidenceHigh => ("HIGH CONFIDENCE", "高置信度"),
LogsConfidenceMedium => ("MEDIUM CONFIDENCE", "中置信度"),
LogsConfidenceLow => ("LOW CONFIDENCE", "低置信度"),
LogsSubtitleLive => ("Running now — logs stream as they arrive", "正在运行 — 日志实时滚动"),
LogsConfidence => ("Confidence {percent}%", "置信度 {percent}%"),
LogsSuggestedFix => ("Suggested fix", "建议动作"),
LogsSummaryLabel => ("One-line summary", "一行摘要"),
LogsCopySummary => ("Copy summary", "复制摘要"),
LogsEmptyTitle => ("No output yet", "还没有输出"),
LogsEmptyBody => (
"Logs stream here while the launcher or the game is running. Pick an instance and start it to see the first lines.",
"启动器或游戏运行时,日志会实时出现在这里。选择一个实例并启动,就能看到第一批日志。"
),
LogsEmptyAction => ("Choose an instance", "选择实例"),
// 装配顺序 = `Key` 的声明顺序。**与拆分前逐条一致**,动它等于动 `Key` 的 `Ord` 与导出表顺序,
// 金表(`catalog/golden.rs`)会红。设置页那五个域按页面上的分组顺序排,中间三个现在是空的。
nav_entries! {
instances_entries,
loader_entries,
launch_entries,
font_entries,
settings_general_entries,
settings_appearance_entries,
settings_java_entries,
settings_download_entries,
settings_ai_entries,
core_text_entries,
download_entries,
logs_entries,
finish,
{}
}
/// 一套语言的目录(不可变;`Copy`,随手传值)。
@@ -306,5 +271,3 @@ pub fn placeholders(text: &str) -> Vec<&str> {
}
found
}
#[cfg(test)]
mod tests;
+15
View File
@@ -0,0 +1,15 @@
//! core / presenter 只给了中文串、UI 又不许画原文时的兜底文案(口径见 [`crate::bridge`])。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节。
macro_rules! core_text_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// ── core / presenter 只给了中文串、UI 又不许画原文时的兜底(见 `bridge`)
CoreTextUntranslated => ("[system text not translated]", "[未翻译的系统文案]"),
} }
};
}
pub(crate) use core_text_entries;
+65
View File
@@ -0,0 +1,65 @@
//! 下载页文案(M94)。
//!
//! 一句话一个 key:带变量的句子用 `{name}`,页面只做 [`crate::Catalog::render`],不拼片段
//! ([`crate::bridge`] 的规矩 1)。
//!
//! 文案口径(M94 r1 / P2-2):**只说这个页面真能做到的动作**(切分类、刷新、看日志),
//! 不写「稍后会自动重试」这类 UI 没有的能力,也不拿文案去补功能。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节。
macro_rules! download_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// --- download page (M94) ---
DownloadTitle => ("Downloads", "下载"),
DownloadSubtitle => ("Versions you can install", "可以安装的版本"),
DownloadRefresh => ("Refresh", "刷新"),
DownloadInstall => ("Install", "安装"),
DownloadCancel => ("Cancel install", "取消安装"),
DownloadInstalled => ("Installed", "已安装"),
DownloadCount => ("{count} versions", "{count} 个版本"),
DownloadRowSupporting => ("{kind} · released {date}", "{kind} · 发布于 {date}"),
DownloadSelectHint => ("Pick a version, then install", "选一个版本再安装"),
DownloadLoading => ("Loading versions…", "正在加载版本列表…"),
DownloadLoadingHint => (
"If this stays here, press Refresh in the top right.",
"如果一直停在这里,点右上角的刷新。"
),
DownloadEmptyTitle => ("No versions to show", "没有可显示的版本"),
DownloadEmptyBody => (
"Pick another category, or refresh the list.",
"换一个分类,或刷新一次列表。"
),
DownloadCategoryAll => ("All", "全部"),
DownloadCategoryRelease => ("Release", "正式版"),
DownloadCategorySnapshot => ("Snapshot", "预览版"),
DownloadCategoryAncient => ("Ancient", "远古版"),
DownloadStagePlanning => ("Planning", "解析版本"),
DownloadStageDownloading => ("Downloading files", "下载文件"),
DownloadStageInstalling => ("Building the version", "生成版本"),
DownloadStageRegistering => ("Registering the instance", "登记实例"),
DownloadStageDone => ("Done", "已完成"),
DownloadStageFailed => ("Install failed", "安装失败"),
DownloadStageCancelled => ("Install cancelled", "安装已取消"),
DownloadProgress => (
"{done}/{total} files",
"{done}/{total} 文件"
),
DownloadProgressUnknown => ("Waiting for the first file…", "等第一个文件…"),
// 速率与剩余时间是两个独立短语,各占一个 key(`DownloadRate` 只负责把它们排成一行)
DownloadRate => ("{speed} · {eta}", "{speed},{eta}"),
DownloadEtaSecs => ("{secs}s left", "剩 {secs}s"),
DownloadEtaMins => ("{mins}m{secs}s left", "剩 {mins}m{secs}s"),
DownloadFailedHint => (
"The Logs page has the full reason.",
"完整原因在日志页。"
),
DownloadOpenLogs => ("Open logs", "打开日志"),
DownloadDismiss => ("Dismiss", "关闭"),
} }
};
}
pub(crate) use download_entries;
+24
View File
@@ -0,0 +1,24 @@
//! 中文字体文案(D28):中文环境缺字体时的下载提示 / 失败回退。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节。
macro_rules! font_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// ── 中文字体(D28:中文环境缺字体时的下载提示 / 失败回退)
FontCjkNeedsDownload => (
"No Chinese font found on this system. Download an open-source (OFL) Chinese font?",
"系统里没找到中文字体。要下载一套开源(OFL)中文字体吗?"
),
FontCjkDownloading => ("Downloading Chinese font… {percent}%", "正在下载中文字体… {percent}%"),
FontCjkDownloadFailed => (
"Chinese font download failed; the interface stays in English.",
"中文字体下载失败,界面暂时保持英文。"
),
FontCjkReady => ("Chinese font ready: {family}", "中文字体已就绪:{family}"),
} }
};
}
pub(crate) use font_entries;
+97
View File
@@ -0,0 +1,97 @@
//! 拆分前的 `key -> (en, zh-CN)` **逐字快照**(金表)。
//!
//! ## 它是什么
//!
//! M99 把 `catalog.rs` 的单张条目表按域拆到 `catalog/<域>.rs` 之后,这 72 行就是「拆分前那一份」
//! 的原样导出:由拆分前的 `Catalog::of(En)` / `Catalog::of(ZhCn)` 逐条 `println!` 出来,
//! 再原封不动抄进来(生成方式见 `catalog/tests.rs` 里 `split_keeps_every_translation` 的注释)。
//!
//! ## 为什么不是「数条数」
//!
//! 条数相等抓不住任何一种真会发生的坏法:某条译文在搬移时被漏掉、另一条被复制两次,条数照旧。
//! 这张表比的是**全量、逐字节、按声明顺序**的三元组,于是下面四种坏法都会红:
//!
//! | 坏法 | 红在哪 |
//! |---|---|
//! | 某个 key 整条被搬丢 | 条数不等 / 该行 key 名对不上 |
//! | 两条 key 的译文互抄(同 key 被覆盖到别的译文) | 该行 en 或 zh 不等 |
//! | 某条译文改了一个字符 | 该行不等(`assert_eq!` 带原文) |
//! | 域的顺序被调换(`Key` 的 `Ord` 与导出表顺序都靠它) | 该行 key 名对不上 |
//!
//! **抓不住**:译文本身翻得对不对(金表是「拆分前」的快照,它只能证明「没变」)。
/// 拆分前 `Key::ALL` 的顺序 × (en, zh-CN)。第一列是变体名(`Key::name()` 的返回值)。
pub(super) const GOLDEN: &[(&str, &str, &str)] = &[
("NavInstances", "Instances", "实例"),
("NavDownload", "Downloads", "下载"),
("NavLogs", "Logs", "日志"),
("NavSettings", "Settings", "设置"),
("InstancesNew", "New instance", "新建实例"),
("InstancesImport", "Import", "导入"),
("InstancesBack", "Back to instances", "返回实例列表"),
("InstancesPlay", "Play", "启动"),
("InstancesLastPlayed", "Last played {when}", "上次游玩 {when}"),
("InstancesEmptyTitle", "No instance selected", "未选择实例"),
("InstancesEmptyBody", "Pick an instance to see its details.", "选择一个实例查看详情。"),
("LoaderVanilla", "Vanilla", "原版"),
("LaunchOutcomeNormal", "Finished normally", "正常结束"),
("LaunchOutcomeCrash", "The game crashed", "游戏崩溃"),
("LaunchOutcomeSilent", "Suspected silent failure: exit code 0, but the game never really started", "疑似静默失败:退出码 0,但游戏没真正跑起来"),
("LaunchFailedWithCode", "Launch failed (exit {code}): {reason}", "启动失败(退出码 {code}):{reason}"),
("FontCjkNeedsDownload", "No Chinese font found on this system. Download an open-source (OFL) Chinese font?", "系统里没找到中文字体。要下载一套开源(OFL)中文字体吗?"),
("FontCjkDownloading", "Downloading Chinese font… {percent}%", "正在下载中文字体… {percent}%"),
("FontCjkDownloadFailed", "Chinese font download failed; the interface stays in English.", "中文字体下载失败,界面暂时保持英文。"),
("FontCjkReady", "Chinese font ready: {family}", "中文字体已就绪:{family}"),
("SettingsLanguage", "Language", "界面语言"),
("SettingsLanguageHint", "Switches the interface language. Chinese text needs a Chinese font.", "切换界面语言。显示中文需要中文字体。"),
("CoreTextUntranslated", "[system text not translated]", "[未翻译的系统文案]"),
("DownloadTitle", "Downloads", "下载"),
("DownloadSubtitle", "Versions you can install", "可以安装的版本"),
("DownloadRefresh", "Refresh", "刷新"),
("DownloadInstall", "Install", "安装"),
("DownloadCancel", "Cancel install", "取消安装"),
("DownloadInstalled", "Installed", "已安装"),
("DownloadCount", "{count} versions", "{count} 个版本"),
("DownloadRowSupporting", "{kind} · released {date}", "{kind} · 发布于 {date}"),
("DownloadSelectHint", "Pick a version, then install", "选一个版本再安装"),
("DownloadLoading", "Loading versions…", "正在加载版本列表…"),
("DownloadLoadingHint", "If this stays here, press Refresh in the top right.", "如果一直停在这里,点右上角的刷新。"),
("DownloadEmptyTitle", "No versions to show", "没有可显示的版本"),
("DownloadEmptyBody", "Pick another category, or refresh the list.", "换一个分类,或刷新一次列表。"),
("DownloadCategoryAll", "All", "全部"),
("DownloadCategoryRelease", "Release", "正式版"),
("DownloadCategorySnapshot", "Snapshot", "预览版"),
("DownloadCategoryAncient", "Ancient", "远古版"),
("DownloadStagePlanning", "Planning", "解析版本"),
("DownloadStageDownloading", "Downloading files", "下载文件"),
("DownloadStageInstalling", "Building the version", "生成版本"),
("DownloadStageRegistering", "Registering the instance", "登记实例"),
("DownloadStageDone", "Done", "已完成"),
("DownloadStageFailed", "Install failed", "安装失败"),
("DownloadStageCancelled", "Install cancelled", "安装已取消"),
("DownloadProgress", "{done}/{total} files", "{done}/{total} 文件"),
("DownloadProgressUnknown", "Waiting for the first file…", "等第一个文件…"),
("DownloadRate", "{speed} · {eta}", "{speed},{eta}"),
("DownloadEtaSecs", "{secs}s left", "剩 {secs}s"),
("DownloadEtaMins", "{mins}m{secs}s left", "剩 {mins}m{secs}s"),
("DownloadFailedHint", "The Logs page has the full reason.", "完整原因在日志页。"),
("DownloadOpenLogs", "Open logs", "打开日志"),
("DownloadDismiss", "Dismiss", "关闭"),
("LogsSubtitleIdle", "Nothing is running right now", "当前没有运行中的实例"),
("LogsStatus", "{instance} · {when} · {outcome}", "{instance}|{when}|{outcome}"),
("LogsOpenFolder", "Open folder", "打开目录"),
("LogsClear", "Clear", "清空"),
("LogsOutputTitle", "OUTPUT", "输出"),
("LogsDiagnosisTitle", "DIAGNOSIS", "诊断"),
("LogsConfidenceHigh", "HIGH CONFIDENCE", "高置信度"),
("LogsConfidenceMedium", "MEDIUM CONFIDENCE", "中置信度"),
("LogsConfidenceLow", "LOW CONFIDENCE", "低置信度"),
("LogsSubtitleLive", "Running now — logs stream as they arrive", "正在运行 — 日志实时滚动"),
("LogsConfidence", "Confidence {percent}%", "置信度 {percent}%"),
("LogsSuggestedFix", "Suggested fix", "建议动作"),
("LogsSummaryLabel", "One-line summary", "一行摘要"),
("LogsCopySummary", "Copy summary", "复制摘要"),
("LogsEmptyTitle", "No output yet", "还没有输出"),
("LogsEmptyBody", "Logs stream here while the launcher or the game is running. Pick an instance and start it to see the first lines.", "启动器或游戏运行时,日志会实时出现在这里。选择一个实例并启动,就能看到第一批日志。"),
("LogsEmptyAction", "Choose an instance", "选择实例"),
];
+21
View File
@@ -0,0 +1,21 @@
//! 实例页文案:页头动作、返回、上次游玩时间、空态。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节。
macro_rules! instances_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// ── 实例页
InstancesNew => ("New instance", "新建实例"),
InstancesImport => ("Import", "导入"),
InstancesBack => ("Back to instances", "返回实例列表"),
InstancesPlay => ("Play", "启动"),
InstancesLastPlayed => ("Last played {when}", "上次游玩 {when}"),
InstancesEmptyTitle => ("No instance selected", "未选择实例"),
InstancesEmptyBody => ("Pick an instance to see its details.", "选择一个实例查看详情。"),
} }
};
}
pub(crate) use instances_entries;
+21
View File
@@ -0,0 +1,21 @@
//! 启动结局文案:一句话 = 一个 key(原来 UI 把 `"Launch failed"` 与 core 的「静默失败」拼成一句话)。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节。
macro_rules! launch_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// ── 启动结局(原来 UI 把 "Launch failed" 与 core 的「静默失败」拼成一句话,现在一句话 = 一个 key)
LaunchOutcomeNormal => ("Finished normally", "正常结束"),
LaunchOutcomeCrash => ("The game crashed", "游戏崩溃"),
LaunchOutcomeSilent => (
"Suspected silent failure: exit code 0, but the game never really started",
"疑似静默失败:退出码 0,但游戏没真正跑起来"
),
LaunchFailedWithCode => ("Launch failed (exit {code}): {reason}", "启动失败(退出码 {code}):{reason}"),
} }
};
}
pub(crate) use launch_entries;
+15
View File
@@ -0,0 +1,15 @@
//! 加载器文案:品牌名不翻译(Fabric / Forge / Quilt 一律原样),只有「原版」是一个词。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节。
macro_rules! loader_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// ── 加载器(品牌名不翻译:Fabric / Forge / Quilt 一律原样,只有「原版」是词)
LoaderVanilla => ("Vanilla", "原版"),
} }
};
}
pub(crate) use loader_entries;
+40
View File
@@ -0,0 +1,40 @@
//! 日志页文案(M95)。
//!
//! 日志页的**界面文案**(标题 / 按钮 / 分组名 / 空态)。诊断结论的正文不在这里:
//! 那是规则表 `rules.d/*.yaml` 的输出(数据),见 `pages/logs/diagnostic.rs` 的模块文档。
//!
//! `LogsStatus` 是**模板**:中英差别落在分隔符上(中文用全角竖线——`·` 是外来符号,
//! 中文正文里偏挤;这也让「中文目录必须与英文不同」这条守卫成立,不是为过测试硬造差异)。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节。
macro_rules! logs_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// --- logs page (M95) ---
LogsSubtitleIdle => ("Nothing is running right now", "当前没有运行中的实例"),
LogsStatus => ("{instance} · {when} · {outcome}", "{instance}|{when}|{outcome}"),
LogsOpenFolder => ("Open folder", "打开目录"),
LogsClear => ("Clear", "清空"),
LogsOutputTitle => ("OUTPUT", "输出"),
LogsDiagnosisTitle => ("DIAGNOSIS", "诊断"),
LogsConfidenceHigh => ("HIGH CONFIDENCE", "高置信度"),
LogsConfidenceMedium => ("MEDIUM CONFIDENCE", "中置信度"),
LogsConfidenceLow => ("LOW CONFIDENCE", "低置信度"),
LogsSubtitleLive => ("Running now — logs stream as they arrive", "正在运行 — 日志实时滚动"),
LogsConfidence => ("Confidence {percent}%", "置信度 {percent}%"),
LogsSuggestedFix => ("Suggested fix", "建议动作"),
LogsSummaryLabel => ("One-line summary", "一行摘要"),
LogsCopySummary => ("Copy summary", "复制摘要"),
LogsEmptyTitle => ("No output yet", "还没有输出"),
LogsEmptyBody => (
"Logs stream here while the launcher or the game is running. Pick an instance and start it to see the first lines.",
"启动器或游戏运行时,日志会实时出现在这里。选择一个实例并启动,就能看到第一批日志。"
),
LogsEmptyAction => ("Choose an instance", "选择实例"),
} }
};
}
pub(crate) use logs_entries;
+19
View File
@@ -0,0 +1,19 @@
//! 导航轨文案:四个一级页。AI 独立页没有导航高亮项(`Route::Agent.nav()` 是 `None`),所以没有 key。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节:本宏把条目**追加**到 `$acc` 之后,
//! 再把「还没装配的域」原样交给 `$next`。条目顺序 = `Key` 的声明顺序(金表 `catalog::golden` 守门)。
macro_rules! nav_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// ── 导航轨(`pages::Route` 的四项)
NavInstances => ("Instances", "实例"),
NavDownload => ("Downloads", "下载"),
NavLogs => ("Logs", "日志"),
NavSettings => ("Settings", "设置"),
} }
};
}
pub(crate) use nav_entries;
+13
View File
@@ -0,0 +1,13 @@
//! 设置页「AI / About」分组文案:**预留**(页面上还没有这个分组,见 `pages/settings/mod.rs` 的
//! 分组注册表)。
//!
//! 加 key 时把条目写进 `$($acc)*` 后面即可(形状见 [`crate::catalog`] 的「域装配」一节)。
//! 现在没有条目,所以本宏是**空转移器**:只把累积表原样交给下一个域。
macro_rules! settings_ai_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* { $($acc)* } }
};
}
pub(crate) use settings_ai_entries;
@@ -0,0 +1,12 @@
//! 设置页「Appearance」分组文案:**预留**(真控件在 `pages/appearance/section.rs`,文案还没接 i18n)。
//!
//! 加 key 时把条目写进 `$($acc)*` 后面即可(形状见 [`crate::catalog`] 的「域装配」一节)。
//! 现在没有条目,所以本宏是**空转移器**:只把累积表原样交给下一个域。
macro_rules! settings_appearance_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* { $($acc)* } }
};
}
pub(crate) use settings_appearance_entries;
@@ -0,0 +1,12 @@
//! 设置页「Downloads」分组文案:**预留**(页面侧的真控件还没接)。
//!
//! 加 key 时把条目写进 `$($acc)*` 后面即可(形状见 [`crate::catalog`] 的「域装配」一节)。
//! 现在没有条目,所以本宏是**空转移器**:只把累积表原样交给下一个域。
macro_rules! settings_download_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* { $($acc)* } }
};
}
pub(crate) use settings_download_entries;
@@ -0,0 +1,19 @@
//! 设置页「General」分组文案:语言(Theme / Startup page / Close button 的 key 还没建)。
//!
//! 域文件的形状见 [`crate::catalog`] 的「域装配」一节。
macro_rules! settings_general_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* {
$($acc)*
// ── 设置
SettingsLanguage => ("Language", "界面语言"),
SettingsLanguageHint => (
"Switches the interface language. Chinese text needs a Chinese font.",
"切换界面语言。显示中文需要中文字体。"
),
} }
};
}
pub(crate) use settings_general_entries;
+12
View File
@@ -0,0 +1,12 @@
//! 设置页「Java」分组文案:**预留**(页面侧的真控件还没接)。
//!
//! 加 key 时把条目写进 `$($acc)*` 后面即可(形状见 [`crate::catalog`] 的「域装配」一节)。
//! 现在没有条目,所以本宏是**空转移器**:只把累积表原样交给下一个域。
macro_rules! settings_java_entries {
($next:ident, $($rest:ident,)* { $($acc:tt)* }) => {
$next! { $($rest,)* { $($acc)* } }
};
}
pub(crate) use settings_java_entries;
+28
View File
@@ -0,0 +1,28 @@
//! 设置页文案:**一个分组一个文件**,与页面侧的 `pages/settings/**` 同构。
//!
//! 为什么拆到这个粒度:设置页正文接下来由四条并行 mission 分别接(java / download /
//! language+appearance / ai+about),它们都会往设置页加 key。一个分组一个文件之后,每条线只改
//! 自己那个文件,`catalog.rs` 的装配链一行都不用动(链里已经把这五个域接好了)。
//!
//! | 文件 | 分组(`pages/settings` 的 `GROUPS`) | 现状 |
//! |---|---|---|
//! | [`general`] | General(Language / Theme / Startup page / Close button) | 2 条 key(语言) |
//! | [`appearance`] | Appearance(Density / Corner style / Motion / Text scale) | 预留,暂无 key |
//! | [`java`] | Java(Runtime / Default memory / JVM arguments) | 预留,暂无 key |
//! | [`download`] | Downloads(Preferred source / Concurrency / Verify checksums) | 预留,暂无 key |
//! | [`ai`] | AI / About(页面上还没有这个分组) | 预留,暂无 key |
//!
//! 「预留」的文件里是**空转移器**(只把累积表原样交给下一个域):加 key 时把条目写进它
//! `$($acc)*` 后面即可——签名不用改,`catalog.rs` 不用碰,跨线零冲突。
mod ai;
mod appearance;
mod download;
mod general;
mod java;
pub(crate) use ai::settings_ai_entries;
pub(crate) use appearance::settings_appearance_entries;
pub(crate) use download::settings_download_entries;
pub(crate) use general::settings_general_entries;
pub(crate) use java::settings_java_entries;
+37
View File
@@ -162,3 +162,40 @@ fn render_substitutes_and_keeps_unfilled_placeholders_visible() {
assert_eq!(zh.text(Key::InstancesNew), "新建实例");
assert_eq!(zh.lang(), Lang::ZhCn);
}
/// M99:把单张条目表按域拆到 `catalog/<域>.rs` 之后,`key -> (en, zh-CN)` 必须**逐条逐字节**不变。
///
/// **抓得住**:① 某个 key 整条被搬丢(金表有、现在没有 ⇒ 条数或 key 名对不上);② 两条 key 的译文
/// 互抄 / 同 key 被覆盖成别人的译文(该行 en 或 zh 不等);③ 某条译文改了一个字符(逐字节比,
/// **不是**数条数——「A 丢了一条、B 复制成两条」条数照旧);④ 域的顺序被调换(`Key` 的 `Ord` 与
/// `entries()` 的声明顺序都靠它,金表按原顺序比)。
/// **抓不住**:译文本身翻得对不对——金表是「拆分前那一份」的快照,只能证明没变,不能证明翻得好。
///
/// 金表怎么来的:拆分前在这里临时加过一条 `println!("GOLD\t{}\t{}\t{}", …)` 的用例,把
/// `Key::ALL` ×(`Catalog::of(En)`, `Catalog::of(ZhCn)`)导出成 72 行 TSV,再原样抄成
/// `catalog/golden.rs`;那条导出用例随后已删除(不在提交里)。
#[test]
fn split_keeps_every_translation() {
let en = Catalog::of(Lang::En);
let zh = Catalog::of(Lang::ZhCn);
let actual = Key::ALL
.iter()
.map(|key| (key.name(), en.text(*key), zh.text(*key)))
.collect::<Vec<_>>();
assert_eq!(
actual.len(),
super::golden::GOLDEN.len(),
"key 条数与拆分前不一致(某条被搬丢,或多出来一条)"
);
for (index, expected) in super::golden::GOLDEN.iter().enumerate() {
let got = actual[index];
assert_eq!(
got.0, expected.0,
"第 {index} 条的 key 变了:拆分的域顺序被打乱,或某条被搬到了别处"
);
assert_eq!(got.1, expected.1, "{} 的英文译文变了", expected.0);
assert_eq!(got.2, expected.2, "{} 的中文译文变了", expected.0);
}
}