Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 42 (всего у книги 55 страниц)
Только что освещенные интерфейсы стандартной библиотеки С решают простые части проблемы локализации. Для денежных, числовых значений, значений времени и даты, также, как для проблем сортировки строк, применяется управление посредством таблиц специфичных для локали данных (таких, как списки названий месяцев и дней).
Однако, большая часть взаимодействия пользователя с текстовой программой осуществляется в виде выводимых сообщений, таких, как приглашения или сообщения об ошибках. Проблема заключается в необходимости избежания множества версий одной и той же программы, которые отличаются лишь содержанием строк сообщений. Решением де-факто в мире GNU является GNU gettext
. (GNU программы сталкиваются с подобными проблемами с элементами меню; обычно у каждого большого инструментария пользовательского интерфейса свой способ решения этой проблемы.)
GNU gettext
дает возможность перевода сообщений программы на другие языки во время исполнения. Внутри кода программы этот перевод включает несколько шагов, каждый из которых использует свои библиотечные функции. Когда сама программа должным образом подготовлена, несколько утилит на уровне оболочки дают возможность подготовить переводы на другие языки. Каждый такой перевод называется списком сообщений (message catalog).
textdomain()
Законченное приложение может содержать множество компонентов: отдельные исполняемые файлы, написанные на С или C++ или на языках сценариев, которые также могут получить доступ к возможностям gettext
, таких, как gawk
или оболочка Bash Все компоненты приложения разделяют один и тот же текстовый домен, который является строкой, уникально идентифицирующей приложение. (Примерами могут быть «gawk
» или «coreutils
»; первое является простой программой, а последнее – целым набором программ.) Текстовый домен устанавливается функцией textdomain()
:
#include
char* textdomain(const char *domainname)
Каждый компонент должен вызывать эту функцию со строкой, указывающей на текстовый домен, в составе первоначальной инициализации в main()
. Возвращаемое значение является текущим текстовым доменом. Если аргумент domainname
равен NULL
, возвращается текущий домен; в противном случае, он устанавливается в указанное значение, а последнее возвращается. Возвращаемое значение NULL
указывает на какую-нибудь разновидность ошибки.
Если текстовый домен не установлен с помощью textdomain()
, по умолчанию используется «messages
».
gettext()
Следующим после установки текстового домена шагом является использование функции gettext()
(или ее разновидности) для каждой строки, которая должна быть переведена. Несколько функций предоставляют службы перевода:
#include
char *gettext(const char *msgid);
char *dgettext(const char *domainname, const char *msgid);
char *dcgettext(const char *domainname, const char *msgid, int category);
Аргументы, используемые в этих функциях, следующие:
const char *msgid
Переводимая строка. Она действует в качестве ключа к базе данных переводов.
const char *domainname
Текстовый домен, из которого нужно получить перевод. Таким образом, хотя main()
вызвала textdomain()
для установки собственного домена приложения, сообщения могут быть получены из других текстовых доменов. (Это наиболее применимо к сообщениям, которые могли бы быть, например, в текстовом домене библиотеки от третьей стороны.)
int category
Одна из описанных ранее категорий доменов (LC_TIME
и т.п.). Доменом по умолчанию является то, что было раньше установлено с помощью textdomain()
(«messages
», если textdomain()
никогда не вызывалась). Категорией по умолчанию является LC_MESSAGES
. Предположим, main()
делает следующий вызов:
textdomain("killerapp");
Тогда 'gettext("my message")
' эквивалентно 'dgettext("killerapp", "my message")
'. Обе функции, в свою очередь, эквивалентны 'dcgettext("killerapp", "my message", LC_MESSAGES)
'.
В 99,9% времени бывает нужно использовать gettext()
. Однако, другие функции обеспечивают гибкость при работе с другими текстовыми доменами или категориями локалей. Скорее всего, эта гибкость потребуется при программировании библиотек, поскольку автономная библиотека почти наверняка будет использовать свой собственный текстовый домен.
Все функции возвращают строки. Строка является либо переводом данного msgid
, либо, если перевода не существует, первоначальной строкой. Таким образом, всегда имеется какой-нибудь вывод, даже если это первоначальное сообщение (предположительно на английском). Например:
/* Каноническая первая программа, локализованная версия. */
#include
#include
#include
int main(void) {
setlocale(LC_ALL, "");
printf("%sn", gettext("hello, world"));
return 0;
}
Хотя сообщение является простой строкой, мы не используем ее непосредственно в форматирующей строке printf()
, поскольку в общем перевод может содержать символы %
.
Вскоре, в разделе 13.3.4 «Упрощение использования gettext()
», мы увидим, как облегчить использование gettext()
в крупномасштабных, реальных программах.
ngettext()
Перевод во множественном числе доставляет дополнительные трудности. Простой код мог бы выглядеть примерно так:
printf("%d word%s misspelledn", nwords, nwords > 1 ? "s" : "");
/* или */
printf("%d %s misspelledn", nwords, nwords == 1 ? "word" : "words").
Это подходит для английского языка, но перевод становится трудным. Во-первых, во многих языках множественное число не образуется с такой же легкостью, как в английском (добавлением суффикса s
для большинства слов). Во-вторых, во многих языках, особенно в Восточной Европе, имеются несколько форм множественного числа, каждая из которых указывает на то, сколько объектов обозначает форма. Соответственно даже код наподобие следующего не будет достаточным:
if (nwords == l)
printf("one word misspelledn");
else
printf("%d words misspelledn", nwords);
Решением является параллельный набор процедур специально для форматирования множественных значений:
#include
char *ngettext(const char *msgid, const char *msgid_plural,
unsigned long int n);
char *dngettext(const char *domainname, const char *msgid,
const char *msgid_plural, unsigned long int n);
char *dcngettext(const char *domainname, const char *nmgid,
const char *msgid_plural, unsigned long int n,
int category)
Помимо первоначального аргумента msgid
, эти функции принимают дополнительные аргументы:
const char *msgid_plural
Строка по умолчанию для использования в качестве множественного числа. Вскоре будут примеры.
unsigned long int n
Число имеющихся элементов.
Список сообщений каждой локали указывает, как переводить множественные числа.[145]145
Подробности приведены в документации GNU gettext
. Здесь мы концентрируемся на потребностях разработчика, а не переводчика – Примеч. автора.
[Закрыть] Функция ngettext()
(и ее варианты) проверяет n
и на основании спецификации в списке сообщений возвращает соответствующий перевод msgid
. Если в списке нет перевода для msgid
, или находясь в локали «С», ngettext()
возвращает msgid
, если 'n == 1
'; в противном случае она возвращает msgid_plural
. Таким образом, наш пример ошибочных слов выглядит следующим образом:
printf("%sn", ngettext("%d word misspelled", "%d words misspelled", nwords), nwords);
Обратите внимание, что nwords
должен быть передан ngettext()
для выбора форматирующей строки, а затем printf()
для форматирования. Вдобавок, будьте осмотрительны и не используйте макрос или выражение, значение которого каждый раз изменяется, как в случае 'n++
'! Такое может случиться, если вы осуществляете глобальное редактирование, добавляя вызовы ngettext()
и не обращая на это внимания.
gettext()
Вызов gettext()
в исходном коде программы служит двум целям. Во-первых, он осуществляет перевод во время исполнения, что является в конце концов главным. Однако, он служит также для отметки строк, которые нужно перевести. Утилита xgettext
читает исходный код программы и извлекает все оригинальные строки, которые нужно перевести. (Далее в главе мы кратко рассмотрим это.)
Рассмотрим все-таки случай, когда статические строки не используются непосредственно:
static char *copyrights[] = {
"Copyright 2004, Jane Programmer",
"Permission is granted ...",
/* ... Здесь куча легальностей */
NULL
};
void copyright(void) {
int i;
for (i = 0; copyrights[i] != NULL, i++)
printf("%sn", gettext(copyrights[i]));
}
Здесь мы хотели бы иметь возможность вывести переводы строк об авторских правах, если они доступны. Однако, как извлекающее устройство xgettext
предполагает найти эти строки? Мы не можем заключить их в вызовы gettext()
, поскольку это не будет работать во время компиляции:
/* ПЛОХОЙ КОД: не будет компилироваться */
static char *copyrights[] = {
gettext("Copyright 2004, Jane Programmer"),
gettext("Permission is granted ..."),
/* ... Здесь куча легальностей */
NULL
};
gettext.h
"Здесь мы предполагаем, что вы хотите написать программу, которая может использоваться вместе с библиотекой GNU gettext
на любой системе Unix, а не только GNU/Linux. Следующий раздел описывает, что сделать для программ только для GNU/Linux.
Пометка строк включает два шага. Первый заключается в использовании вспомогательного заголовка gettext.h
, который поставляется с дистрибутивом GNU gettext
. Этот файл обрабатывает несколько проблем переносимости и компиляции, упрощая использование gettext()
в ваших собственных программах:
#define ENABLELNLS 1 /* ENABLE_NLS должен быть true, чтобы gettext() работала */
#include "gettext.h" /* Вместо
Если макрос ENABLE_NLS
не определен[146]146
Этот макрос обычно определяется автоматически программой configure
, либо в специальном заголовке, либо в командной строке компилятора configure
создается с помощью Autoconf и Automake – Примеч. автора.
[Закрыть] или установлен в ноль, gettext.h
развертывает вызовы gettext()
в первый аргумент. Это делает возможным перенос кода, использующего gettext()
, на системы, в которых не установлены ни GNU gettext
, ни собственная их версия. Помимо прочего, этот заголовочный файл определяет следующий макрос:
/* Вызов псевдофункции, который служит в качестве маркера для
автоматического извлечения сообщений, но не осуществляющий вызов
gettext(). Перевод времени исполнения осуществляется в другом
месте кода. Аргумент String должен быть символической строкой.
Сцепленные строки и другие строковые выражения не будут работать.
Разворачивание макроса не параметризовано, так что он подходит для
инициализации статических переменных 'char[]' или 'const char[]'. */
#define gettext_noop(String) String
Комментарий самодостаточен. С помощью этого макроса мы можем теперь перейти ко второму шагу. Мы перепишем код следующим образом:
#define ENABLE_NLS 1
#include "gettext.h"
static char copyrights[] =
gettext_noop("Copyright 2004, Jane Programmern"
"Permission is granted ...n"
/* ... Здесь куча легальностей */
"So there.");
void copyright(void) {
printf("%sn", gettext(copyrights));
}
Обратите внимание, что мы сделали два изменения. Во-первых, copyrights
теперь является одной длинной строкой, созданной с использованием возможности конкатенации строк стандартного C. Эта простая строка затем включена в вызов gettext_noop()
. Нам нужна одна строка, чтобы легальности могли быть переведены в виде одного элемента
Второе изменение заключается в непосредственном выводе перевода в виде одной строки в copyright()
.
К этому времени вы, возможно, думаете: «Вот здорово, набирать каждый раз 'gettext(...)
' довольно неприятно». Ну, вы правы. Это не только создает лишнюю работу по набиванию, но также и затрудняет чтение исходного кода. Соответственно, когда вы используете заголовочный файл gettext.h
, руководство GNU gettext
рекомендует включить два других макроса с именами _()
и N_()
следующим образом:
#define ENABLE_NLS 1
#include "gettext.h"
#define _(msgid) gettext(msgid)
#define N_(msgid) msgid
Такой подход снижает накладные расходы по использованию gettext()
всего лишь тремя дополнительными символами для переводимой строковой константы и всего лишь четырьмя символами для статических строк:
#include
#define ENABLE_NLS 1
#include "gettext.h"
#define _(msgid) gettext(msgid)
#define N_(msgid) msgid
...
static char copyrights[] =
N_("Copyright 2004, Jane Programmern"
"Permission is granted ...n"
/* ... Здесь куча легальностей */
"So there.");
void copyright(void) {
printf("%sn", gettext(copyrights));
}
int main(void) {
setlocale(LC_ALL, ""); /* gettext.h gets
printf("%sn", _("hello, world"));
copyright();
exit(0);
}
Эти макросы скромны, и на практике все GNU программы, использующие GNU gettext
, следуют этому соглашению. Если вы собираетесь использовать GNU gettext
, вам тоже нужно следовать этому соглашению.
Для программ, которые будут использоваться лишь на системах с GLIBC, использование заголовочных файлов и макросов похоже, но проще:
#include
#include
#define _(msgid) gettext(msgid)
#define N_(msgid) msgid
/* ... все остальное то же ... */
Как мы видели ранее, заголовочный файл
объявляет gettext()
и другие функции. Вам все равно нужно определять _()
и N_()
, но не нужно беспокоиться о ENABLE_NLS
или включении с исходным кодом вашей программы файла gettext.h
.
printf()
Иногда при переводах порядок слов, естественный для английского языка, не подходит в других языках. Например, на английском прилагательные идут перед определяемыми существительными, а на многих других языках – после. Таким образом, следующий код представляет проблему:
char *animal_color, *animal;
if (...) {
animal_color = _("brown");
animal = _("cat");
} else if (...) {
...
} else {
...
}
printf(_("the %s %s looks at you enquiringly.n"), animal_color, color);
Здесь форматирующая строка, animal_color
и animal
неудачно включены в вызов gettext()
. Однако, после перевода утверждение будет неверным, поскольку порядок аргументов не может быть изменен во время исполнения.
Чтобы обойти это, версия семейства printf()
POSIX (но не ISO С) допускает использовать в описателе формата указатель положения. Он принимает форму десятичного числа, за которым следует символ $
, сразу после начального символа %
. Например printf("%2$s, %1sn", "world", "hello")
;
Указатель положения обозначает аргумент из списка, который следует использовать, отсчет начинается с 1 и не включает саму форматирующую строку. Этот пример выводит знаменитое сообщение 'hello, world
' в правильном порядке.
GLIBC и Solaris реализуют эту возможность. Поскольку это часть POSIX, если printf()
вашего поставщика Unix не реализует ее, она вскоре должна появиться.
За указателем положения могут следовать любые обычные флаги printf()
, указатели ширины полей и точности. Вот правила для использования указателей положения:
• Форма с указателем положения не может смешиваться с формой без нее. Другими словами, или каждый указатель формата включает указатель положения, или ни один его не включает. Конечно, %%
может использоваться всегда.
• Если в форматирующей строке используется N-й аргумент, в этой строке должны использоваться также все аргументы до N. Соответственно, следующее неверно printf("%3$s %1$sn", "hello", "cruel", "world")
;
• Ссылка на определенный аргумент может быть сделана указателем положения несколько раз. Не позиционные спецификаторы формата всегда движутся через список аргументов последовательно.
Эта возможность не предназначена для непосредственного использования программистами приложений, она скорее для переводчиков. Например, перевод предыдущей форматирующей строки, "The %s %s looks at you enquiringly.n"
, на французский мог бы быть:
"Le %2$s %1$s te regarde d'un aire interrogateur.n"
(Даже этот перевод не совершенен: артикль «Le» имеет род. Подготовка программы к переводу трудная задача!)
Коллекция сообщений в программе называется списком сообщений (message catalog). Этот термин применяется также к каждому из переводов сообщений на другой язык. Когда программа установлена, каждый перевод также устанавливается в стандартное место, где gettext()
может во время исполнения найти нужный перевод.
Может оказаться полезным разместить переводы не в стандартном, а в другом каталоге, особенно для тестирования программы. Особенно на больших системах, у обычного пользователя может не быть необходимых разрешений для установки файлов в системные каталоги. Функция bindtextdomain()
дает gettext()
альтернативное место для поиска переводов:
#include
char *bindtextdomain(const char *domainname,
const char *dirname);
Полезные каталоги включают '.
' для текущего каталога и /tmp
. Может оказаться удобным также получить каталог из переменной окружения, подобно этому:
char *td_dir;
setlocale(LC_ALL, "");
textdomain("killerapp");
if ((td_dir = getenv("KILLERAPP_TD_DIR")) != NULL)
bindtextdomain("killerapp", td_dir);
bindtextdomain()
должна быть вызвана до вызовов любой из функций из семейства gettext()
. Мы увидим пример ее использования в разделе 13.3.8 «Создание переводов»
К настоящему моменту мы рассмотрели все компоненты, из которых состоит интернационализированная программа. Данный раздел подводит итоги.
1. Включите в свое приложение заголовочный файл gettext.h
, добавьте определения для макросов _()
и N_()
в заголовочный файл, который включается во все ваши исходные файлы на С. Не забудьте определить именованную константу ENABLE_NLS
.
2. Вызовите соответствующим образом setlocale()
. Проще всего вызвать 'setlocale(LC_ALL, "")
', но иногда приложению может потребоваться быть более разборчивым в отношении используемых категорий локали.
3. Выберите для приложения текстовый домен и установите его с помощью textdomain()
.
4. При тестировании свяжите текстовый домен с определенным каталогом при помощи bindtextdomain()
.
5. Используйте соответствующим образом strfmon()
, strftime()
и флаг '
. Если нужна другая информация о локали, используйте nl_langinfo()
, особенно в сочетании с strftime()
.
6. Пометьте все строки, которые должны быть переведены, соответствующими вызовами _()
или N_()
.
Хотя некоторые не следует так помечать. Например, если вы используете getopt_long()
(см. раздел 2.1.2 «Длинные опции GNU»), вы, вероятно, не захотите, чтобы имена длинных опций были помечены для перевода. Не требуют перевода и простые форматирующие строки наподобие "%d %dn
", также как отладочные сообщения.
7. В нужных местах используйте ngettext()
(или ее варианты) для значений, которые могут быть 1 или больше 1.
8. Упростите жизнь для своих переводчиков, используя строки с полными предложениями вместо замены слов с помощью %s
и ?:
. Например:
if (/* возникла ошибка */) { /* ВЕРНО */
/* Использовать несколько строк для упрощения перевода. */
if (input_type == INPUT_FILE)
fprintf(stderr, _("%s: cannot read file: %sn"),
argv[0], strerror(errno));
else
fprintf(stderr, _("%s: cannot read pipe: %sn"),
argv[0], strerror(errno));
Это лучше, чем
if (/* возникла ошибка */) { /* НЕВЕРНО */
fprintf(stderr, _("%s: cannot read %s: %sn"), argv[0],
input_type == INPUT_FILE ? _("file") : _("pipe"),
strerror(errno));
}
Как только что показано, хорошей мыслью является включение комментария, сообщающего о намеренном использовании нескольких строк, чтобы упростить перевод сообщений.
После интернационализации программы необходимо подготовить переводы. Это осуществляется с помощью нескольких инструментов уровня оболочки. Мы начнем с интернационализированной версии ch06-echodate.c
из раздела 6.1.4 «Преобразование разложенного времени в time_t
»:
/* ch13-echodate.c – демонстрация переводов */
#include
#include
#include
#define ENABLE_NLS 1
#include "gettext.h"
#define _(msgid) gettext(msgid)
#define N_(msgid) msgid
int main (void) {
struct tm tm;
time_t then;
setlocale(LC_ALL, "");
bindtextdomain("echodate", ".");
textdomain("echodate");
printf("%s", _("Enter a Date/time as YYYY/MM/DD HH:MM:SS : "));
scanf("%d/%d/%d %d:%d:%d",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
/* Проверка ошибок для краткости опущена. */
tm.tm_year -= 1900;
tm.tm_mon -= 1;
tm.tm_isdst = -1; /* О летнем времени ничего не известно */
then = mktime(&tm);
printf(_("Got: %s"), ctime(&then));
exit(0);
}
Мы намеренно использовали "gettext.h"
, а не
. Если наше приложение поставляется с отдельной копией библиотеки gettext
, тогда "gettext.h"
найдет ее, избежав использования системной копии. С другой стороны, если имеется лишь системная копия, она будет найдена, если локальной копии нет. Общеизвестно, что ситуация усложнена фактом наличия на системах Solaris библиотеки gettext
, которая не имеет всех возможностей версии GNU.
Переходя к созданию переводов, первым шагом является извлечение переводимых строк. Это осуществляется программой xgettext
:
$ xgettext –keyword=_ –keyword=N_
> –default-domain=echodate ch13-echodate.с
Опции –keyword
сообщает xgettext
, что нужно искать макросы _()
и N_()
. Программа уже знает, как извлекать строки из gettext()
и ее вариантов, а также из gettext_noop()
.
Вывод xgettext
называется переносимым объектным файлом. Имя файла по умолчанию messages.ро
, что соответствует текстовому домену по умолчанию "messages"
. Опция –default-domain
обозначает текстовый домен для использования в имени выходного файла. В данном случае, файл назван echodate.ро
. Вот его содержание:
# SOME DESCRIPTIVE TITLE. /* Шаблон, нужно отредактировать */
# Copyright (С) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR
#
#, fuzzy
msgid "" /* Подробная информация */
msgstr "" /* Заполняет каждый переводчик */
"Project-Id-Version: PACKAGE VERSIONn"
"Report-Msgid-Bugs-To: n"
"POT-Creation-Date: 2003-07-14 18:46-0700n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONEn"
"Last-Translator: FULL NAME
"Language-Team: LANGUAGE
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=CHARSETn"
"Content-Transfer-Encoding: 8bitn"
#: ch13-echodate.c:19 /* Местоположение сообщения */
msgid "Enter a Date/time as YYYY/MM/DD HH:MM:SS : " /* Оригинальное
сообщение */
msgstr "" /* Здесь перевод */
#: ch13-echodate.с:32 /* To же самое для каждого сообщения */
#, с-format
msgid "Got: %s"
msgstr ""
Этот первоначальный файл используется повторно для каждого перевода. Таким образом, это шаблон для переводов, и по соглашению, для отображения этого факта он должен быть переименован с расширением .pot
(portable object template – переносимый объектный шаблон):
$ mv echodate.ро echodate.pot
He владея свободно несколькими языками, мы решили перевести сообщения на свинский латинский. Следующим шагом является создание перевода. Это осуществляется копированием файла шаблона и добавлением к новой копии перевода:
$ cp echodate.pot piglat.po
$ vi piglat.po /* Добавить переводы, используя любимый редактор */
Имя по соглашению должно быть язык.po
, где язык
является стандартным международным сокращением из двух или трех букв для обозначения языка. Иногда используется форма язык_страна.po
: например, pt_BR.po
для португальского в Бразилии. Поскольку свинский латинский не является настоящим языком, мы назвали файл piglat.ро
.[147]147
Pig – свинья, поросенок (англ.) – Примеч. перев.
[Закрыть] Вот содержание после добавления перевода:
# echodate translations into pig Latin
# Copyright (C) 2004 Prentice-Hall
# This file is distributed under the same license as the echodate package.
# Arnold Robbins
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: echodate 1.0n"
"Report-Msgid-Bugs-To: [email protected]"
"POT-Creation-Date: 2003-07-14 18:46-0700n"
"PO-Revision-Date: 2003-07-14 19:00+8n"
"Last-Translator: Arnold Robbins
"Language-Team: Pig Latin
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=ASCIIn"
"Content-Transfer-Encoding: 8bitn"
#: ch13-echodate.с:19
msgid "Enter a Date/time as YYYY/MM/DD HH:MM:SS : "
msgstr "Enteray A Ateday/imetay asay YYYY/MM/DD HH:MM:SS : "
#: ch13-echodate.c:32
#, c-format
msgid "Got: %s"
msgstr "Otgay: %s"
Хотя можно было бы произвести линейный поиск в переносимом объектном файле, такой поиск был бы медленным. Например, в gawk
имеется примерно 350 отдельных сообщений, а в GNU Coreutils – свыше 670. Линейный поиск в файле с сотнями сообщений был бы заметно медленным. Поэтому GNU gettext
использует для быстрого поиска сообщений двоичный формат. Сравнение осуществляет msgfmt
, выдавая объектный файл сообщений:
$ msgfmt piglat.po -о piglat.mo
При сопровождении программы изменяются строки, используемые программой: добавляются новые, другие удаляются или изменяются. По крайней мере, может измениться положение строки в исходном файле. Таким образом, файлы переводов .ро
, вероятно, устареют. Программа msgmerge
объединяет старые файлы переводов с новым файлом .pot
. Затем результат может быть обновлен. Этот пример выполняет объединение и повторное компилирование:
$ msgmerge piglat.po echodate.pot -o piglat.new.po /* Объединить файлы */
$ mv piglat.new.po piglat.po /* Переименовать результат */
$ vi piglat.po /* Модернизировать перевод */
$ msgfmt piglat.po -o piglat.mo /* Восстановить файл .mo */
Откомпилированные файлы .mo
помещаются в файл base/locale/category/textdomain.mo
. На системах GNU/Linux base
является /usr/share/locale
. locale
является обозначением языка, например, 'es
', 'fr
' и т.д. category
является категорией локали; для сообщений это LC_MESSAGES
. textdomain
является текстовым доменом программы, в нашем случае это echodate
. В качестве реального примера в /usr/share/locale/es/LC_MESSAGES/coreutils.mo
находится перевод GNU Coreutils на испанский.
Функция bindtextdomain()
изменяет в местоположении часть base
. В ch13-echodate.c
мы меняем ее на '.
'. Таким образом, нужно создать соответствующие каталоги и поместить туда перевод на свинский латинский:
$ mkdir -р en_US/LC_MESSAGES /* Нужно использовать реальную локаль */
$ cp piglat.mo en_US/LC_MESSAGES/echodate.mo /* Поместить файл в нужное место */
Должна использоваться реальная локаль[148]148
Мы тщетно потратили 30 или 45 минут, пытаясь использовать каталог piglat/LC_MESSAGES
и установку LC_ALL=piglat
' без всякого успеха, пока не выяснили это – Примеч. автора.
[Закрыть]; мы «притворяемся» использующими "en_US
". Разместив перевод, устанавливаем соответствующим образом LC_ALL
, скрещиваем пальцы и запускаем программу:
$ LC_ALL=en_US ch13-echodate /* Запуск программы */
Enteray A Ateday/imetay asay YYYY/MM/DD HH:MM:SS : 2003/07/14 21:19:26
Otgay: Mon Jul 14 21:19:26 2003
Последнюю версию GNU gettext
можно найти в каталоге дистрибутива GNU gettext
.[149]149
ftp://ftp.gnu.org/gnu/gettext
– Примеч. автора.
[Закрыть]
Этот раздел лишь слегка коснулся поверхности процесса локализации. GNU gettext
предоставляет множество инструментов для работы с переводами, и в особенности для облегчения поддержания современности переводов по мере развития исходного кода программы. Процесс ручного обновления переводов осуществим, но утомителен. Эта задача легко автоматизируется с помощью make
; в частности, GNU gettext
хорошо интегрируется для обеспечения этой возможности с Autoconf и Automake, снимая с программиста значительный груз по разработке.
Рекомендуем прочесть документацию GNU gettext
, чтобы больше узнать как об этих проблемах в частности, так и о GNU gettext
в общем.