Почему ваш C++ код должен заговорить на всех языках мира?
Представьте: вы написали идеальный парсер или высокопроизводительный сервер на C++. Он летает, жрет минимум памяти, но выводит сообщения только на русском. А завтра ваш продукт покупает японская корпорация. Что делать? Переписывать весь вывод? Вставлять сотни if (lang == "jp")? Это путь в ад.
Встречайте интернационализацию C++ (или просто i18n C++ — от слова "internationalization", где между i и n 18 букв). Это не просто "перевод кнопок", а целая философия проектирования, которая позволяет вашему приложению корректно работать с любым языком, культурой и кодировкой. Без интернационализации C++ проект обречен на локальную нишу. С ней — вы выходите на мировой рынок.
В этой статье мы разберем, как приручить локализацию C++ с помощью стандартных средств и мощных библиотек. Никакой воды — только рабочий код и реальные кейсы.
Три кита интернационализации: Locale, Encoding и Messages
Прежде чем писать код, давайте разберемся, из чего состоит интернационализация C++ на практике. Вам нужно решить три задачи:
- Локаль (Locale): Как форматировать даты, числа и валюту. В США пишут 03/15/2024, в Германии — 15.03.2024.
- Кодировка (Encoding): Как работать с Unicode. Кириллица, иероглифы, арабская вязь — все это должно храниться и выводиться без кракозябр.
- Сообщения (Messages): Как хранить и подставлять переведенные строки. "File not found" по-русски — "Файл не найден".
Игнорирование хотя бы одного пункта превратит ваше приложение в тыкву. Поехали по порядку.
Базовый уровень: Стандартная локаль и std::locale
Стандартная библиотека C++ предоставляет класс std::locale. Это ваш первый инструмент для интернационализации C++. Он умеет менять разделители дробной части и формат вывода дат.
Пример: форматирование чисел и дат
#include <iostream>
#include <locale>
#include <ctime>
#include <iomanip>
void print_date(double value) {
std::cout.imbue(std::locale("en_US.UTF-8"));
std::cout << "EN: " << value << std::endl;
std::cout.imbue(std::locale("de_DE.UTF-8"));
std::cout << "DE: " << value << std::endl;
std::cout.imbue(std::locale("ru_RU.UTF-8"));
std::cout << "RU: " << value << std::endl;
}
int main() {
double pi = 3.14159;
print_date(pi);
// Работа с датами
std::time_t t = std::time(nullptr);
std::tm* tm = std::localtime(&t);
std::cout.imbue(std::locale("en_US.UTF-8"));
std::cout << "EN date: " << std::put_time(tm, "%x") << std::endl;
std::cout.imbue(std::locale("ru_RU.UTF-8"));
std::cout << "RU date: " << std::put_time(tm, "%x") << std::endl;
return 0;
}
Важно: Названия локалей (например, "ru_RU.UTF-8") зависят от операционной системы. На Windows они могут называться "Russian_Russia.1251". Это первая боль. Стандартная локализация C++ через std::locale — это хорошо для простых задач, но для серьезных проектов лучше использовать специализированные библиотеки.
Продвинутый уровень: Boost.Locale — лучший друг интернационализатора
Если вы пишете на C++ и хотите делать интернационализацию C++ правильно — ставьте Boost.Locale. Эта библиотека решает все три проблемы: она умеет работать с Unicode, форматировать что угодно и вытаскивать переведенные строки из файлов .po и .mo (формат GNU gettext).
Установка и подключение
Boost.Locale входит в состав Boost. Подключите его в своем CMakeLists.txt:
find_package(Boost REQUIRED COMPONENTS locale)
target_link_libraries(your_project Boost::locale)
Пример 1: Правильная работа с UTF-8
Самая частая проблема — вывод Unicode в консоль. Boost.Locale решает ее за вас, генерируя правильные коды для терминала.
#include <boost/locale.hpp>
#include <iostream>
int main() {
// Создаем глобальный генератор локалей
boost::locale::generator gen;
// Задаем локаль по умолчанию (UTF-8)
std::locale loc = gen("ru_RU.UTF-8");
std::locale::global(loc);
// Теперь можем выводить любые символы
std::cout << "Привет, мир! 🌍" << std::endl;
std::cout << "Тест: こんにちは" << std::endl;
// Форматируем число по-русски
std::cout << 1234567.89 << std::endl; // Выведет: 1 234 567,89
return 0;
}
Пример 2: Извлечение переведенных строк (локализация C++)
Это сердце любой локализации C++. Вы пишете код на английском, а переводы храните в отдельных файлах.
#include <boost/locale.hpp>
#include <iostream>
int main() {
boost::locale::generator gen;
// Загружаем переводы из папки "locales"
// Файлы должны быть в формате: locales/ru/LC_MESSAGES/myapp.mo
std::locale loc = gen("ru_RU.UTF-8");
gen.add_messages_path("locales");
gen.add_messages_domain("myapp");
std::locale::global(loc);
// Функция translate() автоматически подставит перевод
std::cout << boost::locale::translate("Hello, World!") << std::endl;
std::cout << boost::locale::translate("File not found") << std::endl;
// Форматирование с подстановкой плюральных форм
int count = 5;
std::cout << boost::locale::format("You have {1} message(s)") % count << std::endl;
return 0;
}
Как это работает: Вы запускаете утилиту xgettext из пакета GNU gettext, она собирает все строки из translate() в шаблон .pot. Переводи