Humanizr/Humanizer · error · InvalidOperationException

Locale '{localeCode}.surfaces.{surface.Key}' must be a mappi

Error message

Locale '{localeCode}.surfaces.{surface.Key}' must be a mapping.

What it means

Each surface value under `surfaces:` must itself be a mapping (CanonicalLocaleAuthoring.cs:117-121). A scalar or sequence (e.g. `clock: '12-hour'`) cannot describe the surface's sub-properties.

Source

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

            else
            {
                surfaces = surfacesValue is SimpleYamlMapping surfacesMapping
                    ? surfacesMapping
                    : throw new InvalidOperationException($"Locale '{localeCode}.surfaces' must be a mapping.");
            }

            foreach (var surface in surfaces.Values)
            {
                if (!SupportedSurfaceNames.Contains(surface.Key, StringComparer.Ordinal))
                {
                    throw new InvalidOperationException(
                        $"Locale '{localeCode}.surfaces' defines unsupported surface '{surface.Key}'. " +
                        $"Supported surfaces: {string.Join(", ", SupportedSurfaceNames)}.");
                }

                if (surface.Value is not SimpleYamlMapping)
                {
                    throw new InvalidOperationException(
                        $"Locale '{localeCode}.surfaces.{surface.Key}' must be a mapping.");
                }

                RejectExplicitDefaultEngines(localeCode, $"surfaces.{surface.Key}", surface.Value);
            }

            return new CanonicalLocaleDocument(
                localeCode,
                variantOf,
                surfaces,
                NormalizeCanonicalText(fileText));
        }

        internal static string Emit(CanonicalLocaleDocument document) => document.CanonicalText;

        internal static LocaleDefinition ToLocaleDefinition(CanonicalLocaleDocument document)
        {
            var features = ImmutableDictionary.CreateBuilder<string, SimpleYamlValue>(StringComparer.Ordinal);

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Convert the surface value to a mapping, e.g. `clock:` then indented properties.
  2. If the surface only needs the default engine, either omit it or, for allowed paths, set `engine: "default"` inside a mapping.
  3. Rebuild.

Example fix

# before
surfaces:
  compass: north
# after
surfaces:
  compass:
    full: ["north","east","south","west"]
Defensive patterns

Strategy: validation

Validate before calling

import yaml, sys
doc = yaml.safe_load(open(sys.argv[1], encoding='utf-8'))
if isinstance(doc.get('surfaces'), dict):
    for k, v in doc['surfaces'].items():
        assert isinstance(v, dict), f'surfaces.{k} must be a mapping'

Prevention

When it happens

Trigger: Writing `surfaces.clock: 'default'` or assigning a list to a surface instead of a keyed block.

Common situations: Shorthand authoring; converting a legacy scalar value into the canonical form incompletely.

Related errors


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