Skip to main content
Version: 4.0 (next)

Parse localized number words

Orientation

Use TryToNumber at an input boundary where invalid integer words are expected. Use ToNumber when invalid text is exceptional and should throw. Humanizer 4 adds the corresponding decimal APIs TryToDecimalNumber and ToDecimalNumber. All four require an explicit CultureInfo, because the same words, magnitude system, and token rules do not mean the same thing in every culture.

Example

This executable parses positive and negative integers, parses English and French decimal words, preserves an authored decimal scale, and verifies the unsupported-culture contract. Convert.ToInt64 keeps the integer assertions compatible with the stable int result and current long result:

Program.cs
using System.Globalization;
using Humanizer;

var culture = CultureInfo.GetCultureInfo("en-US");

var parsed = "two hundred five".ToNumber(culture);
var succeeded = "negative forty-two".TryToNumber(
out var negative,
culture,
out var unrecognized);
var rejected = "two otters".TryToNumber(
out var invalid,
culture,
out var invalidWord);
var decimalSucceeded = "one point five zero".TryToDecimalNumber(
out var decimalValue,
culture,
out var decimalUnrecognized);
var decimalRejected = "one point ten".TryToDecimalNumber(
out var invalidDecimal,
culture,
out var invalidDecimalWord);
var frenchCulture = CultureInfo.GetCultureInfo("fr-FR");
var frenchSucceeded = "un virgule deux".TryToDecimalNumber(
out var frenchDecimal,
frenchCulture,
out var frenchUnrecognized);
var unsupportedCulture = "aon point dhà".TryToDecimalNumber(
out var unsupportedDecimal,
CultureInfo.GetCultureInfo("gd"),
out var unsupportedPhrase);
string nullWords = null!;
var nullRejected = nullWords.TryToDecimalNumber(
out var nullDecimal,
culture,
out var nullUnrecognized);

AssertEqual(205, Convert.ToInt64(parsed));
AssertEqual(true, succeeded);
AssertEqual(-42, Convert.ToInt64(negative));
AssertEqual<string?>(null, unrecognized);
AssertEqual(false, rejected);
AssertEqual(0, Convert.ToInt64(invalid));
AssertEqual("otters", invalidWord);
AssertEqual(true, decimalSucceeded);
AssertEqual("1.50", decimalValue.ToString(CultureInfo.InvariantCulture));
AssertEqual<string?>(null, decimalUnrecognized);
AssertEqual(false, decimalRejected);
AssertEqual(0m, invalidDecimal);
AssertEqual("ten", invalidDecimalWord);
AssertEqual(true, frenchSucceeded);
AssertEqual(1.2m, frenchDecimal);
AssertEqual<string?>(null, frenchUnrecognized);
AssertEqual(false, unsupportedCulture);
AssertEqual(0m, unsupportedDecimal);
AssertEqual("aon point dhà", unsupportedPhrase);
AssertEqual(false, nullRejected);
AssertEqual(0m, nullDecimal);
AssertEqual(string.Empty, nullUnrecognized);
AssertThrows<NotSupportedException>(
() => "aon point dhà".ToDecimalNumber(CultureInfo.GetCultureInfo("gd")));
AssertThrows<ArgumentNullException>(() => nullWords.ToDecimalNumber(culture));

Console.WriteLine("205; -42; 1.50; 1,2; rejected at otters and ten");

static void AssertEqual<T>(T expected, T actual)
{
if (!EqualityComparer<T>.Default.Equals(expected, actual))
throw new InvalidOperationException($"Expected '{expected}', got '{actual}'.");
}

static void AssertThrows<TException>(Action action)
where TException : Exception
{
try
{
action();
}
catch (TException)
{
return;
}

throw new InvalidOperationException($"Expected {typeof(TException).Name}.");
}

Handle expected failure

The longest TryToNumber overload returns:

  • false when a token cannot be recognized.
  • 0 in the numeric result.
  • The first unrecognized word in the final out parameter.

That token is useful for field validation messages. Do not catch FormatException for routine input when TryToNumber expresses the branch directly.

Parse localized decimal words

ToDecimalNumber accepts a localized integer phrase followed by exactly one locale-specific decimal marker and between 1 and 28 localized fractional digit words. English accepts one point two, French accepts un virgule deux, Hindi accepts एक दशमलव दो, and Urdu accepts ایک اعشاریہ دو. The integer part may be omitted, and a locale-supported negative affix applies to the complete value. The authored decimal scale is preserved, including trailing zeros and signed zero. Whitespace-tokenized grammars require boundaries around the decimal marker and each fractional digit, while negative affixes keep their profile-authored boundary. Joined-script profiles such as Chinese use their authored joined tokenization instead.

The integer side and each fractional digit reuse the selected culture's generated number-word profiles. For example, en-IN accepts ninety-three shankh point one, while en-US does not. Locale variants and same-language descendants inherit their localized parent profile; unsupported languages never fall back to English. English preserves scale composition past the long range when the final value fits in decimal; other cultures use the range accepted by their own words-to-number grammar.

Locale-authored decimal markers use reviewed spoken terms where available. Locales without a spoken marker use their standard decimal separator token.

For malformed input, unsupported cultures, or values that cannot fit in decimal, TryToDecimalNumber returns false, sets the result to 0m, and reports a best-effort unrecognized token or phrase when that overload is used. For null or empty input, that reported value is an empty string. ToDecimalNumber instead throws:

ConditionException
null inputArgumentNullException
Malformed or out-of-range phraseFormatException
Unsupported cultureNotSupportedException

Convert to a narrower type

Current builds return long. If the application requires int, use a checked conversion after successful parsing:

var value = checked((int)parsedLong);

Pitfall

The parsers support only the locales and word forms represented by the selected version's generated profiles. They are not general natural-language parsers. Preserve the explicit culture from the user-input boundary.

Version notes

Words-to-number begins in Humanizer 3.0.1; this page is unavailable in 2.x. Releases through 3.0.10 return int. Humanizer 4 returns long, and its analyzer can offer a checked conversion for existing assignments that now fail with CS0029 or CS0266. Decimal word parsing is available in Humanizer 4.