From b1b0dc1625bca52cc5d5c925aeb47b0ae0bf51c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 07:40:35 +0000 Subject: [PATCH] Implement localization in DiscordChatExporter following YoutubeDownloader pattern Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> --- DiscordChatExporter.Gui/App.axaml.cs | 4 + .../Localization/Language.cs | 11 ++ .../LocalizationManager.English.cs | 99 +++++++++++ .../LocalizationManager.French.cs | 101 +++++++++++ .../LocalizationManager.German.cs | 105 ++++++++++++ .../LocalizationManager.Spanish.cs | 99 +++++++++++ .../LocalizationManager.Ukrainian.cs | 98 +++++++++++ .../Localization/LocalizationManager.cs | 158 ++++++++++++++++++ .../Services/SettingsService.cs | 4 + .../Components/DashboardViewModel.cs | 18 +- .../Dialogs/ExportSetupViewModel.cs | 6 +- .../ViewModels/Dialogs/SettingsViewModel.cs | 17 +- .../ViewModels/MainViewModel.cs | 44 +++-- .../Views/Components/DashboardView.axaml | 4 +- .../Views/Dialogs/ExportSetupView.axaml | 54 +++--- .../Views/Dialogs/SettingsView.axaml | 49 ++++-- 16 files changed, 794 insertions(+), 77 deletions(-) create mode 100644 DiscordChatExporter.Gui/Localization/Language.cs create mode 100644 DiscordChatExporter.Gui/Localization/LocalizationManager.English.cs create mode 100644 DiscordChatExporter.Gui/Localization/LocalizationManager.French.cs create mode 100644 DiscordChatExporter.Gui/Localization/LocalizationManager.German.cs create mode 100644 DiscordChatExporter.Gui/Localization/LocalizationManager.Spanish.cs create mode 100644 DiscordChatExporter.Gui/Localization/LocalizationManager.Ukrainian.cs create mode 100644 DiscordChatExporter.Gui/Localization/LocalizationManager.cs diff --git a/DiscordChatExporter.Gui/App.axaml.cs b/DiscordChatExporter.Gui/App.axaml.cs index 6fb013d2..0673e3f4 100644 --- a/DiscordChatExporter.Gui/App.axaml.cs +++ b/DiscordChatExporter.Gui/App.axaml.cs @@ -5,6 +5,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Platform; using DiscordChatExporter.Gui.Framework; +using DiscordChatExporter.Gui.Localization; using DiscordChatExporter.Gui.Services; using DiscordChatExporter.Gui.Utils; using DiscordChatExporter.Gui.Utils.Extensions; @@ -39,6 +40,9 @@ public class App : Application, IDisposable services.AddSingleton(); services.AddSingleton(); + // Localization + services.AddSingleton(); + // View models services.AddTransient(); services.AddTransient(); diff --git a/DiscordChatExporter.Gui/Localization/Language.cs b/DiscordChatExporter.Gui/Localization/Language.cs new file mode 100644 index 00000000..faebe5bf --- /dev/null +++ b/DiscordChatExporter.Gui/Localization/Language.cs @@ -0,0 +1,11 @@ +namespace DiscordChatExporter.Gui.Localization; + +public enum Language +{ + System, + English, + Ukrainian, + German, + French, + Spanish, +} diff --git a/DiscordChatExporter.Gui/Localization/LocalizationManager.English.cs b/DiscordChatExporter.Gui/Localization/LocalizationManager.English.cs new file mode 100644 index 00000000..a4cec7d1 --- /dev/null +++ b/DiscordChatExporter.Gui/Localization/LocalizationManager.English.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; + +namespace DiscordChatExporter.Gui.Localization; + +public partial class LocalizationManager +{ + private static readonly IReadOnlyDictionary EnglishLocalization = + new Dictionary + { + // Dashboard + [nameof(PullGuildsTooltip)] = "Pull available servers and channels (Enter)", + [nameof(SettingsTooltip)] = "Settings", + [nameof(LastMessageSentTooltip)] = "Last message sent:", + // Settings + [nameof(SettingsTitle)] = "Settings", + [nameof(ThemeLabel)] = "Theme", + [nameof(ThemeTooltip)] = "Preferred user interface theme", + [nameof(LanguageLabel)] = "Language", + [nameof(LanguageTooltip)] = "Preferred user interface language", + [nameof(AutoUpdateLabel)] = "Auto-update", + [nameof(AutoUpdateTooltip)] = "Perform automatic updates on every launch", + [nameof(PersistTokenLabel)] = "Persist token", + [nameof(PersistTokenTooltip)] = + "Save the last used token to a file so that it can be persisted between sessions", + [nameof(RateLimitPreferenceLabel)] = "Rate limit preference", + [nameof(RateLimitPreferenceTooltip)] = + "Whether to respect advisory rate limits. If disabled, only hard rate limits (i.e. 429 responses) will be respected.", + [nameof(ShowThreadsLabel)] = "Show threads", + [nameof(ShowThreadsTooltip)] = "Which types of threads to show in the channel list", + [nameof(LocaleLabel)] = "Locale", + [nameof(LocaleTooltip)] = "Locale to use when formatting dates and numbers", + [nameof(NormalizeToUtcLabel)] = "Normalize to UTC", + [nameof(NormalizeToUtcTooltip)] = "Normalize all timestamps to UTC+0", + [nameof(ParallelLimitLabel)] = "Parallel limit", + [nameof(ParallelLimitTooltip)] = "How many channels can be exported at the same time", + // Export Setup + [nameof(ChannelsSelectedText)] = "channels selected", + [nameof(OutputPathLabel)] = "Output path", + [nameof(FormatLabel)] = "Format", + [nameof(FormatTooltip)] = "Export format", + [nameof(AfterDateLabel)] = "After (date)", + [nameof(AfterDateTooltip)] = "Only include messages sent after this date", + [nameof(BeforeDateLabel)] = "Before (date)", + [nameof(BeforeDateTooltip)] = "Only include messages sent before this date", + [nameof(AfterTimeLabel)] = "After (time)", + [nameof(AfterTimeTooltip)] = "Only include messages sent after this time", + [nameof(BeforeTimeLabel)] = "Before (time)", + [nameof(BeforeTimeTooltip)] = "Only include messages sent before this time", + [nameof(PartitionLimitLabel)] = "Partition limit", + [nameof(PartitionLimitTooltip)] = + "Split the output into partitions, each limited to the specified number of messages (e.g. '100') or file size (e.g. '10mb')", + [nameof(MessageFilterLabel)] = "Message filter", + [nameof(MessageFilterTooltip)] = + "Only include messages that satisfy this filter (e.g. 'from:foo#1234' or 'has:image'). See the documentation for more info.", + [nameof(FormatMarkdownLabel)] = "Format markdown", + [nameof(FormatMarkdownTooltip)] = + "Process markdown, mentions, and other special tokens", + [nameof(DownloadAssetsLabel)] = "Download assets", + [nameof(DownloadAssetsTooltip)] = + "Download assets referenced by the export (user avatars, attached files, embedded images, etc.)", + [nameof(ReuseAssetsLabel)] = "Reuse assets", + [nameof(ReuseAssetsTooltip)] = + "Reuse previously downloaded assets to avoid redundant requests", + [nameof(AssetsDirPathLabel)] = "Assets directory path", + [nameof(AssetsDirPathTooltip)] = + "Download assets to this directory. If not specified, the asset directory path will be derived from the output path.", + [nameof(AdvancedOptionsTooltip)] = "Toggle advanced options", + [nameof(ExportButton)] = "EXPORT", + // Common buttons + [nameof(CloseButton)] = "CLOSE", + [nameof(CancelButton)] = "CANCEL", + // Dialog messages + [nameof(UkraineSupportTitle)] = "Thank you for supporting Ukraine!", + [nameof(UkraineSupportMessage)] = """ + As Russia wages a genocidal war against my country, I'm grateful to everyone who continues to stand with Ukraine in our fight for freedom. + + Click LEARN MORE to find ways that you can help. + """, + [nameof(LearnMoreButton)] = "LEARN MORE", + [nameof(UnstableBuildTitle)] = "Unstable build warning", + [nameof(UnstableBuildMessage)] = """ + You're using a development build of {0}. These builds are not thoroughly tested and may contain bugs. + + Auto-updates are disabled for development builds. + + Click SEE RELEASES if you want to download a stable release instead. + """, + [nameof(SeeReleasesButton)] = "SEE RELEASES", + [nameof(UpdateDownloadingMessage)] = "Downloading update to {0} v{1}...", + [nameof(UpdateReadyMessage)] = + "Update has been downloaded and will be installed when you exit", + [nameof(UpdateInstallNowButton)] = "INSTALL NOW", + [nameof(UpdateFailedMessage)] = "Failed to perform application update", + [nameof(ErrorPullingServersTitle)] = "Error pulling servers", + [nameof(ErrorPullingChannelsTitle)] = "Error pulling channels", + [nameof(ErrorExportingTitle)] = "Error exporting channel(s)", + [nameof(SuccessfulExportMessage)] = "Successfully exported {0} channel(s)", + }; +} diff --git a/DiscordChatExporter.Gui/Localization/LocalizationManager.French.cs b/DiscordChatExporter.Gui/Localization/LocalizationManager.French.cs new file mode 100644 index 00000000..48ad4827 --- /dev/null +++ b/DiscordChatExporter.Gui/Localization/LocalizationManager.French.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; + +namespace DiscordChatExporter.Gui.Localization; + +public partial class LocalizationManager +{ + private static readonly IReadOnlyDictionary FrenchLocalization = new Dictionary< + string, + string + > + { + // Dashboard + [nameof(PullGuildsTooltip)] = "Charger les serveurs et canaux disponibles (Entrée)", + [nameof(SettingsTooltip)] = "Paramètres", + [nameof(LastMessageSentTooltip)] = "Dernier message envoyé :", + // Settings + [nameof(SettingsTitle)] = "Paramètres", + [nameof(ThemeLabel)] = "Thème", + [nameof(ThemeTooltip)] = "Thème d'interface préféré", + [nameof(LanguageLabel)] = "Langue", + [nameof(LanguageTooltip)] = "Langue d'interface préférée", + [nameof(AutoUpdateLabel)] = "Mise à jour automatique", + [nameof(AutoUpdateTooltip)] = "Effectuer des mises à jour automatiques à chaque lancement", + [nameof(PersistTokenLabel)] = "Conserver le token", + [nameof(PersistTokenTooltip)] = + "Enregistrer le dernier token utilisé dans un fichier pour le conserver entre les sessions", + [nameof(RateLimitPreferenceLabel)] = "Préférence de limite de débit", + [nameof(RateLimitPreferenceTooltip)] = + "Indique s'il faut respecter les limites de débit recommandées. Si désactivé, seules les limites strictes (réponses 429) seront respectées.", + [nameof(ShowThreadsLabel)] = "Afficher les fils", + [nameof(ShowThreadsTooltip)] = "Quels types de fils afficher dans la liste des canaux", + [nameof(LocaleLabel)] = "Locale", + [nameof(LocaleTooltip)] = "Locale à utiliser pour le formatage des dates et des nombres", + [nameof(NormalizeToUtcLabel)] = "Normaliser en UTC", + [nameof(NormalizeToUtcTooltip)] = "Normaliser tous les horodatages en UTC+0", + [nameof(ParallelLimitLabel)] = "Limite parallèle", + [nameof(ParallelLimitTooltip)] = "Combien de canaux peuvent être exportés simultanément", + // Export Setup + [nameof(ChannelsSelectedText)] = "canaux sélectionnés", + [nameof(OutputPathLabel)] = "Chemin de sortie", + [nameof(FormatLabel)] = "Format", + [nameof(FormatTooltip)] = "Format d'exportation", + [nameof(AfterDateLabel)] = "Après (date)", + [nameof(AfterDateTooltip)] = "Inclure uniquement les messages envoyés après cette date", + [nameof(BeforeDateLabel)] = "Avant (date)", + [nameof(BeforeDateTooltip)] = "Inclure uniquement les messages envoyés avant cette date", + [nameof(AfterTimeLabel)] = "Après (heure)", + [nameof(AfterTimeTooltip)] = "Inclure uniquement les messages envoyés après cette heure", + [nameof(BeforeTimeLabel)] = "Avant (heure)", + [nameof(BeforeTimeTooltip)] = "Inclure uniquement les messages envoyés avant cette heure", + [nameof(PartitionLimitLabel)] = "Limite de partition", + [nameof(PartitionLimitTooltip)] = + "Diviser la sortie en partitions, chacune limitée au nombre de messages spécifié (ex. '100') ou à la taille de fichier (ex. '10mb')", + [nameof(MessageFilterLabel)] = "Filtre de messages", + [nameof(MessageFilterTooltip)] = + "Inclure uniquement les messages satisfaisant ce filtre (ex. 'from:foo#1234' ou 'has:image'). Voir la documentation pour plus d'informations.", + [nameof(FormatMarkdownLabel)] = "Formater le markdown", + [nameof(FormatMarkdownTooltip)] = + "Traiter le markdown, les mentions et autres tokens spéciaux", + [nameof(DownloadAssetsLabel)] = "Télécharger les ressources", + [nameof(DownloadAssetsTooltip)] = + "Télécharger les ressources référencées par l'export (avatars, fichiers joints, images intégrées, etc.)", + [nameof(ReuseAssetsLabel)] = "Réutiliser les ressources", + [nameof(ReuseAssetsTooltip)] = + "Réutiliser les ressources précédemment téléchargées pour éviter les requêtes redondantes", + [nameof(AssetsDirPathLabel)] = "Chemin du dossier des ressources", + [nameof(AssetsDirPathTooltip)] = + "Télécharger les ressources dans ce dossier. Si non spécifié, le chemin sera dérivé du chemin de sortie.", + [nameof(AdvancedOptionsTooltip)] = "Basculer les options avancées", + [nameof(ExportButton)] = "EXPORTER", + // Common buttons + [nameof(CloseButton)] = "FERMER", + [nameof(CancelButton)] = "ANNULER", + // Dialog messages + [nameof(UkraineSupportTitle)] = "Merci de soutenir l'Ukraine !", + [nameof(UkraineSupportMessage)] = """ + Alors que la Russie mène une guerre génocidaire contre mon pays, je suis reconnaissant envers tous ceux qui continuent à soutenir l'Ukraine dans notre lutte pour la liberté. + + Cliquez sur EN SAVOIR PLUS pour trouver des moyens d'aider. + """, + [nameof(LearnMoreButton)] = "EN SAVOIR PLUS", + [nameof(UnstableBuildTitle)] = "Avertissement : version instable", + [nameof(UnstableBuildMessage)] = """ + Vous utilisez une version de développement de {0}. Ces versions ne sont pas rigoureusement testées et peuvent contenir des bugs. + + Les mises à jour automatiques sont désactivées pour les versions de développement. + + Cliquez sur VOIR LES VERSIONS pour télécharger une version stable. + """, + [nameof(SeeReleasesButton)] = "VOIR LES VERSIONS", + [nameof(UpdateDownloadingMessage)] = "Téléchargement de la mise à jour vers {0} v{1}...", + [nameof(UpdateReadyMessage)] = + "La mise à jour a été téléchargée et sera installée à la fermeture", + [nameof(UpdateInstallNowButton)] = "INSTALLER MAINTENANT", + [nameof(UpdateFailedMessage)] = "Échec de la mise à jour de l'application", + [nameof(ErrorPullingServersTitle)] = "Erreur lors du chargement des serveurs", + [nameof(ErrorPullingChannelsTitle)] = "Erreur lors du chargement des canaux", + [nameof(ErrorExportingTitle)] = "Erreur lors de l'exportation des canaux", + [nameof(SuccessfulExportMessage)] = "{0} canal(-aux) exporté(s) avec succès", + }; +} diff --git a/DiscordChatExporter.Gui/Localization/LocalizationManager.German.cs b/DiscordChatExporter.Gui/Localization/LocalizationManager.German.cs new file mode 100644 index 00000000..b88b4281 --- /dev/null +++ b/DiscordChatExporter.Gui/Localization/LocalizationManager.German.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; + +namespace DiscordChatExporter.Gui.Localization; + +public partial class LocalizationManager +{ + private static readonly IReadOnlyDictionary GermanLocalization = new Dictionary< + string, + string + > + { + // Dashboard + [nameof(PullGuildsTooltip)] = "Verfügbare Server und Kanäle laden (Enter)", + [nameof(SettingsTooltip)] = "Einstellungen", + [nameof(LastMessageSentTooltip)] = "Letzte Nachricht gesendet:", + // Settings + [nameof(SettingsTitle)] = "Einstellungen", + [nameof(ThemeLabel)] = "Design", + [nameof(ThemeTooltip)] = "Bevorzugtes Oberflächendesign", + [nameof(LanguageLabel)] = "Sprache", + [nameof(LanguageTooltip)] = "Bevorzugte Sprache der Benutzeroberfläche", + [nameof(AutoUpdateLabel)] = "Automatische Updates", + [nameof(AutoUpdateTooltip)] = "Automatische Updates bei jedem Start durchführen", + [nameof(PersistTokenLabel)] = "Token speichern", + [nameof(PersistTokenTooltip)] = + "Den zuletzt verwendeten Token in einer Datei speichern, damit er zwischen Sitzungen erhalten bleibt", + [nameof(RateLimitPreferenceLabel)] = "Ratenlimit-Einstellung", + [nameof(RateLimitPreferenceTooltip)] = + "Ob empfohlene Ratenlimits eingehalten werden sollen. Wenn deaktiviert, werden nur harte Ratenlimits (d. h. 429-Antworten) eingehalten.", + [nameof(ShowThreadsLabel)] = "Threads anzeigen", + [nameof(ShowThreadsTooltip)] = "Welche Thread-Typen in der Kanalliste angezeigt werden", + [nameof(LocaleLabel)] = "Gebietsschema", + [nameof(LocaleTooltip)] = "Gebietsschema für die Formatierung von Daten und Zahlen", + [nameof(NormalizeToUtcLabel)] = "Auf UTC normalisieren", + [nameof(NormalizeToUtcTooltip)] = "Alle Zeitstempel auf UTC+0 normalisieren", + [nameof(ParallelLimitLabel)] = "Paralleles Limit", + [nameof(ParallelLimitTooltip)] = "Wie viele Kanäle gleichzeitig exportiert werden können", + // Export Setup + [nameof(ChannelsSelectedText)] = "Kanäle ausgewählt", + [nameof(OutputPathLabel)] = "Ausgabepfad", + [nameof(FormatLabel)] = "Format", + [nameof(FormatTooltip)] = "Exportformat", + [nameof(AfterDateLabel)] = "Nach (Datum)", + [nameof(AfterDateTooltip)] = + "Nur Nachrichten einschließen, die nach diesem Datum gesendet wurden", + [nameof(BeforeDateLabel)] = "Vor (Datum)", + [nameof(BeforeDateTooltip)] = + "Nur Nachrichten einschließen, die vor diesem Datum gesendet wurden", + [nameof(AfterTimeLabel)] = "Nach (Uhrzeit)", + [nameof(AfterTimeTooltip)] = + "Nur Nachrichten einschließen, die nach dieser Uhrzeit gesendet wurden", + [nameof(BeforeTimeLabel)] = "Vor (Uhrzeit)", + [nameof(BeforeTimeTooltip)] = + "Nur Nachrichten einschließen, die vor dieser Uhrzeit gesendet wurden", + [nameof(PartitionLimitLabel)] = "Partitionslimit", + [nameof(PartitionLimitTooltip)] = + "Die Ausgabe in Partitionen aufteilen, jede begrenzt auf die angegebene Anzahl von Nachrichten (z. B. '100') oder Dateigröße (z. B. '10mb')", + [nameof(MessageFilterLabel)] = "Nachrichtenfilter", + [nameof(MessageFilterTooltip)] = + "Nur Nachrichten einschließen, die diesem Filter entsprechen (z. B. 'from:foo#1234' oder 'has:image'). Weitere Informationen finden Sie in der Dokumentation.", + [nameof(FormatMarkdownLabel)] = "Markdown formatieren", + [nameof(FormatMarkdownTooltip)] = + "Markdown, Erwähnungen und andere spezielle Token verarbeiten", + [nameof(DownloadAssetsLabel)] = "Assets herunterladen", + [nameof(DownloadAssetsTooltip)] = + "Vom Export referenzierte Assets herunterladen (Benutzeravatare, angehängte Dateien, eingebettete Bilder usw.)", + [nameof(ReuseAssetsLabel)] = "Assets wiederverwenden", + [nameof(ReuseAssetsTooltip)] = + "Zuvor heruntergeladene Assets wiederverwenden, um redundante Anfragen zu vermeiden", + [nameof(AssetsDirPathLabel)] = "Asset-Verzeichnispfad", + [nameof(AssetsDirPathTooltip)] = + "Assets in dieses Verzeichnis herunterladen. Wenn nicht angegeben, wird der Asset-Verzeichnispfad vom Ausgabepfad abgeleitet.", + [nameof(AdvancedOptionsTooltip)] = "Erweiterte Optionen umschalten", + [nameof(ExportButton)] = "EXPORTIEREN", + // Common buttons + [nameof(CloseButton)] = "SCHLIESSEN", + [nameof(CancelButton)] = "ABBRECHEN", + // Dialog messages + [nameof(UkraineSupportTitle)] = "Danke für Ihre Unterstützung der Ukraine!", + [nameof(UkraineSupportMessage)] = """ + Während Russland einen Vernichtungskrieg gegen mein Land führt, bin ich jedem dankbar, der weiterhin an der Seite der Ukraine in unserem Kampf für die Freiheit steht. + + Klicken Sie auf MEHR ERFAHREN, um Möglichkeiten der Unterstützung zu finden. + """, + [nameof(LearnMoreButton)] = "MEHR ERFAHREN", + [nameof(UnstableBuildTitle)] = "Warnung: Instabile Version", + [nameof(UnstableBuildMessage)] = """ + Sie verwenden eine Entwicklungsversion von {0}. Diese Versionen wurden nicht gründlich getestet und können Fehler enthalten. + + Automatische Updates sind für Entwicklungsversionen deaktiviert. + + Klicken Sie auf RELEASES ANZEIGEN, wenn Sie stattdessen eine stabile Version herunterladen möchten. + """, + [nameof(SeeReleasesButton)] = "RELEASES ANZEIGEN", + [nameof(UpdateDownloadingMessage)] = "Update auf {0} v{1} wird heruntergeladen...", + [nameof(UpdateReadyMessage)] = + "Update wurde heruntergeladen und wird beim Beenden installiert", + [nameof(UpdateInstallNowButton)] = "JETZT INSTALLIEREN", + [nameof(UpdateFailedMessage)] = "Anwendungsupdate konnte nicht durchgeführt werden", + [nameof(ErrorPullingServersTitle)] = "Fehler beim Laden der Server", + [nameof(ErrorPullingChannelsTitle)] = "Fehler beim Laden der Kanäle", + [nameof(ErrorExportingTitle)] = "Fehler beim Exportieren der Kanäle", + [nameof(SuccessfulExportMessage)] = "{0} Kanal/-äle erfolgreich exportiert", + }; +} diff --git a/DiscordChatExporter.Gui/Localization/LocalizationManager.Spanish.cs b/DiscordChatExporter.Gui/Localization/LocalizationManager.Spanish.cs new file mode 100644 index 00000000..faebfed4 --- /dev/null +++ b/DiscordChatExporter.Gui/Localization/LocalizationManager.Spanish.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; + +namespace DiscordChatExporter.Gui.Localization; + +public partial class LocalizationManager +{ + private static readonly IReadOnlyDictionary SpanishLocalization = + new Dictionary + { + // Dashboard + [nameof(PullGuildsTooltip)] = "Cargar servidores y canales disponibles (Enter)", + [nameof(SettingsTooltip)] = "Ajustes", + [nameof(LastMessageSentTooltip)] = "Último mensaje enviado:", + // Settings + [nameof(SettingsTitle)] = "Ajustes", + [nameof(ThemeLabel)] = "Tema", + [nameof(ThemeTooltip)] = "Tema de interfaz preferido", + [nameof(LanguageLabel)] = "Idioma", + [nameof(LanguageTooltip)] = "Idioma de interfaz preferido", + [nameof(AutoUpdateLabel)] = "Actualización automática", + [nameof(AutoUpdateTooltip)] = "Realizar actualizaciones automáticas en cada inicio", + [nameof(PersistTokenLabel)] = "Guardar token", + [nameof(PersistTokenTooltip)] = + "Guardar el último token utilizado en un archivo para conservarlo entre sesiones", + [nameof(RateLimitPreferenceLabel)] = "Preferencia de límite de velocidad", + [nameof(RateLimitPreferenceTooltip)] = + "Si se deben respetar los límites de velocidad recomendados. Si está desactivado, solo se respetarán los límites estrictos (respuestas 429).", + [nameof(ShowThreadsLabel)] = "Mostrar hilos", + [nameof(ShowThreadsTooltip)] = "Qué tipos de hilos mostrar en la lista de canales", + [nameof(LocaleLabel)] = "Configuración regional", + [nameof(LocaleTooltip)] = "Configuración regional para el formato de fechas y números", + [nameof(NormalizeToUtcLabel)] = "Normalizar a UTC", + [nameof(NormalizeToUtcTooltip)] = "Normalizar todas las marcas de tiempo a UTC+0", + [nameof(ParallelLimitLabel)] = "Límite paralelo", + [nameof(ParallelLimitTooltip)] = "Cuántos canales pueden exportarse al mismo tiempo", + // Export Setup + [nameof(ChannelsSelectedText)] = "canales seleccionados", + [nameof(OutputPathLabel)] = "Ruta de salida", + [nameof(FormatLabel)] = "Formato", + [nameof(FormatTooltip)] = "Formato de exportación", + [nameof(AfterDateLabel)] = "Después (fecha)", + [nameof(AfterDateTooltip)] = "Solo incluir mensajes enviados después de esta fecha", + [nameof(BeforeDateLabel)] = "Antes (fecha)", + [nameof(BeforeDateTooltip)] = "Solo incluir mensajes enviados antes de esta fecha", + [nameof(AfterTimeLabel)] = "Después (hora)", + [nameof(AfterTimeTooltip)] = "Solo incluir mensajes enviados después de esta hora", + [nameof(BeforeTimeLabel)] = "Antes (hora)", + [nameof(BeforeTimeTooltip)] = "Solo incluir mensajes enviados antes de esta hora", + [nameof(PartitionLimitLabel)] = "Límite de partición", + [nameof(PartitionLimitTooltip)] = + "Dividir la salida en particiones, cada una limitada al número de mensajes especificado (p. ej. '100') o tamaño de archivo (p. ej. '10mb')", + [nameof(MessageFilterLabel)] = "Filtro de mensajes", + [nameof(MessageFilterTooltip)] = + "Solo incluir mensajes que satisfagan este filtro (p. ej. 'from:foo#1234' o 'has:image'). Consulte la documentación para más información.", + [nameof(FormatMarkdownLabel)] = "Formatear markdown", + [nameof(FormatMarkdownTooltip)] = + "Procesar markdown, menciones y otros tokens especiales", + [nameof(DownloadAssetsLabel)] = "Descargar recursos", + [nameof(DownloadAssetsTooltip)] = + "Descargar los recursos referenciados por la exportación (avatares, archivos adjuntos, imágenes incrustadas, etc.)", + [nameof(ReuseAssetsLabel)] = "Reutilizar recursos", + [nameof(ReuseAssetsTooltip)] = + "Reutilizar recursos previamente descargados para evitar solicitudes redundantes", + [nameof(AssetsDirPathLabel)] = "Ruta del directorio de recursos", + [nameof(AssetsDirPathTooltip)] = + "Descargar recursos en este directorio. Si no se especifica, la ruta se derivará de la ruta de salida.", + [nameof(AdvancedOptionsTooltip)] = "Alternar opciones avanzadas", + [nameof(ExportButton)] = "EXPORTAR", + // Common buttons + [nameof(CloseButton)] = "CERRAR", + [nameof(CancelButton)] = "CANCELAR", + // Dialog messages + [nameof(UkraineSupportTitle)] = "¡Gracias por apoyar a Ucrania!", + [nameof(UkraineSupportMessage)] = """ + Mientras Rusia libra una guerra genocida contra mi país, estoy agradecido con todos los que continúan apoyando a Ucrania en nuestra lucha por la libertad. + + Haga clic en MÁS INFORMACIÓN para encontrar formas de ayudar. + """, + [nameof(LearnMoreButton)] = "MÁS INFORMACIÓN", + [nameof(UnstableBuildTitle)] = "Advertencia de versión inestable", + [nameof(UnstableBuildMessage)] = """ + Está usando una versión de desarrollo de {0}. Estas versiones no han sido probadas exhaustivamente y pueden contener errores. + + Las actualizaciones automáticas están desactivadas para las versiones de desarrollo. + + Haga clic en VER VERSIONES si desea descargar una versión estable. + """, + [nameof(SeeReleasesButton)] = "VER VERSIONES", + [nameof(UpdateDownloadingMessage)] = "Descargando actualización a {0} v{1}...", + [nameof(UpdateReadyMessage)] = + "La actualización se ha descargado y se instalará al salir", + [nameof(UpdateInstallNowButton)] = "INSTALAR AHORA", + [nameof(UpdateFailedMessage)] = "Error al realizar la actualización de la aplicación", + [nameof(ErrorPullingServersTitle)] = "Error al cargar servidores", + [nameof(ErrorPullingChannelsTitle)] = "Error al cargar canales", + [nameof(ErrorExportingTitle)] = "Error al exportar canal(es)", + [nameof(SuccessfulExportMessage)] = "{0} canal(es) exportado(s) con éxito", + }; +} diff --git a/DiscordChatExporter.Gui/Localization/LocalizationManager.Ukrainian.cs b/DiscordChatExporter.Gui/Localization/LocalizationManager.Ukrainian.cs new file mode 100644 index 00000000..35be702d --- /dev/null +++ b/DiscordChatExporter.Gui/Localization/LocalizationManager.Ukrainian.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; + +namespace DiscordChatExporter.Gui.Localization; + +public partial class LocalizationManager +{ + private static readonly IReadOnlyDictionary UkrainianLocalization = + new Dictionary + { + // Dashboard + [nameof(PullGuildsTooltip)] = "Завантажити доступні сервери та канали (Enter)", + [nameof(SettingsTooltip)] = "Налаштування", + [nameof(LastMessageSentTooltip)] = "Останнє повідомлення:", + // Settings + [nameof(SettingsTitle)] = "Налаштування", + [nameof(ThemeLabel)] = "Тема", + [nameof(ThemeTooltip)] = "Бажана тема інтерфейсу", + [nameof(LanguageLabel)] = "Мова", + [nameof(LanguageTooltip)] = "Бажана мова інтерфейсу", + [nameof(AutoUpdateLabel)] = "Авто-оновлення", + [nameof(AutoUpdateTooltip)] = "Виконувати автоматичні оновлення при кожному запуску", + [nameof(PersistTokenLabel)] = "Зберігати токен", + [nameof(PersistTokenTooltip)] = + "Зберігати останній використаний токен у файлі для збереження між сеансами", + [nameof(RateLimitPreferenceLabel)] = "Ліміт запитів", + [nameof(RateLimitPreferenceTooltip)] = + "Чи дотримуватись рекомендованих лімітів запитів. Якщо вимкнено, будуть дотримуватись лише жорсткі ліміти (тобто відповіді 429).", + [nameof(ShowThreadsLabel)] = "Показувати теми", + [nameof(ShowThreadsTooltip)] = "Які типи тем показувати у списку каналів", + [nameof(LocaleLabel)] = "Локаль", + [nameof(LocaleTooltip)] = "Локаль для форматування дат та чисел", + [nameof(NormalizeToUtcLabel)] = "Нормалізувати до UTC", + [nameof(NormalizeToUtcTooltip)] = "Нормалізувати всі часові мітки до UTC+0", + [nameof(ParallelLimitLabel)] = "Паралельний ліміт", + [nameof(ParallelLimitTooltip)] = "Скільки каналів може експортуватись одночасно", + // Export Setup + [nameof(ChannelsSelectedText)] = "каналів вибрано", + [nameof(OutputPathLabel)] = "Шлях виведення", + [nameof(FormatLabel)] = "Формат", + [nameof(FormatTooltip)] = "Формат експорту", + [nameof(AfterDateLabel)] = "Після (дата)", + [nameof(AfterDateTooltip)] = "Включати лише повідомлення, надіслані після цієї дати", + [nameof(BeforeDateLabel)] = "До (дата)", + [nameof(BeforeDateTooltip)] = "Включати лише повідомлення, надіслані до цієї дати", + [nameof(AfterTimeLabel)] = "Після (час)", + [nameof(AfterTimeTooltip)] = "Включати лише повідомлення, надіслані після цього часу", + [nameof(BeforeTimeLabel)] = "До (час)", + [nameof(BeforeTimeTooltip)] = "Включати лише повідомлення, надіслані до цього часу", + [nameof(PartitionLimitLabel)] = "Ліміт розподілу", + [nameof(PartitionLimitTooltip)] = + "Розділити вивід на частини, кожна обмежена вказаною кількістю повідомлень (напр. '100') або розміром файлу (напр. '10mb')", + [nameof(MessageFilterLabel)] = "Фільтр повідомлень", + [nameof(MessageFilterTooltip)] = + "Включати лише повідомлення, що відповідають цьому фільтру (напр. 'from:foo#1234' або 'has:image'). Дивіться документацію для більш детальної інформації.", + [nameof(FormatMarkdownLabel)] = "Форматувати markdown", + [nameof(FormatMarkdownTooltip)] = + "Обробляти markdown, згадки та інші спеціальні токени", + [nameof(DownloadAssetsLabel)] = "Завантажувати ресурси", + [nameof(DownloadAssetsTooltip)] = + "Завантажувати ресурси, на які посилається експорт (аватари, вкладені файли, вбудовані зображення тощо)", + [nameof(ReuseAssetsLabel)] = "Повторно використовувати ресурси", + [nameof(ReuseAssetsTooltip)] = + "Повторно використовувати раніше завантажені ресурси, щоб уникнути зайвих запитів", + [nameof(AssetsDirPathLabel)] = "Шлях до директорії ресурсів", + [nameof(AssetsDirPathTooltip)] = + "Завантажувати ресурси до цієї директорії. Якщо не вказано, шлях до директорії ресурсів буде визначено з шляху виведення.", + [nameof(AdvancedOptionsTooltip)] = "Перемкнути розширені параметри", + [nameof(ExportButton)] = "ЕКСПОРТУВАТИ", + // Common buttons + [nameof(CloseButton)] = "ЗАКРИТИ", + [nameof(CancelButton)] = "СКАСУВАТИ", + // Dialog messages + [nameof(UkraineSupportTitle)] = "Дякуємо за підтримку України!", + [nameof(UkraineSupportMessage)] = """ + Поки Росія веде геноцидну війну проти моєї країни, я вдячний кожному, хто продовжує підтримувати Україну у нашій боротьбі за свободу. + + Натисніть ДІЗНАТИСЬ БІЛЬШЕ, щоб знайти способи допомогти. + """, + [nameof(LearnMoreButton)] = "ДІЗНАТИСЬ БІЛЬШЕ", + [nameof(UnstableBuildTitle)] = "Попередження про нестабільну збірку", + [nameof(UnstableBuildMessage)] = """ + Ви використовуєте збірку розробки {0}. Ці збірки не пройшли ретельного тестування та можуть містити помилки. + + Авто-оновлення вимкнено для збірок розробки. + + Натисніть ПЕРЕГЛЯНУТИ РЕЛІЗИ, щоб завантажити стабільний реліз. + """, + [nameof(SeeReleasesButton)] = "ПЕРЕГЛЯНУТИ РЕЛІЗИ", + [nameof(UpdateDownloadingMessage)] = "Завантаження оновлення {0} v{1}...", + [nameof(UpdateReadyMessage)] = "Оновлення завантажено та буде встановлено після виходу", + [nameof(UpdateInstallNowButton)] = "ВСТАНОВИТИ ЗАРАЗ", + [nameof(UpdateFailedMessage)] = "Не вдалося виконати оновлення програми", + [nameof(ErrorPullingServersTitle)] = "Помилка завантаження серверів", + [nameof(ErrorPullingChannelsTitle)] = "Помилка завантаження каналів", + [nameof(ErrorExportingTitle)] = "Помилка експорту каналу(-ів)", + [nameof(SuccessfulExportMessage)] = "Успішно експортовано {0} канал(-ів)", + }; +} diff --git a/DiscordChatExporter.Gui/Localization/LocalizationManager.cs b/DiscordChatExporter.Gui/Localization/LocalizationManager.cs new file mode 100644 index 00000000..ac38664d --- /dev/null +++ b/DiscordChatExporter.Gui/Localization/LocalizationManager.cs @@ -0,0 +1,158 @@ +using System; +using System.Globalization; +using System.Runtime.CompilerServices; +using CommunityToolkit.Mvvm.ComponentModel; +using DiscordChatExporter.Gui.Services; +using DiscordChatExporter.Gui.Utils; +using DiscordChatExporter.Gui.Utils.Extensions; + +namespace DiscordChatExporter.Gui.Localization; + +public partial class LocalizationManager : ObservableObject, IDisposable +{ + private readonly DisposableCollector _eventRoot = new(); + + public LocalizationManager(SettingsService settingsService) + { + _eventRoot.Add( + settingsService.WatchProperty( + o => o.Language, + () => Language = settingsService.Language, + true + ) + ); + + _eventRoot.Add( + this.WatchProperty( + o => o.Language, + () => + { + foreach (var propertyName in EnglishLocalization.Keys) + OnPropertyChanged(propertyName); + } + ) + ); + } + + [ObservableProperty] + public partial Language Language { get; set; } = Language.System; + + private string Get([CallerMemberName] string? key = null) + { + if (string.IsNullOrWhiteSpace(key)) + return string.Empty; + + var localization = Language switch + { + Language.System => + CultureInfo.CurrentUICulture.ThreeLetterISOLanguageName.ToLowerInvariant() switch + { + "ukr" => UkrainianLocalization, + "deu" => GermanLocalization, + "fra" => FrenchLocalization, + "spa" => SpanishLocalization, + _ => EnglishLocalization, + }, + Language.Ukrainian => UkrainianLocalization, + Language.German => GermanLocalization, + Language.French => FrenchLocalization, + Language.Spanish => SpanishLocalization, + _ => EnglishLocalization, + }; + + if ( + localization.TryGetValue(key, out var value) + // English is used as a fallback + || EnglishLocalization.TryGetValue(key, out value) + ) + { + return value; + } + + return $"Missing localization for '{key}'"; + } + + public void Dispose() => _eventRoot.Dispose(); +} + +public partial class LocalizationManager +{ + // ---- Dashboard ---- + + public string PullGuildsTooltip => Get(); + public string SettingsTooltip => Get(); + public string LastMessageSentTooltip => Get(); + + // ---- Settings ---- + + public string SettingsTitle => Get(); + public string ThemeLabel => Get(); + public string ThemeTooltip => Get(); + public string LanguageLabel => Get(); + public string LanguageTooltip => Get(); + public string AutoUpdateLabel => Get(); + public string AutoUpdateTooltip => Get(); + public string PersistTokenLabel => Get(); + public string PersistTokenTooltip => Get(); + public string RateLimitPreferenceLabel => Get(); + public string RateLimitPreferenceTooltip => Get(); + public string ShowThreadsLabel => Get(); + public string ShowThreadsTooltip => Get(); + public string LocaleLabel => Get(); + public string LocaleTooltip => Get(); + public string NormalizeToUtcLabel => Get(); + public string NormalizeToUtcTooltip => Get(); + public string ParallelLimitLabel => Get(); + public string ParallelLimitTooltip => Get(); + + // ---- Export Setup ---- + + public string ChannelsSelectedText => Get(); + public string OutputPathLabel => Get(); + public string FormatLabel => Get(); + public string FormatTooltip => Get(); + public string AfterDateLabel => Get(); + public string AfterDateTooltip => Get(); + public string BeforeDateLabel => Get(); + public string BeforeDateTooltip => Get(); + public string AfterTimeLabel => Get(); + public string AfterTimeTooltip => Get(); + public string BeforeTimeLabel => Get(); + public string BeforeTimeTooltip => Get(); + public string PartitionLimitLabel => Get(); + public string PartitionLimitTooltip => Get(); + public string MessageFilterLabel => Get(); + public string MessageFilterTooltip => Get(); + public string FormatMarkdownLabel => Get(); + public string FormatMarkdownTooltip => Get(); + public string DownloadAssetsLabel => Get(); + public string DownloadAssetsTooltip => Get(); + public string ReuseAssetsLabel => Get(); + public string ReuseAssetsTooltip => Get(); + public string AssetsDirPathLabel => Get(); + public string AssetsDirPathTooltip => Get(); + public string AdvancedOptionsTooltip => Get(); + public string ExportButton => Get(); + + // ---- Common buttons ---- + + public string CloseButton => Get(); + public string CancelButton => Get(); + + // ---- Dialog messages ---- + + public string UkraineSupportTitle => Get(); + public string UkraineSupportMessage => Get(); + public string LearnMoreButton => Get(); + public string UnstableBuildTitle => Get(); + public string UnstableBuildMessage => Get(); + public string SeeReleasesButton => Get(); + public string UpdateDownloadingMessage => Get(); + public string UpdateReadyMessage => Get(); + public string UpdateInstallNowButton => Get(); + public string UpdateFailedMessage => Get(); + public string ErrorPullingServersTitle => Get(); + public string ErrorPullingChannelsTitle => Get(); + public string ErrorExportingTitle => Get(); + public string SuccessfulExportMessage => Get(); +} diff --git a/DiscordChatExporter.Gui/Services/SettingsService.cs b/DiscordChatExporter.Gui/Services/SettingsService.cs index 87fb914b..cca8a3aa 100644 --- a/DiscordChatExporter.Gui/Services/SettingsService.cs +++ b/DiscordChatExporter.Gui/Services/SettingsService.cs @@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Gui.Framework; +using DiscordChatExporter.Gui.Localization; using DiscordChatExporter.Gui.Models; namespace DiscordChatExporter.Gui.Services; @@ -23,6 +24,9 @@ public partial class SettingsService() [ObservableProperty] public partial ThemeVariant Theme { get; set; } + [ObservableProperty] + public partial Language Language { get; set; } + [ObservableProperty] public partial bool IsAutoUpdateEnabled { get; set; } = true; diff --git a/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs index ebd416c2..86626952 100644 --- a/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/Components/DashboardViewModel.cs @@ -13,6 +13,7 @@ using DiscordChatExporter.Core.Exceptions; using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Gui.Framework; +using DiscordChatExporter.Gui.Localization; using DiscordChatExporter.Gui.Models; using DiscordChatExporter.Gui.Services; using DiscordChatExporter.Gui.Utils; @@ -38,13 +39,15 @@ public partial class DashboardViewModel : ViewModelBase ViewModelManager viewModelManager, DialogManager dialogManager, SnackbarManager snackbarManager, - SettingsService settingsService + SettingsService settingsService, + LocalizationManager localizationManager ) { _viewModelManager = viewModelManager; _dialogManager = dialogManager; _snackbarManager = snackbarManager; _settingsService = settingsService; + LocalizationManager = localizationManager; _progressMuxer = Progress.CreateMuxer().WithAutoReset(); @@ -70,6 +73,8 @@ public partial class DashboardViewModel : ViewModelBase [NotifyCanExecuteChangedFor(nameof(ExportCommand))] public partial bool IsBusy { get; set; } + public LocalizationManager LocalizationManager { get; } + public ProgressContainer Progress { get; } = new(); public bool IsProgressIndeterminate => IsBusy && Progress.Current.Fraction is <= 0 or >= 1; @@ -141,7 +146,7 @@ public partial class DashboardViewModel : ViewModelBase catch (Exception ex) { var dialog = _viewModelManager.CreateMessageBoxViewModel( - "Error pulling servers", + LocalizationManager.ErrorPullingServersTitle, ex.ToString() ); @@ -208,7 +213,7 @@ public partial class DashboardViewModel : ViewModelBase catch (Exception ex) { var dialog = _viewModelManager.CreateMessageBoxViewModel( - "Error pulling channels", + LocalizationManager.ErrorPullingChannelsTitle, ex.ToString() ); @@ -303,14 +308,17 @@ public partial class DashboardViewModel : ViewModelBase if (successfulExportCount > 0) { _snackbarManager.Notify( - $"Successfully exported {successfulExportCount} channel(s)" + string.Format( + LocalizationManager.SuccessfulExportMessage, + successfulExportCount + ) ); } } catch (Exception ex) { var dialog = _viewModelManager.CreateMessageBoxViewModel( - "Error exporting channel(s)", + LocalizationManager.ErrorExportingTitle, ex.ToString() ); diff --git a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs index 4574f6ec..afee2f00 100644 --- a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs @@ -12,15 +12,19 @@ using DiscordChatExporter.Core.Exporting.Filtering; using DiscordChatExporter.Core.Exporting.Partitioning; using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Gui.Framework; +using DiscordChatExporter.Gui.Localization; using DiscordChatExporter.Gui.Services; namespace DiscordChatExporter.Gui.ViewModels.Dialogs; public partial class ExportSetupViewModel( DialogManager dialogManager, - SettingsService settingsService + SettingsService settingsService, + LocalizationManager localizationManager ) : DialogViewModelBase { + public LocalizationManager LocalizationManager { get; } = localizationManager; + [ObservableProperty] public partial Guild? Guild { get; set; } diff --git a/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs index d3eaad9f..544f4ba1 100644 --- a/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/Dialogs/SettingsViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Gui.Framework; +using DiscordChatExporter.Gui.Localization; using DiscordChatExporter.Gui.Models; using DiscordChatExporter.Gui.Services; using DiscordChatExporter.Gui.Utils; @@ -16,13 +17,19 @@ public class SettingsViewModel : DialogViewModelBase private readonly DisposableCollector _eventRoot = new(); - public SettingsViewModel(SettingsService settingsService) + public SettingsViewModel( + SettingsService settingsService, + LocalizationManager localizationManager + ) { _settingsService = settingsService; + LocalizationManager = localizationManager; _eventRoot.Add(_settingsService.WatchAllProperties(OnAllPropertiesChanged)); } + public LocalizationManager LocalizationManager { get; } + public IReadOnlyList AvailableThemes { get; } = Enum.GetValues(); public ThemeVariant Theme @@ -31,6 +38,14 @@ public class SettingsViewModel : DialogViewModelBase set => _settingsService.Theme = value; } + public IReadOnlyList AvailableLanguages { get; } = Enum.GetValues(); + + public Language Language + { + get => _settingsService.Language; + set => _settingsService.Language = value; + } + public bool IsAutoUpdateEnabled { get => _settingsService.IsAutoUpdateEnabled; diff --git a/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs b/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs index a40ca6fb..77060b28 100644 --- a/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Avalonia; using CommunityToolkit.Mvvm.Input; using DiscordChatExporter.Gui.Framework; +using DiscordChatExporter.Gui.Localization; using DiscordChatExporter.Gui.Services; using DiscordChatExporter.Gui.Utils.Extensions; using DiscordChatExporter.Gui.ViewModels.Components; @@ -15,7 +16,8 @@ public partial class MainViewModel( DialogManager dialogManager, SnackbarManager snackbarManager, SettingsService settingsService, - UpdateService updateService + UpdateService updateService, + LocalizationManager localizationManager ) : ViewModelBase { public string Title { get; } = $"{Program.Name} v{Program.VersionString}"; @@ -28,14 +30,10 @@ public partial class MainViewModel( return; var dialog = viewModelManager.CreateMessageBoxViewModel( - "Thank you for supporting Ukraine!", - """ - As Russia wages a genocidal war against my country, I'm grateful to everyone who continues to stand with Ukraine in our fight for freedom. - - Click LEARN MORE to find ways that you can help. - """, - "LEARN MORE", - "CLOSE" + localizationManager.UkraineSupportTitle, + localizationManager.UkraineSupportMessage, + localizationManager.LearnMoreButton, + localizationManager.CloseButton ); // Disable this message in the future @@ -56,16 +54,10 @@ public partial class MainViewModel( return; var dialog = viewModelManager.CreateMessageBoxViewModel( - "Unstable build warning", - $""" - You're using a development build of {Program.Name}. These builds are not thoroughly tested and may contain bugs. - - Auto-updates are disabled for development builds. - - Click SEE RELEASES if you want to download a stable release instead. - """, - "SEE RELEASES", - "CLOSE" + localizationManager.UnstableBuildTitle, + string.Format(localizationManager.UnstableBuildMessage, Program.Name), + localizationManager.SeeReleasesButton, + localizationManager.CloseButton ); if (await dialogManager.ShowDialogAsync(dialog) == true) @@ -80,12 +72,18 @@ public partial class MainViewModel( if (updateVersion is null) return; - snackbarManager.Notify($"Downloading update to {Program.Name} v{updateVersion}..."); + snackbarManager.Notify( + string.Format( + localizationManager.UpdateDownloadingMessage, + Program.Name, + updateVersion + ) + ); await updateService.PrepareUpdateAsync(updateVersion); snackbarManager.Notify( - "Update has been downloaded and will be installed when you exit", - "INSTALL NOW", + localizationManager.UpdateReadyMessage, + localizationManager.UpdateInstallNowButton, () => { updateService.FinalizeUpdate(true); @@ -98,7 +96,7 @@ public partial class MainViewModel( catch { // Failure to update shouldn't crash the application - snackbarManager.Notify("Failed to perform application update"); + snackbarManager.Notify(localizationManager.UpdateFailedMessage); } } diff --git a/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml b/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml index 350f6acf..2159d8d7 100644 --- a/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml +++ b/DiscordChatExporter.Gui/Views/Components/DashboardView.axaml @@ -45,7 +45,7 @@ Command="{Binding PullGuildsCommand}" IsDefault="True" Theme="{DynamicResource MaterialFlatButton}" - ToolTip.Tip="Pull available servers and channels (Enter)"> + ToolTip.Tip="{Binding LocalizationManager.PullGuildsTooltip}"> + ToolTip.Tip="{Binding LocalizationManager.SettingsTooltip}"> - + @@ -69,7 +69,7 @@ @@ -146,11 +146,11 @@ + ToolTip.Tip="{Binding LocalizationManager.FormatTooltip}"> @@ -166,9 +166,9 @@ Grid.Row="0" Grid.Column="0" Margin="16,8,8,8" - materialAssists:TextFieldAssist.Label="After (date)" + materialAssists:TextFieldAssist.Label="{Binding LocalizationManager.AfterDateLabel}" SelectedDate="{Binding AfterDate}" - ToolTip.Tip="Only include messages sent after this date"> + ToolTip.Tip="{Binding LocalizationManager.AfterDateTooltip}">