eddy_em: (Default)
[personal profile] eddy_em
Вкратце коснусь двух способов (классического и велосипедного) локализации. С небольшим разбором плюсов и минусов каждого способа.



Велосипедостроение


Когда я начал писать двуязычный (английский + русский) CGI, перво-наперво стал искать возможные методы локализации. Естественно, в основном мне попадался gettext, а для таких вещей его использовать мне показалось чересчур. Да и изучать новый инструмент не хотелось. Поэтому я сделал свой велосипед.


В основе моей локализации лежат макросы _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(_s_Name_, "Ваше имя", "Your name");

Суть такой локализации в том, что заранее отдельно создаются строковые массивы, содержащие фразу на обоих языках. Переменная Lang - просто индекс в данном массиве. Макрос _LANG инициализирует очередной массив с именем _var двумя строками - следующими аргументами. Чтобы вывести нужный текст, используется макрос _L, который просто выводит нужную строку из массива, в зависимости от языка.
Вот пример использования этого велосипеда:

char *ptr = getenv("HTTP_ACCEPT_LANGUAGE");
if(ptr) if(strncmp(ptr, "ru", 2) == 0) Lang = 0;
...
printf("%s:<input ... >\n", _L(_s_Name_));


Достоинства этого метода: в одном бинарнике содержится все, т.е. меньше шансов при переносе CGI из одного хранилища в другое "потерять" локализацию. Минусы тоже очевидны: приходится отдельно подготавливать строковые массивы, а в месте использования правильно указать название массива. Еще один минус: для добавления еще одного языка придется править исходники и перекомпилировать приложение.
Зато это мой родной велосипед и я им горжусь, несмотря на всю его кривизну :) (правда, уже больше не использую в новых приложениях).

Gettext


Gettext - стандартный способ локализации. В отличие от моего "велосипеда" он требует наличия еще как минимум одного дополнительного файла (mo-файл), в котором содержится база данных локализации для функции gettext. Т.к. локализация лежит отдельно, ее всегда можно подправить, не влезая в нутро исходников программы. Однако, при работе с gettext'ом выбор нужной фразы производится в реальном времени: для отображения фразы в соответствии с настройками локали, в базе данных для текущей локали ищется английская фраза (запакованная в макрос gettext). Если есть фраза на нужном языке, она выводится gettext'ом, иначе - отображается фраза на английском.


Средства работы с gettext, в отличие от моего велосипеда, автоматизированы: утилита xgettext "выдирает" из исходников нужные фразы (оформленные определенным макросом), а утилита 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-файл по указанному пути, то после запуска приложение должно "заговорить" на родном для вашей локали языке.


Этот подход полностью противоположен моему "велосипеду": для локализации нужно иметь отдельный файл, однако, для смены языка нет необходимости влезать во "внутренности" исходников программы: достаточно взять готовый po-файл и перевести его на другой язык. Конечно, если mo-файл будет достаточно большим, время, необходимое на поиск нужной фразы, будет несколько больше, чем при работе с моим "велосипедом".




Date: 2014-02-17 02:43 pm (UTC)
From: [identity profile] summer wine (from livejournal.com)
Добрый день! Если Вы заинтересованы в локализации web-ПО, ПО для персональных компьютеров, ПО для мобильных устройств либо иного вида программного обеспечения, я рекомендую Вам использовать этот инструмент на базе web: https://poeditor.com/
POEditor является интуитивным, хорошо проработанным инструментом, обладающим рядом полезных свойств, которые помогают организовать процесс управления переводом. Он поддерживает множество популярных форматов файлов и обладает собственным API, что обеспечивает лучшую автоматизацию. Желаю Вам больших успехов в Ваших проектах!

Date: 2016-01-04 09:18 am (UTC)
From: [identity profile] andy-pix.livejournal.com
ха!
сам когда-то писал подобный велосипед)))

а вот про gettext не слышал

Date: 2016-01-07 04:16 pm (UTC)
From: [identity profile] eddy-em.livejournal.com
> а вот про gettext не слышал

Очень странно. На ЛОРе как только спросишь, как локализовать, сразу понабегут, обосрут и скажут: «Use gettext, bro!»
Аналогично на SO (но там обсирать не будут сильно, ибо чревато; зато могут заминусовать вусмерть). Да и просто в гугле набрать "C локализация".

P.S.В данном примере очень устаревший и не очень-то хороший CMakeLists.txt приведен. Нужно поправить: вот новый, более кошерный.
Edited Date: 2016-01-07 04:20 pm (UTC)

May 2025

S M T W T F S
    123
45678910
11121314151617
1819202122 2324
25262728293031

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated May. 25th, 2025 03:41 am
Powered by Dreamwidth Studios