Локализация числовых/денежных данных

При разработке более-менее крупного проекта возникает проблема с локализацией числовых/денежных данных. В этой заметке расскажу о мучениях при использовании Symfony framework, Sonata Admin Bundle и клиентской части. Но сначала о сути проблемы, так как на первый взгляд она не очень очевидная.

Итак, допустим у нас есть проект, который на стороне сервера (php/шаблонизатор) рендерит числовые/денежные данные. Они должны отображаться в соответствии с установленной локаллю. Пользователь может вводить данные (в своем представлении). При этом данные могут обрабатываться еще и на клиентской части (javascript). К примеру, в большинстве стран Европы, кроме Великобритании и Ирландии десятичный разделитель запятая, в Великобритании и Ирландии - точка. Естественно, что пользователь с Германии будет вводить данные с раздилителем - запятою.

Если на стороне сервера представлять в нужном формате (с помощью NumberFormatter, к примеру), то как быть на стороне js тогда? Нету нативной кроссбраузерной локализации числовых данных. JS работает только с числами в международном формате (к примеру, 12.32), независимо от пользовательских настроек браузера.

Но обо всем по порядку. Сначала о серверном решении.

При использовании Sonata Admin Bundle стоит использовать поля типу money, number - у них уже реализован механизм локализации. Для дополнительных настроек локализации можно еще установить sonata-project/intl-bundle. При использовании вне админки стоит использовать либо twig расширение для intl (твиг фильтры localizednumber, localizedcurrency), либо писать свою обертку поверх intl NumberFormatter и использовать ее. Второе полезно в том случае, если вам лень передавать каждый раз в localizedcurrency код (EUR, к примеру), и если на всем проекте используется какая-то одна валюта. Для этого напишем сервис, который использует NumberFormatter, который будет получать с конфигурации валюту (EUR) и, если нужно, кастомный символ валюты. Удобнее создать отдельные форматтеры для чисел и currency. На стороне клиента используем accounting.js:

composer require bower-asset/accounting.js

(Если используете assetic plugin для composer)

Для него нужно будет написать конфигурацию:

accounting.settings = _.defaults({
    number: {
        decimal: ',',
        thousand: ' ',
        precision: 2,
        grouping: 3
    },
    currency: {
        decimal: ',',
        thousand: ' ',
        precision: 2,
        grouping: 3,
        symbol: '€',
        format: '%v %s'
    },
}, accounting.settings);

Дальше, при любом выводе чисел оборачиваем:

accounting.formatNumber(12.32)

 Обратное преобразование (например, для рассчетов):

accounting.unformat('12,32')

В случае с деньгами:

accounting.formatMoney(12.32)