Humanizr/Humanizer · error · InvalidOperationException

Locale '{localeCode}' defines unsupported top-level property

Error message

Locale '{localeCode}' defines unsupported top-level property '{property}'. Supported properties: {string.Join(", ", SupportedTopLevelNames)}.

What it means

During the source-generator build, CanonicalLocaleAuthoring.Parse rejects any top-level YAML key not in {locale, variantOf, surfaces} (CanonicalLocaleAuthoring.cs:70-75). The canonical schema is intentionally tiny; legacy flat keys (inherits, collectionFormatter, etc.) are rejected here and must be migrated first.

Source

Thrown at src/Humanizer.SourceGenerators/Common/CanonicalLocaleAuthoring.cs:72

        static readonly string[] FormatterGrammarPropertyNames =
        [
            "pluralRule",
            "casePluralRule",
            "dataUnitPluralRule",
            "dataUnitNonIntegralForm",
            "prepositionMode",
            "secondaryPlaceholderMode",
            "timeUnitGenders"
        ];

        internal static CanonicalLocaleDocument Parse(string localeCode, string fileText)
        {
            var root = SimpleYamlParser.Parse(fileText);

            foreach (var property in root.Values.Keys.Where(static property => !SupportedTopLevelNames.Contains(property, StringComparer.Ordinal)))
            {
                throw new InvalidOperationException(
                    $"Locale '{localeCode}' defines unsupported top-level property '{property}'. " +
                    $"Supported properties: {string.Join(", ", SupportedTopLevelNames)}.");
            }

            var declaredLocale = root.GetScalar("locale")
                ?? throw new InvalidOperationException(
                    $"Locale '{localeCode}' must define required top-level property 'locale'.");

            if (!string.Equals(localeCode, declaredLocale, StringComparison.Ordinal))
            {
                throw new InvalidOperationException(
                    $"Locale '{declaredLocale}' must match file locale '{localeCode}'.");
            }

            var variantOf = root.GetScalar("variantOf");

            SimpleYamlMapping surfaces;
            if (!root.TryGetValue("surfaces", out var surfacesValue))

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Read the message: it names the offending property and lists allowed names.
  2. If the property is legacy (inherits, formatter, numberToWords...), run LegacyLocaleMigration.ConvertToCanonicalYaml to migrate it, or move it under the correct surface.
  3. Fix typos: surface -> surfaces.
  4. Remove any genuinely unwanted top-level key.

Example fix

# before
locale: "fr"
inherit: "en"
surfaces:
  clock: {}
# after
locale: "fr"
variantOf: "en"
surfaces:
  clock: {}
Defensive patterns

Strategy: validation

Validate before calling

# Quick YAML lint: allowed top-level keys only
import yaml, sys
ALLOWED = {'locale','variantOf','surfaces'}
doc = yaml.safe_load(open(sys.argv[1], encoding='utf-8'))
bad = [k for k in doc if k not in ALLOWED]
assert not bad, f'unsupported top-level keys: {bad}'

Prevention

When it happens

Trigger: A locale YAML file under src/Humanizer/Locales contains a key outside locale/variantOf/surfaces — e.g. a typo (surface:), a leftover legacy key (inherits:, formatter: at top level), or a copied block pasted at the wrong indentation.

Common situations: Copying an old pre-canonical locale file; typoing 'surfaces' as 'surface'; merging a PR that uses a renamed property.

Related errors


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