diff --git a/src/PSLauncher.App/App.xaml.cs b/src/PSLauncher.App/App.xaml.cs index bffe8f8..7a0f1eb 100644 --- a/src/PSLauncher.App/App.xaml.cs +++ b/src/PSLauncher.App/App.xaml.cs @@ -14,6 +14,7 @@ using PSLauncher.Core.Downloads; using PSLauncher.Core.Installations; using PSLauncher.Core.Integrity; using PSLauncher.Core.Licensing; +using PSLauncher.Core.Localization; using PSLauncher.Core.Manifests; using PSLauncher.Core.Process; using PSLauncher.Core.Updates; @@ -63,6 +64,20 @@ public partial class App : Application "PSLauncher", "logs"); Directory.CreateDirectory(LogsDirectory); + // Bootstrap i18n : on lit d'abord la config (sans services DI) pour fixer la + // langue avant que le moindre élément XAML soit instancié. + try + { + var bootstrapStore = new ConfigStore( + Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance); + var bootstrapCfg = bootstrapStore.Load(); + Strings.Init(bootstrapCfg.Language); + } + catch + { + Strings.Init("auto"); + } + Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .Enrich.FromLogContext() @@ -143,6 +158,9 @@ public partial class App : Application var window = _host.Services.GetRequiredService(); window.DataContext = _host.Services.GetRequiredService(); + // Mise en page droite-à-gauche pour les langues RTL (arabe). + if (Strings.IsRightToLeft) + window.FlowDirection = FlowDirection.RightToLeft; MainWindow = window; window.Show(); } diff --git a/src/PSLauncher.App/ViewModels/SettingsViewModel.cs b/src/PSLauncher.App/ViewModels/SettingsViewModel.cs index e459b29..447496f 100644 --- a/src/PSLauncher.App/ViewModels/SettingsViewModel.cs +++ b/src/PSLauncher.App/ViewModels/SettingsViewModel.cs @@ -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 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] diff --git a/src/PSLauncher.App/Views/MainWindow.xaml b/src/PSLauncher.App/Views/MainWindow.xaml index 53d62fd..8e64541 100644 --- a/src/PSLauncher.App/Views/MainWindow.xaml +++ b/src/PSLauncher.App/Views/MainWindow.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:PSLauncher.App.ViewModels" + xmlns:loc="clr-namespace:PSLauncher.Core.Localization;assembly=PSLauncher.Core" xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework" Title="PROSERVE Launcher" Icon="pack://application:,,,/Resources/favicon.ico" @@ -113,7 +114,7 @@