Files
Unlucky-Launcher/crates/i18n/src/catalog/golden.rs
T
tower-worker a0f61d8bb5 feat(mods): 本地 Mod 列表与启停(M224 MODTOGGLE)
三层一次做完:core 读写目录 → presenter DTO/host → 实例详情页 MODS 区。
r1 复审后 rebase 到 main 71144df(M223 已合入),并修掉四条文档疵。

core(crates/core/src/addon/mods/)
- suffix:`.disabled` / `.old` 的**唯一判据**(四态枚举 + 展示名 + 双向改名目标名),
  收敛原先两份同构实现——`export/scan.rs::is_disabled_path` 与 `import/plan/paths.rs::suffix_variant`
  改为委派/再导出,签名与行为不变。
- list:单根语义(入口是 derive_game_dir 派生出的运行目录)列 `<run>/mods/`,不按扩展名过滤,
  符号链接/目录/怪名字逐条登记原因;`mods/` 本身是符号链接时拒(不跟随)。
- toggle:启停 = 在 `mods/` 里改名。五条守卫(裸文件名 / 目录可用 / 普通文件 / 不覆盖 / 不做
  copy+delete 兜底)+ 幂等;`.disabled` 只插在 `.old` 之前。

presenter
- dto/mods:ModsCommand / ModListView / ModFailureView(不拼文案,只给结构 + OS 原文)。
- host/mods:后台线程列目录与启停,运行目录走与启动同一份 D20 派生、不写任何设置;
  游戏在跑时 host 侧直接挡。
- state/mods:快照整体替换、迟到结果丢弃、在飞标记;`AppState::mods`。

app-iced(实例详情页)
- MODS 区(`pages/instances/mods.rs`):名字 + 大小 + `.old` 徽标 + Switch;运行中/在飞只读。
- 进入详情页即后台列一次(不走 3 s 观察窗,避免打红 M192 的「空闲不烧帧」守卫)。
- MODS 卡排在启动参数**之前**(详情列是滚动区,排在后面 1180×1000 下永远看不见)。
- i18n:19 条中英文案(`catalog/instances.rs` + 金表)。

rebase 冲突解法(三个文件,全是"两侧同段新增",都保留):
- `i18n/catalog/instances.rs` + `i18n/catalog/golden.rs`:M223 的 Adopt* 段在前、M224 的
  InstancesMods* 段在后(= `Key::ALL` 顺序 = 金表顺序)。
- `presenter/src/state.rs`:`adopt` 与 `mods` 字段/初始化/apply_core 分支都留;`tiles()` 归
  `state/tiles.rs`(本分支搬的),`apply_agent`/`agent_rail_visible` 归 `state/agent.rs`
  (M223 搬的)——两处搬移不重叠,state.rs 里都不再出现。

r1 四条文档疵:
1. `pages/instances/mods.rs` 模块文档:挂载位置写反("启动参数卡之后"→实际之前),并补上为什么;
2. `state/mods.rs`:引用不存在的 `CoreEvent::ModsChanged/ModsFailed` → 改成 `CoreEvent::Mods`
   + 文字说明 Ok 落快照 / Err 落具名失败;
3. `app/state/mods_shot.rs`:文档说"目录串取真实派生结果"但代码留空 → 改成"故意留空";
4. `app/state/refresh.rs::note_mods_failure`:文档说"连点两次看见两次红字"与去重机制不符 →
   改成按 `(实例, 文件, 大类)` 三元组去重(防刷屏),换文件/换实例/换大类仍会再弹。

顺带(r1 已确认的 main 红):`pages/download/options/view.rs:88-89` 两条
`clippy::doc_lazy_continuation` 只缩进修掉,文字未动。
2026-11-10 21:09:37 +08:00

137 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 金表的**表数据**(`Key::ALL` 顺序 × (en, zh-CN) 逐字快照,第一列是 `Key::name()`)。「为什么
//! 有这张表、它抓得住什么」全在 `catalog.rs` 的 `mod golden;` 里;本文件只放数据。
pub(super) const GOLDEN: &[(&str, &str, &str)] = &[
("NavInstances", "Instances", "实例"),
("NavDownload", "Versions", "版本管理"),
("NavDownloadExpanded", "Version management", "版本管理"),
("NavLogs", "Logs", "日志"),
("NavSettings", "Settings", "设置"),
("InstancesNew", "New instance", "新建实例"),
("InstancesImport", "Import", "导入"),
("InstancesBack", "Back to instances", "返回实例列表"),
("InstancesPlay", "Play", "启动"),
("InstancesEmptyTitle", "No instance selected", "未选择实例"),
("InstancesEmptyBody", "Pick an instance on the left to see its version, loader and launch options.", "在左侧选择一个实例,查看它的版本、加载器与启动选项。"),
("InstancesTitle", "Instances", "实例"),
("InstancesSubtitle", "Local versions and their mod loaders", "本地版本与它们的模组加载器"),
("InstancesAllTitle", "ALL INSTANCES", "全部实例"),
("InstancesStop", "Stop", "停止"),
("InstancesDuplicate", "Duplicate", "复制"),
("InstancesOpenFolder", "Open folder", "打开文件夹"),
("InstancesDelete", "Delete", "删除"),
("InstancesNarrowNote", "Below 840 px the list and the detail share one column, so opening an instance replaces this screen (full-page switch). Widen the window to see both panes again.", "窗口窄于 840 像素时,列表与详情共用一列:打开一个实例会整页替换这一屏(整页切换)。把窗口拉宽就能重新同时看见两栏。"),
("InstancesMetaVersion", "VERSION", "版本"),
("InstancesMetaLoader", "LOADER", "加载器"),
("InstancesMetaMemory", "MEMORY", "内存"),
("InstancesMetaJava", "JAVA", "Java"),
("InstancesMetaMods", "MODS", "模组"),
("InstancesMetaIsolation", "ISOLATION", "隔离方式"),
("InstancesMetaVersionId", "VERSION ID", "版本标识"),
("InstancesMetaFiles", "FILES", "文件"),
("InstancesMetaLastPlayed", "LAST PLAYED", "上次游玩"),
("InstancesPresentOnDisk", "Present on disk", "已在磁盘上"),
("InstancesMissing", "Missing", "缺失"),
("InstancesNeverPlayed", "Never", "从未游玩"),
("InstancesIsolationIsolated", "Isolated", "独立目录"),
("InstancesIsolationShared", "Shared", "共用目录"),
("InstancesRunning", "Running · pid {pid}", "运行中 · pid {pid}"),
("InstancesFailed", "Launch failed · {reason}", "启动失败 · {reason}"),
("InstancesCrashed", "the game crashed (exit {code})", "游戏崩溃(退出码 {code})"),
("InstancesSilentFailed", "exit 0, but never started", "退出码 0,没起来"),
("InstancesStageDownload", "Downloading", "下载中"),
("InstancesStageVerify", "Verifying", "校验中"),
("InstancesStageLaunch", "Starting", "启动中"),
("InstancesStageWindow", "Waiting for window", "等待游戏窗口"),
("InstancesLaunchTitle", "LAUNCH OPTIONS", "启动参数"),
("InstancesLaunchJvm", "JVM arguments", "JVM 参数"),
("InstancesLaunchJvmHint", "Space separated, quotes allowed. Empty = follow the global extra JVM arguments (both are sent; on duplicates the instance wins).", "空格分隔、支持引号。留空 = 跟随全局附加 JVM 参数(两者叠加下发,同名选项实例赢)。"),
("InstancesLaunchGame", "Game arguments", "游戏参数"),
("InstancesLaunchGameHint", "Appended last, so they override earlier game arguments. Empty = nothing appended.", "追加在命令行最后,可覆盖前面的游戏参数。留空 = 不追加。"),
("InstancesLaunchMemory", "Max memory (MiB)", "最大内存(MiB)"),
("InstancesLaunchMemoryHint", "Empty = follow the global default ({default} MiB).", "留空 = 跟随全局默认({default} MiB)。"),
("InstancesLaunchJava", "Java executable", "Java 可执行文件"),
("InstancesLaunchJavaHint", "Empty = follow the global setting, then auto-pick (managed > JAVA_HOME > PATH).", "留空 = 跟随全局设置,再退到自动挑选(托管 > 环境变量 > 系统路径)。"),
("InstancesLaunchBackend", "Graphics backend", "图形后端"),
("InstancesLaunchBackendGated", "Requires MC {min}+ (this instance: {version}). The flag is not sent on this version.", "需游戏版本 {min} 及以上(本实例 {version}),这个版本不会下发该参数。"),
("InstancesLaunchEffective", "Effective: {value}", "生效:{value}"),
("InstancesBackendDefault", "Default", "默认"),
("InstancesBackendOpengl", "OpenGL", "OpenGL 后端"),
("InstancesBackendVulkan", "Vulkan", "Vulkan 后端"),
("InstancesLaunchSave", "Save", "保存"),
("InstancesLaunchReset", "Reset to global", "恢复跟随全局"),
("AdoptTitle", "Import an existing version", "导入已有版本"),
("AdoptSubtitle", "Register versions another launcher already installed. Nothing is copied: versions, libraries and assets stay where they are.", "把别的启动器已经装好的版本登记成实例。不复制任何东西:versions / libraries / assets 原地不动。"),
("AdoptScanning", "Scanning versions/…", "正在扫描 versions/…"),
("AdoptEmpty", "No version found under {dir}. Install one here, or point the game folder at the .minecraft another launcher uses.", "在 {dir} 下一个版本都没找到。可以用本启动器装一个,或者把游戏目录指向别的启动器用的那个 .minecraft。"),
("AdoptSelectLabel", "Pick a version", "选择版本"),
("AdoptReady", "Ready", "可导入"),
("AdoptBlocked", "{count} problem(s)", "{count} 个问题"),
("AdoptRegistered", "Already used by {name}", "已被 {name} 使用"),
("AdoptTargetLabel", "Where should the game run?", "游戏在哪个目录里跑"),
("AdoptTargetInstanceDir", "New folder in ULL data (default)", "ULL 数据区新建目录(缺省)"),
("AdoptTargetInstanceDirHint", "mods, saves and config stay out of the other launcher's .minecraft", "mods / saves / config 不会落进别人的 .minecraft"),
("AdoptTargetShared", "Share the game folder", "共用游戏目录"),
("AdoptTargetSharedHint", "mods, saves and config are written into the other launcher's .minecraft", "mods / saves / config 会写进别人的 .minecraft"),
("AdoptTargetVersionDir", "Use the version folder", "用版本目录"),
("AdoptTargetVersionDirHint", "player data grows inside versions/<id>", "玩家数据会长在 versions/<id> 里"),
("AdoptNameLabel", "Instance name (empty = version id)", "实例名(留空 = 版本 id)"),
("AdoptPreview", "Check", "预检"),
("AdoptConfirm", "Import", "导入"),
("AdoptBack", "Back", "返回"),
("AdoptClose", "Close", "关闭"),
("AdoptPlanTitle", "Ready to import", "可以导入"),
("AdoptPlanRunDir", "Game folder", "运行目录"),
("AdoptPlanIsolation", "Isolation", "隔离方式"),
("AdoptFailureTitle", "Import failed", "导入失败"),
("AdoptProblemCount", "Every reason is listed below:", "全部原因如下:"),
("AdoptInstanceName", "Instance name", "实例名"),
("AdoptDoneTitle", "Imported", "已导入"),
("AdoptDoneHint", "It is in the instance list now. Player data will live in the game folder below.", "它已经出现在实例列表里;玩家数据会落在下面这个运行目录。"),
("InstancesModsTitle", "MODS", "模组"),
("InstancesModsCount", "{enabled} enabled · {disabled} disabled", "{enabled} 启用 · {disabled} 禁用"),
("InstancesModsLoading", "Reading mods/…", "正在读取 mods/…"),
("InstancesModsEmpty", "No mods here yet — put jars into mods/ and they show up.", "这里还没有模组——把模组文件放进 mods/ 就会出现在这里。"),
("InstancesModsMissingDir", "This instance has no mods/ directory yet.", "这个实例还没有 mods/ 目录。"),
("InstancesModsRunning", "The game is running — toggles are read-only until it exits.", "游戏正在运行——退出前开关只读。"),
("InstancesModsOldBadge", "legacy file", "旧版本残留"),
("InstancesModsSkipped", "{count} entries skipped", "跳过 {count} 项"),
("InstancesModsFailed", "Could not read mods/: {reason}", "读取 mods/ 失败:{reason}"),
("InstancesModsToggleFailed", "Could not toggle {file}: {reason}", "启停 {file} 失败:{reason}"),
("InstancesModsReasonRunning", "the game is running", "游戏正在运行"),
("InstancesModsReasonInstance", "the instance is gone", "实例已不存在"),
("InstancesModsReasonName", "the file name is not usable", "文件名不可用"),
("InstancesModsReasonDirectory", "there is no usable mods/ directory", "没有可用的 mods/ 目录"),
("InstancesModsReasonMissing", "the file is gone", "文件已不在"),
("InstancesModsReasonNotFile", "that is not a regular file", "那不是普通文件"),
("InstancesModsReasonCollision", "a file with that name already exists", "同名文件已存在"),
("InstancesModsReasonRename", "the rename failed", "改名失败"),
];
/// 金表的**全量**行(本文件 → [`tail`] → [`tail_ai`] → [`tail2`],顺序即 `Key::ALL`)。
///
/// 拆成四段只为守 400 行硬线(M157/M159/M197 往金表追加文案后越线);对调用方是**一条**序列,
/// `catalog/tests.rs` 的逐字节断言不用关心分段。
///
/// ⚠️ 加段时**必须**在这里也 chain 上:条数与内容都只能从这一条序列导出(见 [`len`])。
pub(super) fn rows() -> impl Iterator<Item = &'static (&'static str, &'static str, &'static str)> {
GOLDEN
.iter()
.chain(tail::ROWS.iter())
.chain(tail_ai::ROWS.iter())
.chain(tail2::ROWS.iter())
}
/// 金表总行数(`Key::ALL` 的条数)。
///
/// **定义成 `rows().count()`,不是各段 `len()` 相加**:r3 复审实测过一条假绿——把
/// [`rows`] 里的 `chain(tail2::ROWS)` 删掉,`catalog/tests.rs` 仍全绿,因为条数来自一处手工
/// 聚合(仍有 tail2)而内容只遍历 `rows()`(少了 tail2),于是 213/459 行无人看守。
/// 现在条数与内容同源:漏挂一段 ⇒ 条数当场对不上 ⇒ 用例必红。
pub(super) fn len() -> usize {
rows().count()
}
mod tail;
mod tail2;
mod tail_ai;