Localize currency with NumberFormatter

We don't need to create a framework to format localized currency 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() and formatCurrency() methods are like a combination of gettext() and printf(): they look up a localized format and merge 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_currency() primer

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

$fmt = numfmt_create('en_US', NumberFormatter::CURRENCY);
echo numfmt_format_currency($fmt, 2345234.234, 'USD'); // -> $2,345,234.23

numfmt_format_currency() has an OOP equivalent: NumberFormatter::formatCurrency().

$fmt = new NumberFormatter('en_US', NumberFormatter::CURRENCY);
echo $fmt->formatCurrency(2345234.234, 'USD'); // -> $2,345,234.23

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

$fmt = NumberFormatter::create('en_US', NumberFormatter::CURRENCY);
echo $fmt->formatCurrency(2345234.234, 'USD'); // -> $2,345,234.23

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

echo numfmt_format_currency(numfmt_create('en_US', NumberFormatter::CURRENCY), 2345234.234, 'USD'); //-> $2,345,234.23

Encapsulating NumberFormatter methods

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

function currency_format($amount, array $kwargs = []) {
    extract($kwargs + [
        'locale'  => 'en_US',
        'pattern' => NULL,
    ]);
    $fmt = numfmt_create($locale, NumberFormatter::CURRENCY, $pattern);
    $symbol = numfmt_get_symbol($fmt, NumberFormatter::INTL_CURRENCY_SYMBOL);
    return numfmt_format_currency($fmt, $amount, $symbol);
}
echo currency_format(2345234.234); //-> $2,345,234.23

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 currency_format($amount, array $kwargs = []) {
    extract($kwargs + [
-       'locale'  => 'en_US',
+       'locale'  => substr(Locale::getDefault(), 0, 5) ?: NULL,
        'pattern' => NULL,
    ]);
    $fmt = numfmt_create($locale, NumberFormatter::CURRENCY, $pattern);
    $symbol = numfmt_get_symbol($fmt, NumberFormatter::INTL_CURRENCY_SYMBOL);
    return numfmt_format_currency($fmt, $amount, $symbol);
}

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

echo currency_format(2345234.234); //-> 2 345 234,23 €

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