├── .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 | [](https://github.com/LAB02-Research/DeepL-Translator/releases/)
2 | [](#license)
3 | [](https://www.microsoft.com/ "Go to Microsoft homepage")
4 | [](https://img.shields.io/badge/.NET-7.0-blue)
5 | 
6 |
7 |
8 |
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 |
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 | [](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 | 
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 | 
101 |
102 | Document interface:
103 |
104 | 
105 |
106 | Webpage interface:
107 |
108 | 
109 |
110 | 
111 |
112 | You'll be notified of the costs before translating a document:
113 |
114 | 
115 |
116 | 
117 |
118 | 
119 |
120 | If a document or webpage translation will exceed your limit, you'll be notified as well:
121 |
122 | 
123 |
124 | Or if you've already reached your limit:
125 |
126 | 
127 |
128 | Easily check your subscription's state:
129 |
130 | 
131 |
132 | 
133 |
134 | Formality support:
135 |
136 | 
137 |
138 | Hides in your system tray, next to the clock:
139 |
140 | 
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 |
--------------------------------------------------------------------------------