diff --git a/I18N Commander/DataModel/DataModel.csproj b/I18N Commander/DataModel/DataModel.csproj index 07d4dfb..2b1e1d6 100644 --- a/I18N Commander/DataModel/DataModel.csproj +++ b/I18N Commander/DataModel/DataModel.csproj @@ -8,12 +8,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/I18N Commander/Processor/DeepL.cs b/I18N Commander/Processor/DeepL.cs new file mode 100644 index 0000000..bbfe58e --- /dev/null +++ b/I18N Commander/Processor/DeepL.cs @@ -0,0 +1,66 @@ +using DataModel.Database; +using DeepL; + +namespace Processor; + +public static class DeepL +{ + private static bool DEEPL_NOT_AVAILABLE = false; + + public readonly record struct DeepLUsage(bool Enabled, long CharacterCount, long CharacterLimit, bool AuthIssue = false); + + public static async Task GetUsage() + { + if(DEEPL_NOT_AVAILABLE) + return new DeepLUsage(false, 0, 1); + + if (await AppSettings.GetDeepLMode() == SettingDeepLMode.DISABLED) + return new DeepLUsage(false, 0, 1); + + var deepLAPIKey = await AppSettings.GetDeepLAPIKey(); + if(string.IsNullOrWhiteSpace(deepLAPIKey)) + return new DeepLUsage(false, 0, 1); + + try + { + using var deepl = new Translator(deepLAPIKey); + var usage = await deepl.GetUsageAsync(); + + return new DeepLUsage(true, usage.Character!.Count, usage.Character.Limit); + } + catch (AuthorizationException e) + { + DEEPL_NOT_AVAILABLE = true; + return new DeepLUsage(false, 0, 1, true); + } + } + + public static async Task Translate(string text, string targetCulture) + { + if (DEEPL_NOT_AVAILABLE) + return string.Empty; + + if (await AppSettings.GetDeepLMode() == SettingDeepLMode.DISABLED) + return string.Empty; + + var deepLAPIKey = await AppSettings.GetDeepLAPIKey(); + if(string.IsNullOrWhiteSpace(deepLAPIKey)) + return string.Empty; + + try + { + var sourceCultureIndex = await AppSettings.GetDeepLSourceCultureIndex(); + var sourceCulture = await AppSettings.GetCultureCode(sourceCultureIndex); + + using var deepl = new Translator(deepLAPIKey); + var translation = await deepl.TranslateTextAsync(text, sourceCulture, targetCulture); + + return translation.Text; + } + catch (AuthorizationException e) + { + DEEPL_NOT_AVAILABLE = true; + return string.Empty; + } + } +} \ No newline at end of file diff --git a/I18N Commander/Processor/Processor.csproj b/I18N Commander/Processor/Processor.csproj index 94a5986..7b794fd 100644 --- a/I18N Commander/Processor/Processor.csproj +++ b/I18N Commander/Processor/Processor.csproj @@ -11,4 +11,8 @@ + + + + diff --git a/I18N Commander/UI WinForms/Components/Setting.cs b/I18N Commander/UI WinForms/Components/Setting.cs index 639487b..f9d7e2a 100644 --- a/I18N Commander/UI WinForms/Components/Setting.cs +++ b/I18N Commander/UI WinForms/Components/Setting.cs @@ -6,6 +6,8 @@ namespace UI_WinForms.Components; public sealed partial class Setting : UserControl { + private SettingUIData data; + public Setting() { this.InitializeComponent(); @@ -15,6 +17,7 @@ public sealed partial class Setting : UserControl { this.InitializeComponent(); this.Dock = DockStyle.Top; + this.data = settingMetaData; this.labelIcon.Image = settingMetaData.Icon; this.labelSettingName.Text = settingMetaData.SettingName(); this.labelExplanation.Text = settingMetaData.SettingExplanation(); @@ -38,6 +41,8 @@ public sealed partial class Setting : UserControl dataControl.Margin = new Padding(0, (int) margin, 0, (int)margin); }; } + + private void UpdateExplanation() => this.labelExplanation.Text = this.data.SettingExplanation(); private readonly record struct SettingUIData( Bitmap Icon, @@ -109,6 +114,85 @@ public sealed partial class Setting : UserControl return new Setting(settingData); } + private static async Task ShowDeepLUsageSettingAsync() + { + var currentUsage = await Processor.DeepL.GetUsage(); + var percent = currentUsage.Enabled ? currentUsage.CharacterCount / (float)currentUsage.CharacterLimit : 0; + + // Local function to show & update the explanation text: + string GetUsageText() => currentUsage.Enabled ? + $"You used {currentUsage.CharacterCount:###,###,###,##0} characters out of {currentUsage.CharacterLimit:###,###,###,##0} ({percent:P2})." : currentUsage.AuthIssue ? + "Was not able to authorize with DeepL. Please check your API key." : + "DeepL is disabled or the API key is not set."; + + var settingData = new SettingUIData( + Icon: Icons.icons8_increase_512, + SettingName: () => "DeepL Usage", + SettingExplanation: GetUsageText, + SetupDataControl: () => + { + var progressbar = new ProgressBar(); + progressbar.Maximum = 100; + progressbar.Margin = new Padding(0, 16, 0, 16); + progressbar.Dock = DockStyle.Fill; + progressbar.Style = ProgressBarStyle.Continuous; + progressbar.Value = percent switch + { + < 0 => 0, + > 1 => 100, + + _ => (int)(percent * 100) + }; + + var reloadButton = new Button(); + reloadButton.Text = string.Empty; + reloadButton.Image = Icons.icons8_reload_512; + reloadButton.FlatStyle = FlatStyle.Flat; + reloadButton.FlatAppearance.BorderSize = 0; + reloadButton.BackColor = Color.Empty; + reloadButton.UseVisualStyleBackColor = true; + reloadButton.Size = new Size(60, 60); + reloadButton.Click += async (sender, args) => + { + var usage = await Processor.DeepL.GetUsage(); + + // Update the outer variables: + percent = usage.Enabled ? usage.CharacterCount / (float)usage.CharacterLimit : 0; + currentUsage = usage; + + // Update the progress bar: + progressbar.Value = percent switch + { + < 0 => 0, + > 1 => 100, + + _ => (int)(percent * 100) + }; + + // Update the explanation text. Therefore, we need to get the setting object through the chain of parents: + var setting = (Setting) ((Control) sender).Parent.Parent.Parent; + setting.UpdateExplanation(); + }; + + // Setup the layout: + var layout = new TableLayoutPanel(); + layout.ColumnCount = 2; + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100)); + layout.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 66)); + layout.RowCount = 1; + layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); + layout.Controls.Add(progressbar, 0, 0); + layout.Controls.Add(reloadButton, 1, 0); + layout.Dock = DockStyle.Fill; + + return layout; + } + ); + + var setting = new Setting(settingData); + return setting; + } + private static async Task ShowDeepLActionSettingAsync() { var currentSetting = await AppSettings.GetDeepLAction(); @@ -264,7 +348,8 @@ public sealed partial class Setting : UserControl { yield return setting; } - + + yield return ShowDeepLUsageSettingAsync(); yield return ShowDeepLActionSettingAsync(); yield return ShowDeepLAPIKeySettingAsync(); yield return ShowDeepLModeSettingAsync(); diff --git a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs index ab02ede..eec4a58 100644 --- a/I18N Commander/UI WinForms/Resources/Icons.Designer.cs +++ b/I18N Commander/UI WinForms/Resources/Icons.Designer.cs @@ -170,6 +170,16 @@ namespace UI_WinForms.Resources { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap icons8_increase_512 { + get { + object obj = ResourceManager.GetObject("icons8_increase_512", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -230,6 +240,26 @@ namespace UI_WinForms.Resources { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap icons8_refresh_512 { + get { + object obj = ResourceManager.GetObject("icons8_refresh_512", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap icons8_reload_512 { + get { + object obj = ResourceManager.GetObject("icons8_reload_512", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/I18N Commander/UI WinForms/Resources/Icons.resx b/I18N Commander/UI WinForms/Resources/Icons.resx index f4a76ae..ccb480d 100644 --- a/I18N Commander/UI WinForms/Resources/Icons.resx +++ b/I18N Commander/UI WinForms/Resources/Icons.resx @@ -151,6 +151,9 @@ icons8-document-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + icons8-increase-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + icons8-key-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -169,6 +172,12 @@ icons8-play-512 (2).png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + icons8-refresh-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + icons8-reload-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + icons8-remove-tag-512.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/I18N Commander/UI WinForms/Resources/icons8-increase-512.png b/I18N Commander/UI WinForms/Resources/icons8-increase-512.png new file mode 100644 index 0000000..e8cccca Binary files /dev/null and b/I18N Commander/UI WinForms/Resources/icons8-increase-512.png differ diff --git a/I18N Commander/UI WinForms/Resources/icons8-refresh-512.png b/I18N Commander/UI WinForms/Resources/icons8-refresh-512.png new file mode 100644 index 0000000..c496bae Binary files /dev/null and b/I18N Commander/UI WinForms/Resources/icons8-refresh-512.png differ diff --git a/I18N Commander/UI WinForms/Resources/icons8-reload-512.png b/I18N Commander/UI WinForms/Resources/icons8-reload-512.png new file mode 100644 index 0000000..346a4b9 Binary files /dev/null and b/I18N Commander/UI WinForms/Resources/icons8-reload-512.png differ