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:
1 parent
4b1471a60d
commit
330f7216fd
29 files changed
+1173
-541
No files matched your search
+85
-122
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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", "选择实例"),
|
||||
];
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user