Локализация - dim13/lor GitHub Wiki
Существует множество способов локализации своих приложений. Наиболее
распространенным является использование gettext
, но бывают случаи,
когда необходим более простой способ локализации, когда перевод "вшит" в
бинарник (и когда языков немного).
Для локализации простого двуязычного приложения (например, CGI) можно использовать статическую локализацию. Суть такой локализации в том, что заранее отдельно создаются строковые массивы, содержащие фразу на обоих языках. В зависимости от языка пользователя из массива выбирается фраза с нужным индексом.
Определим макросы _LANG
и _L
, которые позволяют выбрать нужный текст
в зависимости от текущего языка. Язык определяется переменной Lang
:
#define _LANG(_var, _ru, _en) char _var##ru[] = _ru;\
char _var##en[] = _en;\
char *_var[2] = {_var##ru, _var##en};
#define _L(x) (x[Lang])
unsigned char Lang = 1; // по умолчанию - английский
Макрос _LANG
инициализирует очередной массив с именем _var
двумя
строками - следующими аргументами. Чтобы вывести нужный текст,
используется макрос _L
, который просто выводит нужную строку из
массива, в зависимости от языка. Вот пример использования этого метода:
char *ptr = getenv("HTTP_ACCEPT_LANGUAGE");
if(ptr && strncmp(ptr, "ru", 2) == 0) Lang = 0;
...
printf("%s:<input ... >\n", _L(_s_Name_));
Отдельно необходимо будет заполнить массив с фразами на русском и
английском языке:
_LANG(_s_Name_, "Ваше имя", "Your name");
Достоинства этого метода: в одном бинарнике содержится все, т.е. меньше шансов при переносе CGI из одного хранилища в другое "потерять" локализацию. Минусы тоже очевидны: приходится отдельно подготавливать строковые массивы, а в месте использования правильно указать название массива. Еще один минус: для добавления еще одного языка придется править исходники и перекомпилировать приложение.
Gettext - стандартный способ локализации. В отличие от статической
локализации он требует наличия еще как минимум одного
дополнительного файла (mo-файл), в котором содержится база
данных локализации для функции gettext
. Т.к. локализация лежит
отдельно, ее всегда можно подправить, не влезая в нутро
исходников программы. Однако, при работе с gettext
'ом выбор
нужной фразы производится в реальном времени: для отображения фразы в
соответствии с настройками локали, в базе данных для текущей локали
ищется английская фраза (запакованная в макрос gettext
). Если есть
фраза на нужном языке, она выводится gettext
'ом, иначе - отображается
фраза на английском.
Средства работы с gettext
автоматизированы: утилита xgettext
выбирает из исходников нужные фразы (оформленные определенным
макросом) и помещает их в po-файл, а утилита poedit
позволяет
редактировать po-файлы (кроме того, их можно редактировать
вручную).
База данных (mo-файл) для каждой локали формируется при помощи утилиты
msgfmt из текстового po-файла (который, кстати, тоже отдельный для
каждой локали). Так как в процессе разработки зачастую приходится
к уже имеющемуся po-файлу с переводом добавлять новые фразы, есть
удобная утилита msgmerge, добавляющая лишь новые, не переведенные,
фразы. Естественно, в cmake
есть макросы для работы с gettext
, так
что разработчкику все сделать достаточно просто.
Чтобы было удобно работать с xgettext
, необходимо определить какие-то
простые макросы для gettext
, например:
#define _(String) gettext(String)
#define gettext_noop(String) String
#define N_(String) gettext_noop(String)
В этом случае, например, фраза printf(_("Capture frame %d\n"), j);
при
запуске xgettext
с обозначением разделителей как -k_ -kN_
создаст
po-файл, в котором будут строки
msgid "Capture frame %d\n"
msgstr ""
msgid
- идентификатор нашего сообщения (т.е. само сообщение на
английском), а msgstr
- это же сообщение на другом языке.
Подготовив такой файл, переведя его и сформировав из него при помощи
msgfmt
базу данных для нашей локали, мы сможем видеть перевод. Но для
этого еще необходимо будет указать gettext
'у, где искать mo-файл. Это
делается при помощи функций
bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
textdomain(GETTEXT_PACKAGE);
Макросы GETTEXT_PACKAGE
и LOCALEDIR
определяются обычно при
подготовке компиляции (например, тем же cmake
). LOCALEDIR
-
путь к директории, в которой лежат файлы локализации (если мы хотим
русифицировать приложение, в директории LOCALEDIR
должна быть
вложена директория ru/LC_MESSAGES
, где ru
- нужный нам язык,
LC_MESSAGES
- сами сообщения (по значению этой переменной и
определяется язык, кстати).
Чтобы оформить все это в CMakeLists.txt
, достаточно указать, что нам
необходимо подключить нужные библиотеки для работы gettext
и найти
xgettext
(если po-файл еще не сформирован):
find_package(Gettext REQUIRED)
find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext)
Введем макросы:
-
PO_FILE
- po-файл, формируемыйxgettext
; -
RU_FILE
- переведенный po-файл (в который будут добавляться новые данные при помощи msgmerge); -
MO_FILE
- mo-файл (т.е. сама база данных - только он и нужен в конечном итоге).
Тогда для генерирования po- и mo-файлов "на лету" нам нужно дописать в
CMakeLists.txt
:
add_custom_command(
OUTPUT ${PO_FILE}
COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} --from-code=koi8-r ${SOURCES} -c -k_ -kN_ -o ${PO_FILE}
COMMAND sed 's/charset=UTF-8/charset=koi8-r/' ${PO_FILE} | enconv > tmp && mv -f tmp ${PO_FILE}
COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} -Uis ${RU_FILE} ${PO_FILE}
DEPENDS ${SOURCES})
add_custom_command(
OUTPUT ${MO_FILE}
COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} ${RU_FILE} -o ${MO_FILE}
DEPENDS ${RU_FILE})
(локаль у меня - КОИ8, поэтому необходимо указать входную локаль
для xgettext
'а, а также подкорректировать неправильное отображение
локали в формируемом po-файле.
- Облегчение работы переводчика
Ключ
-c
позволяет в po-файл добавлять строку с комментариями, расположенными до строки, где встречается макрос, указанный в аргументе ключа-k
. Таким образом, если в комментариях сразу писать перевод на русский язык, можно даже будет автоматизировать создание готового po-файла с переводом.
Если все выполнить правильно и не забыть скопировать mo-файл по указанному пути, то после запуска приложение должно "заговорить" на родном для вашей локали языке.