Humanizr/Humanizer · error · InvalidOperationException

Phrase '{path}' must define 'numeric', 'text', or 'words'.

Error message

Phrase '{path}' must define 'numeric', 'text', or 'words'.

What it means

Thrown by ParseSinglePhraseWithWordsVariant when a duration phrase's 'single' entry is a mapping but defines none of 'numeric', 'text', or 'words'. The single form must carry at least one of these so the generator can render the count-1 case. Surfaced as compiler diagnostic HSG003 (severity Error).

Source

Thrown at src/Humanizer.SourceGenerators/Common/LocalePhraseNormalization.cs:333

                namedTemplate);
        }

        static (string? Single, string? WordsVariant) ParseSinglePhraseWithWordsVariant(SimpleYamlValue value, string path)
        {
            if (value is SimpleYamlScalar scalar)
            {
                return (ValidateLiteralText(path, scalar.Value), null);
            }

            var mapping = ExpectMapping(value, path);
            RejectUnknownKeys(mapping, path, ["numeric", "text", "words"]);

            var single = GetOptionalLiteral(mapping, "numeric", $"{path}.numeric") ??
                GetOptionalLiteral(mapping, "text", $"{path}.text");
            var wordsVariant = GetOptionalLiteral(mapping, "words", $"{path}.words");
            if (single is null && wordsVariant is null)
            {
                throw new InvalidOperationException($"Phrase '{path}' must define 'numeric', 'text', or 'words'.");
            }

            return (single, wordsVariant);
        }

        static PhraseForms? ParseOptionalPhraseForms(
            SimpleYamlMapping mapping,
            string path,
            params string[] allowedPlaceholders) =>
            ParseOptionalPhraseForms(mapping, path, preserveDuplicateForms: false, allowedPlaceholders);

        static PhraseForms? ParseOptionalPhraseForms(
            SimpleYamlMapping mapping,
            string path,
            bool preserveDuplicateForms,
            params string[] allowedPlaceholders)
        {
            if (mapping.TryGetValue("forms", out var formsValue))

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Provide 'numeric' (digit form, e.g. '1 hour'), 'text' (spelled-out form), and/or 'words' (words-only variant) under the single mapping.
  2. If no distinction is needed, replace the mapping with a scalar string for single.
  3. Remove the single key to inherit or omit it as the locale design allows.
  4. Rebuild to confirm the diagnostic clears.

Example fix

# before
duration:
  hour:
    single: {}
# after
duration:
  hour:
    single:
      numeric: 1 hour
      words: one hour
Defensive patterns

Strategy: validation

Validate before calling

# A 'single' mapping under a duration unit must define numeric/text/words.
#   yq -o=json '.phrases.duration | to_entries[] | select(.value.single | tag == "!!map") | select((.value.single.numeric // .value.single.text // .value.single.words) == null) | .key' src/Humanizer/Locales/<code>.yml

Prevention

When it happens

Trigger: Authoring phrases.duration.<unit>.single as a mapping with only unknown keys, or with all three of numeric/text/words set to unquoted null.

Common situations: Author writes a single: {} placeholder intending to come back to it; copies a mapping from another unit and removes the value lines; sets 'numeric:' with no value.

Related errors


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