{"record":{"id":"6dd94cd39ea42daf","repo":"Humanizr/Humanizer","slug":"harmony-ordinal-membership-suffix-data-is-incomple","errorCode":null,"errorMessage":"Harmony-ordinal membership suffix data is incomplete.","messagePattern":"Harmony-ordinal membership suffix data is incomplete\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer/Localisation/NumberToWords/HarmonyOrdinalNumberToWordsConverter.cs","lineNumber":222,"sourceCode":"                break;\n            }\n        }\n\n        word = ApplyHarmonyStemAdjustments(word, suffixFoundOnLastVowel);\n        return word + suffix;\n    }\n\n    // Some locales split between two ordinal suffixes by final-character membership rather than by\n    // the last vowel. That alternative stays structural because the deciding character set and the\n    // suffix pair still come from generated data.\n    /// <summary>\n    /// Appends the ordinal suffix selected by final-character membership.\n    /// </summary>\n    static string AppendMembershipOrdinalSuffix(string word, string? secondOrdinalSuffixCharacters, string[] ordinalSuffixPair)\n    {\n        if (string.IsNullOrEmpty(secondOrdinalSuffixCharacters) || ordinalSuffixPair.Length != 2)\n        {\n            throw new InvalidOperationException(\"Harmony-ordinal membership suffix data is incomplete.\");\n        }\n\n        // Membership-based locales still keep the suffix choice data-driven; the final character\n        // set selects which of the two generated suffixes should be used.\n        var suffixIndex = secondOrdinalSuffixCharacters.Contains(word[^1]) ? 1 : 0;\n        return word + ordinalSuffixPair[suffixIndex];\n    }\n\n    // Harmony adjustments operate on the already-rendered cardinal word so the family can model\n    // softening and terminal-vowel dropping without duplicating the full cardinal renderer.\n    /// <summary>\n    /// Applies the locale's stem adjustments before a harmony suffix is appended.\n    /// </summary>\n    string ApplyHarmonyStemAdjustments(string word, bool suffixFoundOnLastVowel)\n    {\n        // The stem edits happen before the suffix is appended so the same base word can support\n        // softening and terminal-vowel loss without duplicating the cardinal renderer.\n        if (profile.SoftenTerminalTBeforeSuffix && word[^1] == 't')","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer/Localisation/NumberToWords/HarmonyOrdinalNumberToWordsConverter.cs#L204-L240","documentation":"The FinalCharacterMembership ordinal strategy needs both a non-empty SecondOrdinalSuffixCharacters set and exactly a 2-element OrdinalSuffixPair. The profile supplied incomplete data (null/empty character set, or a pair whose length is not 2). Profile-data defect fired during ConvertToOrdinal.","triggerScenarios":"number.ToOrdinalWords(<turkic culture>) where OrdinalSuffixStrategy == FinalCharacterMembership and secondOrdinalSuffixCharacters is null/empty or ordinalSuffixPair.Length != 2.","commonSituations":"Locale YAML set the membership strategy but left secondOrdinalSuffixCharacters empty or ordinalSuffixPair with the wrong arity.","solutions":["Report the locale bug — membership suffix data is incomplete.","Switch to a locale/version with complete membership suffix data.","If authoring, provide both secondOrdinalSuffixCharacters and a 2-element ordinalSuffixPair."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Membership suffix data is internal; no public pre-check. Catch InvalidOperationException from\n// ConvertToOrdinal referencing the membership suffix message.","typeGuard":null,"tryCatchPattern":"string ordinal;\ntry\n{\n    ordinal = number.ToOrdinalWords(culture);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"membership suffix data is incomplete\"))\n{\n    ordinal = number.ToOrdinalWords(CultureInfo.InvariantCulture);\n}","preventionTips":["Smoke-test ordinal conversion for each Turkic locale after upgrades.","Report incomplete membership suffix data upstream.","Fall back to a verified locale when ordinal data is incomplete."],"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"}