Humanizr/Humanizer · error · InvalidOperationException

Phrase '{path}' must be a scalar string.

Error message

Phrase '{path}' must be a scalar string.

What it means

Thrown by GetOptionalLiteral when a YAML key that must hold a scalar string (e.g. symbol, default, beforeCount, now) is instead a mapping or sequence. Literal phrase fields only accept scalars (an unquoted 'null' is treated as absent). Surfaced as compiler diagnostic HSG003 (severity Error).

Source

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

                if (!placeholders.Contains(placeholderName))
                {
                    placeholders.Add(placeholderName);
                }
            }

            return new NamedTemplatePhrase(templateName, template, placeholders.ToImmutable());
        }

        static string? GetOptionalLiteral(SimpleYamlMapping mapping, string key, string path, params string[] allowedPlaceholders)
        {
            if (!mapping.TryGetValue(key, out var value))
            {
                return null;
            }

            if (value is not SimpleYamlScalar scalar)
            {
                throw new InvalidOperationException($"Phrase '{path}' must be a scalar string.");
            }

            if (!scalar.IsQuoted && string.Equals(scalar.Value, "null", StringComparison.Ordinal))
            {
                return null;
            }

            return ValidateLiteralText(path, scalar.Value, allowedPlaceholders);
        }

        static string? GetOptionalLiteralOrExplicitTemplate(SimpleYamlMapping mapping, string key, string path)
        {
            if (!mapping.TryGetValue(key, out var value))
            {
                return null;
            }

            if (value is SimpleYamlScalar scalar)

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Replace the mapping/sequence with a scalar string value for that key.
  2. Fix YAML indentation so sibling keys are not accidentally nested under the literal field.
  3. Move the nested content to the correct parent (e.g. under 'forms:') if it was meant to be form data.
  4. Rebuild to confirm the diagnostic clears.

Example fix

# before
dataUnits:
  byte:
    symbol:
      singular: B
# after
dataUnits:
  byte:
    symbol: B
Defensive patterns

Strategy: validation

Validate before calling

# Literal fields (symbol, default, zero, singular, dual, paucal, plural, many,
# beforeCount, afterCount, now, today, never) must be scalars.
yq -o=json '[.. | select(tag == "!!map") | to_entries[] | select(.key | test("^(symbol|default|zero|singular|dual|paucal|plural|many|beforeCount|afterCount|now|today|never)$")) | select(.value | tag != "!!str")] ' src/Humanizer/Locales/<code>.yml

Prevention

When it happens

Trigger: Writing 'default: singular: ...' under a field that expects a plain string, or putting a list where a scalar is required; wrong indentation merging a child mapping under a literal key.

Common situations: Indentation slip that nests the next unit's keys under the current literal; author treats a literal field as a forms block; copy-paste of a mapping into a scalar slot.

Related errors


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