Humanizr/Humanizer · error · NotSupportedException

Culture '{Culture.Name}' does not support grammatical-case d

Error message

Culture '{Culture.Name}' does not support grammatical-case duration phrases.

What it means

Thrown when the culture's case table Classification is LocaleDurationCaseClassification.NotApplicable. This indicates the locale's language does not use grammatical case at all (e.g. English, most Romance languages), so asking for a case-aware duration is a category error. NotSupportedException is thrown because the operation is semantically inapplicable to the locale, not because the arguments are invalid.

Source

Thrown at src/Humanizer/Localisation/Formatters/DefaultFormatter.cs:164

        if ((uint)timeUnit > (uint)TimeUnit.Year)
        {
            throw new ArgumentOutOfRangeException(nameof(timeUnit), timeUnit, "Unsupported time unit.");
        }

        var table = LocaleDurationCaseTableCatalog.Resolve(Culture)
            ?? throw new NotSupportedException(
                $"Culture '{Culture.Name}' has no grammatical-case classification for duration phrases.");

        if (table.Classification == LocaleDurationCaseClassification.Unsupported)
        {
            throw new NotSupportedException(
                $"Culture '{Culture.Name}' has an applicable grammatical case system, but verified duration forms are unavailable.");
        }

        if (table.Classification == LocaleDurationCaseClassification.NotApplicable)
        {
            throw new NotSupportedException(
                $"Culture '{Culture.Name}' does not support grammatical-case duration phrases.");
        }

        if (!table.TryGetCase(grammaticalCase, out var caseOverlay))
        {
            throw new NotSupportedException(
                $"Culture '{Culture.Name}' does not support grammatical case '{grammaticalCase}' for duration phrases.");
        }

        var unitOverlay = caseOverlay.Units[(int)timeUnit];
        return unitOverlay.Kind switch
        {
            LocalizedDurationCaseUnitKind.SameAsNominative => FormatCaseAwareNominative(timeUnit, unit),
            LocalizedDurationCaseUnitKind.NotApplicable => throw new NotSupportedException(
                $"Culture '{Culture.Name}' does not apply grammatical case '{grammaticalCase}' to duration unit '{timeUnit}'."),
            LocalizedDurationCaseUnitKind.Unsupported => throw new NotSupportedException(
                $"Culture '{Culture.Name}' does not support grammatical case '{grammaticalCase}' for duration unit '{timeUnit}'."),
            _ => FormatCaseAwareTimeSpanPhrase(

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Gate the case-aware call on locale: only request a GrammaticalCase for languages that have one, and use the plain overload otherwise.
  2. Catch NotSupportedException and fall back to TimeSpanHumanize(timeUnit, unit).
  3. Maintain a set of case-aware locale prefixes and skip the case path for the rest.

Example fix

// before
var text = caseFormatter.TimeSpanHumanize(TimeUnit.Day, 1, GrammaticalCase.Genitive);

// after
static readonly HashSet<string> CaseAwareLocales = new() { "ru", "pl", "uk", "cs", "sk", "lt", "lv" };
var text = CaseAwareLocales.Contains(formatter.Culture.TwoLetterISOLanguageName)
    ? caseFormatter.TimeSpanHumanize(TimeUnit.Day, 1, GrammaticalCase.Genitive)
    : formatter.TimeSpanHumanize(TimeUnit.Day, 1);
Defensive patterns

Strategy: try-catch

Validate before calling

// The NotApplicable classification is locale-driven. Skip the case path for
// languages with no case system by consulting a locale allow-list.

Type guard

static readonly HashSet<string> CaseAwareLocales = new() { "ru", "uk", "pl", "cs", "sk", "lt", "lv" };
static bool LocaleUsesGrammaticalCase(CultureInfo c) =>
    CaseAwareLocales.Contains(c.TwoLetterISOLanguageName);

Try / catch

try { return caseFormatter.TimeSpanHumanize(unit, count, gCase); }
catch (NotSupportedException) { return formatter.TimeSpanHumanize(unit, count); }

Prevention

When it happens

Trigger: Casting a Western-locale DefaultFormatter to IGrammaticalCaseTimeSpanFormatter and requesting a specific GrammaticalCase; programmatically selecting a case for a locale whose language has no case system; generic code that always requests Genitive regardless of locale.

Common situations: Generic duration-formatting code that uniformly applies a GrammaticalCase to every locale; locale-agnostic UI that assumes case applies everywhere; mixing case-aware Slavic formatting with non-case Western locales in the same pipeline.

Related errors


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