From af9cca5fb315bfbe632d37c8250cac6fc6bc76d6 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 12 Jul 2018 20:11:22 +0100 Subject: [PATCH] [pollock] add rufus.loc download and menu selection * Also handle duplicate messages in .pot/.po --- appveyor.yml | 1 + res/localization/Pollock.cs | 249 +++++++++++++++++++++++++++++++----- src/rufus.rc | 10 +- 3 files changed, 222 insertions(+), 38 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index e1a5f615..08bbead6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ skip_commits: files: - res/* - '**/*.cmd' + - '**/*.cs' - '**/*.md' - '**/*.sh' - '**/*.txt' diff --git a/res/localization/Pollock.cs b/res/localization/Pollock.cs index e79a5607..354e9399 100644 --- a/res/localization/Pollock.cs +++ b/res/localization/Pollock.cs @@ -19,11 +19,14 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; +using System.Net; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Threading; [assembly: AssemblyTitle("Pollock")] [assembly: AssemblyDescription("Poedit ↔ Rufus loc conversion utility")] @@ -85,6 +88,11 @@ namespace pollock private const string LANG_LCID = "X-Rufus-LCID"; private static Encoding encoding = new UTF8Encoding(false); private static List rtl_languages = new List { "ar-SA", "he-IL", "fa-IR" }; + private static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + private static WebClient wc = new WebClient(); + private static int download_status; + private static bool in_progress = false; + private static double speed = 0.0f; /// /// Wait for a key to be pressed. @@ -104,11 +112,10 @@ namespace pollock /// /// The directy where the loc file is located. /// A list of Language elements. - static List ParseLocFile(string path) + static List ParseLocFile(string path, string id = null) { var rufus_loc = path + @"\rufus.loc"; var rufus_pot = path + @"\rufus.pot"; - var watch = System.Diagnostics.Stopwatch.StartNew(); var lines = File.ReadAllLines(rufus_loc); int line_nr = 0; string format = "D" + (int)(Math.Log10((double)lines.Count()) + 0.99999); @@ -118,6 +125,9 @@ namespace pollock List parts; List langs = new List(); Language lang = null; + bool skip_line = false; + + sw.Start(); if (!File.Exists(rufus_loc)) { @@ -143,6 +153,8 @@ namespace pollock } if (string.IsNullOrEmpty(data)) continue; + if (skip_line && data[0] != 'l') + continue; switch (data[0]) { case '#': @@ -150,9 +162,6 @@ namespace pollock break; case 'l': comment = null; - if (lang != null) - langs.Add(lang); - lang = new Language(); parts = Regex.Matches(data, @"[\""].+?[\""]|[^ ]+") .Cast() .Select(m => m.Value) @@ -162,6 +171,19 @@ namespace pollock Console.WriteLine("Error: Invalid 'l' command"); return null; } + string lid = parts[1].Replace("\"", ""); + if (id != null) + { + if ((!skip_line) && (id != lid) && (lid != "en-US")) + skip_line = true; + else if (skip_line && (id == lid)) + skip_line = false; + if (skip_line) + break; + } + if (lang != null) + langs.Add(lang); + lang = new Language(); lang.id = parts[1].Replace("\"", ""); lang.name = parts[2].Replace("\"", ""); Console.WriteLine($"Found language {lang.id} '{lang.name}'"); @@ -222,9 +244,10 @@ namespace pollock if (lang != null) langs.Add(lang); - watch.Stop(); + sw.Stop(); Console.WriteLine($"{(cancel_requested ? "CANCELLED after" : "DONE in")}" + - $" {watch.ElapsedMilliseconds / 1000.0}s."); + $" {sw.ElapsedMilliseconds / 1000.0}s."); + sw.Reset(); return langs; } @@ -235,7 +258,7 @@ namespace pollock /// The path where the .po/.pot files should be created. /// A lits of Languages elements /// true on success, false on error. - static bool CreatePoFiles(string path, List langs) + static bool CreatePoFiles(string path, List langs, bool merge_pot = false) { if (langs == null) return false; @@ -244,14 +267,28 @@ namespace pollock if (en_US == null) return false; + var msg_to_ids = new Dictionary>(); + + // Build a dictionary of message string to List so that we can identify duplicates and remove them + foreach (var section in en_US.sections) + { + foreach (var msg in section.Value) + { + if (msg_to_ids.ContainsKey(msg.str)) + msg_to_ids[msg.str].Add(new Id(section.Key, msg.id)); + else + msg_to_ids.Add(msg.str, new List() { new Id(section.Key, msg.id) }); + } + } + foreach (var lang in langs) { bool is_pot = (lang.id == "en-US"); var target = path + @"\" + (is_pot ? "rufus.pot" : lang.id + ".po"); Console.WriteLine($"Creating '{target}'"); + using (var writer = new StreamWriter(target, false, encoding)) { - writer.WriteLine("#, fuzzy"); writer.WriteLine(); writer.WriteLine("msgid \"\""); writer.WriteLine("msgstr \"\""); @@ -272,15 +309,28 @@ namespace pollock writer.WriteLine($"\"X-Rufus-LanguageName: {lang.name}\\n\""); writer.WriteLine($"\"X-Rufus-LCID: {lang.lcid}\\n\""); + var dupes = new List(); + foreach (var section in lang.sections) { foreach (var msg in section.Value) { + var en_str = en_US.sections[section.Key].Find(x => x.id == msg.id).str; + + // Handle duplicates + if (dupes.Contains(en_str)) + continue; writer.WriteLine(); - if (section.Key == "MSG") - writer.WriteLine($"#. • {msg.id}"); - else - writer.WriteLine($"#. • {section.Key} → {msg.id}"); + foreach (var id in msg_to_ids[en_str]) + { + if (id.group == "MSG") + writer.WriteLine($"#. • {id.id}"); + else + writer.WriteLine($"#. • {id.group} → {id.id}"); + } + if (msg_to_ids[en_str].Count > 1) + dupes.Add(en_str); + if (lang.comments.ContainsKey(msg.id)) { if (is_pot) @@ -296,7 +346,7 @@ namespace pollock } else { - writer.WriteLine($"msgid {en_US.sections[section.Key].Find(x => x.id == msg.id).str}"); + writer.WriteLine($"msgid {en_str}"); writer.WriteLine($"msgstr {msg.str}"); } } @@ -321,7 +371,6 @@ namespace pollock } Console.WriteLine($"Importing data from '{file}':"); bool is_pot = file.EndsWith(".pot"); - var watch = System.Diagnostics.Stopwatch.StartNew(); var lines = File.ReadAllLines(file); string format = "D" + (int)(Math.Log10((double)lines.Count()) + 0.99999); int line_nr = 0; @@ -332,6 +381,9 @@ namespace pollock List comments = new List(); List codes = new List(); int msg_type = 0; + + sw.Start(); + foreach (var line in lines) { if (cancel_requested) @@ -444,9 +496,10 @@ namespace pollock // Sort the MSG section alphabetically lang.sections["MSG"] = lang.sections["MSG"].OrderBy(x => x.id).ToList(); - watch.Stop(); + sw.Stop(); Console.WriteLine($"{(cancel_requested ? "CANCELLED after" : "DONE in")}" + - $" {watch.ElapsedMilliseconds / 1000.0}s."); + $" {sw.ElapsedMilliseconds / 1000.0}s."); + sw.Reset(); return lang; } @@ -496,8 +549,6 @@ namespace pollock { if (lang == null) return false; - Encoding encoding = new UTF8Encoding(false); - var watch = System.Diagnostics.Stopwatch.StartNew(); var target = path + @"\rufus.loc"; var lines = File.ReadAllLines(target); using (var writer = new StreamWriter(target, false, encoding)) @@ -532,9 +583,10 @@ namespace pollock { if ((list == null) || (list.Count == 0)) return false; - - var watch = System.Diagnostics.Stopwatch.StartNew(); var target = path + @"\rufus.loc"; + + sw.Start(); + Console.WriteLine($"Creating '{target}':"); using (var writer = new StreamWriter(target, false, encoding)) { @@ -559,12 +611,83 @@ namespace pollock WriteLoc(writer, lang); } } - watch.Stop(); + + sw.Stop(); Console.WriteLine($"{(cancel_requested ? "CANCELLED after" : "DONE in")}" + - $" {watch.ElapsedMilliseconds / 1000.0}s."); + $" {sw.ElapsedMilliseconds / 1000.0}s."); + sw.Reset(); + return true; } + static bool DownloadFile(string url, string dest) + { + download_status = 0; + in_progress = false; + using (wc) + { + wc.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted); + wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress); + + Console.WriteLine($"Downloading {url}:"); + sw.Start(); + + try + { + wc.DownloadFileAsync(new Uri(url), dest); + } + catch (Exception e) + { + Console.WriteLine("ERROR: " + e.Message); + return false; + } + } + while (download_status == 0) + Thread.Sleep(100); + + Console.WriteLine(); + if (download_status == 1) + { + Console.WriteLine("Download complete"); + return true; + } + + Console.WriteLine("Download has been canceled."); + return false; + } + + // The event that will fire whenever the progress of the WebClient is changed + static void DownloadProgress(object sender, DownloadProgressChangedEventArgs e) + { + if (cancel_requested) + { + wc.CancelAsync(); + return; + } + if (in_progress) + return; + + // Prevent this call from being re-entrant + in_progress = true; + + speed = (e.BytesReceived / 1024d / sw.Elapsed.TotalSeconds); + Console.SetCursorPosition(0, Console.CursorTop); + Console.Write($" {e.ProgressPercentage.ToString("000.0")} % ({speed.ToString("0.00")} KB/s)"); + in_progress = false; + } + + // The event that will trigger when the WebClient is completed + static void DownloadCompleted(object sender, AsyncCompletedEventArgs e) + { + if (!e.Cancelled) + { + Console.SetCursorPosition(0, Console.CursorTop); + Console.Write($" 100.0 % ({speed.ToString("0.00")} KB/s)"); + } + sw.Reset(); + download_status = (e.Cancelled) ? 2 : 1; + } + static void Main(string[] args) { Console.OutputEncoding = System.Text.Encoding.UTF8; @@ -576,23 +699,83 @@ namespace pollock Console.WriteLine($"{app_name} {app_version} - Poedit to rufus.loc conversion utility"); var path = @"C:\pollock"; + var loc = path + @"\download.loc"; + + // Download the loc file + //var url = "https://github.com/pbatard/rufus/raw/master/res/localization/rufus.loc"; + //if (!DownloadFile(url, loc)) + // goto Exit; + + // Convert to CRLF and get all the language ids + var lines = File.ReadAllLines(loc); + string id = "", name = ""; + var list = new List(); + using (var writer = new StreamWriter(loc, false, encoding)) + { + foreach (var line in lines) + { + if (line.StartsWith("l ")) + { + var el = line.Split('\"'); + id = el[1]; + name = el[3].Split('(')[0].Trim(); + } + else if (line.StartsWith("v ")) + { + if (id != "en-US") + list.Add(new string[] { name, id, line.Substring(2) }); + } + writer.WriteLine(line); + } + } + +Menu: + Console.WriteLine(); + Console.WriteLine("Please enter the number of the language you want to edit or 'q' to quit:"); + Console.WriteLine(); + int split = (list.Count + 1) / 2; + for (int i = 0; i < split; i++) + { + name = $"{list[i][0]} ({list[i][1]})"; + Console.Write($"[{(i+1).ToString("00")}] {name,-29} (v{list[i][2]})"); + name = $"{list[i + split][0]} ({list[i + split][1]})"; + Console.WriteLine($" | [{(i + 1 + split).ToString("00")}] {name,-29} (v{list[i + split][2]})"); + } + Console.WriteLine(); + +Retry: + string input = Console.ReadLine(); + if (input.StartsWith("q")) + goto Exit; + if (!Int32.TryParse(input, out int number) || (number <= 0) || (number > list.Count)) + { + if (input.StartsWith("m")) + goto Menu; + Console.WriteLine("Invalid selection (Type 'm' to display the menu again)"); + goto Retry; + } + + number--; + Console.WriteLine($"{list[number][0]} was selected"); + CreatePoFiles(path, ParseLocFile(path, list[number][1])); // NB: Can find PoEdit from Computer\HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache //CreatePoFiles(path, ParseLocFile(@"C:\rufus\res\localization")); - var en_US = ParsePoFile(path + @"\rufus.pot"); - var fr_FR = ParsePoFile(path + @"\fr-FR.po"); - var ar_SA = ParsePoFile(path + @"\ar-SA.po"); - var vi_VN = ParsePoFile(path + @"\vi-VN.po"); - List list = new List(); - list.Add(en_US); - list.Add(ar_SA); - list.Add(fr_FR); - list.Add(vi_VN); - SaveLocFile(path, list); + //var en_US = ParsePoFile(path + @"\rufus.pot"); + //var fr_FR = ParsePoFile(path + @"\fr-FR.po"); + //var ar_SA = ParsePoFile(path + @"\ar-SA.po"); + //var vi_VN = ParsePoFile(path + @"\vi-VN.po"); + //List list = new List(); + //list.Add(en_US); + //list.Add(ar_SA); + //list.Add(fr_FR); + //list.Add(vi_VN); + //SaveLocFile(path, list); // UpdateLocFile(path + @"\test", fr_FR); +Exit: WaitForKey(); } } diff --git a/src/rufus.rc b/src/rufus.rc index b9afde4f..3dac1411 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 232, 326 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_ACCEPTFILES -CAPTION "Rufus 3.2.1334" +CAPTION "Rufus 3.2.1335" FONT 9, "Segoe UI Symbol", 400, 0, 0x0 BEGIN LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP @@ -392,8 +392,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 3,2,1334,0 - PRODUCTVERSION 3,2,1334,0 + FILEVERSION 3,2,1335,0 + PRODUCTVERSION 3,2,1335,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -411,13 +411,13 @@ BEGIN VALUE "Comments", "https://akeo.ie" VALUE "CompanyName", "Akeo Consulting" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "3.2.1334" + VALUE "FileVersion", "3.2.1335" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2018 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "https://www.gnu.org/copyleft/gpl.html" VALUE "OriginalFilename", "rufus-3.2.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "3.2.1334" + VALUE "ProductVersion", "3.2.1335" END END BLOCK "VarFileInfo"