Humanizr/Humanizer · error · NotSupportedException

Culture '{Culture.Name}' has no grammatical-case classificat

Error message

Culture '{Culture.Name}' has no grammatical-case classification for duration phrases.

What it means

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.

Source

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

    {
        if (GetType().Assembly != typeof(DefaultFormatter).Assembly)
        {
            throw new NotSupportedException(
                $"Custom formatter type '{GetType().FullName}' must explicitly implement {nameof(IGrammaticalCaseTimeSpanFormatter)} to support grammatical-case-aware durations.");
        }

        if ((uint)grammaticalCase > (uint)GrammaticalCase.Causal)
        {
            throw new ArgumentOutOfRangeException(nameof(grammaticalCase), grammaticalCase, "Unsupported grammatical case.");
        }

        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.");

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Only call the case-aware (IGrammaticalCaseTimeSpanFormatter) path for locales known to ship case tables (typically Slavic/Baltic languages); otherwise use the standard TimeSpanHumanize overloads.
  2. Wrap case-aware calls in try/catch (NotSupportedException) and fall back to the nominative TimeSpanHumanize(timeUnit, unit).
  3. If you need case-aware output for an unsupported locale, contribute the locale's case data to Humanizer rather than forcing the API.

Example fix

// before
var caseFormatter = (IGrammaticalCaseTimeSpanFormatter)formatter;
var text = caseFormatter.TimeSpanHumanize(TimeUnit.Day, 2, GrammaticalCase.Genitive); // throws for en

// after
string text;
try
{
    var caseFormatter = (IGrammaticalCaseTimeSpanFormatter)formatter;
    text = caseFormatter.TimeSpanHumanize(TimeUnit.Day, 2, GrammaticalCase.Genitive);
}
catch (NotSupportedException)
{
    text = formatter.TimeSpanHumanize(TimeUnit.Day, 2); // nominative fallback
}
Defensive patterns

Strategy: try-catch

Validate before calling

// No public API exposes the catalog; the robust guard is to attempt the call
// and fall back. If you control the locale set, restrict case-aware calls to
// known case-bearing languages before invoking.

Type guard

static readonly HashSet<string> CaseAwareLocales = new() { "ru", "uk", "pl", "cs", "sk", "lt", "lv" };
static bool CultureHasCaseTables(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: 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.

Common situations: 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.

Related errors


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