From 634cb0ce84075cd647ac4b443dd8e6db911c2305 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 13 Nov 2021 14:09:05 +0100 Subject: [PATCH 01/10] Add ability to alter format string in translatable string. --- osu.Framework/Localisation/TranslatableString.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Framework/Localisation/TranslatableString.cs b/osu.Framework/Localisation/TranslatableString.cs index b9eeb465f2..e6e44a5e5e 100644 --- a/osu.Framework/Localisation/TranslatableString.cs +++ b/osu.Framework/Localisation/TranslatableString.cs @@ -48,12 +48,14 @@ public TranslatableString(string key, FormattableString interpolation) Args = interpolation.GetArguments(); } + protected virtual string GetLocalisedFormat(LocalisationParameters parameters, string format) => format; + public string GetLocalised(LocalisationParameters parameters) { if (parameters.Store == null) return ToString(); - string localisedFormat = parameters.Store.Get(Key) ?? Fallback; + string localisedFormat = GetLocalisedFormat(parameters, parameters.Store.Get(Key) ?? Fallback); try { @@ -71,14 +73,14 @@ public string GetLocalised(LocalisationParameters parameters) catch (FormatException e) { // The formatting has failed - Logger.Log($"Localised format failed. Key: {Key}, culture: {parameters.Store.EffectiveCulture}, fallback format string: \"{Fallback}\", localised format string: \"{localisedFormat}\". Exception: {e}", + Logger.Log($"Localised format failed. Key: {Key}, culture: {parameters.Store.EffectiveCulture}, fallback format string: \"{GetLocalisedFormat(parameters, Fallback)}\", localised format string: \"{localisedFormat}\". Exception: {e}", LoggingTarget.Runtime, LogLevel.Verbose); } return ToString(); } - public override string ToString() => string.Format(CultureInfo.InvariantCulture, Fallback, Args); + public override string ToString() => string.Format(CultureInfo.InvariantCulture, GetLocalisedFormat(new LocalisationParameters(null, false), Fallback), Args); public bool Equals(TranslatableString? other) { From 233e47fbc25f657fe41384bb97c6d6cfa62507ea Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 13 Nov 2021 14:35:00 +0100 Subject: [PATCH 02/10] Introduce a PluralisableString class. --- .../Localisation/LocalisationTest.cs | 56 +++ .../Localisation/PluralisableString.cs | 341 ++++++++++++++++++ .../Localisation/TranslatableString.cs | 2 +- 3 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 osu.Framework/Localisation/PluralisableString.cs diff --git a/osu.Framework.Tests/Localisation/LocalisationTest.cs b/osu.Framework.Tests/Localisation/LocalisationTest.cs index 05fc1abedf..bb2f76e9d1 100644 --- a/osu.Framework.Tests/Localisation/LocalisationTest.cs +++ b/osu.Framework.Tests/Localisation/LocalisationTest.cs @@ -378,6 +378,44 @@ public void TestTranslatableComplexStringUsesFallbackFormatWithTranslatedParts() Assert.AreEqual("12.34 / number 98.76% EN / number romanised EN", text.Value); } + [Test] + public void TestPluralisableString() + { + const string key = FakeStorage.LOCALISABLE_PLURALISABLE_STRING_EN; + + manager.AddLanguage("fr", new FakeStorage("fr")); + + var textSingularVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, 1)); + Assert.AreEqual("1 circle", textSingularVariant.Value); + + config.SetValue(FrameworkSetting.Locale, "fr"); + Assert.AreEqual("1 cercle", textSingularVariant.Value); + + var textPluralVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 2, 2)); + Assert.AreEqual("2 cercles", textPluralVariant.Value); + + config.SetValue(FrameworkSetting.Locale, "en"); + Assert.AreEqual("2 circles", textPluralVariant.Value); + } + + [Test] + public void TestPluralisableStringNonEnglishPluralRules() + { + const string key = FakeStorage.LOCALISABLE_PLURALISABLE_STRING_EN; + + manager.AddLanguage("pl", new FakeStorage("pl")); + config.SetValue(FrameworkSetting.Locale, "pl"); + + var textFirstVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, 1)); + Assert.AreEqual("1 krąg", textFirstVariant.Value); + + var textSecondVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 3, 3)); + Assert.AreEqual("3 kręgi", textSecondVariant.Value); + + var textThirdVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 13, 13)); + Assert.AreEqual("13 kręgów", textThirdVariant.Value); + } + private class FakeFrameworkConfigManager : FrameworkConfigManager { protected override string Filename => null; @@ -406,6 +444,9 @@ private class FakeStorage : ILocalisationStore public const string LOCALISABLE_NUMBER_FORMAT_STRING_FR = "number {0} FR"; public const string LOCALISABLE_COMPLEX_FORMAT_STRING_EN = "number {0} with {1} and {2} EN"; public const string LOCALISABLE_COMPLEX_FORMAT_STRING_FR = "number {0} with {1} and {2} FR"; + public const string LOCALISABLE_PLURALISABLE_STRING_EN = "{0} circle|{0} circles"; + public const string LOCALISABLE_PLURALISABLE_STRING_FR = "{0} cercle|{0} cercles"; + public const string LOCALISABLE_PLURALISABLE_STRING_PL = "{0} krąg|{0} kręgi|{0} kręgów"; public CultureInfo EffectiveCulture { get; } @@ -469,6 +510,21 @@ public string Get(string name) return LOCALISABLE_COMPLEX_FORMAT_STRING_FR; } + case LOCALISABLE_PLURALISABLE_STRING_EN: + { + switch (locale) + { + default: + return LOCALISABLE_PLURALISABLE_STRING_EN; + + case "fr": + return LOCALISABLE_PLURALISABLE_STRING_FR; + + case "pl": + return LOCALISABLE_PLURALISABLE_STRING_PL; + } + } + default: return null; } diff --git a/osu.Framework/Localisation/PluralisableString.cs b/osu.Framework/Localisation/PluralisableString.cs new file mode 100644 index 0000000000..97f4ecab82 --- /dev/null +++ b/osu.Framework/Localisation/PluralisableString.cs @@ -0,0 +1,341 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; + +#nullable enable + +namespace osu.Framework.Localisation +{ + public class PluralisableString : TranslatableString + { + private const char variant_delimiter = '|'; + + public readonly int Count; + + public PluralisableString(string key, string fallback, int count, params object[] args) + : base(key, fallback, args) + { + Count = count; + } + + public PluralisableString(string key, FormattableString interpolation, int count) + : base(key, interpolation) + { + Count = count; + } + + protected override string GetLocalisedFormat(LocalisationParameters parameters, string format) + { + string[] variants = format.Split(variant_delimiter); + return variants.ElementAtOrDefault(getPluralIndex(parameters)) ?? variants.ElementAt(variants.Length); + } + + public bool Equals(PluralisableString? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Count == other.Count && Equals(other as TranslatableString); + } + + public override bool Equals(ILocalisableStringData? other) => other is PluralisableString pluralisable && Equals(pluralisable); + public override bool Equals(object? obj) => obj is PluralisableString pluralisable && Equals(pluralisable); + + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(base.GetHashCode()); + hashCode.Add(Count); + return hashCode.ToHashCode(); + } + + #region Mapping of plural variant indices according to locale + + private int getPluralIndex(LocalisationParameters parameters) + { + switch (parameters.Store?.EffectiveCulture?.Name ?? string.Empty) + { + case "af": + case "af_ZA": + case "bn": + case "bn_BD": + case "bn_IN": + case "bg": + case "bg_BG": + case "ca": + case "ca_AD": + case "ca_ES": + case "ca_FR": + case "ca_IT": + case "da": + case "da_DK": + case "de": + case "de_AT": + case "de_BE": + case "de_CH": + case "de_DE": + case "de_LI": + case "de_LU": + case "el": + case "el_CY": + case "el_GR": + case "en": + case "en_AG": + case "en_AU": + case "en_BW": + case "en_CA": + case "en_DK": + case "en_GB": + case "en_HK": + case "en_IE": + case "en_IN": + case "en_NG": + case "en_NZ": + case "en_PH": + case "en_SG": + case "en_US": + case "en_ZA": + case "en_ZM": + case "en_ZW": + case "eo": + case "eo_US": + case "es": + case "es_AR": + case "es_BO": + case "es_CL": + case "es_CO": + case "es_CR": + case "es_CU": + case "es_DO": + case "es_EC": + case "es_ES": + case "es_GT": + case "es_HN": + case "es_MX": + case "es_NI": + case "es_PA": + case "es_PE": + case "es_PR": + case "es_PY": + case "es_SV": + case "es_US": + case "es_UY": + case "es_VE": + case "et": + case "et_EE": + case "eu": + case "eu_ES": + case "eu_FR": + case "fa": + case "fa_IR": + case "fi": + case "fi_FI": + case "fo": + case "fo_FO": + case "fur": + case "fur_IT": + case "fy": + case "fy_DE": + case "fy_NL": + case "gl": + case "gl_ES": + case "gu": + case "gu_IN": + case "ha": + case "ha_NG": + case "he": + case "he_IL": + case "hu": + case "hu_HU": + case "is": + case "is_IS": + case "it": + case "it_CH": + case "it_IT": + case "ku": + case "ku_TR": + case "lb": + case "lb_LU": + case "ml": + case "ml_IN": + case "mn": + case "mn_MN": + case "mr": + case "mr_IN": + case "nah": + case "nb": + case "nb_NO": + case "ne": + case "ne_NP": + case "nl": + case "nl_AW": + case "nl_BE": + case "nl_NL": + case "nn": + case "nn_NO": + case "no": + case "om": + case "om_ET": + case "om_KE": + case "or": + case "or_IN": + case "pa": + case "pa_IN": + case "pa_PK": + case "pap": + case "pap_AN": + case "pap_AW": + case "pap_CW": + case "ps": + case "ps_AF": + case "pt": + case "pt_BR": + case "pt_PT": + case "so": + case "so_DJ": + case "so_ET": + case "so_KE": + case "so_SO": + case "sq": + case "sq_AL": + case "sq_MK": + case "sv": + case "sv_FI": + case "sv_SE": + case "sw": + case "sw_KE": + case "sw_TZ": + case "ta": + case "ta_IN": + case "ta_LK": + case "te": + case "te_IN": + case "tk": + case "tk_TM": + case "ur": + case "ur_IN": + case "ur_PK": + case "zu": + case "zu_ZA": + return (Count == 1) ? 0 : 1; + + case "am": + case "am_ET": + case "bh": + case "fil": + case "fil_PH": + case "fr": + case "fr_BE": + case "fr_CA": + case "fr_CH": + case "fr_FR": + case "fr_LU": + case "gun": + case "hi": + case "hi_IN": + case "hy": + case "hy_AM": + case "ln": + case "ln_CD": + case "mg": + case "mg_MG": + case "nso": + case "nso_ZA": + case "ti": + case "ti_ER": + case "ti_ET": + case "wa": + case "wa_BE": + case "xbr": + return ((Count == 0) || (Count == 1)) ? 0 : 1; + + case "be": + case "be_BY": + case "bs": + case "bs_BA": + case "hr": + case "hr_HR": + case "ru": + case "ru_RU": + case "ru_UA": + case "sr": + case "sr_ME": + case "sr_RS": + case "uk": + case "uk_UA": + return ((Count % 10 == 1) && (Count % 100 != 11)) ? 0 : (((Count % 10 >= 2) && (Count % 10 <= 4) && ((Count % 100 < 10) || (Count % 100 >= 20))) ? 1 : 2); + + case "cs": + case "cs_CZ": + case "sk": + case "sk_SK": + return (Count == 1) ? 0 : (((Count >= 2) && (Count <= 4)) ? 1 : 2); + + case "ga": + case "ga_IE": + return (Count == 1) ? 0 : ((Count == 2) ? 1 : 2); + + case "lt": + case "lt_LT": + return ((Count % 10 == 1) && (Count % 100 != 11)) ? 0 : (((Count % 10 >= 2) && ((Count % 100 < 10) || (Count % 100 >= 20))) ? 1 : 2); + + case "sl": + case "sl_SI": + return (Count % 100 == 1) ? 0 : ((Count % 100 == 2) ? 1 : (((Count % 100 == 3) || (Count % 100 == 4)) ? 2 : 3)); + + case "mk": + case "mk_MK": + return (Count % 10 == 1) ? 0 : 1; + + case "mt": + case "mt_MT": + return (Count == 1) ? 0 : (((Count == 0) || ((Count % 100 > 1) && (Count % 100 < 11))) ? 1 : (((Count % 100 > 10) && (Count % 100 < 20)) ? 2 : 3)); + + case "lv": + case "lv_LV": + return (Count == 0) ? 0 : (((Count % 10 == 1) && (Count % 100 != 11)) ? 1 : 2); + + case "pl": + case "pl_PL": + return (Count == 1) ? 0 : (((Count % 10 >= 2) && (Count % 10 <= 4) && ((Count % 100 < 12) || (Count % 100 > 14))) ? 1 : 2); + + case "cy": + case "cy_GB": + return (Count == 1) ? 0 : ((Count == 2) ? 1 : (((Count == 8) || (Count == 11)) ? 2 : 3)); + + case "ro": + case "ro_RO": + return (Count == 1) ? 0 : (((Count == 0) || ((Count % 100 > 0) && (Count % 100 < 20))) ? 1 : 2); + + case "ar": + case "ar_AE": + case "ar_BH": + case "ar_DZ": + case "ar_EG": + case "ar_IN": + case "ar_IQ": + case "ar_JO": + case "ar_KW": + case "ar_LB": + case "ar_LY": + case "ar_MA": + case "ar_OM": + case "ar_QA": + case "ar_SA": + case "ar_SD": + case "ar_SS": + case "ar_SY": + case "ar_TN": + case "ar_YE": + return (Count == 0) ? 0 : ((Count == 1) ? 1 : ((Count == 2) ? 2 : (((Count % 100 >= 3) && (Count % 100 <= 10)) ? 3 : (((Count % 100 >= 11) && (Count % 100 <= 99)) ? 4 : 5)))); + + default: + return 0; + } + } + + #endregion + } +} diff --git a/osu.Framework/Localisation/TranslatableString.cs b/osu.Framework/Localisation/TranslatableString.cs index e6e44a5e5e..be4ac0e2e2 100644 --- a/osu.Framework/Localisation/TranslatableString.cs +++ b/osu.Framework/Localisation/TranslatableString.cs @@ -92,7 +92,7 @@ public bool Equals(TranslatableString? other) && Args.SequenceEqual(other.Args); } - public bool Equals(ILocalisableStringData? other) => other is TranslatableString translatable && Equals(translatable); + public virtual bool Equals(ILocalisableStringData? other) => other is TranslatableString translatable && Equals(translatable); public override bool Equals(object? obj) => obj is TranslatableString translatable && Equals(translatable); public override int GetHashCode() From 562754a286e7f4c10b4ca3c1249c589d6973dd68 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 13 Nov 2021 14:43:57 +0100 Subject: [PATCH 03/10] Add XMLDoc --- osu.Framework/Localisation/PluralisableString.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Framework/Localisation/PluralisableString.cs b/osu.Framework/Localisation/PluralisableString.cs index 97f4ecab82..28fabd19da 100644 --- a/osu.Framework/Localisation/PluralisableString.cs +++ b/osu.Framework/Localisation/PluralisableString.cs @@ -8,6 +8,9 @@ namespace osu.Framework.Localisation { + /// + /// A string which can display a plural variant of a localised string according to the plural context. + /// public class PluralisableString : TranslatableString { private const char variant_delimiter = '|'; From 91cfc66427030f8f5993d747ade74f029ac4fadc Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 13 Nov 2021 14:59:55 +0100 Subject: [PATCH 04/10] Make separator a parameter. --- osu.Framework.Tests/Localisation/LocalisationTest.cs | 10 +++++----- osu.Framework/Localisation/PluralisableString.cs | 12 +++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Framework.Tests/Localisation/LocalisationTest.cs b/osu.Framework.Tests/Localisation/LocalisationTest.cs index bb2f76e9d1..cbe9bb861f 100644 --- a/osu.Framework.Tests/Localisation/LocalisationTest.cs +++ b/osu.Framework.Tests/Localisation/LocalisationTest.cs @@ -385,13 +385,13 @@ public void TestPluralisableString() manager.AddLanguage("fr", new FakeStorage("fr")); - var textSingularVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, 1)); + var textSingularVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, '|', 1)); Assert.AreEqual("1 circle", textSingularVariant.Value); config.SetValue(FrameworkSetting.Locale, "fr"); Assert.AreEqual("1 cercle", textSingularVariant.Value); - var textPluralVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 2, 2)); + var textPluralVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 2, '|', 2)); Assert.AreEqual("2 cercles", textPluralVariant.Value); config.SetValue(FrameworkSetting.Locale, "en"); @@ -406,13 +406,13 @@ public void TestPluralisableStringNonEnglishPluralRules() manager.AddLanguage("pl", new FakeStorage("pl")); config.SetValue(FrameworkSetting.Locale, "pl"); - var textFirstVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, 1)); + var textFirstVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, '|', 1)); Assert.AreEqual("1 krąg", textFirstVariant.Value); - var textSecondVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 3, 3)); + var textSecondVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 3, '|', 3)); Assert.AreEqual("3 kręgi", textSecondVariant.Value); - var textThirdVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 13, 13)); + var textThirdVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 13, '|', 13)); Assert.AreEqual("13 kręgów", textThirdVariant.Value); } diff --git a/osu.Framework/Localisation/PluralisableString.cs b/osu.Framework/Localisation/PluralisableString.cs index 28fabd19da..b1d6636bf1 100644 --- a/osu.Framework/Localisation/PluralisableString.cs +++ b/osu.Framework/Localisation/PluralisableString.cs @@ -13,25 +13,27 @@ namespace osu.Framework.Localisation /// public class PluralisableString : TranslatableString { - private const char variant_delimiter = '|'; - public readonly int Count; - public PluralisableString(string key, string fallback, int count, params object[] args) + public readonly char Separator; + + public PluralisableString(string key, string fallback, int count, char separator, params object[] args) : base(key, fallback, args) { Count = count; + Separator = separator; } - public PluralisableString(string key, FormattableString interpolation, int count) + public PluralisableString(string key, FormattableString interpolation, int count, char separator) : base(key, interpolation) { Count = count; + Separator = separator; } protected override string GetLocalisedFormat(LocalisationParameters parameters, string format) { - string[] variants = format.Split(variant_delimiter); + string[] variants = format.Split(Separator); return variants.ElementAtOrDefault(getPluralIndex(parameters)) ?? variants.ElementAt(variants.Length); } From e7a1c212f218ca3933b7104383a41c6f2dd153a7 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 13 Nov 2021 15:04:59 +0100 Subject: [PATCH 05/10] Fix miscalculated index. --- osu.Framework/Localisation/PluralisableString.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Framework/Localisation/PluralisableString.cs b/osu.Framework/Localisation/PluralisableString.cs index b1d6636bf1..2383985925 100644 --- a/osu.Framework/Localisation/PluralisableString.cs +++ b/osu.Framework/Localisation/PluralisableString.cs @@ -34,7 +34,7 @@ public PluralisableString(string key, FormattableString interpolation, int count protected override string GetLocalisedFormat(LocalisationParameters parameters, string format) { string[] variants = format.Split(Separator); - return variants.ElementAtOrDefault(getPluralIndex(parameters)) ?? variants.ElementAt(variants.Length); + return variants.ElementAtOrDefault(getPluralIndex(parameters)) ?? variants.ElementAt(variants.Length - 1); } public bool Equals(PluralisableString? other) From 2183ee57dd44daa897ee70cb530c3133cc2aa13e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 13 Nov 2021 20:48:04 +0100 Subject: [PATCH 06/10] Add missing XMLDoc --- .../Localisation/PluralisableString.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Framework/Localisation/PluralisableString.cs b/osu.Framework/Localisation/PluralisableString.cs index 2383985925..0bdeb8d9f5 100644 --- a/osu.Framework/Localisation/PluralisableString.cs +++ b/osu.Framework/Localisation/PluralisableString.cs @@ -13,10 +13,24 @@ namespace osu.Framework.Localisation /// public class PluralisableString : TranslatableString { + /// + /// The plural count to use when applying plural rules. + /// public readonly int Count; + /// + /// The character to use as a plural variant separator in localised text. + /// public readonly char Separator; + /// + /// Creates a using texts. + /// + /// The key for to look up with. + /// The fallback string to use when no translation can be found. + /// The plural count to use when applying plural rules. + /// The character to use as a plural variant separator in localised text. + /// Optional formattable arguments. public PluralisableString(string key, string fallback, int count, char separator, params object[] args) : base(key, fallback, args) { @@ -24,6 +38,13 @@ public PluralisableString(string key, string fallback, int count, char separator Separator = separator; } + /// + /// Creates a using interpolated string. + /// + /// The key for to look up with. + /// The interpolated string containing fallback and formattable arguments. + /// The plural count to use when applying plural rules. + /// The character to use as a plural variant separator in localised text. public PluralisableString(string key, FormattableString interpolation, int count, char separator) : base(key, interpolation) { From 194b10b26dc055a713a164006d9dfd0a072fa010 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 14 Nov 2021 14:41:45 +0100 Subject: [PATCH 07/10] Update hashcode for pluralisable string. --- osu.Framework/Localisation/PluralisableString.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Framework/Localisation/PluralisableString.cs b/osu.Framework/Localisation/PluralisableString.cs index 0bdeb8d9f5..41a591e233 100644 --- a/osu.Framework/Localisation/PluralisableString.cs +++ b/osu.Framework/Localisation/PluralisableString.cs @@ -74,6 +74,7 @@ public override int GetHashCode() var hashCode = new HashCode(); hashCode.Add(base.GetHashCode()); hashCode.Add(Count); + hashCode.Add(Separator); return hashCode.ToHashCode(); } From d03d672525c6606254a8ebeb643fd152d3eaabf2 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sun, 14 Nov 2021 14:54:35 +0100 Subject: [PATCH 08/10] Make variant delimiter const again. --- .../Localisation/LocalisationTest.cs | 10 +++++----- .../Localisation/PluralisableString.cs | 18 +++++------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/osu.Framework.Tests/Localisation/LocalisationTest.cs b/osu.Framework.Tests/Localisation/LocalisationTest.cs index cbe9bb861f..bb2f76e9d1 100644 --- a/osu.Framework.Tests/Localisation/LocalisationTest.cs +++ b/osu.Framework.Tests/Localisation/LocalisationTest.cs @@ -385,13 +385,13 @@ public void TestPluralisableString() manager.AddLanguage("fr", new FakeStorage("fr")); - var textSingularVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, '|', 1)); + var textSingularVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, 1)); Assert.AreEqual("1 circle", textSingularVariant.Value); config.SetValue(FrameworkSetting.Locale, "fr"); Assert.AreEqual("1 cercle", textSingularVariant.Value); - var textPluralVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 2, '|', 2)); + var textPluralVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 2, 2)); Assert.AreEqual("2 cercles", textPluralVariant.Value); config.SetValue(FrameworkSetting.Locale, "en"); @@ -406,13 +406,13 @@ public void TestPluralisableStringNonEnglishPluralRules() manager.AddLanguage("pl", new FakeStorage("pl")); config.SetValue(FrameworkSetting.Locale, "pl"); - var textFirstVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, '|', 1)); + var textFirstVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 1, 1)); Assert.AreEqual("1 krąg", textFirstVariant.Value); - var textSecondVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 3, '|', 3)); + var textSecondVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 3, 3)); Assert.AreEqual("3 kręgi", textSecondVariant.Value); - var textThirdVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 13, '|', 13)); + var textThirdVariant = manager.GetLocalisedBindableString(new PluralisableString(key, key, 13, 13)); Assert.AreEqual("13 kręgów", textThirdVariant.Value); } diff --git a/osu.Framework/Localisation/PluralisableString.cs b/osu.Framework/Localisation/PluralisableString.cs index 41a591e233..9260cd9696 100644 --- a/osu.Framework/Localisation/PluralisableString.cs +++ b/osu.Framework/Localisation/PluralisableString.cs @@ -13,29 +13,24 @@ namespace osu.Framework.Localisation /// public class PluralisableString : TranslatableString { + private const char variant_separator = '|'; + /// /// The plural count to use when applying plural rules. /// public readonly int Count; - /// - /// The character to use as a plural variant separator in localised text. - /// - public readonly char Separator; - /// /// Creates a using texts. /// /// The key for to look up with. /// The fallback string to use when no translation can be found. /// The plural count to use when applying plural rules. - /// The character to use as a plural variant separator in localised text. /// Optional formattable arguments. - public PluralisableString(string key, string fallback, int count, char separator, params object[] args) + public PluralisableString(string key, string fallback, int count, params object[] args) : base(key, fallback, args) { Count = count; - Separator = separator; } /// @@ -44,17 +39,15 @@ public PluralisableString(string key, string fallback, int count, char separator /// The key for to look up with. /// The interpolated string containing fallback and formattable arguments. /// The plural count to use when applying plural rules. - /// The character to use as a plural variant separator in localised text. - public PluralisableString(string key, FormattableString interpolation, int count, char separator) + public PluralisableString(string key, FormattableString interpolation, int count) : base(key, interpolation) { Count = count; - Separator = separator; } protected override string GetLocalisedFormat(LocalisationParameters parameters, string format) { - string[] variants = format.Split(Separator); + string[] variants = format.Split(variant_separator); return variants.ElementAtOrDefault(getPluralIndex(parameters)) ?? variants.ElementAt(variants.Length - 1); } @@ -74,7 +67,6 @@ public override int GetHashCode() var hashCode = new HashCode(); hashCode.Add(base.GetHashCode()); hashCode.Add(Count); - hashCode.Add(Separator); return hashCode.ToHashCode(); } From 1de9b47eb2578a0826380ecda5c88c1333d32177 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 27 Nov 2021 17:31:03 +0100 Subject: [PATCH 09/10] Mention current fallback method in case of missing plural form for current locale + add corresponding test. --- .../Localisation/LocalisationTest.cs | 28 +++++++++++++++++++ .../Localisation/PluralisableString.cs | 2 ++ 2 files changed, 30 insertions(+) diff --git a/osu.Framework.Tests/Localisation/LocalisationTest.cs b/osu.Framework.Tests/Localisation/LocalisationTest.cs index bb2f76e9d1..106aca2884 100644 --- a/osu.Framework.Tests/Localisation/LocalisationTest.cs +++ b/osu.Framework.Tests/Localisation/LocalisationTest.cs @@ -416,6 +416,20 @@ public void TestPluralisableStringNonEnglishPluralRules() Assert.AreEqual("13 kręgów", textThirdVariant.Value); } + [Test] + public void TestPluralisableStringPluralFormFallback() + { + const string key = FakeStorage.LOCALISABLE_PLURALISABLE_INCOMPLETE_EN; + + manager.AddLanguage("fr", new FakeStorage("fr")); + + var textString = manager.GetLocalisedBindableString(new PluralisableString(key, key, 2, 2)); + Assert.AreEqual("2 circle", textString.Value); + + config.SetValue(FrameworkSetting.Locale, "en"); + Assert.AreEqual("2 cercle", textString.Value); + } + private class FakeFrameworkConfigManager : FrameworkConfigManager { protected override string Filename => null; @@ -447,6 +461,8 @@ private class FakeStorage : ILocalisationStore public const string LOCALISABLE_PLURALISABLE_STRING_EN = "{0} circle|{0} circles"; public const string LOCALISABLE_PLURALISABLE_STRING_FR = "{0} cercle|{0} cercles"; public const string LOCALISABLE_PLURALISABLE_STRING_PL = "{0} krąg|{0} kręgi|{0} kręgów"; + public const string LOCALISABLE_PLURALISABLE_INCOMPLETE_EN = "{0} circle"; + public const string LOCALISABLE_PLURALISABLE_INCOMPLETE_FR = "{0} cercle"; public CultureInfo EffectiveCulture { get; } @@ -525,6 +541,18 @@ public string Get(string name) } } + case LOCALISABLE_PLURALISABLE_INCOMPLETE_EN: + { + switch (locale) + { + default: + return LOCALISABLE_PLURALISABLE_INCOMPLETE_EN; + + case "fr": + return LOCALISABLE_PLURALISABLE_INCOMPLETE_FR; + } + } + default: return null; } diff --git a/osu.Framework/Localisation/PluralisableString.cs b/osu.Framework/Localisation/PluralisableString.cs index 9260cd9696..c7fa7c9b1d 100644 --- a/osu.Framework/Localisation/PluralisableString.cs +++ b/osu.Framework/Localisation/PluralisableString.cs @@ -48,6 +48,8 @@ public PluralisableString(string key, FormattableString interpolation, int count protected override string GetLocalisedFormat(LocalisationParameters parameters, string format) { string[] variants = format.Split(variant_separator); + + //use the last plural form available for the current locale if the requested form is missing from translated strings. return variants.ElementAtOrDefault(getPluralIndex(parameters)) ?? variants.ElementAt(variants.Length - 1); } From 211114a3bac4dccbd7218f80ab5e184ff4376b55 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Sat, 27 Nov 2021 17:48:34 +0100 Subject: [PATCH 10/10] Fix tests failing. --- osu.Framework.Tests/Localisation/LocalisationTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Framework.Tests/Localisation/LocalisationTest.cs b/osu.Framework.Tests/Localisation/LocalisationTest.cs index 106aca2884..c5383dca5f 100644 --- a/osu.Framework.Tests/Localisation/LocalisationTest.cs +++ b/osu.Framework.Tests/Localisation/LocalisationTest.cs @@ -426,7 +426,7 @@ public void TestPluralisableStringPluralFormFallback() var textString = manager.GetLocalisedBindableString(new PluralisableString(key, key, 2, 2)); Assert.AreEqual("2 circle", textString.Value); - config.SetValue(FrameworkSetting.Locale, "en"); + config.SetValue(FrameworkSetting.Locale, "fr"); Assert.AreEqual("2 cercle", textString.Value); }