Humanizr/Humanizer · error · InvalidOperationException
Canonical list templates must use '{0}' and '{1}' as an infi
Error message
Canonical list templates must use '{0}' and '{1}' as an infix template. What it means
Thrown by ExtractTemplateSeparator when a canonical list template (pairTemplate, finalTemplate, or serialTemplate) does not start with '{0}' and end with '{1}'. The generator extracts the infix separator between these two placeholders to produce the legacy 'value' string. Templates that don't follow this exact infix shape cannot be normalized.
Source
Thrown at src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs:629
values["value"] = new SimpleYamlScalar(ExtractTemplateSeparator(finalTemplate), isQuoted: true);
}
return new SimpleYamlMapping(values.ToImmutable());
}
static bool ReadBoolean(SimpleYamlMapping mapping, string key) =>
mapping.TryGetValue(key, out var value) &&
value is SimpleYamlScalar scalar &&
bool.TryParse(scalar.Value, out var result) &&
result;
static string ExtractTemplateSeparator(string template)
{
const string start = "{0}";
const string end = "{1}";
return !template.StartsWith(start, StringComparison.Ordinal) ||
!template.EndsWith(end, StringComparison.Ordinal)
? throw new InvalidOperationException("Canonical list templates must use '{0}' and '{1}' as an infix template.")
: template.Substring(start.Length, template.Length - start.Length - end.Length).Trim();
}
}
public static class LegacyLocaleMigration
{
static readonly string[] LegacyTopLevelNames =
[
"inherits",
"collectionFormatter",
"dateOnlyToOrdinalWords",
"dateToOrdinalWords",
"durationCases",
"formatter",
"grammar",
"headings",
"numberToWords",
"ordinalizer",View on GitHub (pinned to ffc2b77c0f)
Solutions
- Rewrite the template as an infix: start with '{0}', end with '{1}', put the separator between them, e.g. 'pairTemplate: "{0}, {1}"'.
- If you only want a bare separator with no special final/serial behavior, use the legacy 'value' scalar instead of templates.
- Verify that finalTemplate and serialTemplate (if present) also follow the same {0}...{1} shape.
Example fix
# before
surfaces:
list:
engine: conjunction
pairTemplate: ", "
# after
surfaces:
list:
engine: conjunction
pairTemplate: "{0}, {1}" Defensive patterns
Strategy: validation
Validate before calling
// Ensure list templates use {0}...{1} infix shape
static bool TemplateIsInfix(string template) =>
template.StartsWith("{0}", StringComparison.Ordinal) &&
template.EndsWith("{1}", StringComparison.Ordinal) &&
template.Length >= "{0}{1}".Length; Prevention
- All list templates must start with {0} and end with {1} with the separator between them.
- Use 'value' (bare separator) instead of templates for simple delimited output.
- Test template strings with a quick regex: ^\\{0\}.*\\{1\}$
When it happens
Trigger: NormalizeListSurface calls ExtractTemplateSeparator on finalTemplate (which defaults to pairTemplate if finalTemplate is absent). If the template string lacks the {0}...{1} wrapper — e.g. 'pairTemplate: ", "' or 'pairTemplate: "{0}{1}"' — StartsWith/EndsWith fails and the throw expression fires.
Common situations: A contributor writes a plain separator string (e.g. ', ') instead of a full template, or puts the separator outside the placeholders. Also occurs when copy-pasting a template from documentation that omits the placeholders.
Related errors
- Locale '{localeCode}.surfaces.list' must define 'engine'.
- Locale '{localeCode}.surfaces.list' must define canonical li
- Template '{path}' must define a scalar 'value'.
- Template '{path}' cannot use numeric placeholder '{{placehol
- Template '{path}' uses unsupported placeholder '{{placeholde
AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13).
Data as JSON: /api/errors/255ba54cc131c998.
Report an issue: GitHub.