Локализация ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-17

Локализация и интернационализация в Java. Practical Tasks

Положили в messages_ru.properties строку title=Отчёт, запустили программу — а на консоли вместо «Отчёт» набор символов вида «Ð...». До Java 9 класс ResourceBundle читал properties-файлы в кодировке ISO-8859-1, и кириллица из файла в UTF-8 превращалась в мусор. Начиная с Java 9 (JEP 226) такие файлы по умолчанию читаются в UTF-8, но это лишь один из нюансов, на которых спотыкаются при первой локализации.

На этой странице — 3 задачи по локализации и интернационализации в Java: properties-файлы с текстами на русском и английском, отчет, заголовок которого меняется вместе с локалью, и цены в валюте текущей локали для учебного интернет-магазина. Для решения понадобятся классы Locale, ResourceBundle и NumberFormat; общая теория — в уроке «Локализация и интернационализация в Java».

Темы Java на русском и английском (ResourceBundle)

  1. Создать properties-файлы на русском и английском языках, содержащие названия изученных тем по Java.
  2. Создать метод, который получает в качестве параметра локаль и распечатывает всю информацию из properties-файла.

Подсказка

Перебрать все ключи поможет метод ResourceBundle.keySet(), значение по ключу возвращает getString(key). Файлы положите в classpath, например src/main/resources/topics_ru.properties и topics_en.properties, и загружайте через ResourceBundle.getBundle("topics", locale).

Класс FullReport: отчет с заголовком на языке локали

Отчёт о сотрудниках · part 2 of 4 · ← part 1 · part 3 →

  1. Создать класс FullReport, который выводит ту же информацию, что и класс Report (часть 1 на странице заданий по строкам), но заголовок отчета должен изменяться в зависимости от выбранной локали (используем класс ResourceBundle).
  2. Для форматирования чисел использовать класс NumberFormat.

С чего начать

Вынесите заголовки отчета в report_ru.properties и report_en.properties, а числа форматируйте через NumberFormat.getNumberInstance(locale) или getCurrencyInstance(locale). Локаль передавайте параметром, а не берите Locale.getDefault() внутри метода: так отчет легко проверить для обеих локалей в одном запуске.

Чек с ценами в валюте текущей локали

Интернет-магазин · part 4 of 11 · ← part 3 · part 5 →

  1. При покупке вывести на консоль отчет о купленных продуктах. Денежные единицы должны соответствовать текущей локали (используем класс NumberFormat). Например, для локали uk_UA:
    Продукты        Цена
    --------------------------------------
    Джинсы     1 500,78 ₴
    Юбка       1 000,56 ₴
    Галстук      500,78 ₴
    --------------------------------------
    Итого:     3 002,12 ₴
    
  2. Отчет должен быть на языке текущей локали — используем ResourceBundle.

Нюанс с деньгами

Храните цены в BigDecimal, а не в double: иначе итог может разойтись с суммой строк на копейку. Разделитель тысяч в выводе NumberFormat — неразрывный пробел, поэтому не сравнивайте результат со строкой, набранной вручную.

Solution on Patreon →

На чём чаще всего ошибаются

В задачах на локализацию программа чаще всего ломается не из-за логики, а из-за окружения: properties-файл не попал в classpath, в одном из файлов не хватает ключа, а отформатированная сумма содержит неразрывный пробел вместо обычного. В таблице — симптом, причина и исправление.

Ошибка Что происходит Как исправить
Файл лежит не в classpath или базовое имя указано без пакета MissingResourceException: Can't find bundle for base name messages, locale ru_RU Положить файлы в src/main/resources (Maven/Gradle); для файла в папке i18n вызывать getBundle("i18n.messages")
Ключ есть в messages_en.properties, но забыт в messages_ru.properties Значение молча берётся из базового файла messages.properties; если ключа нет и там — MissingResourceException: Can't find resource for bundle ..., key ... Держать одинаковый набор ключей во всех файлах, базовый файл без суффикса использовать как запасной
Результат format() сравнивают со строкой, набранной вручную "1 500,78 ₴".equals(...) возвращает false: разделитель тысяч — неразрывный пробел (U+00A0 или U+202F, зависит от локали и версии JDK) Сравнивать с результатом того же NumberFormat или отключить группировку: setGroupingUsed(false)
Цены хранятся в double 0.1 + 0.2 даёт 0.30000000000000004, итог чека расходится с суммой строк на копейку Хранить суммы в BigDecimal; NumberFormat.format() принимает его как Number
Локаль создают через new Locale("ru", "RU") С Java 19 конструкторы Locale помечены @Deprecated, компилятор выдаёт предупреждение Locale.of("ru", "RU") (Java 19+) или Locale.forLanguageTag("ru-RU")

Что выведет этот код: запасная локаль

Если для запрошенной локали нет своего properties-файла, ResourceBundle.getBundle() сначала ищет файл для локали по умолчанию (Locale.getDefault()) и только потом берёт базовый файл без суффикса. Поэтому на компьютере с русской локалью «немецкий» отчет внезапно выводится по-русски, а не на языке базового файла.

// src/main/resources/messages.properties:    title=Report
// src/main/resources/messages_ru.properties: title=Отчёт
Locale.setDefault(Locale.of("ru", "RU"));   // до Java 19: new Locale("ru", "RU")

ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.GERMANY);
System.out.println(bundle.getString("title"));
System.out.println(bundle.getLocale());
// Вывод:
// Отчёт
// ru

Файла messages_de_DE.properties нет, поэтому поиск переходит к локали по умолчанию ru_RU и находит messages_ru.properties. Чтобы при отсутствии перевода всегда получать базовый файл, отключите этот шаг через ResourceBundle.Control:

ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.GERMANY,
        ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_DEFAULT));
System.out.println(bundle.getString("title"));
System.out.println(bundle.getLocale().equals(Locale.ROOT));
// Вывод:
// Report
// true

Вариант с Control работает в обычном (немодульном) проекте; если вызвать его из именованного модуля Java 9+, будет выброшено UnsupportedOperationException.

Проверь свой код

Перед тем как сверяться с решением, пройдитесь по рекомендациям по оформлению кода: имена классов и методов, отсутствие «магических» строк вместо ключей из properties-файла, локаль, переданная параметром, а не зашитая в метод.

Часто задаваемые вопросы

Нужно ли экранировать кириллицу в properties-файлах через \uXXXX?

Для ResourceBundle в Java 9+ нет: файлы читаются в UTF-8, а если файл не декодируется как UTF-8, он перечитывается как ISO-8859-1. Экранирование нужно на Java 8 и при загрузке через Properties.load(InputStream), который по-прежнему использует ISO-8859-1. Во втором случае передайте Reader: properties.load(new InputStreamReader(in, StandardCharsets.UTF_8)).

Чем ResourceBundle отличается от Properties?

Properties — просто словарь «ключ → значение» из одного файла. ResourceBundle сам выбирает файл по локали, строит цепочку родителей (messages_ru_RU → messages_ru → messages) и кеширует загруженные бандлы. Для текстов интерфейса на нескольких языках нужен именно ResourceBundle.

Как вывести цену в гривнах, если на компьютере английская локаль?

Передайте локаль явно: NumberFormat.getCurrencyInstance(Locale.of("uk", "UA")). Вызов setCurrency(Currency.getInstance("UAH")) у форматтера английской локали поменяет только символ или код валюты, а разделители и порядок останутся английскими. Частые ошибки с форматированием сумм — в разделе «На чём чаще всего ошибаются».

Как проверить программу для другой локали, не меняя настройки ОС?

Узнать текущую локаль можно через Locale.getDefault(). Поменять её — параметрами JVM -Duser.language=en -Duser.country=US или вызовом Locale.setDefault(...) в начале main. Надёжнее всего передавать локаль в методы параметром: тогда отчет проверяется для обеих локалей в одном запуске.

Comments

Please log in or register to have a possibility to add comment.