Implement localization in DiscordChatExporter following YoutubeDownloader pattern

Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-24 07:40:35 +00:00
parent 10a5d1b2bf
commit b1b0dc1625
16 changed files with 794 additions and 77 deletions

View File

@@ -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<SettingsService>();
services.AddSingleton<UpdateService>();
// Localization
services.AddSingleton<LocalizationManager>();
// View models
services.AddTransient<MainViewModel>();
services.AddTransient<DashboardViewModel>();

View File

@@ -0,0 +1,11 @@
namespace DiscordChatExporter.Gui.Localization;
public enum Language
{
System,
English,
Ukrainian,
German,
French,
Spanish,
}

View File

@@ -0,0 +1,99 @@
using System.Collections.Generic;
namespace DiscordChatExporter.Gui.Localization;
public partial class LocalizationManager
{
private static readonly IReadOnlyDictionary<string, string> EnglishLocalization =
new Dictionary<string, string>
{
// 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)",
};
}

View File

@@ -0,0 +1,101 @@
using System.Collections.Generic;
namespace DiscordChatExporter.Gui.Localization;
public partial class LocalizationManager
{
private static readonly IReadOnlyDictionary<string, string> 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",
};
}

View File

@@ -0,0 +1,105 @@
using System.Collections.Generic;
namespace DiscordChatExporter.Gui.Localization;
public partial class LocalizationManager
{
private static readonly IReadOnlyDictionary<string, string> 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",
};
}

View File

@@ -0,0 +1,99 @@
using System.Collections.Generic;
namespace DiscordChatExporter.Gui.Localization;
public partial class LocalizationManager
{
private static readonly IReadOnlyDictionary<string, string> SpanishLocalization =
new Dictionary<string, string>
{
// 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",
};
}

View File

@@ -0,0 +1,98 @@
using System.Collections.Generic;
namespace DiscordChatExporter.Gui.Localization;
public partial class LocalizationManager
{
private static readonly IReadOnlyDictionary<string, string> UkrainianLocalization =
new Dictionary<string, string>
{
// 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} канал(-ів)",
};
}

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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<Percentage> 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()
);

View File

@@ -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; }

View File

@@ -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<ThemeVariant> AvailableThemes { get; } = Enum.GetValues<ThemeVariant>();
public ThemeVariant Theme
@@ -31,6 +38,14 @@ public class SettingsViewModel : DialogViewModelBase
set => _settingsService.Theme = value;
}
public IReadOnlyList<Language> AvailableLanguages { get; } = Enum.GetValues<Language>();
public Language Language
{
get => _settingsService.Language;
set => _settingsService.Language = value;
}
public bool IsAutoUpdateEnabled
{
get => _settingsService.IsAutoUpdateEnabled;

View File

@@ -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);
}
}

View File

@@ -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}">
<materialIcons:MaterialIcon
Width="24"
Height="24"
@@ -64,7 +64,7 @@
Command="{Binding ShowSettingsCommand}"
Foreground="{DynamicResource MaterialDarkForegroundBrush}"
Theme="{DynamicResource MaterialFlatButton}"
ToolTip.Tip="Settings">
ToolTip.Tip="{Binding LocalizationManager.SettingsTooltip}">
<materialIcons:MaterialIcon
Width="24"
Height="24"

View File

@@ -38,7 +38,7 @@
IsVisible="{Binding !IsSingleChannel}"
TextTrimming="CharacterEllipsis">
<Run Text="{Binding Channels.Count, FallbackValue=0, Mode=OneWay}" />
<Run Text="channels selected" />
<Run Text="{Binding LocalizationManager.ChannelsSelectedText}" />
</TextBlock>
<!-- Category and channel name (for single channel) -->
@@ -69,7 +69,7 @@
<!-- Output path -->
<TextBox
Margin="16,8"
materialAssists:TextFieldAssist.Label="Output path"
materialAssists:TextFieldAssist.Label="{Binding LocalizationManager.OutputPathLabel}"
Text="{Binding OutputPath}"
Theme="{DynamicResource FilledTextBox}">
<ToolTip.Tip>
@@ -146,11 +146,11 @@
<!-- Format -->
<ComboBox
Margin="16,8"
materialAssists:ComboBoxAssist.Label="Format"
materialAssists:ComboBoxAssist.Label="{Binding LocalizationManager.FormatLabel}"
ItemsSource="{Binding AvailableFormats}"
SelectedItem="{Binding SelectedFormat}"
Theme="{DynamicResource MaterialFilledComboBox}"
ToolTip.Tip="Export format">
ToolTip.Tip="{Binding LocalizationManager.FormatTooltip}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:ExportFormatToStringConverter.Instance}}" />
@@ -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}">
<DatePicker.Styles>
<Style Selector="DatePicker">
<Style Selector="^ /template/ TextBox#DisplayTextBox">
@@ -181,9 +181,9 @@
Grid.Row="0"
Grid.Column="1"
Margin="8,8,16,8"
materialAssists:TextFieldAssist.Label="Before (date)"
materialAssists:TextFieldAssist.Label="{Binding LocalizationManager.BeforeDateLabel}"
SelectedDate="{Binding BeforeDate}"
ToolTip.Tip="Only include messages sent before this date">
ToolTip.Tip="{Binding LocalizationManager.BeforeDateTooltip}">
<DatePicker.Styles>
<Style Selector="DatePicker">
<Style Selector="^ /template/ TextBox#DisplayTextBox">
@@ -198,11 +198,11 @@
Grid.Row="1"
Grid.Column="0"
Margin="16,8,8,8"
materialAssists:TextFieldAssist.Label="After (time)"
materialAssists:TextFieldAssist.Label="{Binding LocalizationManager.AfterTimeLabel}"
ClockIdentifier="{x:Static utils:Internationalization.AvaloniaClockIdentifier}"
IsEnabled="{Binding IsAfterDateSet}"
SelectedTime="{Binding AfterTime}"
ToolTip.Tip="Only include messages sent after this time">
ToolTip.Tip="{Binding LocalizationManager.AfterTimeTooltip}">
<TimePicker.Styles>
<Style Selector="TimePicker">
<Style Selector="^ /template/ TextBox#PART_DisplayTextBox">
@@ -215,11 +215,11 @@
Grid.Row="1"
Grid.Column="1"
Margin="8,8,16,8"
materialAssists:TextFieldAssist.Label="Before (time)"
materialAssists:TextFieldAssist.Label="{Binding LocalizationManager.BeforeTimeLabel}"
ClockIdentifier="{x:Static utils:Internationalization.AvaloniaClockIdentifier}"
IsEnabled="{Binding IsBeforeDateSet}"
SelectedTime="{Binding BeforeTime}"
ToolTip.Tip="Only include messages sent before this time">
ToolTip.Tip="{Binding LocalizationManager.BeforeTimeTooltip}">
<TimePicker.Styles>
<Style Selector="TimePicker">
<Style Selector="^ /template/ TextBox#PART_DisplayTextBox">
@@ -233,25 +233,25 @@
<!-- Partitioning -->
<TextBox
Margin="16,8"
materialAssists:TextFieldAssist.Label="Partition limit"
materialAssists:TextFieldAssist.Label="{Binding LocalizationManager.PartitionLimitLabel}"
Text="{Binding PartitionLimitValue}"
Theme="{DynamicResource FilledTextBox}"
ToolTip.Tip="Split the output into partitions, each limited to the specified number of messages (e.g. '100') or file size (e.g. '10mb')" />
ToolTip.Tip="{Binding LocalizationManager.PartitionLimitTooltip}" />
<!-- Filtering -->
<TextBox
Margin="16,8"
materialAssists:TextFieldAssist.Label="Message filter"
materialAssists:TextFieldAssist.Label="{Binding LocalizationManager.MessageFilterLabel}"
Text="{Binding MessageFilterValue}"
Theme="{DynamicResource FilledTextBox}"
ToolTip.Tip="Only include messages that satisfy this filter (e.g. 'from:foo#1234' or 'has:image'). See the documentation for more info." />
ToolTip.Tip="{Binding LocalizationManager.MessageFilterTooltip}" />
<!-- Markdown formatting -->
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="Process markdown, mentions, and other special tokens">
<TextBlock DockPanel.Dock="Left" Text="Format markdown" />
ToolTip.Tip="{Binding LocalizationManager.FormatMarkdownTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.FormatMarkdownLabel}" />
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding ShouldFormatMarkdown}" />
</DockPanel>
@@ -259,8 +259,8 @@
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="Download assets referenced by the export (user avatars, attached files, embedded images, etc.)">
<TextBlock DockPanel.Dock="Left" Text="Download assets" />
ToolTip.Tip="{Binding LocalizationManager.DownloadAssetsTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.DownloadAssetsLabel}" />
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding ShouldDownloadAssets}" />
</DockPanel>
@@ -269,19 +269,19 @@
Margin="16,8"
IsEnabled="{Binding ShouldDownloadAssets}"
LastChildFill="False"
ToolTip.Tip="Reuse previously downloaded assets to avoid redundant requests">
<TextBlock DockPanel.Dock="Left" Text="Reuse assets" />
ToolTip.Tip="{Binding LocalizationManager.ReuseAssetsTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.ReuseAssetsLabel}" />
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding ShouldReuseAssets}" />
</DockPanel>
<!-- Assets path -->
<TextBox
Margin="16,8"
materialAssists:TextFieldAssist.Label="Assets directory path"
materialAssists:TextFieldAssist.Label="{Binding LocalizationManager.AssetsDirPathLabel}"
IsEnabled="{Binding ShouldDownloadAssets}"
Text="{Binding AssetsDirPath}"
Theme="{DynamicResource FilledTextBox}"
ToolTip.Tip="Download assets to this directory. If not specified, the asset directory path will be derived from the output path.">
ToolTip.Tip="{Binding LocalizationManager.AssetsDirPathTooltip}">
<TextBox.InnerRightContent>
<Button
Margin="8,8,8,6"
@@ -310,7 +310,7 @@
Grid.Column="0"
IsChecked="{Binding IsAdvancedSectionDisplayed}"
Theme="{DynamicResource MaterialOutlineButton}"
ToolTip.Tip="Toggle advanced options">
ToolTip.Tip="{Binding LocalizationManager.AdvancedOptionsTooltip}">
<Button.Styles>
<Style Selector="ToggleButton">
<Setter Property="Content" Value="MORE" />
@@ -325,14 +325,14 @@
<Button
Grid.Column="2"
Command="{Binding ConfirmCommand}"
Content="EXPORT"
Content="{Binding LocalizationManager.ExportButton}"
IsDefault="True"
Theme="{DynamicResource MaterialOutlineButton}" />
<Button
Grid.Column="3"
Margin="16,0,0,0"
Command="{Binding CloseCommand}"
Content="CANCEL"
Content="{Binding LocalizationManager.CancelButton}"
IsCancel="True"
Theme="{DynamicResource MaterialOutlineButton}" />
</Grid>

View File

@@ -12,7 +12,7 @@
Margin="16"
FontSize="19"
FontWeight="Light"
Text="Settings" />
Text="{Binding LocalizationManager.SettingsTitle}" />
<Border
Grid.Row="1"
@@ -25,8 +25,8 @@
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="Preferred user interface theme">
<TextBlock DockPanel.Dock="Left" Text="Theme" />
ToolTip.Tip="{Binding LocalizationManager.ThemeTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.ThemeLabel}" />
<ComboBox
Width="150"
DockPanel.Dock="Right"
@@ -34,14 +34,27 @@
SelectedItem="{Binding Theme}" />
</DockPanel>
<!-- Language -->
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="{Binding LocalizationManager.LanguageTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.LanguageLabel}" />
<ComboBox
Width="150"
DockPanel.Dock="Right"
ItemsSource="{Binding AvailableLanguages}"
SelectedItem="{Binding Language}" />
</DockPanel>
<!-- Auto-updates -->
<DockPanel
Margin="16,8"
IsVisible="{OnPlatform False,
Windows=True}"
LastChildFill="False"
ToolTip.Tip="Perform automatic updates on every launch">
<TextBlock DockPanel.Dock="Left" Text="Auto-update" />
ToolTip.Tip="{Binding LocalizationManager.AutoUpdateTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.AutoUpdateLabel}" />
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding IsAutoUpdateEnabled}" />
</DockPanel>
@@ -49,8 +62,8 @@
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="Save the last used token to a file so that it can be persisted between sessions">
<TextBlock DockPanel.Dock="Left" Text="Persist token" />
ToolTip.Tip="{Binding LocalizationManager.PersistTokenTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.PersistTokenLabel}" />
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding IsTokenPersisted}" />
</DockPanel>
@@ -58,8 +71,8 @@
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="Whether to respect advisory rate limits. If disabled, only hard rate limits (i.e. 429 responses) will be respected.">
<TextBlock DockPanel.Dock="Left" Text="Rate limit preference" />
ToolTip.Tip="{Binding LocalizationManager.RateLimitPreferenceTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.RateLimitPreferenceLabel}" />
<ComboBox
Width="150"
DockPanel.Dock="Right"
@@ -77,8 +90,8 @@
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="Which types of threads to show in the channel list">
<TextBlock DockPanel.Dock="Left" Text="Show threads" />
ToolTip.Tip="{Binding LocalizationManager.ShowThreadsTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.ShowThreadsLabel}" />
<ComboBox
Width="150"
DockPanel.Dock="Right"
@@ -90,8 +103,8 @@
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="Locale to use when formatting dates and numbers">
<TextBlock DockPanel.Dock="Left" Text="Locale" />
ToolTip.Tip="{Binding LocalizationManager.LocaleTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.LocaleLabel}" />
<ComboBox
Width="150"
DockPanel.Dock="Right"
@@ -109,8 +122,8 @@
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="Normalize all timestamps to UTC+0">
<TextBlock DockPanel.Dock="Left" Text="Normalize to UTC" />
ToolTip.Tip="{Binding LocalizationManager.NormalizeToUtcTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.NormalizeToUtcLabel}" />
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding IsUtcNormalizationEnabled}" />
</DockPanel>
@@ -118,8 +131,8 @@
<DockPanel
Margin="16,8"
LastChildFill="False"
ToolTip.Tip="How many channels can be exported at the same time">
<TextBlock DockPanel.Dock="Left" Text="Parallel limit" />
ToolTip.Tip="{Binding LocalizationManager.ParallelLimitTooltip}">
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.ParallelLimitLabel}" />
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<TextBlock Margin="10,0" Text="{Binding ParallelLimit}" />
<Slider
@@ -141,7 +154,7 @@
Margin="16"
HorizontalAlignment="Stretch"
Command="{Binding CloseCommand}"
Content="CLOSE"
Content="{Binding LocalizationManager.CloseButton}"
IsCancel="True"
IsDefault="True"
Theme="{DynamicResource MaterialOutlineButton}" />