mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-02-26 19:23:48 +00:00
Add localization (#1482)
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -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>();
|
||||
|
||||
176
DiscordChatExporter.Gui/Converters/MarkdownToInlinesConverter.cs
Normal file
176
DiscordChatExporter.Gui/Converters/MarkdownToInlinesConverter.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Avalonia.Controls.Documents;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using DiscordChatExporter.Gui.Utils.Extensions;
|
||||
using DiscordChatExporter.Gui.Views.Controls;
|
||||
using Markdig;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using MarkdownInline = Markdig.Syntax.Inlines.Inline;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Converters;
|
||||
|
||||
public class MarkdownToInlinesConverter : IValueConverter
|
||||
{
|
||||
public static readonly MarkdownToInlinesConverter Instance = new();
|
||||
|
||||
private static readonly MarkdownPipeline MarkdownPipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmphasisExtras()
|
||||
.Build();
|
||||
|
||||
private static void ProcessInline(
|
||||
InlineCollection inlines,
|
||||
MarkdownInline markdownInline,
|
||||
FontWeight? fontWeight = null,
|
||||
FontStyle? fontStyle = null,
|
||||
TextDecorationCollection? textDecorations = null
|
||||
)
|
||||
{
|
||||
switch (markdownInline)
|
||||
{
|
||||
case LiteralInline literal:
|
||||
{
|
||||
var run = new Run(literal.Content.ToString());
|
||||
|
||||
if (fontWeight is not null)
|
||||
run.FontWeight = fontWeight.Value;
|
||||
if (fontStyle is not null)
|
||||
run.FontStyle = fontStyle.Value;
|
||||
if (textDecorations is not null)
|
||||
run.TextDecorations = textDecorations;
|
||||
|
||||
inlines.Add(run);
|
||||
break;
|
||||
}
|
||||
|
||||
case LineBreakInline:
|
||||
{
|
||||
inlines.Add(new LineBreak());
|
||||
break;
|
||||
}
|
||||
|
||||
case EmphasisInline emphasis:
|
||||
{
|
||||
var newWeight = fontWeight;
|
||||
var newStyle = fontStyle;
|
||||
var newDecorations = textDecorations;
|
||||
|
||||
switch (emphasis.DelimiterChar)
|
||||
{
|
||||
case '*' or '_' when emphasis.DelimiterCount == 2:
|
||||
newWeight = FontWeight.SemiBold;
|
||||
break;
|
||||
case '*' or '_':
|
||||
newStyle = FontStyle.Italic;
|
||||
break;
|
||||
case '~':
|
||||
newDecorations = TextDecorations.Strikethrough;
|
||||
break;
|
||||
case '+':
|
||||
newDecorations = TextDecorations.Underline;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var child in emphasis)
|
||||
ProcessInline(inlines, child, newWeight, newStyle, newDecorations);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LinkInline link:
|
||||
{
|
||||
inlines.Add(
|
||||
new InlineUIContainer(
|
||||
new HyperLink
|
||||
{
|
||||
Text = link.GetInnerText(),
|
||||
Url = link.Url,
|
||||
VerticalAlignment = VerticalAlignment.Bottom,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ContainerInline container:
|
||||
{
|
||||
foreach (var child in container)
|
||||
ProcessInline(inlines, child, fontWeight, fontStyle, textDecorations);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
var inlines = new InlineCollection();
|
||||
if (value is not string { Length: > 0 } text)
|
||||
return inlines;
|
||||
|
||||
var isFirst = true;
|
||||
|
||||
foreach (var block in Markdown.Parse(text, MarkdownPipeline))
|
||||
{
|
||||
switch (block)
|
||||
{
|
||||
case ParagraphBlock { Inline: not null } paragraph:
|
||||
{
|
||||
if (!isFirst)
|
||||
{
|
||||
// Insert a blank line between paragraphs
|
||||
inlines.Add(new LineBreak());
|
||||
inlines.Add(new LineBreak());
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
|
||||
foreach (var markdownInline in paragraph.Inline!)
|
||||
ProcessInline(inlines, markdownInline);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ListBlock list:
|
||||
{
|
||||
var itemOrder = 1;
|
||||
if (list.IsOrdered && int.TryParse(list.OrderedStart, out var startNum))
|
||||
itemOrder = startNum;
|
||||
|
||||
foreach (var listItem in list.OfType<ListItemBlock>())
|
||||
{
|
||||
if (!isFirst)
|
||||
inlines.Add(new LineBreak());
|
||||
isFirst = false;
|
||||
|
||||
var prefix = list.IsOrdered ? $"{itemOrder++}. " : $"{list.BulletType} ";
|
||||
inlines.Add(new Run(prefix));
|
||||
|
||||
foreach (var subBlock in listItem.OfType<ParagraphBlock>())
|
||||
{
|
||||
if (subBlock is { Inline: not null } p)
|
||||
foreach (var markdownInline in p.Inline)
|
||||
ProcessInline(inlines, markdownInline);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inlines;
|
||||
}
|
||||
|
||||
public object? ConvertBack(
|
||||
object? value,
|
||||
Type targetType,
|
||||
object? parameter,
|
||||
CultureInfo culture
|
||||
) => throw new NotSupportedException();
|
||||
}
|
||||
@@ -37,6 +37,7 @@
|
||||
<PackageReference Include="Deorcify" Version="1.1.0" PrivateAssets="all" />
|
||||
<PackageReference Include="DialogHost.Avalonia" Version="0.10.4" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="Markdig" Version="1.0.0" />
|
||||
<PackageReference Include="Material.Avalonia" Version="3.9.2" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
|
||||
|
||||
11
DiscordChatExporter.Gui/Localization/Language.cs
Normal file
11
DiscordChatExporter.Gui/Localization/Language.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace DiscordChatExporter.Gui.Localization;
|
||||
|
||||
public enum Language
|
||||
{
|
||||
System,
|
||||
English,
|
||||
Ukrainian,
|
||||
German,
|
||||
French,
|
||||
Spanish,
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
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:",
|
||||
[nameof(TokenWatermark)] = "Token",
|
||||
// Token instructions (personal account)
|
||||
[nameof(TokenPersonalHeader)] = "To get the token for your personal account:",
|
||||
[nameof(TokenPersonalTosWarning)] =
|
||||
"* Automating user accounts is technically against TOS — **use at your own risk**!",
|
||||
[nameof(TokenPersonalInstructions)] = """
|
||||
1. Open Discord in your web browser and login
|
||||
2. Open any server or direct message channel
|
||||
3. Press **Ctrl+Shift+I** to show developer tools
|
||||
4. Navigate to the **Network** tab
|
||||
5. Press **Ctrl+R** to reload
|
||||
6. Switch between random channels to trigger network requests
|
||||
7. Search for a request that starts with **messages**
|
||||
8. Select the **Headers** tab on the right
|
||||
9. Scroll down to the **Request Headers** section
|
||||
10. Copy the value of the **authorization** header
|
||||
""",
|
||||
// Token instructions (bot)
|
||||
[nameof(TokenBotHeader)] = "To get the token for your bot:",
|
||||
[nameof(TokenBotInstructions)] = """
|
||||
The token is generated during bot creation. If you lost it, generate a new one:
|
||||
|
||||
1. Open Discord [developer portal](https://discord.com/developers/applications)
|
||||
2. Open your application's settings
|
||||
3. Navigate to the **Bot** section on the left
|
||||
4. Under **Token** click **Reset Token**
|
||||
5. Click **Yes, do it!** and authenticate to confirm
|
||||
* Integrations using the previous token will stop working until updated
|
||||
* Your bot needs to have the **Message Content Intent** enabled to read messages
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"If you have questions or issues, please refer to the [documentation](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
// 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(OutputPathTooltip)] = """
|
||||
Output file or directory path.
|
||||
|
||||
If a directory is specified, file names will be generated automatically based on the channel names and export parameters.
|
||||
|
||||
Directory paths must end with a slash to avoid ambiguity.
|
||||
|
||||
Available template tokens:
|
||||
- **%g** — server ID
|
||||
- **%G** — server name
|
||||
- **%t** — category ID
|
||||
- **%T** — category name
|
||||
- **%c** — channel ID
|
||||
- **%C** — channel name
|
||||
- **%p** — channel position
|
||||
- **%P** — category position
|
||||
- **%a** — after date
|
||||
- **%b** — before date
|
||||
- **%d** — current date
|
||||
""",
|
||||
[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(ErrorPullingGuildsTitle)] = "Error pulling servers",
|
||||
[nameof(ErrorPullingChannelsTitle)] = "Error pulling channels",
|
||||
[nameof(ErrorExportingTitle)] = "Error exporting channel(s)",
|
||||
[nameof(SuccessfulExportMessage)] = "Successfully exported {0} channel(s)",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
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é :",
|
||||
[nameof(TokenWatermark)] = "Token",
|
||||
// Token instructions (personal account)
|
||||
[nameof(TokenPersonalHeader)] = "Obtenir le token pour votre compte personnel :",
|
||||
[nameof(TokenPersonalTosWarning)] =
|
||||
"* L'automatisation des comptes est techniquement contraire aux CGU — **à vos risques et périls**!",
|
||||
[nameof(TokenPersonalInstructions)] = """
|
||||
1. Ouvrez Discord dans votre navigateur web et connectez-vous
|
||||
2. Ouvrez n'importe quel serveur ou canal de message direct
|
||||
3. Appuyez sur **Ctrl+Shift+I** pour afficher les outils de développement
|
||||
4. Naviguez vers l'onglet **Network**
|
||||
5. Appuyez sur **Ctrl+R** pour recharger
|
||||
6. Changez de canal pour déclencher des requêtes réseau
|
||||
7. Cherchez une requête commençant par **messages**
|
||||
8. Sélectionnez l'onglet **Headers** à droite
|
||||
9. Faites défiler jusqu'à la section **Request Headers**
|
||||
10. Copiez la valeur de l'en-tête **authorization**
|
||||
""",
|
||||
// Token instructions (bot)
|
||||
[nameof(TokenBotHeader)] = "Obtenir le token pour votre bot :",
|
||||
[nameof(TokenBotInstructions)] = """
|
||||
Le token est généré lors de la création du bot. Si vous l'avez perdu, générez-en un nouveau :
|
||||
|
||||
1. Ouvrez Discord [portail développeur](https://discord.com/developers/applications)
|
||||
2. Ouvrez les paramètres de votre application
|
||||
3. Naviguez vers la section **Bot** à gauche
|
||||
4. Sous **Token**, cliquez sur **Reset Token**
|
||||
5. Cliquez sur **Yes, do it!** et confirmez
|
||||
* Les intégrations utilisant l'ancien token cesseront de fonctionner jusqu'à leur mise à jour
|
||||
* Votre bot doit avoir l'option **Message Content Intent** activée pour lire les messages
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"Pour les questions ou problèmes, veuillez consulter la [documentation](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
// 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(OutputPathTooltip)] = """
|
||||
Chemin du fichier ou répertoire de sortie.
|
||||
|
||||
Si un répertoire est spécifié, les noms de fichiers seront générés automatiquement en fonction des noms de canaux et des paramètres d'exportation.
|
||||
|
||||
Les chemins de répertoire doivent se terminer par un slash pour éviter toute ambiguïté.
|
||||
|
||||
Jetons de modèle disponibles :
|
||||
- **%g** — ID du serveur
|
||||
- **%G** — nom du serveur
|
||||
- **%t** — ID de la catégorie
|
||||
- **%T** — nom de la catégorie
|
||||
- **%c** — ID du canal
|
||||
- **%C** — nom du canal
|
||||
- **%p** — position du canal
|
||||
- **%P** — position de la catégorie
|
||||
- **%a** — date après
|
||||
- **%b** — date avant
|
||||
- **%d** — date actuelle
|
||||
""",
|
||||
[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(ErrorPullingGuildsTitle)] = "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",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
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:",
|
||||
[nameof(TokenWatermark)] = "Token",
|
||||
// Token instructions (personal account)
|
||||
[nameof(TokenPersonalHeader)] = "Token für Ihr persönliches Konto abrufen:",
|
||||
[nameof(TokenPersonalTosWarning)] =
|
||||
"* Das Automatisieren von Benutzerkonten verstößt technisch gegen die AGB — **auf eigene Gefahr**!",
|
||||
[nameof(TokenPersonalInstructions)] = """
|
||||
1. Öffnen Sie Discord in Ihrem Webbrowser und melden Sie sich an
|
||||
2. Öffnen Sie einen Server oder einen direkten Nachrichtenkanal
|
||||
3. Drücken Sie **Ctrl+Shift+I**, um die Entwicklertools anzuzeigen
|
||||
4. Navigieren Sie zum Reiter **Network**
|
||||
5. Drücken Sie **Ctrl+R** zum Neuladen
|
||||
6. Wechseln Sie zwischen Kanälen, um Netzwerkanfragen auszulösen
|
||||
7. Suchen Sie nach einer Anfrage, die mit **messages** beginnt
|
||||
8. Wählen Sie den Reiter **Headers** auf der rechten Seite
|
||||
9. Scrollen Sie nach unten zum Abschnitt **Request Headers**
|
||||
10. Kopieren Sie den Wert des Headers **authorization**
|
||||
""",
|
||||
// Token instructions (bot)
|
||||
[nameof(TokenBotHeader)] = "Token für Ihren Bot abrufen:",
|
||||
[nameof(TokenBotInstructions)] = """
|
||||
Der Token wird bei der Bot-Erstellung generiert. Falls er verloren gegangen ist, generieren Sie einen neuen:
|
||||
|
||||
1. Öffnen Sie Discord [Entwicklerportal](https://discord.com/developers/applications)
|
||||
2. Öffnen Sie die Einstellungen Ihrer Anwendung
|
||||
3. Navigieren Sie zum Abschnitt **Bot** auf der linken Seite
|
||||
4. Klicken Sie unter **Token** auf **Reset Token**
|
||||
5. Klicken Sie auf **Yes, do it!** und bestätigen Sie
|
||||
* Integrationen, die den alten Token verwenden, hören auf zu funktionieren, bis sie aktualisiert werden
|
||||
* Ihr Bot benötigt die aktivierte **Message Content Intent**, um Nachrichten zu lesen
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"Bei Fragen oder Problemen lesen Sie die [Dokumentation](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
// 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(OutputPathTooltip)] = """
|
||||
Ausgabedatei- oder Verzeichnispfad.
|
||||
|
||||
Wenn ein Verzeichnis angegeben wird, werden Dateinamen automatisch basierend auf den Kanalnamen und Exportparametern generiert.
|
||||
|
||||
Verzeichnispfade müssen mit einem Schrägstrich enden, um Mehrdeutigkeiten zu vermeiden.
|
||||
|
||||
Verfügbare Vorlagen-Token:
|
||||
- **%g** — Server-ID
|
||||
- **%G** — Servername
|
||||
- **%t** — Kategorie-ID
|
||||
- **%T** — Kategoriename
|
||||
- **%c** — Kanal-ID
|
||||
- **%C** — Kanalname
|
||||
- **%p** — Kanalposition
|
||||
- **%P** — Kategorieposition
|
||||
- **%a** — Datum ab
|
||||
- **%b** — Datum bis
|
||||
- **%d** — aktuelles Datum
|
||||
""",
|
||||
[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(ErrorPullingGuildsTitle)] = "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",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
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:",
|
||||
[nameof(TokenWatermark)] = "Token",
|
||||
// Token instructions (personal account)
|
||||
[nameof(TokenPersonalHeader)] = "Cómo obtener el token para tu cuenta personal:",
|
||||
[nameof(TokenPersonalTosWarning)] =
|
||||
"* Automatizar cuentas de usuario técnicamente va en contra de los ToS — **bajo tu propio riesgo**!",
|
||||
[nameof(TokenPersonalInstructions)] = """
|
||||
1. Abre Discord en tu navegador web e inicia sesión
|
||||
2. Abre cualquier servidor o canal de mensaje directo
|
||||
3. Presiona **Ctrl+Shift+I** para mostrar las herramientas de desarrollo
|
||||
4. Navega a la pestaña **Network**
|
||||
5. Presiona **Ctrl+R** para recargar
|
||||
6. Cambia entre canales para activar solicitudes de red
|
||||
7. Busca una solicitud que comience con **messages**
|
||||
8. Selecciona la pestaña **Headers** a la derecha
|
||||
9. Desplázate hasta la sección **Request Headers**
|
||||
10. Copia el valor del encabezado **authorization**
|
||||
""",
|
||||
// Token instructions (bot)
|
||||
[nameof(TokenBotHeader)] = "Cómo obtener el token para tu bot:",
|
||||
[nameof(TokenBotInstructions)] = """
|
||||
El token se genera al crear el bot. Si lo perdiste, genera uno nuevo:
|
||||
|
||||
1. Abre Discord [portal de desarrolladores](https://discord.com/developers/applications)
|
||||
2. Abre la configuración de tu aplicación
|
||||
3. Navega a la sección **Bot** en el lado izquierdo
|
||||
4. En **Token**, haz clic en **Reset Token**
|
||||
5. Haz clic en **Yes, do it!** y autentica para confirmar
|
||||
* Las integraciones que usen el token anterior dejarán de funcionar hasta que se actualicen
|
||||
* Tu bot necesita tener habilitado **Message Content Intent** para leer mensajes
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"Si tienes preguntas o problemas, consulta la [documentación](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
// 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(OutputPathTooltip)] = """
|
||||
Ruta del archivo o directorio de salida.
|
||||
|
||||
Si se especifica un directorio, los nombres de archivo se generarán automáticamente según los nombres de los canales y los parámetros de exportación.
|
||||
|
||||
Las rutas de directorio deben terminar con una barra diagonal para evitar ambigüedades.
|
||||
|
||||
Tokens de plantilla disponibles:
|
||||
- **%g** — ID del servidor
|
||||
- **%G** — nombre del servidor
|
||||
- **%t** — ID de categoría
|
||||
- **%T** — nombre de categoría
|
||||
- **%c** — ID del canal
|
||||
- **%C** — nombre del canal
|
||||
- **%p** — posición del canal
|
||||
- **%P** — posición de la categoría
|
||||
- **%a** — fecha desde
|
||||
- **%b** — fecha hasta
|
||||
- **%d** — fecha actual
|
||||
""",
|
||||
[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(ErrorPullingGuildsTitle)] = "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",
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
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)] = "Останнє повідомлення:",
|
||||
[nameof(TokenWatermark)] = "Токен",
|
||||
// Token instructions (personal account)
|
||||
[nameof(TokenPersonalHeader)] = "Як отримати токен для персонального акаунту:",
|
||||
[nameof(TokenPersonalTosWarning)] =
|
||||
"* Автоматизація облікових записів технічно порушує Умови обслуговування — **на власний ризик**!",
|
||||
[nameof(TokenPersonalInstructions)] = """
|
||||
1. Відкрийте Discord у вашому веб-браузері та увійдіть
|
||||
2. Відкрийте будь-який сервер або канал особистих повідомлень
|
||||
3. Натисніть **Ctrl+Shift+I**, щоб відкрити інструменти розробника
|
||||
4. Перейдіть на вкладку **Network**
|
||||
5. Натисніть **Ctrl+R** для перезавантаження
|
||||
6. Перемикайтеся між каналами, щоб викликати мережеві запити
|
||||
7. Знайдіть запит, що починається з **messages**
|
||||
8. Виберіть вкладку **Headers** праворуч
|
||||
9. Прокрутіть до розділу **Request Headers**
|
||||
10. Скопіюйте значення заголовка **authorization**
|
||||
""",
|
||||
// Token instructions (bot)
|
||||
[nameof(TokenBotHeader)] = "Як отримати токен для бота:",
|
||||
[nameof(TokenBotInstructions)] = """
|
||||
Токен генерується під час створення бота. Якщо ви його втратили, згенеруйте новий:
|
||||
|
||||
1. Відкрийте Discord [портал розробника](https://discord.com/developers/applications)
|
||||
2. Відкрийте налаштування вашого застосунку
|
||||
3. Перейдіть до розділу **Bot** ліворуч
|
||||
4. В розділі **Token** натисніть **Reset Token**
|
||||
5. Натисніть **Yes, do it!** та підтвердьте
|
||||
* Інтеграції, що використовують попередній токен, перестануть працювати
|
||||
* Ваш бот повинен мати включений **Message Content Intent** для читання повідомлень
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"Якщо у вас є запитання або проблеми, зверніться до [документації](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
// 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(OutputPathTooltip)] = """
|
||||
Шлях до файлу або директорії виводу.
|
||||
|
||||
Якщо вказано директорію, імена файлів генеруватимуться автоматично на основі назв каналів та параметрів експорту.
|
||||
|
||||
Шляхи до директорій повинні закінчуватись слешем для уникнення неоднозначності.
|
||||
|
||||
Доступні шаблонні токени:
|
||||
- **%g** — ID сервера
|
||||
- **%G** — назва сервера
|
||||
- **%t** — ID категорії
|
||||
- **%T** — назва категорії
|
||||
- **%c** — ID каналу
|
||||
- **%C** — назва каналу
|
||||
- **%p** — позиція каналу
|
||||
- **%P** — позиція категорії
|
||||
- **%a** — дата після
|
||||
- **%b** — дата до
|
||||
- **%d** — поточна дата
|
||||
""",
|
||||
[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(ErrorPullingGuildsTitle)] = "Помилка завантаження серверів",
|
||||
[nameof(ErrorPullingChannelsTitle)] = "Помилка завантаження каналів",
|
||||
[nameof(ErrorExportingTitle)] = "Помилка експорту каналу(-ів)",
|
||||
[nameof(SuccessfulExportMessage)] = "Успішно експортовано {0} канал(-ів)",
|
||||
};
|
||||
}
|
||||
170
DiscordChatExporter.Gui/Localization/LocalizationManager.cs
Normal file
170
DiscordChatExporter.Gui/Localization/LocalizationManager.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
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();
|
||||
public string TokenWatermark => Get();
|
||||
|
||||
// Token instructions (personal account)
|
||||
public string TokenPersonalHeader => Get();
|
||||
public string TokenPersonalTosWarning => Get();
|
||||
public string TokenPersonalInstructions => Get();
|
||||
|
||||
// Token instructions (bot)
|
||||
public string TokenBotHeader => Get();
|
||||
public string TokenBotInstructions => Get();
|
||||
public string TokenHelpText => 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 OutputPathTooltip => 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 ErrorPullingGuildsTitle => Get();
|
||||
public string ErrorPullingChannelsTitle => Get();
|
||||
public string ErrorExportingTitle => Get();
|
||||
public string SuccessfulExportMessage => Get();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Linq;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using MarkdownInline = Markdig.Syntax.Inlines.Inline;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Utils.Extensions;
|
||||
|
||||
internal static class MarkdigExtensions
|
||||
{
|
||||
extension(MarkdownInline inline)
|
||||
{
|
||||
public string GetInnerText() =>
|
||||
inline switch
|
||||
{
|
||||
LiteralInline literal => literal.Content.ToString(),
|
||||
ContainerInline container => string.Concat(container.Select(c => c.GetInnerText())),
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -102,9 +107,6 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
private async Task ShowSettingsAsync() =>
|
||||
await _dialogManager.ShowDialogAsync(_viewModelManager.CreateSettingsViewModel());
|
||||
|
||||
[RelayCommand]
|
||||
private void ShowHelp() => Process.StartShellExecute(Program.ProjectDocumentationUrl);
|
||||
|
||||
private bool CanPullGuilds() => !IsBusy && !string.IsNullOrWhiteSpace(Token);
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanPullGuilds))]
|
||||
@@ -141,7 +143,7 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
catch (Exception ex)
|
||||
{
|
||||
var dialog = _viewModelManager.CreateMessageBoxViewModel(
|
||||
"Error pulling servers",
|
||||
LocalizationManager.ErrorPullingGuildsTitle,
|
||||
ex.ToString()
|
||||
);
|
||||
|
||||
@@ -208,7 +210,7 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
catch (Exception ex)
|
||||
{
|
||||
var dialog = _viewModelManager.CreateMessageBoxViewModel(
|
||||
"Error pulling channels",
|
||||
LocalizationManager.ErrorPullingChannelsTitle,
|
||||
ex.ToString()
|
||||
);
|
||||
|
||||
@@ -303,14 +305,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()
|
||||
);
|
||||
|
||||
@@ -322,13 +327,6 @@ public partial class DashboardViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenDiscord() => Process.StartShellExecute("https://discord.com/app");
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenDiscordDeveloperPortal() =>
|
||||
Process.StartShellExecute("https://discord.com/developers/applications");
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
|
||||
xmlns:components="clr-namespace:DiscordChatExporter.Gui.ViewModels.Components"
|
||||
xmlns:controls="clr-namespace:DiscordChatExporter.Gui.Views.Controls"
|
||||
xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:materialStyles="clr-namespace:Material.Styles.Controls;assembly=Material.Styles"
|
||||
@@ -27,7 +26,7 @@
|
||||
RevealPassword="{Binding $self.IsFocused}"
|
||||
Text="{Binding Token}"
|
||||
Theme="{DynamicResource SoloTextBox}"
|
||||
Watermark="Token">
|
||||
Watermark="{Binding LocalizationManager.TokenWatermark}">
|
||||
<TextBox.InnerLeftContent>
|
||||
<materialIcons:MaterialIcon
|
||||
Grid.Column="0"
|
||||
@@ -45,7 +44,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 +63,7 @@
|
||||
Command="{Binding ShowSettingsCommand}"
|
||||
Foreground="{DynamicResource MaterialDarkForegroundBrush}"
|
||||
Theme="{DynamicResource MaterialFlatButton}"
|
||||
ToolTip.Tip="Settings">
|
||||
ToolTip.Tip="{Binding LocalizationManager.SettingsTooltip}">
|
||||
<materialIcons:MaterialIcon
|
||||
Width="24"
|
||||
Height="24"
|
||||
@@ -168,7 +167,7 @@
|
||||
<Setter Property="ToolTip.Tip">
|
||||
<Template>
|
||||
<TextBlock>
|
||||
<Run Text="Last message sent:" />
|
||||
<Run Text="{Binding #UserControl.DataContext.LocalizationManager.LastMessageSentTooltip}" />
|
||||
<Run FontWeight="SemiBold" Text="{Binding Channel.LastMessageId, Converter={x:Static converters:SnowflakeToTimestampStringConverter.Instance}, TargetNullValue=never, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</Template>
|
||||
@@ -219,172 +218,67 @@
|
||||
<!-- Placeholder / usage instructions -->
|
||||
<Panel IsVisible="{Binding !AvailableGuilds.Count}">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock
|
||||
Margin="32,16"
|
||||
FontSize="14"
|
||||
FontWeight="Light"
|
||||
LineHeight="23">
|
||||
<StackPanel Margin="32,16" Spacing="0">
|
||||
|
||||
<!-- User token -->
|
||||
<InlineUIContainer>
|
||||
<materialIcons:MaterialIcon
|
||||
Width="18"
|
||||
Height="18"
|
||||
Margin="0,-2,0,0"
|
||||
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
||||
Kind="Account" />
|
||||
</InlineUIContainer>
|
||||
<Run BaselineAlignment="Center" Text="" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Text="To get the token for your personal account:" />
|
||||
<LineBreak />
|
||||
<TextBlock>
|
||||
<InlineUIContainer>
|
||||
<materialIcons:MaterialIcon
|
||||
Width="18"
|
||||
Height="18"
|
||||
Margin="0,-2,0,0"
|
||||
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
||||
Kind="Account" />
|
||||
</InlineUIContainer>
|
||||
<Run Text=" " />
|
||||
<Run
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding LocalizationManager.TokenPersonalHeader}" />
|
||||
</TextBlock>
|
||||
|
||||
<Run BaselineAlignment="Center" Text="* Automating user accounts is technically against TOS —" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="use at your own risk" /><Run Text="!" />
|
||||
<LineBreak />
|
||||
<TextBlock Inlines="{Binding LocalizationManager.TokenPersonalTosWarning, Converter={x:Static converters:MarkdownToInlinesConverter.Instance}}"
|
||||
FontSize="14"
|
||||
FontWeight="Light"
|
||||
LineHeight="23"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="1. Open Discord in your" />
|
||||
<controls:HyperLink Command="{Binding OpenDiscordCommand}" Text="web browser" />
|
||||
<Run BaselineAlignment="Center" Text="and login" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="2. Open any server or direct message channel" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="3. Press" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Ctrl+Shift+I" />
|
||||
<Run BaselineAlignment="Center" Text="to show developer tools" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="4. Navigate to the" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Network" />
|
||||
<Run BaselineAlignment="Center" Text="tab" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="5. Press" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Ctrl+R" />
|
||||
<Run BaselineAlignment="Center" Text="to reload" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="6. Switch between random channels to trigger network requests" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="7. Search for a request that starts with" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="messages" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="8. Select the" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Headers" />
|
||||
<Run BaselineAlignment="Center" Text="tab on the right" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="9. Scroll down to the" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Request Headers" />
|
||||
<Run BaselineAlignment="Center" Text="section" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="10. Copy the value of the" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="authorization" />
|
||||
<Run BaselineAlignment="Center" Text="header" />
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<TextBlock Inlines="{Binding LocalizationManager.TokenPersonalInstructions, Converter={x:Static converters:MarkdownToInlinesConverter.Instance}}"
|
||||
FontSize="14"
|
||||
FontWeight="Light"
|
||||
LineHeight="23"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<!-- Bot token -->
|
||||
<InlineUIContainer>
|
||||
<materialIcons:MaterialIcon
|
||||
Width="18"
|
||||
Height="18"
|
||||
Margin="0,-2,0,0"
|
||||
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
||||
Kind="Robot" />
|
||||
</InlineUIContainer>
|
||||
<Run BaselineAlignment="Center" Text="" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Text="To get the token for your bot:" />
|
||||
<LineBreak />
|
||||
<TextBlock Margin="0,12,0,0">
|
||||
<InlineUIContainer>
|
||||
<materialIcons:MaterialIcon
|
||||
Width="18"
|
||||
Height="18"
|
||||
Margin="0,-2,0,0"
|
||||
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
||||
Kind="Robot" />
|
||||
</InlineUIContainer>
|
||||
<Run Text=" " />
|
||||
<Run
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Text="{Binding LocalizationManager.TokenBotHeader}" />
|
||||
</TextBlock>
|
||||
|
||||
<Run BaselineAlignment="Center" Text="The token is generated during bot creation. If you lost it, generate a new one:" />
|
||||
<LineBreak />
|
||||
<TextBlock Inlines="{Binding LocalizationManager.TokenBotInstructions, Converter={x:Static converters:MarkdownToInlinesConverter.Instance}}"
|
||||
FontSize="14"
|
||||
FontWeight="Light"
|
||||
LineHeight="23"
|
||||
TextWrapping="Wrap" />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="1. Open Discord" />
|
||||
<controls:HyperLink Command="{Binding OpenDiscordDeveloperPortalCommand}" Text="developer portal" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="2. Open your application's settings" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="3. Navigate to the" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Bot" />
|
||||
<Run BaselineAlignment="Center" Text="section on the left" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="4. Under" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Token" />
|
||||
<Run BaselineAlignment="Center" Text="click" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Reset Token" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="5. Click" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Yes, do it!" />
|
||||
<Run BaselineAlignment="Center" Text="and authenticate to confirm" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="* Integrations using the previous token will stop working until updated" />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="* Your bot needs to have the" />
|
||||
<Run
|
||||
BaselineAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
Text="Message Content Intent" />
|
||||
<Run BaselineAlignment="Center" Text="enabled to read messages" />
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
|
||||
<Run BaselineAlignment="Center" Text="If you have questions or issues, please refer to the" />
|
||||
<controls:HyperLink Command="{Binding ShowHelpCommand}" Text="documentation" />
|
||||
</TextBlock>
|
||||
<TextBlock Margin="0,12,0,0"
|
||||
FontSize="14"
|
||||
FontWeight="Light"
|
||||
LineHeight="23"
|
||||
TextWrapping="Wrap"
|
||||
Inlines="{Binding LocalizationManager.TokenHelpText, Converter={x:Static converters:MarkdownToInlinesConverter.Instance}}" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Panel>
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Windows.Input;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Input;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using DiscordChatExporter.Gui.Utils.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Views.Controls;
|
||||
|
||||
@@ -16,6 +18,12 @@ public partial class HyperLink : UserControl
|
||||
public static readonly StyledProperty<object?> CommandParameterProperty =
|
||||
Button.CommandParameterProperty.AddOwner<HyperLink>();
|
||||
|
||||
// If Url is set and Command is not set, clicking will open this URL in the default browser.
|
||||
public static readonly StyledProperty<string?> UrlProperty = AvaloniaProperty.Register<
|
||||
HyperLink,
|
||||
string?
|
||||
>(nameof(Url));
|
||||
|
||||
public HyperLink() => InitializeComponent();
|
||||
|
||||
public string? Text
|
||||
@@ -36,14 +44,22 @@ public partial class HyperLink : UserControl
|
||||
set => SetValue(CommandParameterProperty, value);
|
||||
}
|
||||
|
||||
public string? Url
|
||||
{
|
||||
get => GetValue(UrlProperty);
|
||||
set => SetValue(UrlProperty, value);
|
||||
}
|
||||
|
||||
private void TextBlock_OnPointerReleased(object? sender, PointerReleasedEventArgs args)
|
||||
{
|
||||
if (Command is null)
|
||||
return;
|
||||
|
||||
if (!Command.CanExecute(CommandParameter))
|
||||
return;
|
||||
|
||||
Command.Execute(CommandParameter);
|
||||
if (Command is not null)
|
||||
{
|
||||
if (Command.CanExecute(CommandParameter))
|
||||
Command.Execute(CommandParameter);
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(Url))
|
||||
{
|
||||
Process.StartShellExecute(Url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,64 +69,14 @@
|
||||
<!-- 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>
|
||||
<TextBlock>
|
||||
<Run Text="Output file or directory path." />
|
||||
<LineBreak />
|
||||
<Run Text="If a directory is specified, file names will be generated automatically based on the channel names and export parameters." />
|
||||
<LineBreak />
|
||||
<Run Text="Directory paths must end with a slash to avoid ambiguity." />
|
||||
<LineBreak />
|
||||
<LineBreak />
|
||||
<Run Text="Available template tokens:" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%g" />
|
||||
<Run Text="— server ID" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%G" />
|
||||
<Run Text="— server name" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%t" />
|
||||
<Run Text="— category ID" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%T" />
|
||||
<Run Text="— category name" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%c" />
|
||||
<Run Text="— channel ID" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%C" />
|
||||
<Run Text="— channel name" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%p" />
|
||||
<Run Text="— channel position" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%P" />
|
||||
<Run Text="— category position" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%a" />
|
||||
<Run Text="— after date" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%b" />
|
||||
<Run Text="— before date" />
|
||||
<LineBreak />
|
||||
<Run Text=" " />
|
||||
<Run FontWeight="SemiBold" Text="%d" />
|
||||
<Run Text="— current date" />
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
MaxWidth="400"
|
||||
TextWrapping="Wrap"
|
||||
Inlines="{Binding LocalizationManager.OutputPathTooltip, Converter={x:Static converters:MarkdownToInlinesConverter.Instance}}" />
|
||||
</ToolTip.Tip>
|
||||
<TextBox.InnerRightContent>
|
||||
<Button
|
||||
@@ -146,11 +96,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 +116,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 +131,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 +148,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 +165,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 +183,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 +209,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 +219,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 +260,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 +275,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>
|
||||
|
||||
@@ -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}" />
|
||||
|
||||
Reference in New Issue
Block a user