Humanizr/Humanizer · error · InvalidOperationException

Locale '{locale.LocaleCode}' must classify the durationCases

Error message

Locale '{locale.LocaleCode}' must classify the durationCases surface.

What it means

Thrown by DurationCaseCoverageInput.Create when iterating release locales and a locale's ResolvedFeatures dictionary has no 'durationCases' entry. Every locale shipped in a release must classify how duration units inflect by grammatical case — the generator cannot produce coverage tables without this classification.

Source

Thrown at src/Humanizer.SourceGenerators/Common/DurationCaseModels.cs:132

        public static DurationCaseCoverageInput Create(LocaleCatalogInput localeCatalog)
        {
            if (!localeCatalog.Diagnostics.IsEmpty)
            {
                throw new InvalidOperationException(
                    string.Join(
                        "\n",
                        localeCatalog.Diagnostics
                            .Select(static diagnostic => diagnostic.GetMessage())
                            .OrderBy(static message => message, StringComparer.Ordinal)));
            }

            var catalogs = ImmutableArray.CreateBuilder<DurationCaseCatalog>(localeCatalog.Locales.Length);
            var rows = ImmutableArray.CreateBuilder<DurationCaseCoverageRow>(localeCatalog.Locales.Length);
            foreach (var locale in localeCatalog.Locales)
            {
                if (!locale.ResolvedFeatures.TryGetValue("durationCases", out var value))
                {
                    throw new InvalidOperationException(
                        $"Locale '{locale.LocaleCode}' must classify the durationCases surface.");
                }

                locale.ResolvedFeatures.TryGetValue("phrases", out var phrases);
                var catalog = DurationCaseNormalization.Parse(
                    locale.LocaleCode,
                    value,
                    GetExplicitDurationCaseNumberDetector(locale),
                    phrases);
                catalogs.Add(catalog);
                rows.Add(new DurationCaseCoverageRow(
                    locale.LocaleCode,
                    locale.AuthoredFeatureNames.Contains("durationCases")
                        ? catalog.Classification switch
                        {
                            DurationCaseClassification.Distinct => "distinct",
                            DurationCaseClassification.Invariant => "invariant",
                            DurationCaseClassification.NotApplicable => "not-applicable",

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Add a durationCases surface to the locale file with a classification (e.g. 'durationCases:\n classification: not-applicable' with reason/sources/provenance if it's a release locale).
  2. If the locale is a variant, ensure its variantOf parent has a durationCases classification that resolves down.
  3. For locales where duration cases don't apply, classify as 'not-applicable' with the required reason and provenance contract.

Example fix

# before (no durationCases surface)
surfaces:
  list:
    engine: oxford
# after
surfaces:
  list:
    engine: oxford
  durationCases:
    classification: not-applicable
    reason: "English does not inflect duration units by case."
    sources: []
    provenance: { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every release locale resolves a durationCases feature
static bool AllLocalesHaveDurationCases(LocaleCatalogInput catalog) =>
    catalog.Locales.All(locale => locale.ResolvedFeatures.ContainsKey("durationCases"));

Prevention

When it happens

Trigger: Create loops over localeCatalog.Locales and calls TryGetValue('durationCases'). If it returns false for any locale, this throws. This happens when a locale file omits the durationCases surface entirely and no parent locale provides it via inheritance resolution.

Common situations: A new locale is added without a durationCases surface block and without a variantOf parent that resolves one. Or an existing locale's durationCases block was accidentally deleted during editing. Release builds enforce completeness, so dev/preview locales that skip this will fail the release gate.

Related errors


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