{"record":{"id":"c844bdd337c5ad86","repo":"Humanizr/Humanizer","slug":"culture-culture-name-has-no-grammatical-case-c","errorCode":null,"errorMessage":"Culture '{Culture.Name}' has no grammatical-case classification for duration phrases.","messagePattern":"Culture '(.+?)' has no grammatical-case classification for duration phrases\\.","errorType":"exception","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"src/Humanizer/Localisation/Formatters/DefaultFormatter.cs","lineNumber":153,"sourceCode":"    {\n        if (GetType().Assembly != typeof(DefaultFormatter).Assembly)\n        {\n            throw new NotSupportedException(\n                $\"Custom formatter type '{GetType().FullName}' must explicitly implement {nameof(IGrammaticalCaseTimeSpanFormatter)} to support grammatical-case-aware durations.\");\n        }\n\n        if ((uint)grammaticalCase > (uint)GrammaticalCase.Causal)\n        {\n            throw new ArgumentOutOfRangeException(nameof(grammaticalCase), grammaticalCase, \"Unsupported grammatical case.\");\n        }\n\n        if ((uint)timeUnit > (uint)TimeUnit.Year)\n        {\n            throw new ArgumentOutOfRangeException(nameof(timeUnit), timeUnit, \"Unsupported time unit.\");\n        }\n\n        var table = LocaleDurationCaseTableCatalog.Resolve(Culture)\n            ?? throw new NotSupportedException(\n                $\"Culture '{Culture.Name}' has no grammatical-case classification for duration phrases.\");\n\n        if (table.Classification == LocaleDurationCaseClassification.Unsupported)\n        {\n            throw new NotSupportedException(\n                $\"Culture '{Culture.Name}' has an applicable grammatical case system, but verified duration forms are unavailable.\");\n        }\n\n        if (table.Classification == LocaleDurationCaseClassification.NotApplicable)\n        {\n            throw new NotSupportedException(\n                $\"Culture '{Culture.Name}' does not support grammatical-case duration phrases.\");\n        }\n\n        if (!table.TryGetCase(grammaticalCase, out var caseOverlay))\n        {\n            throw new NotSupportedException(\n                $\"Culture '{Culture.Name}' does not support grammatical case '{grammaticalCase}' for duration phrases.\");","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer/Localisation/Formatters/DefaultFormatter.cs#L135-L171","documentation":"Thrown when LocaleDurationCaseTableCatalog.Resolve(Culture) returns null for the formatter's culture, meaning Humanizer has no grammatical-case classification data at all for that locale. This is a data-coverage gap: the case-aware duration path requires a registered case table, and none exists for the culture. It is a NotSupportedException, not an argument error, because the inputs are valid but the locale is unsupported.","triggerScenarios":"Constructing a DefaultFormatter for a culture without grammatical-case data and calling IGrammaticalCaseTimeSpanFormatter.TimeSpanHumanize; routing a locale that has no Slavic/Baltic-style case system through the case-aware API. The error is culture-driven, so reproducing it requires a specific Culture on the formatter.","commonSituations":"Calling case-aware duration formatting for Western locales (en, fr, de) where grammatical case does not apply; a request locale that resolves to a culture Humanizer has not equipped with case tables; assuming all DefaultFormatter cultures support case-aware durations.","solutions":["Only call the case-aware (IGrammaticalCaseTimeSpanFormatter) path for locales known to ship case tables (typically Slavic/Baltic languages); otherwise use the standard TimeSpanHumanize overloads.","Wrap case-aware calls in try/catch (NotSupportedException) and fall back to the nominative TimeSpanHumanize(timeUnit, unit).","If you need case-aware output for an unsupported locale, contribute the locale's case data to Humanizer rather than forcing the API."],"exampleFix":"// before\nvar caseFormatter = (IGrammaticalCaseTimeSpanFormatter)formatter;\nvar text = caseFormatter.TimeSpanHumanize(TimeUnit.Day, 2, GrammaticalCase.Genitive); // throws for en\n\n// after\nstring text;\ntry\n{\n    var caseFormatter = (IGrammaticalCaseTimeSpanFormatter)formatter;\n    text = caseFormatter.TimeSpanHumanize(TimeUnit.Day, 2, GrammaticalCase.Genitive);\n}\ncatch (NotSupportedException)\n{\n    text = formatter.TimeSpanHumanize(TimeUnit.Day, 2); // nominative fallback\n}","handlingStrategy":"try-catch","validationCode":"// No public API exposes the catalog; the robust guard is to attempt the call\n// and fall back. If you control the locale set, restrict case-aware calls to\n// known case-bearing languages before invoking.","typeGuard":"static readonly HashSet<string> CaseAwareLocales = new() { \"ru\", \"uk\", \"pl\", \"cs\", \"sk\", \"lt\", \"lv\" };\nstatic bool CultureHasCaseTables(CultureInfo c) =>\n    CaseAwareLocales.Contains(c.TwoLetterISOLanguageName);","tryCatchPattern":"try { return caseFormatter.TimeSpanHumanize(unit, count, gCase); }\ncatch (NotSupportedException) { return formatter.TimeSpanHumanize(unit, count); }","preventionTips":["Gate case-aware calls on a known set of case-bearing locales.","Always provide a nominative fallback when locale data may be missing.","Do not assume every DefaultFormatter culture supports case-aware durations."],"tags":["localization","grammatical-case","formatter","not-supported","culture"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}