Humanizr/Humanizer · error · InvalidOperationException

Locale '{localeCode}' must author durationCases instead of i

Error message

Locale '{localeCode}' must author durationCases instead of inheriting them from language or script-incompatible locale '{inheritedLocaleCode}'.

What it means

durationCases encode language- and script-specific duration grammar forms that do not transfer across language boundaries. The generator refuses to silently inherit durationCases from an ancestor whose language subtag differs or whose script is incompatible, forcing the locale author to explicitly author correct forms for their language.

Source

Thrown at src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs:632

            ImmutableDictionary<string, SimpleYamlValue> inheritedFeatures)
        {
            _ = features.TryGetValue(featureName, out var localValue);
            _ = inheritedFeatures.TryGetValue(featureName, out var inheritedValue);
            if (featureName == "inflection" &&
                inheritedLocaleCode is not null &&
                !SharesExactLanguageSubtag(localeCode, inheritedLocaleCode))
            {
                inheritedValue = null;
            }

            if (featureName == "durationCases" &&
                localValue is null &&
                inheritedValue is not null &&
                inheritedLocaleCode is not null &&
                (!SharesExactLanguageSubtag(localeCode, inheritedLocaleCode) ||
                 !HasCompatibleScript(localeCode, inheritedLocaleCode)))
            {
                throw new InvalidOperationException(
                    $"Locale '{localeCode}' must author durationCases instead of inheriting them from language or script-incompatible locale '{inheritedLocaleCode}'.");
            }

            return MergeFeatureValue(localeCode, featureName, inheritedValue, localValue);
        }

        static bool SharesExactLanguageSubtag(string localeCode, string inheritedLocaleCode) =>
            string.Equals(
                GetExactLanguageSubtag(localeCode),
                GetExactLanguageSubtag(inheritedLocaleCode),
                StringComparison.OrdinalIgnoreCase);

        static string GetExactLanguageSubtag(string localeCode)
        {
            var separatorIndex = localeCode.IndexOf('-');
            return separatorIndex >= 0
                ? localeCode.Substring(0, separatorIndex)
                : localeCode;

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Author a 'durationCases:' block directly in the locale's own YAML file, duplicating from a same-language locale and adapting the forms.
  2. Change the 'inherits:' target to a locale sharing the same language subtag and a compatible script.
  3. If the locale genuinely shares the language, verify the script subtags match (both absent, or both identical, or both Han).

Example fix

# before — de-CH.yml
inherits: fr
# (no durationCases block)

# after — de-CH.yml
inherits: de
durationCases:
  # ... German-specific forms authored here
Defensive patterns

Strategy: validation

Validate before calling

# Before building, verify every locale either authors durationCases locally
# or inherits from a same-language, script-compatible parent.
import pathlib, re

def lang_subtag(code):
    return code.split('-')[0].lower()

def script_subtag(code):
    parts = code.split('-')
    return parts[1] if len(parts) > 1 and len(parts[1]) == 4 else None

locales_dir = pathlib.Path('src/Humanizer/Locales')
for f in locales_dir.rglob('*.yml'):
    text = f.read_text()
    code = f.stem
    has_local = 'durationCases:' in text
    m = re.search(r'^inherits:\s*(\S+)', text, re.MULTILINE)
    parent = m.group(1) if m else None
    if not has_local and parent:
        if lang_subtag(code) != lang_subtag(parent):
            print(f'{code}: must author durationCases (different language from {parent})')
        elif script_subtag(code) and script_subtag(parent) and script_subtag(code) != script_subtag(parent):
            print(f'{code}: must author durationCases (incompatible script with {parent})')

Prevention

When it happens

Trigger: A locale omits a local 'durationCases:' block AND its resolved ancestor has a different exact language subtag (e.g. 'de-CH' inheriting from 'fr') or an incompatible script subtag (e.g. 'zh-Hant' inheriting from 'zh-Hans').

Common situations: Creating a regional variant that inherits from a base locale of a different language; duplicating a locale file and changing only the 'inherits:' target without re-authoring duration grammar; merging locale data from an unrelated language family.

Related errors


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