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
- Provide a non-null specialSeventyOneWord in the locale profile data (YAML) wherever specialSeventyOneEnabled is true.
- If the locale does not use a special 71 spelling, set specialSeventyOneEnabled to false and remove the word.
- Regenerate the locale source from the YAML using the project's source generator so the profile and its fields stay consistent.
- 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
- Treat locale YAML edits as code changes: test them with a sample Convert call before shipping.
- Keep specialSeventyOneEnabled and specialSeventyOneWord in sync — enabling one requires the other.
- Run the project's locale tests after any YAML change to catch profile-construction errors at CI.
- Regenerate locale source via the source generator after editing YAML rather than editing generated code by hand.
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
- Linked-vigesimal parser profiles require positive scale valu
- Linked-vigesimal parser profiles require descending scales.
- Linked-vigesimal parser profiles require descending scales w
- Cannot extract negative prefix for culture '{culture.Name}'
- '{path}.units' must explicitly define '{unitName}'.
AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13).
Data as JSON: /api/errors/eefb069405e0bb44.
Report an issue: GitHub.