Humanizr/Humanizer · error · InvalidOperationException

Locale '{localeCode}.surfaces.number.words' must be a mappin

Error message

Locale '{localeCode}.surfaces.number.words' must be a mapping, not a scalar or sequence.

What it means

`surfaces.number.words` holds the number-to-words engine description and must be a mapping (CanonicalLocaleAuthoring.cs:219-225). A scalar or sequence cannot express the engine/argument/profile structure.

Source

Thrown at src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs:223

            return new LocaleDefinition(document.LocaleCode, document.VariantOf, features.ToImmutable());
        }

        static void AddNumberFeatures(
            string localeCode,
            SimpleYamlMapping numberSurface,
            ImmutableDictionary<string, SimpleYamlValue>.Builder features)
        {
            foreach (var property in numberSurface.Values.Keys.Where(static property => property is not ("words" or "parse" or "formatting")))
            {
                throw new InvalidOperationException(
                    $"Locale '{localeCode}.surfaces.number' defines unsupported property '{property}'. Supported properties: words, parse, formatting.");
            }

            if (numberSurface.TryGetValue("words", out var wordsValue))
            {
                if (wordsValue is not SimpleYamlMapping wordsMapping)
                {
                    throw new InvalidOperationException(
                        $"Locale '{localeCode}.surfaces.number.words' must be a mapping, not a scalar or sequence.");
                }

                features["numberToWords"] = wordsMapping;
            }

            if (numberSurface.TryGetValue("parse", out var parseValue))
            {
                if (parseValue is not SimpleYamlMapping parseMapping)
                {
                    throw new InvalidOperationException(
                        $"Locale '{localeCode}.surfaces.number.parse' must be a mapping, not a scalar or sequence.");
                }

                features["wordsToNumber"] = parseMapping;
            }

            if (numberSurface.TryGetValue("formatting", out var fmtValue))

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Provide `words:` as a mapping with the engine key and any arguments.
  2. If the locale has no spellout, omit words entirely.
  3. Rebuild.

Example fix

# before
surfaces:
  number:
    words: default
# after
surfaces:
  number:
    words:
      engine: "english"
      argument: ""
Defensive patterns

Strategy: validation

Validate before calling

import yaml, sys
doc = yaml.safe_load(open(sys.argv[1], encoding='utf-8'))
words = ((doc.get('surfaces') or {}).get('number') or {}).get('words')
if words is not None:
    assert isinstance(words, dict), 'surfaces.number.words must be a mapping'

Prevention

When it happens

Trigger: Writing `words: "engine"` or `words: [...]` under surfaces.number.

Common situations: Porting a locale whose words block was a single string; truncating during migration.

Related errors


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