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:
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:
falsewhen a token cannot be recognized.0in the numeric result.- The first unrecognized word in the final
outparameter.
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:
| Condition | Exception |
|---|---|
null input | ArgumentNullException |
| Malformed or out-of-range phrase | FormatException |
| Unsupported culture | NotSupportedException |
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.