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
- Add the missing unit key (named exactly, e.g. 'millisecond:') under the case's units: mapping.
- If the unit has no distinct form in this case, set it to 'sameAsNominative: true' or 'unsupported: true' instead of authoring a phrase.
- Cross-check the unit list against the locale's nominative block and the TimeUnits array in DurationCaseModels.cs:243.
- 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
- Author new cases by copying a complete nominative block, then editing, so all eight units are present.
- Keep the TimeUnits list (DurationCaseModels.cs:243) open when adding cases.
- Run the locale's tests under tests/Humanizer.Tests/Localisation/<culture> before pushing.
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
- '{path}.sameAsNominative' must be true.
- '{path}.unsupported' must be true.
- Case overlay '{path}' must explicitly define singular and nu
- '{path}' must be a mapping.
- '{path}' defines unsupported property '{key}'. Supported pro
AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13).
Data as JSON: /api/errors/72a2b544ee6128ad.
Report an issue: GitHub.