Humanizr/Humanizer · error · InvalidOperationException
Locale '{document.LocaleCode}.surfaces.{surface.Key}' must b
Error message
Locale '{document.LocaleCode}.surfaces.{surface.Key}' must be a mapping. What it means
ToLocaleDefinition re-checks that each surface value is a mapping before dispatching on its name (CanonicalLocaleAuthoring.cs:139-145). The Parse path already enforces this, so reaching this throw means a CanonicalLocaleDocument was constructed or mutated outside Parse (e.g. legacy migration, a test fixture, or code that built the document by hand).
Source
Thrown at src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs:143
return new CanonicalLocaleDocument(
localeCode,
variantOf,
surfaces,
NormalizeCanonicalText(fileText));
}
internal static string Emit(CanonicalLocaleDocument document) => document.CanonicalText;
internal static LocaleDefinition ToLocaleDefinition(CanonicalLocaleDocument document)
{
var features = ImmutableDictionary.CreateBuilder<string, SimpleYamlValue>(StringComparer.Ordinal);
foreach (var surface in document.Surfaces.Values)
{
if (surface.Value is not SimpleYamlMapping surfaceMapping)
{
throw new InvalidOperationException(
$"Locale '{document.LocaleCode}.surfaces.{surface.Key}' must be a mapping.");
}
switch (surface.Key)
{
case "list":
features["collectionFormatter"] = NormalizeListSurface(document.LocaleCode, surfaceMapping);
break;
case "formatter":
features["formatter"] = surfaceMapping;
var grammar = ImmutableDictionary.CreateBuilder<string, SimpleYamlValue>(StringComparer.Ordinal);
foreach (var propertyName in FormatterGrammarPropertyNames.Where(surfaceMapping.Values.ContainsKey))
{
grammar[propertyName] = surfaceMapping.Values[propertyName];
}
if (grammar.Count > 0)View on GitHub (pinned to ffc2b77c0f)
Solutions
- Always obtain documents via CanonicalLocaleAuthoring.Parse so the surface-shape invariant holds.
- If constructing programmatically, ensure every value in the surfaces mapping is a SimpleYamlMapping.
- Reproduce via Parse to surface the earlier, more specific error and fix it there.
Example fix
// before (test/migration builds a document with a scalar surface)
var doc = new CanonicalLocaleDocument("en", null, surfacesWithScalarClock, text);
ToLocaleDefinition(doc); // throws
// after
var doc = CanonicalLocaleAuthoring.Parse("en", canonicalYaml); // surfaces.clock is a mapping Defensive patterns
Strategy: type-guard
Validate before calling
// Before calling ToLocaleDefinition, ensure every surface is a mapping
static bool AllSurfacesAreMappings(CanonicalLocaleDocument d)
=> d.Surfaces.Values.All(kv => kv.Value is SimpleYamlMapping); Type guard
static bool IsValidLocaleDocument(CanonicalLocaleDocument d) =>
d.Surfaces.Values.All(kv => kv.Value is SimpleYamlMapping); Prevention
- Treat Parse as the only constructor of CanonicalLocaleDocument.
- If you must build one by hand, assert all surfaces are SimpleYamlMapping first.
- Prefer fixing the input YAML over special-casing the conversion path.
When it happens
Trigger: A code path builds a CanonicalLocaleDocument directly with a non-mapping surface value and passes it to ToLocaleDefinition; or the migration helper emits a malformed surface.
Common situations: Writing a unit test that constructs CanonicalLocaleDocument manually; extending the migration tooling and producing a sequence/scalar surface.
Related errors
- Locale '{localeCode}' defines unsupported top-level property
- Locale '{localeCode}' must define required top-level propert
- Locale '{declaredLocale}' must match file locale '{localeCod
- Locale '{localeCode}' must define required top-level propert
- Locale '{localeCode}.surfaces' must be a mapping.
AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13).
Data as JSON: /api/errors/432cf8c04b9451cc.
Report an issue: GitHub.