From 8260181bb263c7668b279d94baf1776bb844f9e0 Mon Sep 17 00:00:00 2001 From: Corentin Aulagnet Date: Sat, 25 Jun 2022 02:18:17 +0200 Subject: [PATCH 1/6] First version of multi lingual support. French is fully added and a little bit tested. A class Translator is added, it stores all the translations for the part names (like string, barrel, etc) --- WFInfo/Data.cs | 52 ++++++++++++--- WFInfo/Ocr.cs | 4 +- WFInfo/Services/TesseractService.cs | 3 +- WFInfo/Settings/SettingsWindow.xaml | 4 ++ WFInfo/Settings/SettingsWindow.xaml.cs | 2 +- WFInfo/Translator.cs | 90 ++++++++++++++++++++++++++ 6 files changed, 141 insertions(+), 14 deletions(-) create mode 100644 WFInfo/Translator.cs diff --git a/WFInfo/Data.cs b/WFInfo/Data.cs index 0276f754..dcd6288d 100644 --- a/WFInfo/Data.cs +++ b/WFInfo/Data.cs @@ -26,7 +26,8 @@ class Data public JObject relicData; // Contains relicData from Warframe PC Drops {: {"A1":{"vaulted": true,: }, ...}, "Meso": ..., "Neo": ..., "Axi": ...} public JObject equipmentData; // Contains equipmentData from Warframe PC Drops {: {"vaulted": true, "PARTS": {:{"relic_name":|"","count":}, ...}}, ...} public JObject nameData; // Contains relic to market name translation {: } - + public JObject translationData; // Contains names of parts in locale language and in english { : } + private static List>> korean = new List>>() { new Dictionary>() { { 0, new List{ 6, 7, 8, 16 } }, // ㅁ, ㅂ, ㅃ, ㅍ @@ -55,6 +56,8 @@ class Data private readonly string equipmentDataPath; private readonly string relicDataPath; private readonly string nameDataPath; + private readonly string translationDataPath; + public string JWT; // JWT is the security key, store this as email+pw combo private readonly WebSocket marketSocket = new WebSocket("wss://warframe.market/socket?platform=pc"); private readonly string filterAllJSON = "https://api.warframestat.us/wfinfo/filtered_items"; @@ -77,6 +80,7 @@ public Data(IReadOnlyApplicationSettings settings) equipmentDataPath = applicationDirectory + @"\eqmt_data.json"; relicDataPath = applicationDirectory + @"\relic_data.json"; nameDataPath = applicationDirectory + @"\name_data.json"; + translationDataPath = applicationDirectory + @"\translation_data.json"; Directory.CreateDirectory(applicationDirectory); @@ -153,7 +157,7 @@ public int GetGithubVersion() public void ReloadItems() { marketItems = new JObject(); - + JObject obj = JsonConvert.DeserializeObject( webClient.DownloadString("https://api.warframe.market/v1/items")); @@ -165,7 +169,7 @@ public void ReloadItems() if (name.Contains("Prime ")) marketItems[item["id"].ToString()] = name + "|" + item["url_name"]; } - + try { using (var request = new HttpRequestMessage() @@ -201,7 +205,7 @@ public void ReloadItems() { Main.AddLog("GetTopListings: " + e.Message); } - + marketItems["version"] = Main.BuildVersion; Main.AddLog("Item database has been downloaded"); @@ -298,7 +302,8 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) relicData = File.Exists(relicDataPath) ? JsonConvert.DeserializeObject(File.ReadAllText(relicDataPath)) : new JObject(); if (nameData == null) nameData = File.Exists(nameDataPath) ? JsonConvert.DeserializeObject(File.ReadAllText(nameDataPath)) : new JObject(); - + if (translationData == null) + translationData = File.Exists(translationDataPath) ? JsonConvert.DeserializeObject(File.ReadAllText(translationDataPath)) : new JObject(); // fill in equipmentData (NO OVERWRITE) // fill in nameData // fill in relicData @@ -311,7 +316,7 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) equipmentData["timestamp"] = DateTime.Now; relicData["timestamp"] = DateTime.Now; nameData = new JObject(); - + translationData = new JObject(); foreach (KeyValuePair era in allFiltered["relics"].ToObject()) { relicData[era.Key] = new JObject(); @@ -344,7 +349,6 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) equipmentData[primeName]["parts"][partName]["count"] = part.Value["count"]; equipmentData[primeName]["parts"][partName]["ducats"] = part.Value["ducats"]; - string gameName = part.Key; if (prime.Value["type"].ToString() == "Archwing" && (part.Key.Contains("Systems") || part.Key.Contains("Harness") || part.Key.Contains("Wings"))) { @@ -357,6 +361,8 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) if (marketData.TryGetValue(partName, out _)) { nameData[gameName] = partName; + string localeRelicName = Translator.TranslateParName(gameName, _settings.Locale); + translationData[localeRelicName] = partName; marketData[partName]["ducats"] = Convert.ToInt32(part.Value["ducats"].ToString(), Main.culture); } } @@ -366,15 +372,29 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) foreach (KeyValuePair ignored in allFiltered["ignored_items"].ToObject()) { nameData[ignored.Key] = ignored.Key; + translationData[ignored.Key] = ignored.Key; } - + translationData["locale"]=_settings.Locale; Main.AddLog("Prime Database has been downloaded"); return true; } Main.AddLog("Prime Database is up to date"); return false; } - + public void UpdateTranslationdb() + { + if (translationData["locale"].ToString() != _settings.Locale) + { + translationData = new JObject(); + foreach (KeyValuePair englishNames in nameData) + { + string localeRelicName = Translator.TranslateParName(englishNames.Key, _settings.Locale); + translationData[localeRelicName] = englishNames.Value.ToString(); + } + translationData["locale"] = _settings.Locale; + SaveDatabase(translationDataPath, translationData); + } + } private void RefreshMarketDucats() { //equipmentData[primeName]["parts"][partName]["ducats"] @@ -478,6 +498,7 @@ public void SaveAllJSONs() SaveDatabase(nameDataPath, nameData); SaveDatabase(marketItemsPath, marketItems); SaveDatabase(marketDataPath, marketData); + SaveDatabase(translationDataPath, translationData); } public void ForceEquipmentUpdate() @@ -853,7 +874,18 @@ public string GetPartName(string name, out int low, bool suppressLogging) string lowest = null; string lowest_unfiltered = null; low = 9999; - foreach (KeyValuePair prop in nameData) + JObject namesData; + switch (_settings.Locale) + { + case "fr": + namesData = translationData; + break; + default: + namesData = nameData; + break; + } + + foreach (KeyValuePair prop in namesData) { int val = LevenshteinDistance(prop.Key, name); if (val < low) diff --git a/WFInfo/Ocr.cs b/WFInfo/Ocr.cs index 46f83a8e..0530e7e1 100644 --- a/WFInfo/Ocr.cs +++ b/WFInfo/Ocr.cs @@ -115,7 +115,7 @@ public enum WindowStyle // Screen / Resolution Scaling - Used to adjust pixel values to each person's monitor public static double screenScaling; - public static Regex RE = new Regex("[^a-z가-힣]", RegexOptions.IgnoreCase | RegexOptions.Compiled); + public static Regex RE = new Regex("[^a-z가-힣éèêëîïàäâÉÈç]", RegexOptions.IgnoreCase | RegexOptions.Compiled); // Pixel measurements for reward screen @ 1920 x 1080 with 100% scale https://docs.google.com/drawings/d/1Qgs7FU2w1qzezMK-G1u9gMTsQZnDKYTEU36UPakNRJQ/edit public const int pixleRewardWidth = 968; @@ -825,7 +825,7 @@ internal static void ProcessSnapIt(Bitmap snapItImage, Bitmap fullShot, Point sn continue; } Debug.WriteLine($"Part {foundParts.IndexOf(part)} out of {foundParts.Count}"); - string name = Main.dataBase.GetPartName(part.Name, out firstProximity[0], false); + string name = Main.dataBase.GetPartName(part.Name, out firstProximity[0], false);//Gives the part name in the locale language, translation is done in GetPartName part.Name = name; foundParts[i] = part; JObject job = Main.dataBase.marketData.GetValue(name).ToObject(); diff --git a/WFInfo/Services/TesseractService.cs b/WFInfo/Services/TesseractService.cs index b200bb32..b187b4db 100644 --- a/WFInfo/Services/TesseractService.cs +++ b/WFInfo/Services/TesseractService.cs @@ -93,7 +93,8 @@ private void getLocaleTessdata() JObject traineddata_checksums = new JObject { {"en", "7af2ad02d11702c7092a5f8dd044d52f"}, - {"ko", "c776744205668b7e76b190cc648765da"} + {"ko", "c776744205668b7e76b190cc648765da"}, + {"fr", "a73e70c872f262895d93976febeb1638"} }; // get trainned data diff --git a/WFInfo/Settings/SettingsWindow.xaml b/WFInfo/Settings/SettingsWindow.xaml index b553ddca..e68c7f08 100644 --- a/WFInfo/Settings/SettingsWindow.xaml +++ b/WFInfo/Settings/SettingsWindow.xaml @@ -382,6 +382,10 @@ Content="한국어" FontSize="14" Background="#FF1B1B1B" /> + diff --git a/WFInfo/Settings/SettingsWindow.xaml.cs b/WFInfo/Settings/SettingsWindow.xaml.cs index 5527cf56..eb210dfe 100644 --- a/WFInfo/Settings/SettingsWindow.xaml.cs +++ b/WFInfo/Settings/SettingsWindow.xaml.cs @@ -186,7 +186,7 @@ private void localeComboboxSelectionChanged(object sender, System.Windows.Contro Save(); _ = OCR.updateEngineAsync(); - + Main.dataBase.UpdateTranslationdb(); _ = Task.Run(async () => { Main.dataBase.ReloadItems(); diff --git a/WFInfo/Translator.cs b/WFInfo/Translator.cs new file mode 100644 index 00000000..dec2fb29 --- /dev/null +++ b/WFInfo/Translator.cs @@ -0,0 +1,90 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows.Forms; +using Tesseract; +using WFInfo.Settings; + +namespace WFInfo +{ + public static class Translator + { + public static Dictionary frPartTranslations = new Dictionary() + { + {"String","Corde" }, + {"Barrel","Canon" }, + {"Stock","Crosse" }, + {"Receiver","Culasse" }, + {"Blueprint","Schéma" }, + {"Link","Lien" }, + {"Blades","Lames" }, + {"Blade","Lame" }, + {"Gauntlet","Gantelet" }, + {"Lower Limb","Partie Inférieure" }, + {"Upper Limb","Partie Supérieure" }, + {"Neuroptics","Neuroptiques" }, + {"Systems","Systèmes" }, + {"Handle","Manche" }, + {"Ornament","Ornement" }, + {"Cerebrum","Cerveau" }, + {"Grip","Prise" }, + {"Head","Tête" }, + {"Disc","Disque" }, + {"Pouch","Pochette" }, + {"Stars","Étoiles" }, + {"Collar","Collier" }, + {"Band","Lanière" }, + {"Buckle","Boucle" }, + {"Boot","Botte" }, + {"Hilt","Garde" }, + {"Chain","Chaîne" }, + {"Harness","Harnais" }, + {"Wings","Ailes" }, + {"Guard","Quillon" }, + {"Exilus Weapon Adapter","Adaptateur d'arme Exilus" }, + {"Riven Sliver","Brisure Riven" }, + {"Ayatan Amber Star","Étoile Ayatan Ambre" }, + }; + /* + * Replace each known words to be translated (given in localePartTranslation Dictionnary) by its english translation + */ + public static string TranslateParName(string partName,string locale) + { + if (!string.IsNullOrEmpty(partName)) + { + switch (locale) + { + case "fr": + string localPartName = partName; + foreach (string key in frPartTranslations.Keys) + { + string partTranslation; + frPartTranslations.TryGetValue(key, out partTranslation); + localPartName = localPartName.Replace(key, partTranslation); + } + + return localPartName.Length == 0 ? partName : localPartName; + default: + return partName; + } + } + else + { + return null; + } + + } + } +} From 6ff1c082ee455acc7ba1c972f600ed366436c7a4 Mon Sep 17 00:00:00 2001 From: Corentin Aulagnet Date: Thu, 30 Jun 2022 19:47:51 +0200 Subject: [PATCH 2/6] Modified the way the translation is done to use the data fetch online on warframe market. It is similar to https://github.com/WFCD/WFinfo/pull/231 MasterIt has not been tested. Displays are not translated. --- WFInfo/Data.cs | 54 +++++++------------------- WFInfo/Ocr.cs | 4 +- WFInfo/Settings/SettingsWindow.xaml.cs | 1 - 3 files changed, 18 insertions(+), 41 deletions(-) diff --git a/WFInfo/Data.cs b/WFInfo/Data.cs index dcd6288d..44e5788d 100644 --- a/WFInfo/Data.cs +++ b/WFInfo/Data.cs @@ -26,7 +26,6 @@ class Data public JObject relicData; // Contains relicData from Warframe PC Drops {: {"A1":{"vaulted": true,: }, ...}, "Meso": ..., "Neo": ..., "Axi": ...} public JObject equipmentData; // Contains equipmentData from Warframe PC Drops {: {"vaulted": true, "PARTS": {:{"relic_name":|"","count":}, ...}}, ...} public JObject nameData; // Contains relic to market name translation {: } - public JObject translationData; // Contains names of parts in locale language and in english { : } private static List>> korean = new List>>() { new Dictionary>() { @@ -56,7 +55,6 @@ class Data private readonly string equipmentDataPath; private readonly string relicDataPath; private readonly string nameDataPath; - private readonly string translationDataPath; public string JWT; // JWT is the security key, store this as email+pw combo private readonly WebSocket marketSocket = new WebSocket("wss://warframe.market/socket?platform=pc"); @@ -80,7 +78,6 @@ public Data(IReadOnlyApplicationSettings settings) equipmentDataPath = applicationDirectory + @"\eqmt_data.json"; relicDataPath = applicationDirectory + @"\relic_data.json"; nameDataPath = applicationDirectory + @"\name_data.json"; - translationDataPath = applicationDirectory + @"\translation_data.json"; Directory.CreateDirectory(applicationDirectory); @@ -302,8 +299,6 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) relicData = File.Exists(relicDataPath) ? JsonConvert.DeserializeObject(File.ReadAllText(relicDataPath)) : new JObject(); if (nameData == null) nameData = File.Exists(nameDataPath) ? JsonConvert.DeserializeObject(File.ReadAllText(nameDataPath)) : new JObject(); - if (translationData == null) - translationData = File.Exists(translationDataPath) ? JsonConvert.DeserializeObject(File.ReadAllText(translationDataPath)) : new JObject(); // fill in equipmentData (NO OVERWRITE) // fill in nameData // fill in relicData @@ -316,7 +311,6 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) equipmentData["timestamp"] = DateTime.Now; relicData["timestamp"] = DateTime.Now; nameData = new JObject(); - translationData = new JObject(); foreach (KeyValuePair era in allFiltered["relics"].ToObject()) { relicData[era.Key] = new JObject(); @@ -362,7 +356,6 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) { nameData[gameName] = partName; string localeRelicName = Translator.TranslateParName(gameName, _settings.Locale); - translationData[localeRelicName] = partName; marketData[partName]["ducats"] = Convert.ToInt32(part.Value["ducats"].ToString(), Main.culture); } } @@ -372,29 +365,13 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) foreach (KeyValuePair ignored in allFiltered["ignored_items"].ToObject()) { nameData[ignored.Key] = ignored.Key; - translationData[ignored.Key] = ignored.Key; } - translationData["locale"]=_settings.Locale; Main.AddLog("Prime Database has been downloaded"); return true; } Main.AddLog("Prime Database is up to date"); return false; } - public void UpdateTranslationdb() - { - if (translationData["locale"].ToString() != _settings.Locale) - { - translationData = new JObject(); - foreach (KeyValuePair englishNames in nameData) - { - string localeRelicName = Translator.TranslateParName(englishNames.Key, _settings.Locale); - translationData[localeRelicName] = englishNames.Value.ToString(); - } - translationData["locale"] = _settings.Locale; - SaveDatabase(translationDataPath, translationData); - } - } private void RefreshMarketDucats() { //equipmentData[primeName]["parts"][partName]["ducats"] @@ -498,7 +475,6 @@ public void SaveAllJSONs() SaveDatabase(nameDataPath, nameData); SaveDatabase(marketItemsPath, marketItems); SaveDatabase(marketDataPath, marketData); - SaveDatabase(translationDataPath, translationData); } public void ForceEquipmentUpdate() @@ -616,11 +592,19 @@ public int LevenshteinDistance(string s, string t) case "ko": // for korean return LevenshteinDistanceKorean(s, t); + case "fr": + return LevenshteinDistanceFrench(s, t); default: return LevenshteinDistanceDefault(s, t); } } - + int LevenshteinDistanceFrench(string firstWord, string secondWord) + { + firstWord = getLocaleNameData(firstWord); + firstWord = firstWord.Replace("Schéma", "").Replace("-", "").Trim(); + secondWord = secondWord.Replace("Schéma", "").Trim(); + return LevenshteinDistanceDefault(firstWord, secondWord); + } public int LevenshteinDistanceDefault(string s, string t) { // Levenshtein Distance determines how many character changes it takes to form a known result @@ -678,6 +662,10 @@ public static bool isKorean(String str) if (0xAC00 <= c && c <= 0xD7A3) return true; return false; } + /* + * Returns the locale market name of s + * s is the market name in english + */ public string getLocaleNameData(string s) { bool saveDatabases = false; @@ -687,7 +675,7 @@ public string getLocaleNameData(string s) if (marketItem.Key == "version") continue; string[] split = marketItem.Value.ToString().Split('|'); - if (split[0] == s) + if (split[0].Replace("Blueprint", "").Trim() == s.Replace("Blueprint", "").Trim()) { if (split.Length == 3) { @@ -874,18 +862,7 @@ public string GetPartName(string name, out int low, bool suppressLogging) string lowest = null; string lowest_unfiltered = null; low = 9999; - JObject namesData; - switch (_settings.Locale) - { - case "fr": - namesData = translationData; - break; - default: - namesData = nameData; - break; - } - - foreach (KeyValuePair prop in namesData) + foreach (KeyValuePair prop in nameData) { int val = LevenshteinDistance(prop.Key, name); if (val < low) @@ -900,7 +877,6 @@ public string GetPartName(string name, out int low, bool suppressLogging) lowest_unfiltered = prop.Key; } } - if (!suppressLogging) Main.AddLog("Found part(" + low + "): \"" + lowest_unfiltered + "\" from \"" + name + "\""); return lowest; diff --git a/WFInfo/Ocr.cs b/WFInfo/Ocr.cs index 0530e7e1..8aa25c02 100644 --- a/WFInfo/Ocr.cs +++ b/WFInfo/Ocr.cs @@ -788,7 +788,9 @@ private static WFtheme GetClosestTheme(Color clr, out int threshold) /// If part name is close enough to valid to actually process internal static bool PartNameValid (string partName) { - if ((partName.Length < 13 && _settings.Locale == "en") || (partName.Replace(" ", "").Length < 6 && _settings.Locale == "ko")) // if part name is smaller than "Bo prime handle" skip current part + if ((partName.Length < 13 && _settings.Locale == "en") + || (partName.Replace(" ", "").Length < 6 && _settings.Locale == "ko") + || partName.Length < 13 && _settings.Locale == "fr") // if part name is smaller than "Bo prime handle" skip current part //TODO: Add a min character for other locale here. return false; return true; diff --git a/WFInfo/Settings/SettingsWindow.xaml.cs b/WFInfo/Settings/SettingsWindow.xaml.cs index eb210dfe..7a6f4995 100644 --- a/WFInfo/Settings/SettingsWindow.xaml.cs +++ b/WFInfo/Settings/SettingsWindow.xaml.cs @@ -186,7 +186,6 @@ private void localeComboboxSelectionChanged(object sender, System.Windows.Contro Save(); _ = OCR.updateEngineAsync(); - Main.dataBase.UpdateTranslationdb(); _ = Task.Run(async () => { Main.dataBase.ReloadItems(); From 07774e38e830ff4684a121105d62d529bdee223d Mon Sep 17 00:00:00 2001 From: Corentin Aulagnet Date: Thu, 7 Jul 2022 21:44:23 +0200 Subject: [PATCH 3/6] Modifed the MasterIt code to work with Localization and added the french langage. I was'nt able to test it as it does not work at the time of this commit --- WFInfo/Ocr.cs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/WFInfo/Ocr.cs b/WFInfo/Ocr.cs index 8aa25c02..c4b74e44 100644 --- a/WFInfo/Ocr.cs +++ b/WFInfo/Ocr.cs @@ -1437,11 +1437,31 @@ internal static void ProcessProfileScreen(Bitmap fullShot) for (int i = 0; i < foundParts.Count; i++) { InventoryItem part = foundParts[i]; - if (!PartNameValid(part.Name + " Blueprint")) - continue; - string name = Main.dataBase.GetPartName(part.Name+" Blueprint", out int proximity, true); //add blueprint to name to check against prime drop table - string checkName = Main.dataBase.GetPartName(part.Name + " prime Blueprint", out int primeProximity, true); //also add prime to check if that gives better match. If so, this is a non-prime - Main.AddLog("Checking \"" + part.Name.Trim() +"\", (" + proximity +")\"" + name + "\", +prime (" + primeProximity + ")\"" + checkName + "\""); + string name; + string checkName; + int proximity; + int primeProximity; + switch (_settings.Locale) + { + case "fr": + if (!PartNameValid(part.Name + " Schéma")) + continue; + name = Main.dataBase.GetPartName(part.Name + " Schéma", out proximity, true); //add blueprint to name to check against prime drop table + checkName = Main.dataBase.GetPartName(part.Name + " prime Schéma", out primeProximity, true); //also add prime to check if that gives better match. If so, this is a non-prime + break; + case "en": + if (!PartNameValid(part.Name + " Blueprint")) + continue; + name = Main.dataBase.GetPartName(part.Name + " Blueprint", out proximity, true); //add blueprint to name to check against prime drop table + checkName = Main.dataBase.GetPartName(part.Name + " prime Blueprint", out primeProximity, true); //also add prime to check if that gives better match. If so, this is a non-prime + break; + default: + if (!PartNameValid(part.Name + " Blueprint")) + continue; + name = Main.dataBase.GetPartName(part.Name + " Blueprint", out proximity, true); //add blueprint to name to check against prime drop table + checkName = Main.dataBase.GetPartName(part.Name + " prime Blueprint", out primeProximity, true); //also add prime to check if that gives better match. If so, this is a non-prime + break; + } //Decide if item is an actual prime, if so mark as mastered if (proximity < 3 && proximity < primeProximity && part.Name.Length > 6 && name.Contains("Prime")) @@ -1702,7 +1722,7 @@ private static List FindOwnedItems(Bitmap ProfileImage, string ti //do OCR - _tesseractService.FirstEngine.SetVariable("tessedit_char_whitelist", " ABCDEFGHIJKLMNOPQRSTUVWXYZ&"); + _tesseractService.FirstEngine.SetVariable("tessedit_char_whitelist", " ABCDEFGHIJKLMNOPQRSTUVWXYZ&îïÎÏôÔöÖâäàÂêëËÊéèÉÈ"); using (var page = _tesseractService.FirstEngine.Process(cloneBitmap, PageSegMode.SingleLine)) { using (var iterator = page.GetIterator()) From cdeea00ff5d8d7e48d0aba6aa0055859e63fdc44 Mon Sep 17 00:00:00 2001 From: Corentin Aulagnet Date: Sat, 25 Jun 2022 02:18:17 +0200 Subject: [PATCH 4/6] First version of multi lingual support. French is fully added and a little bit tested. A class Translator is added, it stores all the translations for the part names (like string, barrel, etc) --- WFInfo/Data.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/WFInfo/Data.cs b/WFInfo/Data.cs index 0bd599df..d1e60f88 100644 --- a/WFInfo/Data.cs +++ b/WFInfo/Data.cs @@ -26,6 +26,7 @@ class Data public JObject relicData; // Contains relicData from Warframe PC Drops {: {"A1":{"vaulted": true,: }, ...}, "Meso": ..., "Neo": ..., "Axi": ...} public JObject equipmentData; // Contains equipmentData from Warframe PC Drops {: {"vaulted": true, "PARTS": {:{"relic_name":|"","count":}, ...}}, ...} public JObject nameData; // Contains relic to market name translation {: } + private static List>> korean = new List>>() { new Dictionary>() { @@ -85,6 +86,7 @@ public Data(IReadOnlyApplicationSettings settings) equipmentDataPath = applicationDirectory + @"\eqmt_data.json"; relicDataPath = applicationDirectory + @"\relic_data.json"; nameDataPath = applicationDirectory + @"\name_data.json"; + translationDataPath = applicationDirectory + @"\translation_data.json"; Directory.CreateDirectory(applicationDirectory); @@ -398,6 +400,7 @@ private bool LoadEqmtData(JObject allFiltered, bool force = false) foreach (KeyValuePair ignored in allFiltered["ignored_items"].ToObject()) { nameData[ignored.Key] = ignored.Key; + translationData[ignored.Key] = ignored.Key; } Main.AddLog("Prime Database has been downloaded"); return true; @@ -510,6 +513,7 @@ public void SaveAllJSONs() SaveDatabase(nameDataPath, nameData); SaveDatabase(marketItemsPath, marketItems); SaveDatabase(marketDataPath, marketData); + SaveDatabase(translationDataPath, translationData); } public void ForceEquipmentUpdate() @@ -898,7 +902,18 @@ public string GetPartName(string name, out int low, bool suppressLogging) string lowest = null; string lowest_unfiltered = null; low = 9999; - foreach (KeyValuePair prop in nameData) + JObject namesData; + switch (_settings.Locale) + { + case "fr": + namesData = translationData; + break; + default: + namesData = nameData; + break; + } + + foreach (KeyValuePair prop in namesData) { int val = LevenshteinDistance(prop.Key, name); if (val < low) From e15a2c879fe1bf4f20ca2e7af4187d5f8d6a752f Mon Sep 17 00:00:00 2001 From: Corentin Aulagnet Date: Wed, 20 Jul 2022 19:01:31 +0200 Subject: [PATCH 5/6] The equipment now translates to the locale langage with the addition of resources files to translate all hard coded texts. The UI texts are not translated yet. Added a status message to inform the user that the locale has been changed The equiment is redrawn each time it is opened --- WFInfo/EquipmentWindow.cs | 14 ++- WFInfo/Main.cs | 9 +- WFInfo/MainWindow.xaml.cs | 3 + WFInfo/Properties/strings.Designer.cs | 117 +++++++++++++++++++++ WFInfo/Properties/strings.fr.resx | 138 +++++++++++++++++++++++++ WFInfo/Properties/strings.resx | 138 +++++++++++++++++++++++++ WFInfo/Settings/SettingsWindow.xaml.cs | 8 +- WFInfo/WFInfo.csproj | 15 +++ 8 files changed, 436 insertions(+), 6 deletions(-) create mode 100644 WFInfo/Properties/strings.Designer.cs create mode 100644 WFInfo/Properties/strings.fr.resx create mode 100644 WFInfo/Properties/strings.resx diff --git a/WFInfo/EquipmentWindow.cs b/WFInfo/EquipmentWindow.cs index 6c42281a..0ef34b0a 100644 --- a/WFInfo/EquipmentWindow.cs +++ b/WFInfo/EquipmentWindow.cs @@ -4,6 +4,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using WFInfo.Settings; namespace WFInfo { @@ -50,7 +51,6 @@ public void reloadItems() public void populate() { - primeTypes = new Dictionary(); foreach (KeyValuePair prime in Main.dataBase.equipmentData) { @@ -78,11 +78,21 @@ public void populate() foreach (KeyValuePair primePart in prime.Value["parts"].ToObject()) { string partName = primePart.Key; + switch (ApplicationSettings.GlobalReadonlySettings.Locale) + { + case "fr": + partName = Main.dataBase.getLocaleNameData(partName); + partName = partName.Replace("-", "").Trim(); + break; + default: + break; + } + if (primePart.Key.IndexOf("Prime") + 6 < primePart.Key.Length) partName = partName.Substring(primePart.Key.IndexOf("Prime") + 6); if (partName.Contains("Kubrow")) - partName = partName.Substring(partName.IndexOf(" Blueprint") + 1); + partName = partName.Substring(partName.IndexOf(" "+ Properties.strings.Blueprint) + 1); TreeNode partNode = new TreeNode(partName, primePart.Value["vaulted"].ToObject() ? "Vaulted" : "", false, 0); partNode.MakeClickable(primePart.Key); if (Main.dataBase.marketData.TryGetValue(primePart.Key.ToString(), out JToken marketValues)) diff --git a/WFInfo/Main.cs b/WFInfo/Main.cs index 1e31caa1..71160eaa 100644 --- a/WFInfo/Main.cs +++ b/WFInfo/Main.cs @@ -9,6 +9,8 @@ using AutoUpdaterDotNET; using System.Windows; using System.Windows.Forms; +using System.Threading; +using System.Globalization; using WebSocketSharp; using WFInfo.Resources; using WFInfo.Settings; @@ -54,6 +56,9 @@ public Main() AutoUpdater.Start("https://github.com/WFCD/WFinfo/releases/latest/download/update.xml"); Task.Factory.StartNew(ThreadedDataLoad); + + //string str = Properties.strings.Primary; To localize all hard coded text + } private void AutoUpdaterOnCheckForUpdateEvent(UpdateInfoEventArgs args) @@ -65,7 +70,7 @@ public static void ThreadedDataLoad() { try { - StatusUpdate("Updating Databases...", 0); + StatusUpdate(Properties.strings.Update_databases, 0); dataBase.Update(); @@ -79,7 +84,7 @@ public static void ThreadedDataLoad() OCR.VerifyWarframe(); LoggedIn(); } - StatusUpdate("WFInfo Initialization Complete", 0); + StatusUpdate(Properties.strings.Init_complete, 0); AddLog("WFInfo has launched successfully"); FinishedLoading(); diff --git a/WFInfo/MainWindow.xaml.cs b/WFInfo/MainWindow.xaml.cs index 65b703b8..d68ca7c8 100644 --- a/WFInfo/MainWindow.xaml.cs +++ b/WFInfo/MainWindow.xaml.cs @@ -196,6 +196,9 @@ private void RelicsClick(object sender, RoutedEventArgs e) private void EquipmentClick(object sender, RoutedEventArgs e) { if (Main.dataBase.equipmentData == null) { ChangeStatus("Equipment data not yet loaded in", 2); return; } + Main.equipmentWindow?.Close(); + Main.equipmentWindow = new EquipmentWindow(); + Main.equipmentWindow.populate(); Main.equipmentWindow.Show(); } diff --git a/WFInfo/Properties/strings.Designer.cs b/WFInfo/Properties/strings.Designer.cs new file mode 100644 index 00000000..ca399e87 --- /dev/null +++ b/WFInfo/Properties/strings.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// Ce code a été généré par un outil. +// Version du runtime :4.0.30319.42000 +// +// Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si +// le code est régénéré. +// +//------------------------------------------------------------------------------ + +namespace WFInfo.Properties { + using System; + + + /// + /// Une classe de ressource fortement typée destinée, entre autres, à la consultation des chaînes localisées. + /// + // Cette classe a été générée automatiquement par la classe StronglyTypedResourceBuilder + // à l'aide d'un outil, tel que ResGen ou Visual Studio. + // Pour ajouter ou supprimer un membre, modifiez votre fichier .ResX, puis réexécutez ResGen + // avec l'option /str ou régénérez votre projet VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal strings() { + } + + /// + /// Retourne l'instance ResourceManager mise en cache utilisée par cette classe. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WFInfo.Properties.strings", typeof(strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Remplace la propriété CurrentUICulture du thread actuel pour toutes + /// les recherches de ressources à l'aide de cette classe de ressource fortement typée. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Recherche une chaîne localisée semblable à Blueprint. + /// + internal static string Blueprint { + get { + return ResourceManager.GetString("Blueprint", resourceCulture); + } + } + + /// + /// Recherche une chaîne localisée semblable à Changed langage to english. + /// + internal static string Changed_locale { + get { + return ResourceManager.GetString("Changed_locale", resourceCulture); + } + } + + /// + /// Recherche une chaîne localisée semblable à WFInfo Initialization Complete. + /// + internal static string Init_complete { + get { + return ResourceManager.GetString("Init_complete", resourceCulture); + } + } + + /// + /// Recherche une chaîne localisée semblable à Primary. + /// + internal static string Primary { + get { + return ResourceManager.GetString("Primary", resourceCulture); + } + } + + /// + /// Recherche une chaîne localisée semblable à Status. + /// + internal static string Status { + get { + return ResourceManager.GetString("Status", resourceCulture); + } + } + + /// + /// Recherche une chaîne localisée semblable à Updating Databases.... + /// + internal static string Update_databases { + get { + return ResourceManager.GetString("Update_databases", resourceCulture); + } + } + } +} diff --git a/WFInfo/Properties/strings.fr.resx b/WFInfo/Properties/strings.fr.resx new file mode 100644 index 00000000..3238aad3 --- /dev/null +++ b/WFInfo/Properties/strings.fr.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Schéma + + + Langue changée en français + + + Initialisation de WFInfo terminée + + + Primaire + + + Statut + + + Mise à jour des bases de données... + + \ No newline at end of file diff --git a/WFInfo/Properties/strings.resx b/WFInfo/Properties/strings.resx new file mode 100644 index 00000000..7b614090 --- /dev/null +++ b/WFInfo/Properties/strings.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Blueprint + + + Changed langage to english + + + WFInfo Initialization Complete + + + Primary + + + Status + + + Updating Databases... + + \ No newline at end of file diff --git a/WFInfo/Settings/SettingsWindow.xaml.cs b/WFInfo/Settings/SettingsWindow.xaml.cs index 7a6f4995..15a0344c 100644 --- a/WFInfo/Settings/SettingsWindow.xaml.cs +++ b/WFInfo/Settings/SettingsWindow.xaml.cs @@ -3,7 +3,8 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; - +using System.Threading; +using System.Globalization; namespace WFInfo.Settings { /// @@ -182,14 +183,17 @@ private void localeComboboxSelectionChanged(object sender, System.Windows.Contro ComboBoxItem item = (ComboBoxItem) localeCombobox.SelectedItem; string selectedLocale = item.Tag.ToString(); + string oldLocale = _viewModel.Locale; _viewModel.Locale = selectedLocale; Save(); - + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(selectedLocale); _ = OCR.updateEngineAsync(); _ = Task.Run(async () => { Main.dataBase.ReloadItems(); }); + if(selectedLocale != oldLocale) + Main.StatusUpdate(Properties.strings.Changed_locale, 0); } diff --git a/WFInfo/WFInfo.csproj b/WFInfo/WFInfo.csproj index d9d5f2d7..3d009194 100644 --- a/WFInfo/WFInfo.csproj +++ b/WFInfo/WFInfo.csproj @@ -48,6 +48,10 @@ + + + + @@ -64,6 +68,10 @@ + + ResXFileCodeGenerator + strings.Designer.cs + @@ -97,4 +105,11 @@ + + + True + True + strings.resx + + \ No newline at end of file From 7107da15ccecd301554485f1213c6bdd4276c74f Mon Sep 17 00:00:00 2001 From: Corentin Aulagnet Date: Wed, 20 Jul 2022 19:29:23 +0200 Subject: [PATCH 6/6] Corrected a bug where the culture would not initialze properly hence displaying the initialization messages from the wrong locale. --- WFInfo/MainWindow.xaml.cs | 9 +++++++-- WFInfo/Settings/SettingsWindow.xaml.cs | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/WFInfo/MainWindow.xaml.cs b/WFInfo/MainWindow.xaml.cs index d68ca7c8..13600652 100644 --- a/WFInfo/MainWindow.xaml.cs +++ b/WFInfo/MainWindow.xaml.cs @@ -14,7 +14,8 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using WFInfo.Settings; - +using System.Globalization; +using System.Threading; namespace WFInfo { /// @@ -39,7 +40,11 @@ public MainWindow() Main.AddLog("Duplicate process found"); Close(); } - + //Set default culture of all new Thread to the selected culture and chage the culture of this thread + CultureInfo culture = CultureInfo.GetCultureInfo(_settingsViewModel.Locale); + CultureInfo.DefaultThreadCurrentCulture = culture; + CultureInfo.DefaultThreadCurrentUICulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; INSTANCE = this; main = new Main(); diff --git a/WFInfo/Settings/SettingsWindow.xaml.cs b/WFInfo/Settings/SettingsWindow.xaml.cs index 15a0344c..8de19752 100644 --- a/WFInfo/Settings/SettingsWindow.xaml.cs +++ b/WFInfo/Settings/SettingsWindow.xaml.cs @@ -186,7 +186,12 @@ private void localeComboboxSelectionChanged(object sender, System.Windows.Contro string oldLocale = _viewModel.Locale; _viewModel.Locale = selectedLocale; Save(); + //Set default culture of all new Thread to the selected culture and chage the culture of this thread + CultureInfo culture = CultureInfo.GetCultureInfo(selectedLocale); + CultureInfo.DefaultThreadCurrentCulture = culture; + CultureInfo.DefaultThreadCurrentUICulture = culture; Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(selectedLocale); + _ = OCR.updateEngineAsync(); _ = Task.Run(async () => {