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

  1. Remove the duplicate entry so each normalized form appears exactly once.
  2. 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.
  3. 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

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


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