Humanizr/Humanizer · error · InvalidOperationException
Inflection {subject} contains a duplicate after normalizatio
Error message
Inflection {subject} contains a duplicate after normalization. What it means
After NFC normalization and optional simple-lowercasing, all values within a single authored-text collection (e.g., accepted forms, guards) must be distinct. The validator builds a HashSet and rejects any value whose normalized form was already seen, preventing the generated catalog from carrying redundant entries that would collapse at runtime.
Source
Thrown at src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/InflectionCatalogValidation.cs:205
ImmutableArray<string> ownerScripts,
string subject,
bool allowStemPlaceholder,
bool allowNonLetters)
{
var normalized = ImmutableArray.CreateBuilder<string>(values.Length);
var seen = new HashSet<string>(StringComparer.Ordinal);
foreach (var value in values)
{
var form = NormalizeAuthoredText(
value,
casing,
ownerScripts,
subject,
allowStemPlaceholder,
allowNonLetters);
if (!seen.Add(form))
{
throw new InvalidOperationException(
$"Inflection {subject} contains a duplicate after normalization.");
}
normalized.Add(form);
}
return normalized.ToImmutable();
}
static string NormalizeAuthoredText(
string value,
string casing,
ImmutableArray<string> ownerScripts,
string subject,
bool allowStemPlaceholder,
bool allowNonLetters)
{
string normalized;View on GitHub (pinned to ffc2b77c0f)
Solutions
- Remove the duplicate entry so each normalized form appears exactly once.
- If both forms are genuinely needed, verify the casing mode is set to 'lower-title-upper' to keep them distinct only when they differ after lowercasing.
- Check for invisible Unicode differences (combining marks, zero-width characters) that cause unexpected normalization collisions.
Example fix
# before accepted: - 'café' # NFC precomposed - 'cafe\u0301' # decomposed, same after NFC # after accepted: - 'café'
Defensive patterns
Strategy: validation
Validate before calling
# Before building, check for post-normalization duplicates in accepted/guard lists.
python3 -c "
import yaml, sys, unicodedata
for f in sys.argv[1:]:
d = yaml.safe_load(open(f))
owners = (d.get('inflection',{}).get('owners') or [])
for owner in owners:
casing = owner.get('casing','')
for lex in (owner.get('lexemes') or []):
for field in ('singular','dictionaryPlural'):
vals = ((lex.get(field) or {}).get('accepted') or [])
seen = set()
for v in vals:
n = unicodedata.normalize('NFC', v)
if casing == 'lower-title-upper': n = n.lower()
if n in seen:
print(f'{f}: duplicate after normalization in {field}: {v!r}')
seen.add(n)
" src/Humanizer/Locales/*.yml Prevention
- Normalize all YAML text to NFC before committing — use a pre-commit hook or editor setting.
- Avoid listing both precomposed and decomposed forms of the same character.
- Run a Unicode normalization diff check when adding alternative spellings.
When it happens
Trigger: Two values in the same list normalize to the same string after Unicode FormC normalization and case folding. For example, listing both a precomposed and decomposed form of the same character, or both 'cafe' and 'café' if they normalize identically in the configured casing mode.
Common situations: Adding alternative spellings that turn out to be Unicode-equivalent. Copy-pasting a form with different byte representations but identical normalization. Listing both title-case and lower-case variants under a case-folding casing mode.
Related errors
- Inflection {subject} contains invalid Unicode.
- Inflection {subject} has an unsupported casing expansion.
- Inflection {subject} contains text outside its declared scri
- Productive evidence '{direction}' requires at least 100 atte
- {subject} references unknown source '{source}'.
AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13).
Data as JSON: /api/errors/4ad90a0bccece853.
Report an issue: GitHub.