From 0c0ff25cb0d8a88c1d7f29d549f1fdde5506de07 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Wed, 6 Jul 2022 02:09:10 +0200 Subject: [PATCH] Add TranslationChecker to check for incorrect format strings --- .gitignore | 1 - TranslationChecker.exe | Bin 0 -> 28160 bytes TranslationChecker/.gitignore | 10 + TranslationChecker/TranslationChecker.sln | 25 ++ .../TranslationChecker/App.config | 6 + .../TranslationChecker/LanguageCodes.cs | 337 ++++++++++++++++++ .../TranslationChecker/Program.cs | 296 +++++++++++++++ .../Properties/AssemblyInfo.cs | 36 ++ .../TranslationChecker.csproj | 54 +++ crowdin-sync.bat | 19 +- 10 files changed, 776 insertions(+), 8 deletions(-) create mode 100644 TranslationChecker.exe create mode 100644 TranslationChecker/.gitignore create mode 100644 TranslationChecker/TranslationChecker.sln create mode 100644 TranslationChecker/TranslationChecker/App.config create mode 100644 TranslationChecker/TranslationChecker/LanguageCodes.cs create mode 100644 TranslationChecker/TranslationChecker/Program.cs create mode 100644 TranslationChecker/TranslationChecker/Properties/AssemblyInfo.cs create mode 100644 TranslationChecker/TranslationChecker/TranslationChecker.csproj diff --git a/.gitignore b/.gitignore index 7f53c4f..8168521 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ *.qm *.jar *.zip -*.exe translations/ diff --git a/TranslationChecker.exe b/TranslationChecker.exe new file mode 100644 index 0000000000000000000000000000000000000000..3bb22315db08d5a0a12ee0d3d40bb6e85a5f6671 GIT binary patch literal 28160 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 L{mak)cNX|xLW;sU literal 0 HcmV?d00001 diff --git a/TranslationChecker/.gitignore b/TranslationChecker/.gitignore new file mode 100644 index 0000000..2e6ffad --- /dev/null +++ b/TranslationChecker/.gitignore @@ -0,0 +1,10 @@ +.vs/ +bin/ +obj/ +Release/ +Debug/ +x64/ +*.aps +*.vcxproj.user +packages/ +*.user diff --git a/TranslationChecker/TranslationChecker.sln b/TranslationChecker/TranslationChecker.sln new file mode 100644 index 0000000..0161a02 --- /dev/null +++ b/TranslationChecker/TranslationChecker.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32616.157 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TranslationChecker", "TranslationChecker\TranslationChecker.csproj", "{7D7EEC8B-34FF-43E7-AC2B-CB0A68334CFA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7D7EEC8B-34FF-43E7-AC2B-CB0A68334CFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D7EEC8B-34FF-43E7-AC2B-CB0A68334CFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D7EEC8B-34FF-43E7-AC2B-CB0A68334CFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D7EEC8B-34FF-43E7-AC2B-CB0A68334CFA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {ED6D654E-2A4F-46AD-9001-5E0E306691A6} + EndGlobalSection +EndGlobal diff --git a/TranslationChecker/TranslationChecker/App.config b/TranslationChecker/TranslationChecker/App.config new file mode 100644 index 0000000..5754728 --- /dev/null +++ b/TranslationChecker/TranslationChecker/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/TranslationChecker/TranslationChecker/LanguageCodes.cs b/TranslationChecker/TranslationChecker/LanguageCodes.cs new file mode 100644 index 0000000..7f08f9b --- /dev/null +++ b/TranslationChecker/TranslationChecker/LanguageCodes.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TranslationChecker +{ + static class LanguageCodes + { + public static string GetLanguageName(string code) + { + if (Values.ContainsKey(code)) + return Values[code]; + return $"Unknown, {code}"; + } + + // https://developer.crowdin.com/language-codes/ + public static Dictionary Values = new Dictionary + { + { "ach", "Acholi" }, + { "aa", "Afar" }, + { "af", "Afrikaans" }, + { "ak", "Akan" }, + { "tw", "Akan, Twi" }, + { "sq", "Albanian" }, + { "am", "Amharic" }, + { "ar", "Arabic" }, + { "ar-BH", "Arabic, Bahrain" }, + { "ar-EG", "Arabic, Egypt" }, + { "ar-SA", "Arabic, Saudi Arabia" }, + { "ar-YE", "Arabic, Yemen" }, + { "an", "Aragonese" }, + { "hy-AM", "Armenian" }, + { "frp", "Arpitan" }, + { "as", "Assamese" }, + { "ast", "Asturian" }, + { "tay", "Atayal" }, + { "av", "Avaric" }, + { "ae", "Avestan" }, + { "ay", "Aymara" }, + { "az", "Azerbaijani" }, + { "ban", "Balinese" }, + { "bal", "Balochi" }, + { "bm", "Bambara" }, + { "ba", "Bashkir" }, + { "eu", "Basque" }, + { "be", "Belarusian" }, + { "bn", "Bengali" }, + { "bn-IN", "Bengali, India" }, + { "ber", "Berber" }, + { "bh", "Bihari" }, + { "bfo", "Birifor" }, + { "bi", "Bislama" }, + { "bs", "Bosnian" }, + { "br-FR", "Breton" }, + { "bg", "Bulgarian" }, + { "my", "Burmese" }, + { "ca", "Catalan" }, + { "ceb", "Cebuano" }, + { "ch", "Chamorro" }, + { "ce", "Chechen" }, + { "chr", "Cherokee" }, + { "ny", "Chewa" }, + { "zh-CN", "Chinese Simplified" }, + { "zh-TW", "Chinese Traditional" }, + { "zh-HK", "Chinese Traditional, Hong Kong" }, + { "zh-MO", "Chinese Traditional, Macau" }, + { "zh-SG", "Chinese Traditional, Singapore" }, + { "cv", "Chuvash" }, + { "kw", "Cornish" }, + { "co", "Corsican" }, + { "cr", "Cree" }, + { "hr", "Croatian" }, + { "cs", "Czech" }, + { "da", "Danish" }, + { "fa-AF", "Dari" }, + { "dv", "Dhivehi" }, + { "nl", "Dutch" }, + { "nl-BE", "Dutch, Belgium" }, + { "nl-SR", "Dutch, Suriname" }, + { "dz", "Dzongkha" }, + { "en", "English" }, + { "en-UD", "English (upside down)" }, + { "en-AR", "English, Arabia" }, + { "en-AU", "English, Australia" }, + { "en-BZ", "English, Belize" }, + { "en-CA", "English, Canada" }, + { "en-CB", "English, Caribbean" }, + { "en-CN", "English, China" }, + { "en-DK", "English, Denmark" }, + { "en-HK", "English, Hong Kong" }, + { "en-IN", "English, India" }, + { "en-ID", "English, Indonesia" }, + { "en-IE", "English, Ireland" }, + { "en-JM", "English, Jamaica" }, + { "en-JA", "English, Japan" }, + { "en-MY", "English, Malaysia" }, + { "en-NZ", "English, New Zealand" }, + { "en-NO", "English, Norway" }, + { "en-PH", "English, Philippines" }, + { "en-PR", "English, Puerto Rico" }, + { "en-SG", "English, Singapore" }, + { "en-ZA", "English, South Africa" }, + { "en-SE", "English, Sweden" }, + { "en-GB", "English, United Kingdom" }, + { "en-US", "English, United States" }, + { "en-ZW", "English, Zimbabwe" }, + { "eo", "Esperanto" }, + { "et", "Estonian" }, + { "ee", "Ewe" }, + { "fo", "Faroese" }, + { "fj", "Fijian" }, + { "fil", "Filipino" }, + { "fi", "Finnish" }, + { "vls-BE", "Flemish" }, + { "fra-DE", "Franconian" }, + { "fr", "French" }, + { "fr-BE", "French, Belgium" }, + { "fr-CA", "French, Canada" }, + { "fr-LU", "French, Luxembourg" }, + { "fr-QC", "French, Quebec" }, + { "fr-CH", "French, Switzerland" }, + { "fy-NL", "Frisian" }, + { "fur-IT", "Friulian" }, + { "ff", "Fula" }, + { "gaa", "Ga" }, + { "gl", "Galician" }, + { "ka", "Georgian" }, + { "de", "German" }, + { "de-AT", "German, Austria" }, + { "de-BE", "German, Belgium" }, + { "de-LI", "German, Liechtenstein" }, + { "de-LU", "German, Luxembourg" }, + { "de-CH", "German, Switzerland" }, + { "got", "Gothic" }, + { "el", "Greek" }, + { "el-CY", "Greek, Cyprus" }, + { "kl", "Greenlandic" }, + { "gn", "Guarani" }, + { "gu-IN", "Gujarati" }, + { "ht", "Haitian Creole" }, + { "ha", "Hausa" }, + { "haw", "Hawaiian" }, + { "he", "Hebrew" }, + { "hz", "Herero" }, + { "hil", "Hiligaynon" }, + { "hi", "Hindi" }, + { "ho", "Hiri Motu" }, + { "hmn", "Hmong" }, + { "hu", "Hungarian" }, + { "is", "Icelandic" }, + { "ido", "Ido" }, + { "ig", "Igbo" }, + { "ilo", "Ilokano" }, + { "id", "Indonesian" }, + { "iu", "Inuktitut" }, + { "ga-IE", "Irish" }, + { "it", "Italian" }, + { "it-CH", "Italian, Switzerland" }, + { "ja", "Japanese" }, + { "jv", "Javanese" }, + { "quc", "K'iche'" }, + { "kab", "Kabyle" }, + { "kn", "Kannada" }, + { "pam", "Kapampangan" }, + { "ks", "Kashmiri" }, + { "ks-PK", "Kashmiri, Pakistan" }, + { "csb", "Kashubian" }, + { "kk", "Kazakh" }, + { "km", "Khmer" }, + { "rw", "Kinyarwanda" }, + { "tlh-AA", "Klingon" }, + { "kv", "Komi" }, + { "kg", "Kongo" }, + { "kok", "Konkani" }, + { "ko", "Korean" }, + { "ku", "Kurdish" }, + { "kmr", "Kurmanji (Kurdish)" }, + { "kj", "Kwanyama" }, + { "ky", "Kyrgyz" }, + { "lol", "LOLCAT" }, + { "lo", "Lao" }, + { "la-LA", "Latin" }, + { "lv", "Latvian" }, + { "lij", "Ligurian" }, + { "li", "Limburgish" }, + { "ln", "Lingala" }, + { "lt", "Lithuanian" }, + { "jbo", "Lojban" }, + { "nds", "Low German" }, + { "dsb-DE", "Lower Sorbian" }, + { "lg", "Luganda" }, + { "luy", "Luhya" }, + { "lb", "Luxembourgish" }, + { "mk", "Macedonian" }, + { "mai", "Maithili" }, + { "mg", "Malagasy" }, + { "ms", "Malay" }, + { "ms-BN", "Malay, Brunei" }, + { "ml-IN", "Malayalam" }, + { "mt", "Maltese" }, + { "gv", "Manx" }, + { "mi", "Maori" }, + { "arn", "Mapudungun" }, + { "mr", "Marathi" }, + { "mh", "Marshallese" }, + { "moh", "Mohawk" }, + { "mn", "Mongolian" }, + { "sr-Cyrl-ME", "Montenegrin (Cyrillic)" }, + { "me", "Montenegrin (Latin)" }, + { "mos", "Mossi" }, + { "na", "Nauru" }, + { "ng", "Ndonga" }, + { "ne-NP", "Nepali" }, + { "ne-IN", "Nepali, India" }, + { "pcm", "Nigerian Pidgin" }, + { "se", "Northern Sami" }, + { "nso", "Northern Sotho" }, + { "no", "Norwegian" }, + { "nb", "Norwegian Bokmal" }, + { "nn-NO", "Norwegian Nynorsk" }, + { "oc", "Occitan" }, + { "or", "Odia" }, + { "oj", "Ojibwe" }, + { "om", "Oromo" }, + { "os", "Ossetian" }, + { "pi", "Pali" }, + { "pap", "Papiamento" }, + { "ps", "Pashto" }, + { "fa", "Persian" }, + { "en-PT", "Pirate English" }, + { "pl", "Polish" }, + { "pt-PT", "Portuguese" }, + { "pt-BR", "Portuguese, Brazilian" }, + { "pa-IN", "Punjabi" }, + { "pa-PK", "Punjabi, Pakistan" }, + { "qu", "Quechua" }, + { "qya-AA", "Quenya" }, + { "ro", "Romanian" }, + { "rm-CH", "Romansh" }, + { "rn", "Rundi" }, + { "ru", "Russian" }, + { "ru-BY", "Russian, Belarus" }, + { "ru-MD", "Russian, Moldova" }, + { "ru-UA", "Russian, Ukraine" }, + { "ry-UA", "Rusyn" }, + { "sah", "Sakha" }, + { "sg", "Sango" }, + { "sa", "Sanskrit" }, + { "sat", "Santali" }, + { "sc", "Sardinian" }, + { "sco", "Scots" }, + { "gd", "Scottish Gaelic" }, + { "sr", "Serbian (Cyrillic)" }, + { "sr-CS", "Serbian (Latin)" }, + { "sh", "Serbo-Croatian" }, + { "crs", "Seychellois Creole" }, + { "sn", "Shona" }, + { "ii", "Sichuan Yi" }, + { "sd", "Sindhi" }, + { "si-LK", "Sinhala" }, + { "sk", "Slovak" }, + { "sl", "Slovenian" }, + { "so", "Somali" }, + { "son", "Songhay" }, + { "ckb", "Sorani (Kurdish)" }, + { "nr", "Southern Ndebele" }, + { "sma", "Southern Sami" }, + { "st", "Southern Sotho" }, + { "es-ES", "Spanish" }, + { "es-EM", "Spanish (Modern)" }, + { "es-AR", "Spanish, Argentina" }, + { "es-BO", "Spanish, Bolivia" }, + { "es-CL", "Spanish, Chile" }, + { "es-CO", "Spanish, Colombia" }, + { "es-CR", "Spanish, Costa Rica" }, + { "es-DO", "Spanish, Dominican Republic" }, + { "es-EC", "Spanish, Ecuador" }, + { "es-SV", "Spanish, El Salvador" }, + { "es-GT", "Spanish, Guatemala" }, + { "es-HN", "Spanish, Honduras" }, + { "es-419", "Spanish, Latin America" }, + { "es-MX", "Spanish, Mexico" }, + { "es-NI", "Spanish, Nicaragua" }, + { "es-PA", "Spanish, Panama" }, + { "es-PY", "Spanish, Paraguay" }, + { "es-PE", "Spanish, Peru" }, + { "es-PR", "Spanish, Puerto Rico" }, + { "es-US", "Spanish, United States" }, + { "es-UY", "Spanish, Uruguay" }, + { "es-VE", "Spanish, Venezuela" }, + { "su", "Sundanese" }, + { "sw", "Swahili" }, + { "sw-KE", "Swahili, Kenya" }, + { "sw-TZ", "Swahili, Tanzania" }, + { "ss", "Swati" }, + { "sv-SE", "Swedish" }, + { "sv-FI", "Swedish, Finland" }, + { "syc", "Syriac" }, + { "tl", "Tagalog" }, + { "ty", "Tahitian" }, + { "tg", "Tajik" }, + { "tzl", "Talossan" }, + { "ta", "Tamil" }, + { "tt-RU", "Tatar" }, + { "te", "Telugu" }, + { "kdh", "Tem (Kotokoli)" }, + { "th", "Thai" }, + { "bo-BT", "Tibetan" }, + { "ti", "Tigrinya" }, + { "ts", "Tsonga" }, + { "tn", "Tswana" }, + { "tr", "Turkish" }, + { "tr-CY", "Turkish, Cyprus" }, + { "tk", "Turkmen" }, + { "uk", "Ukrainian" }, + { "hsb-DE", "Upper Sorbian" }, + { "ur-IN", "Urdu (India)" }, + { "ur-PK", "Urdu (Pakistan)" }, + { "ug", "Uyghur" }, + { "uz", "Uzbek" }, + { "val-ES", "Valencian" }, + { "ve", "Venda" }, + { "vec", "Venetian" }, + { "vi", "Vietnamese" }, + { "wa", "Walloon" }, + { "cy", "Welsh" }, + { "wo", "Wolof" }, + { "xh", "Xhosa" }, + { "yi", "Yiddish" }, + { "yo", "Yoruba" }, + { "zea", "Zeelandic" }, + { "zu", "Zulu" }, + }; + } +} diff --git a/TranslationChecker/TranslationChecker/Program.cs b/TranslationChecker/TranslationChecker/Program.cs new file mode 100644 index 0000000..c7930aa --- /dev/null +++ b/TranslationChecker/TranslationChecker/Program.cs @@ -0,0 +1,296 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace TranslationChecker +{ + // https://json2csharp.com/code-converters/xml-to-csharp + + [XmlRoot(ElementName = "translation")] + public class Translation + { + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + + [XmlElement(ElementName = "numerusform")] + public List Numerusforms { get; set; } + + [XmlText] + public string Text { get; set; } + } + + [XmlRoot(ElementName = "location")] + public class Location + { + [XmlAttribute(AttributeName = "filename")] + public string Filename { get; set; } + + [XmlAttribute(AttributeName = "line")] + public int Line { get; set; } + } + + [XmlRoot(ElementName = "message")] + public class Message + { + [XmlAttribute(AttributeName = "numerus")] + public string Numerus { get; set; } + + [XmlElement(ElementName = "location")] + public List Locations { get; set; } + + [XmlElement(ElementName = "source")] + public string Source { get; set; } + + [XmlElement(ElementName = "translation")] + public Translation Translation { get; set; } + } + + [XmlRoot(ElementName = "context")] + public class Context + { + [XmlElement(ElementName = "name")] + public string Name { get; set; } + + [XmlElement(ElementName = "message")] + public List Messages { get; set; } + } + + [XmlRoot(ElementName = "TS")] + public class QtTranslations + { + [XmlElement(ElementName = "context")] + public List Contexts { get; set; } + + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + + [XmlAttribute(AttributeName = "language")] + public string Language { get; set; } + + [XmlAttribute(AttributeName = "sourcelanguage")] + public string Sourcelanguage { get; set; } + } + + public class Utf8StringWriter : StringWriter + { + public override Encoding Encoding { get { return new UTF8Encoding(false); } } + } + + internal class Program + { + /// + /// Checks if the file serializes back from deserialized form without any differences. + /// + static bool CheckSerialize(string tsFile) + { + var utf8 = new UTF8Encoding(false); + var xml = File.ReadAllText(tsFile, utf8); + + XmlSerializer serializer = new XmlSerializer(typeof(QtTranslations)); + using (var reader = new StringReader(xml)) + { + var test = (QtTranslations)serializer.Deserialize(reader); + using (var writer = new Utf8StringWriter()) + using (var xw = XmlWriter.Create(writer, new XmlWriterSettings + { + Indent = true, + })) + { + xw.WriteDocType("TS", null, null, null); + var ns = new XmlSerializerNamespaces(); + ns.Add("", ""); + serializer.Serialize(xw, test, ns); + var xml2 = writer.ToString().Replace(" />", "/>").Replace("", "").Replace("\r", "") + "\n"; + if (xml2 != xml) + { + File.WriteAllText(tsFile + ".serialized", xml2, utf8); + Console.WriteLine($"Serialization error with file: {tsFile}"); + return false; + } + } + } + return true; + } + + // References: + // - https://en.cppreference.com/w/cpp/io/c/fprintf + // - https://docs.microsoft.com/en-us/cpp/c-runtime-library/format-specification-syntax-printf-and-wprintf-functions?view=msvc-170#flags + static Regex FormatSpecifierRegex = new Regex(@"(%[-+ #]*(\d+|\*)?(.\d+|.\*)?(hh|h|l|ll|j|z|t|L|I|I32|I64|w)?[csdioxXufFeEaAgGp]|%n|%%)"); + + static Regex QtArgumentRegex = new Regex(@"(%\d+)"); + + static bool CheckTranslation(string original, string translation) + { + // This checks for context menu keys, disable for now because there is a lot of noise +#if false + if (original.Count(c => c == '&') != translation.Count(c => c == '&')) + return false; +#endif + + // Check if original has the same format strings as the translation + var originalMatches = FormatSpecifierRegex.Matches(original); + var translationMatches = FormatSpecifierRegex.Matches(translation); + if (originalMatches.Count != translationMatches.Count) + { + return false; + } + for (var i = 0; i < originalMatches.Count; i++) + { + var originalMatch = originalMatches[i]; + var translationMatch = translationMatches[i]; + if (originalMatch.ToString() != translationMatch.ToString()) + return false; + } + if (originalMatches.Count == 0) + { + originalMatches = QtArgumentRegex.Matches(original); + translationMatches = QtArgumentRegex.Matches(translation); + string SortedArgs(MatchCollection matches) + { + var sorted = new List(); + for (var i = 0; i < matches.Count; i++) + sorted.Add(matches[i].ToString()); + sorted.Sort(); + return string.Join(" ", sorted); + } + return SortedArgs(originalMatches) == SortedArgs(translationMatches); + } + return true; + } + + static string FixTranslation(string original, string translation) + { + // TODO: try to fix almost-correct format strings (like %hello -> %shello) + return original; + } + + static int ErrorCount = 0; + + static bool CheckFile(string tsFile, bool fix) + { + if (!CheckSerialize(tsFile)) + { + return false; + } + + var utf8 = new UTF8Encoding(false); + var xml = File.ReadAllText(tsFile, utf8); + var success = true; + + void ReportError(Message message, string translation) + { + var location = message.Locations.First(); + Console.WriteLine($" Format string error ({location.Filename}:{location.Line})\n Source:\n '{message.Source}'\n Translation:\n '{translation}'"); + success = false; + ErrorCount++; + } + + XmlSerializer serializer = new XmlSerializer(typeof(QtTranslations)); + using (var reader = new StringReader(xml)) + { + var ts = (QtTranslations)serializer.Deserialize(reader); + var url = $"https://crowdin.com/translate/x64dbg/1/en-{ts.Language.ToLower().Replace("-", "")}"; + Console.WriteLine($"Checking {tsFile} ({LanguageCodes.GetLanguageName(ts.Language)}) => {url}"); + + foreach (var context in ts.Contexts) + { + foreach (var message in context.Messages) + { + var original = message.Source; + if (message.Translation.Type == "unfinished") + { + continue; + } + + if (message.Numerus == "yes") + { + for (var i = 0; i < message.Translation.Numerusforms.Count; i++) + { + var translation = message.Translation.Numerusforms[i]; + if (!CheckTranslation(original, translation)) + { + ReportError(message, translation); + if (fix) + message.Translation.Numerusforms[i] = FixTranslation(original, translation); + } + } + } + else + { + var translation = message.Translation.Text; + if (!CheckTranslation(original, translation)) + { + ReportError(message, translation); + if (fix) + message.Translation.Text = FixTranslation(original, translation); + } + } + } + } + + if (!success && fix) + { + using (var writer = new Utf8StringWriter()) + using (var xw = XmlWriter.Create(writer, new XmlWriterSettings + { + Indent = true, + })) + { + xw.WriteDocType("TS", null, null, null); + var ns = new XmlSerializerNamespaces(); + ns.Add("", ""); + serializer.Serialize(xw, ts, ns); + var xml2 = writer.ToString().Replace(" />", "/>").Replace("", "").Replace("\r", "") + "\n"; + File.WriteAllText(tsFile, xml2, utf8); + } + } + } + + return success; + } + + static int Main(string[] args) + { + Console.OutputEncoding = Encoding.UTF8; + + if (args.Length < 1) + { + Console.WriteLine("Usage: TranslationChecker x64dbg.ts [--fix]"); + return 1; + } + + var fix = false; + var folder = false; + for (var i = 1; i < args.Length; i++) + { + if (args[i] == "--fix") + fix = true; + else if (args[i] == "--folder") + folder = true; + } + + var success = true; + if (folder) + { + foreach (var tsFile in Directory.EnumerateFiles(args[0], "*.ts", SearchOption.AllDirectories)) + { + if (!CheckFile(tsFile, fix)) + success = false; + } + } + else + { + success = CheckFile(args[0], fix); + } + + Console.WriteLine($"\nTotal errors: {ErrorCount}"); + return success ? 0 : 1; + } + } +} diff --git a/TranslationChecker/TranslationChecker/Properties/AssemblyInfo.cs b/TranslationChecker/TranslationChecker/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e5d9432 --- /dev/null +++ b/TranslationChecker/TranslationChecker/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TranslationChecker")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TranslationChecker")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7d7eec8b-34ff-43e7-ac2b-cb0a68334cfa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TranslationChecker/TranslationChecker/TranslationChecker.csproj b/TranslationChecker/TranslationChecker/TranslationChecker.csproj new file mode 100644 index 0000000..37a031d --- /dev/null +++ b/TranslationChecker/TranslationChecker/TranslationChecker.csproj @@ -0,0 +1,54 @@ + + + + + Debug + AnyCPU + {7D7EEC8B-34FF-43E7-AC2B-CB0A68334CFA} + Exe + TranslationChecker + TranslationChecker + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/crowdin-sync.bat b/crowdin-sync.bat index 6cf740c..87c9198 100644 --- a/crowdin-sync.bat +++ b/crowdin-sync.bat @@ -1,17 +1,22 @@ @echo off if "%QT64PATH%"=="" set QT64PATH=c:\Qt\qt-5.6.2-x64-msvc2013\5.6\msvc2013_64\bin SET PATH=%PATH%;%QT64PATH%;c:\Program Files (x86)\7-Zip -del /S /Q *.qm -curl -k https://api.crowdin.com/api/project/x64dbg/export?key=%CROWDIN_API_KEY% -curl -k -o translations.zip https://api.crowdin.com/api/project/x64dbg/download/all.zip?key=%CROWDIN_API_KEY% -rmdir /S /Q translations -7z x -otranslations translations.zip +del /S /Q *.qm >nul 2>&1 +curl -s -k https://api.crowdin.com/api/project/x64dbg/export?key=%CROWDIN_API_KEY% +curl -s -k -o translations.zip https://api.crowdin.com/api/project/x64dbg/download/all.zip?key=%CROWDIN_API_KEY% +rmdir /S /Q translations >nul 2>&1 +7z x -otranslations translations.zip >nul 2>&1 +TranslationChecker.exe translations --folder --fix +set CHECKER_ERRORLEVEL=%ERRORLEVEL% cd translations for /D %%a in (*) do (set fname=%%a) & call :rename -move *.qm ..\ +move *.qm ..\ >nul 2>&1 cd .. +exit /b %CHECKER_ERRORLEVEL% + goto :eof + :rename set trname=x64dbg_%fname:-=_%.ts -copy %fname%\x64dbg.ts %trname% +copy %fname%\x64dbg.ts %trname% >nul 2>&1 lrelease -nounfinished %trname%