Humanizr/Humanizer · error · InvalidOperationException
Locale '{localeCode}.surfaces.list' must define canonical li
Error message
Locale '{localeCode}.surfaces.list' must define canonical list templates or legacy 'value'. What it means
Thrown by NormalizeListSurface when surfaces.list declares an engine and has no legacy 'value', but is missing the required 'pairTemplate'. The canonical list engine needs at least pairTemplate to build the join logic. The only exception is engine: 'oxford' with no templates at all (which uses built-in defaults). Any other engine without pairTemplate fails.
Source
Thrown at src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs:590
{
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");
var cliticizesFinal = ReadBoolean(mapping, "cliticizesFinal");
values["engine"] = new SimpleYamlScalar(
oxfordComma || engine == "oxford"
? "oxford"
: cliticizesFinal
? "clitic"
: engine == "delimited" || pairTemplate == finalTemplate && pairTemplate == serialTemplate
? "delimited"
: "conjunction",
isQuoted: true);
View on GitHub (pinned to ffc2b77c0f)
Solutions
- Add a 'pairTemplate' scalar using {0} and {1} placeholders, e.g. 'pairTemplate: "{0}, {1}"'.
- If you intended the oxford default templates, set 'engine: oxford' and remove all template keys so the built-in oxford defaults apply.
- Switch to the legacy 'value' form (a bare separator scalar) if you only need simple delimited output.
Example fix
# before
surfaces:
list:
engine: conjunction
# after
surfaces:
list:
engine: conjunction
pairTemplate: "{0}, {1}" Defensive patterns
Strategy: validation
Validate before calling
// Verify pairTemplate is present when engine is not bare-oxford
static bool ListTemplatesAreComplete(SimpleYamlMapping listMap)
{
if (listMap.TryGetValue("value", out _)) return true;
var engine = listMap.GetScalar("engine");
if (engine == "oxford" && listMap.GetScalar("pairTemplate") is null && listMap.GetScalar("finalTemplate") is null)
return true;
return listMap.GetScalar("pairTemplate") is not null;
} Prevention
- Always provide pairTemplate when using conjunction/delimited/clitic engines.
- Remember that oxford with no templates is the only engine that doesn't require pairTemplate.
- Validate templates in a local test before pushing.
When it happens
Trigger: NormalizeListSurface checks: if value is absent and engine is present and not (oxford with no templates), then pairTemplate must exist. For example 'surfaces:\n list:\n engine: conjunction' with no pairTemplate triggers it.
Common situations: A contributor sets engine to 'conjunction' or 'delimited' but only provides finalTemplate or serialTemplate, forgetting pairTemplate. Or they partially fill in templates after copying an oxford block (which doesn't require them).
Related errors
- Locale '{localeCode}.surfaces.list' must define 'engine'.
- 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/3728789eeecf0b12.
Report an issue: GitHub.