i18n: 5 languages (FR/EN/ZH/TH/AR), auto-detect from Windows + Settings picker
Localization infrastructure
---------------------------
PSLauncher.Core/Localization/Strings.cs is a static class exposing each
UI string as a static property. T(fr,en,zh,th,ar) helper switches by the
current language code. ~50 keys cover the visible top-level strings:
top bar, body, status badges, action buttons, "..." menu items, license
badge texts, Settings section headers, Onboarding dialog, Update dialog.
Bindings via x:Static in XAML:
xmlns:loc="clr-namespace:PSLauncher.Core.Localization;assembly=PSLauncher.Core"
Content="{x:Static loc:Strings.ActionLaunch}"
Auto-detection
--------------
LocalConfig gains a Language field defaulting to "auto". Strings.Init()
called at App.xaml.cs OnStartup before any UI:
- "auto" (or unknown code) → reads CultureInfo.CurrentUICulture
.TwoLetterISOLanguageName, picks the matching supported language,
falls back to English.
- explicit code → forced.
The chosen culture is then propagated to CurrentCulture / CurrentUICulture
so date/number formats follow.
Settings picker
---------------
SettingsDialog gets a top section "LANGUE" with a ComboBox bound to
SettingsViewModel.AvailableLanguages (Auto / FR / EN / ZH / TH / AR).
On Save, if the language code changed, prompt the user to confirm
restart, spawn `cmd /c timeout 1 & start PSLauncher.exe` and Shutdown
the current process — the new instance picks up the language at
bootstrap.
RTL for Arabic
--------------
Strings.IsRightToLeft is true when lang=="ar". App.xaml.cs sets
window.FlowDirection = RightToLeft on the MainWindow — WPF mirrors the
layout (icons on right, text aligned right).
Translations
------------
Done as best-effort by the assistant. The product wordmark "PROSERVE"
stays untranslated (brand). Logs and debug-level messages remain in
French in code — only user-visible UI is localized. Operator can
refine translations by editing Strings.cs and rebuilding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,8 @@ using PSLauncher.Models;
|
||||
|
||||
namespace PSLauncher.App.ViewModels;
|
||||
|
||||
public sealed record LanguageOption(string Code, string Name);
|
||||
|
||||
public sealed partial class SettingsViewModel : ObservableObject
|
||||
{
|
||||
private readonly IConfigStore _configStore;
|
||||
@@ -29,6 +31,12 @@ public sealed partial class SettingsViewModel : ObservableObject
|
||||
|
||||
[ObservableProperty] private string _serverBaseUrl;
|
||||
[ObservableProperty] private string _installRoot;
|
||||
[ObservableProperty] private string _language;
|
||||
|
||||
public IReadOnlyList<LanguageOption> AvailableLanguages { get; } =
|
||||
PSLauncher.Core.Localization.Strings.Available
|
||||
.Select(t => new LanguageOption(t.Code, t.Name))
|
||||
.ToList();
|
||||
[ObservableProperty] private string? _connectionStatus;
|
||||
[ObservableProperty] private bool _isTestingConnection;
|
||||
[ObservableProperty] private string _cacheSizeDisplay = "—";
|
||||
@@ -102,6 +110,7 @@ public sealed partial class SettingsViewModel : ObservableObject
|
||||
|
||||
_serverBaseUrl = config.ServerBaseUrl;
|
||||
_installRoot = config.InstallRoot;
|
||||
_language = string.IsNullOrWhiteSpace(config.Language) ? "auto" : config.Language;
|
||||
|
||||
LauncherVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0";
|
||||
MachineId = licenseService.GetMachineId();
|
||||
@@ -201,10 +210,48 @@ public sealed partial class SettingsViewModel : ObservableObject
|
||||
[RelayCommand]
|
||||
private void Save()
|
||||
{
|
||||
var languageChanged = !string.Equals(
|
||||
(_config.Language ?? "auto").ToLowerInvariant(),
|
||||
(Language ?? "auto").ToLowerInvariant(),
|
||||
StringComparison.Ordinal);
|
||||
|
||||
_config.ServerBaseUrl = ServerBaseUrl.TrimEnd('/');
|
||||
_config.InstallRoot = InstallRoot;
|
||||
_config.Language = Language ?? "auto";
|
||||
_configStore.Save(_config);
|
||||
_logger.LogInformation("Settings saved");
|
||||
_logger.LogInformation("Settings saved (language={Lang}, changed={Changed})", _config.Language, languageChanged);
|
||||
|
||||
if (languageChanged)
|
||||
{
|
||||
// Le launcher doit être relancé pour que les bindings x:Static reflètent
|
||||
// la nouvelle langue. Plutôt que de demander à l'utilisateur de fermer/rouvrir,
|
||||
// on spawne un cmd qui attend la fermeture puis relance.
|
||||
var ok = MessageBox.Show(
|
||||
"Le launcher va redémarrer pour appliquer la nouvelle langue.",
|
||||
"Changement de langue",
|
||||
MessageBoxButton.OKCancel, MessageBoxImage.Information, MessageBoxResult.OK);
|
||||
if (ok == MessageBoxResult.OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
var exe = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName;
|
||||
if (!string.IsNullOrEmpty(exe))
|
||||
{
|
||||
var pid = Environment.ProcessId;
|
||||
var cmd = $"/c (echo Wait && timeout /t 1 /nobreak > nul) & start \"\" \"{exe}\"";
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = cmd,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
});
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { _logger.LogError(ex, "Restart on language change failed"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
Reference in New Issue
Block a user