Skip to content

Commit b7cfc51

Browse files
authored
PluralRule for DualFromZeroToTwo: Does not cover value of 2 - Closes #369 (#370)
* Closes #369 PluralRule for DualFromZeroToTwo: Does not cover value of 2 The `Dictionary<string, PluralRuleDelegate> IsoLangToDelegate` is backed by a default dictionary. It can be restored with `PluralRules.RestoreDefault()` if one of the delegates was changed. Both has global effect. Extract class `CustomPluralRuleProvider` to its own file `PluralRuleDelegate DualFromZeroToTwo`: * with 3 words, the index is for counts of 0, > 0 and < 2, more than 2 * with 4 words, the index is for counts of 0, > 0 and < 2, >= 2 and < 3, more than 3
1 parent f89ccf9 commit b7cfc51

File tree

4 files changed

+233
-88
lines changed

4 files changed

+233
-88
lines changed

src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs

Lines changed: 137 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Globalization;
45
using NUnit.Framework;
@@ -23,12 +24,12 @@ private static SmartFormatter GetFormatter(SmartSettings? smartSettings = null)
2324

2425
private static void TestAllResults(CultureInfo cultureInfo, string format, ExpectedResults expectedValuesAndResults)
2526
{
26-
foreach (var test in expectedValuesAndResults)
27+
foreach (var testResult in expectedValuesAndResults)
2728
{
2829
var smart = GetFormatter();
29-
var value = test.Key;
30-
var expected = test.Value;
31-
var actual = smart.Format(cultureInfo, format, value);
30+
var count = testResult.Key;
31+
var expected = testResult.Value;
32+
var actual = smart.Format(cultureInfo, format, count);
3233

3334
Assert.That(actual, Is.EqualTo(expected));
3435
Debug.WriteLine(actual);
@@ -153,6 +154,46 @@ public void Test_English_Unsigned()
153154
}
154155
}
155156

157+
[TestCase(0, "{0} personne")] // 0 is singular
158+
[TestCase(1, "{0} personne")] // 1 is singular
159+
[TestCase(2, "{0} personnes")] // 2 or more is plural
160+
[TestCase(50, "{0} personnes")]
161+
public void Test_French_2words(int count, string expected)
162+
{
163+
var smart = GetFormatter();
164+
var ci = CultureInfo.GetCultureInfo("fr");
165+
var actual = smart.Format(ci, "{0:plural:{0} personne|{0} personnes}", count);
166+
167+
Assert.That(actual, Is.EqualTo(string.Format(ci, expected, count)));
168+
}
169+
170+
[TestCase(0, "pas de personne")] // 0 is singular
171+
[TestCase(1, "une personne")] // 1 is singular
172+
[TestCase(2, "{0} personnes")] // 2 or more is plural
173+
[TestCase(50, "{0} personnes")]
174+
public void Test_French_3words(int count, string expected)
175+
{
176+
var smart = GetFormatter();
177+
var ci = CultureInfo.GetCultureInfo("fr");
178+
var actual = smart.Format(ci, "{0:plural:pas de personne|une personne|{0} personnes}", count);
179+
180+
Assert.That(actual, Is.EqualTo(string.Format(ci, expected, count)));
181+
}
182+
183+
[TestCase(-1, "-")]
184+
[TestCase(0, "pas de personne")] // 0 is singular
185+
[TestCase(1, "une personne")] // 1 is singular
186+
[TestCase(2, "{0} personnes")] // 2 is plural
187+
[TestCase(50, "{0} personnes")] // more than 2
188+
public void Test_French_4words(int count, string expected)
189+
{
190+
var smart = GetFormatter();
191+
var ci = CultureInfo.GetCultureInfo("fr");
192+
var actual = smart.Format(ci, "{0:plural:-|pas de personne|une personne|{0} personnes}", count);
193+
194+
Assert.That(actual, Is.EqualTo(string.Format(ci, expected, count)));
195+
}
196+
156197
[Test]
157198
public void Test_Turkish()
158199
{
@@ -273,7 +314,7 @@ public void NamedFormatter_should_use_specific_language(string format, object ar
273314
[TestCase("{0:plural:zero|one|many}", new string[0], "zero")]
274315
[TestCase("{0:plural:zero|one|many}", new[] { "alice" }, "one")]
275316
[TestCase("{0:plural:zero|one|many}", new[] { "alice", "bob" }, "many")]
276-
public void Test_should_allow_ienumerable_parameter(string format, object arg0, string expectedResult)
317+
public void Should_Allow_IEnumerable_Parameter(string format, object arg0, string expectedResult)
277318
{
278319
var smart = GetFormatter();
279320
var culture = new CultureInfo("en-US");
@@ -282,23 +323,54 @@ public void Test_should_allow_ienumerable_parameter(string format, object arg0,
282323
}
283324

284325
[Test]
285-
public void Test_With_CustomPluralRuleProvider()
326+
public void Use_CustomPluralRuleProvider()
286327
{
287328
var smart = GetFormatter();
288-
var actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("de")), "{0:plural:Frau|Frauen}", new string[2], "more");
289-
Assert.That(actualResult, Is.EqualTo("Frauen"));
290329

291-
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("en")), "{0:plural:person|people}", new string[2], "more");
292-
Assert.That(actualResult, Is.EqualTo("people"));
330+
Assert.Multiple(() =>
331+
{
332+
// ** German **
333+
var actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("de")),
334+
"{0:plural:Frau|Frauen}", new string[2], "more");
335+
Assert.That(actualResult, Is.EqualTo("Frauen"));
336+
337+
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("de")),
338+
"{0:plural:Frau|Frauen|einige Frauen|viele Frauen}", new string[4], "more");
339+
Assert.That(actualResult, Is.EqualTo("viele Frauen"));
293340

294-
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("en")), "{0:plural:person|people}", new string[1], "one");
295-
Assert.That(actualResult, Is.EqualTo("person"));
341+
// ** English **
296342

297-
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("fr")), "{0:plural:une personne|deux personnes|plusieurs personnes}", new string[3], "several");
298-
Assert.That(actualResult, Is.EqualTo("plusieurs personnes"));
343+
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("en")),
344+
"{0:plural:person|people}", new string[2], "more");
345+
Assert.That(actualResult, Is.EqualTo("people"));
299346

300-
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("fr")), "{0:plural:une personne|deux personnes|plusieurs personnes|beaucoup de personnes}", new string[3], "several");
301-
Assert.That(actualResult, Is.EqualTo("beaucoup de personnes"));
347+
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("en")),
348+
"{0:plural:person|people}", new string[1], "one");
349+
Assert.That(actualResult, Is.EqualTo("person"));
350+
351+
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("fr")),
352+
"{0:plural:pas de personne|une personne|plusieurs personnes}", Array.Empty<string>(), "none");
353+
Assert.That(actualResult, Is.EqualTo("pas de personne"));
354+
355+
// ** French **
356+
357+
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("fr")),
358+
"{0:plural:pas de personne|une personne|plusieurs personnes}", new string[1], "one");
359+
Assert.That(actualResult, Is.EqualTo("une personne"));
360+
361+
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("fr")),
362+
"{0:plural:pas de personne|une personne|deux personnes}", new string[2], "two");
363+
Assert.That(actualResult, Is.EqualTo("deux personnes"));
364+
365+
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("fr")),
366+
"{0:plural:pas de personne|une personne|deux personnes|plusieurs personnes}", new string[3], "several");
367+
Assert.That(actualResult, Is.EqualTo("plusieurs personnes"));
368+
369+
actualResult = smart.Format(new CustomPluralRuleProvider(PluralRules.GetPluralRule("fr")),
370+
"{0:plural:une personne|deux personnes|plusieurs personnes|beaucoup de personnes}", new string[3],
371+
"many");
372+
Assert.That(actualResult, Is.EqualTo("beaucoup de personnes"));
373+
});
302374
}
303375

304376
[TestCase("{0:plural:one|many} {1:plural:one|many} {2:plural:one|many}", "many one many")]
@@ -318,7 +390,10 @@ public void Should_Process_Signed_And_Unsigned_Numbers()
318390
{
319391
var smart = GetFormatter();
320392
foreach (var number in new object[]
321-
{ (long)123, (ulong)123, (short)123, (ushort)123, (int)123, (uint)123 })
393+
{
394+
(long)123, (ulong)123, (short)123, (ushort)123, (int)123, (uint)123,
395+
(long)-123, (short) -123, (int) -123
396+
})
322397
{
323398
Assert.That(smart.Format("{0:plural(en):zero|one|many}", number), Is.EqualTo("many"));
324399
}
@@ -363,4 +438,48 @@ public void Pluralization_With_Changed_SplitChar(int numOfPeople, string format,
363438
var result = smart.Format(format, data);
364439
Assert.That(result, Is.EqualTo(expected));
365440
}
441+
442+
[TestCase(0, "nobody", "pas de personne")] // 0 is singular
443+
[TestCase(1, "{0} person", "{0} personne")] // 1 is singular
444+
[TestCase(2, "{0} people", "{0} personnes")] // 2 or more is plural
445+
[TestCase(5, "a couple of people", "quelques personnes")]
446+
[TestCase(15, "many people", "beaucoup de gens")]
447+
[TestCase(50, "a lot of people", "une foule de personnes")]
448+
public void Pluralization_With_Changed_Default_Rule_Delegate(int count, string rawExpectedEnglish, string rawExpectedFrench)
449+
{
450+
// Note: This test changes a default rule delegate *globally*.
451+
// It is not recommended, but possible.
452+
PluralRules.IsoLangToDelegate["en"] = (value, wordsCount) =>
453+
{
454+
if (wordsCount != 6) return -1;
455+
456+
return Math.Abs(value) switch
457+
{
458+
<= 0 => 0,
459+
> 0 and < 2 => 1,
460+
>= 2 and < 3 => 2,
461+
> 2 and < 10 => 3,
462+
>= 10 and < 20 => 4,
463+
>= 20 => 5
464+
};
465+
};
466+
// Use the same rule delegate for English and French:
467+
PluralRules.IsoLangToDelegate["fr"] = PluralRules.IsoLangToDelegate["en"];
468+
469+
var smart = GetFormatter();
470+
var ciEnglish = CultureInfo.GetCultureInfo("en");
471+
var ciFrench = CultureInfo.GetCultureInfo("fr");
472+
473+
var actualEnglish = smart.Format(ciEnglish, "{0:plural:nobody|{} person|{} people|a couple of people|many people|a lot of people}", count);
474+
var actualFrench = smart.Format(ciFrench, "{0:plural:pas de personne|{} personne|{} personnes|quelques personnes|beaucoup de gens|une foule de personnes}", count);
475+
476+
// Restore default rule delegates:
477+
PluralRules.RestoreDefault();
478+
479+
Assert.Multiple(() =>
480+
{
481+
Assert.That(actualEnglish, Is.EqualTo(string.Format(ciEnglish, rawExpectedEnglish, count)));
482+
Assert.That(actualFrench, Is.EqualTo(string.Format(ciFrench, rawExpectedFrench, count)));
483+
});
484+
}
366485
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// Copyright SmartFormat Project maintainers and contributors.
3+
// Licensed under the MIT license.
4+
//
5+
6+
using System;
7+
using SmartFormat.Utilities;
8+
9+
namespace SmartFormat.Extensions;
10+
/// <summary>
11+
/// Use this class to provide custom plural rules to Smart.Format
12+
/// </summary>
13+
public class CustomPluralRuleProvider : IFormatProvider
14+
{
15+
private readonly PluralRules.PluralRuleDelegate _pluralRule;
16+
17+
/// <summary>
18+
/// Creates a new instance of a <see cref="CustomPluralRuleProvider"/>.
19+
/// </summary>
20+
/// <param name="pluralRule">The delegate for plural rules.</param>
21+
public CustomPluralRuleProvider(PluralRules.PluralRuleDelegate pluralRule)
22+
{
23+
_pluralRule = pluralRule;
24+
}
25+
26+
/// <summary>
27+
/// Gets the format <see cref="object"/> for a <see cref="CustomPluralRuleProvider"/>.
28+
/// </summary>
29+
/// <param name="formatType"></param>
30+
/// <returns>The format <see cref="object"/> for a <see cref="CustomPluralRuleProvider"/> or <see langword="null"/>.</returns>
31+
public object? GetFormat(Type? formatType)
32+
{
33+
return formatType == typeof(CustomPluralRuleProvider) ? this : default;
34+
}
35+
36+
/// <summary>
37+
/// Gets the <see cref="PluralRules.PluralRuleDelegate"/> of the current <see cref="CustomPluralRuleProvider"/> instance.
38+
/// </summary>
39+
/// <returns></returns>
40+
public PluralRules.PluralRuleDelegate GetPluralRule()
41+
{
42+
return _pluralRule;
43+
}
44+
}

src/SmartFormat/Extensions/PluralLocalizationFormatter.cs

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -222,38 +222,3 @@ private static CultureInfo GetCultureInfo(IFormattingInfo formattingInfo)
222222
}
223223
}
224224

225-
/// <summary>
226-
/// Use this class to provide custom plural rules to Smart.Format
227-
/// </summary>
228-
public class CustomPluralRuleProvider : IFormatProvider
229-
{
230-
private readonly PluralRules.PluralRuleDelegate _pluralRule;
231-
232-
/// <summary>
233-
/// Creates a new instance of a <see cref="CustomPluralRuleProvider"/>.
234-
/// </summary>
235-
/// <param name="pluralRule">The delegate for plural rules.</param>
236-
public CustomPluralRuleProvider(PluralRules.PluralRuleDelegate pluralRule)
237-
{
238-
_pluralRule = pluralRule;
239-
}
240-
241-
/// <summary>
242-
/// Gets the format <see cref="object"/> for a <see cref="CustomPluralRuleProvider"/>.
243-
/// </summary>
244-
/// <param name="formatType"></param>
245-
/// <returns>The format <see cref="object"/> for a <see cref="CustomPluralRuleProvider"/> or <see langword="null"/>.</returns>
246-
public object? GetFormat(Type? formatType)
247-
{
248-
return formatType == typeof(CustomPluralRuleProvider) ? this : default;
249-
}
250-
251-
/// <summary>
252-
/// Gets the <see cref="PluralRules.PluralRuleDelegate"/> of the current <see cref="CustomPluralRuleProvider"/> instance.
253-
/// </summary>
254-
/// <returns></returns>
255-
public PluralRules.PluralRuleDelegate GetPluralRule()
256-
{
257-
return _pluralRule;
258-
}
259-
}

0 commit comments

Comments
 (0)