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
- Add an 'engine' scalar to the surfaces.list block, e.g. 'engine: oxford' or 'engine: conjunction'.
- Alternatively, provide a legacy 'value' scalar (a bare separator string like ', ') if you only need simple delimiter joining.
- 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
- Always include 'engine' in surfaces.list blocks unless using the legacy 'value' scalar.
- Cross-reference the list surface against en.yml or another shipped locale.
- Run dotnet build after every locale edit to catch source-generator errors early.
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
- Locale '{localeCode}.surfaces.list' must define canonical li
- Canonical list templates must use '{0}' and '{1}' as an infi
- Locale '{localeCode}.{featureName}' must declare an 'engine'
- Locale '{localeCode}.{path}' defines duplicate numeric minut
- Locale '{localeCode}.{path}.{key}' must be a string.
AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13).
Data as JSON: /api/errors/cf2a10eb7df8c341.
Report an issue: GitHub.