Humanizr/Humanizer · critical · InvalidOperationException

A dedicated 71 spelling is required when specialSeventyOneEn

Error message

A dedicated 71 spelling is required when specialSeventyOneEnabled is true.

What it means

This InvalidOperationException is thrown inside the VariantDecadeNumberToWordsProfile constructor (a primary-constructor field initializer) when specialSeventyOneEnabled is true but specialSeventyOneWord is null. It is a locale-authoring invariant: French-family locales that spell 71 as a dedicated word (e.g. 'soixante et onze') must supply that word in the profile. The exception fires during locale registry initialization, not during a normal Convert call, so it typically surfaces at first use of the affected culture or during application warm-up.

Source

Thrown at src/Humanizer/Localisation/NumberToWords/VariantDecadeNumberToWordsConverter.cs:289

    VariantDecadeSeventyStrategy seventyStrategy,
    VariantDecadeNinetyStrategy ninetyStrategy,
    bool specialSeventyOneEnabled,
    string? specialSeventyOneWord,
    bool pluralizeExactEighty,
    FrozenSet<int> tensUsingEtWhenUnitIsOne,
    string[] tensMap,
    VariantDecadeScale[] scales)
{
    /// <summary>Gets the word used to prefix negative values.</summary>
    public string MinusWord { get; } = minusWord;
    /// <summary>Gets the strategy used for the seventies.</summary>
    public VariantDecadeSeventyStrategy SeventyStrategy { get; } = seventyStrategy;
    /// <summary>Gets the strategy used for the nineties.</summary>
    public VariantDecadeNinetyStrategy NinetyStrategy { get; } = ninetyStrategy;
    /// <summary>Gets a value indicating whether the locale uses a dedicated spelling for 71.</summary>
    public bool SpecialSeventyOneEnabled { get; } = specialSeventyOneEnabled;
    /// <summary>Gets the optional dedicated spelling for 71 when the locale needs one.</summary>
    public string SpecialSeventyOneWord { get; } = specialSeventyOneWord ?? throw new InvalidOperationException("A dedicated 71 spelling is required when specialSeventyOneEnabled is true.");
    /// <summary>Gets a value indicating whether exact 80 takes a plural suffix.</summary>
    public bool PluralizeExactEighty { get; } = pluralizeExactEighty;
    /// <summary>Gets the set of tens indices that use an "et un" style joiner when the unit is one.</summary>
    public FrozenSet<int> TensUsingEtWhenUnitIsOne { get; } = tensUsingEtWhenUnitIsOne;
    /// <summary>Gets the tens lexicon.</summary>
    public string[] TensMap { get; } = tensMap;
    /// <summary>Gets the descending scale rows.</summary>
    public VariantDecadeScale[] Scales { get; } = scales;
}

readonly record struct VariantDecadeScale(
    ulong Value,
    string Singular,
    string Plural,
    bool OmitOne = false,
    bool PluralizeMultiplier = true);

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Provide a non-null specialSeventyOneWord in the locale profile data (YAML) wherever specialSeventyOneEnabled is true.
  2. If the locale does not use a special 71 spelling, set specialSeventyOneEnabled to false and remove the word.
  3. Regenerate the locale source from the YAML using the project's source generator so the profile and its fields stay consistent.
  4. Revert any hand-edit that changed one field without the other.

Example fix

// before (locale YAML fragment)
//  special_seventy_one_enabled: true
//  # special_seventy_one_word missing

// after
//  special_seventy_one_enabled: true
//  special_seventy_one_word: "soixante et onze"
Defensive patterns

Strategy: validation

Validate before calling

// For locale authors: verify profile consistency before registration
bool IsProfileValid(bool enabled, string? word) =>
    !enabled || !string.IsNullOrEmpty(word);

Try / catch

try
{
    // First use of the affected culture triggers profile construction
    return number.ToWords(new CultureInfo("fr-BE"));
}
catch (InvalidOperationException ex) when (ex.Message.Contains("71 spelling"))
{
    // Locale data is corrupt; fall back to a known-good culture
    return number.ToWords(new CultureInfo("fr-FR"));
}

Prevention

When it happens

Trigger: Constructing or resolving a VariantDecadeNumberToWordsProfile whose YAML or generated registry data sets specialSeventyOneEnabled: true while leaving the special_seventy_one_word field empty or null. This happens when a locale file is hand-edited, a code generator produces an incomplete profile, or a locale fork omits the word.

Common situations: Editing a French-family locale YAML file and setting special_seventy_one_enabled without providing special_seventy_one_word; running a partial locale generator output; forking a locale and forgetting the 71 entry; upgrading Humanizer and hitting a regression in generated locale code.

Related errors


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