Humanizr/Humanizer · error · InvalidOperationException

Phrase ' ' must define forms, 'symbol', or 'template'.

Error message

Phrase '{path}' must define forms, 'symbol', or 'template'.

What it means

Thrown by the Humanizer source generator while parsing a locale's phrases.dataUnits.<unit> entry in src/Humanizer/Locales/*.yml. A data-unit phrase mapping must provide at least one of 'forms', 'symbol', or 'template'; if all three are absent the generator cannot emit a usable phrase and aborts that locale. The exception is caught in LocaleYamlCatalog and surfaced as compiler diagnostic HSG003 'Invalid locale definition' (severity Error) at build time.

Solutions

  1. Open the locale YAML at the path in the message and add at least one of 'forms' (default/singular/plural/...), 'symbol', or 'template' under the offending dataUnits.<unit> entry.
  2. If the unit should be a plain string, replace the mapping with a scalar value (e.g. 'byte: Bytes').
  3. If the entry is unwanted, delete the unit key from dataUnits entirely.
  4. Rebuild; the HSG003 diagnostic for that locale disappears when the phrase is well-formed.

Example fix

# before
dataUnits:
  byte: {}
# after
dataUnits:
  byte:
    forms:
      singular: byte
      plural: bytes
Defensive patterns

Strategy: validation

Validate before calling

# Before authoring, confirm each dataUnits unit has forms, symbol, or template.
# Quick YAML lint (requires yq):
#   yq -o=json '.phrases.dataUnits | to_entries | map(select((.value.forms // .value.symbol // .value.template // .value) == null))' src/Humanizer/Locales/<code>.yml
# Any non-empty output is a unit that will trigger this error.

Prevention

When it happens

Trigger: Authoring phrases.dataUnits.<unit> as a mapping that omits forms, symbol, and template (e.g. an empty block, or a block holding only countPlacement/unknown keys). A null-valued 'symbol:' (unquoted null) also counts as absent because GetOptionalLiteral returns null for it.

Common situations: Copying a sibling unit block and deleting its forms/symbol lines without replacing them; writing 'symbol:' with no value; editing a locale file and leaving a stub unit entry while translating.

Related errors


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

Appendix: source

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

        static DataUnitPhrase ParseDataUnitPhrase(SimpleYamlValue value, string path)
        {
            if (value is SimpleYamlScalar scalar)
            {
                return new DataUnitPhrase(PhraseForms.FromScalar(ValidateLiteralText(path, scalar.Value)));
            }

            var mapping = ExpectMapping(value, path);
            RejectUnknownKeys(mapping, path, ["forms", "default", "zero", "singular", "dual", "paucal", "plural", "many", "symbol", "template"]);

            var forms = ParseOptionalPhraseForms(mapping, path);
            var symbol = GetOptionalLiteral(mapping, "symbol", $"{path}.symbol");
            var template = mapping.TryGetValue("template", out var templateValue)
                ? ParseNamedTemplate(templateValue, $"{path}.template", ["count", "unit"])
                : null;
            if (forms is null && symbol is null && template is null)
            {
                throw new InvalidOperationException($"Phrase '{path}' must define forms, 'symbol', or 'template'.");
            }

            return new DataUnitPhrase(forms, symbol, template);
        }

        static TimeUnitPhraseSet ParseTimeUnit(string localeCode, SimpleYamlValue value, string path)
        {
            var mapping = ExpectMapping(value, path);
            var builder = ImmutableDictionary.CreateBuilder<string, TimeUnitPhrase>(StringComparer.Ordinal);

            foreach (var entry in mapping.Values)
            {
                builder[entry.Key] = ParseTimeUnitPhrase(entry.Value, $"{path}.{entry.Key}");
            }

            return new TimeUnitPhraseSet(builder.ToImmutable());
        }

View on GitHub (pinned to ffc2b77c0f)