From 2129d8cee62bf9820b0870f612072baf1b6252e3 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Wed, 4 Nov 2026 23:52:58 +0800 Subject: [PATCH] =?UTF-8?q?fix(packui):=20M113=20=E7=AC=AC=E4=B8=80?= =?UTF-8?q?=E6=89=B9=E4=BF=AE=E6=AD=A3=20=E2=80=94=E2=80=94=20=E4=B8=80?= =?UTF-8?q?=E6=AC=A1=E5=8F=AA=E6=91=8A=E5=BC=80=E4=B8=80=E4=BB=B6=E4=BA=8B?= =?UTF-8?q?=E3=80=81=E6=9C=89=E7=95=8C=E6=BB=9A=E5=8A=A8=E3=80=81=E9=98=B6?= =?UTF-8?q?=E6=AE=B5=E8=AF=8D=E4=B8=8D=E5=86=8D=E6=89=93=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D30 出图实测暴露的四个问题,都在本批范围内修掉: 1. **动作按钮被顶出屏幕**:内容库页正列没有整页滚动(只有结果列表自己滚),入口 + 报告 + 导出三块同时摊开时,「确认写盘 / 关闭 / 以包为准重新导入」落在 900px 以下。 改成一态一块(导出面板 > 进度 > 结果 > 入口);拖拽是窗口级事件,收起入口不丢功能。 2. **阶段词打架**:复用面板自带的阶段词是安装语义(Building the version),与 「Step 2/5 · Unpacking overrides」并排时两种说法。现在面板阶段词留空、由步骤行说真话 —— 仍然只有一份进度条 / 计数 / 取消按钮,没有第二套进度 UI。 3. **列表过长**:缺文件 / 冲突 / 预检样例与已知损失都套上「超过 n 条才滚」的有界容器 (packui/view/parts.rs 的 bounded),少量条目时不浪费空间。 4. **英文单复数**:计数行改成「标签:值」(Files written: 2、cannot export: 1)。 PackPathPlaceholder / PackFormatModrinth 的中文与英文雷同,被 chinese_catalog_is_actually_chinese 抓到,一并改掉。 另:app/update.rs 加完拖拽分支后 408 行超线 → 两个钩子改成返回 Task、三个分支各一行, 现在 397 行。新增致命态出图夹具(ULL_CONTENT_PACK=failed),用来证明「容忍的缺文件」与 「致命回滚」在视觉上可区分。 门禁:cargo check ✓ / clippy -D warnings ✓ / cargo test -p unlucky-app-iced(467+6)✓ / cargo test -p unlucky-i18n(24)✓ / rustfmt --check ✓ / wc -l 全表 ≤400 ✓ --- crates/app-iced/src/app/update.rs | 21 ++------- crates/app-iced/src/app/update/content.rs | 12 ++++- .../app-iced/src/pages/content/packui/demo.rs | 29 ++++++++---- .../pages/content/packui/model/progress.rs | 46 +++++++++++++------ .../app-iced/src/pages/content/packui/view.rs | 29 +++++++----- .../src/pages/content/packui/view/export.rs | 16 ++++--- .../src/pages/content/packui/view/parts.rs | 30 ++++++++++++ .../src/pages/content/packui/view/report.rs | 34 +++++++++----- crates/i18n/src/catalog/content.rs | 15 +++--- crates/i18n/src/catalog/golden.rs | 12 ++--- 10 files changed, 161 insertions(+), 83 deletions(-) diff --git a/crates/app-iced/src/app/update.rs b/crates/app-iced/src/app/update.rs index e46b7f7..1b583ca 100644 --- a/crates/app-iced/src/app/update.rs +++ b/crates/app-iced/src/app/update.rs @@ -100,22 +100,11 @@ impl App { self.view_dirty = true; self.refresh_view(); } - // M113: import/export —— 拖入整合包(文件或 MultiMC·Prism 实例目录)。 - // 这是本版**唯一**的「选文件」通道:依赖树里没有对话框依赖,加它要动根 - // `Cargo.toml` 与许可门禁(见 `pages/content/packui.rs` 的边界说明)。 - window::Event::FileDropped(path) => { - self.on_pack_dropped(path); - self.refresh_view(); - } - window::Event::FileHovered(_) => { - self.on_pack_hover(true); - self.refresh_view(); - } - window::Event::FilesHoveredLeft => { - self.on_pack_hover(false); - self.refresh_view(); - } - // 其余窗口事件本仓不消费(非穷尽枚举,保留兜底免得升级 iced 时炸) + // M113: import/export —— 拖入整合包(本版唯一的选文件通道;窗口级事件, + // 所以入口收起时也照样接得住。细节在 `on_pack_dropped` / `on_pack_hover`) + window::Event::FileDropped(path) => return self.on_pack_dropped(path), + window::Event::FileHovered(_) => return self.on_pack_hover(true), + window::Event::FilesHoveredLeft => return self.on_pack_hover(false), _ => {} } Task::none() diff --git a/crates/app-iced/src/app/update/content.rs b/crates/app-iced/src/app/update/content.rs index e23eefe..1fd3afb 100644 --- a/crates/app-iced/src/app/update/content.rs +++ b/crates/app-iced/src/app/update/content.rs @@ -188,16 +188,24 @@ impl App { } /// 拖入一个文件/目录(`app/update.rs` 的窗口事件转发到这里)。 - pub(super) fn on_pack_dropped(&mut self, path: std::path::PathBuf) { + pub(super) fn on_pack_dropped(&mut self, path: std::path::PathBuf) -> Task { // 拖入是「现在就要看到结果」的动作:先把用户带到内容库页,再落草稿 self.state .navigate(unlucky_presenter::router::Route::Content); self.on_pack(pages::content::PackMessage::Dropped(path)); + self.refresh_view(); + Task::none() } /// 拖拽悬停状态(有文件在窗口上方时高亮入口)。 - pub(super) fn on_pack_hover(&mut self, hover: bool) { + pub(super) fn on_pack_hover(&mut self, hover: bool) -> Task { + // 状态没变就不重画:winit 对同一批文件会连发几次 hover 事件 + let changed = self.content.pack.hover != hover; self.on_pack(pages::content::PackMessage::Hover(hover)); + if changed { + self.refresh_view(); + } + Task::none() } /// 改一个筛选字段并**立刻重查**(第一页)。 diff --git a/crates/app-iced/src/pages/content/packui/demo.rs b/crates/app-iced/src/pages/content/packui/demo.rs index e3aaf5c..0a233f8 100644 --- a/crates/app-iced/src/pages/content/packui/demo.rs +++ b/crates/app-iced/src/pages/content/packui/demo.rs @@ -8,7 +8,7 @@ //! //! | 变量 | 取值 | 作用 | //! |---|---|---| -//! | `ULL_CONTENT_PACK` | `importing` / `partial` / `conflict` / `export` | 选导入导出区的哪一态(不设 = 只有入口) | +//! | `ULL_CONTENT_PACK` | `importing` / `partial` / `conflict` / `failed` / `export` | 选导入导出区的哪一态(不设 = 只有入口) | //! //! 夹具**不直接造视图模型**,而是造 presenter 侧的快照(`ImportState` / `ExportState`), //! 再走产品路径同一个 [`Pack::of`]。这样截图里看到的每一行字都是产品路径会画的那一行, @@ -19,9 +19,9 @@ use std::path::PathBuf; use unlucky_i18n::Lang; use unlucky_presenter::dto::{ ConflictChoiceView, ConflictFileView, ConflictReasonView, ExportClassView, ExportFileView, - ExportPreviewView, ExportReasonView, ExportState, FileActionView, ImportProgressView, - ImportReportView, ImportStageView, ImportState, ImportStatusView, InstanceId, LoaderKind, - MissingFileView, MissingReasonView, PackFormatView, + ExportPreviewView, ExportReasonView, ExportState, FileActionView, ImportErrorView, + ImportProgressView, ImportReportView, ImportStageView, ImportState, ImportStatusView, + InstanceId, LoaderKind, MissingFileView, MissingReasonView, PackFormatView, }; use crate::pages::content::Target; @@ -73,6 +73,19 @@ pub(super) fn pack_in(state: &str, lang: Lang) -> (Pack, PackPageState) { import.report = Some(report(ImportStatusView::Partial, true)); page.expanded = Some("config/sodium-options.json".to_string()); } + // 致命态:整包回滚 + 容忍域仍在(展示两类失败的视觉分野) + "failed" => { + let mut fatal = report(ImportStatusView::Failed, false); + fatal.error = Some(ImportErrorView { + code: "pack_traversal".to_string(), + label: "Path traversal".to_string(), + message: "overrides/../../etc/passwd".to_string(), + hint: Some("Re-download the pack from its source".to_string()), + }); + fatal.files.clear(); + fatal.bytes_written = 0; + import.report = Some(fatal); + } "export" => { page.export_open = true; page.export_name = "Survival 1.20.1".to_string(); @@ -122,11 +135,7 @@ fn report(status: ImportStatusView, conflicts: bool) -> ImportReportView { target: PathBuf::from("C:/ULL/instances/survival/.minecraft"), files: vec![ file("mods/sodium.jar", FileActionView::Installed, 812_000), - file( - "mods/lithium.jar", - FileActionView::SkippedIdentical, - 640_000, - ), + file("mods/lithium.jar", FileActionView::Installed, 640_000), file("config/xaeroplus.cfg", FileActionView::KeptUser, 4_200), ], conflicts: Vec::new(), @@ -151,7 +160,7 @@ fn report(status: ImportStatusView, conflicts: bool) -> ImportReportView { }, ], rejected: vec!["org.lwjgl3:3.3.1".to_string()], - warnings: vec!["包内 `server-overrides/` 未解压(客户端导入)".to_string()], + warnings: vec!["`server-overrides/` was not unpacked (client import)".to_string()], error: None, registered: Some("Survival".to_string()), bytes_written: 3_400_000, diff --git a/crates/app-iced/src/pages/content/packui/model/progress.rs b/crates/app-iced/src/pages/content/packui/model/progress.rs index 5b9ebaa..31e94f8 100644 --- a/crates/app-iced/src/pages/content/packui/model/progress.rs +++ b/crates/app-iced/src/pages/content/packui/model/progress.rs @@ -4,23 +4,24 @@ //! //! 「沿用既有安装面板组件与状态机,禁止再写第二套进度 UI」—— //! -//! 1. 导入的 `ImportStageView` / 计数 / 当前路径先映射成 [`InstallProgressView`](本文件 -//! [`panel_progress`]); -//! 2. 画法交给 `pages::download::install_panel`(M109 已经打过薄包装,同一个函数、同一套几何 -//! 与文案); -//! 3. 面板只认四个粗阶段(规划 / 下载 / 安装 / 登记),任务书要的「解包 / overrides / 补文件」 -//! 三档由**一行步骤说明**([`stage_line`])承载。 +//! 1. 导入的 `ImportStageView` / 计数先映射成面板的入参形状([`panel_progress`] → +//! `InstallProgressView` → [`InstallView`]); +//! 2. 画法交给 `pages::download::install_panel`(同一个函数、同一套几何,进度条 / 计数 / +//! 取消按钮都归它); +//! 3. 任务书要的「解包 / overrides / 补文件」三档由**一行步骤说明**([`stage_line`])承载, +//! 面板右边那个阶段词**留空**。 //! -//! 第 3 点不是第二套进度 UI:多出来的只是一句文字,进度条、计数行、取消按钮仍然只有那一份。 -//! 为什么不再造一条按 `ImportStageView` 五档划分的进度条:那正是「第二套」的定义,而且两块条 -//! 同时出现时用户不知道该信哪一条。 +//! 第 3 点的理由:面板自带的四个阶段词是**安装**语义(规划 / 下载 / 生成版本 / 登记实例), +//! 套到导入上会出现「Step 2/5 · Unpacking overrides」旁边写着「Building the version」—— +//! 同一块地方两种说法(出图时实测到了)。留空 + 由步骤行说真话,是**不动那个组件**的最小解; +//! 多出来的只有一句文字,没有第二条进度条。 use unlucky_i18n::{Catalog, Key}; use unlucky_presenter::dto::{ ImportProgressView, ImportStageView, InstallProgressView, InstallStage, }; -use crate::pages::download::Download; +use crate::pages::download::{Download, InstallView}; /// 抛进面板的进度行。 #[derive(Debug, Clone, PartialEq)] @@ -100,14 +101,31 @@ pub fn panel_progress(progress: &ImportProgressView) -> InstallProgressView { /// 进度事件 → 面板要的视图模型。 pub(super) fn running_of(progress: &ImportProgressView, catalog: Catalog) -> Running { + let panel = panel_progress(progress); Running { pack: progress.pack.clone(), stage_line: stage_line(progress.stage, catalog), panel: Download { - install: Some(crate::pages::download::install_view( - &panel_progress(progress), - catalog, - )), + install: Some(InstallView { + version: panel.version.clone(), + // 只用于 `running()` / `failed()` 的判定;**不**用它给用户看(见模块文档第 3 点) + stage: panel.stage, + stage_label: String::new(), + detail: if progress.is_known() { + catalog.render( + Key::DownloadProgress, + &[ + ("done", &progress.done.to_string()), + ("total", &progress.total.to_string()), + ], + ) + } else { + catalog.text(Key::DownloadProgressUnknown).to_owned() + }, + rate: None, + ratio: progress.ratio(), + known: progress.is_known(), + }), ..Download::loading() }, } diff --git a/crates/app-iced/src/pages/content/packui/view.rs b/crates/app-iced/src/pages/content/packui/view.rs index 2db4570..fb46c49 100644 --- a/crates/app-iced/src/pages/content/packui/view.rs +++ b/crates/app-iced/src/pages/content/packui/view.rs @@ -80,22 +80,27 @@ pub(crate) fn section<'a>( catalog: Catalog, model: &'a Pack, ) -> Element<'a, PageMessage> { - let mut block = column![entry(ui, catalog, model)] - .spacing(ui.space(Space::X3)) - .width(Length::Fill); + let mut block = column![].spacing(ui.space(Space::X3)).width(Length::Fill); - if let Some(running) = &model.running { + // **一次只摊开一件事**:入口(拖什么/装到哪)、进度、结果、导出预检各占一态。 + // 全都堆在一起时,内容库页正列没有整页滚动 ⇒ 动作按钮被顶出屏幕(出图实测:冲突态与 + // 导出预检态的「确认写盘 / 关闭」都落在 900px 以下)。收起的入口不会丢功能:拖拽是窗口级 + // 事件,任何时候把文件拖进来都算数。 + if model.export.open { + block = block.push(export_block(ui, catalog, &model.export)); + } else if let Some(running) = &model.running { block = block.push(running_block(ui, catalog, running)); - } - if let Some(probe) = &model.probe { - block = block.push(probe_block(ui, catalog, probe)); - } - if let Some(report) = &model.report { - block = block.push(divider::full(ui)); + } else if let Some(report) = &model.report { block = block.push(report_block(ui, catalog, report)); + } else { + block = block.push(entry(ui, catalog, model)); + if let Some(probe) = &model.probe { + // 探测失败要和路径输入框在一起:用户下一步就是改路径 + block = block.push(probe_block(ui, catalog, probe)); + } + block = block.push(divider::full(ui)); + block = block.push(export_block(ui, catalog, &model.export)); } - block = block.push(divider::full(ui)); - block = block.push(export_block(ui, catalog, &model.export)); container(block).width(Length::Fill).into() } diff --git a/crates/app-iced/src/pages/content/packui/view/export.rs b/crates/app-iced/src/pages/content/packui/view/export.rs index 7c14a3e..82c92f4 100644 --- a/crates/app-iced/src/pages/content/packui/view/export.rs +++ b/crates/app-iced/src/pages/content/packui/view/export.rs @@ -20,7 +20,7 @@ use crate::theme::Ui; use super::super::model::{Export, Preview}; use super::super::PackMessage; use super::intent; -use super::parts::{rows_block, tolerable_title}; +use super::parts::{bounded, rows_block, tolerable_title}; /// 导出块。 pub(super) fn export_block<'a>( @@ -232,8 +232,11 @@ pub(super) fn preview_block<'a>( block = block.push(rows_block(ui, catalog, &preview.skipped)); } + // 样例 + 已知损失一起套有界滚动:预检是「摊开给你看」的块,条目多时它会把 + // 「确认写盘」顶出屏幕(出图时实测到了) + let mut extras = column![].spacing(ui.space(Space::X1)).width(Length::Fill); for path in &preview.inline_sample { - block = block.push(frame::text_role( + extras = extras.push(frame::text_role( ui, TypeRole::BodySmall, false, @@ -242,7 +245,7 @@ pub(super) fn preview_block<'a>( )); } if let Some(more) = &preview.more_line { - block = block.push(frame::text_role( + extras = extras.push(frame::text_role( ui, TypeRole::BodySmall, false, @@ -250,9 +253,8 @@ pub(super) fn preview_block<'a>( ui.color(Role::OnSurfaceVariant), )); } - if !preview.notes.is_empty() { - block = block.push(frame::text_role( + extras = extras.push(frame::text_role( ui, TypeRole::LabelMedium, false, @@ -260,7 +262,7 @@ pub(super) fn preview_block<'a>( ui.color(Role::OnSurfaceVariant), )); for note in &preview.notes { - block = block.push(frame::text_role( + extras = extras.push(frame::text_role( ui, TypeRole::BodySmall, false, @@ -269,6 +271,8 @@ pub(super) fn preview_block<'a>( )); } } + let extras_rows = preview.inline_sample.len() + preview.notes.len(); + block = block.push(bounded(ui, extras, extras_rows, 4, 160.0)); block.into() } diff --git a/crates/app-iced/src/pages/content/packui/view/parts.rs b/crates/app-iced/src/pages/content/packui/view/parts.rs index bfd6ba2..c3c6e23 100644 --- a/crates/app-iced/src/pages/content/packui/view/parts.rs +++ b/crates/app-iced/src/pages/content/packui/view/parts.rs @@ -97,3 +97,33 @@ pub(super) fn rows_block<'a>( .width(Length::Fill) .into() } + +/// 逐条列表的**有界**容器:条目多时给一个固定高度的滚动区。 +/// +/// 为什么必须要有界:这一块长在内容库页正列的上方,而整页没有滚动(只有结果列表自己滚)⇒ +/// 报告一长,「关闭 / 以包为准重新导入 / 确认写盘」这些动作就被顶出屏幕。出图时实测到了这一点 +/// (冲突态与导出预检态的动作按钮落在 900px 以下)。 +/// +/// 少于 `rows_threshold` 条时直接铺开:给两行内容套一个 120px 的空框,比溢出更难看。 +pub(super) fn bounded<'a>( + ui: &'a Ui, + content: impl Into>, + rows: usize, + rows_threshold: usize, + height: f32, +) -> Element<'a, PageMessage> { + let content = content.into(); + if rows <= rows_threshold { + return content; + } + iced::widget::scrollable(content) + .height(Length::Fixed(height)) + .direction(iced::widget::scrollable::Direction::Vertical( + iced::widget::scrollable::Scrollbar::new() + .width(frame::SCROLLBAR_WIDTH) + .scroller_width(frame::SCROLLBAR_WIDTH) + .margin(frame::SCROLLBAR_MARGIN), + )) + .style(frame::scroll_style(ui)) + .into() +} diff --git a/crates/app-iced/src/pages/content/packui/view/report.rs b/crates/app-iced/src/pages/content/packui/view/report.rs index cfdf65a..fb09ff3 100644 --- a/crates/app-iced/src/pages/content/packui/view/report.rs +++ b/crates/app-iced/src/pages/content/packui/view/report.rs @@ -18,7 +18,7 @@ use crate::theme::Ui; use super::super::model::{ConflictRow, Report}; use super::super::PackMessage; use super::intent; -use super::parts::{marker_box, rows_block, tolerable_title}; +use super::parts::{bounded, marker_box, rows_block, tolerable_title}; /// 结果块。 pub(super) fn report_block<'a>( @@ -119,7 +119,13 @@ pub(super) fn report_block<'a>( block = block.push( column![ tolerable_title(ui, catalog, title.as_str()), - rows_block(ui, catalog, &report.missing), + bounded( + ui, + rows_block(ui, catalog, &report.missing), + report.missing.len(), + 3, + 120.0, + ), ] .spacing(ui.space(Space::X1)) .width(Length::Fill), @@ -133,15 +139,21 @@ pub(super) fn report_block<'a>( block = block.push( column![ tolerable_title(ui, catalog, title.as_str()), - column( - report - .conflicts - .iter() - .map(|conflict| conflict_row(ui, catalog, conflict)) - .collect::>() - ) - .spacing(ui.space(Space::X1)) - .width(Length::Fill), + bounded( + ui, + column( + report + .conflicts + .iter() + .map(|conflict| conflict_row(ui, catalog, conflict)) + .collect::>() + ) + .spacing(ui.space(Space::X1)) + .width(Length::Fill), + report.conflicts.len(), + 2, + 160.0, + ), ] .spacing(ui.space(Space::X1)) .width(Length::Fill), diff --git a/crates/i18n/src/catalog/content.rs b/crates/i18n/src/catalog/content.rs index df91d40..ef63b52 100644 --- a/crates/i18n/src/catalog/content.rs +++ b/crates/i18n/src/catalog/content.rs @@ -96,7 +96,10 @@ macro_rules! content_entries { "本版没有系统文件选择框:请把文件拖进来,或在下面粘贴路径(文件框需要引入对话框依赖,当前依赖树里没有)。" ), PackPathLabel => ("Pack path", "整合包路径"), - PackPathPlaceholder => ("D:\\packs\\Better MC.mrpack", "D:\\packs\\Better MC.mrpack"), + PackPathPlaceholder => ( + "D:\\packs\\Better MC.mrpack", + "D:\\整合包\\更好的MC.mrpack" + ), PackImportAction => ("Import", "导入"), PackCancel => ("Cancel import", "取消导入"), PackDismiss => ("Dismiss", "关闭"), @@ -126,13 +129,13 @@ macro_rules! content_entries { PackStatusSuccess => ("Imported", "已导入"), PackStatusPartial => ("Imported with gaps", "导入完成,但有缺口"), PackStatusFailed => ("Import failed — rolled back", "导入失败 —— 已整包回滚"), - PackReportWritten => ("{count} files written · {bytes}", "{count} 个文件已写盘 · {bytes}"), + PackReportWritten => ("Files written: {count} · {bytes}", "{count} 个文件已写盘 · {bytes}"), PackReportMissing => ( - "{count} files could not be fetched (tolerated)", + "Could not fetch (tolerated): {count}", "有 {count} 个文件没搞到(可容忍)" ), PackReportConflicts => ( - "{count} files you had changed (yours kept by default)", + "You had changed (yours kept by default): {count}", "{count} 个文件你改过(默认保留你的)" ), PackReportRejectedNote => ("not supported by the client", "客户端不支持"), @@ -162,7 +165,7 @@ macro_rules! content_entries { "包内 {pack} · 基线 {base} · 磁盘 {disk}" ), PackConflictDetail => ("Hashes", "三方哈希"), - PackFormatModrinth => ("Modrinth .mrpack", "Modrinth .mrpack"), + PackFormatModrinth => ("Modrinth .mrpack", "Modrinth 的 .mrpack"), PackFormatCurseforge => ("CurseForge manifest", "CurseForge 清单"), PackFormatMultimc => ("MultiMC / Prism instance", "MultiMC·Prism 实例"), PackExportTitle => ("Export this instance", "导出这个实例"), @@ -182,7 +185,7 @@ macro_rules! content_entries { PackExportFailed => ("Export failed: {reason}", "导出失败:{reason}"), PackExportPending => ("Scanning the instance…", "正在扫描实例…"), PackPreviewHead => ( - "Will inline {inline} files · {skipped} items cannot be exported", + "Will inline {inline} files · cannot export: {skipped}", "将内联 {inline} 个文件 · 无法导出 {skipped} 项" ), PackPreviewRemote => ( diff --git a/crates/i18n/src/catalog/golden.rs b/crates/i18n/src/catalog/golden.rs index d7ddcd6..48e457e 100644 --- a/crates/i18n/src/catalog/golden.rs +++ b/crates/i18n/src/catalog/golden.rs @@ -225,7 +225,7 @@ pub(super) const GOLDEN: &[(&str, &str, &str)] = &[ ("PackDropActive", "Release to probe the pack", "松手开始探测"), ("PackNoDialog", "This build has no system file picker: drag the file in, or paste its path below (a picker needs a dialog dependency that is not in the tree).", "本版没有系统文件选择框:请把文件拖进来,或在下面粘贴路径(文件框需要引入对话框依赖,当前依赖树里没有)。"), ("PackPathLabel", "Pack path", "整合包路径"), - ("PackPathPlaceholder", "D:\\packs\\Better MC.mrpack", "D:\\packs\\Better MC.mrpack"), + ("PackPathPlaceholder", "D:\\packs\\Better MC.mrpack", "D:\\整合包\\更好的MC.mrpack"), ("PackImportAction", "Import", "导入"), ("PackCancel", "Cancel import", "取消导入"), ("PackDismiss", "Dismiss", "关闭"), @@ -246,9 +246,9 @@ pub(super) const GOLDEN: &[(&str, &str, &str)] = &[ ("PackStatusSuccess", "Imported", "已导入"), ("PackStatusPartial", "Imported with gaps", "导入完成,但有缺口"), ("PackStatusFailed", "Import failed — rolled back", "导入失败 —— 已整包回滚"), - ("PackReportWritten", "{count} files written · {bytes}", "{count} 个文件已写盘 · {bytes}"), - ("PackReportMissing", "{count} files could not be fetched (tolerated)", "有 {count} 个文件没搞到(可容忍)"), - ("PackReportConflicts", "{count} files you had changed (yours kept by default)", "{count} 个文件你改过(默认保留你的)"), + ("PackReportWritten", "Files written: {count} · {bytes}", "{count} 个文件已写盘 · {bytes}"), + ("PackReportMissing", "Could not fetch (tolerated): {count}", "有 {count} 个文件没搞到(可容忍)"), + ("PackReportConflicts", "You had changed (yours kept by default): {count}", "{count} 个文件你改过(默认保留你的)"), ("PackReportRejectedNote", "not supported by the client", "客户端不支持"), ("PackOverwriteAction", "Re-import, the pack wins", "以包为准重新导入"), ("PackOverwriteNote", "Keeping both (renamed) is not supported yet: the import pipeline has one global conflict policy and no per-file decision channel.", "「两者都留(改名)」暂不支持:导入管线只有一套全局冲突策略,没有逐文件决策通道。"), @@ -264,7 +264,7 @@ pub(super) const GOLDEN: &[(&str, &str, &str)] = &[ ("PackChoiceOverwrite", "pack version used", "用包里的"), ("PackConflictHashes", "pack {pack} · base {base} · disk {disk}", "包内 {pack} · 基线 {base} · 磁盘 {disk}"), ("PackConflictDetail", "Hashes", "三方哈希"), - ("PackFormatModrinth", "Modrinth .mrpack", "Modrinth .mrpack"), + ("PackFormatModrinth", "Modrinth .mrpack", "Modrinth 的 .mrpack"), ("PackFormatCurseforge", "CurseForge manifest", "CurseForge 清单"), ("PackFormatMultimc", "MultiMC / Prism instance", "MultiMC·Prism 实例"), ("PackExportTitle", "Export this instance", "导出这个实例"), @@ -280,7 +280,7 @@ pub(super) const GOLDEN: &[(&str, &str, &str)] = &[ ("PackExportDone", "Wrote {path} · {bytes}", "已写出 {path} · {bytes}"), ("PackExportFailed", "Export failed: {reason}", "导出失败:{reason}"), ("PackExportPending", "Scanning the instance…", "正在扫描实例…"), - ("PackPreviewHead", "Will inline {inline} files · {skipped} items cannot be exported", "将内联 {inline} 个文件 · 无法导出 {skipped} 项"), + ("PackPreviewHead", "Will inline {inline} files · cannot export: {skipped}", "将内联 {inline} 个文件 · 无法导出 {skipped} 项"), ("PackPreviewRemote", "{count} files can be referenced remotely", "{count} 个文件可走远程引用"), ("PackPreviewInlineBytes", "{bytes} copied into overrides/", "{bytes} 会原样拷进 overrides/"), ("PackPreviewSkippedTitle", "Not in the pack (listed one by one)", "没进包(逐条列出)"),