Humanizr/Humanizer · error · InvalidOperationException

Phrase '{path}' can only use placeholders in explicit 'templ

Error message

Phrase '{path}' can only use placeholders in explicit 'template' entries.

What it means

Thrown by ValidateLiteralText when a literal phrase string contains any placeholder but the field allows none (allowedPlaceholders is empty). Many literal fields (e.g. data-unit symbol, relativeDate now) are pure text and cannot interpolate; formatting belongs in an explicit 'template' entry. Surfaced as compiler diagnostic HSG003 (severity Error).

Source

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

        static string ValidateLiteralText(string path, string text, params string[] allowedPlaceholders)
        {
            foreach (var placeholder in GetPlaceholderNames(text))
            {
                if (int.TryParse(placeholder, out _))
                {
                    throw new InvalidOperationException($"Phrase '{path}' cannot use numeric placeholder '{{{placeholder}}}'.");
                }

                if (placeholder.Length == 0 || !char.IsLetter(placeholder[0]) || placeholder.Any(static c => !char.IsLetterOrDigit(c) && c != '_'))
                {
                    throw new InvalidOperationException($"Phrase '{path}' uses unsupported placeholder '{{{placeholder}}}'.");
                }

                if (!allowedPlaceholders.Contains(placeholder, StringComparer.Ordinal))
                {
                    throw new InvalidOperationException(
                        allowedPlaceholders.Length == 0
                            ? $"Phrase '{path}' can only use placeholders in explicit 'template' entries."
                            : $"Phrase '{path}' can only use placeholders: {string.Join(", ", allowedPlaceholders.Select(static value => $"{{{value}}}"))}.");
                }
            }

            return text;
        }

        static IEnumerable<string> GetPlaceholderNames(string text) =>
            PlaceholderRegex.Matches(text)
                .Cast<Match>()
                .Select(static match => match.Groups["name"].Value);

        static SimpleYamlMapping ExpectMapping(SimpleYamlValue value, string path) =>
            value as SimpleYamlMapping ??
            throw new InvalidOperationException($"Phrase section '{path}' must be a mapping.");

        static void RejectUnknownKeys(SimpleYamlMapping mapping, string path, params string[] allowedKeys)
        {

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Remove the placeholder from the literal so it is plain text.
  2. If interpolation is required, add an explicit 'template' entry to that phrase (templates permit the surface's allowed placeholders).
  3. Rebuild to confirm the diagnostic clears.

Example fix

# before
dataUnits:
  byte:
    symbol: '{count} B'
# after
dataUnits:
  byte:
    symbol: B
    template:
      value: '{count} {unit}'
Defensive patterns

Strategy: validation

Validate before calling

# Surfaces whose literals allow NO placeholders include data-unit 'symbol',
# relativeDate now/today/never, and duration 'zero'. Find any {placeholder} there.
grep -nE '(symbol|now|today|never|zero):.*\{[^}]+\}' src/Humanizer/Locales/<code>.yml

Prevention

When it happens

Trigger: Writing 'symbol: {count} B' or 'now: {value}' on a surface whose literals take no placeholders.

Common situations: Author assumes all fields accept {count}; migrating from a format-string-based resource; copy-paste of a templated value into a literal slot.

Related errors


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