Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added formatter for localization #207

Merged
merged 53 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
afd5b77
Update issue templates
axunonb Apr 17, 2021
1d22508
Inserted a link to merged version/v3 changes
axunonb Apr 27, 2021
11c3033
Updated README
axunonb Apr 27, 2021
d1779f9
Merge remote-tracking branch 'upstream/main' into version/v3.0
axunonb Apr 29, 2021
cb5559a
Make parser fully covered with unit tests
axunonb Apr 29, 2021
e327de5
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb Apr 29, 2021
cab97df
Updated CHANGES.md
axunonb Apr 29, 2021
e8d9c93
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb Apr 29, 2021
294f810
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb Apr 29, 2021
792c391
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 3, 2021
4f45a84
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 9, 2021
e49db1e
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 9, 2021
2c562b5
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 9, 2021
2ba8c5f
Updated .editorconfig
axunonb May 9, 2021
7c3a12e
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 25, 2021
bda63e0
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 25, 2021
ef58744
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 26, 2021
67b2119
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 27, 2021
974224b
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 27, 2021
3e6e8ae
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 27, 2021
6219b47
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 27, 2021
34f2821
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 27, 2021
6ae6516
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 29, 2021
1d7e6f3
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 29, 2021
250270b
Merge remote-tracking branch 'upstream/version/v3.0' into version/v3.0
axunonb May 30, 2021
6550a35
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 2, 2021
a457c81
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 13, 2021
d42aaa6
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 27, 2021
1e3fccc
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 27, 2021
dc58bf2
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 27, 2021
5cdd6ae
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 29, 2021
c1f65cd
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 30, 2021
309fc5a
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 30, 2021
f7d1bbb
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 31, 2021
1062b20
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 31, 2021
6462ad3
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Aug 31, 2021
7927e19
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Sep 3, 2021
9b5bef2
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Sep 5, 2021
58d80ef
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Sep 7, 2021
63991b0
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Sep 13, 2021
11e2abf
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Sep 13, 2021
5cfb290
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Sep 14, 2021
40566b6
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Sep 20, 2021
52be227
Bumped version to 3.0.0-alpha.3
axunonb Sep 20, 2021
6760a2c
Merge branch 'axuno:version/v3.0' into version/v3.0
axunonb Sep 20, 2021
9dc493d
Initial commit
axunonb Oct 13, 2021
b1b1176
Update README.md
axunonb Oct 14, 2021
5d3f8ef
Tests with nestandard2.1
axunonb Oct 14, 2021
9f3dced
Downgraded AppVeyor/CI tests from net5.0 to netstandard2.1
axunonb Oct 14, 2021
220bb5b
Updated unit test packages to latest version
axunonb Oct 14, 2021
ee6a946
Corrected moniker for unit tests
axunonb Oct 14, 2021
f319f08
Updated appveyor.yml
axunonb Oct 14, 2021
9298f46
Fix: AltCover makes unit tests with resource files fail
axunonb Oct 14, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,31 +128,68 @@ Smart.Format("{TheValue:isnull:The value is null|The value is {}}", new {TheValu
// Result: "The value is 1234"
```

### 11. Improved custom `ISource` and `IFormatter` implementations ([#180](https://github.com/axuno/SmartFormat/pull/180))
### 11. Added features to localize literals and placeholders

#### Features
* Added `LocalizationFormatter`
* Added `ILocalizationProvider` and a standard implemention as `LocalizationProvider`, which handles `resx` resource files
* `SmartSettings` were exended with category `Localization`.
* Custom `IFormatter` can now make use of localization, if needed.

#### Examples
Culture-specific results shown here are included in embedded resource files, which are omitted for brevity.

a) Localize pure literals into Spanish:
```CSharp
// culture supplied as a format option
_ = Smart.Format(culture, "{:L(en):WeTranslateText}");
// culture supplied as an argument to the formatter
var culture = CultureInfo.GetCultureInfo("es");
_ = Smart.Format(culture, "{:L:WeTranslateText}");
// result for both: "Traducimos el texto"
```
b) Localized strings may contain placeholders
```CSharp
_ = Smart.Format("{0} {1:L(es):has {:#,#} inhabitants}", "X-City", 8900000);
// result: "X-City tiene 8.900.000 habitantes"
_ = Smart.Format("{0} {1:L(es):has {:#,#} inhabitants}", "X-City", 8900000);
// result: "X-City has 8,900,000 inhabitants"
```
c) Localization can be used together with other formatters
```CSharp
_ = Smart.Format("{0:plural:{:L(en):{} item}|{:L(en):{} items}}", 0;
// result for English: 0 items
_ = Smart.Format("{0:plural:{:L(fr):{} item}|{:L(fr):{} items}}", 0;
// result for French: 0 élément
_ = Smart.Format("{0:plural:{:L(fr):{} item}|{:L(fr):{} items}}", 200;
// result for French: 200 éléments
```

### 12. Improved custom `ISource` and `IFormatter` implementations ([#180](https://github.com/axuno/SmartFormat/pull/180))
Any custom exensions can implement `IInitializer`. Then, the `SmartFormatter` will call `Initialize(SmartFormatter smartFormatter)` of the extension, before adding it to the extension list.

### 12. `IFormatter`s have one single, unique name ([#185](https://github.com/axuno/SmartFormat/pull/185))
### 13. `IFormatter`s have one single, unique name ([#185](https://github.com/axuno/SmartFormat/pull/185))
In v2, `IFormatter`s could have an unlimited number of names.
To improve performance, in v3, this is limited to one single, unique name.

### 13. JSON support ([#177](https://github.com/axuno/SmartFormat/pull/177), [#201](https://github.com/axuno/SmartFormat/pull/201))
### 14. JSON support ([#177](https://github.com/axuno/SmartFormat/pull/177), [#201](https://github.com/axuno/SmartFormat/pull/201))

Separation of `JsonSource` into 2 `ISource` extensions:
* `NewtonSoftJsonSource`
* `SystemTextJsonSource`

Fix: `NewtonSoftJsonSource` handles `null` values correctly ([#201](https://github.com/axuno/SmartFormat/pull/201))

### 14. `SmartFormatter` takes `IList<object>` parameters
### 15. `SmartFormatter` takes `IList<object>` parameters
Added support for `IList<object>` parameters to the `SmartFormatter` (thanks to [@karljj1](https://github.com/karljj1)) ([#154](https://github.com/axuno/SmartFormat/pull/154))

### 15. `SmartObjects` have been removed
### 16. `SmartObjects` have been removed
* Removed obsolete `SmartObjects` (which have been replaced by `ValueTuple`) ([`092b7b1`](https://github.com/axuno/SmartFormat/commit/092b7b1b5873301bdfeb2b62f221f936efc81430))

### 16. Bugfix for plural rule ([#182](https://github.com/axuno/SmartFormat/pull/182))
### 17. Bugfix for plural rule ([#182](https://github.com/axuno/SmartFormat/pull/182))
* Fixes #179 (DualFromZeroToTwo plural rule). Thanks to @OhSoGood

### 17. Improved parsing of HTML input ([#203](https://github.com/axuno/SmartFormat/pull/203))
### 18. Improved parsing of HTML input ([#203](https://github.com/axuno/SmartFormat/pull/203))

Introduced experimental `bool ParserSettings.ParseInputAsHtml`.
The default is `false`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ See [changelog](CHANGES.md) for changes.

<hr>

We have started to work on a new version of ```SmartFormat.Net``` and **would like to collect your input using [GitHub Discussions](https://github.com/axuno/SmartFormat/discussions/139)**.
We have started to work on a new version of ```SmartFormat.Net``` and **would like to collect your input using [GitHub Discussions](https://github.com/axuno/SmartFormat/discussions/139)**.

4 changes: 2 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ for:
dotnet pack SmartFormat --verbosity minimal --configuration release /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg /p:PackageOutputPath=../../artifacts /p:ContinuousIntegrationBuild=true /p:Version=$version /p:FileVersion=$versionFile
test_script:
- cmd: nuget install Appveyor.TestLogger
- cmd: dotnet test --no-build --framework net5.0 --test-adapter-path:. --logger:Appveyor SmartFormat.sln /p:configuration=release /p:AltCover=true /p:AltCoverXmlReport="coverage.xml" /p:AltCover=true /p:AltCoverStrongNameKey="..\SmartFormat\SmartFormat.snk" /p:AltCoverAssemblyExcludeFilter="SmartFormat.Tests|NUnit3.TestAdapter" /p:AltCoverLineCover="true"
- cmd: dotnet test --no-build --framework net5.0 --test-adapter-path:. --logger:Appveyor SmartFormat.sln /p:configuration=release /p:AltCover=true /p:AltCoverXmlReport="coverage.xml" /p:AltCoverInplace=true /p:AltCoverForce=true /p:AltCoverStrongNameKey="..\SmartFormat\SmartFormat.snk" /p:AltCoverAssemblyExcludeFilter="SmartFormat.Tests|NUnit3.TestAdapter" /p:AltCoverLineCover="true"
- cmd: nuget install codecov -excludeversion
- cmd: .\Codecov\Tools\win7-x86\codecov.exe -f ".\SmartFormat.Tests\coverage.net5.0.xml" -n net5.0win
artifacts:
Expand All @@ -57,5 +57,5 @@ for:
- dotnet add ./SmartFormat.Tests/SmartFormat.Tests.csproj package AltCover
- dotnet build SmartFormat.sln /verbosity:minimal /t:rebuild /p:configuration=release /nowarn:CS1591,CS0618
test_script:
- dotnet test --no-build --framework net5.0 SmartFormat.sln /p:configuration=release /p:AltCover=true /p:AltCoverXmlReport="coverage.xml" /p:AltCover=true /p:AltCoverStrongNameKey="../SmartFormat/SmartFormat.snk" /p:AltCoverAssemblyExcludeFilter="SmartFormat.Tests|NUnit3.TestAdapter|Cysharp" /p:AltCoverLineCover="true"
- dotnet test --no-build --framework net5.0 SmartFormat.sln /p:configuration=release /p:AltCover=true /p:AltCoverXmlReport="coverage.xml" /p:AltCoverInplace=true /p:AltCoverForce=true /p:AltCoverStrongNameKey="../SmartFormat/SmartFormat.snk" /p:AltCoverAssemblyExcludeFilter="SmartFormat.Tests|NUnit3.TestAdapter|Cysharp" /p:AltCoverLineCover="true"
- bash <(curl -s https://codecov.io/bash) -f ./SmartFormat.Tests/coverage.net5.0.xml -n net5.0linux
4 changes: 1 addition & 3 deletions src/Demo.NetFramework/Demo.NetFramework.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@
</ItemGroup>

<ItemGroup>
<Compile Update="SmartFormatDemo.cs">
<SubType>Form</SubType>
</Compile>
<Compile Update="SmartFormatDemo.cs" />
<Compile Update="SmartFormatDemo.Designer.cs">
<DependentUpon>SmartFormatDemo.cs</DependentUpon>
</Compile>
Expand Down
207 changes: 207 additions & 0 deletions src/SmartFormat.Tests/Extensions/LocalizationFormatterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using NUnit.Framework;
using SmartFormat.Core.Formatting;
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;
using SmartFormat.Tests.Localization;

namespace SmartFormat.Tests.Extensions
{
[TestFixture]
public class LocalizationFormatterTests
{
private static SmartFormatter GetFormatterWithRegisteredResource(CaseSensitivityType caseSensitivity = CaseSensitivityType.CaseSensitive, FormatErrorAction formatErrorAction = FormatErrorAction.ThrowError)
{
var formatter = new LocalizationFormatter {CanAutoDetect = false};
var smart = Smart.CreateDefaultSmartFormat(new SmartSettings
{
CaseSensitivity = caseSensitivity,
Localization =
{
LocalizationProvider = new LocalizationProvider(true, LocTest1.ResourceManager)
{ FallbackCulture = null, ReturnNameIfNotFound = false }
},
Formatter = { ErrorAction = formatErrorAction }
});
smart.AddExtensions(formatter);

return smart;
}

[Test]
public void Missing_Format_Should_Throw()
{
var smart = GetFormatterWithRegisteredResource(CaseSensitivityType.CaseSensitive);
Assert.That(() => smart.Format("{:L:}"), Throws.InstanceOf<LocalizationFormattingException>().With.InnerException.InstanceOf<ArgumentException>());
}

[Test]
public void Unknown_Culture_Should_Throw()
{
var smart = GetFormatterWithRegisteredResource(CaseSensitivityType.CaseSensitive);
Assert.That(() => smart.Format("{:L(unknown):dummy}"), Throws.InstanceOf<LocalizationFormattingException>());
}

[Test]
public void No_Initialization_Of_LocalizationProvider_Should_Throw()
{
var smart = GetFormatterWithRegisteredResource();
var formatter = smart.GetFormatterExtension<LocalizationFormatter>();
formatter!.LocalizationProvider = null;
Assert.That(() => smart.Format("{:L(en):dummy}"), Throws.InstanceOf<LocalizationFormattingException>().With.InnerException.InstanceOf<NullReferenceException>());
}

[TestCase(FormatErrorAction.Ignore)]
[TestCase(FormatErrorAction.MaintainTokens)]
[TestCase(FormatErrorAction.OutputErrorInResult)]
[TestCase(FormatErrorAction.ThrowError)]
public void No_Localized_String_Found(FormatErrorAction errorAction)
{
var smart = GetFormatterWithRegisteredResource(CaseSensitivityType.CaseSensitive, errorAction);
string? result;

switch (errorAction)
{
case FormatErrorAction.ThrowError:
Assert.That(() => smart.Format("{:L(es):NonExisting}"), Throws.InstanceOf<FormattingException>());
break;
case FormatErrorAction.OutputErrorInResult:
result = smart.Format("{:L(es):NonExisting}");
Assert.That(result, Contains.Substring("No localized string found"));
break;
case FormatErrorAction.Ignore:
result = smart.Format("{:L(es):NonExisting}");
Assert.That(result, Is.EqualTo(string.Empty));
break;
case FormatErrorAction.MaintainTokens:
result = smart.Format("{:L(es):NonExisting}");
Assert.That(result, Is.EqualTo("{:L(es):NonExisting}"));
break;
default:
throw new ArgumentOutOfRangeException(nameof(errorAction), errorAction, null);
}
}

[Test]
public void No_Localized_String_Found_With_Name_Fallback()
{
var smart = GetFormatterWithRegisteredResource();
((LocalizationProvider)smart.GetFormatterExtension<LocalizationFormatter>()!.LocalizationProvider!)
.ReturnNameIfNotFound = true;
var actual = smart.Format("{:L(es):NonExisting}");
Assert.That(actual, Is.EqualTo("NonExisting"));
}

[Test]
public void No_Localized_String_Only_In_Fallback_Culture()
{
var smart = GetFormatterWithRegisteredResource();
var actual = smart.Format(CultureInfo.GetCultureInfo("pt"), "{:L:OnlyExistForInvariantCulture}");
Assert.That(actual, Is.EqualTo("This entry only exists in the invariant culture resource"));
}

[Test]
public void Should_Use_Existing_Localized_Format()
{
var smart = GetFormatterWithRegisteredResource(CaseSensitivityType.CaseSensitive);
var locFormatter = smart.GetFormatterExtension<LocalizationFormatter>();
_ = smart.Format("{:L(es):WeTranslateText}");
var result = smart.Format("{:L(es):WeTranslateText}");

Assert.That(locFormatter!.LocalizedFormatCache!.Keys.Contains(result), Is.True);
}

[TestCase("{:L():WeTranslateText}", "Traducimos el texto", "es")]
[TestCase("{:L:WeTranslateText}", "Traducimos el texto", "es")]
[TestCase("{:L():WeTranslateText}", "We translate text", "")]
[TestCase("{:L:WeTranslateText}", "We translate text", "")]
public void Pure_Text_CurrentCulture(string format, string expected, string culture)
{
var smart = GetFormatterWithRegisteredResource(CaseSensitivityType.CaseSensitive);
CultureInfo.CurrentUICulture = culture == string.Empty ? CultureInfo.InvariantCulture : CultureInfo.GetCultureInfo(culture);

var actual = smart.Format(format);
Assert.That(actual, Is.EqualTo(expected));
}

[TestCase("{:L():WeTranslateText}", "Traducimos el texto", "es")]
[TestCase("{:L:WeTranslateText}", "Traducimos el texto", "es")]
[TestCase("{:L():WeTranslateText}", "We translate text", "")]
[TestCase("{:L:WeTranslateText}", "We translate text", "")]
public void Pure_Text_CultureByArgument(string format, string expected, string cultureString)
{
var smart = GetFormatterWithRegisteredResource(CaseSensitivityType.CaseSensitive);
var culture = cultureString == string.Empty ? CultureInfo.InvariantCulture : CultureInfo.GetCultureInfo(cultureString);

var actual = smart.Format(culture, format);
Assert.That(actual, Is.EqualTo(expected));
}

[TestCase("{:L(es):WeTranslateText}", "Traducimos el texto")]
[TestCase("{:L(en):WeTranslateText}", "We translate text")]
[TestCase("{:L(fr):WeTranslateText}", "Nous traduisons des textes")]
[TestCase("{:L(de):WeTranslateText}", "Wir übersetzen Text")]
public void Pure_Text_CultureByFormatString(string format, string expected)
{
var smart = GetFormatterWithRegisteredResource(CaseSensitivityType.CaseSensitive);

var actual = smart.Format(format);
Assert.That(actual, Is.EqualTo(expected));
}

// Possible, but not recommended
[TestCase("{:L(es):{0} has {1:#,#} inhabitants}", "{1} tiene {2:#,#} habitantes", "es")]
[TestCase("{:L(en):{0} has {1:#,#} inhabitants}", "{1} has {2:#,#} inhabitants", "en")]
[TestCase("{:L(fr):{0} has {1:#,#} inhabitants}", "{1} compte {2:#,#} habitants", "fr")]
[TestCase("{:L(de):{0} has {1:#,#} inhabitants}", "{1} hat {2:#,#} Einwohner", "de")]
// Best practice, because the selector is not part of the format to localize ({:#,#} applies for any selector)
[TestCase("{0} {1:L(es):has {:#,#} inhabitants}", "{1} tiene {2:#,#} habitantes", "es")]
[TestCase("{0} {1:L(en):has {:#,#} inhabitants}", "{1} has {2:#,#} inhabitants", "en")]
[TestCase("{0} {1:L(fr):has {:#,#} inhabitants}", "{1} compte {2:#,#} habitants", "fr")]
[TestCase("{0} {1:L(de):has {:#,#} inhabitants}", "{1} hat {2:#,#} Einwohner", "de")]
// Best practice (same localization as above, with different selector)
[TestCase("{2.City.Name} {2.City.Inhabitants:L(es):has {:#,#} inhabitants}", "{1} tiene {2:#,#} habitantes", "es")]
[TestCase("{2.City.Name} {2.City.Inhabitants:L(en):has {:#,#} inhabitants}", "{1} has {2:#,#} inhabitants", "en")]
[TestCase("{2.City.Name} {2.City.Inhabitants:L(fr):has {:#,#} inhabitants}", "{1} compte {2:#,#} habitants", "fr")]
[TestCase("{2.City.Name} {2.City.Inhabitants:L(de):has {:#,#} inhabitants}", "{1} hat {2:#,#} Einwohner", "de")]
public void TextWithPlaceholder_CultureByFormatString(string format, string expected, string culture)
{
var smart = GetFormatterWithRegisteredResource(CaseSensitivityType.CaseSensitive);
// number should be localized
expected = string.Format(CultureInfo.GetCultureInfo(culture), expected, new {City = new { Name = "X-City", Inhabitants = 8900000}}, "X-City", 8900000);

var actual = smart.Format(format, "X-City", 8900000, new {City = new { Name = "X-City", Inhabitants = 8900000}});
Assert.That(actual, Is.EqualTo(expected));
}

[TestCase("{0:cond:{:L:{} items}|{:L:{} item}|{:L:{} items}}", 0, "en", "0 items")]
[TestCase("{0:cond:{:L:{} items}|{:L:{} item}|{:L:{} items}}", 1, "en", "1 item")]
[TestCase("{0:cond:{:L:{} items}|{:L:{} item}|{:L:{} items}}", 200, "en", "200 items")]
[TestCase("{0:cond:{:L:{} items}|{:L:{} item}|{:L:{} items}}", 200, "es", "200 elementos")]
[TestCase("{0:cond:{:L:{} items}|{:L:{} item}|{:L:{} items}}", 200, "fr", "200 éléments")]
[TestCase("{0:cond:{:L:{} items}|{:L:{} item}|{:L:{} items}}", 200, "de", "200 Elemente")]
public void Combine_With_ConditionalFormatter(string format, int count, string cultureName, string expected)
{
// Just for demo - PluralLocalizationFormatter is the best choice for pluralization
// zero and two hundred: plural, one: singular
var smart = GetFormatterWithRegisteredResource();
var actual = smart.Format(CultureInfo.GetCultureInfo(cultureName), format, count);
Assert.That(actual, Is.EqualTo(expected));
}

[TestCase("{0:plural:{:L:{} item}|{:L:{} items}}", 0, "en", "0 items")]
[TestCase("{0:plural:{:L:{} item}|{:L:{} items}}", 1, "en", "1 item")]
[TestCase("{0:plural:{:L:{} item}|{:L:{} items}}", 200, "de", "200 Elemente")]
[TestCase("{0:plural:{:L:{} item}|{:L:{} items}}", 0, "fr", "0 élément")]
[TestCase("{0:plural:{:L:{} item}|{:L:{} items}}", 1, "fr", "1 élément")]
[TestCase("{0:plural:{:L:{} item}|{:L:{} items}}", 200, "fr", "200 éléments")]
public void Combine_With_PluralLocalizationFormatter(string format, int count, string cultureName, string expected)
{
var smart = GetFormatterWithRegisteredResource();
var actual = smart.Format(CultureInfo.GetCultureInfo(cultureName), format, count);
Assert.That(actual, Is.EqualTo(expected));
}
}
}
Loading