Humanizr/Humanizer · error · InvalidOperationException

Billion-word cardinal strategy requires a plural billion wor

Error message

Billion-word cardinal strategy requires a plural billion word.

What it means

Thrown when a locale's cardinal scale declares billionStrategy: 'billion-word' but omits the billionPluralWord property. The BillionStrategyCardinalScale row needs both singular and plural billion terms to pluralize counts of billions; the generator refuses to emit an incomplete row. It is the sibling check to the singular-billion-word error.

Source

Thrown at src/Humanizer.SourceGenerators/Common/GenerationHelpers.cs:738

            first = false;
        }

        builder.Append(" }");
        return builder.ToString();
    }

    static string CreateLegacyBillionStrategyCardinalScaleArrayExpression(JsonElement cardinalElement)
    {
        var millionScale = $"new(1000000UL, {QuoteLiteral(GetRequiredString(cardinalElement, "millionSingularWord"))}, {QuoteLiteral(GetRequiredString(cardinalElement, "millionPluralWord"))})";
        if (GetRequiredString(cardinalElement, "billionStrategy") != "billion-word")
        {
            return $"new BillionStrategyCardinalScale[] {{ {millionScale} }}";
        }

        var billionSingular = GetOptionalString(cardinalElement, "billionSingularWord")
            ?? throw new InvalidOperationException("Billion-word cardinal strategy requires a singular billion word.");
        var billionPlural = GetOptionalString(cardinalElement, "billionPluralWord")
            ?? throw new InvalidOperationException("Billion-word cardinal strategy requires a plural billion word.");
        return $"new BillionStrategyCardinalScale[] {{ new(1000000000UL, {QuoteLiteral(billionSingular)}, {QuoteLiteral(billionPlural)}), {millionScale} }}";
    }

    static string CreateContractedOneScaleArrayExpression(JsonElement arrayElement)
    {
        if (arrayElement.ValueKind != JsonValueKind.Array)
        {
            throw new InvalidOperationException("Expected JSON array.");
        }

        var builder = new StringBuilder("new ContractedOneScaleNumberToWordsConverter.Scale[] { ");
        var first = true;

        foreach (var item in arrayElement.EnumerateArray())
        {
            if (item.ValueKind != JsonValueKind.Object)
            {
                continue;

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Add the 'billionPluralWord' key alongside billionSingularWord under numberToWords cardinal (e.g. billionPluralWord: 'billions').
  2. If the locale does not distinguish singular/plural billion forms, set billionStrategy to a non-'billion-word' value so neither key is required.
  3. Mirror the exact key set from a working billion-word locale such as en.yml.

Example fix

# before
cardinal:
  billionStrategy: 'billion-word'
  billionSingularWord: 'un milliard'
# after
cardinal:
  billionStrategy: 'billion-word'
  billionSingularWord: 'un milliard'
  billionPluralWord: 'milliards'
Defensive patterns

Strategy: validation

Validate before calling

# Ensure the plural billion word accompanies the singular one:
#   rg "billionSingularWord" src/Humanizer/Locales/<code>.yml
#   rg "billionPluralWord"  src/Humanizer/Locales/<code>.yml
# Both must return a match within the cardinal block when billionStrategy is 'billion-word'.

Prevention

When it happens

Trigger: A locale YAML sets numberToWords cardinal.billionStrategy to 'billion-word' and provides billionSingularWord but not billionPluralWord. The throw happens immediately after the singular-word null-coalesce in CreateLegacyBillionStrategyCardinalScaleArrayExpression.

Common situations: A contributor adds the singular billion word while editing but forgets the plural, or copies a locale where the two keys diverge in naming. Surfaces as a CS8785 generator failure at build time, often right after the singular-word check is satisfied.

Related errors


AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13). Data as JSON: /api/errors/1782a9a42a4cb6d2. Report an issue: GitHub.