A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using System.Collections.Generic;
6using System.Globalization;
7using System.IO;
8using System.Linq;
9using System.Threading.Tasks;
10using NUnit.Framework;
11using osu.Framework.Configuration;
12using osu.Framework.Extensions.LocalisationExtensions;
13using osu.Framework.Localisation;
14
15namespace osu.Framework.Tests.Localisation
16{
17 [TestFixture]
18 public class LocalisationTest
19 {
20 private FrameworkConfigManager config;
21 private LocalisationManager manager;
22
23 [SetUp]
24 public void Setup()
25 {
26 config = new FakeFrameworkConfigManager();
27 manager = new LocalisationManager(config);
28 manager.AddLanguage("en", new FakeStorage("en"));
29 }
30
31 [Test]
32 public void TestNoLanguagesAdded()
33 {
34 // reinitialise without the default language
35 manager = new LocalisationManager(config);
36
37 var localisedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_STRING_EN, FakeStorage.LOCALISABLE_STRING_EN));
38 Assert.AreEqual(FakeStorage.LOCALISABLE_STRING_EN, localisedText.Value);
39 }
40
41 [Test]
42 public void TestConfigSettingRetainedWhenAddingNewLanguage()
43 {
44 config.SetValue(FrameworkSetting.Locale, "ja-JP");
45
46 // ensure that adding a new language which doesn't match the user's choice doesn't cause the configuration value to get reset.
47 manager.AddLanguage("po", new FakeStorage("po-OP"));
48 Assert.AreEqual("ja-JP", config.Get<string>(FrameworkSetting.Locale));
49
50 var localisedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_STRING_EN, FakeStorage.LOCALISABLE_STRING_EN));
51 Assert.AreEqual(FakeStorage.LOCALISABLE_STRING_EN, localisedText.Value);
52
53 // ensure that if the user's selection is added in a further AddLanguage call, the manager correctly translates strings.
54 manager.AddLanguage("ja-JP", new FakeStorage("ja-JP"));
55 Assert.AreEqual(FakeStorage.LOCALISABLE_STRING_JA_JP, localisedText.Value);
56 }
57
58 [Test]
59 public void TestNotLocalised()
60 {
61 manager.AddLanguage("ja-JP", new FakeStorage("ja-JP"));
62 config.SetValue(FrameworkSetting.Locale, "ja-JP");
63
64 var localisedText = manager.GetLocalisedString(FakeStorage.LOCALISABLE_STRING_EN);
65
66 Assert.AreEqual(FakeStorage.LOCALISABLE_STRING_EN, localisedText.Value);
67
68 localisedText.Text = FakeStorage.LOCALISABLE_STRING_JA;
69
70 Assert.AreEqual(FakeStorage.LOCALISABLE_STRING_JA, localisedText.Value);
71 }
72
73 [Test]
74 public void TestLocalised()
75 {
76 manager.AddLanguage("ja-JP", new FakeStorage("ja-JP"));
77
78 var localisedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_STRING_EN, FakeStorage.LOCALISABLE_STRING_EN));
79
80 Assert.AreEqual(FakeStorage.LOCALISABLE_STRING_EN, localisedText.Value);
81
82 config.SetValue(FrameworkSetting.Locale, "ja-JP");
83 Assert.AreEqual(FakeStorage.LOCALISABLE_STRING_JA_JP, localisedText.Value);
84 }
85
86 [Test]
87 public void TestLocalisationFallback()
88 {
89 manager.AddLanguage("ja", new FakeStorage("ja"));
90
91 config.SetValue(FrameworkSetting.Locale, "ja-JP");
92
93 var localisedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_STRING_EN, FakeStorage.LOCALISABLE_STRING_EN));
94
95 Assert.AreEqual(FakeStorage.LOCALISABLE_STRING_JA, localisedText.Value);
96 }
97
98 [Test]
99 public void TestFormatted()
100 {
101 const string to_format = "this {0} {1} formatted";
102 const string arg_0 = "has";
103 const string arg_1 = "been";
104
105 string expectedResult = string.Format(to_format, arg_0, arg_1);
106
107 var formattedText = manager.GetLocalisedString(string.Format(to_format, arg_0, arg_1));
108
109 Assert.AreEqual(expectedResult, formattedText.Value);
110 }
111
112 [Test]
113 public void TestFormattedInterpolation()
114 {
115 const string arg_0 = "formatted";
116
117 manager.AddLanguage("ja-JP", new FakeStorage("ja"));
118 config.SetValue(FrameworkSetting.Locale, "ja-JP");
119
120 string expectedResult = string.Format(FakeStorage.LOCALISABLE_FORMAT_STRING_JA, arg_0);
121
122 var formattedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_FORMAT_STRING_EN, interpolation: $"The {arg_0} fallback should only matches argument count"));
123
124 Assert.AreEqual(expectedResult, formattedText.Value);
125 }
126
127 [Test]
128 public void TestFormattedAndLocalised()
129 {
130 const string arg_0 = "formatted";
131
132 string expectedResult = string.Format(FakeStorage.LOCALISABLE_FORMAT_STRING_JA, arg_0);
133
134 manager.AddLanguage("ja", new FakeStorage("ja"));
135 config.SetValue(FrameworkSetting.Locale, "ja");
136
137 var formattedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_FORMAT_STRING_EN, FakeStorage.LOCALISABLE_FORMAT_STRING_EN, arg_0));
138
139 Assert.AreEqual(expectedResult, formattedText.Value);
140 }
141
142 [Test]
143 public void TestNumberCultureAware()
144 {
145 const double value = 1.23;
146
147 manager.AddLanguage("fr", new FakeStorage("fr"));
148 config.SetValue(FrameworkSetting.Locale, "fr");
149
150 var expectedResult = string.Format(new CultureInfo("fr"), FakeStorage.LOCALISABLE_NUMBER_FORMAT_STRING_FR, value);
151 Assert.AreEqual("number 1,23 FR", expectedResult); // FR uses comma for decimal point.
152
153 var formattedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_NUMBER_FORMAT_STRING_EN, null, value));
154
155 Assert.AreEqual(expectedResult, formattedText.Value);
156 }
157
158 [Test]
159 public void TestStorageNotFound()
160 {
161 manager.AddLanguage("ja", new FakeStorage("ja"));
162 config.SetValue(FrameworkSetting.Locale, "ja");
163
164 const string expected_fallback = "fallback string";
165
166 var formattedText = manager.GetLocalisedString(new TranslatableString("no such key", expected_fallback));
167
168 Assert.AreEqual(expected_fallback, formattedText.Value);
169 }
170
171 [Test]
172 public void TestUnicodePreference()
173 {
174 const string non_unicode = "non unicode";
175 const string unicode = "unicode";
176
177 var text = manager.GetLocalisedString(new RomanisableString(unicode, non_unicode));
178
179 config.SetValue(FrameworkSetting.ShowUnicode, true);
180 Assert.AreEqual(unicode, text.Value);
181
182 config.SetValue(FrameworkSetting.ShowUnicode, false);
183 Assert.AreEqual(non_unicode, text.Value);
184 }
185
186 [Test]
187 public void TestUnicodeStringChanging()
188 {
189 const string non_unicode_1 = "non unicode 1";
190 const string non_unicode_2 = "non unicode 2";
191 const string unicode_1 = "unicode 1";
192 const string unicode_2 = "unicode 2";
193
194 var text = manager.GetLocalisedString(new RomanisableString(unicode_1, non_unicode_1));
195
196 config.SetValue(FrameworkSetting.ShowUnicode, false);
197 Assert.AreEqual(non_unicode_1, text.Value);
198
199 text.Text = new RomanisableString(unicode_1, non_unicode_2);
200 Assert.AreEqual(non_unicode_2, text.Value);
201
202 config.SetValue(FrameworkSetting.ShowUnicode, true);
203 Assert.AreEqual(unicode_1, text.Value);
204
205 text.Text = new RomanisableString(unicode_2, non_unicode_2);
206 Assert.AreEqual(unicode_2, text.Value);
207 }
208
209 [Test]
210 public void TestEmptyStringFallback([Values("", null)] string emptyValue)
211 {
212 const string non_unicode_fallback = "non unicode";
213 const string unicode_fallback = "unicode";
214
215 var text = manager.GetLocalisedString(new RomanisableString(unicode_fallback, emptyValue));
216
217 config.SetValue(FrameworkSetting.ShowUnicode, false);
218 Assert.AreEqual(unicode_fallback, text.Value);
219
220 text = manager.GetLocalisedString(new RomanisableString(emptyValue, non_unicode_fallback));
221
222 config.SetValue(FrameworkSetting.ShowUnicode, true);
223 Assert.AreEqual(non_unicode_fallback, text.Value);
224 }
225
226 /// <summary>
227 /// This tests the <see cref="LocalisableFormattableString"/>, which allows for formatting <see cref="IFormattable"/>s,
228 /// without necessarily being in a <see cref="TranslatableString"/> which requires keys mapping to strings from localistaion stores.
229 /// </summary>
230 [Test]
231 public void TestLocalisableFormattableString()
232 {
233 manager.AddLanguage("fr", new FakeStorage("fr"));
234
235 var dateTime = new DateTime(1);
236 const string format = "MMM yyyy";
237
238 var text = manager.GetLocalisedString(dateTime.ToLocalisableString(format));
239
240 Assert.AreEqual("Jan 0001", text.Value);
241
242 config.SetValue(FrameworkSetting.Locale, "fr");
243 Assert.AreEqual("janv. 0001", text.Value);
244 }
245
246 [Test]
247 public void TestCaseTransformableString()
248 {
249 const string localisable_string_en_title_case = "Localised EN";
250
251 config.SetValue(FrameworkSetting.Locale, "en");
252
253 var uppercasedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_STRING_EN, FakeStorage.LOCALISABLE_STRING_EN).ToUpper());
254 var titleText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_STRING_EN, FakeStorage.LOCALISABLE_STRING_EN).ToTitle());
255
256 Assert.AreEqual(uppercasedText.Value, "LOCALISED EN");
257 Assert.AreEqual(titleText.Value, localisable_string_en_title_case);
258 }
259
260 [Test]
261 public void TestCaseTransformableStringNonEnglishCultureCasing()
262 {
263 manager.AddLanguage("tr", new FakeStorage("tr"));
264
265 var uppercasedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_STRING_EN, FakeStorage.LOCALISABLE_STRING_EN).ToUpper());
266 var lowercasedText = manager.GetLocalisedString(new TranslatableString(FakeStorage.LOCALISABLE_STRING_EN, FakeStorage.LOCALISABLE_STRING_EN).ToLower());
267
268 config.SetValue(FrameworkSetting.Locale, "en");
269
270 Assert.AreEqual(uppercasedText.Value, "LOCALISED EN");
271 Assert.AreEqual(lowercasedText.Value, "localised en");
272
273 config.SetValue(FrameworkSetting.Locale, "tr");
274
275 Assert.AreEqual(uppercasedText.Value, "LOCALİSED TR (İ/I)");
276 Assert.AreEqual(lowercasedText.Value, "localised tr (i/ı)");
277 }
278
279 [Test]
280 public void TestTranslatableEvaluatingLocalisableFormattableString()
281 {
282 const string key = FakeStorage.LOCALISABLE_NUMBER_FORMAT_STRING_EN;
283
284 manager.AddLanguage("fr", new FakeStorage("fr"));
285
286 var text = manager.GetLocalisedString(new TranslatableString(key, key, new LocalisableFormattableString(0.1234, "0.00%")));
287
288 Assert.AreEqual("number 12.34% EN", text.Value);
289
290 config.SetValue(FrameworkSetting.Locale, "fr");
291
292 Assert.AreEqual("number 12,34% FR", text.Value);
293 }
294
295 [Test]
296 public void TestTranslatableEvaluatingRomanisableString()
297 {
298 const string key = FakeStorage.LOCALISABLE_FORMAT_STRING_EN;
299
300 var text = manager.GetLocalisedString(new TranslatableString(key, key, new RomanisableString("unicode", "romanised")));
301
302 Assert.AreEqual("unicode localised EN", text.Value);
303
304 config.SetValue(FrameworkSetting.ShowUnicode, false);
305
306 Assert.AreEqual("romanised localised EN", text.Value);
307 }
308
309 [Test]
310 public void TestTranslatableEvaluatingTranslatableString()
311 {
312 const string key = FakeStorage.LOCALISABLE_FORMAT_STRING_EN;
313 const string nested_key = FakeStorage.LOCALISABLE_STRING_EN;
314
315 manager.AddLanguage("ja", new FakeStorage("ja"));
316
317 var text = manager.GetLocalisedString(new TranslatableString(key, key, new TranslatableString(nested_key, nested_key)));
318
319 Assert.AreEqual("localised EN localised EN", text.Value);
320
321 config.SetValue(FrameworkSetting.Locale, "ja");
322
323 Assert.AreEqual("localised JA localised JA", text.Value);
324 }
325
326 [Test]
327 public void TestTranslatableEvaluatingComplexString()
328 {
329 const string key = FakeStorage.LOCALISABLE_COMPLEX_FORMAT_STRING_EN;
330 const string nested_key = FakeStorage.LOCALISABLE_NUMBER_FORMAT_STRING_EN;
331
332 manager.AddLanguage("fr", new FakeStorage("fr"));
333
334 var text = manager.GetLocalisedString(new TranslatableString(key, key,
335 new LocalisableFormattableString(12.34, "0.00"),
336 new TranslatableString(nested_key, nested_key, new LocalisableFormattableString(0.9876, "0.00%")),
337 new TranslatableString(nested_key, nested_key, new RomanisableString("unicode", "romanised"))));
338
339 Assert.AreEqual("number 12.34 with number 98.76% EN and number unicode EN EN", text.Value);
340
341 config.SetValue(FrameworkSetting.Locale, "fr");
342
343 Assert.AreEqual("number 12,34 with number 98,76% FR and number unicode FR FR", text.Value);
344
345 config.SetValue(FrameworkSetting.ShowUnicode, false);
346
347 Assert.AreEqual("number 12,34 with number 98,76% FR and number romanised FR FR", text.Value);
348
349 config.SetValue(FrameworkSetting.Locale, "en");
350
351 Assert.AreEqual("number 12.34 with number 98.76% EN and number romanised EN EN", text.Value);
352 }
353
354 private class FakeFrameworkConfigManager : FrameworkConfigManager
355 {
356 protected override string Filename => null;
357
358 public FakeFrameworkConfigManager()
359 : base(null)
360 {
361 }
362
363 protected override void InitialiseDefaults()
364 {
365 SetDefault(FrameworkSetting.Locale, "");
366 SetDefault(FrameworkSetting.ShowUnicode, true);
367 }
368 }
369
370 private class FakeStorage : ILocalisationStore
371 {
372 public const string LOCALISABLE_STRING_EN = "localised EN";
373 public const string LOCALISABLE_STRING_JA = "localised JA";
374 public const string LOCALISABLE_STRING_JA_JP = "localised JA-JP";
375 public const string LOCALISABLE_STRING_TR = "localised TR (i/I)";
376 public const string LOCALISABLE_FORMAT_STRING_EN = "{0} localised EN";
377 public const string LOCALISABLE_FORMAT_STRING_JA = "{0} localised JA";
378 public const string LOCALISABLE_NUMBER_FORMAT_STRING_EN = "number {0} EN";
379 public const string LOCALISABLE_NUMBER_FORMAT_STRING_FR = "number {0} FR";
380 public const string LOCALISABLE_COMPLEX_FORMAT_STRING_EN = "number {0} with {1} and {2} EN";
381 public const string LOCALISABLE_COMPLEX_FORMAT_STRING_FR = "number {0} with {1} and {2} FR";
382
383 public CultureInfo EffectiveCulture { get; }
384
385 private readonly string locale;
386
387 public FakeStorage(string locale)
388 {
389 this.locale = locale;
390 EffectiveCulture = new CultureInfo(locale);
391 }
392
393 public async Task<string> GetAsync(string name) => await Task.Run(() => Get(name)).ConfigureAwait(false);
394
395 public string Get(string name)
396 {
397 switch (name)
398 {
399 case LOCALISABLE_STRING_EN:
400 switch (locale)
401 {
402 default:
403 return LOCALISABLE_STRING_EN;
404
405 case "ja":
406 return LOCALISABLE_STRING_JA;
407
408 case "ja-JP":
409 return LOCALISABLE_STRING_JA_JP;
410
411 case "tr":
412 return LOCALISABLE_STRING_TR;
413 }
414
415 case LOCALISABLE_FORMAT_STRING_EN:
416 switch (locale)
417 {
418 default:
419 return LOCALISABLE_FORMAT_STRING_EN;
420
421 case "ja":
422 return LOCALISABLE_FORMAT_STRING_JA;
423 }
424
425 case LOCALISABLE_NUMBER_FORMAT_STRING_EN:
426 switch (locale)
427 {
428 default:
429 return LOCALISABLE_NUMBER_FORMAT_STRING_EN;
430
431 case "fr":
432 return LOCALISABLE_NUMBER_FORMAT_STRING_FR;
433 }
434
435 case LOCALISABLE_COMPLEX_FORMAT_STRING_EN:
436 switch (locale)
437 {
438 default:
439 return LOCALISABLE_COMPLEX_FORMAT_STRING_EN;
440
441 case "fr":
442 return LOCALISABLE_COMPLEX_FORMAT_STRING_FR;
443 }
444
445 default:
446 return null;
447 }
448 }
449
450 public Stream GetStream(string name) => throw new NotSupportedException();
451
452 public void Dispose()
453 {
454 }
455
456 public IEnumerable<string> GetAvailableResources() => Enumerable.Empty<string>();
457 }
458 }
459}