Humanizr/Humanizer · error · InvalidOperationException

Harmony-ordinal suffix data is missing.

Error message

Harmony-ordinal suffix data is missing.

What it means

The LastVowelMap suffix strategy requires a non-null suffix dictionary, but the profile supplied null. Reached from ConvertToOrdinal (profile.OrdinalSuffixes) when OrdinalSuffixStrategy is LastVowelMap, or from ConvertToTuple (profile.TupleSuffixes). Profile-data defect: the YAML set the strategy but omitted the suffix map.

Source

Thrown at src/Humanizer/Localisation/NumberToWords/HarmonyOrdinalNumberToWordsConverter.cs:185

            return base.ConvertToTuple(number);
        }

        return AppendHarmonySuffix(Convert(number), profile.TupleSuffixes, useTerminalVowelOrdinalSuffixes: false);
    }

    // Vowel-harmony ordinals are the common family rule. The runtime searches backward for the
    // last vowel with a configured suffix, optionally swapping to terminal-vowel suffixes only when
    // the rendered ordinal stem already ends in a vowel. This keeps ordinary ordinalSuffixes as the
    // default/consonant-final map for stems such as Kyrgyz "миллиард" while still allowing
    // vowel-final stems such as "жыйырма" or Kazakh "екі" to use a different suffix.
    /// <summary>
    /// Appends the harmony suffix selected by the last vowel in the word.
    /// </summary>
    string AppendHarmonySuffix(string word, FrozenDictionary<char, string>? suffixes, bool useTerminalVowelOrdinalSuffixes)
    {
        if (suffixes is null)
        {
            throw new InvalidOperationException("Harmony-ordinal suffix data is missing.");
        }

        var terminalVowelSuffixes = profile.TerminalVowelOrdinalSuffixes;
        if (useTerminalVowelOrdinalSuffixes && terminalVowelSuffixes is not null && terminalVowelSuffixes.ContainsKey(word[^1]))
        {
            suffixes = terminalVowelSuffixes;
        }

        // Walk backward so the suffix is chosen from the last vowel rather than from the first
        // vowel or from a locale-specific hardcoded branch.
        var suffix = string.Empty;
        var suffixFoundOnLastVowel = false;

        for (var i = word.Length - 1; i >= 0; i--)
        {
            if (suffixes.TryGetValue(word[i], out suffix))
            {
                suffixFoundOnLastVowel = i == word.Length - 1;

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Report the locale bug — the harmony suffix map is missing.
  2. Use a locale/version with complete harmony suffix data.
  3. If authoring, populate ordinalSuffixes (and tupleSuffixes if tuples use harmony) in the locale YAML.
Defensive patterns

Strategy: try-catch

Validate before calling

// Suffix maps are internal profile data; no public pre-check exists. Wrap ConvertToOrdinal/ToWords
// and catch InvalidOperationException referencing the harmony suffix message.

Try / catch

string ordinal;
try
{
    ordinal = number.ToOrdinalWords(culture);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Harmony-ordinal suffix data is missing"))
{
    ordinal = number.ToOrdinalWords(CultureInfo.InvariantCulture);
}

Prevention

When it happens

Trigger: number.ToOrdinalWords(<turkic culture>) with OrdinalSuffixStrategy == LastVowelMap and profile.OrdinalSuffixes == null; or number.ToWords tuple path where profile.TupleSuffixes == null and NamedTuples misses the value.

Common situations: Locale YAML authored the strategy but left the suffix map empty; a source-generator regression that dropped the map.

Related errors


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