├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── images ├── deepl_full_logo.png ├── logo.png └── logo_notext.png └── src ├── DeepLClient.sln └── DeepLClient ├── Controls ├── DocumentsPage.Designer.cs ├── DocumentsPage.cs ├── DocumentsPage.resx ├── TextPage.Designer.cs ├── TextPage.cs ├── TextPage.resx ├── UrlPage.Designer.cs ├── UrlPage.cs └── UrlPage.resx ├── DeepLClient.csproj ├── Enums ├── DocumentType.cs └── TranslationEventType.cs ├── Extensions ├── DictionaryExtensions.cs ├── HashExtensions.cs ├── TranslationEventExtensions.cs └── WebpageResultExtensions.cs ├── Forms ├── About.Designer.cs ├── About.cs ├── About.resx ├── Configuration.Designer.cs ├── Configuration.cs ├── Configuration.resx ├── Dialogs │ ├── ConfirmDocument.Designer.cs │ ├── ConfirmDocument.cs │ ├── ConfirmDocument.resx │ ├── LimitExceeded.Designer.cs │ ├── LimitExceeded.cs │ └── LimitExceeded.resx ├── LoggingConfiguration.Designer.cs ├── LoggingConfiguration.cs ├── LoggingConfiguration.resx ├── MailConfiguration.Designer.cs ├── MailConfiguration.cs ├── MailConfiguration.resx ├── Main.Designer.cs ├── Main.cs ├── Main.resx ├── SubscriptionInfo.Designer.cs ├── SubscriptionInfo.cs └── SubscriptionInfo.resx ├── Functions ├── ComboBoxTheme.cs ├── CustomApplicationContext.cs ├── HelperFunctions.cs ├── MessageBoxAdv2.cs ├── NativeFunctions.cs └── PrintFunctions.cs ├── Managers ├── DeepLManager.cs ├── DocumentManager.cs ├── HotkeyManager.cs ├── LaunchManager.cs ├── MainFormManager.cs ├── ProcessManager.cs ├── SettingsManager.cs ├── StorageManager.cs ├── SubscriptionManager.cs ├── TranslationEventsManager.cs ├── UpdateManager.cs └── UrlManager.cs ├── Models ├── AppSettings.cs ├── EmailSettings.cs ├── TranslationEvent.cs └── WebpageResult.cs ├── Program.cs ├── Properties ├── Resources.Designer.cs └── Resources.resx ├── Resources ├── book_icon_16.png ├── book_icon_24.png ├── book_icon_48.png ├── clean_icon_16.png ├── clean_icon_24.png ├── clean_icon_32.png ├── clipboard_icon_16.png ├── config_icon_24.png ├── config_icon_64.png ├── document_icon_18.png ├── document_icon_32.png ├── exit_icon_24.png ├── globe_icon_16.png ├── globe_icon_24.png ├── hide_icon_24.png ├── icon_32.png ├── icon_white_32.ico ├── info_icon_16.png ├── info_icon_24.png ├── logo.png ├── logo_notext.png ├── no_icon_16.png ├── no_icon_24.png ├── price_icon_24.png ├── price_icon_64.png ├── print_icon_16.png ├── print_icon_24.png ├── save_icon_24.png ├── show_icon_24.png ├── switch_icon_24.png ├── switch_icon_32.png ├── switch_icon_48.png ├── text_icon_18.png ├── text_icon_32.png ├── warning_icon_16.png ├── warning_icon_24.png └── yes_icon_24.png └── Variables.cs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: LAB02-Admin 2 | ko_fi: lab02research 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015/2017 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # Visual Studio 2017 auto generated files 39 | Generated\ Files/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUnit 46 | *.VisualState.xml 47 | TestResult.xml 48 | nunit-*.xml 49 | 50 | # Build Results of an ATL Project 51 | [Dd]ebugPS/ 52 | [Rr]eleasePS/ 53 | dlldata.c 54 | 55 | # Benchmark Results 56 | BenchmarkDotNet.Artifacts/ 57 | 58 | # .NET Core 59 | project.lock.json 60 | project.fragment.lock.json 61 | artifacts/ 62 | 63 | # StyleCop 64 | StyleCopReport.xml 65 | 66 | # Files built by Visual Studio 67 | *_i.c 68 | *_p.c 69 | *_h.h 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.iobj 74 | *.pch 75 | *.pdb 76 | *.ipdb 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *_wpftmp.csproj 87 | *.log 88 | *.vspscc 89 | *.vssscc 90 | .builds 91 | *.pidb 92 | *.svclog 93 | *.scc 94 | 95 | # Chutzpah Test files 96 | _Chutzpah* 97 | 98 | # Visual C++ cache files 99 | ipch/ 100 | *.aps 101 | *.ncb 102 | *.opendb 103 | *.opensdf 104 | *.sdf 105 | *.cachefile 106 | *.VC.db 107 | *.VC.VC.opendb 108 | 109 | # Visual Studio profiler 110 | *.psess 111 | *.vsp 112 | *.vspx 113 | *.sap 114 | 115 | # Visual Studio Trace Files 116 | *.e2e 117 | 118 | # TFS 2012 Local Workspace 119 | $tf/ 120 | 121 | # Guidance Automation Toolkit 122 | *.gpState 123 | 124 | # ReSharper is a .NET coding add-in 125 | _ReSharper*/ 126 | *.[Rr]e[Ss]harper 127 | *.DotSettings.user 128 | 129 | # JustCode is a .NET coding add-in 130 | .JustCode 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | *.nupkg 189 | # NuGet Symbol Packages 190 | *.snupkg 191 | # The packages folder can be ignored because of Package Restore 192 | **/[Pp]ackages/* 193 | # except build/, which is used as an MSBuild target. 194 | !**/[Pp]ackages/build/ 195 | # Uncomment if necessary however generally it will be regenerated when needed 196 | #!**/[Pp]ackages/repositories.config 197 | # NuGet v3's project.json files produces more ignorable files 198 | *.nuget.props 199 | *.nuget.targets 200 | 201 | # Microsoft Azure Build Output 202 | csx/ 203 | *.build.csdef 204 | 205 | # Microsoft Azure Emulator 206 | ecf/ 207 | rcf/ 208 | 209 | # Windows Store app package directories and files 210 | AppPackages/ 211 | BundleArtifacts/ 212 | Package.StoreAssociation.xml 213 | _pkginfo.txt 214 | *.appx 215 | *.appxbundle 216 | *.appxupload 217 | 218 | # Visual Studio cache files 219 | # files ending in .cache can be ignored 220 | *.[Cc]ache 221 | # but keep track of directories ending in .cache 222 | !?*.[Cc]ache/ 223 | 224 | # Others 225 | ClientBin/ 226 | ~$* 227 | *~ 228 | *.dbmdl 229 | *.dbproj.schemaview 230 | *.jfm 231 | *.pfx 232 | *.publishsettings 233 | orleans.codegen.cs 234 | 235 | # Including strong name files can present a security risk 236 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 237 | #*.snk 238 | 239 | # Since there are multiple workflows, uncomment next line to ignore bower_components 240 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 241 | #bower_components/ 242 | 243 | # RIA/Silverlight projects 244 | Generated_Code/ 245 | 246 | # Backup & report files from converting an old project file 247 | # to a newer Visual Studio version. Backup files are not needed, 248 | # because we have git ;-) 249 | _UpgradeReport_Files/ 250 | Backup*/ 251 | UpgradeLog*.XML 252 | UpgradeLog*.htm 253 | ServiceFabricBackup/ 254 | *.rptproj.bak 255 | 256 | # SQL Server files 257 | *.mdf 258 | *.ldf 259 | *.ndf 260 | 261 | # Business Intelligence projects 262 | *.rdl.data 263 | *.bim.layout 264 | *.bim_*.settings 265 | *.rptproj.rsuser 266 | *- [Bb]ackup.rdl 267 | *- [Bb]ackup ([0-9]).rdl 268 | *- [Bb]ackup ([0-9][0-9]).rdl 269 | 270 | # Microsoft Fakes 271 | FakesAssemblies/ 272 | 273 | # GhostDoc plugin setting file 274 | *.GhostDoc.xml 275 | 276 | # Node.js Tools for Visual Studio 277 | .ntvs_analysis.dat 278 | node_modules/ 279 | 280 | # Visual Studio 6 build log 281 | *.plg 282 | 283 | # Visual Studio 6 workspace options file 284 | *.opt 285 | 286 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 287 | *.vbw 288 | 289 | # Visual Studio LightSwitch build output 290 | **/*.HTMLClient/GeneratedArtifacts 291 | **/*.DesktopClient/GeneratedArtifacts 292 | **/*.DesktopClient/ModelManifest.xml 293 | **/*.Server/GeneratedArtifacts 294 | **/*.Server/ModelManifest.xml 295 | _Pvt_Extensions 296 | 297 | # Paket dependency manager 298 | .paket/paket.exe 299 | paket-files/ 300 | 301 | # FAKE - F# Make 302 | .fake/ 303 | 304 | # CodeRush personal settings 305 | .cr/personal 306 | 307 | # Python Tools for Visual Studio (PTVS) 308 | __pycache__/ 309 | *.pyc 310 | 311 | # Cake - Uncomment if you are using it 312 | # tools/** 313 | # !tools/packages.config 314 | 315 | # Tabs Studio 316 | *.tss 317 | 318 | # Telerik's JustMock configuration file 319 | *.jmconfig 320 | 321 | # BizTalk build output 322 | *.btp.cs 323 | *.btm.cs 324 | *.odx.cs 325 | *.xsd.cs 326 | 327 | # OpenCover UI analysis results 328 | OpenCover/ 329 | 330 | # Azure Stream Analytics local run output 331 | ASALocalRun/ 332 | 333 | # MSBuild Binary and Structured Log 334 | *.binlog 335 | 336 | # NVidia Nsight GPU debugger configuration file 337 | *.nvuser 338 | 339 | # MFractors (Xamarin productivity tool) working folder 340 | .mfractor/ 341 | 342 | # Local History for Visual Studio 343 | .localhistory/ 344 | 345 | # BeatPulse healthcheck temp database 346 | healthchecksdb 347 | 348 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 349 | MigrationBackup/ 350 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2023 LAB02 Research 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/LAB02-Research/DeepL-Translator)](https://github.com/LAB02-Research/DeepL-Translator/releases/) 2 | [![license](https://img.shields.io/badge/license-MIT-blue)](#license) 3 | [![OS - Windows](https://img.shields.io/badge/OS-Windows-blue?logo=windows&logoColor=white)](https://www.microsoft.com/ "Go to Microsoft homepage") 4 | [![dotnet](https://img.shields.io/badge/.NET-7.0-blue)](https://img.shields.io/badge/.NET-7.0-blue) 5 | ![GitHub all releases](https://img.shields.io/github/downloads/LAB02-Research/DeepL-Translator/total?color=blue) 6 | 7 | 8 | DeepL logo 9 | 10 | # DeepL Translator 11 | 12 | DeepL Translator is a Windows-based client application for [DeepL's translation API](https://www.deepl.com/pro-api?cta=header-pro-api), developed in .NET 7. 13 | 14 | Click [here](https://github.com/LAB02-Research/DeepL-Translator/releases/latest/download/DeepL.Translator.Installer.exe) to download the latest installer. 15 | 16 | ---- 17 | 18 | ### Content 19 | 20 | * [Introduction](#introduction) 21 | * [Download](#download) 22 | * [Functionality](#functionality) 23 | * [Usage](#usage) 24 | * [Screenshots](#screenshots) 25 | * [Implemented](#implemented) 26 | * [Credits and Licensing](#credits-and-licensing) 27 | 28 | ---- 29 | 30 | ### Introduction 31 | 32 | [DeepL](https://www.deepl.com/pro-api) provides AI/ML translation services. They differ from for example Google Translate in that they don't translate *as-is*, but also contextually. I've been using it a lot for translating from and into French, and it's truly amazing what they can do. Besides plain text, it's also possible to translate entire documents, while preserving their layout. 33 | 34 |

35 | 36 | DeepL logo 37 |

38 | 39 | DeepL offers a Windows app for their translation services, but that client doesn't support the API, only regular subscriptions. Using the API can be more cost efficient for both the free and pro subscriptions: the free subscription offers 500.000 characters per month, which can be used for both documents and text. The pro subscription charges per character, instead of having to pay a set price monthly regardless of usage. 40 | 41 | So, since there isn't an API client available, I built one that I'd want to use. It even has a feature that DeepL doesn't yet provide: **translating webpages**! It uses Firefox's *reading mode* engine which not only makes it easier for you to read the translation, but also reduces the amount of characters. 42 | 43 | **Note: regardless of how it may seem, I'm not affiliated with DeepL in any way. I did not get a dime for writing this.** 44 | If you appreciate my work, I'd appreciate a coffee! 45 | 46 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/lab02research) 47 | 48 | ---- 49 | 50 | ### Download 51 | 52 | Click [here](https://github.com/LAB02-Research/DeepL-Translator/releases/latest/download/DeepL.Translator.Installer.exe) to download the latest installer, or [here](https://github.com/LAB02-Research/DeepL-Translator/releases/latest/) for the zip package and source code. 53 | 54 | ---- 55 | 56 | ### Functionality 57 | 58 | * **Text**: translate written text, or just drag and drop a .txt file. 59 | 60 | * **Documents**: translate a variaty of document types. Supports drag and drop, and has lots of checks to make sure your document will be accepted by DeepL. 61 | 62 | * **Webpages**: translate any webpage. Extracts the relevant text to reduce the amount of characters and increase the readability. 63 | 64 | * **Language detection**: all of the above options support automatic source language detection. 65 | 66 | * **Hotkey**: easily consult the app by pressing your hotkey. If you have text selected, it'll be conveniently pasted into the source textbox. Select an url and it'll get translated on the webpage tab right away. 67 | 68 | * **Formality**: supports setting a different formality (as long as the language supports it). 69 | 70 | * **Cost management**: tells you how much your text or document will cost in terms of both characters and hard cash. Shows the current state of your subscription. 71 | 72 | * **UI**: full fledged interface, built to be easy and intuitive to use with as little input as possible. Resides in the system tray. 73 | 74 | * **Print or save**: print your translations, or store them locally for later reference. 75 | 76 | * **Updater**: contains an automatic updater that'll keep your translator up-to-date in the background. 77 | 78 | ---- 79 | 80 | ### Usage 81 | 82 | Just install the [latest installer](https://github.com/LAB02-Research/DeepL-Translator/releases/latest/download/DeepL.Translator.Installer.exe). It'll make sure you have all the required prerequisites, and the application will launch when done (and notify you of a missing API key). 83 | 84 | You can get your free or pro key here: [https://www.deepl.com/pro-api/](https://www.deepl.com/pro-api/) 85 | 86 | Make sure you set the correct domain, corresponding to your subscription: 87 | 88 | ![image](https://user-images.githubusercontent.com/81011038/225697022-7adb0b3a-f2be-417f-8a14-f516253eff78.png) 89 | 90 | **Note: if you use a pro subscription and a different currency than euros, make sure to set the correct price-per-character value.** 91 | 92 | ---- 93 | 94 | ### Screenshots 95 | 96 | *What's the point of open sourcing your software if you don't share screenshots ..* 97 | 98 | Text interface: 99 | 100 | ![image](https://user-images.githubusercontent.com/81011038/228805991-3078c003-dc00-4eb2-be52-59902b0bc8a0.png) 101 | 102 | Document interface: 103 | 104 | ![image](https://user-images.githubusercontent.com/81011038/228806150-87e997bc-ae37-4cd5-9cd1-08262619151d.png) 105 | 106 | Webpage interface: 107 | 108 | ![image](https://user-images.githubusercontent.com/81011038/225082337-98a958df-109d-4536-a59c-c82f896cbd41.png) 109 | 110 | ![image](https://user-images.githubusercontent.com/81011038/225969909-95f3d2e3-235d-44b7-898b-6c4c508d27b4.png) 111 | 112 | You'll be notified of the costs before translating a document: 113 | 114 | ![image](https://user-images.githubusercontent.com/81011038/225082514-5d73345a-1e4d-4e30-85f3-6eedfe9114f4.png) 115 | 116 | ![image](https://user-images.githubusercontent.com/81011038/225082586-eb3c6ada-7c28-49c4-81c9-d08498469ff5.png) 117 | 118 | ![image](https://user-images.githubusercontent.com/81011038/225082691-96a85ca7-6bc0-48a6-a3d3-d6eea768336f.png) 119 | 120 | If a document or webpage translation will exceed your limit, you'll be notified as well: 121 | 122 | ![image](https://user-images.githubusercontent.com/81011038/225082718-046a7d04-76f0-4026-9d9e-aaf1fbef0ceb.png) 123 | 124 | Or if you've already reached your limit: 125 | 126 | ![image](https://user-images.githubusercontent.com/81011038/225083048-44d79723-366a-455e-9a89-26eaee470227.png) 127 | 128 | Easily check your subscription's state: 129 | 130 | ![image](https://user-images.githubusercontent.com/81011038/225083226-9b4efe83-d717-4da7-8eda-9c61553215ae.png) 131 | 132 | ![image](https://user-images.githubusercontent.com/81011038/225083250-0be21350-363c-432b-a498-d5bf18535281.png) 133 | 134 | Formality support: 135 | 136 | ![image](https://user-images.githubusercontent.com/81011038/224790184-dfed63ef-b438-4d2e-8843-e4114d831f78.png) 137 | 138 | Hides in your system tray, next to the clock: 139 | 140 | ![image](https://user-images.githubusercontent.com/81011038/224070094-6a396395-7d95-4b44-9246-341cd76d0d38.png) 141 | 142 | ---- 143 | 144 | ### Implemented 145 | 146 | This is a list of features and fixes that have so far been done: 147 | 148 | - Make main window resizable 149 | - Toggle button to switch source and target language 150 | - 'Document in use' check 151 | - Add scrollbars to text translation fields 152 | - Apply theme to webpage reading mode scrollbars 153 | - Button tooltips 154 | - Limit nagging popups to max once 155 | - Always on top 156 | - Support for other currencies 157 | - Recognize urls when using global hotkey, insert into webpage tab 158 | - Add 'open in browser' button for translated webpage 159 | - Support for drag 'n dropping URLs 160 | - Add 'save translation' support 161 | - Add 'print translation' support 162 | - Global hotkey 163 | - Fetch selection after using global hotkey 164 | - Set focus to 'translate' button after selecting document 165 | - Set focus to 'copy to clipboard' after translating text 166 | - Fix scaling on non-default DPI modes 167 | - Ignore 'characters left' for pro 168 | - Emphasize 'limit (will be) reached' message with red 169 | - Revert to 'text' page on hide 170 | - Fix tray icon on dark backgrounds 171 | - Auto launch on Windows login 172 | - Domain info popup 173 | - UI stays locked after 'translation failed' 174 | - Allow only single instance 175 | - Press 'esc' in any window to hide 176 | - Set money cost to zero on free 177 | - Show warning when limit passed 178 | - Show warning when limit will be passed by translation 179 | - Add 'clear' buttons 180 | - Add 'open subscription' button to info page 181 | - Improve installer graphics 182 | - Bundle .NET 7 and WebView2 with installer 183 | 184 | ---- 185 | 186 | ### Credits and Licensing 187 | 188 | Thanks to [DeepL](https://www.deepl.com/pro-api) for providing such a great translation service! 189 | 190 | And a big thank you to all other packages: 191 | 192 | [ByteSize](https://github.com/omar/ByteSize), [DeepL.net](https://github.com/DeepLcom/deepl-dotnet), [Microsoft.Web.WebView2](https://learn.microsoft.com/en-us/microsoft-edge/webview2/), [Newtonsoft.Json](https://www.newtonsoft.com/json), [HotkeyListener](https://github.com/Willy-Kimura/HotkeyListener) (and [@ruffk](https://github.com/ruffk)'s [core version](https://github.com/ruffk/HotkeyListener)), [Serilog](https://github.com/serilog/serilog), [SmartReader](https://github.com/strumenta/SmartReader), [Syncfusion](https://www.syncfusion.com). 193 | 194 | Please consult their individual licensing if you plan to use any of their code. 195 | 196 | DeepL Translator is released under the [MIT license](https://opensource.org/licenses/MIT). 197 | 198 | **LAB02 Research is not in any way affiliated with DeepL, and does not receive any commisions or other payment for developing this application.** 199 | 200 | --- 201 | -------------------------------------------------------------------------------- /images/deepl_full_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/images/deepl_full_logo.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/images/logo.png -------------------------------------------------------------------------------- /images/logo_notext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/images/logo_notext.png -------------------------------------------------------------------------------- /src/DeepLClient.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33414.496 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeepLClient", "DeepLClient\DeepLClient.csproj", "{B6D48AE0-5D23-423F-B803-60D562667B10}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B6D48AE0-5D23-423F-B803-60D562667B10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {B6D48AE0-5D23-423F-B803-60D562667B10}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {B6D48AE0-5D23-423F-B803-60D562667B10}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {B6D48AE0-5D23-423F-B803-60D562667B10}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | VisualSVNWorkingCopyRoot = . 24 | SolutionGuid = {A0E5BB9D-1D72-4D4B-B516-2B708516A3A7} 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /src/DeepLClient/Controls/DocumentsPage.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | 49, 25 62 | 63 | -------------------------------------------------------------------------------- /src/DeepLClient/Controls/TextPage.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | 348, 27 62 | 63 | 64 | 65 | 66 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 67 | vAAADrwBlbxySQAAAIJJREFUOE+1kNENgCAMBRnYPRjAARyBDwdxFWABLVhMpRQU8ZKLTX15ARTFOTeD 68 | e0ODcU4I4ChSzXwu6AIan9xb0sQjvdV7v8BXh7mrANTW2inMvQWX4wvwbUVYni0KoWSxAB5jvS0a0Cy4 69 | 4fokFWShaAwAdGZUfyL/FuTvIUjurdQBaE0luFgHKUIAAAAASUVORK5CYII= 70 | 71 | 72 | 73 | 192, 22 74 | 75 | 76 | 17, 17 77 | 78 | -------------------------------------------------------------------------------- /src/DeepLClient/Controls/UrlPage.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | 61 | 35, 26 62 | 63 | 64 | 65 | 66 | iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 67 | vAAADrwBlbxySQAAAcRJREFUSEu1lqFPQlEUxgkGA8FgMBgMBv8Ag8GNYsf37mPI5kA2gyP5BxjcDASj 68 | wWAwGAwEo4FAMBgMTEBgc45AIDgGb7oRCM/vPA/svesB4fn8tt/23rnnfvfcy3l3RBzH+VdmVikWW6ha 69 | 1l4tkTigZw6Hp5plXb5YluOi1C2Hw1HdMLbG5gzthof/LlT/qC+AWKuVySxySnCh0kPdfERdqTNOC6Zy 70 | PL6E8+5I5kRNqUEjmVzj9PmFYzj3mSp1DwparMDp86limhswGHrNbNt28cZcTHOHp80ut1rNqN/tvmGB 71 | gR7HTqtzfRuoaFc3Iexery3uAKAZjnn6dFHrofpXyWTaAqDXTKWW2WayUMmJMNnF7vc/aYFGNtuVxlHY 72 | FdvIejaMVSR9iJMBmRPNXK4tjYNhRalNtvsp/Fg3wqQxMyxAu3hgO79wS26LEzx0isUS0Uin+9L4GKX2 73 | 2fZb1GIIlsXkIODrR8FRtnd7/khM9ON2EEHP2phE3jWn+wZ3yruQ4INuz9EC9CzleKF7CnnrEbxc6IMT 74 | GJIxm/uukEkg944659dqgkK7oPP3347h8kTtGcUip3i5Dpk8vFfEvxrh4US+ALYVzF69gAo/AAAAAElF 75 | TkSuQmCC 76 | 77 | 78 | 79 | 80 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 81 | vAAADrwBlbxySQAAAIJJREFUOE+1kNENgCAMBRnYPRjAARyBDwdxFWABLVhMpRQU8ZKLTX15ARTFOTeD 82 | e0ODcU4I4ChSzXwu6AIan9xb0sQjvdV7v8BXh7mrANTW2inMvQWX4wvwbUVYni0KoWSxAB5jvS0a0Cy4 83 | 4fokFWShaAwAdGZUfyL/FuTvIUjurdQBaE0luFgHKUIAAAAASUVORK5CYII= 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/DeepLClient/DeepLClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net7.0-windows10.0.17763.0 6 | disable 7 | true 8 | enable 9 | Resources\icon_white_32.ico 10 | DeepL Translator 11 | 2023.5.24.0 12 | LAB02 RESEARCH 13 | Front-end client for DeepL translation services. 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | UserControl 41 | 42 | 43 | UserControl 44 | 45 | 46 | Form 47 | 48 | 49 | Form 50 | 51 | 52 | Form 53 | 54 | 55 | Form 56 | 57 | 58 | Form 59 | 60 | 61 | Form 62 | 63 | 64 | Form 65 | 66 | 67 | True 68 | True 69 | Resources.resx 70 | 71 | 72 | 73 | 74 | 75 | ResXFileCodeGenerator 76 | Resources.Designer.cs 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/DeepLClient/Enums/DocumentType.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace DeepLClient.Enums; 4 | 5 | [SuppressMessage("ReSharper", "InconsistentNaming")] 6 | public enum DocumentType 7 | { 8 | Word, 9 | PowerPoint, 10 | PDF, 11 | Text, 12 | HTML, 13 | Unsupported 14 | } -------------------------------------------------------------------------------- /src/DeepLClient/Enums/TranslationEventType.cs: -------------------------------------------------------------------------------- 1 | namespace DeepLClient.Enums; 2 | 3 | public enum TranslationEventType 4 | { 5 | Text, 6 | Document, 7 | Webpage 8 | } -------------------------------------------------------------------------------- /src/DeepLClient/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DeepLClient.Extensions 8 | { 9 | public static class DictionaryExtensions 10 | { 11 | /// 12 | /// Fetch a KeyValuePair by key 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | public static KeyValuePair GetEntry(this IDictionary dictionary, TKey key) 20 | { 21 | return new KeyValuePair(key, dictionary[key]); 22 | } 23 | 24 | /// 25 | /// Fetch a KeyValuePair by entry 26 | /// 27 | /// 28 | /// 29 | /// 30 | public static KeyValuePair GetKeyByEntry(this IDictionary dictionary, string value) 31 | { 32 | var searchVal = value; 33 | 34 | if (dictionary.All(x => x.Value != value)) 35 | { 36 | // is it a combined language? 37 | if (value.Contains("-")) 38 | { 39 | // yep, try again without parenthesis by splitting the string 40 | var split = value.Split('-').First().Trim(); 41 | 42 | // if found, set the search value to the split value, otherwise set it to "AUTO DETECT" 43 | searchVal = dictionary.Any(x => x.Value == split) ? split : "AUTO DETECT"; 44 | } 45 | else 46 | { 47 | // nope, search for the first value in the dictionary that starts with the value 48 | // if not found, set the search value to "AUTO DETECT" 49 | searchVal = dictionary.FirstOrDefault(x => x.Value.StartsWith(value)).Value ?? "AUTO DETECT"; 50 | } 51 | } 52 | 53 | // fetch the key by value 54 | var key = dictionary.FirstOrDefault(x => x.Value == searchVal).Key; 55 | 56 | // done 57 | return new KeyValuePair(key, dictionary[key]); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/DeepLClient/Extensions/HashExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace DeepLClient.Extensions 9 | { 10 | internal static class HashExtensions 11 | { 12 | /// 13 | /// Hashes the provided input 14 | /// 15 | /// 16 | /// 17 | /// 18 | internal static string GetHash(this HashAlgorithm hashAlgorithm, string input) 19 | { 20 | // https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.hashalgorithm.computehash?view=net-7.0 21 | var data = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); 22 | var sBuilder = new StringBuilder(); 23 | foreach (var t in data) sBuilder.Append(t.ToString("x2")); 24 | return sBuilder.ToString(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/DeepLClient/Extensions/TranslationEventExtensions.cs: -------------------------------------------------------------------------------- 1 | using DeepLClient.Models; 2 | using DeepLClient.Enums; 3 | using DeepLClient.Managers; 4 | 5 | namespace DeepLClient.Extensions 6 | { 7 | public static class TranslationEventExtensions 8 | { 9 | /// 10 | /// Sets the translation event to a 'text' translation 11 | /// 12 | /// 13 | /// 14 | /// 15 | public static TranslationEvent SetTextEvent(this TranslationEvent translationEvent, double characters) 16 | { 17 | return SetEvent(translationEvent, TranslationEventType.Text, characters); 18 | } 19 | 20 | /// 21 | /// Sets the translation event to a 'document' translation 22 | /// 23 | /// 24 | /// 25 | /// 26 | public static TranslationEvent SetDocumentEvent(this TranslationEvent translationEvent, double characters) 27 | { 28 | return SetEvent(translationEvent, TranslationEventType.Document, characters); 29 | } 30 | 31 | /// 32 | /// Sets the translation event to a 'webpag' translation 33 | /// 34 | /// 35 | /// 36 | /// 37 | public static TranslationEvent SetWebpageEvent(this TranslationEvent translationEvent, double characters) 38 | { 39 | return SetEvent(translationEvent, TranslationEventType.Webpage, characters); 40 | } 41 | 42 | private static TranslationEvent SetEvent(TranslationEvent translationEvent, TranslationEventType type, double characters) 43 | { 44 | var user = string.IsNullOrEmpty(Variables.AppSettings.User) 45 | ? Environment.UserName 46 | : Variables.AppSettings.User; 47 | 48 | var (apiSection, apiHash) = SubscriptionManager.GetAnonymisedApiKey(); 49 | 50 | translationEvent.Name = user; 51 | translationEvent.MomentUtc = DateTime.UtcNow; 52 | translationEvent.Type = type; 53 | translationEvent.Characters = characters; 54 | translationEvent.EstimatedCost = SubscriptionManager.CalculateCost(characters, false); 55 | translationEvent.ApiKeySegment = apiSection; 56 | translationEvent.ApiKeyHash = apiHash; 57 | translationEvent.Domain = Variables.AppSettings.DeepLDomain; 58 | 59 | return translationEvent; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/DeepLClient/Extensions/WebpageResultExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using DeepLClient.Models; 7 | 8 | namespace DeepLClient.Extensions 9 | { 10 | public static class WebpageResultExtensions 11 | { 12 | public static WebpageResult SetReadableFailed(this WebpageResult webpageResult, string content, string title) 13 | { 14 | webpageResult.Success = true; 15 | webpageResult.IsReadable = false; 16 | webpageResult.Content = content; 17 | webpageResult.Title = title; 18 | webpageResult.Error = "unable to determine what part of the site is relevant text"; 19 | 20 | return webpageResult; 21 | } 22 | 23 | public static WebpageResult SetSuccess(this WebpageResult webpageResult, string content, string title) 24 | { 25 | webpageResult.Success = true; 26 | webpageResult.IsReadable = true; 27 | webpageResult.Content = content; 28 | webpageResult.Title = title; 29 | 30 | return webpageResult; 31 | } 32 | 33 | public static WebpageResult SetFailed(this WebpageResult webpageResult, string error) 34 | { 35 | webpageResult.Success = false; 36 | webpageResult.IsReadable = false; 37 | webpageResult.Error = error; 38 | 39 | return webpageResult; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/DeepLClient/Forms/About.cs: -------------------------------------------------------------------------------- 1 | using DeepLClient.Functions; 2 | using Syncfusion.Windows.Forms; 3 | 4 | namespace DeepLClient.Forms 5 | { 6 | public partial class About : MetroForm 7 | { 8 | public About() 9 | { 10 | InitializeComponent(); 11 | } 12 | 13 | private void About_Load(object sender, EventArgs e) 14 | { 15 | // set topmost 16 | TopMost = Variables.AppSettings.AlwaysOnTop; 17 | 18 | // catch all key presses 19 | KeyPreview = true; 20 | 21 | // set title 22 | Text = $"About | {Variables.ApplicationName}"; 23 | 24 | // set version 25 | LblVersion.Text = $"v{Variables.Version}"; 26 | } 27 | 28 | private void BtnYes_Click(object sender, EventArgs e) 29 | { 30 | Close(); 31 | } 32 | 33 | private void LblLab02Research_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://lab02-research.org"); 34 | 35 | private void LblByteSize_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://github.com/omar/ByteSize"); 36 | 37 | private void LblDeepLnet_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://github.com/DeepLcom/deepl-dotnet"); 38 | 39 | private void LblWebView2_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://learn.microsoft.com/en-us/microsoft-edge/webview2/"); 40 | 41 | private void LblNewtonsoftJson_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://www.newtonsoft.com/json"); 42 | 43 | private void LblHotkeyListener_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://github.com/ruffk/HotkeyListener"); 44 | 45 | private void LblSerilog_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://github.com/serilog/serilog"); 46 | 47 | private void LblSmartReader_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://github.com/strumenta/SmartReader"); 48 | 49 | private void LblSyncfusion_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://www.syncfusion.com/"); 50 | 51 | private void PbBMAC_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://www.buymeacoffee.com/lab02research"); 52 | 53 | private void PbPayPal_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://www.paypal.com/donate/?hosted_button_id=5YL6UP94AQSPC"); 54 | 55 | private void LblDeepLProject_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://github.com/LAB02-Research/DeepL-Translator"); 56 | 57 | private void PbLogo_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://www.deepl.com/pro-api"); 58 | 59 | private void LblDeepL_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://www.deepl.com/pro-api"); 60 | 61 | private void About_KeyUp(object sender, KeyEventArgs e) 62 | { 63 | if (e.KeyCode != Keys.Escape) return; 64 | Close(); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/DeepLClient/Forms/Dialogs/ConfirmDocument.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace DeepLClient.Forms.Dialogs 2 | { 3 | partial class ConfirmDocument 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ConfirmDocument)); 32 | BtnNo = new Syncfusion.WinForms.Controls.SfButton(); 33 | BtnYes = new Syncfusion.WinForms.Controls.SfButton(); 34 | PbCost = new PictureBox(); 35 | LblIntro = new Label(); 36 | LblTxt = new Label(); 37 | LblDocument = new Label(); 38 | LblCostInfo = new Label(); 39 | LblConfirm = new Label(); 40 | ((System.ComponentModel.ISupportInitialize)PbCost).BeginInit(); 41 | SuspendLayout(); 42 | // 43 | // BtnNo 44 | // 45 | BtnNo.AccessibleDescription = ""; 46 | BtnNo.AccessibleName = ""; 47 | BtnNo.AccessibleRole = AccessibleRole.PushButton; 48 | BtnNo.BackColor = Color.FromArgb(63, 63, 70); 49 | BtnNo.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 50 | BtnNo.ForeColor = Color.FromArgb(241, 241, 241); 51 | BtnNo.ImageSize = new Size(16, 16); 52 | BtnNo.Location = new Point(115, 288); 53 | BtnNo.Name = "BtnNo"; 54 | BtnNo.Size = new Size(157, 31); 55 | BtnNo.Style.BackColor = Color.FromArgb(63, 63, 70); 56 | BtnNo.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); 57 | BtnNo.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); 58 | BtnNo.Style.ForeColor = Color.FromArgb(241, 241, 241); 59 | BtnNo.Style.HoverBackColor = Color.FromArgb(63, 63, 70); 60 | BtnNo.Style.HoverForeColor = Color.FromArgb(241, 241, 241); 61 | BtnNo.Style.Image = Properties.Resources.no_icon_16; 62 | BtnNo.Style.PressedForeColor = Color.Black; 63 | BtnNo.TabIndex = 1; 64 | BtnNo.UseVisualStyleBackColor = false; 65 | BtnNo.Click += BtnNo_Click; 66 | // 67 | // BtnYes 68 | // 69 | BtnYes.AccessibleDescription = ""; 70 | BtnYes.AccessibleName = ""; 71 | BtnYes.AccessibleRole = AccessibleRole.PushButton; 72 | BtnYes.BackColor = Color.FromArgb(63, 63, 70); 73 | BtnYes.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 74 | BtnYes.ForeColor = Color.FromArgb(241, 241, 241); 75 | BtnYes.ImageSize = new Size(24, 24); 76 | BtnYes.Location = new Point(278, 288); 77 | BtnYes.Name = "BtnYes"; 78 | BtnYes.Size = new Size(324, 31); 79 | BtnYes.Style.BackColor = Color.FromArgb(63, 63, 70); 80 | BtnYes.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); 81 | BtnYes.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); 82 | BtnYes.Style.ForeColor = Color.FromArgb(241, 241, 241); 83 | BtnYes.Style.HoverBackColor = Color.FromArgb(63, 63, 70); 84 | BtnYes.Style.HoverForeColor = Color.FromArgb(241, 241, 241); 85 | BtnYes.Style.Image = Properties.Resources.yes_icon_24; 86 | BtnYes.Style.PressedForeColor = Color.Black; 87 | BtnYes.TabIndex = 0; 88 | BtnYes.UseVisualStyleBackColor = false; 89 | BtnYes.Click += BtnYes_Click; 90 | // 91 | // PbCost 92 | // 93 | PbCost.Image = Properties.Resources.price_icon_64; 94 | PbCost.Location = new Point(22, 28); 95 | PbCost.Name = "PbCost"; 96 | PbCost.Size = new Size(64, 64); 97 | PbCost.SizeMode = PictureBoxSizeMode.AutoSize; 98 | PbCost.TabIndex = 6; 99 | PbCost.TabStop = false; 100 | // 101 | // LblIntro 102 | // 103 | LblIntro.AccessibleDescription = ""; 104 | LblIntro.AccessibleName = ""; 105 | LblIntro.AccessibleRole = AccessibleRole.StaticText; 106 | LblIntro.AutoSize = true; 107 | LblIntro.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 108 | LblIntro.Location = new Point(115, 28); 109 | LblIntro.Name = "LblIntro"; 110 | LblIntro.Size = new Size(105, 19); 111 | LblIntro.TabIndex = 71; 112 | LblIntro.Text = "You've selected:"; 113 | // 114 | // LblTxt 115 | // 116 | LblTxt.AccessibleDescription = ""; 117 | LblTxt.AccessibleName = ""; 118 | LblTxt.AccessibleRole = AccessibleRole.StaticText; 119 | LblTxt.Font = new Font("Segoe UI", 8.25F, FontStyle.Regular, GraphicsUnit.Point); 120 | LblTxt.Location = new Point(115, 252); 121 | LblTxt.Name = "LblTxt"; 122 | LblTxt.Size = new Size(487, 33); 123 | LblTxt.TabIndex = 77; 124 | LblTxt.Text = "Tip: you're trying to translate a small .txt file. Did you know you can just drag and drop them onto the text page? That way it'll use up less characters."; 125 | LblTxt.Visible = false; 126 | // 127 | // LblDocument 128 | // 129 | LblDocument.AccessibleDescription = ""; 130 | LblDocument.AccessibleName = ""; 131 | LblDocument.AccessibleRole = AccessibleRole.StaticText; 132 | LblDocument.AutoSize = true; 133 | LblDocument.Font = new Font("Segoe UI", 9.75F, FontStyle.Bold, GraphicsUnit.Point); 134 | LblDocument.Location = new Point(115, 72); 135 | LblDocument.Name = "LblDocument"; 136 | LblDocument.Size = new Size(13, 17); 137 | LblDocument.TabIndex = 78; 138 | LblDocument.Text = "-"; 139 | // 140 | // LblCostInfo 141 | // 142 | LblCostInfo.AccessibleDescription = ""; 143 | LblCostInfo.AccessibleName = ""; 144 | LblCostInfo.AccessibleRole = AccessibleRole.StaticText; 145 | LblCostInfo.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 146 | LblCostInfo.Location = new Point(115, 114); 147 | LblCostInfo.Name = "LblCostInfo"; 148 | LblCostInfo.Size = new Size(487, 87); 149 | LblCostInfo.TabIndex = 79; 150 | LblCostInfo.Text = "It contains {0} characters, and will probably cost around {1}."; 151 | // 152 | // LblConfirm 153 | // 154 | LblConfirm.AccessibleDescription = ""; 155 | LblConfirm.AccessibleName = ""; 156 | LblConfirm.AccessibleRole = AccessibleRole.StaticText; 157 | LblConfirm.AutoSize = true; 158 | LblConfirm.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 159 | LblConfirm.Location = new Point(115, 208); 160 | LblConfirm.Name = "LblConfirm"; 161 | LblConfirm.Size = new Size(288, 19); 162 | LblConfirm.TabIndex = 80; 163 | LblConfirm.Text = "Are you sure you want to use this document?"; 164 | // 165 | // ConfirmDocument 166 | // 167 | AutoScaleDimensions = new SizeF(96F, 96F); 168 | AutoScaleMode = AutoScaleMode.Dpi; 169 | BackColor = Color.FromArgb(45, 45, 48); 170 | CaptionBarColor = Color.FromArgb(63, 63, 70); 171 | CaptionFont = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point); 172 | CaptionForeColor = Color.FromArgb(241, 241, 241); 173 | ClientSize = new Size(626, 330); 174 | Controls.Add(LblConfirm); 175 | Controls.Add(LblCostInfo); 176 | Controls.Add(LblDocument); 177 | Controls.Add(LblTxt); 178 | Controls.Add(PbCost); 179 | Controls.Add(BtnYes); 180 | Controls.Add(BtnNo); 181 | Controls.Add(LblIntro); 182 | DoubleBuffered = true; 183 | Font = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point); 184 | ForeColor = Color.FromArgb(241, 241, 241); 185 | FormBorderStyle = FormBorderStyle.FixedSingle; 186 | Icon = (Icon)resources.GetObject("$this.Icon"); 187 | MetroColor = Color.FromArgb(63, 63, 70); 188 | Name = "ConfirmDocument"; 189 | ShowMaximizeBox = false; 190 | ShowMinimizeBox = false; 191 | StartPosition = FormStartPosition.CenterScreen; 192 | Text = "Cost Confirmation"; 193 | Load += ConfirmDocument_Load; 194 | KeyUp += ConfirmDocument_KeyUp; 195 | ((System.ComponentModel.ISupportInitialize)PbCost).EndInit(); 196 | ResumeLayout(false); 197 | PerformLayout(); 198 | } 199 | 200 | #endregion 201 | private Syncfusion.WinForms.Controls.SfButton BtnNo; 202 | private Syncfusion.WinForms.Controls.SfButton BtnYes; 203 | private PictureBox PbCost; 204 | private Label LblIntro; 205 | private Label LblTxt; 206 | private Label LblDocument; 207 | private Label LblCostInfo; 208 | private Label LblConfirm; 209 | } 210 | } -------------------------------------------------------------------------------- /src/DeepLClient/Forms/Dialogs/ConfirmDocument.cs: -------------------------------------------------------------------------------- 1 | using DeepLClient.Managers; 2 | using Syncfusion.Windows.Forms; 3 | 4 | namespace DeepLClient.Forms.Dialogs 5 | { 6 | public partial class ConfirmDocument : MetroForm 7 | { 8 | private readonly long _charCount; 9 | private readonly string _chars; 10 | private readonly string _file; 11 | private readonly bool _isTxt; 12 | 13 | public ConfirmDocument(long chars, string file, bool isTxt = false) 14 | { 15 | _charCount = chars; 16 | _chars = $"{chars:N0}"; 17 | _file = file; 18 | _isTxt = isTxt; 19 | 20 | InitializeComponent(); 21 | } 22 | 23 | private void ConfirmDocument_Load(object sender, EventArgs e) 24 | { 25 | // set topmost 26 | TopMost = Variables.AppSettings.AlwaysOnTop; 27 | 28 | // catch all key presses 29 | KeyPreview = true; 30 | 31 | // set title 32 | Text = $"Cost Confirmation | {Variables.ApplicationName}"; 33 | 34 | // set content 35 | LblDocument.Text = _file; 36 | 37 | // inform the user of possible minimum cost 38 | var charInfo = _charCount > Variables.AppSettings.MinimumCharactersPerDocument 39 | ? $"It contains {_chars} characters." 40 | : $"It contains {_chars} characters. However, since it's a document, it'll cost the minimum of {Variables.AppSettings.MinimumCharactersPerDocument:N0} characters."; 41 | 42 | // show relevant variant 43 | LblCostInfo.Text = SubscriptionManager.UsingFreeSubscription() 44 | ? $"{charInfo}\r\n\r\nYou're on a free subscription, so no costs." 45 | : $"{charInfo}\r\n\r\nIt will probably cost around {SubscriptionManager.CalculateCostString(_charCount)}."; 46 | 47 | // optionally tip about small text files 48 | if (_isTxt && _charCount < Variables.AppSettings.MinimumCharactersPerDocument) LblTxt.Visible = true; 49 | 50 | // done 51 | ActiveControl = BtnYes; 52 | } 53 | 54 | private void BtnNo_Click(object sender, EventArgs e) => DialogResult = DialogResult.Cancel; 55 | 56 | private void BtnYes_Click(object sender, EventArgs e) => DialogResult = DialogResult.OK; 57 | 58 | private void ConfirmDocument_KeyUp(object sender, KeyEventArgs e) 59 | { 60 | if (e.KeyCode != Keys.Escape) return; 61 | DialogResult = DialogResult.Cancel; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/DeepLClient/Forms/Dialogs/LimitExceeded.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace DeepLClient.Forms.Dialogs 2 | { 3 | partial class LimitExceeded 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LimitExceeded)); 32 | BtnNo = new Syncfusion.WinForms.Controls.SfButton(); 33 | BtnYes = new Syncfusion.WinForms.Controls.SfButton(); 34 | PbCost = new PictureBox(); 35 | LblLoading = new Label(); 36 | LblIntro = new Label(); 37 | LblInfo = new Label(); 38 | ((System.ComponentModel.ISupportInitialize)PbCost).BeginInit(); 39 | SuspendLayout(); 40 | // 41 | // BtnNo 42 | // 43 | BtnNo.AccessibleDescription = ""; 44 | BtnNo.AccessibleName = ""; 45 | BtnNo.AccessibleRole = AccessibleRole.PushButton; 46 | BtnNo.BackColor = Color.FromArgb(63, 63, 70); 47 | BtnNo.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 48 | BtnNo.ForeColor = Color.FromArgb(241, 241, 241); 49 | BtnNo.ImageSize = new Size(16, 16); 50 | BtnNo.Location = new Point(115, 227); 51 | BtnNo.Name = "BtnNo"; 52 | BtnNo.Size = new Size(157, 31); 53 | BtnNo.Style.BackColor = Color.FromArgb(63, 63, 70); 54 | BtnNo.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); 55 | BtnNo.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); 56 | BtnNo.Style.ForeColor = Color.FromArgb(241, 241, 241); 57 | BtnNo.Style.HoverBackColor = Color.FromArgb(63, 63, 70); 58 | BtnNo.Style.HoverForeColor = Color.FromArgb(241, 241, 241); 59 | BtnNo.Style.Image = Properties.Resources.no_icon_16; 60 | BtnNo.Style.PressedForeColor = Color.Black; 61 | BtnNo.TabIndex = 1; 62 | BtnNo.UseVisualStyleBackColor = false; 63 | BtnNo.Click += BtnNo_Click; 64 | // 65 | // BtnYes 66 | // 67 | BtnYes.AccessibleDescription = ""; 68 | BtnYes.AccessibleName = ""; 69 | BtnYes.AccessibleRole = AccessibleRole.PushButton; 70 | BtnYes.BackColor = Color.FromArgb(63, 63, 70); 71 | BtnYes.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 72 | BtnYes.ForeColor = Color.FromArgb(241, 241, 241); 73 | BtnYes.ImageSize = new Size(24, 24); 74 | BtnYes.Location = new Point(278, 227); 75 | BtnYes.Name = "BtnYes"; 76 | BtnYes.Size = new Size(324, 31); 77 | BtnYes.Style.BackColor = Color.FromArgb(63, 63, 70); 78 | BtnYes.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); 79 | BtnYes.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); 80 | BtnYes.Style.ForeColor = Color.FromArgb(241, 241, 241); 81 | BtnYes.Style.HoverBackColor = Color.FromArgb(63, 63, 70); 82 | BtnYes.Style.HoverForeColor = Color.FromArgb(241, 241, 241); 83 | BtnYes.Style.Image = Properties.Resources.yes_icon_24; 84 | BtnYes.Style.PressedForeColor = Color.Black; 85 | BtnYes.TabIndex = 0; 86 | BtnYes.UseVisualStyleBackColor = false; 87 | BtnYes.Click += BtnYes_Click; 88 | // 89 | // PbCost 90 | // 91 | PbCost.Image = Properties.Resources.price_icon_64; 92 | PbCost.Location = new Point(22, 28); 93 | PbCost.Name = "PbCost"; 94 | PbCost.Size = new Size(64, 64); 95 | PbCost.SizeMode = PictureBoxSizeMode.AutoSize; 96 | PbCost.TabIndex = 6; 97 | PbCost.TabStop = false; 98 | // 99 | // LblLoading 100 | // 101 | LblLoading.AccessibleDescription = ""; 102 | LblLoading.AccessibleName = ""; 103 | LblLoading.AccessibleRole = AccessibleRole.StaticText; 104 | LblLoading.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 105 | LblLoading.Location = new Point(115, 28); 106 | LblLoading.Name = "LblLoading"; 107 | LblLoading.Size = new Size(487, 183); 108 | LblLoading.TabIndex = 71; 109 | LblLoading.Text = "Please hold while we're gathering info .."; 110 | // 111 | // LblIntro 112 | // 113 | LblIntro.AccessibleDescription = ""; 114 | LblIntro.AccessibleName = ""; 115 | LblIntro.AccessibleRole = AccessibleRole.StaticText; 116 | LblIntro.AutoSize = true; 117 | LblIntro.Font = new Font("Segoe UI", 9.75F, FontStyle.Bold, GraphicsUnit.Point); 118 | LblIntro.ForeColor = Color.FromArgb(255, 128, 128); 119 | LblIntro.Location = new Point(115, 28); 120 | LblIntro.Name = "LblIntro"; 121 | LblIntro.Size = new Size(0, 17); 122 | LblIntro.TabIndex = 72; 123 | LblIntro.Visible = false; 124 | // 125 | // LblInfo 126 | // 127 | LblInfo.AccessibleDescription = ""; 128 | LblInfo.AccessibleName = ""; 129 | LblInfo.AccessibleRole = AccessibleRole.StaticText; 130 | LblInfo.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 131 | LblInfo.Location = new Point(115, 71); 132 | LblInfo.Name = "LblInfo"; 133 | LblInfo.Size = new Size(487, 140); 134 | LblInfo.TabIndex = 73; 135 | LblInfo.Visible = false; 136 | // 137 | // LimitExceeded 138 | // 139 | AutoScaleDimensions = new SizeF(96F, 96F); 140 | AutoScaleMode = AutoScaleMode.Dpi; 141 | BackColor = Color.FromArgb(45, 45, 48); 142 | CaptionBarColor = Color.FromArgb(63, 63, 70); 143 | CaptionFont = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point); 144 | CaptionForeColor = Color.FromArgb(241, 241, 241); 145 | ClientSize = new Size(626, 266); 146 | Controls.Add(LblLoading); 147 | Controls.Add(LblInfo); 148 | Controls.Add(LblIntro); 149 | Controls.Add(PbCost); 150 | Controls.Add(BtnYes); 151 | Controls.Add(BtnNo); 152 | DoubleBuffered = true; 153 | Font = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point); 154 | ForeColor = Color.FromArgb(241, 241, 241); 155 | FormBorderStyle = FormBorderStyle.FixedSingle; 156 | Icon = (Icon)resources.GetObject("$this.Icon"); 157 | MetroColor = Color.FromArgb(63, 63, 70); 158 | Name = "LimitExceeded"; 159 | ShowMaximizeBox = false; 160 | ShowMinimizeBox = false; 161 | StartPosition = FormStartPosition.CenterScreen; 162 | Text = "Limit Warning"; 163 | Load += LimitExceeded_Load; 164 | KeyUp += LimitExceeded_KeyUp; 165 | ((System.ComponentModel.ISupportInitialize)PbCost).EndInit(); 166 | ResumeLayout(false); 167 | PerformLayout(); 168 | } 169 | 170 | #endregion 171 | private Syncfusion.WinForms.Controls.SfButton BtnNo; 172 | private Syncfusion.WinForms.Controls.SfButton BtnYes; 173 | private PictureBox PbCost; 174 | private Label LblLoading; 175 | private Label LblIntro; 176 | private Label LblInfo; 177 | } 178 | } -------------------------------------------------------------------------------- /src/DeepLClient/Forms/Dialogs/LimitExceeded.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DeepL.Model; 3 | using Syncfusion.Windows.Forms; 4 | 5 | namespace DeepLClient.Forms.Dialogs 6 | { 7 | public partial class LimitExceeded : MetroForm 8 | { 9 | private Usage _usage; 10 | private readonly long _charCount; 11 | private readonly string _chars; 12 | private readonly bool _isDocument; 13 | 14 | public LimitExceeded(long chars, bool isDocument = false) 15 | { 16 | _charCount = chars; 17 | _chars = $"{chars:N0}"; 18 | _isDocument = isDocument; 19 | 20 | InitializeComponent(); 21 | } 22 | 23 | private async void LimitExceeded_Load(object sender, EventArgs e) 24 | { 25 | // set topmost 26 | TopMost = Variables.AppSettings.AlwaysOnTop; 27 | 28 | // catch all key presses 29 | KeyPreview = true; 30 | 31 | // set title 32 | Text = $"Limit Warning | {Variables.ApplicationName}"; 33 | 34 | // get the current state 35 | _usage = await Variables.Translator.GetUsageAsync(); 36 | 37 | // set focus to yes 38 | ActiveControl = BtnYes; 39 | 40 | // check for limit 41 | if (_usage.AnyLimitReached) 42 | { 43 | SetLimitReached(); 44 | return; 45 | } 46 | 47 | // set limit info 48 | var charsLeft = _usage.Character!.Limit - _usage.Character!.Count; 49 | 50 | LblIntro.Text = "You'll probably reach your limit with this translation!"; 51 | 52 | var inf = new StringBuilder(); 53 | inf.AppendLine($"Your limit is set to {_usage.Character!.Limit:N0}. You've used {_usage.Character!.Count:N0}, so you have {charsLeft:N0} left."); 54 | inf.AppendLine(""); 55 | 56 | // inform the user of possible minimum cost 57 | if (_isDocument && _charCount < Variables.AppSettings.MinimumCharactersPerDocument) inf.AppendLine($"This translation contains {_chars} characters. However, since it's a document, it'll cost the minimum of {Variables.AppSettings.MinimumCharactersPerDocument:N0} characters."); 58 | else inf.AppendLine($"This translation will use {_chars} characters."); 59 | 60 | inf.AppendLine(""); 61 | inf.AppendLine("Do you still want to try to translate?"); 62 | 63 | LblInfo.Text = inf.ToString(); 64 | 65 | // show info 66 | LblLoading.Visible = false; 67 | LblIntro.Visible = true; 68 | LblInfo.Visible = true; 69 | } 70 | 71 | private void SetLimitReached() 72 | { 73 | LblIntro.Text = "DeepL has flagged your subscription's limit as reached!"; 74 | 75 | var inf = new StringBuilder(); 76 | inf.AppendLine($"Your limit is set to {_usage.Character!.Limit:N0}."); 77 | inf.AppendLine(""); 78 | inf.AppendLine(""); 79 | inf.AppendLine("Do you still want to try to translate?"); 80 | 81 | LblInfo.Text = inf.ToString(); 82 | 83 | // show info 84 | LblLoading.Visible = false; 85 | LblIntro.Visible = true; 86 | LblInfo.Visible = true; 87 | } 88 | 89 | private void BtnNo_Click(object sender, EventArgs e) => DialogResult = DialogResult.Cancel; 90 | 91 | private void BtnYes_Click(object sender, EventArgs e) => DialogResult = DialogResult.OK; 92 | 93 | private void LimitExceeded_KeyUp(object sender, KeyEventArgs e) 94 | { 95 | if (e.KeyCode != Keys.Escape) return; 96 | DialogResult = DialogResult.Cancel; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/DeepLClient/Forms/LoggingConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DeepL; 3 | using DeepLClient.Extensions; 4 | using DeepLClient.Functions; 5 | using DeepLClient.Managers; 6 | using Serilog; 7 | using Syncfusion.Windows.Forms; 8 | using WK.Libraries.HotkeyListenerNS; 9 | 10 | namespace DeepLClient.Forms 11 | { 12 | public partial class LoggingConfiguration : MetroForm 13 | { 14 | public LoggingConfiguration() 15 | { 16 | InitializeComponent(); 17 | } 18 | 19 | private void LoggingConfiguration_Load(object sender, EventArgs e) 20 | { 21 | try 22 | { 23 | // set topmost 24 | TopMost = Variables.AppSettings.AlwaysOnTop; 25 | 26 | // catch all key presses 27 | KeyPreview = true; 28 | 29 | // set title 30 | Text = $"Logging | {Variables.ApplicationName}"; 31 | 32 | // load settings 33 | TbUser.Text = Variables.AppSettings.User; 34 | } 35 | catch (Exception ex) 36 | { 37 | Log.Fatal(ex, "[CONFIGURATION-LOGGING] Error loading settings: {err}", ex.Message); 38 | MessageBoxAdv2.Show(this, "Something went wrong loading your settings.\r\n\r\nPlease manually review your config file and restart.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); 39 | Close(); 40 | } 41 | } 42 | 43 | private void BtnStore_Click(object sender, EventArgs e) 44 | { 45 | try 46 | { 47 | // get config 48 | var user = TbUser.Text.Trim(); 49 | var launchHidden = CbLaunchHidden.Checked; 50 | 51 | // check domain 52 | if (string.IsNullOrEmpty(user)) 53 | { 54 | MessageBoxAdv2.Show(this, "Please select the right domain.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); 55 | ActiveControl = TbUser; 56 | return; 57 | } 58 | 59 | // set the new configuration 60 | Variables.AppSettings.User = user; 61 | Variables.AppSettings.LaunchHidden = launchHidden; 62 | 63 | // store config 64 | var success = SettingsManager.StoreAppSettings(); 65 | if (!success) MessageBoxAdv2.Show(this, "Something went wrong saving your settings.\r\n\r\nPlease check the logs and retry.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); 66 | 67 | // done 68 | DialogResult = DialogResult.OK; 69 | } 70 | catch (Exception ex) 71 | { 72 | Log.Fatal(ex, "[CONFIGURATION-LOGGING] Error saving settings: {err}", ex.Message); 73 | MessageBoxAdv2.Show(this, "Something went wrong saving your settings.\r\n\r\nPlease check the logs and retry.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); 74 | } 75 | } 76 | 77 | private void BtnCancel_Click(object sender, EventArgs e) => DialogResult = DialogResult.Cancel; 78 | 79 | private void LoggingConfiguration_KeyUp(object sender, KeyEventArgs e) 80 | { 81 | if (e.KeyCode != Keys.Escape) return; 82 | Close(); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/DeepLClient/Forms/MailConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DeepL; 3 | using DeepLClient.Extensions; 4 | using DeepLClient.Functions; 5 | using DeepLClient.Managers; 6 | using Serilog; 7 | using Syncfusion.Windows.Forms; 8 | using WK.Libraries.HotkeyListenerNS; 9 | 10 | namespace DeepLClient.Forms 11 | { 12 | public partial class MailConfiguration : MetroForm 13 | { 14 | public MailConfiguration() 15 | { 16 | InitializeComponent(); 17 | } 18 | 19 | private void MailConfiguration_Load(object sender, EventArgs e) 20 | { 21 | try 22 | { 23 | // set topmost 24 | TopMost = Variables.AppSettings.AlwaysOnTop; 25 | 26 | // catch all key presses 27 | KeyPreview = true; 28 | 29 | // set title 30 | Text = $"E-mail | {Variables.ApplicationName}"; 31 | 32 | // load settings 33 | TbUser.Text = Variables.AppSettings.User; 34 | } 35 | catch (Exception ex) 36 | { 37 | Log.Fatal(ex, "[CONFIGURATION-EMAIL] Error loading settings: {err}", ex.Message); 38 | MessageBoxAdv2.Show(this, "Something went wrong loading your settings.\r\n\r\nPlease manually review your config file and restart.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); 39 | Close(); 40 | } 41 | } 42 | 43 | private void BtnStore_Click(object sender, EventArgs e) 44 | { 45 | try 46 | { 47 | // get config 48 | var user = TbUser.Text.Trim(); 49 | var launchHidden = CbLaunchHidden.Checked; 50 | 51 | // check domain 52 | if (string.IsNullOrEmpty(user)) 53 | { 54 | MessageBoxAdv2.Show(this, "Please select the right domain.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); 55 | ActiveControl = TbUser; 56 | return; 57 | } 58 | 59 | // set the new configuration 60 | Variables.AppSettings.User = user; 61 | Variables.AppSettings.LaunchHidden = launchHidden; 62 | 63 | // store config 64 | var success = SettingsManager.StoreAppSettings(); 65 | if (!success) MessageBoxAdv2.Show(this, "Something went wrong saving your settings.\r\n\r\nPlease check the logs and retry.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); 66 | 67 | // done 68 | DialogResult = DialogResult.OK; 69 | } 70 | catch (Exception ex) 71 | { 72 | Log.Fatal(ex, "[CONFIGURATION-EMAIL] Error saving settings: {err}", ex.Message); 73 | MessageBoxAdv2.Show(this, "Something went wrong saving your settings.\r\n\r\nPlease check the logs and retry.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); 74 | } 75 | } 76 | 77 | private void BtnCancel_Click(object sender, EventArgs e) => DialogResult = DialogResult.Cancel; 78 | 79 | private void MailConfiguration_KeyUp(object sender, KeyEventArgs e) 80 | { 81 | if (e.KeyCode != Keys.Escape) return; 82 | Close(); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/DeepLClient/Forms/Main.cs: -------------------------------------------------------------------------------- 1 | using DeepLClient.Functions; 2 | using DeepLClient.Managers; 3 | using Serilog; 4 | using Syncfusion.Windows.Forms; 5 | using System.Diagnostics.CodeAnalysis; 6 | using WK.Libraries.HotkeyListenerNS; 7 | 8 | namespace DeepLClient.Forms 9 | { 10 | [SuppressMessage("ReSharper", "EmptyGeneralCatchClause")] 11 | public partial class Main : MetroForm 12 | { 13 | public Main() 14 | { 15 | InitializeComponent(); 16 | } 17 | 18 | private async void Main_Load(object sender, EventArgs e) 19 | { 20 | try 21 | { 22 | // set title 23 | Text = $"{Variables.ApplicationName} | LAB02 Research | {Variables.Version}"; 24 | 25 | // set systray icon text 26 | NotifyIcon.Text = $"{Variables.ApplicationName} | LAB02 Research"; 27 | 28 | // catch all key presses 29 | KeyPreview = true; 30 | 31 | // check for updates 32 | _ = Task.Run(UpdateManager.Initialize); 33 | 34 | // initialize ourselves 35 | await Variables.MainFormManager.Initialize(); 36 | 37 | // monitor subscription limits 38 | _ = Task.Run(SubscriptionManager.Initialize); 39 | 40 | // process translation events logging 41 | _ = Task.Run(TranslationEventsManager.Initialize); 42 | 43 | // initialize the global hotkey 44 | InitializeHotkey(); 45 | 46 | #if DEBUG 47 | // always show when debugging 48 | await Task.Delay(250); 49 | Variables.MainFormManager.ShowMain(false); 50 | #endif 51 | 52 | // set topmost 53 | TopMost = Variables.AppSettings.AlwaysOnTop; 54 | } 55 | catch (Exception ex) 56 | { 57 | Log.Fatal(ex, "[MAIN] Error on Main_Load: {err}", ex.Message); 58 | MessageBoxAdv2.Show(this, "Something went wrong while launching the app." + 59 | "\r\n\r\nPlease make sure everything's configured correctly, and your internet connection is working." + 60 | "\r\n\r\nIf the problem persists, check your logs and contact the developer.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); 61 | 62 | 63 | // we're done for 64 | ProcessManager.Shutdown(); 65 | } 66 | } 67 | 68 | /// 69 | /// Initialises and binds the hotkey 70 | /// Strange things happen when we put this in a seperate class .. 71 | /// 72 | private void InitializeHotkey() 73 | { 74 | try 75 | { 76 | // create a hotkey listener 77 | Variables.HotkeyListener = new HotkeyListener(); 78 | 79 | // prepare listener 80 | Variables.HotkeyListener.HotkeyPressed += HotkeyListener_HotkeyPressed; 81 | 82 | // initialise the manager 83 | Variables.HotkeyManager.Initialize(); 84 | } 85 | catch (Exception ex) 86 | { 87 | Log.Fatal(ex, "[MAIN] Error setting up the hotkey: {err}", ex.Message); 88 | } 89 | } 90 | 91 | /// 92 | /// Fires when a registered hotkey is pressed 93 | /// 94 | /// 95 | /// 96 | private void HotkeyListener_HotkeyPressed(object sender, HotkeyEventArgs e) 97 | { 98 | HotkeyManager.ProcessHotkey(e); 99 | } 100 | 101 | private void BtnConfig_Click(object sender, EventArgs e) 102 | { 103 | // show configuration dialog 104 | using var config = new Configuration(); 105 | var result = config.ShowDialog(this); 106 | if (result != DialogResult.OK) return; 107 | 108 | // set topmost 109 | TopMost = Variables.AppSettings.AlwaysOnTop; 110 | 111 | // reset deepl? 112 | if (!config.ResetDeepL) return; 113 | 114 | // yep 115 | _ = Variables.MainFormManager.Initialize(); 116 | } 117 | 118 | private void BtnSubscriptionInfo_Click(object sender, EventArgs e) 119 | { 120 | // show subscription info dialog 121 | using var subscriptionInfo = new SubscriptionInfo(); 122 | subscriptionInfo.ShowDialog(this); 123 | } 124 | 125 | private void LblWarning_Click(object sender, EventArgs e) 126 | { 127 | // show subscription info dialog 128 | using var subscriptionInfo = new SubscriptionInfo(); 129 | subscriptionInfo.ShowDialog(this); 130 | } 131 | 132 | private void Main_FormClosing(object sender, FormClosingEventArgs e) 133 | { 134 | // if we're already closing, nevermind 135 | if (Variables.ShuttingDown) 136 | { 137 | e.Cancel = true; 138 | return; 139 | } 140 | 141 | // how bad do they want it? 142 | if (e.CloseReason is CloseReason.TaskManagerClosing or CloseReason.WindowsShutDown) 143 | { 144 | e.Cancel = true; 145 | ProcessManager.Shutdown(); 146 | return; 147 | } 148 | 149 | // just hide 150 | Invoke(new MethodInvoker(Hide)); 151 | 152 | // cancel closing 153 | e.Cancel = true; 154 | 155 | // set to text (if possible) 156 | Variables.MainFormManager.ActivateText(); 157 | } 158 | 159 | /// 160 | /// Shows the application by clicking the tray icon 161 | /// 162 | /// 163 | /// 164 | private void NotifyIcon_MouseClick(object sender, MouseEventArgs e) 165 | { 166 | if (e.Button == MouseButtons.Right) return; 167 | Variables.MainFormManager.ShowMain(false); 168 | } 169 | 170 | /// 171 | /// Hides or closes the application 172 | /// 173 | /// 174 | /// 175 | private void BtnHide_Click(object sender, EventArgs e) 176 | { 177 | // shutdown if ctrl is pressed 178 | if (ModifierKeys.HasFlag(Keys.Control)) 179 | { 180 | Variables.MainFormManager.Shutdown(); 181 | return; 182 | } 183 | 184 | // hide 185 | Hide(); 186 | 187 | // set to text (if possible) 188 | Variables.MainFormManager.ActivateText(); 189 | } 190 | 191 | /// 192 | /// Hides and resets when esc is pressed 193 | /// 194 | /// 195 | /// 196 | private void Main_KeyUp(object sender, KeyEventArgs e) 197 | { 198 | // hide on esc 199 | if (e.KeyCode != Keys.Escape) return; 200 | Hide(); 201 | 202 | // set to text (if possible) 203 | Variables.MainFormManager.ActivateText(); 204 | } 205 | 206 | /// 207 | /// Shows the 'about' form 208 | /// 209 | private void ShowAbout() 210 | { 211 | using var about = new About(); 212 | about.ShowDialog(this); 213 | } 214 | 215 | private void BtnAbout_Click(object sender, EventArgs e) => ShowAbout(); 216 | 217 | private void TsAbout_Click(object sender, EventArgs e) => ShowAbout(); 218 | 219 | private void NotifyIcon_DoubleClick(object sender, EventArgs e) => Variables.MainFormManager.ShowMain(false); 220 | 221 | private void TsExit_Click(object sender, EventArgs e) => Variables.MainFormManager.Shutdown(); 222 | 223 | private void TsShow_Click(object sender, EventArgs e) => Variables.MainFormManager.ShowMain(false); 224 | } 225 | } -------------------------------------------------------------------------------- /src/DeepLClient/Forms/SubscriptionInfo.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace DeepLClient.Forms 2 | { 3 | partial class SubscriptionInfo 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | components = new System.ComponentModel.Container(); 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SubscriptionInfo)); 33 | BtnYes = new Syncfusion.WinForms.Controls.SfButton(); 34 | PbCost = new PictureBox(); 35 | LblSourceLanguage = new Label(); 36 | label1 = new Label(); 37 | label2 = new Label(); 38 | LblLimitReached = new Label(); 39 | LblCharacterLimit = new Label(); 40 | LblCharactersUsed = new Label(); 41 | LblCharactersLeft = new Label(); 42 | LblCost = new Label(); 43 | label4 = new Label(); 44 | LblSubscriptionPage = new Label(); 45 | ToolTip = new ToolTip(components); 46 | ((System.ComponentModel.ISupportInitialize)PbCost).BeginInit(); 47 | SuspendLayout(); 48 | // 49 | // BtnYes 50 | // 51 | BtnYes.AccessibleDescription = ""; 52 | BtnYes.AccessibleName = ""; 53 | BtnYes.AccessibleRole = AccessibleRole.PushButton; 54 | BtnYes.BackColor = Color.FromArgb(63, 63, 70); 55 | BtnYes.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 56 | BtnYes.ForeColor = Color.FromArgb(241, 241, 241); 57 | BtnYes.ImageSize = new Size(24, 24); 58 | BtnYes.Location = new Point(115, 193); 59 | BtnYes.Name = "BtnYes"; 60 | BtnYes.Size = new Size(501, 31); 61 | BtnYes.Style.BackColor = Color.FromArgb(63, 63, 70); 62 | BtnYes.Style.FocusedBackColor = Color.FromArgb(63, 63, 70); 63 | BtnYes.Style.FocusedForeColor = Color.FromArgb(241, 241, 241); 64 | BtnYes.Style.ForeColor = Color.FromArgb(241, 241, 241); 65 | BtnYes.Style.HoverBackColor = Color.FromArgb(63, 63, 70); 66 | BtnYes.Style.HoverForeColor = Color.FromArgb(241, 241, 241); 67 | BtnYes.Style.Image = Properties.Resources.yes_icon_24; 68 | BtnYes.Style.PressedForeColor = Color.Black; 69 | BtnYes.TabIndex = 0; 70 | BtnYes.UseVisualStyleBackColor = false; 71 | BtnYes.Click += BtnYes_Click; 72 | // 73 | // PbCost 74 | // 75 | PbCost.Image = Properties.Resources.price_icon_64; 76 | PbCost.Location = new Point(22, 28); 77 | PbCost.Name = "PbCost"; 78 | PbCost.Size = new Size(64, 64); 79 | PbCost.SizeMode = PictureBoxSizeMode.AutoSize; 80 | PbCost.TabIndex = 6; 81 | PbCost.TabStop = false; 82 | // 83 | // LblSourceLanguage 84 | // 85 | LblSourceLanguage.AccessibleDescription = ""; 86 | LblSourceLanguage.AccessibleName = ""; 87 | LblSourceLanguage.AccessibleRole = AccessibleRole.StaticText; 88 | LblSourceLanguage.AutoSize = true; 89 | LblSourceLanguage.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 90 | LblSourceLanguage.Location = new Point(115, 28); 91 | LblSourceLanguage.Name = "LblSourceLanguage"; 92 | LblSourceLanguage.Size = new Size(153, 19); 93 | LblSourceLanguage.TabIndex = 67; 94 | LblSourceLanguage.Text = "character monthly limit:"; 95 | // 96 | // label1 97 | // 98 | label1.AccessibleDescription = ""; 99 | label1.AccessibleName = ""; 100 | label1.AccessibleRole = AccessibleRole.StaticText; 101 | label1.AutoSize = true; 102 | label1.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 103 | label1.Location = new Point(403, 28); 104 | label1.Name = "label1"; 105 | label1.Size = new Size(107, 19); 106 | label1.TabIndex = 68; 107 | label1.Text = "characters used:"; 108 | // 109 | // label2 110 | // 111 | label2.AccessibleDescription = ""; 112 | label2.AccessibleName = ""; 113 | label2.AccessibleRole = AccessibleRole.StaticText; 114 | label2.AutoSize = true; 115 | label2.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 116 | label2.Location = new Point(115, 73); 117 | label2.Name = "label2"; 118 | label2.Size = new Size(97, 19); 119 | label2.TabIndex = 69; 120 | label2.Text = "characters left:"; 121 | // 122 | // LblLimitReached 123 | // 124 | LblLimitReached.AccessibleDescription = ""; 125 | LblLimitReached.AccessibleName = ""; 126 | LblLimitReached.AccessibleRole = AccessibleRole.StaticText; 127 | LblLimitReached.AutoSize = true; 128 | LblLimitReached.Font = new Font("Segoe UI", 9.75F, FontStyle.Bold, GraphicsUnit.Point); 129 | LblLimitReached.ForeColor = Color.FromArgb(255, 128, 128); 130 | LblLimitReached.Location = new Point(115, 121); 131 | LblLimitReached.Name = "LblLimitReached"; 132 | LblLimitReached.Size = new Size(0, 17); 133 | LblLimitReached.TabIndex = 70; 134 | // 135 | // LblCharacterLimit 136 | // 137 | LblCharacterLimit.AccessibleDescription = ""; 138 | LblCharacterLimit.AccessibleName = ""; 139 | LblCharacterLimit.AccessibleRole = AccessibleRole.StaticText; 140 | LblCharacterLimit.AutoSize = true; 141 | LblCharacterLimit.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 142 | LblCharacterLimit.Location = new Point(311, 28); 143 | LblCharacterLimit.Name = "LblCharacterLimit"; 144 | LblCharacterLimit.Size = new Size(17, 19); 145 | LblCharacterLimit.TabIndex = 71; 146 | LblCharacterLimit.Text = "0"; 147 | // 148 | // LblCharactersUsed 149 | // 150 | LblCharactersUsed.AccessibleDescription = ""; 151 | LblCharactersUsed.AccessibleName = ""; 152 | LblCharactersUsed.AccessibleRole = AccessibleRole.StaticText; 153 | LblCharactersUsed.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 154 | LblCharactersUsed.Location = new Point(519, 28); 155 | LblCharactersUsed.Name = "LblCharactersUsed"; 156 | LblCharactersUsed.Size = new Size(97, 19); 157 | LblCharactersUsed.TabIndex = 72; 158 | LblCharactersUsed.Text = "0"; 159 | LblCharactersUsed.TextAlign = ContentAlignment.TopRight; 160 | // 161 | // LblCharactersLeft 162 | // 163 | LblCharactersLeft.AccessibleDescription = ""; 164 | LblCharactersLeft.AccessibleName = ""; 165 | LblCharactersLeft.AccessibleRole = AccessibleRole.StaticText; 166 | LblCharactersLeft.AutoSize = true; 167 | LblCharactersLeft.Font = new Font("Segoe UI", 9.75F, FontStyle.Bold, GraphicsUnit.Point); 168 | LblCharactersLeft.Location = new Point(312, 74); 169 | LblCharactersLeft.Name = "LblCharactersLeft"; 170 | LblCharactersLeft.Size = new Size(15, 17); 171 | LblCharactersLeft.TabIndex = 73; 172 | LblCharactersLeft.Text = "0"; 173 | // 174 | // LblCost 175 | // 176 | LblCost.AccessibleDescription = ""; 177 | LblCost.AccessibleName = ""; 178 | LblCost.AccessibleRole = AccessibleRole.StaticText; 179 | LblCost.Font = new Font("Segoe UI", 9.75F, FontStyle.Bold, GraphicsUnit.Point); 180 | LblCost.Location = new Point(519, 74); 181 | LblCost.Name = "LblCost"; 182 | LblCost.Size = new Size(97, 19); 183 | LblCost.TabIndex = 75; 184 | LblCost.Text = "€ 0,00"; 185 | LblCost.TextAlign = ContentAlignment.TopRight; 186 | // 187 | // label4 188 | // 189 | label4.AccessibleDescription = ""; 190 | label4.AccessibleName = ""; 191 | label4.AccessibleRole = AccessibleRole.StaticText; 192 | label4.AutoSize = true; 193 | label4.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point); 194 | label4.Location = new Point(403, 73); 195 | label4.Name = "label4"; 196 | label4.Size = new Size(101, 19); 197 | label4.TabIndex = 74; 198 | label4.Text = "estimated cost:"; 199 | // 200 | // LblSubscriptionPage 201 | // 202 | LblSubscriptionPage.AccessibleDescription = "Created by link. Opens the LAB02 Research webpage."; 203 | LblSubscriptionPage.AccessibleName = "Created by"; 204 | LblSubscriptionPage.AccessibleRole = AccessibleRole.Link; 205 | LblSubscriptionPage.AutoSize = true; 206 | LblSubscriptionPage.Cursor = Cursors.Hand; 207 | LblSubscriptionPage.Font = new Font("Segoe UI", 10F, FontStyle.Underline, GraphicsUnit.Point); 208 | LblSubscriptionPage.Location = new Point(115, 160); 209 | LblSubscriptionPage.Name = "LblSubscriptionPage"; 210 | LblSubscriptionPage.Size = new Size(177, 19); 211 | LblSubscriptionPage.TabIndex = 76; 212 | LblSubscriptionPage.Text = "open subscription webpage"; 213 | LblSubscriptionPage.Click += LblSubscriptionPage_Click; 214 | // 215 | // ToolTip 216 | // 217 | ToolTip.AutoPopDelay = 5000; 218 | ToolTip.BackColor = Color.FromArgb(241, 241, 241); 219 | ToolTip.ForeColor = Color.FromArgb(45, 45, 48); 220 | ToolTip.InitialDelay = 1000; 221 | ToolTip.ReshowDelay = 100; 222 | // 223 | // SubscriptionInfo 224 | // 225 | AutoScaleDimensions = new SizeF(96F, 96F); 226 | AutoScaleMode = AutoScaleMode.Dpi; 227 | BackColor = Color.FromArgb(45, 45, 48); 228 | CaptionBarColor = Color.FromArgb(63, 63, 70); 229 | CaptionFont = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point); 230 | CaptionForeColor = Color.FromArgb(241, 241, 241); 231 | ClientSize = new Size(627, 231); 232 | Controls.Add(LblSubscriptionPage); 233 | Controls.Add(LblCost); 234 | Controls.Add(label4); 235 | Controls.Add(LblCharactersLeft); 236 | Controls.Add(LblCharactersUsed); 237 | Controls.Add(LblCharacterLimit); 238 | Controls.Add(LblLimitReached); 239 | Controls.Add(label2); 240 | Controls.Add(label1); 241 | Controls.Add(LblSourceLanguage); 242 | Controls.Add(PbCost); 243 | Controls.Add(BtnYes); 244 | DoubleBuffered = true; 245 | Font = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point); 246 | ForeColor = Color.FromArgb(241, 241, 241); 247 | FormBorderStyle = FormBorderStyle.FixedSingle; 248 | Icon = (Icon)resources.GetObject("$this.Icon"); 249 | MetroColor = Color.FromArgb(63, 63, 70); 250 | Name = "SubscriptionInfo"; 251 | ShowMaximizeBox = false; 252 | ShowMinimizeBox = false; 253 | StartPosition = FormStartPosition.CenterScreen; 254 | Text = "Subscription Information"; 255 | Load += SubscriptionInfo_Load; 256 | KeyUp += SubscriptionInfo_KeyUp; 257 | ((System.ComponentModel.ISupportInitialize)PbCost).EndInit(); 258 | ResumeLayout(false); 259 | PerformLayout(); 260 | } 261 | 262 | #endregion 263 | private Syncfusion.WinForms.Controls.SfButton BtnYes; 264 | private PictureBox PbCost; 265 | private Label LblSourceLanguage; 266 | private Label label1; 267 | private Label label2; 268 | private Label LblLimitReached; 269 | private Label LblCharacterLimit; 270 | private Label LblCharactersUsed; 271 | private Label LblCharactersLeft; 272 | private Label LblCost; 273 | private Label label4; 274 | private Label LblSubscriptionPage; 275 | private ToolTip ToolTip; 276 | } 277 | } -------------------------------------------------------------------------------- /src/DeepLClient/Forms/SubscriptionInfo.cs: -------------------------------------------------------------------------------- 1 | using DeepL; 2 | using DeepLClient.Functions; 3 | using DeepLClient.Managers; 4 | using Serilog; 5 | using Syncfusion.Windows.Forms; 6 | 7 | namespace DeepLClient.Forms 8 | { 9 | public partial class SubscriptionInfo : MetroForm 10 | { 11 | public SubscriptionInfo() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | private async void SubscriptionInfo_Load(object sender, EventArgs e) 17 | { 18 | try 19 | { 20 | // set topmost 21 | TopMost = Variables.AppSettings.AlwaysOnTop; 22 | 23 | // catch all key presses 24 | KeyPreview = true; 25 | 26 | // set title 27 | Text = $"Subscription Information | {Variables.ApplicationName}"; 28 | 29 | // set cost info 30 | LblCost.Text = SubscriptionManager.BaseCostNotation(); 31 | 32 | // get the current state 33 | var state = await Variables.Translator.GetUsageAsync(); 34 | 35 | if (state.Character == null) 36 | { 37 | // unknown, weird 38 | LblCharacterLimit.Text = "?"; 39 | LblCharactersUsed.Text = "?"; 40 | LblCharactersLeft.Text = "?"; 41 | LblCost.Text = "?"; 42 | 43 | // we're done 44 | return; 45 | } 46 | 47 | // set characters used 48 | LblCharactersUsed.Text = $"{state.Character.Count:N0}"; 49 | 50 | // determine limit and how many chars are left, only on free 51 | if (SubscriptionManager.UsingFreeSubscription()) 52 | { 53 | // get our limit 54 | LblCharacterLimit.Text = $"{state.Character.Limit:N0}"; 55 | 56 | // get our remaining chars 57 | var charactersLeft = state.Character.Limit - state.Character.Count; 58 | if (charactersLeft < 0) charactersLeft = 0; 59 | LblCharactersLeft.Text = $"{charactersLeft:N0}"; 60 | 61 | // warn the user if we're getting close to the limit 62 | if (charactersLeft < 10000) LblCharactersLeft.ForeColor = Color.FromArgb(255, 128, 128); 63 | 64 | // no cost :D 65 | LblCost.Text = "FREE"; 66 | 67 | // check for reached limit 68 | if (state.Character.LimitReached || charactersLeft == 0) LblLimitReached.Text = "you've reached the monthly limit, and are unable to translate until next month!"; 69 | } 70 | else 71 | { 72 | // infinity 73 | LblCharacterLimit.Text = "∞"; 74 | LblCharactersLeft.Text = "∞"; 75 | 76 | // not for free though 77 | LblCost.Text = SubscriptionManager.CalculateCostString(state.Character.Count, false); 78 | } 79 | 80 | // done 81 | ActiveControl = BtnYes; 82 | } 83 | catch (ConnectionException ex) 84 | { 85 | if (IsDisposed) return; 86 | Log.Fatal(ex, "[SUBSCRIPTION] Unable to establish a connection to DeepL's servers: {err}", ex.Message); 87 | MessageBoxAdv2.Show(this, "Unable to establish a connection to DeepL's servers.\r\n\r\nPlease try again later.", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); 88 | DialogResult = DialogResult.Cancel; 89 | } 90 | catch (Exception ex) 91 | { 92 | if (IsDisposed) return; 93 | Log.Fatal(ex, "[SUBSCRIPTION] Something went wrong: {err}", ex.Message); 94 | MessageBoxAdv2.Show(this, $"Something went wrong:\r\n\r\n{ex.Message}", Variables.MessageBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); 95 | DialogResult = DialogResult.Cancel; 96 | } 97 | } 98 | 99 | private void SubscriptionInfo_KeyUp(object sender, KeyEventArgs e) 100 | { 101 | if (e.KeyCode != Keys.Escape) return; 102 | DialogResult = DialogResult.Cancel; 103 | } 104 | 105 | private void BtnYes_Click(object sender, EventArgs e) => DialogResult = DialogResult.OK; 106 | 107 | private void LblSubscriptionPage_Click(object sender, EventArgs e) => HelperFunctions.LaunchUrl("https://www.deepl.com/account/plan"); 108 | } 109 | } -------------------------------------------------------------------------------- /src/DeepLClient/Functions/ComboBoxTheme.cs: -------------------------------------------------------------------------------- 1 | namespace DeepLClient.Functions 2 | { 3 | /// 4 | /// Makes sure our combobox has the right colors 5 | /// Source: https://stackoverflow.com/a/60421006 6 | /// Source: https://stackoverflow.com/a/11650321 7 | /// 8 | internal static class ComboBoxTheme 9 | { 10 | /// 11 | /// Converts the items directly to strings 12 | /// 13 | /// 14 | /// 15 | internal static void DrawItem(object sender, DrawItemEventArgs e) 16 | { 17 | // only relevant for listviews in detail mode 18 | if (sender is not ComboBox comboBox) return; 19 | 20 | // only if there are items 21 | if (comboBox.Items.Count <= 0) 22 | { 23 | // limit the dropdown's height 24 | comboBox.DropDownHeight = 20; 25 | return; 26 | } 27 | 28 | // reset the dropdown's height 29 | comboBox.DropDownHeight = 300; 30 | 31 | // fetch the index 32 | var index = e.Index >= 0 ? e.Index : 0; 33 | 34 | // draw the control's background 35 | e.DrawBackground(); 36 | 37 | // check if we have an item to process 38 | if (index != -1) 39 | { 40 | // optionally set the item's background color as selected 41 | if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) 42 | { 43 | e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(241, 241, 241)), e.Bounds); 44 | } 45 | 46 | // draw the string 47 | var brush = (e.State & DrawItemState.Selected) > 0 ? new SolidBrush(Color.FromArgb(63, 63, 70)) : new SolidBrush(comboBox.ForeColor); 48 | e.Graphics.DrawString(comboBox.Items[index].ToString(), Variables.DefaultFont, brush, e.Bounds, StringFormat.GenericDefault); 49 | } 50 | 51 | // draw focus rectangle 52 | e.DrawFocusRectangle(); 53 | } 54 | 55 | /// 56 | /// Converts the items to KeyValuePair[int, string] 57 | /// 58 | /// 59 | /// 60 | internal static void DrawDictionaryIntStringItem(object sender, DrawItemEventArgs e) 61 | { 62 | // only relevant for listviews in detail mode 63 | if (sender is not ComboBox comboBox) return; 64 | 65 | // only if there are items 66 | if (comboBox.Items.Count <= 0) return; 67 | 68 | // fetch the index 69 | var index = e.Index >= 0 ? e.Index : 0; 70 | 71 | // draw the control's background 72 | e.DrawBackground(); 73 | 74 | // check if we have an item to process 75 | if (index != -1) 76 | { 77 | // optionally set the item's background color as selected 78 | if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) 79 | { 80 | e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(241, 241, 241)), e.Bounds); 81 | } 82 | 83 | // get the value 84 | var value = (KeyValuePair)comboBox.Items[index]; 85 | 86 | // draw the string 87 | var brush = (e.State & DrawItemState.Selected) > 0 ? new SolidBrush(Color.FromArgb(63, 63, 70)) : new SolidBrush(comboBox.ForeColor); 88 | e.Graphics.DrawString(value.Value, Variables.DefaultFont, brush, e.Bounds, StringFormat.GenericDefault); 89 | } 90 | 91 | // draw focus rectangle 92 | e.DrawFocusRectangle(); 93 | } 94 | 95 | /// 96 | /// Converts the items to KeyValuePair[string, string] 97 | /// 98 | /// 99 | /// 100 | internal static void DrawDictionaryStringStringItem(object sender, DrawItemEventArgs e) 101 | { 102 | // only relevant for listviews in detail mode 103 | if (sender is not ComboBox comboBox) return; 104 | 105 | // only if there are items 106 | if (comboBox.Items.Count <= 0) return; 107 | 108 | // fetch the index 109 | var index = e.Index >= 0 ? e.Index : 0; 110 | 111 | // draw the control's background 112 | e.DrawBackground(); 113 | 114 | // check if we have an item to process 115 | if (index != -1) 116 | { 117 | // optionally set the item's background color as selected 118 | if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) 119 | { 120 | e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(241, 241, 241)), e.Bounds); 121 | } 122 | 123 | // get the value 124 | var value = (KeyValuePair)comboBox.Items[index]; 125 | 126 | // draw the string 127 | var brush = (e.State & DrawItemState.Selected) > 0 ? new SolidBrush(Color.FromArgb(63, 63, 70)) : new SolidBrush(comboBox.ForeColor); 128 | e.Graphics.DrawString(value.Value, Variables.DefaultFont, brush, e.Bounds, StringFormat.GenericDefault); 129 | } 130 | 131 | // draw focus rectangle 132 | e.DrawFocusRectangle(); 133 | } 134 | 135 | /// 136 | /// Converts the items to KeyValuePair[string, string] and draws the key value 137 | /// 138 | /// 139 | /// 140 | internal static void DrawDictionaryStringStringItemUsingKey(object sender, DrawItemEventArgs e) 141 | { 142 | // only relevant for listviews in detail mode 143 | if (sender is not ComboBox comboBox) return; 144 | 145 | // only if there are items 146 | if (comboBox.Items.Count <= 0) return; 147 | 148 | // fetch the index 149 | var index = e.Index >= 0 ? e.Index : 0; 150 | 151 | // draw the control's background 152 | e.DrawBackground(); 153 | 154 | // check if we have an item to process 155 | if (index != -1) 156 | { 157 | // optionally set the item's background color as selected 158 | if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) 159 | { 160 | e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(241, 241, 241)), e.Bounds); 161 | } 162 | 163 | // get the value 164 | var value = (KeyValuePair)comboBox.Items[index]; 165 | 166 | // draw the string 167 | var brush = (e.State & DrawItemState.Selected) > 0 ? new SolidBrush(Color.FromArgb(63, 63, 70)) : new SolidBrush(comboBox.ForeColor); 168 | e.Graphics.DrawString(value.Key, Variables.DefaultFont, brush, e.Bounds, StringFormat.GenericDefault); 169 | } 170 | 171 | // draw focus rectangle 172 | e.DrawFocusRectangle(); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/DeepLClient/Functions/CustomApplicationContext.cs: -------------------------------------------------------------------------------- 1 | namespace DeepLClient.Functions 2 | { 3 | /// 4 | /// Makes sure our form is completely hidden on startup 5 | /// Source: https://www.mking.net/blog/setting-a-winforms-form-to-be-hidden-on-startup 6 | /// 7 | public class CustomApplicationContext : ApplicationContext 8 | { 9 | private Form _mainForm; 10 | 11 | public CustomApplicationContext(Form mainForm) 12 | { 13 | _mainForm = mainForm; 14 | if (_mainForm == null) return; 15 | 16 | // Wire up the destroy events similar to how the base ApplicationContext 17 | // does things when a form is provided. 18 | _mainForm.HandleDestroyed += OnFormDestroy; 19 | 20 | // We still want to call Show() here, but we can at least hide it from the user 21 | // by setting Opacity to 0 while the form is being shown for the first time. 22 | _mainForm.Opacity = 0; 23 | _mainForm.Show(); 24 | _mainForm.Hide(); 25 | _mainForm.Opacity = 1; 26 | } 27 | 28 | /// 29 | /// Handles the event. 30 | /// 31 | /// The source of the event. 32 | /// An that contains the event data. 33 | private void OnFormDestroy(object sender, EventArgs e) 34 | { 35 | if (sender is not Form form || form.RecreatingHandle) return; 36 | 37 | form.HandleDestroyed -= OnFormDestroy; 38 | OnMainFormClosed(sender, e); 39 | } 40 | 41 | /// 42 | /// Performs application-defined tasks associated with freeing, releasing, 43 | /// or resetting unmanaged resources. 44 | /// 45 | /// 46 | /// true if invoked from the method; 47 | /// false if invoked from the finalizer. 48 | /// 49 | protected override void Dispose(bool disposing) 50 | { 51 | if (disposing) 52 | { 53 | if (_mainForm != null) 54 | { 55 | if (!_mainForm.IsDisposed) _mainForm.Dispose(); 56 | _mainForm = null; 57 | } 58 | } 59 | 60 | base.Dispose(disposing); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/DeepLClient/Functions/HelperFunctions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | using Syncfusion.Windows.Forms; 5 | using static DeepLClient.Functions.NativeFunctions; 6 | 7 | namespace DeepLClient.Functions 8 | { 9 | internal static class HelperFunctions 10 | { 11 | /// 12 | /// Initializes Syncfusion's messagebox style 13 | /// Todo: incomplete, button color etc need to be done 14 | /// 15 | internal static void SetMsgBoxStyle(Font font) 16 | { 17 | var style = new MetroStyleColorTable 18 | { 19 | BackColor = Color.FromArgb(45, 45, 48), 20 | BorderColor = Color.FromArgb(115, 115, 115), 21 | CaptionBarColor = Color.FromArgb(63, 63, 70), 22 | CaptionForeColor = Color.FromArgb(241, 241, 241), 23 | ForeColor = Color.FromArgb(241, 241, 241) 24 | }; 25 | 26 | MessageBoxAdv.ApplyAeroTheme = false; 27 | MessageBoxAdv.MetroColorTable = style; 28 | MessageBoxAdv.MessageBoxStyle = MessageBoxAdv.Style.Metro; 29 | 30 | MessageBoxAdv.CaptionFont = font; 31 | MessageBoxAdv.ButtonFont = font; 32 | MessageBoxAdv.DetailsFont = font; 33 | MessageBoxAdv.MessageFont = font; 34 | } 35 | 36 | /// 37 | /// Returns an info text explaining the use of formality. 38 | /// 39 | /// 40 | internal static string GetFormalityExplanation() 41 | { 42 | var info = new StringBuilder(); 43 | 44 | // set generic info 45 | info.AppendLine("By changing the formality, you can change the tone of the translation."); 46 | info.AppendLine(""); 47 | info.AppendLine("For instance, for friends and family, you could use 'less'."); 48 | info.AppendLine("But for business translations, you can use 'more'."); 49 | info.AppendLine(""); 50 | info.AppendLine("This is only supported by these languages:"); 51 | info.AppendLine(""); 52 | 53 | // get the supported languages 54 | var languages = (from lang in Variables.TargetLanguages where Variables.FormalitySupportedLanguages.Contains(lang.Value) select lang.Key).ToList(); 55 | 56 | // add them 57 | info.AppendLine($"{string.Join(", ", languages)}."); 58 | 59 | // done 60 | return info.ToString(); 61 | } 62 | 63 | /// 64 | /// Returns whether the user has been idle for the amount of minutes provided 65 | /// 66 | /// 67 | /// 68 | internal static bool UserIdleForMinutes(int minutes) 69 | { 70 | return (DateTime.Now - GetLastInputTime()).TotalMilliseconds > minutes; 71 | } 72 | 73 | /// 74 | /// Returns the last moment the user executed any input 75 | /// 76 | /// 77 | internal static DateTime GetLastInputTime() 78 | { 79 | var lastInputInfo = new LASTINPUTINFO(); 80 | lastInputInfo.cbSize = Marshal.SizeOf(lastInputInfo); 81 | lastInputInfo.dwTime = 0; 82 | 83 | var envTicks = Environment.TickCount; 84 | 85 | if (!GetLastInputInfo(ref lastInputInfo)) return DateTime.Now; 86 | var lastInputTick = Convert.ToDouble(lastInputInfo.dwTime); 87 | 88 | var idleTime = envTicks - lastInputTick; 89 | return idleTime > 0 ? DateTime.Now - TimeSpan.FromMilliseconds(idleTime) : DateTime.Now; 90 | } 91 | 92 | /// 93 | /// Launches the url on the system's default browser 94 | /// 95 | /// 96 | internal static void LaunchUrl(string url) 97 | { 98 | using (_ = Process.Start(new ProcessStartInfo(url) { UseShellExecute = true })) { } 99 | } 100 | 101 | /// 102 | /// Opens Explorer with the provided file selected 103 | /// 104 | /// 105 | internal static void OpenFileInExplorer(string file) 106 | { 107 | if (string.IsNullOrEmpty(file)) return; 108 | if (!File.Exists(file)) return; 109 | 110 | var argument = "/select, \"" + file + "\""; 111 | Process.Start("explorer.exe", argument); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/DeepLClient/Functions/MessageBoxAdv2.cs: -------------------------------------------------------------------------------- 1 | using Syncfusion.Windows.Forms; 2 | 3 | namespace DeepLClient.Functions 4 | { 5 | internal static class MessageBoxAdv2 6 | { 7 | /// 8 | /// Shows a MessageBoxAdv through the main form's handle, while suspending and resuming topmost 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | internal static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon) 16 | { 17 | return !Variables.MainFormManager.MainFormReady() 18 | ? DialogResult.Cancel 19 | : Show(Variables.MainFormManager.MainForm, text, caption, buttons, icon); 20 | } 21 | 22 | /// 23 | /// Shows a MessageBoxAdv, while suspending and resuming topmost 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | /// 31 | internal static DialogResult Show(Form owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon) 32 | { 33 | // suspend topmost 34 | if (owner.TopMost) owner.TopMost = false; 35 | 36 | // show our dialog 37 | var dialogResult = MessageBoxAdv.Show(owner, text, caption, buttons, icon); 38 | 39 | // resume topmost 40 | if (Variables.AppSettings.AlwaysOnTop) owner.TopMost = true; 41 | 42 | // done 43 | return dialogResult; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/DeepLClient/Functions/NativeFunctions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace DeepLClient.Functions 4 | { 5 | internal static class NativeFunctions 6 | { 7 | [DllImport("advapi32.dll", SetLastError = true)] 8 | internal static extern bool OpenProcessToken(nint processHandle, uint desiredAccess, out nint tokenHandle); 9 | 10 | [DllImport("kernel32.dll", SetLastError = true)] 11 | [return: MarshalAs(UnmanagedType.Bool)] 12 | internal static extern bool CloseHandle(nint hObject); 13 | 14 | [DllImport("User32.dll")] 15 | internal static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); 16 | 17 | [StructLayout(LayoutKind.Sequential)] 18 | // ReSharper disable once InconsistentNaming 19 | internal struct LASTINPUTINFO 20 | { 21 | internal static readonly int SizeOf = Marshal.SizeOf(typeof(LASTINPUTINFO)); 22 | 23 | [MarshalAs(UnmanagedType.U4)] 24 | internal int cbSize; 25 | [MarshalAs(UnmanagedType.U4)] 26 | internal uint dwTime; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/DeepLClient/Functions/PrintFunctions.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing.Printing; 2 | using Serilog; 3 | 4 | namespace DeepLClient.Functions 5 | { 6 | internal static class PrintFunctions 7 | { 8 | private static StringReader _stringReader; 9 | private static readonly Font Font = new("Verdana", 10); 10 | 11 | /// 12 | /// Attempts to print the provided text 13 | /// 14 | /// 15 | /// 16 | /// 17 | internal static bool PrintText(string text, PrinterSettings printerSettings) 18 | { 19 | try 20 | { 21 | _stringReader = new StringReader (text); 22 | 23 | // prepare our document with the provided settings 24 | using var pd = new PrintDocument(); 25 | pd.PrinterSettings = printerSettings; 26 | 27 | // bind to the print handler 28 | pd.PrintPage += PrintTextFileHandler; 29 | 30 | // print 31 | pd.Print(); 32 | 33 | // close the stringreader 34 | _stringReader?.Close(); 35 | 36 | // unbind 37 | pd.PrintPage -= PrintTextFileHandler; 38 | 39 | // done 40 | return true; 41 | } 42 | catch (Exception ex) 43 | { 44 | Log.Fatal(ex, "[PRINT] Error trying to print text: {err}", ex.Message); 45 | return false; 46 | } 47 | } 48 | 49 | private static void PrintTextFileHandler(object sender, PrintPageEventArgs ppeArgs) 50 | { 51 | try 52 | { 53 | if (_stringReader == null) return; 54 | 55 | // get the graphics handler 56 | using var graphics = ppeArgs.Graphics; 57 | if (graphics == null) return; 58 | 59 | // prepare some vars 60 | float leftMargin = ppeArgs.MarginBounds.Left; 61 | float topMargin = ppeArgs.MarginBounds.Top; 62 | var linesPerPage = ppeArgs.MarginBounds.Height/Font.GetHeight(graphics); 63 | 64 | // print 65 | var count = 0; 66 | string line = null; 67 | while (count < linesPerPage && ( line = _stringReader.ReadLine ()) != null) 68 | { 69 | var yPos = topMargin + count * Font.GetHeight(graphics); 70 | graphics.DrawString (line, Font, Brushes.Black,leftMargin, yPos, new StringFormat()); 71 | count++; 72 | } 73 | 74 | // todo: check multiple pages 75 | ppeArgs.HasMorePages = line != null; 76 | } 77 | catch (Exception ex) 78 | { 79 | Log.Fatal(ex, "[PRINT] Error printing text: {err}", ex.Message); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/DeepLManager.cs: -------------------------------------------------------------------------------- 1 | using DeepL; 2 | using Serilog; 3 | 4 | namespace DeepLClient.Managers 5 | { 6 | internal static class DeepLManager 7 | { 8 | internal static bool IsInitialised { get; private set; } 9 | 10 | /// 11 | /// Establishes contact with DeepL's API and fetches all entities 12 | /// 13 | /// 14 | internal static async Task InitializeAsync() 15 | { 16 | try 17 | { 18 | IsInitialised = false; 19 | 20 | // optionally dispose an old translator 21 | Variables.Translator?.Dispose(); 22 | 23 | // prepare options 24 | var options = new TranslatorOptions { 25 | appInfo = new AppInfo { AppName = "DeepL Translator by LAB02 Research", AppVersion = Variables.Version } 26 | }; 27 | 28 | // create translator 29 | Variables.Translator = new Translator(Variables.AppSettings.DeepLAPIKey, options); 30 | 31 | // load formalities 32 | if (!Variables.Formalities.Any()) 33 | { 34 | var formalities = Enum.GetValuesAsUnderlyingType(); 35 | foreach (var formalityObj in formalities) 36 | { 37 | var formality = (Formality)formalityObj; 38 | Variables.Formalities.Add((int)formality, formality.ToString()); 39 | } 40 | } 41 | 42 | // load source languages 43 | Variables.SourceLanguages.Clear(); 44 | var sourceLanguages = await Variables.Translator.GetSourceLanguagesAsync(); 45 | foreach (var language in sourceLanguages) Variables.SourceLanguages.Add(language.Name, language.Code); 46 | 47 | // add auto detect option 48 | Variables.SourceLanguages.Add("AUTO DETECT", "AUTO DETECT"); 49 | 50 | // load target languages 51 | Variables.TargetLanguages.Clear(); 52 | var targetLanguages = await Variables.Translator.GetTargetLanguagesAsync(); 53 | foreach (var language in targetLanguages) 54 | { 55 | Variables.TargetLanguages.Add(language.Name, language.Code); 56 | if (language.SupportsFormality) Variables.FormalitySupportedLanguages.Add(language.Code); 57 | } 58 | 59 | // done 60 | IsInitialised = true; 61 | return true; 62 | } 63 | catch (ConnectionException ex) 64 | { 65 | Log.Fatal(ex, "[DEEPL] Error trying to contact the DeepL server: {err}", ex.Message); 66 | return false; 67 | } 68 | catch (Exception ex) 69 | { 70 | Log.Fatal(ex, "[DEEPL] Error initialising: {err}", ex.Message); 71 | return false; 72 | } 73 | } 74 | 75 | /// 76 | /// Returns whether the selected target language supports formality 77 | /// 78 | /// 79 | /// 80 | internal static bool TargetLanguageSupportsFormality(ComboBox selectedTargetLanguage) 81 | { 82 | try 83 | { 84 | string targetLanguage = null; 85 | if (selectedTargetLanguage.SelectedItem != null) 86 | { 87 | var item = (KeyValuePair)selectedTargetLanguage.SelectedItem; 88 | targetLanguage = item.Value; 89 | } 90 | 91 | if (targetLanguage == null) return false; 92 | 93 | // check formality supported 94 | return Variables.FormalitySupportedLanguages.Contains(targetLanguage); 95 | } 96 | catch (Exception ex) 97 | { 98 | Log.Fatal(ex, "[DEEPL] Error determining whether target language supports formality: {err}", ex.Message); 99 | return false; 100 | } 101 | } 102 | 103 | /// 104 | /// Looks up the human readable language value for the provided language code 105 | /// 106 | /// 107 | /// 108 | internal static string GetSourceLanguageByLanguageCode(string languageCode) 109 | { 110 | return Variables.SourceLanguages.FirstOrDefault(x => x.Value == languageCode, new KeyValuePair(string.Empty, string.Empty)).Key; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/DocumentManager.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Diagnostics.CodeAnalysis; 3 | using ByteSizeLib; 4 | using DeepLClient.Enums; 5 | using DeepLClient.Functions; 6 | using Serilog; 7 | using Syncfusion.DocIO.DLS; 8 | using Syncfusion.Pdf; 9 | using Syncfusion.Pdf.Parsing; 10 | using Syncfusion.Presentation; 11 | using FormatType = Syncfusion.DocIO.FormatType; 12 | 13 | namespace DeepLClient.Managers 14 | { 15 | internal static class DocumentManager 16 | { 17 | /// 18 | /// Try to determine the character count of the PDF file 19 | /// 20 | /// 21 | /// 22 | internal static (long charCount, bool success) GetPdfCharacterCount(string file) 23 | { 24 | try 25 | { 26 | using var inputStream = new FileStream(file, FileMode.Open); 27 | using var loadedDocument = new PdfLoadedDocument(inputStream); 28 | 29 | var characterCount = 0L; 30 | foreach (PdfLoadedPage page in loadedDocument.Pages) 31 | { 32 | var pageText = page.ExtractText(); 33 | if (string.IsNullOrEmpty(pageText)) continue; 34 | 35 | characterCount += pageText.Length; 36 | } 37 | 38 | loadedDocument.Close(true); 39 | return (characterCount, true); 40 | } 41 | catch (IOException ex) 42 | { 43 | Log.Fatal(ex, "[DM] IO error getting wordcount for {file}: {err}", file, ex.Message); 44 | return (-1, false); 45 | } 46 | catch (Exception ex) 47 | { 48 | Log.Fatal(ex, "[DM] Error getting wordcount for {file}: {err}", file, ex.Message); 49 | return (-1, false); 50 | } 51 | } 52 | 53 | /// 54 | /// Try to determine the character count of the DOCX file 55 | /// 56 | /// 57 | /// 58 | internal static (long charCount, bool success) GetDocCharacterCount(string file) 59 | { 60 | try 61 | { 62 | using var document = new WordDocument(file, FormatType.Automatic); 63 | 64 | // update wordcount 65 | document.UpdateWordCount(false); 66 | 67 | return (document.BuiltinDocumentProperties.CharCount, true); 68 | } 69 | catch (IOException ex) 70 | { 71 | Log.Fatal(ex, "[DM] IO error getting wordcount for {file}: {err}", file, ex.Message); 72 | return (-1, false); 73 | } 74 | catch (Exception ex) 75 | { 76 | Log.Fatal(ex, "[DM] Error getting wordcount for {file}: {err}", file, ex.Message); 77 | return (-1, false); 78 | } 79 | } 80 | 81 | /// 82 | /// Try to determine the character count of the TXT file 83 | /// 84 | /// 85 | /// 86 | internal static (long charCount, bool success) GetTxtCharacterCount(string file) 87 | { 88 | try 89 | { 90 | var content = File.ReadAllText(file); 91 | return (content.Length, true); 92 | } 93 | catch (IOException ex) 94 | { 95 | Log.Fatal(ex, "[DM] IO error getting wordcount for {file}: {err}", file, ex.Message); 96 | return (-1, false); 97 | } 98 | catch (Exception ex) 99 | { 100 | Log.Fatal(ex, "[DM] Error getting wordcount for {file}: {err}", file, ex.Message); 101 | return (-1, false); 102 | } 103 | } 104 | 105 | /// 106 | /// Try to determine the character count of the HTML file 107 | /// 108 | /// 109 | /// 110 | internal static (long charCount, bool success) GetHtmlCharacterCount(string file) 111 | { 112 | try 113 | { 114 | var content = File.ReadAllText(file); 115 | return (content.Length, true); 116 | } 117 | catch (IOException ex) 118 | { 119 | Log.Fatal(ex, "[DM] IO error getting wordcount for {file}: {err}", file, ex.Message); 120 | return (-1, false); 121 | } 122 | catch (Exception ex) 123 | { 124 | Log.Fatal(ex, "[DM] Error getting wordcount for {file}: {err}", file, ex.Message); 125 | return (-1, false); 126 | } 127 | } 128 | 129 | /// 130 | /// Try to determine the character count of the PPTX file 131 | /// 132 | /// 133 | /// 134 | internal static (long charCount, bool success) GetPowerPointCharacterCount(string file) 135 | { 136 | try 137 | { 138 | using var presentation = Presentation.Open(file); 139 | var count = (from slide in presentation.Slides 140 | from slideItem in slide.Shapes 141 | select (IShape)slideItem 142 | into shape 143 | select shape.TextBody.Text.Length).Sum(); 144 | 145 | return (count, true); 146 | } 147 | catch (IOException ex) 148 | { 149 | Log.Fatal(ex, "[DM] IO error getting wordcount for {file}: {err}", file, ex.Message); 150 | return (-1, false); 151 | } 152 | catch (Exception ex) 153 | { 154 | Log.Fatal(ex, "[DM] Error getting wordcount for {file}: {err}", file, ex.Message); 155 | return (-1, false); 156 | } 157 | } 158 | 159 | /// 160 | /// Tries to determine the character count of the file, based on its document type 161 | /// 162 | /// 163 | /// 164 | /// 165 | internal static async Task<(long charCount, bool success)> GetDocumentCharacterCountAsync(DocumentType docType, string file) 166 | { 167 | try 168 | { 169 | switch (docType) 170 | { 171 | case DocumentType.Word: 172 | return await Task.Run(() => GetDocCharacterCount(file)); 173 | case DocumentType.PowerPoint: 174 | return await Task.Run(() => GetPowerPointCharacterCount(file)); 175 | case DocumentType.PDF: 176 | return await Task.Run(() => GetPdfCharacterCount(file)); 177 | case DocumentType.Text: 178 | return await Task.Run(() => GetTxtCharacterCount(file)); 179 | case DocumentType.HTML: 180 | return await Task.Run(() => GetHtmlCharacterCount(file)); 181 | } 182 | 183 | Log.Error("[DM] Error determining character count for {file}: unknown filetype", file); 184 | return (0L, false); 185 | } 186 | catch (Exception ex) 187 | { 188 | Log.Fatal(ex, "[DM] Error determining character count for {file}: {err}", file, ex.Message); 189 | return (0L, false); 190 | } 191 | } 192 | 193 | /// 194 | /// Returns a filter list of supported document types, used by OpenFileDialogs 195 | /// 196 | /// 197 | internal static string GetFileTypeFilters() 198 | { 199 | return "Supported (*.docx;*.pptx;*.pdf;*.txt;*.html)|*.docx;*.pptx;*.pdf;*.txt;*.html" + 200 | "|Word (*.docx)|*.docx" + 201 | "|PowerPoint (*.pptx)|*.pptx" + 202 | "|PDF (*.pdf)|*.pdf" + 203 | "|Text (*.txt)|*.txt" + 204 | "|HTML (*.html)|*.html"; 205 | } 206 | 207 | /// 208 | /// Determines whether the provided file is supported 209 | /// 210 | /// 211 | /// 212 | /// 213 | /// 214 | internal static bool FileIsSupported(string file, bool onlyText = false, bool onlyHtml = false) 215 | { 216 | List supportedExts; 217 | 218 | if (onlyHtml) supportedExts = new List { ".html" }; 219 | else if (onlyText) supportedExts = new List { ".txt" }; 220 | else supportedExts = new List { ".docx", ".pptx", ".pdf", ".txt", ".html" }; 221 | 222 | var ext = Path.GetExtension(file).ToLower(); 223 | return supportedExts.Contains(ext); 224 | } 225 | 226 | /// 227 | /// Converts the file's type to a DocumentType 228 | /// 229 | /// 230 | /// 231 | internal static DocumentType GetFileDocumentType(string file) 232 | { 233 | var ext = Path.GetExtension(file).ToLower(); 234 | switch (ext) 235 | { 236 | case ".docx": 237 | return DocumentType.Word; 238 | case ".pptx": 239 | return DocumentType.PowerPoint; 240 | case ".pdf": 241 | return DocumentType.PDF; 242 | case ".txt": 243 | return DocumentType.Text; 244 | case ".html": 245 | return DocumentType.HTML; 246 | default: 247 | return DocumentType.Unsupported; 248 | } 249 | } 250 | 251 | /// 252 | /// Checks whether the document's size exceeds the max allowed size 253 | /// 254 | /// 255 | /// 256 | [SuppressMessage("ReSharper", "InconsistentNaming")] 257 | internal static (bool tooLarge, double sizeMB) CheckDocumentSize(string file) 258 | { 259 | try 260 | { 261 | var sizeMB = GetDocumentSizeMB(file); 262 | return sizeMB > Variables.AppSettings.DocumentMaxSizeMB ? (true, sizeMB) : (false, sizeMB); 263 | } 264 | catch (Exception ex) 265 | { 266 | Log.Fatal(ex, "[DM] Error checking file size for {file}: {err}", file, ex.Message); 267 | return (false, 0d); 268 | } 269 | } 270 | 271 | /// 272 | /// Gets the document's size in MB 273 | /// 274 | /// 275 | /// 276 | [SuppressMessage("ReSharper", "InconsistentNaming")] 277 | internal static double GetDocumentSizeMB(string file) 278 | { 279 | try 280 | { 281 | var sizeBytes = Convert.ToDouble(new FileInfo(file).Length); 282 | return new ByteSize(sizeBytes).MegaBytes; 283 | } 284 | catch (Exception ex) 285 | { 286 | Log.Fatal(ex, "[DM] Error getting file size for {file}: {err}", file, ex.Message); 287 | return 0d; 288 | } 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/HotkeyManager.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using WK.Libraries.HotkeyListenerNS; 3 | 4 | namespace DeepLClient.Managers 5 | { 6 | internal class HotkeyManager 7 | { 8 | /// 9 | /// Initialises the global hotkey 10 | /// 11 | internal void Initialize() 12 | { 13 | Variables.MainFormManager?.RunOnUiThread(delegate 14 | { 15 | // check if the global hotkey's active 16 | if (!Variables.AppSettings.GlobalHotkeyEnabled) return; 17 | 18 | // anything stored? 19 | if (!string.IsNullOrEmpty(Variables.AppSettings.GlobalHotkey)) 20 | { 21 | Variables.GlobalHotkey = new Hotkey(Variables.AppSettings.GlobalHotkey); 22 | } 23 | 24 | // check if it's configured 25 | if (Variables.GlobalHotkey == null || Variables.GlobalHotkey.ToString() == "None") return; 26 | 27 | // all good, bind 28 | Variables.HotkeyListener?.Add(Variables.GlobalHotkey); 29 | 30 | Log.Information("[HOTKEY] Completed bind for global hotkey: {key}", Variables.GlobalHotkey.ToString()); 31 | }); 32 | } 33 | 34 | /// 35 | /// Processes the hotkey 36 | /// 37 | /// 38 | internal static void ProcessHotkey(HotkeyEventArgs e) 39 | { 40 | try 41 | { 42 | // get the string value 43 | var hotkey = e.Hotkey.ToString(); 44 | Log.Debug("[HOTKEY] Detected: {key}", hotkey); 45 | 46 | // relevant? 47 | if (string.IsNullOrEmpty(hotkey)) return; 48 | if (e.Hotkey != Variables.GlobalHotkey) return; 49 | 50 | // yep, any selected text? 51 | var selection = e.SourceApplication.Selection; 52 | if (!string.IsNullOrWhiteSpace(selection)) 53 | { 54 | // set url or text 55 | if (selection.ToLower().StartsWith("http")) Variables.MainFormManager?.SetSourceUrl(selection.Trim()); 56 | else Variables.MainFormManager?.SetSourceText(selection.Trim(), true); 57 | return; 58 | } 59 | 60 | // nope, just show 61 | Variables.MainFormManager?.ShowMain(false); 62 | } 63 | catch (Exception ex) 64 | { 65 | Log.Fatal(ex, "[HOTKEY] Error handling hotkey: {err}", ex.Message); 66 | } 67 | } 68 | 69 | /// 70 | /// Process a changed quickactions hotkey 71 | /// 72 | /// 73 | /// 74 | internal void HotkeyChanged(Hotkey previousKey, bool register = true) 75 | { 76 | Variables.MainFormManager?.RunOnUiThread(delegate 77 | { 78 | Variables.HotkeyListener?.Remove(previousKey); 79 | if (register && Variables.GlobalHotkey != null && Variables.GlobalHotkey.KeyCode != Keys.None) Variables.HotkeyListener?.Add(Variables.GlobalHotkey); 80 | }); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/LaunchManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32; 2 | using Serilog; 3 | 4 | namespace DeepLClient.Managers 5 | { 6 | internal static class LaunchManager 7 | { 8 | private const string RunKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"; 9 | private static readonly RegistryView RegView = Environment.Is64BitOperatingSystem ? RegistryView.Registry32 : RegistryView.Default; 10 | 11 | /// 12 | /// Checks whether the current executable has been set as run-on-login 13 | /// 14 | /// 15 | internal static bool CheckLaunchOnUserLogin() 16 | { 17 | try 18 | { 19 | // we don't want debug builds to auto launch 20 | if (Variables.DebugMode) return true; 21 | 22 | using var localKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegView); 23 | using var key = localKey.OpenSubKey(RunKey, false); 24 | var val = (string)key?.GetValue(Variables.ApplicationName, string.Empty); 25 | if (string.IsNullOrWhiteSpace(val)) return false; 26 | 27 | return val == Variables.ApplicationExecutable; 28 | } 29 | catch (Exception ex) 30 | { 31 | Log.Fatal(ex, "[LAUNCH] Unable to get status of run-on-login: {msg}", ex.Message); 32 | return false; 33 | } 34 | } 35 | 36 | /// 37 | /// Places the executable in HKCU's run key 38 | /// 39 | /// 40 | internal static bool EnableLaunchOnUserLogin() 41 | { 42 | try 43 | { 44 | // we don't want debug builds to auto launch 45 | if (Variables.DebugMode) return true; 46 | 47 | using var localKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegView); 48 | using var key = localKey.CreateSubKey(RunKey, true); 49 | key.OpenSubKey("Run", true); 50 | key.SetValue(Variables.ApplicationName, Variables.ApplicationExecutable, RegistryValueKind.String); 51 | key.Flush(); 52 | 53 | return true; 54 | } 55 | catch (Exception ex) 56 | { 57 | Log.Fatal(ex, "[LAUNCH] Unable to set executable as run-on-login: {msg}", ex.Message); 58 | return false; 59 | } 60 | } 61 | 62 | 63 | /// 64 | /// Removes the executable from HKCU's run key 65 | /// 66 | /// 67 | internal static bool DisableLaunchOnUserLogin() 68 | { 69 | try 70 | { 71 | // we don't want debug builds to auto launch 72 | if (Variables.DebugMode) return true; 73 | 74 | using var localKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegView); 75 | using var key = localKey.OpenSubKey(RunKey, true); 76 | key?.DeleteValue(Variables.ApplicationName, false); 77 | key?.Flush(); 78 | return true; 79 | } 80 | catch (Exception ex) 81 | { 82 | Log.Fatal(ex, "[LAUNCH] Unable to remove executable from run-on-login: {msg}", ex.Message); 83 | return false; 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/ProcessManager.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Security.Principal; 4 | using DeepLClient.Functions; 5 | using Serilog; 6 | 7 | namespace DeepLClient.Managers 8 | { 9 | [SuppressMessage("ReSharper", "EmptyGeneralCatchClause")] 10 | internal static class ProcessManager 11 | { 12 | private static bool _shutdownCalled = false; 13 | private static Mutex _mutex = null; 14 | 15 | /// 16 | /// Checks whether we're the first instance 17 | /// 18 | /// 19 | internal static bool IsFirstInstance() 20 | { 21 | try 22 | { 23 | // check if we can claim the mutex 24 | _mutex = new Mutex(true, Variables.ApplicationName, out var isFirstInstance); 25 | return isFirstInstance; 26 | } 27 | catch (Exception ex) 28 | { 29 | Log.Fatal(ex, "[PROCESS] Error processing mutex: {err}", ex.Message); 30 | return false; 31 | } 32 | } 33 | 34 | /// 35 | /// Releases our mutex lock, allowing another instance to load 36 | /// 37 | internal static void ReleaseMutex() 38 | { 39 | try 40 | { 41 | _mutex?.ReleaseMutex(); 42 | } 43 | catch { } 44 | } 45 | 46 | /// 47 | /// Closes all instances of the provided process name (do not include the extension) 48 | /// 49 | /// 50 | internal static void CloseAllInstancesForCurrentUser(string processName) 51 | { 52 | try 53 | { 54 | var procList = Process.GetProcesses().Where(proc => proc.ProcessName == processName); 55 | var procListEnumerable = procList as Process[] ?? procList.ToArray(); 56 | 57 | if (!procListEnumerable.Any()) return; 58 | 59 | foreach (var proc in procListEnumerable) 60 | { 61 | try 62 | { 63 | if (Environment.UserName != GetProcessOwner(proc)) continue; 64 | proc.Kill(); 65 | Thread.Sleep(1500); 66 | } 67 | catch { } 68 | finally 69 | { 70 | proc?.Dispose(); 71 | } 72 | } 73 | } 74 | catch (Exception ex) 75 | { 76 | Log.Fatal(ex, "[PROCESS] Something went wrong when killing {proc}: {err}", processName, ex.Message); 77 | } 78 | } 79 | 80 | /// 81 | /// Gets the owner of the provided process 82 | /// 83 | /// 84 | /// 85 | /// 86 | private static string GetProcessOwner(Process process, bool includeDomain = false) 87 | { 88 | var processHandle = nint.Zero; 89 | try 90 | { 91 | NativeFunctions.OpenProcessToken(process.Handle, 8, out processHandle); 92 | using var wi = new WindowsIdentity(processHandle); 93 | var user = wi.Name; 94 | // ReSharper disable once StringIndexOfIsCultureSpecific.1 95 | if (!includeDomain) return user.Contains(@"\") ? user[(user.IndexOf(@"\") + 1)..] : user; 96 | else return user; 97 | } 98 | catch 99 | { 100 | return null; 101 | } 102 | finally 103 | { 104 | if (processHandle != nint.Zero) NativeFunctions.CloseHandle(processHandle); 105 | } 106 | } 107 | 108 | /// 109 | /// Shutsdown all relevant aspects and closes us down (somewhat) nicely 110 | /// 111 | internal static void Shutdown() 112 | { 113 | var logClosed = false; 114 | var exitCode = 0; 115 | 116 | try 117 | { 118 | // process only once 119 | if (_shutdownCalled) return; 120 | _shutdownCalled = true; 121 | 122 | // announce we're stopping 123 | Variables.ShuttingDown = true; 124 | 125 | // log our demise 126 | Log.Information("[SYSTEM] Application shutting down"); 127 | 128 | // remove tray icon 129 | Variables.MainFormManager?.HideTrayIcon(); 130 | 131 | // hide our ui 132 | Variables.MainFormManager?.HideMain(); 133 | 134 | // unbind all hotkeys 135 | Variables.HotkeyListener?.RemoveAll(); 136 | 137 | // dispose global hotkey 138 | Variables.HotkeyListener?.Dispose(); 139 | 140 | // dispose our translator 141 | Variables.Translator?.Dispose(); 142 | 143 | // release our lock 144 | ReleaseMutex(); 145 | 146 | // flush the log 147 | Log.Information("[SYSTEM] Application shutdown complete"); 148 | Log.CloseAndFlush(); 149 | logClosed = true; 150 | } 151 | catch (Exception ex) 152 | { 153 | exitCode = 1; 154 | 155 | if (!logClosed) 156 | { 157 | Log.Error("[SYSTEM] Error shutting down nicely: {msg}", ex.Message); 158 | Log.CloseAndFlush(); 159 | } 160 | } 161 | finally 162 | { 163 | // dispose the main form 164 | Variables.MainFormManager?.Dispose(); 165 | 166 | // use the axe 167 | Environment.Exit(exitCode); 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/StorageManager.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Serilog; 3 | 4 | namespace DeepLClient.Managers 5 | { 6 | internal static class StorageManager 7 | { 8 | /// 9 | /// Downloads the provided uri into the provided local file 10 | /// 11 | /// 12 | /// 13 | /// 14 | [SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")] 15 | internal static async Task DownloadFileAsync(string uri, string localFile) 16 | { 17 | try 18 | { 19 | if (string.IsNullOrWhiteSpace(uri)) 20 | { 21 | Log.Error("[STORAGE] Unable to download file: got an empty uri"); 22 | return false; 23 | } 24 | 25 | if (string.IsNullOrWhiteSpace(localFile)) 26 | { 27 | Log.Error("[STORAGE] Unable to download file: got an empty local file"); 28 | return false; 29 | } 30 | 31 | if (!uri.ToLower().StartsWith("http")) 32 | { 33 | Log.Error("[STORAGE] Unable to download file: only HTTP uri's are allowed, got: {uri}", uri); 34 | return false; 35 | } 36 | 37 | var localFilePath = Path.GetDirectoryName(localFile); 38 | if (!Directory.Exists(localFilePath)) Directory.CreateDirectory(localFilePath!); 39 | 40 | // parse the uri as a check 41 | var safeUri = new Uri(uri); 42 | 43 | // download the file 44 | await DownloadRemoteFileAsync(safeUri.AbsoluteUri, localFile); 45 | 46 | return true; 47 | } 48 | catch (Exception ex) 49 | { 50 | Log.Fatal(ex, "[STORAGE] Error downloading file: {uri}", uri); 51 | return false; 52 | } 53 | } 54 | 55 | /// 56 | /// Downloads the provided URI to a local file 57 | /// 58 | /// 59 | /// 60 | /// 61 | private static async Task DownloadRemoteFileAsync(string uri, string localFile) 62 | { 63 | try 64 | { 65 | if (File.Exists(localFile)) 66 | { 67 | File.Delete(localFile); 68 | await Task.Delay(50); 69 | } 70 | 71 | // get a stream from our http client 72 | await using var stream = await Variables.HttpClient.GetStreamAsync(uri); 73 | 74 | // get a local file stream 75 | await using var fileStream = new FileStream(localFile!, FileMode.CreateNew); 76 | 77 | // transfer the data 78 | await stream.CopyToAsync(fileStream); 79 | 80 | // done 81 | return true; 82 | } 83 | catch (Exception ex) 84 | { 85 | Log.Error("[STORAGE] Error while downloading file!\r\nRemote URI: {uri}\r\nLocal file: {localFile}\r\nError: {err}", uri, localFile, ex.Message); 86 | return false; 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/SubscriptionManager.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Security.Cryptography; 3 | using DeepLClient.Extensions; 4 | using Serilog; 5 | 6 | namespace DeepLClient.Managers 7 | { 8 | [SuppressMessage("ReSharper", "EmptyGeneralCatchClause")] 9 | internal static class SubscriptionManager 10 | { 11 | private static bool _limitReachedWarningShown = false; 12 | private static bool _limitPendingWarningShown = false; 13 | 14 | /// 15 | /// Initialises the background watcher, which will notify when a subscription limit has been reached 16 | /// 17 | internal static void Initialize() 18 | { 19 | // start monitoring subscription limits 20 | _ = Task.Run(MonitorLimits); 21 | } 22 | 23 | private static async void MonitorLimits() 24 | { 25 | while (!Variables.ShuttingDown) 26 | { 27 | try 28 | { 29 | // only on free 30 | if (!UsingFreeSubscription()) continue; 31 | 32 | // check if the manager's ready 33 | while (!DeepLManager.IsInitialised) await Task.Delay(150); 34 | 35 | // get the current state 36 | var state = await Variables.Translator.GetUsageAsync(); 37 | if (state.Character == null) continue; 38 | 39 | // get remaining chars 40 | var charsRemaining = state.Character.Limit - state.Character.Count; 41 | 42 | // anything to warn about? 43 | switch (state.AnyLimitReached) 44 | { 45 | case false when charsRemaining > 10000: 46 | { 47 | // nope 48 | if (!_limitReachedWarningShown && !_limitPendingWarningShown) break; 49 | 50 | // hide the warnings 51 | Variables.MainFormManager?.HideWarningMessage(); 52 | _limitReachedWarningShown = false; 53 | _limitPendingWarningShown = false; 54 | 55 | break; 56 | } 57 | case true when !_limitReachedWarningShown: 58 | { 59 | // we've reached a limit, motify 60 | Variables.MainFormManager?.ShowWarningMessage("CHARACTER LIMIT REACHED, PLEASE REVIEW YOUR SUBSCRIPTION"); 61 | _limitReachedWarningShown = true; 62 | 63 | break; 64 | } 65 | default: 66 | { 67 | if (charsRemaining > 10000) break; 68 | 69 | // we're near a limit 70 | Variables.MainFormManager?.ShowWarningMessage($"YOU HAVE {charsRemaining:N0} CHARACTERS LEFT"); 71 | _limitPendingWarningShown = true; 72 | 73 | break; 74 | } 75 | } 76 | } 77 | catch { } 78 | finally 79 | { 80 | // don't bother the api too much 81 | await Task.Delay(TimeSpan.FromMinutes(5)); 82 | } 83 | } 84 | } 85 | 86 | /// 87 | /// Calculate the projected cost of the translation and returns it as a € #,## string. 88 | /// In case of a document, the configured minimim characters per document is used 89 | /// 90 | /// 91 | /// 92 | /// 93 | internal static string CalculateCostString(double characterCount, bool isDocument = true) 94 | { 95 | // ignore for free 96 | if (UsingFreeSubscription()) return "FREE"; 97 | 98 | // get the price 99 | var price = CalculateCost(characterCount, isDocument); 100 | 101 | // any costs? 102 | if (price == 0) return $"{0:C2}"; 103 | 104 | // yep, write it out 105 | var priceString = $"{price:C2}"; 106 | if (price < 0.01) priceString = $"< {0.01:C2}"; 107 | 108 | // done 109 | return priceString; 110 | } 111 | 112 | /// 113 | /// Calculate the projected cost of the translation 114 | /// 115 | /// 116 | /// 117 | /// 118 | internal static double CalculateCost(double characterCount, bool isDocument = true) 119 | { 120 | // ignore for free 121 | if (UsingFreeSubscription()) return 0d; 122 | 123 | // any chars? 124 | if (characterCount == 0) return 0d; 125 | 126 | // yep, calculate accordingly 127 | if (isDocument && characterCount < Variables.AppSettings.MinimumCharactersPerDocument) characterCount = Variables.AppSettings.MinimumCharactersPerDocument; 128 | var price = characterCount * Variables.AppSettings.PricePerCharacter; 129 | 130 | // done 131 | return price; 132 | } 133 | 134 | /// 135 | /// Checks for a free subscription domain 136 | /// 137 | /// 138 | internal static bool UsingFreeSubscription() 139 | { 140 | return !string.IsNullOrEmpty(Variables.AppSettings.DeepLDomain) && Variables.AppSettings.DeepLDomain.ToLower().Contains("free"); 141 | } 142 | 143 | /// 144 | /// Returns either free or 0,00 145 | /// 146 | /// 147 | internal static string BaseCostNotation() 148 | { 149 | return UsingFreeSubscription() ? "FREE" : $"{Variables.CurrencySymbol}0,00"; 150 | } 151 | 152 | /// 153 | /// Calculates whether the amount of chars will exceed the subscription limit 154 | /// 155 | /// 156 | /// 157 | /// 158 | internal static async Task CharactersWillExceedLimitAsync(double characterCount, bool isDocument = false) 159 | { 160 | // only for free 161 | if (!UsingFreeSubscription()) return false; 162 | 163 | // get the current state 164 | var state = await Variables.Translator.GetUsageAsync(); 165 | 166 | // do we have char info? 167 | if (state.Character == null) 168 | { 169 | Log.Error("[SUBSCRIPTION] Received null character info."); 170 | return false; 171 | } 172 | 173 | // is the limit already reached? 174 | if (state.Character.LimitReached) return true; 175 | 176 | // correct for doc minimum chars 177 | if (isDocument && characterCount < Variables.AppSettings.MinimumCharactersPerDocument) characterCount = Variables.AppSettings.MinimumCharactersPerDocument; 178 | 179 | // return projected state 180 | return (state.Character.Limit - state.Character.Count - characterCount) < 0; 181 | } 182 | 183 | /// 184 | /// Returns whether the character limit has been reached 185 | /// 186 | /// 187 | internal static async Task IsLimitReachedAsync() 188 | { 189 | // only for free 190 | if (!UsingFreeSubscription()) return false; 191 | 192 | // get the current state 193 | var state = await Variables.Translator.GetUsageAsync(); 194 | 195 | // do we have char info? 196 | if (state.Character == null) 197 | { 198 | Log.Error("[SUBSCRIPTION] Received null character info."); 199 | return false; 200 | } 201 | 202 | // is the limit already reached? 203 | return state.Character.LimitReached; 204 | } 205 | 206 | /// 207 | /// Returns a subsection of the API key, and a SHA256 hash of the entire key 208 | /// 209 | /// 210 | internal static (string apiSection, string apiHash) GetAnonymisedApiKey() 211 | { 212 | var key = Variables.AppSettings.DeepLAPIKey; 213 | if (string.IsNullOrEmpty(key) || key.Length < 6) return (string.Empty, string.Empty); 214 | 215 | // get a section of the key 216 | var apiSection = $"{key[..6]}..{key.Substring(key.Length - 6, 6)}"; 217 | 218 | // get the hash 219 | using var sha = SHA256.Create(); 220 | var apiHash = sha.GetHash(key); 221 | 222 | // done 223 | return (apiSection, apiHash); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/TranslationEventsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using DeepLClient.Extensions; 7 | using DeepLClient.Models; 8 | using Newtonsoft.Json; 9 | using Serilog; 10 | 11 | namespace DeepLClient.Managers 12 | { 13 | internal static class TranslationEventsManager 14 | { 15 | /// 16 | /// Initialises the translation events manager, loading stored events and periodically mailing the logs (if configured) 17 | /// 18 | internal static void Initialize() 19 | { 20 | //not implemented yet 21 | return; 22 | 23 | try 24 | { 25 | // either load or remove all events 26 | if (!Variables.AppSettings.EnableUsageLogging) PermanentlyPurgeAllEvents(true); 27 | else LoadStoredEvents(); 28 | 29 | // enable the periodic mailer 30 | Task.Run(PeriodicallyMailTranslationEvents); 31 | } 32 | catch (Exception ex) 33 | { 34 | Log.Fatal(ex, "[TEM] Error initialising: {err}", ex.Message); 35 | } 36 | } 37 | 38 | /// 39 | /// Add and store a new text translation event 40 | /// 41 | /// 42 | internal static void StoreTextTranslationEvent(double characters) 43 | { 44 | //not implemented yet 45 | return; 46 | 47 | if (!Variables.AppSettings.EnableUsageLogging) return; 48 | 49 | Task.Run(delegate 50 | { 51 | var translationEvent = new TranslationEvent().SetTextEvent(characters); 52 | Variables.TranslationEvents.Add(translationEvent); 53 | 54 | StoreAllEvents(); 55 | }); 56 | } 57 | 58 | /// 59 | /// Add and store a new document translation event 60 | /// 61 | /// 62 | internal static void StoreDocumentTranslationEvent(double characters) 63 | { 64 | //not implemented yet 65 | return; 66 | 67 | if (!Variables.AppSettings.EnableUsageLogging) return; 68 | 69 | Task.Run(delegate 70 | { 71 | var translationEvent = new TranslationEvent().SetDocumentEvent(characters); 72 | Variables.TranslationEvents.Add(translationEvent); 73 | 74 | StoreAllEvents(); 75 | }); 76 | } 77 | 78 | /// 79 | /// Add and store a new webpage translation event 80 | /// 81 | /// 82 | internal static void StoreWebpageTranslationEvent(double characters) 83 | { 84 | //not implemented yet 85 | return; 86 | 87 | if (!Variables.AppSettings.EnableUsageLogging) return; 88 | 89 | Task.Run(delegate 90 | { 91 | var translationEvent = new TranslationEvent().SetWebpageEvent(characters); 92 | Variables.TranslationEvents.Add(translationEvent); 93 | 94 | StoreAllEvents(); 95 | }); 96 | } 97 | 98 | private static void LoadStoredEvents() 99 | { 100 | //not implemented yet 101 | return; 102 | 103 | try 104 | { 105 | // check if there's a log found 106 | if (!Directory.Exists(Variables.ConfigPath) || !File.Exists(Variables.TranslationEventsLogFile)) return; 107 | 108 | // yep, load it 109 | var logStr = File.ReadAllText(Variables.TranslationEventsLogFile); 110 | 111 | // content? 112 | if (string.IsNullOrWhiteSpace(logStr)) return; 113 | 114 | // yep, try to parse 115 | Variables.TranslationEvents = JsonConvert.DeserializeObject>(logStr); 116 | if (Variables.TranslationEvents == null) 117 | { 118 | Log.Error("[TEM] Parsing stored event logs returned a null object"); 119 | Variables.TranslationEvents = new List(); 120 | return; 121 | } 122 | 123 | // done 124 | Log.Information("[TEM] Found {count} translation events", Variables.TranslationEvents.Count); 125 | } 126 | catch (Exception ex) 127 | { 128 | Log.Fatal(ex, "[TEM] Error loading stored events: {err}", ex.Message); 129 | } 130 | } 131 | 132 | /// 133 | /// Stores all translation events to disk 134 | /// 135 | internal static void StoreAllEvents() 136 | { 137 | //not implemented yet 138 | return; 139 | 140 | try 141 | { 142 | // check the path 143 | if (!Directory.Exists(Variables.ConfigPath)) Directory.CreateDirectory(Variables.ConfigPath); 144 | 145 | // store values 146 | var events = JsonConvert.SerializeObject(Variables.TranslationEvents, Formatting.Indented); 147 | File.WriteAllText(Variables.TranslationEventsLogFile, events); 148 | 149 | // done 150 | } 151 | catch (Exception ex) 152 | { 153 | Log.Fatal(ex, "[TEM] Error storing translation events: {err}", ex.Message); 154 | } 155 | } 156 | 157 | /// 158 | /// This will clear ALL translation events 159 | /// 160 | internal static void PermanentlyPurgeAllEvents(bool areYouSureEverythingWillBeCleared) 161 | { 162 | //not implemented yet 163 | return; 164 | 165 | try 166 | { 167 | if (!areYouSureEverythingWillBeCleared) return; 168 | 169 | // remove all events 170 | Variables.TranslationEvents?.Clear(); 171 | 172 | // delete the logfile 173 | if (File.Exists(Variables.TranslationEventsLogFile)) File.Delete(Variables.TranslationEventsLogFile); 174 | 175 | // done 176 | Log.Information("[TEM] All stored events purged"); 177 | } 178 | catch (Exception ex) 179 | { 180 | Log.Fatal(ex, "[TEM] Error purging all translation events: {err}", ex.Message); 181 | } 182 | } 183 | 184 | /// 185 | /// Purges all events that are older than the configured amount of days 186 | /// 187 | internal static void PurgeOldEvents() 188 | { 189 | //not implemented yet 190 | return; 191 | 192 | try 193 | { 194 | if (Variables.AppSettings.RemoveUsageLogEntriesOlderThanDays < 0) return; 195 | if (Variables.AppSettings.RemoveUsageLogEntriesOlderThanDays == 0) 196 | { 197 | // 0 means always clear everything 198 | PermanentlyPurgeAllEvents(true); 199 | return; 200 | } 201 | 202 | var oldCount = Variables.TranslationEvents.Count; 203 | if (oldCount == 0) return; 204 | 205 | var now = DateTime.UtcNow; 206 | 207 | // fetch new events 208 | var newEvents = Variables.TranslationEvents.Where(translationEvent => 209 | (now - translationEvent.MomentUtc).TotalDays <= Variables.AppSettings.RemoveUsageLogEntriesOlderThanDays).ToList(); 210 | 211 | // any change? 212 | if (newEvents.Count == oldCount) return; 213 | 214 | // yep, current events 215 | Variables.TranslationEvents.Clear(); 216 | 217 | // add the remaining events 218 | foreach (var translationEvent in newEvents) Variables.TranslationEvents.Add(translationEvent); 219 | 220 | // store it 221 | StoreAllEvents(); 222 | 223 | // done 224 | Log.Information("[TEM] Old event purging complete, removed {count} events", oldCount - newEvents.Count); 225 | } 226 | catch (Exception ex) 227 | { 228 | Log.Fatal(ex, "[TEM] Error purging old translation events: {err}", ex.Message); 229 | } 230 | } 231 | 232 | /// 233 | /// Continuously checks whether a mail containing the translation event logs needs to be sent 234 | /// 235 | private static async void PeriodicallyMailTranslationEvents() 236 | { 237 | //not implemented yet 238 | return; 239 | 240 | while (!Variables.ShuttingDown) 241 | { 242 | try 243 | { 244 | // does the user want it? 245 | if (!Variables.AppSettings.EnablePeriodicUsageMail) continue; 246 | 247 | // how long ago since our last mail? 248 | var elapsed = DateTime.Now - SettingsManager.GetTranslationEventsLastMailed(); 249 | if (elapsed.TotalDays < Variables.AppSettings.SendUsageLogMailEveryDays) 250 | { 251 | // too soon 252 | continue; 253 | } 254 | 255 | // period elapsed, time to mail 256 | 257 | // fetch the relevant (not yet mailed) events 258 | var mailEvents = new List(); 259 | 260 | // prepare the horizon (latest relevant moment) 261 | var horizon = DateTime.UtcNow.AddDays(-Variables.AppSettings.SendUsageLogMailEveryDays); 262 | foreach (var translationEvent in Variables.TranslationEvents.Where(x => !x.Mailed)) 263 | { 264 | // relevant? 265 | if (translationEvent.MomentUtc < horizon) continue; 266 | 267 | // yep 268 | mailEvents.Add(translationEvent); 269 | 270 | // flag as mailed 271 | translationEvent.Mailed = true; 272 | } 273 | 274 | if (!mailEvents.Any()) 275 | { 276 | // nothing to do! 277 | continue; 278 | } 279 | 280 | // ok we have some info to mail 281 | // todo: mail 282 | 283 | // store the 'mailed' flags 284 | StoreAllEvents(); 285 | } 286 | catch (Exception ex) 287 | { 288 | Log.Fatal(ex, "[TEM] Error preparing events log e-mail: {err}", ex.Message); 289 | } 290 | finally 291 | { 292 | await Task.Delay(TimeSpan.FromMinutes(30)); 293 | } 294 | } 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/UpdateManager.cs: -------------------------------------------------------------------------------- 1 | using DeepLClient.Functions; 2 | using Microsoft.Win32; 3 | using System.Diagnostics; 4 | using Serilog; 5 | 6 | namespace DeepLClient.Managers 7 | { 8 | internal static class UpdateManager 9 | { 10 | private static bool _updateActive; 11 | 12 | /// 13 | /// Executes an initial update check, with periodic checks afterwards 14 | /// 15 | internal static async void Initialize() 16 | { 17 | try 18 | { 19 | // check for updates 20 | AreWeUpdated(); 21 | 22 | // initial update check, regardless of idle 23 | await CheckForUpdateAsync(false); 24 | 25 | // commence periodic check 26 | _ = Task.Run(PeriodicUpdateCheck); 27 | 28 | // done 29 | Log.Information("[UPDATER] Initialised"); 30 | } 31 | catch (Exception ex) 32 | { 33 | Log.Fatal(ex, ex.Message); 34 | } 35 | } 36 | 37 | /// 38 | /// Periodically checks for new updates 39 | /// 40 | private static async void PeriodicUpdateCheck() 41 | { 42 | while (!Variables.ShuttingDown) 43 | { 44 | // wait a bit 45 | await Task.Delay(TimeSpan.FromMinutes(30)); 46 | 47 | // check updates 48 | await CheckForUpdateAsync(); 49 | } 50 | } 51 | 52 | /// 53 | /// Checks for new updates. Will install and close the client when found. 54 | /// Only if the user is idle long enough, when checkForIdle is TRUE 55 | /// 56 | /// 57 | internal static async Task CheckForUpdateAsync(bool checkForIdle = true) 58 | { 59 | try 60 | { 61 | // don't update in debug mode 62 | if (Variables.DebugMode) return; 63 | 64 | // are we already updating? 65 | if (_updateActive) return; 66 | 67 | // is the pc idle long enough? 68 | if (checkForIdle && !HelperFunctions.UserIdleForMinutes(15)) return; 69 | 70 | // let's start 71 | _updateActive = true; 72 | 73 | // check whether wyupdate.exe and client.wyc are available 74 | if (!File.Exists(Variables.WyUpdateBinary)) 75 | { 76 | Log.Warning("[UPDATER] Component wyUpdate.exe not found, downloading .."); 77 | var wyUpdateOk = await StorageManager.DownloadFileAsync( $"{Variables.WyUpdateDownloadSource}wyUpdate.exe", Variables.WyUpdateBinary); 78 | if (!wyUpdateOk) 79 | { 80 | Log.Error("[UPDATER] Downloading wyUpdate.exe failed"); 81 | _updateActive = false; 82 | return; 83 | } 84 | Log.Information("[UPDATER] Component wyUpdate.exe restored"); 85 | } 86 | 87 | if (!File.Exists(Variables.WyUpdateClientConfig)) 88 | { 89 | Log.Warning("[UPDATER] Component client.wyc not found, downloading .."); 90 | var clientOk = await StorageManager.DownloadFileAsync($"{Variables.WyUpdateDownloadSource}client.wyc", Variables.WyUpdateClientConfig); 91 | if (!clientOk) 92 | { 93 | Log.Error("[UPDATER] Downloading client.wyc failed"); 94 | _updateActive = false; 95 | return; 96 | } 97 | Log.Information("[UPDATER] Component client.wyc restored"); 98 | } 99 | 100 | // updater is available, let's check for an update 101 | var isUpdateReady = await IsUpdateReadyAsync(); 102 | if (!isUpdateReady) 103 | { 104 | // nothing to do 105 | _updateActive = false; 106 | return; 107 | } 108 | 109 | // new update available 110 | Log.Information("[UPDATER] New update available!"); 111 | 112 | // is the pc still idle? 113 | if (checkForIdle && !HelperFunctions.UserIdleForMinutes(15)) 114 | { 115 | Log.Information("[UPDATER] Cancelling update, user became active"); 116 | return; 117 | } 118 | 119 | // looks good, install 120 | Log.Information("[UPDATER] System ready, commencing update .."); 121 | StartUpdate(); 122 | } 123 | catch (Exception ex) 124 | { 125 | Log.Fatal(ex, "[UPDATER] Error checking for update: {err}", ex.Message); 126 | _updateActive = false; 127 | } 128 | } 129 | 130 | /// 131 | /// Asks wyUpdate whether there's a new update pending 132 | /// Using wyUpdate.exe instead of the library, as recommended by wyDay 133 | /// 134 | /// 135 | private static async Task IsUpdateReadyAsync() 136 | { 137 | try 138 | { 139 | // updater found? 140 | if (!File.Exists(Variables.WyUpdateBinary)) 141 | { 142 | Log.Error("[UPDATER] WyUpdate.exe not found, unable to check for updates"); 143 | return false; 144 | } 145 | 146 | // prepare wyUpdate in check-only mode 147 | var startInfo = new ProcessStartInfo 148 | { 149 | FileName = Variables.WyUpdateBinary, 150 | Arguments = "/quickcheck /justcheck /noerr", 151 | WorkingDirectory = Variables.StartupPath 152 | }; 153 | 154 | // prepare our process 155 | using var process = new Process(); 156 | process.StartInfo = startInfo; 157 | 158 | // try to start it 159 | var started = process.Start(); 160 | 161 | // did we start? 162 | if (!started) 163 | { 164 | Log.Error("[UPDATER] Unable to start wyUpdate, unable to check for updates"); 165 | return false; 166 | } 167 | 168 | // give it 15 min 169 | process.WaitForInputIdle(); 170 | process.WaitForExit(Convert.ToInt32(TimeSpan.FromMinutes(15).TotalMilliseconds)); 171 | 172 | // did wyUpdate neatly close? 173 | if (!process.HasExited) 174 | { 175 | if (process.Responding) 176 | { 177 | Log.Error("[UPDATER] wyUpdate still busy after 15 minutes, closed responsively"); 178 | 179 | // ask it to close 180 | process.CloseMainWindow(); 181 | 182 | // give it time to close 183 | await Task.Delay(TimeSpan.FromSeconds(1)); 184 | 185 | // kill it 186 | CloseWyUpdateInstances(); 187 | } 188 | else 189 | { 190 | Log.Error("[UPDATER] wyUpdate still busy after 15 minutes, forcibly closed"); 191 | 192 | // kill it 193 | process.Kill(); 194 | 195 | // give it time to close 196 | await Task.Delay(TimeSpan.FromSeconds(1)); 197 | 198 | // multikill it 199 | CloseWyUpdateInstances(); 200 | } 201 | } 202 | 203 | // check the result 204 | var procExitCode = process.ExitCode; 205 | switch (procExitCode) 206 | { 207 | case 0: 208 | // nothing to do 209 | return false; 210 | case 1: 211 | // error 212 | Log.Error("[UPDATER] wyUpdate returned exitcode 1 when checking for updates"); 213 | return false; 214 | case 2: 215 | // update available 216 | return true; 217 | default: 218 | // shouldn't happen 219 | Log.Error("[UPDATER] wyUpdate returned an unknown exitcode: {procExitCode}", procExitCode); 220 | return false; 221 | } 222 | } 223 | catch (Exception ex) 224 | { 225 | Log.Fatal(ex, "[UPDATER] Error querying wyUpdate for updates: {err}", ex.Message); 226 | return false; 227 | } 228 | } 229 | 230 | /// 231 | /// Gives wyUpdate the command to start updating, and closes the client 232 | /// Using wyUpdate.exe instead of the library, as recommended by wyDay 233 | /// 234 | private static void StartUpdate() 235 | { 236 | try 237 | { 238 | // updater found? 239 | if (!File.Exists(Variables.WyUpdateBinary)) 240 | { 241 | Log.Error("[UPDATER] WyUpdate.exe not found, unable to check for updates"); 242 | return; 243 | } 244 | 245 | // hide our tray icon 246 | Variables.MainFormManager?.HideTrayIcon(); 247 | 248 | // dispose the translator 249 | Variables.Translator?.Dispose(); 250 | 251 | // release our lock 252 | ProcessManager.ReleaseMutex(); 253 | 254 | // prepare wyUpdate in silent-update mode 255 | var startInfo = new ProcessStartInfo 256 | { 257 | FileName = Variables.WyUpdateBinary, 258 | Arguments = "/skipinfo", 259 | WorkingDirectory = Variables.StartupPath 260 | }; 261 | 262 | // start it 263 | Process.Start(startInfo); 264 | } 265 | catch (Exception ex) 266 | { 267 | Log.Fatal(ex, "[UPDATER] Error executing update install: {err}", ex.Message); 268 | } 269 | finally 270 | { 271 | // note: this is a risk, in case the updater doesn't properly launch 272 | // but if we're not closed when it has launched, it'll get stuck waiting for us until the end of times 273 | 274 | // bye bye 275 | ProcessManager.Shutdown(); 276 | } 277 | } 278 | 279 | /// 280 | /// Active instances of wyUpdate can prevent us from updating 281 | /// 282 | private static void CloseWyUpdateInstances() => ProcessManager.CloseAllInstancesForCurrentUser("wyUpdate"); 283 | 284 | /// 285 | /// Checks whether we're updated 286 | /// 287 | private static void AreWeUpdated() 288 | { 289 | try 290 | { 291 | var lastVersion = (string)Registry.GetValue(Variables.RootRegKey, "LastVersion", string.Empty); 292 | if (string.IsNullOrEmpty(lastVersion)) return; 293 | if (lastVersion == Variables.Version) return; 294 | 295 | Log.Information("[UPDATER] Succesfully updated from {old} to {new}", lastVersion, Variables.Version); 296 | } 297 | catch (Exception ex) 298 | { 299 | Log.Fatal(ex, "[UPDATER] Error checking whether we're updated: {err}", ex.Message); 300 | } 301 | finally 302 | { 303 | StoreVersionNumber(); 304 | } 305 | } 306 | 307 | /// 308 | /// Stores our current version number 309 | /// 310 | private static void StoreVersionNumber() 311 | { 312 | try 313 | { 314 | Registry.SetValue(Variables.RootRegKey, "LastVersion", Variables.Version, RegistryValueKind.String); 315 | } 316 | catch (Exception ex) 317 | { 318 | Log.Fatal(ex, "[UPDATER] Error storing our version: {err}", ex.Message); 319 | } 320 | } 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /src/DeepLClient/Managers/UrlManager.cs: -------------------------------------------------------------------------------- 1 | using SmartReader; 2 | using System.Net.Sockets; 3 | using System.Text; 4 | using DeepLClient.Extensions; 5 | using Serilog; 6 | using DeepLClient.Models; 7 | 8 | namespace DeepLClient.Managers 9 | { 10 | internal static class UrlManager 11 | { 12 | /// 13 | /// Attempts to load the url, and extract the relevant readable text 14 | /// 15 | /// 16 | /// 17 | /// 18 | internal static async Task GetReadableContentAsync(string url, bool isLocaLFile = false) 19 | { 20 | var webpageResult = new WebpageResult(); 21 | 22 | try 23 | { 24 | // fetch the content if it's a local file, otherwise download it 25 | var reader = isLocaLFile 26 | ? new Reader(url, await File.ReadAllTextAsync(url)) 27 | : new Reader(url); 28 | 29 | // get the article 30 | var article = await reader.GetArticleAsync(); 31 | 32 | // readable? 33 | if (!article.IsReadable) 34 | { 35 | // nope 36 | Log.Warning("[URL] Fetching readable text failed: {url}", url); 37 | return webpageResult.SetReadableFailed(article.Content, article.Title); 38 | } 39 | 40 | // done 41 | return webpageResult.SetSuccess(article.Content, article.Title); 42 | } 43 | catch (UriFormatException ex) 44 | { 45 | Log.Fatal(ex, "[URL] Unable to parse host '{url}': {err}", url, ex.Message); 46 | return webpageResult.SetFailed("provided url is in the wrong format"); 47 | } 48 | catch (HttpRequestException ex) 49 | { 50 | Log.Fatal(ex, "[URL] Unable to contact host '{url}': {err}", url, ex.Message); 51 | 52 | if (ex.InnerException?.GetType() == typeof(SocketException)) 53 | { 54 | var exc = (SocketException)ex.InnerException; 55 | 56 | return exc.SocketErrorCode switch 57 | { 58 | SocketError.HostNotFound => webpageResult.SetFailed("the remote host address wasn't found"), 59 | SocketError.AccessDenied => webpageResult.SetFailed("access denied by the remote host"), 60 | SocketError.TimedOut => webpageResult.SetFailed("the host didn't respond"), 61 | SocketError.HostDown => webpageResult.SetFailed("the remote host is offline"), 62 | SocketError.HostUnreachable or SocketError.NetworkUnreachable => webpageResult.SetFailed("the remote host couldn't be reached"), 63 | SocketError.NotConnected => webpageResult.SetFailed("no internet connection"), 64 | _ => webpageResult.SetFailed($"error trying to contact the host: {exc.SocketErrorCode.ToString().ToLower()}") 65 | }; 66 | } 67 | 68 | var statusCode = ex.StatusCode.ToString(); 69 | return !string.IsNullOrWhiteSpace(statusCode) 70 | ? webpageResult.SetFailed($"error trying to contact the host: {statusCode}") 71 | : webpageResult.SetFailed("unknown error trying to contact the host"); 72 | } 73 | catch (Exception ex) 74 | { 75 | Log.Fatal(ex, "[URL] Unable to process host '{url}': {err}", url, ex.Message); 76 | return webpageResult.SetFailed("unknown error trying to contact the host"); 77 | } 78 | } 79 | 80 | /// 81 | /// Strips tags etc from the text. 82 | /// 83 | /// 84 | /// 85 | internal static string CleanText(string rawText) 86 | { 87 | if (string.IsNullOrEmpty(rawText)) return string.Empty; 88 | 89 | if (rawText.StartsWith("\"")) rawText = rawText.Remove(0, 1); 90 | if (rawText.EndsWith("\"")) rawText = rawText.Remove(rawText.Length - 1, 1); 91 | 92 | rawText = rawText.Replace("\\n\\n", Environment.NewLine); 93 | rawText = rawText.Replace("\\n", Environment.NewLine); 94 | rawText = rawText.Replace("\\\"", "\""); 95 | 96 | return rawText; 97 | } 98 | 99 | /// 100 | /// Checks if the value contains an url 101 | /// 102 | /// 103 | /// 104 | internal static bool IsUrl(string value) 105 | { 106 | return !string.IsNullOrWhiteSpace(value) && value.ToLower().StartsWith("http"); 107 | } 108 | 109 | /// 110 | /// Checks if the value contains an uri to a local or network file 111 | /// 112 | /// 113 | /// 114 | internal static bool IsLocalOrNetworkFile(string value) 115 | { 116 | if (string.IsNullOrWhiteSpace(value)) return false; 117 | return value.Substring(1, 2) == ":\\" || value[..2] == "\\\\"; 118 | } 119 | 120 | /// 121 | /// Wraps the provided content into a reading mode html page, and adds the title 122 | /// 123 | /// 124 | /// 125 | /// 126 | internal static string WrapContentInHtml(string content, string title) 127 | { 128 | var htmlContent = new StringBuilder(); 129 | htmlContent.AppendLine(""); 130 | htmlContent.AppendLine(""); 131 | htmlContent.AppendLine($"{title}"); 132 | htmlContent.AppendLine(""); 139 | htmlContent.AppendLine(""); 140 | htmlContent.AppendLine(""); 141 | htmlContent.AppendLine(content); 142 | htmlContent.AppendLine(""); 143 | htmlContent.AppendLine(""); 144 | 145 | return htmlContent.ToString(); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/DeepLClient/Models/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using DeepL; 3 | 4 | namespace DeepLClient.Models 5 | { 6 | [SuppressMessage("ReSharper", "InconsistentNaming")] 7 | public class AppSettings 8 | { 9 | public AppSettings() 10 | { 11 | // 12 | } 13 | 14 | public string User { get; set; } = string.Empty; 15 | public string DeepLDomain { get; set; } = string.Empty; 16 | public string DeepLAPIKey { get; set; } = string.Empty; 17 | public bool LaunchHidden { get; set; } = true; 18 | public bool StoreLastUsedSourceLanguage { get; set; } = true; 19 | public string LastSourceLanguage { get; set; } = string.Empty; 20 | public bool StoreLastUsedTargetLanguage { get; set; } = true; 21 | public string LastTargetLanguage { get; set;} = string.Empty; 22 | public Formality DefaultFormality { get; set; } = Formality.Default; 23 | public double PricePerCharacter { get; set; } = 0.00002; 24 | public double MinimumCharactersPerDocument { get; set; } = 50000; 25 | public int DocumentMaxSizeMB { get; set; } = 20; 26 | public bool CopyTranslationToClipboard { get; set; } = true; 27 | public bool LaunchOnWindowsLogin { get; set; } = true; 28 | public bool GlobalHotkeyEnabled { get; set; } = true; 29 | public string GlobalHotkey { get; set; } = "Shift, Control + T"; 30 | public bool AlwaysOnTop { get; set; } = false; 31 | public bool EnableUsageLogging { get; set; } = true; 32 | public int RemoveUsageLogEntriesOlderThanDays { get; set; } = 31; 33 | public bool EnablePeriodicUsageMail { get; set; } = false; 34 | public int SendUsageLogMailEveryDays { get; set; } = 7; 35 | public string UsageLogMailReceiverAddress { get; set; } = string.Empty; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/DeepLClient/Models/EmailSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DeepLClient.Models 8 | { 9 | public class EmailSettings 10 | { 11 | public EmailSettings() 12 | { 13 | // 14 | } 15 | 16 | public string SmtpServerAddress { get; set; } = string.Empty; 17 | public int SmtpServerPort { get; set; } = 25; 18 | public string SmtpServerUsername { get; set; } = string.Empty; 19 | public string SmtpServerPassword { get; set; } = string.Empty; 20 | public bool SmtpUseSsl { get; set; } 21 | public string SenderEmailAddress { get; set; } = string.Empty; 22 | public string SenderName { get; set; } = "DeepL Translator"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/DeepLClient/Models/TranslationEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using DeepLClient.Enums; 7 | 8 | namespace DeepLClient.Models 9 | { 10 | public class TranslationEvent 11 | { 12 | public TranslationEvent() 13 | { 14 | // 15 | } 16 | 17 | public TranslationEvent(string name, DateTime momentUtc, TranslationEventType type, double characters, double estimatedCost, string apiKeySegment, string apiKeyHash, string domain) 18 | { 19 | Name = name; 20 | MomentUtc = momentUtc; 21 | Type = type; 22 | Characters = characters; 23 | EstimatedCost = estimatedCost; 24 | ApiKeySegment = apiKeySegment; 25 | ApiKeyHash = apiKeyHash; 26 | Domain = domain; 27 | } 28 | 29 | public string Name { get; set; } 30 | public DateTime MomentUtc { get; set; } 31 | public TranslationEventType Type { get; set; } 32 | public double Characters { get; set; } 33 | public double EstimatedCost { get; set; } 34 | public string ApiKeySegment { get; set; } 35 | public string ApiKeyHash { get; set; } 36 | public string Domain { get; set; } 37 | public bool Mailed { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/DeepLClient/Models/WebpageResult.cs: -------------------------------------------------------------------------------- 1 | namespace DeepLClient.Models 2 | { 3 | public class WebpageResult 4 | { 5 | public WebpageResult() 6 | { 7 | // 8 | } 9 | 10 | public bool Success { get; set; } 11 | public bool IsReadable { get; set; } 12 | public string Content { get; set; } = string.Empty; 13 | public string Title { get; set; } = string.Empty; 14 | public string Error { get; set; } = string.Empty; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/DeepLClient/Program.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Events; 2 | using Serilog; 3 | using System.Text; 4 | using DeepLClient.Forms; 5 | using DeepLClient.Functions; 6 | using DeepLClient.Managers; 7 | 8 | namespace DeepLClient 9 | { 10 | internal static class Program 11 | { 12 | [STAThread] 13 | private static void Main() 14 | { 15 | try 16 | { 17 | // singleton app 18 | if (!ProcessManager.IsFirstInstance()) return; 19 | 20 | // syncfusion license 21 | Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(Variables.SyncfusionLicense); 22 | 23 | // prepare logger 24 | Variables.LevelSwitch.MinimumLevel = LogEventLevel.Information; 25 | 26 | #if DEBUG 27 | Variables.LevelSwitch.MinimumLevel = LogEventLevel.Debug; 28 | Variables.DebugMode = true; 29 | #endif 30 | 31 | // prepare a serilog logger 32 | Log.Logger = new LoggerConfiguration() 33 | .MinimumLevel.ControlledBy(Variables.LevelSwitch) 34 | .WriteTo.File(Path.Combine(Variables.LogPath, $"[{DateTime.Now:yyyy-MM-dd}] {Variables.ApplicationName}_.log"), 35 | rollingInterval: RollingInterval.Day, 36 | fileSizeLimitBytes: 10000000, 37 | retainedFileCountLimit: 10, 38 | rollOnFileSizeLimit: true, 39 | buffered: true, 40 | flushToDiskInterval: TimeSpan.FromMilliseconds(150)) 41 | .CreateLogger(); 42 | 43 | // log our launch 44 | Log.Information("[MAIN] {name} version: {version}", Variables.ApplicationName, Variables.Version); 45 | 46 | // prepare application 47 | Application.EnableVisualStyles(); 48 | Application.SetCompatibleTextRenderingDefault(false); 49 | 50 | // set scaling 51 | Application.SetHighDpiMode(HighDpiMode.DpiUnaware); 52 | 53 | // register the encoding provider for non-default encodings 54 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 55 | 56 | // prepare default application 57 | var mainForm = new Main(); 58 | 59 | // bind the manager 60 | Variables.MainFormManager = new MainFormManager(mainForm); 61 | 62 | // prepare msgbox 63 | HelperFunctions.SetMsgBoxStyle(Variables.DefaultFont); 64 | 65 | // load app settings 66 | var settingsLoaded = SettingsManager.LoadAppSettings(); 67 | if (!settingsLoaded) return; 68 | 69 | // load e-mail settings 70 | SettingsManager.LoadEmailSettings(); 71 | 72 | // store user 73 | if (!string.IsNullOrEmpty(Variables.AppSettings.User)) Log.Information("[MAIN] Registered user: {version}", Variables.AppSettings.User); 74 | else Log.Information("[MAIN] No registered user."); 75 | 76 | // check if we're showing ourselves because of a missing api key 77 | var forceShow = !SettingsManager.GetApiKeyMissingShown() && string.IsNullOrEmpty(Variables.AppSettings.DeepLAPIKey); 78 | 79 | // run hidden? 80 | if (!forceShow && Variables.AppSettings.LaunchHidden) Application.Run(new CustomApplicationContext(mainForm)); 81 | else Application.Run(mainForm); 82 | } 83 | catch (AccessViolationException ex) 84 | { 85 | Log.Fatal(ex, "[PROGRAM] AccessViolationException: {err}", ex.Message); 86 | Log.CloseAndFlush(); 87 | } 88 | catch (Exception ex) 89 | { 90 | Log.Fatal(ex, "[PROGRAM] {err}", ex.Message); 91 | Log.CloseAndFlush(); 92 | } 93 | finally 94 | { 95 | Log.CloseAndFlush(); 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/DeepLClient/Resources/book_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/book_icon_16.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/book_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/book_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/book_icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/book_icon_48.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/clean_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/clean_icon_16.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/clean_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/clean_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/clean_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/clean_icon_32.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/clipboard_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/clipboard_icon_16.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/config_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/config_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/config_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/config_icon_64.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/document_icon_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/document_icon_18.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/document_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/document_icon_32.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/exit_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/exit_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/globe_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/globe_icon_16.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/globe_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/globe_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/hide_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/hide_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/icon_32.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/icon_white_32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/icon_white_32.ico -------------------------------------------------------------------------------- /src/DeepLClient/Resources/info_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/info_icon_16.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/info_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/info_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/logo.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/logo_notext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/logo_notext.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/no_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/no_icon_16.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/no_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/no_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/price_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/price_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/price_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/price_icon_64.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/print_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/print_icon_16.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/print_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/print_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/save_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/save_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/show_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/show_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/switch_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/switch_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/switch_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/switch_icon_32.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/switch_icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/switch_icon_48.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/text_icon_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/text_icon_18.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/text_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/text_icon_32.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/warning_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/warning_icon_16.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/warning_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/warning_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Resources/yes_icon_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LAB02-Research/DeepL-Translator/ef4466677bdbb3099184742733f98c7384def515/src/DeepLClient/Resources/yes_icon_24.png -------------------------------------------------------------------------------- /src/DeepLClient/Variables.cs: -------------------------------------------------------------------------------- 1 | using DeepLClient.Models; 2 | using Serilog.Core; 3 | using System.Reflection; 4 | using DeepL; 5 | using System.Globalization; 6 | using WK.Libraries.HotkeyListenerNS; 7 | using DeepLClient.Managers; 8 | using Microsoft.Web.WebView2.Core; 9 | 10 | namespace DeepLClient 11 | { 12 | internal static class Variables 13 | { 14 | /// 15 | /// Application info 16 | /// 17 | public static string ApplicationName { get; } = "DeepL Translator"; 18 | public static string MessageBoxTitle { get; } = $"{ApplicationName} | LAB02 Research"; 19 | public static string ApplicationExecutable { get; } = Assembly.GetExecutingAssembly().Location.Replace(".dll", ".exe"); 20 | public static string Version { get; } = Application.ProductVersion; 21 | 22 | /// 23 | /// Constants 24 | /// 25 | internal const string SyncfusionLicense = "MjE3MTUwNkAzMjMxMmUzMjJlMzVjUGlGeU1CWllEcDBkVG55dWtnQnFhU0QrK25lY01QZGFPQU4wR2xyWWw4PQ=="; 26 | internal static string RootRegKey { get; } = @$"HKEY_CURRENT_USER\SOFTWARE\LAB02Research\{ApplicationName}"; 27 | internal static string CurrencySymbol { get; } = NumberFormatInfo.CurrentInfo.CurrencySymbol; 28 | 29 | /// 30 | /// Internal references 31 | /// 32 | internal static MainFormManager MainFormManager { get; set; } 33 | internal static HttpClient HttpClient { get; set; } = new(); 34 | internal static Font DefaultFont { get; } = new("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point); 35 | internal static CoreWebView2Environment WebViewEnvironment { get; set; } = null; 36 | 37 | /// 38 | /// HotKey 39 | /// 40 | internal static Hotkey GlobalHotkey { get; set; } = new(Keys.Control | Keys.Shift, Keys.T); 41 | internal static HotkeyListener HotkeyListener { get; set; } 42 | internal static HotkeyManager HotkeyManager { get; } = new(); 43 | 44 | 45 | /// 46 | /// DeepL 47 | /// 48 | internal static Translator Translator { get; set; } 49 | internal static SortedDictionary SourceLanguages { get; } = new(); 50 | internal static SortedDictionary TargetLanguages { get; } = new(); 51 | internal static SortedDictionary Formalities { get; } = new(); 52 | internal static List FormalitySupportedLanguages { get; } = new(); 53 | 54 | /// 55 | /// Internal state 56 | /// 57 | internal static LoggingLevelSwitch LevelSwitch { get; } = new(); 58 | internal static bool DebugMode { get; set; } = false; 59 | internal static bool ShuttingDown { get; set; } 60 | 61 | /// 62 | /// Local IO 63 | /// 64 | internal static string StartupPath { get; } = Path.GetDirectoryName(Application.ExecutablePath); 65 | internal static string LogPath { get; } = Path.Combine(StartupPath, "logs"); 66 | internal static string ConfigPath { get; } = Path.Combine(StartupPath, "config"); 67 | internal static string AppSettingsFile { get; } = Path.Combine(ConfigPath, "appsettings.json"); 68 | internal static string EmailSettingsFile { get; } = Path.Combine(ConfigPath, "emailsettings.json"); 69 | internal static string TranslationEventsLogFile { get; } = Path.Combine(ConfigPath, "translationeventslog.json"); 70 | internal static string CachePath { get; } = Path.Combine(StartupPath, "cache"); 71 | internal static string WebViewCachePath { get; } = Path.Combine(CachePath, "webview"); 72 | internal static string WebPagesCachePath { get; } = Path.Combine(CachePath, "webpages"); 73 | 74 | /// 75 | /// Config 76 | /// 77 | internal static AppSettings AppSettings { get; set; } 78 | internal static EmailSettings EmailSettings { get; set; } 79 | 80 | /// 81 | /// Usage Logging 82 | /// 83 | internal static List TranslationEvents { get; set; } = new(); 84 | 85 | /// 86 | /// Updater 87 | /// 88 | internal static string WyUpdateDownloadSource { get; } = "https://shares.lab02-research.org/deeplclient/"; 89 | internal static string WyUpdateBinary { get; } = Path.Combine(StartupPath, "wyUpdate.exe"); 90 | internal static string WyUpdateClientConfig { get; } = Path.Combine(StartupPath, "client.wyc"); 91 | } 92 | } 93 | --------------------------------------------------------------------------------