Humanizr/Humanizer · error · InvalidOperationException

Locale '{localeCode}.surfaces.list' must define 'engine'.

Error message

Locale '{localeCode}.surfaces.list' must define 'engine'.

What it means

Thrown by NormalizeListSurface when a locale's surfaces.list block has neither a legacy 'value' scalar nor an 'engine' scalar. The list/collection-formatter surface must declare which engine it uses (e.g. oxford, conjunction, delimited, clitic) unless it provides a legacy 'value' separator string. Without one or the other the generator cannot determine how to join list items.

Source

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

                    {
                        RejectExplicitDefaultEngines(localeCode, $"{path}[{index}]", sequence.Items[index]);
                    }

                    break;
                default:
                    break;
            }
        }

        static SimpleYamlMapping NormalizeListSurface(string localeCode, SimpleYamlMapping mapping)
        {
            if (mapping.TryGetValue("value", out _))
            {
                return mapping;
            }

            var engine = mapping.GetScalar("engine")
                ?? throw new InvalidOperationException($"Locale '{localeCode}.surfaces.list' must define 'engine'.");
            var pairTemplate = mapping.GetScalar("pairTemplate");
            var finalTemplate = mapping.GetScalar("finalTemplate");
            var serialTemplate = mapping.GetScalar("serialTemplate");
            if (engine == "oxford" && pairTemplate is null && finalTemplate is null)
            {
                return mapping;
            }

            if (pairTemplate is null)
            {
                throw new InvalidOperationException(
                    $"Locale '{localeCode}.surfaces.list' must define canonical list templates or legacy 'value'.");
            }

            finalTemplate ??= pairTemplate;

            var values = ImmutableDictionary.CreateBuilder<string, SimpleYamlValue>(StringComparer.Ordinal);
            var oxfordComma = ReadBoolean(mapping, "oxfordComma");

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Add an 'engine' scalar to the surfaces.list block, e.g. 'engine: oxford' or 'engine: conjunction'.
  2. Alternatively, provide a legacy 'value' scalar (a bare separator string like ', ') if you only need simple delimiter joining.
  3. Check the locale file against a known-good locale (e.g. en.yml) to confirm the expected list surface shape.

Example fix

# before
surfaces:
  list:
    pairTemplate: "{0}, {1}"
# after
surfaces:
  list:
    engine: oxford
    pairTemplate: "{0}, {1}"
Defensive patterns

Strategy: validation

Validate before calling

// Check surfaces.list has engine or value before invoking the generator
static bool ListSurfaceIsValid(SimpleYamlMapping surfaces)
{
    if (!surfaces.TryGetValue("list", out var list) || list is not SimpleYamlMapping listMap)
        return true; // no list surface, no problem
    return listMap.TryGetValue("value", out _) || listMap.GetScalar("engine") is not null;
}

Prevention

When it happens

Trigger: ToLocaleDefinition calls NormalizeListSurface for every locale with a 'list' surface. If the mapping has no 'value' key and no 'engine' key, GetScalar returns null and the ?? operator throws. For example: 'surfaces:\n list:\n pairTemplate: "{0}, {1}"' without an engine line.

Common situations: A contributor writes a list surface from scratch and forgets the engine line, or renames 'engine' to something else. Also happens when migrating from a legacy collectionFormatter that was a bare scalar (which maps to engine only) but the new file uses mapping syntax without re-adding engine.

Related errors


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