├── .github
└── FUNDING.yml
├── .gitignore
├── App.config
├── Images
├── AnalyzeScreen.png
├── Cancel.png
├── LanguagesScreen.png
├── Logo.ico
├── Open.png
├── Restart.png
├── Sort.png
├── ToolsScreen.png
├── Translate.png
├── TranslateResxScreen.png
├── TranslateTextScreen.png
└── Update.png
├── LICENSE
├── MainWindow.Designer.cs
├── MainWindow.cs
├── MainWindow.resx
├── Panels
├── AnalyzeControlPanel.Designer.cs
├── AnalyzeControlPanel.cs
├── AnalyzeControlPanel.resx
├── LanguageComboBox.cs
├── TextControlPanel.Designer.cs
├── TextControlPanel.cs
├── TextControlPanel.resx
├── ToolsControlPanel.Designer.cs
├── ToolsControlPanel.cs
└── ToolsControlPanel.resx
├── Program.cs
├── Properties
├── AssemblyInfo.cs
├── Resources.Designer.cs
├── Resources.ar-SA.resx
├── Resources.de-DE-hints.xml
├── Resources.de-DE.resx
├── Resources.es-ES.resx
├── Resources.fr-FR.resx
├── Resources.he-IL.resx
├── Resources.ja-JP.resx
├── Resources.nl-NL.resx
├── Resources.pl-PL.resx
├── Resources.pt-BR.resx
├── Resources.resx
├── Resources.zh-CN.resx
├── Settings.Designer.cs
└── Settings.settings
├── README.md
├── ResxProvider.cs
├── ResxTranslator.csproj
├── ResxTranslator.sln
├── SettingsProvider.cs
├── StringExtensions.cs
├── Translator.cs
└── packages.config
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [stevencohn]
2 |
--------------------------------------------------------------------------------
/.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 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/App.config:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/Images/AnalyzeScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/AnalyzeScreen.png
--------------------------------------------------------------------------------
/Images/Cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/Cancel.png
--------------------------------------------------------------------------------
/Images/LanguagesScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/LanguagesScreen.png
--------------------------------------------------------------------------------
/Images/Logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/Logo.ico
--------------------------------------------------------------------------------
/Images/Open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/Open.png
--------------------------------------------------------------------------------
/Images/Restart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/Restart.png
--------------------------------------------------------------------------------
/Images/Sort.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/Sort.png
--------------------------------------------------------------------------------
/Images/ToolsScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/ToolsScreen.png
--------------------------------------------------------------------------------
/Images/Translate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/Translate.png
--------------------------------------------------------------------------------
/Images/TranslateResxScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/TranslateResxScreen.png
--------------------------------------------------------------------------------
/Images/TranslateTextScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/TranslateTextScreen.png
--------------------------------------------------------------------------------
/Images/Update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stevencohn/ResxTranslator/63afea829a7d54c9cf4a0ac1a6459c620e47d66a/Images/Update.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Steven
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MainWindow.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2020 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | #pragma warning disable S1066 // Collapsible "if" statements should be merged
6 |
7 | namespace ResxTranslator
8 | {
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Diagnostics;
12 | using System.Drawing;
13 | using System.IO;
14 | using System.Linq;
15 | using System.Text.RegularExpressions;
16 | using System.Threading;
17 | using System.Web;
18 | using System.Windows.Forms;
19 | using System.Xml.Linq;
20 |
21 |
22 | public partial class MainWindow : Form
23 | {
24 | private const string CodePattern = @".+\.(?.+)\.resx";
25 | private const string FilePattern = @"(?.+)(\.(?.+)){0,1}\.resx";
26 | private const string NL = "\n";
27 |
28 | private int strings = 0;
29 | private bool backendUpdate = false;
30 | private CancellationTokenSource cancellation;
31 |
32 |
33 | public MainWindow()
34 | {
35 | InitializeComponent();
36 |
37 | Width = Math.Min(1700, Screen.FromControl(this).WorkingArea.Width - 600);
38 | Height = Math.Min(1000, Screen.FromControl(this).WorkingArea.Height - 300);
39 |
40 | PopulateLanguages();
41 |
42 | languageList.Dock = DockStyle.Fill;
43 | logBox.Dock = DockStyle.Fill;
44 | logBox.Visible = true;
45 |
46 | cancelButton.Top = translateButton.Top;
47 | cancelButton.Left = translateButton.Left;
48 | restartButton.Top = translateButton.Top;
49 | restartButton.Left = translateButton.Left;
50 |
51 | var settings = new SettingsProvider();
52 | var inputPath = settings.Get("inputPath");
53 | if (string.IsNullOrEmpty(inputPath))
54 | {
55 | var args = Environment.GetCommandLineArgs();
56 | if (args.Length > 1)
57 | {
58 | if (File.Exists(args[1]))
59 | {
60 | inputPath = args[1];
61 | }
62 |
63 | }
64 | }
65 |
66 | if (!string.IsNullOrEmpty(inputPath))
67 | {
68 | inputBox.Text = inputPath;
69 | }
70 | }
71 |
72 |
73 | private void PopulateLanguages()
74 | {
75 | foreach (var code in Translator.Codes)
76 | {
77 | var name = Translator.GetDisplayName(code);
78 |
79 | codeBox.Items.Add($"{name} [{code}]");
80 | languageList.Items.Add(name);
81 | }
82 | }
83 |
84 |
85 | private void PrepTabOnSelectedIndexChanged(object sender, EventArgs e)
86 | {
87 | switch (tabs.SelectedIndex)
88 | {
89 | case 2: analyzeControlPanel.SetFilePath(inputBox.Text); break;
90 | case 3: toolsControlPanel.SetFilePath(inputBox.Text); break;
91 | }
92 | }
93 |
94 |
95 | // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
96 | // Translate File
97 |
98 | private void ChangedResxInput(object sender, EventArgs e)
99 | {
100 | var fileOK = inputBox.Text.Length > 0 && File.Exists(inputBox.Text);
101 |
102 | if (fileOK)
103 | {
104 | var match = Regex.Match(
105 | Path.GetFileName(inputBox.Text), CodePattern);
106 |
107 | if (match.Success)
108 | {
109 | var code = match.Groups["code"].Value.ToLower();
110 | codeBox.SelectedIndex = Translator.Codes.IndexOf(code);
111 | }
112 | else
113 | {
114 | codeBox.SelectedIndex = Translator.Codes.IndexOf("en");
115 | }
116 | }
117 |
118 | if (sender == inputBox)
119 | {
120 | AutosetLanguages();
121 | }
122 |
123 | if (sender == inputBox || sender == languageList)
124 | {
125 | if (Translator.Estimate(inputBox.Text, out strings, out var seconds))
126 | {
127 | var langs = Math.Max(languageList.CheckedIndices.Count, 1);
128 | var span = new TimeSpan(0, 0, seconds * langs);
129 | estimationLabel.Text = strings == 0
130 | ? string.Empty
131 | : $"{strings} strings. Estimated completion in {span}";
132 | }
133 | }
134 |
135 | if (logBox.Visible)
136 | {
137 | languageList.Visible = true;
138 | logBox.Visible = false;
139 | logBox.Clear();
140 | }
141 |
142 | translateButton.Enabled =
143 | fileOK &&
144 | languageList.CheckedIndices.Count > 0 &&
145 | estimationLabel.Text.Length > 0;
146 | }
147 |
148 |
149 | private void AutosetLanguages()
150 | {
151 | backendUpdate = true;
152 |
153 | var regex = new Regex(@"\.(([a-z]{2,3})\-[A-Z]{2})\.resx");
154 |
155 | var dir = Path.GetDirectoryName(inputBox.Text);
156 | if (Directory.Exists(dir))
157 | {
158 | for (int i = 0; i < languageList.Items.Count; i++)
159 | {
160 | languageList.Items[0].Checked = false;
161 | }
162 |
163 | var files = Directory.GetFiles(dir, "*.resx")
164 | .Where(f => regex.IsMatch(f))
165 | .ToList();
166 |
167 | foreach (var file in files)
168 | {
169 | var match = regex.Match(file);
170 |
171 | // group 2 is the inner capture
172 | var lang = match.Groups[2].Value;
173 |
174 | if (lang == "zh")
175 | {
176 | // group 1 is the outer capture
177 | lang = match.Groups[1].Value;
178 | }
179 |
180 | var index = Translator.Codes.IndexOf(lang);
181 | if (index >= 0)
182 | {
183 | languageList.Items[index].Checked = true;
184 | }
185 | }
186 |
187 | }
188 |
189 | backendUpdate = false;
190 | }
191 |
192 |
193 | private void BrowseFiles(object sender, EventArgs e)
194 | {
195 | if (openFileDialog.ShowDialog() == DialogResult.OK)
196 | {
197 | if (sender == browseFileButton)
198 | {
199 | inputBox.Text = openFileDialog.FileName;
200 | }
201 | }
202 | }
203 |
204 |
205 | private void BrowseFolders(object sender, EventArgs e)
206 | {
207 | if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
208 | {
209 | outputBox.Text = folderBrowserDialog.SelectedPath;
210 | }
211 | }
212 |
213 |
214 | private void CompareChanged(object sender, EventArgs e)
215 | {
216 | estimationLabel.ForeColor = newStringsBox.Checked ? Color.Gray : Color.Black;
217 | }
218 |
219 |
220 | private void CheckedLanguage(object sender, ItemCheckedEventArgs e)
221 | {
222 | if (!backendUpdate)
223 | {
224 | ChangedResxInput(sender, e);
225 | }
226 | }
227 |
228 |
229 | private async void TranslateFile(object sender, EventArgs e)
230 | {
231 | LockControls();
232 |
233 | translateButton.Visible = false;
234 | cancelButton.Visible = true;
235 |
236 | if (!newStringsBox.Checked)
237 | {
238 | // this is an estimate
239 | progressBar.Maximum = strings * languageList.CheckedIndices.Count;
240 | }
241 |
242 | var inputPath = Path.GetFullPath(inputBox.Text);
243 | var fromCode = Translator.Codes[codeBox.SelectedIndex];
244 |
245 | var outputDir = outputBox.Text.Length > 0
246 | ? Path.GetFullPath(outputBox.Text)
247 | : Path.GetDirectoryName(inputPath);
248 |
249 | if (!Directory.Exists(outputDir))
250 | {
251 | Directory.CreateDirectory(outputDir);
252 | }
253 |
254 | string filepath = null;
255 | var match = Regex.Match(inputPath, FilePattern);
256 | if (match.Success)
257 | {
258 | filepath = match.Groups["file"].Value;
259 | }
260 |
261 | if (sortBox.Checked)
262 | {
263 | var root = XElement.Load(inputPath);
264 | ResxProvider.SortData(root);
265 | root.Save(inputPath, SaveOptions.None);
266 | }
267 |
268 | var translator = new Translator();
269 | cancellation = new CancellationTokenSource();
270 |
271 | var watch = new Stopwatch();
272 | watch.Start();
273 |
274 | foreach (var index in languageList.CheckedIndices)
275 | {
276 | var toCode = Translator.Codes[(int)index];
277 |
278 | var displayName = Translator.GetDisplayName(toCode);
279 | var cultureName = Translator.GetCultureName(toCode);
280 | var outputFile = $"{filepath}.{cultureName}.resx";
281 |
282 | statusLabel.Text = $"Translating to {toCode} - {displayName} ({cultureName})";
283 |
284 | // load source resx for every target language
285 | // this will be translated in memory and stored for each language
286 | var root = XElement.Load(inputPath);
287 |
288 | var data = ResxProvider.CollectStrings(root);
289 |
290 | if (newStringsBox.Checked && File.Exists(outputFile))
291 | {
292 | data = ResxProvider.CollectNewStrings(data, outputFile);
293 | var span = new TimeSpan(0, 0, (int)(data.Count * 0.1));
294 | estimationLabel.Text = $"{data.Count} {toCode} strings. Estimated completion in {span}";
295 |
296 | // count per file
297 | progressBar.Maximum = data.Count;
298 | progressBar.Value = 0;
299 | }
300 |
301 | try
302 | {
303 | Log($"{NL}Translating {data.Count} strings to {toCode}{NL}", Color.Green);
304 |
305 | translator.LoadHints($"{filepath}.{cultureName}-hints.xml");
306 |
307 | var success = await translator.TranslateResx(
308 | data, fromCode, toCode, cancellation,
309 | (message, color, increment) =>
310 | {
311 | Log(message, color);
312 |
313 | if (increment)
314 | {
315 | progressBar.Increment(1);
316 | }
317 | });
318 |
319 | if (cancellation.IsCancellationRequested)
320 | {
321 | break;
322 | }
323 |
324 | if (success)
325 | {
326 | root = ApplyTranslations(root, data, inputPath, outputFile, out var deleted);
327 |
328 | var hintCount = 0;
329 | if (hintOverrideBox.Checked && translator.Hints.HasElements)
330 | {
331 | hintCount = ResxProvider.MergeHints(root, translator.Hints);
332 | Log($"Merged {hintCount} hints{NL}", Color.Green);
333 | }
334 |
335 | if (data.Count > 0 || hintCount > 0 || deleted > 0)
336 | {
337 | root.Save(outputFile);
338 | Log($"Saved {outputFile}{NL}", Color.Blue);
339 | }
340 | else
341 | {
342 | Log($"No changes{NL}", Color.Blue);
343 | }
344 | }
345 | }
346 | catch (HttpException exc)
347 | {
348 | Log(exc.Message + NL, Color.Red);
349 | }
350 | }
351 |
352 | if (clearBox.Checked && !cancellation.IsCancellationRequested)
353 | {
354 | // we're done with this file so clear the !EDIT markers
355 | var count = Translator.ClearMarkers(inputPath);
356 | if (count > 0)
357 | Log($"Cleared {count} !EDIT markers{NL}");
358 | }
359 |
360 | cancelButton.Visible = false;
361 | restartButton.Visible = true;
362 |
363 | if (cancellation.IsCancellationRequested)
364 | {
365 | statusLabel.Text = "Cancelled";
366 | progressBar.Value = progressBar.Maximum;
367 | }
368 | else
369 | {
370 | statusLabel.Text = "Done";
371 | }
372 |
373 | watch.Stop();
374 | Log($"ellapsed time {watch.ElapsedMilliseconds}ms{NL}", Color.DarkCyan);
375 |
376 | cancellation.Dispose();
377 |
378 | var settings = new SettingsProvider();
379 | settings.Set("inputPath", inputPath);
380 | settings.Save();
381 | }
382 |
383 |
384 | private void LockControls()
385 | {
386 | languageList.Visible = false;
387 | logBox.Visible = true;
388 | logBox.Clear();
389 |
390 | inputBox.Enabled = false;
391 | codeBox.Enabled = false;
392 | browseFileButton.Enabled = false;
393 | outputBox.Enabled = false;
394 | browseFolderButton.Enabled = false;
395 | newStringsBox.Enabled = false;
396 | hintOverrideBox.Enabled = false;
397 | sortBox.Enabled = false;
398 | clearBox.Enabled = false;
399 | }
400 |
401 |
402 | private void Log(string message, Color? color = null)
403 | {
404 | if (color == null || color.Equals(Color.Black))
405 | {
406 | logBox.AppendText(message);
407 | return;
408 | }
409 |
410 | var fore = logBox.SelectionColor;
411 | logBox.SelectionColor = (Color)color;
412 | logBox.AppendText(message);
413 | logBox.SelectionColor = fore;
414 | }
415 |
416 |
417 | private XElement ApplyTranslations(
418 | XElement root, List data, string inputPath, string outputFile,
419 | out int deletedCount)
420 | {
421 | // add or update changes...
422 |
423 | if (newStringsBox.Checked && File.Exists(outputFile))
424 | {
425 | root = XElement.Load(outputFile);
426 | foreach (var d in data)
427 | {
428 | var element = root.Elements("data")
429 | .FirstOrDefault(e => e.Attribute("name").Value == d.Attribute("name").Value);
430 |
431 | if (element != null)
432 | {
433 | if (element.Element("value") is XElement evalue &&
434 | d.Element("value") is XElement dvalue)
435 | {
436 | evalue.Value = dvalue.Value;
437 | }
438 |
439 | // also pick up any changes to the comment
440 | var ecomment = element.Element("comment");
441 | var dcomment = d.Element("comment");
442 | if (dcomment != null)
443 | {
444 | if (ecomment != null)
445 | {
446 | // update comment
447 | ecomment.Value = Translator.ClearMarker(dcomment.Value);
448 | }
449 | else
450 | {
451 | // restore missing comment
452 | element.Add(new XElement("comment",
453 | Translator.ClearMarker(dcomment.Value)));
454 | }
455 | }
456 | }
457 | else
458 | {
459 | root.Add(d);
460 | }
461 | }
462 | }
463 |
464 | // remove deleted items...
465 |
466 | var sourceData = XElement.Load(inputPath).Elements("data");
467 |
468 | var deleted = root.Elements("data")
469 | .Where(e => !sourceData.Any(d => d.Attribute("name").Value == e.Attribute("name").Value))
470 | .ToList();
471 |
472 | deleted.ForEach(d =>
473 | {
474 | Log($"Deleted {d.Attribute("name").Value}{NL}", Color.DarkRed);
475 | });
476 |
477 | deletedCount = deleted.Count;
478 | deleted.Remove();
479 |
480 | if (sortBox.Checked)
481 | {
482 | ResxProvider.SortData(root);
483 | }
484 |
485 | return root;
486 | }
487 |
488 |
489 | private void CancelTranslation(object sender, EventArgs e)
490 | {
491 | cancellation.Cancel();
492 | cancelButton.Enabled = false;
493 | }
494 |
495 |
496 | private void Restart(object sender, EventArgs e)
497 | {
498 | inputBox.Enabled = true;
499 | codeBox.Enabled = true;
500 | browseFileButton.Enabled = true;
501 | outputBox.Enabled = true;
502 | browseFolderButton.Enabled = true;
503 | newStringsBox.Enabled = true;
504 | hintOverrideBox.Enabled = true;
505 | sortBox.Enabled = true;
506 | clearBox.Enabled = true;
507 |
508 | languageList.Visible = true;
509 | logBox.Visible = false;
510 | logBox.Clear();
511 |
512 | statusLabel.Text = "Status...";
513 | progressBar.Value = 0;
514 |
515 | restartButton.Visible = false;
516 | translateButton.Visible = true;
517 | }
518 | }
519 | }
520 |
--------------------------------------------------------------------------------
/Panels/AnalyzeControlPanel.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace ResxTranslator.Panels
2 | {
3 | partial class AnalyzeControlPanel
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 Component 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(AnalyzeControlPanel));
32 | this.introLabel = new System.Windows.Forms.Label();
33 | this.logbox = new System.Windows.Forms.RichTextBox();
34 | this.cmdPanel = new System.Windows.Forms.Panel();
35 | this.analyzeButton = new System.Windows.Forms.Button();
36 | this.sourceBox = new System.Windows.Forms.TextBox();
37 | this.browseButton = new System.Windows.Forms.Button();
38 | this.sourceLabel = new System.Windows.Forms.Label();
39 | this.introPanel = new System.Windows.Forms.Panel();
40 | this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
41 | this.cmdPanel.SuspendLayout();
42 | this.introPanel.SuspendLayout();
43 | this.SuspendLayout();
44 | //
45 | // introLabel
46 | //
47 | this.introLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
48 | this.introLabel.AutoSize = true;
49 | this.introLabel.ImeMode = System.Windows.Forms.ImeMode.NoControl;
50 | this.introLabel.Location = new System.Drawing.Point(6, 26);
51 | this.introLabel.Name = "introLabel";
52 | this.introLabel.Size = new System.Drawing.Size(551, 20);
53 | this.introLabel.TabIndex = 11;
54 | this.introLabel.Text = "Results - entries with either !NODUP or !SKIP in their comments will be ignored";
55 | //
56 | // logbox
57 | //
58 | this.logbox.Dock = System.Windows.Forms.DockStyle.Fill;
59 | this.logbox.Location = new System.Drawing.Point(0, 106);
60 | this.logbox.Margin = new System.Windows.Forms.Padding(0);
61 | this.logbox.Name = "logbox";
62 | this.logbox.Size = new System.Drawing.Size(1013, 651);
63 | this.logbox.TabIndex = 12;
64 | this.logbox.Text = "";
65 | //
66 | // cmdPanel
67 | //
68 | this.cmdPanel.Controls.Add(this.analyzeButton);
69 | this.cmdPanel.Controls.Add(this.sourceBox);
70 | this.cmdPanel.Controls.Add(this.browseButton);
71 | this.cmdPanel.Controls.Add(this.sourceLabel);
72 | this.cmdPanel.Dock = System.Windows.Forms.DockStyle.Top;
73 | this.cmdPanel.Location = new System.Drawing.Point(0, 0);
74 | this.cmdPanel.Name = "cmdPanel";
75 | this.cmdPanel.Size = new System.Drawing.Size(1013, 55);
76 | this.cmdPanel.TabIndex = 10;
77 | //
78 | // analyzeButton
79 | //
80 | this.analyzeButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
81 | this.analyzeButton.BackgroundImage = global::ResxTranslator.Properties.Resources.Translate;
82 | this.analyzeButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center;
83 | this.analyzeButton.Enabled = false;
84 | this.analyzeButton.ImeMode = System.Windows.Forms.ImeMode.NoControl;
85 | this.analyzeButton.Location = new System.Drawing.Point(950, 5);
86 | this.analyzeButton.Name = "analyzeButton";
87 | this.analyzeButton.Size = new System.Drawing.Size(60, 47);
88 | this.analyzeButton.TabIndex = 7;
89 | this.analyzeButton.UseVisualStyleBackColor = true;
90 | this.analyzeButton.Click += new System.EventHandler(this.AnalyzeOnClick);
91 | //
92 | // sourceBox
93 | //
94 | this.sourceBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
95 | | System.Windows.Forms.AnchorStyles.Right)));
96 | this.sourceBox.Location = new System.Drawing.Point(119, 15);
97 | this.sourceBox.Margin = new System.Windows.Forms.Padding(3, 3, 3, 8);
98 | this.sourceBox.Name = "sourceBox";
99 | this.sourceBox.Size = new System.Drawing.Size(759, 26);
100 | this.sourceBox.TabIndex = 5;
101 | this.sourceBox.TextChanged += new System.EventHandler(this.EnableControlsOnTextChanged);
102 | //
103 | // browseButton
104 | //
105 | this.browseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
106 | this.browseButton.AutoSize = true;
107 | this.browseButton.Image = ((System.Drawing.Image)(resources.GetObject("browseButton.Image")));
108 | this.browseButton.ImeMode = System.Windows.Forms.ImeMode.NoControl;
109 | this.browseButton.Location = new System.Drawing.Point(884, 5);
110 | this.browseButton.Name = "browseButton";
111 | this.browseButton.Size = new System.Drawing.Size(60, 47);
112 | this.browseButton.TabIndex = 6;
113 | this.browseButton.UseVisualStyleBackColor = true;
114 | this.browseButton.Click += new System.EventHandler(this.BrowseOnClick);
115 | //
116 | // sourceLabel
117 | //
118 | this.sourceLabel.AutoSize = true;
119 | this.sourceLabel.ImeMode = System.Windows.Forms.ImeMode.NoControl;
120 | this.sourceLabel.Location = new System.Drawing.Point(9, 18);
121 | this.sourceLabel.Name = "sourceLabel";
122 | this.sourceLabel.Size = new System.Drawing.Size(104, 20);
123 | this.sourceLabel.TabIndex = 4;
124 | this.sourceLabel.Text = "Source Resx:";
125 | //
126 | // introPanel
127 | //
128 | this.introPanel.Controls.Add(this.introLabel);
129 | this.introPanel.Dock = System.Windows.Forms.DockStyle.Top;
130 | this.introPanel.Location = new System.Drawing.Point(0, 55);
131 | this.introPanel.Name = "introPanel";
132 | this.introPanel.Size = new System.Drawing.Size(1013, 51);
133 | this.introPanel.TabIndex = 13;
134 | //
135 | // openFileDialog
136 | //
137 | this.openFileDialog.DefaultExt = "resx";
138 | this.openFileDialog.Filter = "Resx Files (*.resx)|*.resx|Hint Files (*-hints.xml)|*-hints.xml";
139 | this.openFileDialog.Title = "Choose source resx file";
140 | //
141 | // AnalyzeControlPanel
142 | //
143 | this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
144 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
145 | this.Controls.Add(this.logbox);
146 | this.Controls.Add(this.introPanel);
147 | this.Controls.Add(this.cmdPanel);
148 | this.Name = "AnalyzeControlPanel";
149 | this.Size = new System.Drawing.Size(1013, 757);
150 | this.cmdPanel.ResumeLayout(false);
151 | this.cmdPanel.PerformLayout();
152 | this.introPanel.ResumeLayout(false);
153 | this.introPanel.PerformLayout();
154 | this.ResumeLayout(false);
155 |
156 | }
157 |
158 | #endregion
159 |
160 | private System.Windows.Forms.Label introLabel;
161 | private System.Windows.Forms.RichTextBox logbox;
162 | private System.Windows.Forms.Panel cmdPanel;
163 | private System.Windows.Forms.Button analyzeButton;
164 | private System.Windows.Forms.TextBox sourceBox;
165 | private System.Windows.Forms.Button browseButton;
166 | private System.Windows.Forms.Label sourceLabel;
167 | private System.Windows.Forms.Panel introPanel;
168 | private System.Windows.Forms.OpenFileDialog openFileDialog;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/Panels/AnalyzeControlPanel.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2020 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | namespace ResxTranslator.Panels
6 | {
7 | using System;
8 | using System.Drawing;
9 | using System.IO;
10 | using System.Linq;
11 | using System.Windows.Forms;
12 | using System.Xml.Linq;
13 |
14 | public partial class AnalyzeControlPanel : UserControl
15 | {
16 | private const string NL = "\n";
17 |
18 |
19 | public AnalyzeControlPanel()
20 | {
21 | InitializeComponent();
22 | }
23 |
24 |
25 | ///
26 | /// Set the initial file path, Called from the main window to inherit the path
27 | /// from the main tab
28 | ///
29 | ///
30 | public void SetFilePath(string path)
31 | {
32 | sourceBox.Text = path;
33 | }
34 |
35 |
36 | private void BrowseOnClick(object sender, EventArgs e)
37 | {
38 | if (openFileDialog.ShowDialog() == DialogResult.OK)
39 | {
40 | sourceBox.Text = openFileDialog.FileName;
41 | }
42 | }
43 |
44 |
45 | private void EnableControlsOnTextChanged(object sender, EventArgs e)
46 | {
47 | analyzeButton.Enabled =
48 | sourceBox.Text.Length > 0 &&
49 | File.Exists(sourceBox.Text);
50 | }
51 |
52 |
53 | private void AnalyzeOnClick(object sender, EventArgs e)
54 | {
55 | logbox.Clear();
56 |
57 | var root = XElement.Load(sourceBox.Text);
58 |
59 | var data = root.Elements("data")
60 | .Where(d => d.Attribute("type") == null)
61 | .Select(d => new
62 | {
63 | Data = d,
64 | Comment = d.Element("comment")?.Value
65 | })
66 | .Where(a => string.IsNullOrWhiteSpace(a.Comment) ||
67 | !(a.Comment.ContainsICIC("!SKIP") || a.Comment.ContainsICIC("!NODUP")))
68 | .Select(d => d.Data);
69 |
70 | Log($"Resx contains {data.Count()} strings" + NL);
71 |
72 | var groups = data.GroupBy(d => d.Element("value").Value);
73 | Log($"Found {groups.Count()} unique strings" + NL);
74 |
75 | var unique = groups.Where(g => g.Count() > 1);
76 | Log($"Found {unique.Count()} duplicate strings" + NL + NL);
77 |
78 | Log(new String('=', 100) + NL);
79 |
80 | var delims = new[] { ' ', '\n', '\r' };
81 |
82 | var words = unique.Where(g => !delims.Any(d => g.Key.Contains(d)));
83 | Log($"found {words.Count()} single-word duplicates" + NL, Color.DarkRed);
84 | foreach (var group in words.OrderBy(w => w.Key))
85 | {
86 | Log(NL + group.Key + NL, Color.Red);
87 | foreach (var item in group)
88 | {
89 | Log($" . . . {item.Attribute("name").Value}" + NL);
90 | }
91 | }
92 |
93 | Log(NL + new String('=', 100) + NL);
94 |
95 | var phrases = unique.Where(g => delims.Any(d => g.Key.Contains(d)));
96 | Log($"found {phrases.Count()} multi-word duplicate phrases" + NL, Color.DarkBlue);
97 | foreach (var group in phrases.OrderBy(p => p.Key))
98 | {
99 | Log(NL + group.Key + NL, Color.Blue);
100 | foreach (var item in group)
101 | {
102 | Log($" . . . {item.Attribute("name").Value}" + NL);
103 | }
104 | }
105 | }
106 |
107 |
108 | private void Log(string message, Color? color = null)
109 | {
110 | if (color == null || color.Equals(Color.Black))
111 | {
112 | logbox.AppendText(message);
113 | return;
114 | }
115 |
116 | var fore = logbox.SelectionColor;
117 | logbox.SelectionColor = (Color)color;
118 | logbox.AppendText(message);
119 | logbox.SelectionColor = fore;
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Panels/AnalyzeControlPanel.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 |
123 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
124 | vAAADrwBlbxySQAAAAd0SU1FB9ULGg0YMGpJmnAAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAu
125 | MjHxIGmVAAABBElEQVQ4T6WRsRGDMAxFPQU7ULtiCNbwCmxAnYo7j5AiDRMwgHsq5qBU9KUYGzDkcine
126 | nbH9voRsiOgviptHlmWhYRgE7wNN00TrugpF4YhznkIIvNRvBM3zTNiX9NfzcSIXjuAMsgToZc/7EADW
127 | /jYkyluASkkWgtt1lGbg+DyFZQG5rAElvgbgd0BePSd20ve9hGmAzCCJ2sEV2klgGUEpgMFaBpq1fMby
128 | VcsDtPKU8qaQ99XLYsSzHGexBdxX/8hcOXhDzmp1CcAwrqvnosqo7hyfxVdAK1odgzGXSNuQufo4jvsA
129 | 0HUdX3CSjgG1bUtN05Bloa5rEaqq2sTI7uN3yLwBpM724PGQQrAAAAAASUVORK5CYII=
130 |
131 |
132 |
133 | 17, 17
134 |
135 |
--------------------------------------------------------------------------------
/Panels/LanguageComboBox.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2020 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | namespace ResxTranslator.Panels
6 | {
7 | using System.Windows.Forms;
8 |
9 |
10 | internal class LanguageComboBox : ComboBox
11 | {
12 | public LanguageComboBox()
13 | {
14 | PopulateLanguages();
15 | }
16 |
17 |
18 | private void PopulateLanguages()
19 | {
20 | foreach (var code in Translator.Codes)
21 | {
22 | Items.Add(Translator.GetDisplayName(code));
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Panels/TextControlPanel.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2020 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | namespace ResxTranslator.Panels
6 | {
7 | using System;
8 | using System.Diagnostics;
9 | using System.Drawing;
10 | using System.Threading;
11 | using System.Threading.Tasks;
12 | using System.Web;
13 | using System.Windows.Forms;
14 |
15 |
16 | public partial class TextControlPanel : UserControl
17 | {
18 | private const string NL = "\n";
19 |
20 | private CancellationTokenSource cancellation;
21 |
22 |
23 | public TextControlPanel()
24 | {
25 | InitializeComponent();
26 | cancellation = new CancellationTokenSource();
27 |
28 | fromCodeBox.Items.Insert(0, "auto (Detect)");
29 | fromCodeBox.SelectedIndex = 0;
30 |
31 | var index = Translator.Codes.IndexOf(
32 | Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName);
33 |
34 | toCodeBox.SelectedIndex = index < 0 ? Translator.Codes.IndexOf("en") : index;
35 | }
36 |
37 |
38 | private void ChangeLanguagesOnSelectedIndexChanged(object sender, EventArgs e)
39 | {
40 | translateButton.Enabled =
41 | fromCodeBox.SelectedIndex >= 0 &&
42 | toCodeBox.SelectedIndex >= 0 &&
43 | textBox.Text.Length > 0;
44 | }
45 |
46 |
47 | private void EnableControlsOnTextChanged(object sender, EventArgs e)
48 | {
49 | translateButton.Enabled = textBox.Text.Trim().Length > 0;
50 | }
51 |
52 |
53 | private async void TranslateOnClick(object sender, EventArgs e)
54 | {
55 | translateButton.Visible = false;
56 | cancelButton.Visible = true;
57 |
58 | logbox.Clear();
59 | logbox.ForeColor = Color.Black;
60 |
61 | var fromCode = fromCodeBox.SelectedIndex == 0
62 | ? "auto"
63 | : Translator.Codes[fromCodeBox.SelectedIndex - 1];
64 |
65 | var toCode = Translator.Codes[toCodeBox.SelectedIndex];
66 |
67 | var translator = new Translator();
68 |
69 | var watch = new Stopwatch();
70 | watch.Start();
71 |
72 | try
73 | {
74 | using (cancellation = new CancellationTokenSource())
75 | {
76 | var parts = textBox.Text.Split('\n');
77 | for (int i = 0; i < parts.Length; i++)
78 | {
79 | var result = await translator.Translate(
80 | parts[i], fromCode, toCode, cancellation);
81 |
82 | watch.Stop();
83 |
84 | if (cancellation.IsCancellationRequested)
85 | {
86 | Log("Cancelled" + NL, Color.Red);
87 | break;
88 | }
89 | else
90 | {
91 | Log(result + NL);
92 |
93 | if (translator.Inflated(parts[i], result))
94 | {
95 | Log("*** possible inflation detected ***" + NL, Color.Maroon);
96 | }
97 |
98 | Log($"ellapsed time {watch.ElapsedMilliseconds}ms{NL}", Color.DarkCyan);
99 | }
100 | }
101 | }
102 | }
103 | catch (TaskCanceledException)
104 | {
105 | Log("Cancelled" + NL, Color.DarkRed);
106 | }
107 | catch (HttpException exc)
108 | {
109 | Log(exc.Message + NL, Color.Red);
110 | }
111 | finally
112 | {
113 | if (watch.IsRunning)
114 | {
115 | watch.Stop();
116 | }
117 | }
118 |
119 | cancelButton.Visible = false;
120 | translateButton.Visible = true;
121 | }
122 |
123 |
124 | private void CancelOnClick(object sender, EventArgs e)
125 | {
126 | cancellation.Cancel();
127 | }
128 |
129 |
130 | private void Log(string message, Color? color = null)
131 | {
132 | if (color == null || color.Equals(Color.Black))
133 | {
134 | logbox.AppendText(message);
135 | return;
136 | }
137 |
138 | var fore = logbox.SelectionColor;
139 | logbox.SelectionColor = (Color)color;
140 | logbox.AppendText(message);
141 | logbox.SelectionColor = fore;
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/Panels/TextControlPanel.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 |
123 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
124 | vAAADrwBlbxySQAAAixJREFUOE+Vks1rE0EYxvdfMGlDLR48VDx4KKgXoeCpUC9+IFhxt6bqwVxUbEVL
125 | xRYqFNuq1KKk1A9QelAQTaEo6EXEbjZNTBs02qClS2k+mrTutkm7ybqbp7PZzSbbBMHDb5h553med3Z2
126 | KAAWZGER8XfDCN+lEbjRDH/XEcwMXQI/+QLrqQ0iserNSV5OIzFxC1x7LabO7KgK69yLiGeCyLcF5LMC
127 | +EcnwdK2qkYLdC1m3W4zpDCkJjtKZmYXOFfDNqMNXtc+eNsMDV2PsOdzIYRShe8IXHDoG0w9vr18CykW
128 | ROjqHtPMXWlB6ncUy55rZiP24iGsxjZBCV/6SaLRiXFg5uEIJOkv5IQe4us4hlV+Bdod8Y9PlU6qneI9
129 | Cyr+pNXoZEDbEXxwD1lZRS75C+vLItTcGvgxYi7XEaaHR0FFR1osxSKhp+NQVfKV+RyWxs9WmAv09IKK
130 | uY9XbHCdJyAkJNJZgiwrUIQ5hLsbK3UD90GtfOwuK9rh62IgJiUomTgiA4fxdbAPG+ks1MwifvYeLNM6
131 | EHr9AZQSn4LPadeLzt2Y/+SHnIlirr9Jr5E7mb59HWlxE2vcKLyMrmXPNyK58Ed/DNHnp0t/or0Bgc4D
132 | +tzERv5GE3zn6vQ17cDss1f6O9AGNb2A8M39pZB/UgN/z+WC2QzQUMR5RIaayWurqWIyaKtDkNxJ0aNh
133 | ToqIwTf4cecoWKbMSO9EoM+FJc5LJFa9ZfH/gNoC52604oOVNVUAAAAASUVORK5CYII=
134 |
135 |
136 |
137 |
138 | iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
139 | vAAADrwBlbxySQAABCFJREFUSEudlG1MW1UYx/thZpl+1US3ROc+7bvyMnFuiW7qZJEQNVgMiLxFYcRO
140 | IE4KbDSwiS4RCYaXZZsEwbFZ3gR5mRKTQkNbShF5K1D6etve9t4LKLDygb/nXNp6gdUhT/LLzT3nPP//
141 | uc9zzpW53e53GYYxu1wubi84nU7KtN1uTzabzQdljwoibiJgJxaLBf39/SAiu+aC9BLDZ4MykYMsWqQJ
142 | BoMBfX196O3tFWltbUV2djYaGxvDY5SJiQnRgHyFxuPxHAvKRA6yWDRoaGhAWloaUlJSRJKTk5GQkAC5
143 | XB4eo6jV6v0Z0FKMj4/DZDKJjI2NQa/Xw2g0Ijc3FzU1NeLYwsLC/gxC6HQ6sSxWq1V8pwaZmZkYGBgA
144 | KWd43b4NJicnxfJQE5vNhvr6eigUil3NdjicGpttHwaU9vZ2scG1tbXIy8tDR0cHEXRsWzNrcWr+mN2n
145 | ATnjKCwsRGJiIlQqFbTGGXSPeNA+7A1zc8A7lVHLZZ2+wr8dXSzhiy2iin2vxirsh7YZ0BprtVqUlZUh
146 | NTUV+fn5SP/4IpKuTSC+kse5L4UwZyr4QNxlwR1TIjijQxRvwxqt9NeFDeg9KC8vF49lRkaGeObn5uZw
147 | q02PsyoGMSVL/x8lvx42GBoaQkFBAZqamrC4uBg+MT2kNPGVwsMF9kD4JlOm5hm0/O6FedEd7keP7uEG
148 | J0qXUNTCoa6fQ3pd5A1Qg2EXEdJOeHDhJoeXLgtQ3PZDP7VlEsng/WoBf1p8WF5ioZvx4eSV7fMhZE6n
149 | O14/zbQVfO+ZjCvhAqGJ4hZ2xDDN3P9h0Gs4e5VflybFkt1XdXNgvCwMsz4IPEs2x4fnpchIcx/rGrYf
150 | OVliT38+x8a8ULS1MKna92Kb1n30kxt8MjktTmnS+a8E/GryieIXbvHwcyx+MfgQK1kTIngbZLLjeQvx
151 | T6ZOO45kWRClFJBWxTxFx+XfLr9JymaXJuXd5mFxsvi6k8Mp1ZJoNu/w4b2q3aUUxWkc/9T61jMZM/an
152 | 06c3j+UsbH52j3+urhOP191fS1I0/e16OVhj+vyu1w9BYPGbyQ/1MOkX6YHXx6KijYts8EHj5hPNmrXq
153 | LkNg5WfjBqR0jW5A2bqKV1TLOEcaTpvqcLMw232YDUL70a3348zV7V8RlN+K63fsh9Qja593jQbYnSZ3
154 | tAG8880KLjbysLpYFP3Ik9ss4LUgFWqOGLLIIc2W9iIo/W9U9Wwe7DQG5J2jG3c7DIG7P+keDDdpHqxl
155 | 3fiLHOElfFTL41IzhzfI70K609evCVDd40gfHmFAY3AQB9pHVw83a1YPV3asf5hUvcLEEXGaQC8YJSQg
156 | hW5g59yW4n/E+evLsSdKhXlp0l6JVvJCUCZynCrFAfJnLItWCv6YEn5jzygFJuoSJ/8HbVeAmG5aes4A
157 | AAAASUVORK5CYII=
158 |
159 |
160 |
--------------------------------------------------------------------------------
/Panels/ToolsControlPanel.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace ResxTranslator.Panels
2 | {
3 | partial class ToolsControlPanel
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 Component 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(ToolsControlPanel));
32 | this.logbox = new System.Windows.Forms.RichTextBox();
33 | this.updateLabel = new System.Windows.Forms.Label();
34 | this.updateButton = new System.Windows.Forms.Button();
35 | this.sortLabel = new System.Windows.Forms.Label();
36 | this.sortButton = new System.Windows.Forms.Button();
37 | this.cmdPanel = new System.Windows.Forms.Panel();
38 | this.sourceBox = new System.Windows.Forms.TextBox();
39 | this.browseButton = new System.Windows.Forms.Button();
40 | this.sourceLabel = new System.Windows.Forms.Label();
41 | this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
42 | this.toolPanel = new System.Windows.Forms.Panel();
43 | this.cmdPanel.SuspendLayout();
44 | this.toolPanel.SuspendLayout();
45 | this.SuspendLayout();
46 | //
47 | // logbox
48 | //
49 | this.logbox.Dock = System.Windows.Forms.DockStyle.Fill;
50 | this.logbox.Location = new System.Drawing.Point(3, 256);
51 | this.logbox.Margin = new System.Windows.Forms.Padding(0);
52 | this.logbox.Name = "logbox";
53 | this.logbox.Size = new System.Drawing.Size(998, 465);
54 | this.logbox.TabIndex = 21;
55 | this.logbox.Text = "";
56 | //
57 | // updateLabel
58 | //
59 | this.updateLabel.AutoSize = true;
60 | this.updateLabel.ImeMode = System.Windows.Forms.ImeMode.NoControl;
61 | this.updateLabel.Location = new System.Drawing.Point(133, 132);
62 | this.updateLabel.Name = "updateLabel";
63 | this.updateLabel.Size = new System.Drawing.Size(475, 20);
64 | this.updateLabel.TabIndex = 20;
65 | this.updateLabel.Text = "Update values in hints file; based on its ./Resources.resx";
66 | //
67 | // updateButton
68 | //
69 | this.updateButton.BackgroundImage = global::ResxTranslator.Properties.Resources.Update;
70 | this.updateButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center;
71 | this.updateButton.Enabled = false;
72 | this.updateButton.ImeMode = System.Windows.Forms.ImeMode.NoControl;
73 | this.updateButton.Location = new System.Drawing.Point(39, 116);
74 | this.updateButton.Name = "updateButton";
75 | this.updateButton.Size = new System.Drawing.Size(85, 47);
76 | this.updateButton.TabIndex = 19;
77 | this.updateButton.UseVisualStyleBackColor = true;
78 | this.updateButton.Click += new System.EventHandler(this.UpdateHintsOnClick);
79 | //
80 | // sortLabel
81 | //
82 | this.sortLabel.AutoSize = true;
83 | this.sortLabel.ImeMode = System.Windows.Forms.ImeMode.NoControl;
84 | this.sortLabel.Location = new System.Drawing.Point(133, 44);
85 | this.sortLabel.Name = "sortLabel";
86 | this.sortLabel.Size = new System.Drawing.Size(287, 20);
87 | this.sortLabel.TabIndex = 18;
88 | this.sortLabel.Text = "Sort all elements in resource file";
89 | //
90 | // sortButton
91 | //
92 | this.sortButton.BackgroundImage = global::ResxTranslator.Properties.Resources.Sort;
93 | this.sortButton.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Center;
94 | this.sortButton.Enabled = false;
95 | this.sortButton.ImeMode = System.Windows.Forms.ImeMode.NoControl;
96 | this.sortButton.Location = new System.Drawing.Point(39, 28);
97 | this.sortButton.Name = "sortButton";
98 | this.sortButton.Size = new System.Drawing.Size(85, 47);
99 | this.sortButton.TabIndex = 17;
100 | this.sortButton.UseVisualStyleBackColor = true;
101 | this.sortButton.Click += new System.EventHandler(this.SortResourcesOnClick);
102 | //
103 | // cmdPanel
104 | //
105 | this.cmdPanel.Controls.Add(this.sourceBox);
106 | this.cmdPanel.Controls.Add(this.browseButton);
107 | this.cmdPanel.Controls.Add(this.sourceLabel);
108 | this.cmdPanel.Dock = System.Windows.Forms.DockStyle.Top;
109 | this.cmdPanel.Location = new System.Drawing.Point(3, 3);
110 | this.cmdPanel.Name = "cmdPanel";
111 | this.cmdPanel.Size = new System.Drawing.Size(998, 55);
112 | this.cmdPanel.TabIndex = 16;
113 | //
114 | // sourceBox
115 | //
116 | this.sourceBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
117 | | System.Windows.Forms.AnchorStyles.Right)));
118 | this.sourceBox.Location = new System.Drawing.Point(119, 15);
119 | this.sourceBox.Margin = new System.Windows.Forms.Padding(3, 3, 3, 8);
120 | this.sourceBox.Name = "sourceBox";
121 | this.sourceBox.Size = new System.Drawing.Size(786, 26);
122 | this.sourceBox.TabIndex = 5;
123 | this.sourceBox.TextChanged += new System.EventHandler(this.EnableControlsOnTextChanged);
124 | //
125 | // browseButton
126 | //
127 | this.browseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
128 | this.browseButton.AutoSize = true;
129 | this.browseButton.Image = ((System.Drawing.Image)(resources.GetObject("browseButton.Image")));
130 | this.browseButton.ImeMode = System.Windows.Forms.ImeMode.NoControl;
131 | this.browseButton.Location = new System.Drawing.Point(911, 5);
132 | this.browseButton.Name = "browseButton";
133 | this.browseButton.Size = new System.Drawing.Size(60, 47);
134 | this.browseButton.TabIndex = 6;
135 | this.browseButton.UseVisualStyleBackColor = true;
136 | this.browseButton.Click += new System.EventHandler(this.BrowseOnClick);
137 | //
138 | // sourceLabel
139 | //
140 | this.sourceLabel.AutoSize = true;
141 | this.sourceLabel.ImeMode = System.Windows.Forms.ImeMode.NoControl;
142 | this.sourceLabel.Location = new System.Drawing.Point(9, 18);
143 | this.sourceLabel.Name = "sourceLabel";
144 | this.sourceLabel.Size = new System.Drawing.Size(104, 20);
145 | this.sourceLabel.TabIndex = 4;
146 | this.sourceLabel.Text = "Source Resx:";
147 | //
148 | // openFileDialog
149 | //
150 | this.openFileDialog.DefaultExt = "resx";
151 | this.openFileDialog.Filter = "Resx Files (*.resx)|*.resx|Hint Files (*-hints.xml)|*-hints.xml";
152 | this.openFileDialog.Title = "Choose source resx file";
153 | //
154 | // toolPanel
155 | //
156 | this.toolPanel.Controls.Add(this.sortButton);
157 | this.toolPanel.Controls.Add(this.sortLabel);
158 | this.toolPanel.Controls.Add(this.updateLabel);
159 | this.toolPanel.Controls.Add(this.updateButton);
160 | this.toolPanel.Dock = System.Windows.Forms.DockStyle.Top;
161 | this.toolPanel.Location = new System.Drawing.Point(3, 58);
162 | this.toolPanel.Name = "toolPanel";
163 | this.toolPanel.Size = new System.Drawing.Size(998, 198);
164 | this.toolPanel.TabIndex = 22;
165 | //
166 | // ToolsControlPanel
167 | //
168 | this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
169 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
170 | this.Controls.Add(this.logbox);
171 | this.Controls.Add(this.toolPanel);
172 | this.Controls.Add(this.cmdPanel);
173 | this.Name = "ToolsControlPanel";
174 | this.Padding = new System.Windows.Forms.Padding(3);
175 | this.Size = new System.Drawing.Size(1004, 724);
176 | this.cmdPanel.ResumeLayout(false);
177 | this.cmdPanel.PerformLayout();
178 | this.toolPanel.ResumeLayout(false);
179 | this.toolPanel.PerformLayout();
180 | this.ResumeLayout(false);
181 |
182 | }
183 |
184 | #endregion
185 |
186 | private System.Windows.Forms.RichTextBox logbox;
187 | private System.Windows.Forms.Label updateLabel;
188 | private System.Windows.Forms.Button updateButton;
189 | private System.Windows.Forms.Label sortLabel;
190 | private System.Windows.Forms.Button sortButton;
191 | private System.Windows.Forms.Panel cmdPanel;
192 | private System.Windows.Forms.TextBox sourceBox;
193 | private System.Windows.Forms.Button browseButton;
194 | private System.Windows.Forms.Label sourceLabel;
195 | private System.Windows.Forms.OpenFileDialog openFileDialog;
196 | private System.Windows.Forms.Panel toolPanel;
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/Panels/ToolsControlPanel.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2020 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | namespace ResxTranslator.Panels
6 | {
7 | using System;
8 | using System.Data;
9 | using System.Drawing;
10 | using System.IO;
11 | using System.Linq;
12 | using System.Windows.Forms;
13 | using System.Xml.Linq;
14 |
15 |
16 | public partial class ToolsControlPanel : UserControl
17 | {
18 | private const string NL = "\n";
19 |
20 |
21 | public ToolsControlPanel()
22 | {
23 | InitializeComponent();
24 | }
25 |
26 |
27 | ///
28 | /// Set the initial file path, Called from the main window to inherit the path
29 | /// from the main tab
30 | ///
31 | ///
32 | public void SetFilePath(string path)
33 | {
34 | sourceBox.Text = path;
35 | }
36 |
37 |
38 | private void EnableControlsOnTextChanged(object sender, EventArgs e)
39 | {
40 | var path = Environment.ExpandEnvironmentVariables(sourceBox.Text.Trim());
41 | if (path.Length > 0 && File.Exists(path))
42 | {
43 | var type = Path.GetExtension(path);
44 | if (type.Equals(".resx", StringComparison.InvariantCultureIgnoreCase))
45 | {
46 | sortButton.Enabled = sortLabel.Enabled = true;
47 | updateButton.Enabled = updateLabel.Enabled = false;
48 | }
49 | else
50 | {
51 | sortButton.Enabled = sortLabel.Enabled = false;
52 | updateButton.Enabled = updateLabel.Enabled = true;
53 | }
54 | }
55 | else
56 | {
57 | sortButton.Enabled = sortLabel.Enabled = false;
58 | updateButton.Enabled = updateLabel.Enabled = false;
59 | }
60 | }
61 |
62 |
63 | private void BrowseOnClick(object sender, EventArgs e)
64 | {
65 | if (openFileDialog.ShowDialog() == DialogResult.OK)
66 | {
67 | sourceBox.Text = openFileDialog.FileName;
68 | }
69 | }
70 |
71 |
72 | ///
73 | /// Intelligently sorts resources in the specified resource file
74 | ///
75 | ///
76 | ///
77 | private void SortResourcesOnClick(object sender, EventArgs e)
78 | {
79 | logbox.Clear();
80 |
81 | var path = Environment.ExpandEnvironmentVariables(sourceBox.Text);
82 | if (File.Exists(path))
83 | {
84 | var root = XElement.Load(path);
85 | ResxProvider.SortData(root);
86 | root.Save(path, SaveOptions.None);
87 | Log($"{Path.GetFileName(path)} sorted and saved{NL}", Color.Green);
88 | }
89 | }
90 |
91 |
92 | ///
93 | ///
94 | ///
95 | ///
96 | ///
97 | private void UpdateHintsOnClick(object sender, EventArgs e)
98 | {
99 | logbox.Clear();
100 |
101 | var path = Environment.ExpandEnvironmentVariables(sourceBox.Text);
102 | if (!File.Exists(path))
103 | {
104 | Log($"could not find {path}{NL}", Color.Red);
105 | return;
106 | }
107 |
108 | var xpath = Path.Combine(Path.GetDirectoryName(path), "Resources.resx");
109 | if (!File.Exists(xpath))
110 | {
111 | Log($"could not find {xpath}{NL}", Color.Red);
112 | return;
113 | }
114 |
115 | var xroot = XElement.Load(xpath);
116 | var hroot = XElement.Load(path);
117 | var updated = 0;
118 | var renamed = 0;
119 | var errors = 0;
120 |
121 | var hints = hroot.Elements("hint").ToList();
122 |
123 | hints.ForEach(hint =>
124 | {
125 | hint.Remove();
126 |
127 | var name = hint.Attribute("name").Value;
128 | var data = xroot.Elements("data")
129 | .FirstOrDefault(d => d.Attribute("name").Value.Equals(
130 | name, StringComparison.InvariantCultureIgnoreCase));
131 |
132 | if (data != null)
133 | {
134 | // should only be a case-difference
135 | var dataname = data.Attribute("name").Value;
136 | if (dataname != name)
137 | {
138 | Log($"correcting name {dataname}{NL}");
139 | hint.Attribute("name").Value = dataname;
140 | renamed++;
141 | }
142 |
143 | var source = hint.Element("source");
144 | if (source != null)
145 | {
146 | var value = data.Element("value").Value.Trim();
147 | if (source.Value.Trim() != value)
148 | {
149 | Log($"updating source for {name}{NL}");
150 | source.Value = data.Element("value").Value;
151 | updated++;
152 | }
153 | }
154 | else
155 | {
156 | Log($"adding missing source for {name}{NL}");
157 | hint.AddFirst(new XElement("source", data.Element("value").Value));
158 | updated++;
159 | }
160 | }
161 | else
162 | {
163 | errors++;
164 | }
165 | });
166 |
167 | Log($"{NL}Summary{NL}", Color.DarkBlue);
168 |
169 | if (updated == 0 && renamed == 0 && errors == 0)
170 | {
171 | Log($"... No updates needed{NL}");
172 | return;
173 | }
174 |
175 | if (updated > 0)
176 | {
177 | Log($"... {updated} sources updated{NL}", Color.Blue);
178 | }
179 |
180 | if (renamed > 0)
181 | {
182 | Log($"... {renamed} name corrections", Color.Blue);
183 | }
184 |
185 | if (errors > 0)
186 | {
187 | Log($"... {errors} errors were found!", Color.Red);
188 | }
189 |
190 | if (updated > 0 || renamed > 0)
191 | {
192 | hroot.Add(hints.OrderBy(d => d.Attribute("name").Value));
193 | hroot.Save(path, SaveOptions.None);
194 | }
195 | }
196 |
197 |
198 | private void Log(string message, Color? color = null)
199 | {
200 | if (color == null || color.Equals(Color.Black))
201 | {
202 | logbox.AppendText(message);
203 | return;
204 | }
205 |
206 | var fore = logbox.SelectionColor;
207 | logbox.SelectionColor = (Color)color;
208 | logbox.AppendText(message);
209 | logbox.SelectionColor = fore;
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/Panels/ToolsControlPanel.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 |
123 | iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
124 | vAAADrwBlbxySQAAAAd0SU1FB9ULGg0YMGpJmnAAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAu
125 | MjHxIGmVAAABBElEQVQ4T6WRsRGDMAxFPQU7ULtiCNbwCmxAnYo7j5AiDRMwgHsq5qBU9KUYGzDkcine
126 | nbH9voRsiOgviptHlmWhYRgE7wNN00TrugpF4YhznkIIvNRvBM3zTNiX9NfzcSIXjuAMsgToZc/7EADW
127 | /jYkyluASkkWgtt1lGbg+DyFZQG5rAElvgbgd0BePSd20ve9hGmAzCCJ2sEV2klgGUEpgMFaBpq1fMby
128 | VcsDtPKU8qaQ99XLYsSzHGexBdxX/8hcOXhDzmp1CcAwrqvnosqo7hyfxVdAK1odgzGXSNuQufo4jvsA
129 | 0HUdX3CSjgG1bUtN05Bloa5rEaqq2sTI7uN3yLwBpM724PGQQrAAAAAASUVORK5CYII=
130 |
131 |
132 |
133 | 17, 17
134 |
135 |
--------------------------------------------------------------------------------
/Program.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2020 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | namespace ResxTranslator
6 | {
7 | using System;
8 | using System.Globalization;
9 | using System.Threading;
10 | using System.Windows.Forms;
11 |
12 |
13 | static class Program
14 | {
15 | ///
16 | /// The main entry point for the application.
17 | ///
18 | [STAThread]
19 | static void Main()
20 | {
21 | var info = CultureInfo.GetCultureInfo("en-US");
22 | Thread.CurrentThread.CurrentCulture = info;
23 | Thread.CurrentThread.CurrentUICulture = info;
24 |
25 | Application.EnableVisualStyles();
26 | Application.SetCompatibleTextRenderingDefault(false);
27 | Application.Run(new MainWindow());
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("RoboTranslator")]
5 | [assembly: AssemblyDescription("")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("")]
8 | [assembly: AssemblyProduct("RoboTranslator")]
9 | [assembly: AssemblyCopyright("Copyright © 2020")]
10 | [assembly: AssemblyTrademark("")]
11 | [assembly: AssemblyCulture("")]
12 |
13 | [assembly: ComVisible(false)]
14 | [assembly: Guid("6542dd09-8229-42dd-ab6d-19aaa1488eb6")]
15 |
16 | [assembly: AssemblyVersion("1.0.1.0")]
17 | [assembly: AssemblyFileVersion("1.0.1.0")]
18 |
--------------------------------------------------------------------------------
/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace ResxTranslator.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ResxTranslator.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to I'm not translated!.
65 | ///
66 | internal static string ConfigString {
67 | get {
68 | return ResourceManager.GetString("ConfigString", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to Foobar.
74 | ///
75 | internal static string DemoString {
76 | get {
77 | return ResourceManager.GetString("DemoString", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to This should be overriden in German.
83 | ///
84 | internal static string OverrideMe {
85 | get {
86 | return ResourceManager.GetString("OverrideMe", resourceCulture);
87 | }
88 | }
89 |
90 | ///
91 | /// Looks up a localized string similar to This will not use the hints file.
92 | ///
93 | internal static string OverrideObsolete {
94 | get {
95 | return ResourceManager.GetString("OverrideObsolete", resourceCulture);
96 | }
97 | }
98 |
99 | ///
100 | /// Looks up a localized resource of type System.Drawing.Bitmap.
101 | ///
102 | internal static System.Drawing.Bitmap Sort {
103 | get {
104 | object obj = ResourceManager.GetObject("Sort", resourceCulture);
105 | return ((System.Drawing.Bitmap)(obj));
106 | }
107 | }
108 |
109 | ///
110 | /// Looks up a localized resource of type System.Drawing.Bitmap.
111 | ///
112 | internal static System.Drawing.Bitmap Translate {
113 | get {
114 | object obj = ResourceManager.GetObject("Translate", resourceCulture);
115 | return ((System.Drawing.Bitmap)(obj));
116 | }
117 | }
118 |
119 | ///
120 | /// Looks up a localized resource of type System.Drawing.Bitmap.
121 | ///
122 | internal static System.Drawing.Bitmap Update {
123 | get {
124 | object obj = ResourceManager.GetObject("Update", resourceCulture);
125 | return ((System.Drawing.Bitmap)(obj));
126 | }
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Properties/Resources.ar-SA.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | فوبار
127 | The bar is fooed
128 |
129 |
130 | ينبغي تجاوز هذا باللغة الألمانية
131 | override example
132 |
133 |
134 | هذا لن يستخدم ملف التلميحات
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.de-DE-hints.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | This should be overriden in German
6 |
7 |
8 | Dies sollte in deutscher Sprache ersetzt werden
9 |
10 |
11 |
12 |
13 | This is an obsolete translation
14 |
15 |
16 | Dies ist eine veraltete Übersetzung
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Properties/Resources.de-DE.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | Foobar
127 | The bar is fooed
128 |
129 |
130 | Dies sollte in deutscher Sprache ersetzt werden
131 | override example
132 |
133 |
134 | Dies ist eine veraltete Übersetzung
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.es-ES.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | Foobar
127 | The bar is fooed
128 |
129 |
130 | Esto debería anularse en alemán.
131 | override example
132 |
133 |
134 | Esto no utilizará el archivo de sugerencias.
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.fr-FR.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | Foobar
127 | The bar is fooed
128 |
129 |
130 | Cela devrait être remplacé en allemand
131 | override example
132 |
133 |
134 | Cela n'utilisera pas le fichier d'astuces
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.he-IL.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | פובר
127 | The bar is fooed
128 |
129 |
130 | יש לעקוף את זה בגרמנית
131 | override example
132 |
133 |
134 | זה לא ישתמש בקובץ הרמזים
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.ja-JP.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | フーバー
127 | The bar is fooed
128 |
129 |
130 | これはドイツ語でオーバーライドされる必要があります
131 | override example
132 |
133 |
134 | これはヒントファイルを使用しません
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.nl-NL.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | Foobar
127 | The bar is fooed
128 |
129 |
130 | In het Duits zou dit moeten worden overschreven
131 | override example
132 |
133 |
134 | Hierdoor wordt het hintbestand niet gebruikt
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.pl-PL.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | Foobara
127 | The bar is fooed
128 |
129 |
130 | Należy to zastąpić w języku niemieckim
131 | override example
132 |
133 |
134 | Nie spowoduje to użycia pliku podpowiedzi
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.pt-BR.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | Foobar
127 | The bar is fooed
128 |
129 |
130 | Isso deve ser substituído em alemão
131 | override example
132 |
133 |
134 | Isso não usará o arquivo de dicas
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | !SKIP
124 |
125 |
126 | Foobar
127 | The bar is fooed
128 |
129 |
130 | This should be overriden in German
131 | override example
132 |
133 |
134 | This will not use the hints file
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Resources.zh-CN.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | I'm not translated!
123 | SKIP
124 |
125 |
126 | 富巴
127 | The bar is fooed
128 |
129 |
130 | 这应该在德语中被覆盖
131 | override example
132 |
133 |
134 | 这不会使用提示文件
135 | obsolete override example
136 |
137 |
138 | ..\images\sort.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
139 |
140 |
141 | ..\Images\Translate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
142 |
143 |
144 | ..\images\update.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
145 |
146 |
--------------------------------------------------------------------------------
/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace ResxTranslator.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ResxTranslator
2 | Translates entire resx files or individual bits of text to multiple languages.
3 |
4 | There are four main tabs
5 |
6 | - Translate Resx file - translates a resx file to the specified languages, fully explained below.
7 | - Translate Text - translates a given text to a selected language. Use this for quick translations
8 | - Analyze - identifies duplicate words and phrases in the resx file, useful for consolidating and cleaning up resource strings
9 | - Tools - Additional resx management tools
10 |
11 | Inspired by https://github.com/salarcode/AutoResxTranslator
12 |
13 | ## Translate Resx File
14 | Translates an entire .resx file to one or more languages.
15 |
16 | 1. Choose a .resx file to translate. It will detect the language based on the language/culture
17 | code in the filename, or English if no code is included. It will remember the last file
18 | translated.
19 | 2. Choose the output directory where new .resx files should be stored. If this is left blank
20 | then new files are stored in the same directory as the source file.
21 | 3. Choose the target translation languages.
22 |
23 | _Options_
24 |
25 | * Enable the _Translate only new strings_ checkbox to translate only new strings that were added
26 | to the source resx file that are not yet in the target resx file(s). It will detect entries
27 | that were deleted in the source file and remove them from the target files. It also detects
28 | the keyword **!EDIT** in the comment of each entry; if this keyword is found, it will translate
29 | that entry and replace the value in all taret files; if this keyword is not found and the
30 | resource identifier exists in both the source and target file, that entry is left untouched.
31 |
32 | * Enable the _Clear markers_ checkbox to remove the **!EDIT** keyword from all entires in the source
33 | resx file. If generating all languages in a single batch then you can safely enable this option.
34 | If translating one language at a time, enable this only when processing the last target file
35 | in your workflow, otherwise subsequent runs may not produce the correct results.
36 |
37 | ### Language Selections
38 |
39 | 
40 |
41 | ### Working
42 |
43 | 
44 |
45 | ### Skipping Resources
46 |
47 | If the resource file includes control or configuration entries that should not be translated
48 | then flag these by including the word **!SKIP** in the entry's comment. It must be capitalized.
49 | The comment can include other text besides the word **!SKIP**.
50 |
51 | ### Using an Override Hint File
52 |
53 | While Google translator is generally quite good, there are nuances in languages that it can't
54 | predict and may make a native speaker question the appropriateness of the translation. For each
55 | language, you can provide an optional hint file that contains preferred translations for each
56 | resource identifier. This must be an XML file located in the same directory as the source resx
57 | file and must be named _name_._code_-hints.xml, for example Resources.de-DE-hints.xml.
58 |
59 | The contents of the hints file should look similar to the following:
60 |
61 | ```xml
62 |
63 |
64 |
65 |
66 | copy of the source text from the main resx file
67 |
68 |
69 | my preferred translation here
70 |
71 |
72 |
73 |
74 | copy of the source text from the main resx file
75 |
76 |
77 | my preferred translation here
78 |
79 |
80 |
81 | ```
82 | When resources are translated to that language, ResxTranslator will first look in this hint file
83 | each resource identifier and use the available text before attempting to use Google translate.
84 |
85 | Notice that the file includes a copy of the original source text. ResxTranslator uses this to
86 | detect if the source item has been updated since this hint was last created and display a warning
87 | that the hint may be out of date and needs correcting.
88 |
89 | ## Translate Text
90 | Translate one string, phrase, or paragraph of text.
91 |
92 | 
93 |
94 | ### Inflation Detection
95 |
96 | The free Google translator will sometime add extra spaces around non-alphanumeric characters
97 | when translating. For example "x+1" may become "x + 1" (from no spaces around the plus sign,
98 | to spaces around the plus sign.) x
99 |
100 |
101 | ResxTranslator attempts to detect this string *inflation* and displays a warning for each
102 | string that may need manual tuning. Of course, the program itself has no way of knowing the
103 | exact context of the translation so it simply compares the number of spaces in the input
104 | string and the output string. Most of the time, the translation is accurate and shouldn't
105 | need to be adjusted manually.
106 |
107 | ## Analyze
108 |
109 | The Analyze tab analyzes a resx file looking for duplicate uses of words and phrase.
110 |
111 | 
112 |
113 | ### Ignoring Known Duplicate
114 |
115 | There will likely be legitimate cases where you want to use duplicates but in very
116 | different contexts. In these case, you can tag those resources with the **!NODUP** keyword;
117 | the analyzer will ignore those resources and not report them as duplicates.
118 |
119 | ## Tools
120 |
121 | The Tools tab contains operations that help maintain resx files.
122 |
123 | 
124 |
125 | ### Sort Data Items
126 | Sorts the `` items within a resx file alphabetically by resourceID.
127 | This makes it easier to find specific resources when viewing the file in a text editor.
128 |
129 | ### Update Hint Sources
130 | Updates the values of the `` elements within an override hints file.
131 | This should be used when the original source text has changed so you can verify
132 | individual items. It can also be used to fill in any missing `` elements or values
133 | or correct the case-sensitive resource name of each hint.
134 |
--------------------------------------------------------------------------------
/ResxProvider.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2023 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | namespace ResxTranslator
6 | {
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Xml.Linq;
10 |
11 |
12 | internal static class ResxProvider
13 | {
14 |
15 | ///
16 | /// Gets a list of all translatable string resources
17 | ///
18 | /// An XElement of the root of a resx file
19 | /// The list of data elements of all translatable strings
20 | public static List CollectStrings(XElement root)
21 | {
22 | /*
23 |
24 | Foobar
25 | NO
26 |
27 | */
28 |
29 | // this should filter out all non-string entries and leave only strings
30 | return root.Elements("data")
31 | .Where(e =>
32 | // string entires always have xml:space= and do not have type=
33 | e.Attributes().Any(a => a.Name.LocalName == "space") &&
34 | e.Attribute("type") == null &&
35 | // TODO: what is this for?
36 | e.Attribute("name")?.Value.StartsWith(">>") != true &&
37 | // !SKIP is a special flag indicating this entry should not be translated
38 | e.Element("comment")?.Value.ContainsICIC("!SKIP") != true)
39 | .ToList();
40 | }
41 |
42 |
43 | ///
44 | /// Filters the data list by keeping only items that don't exist in the
45 | /// specified resx file. This finds all new items that need to be translated
46 | ///
47 | ///
48 | ///
49 | ///
50 | public static List CollectNewStrings(List data, string path)
51 | {
52 | try
53 | {
54 | var target = XElement.Load(path);
55 |
56 | return data.Where(d =>
57 | // string entires always have xml:space= and do not have type=
58 | d.Attributes().Any(a => a.Name.LocalName == "space") &&
59 | d.Attribute("type") == null &&
60 | (
61 | // collect all edited entries
62 | d.Element("comment")?.Value.ContainsICIC("!EDIT") == true ||
63 | // collect entries that don't exist in target
64 | !target.Elements("data")
65 | .Any(e => e.Attribute("name")?.Value == d.Attribute("name").Value)
66 | ))
67 | .ToList();
68 | }
69 | catch
70 | {
71 | // TODO: ...
72 | }
73 |
74 | return data;
75 | }
76 |
77 |
78 | ///
79 | /// Force merge hints from the specified file into the data collection.
80 | ///
81 | /// The data collection to modify
82 | /// The path of the hints file
83 | /// Count of hints applied
84 | public static int MergeHints(XElement root, XElement hints)
85 | {
86 | var count = 0;
87 | foreach (var hint in hints.Elements())
88 | {
89 | var preferred = hint.Element("preferred").Value.Trim();
90 |
91 | var element = root.Elements("data").FirstOrDefault(e =>
92 | e.Attribute("name").Value == hint.Attribute("name").Value &&
93 | e.Element("value").Value != preferred);
94 |
95 | if (element != null)
96 | {
97 | // if found here then we did not find the translated value in the !EDIT
98 | // values but found the named item in the root so overwrite...
99 |
100 | element.Element("value").Value = preferred;
101 | count++;
102 | }
103 | }
104 |
105 | return count;
106 | }
107 |
108 |
109 | ///
110 | /// Sorts all data elements of a given resource file, prioritizing strings first,
111 | /// followed by files. Files are ordered by type, then folder path, then name.
112 | ///
113 | /// The root of the resx file as an XElement
114 | public static void SortData(XElement root)
115 | {
116 | var data = root.Elements("data").ToList();
117 | data.ForEach(d => d.Remove());
118 |
119 | var strings = data
120 | .Where(e => e.Attribute("type") == null)
121 | .OrderBy(e => e.Attribute("name").Value)
122 | .ToList();
123 |
124 | if (strings.Any())
125 | {
126 | root.Add(strings);
127 | }
128 |
129 | // sort files by type, then directory path, then resource name...
130 |
131 | var files = data
132 | .Where(e => e.Attribute("type") != null)
133 | .Select(e => new { Element = e, Values = e.Element("value").Value.Split(';') })
134 | .OrderBy(e => e.Values[1]) // type
135 | .ThenBy(e => e.Values[0]) // path
136 | .ThenBy(e => e.Element.Attribute("name").Value)
137 | .Select(e => e.Element)
138 | .ToList();
139 |
140 | if (files.Any())
141 | {
142 | root.Add(files);
143 | }
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/ResxTranslator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {6542DD09-8229-42DD-AB6D-19AAA1488EB6}
8 | WinExe
9 | ResxTranslator
10 | ResxTranslator
11 | v4.8
12 | 512
13 | true
14 | true
15 |
16 |
17 |
18 |
19 | AnyCPU
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 | packages\GTranslate.2.1.1\lib\netstandard2.0\GTranslate.dll
40 |
41 |
42 | packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll
43 |
44 |
45 | ..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.dll
46 |
47 |
48 | packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll
49 |
50 |
51 |
52 | packages\System.Memory.4.5.5\lib\net461\System.Memory.dll
53 |
54 |
55 |
56 | packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll
57 |
58 |
59 | packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
60 |
61 |
62 | packages\System.Text.Encodings.Web.6.0.0\lib\net461\System.Text.Encodings.Web.dll
63 |
64 |
65 | packages\System.Text.Json.6.0.5\lib\net461\System.Text.Json.dll
66 |
67 |
68 | packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll
69 |
70 |
71 | packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | Form
87 |
88 |
89 | MainWindow.cs
90 |
91 |
92 | UserControl
93 |
94 |
95 | AnalyzeControlPanel.cs
96 |
97 |
98 | Component
99 |
100 |
101 | UserControl
102 |
103 |
104 | TextControlPanel.cs
105 |
106 |
107 | UserControl
108 |
109 |
110 | ToolsControlPanel.cs
111 |
112 |
113 |
114 |
115 | True
116 | True
117 | Resources.resx
118 |
119 |
120 |
121 |
122 |
123 |
124 | MainWindow.cs
125 | Designer
126 |
127 |
128 | AnalyzeControlPanel.cs
129 |
130 |
131 | TextControlPanel.cs
132 |
133 |
134 | ToolsControlPanel.cs
135 |
136 |
137 | ResXFileCodeGenerator
138 | Designer
139 | Resources.Designer.cs
140 |
141 |
142 |
143 | SettingsSingleFileGenerator
144 | Settings.Designer.cs
145 |
146 |
147 | True
148 | Settings.settings
149 | True
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
171 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/ResxTranslator.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30621.155
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResxTranslator", "ResxTranslator.csproj", "{6542DD09-8229-42DD-AB6D-19AAA1488EB6}"
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 | {6542DD09-8229-42DD-AB6D-19AAA1488EB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {6542DD09-8229-42DD-AB6D-19AAA1488EB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {6542DD09-8229-42DD-AB6D-19AAA1488EB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {6542DD09-8229-42DD-AB6D-19AAA1488EB6}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {86F16553-67A2-440B-A718-82946574BC3E}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/SettingsProvider.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2021 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | namespace ResxTranslator
6 | {
7 | using System;
8 | using System.IO;
9 | using System.Linq;
10 | using System.Reflection;
11 | using System.Xml.Linq;
12 |
13 |
14 | ///
15 | /// Loads, save, and manages user settings
16 | ///
17 | internal class SettingsProvider
18 | {
19 | private readonly string path;
20 | private readonly XElement root;
21 |
22 |
23 | ///
24 | /// Initialize a new provider.
25 | ///
26 | public SettingsProvider()
27 | {
28 | var attribute = Assembly.GetExecutingAssembly()
29 | .GetCustomAttributes()
30 | .FirstOrDefault();
31 |
32 | var appData = Path.Combine(
33 | Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
34 | attribute != null ? attribute.Product : "RoboTranslator");
35 |
36 | path = Path.Combine(appData, "Settings.xml");
37 |
38 | if (File.Exists(path))
39 | {
40 | try
41 | {
42 | root = XElement.Load(path);
43 | }
44 | catch
45 | {
46 | //
47 | }
48 | }
49 |
50 | if (root == null)
51 | {
52 | // file not found so initialize with defaults
53 | root = new XElement("settings");
54 | }
55 | }
56 |
57 |
58 | public string Get(string name)
59 | {
60 | var element = root.Elements(name).FirstOrDefault();
61 | if (element != null)
62 | {
63 | return element.Value;
64 | }
65 |
66 | return null;
67 | }
68 |
69 |
70 | public void Set(string name, string value)
71 | {
72 | var element = root.Elements(name).FirstOrDefault();
73 | if (element == null)
74 | {
75 | root.Add(new XElement(name, value));
76 | }
77 | else
78 | {
79 | element.Value = value;
80 | }
81 | }
82 |
83 |
84 | public void Save()
85 | {
86 | var dir = Path.GetDirectoryName(path);
87 | if (!Directory.Exists(dir))
88 | {
89 | try
90 | {
91 | Directory.CreateDirectory(dir);
92 | }
93 | catch
94 | {
95 | //
96 | }
97 | }
98 |
99 | root.Save(path, SaveOptions.None);
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2020 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | namespace ResxTranslator
6 | {
7 | using System;
8 | using System.Text;
9 |
10 |
11 | ///
12 | /// This is a customization of the System.Security.SecurityElement.Escape method
13 | /// that ignores apostrophies (') and double-quotes (")
14 | ///
15 | internal static class StringExtensions
16 | {
17 | private static readonly string[] EscapePairs = new string[]
18 | {
19 | // these must be all once character escape sequences or a new escaping algorithm is needed
20 | "<", "<",
21 | ">", ">",
22 | "&", "&"
23 | };
24 |
25 | private static readonly char[] EscapeChars = new char[] { '<', '>', '&' };
26 |
27 |
28 | ///
29 | /// Compares a string against the given instance, as non-case-sensitive.
30 | ///
31 | /// The string instance
32 | /// The other string for comparison
33 | /// True if the instance contains at least one occurance of value
34 | public static bool ContainsICIC(this string s, string value)
35 | {
36 | return s.IndexOf(value, StringComparison.InvariantCultureIgnoreCase) >= 0;
37 | }
38 |
39 |
40 | ///
41 | /// Escapes special XML characters in the given text values so those characters
42 | /// survive round-trips as user-input text
43 | ///
44 | /// The user input string
45 | /// The user string with special XML characters escaped
46 | public static string XmlEscape(this string str)
47 | {
48 | if (str == null)
49 | return null;
50 |
51 | StringBuilder sb = null;
52 |
53 | int strLen = str.Length;
54 | int index; // Pointer into the string that indicates the location of the current '&' character
55 | int newIndex = 0; // Pointer into the string that indicates the start index of the "remaining" string (that still needs to be processed).
56 |
57 | do
58 | {
59 | index = str.IndexOfAny(EscapeChars, newIndex);
60 |
61 | if (index == -1)
62 | {
63 | if (sb == null)
64 | return str;
65 | else
66 | {
67 | sb.Append(str, newIndex, strLen - newIndex);
68 | return sb.ToString();
69 | }
70 | }
71 | else
72 | {
73 | if (sb == null)
74 | sb = new StringBuilder();
75 |
76 | sb.Append(str, newIndex, index - newIndex);
77 | sb.Append(GetEscapeSequence(str[index]));
78 |
79 | newIndex = (index + 1);
80 | }
81 | }
82 | while (true);
83 |
84 | // no normal exit is possible
85 | }
86 |
87 |
88 | private static string GetEscapeSequence(char c)
89 | {
90 | int iMax = EscapePairs.Length;
91 |
92 | for (int i = 0; i < iMax; i += 2)
93 | {
94 | string strEscSeq = EscapePairs[i];
95 | string strEscValue = EscapePairs[i + 1];
96 |
97 | if (strEscSeq[0] == c)
98 | return strEscValue;
99 | }
100 |
101 | return c.ToString();
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/Translator.cs:
--------------------------------------------------------------------------------
1 | //************************************************************************************************
2 | // Copyright © 2020 Steven M Cohn. All rights reserved.
3 | //************************************************************************************************
4 |
5 | #pragma warning disable S125 // commented out code
6 |
7 | namespace ResxTranslator
8 | {
9 | using GTranslate.Translators;
10 | using System.Collections.Generic;
11 | using System.Drawing;
12 | using System.Globalization;
13 | using System.IO;
14 | using System.Linq;
15 | using System.Text;
16 | using System.Text.RegularExpressions;
17 | using System.Threading;
18 | using System.Threading.Tasks;
19 | using System.Xml.Linq;
20 |
21 |
22 | internal enum Status
23 | {
24 | OK,
25 | Working,
26 | Error,
27 | Message
28 | }
29 |
30 |
31 | internal class Translator
32 | {
33 | //private const int PerEstimate = 1250;
34 | private const int PerEstimate = 100;
35 |
36 |
37 | public static readonly List Codes = new List
38 | {
39 | #region Code
40 | "af", // Afrikaans
41 | "sq", // Albanian
42 | "am", // Amharic
43 | "ar", // Arabic
44 | "hy", // Armenian
45 | "az", // Azerbaijani
46 | "eu", // Basque
47 | "be", // Belarusian
48 | "bn", // Bengali
49 | "bs", // Bosnian
50 | "bg", // Bulgarian
51 | "ca", // Catalan
52 | "ceb", // Cebuano
53 | "ny", // Chichewa - (n/a)
54 | "zh-CN", // Chinese (Simplified)
55 | "zh-TW", // Chinese (Traditional)
56 | "co", // Corsican
57 | "hr", // Croatian
58 | "cs", // Czech
59 | "da", // Danish
60 | "nl", // Dutch
61 | "en", // English
62 | "eo", // Esperanto
63 | "et", // Estonian
64 | "tl", // Filipino = fil-PH
65 | "fi", // Finnish
66 | "fr", // French
67 | "fy", // Frisian
68 | "gl", // Galician
69 | "ka", // Georgian
70 | "de", // German
71 | "el", // Greek
72 | "gu", // Gujarati
73 | "ha", // Hausa
74 | "ht", // Haitian Creole... not supported by Windows?
75 | "haw", // Hawaiian
76 | "he", // Hebrew = he-IL
77 | "hi", // Hindi
78 | "hmn", // Hmong - (n/a)
79 | "hu", // Hungarian
80 | "is", // Icelandic
81 | "ig", // Igbo
82 | "id", // Indonesian
83 | "ga", // Irish
84 | "it", // Italian
85 | "ja", // Japanese
86 | "jw", // Javanese - (n/a)
87 | "kn", // Kannada
88 | "kk", // Kazakh
89 | "km", // Khmer
90 | "rw", // Kinyarwanda
91 | "ko", // Korean
92 | "ku", // Kurdish (Kurmanji)
93 | "ky", // Kyrgyz
94 | "lo", // Lao
95 | "la", // Latin
96 | "lv", // Latvian
97 | "lt", // Lithuanian
98 | "lb", // Luxembourgish
99 | "mk", // Macedonian
100 | "mg", // Malagasy
101 | "ms", // Malay
102 | "ml", // Malayalam
103 | "mt", // Maltese
104 | "mi", // Maori
105 | "mr", // Marathi
106 | "mn", // Mongolian
107 | "my", // Myanmar (Burmese)
108 | "ne", // Nepali
109 | "no", // Norwegian
110 | "or", // Odia (Oriya)
111 | "ps", // Pashto
112 | "fa", // Persian
113 | "pl", // Polish
114 | "pt", // Portuguese
115 | "pa", // Punjabi
116 | "ro", // Romanian
117 | "ru", // Russian
118 | "sm", // Samoan - (n/a)
119 | "sr", // Serbian
120 | "gd", // Scots Gaelic
121 | "st", // Sesotho
122 | "sn", // Shona
123 | "sd", // Sindhi
124 | "si", // Sinhala
125 | "sk", // Slovak
126 | "sl", // Slovenian
127 | "so", // Somali
128 | "es", // Spanish
129 | "su", // Sundanese - (n/a)
130 | "sw", // Swahili
131 | "sv", // Swedish
132 | "tg", // Tajik
133 | "ta", // Tamil
134 | "tt", // Tatar
135 | "te", // Telugu
136 | "th", // Thai
137 | "tr", // Turkish
138 | "tk", // Turkmen
139 | "uk", // Ukrainian
140 | "ur", // Urdu
141 | "ug", // Uyghur
142 | "uz", // Uzbek
143 | "vi", // Vietnamese
144 | "cy", // Welsh
145 | "xh", // Xhosa
146 | "yi", // Yiddis
147 | "yo", // Yoruba
148 | "zu" // Zulu
149 | #endregion Code
150 | };
151 |
152 | private static readonly Dictionary FileCodeMap = new Dictionary
153 | {
154 | { "tl", "fil-PH" },
155 | { "iw", "he-IL" }
156 | };
157 |
158 | private static readonly Dictionary FileNameMap = new Dictionary
159 | {
160 | { "ny", "Chichewa" },
161 | { "hmn", "Hmong" },
162 | { "ht", "Haitian Creole" },
163 | { "jw", "Javanese" },
164 | { "sm", "Samoan" },
165 | { "su", "Sudanese" }
166 | };
167 |
168 | private const string NL = "\n";
169 | private const string InflationPattern = @"([^\s](?:[^\w\d\s\p{L}]))|((?:[^\w\d\s\p{L}])[^\s])";
170 |
171 | private readonly Regex inflation = new Regex(InflationPattern);
172 |
173 |
174 | ///
175 | /// Hints root node
176 | ///
177 | public XElement Hints { get; private set; }
178 |
179 |
180 | // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
181 | // Languages...
182 |
183 | ///
184 | /// Gets the Windows-specific culture name, e.g. en -> en-US, for naming
185 | /// satellite assemblies
186 | ///
187 | ///
188 | ///
189 | public static string GetCultureName(string code)
190 | {
191 | return FileCodeMap.ContainsKey(code)
192 | ? FileCodeMap[code]
193 | : CultureInfo.GetCultureInfo(code).TextInfo.CultureName;
194 | }
195 |
196 |
197 | ///
198 | /// Gets the display name of the language for UI controls
199 | ///
200 | ///
201 | ///
202 | public static string GetDisplayName(string code)
203 | {
204 | if (FileCodeMap.ContainsKey(code))
205 | {
206 | code = FileCodeMap[code];
207 | }
208 |
209 | return FileNameMap.ContainsKey(code)
210 | ? FileNameMap[code]
211 | : CultureInfo.GetCultureInfo(code).DisplayName;
212 | }
213 |
214 |
215 | // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
216 | // Data Management...
217 |
218 | ///
219 | ///
220 | ///
221 | ///
222 | ///
223 | ///
224 | ///
225 | public static bool Estimate(string path, out int strings, out int seconds)
226 | {
227 | return Estimate(path, out strings, PerEstimate, out seconds);
228 | }
229 |
230 |
231 | public static bool Estimate(string path, out int strings, int delayInMs, out int seconds)
232 | {
233 | try
234 | {
235 | var root = XElement.Load(path);
236 | var data = ResxProvider.CollectStrings(root);
237 |
238 | strings = data.Count;
239 | seconds = data.Count * delayInMs / 1000;
240 |
241 | return true;
242 | }
243 | catch
244 | {
245 | strings = seconds = 0;
246 | return false;
247 | }
248 | }
249 |
250 |
251 | // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
252 | // Inflation test...
253 |
254 | public bool Inflated(string original, string translation)
255 | {
256 | if (original == null || translation == null)
257 | {
258 | return false;
259 | }
260 |
261 | var oi = inflation.Matches(original).Count;
262 | var ti = inflation.Matches(translation).Count;
263 | return oi != ti;
264 | }
265 |
266 |
267 | // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
268 | // One...
269 |
270 | ///
271 | ///
272 | ///
273 | ///
274 | ///
275 | ///
276 | ///
277 | public async Task Translate(
278 | string text, string fromCode, string toCode, CancellationTokenSource cancellation,
279 | StatusCallback logger = null)
280 | {
281 | if (text.Trim() == string.Empty)
282 | {
283 | return text;
284 | }
285 |
286 | using (var translator = new AggregateTranslator())
287 | {
288 | if (fromCode == "auto")
289 | {
290 | var lang = await translator.DetectLanguageAsync(text);
291 | fromCode = lang.ISO6391;
292 | }
293 |
294 | var result = await translator.TranslateAsync(text, toCode, fromCode);
295 | return result.Translation;
296 | }
297 | }
298 |
299 |
300 | // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
301 | // Resx...
302 |
303 | /*
304 | *
305 | * NOTE, tried to batch up strings, up to 1K of chars, with a delimiter,
306 | * but the free Google Translator doesn't handle most languages correctly;
307 | * e.g. given a batch with 10 strings, Google may only return 7 of them
308 | * and it's unclear which 7 it will return?
309 | *
310 | */
311 |
312 | public delegate void StatusCallback(string message, Color? color = null, bool increment = false);
313 |
314 |
315 | public void LoadHints(string path)
316 | {
317 | if (File.Exists(path))
318 | {
319 | try
320 | {
321 | Hints = XElement.Load(path);
322 | return;
323 | }
324 | catch
325 | {
326 | // no-op
327 | }
328 | }
329 |
330 | Hints = new XElement("hints");
331 | }
332 |
333 |
334 | ///
335 | ///
336 | ///
337 | ///
338 | ///
339 | ///
340 | ///
341 | ///
342 | public async Task TranslateResx(
343 | List data, string fromCode, string toCode,
344 | CancellationTokenSource cancellation,
345 | StatusCallback logger)
346 | {
347 | int index = 0;
348 | int count = 1;
349 | for (; index < data.Count && !cancellation.IsCancellationRequested; index++, count++)
350 | {
351 | var value = data[index].Element("value").Value;
352 |
353 | if (value.Length == 0)
354 | {
355 | continue;
356 | }
357 |
358 | var editing = data[index].Element("comment")?.Value.ContainsICIC("!EDIT") == true;
359 |
360 | var name = editing
361 | ? $"{data[index].Attribute("name").Value} (EDITED)"
362 | : data[index].Attribute("name").Value;
363 |
364 | logger($"{count}/{data.Count}: {name}", increment: true);
365 |
366 | var hint = FindHint(data[index], logger);
367 | if (hint != null)
368 | {
369 | data[index].Element("value").Value = hint;
370 |
371 | // 2192 is right-arrow
372 | logger($" \u2192 using hint override '{hint}'" + NL, Color.SteelBlue);
373 |
374 | if (editing)
375 | {
376 | logger("Hint override will likely need updating", Color.Red);
377 | }
378 | }
379 | else
380 | {
381 | var builder = new StringBuilder();
382 |
383 | // handle values with multiple lines (comboboxes)
384 | var parts = value.Split('\n');
385 | for (int i = 0; i < parts.Length && !cancellation.IsCancellationRequested; i++)
386 | {
387 | if (parts[i].Trim().Length == 0)
388 | {
389 | builder.Append(NL);
390 | }
391 | else
392 | {
393 | var result = await TranslateWithRetry(
394 | parts[i], fromCode, toCode, cancellation, logger);
395 |
396 | if (!string.IsNullOrEmpty(result) && !cancellation.IsCancellationRequested)
397 | {
398 | builder.Append(result);
399 |
400 | if (i < parts.Length - 1)
401 | builder.Append(NL);
402 | }
403 | }
404 | }
405 |
406 | if (builder.Length > 0)
407 | {
408 | var result = builder.ToString();
409 | data[index].Element("value").Value = result;
410 |
411 | // 2192 is right-arrow
412 | logger($" \u2192 '{value}' to '{result}'" + NL);
413 |
414 | if (Inflated(value, result))
415 | {
416 | logger("*** possible inflation detected ***" + NL, Color.Maroon);
417 | }
418 | }
419 | else
420 | {
421 | logger("unknown error" + NL, Color.Red);
422 | }
423 | }
424 | }
425 |
426 | return index == data.Count;
427 | }
428 |
429 |
430 | private string FindHint(XElement data, StatusCallback logger)
431 | {
432 | var name = data.Attribute("name").Value;
433 | var hint = Hints.Elements()
434 | .FirstOrDefault(e => e.Attribute("name").Value == name);
435 |
436 | if (hint != null)
437 | {
438 | if (data.Element("value").Value.Trim() == hint.Element("source").Value.Trim())
439 | {
440 | return hint.Element("preferred").Value.Trim();
441 | }
442 |
443 | logger($" \u2192 hint override is obsolete", Color.Maroon);
444 | }
445 |
446 | return null;
447 | }
448 |
449 |
450 | private async Task TranslateWithRetry(
451 | string text, string fromCode, string toCode,
452 | CancellationTokenSource cancellation, StatusCallback logger)
453 | {
454 | using (var translator = new AggregateTranslator())
455 | {
456 | var result = await translator.TranslateAsync(text, toCode, fromCode);
457 | return result.Translation;
458 | }
459 | }
460 |
461 |
462 | ///
463 | /// Clear the !EDIT marker in the given data element
464 | ///
465 | /// A translation data element with a comment child
466 | public static string ClearMarker(string comment)
467 | {
468 | return Regex.Replace(comment, @"\b!EDIT\b", string.Empty, RegexOptions.IgnoreCase);
469 | }
470 |
471 |
472 | ///
473 | /// Remove the !EDIT markers from the source file after we're done applying
474 | /// the changes to all output files
475 | ///
476 | ///
477 | public static int ClearMarkers(string path)
478 | {
479 | var root = XElement.Load(path);
480 | var comments = root.Elements("data").Elements("comment")
481 | .Where(e => e.Value.ContainsICIC("!EDIT"));
482 |
483 | if (comments.Any())
484 | {
485 | foreach (var comment in comments)
486 | {
487 | comment.Value = ClearMarker(comment.Value);
488 | }
489 |
490 | root.Save(path);
491 | }
492 |
493 | return comments.Count();
494 | }
495 | }
496 | }
497 |
--------------------------------------------------------------------------------
/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------