Humanizr/Humanizer · error · InvalidOperationException

Locale '{localeCode}.{featureName}' must declare an 'engine'

Error message

Locale '{localeCode}.{featureName}' must declare an 'engine'.

What it means

The 'collectionFormatter' feature, when provided as a mapping, must declare an 'engine' key so the generator knows which collection-formatting strategy to wire up. Without it the feature is ambiguous and the generator cannot select an implementation.

Source

Thrown at src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs:966

                var localEngine = localMapping.GetScalar("engine");
                return inheritedEngine is not null &&
                    localEngine is not null &&
                    !string.Equals(inheritedEngine, localEngine, StringComparison.Ordinal)
                    ? localMapping
                    : MergeMappings(path, inheritedMapping, localMapping);
            }

            // Ordered lexical data such as scale lists and token tables stay explicit.
            // A child sequence replaces the parent sequence rather than trying to merge by index.
            return localValue;
        }

        static LocaleFeature CreateMappedFeature(string localeCode, string featureName, SimpleYamlMapping mapping)
        {
            if (featureName == "collectionFormatter")
            {
                var engine = mapping.GetScalar("engine")
                    ?? throw new InvalidOperationException($"Locale '{localeCode}.{featureName}' must declare an 'engine'.");
                return new LocaleFeature(localeCode, featureName, engine, mapping.GetScalar("value"), null, default, false);
            }

            if (featureName == "wordsToNumber" &&
                string.Equals(mapping.GetScalar("engine"), "token-map", StringComparison.Ordinal))
            {
                return new LocaleFeature(localeCode, featureName, "lexicon", localeCode, null, ToJsonElement(NormalizeFeatureReferences(localeCode, featureName, mapping)), false);
            }

            if (featureName is "dateToOrdinalWords" or "dateOnlyToOrdinalWords" &&
                string.Equals(mapping.GetScalar("engine"), "default", StringComparison.Ordinal) &&
                mapping.GetScalar("pattern") is not null)
            {
                var values = mapping.Values.SetItem("engine", new SimpleYamlScalar("pattern", isQuoted: true));
                mapping = new SimpleYamlMapping(values);
            }

            if (featureName is "formatter" or "ordinalizer" or "dateToOrdinalWords" or "dateOnlyToOrdinalWords" or "timeOnlyToClockNotation")

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Add an 'engine:' key to the collectionFormatter mapping (e.g. 'engine: default' or 'engine: oxford').
  2. Verify the engine value matches a supported collection formatter engine name.
  3. If a scalar shorthand is sufficient, replace the mapping with 'collectionFormatter: default'.

Example fix

# before
collectionFormatter:
  value: oxford

# after
collectionFormatter:
  engine: oxford
  value: ""
Defensive patterns

Strategy: validation

Validate before calling

import yaml, pathlib
locales_dir = pathlib.Path('src/Humanizer/Locales')
for f in locales_dir.rglob('*.yml'):
    data = yaml.safe_load(f.read_text())
    if isinstance(data, dict) and isinstance(data.get('collectionFormatter'), dict):
        if 'engine' not in data['collectionFormatter']:
            print(f'{f.name}: collectionFormatter mapping must declare "engine"')

Prevention

When it happens

Trigger: A locale's 'collectionFormatter:' mapping contains a 'value:' key (or other keys) but omits the required 'engine:' key.

Common situations: Authoring collection formatting for the first time and forgetting the engine selector; partially copying a mapping and dropping the engine line; indentation causing 'engine:' to attach elsewhere.

Related errors


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