Humanizr/Humanizer · critical · InvalidOperationException

Linked-vigesimal parser profiles require descending scales.

Error message

Linked-vigesimal parser profiles require descending scales.

What it means

This InvalidOperationException is thrown by LinkedVigesimalWordsToNumberProfile.ValidateScales (called from the profile constructor) when two adjacent scale rows are not strictly descending — i.e. when a later (smaller-position) scale has a value greater than or equal to the previous (larger) scale. The linked-vigesimal parser walks scales from largest to smallest and relies on this ordering to decompose numbers. The check fires at profile construction time, surfacing during locale registry initialization.

Source

Thrown at src/Humanizer/Localisation/WordsToNumber/LinkedVigesimalWordsToNumberConverter.cs:498

    static string[] Tokenize(string value) =>
        value.Split(' ', StringSplitOptions.RemoveEmptyEntries);

    static string Normalize(string value) =>
        string.Join(" ", value.Trim().ToLowerInvariant().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries));

    static LinkedVigesimalScale[] ValidateScales(LinkedVigesimalScale[] value)
    {
        for (var i = 0; i < value.Length; i++)
        {
            if (value[i].Value <= 0)
            {
                throw new InvalidOperationException("Linked-vigesimal parser profiles require positive scale values.");
            }

            if (i > 0 && value[i - 1].Value <= value[i].Value)
            {
                throw new InvalidOperationException("Linked-vigesimal parser profiles require descending scales.");
            }
        }

        return value;
    }
}

/// <summary>Pre-tokenized linked-vigesimal scale phrase.</summary>
readonly record struct TokenizedLinkedVigesimalScale(long Value, string[] OneTokens, string[] OneWithRemainderTokens, string[] NameTokens, string[] NameWithRemainderTokens);

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Order scale values in strictly descending order in the locale YAML (e.g. 8000, 400, 20).
  2. Regenerate the locale source from corrected YAML using the project's source generator.
  3. Verify no duplicate scale values exist in the locale's scale list.
  4. Add a CI test that constructs each locale profile to catch ordering violations early.

Example fix

// before (locale YAML scales)
//  - value: 20
//  - value: 400   # out of order

// after
//  - value: 400
//  - value: 20
Defensive patterns

Strategy: validation

Validate before calling

// For locale authors: validate scale ordering before registration
static bool ScalesAreDescending(long[] scales)
{
    for (var i = 1; i < scales.Length; i++)
    {
        if (scales[i - 1] <= scales[i])
            return false;
    }
    return true;
}

Try / catch

try
{
    var converter = Configurator.GetWordsToNumberConverter(culture);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("descending scales"))
{
    converter = Configurator.GetWordsToNumberConverter(fallbackCulture);
}

Prevention

When it happens

Trigger: Constructing a LinkedVigesimalWordsToNumberProfile with a scales array where any scale[i].Value is greater than or equal to scale[i-1].Value. This arises from locale YAML data with misordered or duplicate scale values.

Common situations: Editing locale YAML and accidentally swapping scale rows or duplicating a value; a locale generator sorting scales incorrectly; forking a locale and reordering entries; a merge conflict that scrambled scale ordering.

Related errors


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