Humanizr/Humanizer · error · InvalidOperationException

Cannot extract negative prefix for culture '{culture.Name}'

Error message

Cannot extract negative prefix for culture '{culture.Name}' from number-word-suffix ordinalizer.

What it means

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.

Source

Thrown at src/Humanizer/Localisation/Ordinalizers/NumberWordSuffixOrdinalizer.cs:59

        var block = ResolveGenderBlock(effectiveGender);

        // Negative numbers: check the absolute magnitude against exact replacements.
        // When found, compose the negative ordinal using the locale's negative prefix
        // (extracted from the converter) plus the ordinalizer's own exact replacement.
        // This ensures negative and positive irregulars both come from ExactReplacements.
        if (number < 0)
        {
            var magnitude = GetAbsoluteValue(number);
            if (magnitude <= int.MaxValue
                && block.ExactReplacements.TryGetValue((int)magnitude, out var negExact))
            {
                var converter = Configurator.GetNumberToWordsConverter(culture);
                var magnitudeCardinal = converter.Convert((long)magnitude, effectiveGender);
                var negativeCardinal = converter.Convert(number, effectiveGender);

                if (!negativeCardinal.EndsWith(magnitudeCardinal, StringComparison.Ordinal))
                {
                    throw new InvalidOperationException(
                        $"Cannot extract negative prefix for culture '{culture.Name}' from number-word-suffix ordinalizer.");
                }

                return negativeCardinal[..^magnitudeCardinal.Length] + negExact;
            }

            var cardinal = Configurator.GetNumberToWordsConverter(culture).Convert(number, effectiveGender);
            return cardinal + block.DefaultSuffix;
        }

        if (TryGetInt32Value(number, out var exactValue) &&
            block.ExactReplacements.TryGetValue(exactValue, out var exact))
        {
            return exact;
        }

        var positiveCardinal = Configurator.GetNumberToWordsConverter(culture).Convert(number, effectiveGender);
        return positiveCardinal + block.DefaultSuffix;

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. 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.
  2. If the locale uses an infix or circumfixed negative form, remove the problematic negative magnitude from the ordinalizer's ExactReplacements for that gender block.
  3. Run a round-trip test: converter.Convert(-N) must EndsWith converter.Convert(N) for each exact-replacement magnitude.
  4. Regenerate the locale source from corrected YAML using the project's source generator.

Example fix

// before: locale converter renders negatives as infix,
// so Convert(-3) => "tri minus" does not end with Convert(3) => "tri"

// after: configure the locale's minus word as a prefix
// so Convert(-3) => "minus tri" which ends with "tri"
// enabling the ordinalizer to extract "minus " + "tretji"
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the locale's cardinal converter produces prefix-style negatives
// before relying on the ordinalizer for negative exact replacements
var culture = new CultureInfo("xx");
var converter = Configurator.GetNumberToWordsConverter(culture);
var positive = converter.Convert(3L, GrammaticalGender.Masculine);
var negative = converter.Convert(-3L, GrammaticalGender.Masculine);
bool isSafeForOrdinalizer = negative.EndsWith(positive, StringComparison.Ordinal);

Try / catch

try
{
    return number.ToOrdinalWords(gender, culture);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("negative prefix"))
{
    // Locale data inconsistency: fall back to a culture with known-good negative rendering
    return number.ToOrdinalWords(gender, fallbackCulture);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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