Skip to content

Add UseScaleWord format to auto-select scale based on locale#1697

Closed
reabr wants to merge 6 commits into
Humanizr:mainfrom
reabr:feature/tometric-locale-scale
Closed

Add UseScaleWord format to auto-select scale based on locale#1697
reabr wants to merge 6 commits into
Humanizr:mainfrom
reabr:feature/tometric-locale-scale

Conversation

@reabr

@reabr reabr commented Mar 28, 2026

Copy link
Copy Markdown

Fixes #1675

What

Adds a new MetricNumeralFormats.UseScaleWord flag that automatically
selects between long and short scale words based on CultureInfo.CurrentUICulture,
as suggested in the issue.

Changes

  • Added UseScaleWord = 16 flag to MetricNumeralFormats enum
  • Added LongScaleCultures lookup set in MetricNumeralExtensions covering
    German, French, Italian, Spanish, Portuguese, Dutch, Russian, Polish and Turkish
  • Updated GetUnitText to handle the new flag by checking the current UI culture

Example

(1E9).ToMetric(MetricNumeralFormats.WithSpace | MetricNumeralFormats.UseScaleWord)

  • en-US"1 billion" (short scale)
  • de-DE"1 milliard" (long scale)
  • fr-FR"1 milliard" (long scale)

Tests

Added three test methods to MetricNumeralTests.cs covering:

  • Short scale (en-US, inherited from class-level [UseCulture])
  • Long scale German (de-DE)
  • Long scale French (fr-FR)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d51f38952c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +461 to +462
var isLongScale = LongScaleCultures.Contains(culture.Name)
|| LongScaleCultures.Contains(culture.TwoLetterISOLanguageName);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prefer region-specific scale over language-wide fallback

UseScaleWord treats a culture as long-scale when either culture.Name or culture.TwoLetterISOLanguageName is in LongScaleCultures. Because the set includes "pt", this forces pt-BR into long-scale even though Humanizer registers a separate Brazilian Portuguese converter (NumberToWordsConverterRegistry) and that converter uses short-scale billions (BrazilianPortugueseNumberToWordsConverter with bilhão at 10^9). In practice, 1E9.ToMetric(WithSpace | UseScaleWord) will return the wrong scale word for pt-BR.

Useful? React with 👍 / 👎.

Comment on lines +518 to +522
static readonly HashSet<string> LongScaleCultures =
[
"de", "de-DE", // German
"fr", "fr-FR", // French
"it", "it-IT", // Italian

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add missing long-scale locales to UseScaleWord map

The hard-coded LongScaleCultures list is incomplete relative to locales already supported by the repo, so UseScaleWord silently falls back to short scale for some long-scale cultures. For example, Czech (cs) is registered and its converter uses miliarda for 10^9 (CzechNumberToWordsConverter), but cs is absent here, so cs-CZ will produce short-scale words. This makes the new “auto-select based on locale” behavior inaccurate for supported cultures.

Useful? React with 👍 / 👎.

@reabr reabr force-pushed the feature/tometric-locale-scale branch from 0ed2d84 to 6483a84 Compare March 28, 2026 18:42
@wyf027

wyf027 commented May 26, 2026

Copy link
Copy Markdown

Rebased this PR onto current main (f9292aa9) to resolve merge conflicts (notably MetricNumeralExtensions.cs closing brace / LongScaleCultures placement). Also added the missing DotNet11 ApiApprover enum entry.

Conflict-free branch: leno23:feat/tometric-use-scale-word-1675-rebase (based on your commits + one follow-up fix).

@reabr — happy to close this in favor of an update to your branch if you prefer authorship; otherwise this rebased branch is available for maintainers to cherry-pick/merge.

@wyf027

wyf027 commented May 26, 2026

Copy link
Copy Markdown

Opened #1799 as a conflict-free rebase on current main for CI/review. Happy to close #1799 if you update this branch instead.

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added automatic culture-based scale word selection for metric numerals, supporting long-scale formatting for German and French locales while defaulting to short-scale for other regions.
  • Tests

    • Added comprehensive test coverage validating scale word formatting for metric numerals across different cultures.
  • Chores

    • Updated public API approval records.

Walkthrough

A new MetricNumeralFormats.UseScaleWord flag enables culture-aware automatic selection between long and short scale words. The implementation checks the current UI culture against a hardcoded set of long-scale cultures, returning the appropriate scale word from metric unit prefix definitions. Tests and API approvals are updated accordingly.

Changes

Culture-aware metric scale word formatting

Layer / File(s) Summary
UseScaleWord enum flag definition
src/Humanizer/MetricNumeralFormats.cs
New public UseScaleWord = 16 flag with XML documentation describing automatic culture-based selection between long and short scale words.
Culture-aware scale word lookup and selection
src/Humanizer/MetricNumeralExtensions.cs
GetUnitText method now checks UseScaleWord flag and selects long or short scale words by matching the current UI culture name (full or two-letter ISO code) against a new LongScaleCultures HashSet. Cultures not in the set default to short scale.
Test coverage for scale word selection
tests/Humanizer.Tests/MetricNumeralTests.cs
Three new parameterized test methods validate short-scale behavior in the default culture and long-scale behavior in German (de-DE) and French (fr-FR) cultures using 1E9 and 1E12 exponents.
Public API approval updates
tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.*.verified.txt
Three API verification files (DotNet10_0, DotNet8_0, Net4_8) are updated to include the new UseScaleWord = 16 enum member in the public API surface.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A flag so fine, UseScaleWord we've made,
Culture-wise, long scales now fade or parade,
In German lands, "milliard" rings so true,
While English speakers count billions through and through,
One feature small, yet cultures all askew—now they align! 🌍

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a UseScaleWord format that auto-selects scale words based on locale.
Description check ✅ Passed The description is well-detailed and clearly related to the changeset, explaining what was added, how it works, and providing concrete examples.
Linked Issues check ✅ Passed The PR fully addresses issue #1675: implements UseScaleWord flag for locale-based auto-selection, supports long-scale cultures, and provides test coverage for both short and long scale locales.
Out of Scope Changes check ✅ Passed All changes are in scope: enum flag addition, culture lookup set, locale-aware logic, and comprehensive test coverage directly address the linked issue requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Humanizer/MetricNumeralExtensions.cs`:
- Around line 498-505: The code in MetricNumeralExtensions.cs currently checks
LongScaleCultures.Contains(culture.Name) OR
Contains(culture.TwoLetterISOLanguageName), which causes regional exceptions
like "pt-BR" to be masked by the language fallback; change the logic in the
branch that computes isLongScale (used when
formatValue.HasFlag(MetricNumeralFormats.UseScaleWord) and returning
UnitPrefixes[symbol].LongScaleWord/ShortScaleWord) to first check for an exact
culture.Name match and only if that fails fall back to checking the
TwoLetterISOLanguageName; apply the same exact fix to the second occurrence
around the later block (the other UseScaleWord branch referenced in the review).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 62af7691-caa9-4767-a688-0c38925829b5

📥 Commits

Reviewing files that changed from the base of the PR and between f9292aa and 1a5cdeb.

📒 Files selected for processing (6)
  • src/Humanizer/MetricNumeralExtensions.cs
  • src/Humanizer/MetricNumeralFormats.cs
  • tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet10_0.verified.txt
  • tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.DotNet8_0.verified.txt
  • tests/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.Approve_Public_Api.Net4_8.verified.txt
  • tests/Humanizer.Tests/MetricNumeralTests.cs

Comment on lines +498 to +505
if (formatValue.HasFlag(MetricNumeralFormats.UseScaleWord))
{
var culture = CultureInfo.CurrentUICulture;
var isLongScale = LongScaleCultures.Contains(culture.Name)
|| LongScaleCultures.Contains(culture.TwoLetterISOLanguageName);
return isLongScale
? UnitPrefixes[symbol].LongScaleWord
: UnitPrefixes[symbol].ShortScaleWord;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle regional short-scale exceptions before the language fallback.

pt-BR still resolves to long scale here because culture.TwoLetterISOLanguageName is "pt", so the explicit “NOT pt-BR” exception in the set is defeated. That makes (1E9).ToMetric(...UseScaleWord) return the wrong word for Brazilian Portuguese.

💡 Suggested fix
+    static readonly HashSet<string> ShortScaleCultureOverrides =
+    [
+        "pt-BR"
+    ];
+
     static string GetUnitText(char symbol, MetricNumeralFormats? formats)
     {
         if (formats.HasValue)
         {
             var formatValue = formats.Value;
@@
             if (formatValue.HasFlag(MetricNumeralFormats.UseScaleWord))
             {
                 var culture = CultureInfo.CurrentUICulture;
-                var isLongScale = LongScaleCultures.Contains(culture.Name)
-                               || LongScaleCultures.Contains(culture.TwoLetterISOLanguageName);
+                var isLongScale = !ShortScaleCultureOverrides.Contains(culture.Name)
+                    && (LongScaleCultures.Contains(culture.Name)
+                        || LongScaleCultures.Contains(culture.TwoLetterISOLanguageName));
                 return isLongScale
                     ? UnitPrefixes[symbol].LongScaleWord
                     : UnitPrefixes[symbol].ShortScaleWord;
             }
         }

Also applies to: 558-565

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Humanizer/MetricNumeralExtensions.cs` around lines 498 - 505, The code in
MetricNumeralExtensions.cs currently checks
LongScaleCultures.Contains(culture.Name) OR
Contains(culture.TwoLetterISOLanguageName), which causes regional exceptions
like "pt-BR" to be masked by the language fallback; change the logic in the
branch that computes isLongScale (used when
formatValue.HasFlag(MetricNumeralFormats.UseScaleWord) and returning
UnitPrefixes[symbol].LongScaleWord/ShortScaleWord) to first check for an exact
culture.Name match and only if that fails fall back to checking the
TwoLetterISOLanguageName; apply the same exact fix to the second occurrence
around the later block (the other UseScaleWord branch referenced in the review).

@reabr reabr closed this May 29, 2026
@reabr

reabr commented May 29, 2026

Copy link
Copy Markdown
Author

@leno23 Appreciate the rebase work! Feel free to proceed with merging your rebased branch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ToMetric() should have option to automatically respect locale's short/long scale preference

2 participants