{"record":{"id":"bbec2061e4b8d0c8","repo":"Humanizr/Humanizer","slug":"cannot-extract-negative-prefix-for-culture-cultu","errorCode":null,"errorMessage":"Cannot extract negative prefix for culture '{culture.Name}' from number-word-suffix ordinalizer.","messagePattern":"Cannot extract negative prefix for culture '(.+?)' from number-word-suffix ordinalizer\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer/Localisation/Ordinalizers/NumberWordSuffixOrdinalizer.cs","lineNumber":59,"sourceCode":"        var block = ResolveGenderBlock(effectiveGender);\n\n        // Negative numbers: check the absolute magnitude against exact replacements.\n        // When found, compose the negative ordinal using the locale's negative prefix\n        // (extracted from the converter) plus the ordinalizer's own exact replacement.\n        // This ensures negative and positive irregulars both come from ExactReplacements.\n        if (number < 0)\n        {\n            var magnitude = GetAbsoluteValue(number);\n            if (magnitude <= int.MaxValue\n                && block.ExactReplacements.TryGetValue((int)magnitude, out var negExact))\n            {\n                var converter = Configurator.GetNumberToWordsConverter(culture);\n                var magnitudeCardinal = converter.Convert((long)magnitude, effectiveGender);\n                var negativeCardinal = converter.Convert(number, effectiveGender);\n\n                if (!negativeCardinal.EndsWith(magnitudeCardinal, StringComparison.Ordinal))\n                {\n                    throw new InvalidOperationException(\n                        $\"Cannot extract negative prefix for culture '{culture.Name}' from number-word-suffix ordinalizer.\");\n                }\n\n                return negativeCardinal[..^magnitudeCardinal.Length] + negExact;\n            }\n\n            var cardinal = Configurator.GetNumberToWordsConverter(culture).Convert(number, effectiveGender);\n            return cardinal + block.DefaultSuffix;\n        }\n\n        if (TryGetInt32Value(number, out var exactValue) &&\n            block.ExactReplacements.TryGetValue(exactValue, out var exact))\n        {\n            return exact;\n        }\n\n        var positiveCardinal = Configurator.GetNumberToWordsConverter(culture).Convert(number, effectiveGender);\n        return positiveCardinal + block.DefaultSuffix;","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer/Localisation/Ordinalizers/NumberWordSuffixOrdinalizer.cs#L41-L77","documentation":"This InvalidOperationException is thrown by NumberWordSuffixOrdinalizer.ConvertCore when ordinalizing a negative number whose magnitude has an exact replacement. The ordinalizer renders the magnitude as a cardinal word, renders the full negative number as a cardinal word, then verifies that the negative phrase ends with the magnitude phrase so it can strip it and prepend the negative prefix. If the locale's number-to-words converter produces a negative phrase that does not end with the magnitude cardinal (e.g. the negative word is infix or the word order differs), the extraction fails and this error signals a locale-data inconsistency.","triggerScenarios":"Calling an ordinalize API on a negative number for a locale whose NumberWordSuffixOrdinalizer has exact replacements that include the magnitude, when the underlying cardinal converter for that culture renders negatives in a way that does not place the magnitude cardinal as a trailing substring of the full negative phrase.","commonSituations":"Adding or modifying a locale's negative-prefix strategy in YAML so that it no longer prefixes the magnitude; introducing a new exact ordinal replacement for a negative magnitude without verifying the cardinal converter's negative form; upgrading Humanizer where a locale's negative rendering changed between versions; locale data regression where the minus word becomes infix.","solutions":["Verify the locale's number-to-words converter renders negatives as a prefix + the magnitude cardinal (e.g. 'minus' + 'three'), so the suffix extraction succeeds.","If the locale uses an infix or circumfixed negative form, remove the problematic negative magnitude from the ordinalizer's ExactReplacements for that gender block.","Run a round-trip test: converter.Convert(-N) must EndsWith converter.Convert(N) for each exact-replacement magnitude.","Regenerate the locale source from corrected YAML using the project's source generator."],"exampleFix":"// before: locale converter renders negatives as infix,\n// so Convert(-3) => \"tri minus\" does not end with Convert(3) => \"tri\"\n\n// after: configure the locale's minus word as a prefix\n// so Convert(-3) => \"minus tri\" which ends with \"tri\"\n// enabling the ordinalizer to extract \"minus \" + \"tretji\"","handlingStrategy":"try-catch","validationCode":"// Verify the locale's cardinal converter produces prefix-style negatives\n// before relying on the ordinalizer for negative exact replacements\nvar culture = new CultureInfo(\"xx\");\nvar converter = Configurator.GetNumberToWordsConverter(culture);\nvar positive = converter.Convert(3L, GrammaticalGender.Masculine);\nvar negative = converter.Convert(-3L, GrammaticalGender.Masculine);\nbool isSafeForOrdinalizer = negative.EndsWith(positive, StringComparison.Ordinal);","typeGuard":null,"tryCatchPattern":"try\n{\n    return number.ToOrdinalWords(gender, culture);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"negative prefix\"))\n{\n    // Locale data inconsistency: fall back to a culture with known-good negative rendering\n    return number.ToOrdinalWords(gender, fallbackCulture);\n}","preventionTips":["Write a round-trip test for each locale: converter.Convert(-N) must EndsWith converter.Convert(N) for exact-replacement magnitudes.","Review locale YAML changes that alter the negative prefix strategy before shipping.","Keep the ordinalizer's ExactReplacements consistent with the cardinal converter's negative rendering.","Run the full locale test suite after any change to negative handling."],"tags":["invalidoperation","ordinalizer","locale-data","negative-numbers","consistency"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}