Linux программирование в примерах - Арнольд Роббинс
Шрифт:
Интервал:
Закладка:
Таблица 13.4. Компоненты спецификации эры
Компонент Значение Направление Символы '+' или '-'. '+' означает, что эра отсчитывается от численно меньшего года к численно большему году, а '-' означает обратный порядок Смешение Ближайший к дате начала эры год Дата начала Дата начала эры в виде 'гггг/мм/дд'. Это соответственно год, месяц и день. Годы до н.э используют для гггг отрицательные значения Дата конца Дата завершения эры в том же самом виде. Допустимы два дополнительных вида: -* означает «начало времени», а +* означает «конец времени» Название эры Название эры, соответствующее спецификации преобразования %EC функции strftime() Формат эры Формат года в пределах эры, соответствующий спецификации преобразования %EY функции strftime()Значение ALT_DIGITS также нуждается в некотором объяснении. Некоторые локали предоставляют «альтернативные цифры». (Рассмотрите арабский язык, в котором используется десятичная система счисления, но изображения для цифр 0–9 другие. Или рассмотрите гипотетическую локаль «Древнего Рима», использующую римские цифры.) Они появляются, например, в различных спецификациях преобразования %OC в функции strftime(). Возвращаемое значение для 'nl_langinfo(ALT_DIGITS)' является разделяемым точками с запятой списком строк символов для альтернативных цифр. Первая должна использоваться для 0, следующая для 1 и т.д. POSIX утверждает, что могут быть предоставлены до 100 альтернативных символов. Сущность в том, чтобы избежать ограничения локалей использованием символов цифр ASCII, когда у локали есть собственная система счисления.
Наконец, 'nl_langinfo(CRNCYSTR)' возвращает символ местной валюты. Первый символ возвращаемого значения, если это '-', '+' или '.', указывает, как должен использоваться символ:
- Символ должен быть перед значением.
+ Символ должен быть после значения.
. Символ должен заменить символ основания (разделитель десятичной дроби).
13.3. Динамический перевод сообщений программ
Только что освещенные интерфейсы стандартной библиотеки С решают простые части проблемы локализации. Для денежных, числовых значений, значений времени и даты, также, как для проблем сортировки строк, применяется управление посредством таблиц специфичных для локали данных (таких, как списки названий месяцев и дней).
Однако, большая часть взаимодействия пользователя с текстовой программой осуществляется в виде выводимых сообщений, таких, как приглашения или сообщения об ошибках. Проблема заключается в необходимости избежания множества версий одной и той же программы, которые отличаются лишь содержанием строк сообщений. Решением де-факто в мире GNU является GNU gettext. (GNU программы сталкиваются с подобными проблемами с элементами меню; обычно у каждого большого инструментария пользовательского интерфейса свой способ решения этой проблемы.)
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("%sn", gettext("hello, world"));
return 0;
}
Хотя сообщение является простой строкой, мы не используем ее непосредственно в форматирующей строке printf(), поскольку в общем перевод может содержать символы %.
Вскоре, в разделе 13.3.4 «Упрощение использования gettext()», мы увидим, как облегчить использование gettext() в крупномасштабных, реальных программах.
13.3.3. Работа с множественными числами: ngettext()
Перевод во множественном числе доставляет дополнительные трудности. Простой код мог бы выглядеть примерно так:
printf("%d word%s misspelledn", nwords, nwords > 1 ? "s" : "");
/* или */
printf("%d %s misspelledn", nwords, nwords == 1 ? "word" : "words").
Это подходит для английского языка, но перевод становится трудным. Во-первых, во многих языках множественное число не образуется с такой же легкостью, как в английском (добавлением суффикса s для большинства слов). Во-вторых, во многих языках, особенно в Восточной Европе, имеются несколько форм множественного числа, каждая из которых указывает на то, сколько объектов обозначает форма. Соответственно даже код наподобие следующего не будет достаточным: