From b22ccfddbdeb1acecb2160e72bf226e6451110a1 Mon Sep 17 00:00:00 2001 From: axunonb Date: Fri, 24 Oct 2025 21:01:06 +0200 Subject: [PATCH 1/3] Fix: Apply absolute value logic in `PluralRules` (#506) This PR addresses a long-standing inconsistency in the pluralization logic used by `PluralRules`. Previously, the implementation did not explicitly evaluate the absolute value of the input number, contrary to the CLDR specification. This led to incorrect plural category resolution for negative numbers, which typically defaulted to the `OTHER` category. #### Changes - The decimal argument passed to all `PluralRules` delegates is now transformed to its absolute value before evaluation. - This change ensures that plural category resolution aligns with CLDR expectations, particularly because locales with plural forms depend on numeric value regardless of sign. #### Impact - Fixes incorrect plural category resolution for negative numbers in locales such as `DualOneOther`, where the number of plural word variants influences the outcome. - May affect pluralization behavior in edge cases across multiple locales. Unit tests have been updated to reflect the corrected logic. - The change affects `PluralLocalizationFormatter` and `TimeFormatter`. - **Thoroughly test the new version in your localization workflows**, especially if you rely on custom pluralization delegates or locale-specific plural form resolution. This change may surface previously masked edge cases. #### Versioning - The PR will be published with a new **minor version**, as this change corrects a bug but may alter behavior in downstream formatting logic. #### Background See discussion in #503 and #497 for context. --- .../PluralLocalizationFormatterTests.cs | 8 +- src/SmartFormat/Utilities/PluralRules.cs | 190 +++++++++++++----- 2 files changed, 140 insertions(+), 58 deletions(-) diff --git a/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs b/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs index 4e3370ce..558104ee 100644 --- a/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs +++ b/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs @@ -99,7 +99,7 @@ public void Test_Default() new CultureInfo("en-US"), "There {0:plural:is|are} {0} {0:plural:item|items} remaining", new ExpectedResults { - { -1, "There are -1 items remaining"}, + { -1, "There is -1 item remaining"}, { 0, "There are 0 items remaining"}, {0.5m, "There are 0.5 items remaining"}, { 1, "There is 1 item remaining"}, @@ -116,7 +116,7 @@ public void Test_English() new CultureInfo("en-US"), "There {0:plural:is|are} {0} {0:plural:item|items} remaining", new ExpectedResults { - { -1, "There are -1 items remaining"}, + { -1, "There is -1 item remaining"}, { 0, "There are 0 items remaining"}, {0.5m, "There are 0.5 items remaining"}, { 1, "There is 1 item remaining"}, @@ -184,7 +184,7 @@ public void Test_French_3words(int count, string expected) Assert.That(actual, Is.EqualTo(string.Format(ci, expected, count))); } - [TestCase(-1, "-")] + [TestCase(-1, "une personne")] // -1 is treated as 1 (singular) [TestCase(0, "pas de personne")] // 0 is singular [TestCase(1, "une personne")] // 1 is singular [TestCase(2, "{0} personnes")] // 2 is plural @@ -214,7 +214,7 @@ public void Test_Turkish() new CultureInfo("tr"), "Seçili {0:plural:nesneyi|nesneleri} silmek istiyor musunuz?", new ExpectedResults { - { -1, "Seçili nesneleri silmek istiyor musunuz?"}, + { -1, "Seçili nesneyi silmek istiyor musunuz?"}, // -1 is treated as 1 (singular) { 0, "Seçili nesneleri silmek istiyor musunuz?"}, {0.5m, "Seçili nesneleri silmek istiyor musunuz?"}, { 1, "Seçili nesneyi silmek istiyor musunuz?"}, diff --git a/src/SmartFormat/Utilities/PluralRules.cs b/src/SmartFormat/Utilities/PluralRules.cs index 876656ae..13a1789c 100644 --- a/src/SmartFormat/Utilities/PluralRules.cs +++ b/src/SmartFormat/Utilities/PluralRules.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Text.RegularExpressions; namespace SmartFormat.Utilities; #pragma warning disable S3776 // disable sonar cognitive complexity warnings @@ -211,6 +210,7 @@ public static void RestoreDefault() private static PluralRuleDelegate DualOneOther => (value, pluralWordsCount) => { + value = Math.Abs(value); return pluralWordsCount switch { 2 => value == 1 ? 0 : 1, 3 => value switch { @@ -224,10 +224,15 @@ public static void RestoreDefault() }; // Dual: one (n == 1), other private static PluralRuleDelegate DualWithZero => - (value, pluralWordsCount) => value == 0 || value == 1 ? 0 : 1; // DualWithZero: one (n == 0..1), other + (value, pluralWordsCount) => + { + value = Math.Abs(value); + return value == 0 || value == 1 ? 0 : 1; + }; // DualWithZero: one (n == 0..1), other private static PluralRuleDelegate DualFromZeroToTwo => (value, pluralWordsCount) => { + value = Math.Abs(value); if (pluralWordsCount == 2) return value is >= 0 and < 2 ? 0 : 1; if (pluralWordsCount == 3) return GetWordsCount3Value(value); @@ -239,6 +244,7 @@ public static void RestoreDefault() private static int GetWordsCount3Value(decimal n) { + n = Math.Abs(n); return n switch { 0 => 0, @@ -249,6 +255,7 @@ private static int GetWordsCount3Value(decimal n) private static int GetWordsCount4Value(decimal n) { + n = Math.Abs(n); return n switch { < 0 => 0, @@ -258,20 +265,35 @@ private static int GetWordsCount4Value(decimal n) }; } - private static PluralRuleDelegate TripleOneTwoOther => (value, pluralWordsCount) => value == 1 ? 0 : value == 2 ? 1 : 2; // Triple: one (n == 1), two (n == 2), other + private static PluralRuleDelegate TripleOneTwoOther => (value, pluralWordsCount) => + { + value = Math.Abs(value); + return value == 1 ? 0 : value == 2 ? 1 : 2; + }; // Triple: one (n == 1), two (n == 2), other + private static PluralRuleDelegate RussianSerboCroatian => (value, pluralWordsCount) => - value % 10 == 1 && value % 100 != 11 ? 0 : // one - (value % 10).BetweenWithoutFraction(2, 4) && !(value % 100).BetweenWithoutFraction(12, 14) ? 1 : // few - 2; // Russian & Serbo-Croatian + { + value = Math.Abs(value); + return value % 10 == 1 && value % 100 != 11 ? 0 : // one + (value % 10).BetweenWithoutFraction(2, 4) && !(value % 100).BetweenWithoutFraction(12, 14) ? 1 : // few + 2; + }; // Russian & Serbo-Croatian + private static PluralRuleDelegate Arabic => (value, pluralWordsCount) => - value == 0 ? 0 : // zero - value == 1 ? 1 : // one - value == 2 ? 2 : // two - (value % 100).BetweenWithoutFraction(3, 10) ? 3 : // few - (value % 100).BetweenWithoutFraction(11, 99) ? 4 : // many - 5; // other + { + value = Math.Abs(value); + return value == 0 ? 0 : // zero + value == 1 ? 1 : // one + value == 2 ? 2 : // two + (value % 100).BetweenWithoutFraction(3, 10) ? 3 : // few + (value % 100).BetweenWithoutFraction(11, 99) ? 4 : // many + 5; + }; // other + private static PluralRuleDelegate Breton => (value, pluralWordsCount) => - value switch + { + value = Math.Abs(value); + return value switch { 0 => 0, // zero 1 => 1, // one @@ -279,16 +301,23 @@ private static int GetWordsCount4Value(decimal n) 3 => 3, // few 6 => 4, // many _ => 5 // other - }; + }; + }; + private static PluralRuleDelegate Czech => (value, pluralWordsCount) => - value == 0 ? 0 : // zero - value == 1 ? 1 : // one - value.BetweenWithoutFraction(2, 4) ? 2 : // few - value % 1 == 0 ? 3 : // many - 4; // other + { + value = Math.Abs(value); + return value == 0 ? 0 : // zero + value == 1 ? 1 : // one + value.BetweenWithoutFraction(2, 4) ? 2 : // few + value % 1 == 0 ? 3 : // many + 4; // other + }; private static PluralRuleDelegate Welsh => (value, pluralWordsCount) => - value switch + { + value = Math.Abs(value); + return value switch { 0 => 0, // zero 1 => 1, // one @@ -297,67 +326,120 @@ private static int GetWordsCount4Value(decimal n) 6 => 4, // many _ => 5 // other }; + }; private static PluralRuleDelegate Manx => (value, pluralWordsCount) => - (value % 10).BetweenWithoutFraction(1, 2) || value % 20 == 0 + { + value = Math.Abs(value); + return (value % 10).BetweenWithoutFraction(1, 2) || value % 20 == 0 ? 0 : // one 1; + }; + private static PluralRuleDelegate Langi => (value, pluralWordsCount) => - value switch + { + value = Math.Abs(value); + return value switch { 0 => 0, > 0 and < 2 => 1, _ => 2 }; + }; + private static PluralRuleDelegate Lithuanian => (value, pluralWordsCount) => - value % 10 == 1 && !(value % 100).BetweenWithoutFraction(11, 19) ? 0 : // one - (value % 10).BetweenWithoutFraction(2, 9) && !(value % 100).BetweenWithoutFraction(11, 19) ? 1 : // few - 2; + { + value = Math.Abs(value); + return value % 10 == 1 && !(value % 100).BetweenWithoutFraction(11, 19) ? 0 : // one + (value % 10).BetweenWithoutFraction(2, 9) && !(value % 100).BetweenWithoutFraction(11, 19) ? 1 : // few + 2; + }; + private static PluralRuleDelegate Latvian => (value, pluralWordsCount) => - value == 0 ? 0 : // zero - value % 10 == 1 && value % 100 != 11 ? 1 : - 2; + { + value = Math.Abs(value); + return value == 0 ? 0 : // zero + value % 10 == 1 && value % 100 != 11 ? 1 : + 2; + }; + private static PluralRuleDelegate Macedonian => (value, pluralWordsCount) => - value % 10 == 1 && value != 11 + { + value = Math.Abs(value); + return value % 10 == 1 && value != 11 ? 0 : // one 1; + }; + private static PluralRuleDelegate Moldavian => (value, pluralWordsCount) => - value == 1 ? 0 : // one - value == 0 || value != 1 && (value % 100).BetweenWithoutFraction(1, 19) ? 1 : // few - 2; + { + value = Math.Abs(value); + return value == 1 ? 0 : // one + value == 0 || value != 1 && (value % 100).BetweenWithoutFraction(1, 19) ? 1 : // few + 2; + }; + private static PluralRuleDelegate Maltese => (value, pluralWordsCount) => - value == 1 ? 0 : // one - value == 0 || (value % 100).BetweenWithoutFraction(2, 10) ? 1 : // few - (value % 100).BetweenWithoutFraction(11, 19) ? 2 : // many - 3; + { + value = Math.Abs(value); + return value == 1 ? 0 : // one + value == 0 || (value % 100).BetweenWithoutFraction(2, 10) ? 1 : // few + (value % 100).BetweenWithoutFraction(11, 19) ? 2 : // many + 3; + }; + private static PluralRuleDelegate Polish => (value, pluralWordsCount) => - value == 1 ? 0 : // one - (value % 10).BetweenWithoutFraction(2, 4) && !(value % 100).BetweenWithoutFraction(12, 14) ? 1 : // few - (value % 10).BetweenWithoutFraction(0, 1) || (value % 10).BetweenWithoutFraction(5, 9) || (value % 100).BetweenWithoutFraction(12, 14) ? 2 : // many - 3; + { + value = Math.Abs(value); + return value == 1 ? 0 : // one + (value % 10).BetweenWithoutFraction(2, 4) && !(value % 100).BetweenWithoutFraction(12, 14) ? 1 : // few + (value % 10).BetweenWithoutFraction(0, 1) || (value % 10).BetweenWithoutFraction(5, 9) || + (value % 100).BetweenWithoutFraction(12, 14) ? 2 : // many + 3; + }; + private static PluralRuleDelegate Romanian => (value, pluralWordsCount) => - value == 1 ? 0 : // one - value == 0 || (value % 100).BetweenWithoutFraction(1, 19) ? 1 : // few - 2; + { + value = Math.Abs(value); + return value == 1 ? 0 : // one + value == 0 || (value % 100).BetweenWithoutFraction(1, 19) ? 1 : // few + 2; + }; + private static PluralRuleDelegate Tachelhit => (value, pluralWordsCount) => - value >= 0 && value <= 1 ? 0 : // one - value.BetweenWithoutFraction(2, 10) ? 1 : // few - 2; + { + value = Math.Abs(value); + return value >= 0 && value <= 1 ? 0 : // one + value.BetweenWithoutFraction(2, 10) ? 1 : // few + 2; + }; + private static PluralRuleDelegate Slovak => (value, pluralWordsCount) => - value == 1 ? 0 : // one - value.BetweenWithoutFraction(2, 4) ? 1 : // few - 2; + { + value = Math.Abs(value); + return value == 1 ? 0 : // one + value.BetweenWithoutFraction(2, 4) ? 1 : // few + 2; + }; + private static PluralRuleDelegate Slovenian => (value, pluralWordsCount) => - value % 100 == 1 ? 0 : // one - value % 100 == 2 ? 1 : // two - (value % 100).BetweenWithoutFraction(3, 4) ? 2 : // few - 3; + { + value = Math.Abs(value); + return value % 100 == 1 ? 0 : // one + value % 100 == 2 ? 1 : // two + (value % 100).BetweenWithoutFraction(3, 4) ? 2 : // few + 3; + }; + private static PluralRuleDelegate CentralMoroccoTamazight => (value, pluralWordsCount) => - value.BetweenWithoutFraction(0, 1) || value.BetweenWithoutFraction(11, 99) + { + value = Math.Abs(value); + return value.BetweenWithoutFraction(0, 1) || value.BetweenWithoutFraction(11, 99) ? 0 : // one 1; + }; /// /// This delegate determines which singular or plural word should be chosen for the given quantity. From a85c568adf9475dd668c8585da369e2065e0b791 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 03:05:39 +0000 Subject: [PATCH 2/3] chore(deps): bump actions/upload-artifact from 4 to 5 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/CodeQuality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CodeQuality.yml b/.github/workflows/CodeQuality.yml index 704fdcc5..ca818947 100644 --- a/.github/workflows/CodeQuality.yml +++ b/.github/workflows/CodeQuality.yml @@ -75,7 +75,7 @@ jobs: dotnet test ./src/SmartFormat.sln --no-build --verbosity normal --configuration Release /p:AltCover=true /p:AltCoverXmlReport="coverage.xml" /p:AltCoverStrongNameKey="../SmartFormat.snk" /p:AltCoverAssemblyExcludeFilter="SmartFormat.Tests|SmartFormat.ZString|NUnit3.TestAdapter|AltCover" /p:AltCoverAttributeFilter="ExcludeFromCodeCoverage" /p:AltCoverLineCover="true" .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" - name: Store coverage report as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: coverage-report path: ./src/SmartFormat.Tests/coverage.*.xml # store all coverage reports From 698239d3f97cb63cb23bb89ae85739bc6caffb7d Mon Sep 17 00:00:00 2001 From: axunonb Date: Sat, 1 Nov 2025 09:41:00 +0100 Subject: [PATCH 3/3] Fix error in Demo project --- src/Demo/SmartFormatDemo.cs | 2 +- .../PluralLocalizationFormatterTests.cs | 8 +- src/SmartFormat/Utilities/PluralRules.cs | 190 +++++------------- 3 files changed, 59 insertions(+), 141 deletions(-) diff --git a/src/Demo/SmartFormatDemo.cs b/src/Demo/SmartFormatDemo.cs index 28b25544..85be28b7 100644 --- a/src/Demo/SmartFormatDemo.cs +++ b/src/Demo/SmartFormatDemo.cs @@ -190,8 +190,8 @@ private void TxtInput_TextChanged(object sender, EventArgs e) private void LstExamples_SelectedIndexChanged(object sender, EventArgs e) { + if (lstExamples.SelectedItem == null) return; var example = (KeyValuePair) lstExamples.SelectedItem; - if (example.Value == null) return; txtInput.Text = example.Value; } } diff --git a/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs b/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs index 558104ee..4e3370ce 100644 --- a/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs +++ b/src/SmartFormat.Tests/Extensions/PluralLocalizationFormatterTests.cs @@ -99,7 +99,7 @@ public void Test_Default() new CultureInfo("en-US"), "There {0:plural:is|are} {0} {0:plural:item|items} remaining", new ExpectedResults { - { -1, "There is -1 item remaining"}, + { -1, "There are -1 items remaining"}, { 0, "There are 0 items remaining"}, {0.5m, "There are 0.5 items remaining"}, { 1, "There is 1 item remaining"}, @@ -116,7 +116,7 @@ public void Test_English() new CultureInfo("en-US"), "There {0:plural:is|are} {0} {0:plural:item|items} remaining", new ExpectedResults { - { -1, "There is -1 item remaining"}, + { -1, "There are -1 items remaining"}, { 0, "There are 0 items remaining"}, {0.5m, "There are 0.5 items remaining"}, { 1, "There is 1 item remaining"}, @@ -184,7 +184,7 @@ public void Test_French_3words(int count, string expected) Assert.That(actual, Is.EqualTo(string.Format(ci, expected, count))); } - [TestCase(-1, "une personne")] // -1 is treated as 1 (singular) + [TestCase(-1, "-")] [TestCase(0, "pas de personne")] // 0 is singular [TestCase(1, "une personne")] // 1 is singular [TestCase(2, "{0} personnes")] // 2 is plural @@ -214,7 +214,7 @@ public void Test_Turkish() new CultureInfo("tr"), "Seçili {0:plural:nesneyi|nesneleri} silmek istiyor musunuz?", new ExpectedResults { - { -1, "Seçili nesneyi silmek istiyor musunuz?"}, // -1 is treated as 1 (singular) + { -1, "Seçili nesneleri silmek istiyor musunuz?"}, { 0, "Seçili nesneleri silmek istiyor musunuz?"}, {0.5m, "Seçili nesneleri silmek istiyor musunuz?"}, { 1, "Seçili nesneyi silmek istiyor musunuz?"}, diff --git a/src/SmartFormat/Utilities/PluralRules.cs b/src/SmartFormat/Utilities/PluralRules.cs index 13a1789c..876656ae 100644 --- a/src/SmartFormat/Utilities/PluralRules.cs +++ b/src/SmartFormat/Utilities/PluralRules.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; namespace SmartFormat.Utilities; #pragma warning disable S3776 // disable sonar cognitive complexity warnings @@ -210,7 +211,6 @@ public static void RestoreDefault() private static PluralRuleDelegate DualOneOther => (value, pluralWordsCount) => { - value = Math.Abs(value); return pluralWordsCount switch { 2 => value == 1 ? 0 : 1, 3 => value switch { @@ -224,15 +224,10 @@ public static void RestoreDefault() }; // Dual: one (n == 1), other private static PluralRuleDelegate DualWithZero => - (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 0 || value == 1 ? 0 : 1; - }; // DualWithZero: one (n == 0..1), other + (value, pluralWordsCount) => value == 0 || value == 1 ? 0 : 1; // DualWithZero: one (n == 0..1), other private static PluralRuleDelegate DualFromZeroToTwo => (value, pluralWordsCount) => { - value = Math.Abs(value); if (pluralWordsCount == 2) return value is >= 0 and < 2 ? 0 : 1; if (pluralWordsCount == 3) return GetWordsCount3Value(value); @@ -244,7 +239,6 @@ public static void RestoreDefault() private static int GetWordsCount3Value(decimal n) { - n = Math.Abs(n); return n switch { 0 => 0, @@ -255,7 +249,6 @@ private static int GetWordsCount3Value(decimal n) private static int GetWordsCount4Value(decimal n) { - n = Math.Abs(n); return n switch { < 0 => 0, @@ -265,35 +258,20 @@ private static int GetWordsCount4Value(decimal n) }; } - private static PluralRuleDelegate TripleOneTwoOther => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 1 ? 0 : value == 2 ? 1 : 2; - }; // Triple: one (n == 1), two (n == 2), other - + private static PluralRuleDelegate TripleOneTwoOther => (value, pluralWordsCount) => value == 1 ? 0 : value == 2 ? 1 : 2; // Triple: one (n == 1), two (n == 2), other private static PluralRuleDelegate RussianSerboCroatian => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value % 10 == 1 && value % 100 != 11 ? 0 : // one - (value % 10).BetweenWithoutFraction(2, 4) && !(value % 100).BetweenWithoutFraction(12, 14) ? 1 : // few - 2; - }; // Russian & Serbo-Croatian - + value % 10 == 1 && value % 100 != 11 ? 0 : // one + (value % 10).BetweenWithoutFraction(2, 4) && !(value % 100).BetweenWithoutFraction(12, 14) ? 1 : // few + 2; // Russian & Serbo-Croatian private static PluralRuleDelegate Arabic => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 0 ? 0 : // zero - value == 1 ? 1 : // one - value == 2 ? 2 : // two - (value % 100).BetweenWithoutFraction(3, 10) ? 3 : // few - (value % 100).BetweenWithoutFraction(11, 99) ? 4 : // many - 5; - }; // other - + value == 0 ? 0 : // zero + value == 1 ? 1 : // one + value == 2 ? 2 : // two + (value % 100).BetweenWithoutFraction(3, 10) ? 3 : // few + (value % 100).BetweenWithoutFraction(11, 99) ? 4 : // many + 5; // other private static PluralRuleDelegate Breton => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value switch + value switch { 0 => 0, // zero 1 => 1, // one @@ -301,23 +279,16 @@ private static int GetWordsCount4Value(decimal n) 3 => 3, // few 6 => 4, // many _ => 5 // other - }; - }; - + }; private static PluralRuleDelegate Czech => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 0 ? 0 : // zero - value == 1 ? 1 : // one - value.BetweenWithoutFraction(2, 4) ? 2 : // few - value % 1 == 0 ? 3 : // many - 4; // other - }; + value == 0 ? 0 : // zero + value == 1 ? 1 : // one + value.BetweenWithoutFraction(2, 4) ? 2 : // few + value % 1 == 0 ? 3 : // many + 4; // other private static PluralRuleDelegate Welsh => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value switch + value switch { 0 => 0, // zero 1 => 1, // one @@ -326,120 +297,67 @@ private static int GetWordsCount4Value(decimal n) 6 => 4, // many _ => 5 // other }; - }; private static PluralRuleDelegate Manx => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return (value % 10).BetweenWithoutFraction(1, 2) || value % 20 == 0 + (value % 10).BetweenWithoutFraction(1, 2) || value % 20 == 0 ? 0 : // one 1; - }; - private static PluralRuleDelegate Langi => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value switch + value switch { 0 => 0, > 0 and < 2 => 1, _ => 2 }; - }; - private static PluralRuleDelegate Lithuanian => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value % 10 == 1 && !(value % 100).BetweenWithoutFraction(11, 19) ? 0 : // one - (value % 10).BetweenWithoutFraction(2, 9) && !(value % 100).BetweenWithoutFraction(11, 19) ? 1 : // few - 2; - }; - + value % 10 == 1 && !(value % 100).BetweenWithoutFraction(11, 19) ? 0 : // one + (value % 10).BetweenWithoutFraction(2, 9) && !(value % 100).BetweenWithoutFraction(11, 19) ? 1 : // few + 2; private static PluralRuleDelegate Latvian => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 0 ? 0 : // zero - value % 10 == 1 && value % 100 != 11 ? 1 : - 2; - }; - + value == 0 ? 0 : // zero + value % 10 == 1 && value % 100 != 11 ? 1 : + 2; private static PluralRuleDelegate Macedonian => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value % 10 == 1 && value != 11 + value % 10 == 1 && value != 11 ? 0 : // one 1; - }; - private static PluralRuleDelegate Moldavian => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 1 ? 0 : // one - value == 0 || value != 1 && (value % 100).BetweenWithoutFraction(1, 19) ? 1 : // few - 2; - }; - + value == 1 ? 0 : // one + value == 0 || value != 1 && (value % 100).BetweenWithoutFraction(1, 19) ? 1 : // few + 2; private static PluralRuleDelegate Maltese => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 1 ? 0 : // one - value == 0 || (value % 100).BetweenWithoutFraction(2, 10) ? 1 : // few - (value % 100).BetweenWithoutFraction(11, 19) ? 2 : // many - 3; - }; - + value == 1 ? 0 : // one + value == 0 || (value % 100).BetweenWithoutFraction(2, 10) ? 1 : // few + (value % 100).BetweenWithoutFraction(11, 19) ? 2 : // many + 3; private static PluralRuleDelegate Polish => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 1 ? 0 : // one - (value % 10).BetweenWithoutFraction(2, 4) && !(value % 100).BetweenWithoutFraction(12, 14) ? 1 : // few - (value % 10).BetweenWithoutFraction(0, 1) || (value % 10).BetweenWithoutFraction(5, 9) || - (value % 100).BetweenWithoutFraction(12, 14) ? 2 : // many - 3; - }; - + value == 1 ? 0 : // one + (value % 10).BetweenWithoutFraction(2, 4) && !(value % 100).BetweenWithoutFraction(12, 14) ? 1 : // few + (value % 10).BetweenWithoutFraction(0, 1) || (value % 10).BetweenWithoutFraction(5, 9) || (value % 100).BetweenWithoutFraction(12, 14) ? 2 : // many + 3; private static PluralRuleDelegate Romanian => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 1 ? 0 : // one - value == 0 || (value % 100).BetweenWithoutFraction(1, 19) ? 1 : // few - 2; - }; - + value == 1 ? 0 : // one + value == 0 || (value % 100).BetweenWithoutFraction(1, 19) ? 1 : // few + 2; private static PluralRuleDelegate Tachelhit => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value >= 0 && value <= 1 ? 0 : // one - value.BetweenWithoutFraction(2, 10) ? 1 : // few - 2; - }; - + value >= 0 && value <= 1 ? 0 : // one + value.BetweenWithoutFraction(2, 10) ? 1 : // few + 2; private static PluralRuleDelegate Slovak => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value == 1 ? 0 : // one - value.BetweenWithoutFraction(2, 4) ? 1 : // few - 2; - }; - + value == 1 ? 0 : // one + value.BetweenWithoutFraction(2, 4) ? 1 : // few + 2; private static PluralRuleDelegate Slovenian => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value % 100 == 1 ? 0 : // one - value % 100 == 2 ? 1 : // two - (value % 100).BetweenWithoutFraction(3, 4) ? 2 : // few - 3; - }; - + value % 100 == 1 ? 0 : // one + value % 100 == 2 ? 1 : // two + (value % 100).BetweenWithoutFraction(3, 4) ? 2 : // few + 3; private static PluralRuleDelegate CentralMoroccoTamazight => (value, pluralWordsCount) => - { - value = Math.Abs(value); - return value.BetweenWithoutFraction(0, 1) || value.BetweenWithoutFraction(11, 99) + value.BetweenWithoutFraction(0, 1) || value.BetweenWithoutFraction(11, 99) ? 0 : // one 1; - }; /// /// This delegate determines which singular or plural word should be chosen for the given quantity.