{"record":{"id":"4d380634261d5c11","repo":"Humanizr/Humanizer","slug":"locale-localecode-must-author-durationcases-in","errorCode":null,"errorMessage":"Locale '{localeCode}' must author durationCases instead of inheriting them from language or script-incompatible locale '{inheritedLocaleCode}'.","messagePattern":"Locale '(.+?)' must author durationCases instead of inheriting them from language or script-incompatible locale '(.+?)'\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs","lineNumber":632,"sourceCode":"            ImmutableDictionary<string, SimpleYamlValue> inheritedFeatures)\n        {\n            _ = features.TryGetValue(featureName, out var localValue);\n            _ = inheritedFeatures.TryGetValue(featureName, out var inheritedValue);\n            if (featureName == \"inflection\" &&\n                inheritedLocaleCode is not null &&\n                !SharesExactLanguageSubtag(localeCode, inheritedLocaleCode))\n            {\n                inheritedValue = null;\n            }\n\n            if (featureName == \"durationCases\" &&\n                localValue is null &&\n                inheritedValue is not null &&\n                inheritedLocaleCode is not null &&\n                (!SharesExactLanguageSubtag(localeCode, inheritedLocaleCode) ||\n                 !HasCompatibleScript(localeCode, inheritedLocaleCode)))\n            {\n                throw new InvalidOperationException(\n                    $\"Locale '{localeCode}' must author durationCases instead of inheriting them from language or script-incompatible locale '{inheritedLocaleCode}'.\");\n            }\n\n            return MergeFeatureValue(localeCode, featureName, inheritedValue, localValue);\n        }\n\n        static bool SharesExactLanguageSubtag(string localeCode, string inheritedLocaleCode) =>\n            string.Equals(\n                GetExactLanguageSubtag(localeCode),\n                GetExactLanguageSubtag(inheritedLocaleCode),\n                StringComparison.OrdinalIgnoreCase);\n\n        static string GetExactLanguageSubtag(string localeCode)\n        {\n            var separatorIndex = localeCode.IndexOf('-');\n            return separatorIndex >= 0\n                ? localeCode.Substring(0, separatorIndex)\n                : localeCode;","sourceCodeStart":614,"sourceCodeEnd":650,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs#L614-L650","documentation":"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.","triggerScenarios":"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').","commonSituations":"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.","solutions":["Author a 'durationCases:' block directly in the locale's own YAML file, duplicating from a same-language locale and adapting the forms.","Change the 'inherits:' target to a locale sharing the same language subtag and a compatible script.","If the locale genuinely shares the language, verify the script subtags match (both absent, or both identical, or both Han)."],"exampleFix":"# before — de-CH.yml\ninherits: fr\n# (no durationCases block)\n\n# after — de-CH.yml\ninherits: de\ndurationCases:\n  # ... German-specific forms authored here","handlingStrategy":"validation","validationCode":"# Before building, verify every locale either authors durationCases locally\n# or inherits from a same-language, script-compatible parent.\nimport pathlib, re\n\ndef lang_subtag(code):\n    return code.split('-')[0].lower()\n\ndef script_subtag(code):\n    parts = code.split('-')\n    return parts[1] if len(parts) > 1 and len(parts[1]) == 4 else None\n\nlocales_dir = pathlib.Path('src/Humanizer/Locales')\nfor f in locales_dir.rglob('*.yml'):\n    text = f.read_text()\n    code = f.stem\n    has_local = 'durationCases:' in text\n    m = re.search(r'^inherits:\\s*(\\S+)', text, re.MULTILINE)\n    parent = m.group(1) if m else None\n    if not has_local and parent:\n        if lang_subtag(code) != lang_subtag(parent):\n            print(f'{code}: must author durationCases (different language from {parent})')\n        elif script_subtag(code) and script_subtag(parent) and script_subtag(code) != script_subtag(parent):\n            print(f'{code}: must author durationCases (incompatible script with {parent})')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["When creating a locale that inherits across a language boundary, always author durationCases explicitly.","Keep a reference locale with the same language nearby to copy duration grammar from.","Add a CI lint that flags durationCases inheritance across differing language subtags."],"tags":["locale","yaml","source-generator","duration","inheritance"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}