{"record":{"id":"ec4d5e8a1ebd2de0","repo":"Humanizr/Humanizer","slug":"harmony-ordinal-suffix-data-is-missing","errorCode":null,"errorMessage":"Harmony-ordinal suffix data is missing.","messagePattern":"Harmony-ordinal suffix data is missing\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer/Localisation/NumberToWords/HarmonyOrdinalNumberToWordsConverter.cs","lineNumber":185,"sourceCode":"            return base.ConvertToTuple(number);\n        }\n\n        return AppendHarmonySuffix(Convert(number), profile.TupleSuffixes, useTerminalVowelOrdinalSuffixes: false);\n    }\n\n    // Vowel-harmony ordinals are the common family rule. The runtime searches backward for the\n    // last vowel with a configured suffix, optionally swapping to terminal-vowel suffixes only when\n    // the rendered ordinal stem already ends in a vowel. This keeps ordinary ordinalSuffixes as the\n    // default/consonant-final map for stems such as Kyrgyz \"миллиард\" while still allowing\n    // vowel-final stems such as \"жыйырма\" or Kazakh \"екі\" to use a different suffix.\n    /// <summary>\n    /// Appends the harmony suffix selected by the last vowel in the word.\n    /// </summary>\n    string AppendHarmonySuffix(string word, FrozenDictionary<char, string>? suffixes, bool useTerminalVowelOrdinalSuffixes)\n    {\n        if (suffixes is null)\n        {\n            throw new InvalidOperationException(\"Harmony-ordinal suffix data is missing.\");\n        }\n\n        var terminalVowelSuffixes = profile.TerminalVowelOrdinalSuffixes;\n        if (useTerminalVowelOrdinalSuffixes && terminalVowelSuffixes is not null && terminalVowelSuffixes.ContainsKey(word[^1]))\n        {\n            suffixes = terminalVowelSuffixes;\n        }\n\n        // Walk backward so the suffix is chosen from the last vowel rather than from the first\n        // vowel or from a locale-specific hardcoded branch.\n        var suffix = string.Empty;\n        var suffixFoundOnLastVowel = false;\n\n        for (var i = word.Length - 1; i >= 0; i--)\n        {\n            if (suffixes.TryGetValue(word[i], out suffix))\n            {\n                suffixFoundOnLastVowel = i == word.Length - 1;","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer/Localisation/NumberToWords/HarmonyOrdinalNumberToWordsConverter.cs#L167-L203","documentation":"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.","triggerScenarios":"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.","commonSituations":"Locale YAML authored the strategy but left the suffix map empty; a source-generator regression that dropped the map.","solutions":["Report the locale bug — the harmony suffix map is missing.","Use a locale/version with complete harmony suffix data.","If authoring, populate ordinalSuffixes (and tupleSuffixes if tuples use harmony) in the locale YAML."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Suffix maps are internal profile data; no public pre-check exists. Wrap ConvertToOrdinal/ToWords\n// and catch InvalidOperationException referencing the harmony suffix message.","typeGuard":null,"tryCatchPattern":"string ordinal;\ntry\n{\n    ordinal = number.ToOrdinalWords(culture);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"Harmony-ordinal suffix data is missing\"))\n{\n    ordinal = number.ToOrdinalWords(CultureInfo.InvariantCulture);\n}","preventionTips":["After upgrading Humanizer, run ordinal + tuple conversions for each Turkic locale.","Report missing harmony suffix maps upstream.","Avoid relying on tuple forms for locales you have not verified."],"tags":["number-to-words","profile-data","internal-invariant","localization"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}