feat(app-iced): M120 实例页与启动阶段标签跟着界面语言走

来源:M106 r1 复审点名——中文机器上首屏(实例页)仍是英中混排(页头 / New instance /
Import / ALL INSTANCES / VERSION / Present on disk / Running · pid / Launch failed)。
用户的原话是「我系统语言是中文 为什么打开软件是英文」。

改了什么
- i18n:catalog/instances.rs 从 6 条死 key 补到 34 条(页头 / 列表列头 / 详情动作 /
  窄窗说明 / 元信息标签与值 / 隔离档位 / 运行状态 / 四个启动阶段)。族在装配链第 2 位,
  新 key 追加在本族末尾,golden 对应位置插入(不是文件末尾追加)。
- 页面:新增 pages/instances/text.rs(Texts)——页面唯一的取词入口,实例页里不再有
  任何可见英文字面量;detail / demo 也改从它取。
- 阶段标签:view/instances.rs 的 Running / Launch failed / 四阶段全部走目录;
  **数字与分隔符不随语言变形**(运行中 · pid 1234 与 Running · pid 1234 只换前半句)。
- 顺手修掉两处「语言反向泄漏」:原版徽标此前直接画 presenter 的中文「原版」(英文界面
  会漏中文),现在走 LoaderVanilla;隔离档位 isolated/shared 也从 core 的稳定 id 换成人话。
- 删掉 InstancesLastPlayed:M117 加的整句 key,从未接线,形状与「标签 + 值」的元信息卡
  不符,由 InstancesMetaLastPlayed + InstancesNeverPlayed 取代。

守门(pages/instances/tests/i18n.rs + app/view/tests.rs)
- 中文态无 ASCII 英文单词(品牌 Java / 技术标识 pid 白名单);
- 每一条 Instances* 的两套译文必须不同(切语言时真的变);
- Running 的数字与 · pid 分隔符在两套语言里逐字保留;
- 页面源码里 29 条旧英文基线不许回来;每条 key 都必须被页面引用(死 key)。

出图:DevDocs/artifacts/m120/01~12(有实例 / 空态 / 运行中 × 浅深 × 中英)。
This commit is contained in:
AskaEth committed 2026-11-05 13:36:44 +08:00
1 parent 2f491c31f9
commit 1a4e1fee12
17 files changed
+924 -119

No files matched your search

+21 -18
View File
@@ -23,6 +23,7 @@ use super::super::layout::{self, FrameMetrics};
use super::super::model::Detail;
use super::super::{Action, Message};
use super::forced_state;
use super::Texts;
// --------------------------------------------------------------- 详情列
@@ -31,9 +32,10 @@ pub(super) fn detail_column<'a>(
metrics: FrameMetrics,
detail: &'a Detail,
forced: Option<Interaction>,
texts: Texts,
) -> Element<'a, Message> {
let mut body = column![
detail_head(ui, detail, forced),
detail_head(ui, detail, forced, texts),
meta_grid(ui, metrics, detail),
divider::full(ui),
]
@@ -43,7 +45,7 @@ pub(super) fn detail_column<'a>(
body = push_text(ui, metrics, body, detail);
// 页头 + 元信息网格 + **一条**分隔线以下才是正文与操作区(M84 那两条线是占位时期的排版残留)
frame::left_aligned(metrics.column, body.push(action_row(ui, forced)))
frame::left_aligned(metrics.column, body.push(action_row(ui, forced, texts)))
}
pub(super) fn narrow_detail<'a>(
@@ -51,19 +53,20 @@ pub(super) fn narrow_detail<'a>(
metrics: FrameMetrics,
detail: &'a Detail,
forced: Option<Interaction>,
texts: Texts,
) -> Element<'a, Message> {
// 窄窗:列表退成上一屏,本屏只放详情(官方 compact 的 list-detail 口径)。
let mut body = column![
row![buttons::text(
ui,
forced_state(
Button::new("Back to instances", Message::Navigate(Route::Instances)),
Button::new(texts.back(), Message::Navigate(Route::Instances)),
forced,
),
)]
.width(Length::Fill)
.align_y(Alignment::Center),
detail_head(ui, detail, forced),
detail_head(ui, detail, forced, texts),
meta_grid(ui, metrics, detail),
divider::full(ui),
]
@@ -76,12 +79,11 @@ pub(super) fn narrow_detail<'a>(
ui,
TypeRole::BodySmall,
false,
"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.",
texts.narrow_note(),
ui.color(Role::OnSurfaceVariant),
));
frame::body(metrics, body.push(action_row(ui, forced)))
frame::body(metrics, body.push(action_row(ui, forced, texts)))
}
/// 正文段(演示数据才有)与运行状态行(真数据才有)——两者都是可选的,顺序固定。
@@ -122,6 +124,7 @@ fn detail_head<'a>(
ui: &'a Ui,
detail: &'a Detail,
forced: Option<Interaction>,
texts: Texts,
) -> Element<'a, Message> {
let titles = column![
frame::text_role(
@@ -147,23 +150,23 @@ fn detail_head<'a>(
buttons::filled(
ui,
forced_state(
Button::new("Play", Message::Launch(detail.id.clone())),
Button::new(texts.play(), Message::Launch(detail.id.clone())),
forced,
),
)
} else {
buttons::filled(ui, Button::disabled("Play"))
buttons::filled(ui, Button::disabled(texts.play()))
};
let stop = if detail.can_stop {
buttons::text(
ui,
forced_state(
Button::new("Stop", Message::Stop(detail.id.clone())),
Button::new(texts.stop(), Message::Stop(detail.id.clone())),
forced,
),
)
} else {
buttons::text(ui, Button::disabled("Stop"))
buttons::text(ui, Button::disabled(texts.stop()))
};
row![titles, launch, stop]
@@ -213,26 +216,26 @@ fn meta_grid<'a>(ui: &'a Ui, metrics: FrameMetrics, detail: &'a Detail) -> Eleme
grid.into()
}
fn action_row<'a>(ui: &'a Ui, forced: Option<Interaction>) -> Element<'a, Message> {
fn action_row<'a>(ui: &'a Ui, forced: Option<Interaction>, texts: Texts) -> Element<'a, Message> {
row![
buttons::text(
ui,
forced_state(
Button::new("Duplicate", Message::Action(Action::Duplicate)),
Button::new(texts.duplicate(), Message::Action(Action::Duplicate)),
forced,
)
),
buttons::text(
ui,
forced_state(
Button::new("Open folder", Message::Action(Action::OpenFolder)),
Button::new(texts.open_folder(), Message::Action(Action::OpenFolder)),
forced,
)
),
buttons::text(
ui,
forced_state(
Button::new("Delete", Message::Action(Action::Delete)),
Button::new(texts.delete(), Message::Action(Action::Delete)),
forced
),
),
@@ -242,12 +245,12 @@ fn action_row<'a>(ui: &'a Ui, forced: Option<Interaction>) -> Element<'a, Messag
.into()
}
pub(super) fn detail_empty<'a>(ui: &'a Ui) -> Element<'a, Message> {
pub(super) fn detail_empty<'a>(ui: &'a Ui, texts: Texts) -> Element<'a, Message> {
frame::empty_state(
ui,
IconKind::Instances,
"No instance selected",
"Pick an instance on the left to see its version, loader and launch options.",
texts.empty_title(),
texts.empty_body(),
Vec::new(),
)
}