GNU gettext дает возможность перевода сообщений программы на другие языки во время исполнения. Внутри кода программы этот перевод включает несколько шагов, каждый из которых использует свои библиотечные функции. Когда сама программа должным образом подготовлена, несколько утилит на уровне оболочки дают возможность подготовить переводы на другие языки. Каждый такой перевод называется списком сообщений (message catalog).
13.3.1. Установка текстового домена: textdomain()
Законченное приложение может содержать множество компонентов: отдельные исполняемые файлы, написанные на С или C++ или на языках сценариев, которые также могут получить доступ к возможностям gettext, таких, как gawk или оболочка Bash Все компоненты приложения разделяют один и тот же текстовый домен, который является строкой, уникально идентифицирующей приложение. (Примерами могут быть «gawk» или «coreutils»; первое является простой программой, а последнее — целым набором программ.) Текстовый домен устанавливается функцией textdomain():
#include <libintl.h> /* GLIBC */
char* textdomain(const char *domainname)
Каждый компонент должен вызывать эту функцию со строкой, указывающей на текстовый домен, в составе первоначальной инициализации в main(). Возвращаемое значение является текущим текстовым доменом. Если аргумент domainname равен NULL, возвращается текущий домен; в противном случае, он устанавливается в указанное значение, а последнее возвращается. Возвращаемое значение NULL указывает на какую-нибудь разновидность ошибки.
Если текстовый домен не установлен с помощью textdomain(), по умолчанию используется «messages».
13.3.2. Перевод сообщений: gettext()
Следующим после установки текстового домена шагом является использование функции gettext() (или ее разновидности) для каждой строки, которая должна быть переведена. Несколько функций предоставляют службы перевода:
#include <libintl.h> /* GLIBC */
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 <stdio.h>
#include <locale.h>
#include <libintl.h>
int main(void) {
setlocale(LC_ALL, "");
printf("%s\n", gettext("hello, world"));
return 0;
}
Хотя сообщение является простой строкой, мы не используем ее непосредственно в форматирующей строке printf(), поскольку в общем перевод может содержать символы %.
Вскоре, в разделе 13.3.4 «Упрощение использования gettext()», мы увидим, как облегчить использование gettext() в крупномасштабных, реальных программах.
13.3.3. Работа с множественными числами: ngettext()
Перевод во множественном числе доставляет дополнительные трудности. Простой код мог бы выглядеть примерно так:
printf("%d word%s misspelled\n", nwords, nwords > 1 ? "s" : "");
/* или */
printf("%d %s misspelled\n", nwords, nwords == 1 ? "word" : "words").
Это подходит для английского языка, но перевод становится трудным. Во-первых, во многих языках множественное число не образуется с такой же легкостью, как в английском (добавлением суффикса s для большинства слов). Во-вторых, во многих языках, особенно в Восточной Европе, имеются несколько форм множественного числа, каждая из которых указывает на то, сколько объектов обозначает форма. Соответственно даже код наподобие следующего не будет достаточным:
if (nwords == l)
printf("one word misspelled\n");
else
printf("%d words misspelled\n", nwords);
Решением является параллельный набор процедур специально для форматирования множественных значений:
#include <libintl.h> /* GLIBC */
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] Функция ngettext() (и ее варианты) проверяет n и на основании спецификации в списке сообщений возвращает соответствующий перевод msgid. Если в списке нет перевода для msgid, или находясь в локали «С», ngettext() возвращает msgid, если 'n == 1'; в противном случае она возвращает msgid_plural. Таким образом, наш пример ошибочных слов выглядит следующим образом:
145
Подробности приведены в документации GNU gettext. Здесь мы концентрируемся на потребностях разработчика, а не переводчика —