Настройка форматирования дат и чисел под локаль в мобильном приложении
Приложение показывает «1,234.56» пользователю из Германии, у которого это выглядит как «одна тысяча двести тридцать четыре целых пятьдесят шесть». Или дату «04/05/2024» — апрель пятое или четвёртое мая? Зависит от локали пользователя, и это не просто косметика: в финансовых и медицинских приложениях неправильное форматирование — баг с реальными последствиями.
Где конкретно ломается
Android. String.format("%.2f", price) — использует Locale.getDefault() системы, но только если не передать локаль явно. На некоторых версиях MIUI Locale.getDefault() возвращает Locale.US независимо от настроек пользователя. Правильно: String.format(Locale.getDefault(Locale.Category.FORMAT), "%.2f", price). Или NumberFormat.getInstance(locale).format(price).
SimpleDateFormat — потокоопасен и устарел. DateTimeFormatter (java.time, API 26+) или ThreeTenABP для старых версий. Паттерн "dd/MM/yyyy" — хардкод, не учитывает локаль. DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(locale) — правильный путь.
iOS. DateFormatter без явно заданного locale берёт Locale.current — кажется, правильно. Но если DateFormatter создаётся в фоновом потоке и кешируется, он может захватить locale до того, как пользователь сменил язык в приложении. NumberFormatter аналогично. Правило: formatter.locale = Locale(identifier: userSelectedLocale) — всегда явно.
Locale(identifier: "ru_RU") vs Locale(identifier: "ru") — разница есть: с регионом применяются региональные настройки чисел и дат (в России разделитель целой/дробной части — запятая, в Беларуси — тоже, но формат телефонов другой).
Flutter. Пакет intl: DateFormat.yMd(locale).format(date), NumberFormat.currency(locale: locale, symbol: symbol).format(amount). Важно: intl не включает locale-данные автоматически — нужен initializeDateFormatting(locale) перед первым использованием, иначе MissingLocaleDataException в рантайме.
Валюта — отдельная история
NumberFormat.getCurrencyInstance(locale).format(amount) — форматирует сумму с символом валюты по правилам локали. Но символ валюты и её позиция зависят от locale: в en_US это $1,234.56, в de_DE — 1.234,56 €, в ru_RU — 1 234,56 ₽. Если у вас мультивалютное приложение, символ нужно передавать явно через NumberFormat.getCurrencyInstance(locale).apply { currency = Currency.getInstance("EUR") }.
Отдельная боль — тысячные разделители. В hi_IN (хинди) группировка не тройками, а по схеме 2-2-3 (лакхи): 1,23,456. NumberFormat это поддерживает, но кастомная реализация через replace — нет.
Что делаем
Аудит всех мест форматирования в коде: grep -rn "SimpleDateFormat\|String.format\|\.toString()". Заменяем на locale-aware форматтеры. Добавляем утилитный слой LocaleFormatter / AppFormatter — централизованное место, где locale подставляется из текущего контекста приложения. Пишем unit-тесты с несколькими locale-фикстурами (de_DE, ar_SA, hi_IN).
Срок: 1 день для приложения без кастомных компонентов отображения. Стоимость рассчитывается индивидуально.







