From 8effc42f92a7f36163de306e5e30387f084e7e78 Mon Sep 17 00:00:00 2001 From: runebaas Date: Sun, 12 May 2019 00:17:34 +0200 Subject: [PATCH] Rewrite huge parts of the localization class to support plurals and localized date time formating, also created a TranslationGuildContext class for use in commands --- Geekbot.net/Commands/Rpg/Cookies.cs | 23 +-- .../Lib/Localization/ITranslationHandler.cs | 2 + .../Localization/TranslationGuildContext.cs | 90 ++++++++++++ .../Lib/Localization/TranslationHandler.cs | 41 ++++-- .../Lib/Localization/Translations.json | 134 ++++++++++-------- 5 files changed, 211 insertions(+), 79 deletions(-) create mode 100644 Geekbot.net/Lib/Localization/TranslationGuildContext.cs diff --git a/Geekbot.net/Commands/Rpg/Cookies.cs b/Geekbot.net/Commands/Rpg/Cookies.cs index 594dd5e..6d527ef 100644 --- a/Geekbot.net/Commands/Rpg/Cookies.cs +++ b/Geekbot.net/Commands/Rpg/Cookies.cs @@ -36,17 +36,18 @@ namespace Geekbot.net.Commands.Rpg { try { - var transDict = await _translation.GetDict(Context); + var transContext = await _translation.GetGuildContext(Context); var actor = await GetUser(Context.User.Id); if (actor.LastPayout.Value.AddHours(24) > DateTimeOffset.Now) { - await ReplyAsync(string.Format(transDict["WaitForMoreCookies"], actor.LastPayout.Value.AddHours(24).ToString("HH:mm:ss"))); + var formatedWaitTime = transContext.FormatDateTimeAsRemaining(actor.LastPayout.Value.AddHours(24)); + await ReplyAsync(transContext.GetString("WaitForMoreCookies", formatedWaitTime)); return; } actor.Cookies += 10; actor.LastPayout = DateTimeOffset.Now; await SetUser(actor); - await ReplyAsync(string.Format(transDict["GetCookies"], 10, actor.Cookies)); + await ReplyAsync(transContext.GetString("GetCookies", 10, actor.Cookies)); } catch (Exception e) @@ -61,9 +62,9 @@ namespace Geekbot.net.Commands.Rpg { try { - var transDict = await _translation.GetDict(Context); + var transContext = await _translation.GetGuildContext(Context); var actor = await GetUser(Context.User.Id); - await ReplyAsync(string.Format(transDict["InYourJar"], actor.Cookies)); + await ReplyAsync(transContext.GetString("InYourJar", actor.Cookies)); } catch (Exception e) { @@ -77,12 +78,12 @@ namespace Geekbot.net.Commands.Rpg { try { - var transDict = await _translation.GetDict(Context); + var transContext = await _translation.GetGuildContext(Context); var giver = await GetUser(Context.User.Id); if (giver.Cookies < amount) { - await ReplyAsync(string.Format(transDict["NotEnoughToGive"])); + await ReplyAsync(transContext.GetString("NotEnoughToGive")); return; } @@ -94,7 +95,7 @@ namespace Geekbot.net.Commands.Rpg await SetUser(giver); await SetUser(taker); - await ReplyAsync(string.Format(transDict["Given"], amount, user.Username)); + await ReplyAsync(transContext.GetString("Given", amount, user.Username)); } catch (Exception e) { @@ -108,12 +109,12 @@ namespace Geekbot.net.Commands.Rpg { try { - var transDict = await _translation.GetDict(Context); + var transContext = await _translation.GetGuildContext(Context); var actor = await GetUser(Context.User.Id); if (actor.Cookies < 5) { - await ReplyAsync(string.Format(transDict["NotEnoughCookiesToEat"])); + await ReplyAsync(transContext.GetString("NotEnoughCookiesToEat")); return; } @@ -122,7 +123,7 @@ namespace Geekbot.net.Commands.Rpg await SetUser(actor); - await ReplyAsync(string.Format(transDict["AteCookies"], amount, actor.Cookies)); + await ReplyAsync(transContext.GetString("AteCookies", amount, actor.Cookies)); } catch (Exception e) { diff --git a/Geekbot.net/Lib/Localization/ITranslationHandler.cs b/Geekbot.net/Lib/Localization/ITranslationHandler.cs index 2124d4e..7a46e56 100644 --- a/Geekbot.net/Lib/Localization/ITranslationHandler.cs +++ b/Geekbot.net/Lib/Localization/ITranslationHandler.cs @@ -7,8 +7,10 @@ namespace Geekbot.net.Lib.Localization public interface ITranslationHandler { Task GetString(ulong guildId, string command, string stringName); + List GetStrings(string language, string command, string stringName); Task> GetDict(ICommandContext context); Task> GetDict(ICommandContext context, string command); + Task GetGuildContext(ICommandContext context); Task SetLanguage(ulong guildId, string language); List SupportedLanguages { get; } } diff --git a/Geekbot.net/Lib/Localization/TranslationGuildContext.cs b/Geekbot.net/Lib/Localization/TranslationGuildContext.cs new file mode 100644 index 0000000..037ef3a --- /dev/null +++ b/Geekbot.net/Lib/Localization/TranslationGuildContext.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Geekbot.net.Lib.Localization +{ + public class TranslationGuildContext + { + public ITranslationHandler TranslationHandler { get; } + public string Language { get; } + public Dictionary Dict { get; } + + public TranslationGuildContext(ITranslationHandler translationHandler, string language, Dictionary dict) + { + TranslationHandler = translationHandler; + Language = language; + Dict = dict; + } + + public string GetString(string stringToFormat, params object[] args) + { + return string.Format(Dict[stringToFormat] ?? "", args); + } + + public string FormatDateTimeAsRemaining(DateTimeOffset dateTime) + { + var remaining = dateTime - DateTimeOffset.Now; + const string formattable = "{0} {1}"; + var sb = new StringBuilder(); + + if (remaining.Days > 0) + { + var s = GetTimeString(TimeTypes.Days); + sb.AppendFormat(formattable, remaining.Days, GetSingOrPlur(remaining.Days, s)); + } + + if (remaining.Hours > 0) + { + if (sb.Length > 0) sb.Append(", "); + var s = GetTimeString(TimeTypes.Hours); + sb.AppendFormat(formattable, remaining.Hours, GetSingOrPlur(remaining.Hours, s)); + } + + if (remaining.Minutes > 0) + { + if (sb.Length > 0) sb.Append(", "); + var s = GetTimeString(TimeTypes.Minutes); + sb.AppendFormat(formattable, remaining.Minutes, GetSingOrPlur(remaining.Minutes, s)); + } + + if (remaining.Seconds > 0) + { + if (sb.Length > 0) + { + var and = TranslationHandler.GetStrings(Language, "dateTime", "And").First(); + sb.AppendFormat(" {0} ", and); + } + var s = GetTimeString(TimeTypes.Seconds); + sb.AppendFormat(formattable, remaining.Seconds, GetSingOrPlur(remaining.Seconds, s)); + } + + return sb.ToString().Trim(); + } + + public Task SetLanguage(ulong guildId, string language) + { + return TranslationHandler.SetLanguage(guildId, language); + } + + private List GetTimeString(TimeTypes type) + { + return TranslationHandler.GetStrings(Language, "dateTime", type.ToString()); + } + + private string GetSingOrPlur(int number, List versions) + { + return number == 1 ? versions[0] : versions[1]; + } + + private enum TimeTypes + { + Days, + Hours, + Minutes, + Seconds + } + } +} \ No newline at end of file diff --git a/Geekbot.net/Lib/Localization/TranslationHandler.cs b/Geekbot.net/Lib/Localization/TranslationHandler.cs index a1b650b..b61c93c 100644 --- a/Geekbot.net/Lib/Localization/TranslationHandler.cs +++ b/Geekbot.net/Lib/Localization/TranslationHandler.cs @@ -17,7 +17,7 @@ namespace Geekbot.net.Lib.Localization private readonly DatabaseContext _database; private readonly IGeekbotLogger _logger; private readonly Dictionary _serverLanguages; - private Dictionary>> _translations; + private Dictionary>>> _translations; public TranslationHandler(DatabaseContext database, IGeekbotLogger logger) { @@ -33,8 +33,8 @@ namespace Geekbot.net.Lib.Localization try { var translationFile = File.ReadAllText(Path.GetFullPath("./Lib/Localization/Translations.json")); - var rawTranslations = JsonSerializer.Deserialize>>>(translationFile); - var sortedPerLanguage = new Dictionary>>(); + var rawTranslations = JsonSerializer.Deserialize>>>>(translationFile); + var sortedPerLanguage = new Dictionary>>>(); foreach (var command in rawTranslations) { foreach (var str in command.Value) @@ -43,8 +43,8 @@ namespace Geekbot.net.Lib.Localization { if (!sortedPerLanguage.ContainsKey(lang.Key)) { - var commandDict = new Dictionary>(); - var strDict = new Dictionary + var commandDict = new Dictionary>>(); + var strDict = new Dictionary> { {str.Key, lang.Value} }; @@ -53,8 +53,10 @@ namespace Geekbot.net.Lib.Localization } if (!sortedPerLanguage[lang.Key].ContainsKey(command.Key)) { - var strDict = new Dictionary(); - strDict.Add(str.Key, lang.Value); + var strDict = new Dictionary> + { + {str.Key, lang.Value} + }; sortedPerLanguage[lang.Key].Add(command.Key, strDict); } if (!sortedPerLanguage[lang.Key][command.Key].ContainsKey(str.Key)) @@ -109,10 +111,16 @@ namespace Geekbot.net.Lib.Localization public async Task GetString(ulong guildId, string command, string stringName) { - var translation = _translations[await GetServerLanguage(guildId)][command][stringName]; - if (!string.IsNullOrWhiteSpace(translation)) return translation; + var serverLang = await GetServerLanguage(guildId); + return GetStrings(serverLang, command, stringName).First(); + } + + public List GetStrings(string language, string command, string stringName) + { + var translation = _translations[language][command][stringName]; + if (!string.IsNullOrWhiteSpace(translation.First())) return translation; translation = _translations[command][stringName]["EN"]; - if (string.IsNullOrWhiteSpace(translation)) + if (string.IsNullOrWhiteSpace(translation.First())) { _logger.Warning(LogSource.Geekbot, $"No translation found for {command} - {stringName}"); } @@ -125,7 +133,8 @@ namespace Geekbot.net.Lib.Localization { var command = context.Message.Content.Split(' ').First().TrimStart('!').ToLower(); var serverLanguage = await GetServerLanguage(context.Guild?.Id ?? 0); - return _translations[serverLanguage][command]; + return _translations[serverLanguage][command] + .ToDictionary(dict => dict.Key, dict => dict.Value.First()); } catch (Exception e) { @@ -133,13 +142,21 @@ namespace Geekbot.net.Lib.Localization return new Dictionary(); } } + + public async Task GetGuildContext(ICommandContext context) + { + var dict = await GetDict(context); + var language = await GetServerLanguage(context.Guild?.Id ?? 0); + return new TranslationGuildContext(this, language, dict); + } public async Task> GetDict(ICommandContext context, string command) { try { var serverLanguage = await GetServerLanguage(context.Guild?.Id ?? 0); - return _translations[serverLanguage][command]; + return _translations[serverLanguage][command] + .ToDictionary(dict => dict.Key, dict => dict.Value.First()); } catch (Exception e) { diff --git a/Geekbot.net/Lib/Localization/Translations.json b/Geekbot.net/Lib/Localization/Translations.json index 7747016..356deb5 100644 --- a/Geekbot.net/Lib/Localization/Translations.json +++ b/Geekbot.net/Lib/Localization/Translations.json @@ -1,130 +1,152 @@ { + "dateTime": { + "Days": { + "EN": ["day", "days"], + "CHDE": ["tag", "täg"] + }, + "Hours": { + "EN": ["hour", "hours"], + "CHDE": ["stund", "stunde"] + }, + "Minutes": { + "EN": ["minute", "minutes"], + "CHDE": ["minute", "minute"] + }, + "Seconds": { + "EN": ["second", "seconds"], + "CHDE": ["sekunde", "sekunde"] + }, + "And": { + "EN": ["and"], + "CHDE": ["und"] + } + }, "admin": { "NewLanguageSet": { - "EN": "I will reply in english from now on", - "CHDE": "I werd ab jetzt uf schwiizerdüütsch antworte, äuuä" + "EN": ["I will reply in english from now on"], + "CHDE": ["I werd ab jetzt uf schwiizerdüütsch antworte, äuuä"] }, "GetLanguage": { - "EN": "I'm talking english", - "CHDE": "I red schwiizerdüütsch" + "EN": ["I'm talking english"], + "CHDE": ["I red schwiizerdüütsch"] } }, "errorHandler": { "SomethingWentWrong": { - "EN": "Something went wrong :confused:", - "CHDE": "Öppis isch schief gange :confused:" + "EN": ["Something went wrong :confused:"], + "CHDE": ["Öppis isch schief gange :confused:"] } }, "httpErrors": { "403": { - "EN": "Seems like i don't have enough permission to that :confused:", - "CHDE": "Gseht danach us das ich nid gnueg recht han zum das mache :confused:" + "EN": ["Seems like i don't have enough permission to that :confused:"], + "CHDE": ["Gseht danach us das ich nid gnueg recht han zum das mache :confused:"] } }, "choose": { "Choice": { - "EN": "I Choose **{0}**", - "CHDE": "I nimme **{0}**" + "EN": ["I Choose **{0}**"], + "CHDE": ["I nimme **{0}**"] } }, "good": { "CannotChangeOwn": { - "EN": "Sorry {0}, but you can't give yourself karma", - "CHDE": "Sorry {0}, aber du chasch dr selber kei karma geh" + "EN": ["Sorry {0}, but you can't give yourself karma"], + "CHDE": ["Sorry {0}, aber du chasch dr selber kei karma geh"] }, "WaitUntill": { - "EN": "Sorry {0}, but you have to wait {1} before you can give karma again...", - "CHDE": "Sorry {0}, aber du musch no {1} warte bisch d wieder karma chasch geh..." + "EN": ["Sorry {0}, but you have to wait {1} before you can give karma again..."], + "CHDE": ["Sorry {0}, aber du musch no {1} warte bisch d wieder karma chasch geh..."] }, "Increased": { - "EN": "Karma gained", - "CHDE": "Karma becho" + "EN": ["Karma gained"], + "CHDE": ["Karma becho"] }, "By": { - "EN": "By", - "CHDE": "Vo" + "EN": ["By"], + "CHDE": ["Vo"] }, "Amount": { - "EN": "Amount", - "CHDE": "Mengi" + "EN": ["Amount"], + "CHDE": ["Mengi"] }, "Current": { - "EN": "Current", - "CHDE": "Jetzt" + "EN": ["Current"], + "CHDE": ["Jetzt"] } }, "bad": { "CannotChangeOwn": { - "EN": "Sorry {0}, but you can't lower your own karma", - "CHDE": "Sorry {0}, aber du chasch dr din eigete karma nid weg neh" + "EN": ["Sorry {0}, but you can't lower your own karma"], + "CHDE": ["Sorry {0}, aber du chasch dr din eigete karma nid weg neh"] }, "WaitUntill": { - "EN": "Sorry {0}, but you have to wait {1} before you can lower karma again...", - "CHDE": "Sorry {0}, aber du musch no {1} warte bisch d wieder karma chasch senke..." + "EN": ["Sorry {0}, but you have to wait {1} before you can lower karma again..."], + "CHDE": ["Sorry {0}, aber du musch no {1} warte bisch d wieder karma chasch senke..."] }, "Decreased": { - "EN": "Karma lowered", - "CHDE": "Karma gsenkt" + "EN": ["Karma lowered"], + "CHDE": ["Karma gsenkt"] }, "By": { - "EN": "By", - "CHDE": "Vo" + "EN": ["By"], + "CHDE": ["Vo"] }, "Amount": { - "EN": "Amount", - "CHDE": "Mengi" + "EN": ["Amount"], + "CHDE": ["Mengi"] }, "Current": { - "EN": "Current", - "CHDE": "Jetzt" + "EN": ["Current"], + "CHDE": ["Jetzt"] } }, "roll": { "Rolled": { - "EN": "{0}, you rolled {1}, your guess was {2}", - "CHDE": "{0}, du hesch {1} grollt und hesch {2} grate" + "EN": ["{0}, you rolled {1}, your guess was {2}"], + "CHDE": ["{0}, du hesch {1} grollt und hesch {2} grate"] }, "Gratz": { - "EN": "Congratulations {0}, your guess was correct!", - "CHDE": "Gratuliere {0}, du hesch richtig grate!" + "EN": ["Congratulations {0}, your guess was correct!"], + "CHDE": ["Gratuliere {0}, du hesch richtig grate!"] }, "RolledNoGuess": { - "EN": "{0}, you rolled {1}", - "CHDE": "{0}, du hesch {1} grollt" + "EN": ["{0}, you rolled {1}"], + "CHDE": ["{0}, du hesch {1} grollt"] }, "NoPrevGuess": { - "EN": ":red_circle: {0}, you can't guess the same number again", - "CHDE": ":red_circle: {0}, du chasch nid nomol es gliche rate" + "EN": [":red_circle: {0}, you can't guess the same number again"], + "CHDE": [":red_circle: {0}, du chasch nid nomol es gliche rate"] } }, "cookie": { "GetCookies": { - "EN": "You got {0} cookies, there are now {1} cookies in you cookie jar", - "CHDE": "Du häsch {0} guetzli becho, du häsch jetzt {1} guetzli ih dr büchse" + "EN": ["You got {0} cookies, there are now {1} cookies in you cookie jar"], + "CHDE": ["Du häsch {0} guetzli becho, du häsch jetzt {1} guetzli ih dr büchse"] }, "WaitForMoreCookies": { - "EN": "You already got cookies in the last 24 hours, wait until {0} for more cookies", - "CHDE": "Du hesch scho guetzli becho ih de letzti 24 stund, wart no bis {0}" + "EN": ["You already got cookies in the last 24 hours, you can have more cookies in {0}"], + "CHDE": ["Du hesch scho guetzli becho ih de letzti 24 stund, du chasch meh ha in {0}"] }, "InYourJar": { - "EN": "There are {0} cookies in you cookie jar", - "CHDE": "Es hät {0} guetzli ih dineri büchs" + "EN": ["There are {0} cookies in you cookie jar"], + "CHDE": ["Es hät {0} guetzli ih dineri büchs"] }, "Given": { - "EN": "You gave {0} cookies to {1}", - "CHDE": "Du hesch {1} {0} guetzli geh" + "EN": ["You gave {0} cookies to {1}"], + "CHDE": ["Du hesch {1} {0} guetzli geh"] }, "NotEnoughToGive": { - "EN": "You don't have enough cookies", - "CHDE": "Du hesch nid gnueg guetzli" + "EN": ["You don't have enough cookies"], + "CHDE": ["Du hesch nid gnueg guetzli"] }, "NotEnoughCookiesToEat": { - "EN": "Your cookie jar looks almost empty, you should probably not eat a cookie", - "CHDE": "Du hesch chuum no guetzli ih dineri büchs, du sötsch warschinli keini esse" + "EN": ["Your cookie jar looks almost empty, you should probably not eat a cookie"], + "CHDE": ["Du hesch chuum no guetzli ih dineri büchs, du sötsch warschinli keini esse"] }, "AteCookies": { - "EN": "You ate {0} cookies, you've only got {1} cookies left", - "CHDE": "Du hesch {0} guetzli gesse und hesch jezt no {1} übrig" + "EN": ["You ate {0} cookies, you've only got {1} cookies left"], + "CHDE": ["Du hesch {0} guetzli gesse und hesch jezt no {1} übrig"] } } } \ No newline at end of file