Skip to content

Commit 8cfec15

Browse files
fix(i18n): 切换语言后刷新启动按钮与版本文本,避免中英混排 (#3306)
* fix(launch): 切换语言后刷新启动按钮与版本文本,避免中英混排 (#3246) 启动页 PageLaunchLeft 为常驻缓存实例,其 Loaded 用 isLoad 守卫、初始化仅 执行一次;BtnLaunch/LabVersion 等文本由 RefreshButtonsUI 以 Lang.Text 直接 赋值(非 DynamicResource 绑定),故语言热切换后不会刷新。又因 RefreshButtonsUI 在“启动状态未变化”时会跳过文本重设,即便切回页面时 BtnLaunch.Loaded 重新触发 它也不生效,导致切回启动页后按钮/版本文本停留在旧语言、与已刷新的绑定文本混排。 订阅 LocalizationService.LanguageChanged(常驻实例,无需退订),事件触发时把 btnLaunchState 置为无效值使文本缓存失效,从而切回页面时 RefreshButtonsUI 以 新语言重新赋值。 * fix(ui): 缓存页文本随语言切换即时刷新,修复中英混排 (#3246) 启动页、下载页等为常驻缓存单例,其 code-behind 用 Lang.Text 一次性赋值的 文本在语言热切换后不会刷新,切回页面时与 DynamicResource 绑定文本混排。 新增 WeakLanguageChanged:以弱引用订阅 LocalizationService.LanguageChanged, 订阅者被 GC 后自动移除。据此让缓存页在语言变更时即时重刷: - PageLaunchLeft:RefreshButtonsUI 以语言令牌为重设条件,重刷启动按钮与版本; - PageDownloadInstall:重建版本分类卡片(标题含动态计数,无法用 DynamicResource); - PageDownloadCompFavorites:按 CompType 重设各分类标题并重排。 回应 review:采用弱事件模式而非构造函数强订阅,规避单例假设失效导致的内存 泄漏;不再使用 -1 之类魔法值表示强制刷新。 * refactor(i18n): 强化 WeakLanguageChanged 的健壮性 (#3246) * fix(launch): 语言切换重建版本卡片时刷新愚人节版本的本地化 lore * 清理无用注释 * 清理无用注释
1 parent 6951fed commit 8cfec15

5 files changed

Lines changed: 133 additions & 79 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using PCL.Core.Logging;
4+
5+
namespace PCL.Core.App.Localization;
6+
7+
public static class WeakLanguageChanged
8+
{
9+
private static readonly object _Lock = new();
10+
private static readonly List<(WeakReference<object> Target, Action<object> Handler)> _Handlers = [];
11+
private static bool _hooked;
12+
13+
public static void Add<T>(T target, Action<T> handler) where T : class
14+
{
15+
if (target is null) throw new ArgumentNullException(nameof(target));
16+
if (handler is null) throw new ArgumentNullException(nameof(handler));
17+
if (handler.Target is not null)
18+
throw new ArgumentException(
19+
"handler 必须是不捕获实例的静态委托,请改用形如 static t => t.Foo() 的写法并通过 target 传入实例。",
20+
nameof(handler));
21+
22+
lock (_Lock)
23+
{
24+
if (!_hooked)
25+
{
26+
LocalizationService.LanguageChanged += _OnLanguageChanged;
27+
_hooked = true;
28+
}
29+
30+
_Handlers.Add((new WeakReference<object>(target), o => handler((T)o)));
31+
}
32+
}
33+
34+
private static void _OnLanguageChanged()
35+
{
36+
(WeakReference<object> Target, Action<object> Handler)[] snapshot;
37+
lock (_Lock)
38+
{
39+
_Handlers.RemoveAll(h => !h.Target.TryGetTarget(out _));
40+
snapshot = _Handlers.ToArray();
41+
}
42+
43+
foreach (var (target, handler) in snapshot)
44+
{
45+
if (!target.TryGetTarget(out var t))
46+
continue;
47+
try
48+
{
49+
handler(t);
50+
}
51+
catch (Exception ex)
52+
{
53+
LogWrapper.Warn(ex, "WeakLanguageChanged", "语言变更处理器执行出错");
54+
}
55+
}
56+
}
57+
}

PCL.Core/Minecraft/McVersionClassifier.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,18 @@ public static McVersionCategory ClassifyVersion(JsonObject version)
3535
return type switch
3636
{
3737
"release" => McVersionCategory.Release,
38-
"special" => McVersionCategory.AprilFools,
38+
"special" => _RefreshAprilFools(version, idLower),
3939
"snapshot" or "pending" => _ClassifySnapshotOrPending(version, idLower),
4040
_ => McVersionCategory.BeforeRelease
4141
};
4242
}
4343

44+
private static McVersionCategory _RefreshAprilFools(JsonObject version, string idLower)
45+
{
46+
_TryMarkAprilFoolsVersion(version, idLower);
47+
return McVersionCategory.AprilFools;
48+
}
49+
4450
public static DateTime GetReleaseTime(JsonObject version)
4551
{
4652
return _GetDateTime(version, "releaseTime");

Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompFavorites.xaml.cs

Lines changed: 22 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public PageDownloadCompFavorites()
4242
ComboTargetFav.SelectionChanged += ComboTargetFav_Selected;
4343
HintGetFail.MouseLeftButtonDown += HintGetFail_MouseLeftButtonDown;
4444
PanSearchBox.TextChanged += SearchRun;
45+
WeakLanguageChanged.Add(this, static page => ModBase.RunInUi(page._RefreshCategoryTitles));
4546
}
4647

4748
private ModComp.CompFavorites.FavData CurrentFavTarget
@@ -154,55 +155,7 @@ private CompListItemContainer GetSuitListContainer(int type)
154155
},
155156
CompType = type
156157
};
157-
switch (type)
158-
{
159-
case -1:
160-
{
161-
newItem.Title = Lang.Text("Download.Comp.Favorites.SearchResults.Title");
162-
break;
163-
}
164-
case (int)ModComp.CompType.Mod:
165-
{
166-
newItem.Title = Lang.Text("Download.Comp.Favorites.Category.Mod");
167-
break;
168-
}
169-
case (int)ModComp.CompType.ModPack:
170-
{
171-
newItem.Title = $"{Lang.Text("Download.Comp.Type.Modpack")} ({{0}})";
172-
break;
173-
}
174-
case (int)ModComp.CompType.ResourcePack:
175-
{
176-
newItem.Title = $"{Lang.Text("Download.Comp.Type.ResourcePack")} ({{0}})";
177-
break;
178-
}
179-
case (int)ModComp.CompType.Shader:
180-
{
181-
newItem.Title = $"{Lang.Text("Download.Comp.Type.Shader")} ({{0}})";
182-
break;
183-
}
184-
case (int)ModComp.CompType.DataPack:
185-
{
186-
newItem.Title = $"{Lang.Text("Download.Comp.Type.DataPack")} ({{0}})";
187-
break;
188-
}
189-
case (int)ModComp.CompType.Plugin:
190-
{
191-
newItem.Title = $"{Lang.Text("Download.Comp.Type.Plugin")} ({{0}})";
192-
break;
193-
}
194-
case (int)ModComp.CompType.World:
195-
{
196-
newItem.Title = $"{Lang.Text("Download.Comp.Type.World")} ({{0}})";
197-
break;
198-
}
199-
200-
default:
201-
{
202-
newItem.Title = $"{Lang.Text("Download.Comp.Favorites.UnknownType")} ({{0}})";
203-
break;
204-
}
205-
}
158+
newItem.Title = _GetCategoryTitleFormat(type);
206159

207160
newItem.Card.Title = string.Format(newItem.Title, 0);
208161
newItem.Card.Children.Add(newItem.ContentList);
@@ -227,6 +180,26 @@ private void RefreshContent()
227180
}
228181
}
229182

183+
private static string _GetCategoryTitleFormat(int type) => type switch
184+
{
185+
-1 => Lang.Text("Download.Comp.Favorites.SearchResults.Title"),
186+
(int)ModComp.CompType.Mod => Lang.Text("Download.Comp.Favorites.Category.Mod"),
187+
(int)ModComp.CompType.ModPack => $"{Lang.Text("Download.Comp.Type.Modpack")} ({{0}})",
188+
(int)ModComp.CompType.ResourcePack => $"{Lang.Text("Download.Comp.Type.ResourcePack")} ({{0}})",
189+
(int)ModComp.CompType.Shader => $"{Lang.Text("Download.Comp.Type.Shader")} ({{0}})",
190+
(int)ModComp.CompType.DataPack => $"{Lang.Text("Download.Comp.Type.DataPack")} ({{0}})",
191+
(int)ModComp.CompType.Plugin => $"{Lang.Text("Download.Comp.Type.Plugin")} ({{0}})",
192+
(int)ModComp.CompType.World => $"{Lang.Text("Download.Comp.Type.World")} ({{0}})",
193+
_ => $"{Lang.Text("Download.Comp.Favorites.UnknownType")} ({{0}})"
194+
};
195+
196+
private void _RefreshCategoryTitles()
197+
{
198+
foreach (var item in itemList)
199+
item.Title = _GetCategoryTitleFormat(item.CompType);
200+
RefreshCardTitle();
201+
}
202+
230203
private void RefreshCardTitle()
231204
{
232205
foreach (var item in itemList)

Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.cs

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public PageDownloadInstall()
2323
InitializeComponent();
2424
PanScroll = PanBack;
2525
LoadMinecraft.Text = Lang.Text("Download.Version.LoadingList");
26+
WeakLanguageChanged.Add(this, static page => page._OnLanguageChanged());
2627
BtnBack.Click += (_, _) => ExitSelectPage();
2728
CardOptiFine.Swap += (_, _) => ReloadSelected();
2829
LoadOptiFine.StateChanged += (_, _, _) => ReloadSelected();
@@ -995,34 +996,7 @@ private void LoadMinecraft_OnFinish()
995996
if (ModDownload.dlClientListLoader.output.Value["versions"] is not JsonArray versions)
996997
return;
997998

998-
var categoryOrder = new[]
999-
{
1000-
McVersionCategory.Release,
1001-
McVersionCategory.Snapshot,
1002-
McVersionCategory.BeforeRelease,
1003-
McVersionCategory.AprilFools
1004-
};
1005-
1006-
var dict = categoryOrder.ToDictionary(
1007-
category => category,
1008-
_ => new List<JsonObject>()
1009-
);
1010-
1011-
foreach (JsonObject version in versions)
1012-
{
1013-
var category = McVersionClassifier.ClassifyVersion(version);
1014-
dict[category].Add(version);
1015-
}
1016-
1017-
foreach (var category in categoryOrder)
1018-
dict[category] = dict[category]
1019-
.OrderByDescending(McVersionClassifier.GetReleaseTime)
1020-
.ToList();
1021-
1022-
PanMinecraft.Children.Clear();
1023-
1024-
_AddLatestVersionCard(dict);
1025-
_AddCategoryCards(dict, categoryOrder);
999+
_RebuildVersionCards(versions);
10261000

10271001
if (mcVersionWaitingForSelect is null) return;
10281002

@@ -1048,6 +1022,45 @@ private void LoadMinecraft_OnFinish()
10481022
}
10491023
}
10501024

1025+
private void _RebuildVersionCards(JsonArray versions)
1026+
{
1027+
var categoryOrder = new[]
1028+
{
1029+
McVersionCategory.Release,
1030+
McVersionCategory.Snapshot,
1031+
McVersionCategory.BeforeRelease,
1032+
McVersionCategory.AprilFools
1033+
};
1034+
1035+
var dict = categoryOrder.ToDictionary(
1036+
category => category,
1037+
_ => new List<JsonObject>()
1038+
);
1039+
1040+
foreach (JsonObject version in versions)
1041+
{
1042+
var category = McVersionClassifier.ClassifyVersion(version);
1043+
dict[category].Add(version);
1044+
}
1045+
1046+
foreach (var category in categoryOrder)
1047+
dict[category] = dict[category]
1048+
.OrderByDescending(McVersionClassifier.GetReleaseTime)
1049+
.ToList();
1050+
1051+
PanMinecraft.Children.Clear();
1052+
1053+
_AddLatestVersionCard(dict);
1054+
_AddCategoryCards(dict, categoryOrder);
1055+
}
1056+
1057+
private void _OnLanguageChanged() => ModBase.RunInUi(() =>
1058+
{
1059+
LoadMinecraft.Text = Lang.Text("Download.Version.LoadingList");
1060+
if (ModDownload.dlClientListLoader.output.Value?["versions"] is JsonArray versions)
1061+
_RebuildVersionCards(versions);
1062+
});
1063+
10511064
private void _AddLatestVersionCard(Dictionary<McVersionCategory, List<JsonObject>> dict)
10521065
{
10531066
var latestRelease = dict[McVersionCategory.Release].FirstOrDefault();

Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public partial class PageLaunchLeft
1616
private double actualUsedHeight;
1717
private double actualUsedWidth;
1818
private int btnLaunchState;
19+
private string _btnLaunchLanguage;
1920
private McInstance btnLaunchVersion;
2021
private bool isHeightAnimating;
2122
public interface ILoginPage { void Reload(); }
@@ -47,6 +48,7 @@ public PageLaunchLeft()
4748
{
4849
InitializeComponent();
4950
Loaded += PageLaunchLeft_Loaded;
51+
WeakLanguageChanged.Add(this, static page => ModBase.RunInUi(page.RefreshButtonsUI));
5052
// Handles
5153
BtnInstance.Click += BtnInstance_Click;
5254
BtnLaunch.Click += BtnLaunch_Click;
@@ -266,11 +268,14 @@ public void RefreshButtonsUI()
266268
currentState = 3;
267269
}
268270

269-
// 更新状态
271+
// 更新状态。
272+
var currentLanguage = LocalizationService.CurrentLanguage.Code;
270273
if (currentState == btnLaunchState &&
274+
currentLanguage == _btnLaunchLanguage &&
271275
((ModInstanceList.McMcInstanceSelected is null ? "" : ModInstanceList.McMcInstanceSelected.PathInstance) ?? "") ==
272276
((btnLaunchVersion is null ? "" : btnLaunchVersion.PathInstance) ?? ""))
273277
goto ExitRefresh;
278+
_btnLaunchLanguage = currentLanguage;
274279
btnLaunchVersion = ModInstanceList.McMcInstanceSelected;
275280
btnLaunchState = currentState;
276281
switch (currentState)

0 commit comments

Comments
 (0)