Humanizr/Humanizer · error · InvalidOperationException

'{path}.units' must explicitly define '{unitName}'.

Error message

'{path}.units' must explicitly define '{unitName}'.

What it means

Thrown by the source generator's DurationCaseNormalization.ParseCase while parsing a duration-case overlay (e.g. the 'genitive' case) in a locale's durationCases YAML. The generator iterates the fixed TimeUnits list (millisecond, second, minute, hour, day, week, month, year) and requires every unit to appear under units:, so emitted code never references a missing case form. The {path} is '<locale>.durationCases.cases.<case>.units' and {unitName} is the missing unit key.

Source

Thrown at src/Humanizer.SourceGenerators/Common/DurationCaseModels.cs:1137

        static DurationCaseOverlay ParseCase(SimpleYamlValue value, string path)
        {
            var mapping = ExpectMapping(value, path);
            RejectUnknownKeys(mapping, path, ["units"]);

            if (!mapping.TryGetValue("units", out var unitsValue))
            {
                throw new InvalidOperationException($"'{path}' must define all duration 'units'.");
            }

            var unitsMapping = ExpectMapping(unitsValue, $"{path}.units");
            RejectUnknownKeys(unitsMapping, $"{path}.units", TimeUnits);
            var units = ImmutableDictionary.CreateBuilder<string, DurationCaseUnit>(StringComparer.Ordinal);
            foreach (var unitName in TimeUnits)
            {
                if (!unitsMapping.TryGetValue(unitName, out var unitValue))
                {
                    throw new InvalidOperationException(
                        $"'{path}.units' must explicitly define '{unitName}'.");
                }

                units[unitName] = ParseUnit(unitValue, $"{path}.units.{unitName}");
            }

            return new DurationCaseOverlay(units.ToImmutable());
        }

        static DurationCaseUnit ParseUnit(SimpleYamlValue value, string path)
        {
            if (ContainsInheritanceMarker(value))
            {
                throw new InvalidOperationException(
                    $"'{path}' contains an unresolved CLDR inheritance marker.");
            }

            var mapping = ExpectMapping(value, path);

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Add the missing unit key (named exactly, e.g. 'millisecond:') under the case's units: mapping.
  2. If the unit has no distinct form in this case, set it to 'sameAsNominative: true' or 'unsupported: true' instead of authoring a phrase.
  3. Cross-check the unit list against the locale's nominative block and the TimeUnits array in DurationCaseModels.cs:243.
  4. Rebuild and run the locale's tests under tests/Humanizer.Tests/Localisation/<culture>.

Example fix

# before
cases:
  genitive:
    units:
      second:
        phrase: { ... }
      minute:
        phrase: { ... }
# after - millisecond added
cases:
  genitive:
    units:
      millisecond:
        sameAsNominative: true
      second:
        phrase: { ... }
      minute:
        phrase: { ... }
Defensive patterns

Strategy: validation

Validate before calling

# Pre-build check over a parsed duration-case overlay (PowerShell + YamlDotNet pattern)
$required = 'millisecond','second','minute','hour','day','week','month','year'
$caseUnits = $parsed.durationCases.cases.$case.units   # the mapping under test
$missing = $required | Where-Object { -not $caseUnits.ContainsKey($_) }
if ($missing) { throw "case '$case' missing units: $($missing -join ', ')" }

Prevention

When it happens

Trigger: A durationCases.cases.<case>.units mapping omits one of the eight fixed time-unit keys (most commonly 'millisecond' or 'year'). ParseCase (DurationCaseModels.cs:1133-1139) fails the TryGetValue for that unitName.

Common situations: A contributor adds a new grammatical case to a Slavic locale and forgets the rarely-used 'millisecond' unit; partial copy-paste from another locale; renaming a unit key while the generator still expects the canonical name.

Related errors


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