Localize numeric value with NumberFormatter

We don't need to create a framework to format localized numeric values. We just need to implement the Intl extension in PHP, either using the object-oriented methods or, even simpler, the procedural functions.

This extension has been available since PHP version 5.3.0.

Understanding localization in PHP

The NumberFormatter format() method is like a combination of gettext() and printf(): it looks up a localized format and merges it with a provided value.

gettext() primer

gettext(), with its alias _(), is a simple lookup function: we feed it a key, normally a string in a default language, and it returns its associated value, the translation. The key/value pairs are located in a messages.po files in a directory tree categorized by locale. These key/value pairs are cached by the web server for fast returns.

setlocale(LC_ALL, 'fr_CA');
echo  _('This is text.'); //-> "Ceci est un texte."

printf() primer

printf() is a function that merges values to a formatted string with placeholders.

printf("The string is %s and the number is %d.", 'simple', 25); //-> "The string is simple and the number is 25."

We can combine gettext() and printf():

printf(_('The number is %d.'), 5); //-> "Le numéro est 5."

numfmt_format() primer

numfmt_format() is a lookup function that merges a value to a locale-formatted string.

$fmt = numfmt_create('en_US', NumberFormatter::DEFAULT_STYLE);
echo numfmt_format($fmt, 2345234.234); //-> 2,345,234.234

numfmt_format() has an OOP equivalent: NumberFormatter::format().

$fmt = new NumberFormatter('en_US', NumberFormatter::DEFAULT_STYLE);
echo $fmt->format(2345234.234); //-> 2,345,234.234

We can use the Facade version of the create() method:

$fmt = NumberFormatter::create('en_US', NumberFormatter::DEFAULT_STYLE);
echo $fmt->format(2345234.234); //-> 2,345,234.234

As we can see, the numfmt_format() function or the NumberFormatter::format() method need to happen in two lines, unless we go with a very long line:

echo numfmt_format(numfmt_create('en_US', NumberFormatter::DEFAULT_STYLE), 2345234.234); //-> 2,345,234.234

Encapsulating NumberFormatter methods

We could encapsulate numfmt_create() and numfmt_format() in one practical custom function with default options that we can override:

function numeric_format($number, array $kwargs = []) {
    extract($kwargs + [
        'locale'  => 'en_US',
        'pattern' => NULL,
        'style'   => NumberFormatter::DEFAULT_STYLE,
        'type'    => NumberFormatter::TYPE_DEFAULT,
    ]);
    $fmt = numfmt_create($locale, $style, $pattern);
    return numfmt_format($fmt, $number, $type);
}
echo numeric_format(2345234.234); //-> 2,345,234.234

Using a default Locale

The Intl extension provides the Locale class to get and set a default locale identifier.

Let's use it in a web application context. We set it in the bootstrap section of a web application:

Locale::setDefault('fr_FR');

We modify our custom function to use the default application locale.

function numeric_format($number, array $kwargs = []) {
    extract($kwargs + [
-       'locale'  => 'en_US',
+       'locale'  => substr(Locale::getDefault(), 0, 5) ?: NULL,
        'pattern' => NULL,
        'style'   => NumberFormatter::DEFAULT_STYLE,
        'type'    => NumberFormatter::TYPE_DEFAULT,
    ]);
    $fmt = numfmt_create($locale, $style, $pattern);
    return numfmt_format($fmt, $number, $type);
}

Then, on a page, we localize an amount based on the application's locale:

echo numeric_format(2345234.234); //-> 2 345 234,234

Variants

Spellout

echo numeric_format(2345234.234, ['style'=>NumberFormatter::SPELLOUT]);
//-> two million three hundred forty-five thousand two hundred thirty-four point two three four
function spellout_format($number, array $kwargs = []) {
    $kwargs += [
        'style' => NumberFormatter::SPELLOUT,
    ];
    return numeric_format($number, $kwargs);
}
echo spellout_format(2345234.234, ['locale'=>'de_DE']);
//-> zwei Millionen dreihundertfünfundvierzigtausendzweihundertvierunddreißig Komma zwei drei vier

Ordinal

echo numeric_format(31, ['style'=>NumberFormatter::ORDINAL]); //-> 31st
function ordinal_format($number, array $kwargs = []) {
    $kwargs += [
        'style' => NumberFormatter::ORDINAL,
    ];
    return numeric_format($number, $kwargs);
}
echo ordinal_format(32); //-> 32nd

numfmt_create() arguments

numfmt_create(string $locale, int $style, ?string $pattern = null): ?NumberFormatter

$locale

If NULL, it will lookup its default value in this order:

  1. ini_get('intl.default_locale')
  2. setlocale(LC_ALL, '0');
  3. $LANG value from the set locale of the operating system.

$style

$pattern