Глава 5. Локализация и Интернационализация

Глава 5. Локализация и Интернационализация

Основной объём этого документа занимают описания, как заставить различные программы понять кириллицу. Обычно каждая программа требует, чтобы это был её собственный метод, как правило, чрезвычайно отличный от других. Кроме того, у некоторых программ поддержка языков, отличных от английского далека от идеала, не говоря уж об их неспособности взаимодействовать, используя родной язык пользователя вместо английского.

Проблемы, перечисленные выше, сильно осложняют жизнь, так как программное обеспечение редко создаётся только для местного рынка. Переработка существенных частей программного обеспечения каждый раз при входе на новый международному рынок очень неэффективна. Интернациональная поддержка, осуществляемая собственными средствами программы, уникальным и присущим только ей способом, в терминах долгосрочного планирования далеко не блестящая идея.

Следовательно, возникает потребность в стандартизации. И такой стандарт существует.

Все связанное с вышеперечисленными проблемами разделено в соответствии c двумя базисными концепциями: localization и internationalization. Под локализацией мы имеем в виду создание программ, способных обрабатывать различные языковые соглашения для различных стран. Позвольте привести пример. Формат даты, принятый в Соединённых Штатах, имеет вид ММ/ДД/ГГ. Однако в России наиболее популярный формат — ДД.ММ.ГГ. Другие проблемы включают в себя представление времени, форматы числа и представления валюты. Кроме этого, один из наиболее важных аспектов локализации — это определение соответствующих классов символов, то есть определение, какие символы в наборе символов являются "кирпичиками"  языка (буквами) и как они упорядочиваются. С другой стороны, локализация не работает со шрифтами.

Интернационализация (или i18n для краткости), как предполагается, решает проблемы, связанные со способностью программы взаимодействовать с пользователем на его родном языке.

Обе эти концепции должны быть стандартизованы, давая программистам непротиворечивый путь создания программ, работающих в национальной среде.

Хотя стандартизация ещё в процессе, много её частей уже фактически являются стандартом, так что они могут использоваться без особых проблем.

Я опишу общую схему создания программ, использующих описанные выше возможности стандартным способом. Так как это заслуживает отдельного документа, я буду давать только очень общее описание и указатели на более полные источники.

Locale

Одно из основных понятий локализации — locale. Под locale подразумевается набор соглашений, специфических для отдельно взятого языка в отдельно взятой стране. В общем случае говорить, что locale определяется только страной, неправильно. Например, в Канаде могут быть определены два locale — язык "Канада/Английский"  и язык "Канада/Французский". Более того, язык "Канада/Английский"  не является эквивалентом языку "Великобритания/Английский"  или "Американский/Английский", точно так же "Канада/Французский"  язык — не эквивалент языку "Франция/Французский"  или языку "Швейцария / Французский"  .

Более подробное описание проблем/возможностей/достоинств локализации на русском языке можно найти на страничке Локализация, как она есть.

Locale с точки зрения пользователя

Каждая locale — это специальная база данных, определяющая, по крайней мере, следующие правила и соглашения:

  1. Классификация символов и преобразований,

  2. Представление валюты,

  3. Представление чисел (то есть Десятичные символы),

  4. Формат даты / времени.

Настройка локали

Прежде всего — подробная документация о локали имеется на www.sensi.org/~alec/locale Обращайтесь туда, если вам нужны нестандартные варианты (например, отключение русскоязычного интерфейса с сохранением правильной сортировки и т.д.)

Документацию по локали X Window можно найти по адресу www.tsu.ru/~pascal/x_locale/

Вот инструкция для нетерпеливых (только для glibc).

Вам нужно:

  • Зайти в /usr/share/locale и создать там симлинк ru_RU.KOI8-R, указывающий на ru_SU. Эта операция необходима только для glibc < 2.1.2.

  • Как-либо прописать установку переменной LANG в стартовых скриптах. В RedHat-based дистрибутивах это делается путём редактирования файла /etc/sysconfig/i18n, где, кроме прочего, должна быть строчка

    LANG=ru_RU.KOI8-R
    

    В общем случае можно прописать в /etc/profile

    LANG=<ваша кодировка>   
    export LANG
    
  • Проверить, что все работает, можно, запустив locale и посмотрев, что она выдаёт, а также набрав cal, date — названия месяцев и дней недели должны быть по-русски.

Гораздо же честнее сделать отдельный настоящий каталог: /usr/share/locale/ru_RU.KOI8-R/ (конечно, если его нет в данном дистрибутиве).

Некоторые дистрибутивы неправильно включают

   LANG=ru
   LC_ALL=ru_RU.KOI8-R

Это НЕПРАВИЛЬНО, почему так делать нельзя — описано ниже.

А теперь поговорим о том же, но гораздо подробнее. Итак:

Как включить локализацию?

Если на UNIX машине (с POSIX:1996) средства locale правильно установлены и программы правильно написаны, то локализация включается путём задания строки окружения LANG:

$ export LANG={язык}

Если такой строки окружения нет, то работает значение локализации по умолчанию: LANG="C" или LANG="POSIX" (что то же самое) — минимальный набор параметров, необходимый для функционирования программ на ANSI C (ISO 9899:1990), в кодировке US-ASCII (7 bit) (“Символы и кодировки”).

Если ваша система имеет полный набор утилит POSIX.2, то узнать установленные в системе и допустимые значения для LANG= можно командой locale:

$ locale -a

По новому стандарту (POSIX.2 приложение E) значения локализации записываются в форме:

        language_TERRITORY.Codeset

или формально:

        language[_TERRITORY[.Codeset[@modifier]]]

Стандарт ISO 639 описывает "language names", ISO 3166 — "territory names". Территории _SU более не существует (вернее, теперь она означает Судан), однако для совместимости некоторые системы продолжают её поддерживать как alias: ru_SU —> ru_RU.

Для русского языка LANG устанавливается, как правило, равным LANG="ru_RU.KOI8-R" или LANG="ru_RU.ISO_8859-5". То есть:

$ export LANG="ru_RU.KOI8-R"

Для установки украинской локали эта переменная должна быть равна соответственно uk_UA.koi8-u.

Согласно стандарту допустимы также короткие именования значений locale, которые часто оформляются как aliases (псевдонимы) полного наименования. Например "C" —> "POSIX".

$ export LANG=ru
$ export LANG=ru_RU
$ export LANG=ru_RU.KOI8-R

Однако, если вы указываете короткое имя, может оказаться, что ваша кодировка оказывается вовсе не KOI8-R (почему следует использовать именно UTF-8, описано в разделе “Символы и кодировки”). Лучше не пользоваться значениями по умолчанию, а указывать точное длинное имя.

Во FreeBSD 2.x так и есть. Для Linux — зависит от дистрибутива. В коммерческих реализациях (Solaris, SCO, AIX etc), как правило, используется значение LANG="ru_RU", или укороченное LANG="ru"  (и, как правило, кодовая страница ISO8859-5 по умолчанию).

Некоторые могут пожелать сделать себе локализацию в другом наборе символов: ru_RU.X-CP-866 (ru_RU.IBM866), ru_RU.x-mac-cyrillic, ru_RU.ISO_8859-5 или даже ru_RU.CP1251 — на это нет никаких ограничений. Все эти кодировки совершенно равноправны и зарегистрированы (кроме x-mac-cyrillic) в IANA. Только не забудьте, что локализация, ввод-вывод и отображение национальных символов на терминале — это совершенно разные вещи.

Если система локализована не полностью и использовать полное переключение на другой язык (с помощью export LANG={язык}) нельзя, можно включить locale только для функций locale API библиотеки libc, задав значение категорий локализации. Можно также присваивать разные значения разным категориям, задавая их имена в строках окружения:

Если вас раздражают русские даты, сообщения и man-ы, но нужно обрабатывать русские буквы и т.д., то сделайте:

$ export LANG="C"
$ export LC_CTYPE="ru_RU.KOI8-R"
$ export LC_COLLATE="ru_RU.KOI8-R"
$ export LC_TIME="C"

Hиже идёт описание различных опций locale

  • LC_CTYPE — определяет одиночные символы,

  • LC_NUMERIC — формат чисел,

  • LC_TIME — формат времени,

  • LC_COLLATE — используется для сравнения строк,

  • LC_MONETARY — валюта,

  • LC_MESSAGES — системные сообщения,

  • LC_PAPER — формат бумаги,

  • LC_NAME — формат имён,

  • LC_ADDRESS — формат адресов,

  • LC_TELEPHONE — формат телефонов.

Не рекомендуется использовать строку окружения:

$ export LC_ALL={язык}

поскольку формально такой категории локализации нет, она "виртуальная"  и обозначает "одновременно все категории". Из-за этого во многих реализациях locale API возникают проблемы. Проблемы могут возникнуть также с программами, работающими с PostScript: в категории LC_NUMERIC локализации ru_RU в соответствии со стандартом ГОСТ в качестве десятичного разделителя используется символ 'запятая': ","  в то время, как в стандарте языка PostScript — точка "."  А категория LC_NUMERIC оказывает влияние на printf("%f",float);. Используйте значение C (POSIX) для LC_NUMERIC, если вы работаете с PostScript:

$ export LC_NUMERIC="POSIX"

Посмотреть текущие значения категорий локализации можно все той же утилитой locale (без параметров).

$ locale

ПРИМЕЧАНИЕ: В некоторых современных системах начинает появляться локализация в UNICODE. Hапример, для России эта локаль включается заданием строки окружения LANG="ru_RU.UTF-8".

Locale зависимое программирование

С locale программа не должна знать о различных символьных преобразованиях и правилах сравнения, описанных выше. Вместо этого они используют специальный API, который действует по правилам, определённым locale. Кроме того, нет необходимости для программы пользоваться только одной locale для соблюдения всех правил — возможно пользоваться другими правилами, описанными в других locale (хотя такой метод не очень хорош).

Из man setlocale(3):

Программа может быть сделана переносимой для всех locale, вызывая setlocale(LC_ALL, "" ) после инициализации программы, используя значения, возвращённые из запроса localeconv() для locale-зависимой информации, и используя strcoll() или strxfrm() для сравнения строк.

Довольно легко определить четыре уровня программной локализации:

  1. Чисто 8ми битное программное обеспечение. То есть программа вызывает setlocale(). Она не делает каких-либо предположений относительно 8-ого бита каждого символа, используя пользовательские функции из ctype.h и ограничения из limits.h, а также заботится относительно signed/unsigned результата. Очень важно, чтобы программа не делала каких-либо предположений относительно характера набора символов и их упорядочения. То есть следует воздержаться от следующих конструкций при программировании:

        if (c >= 'A' && c <= 'Z') {
            ...
    

    Подобные конструкции с точки зрения правильного "locale"-программирования совершенно недопустимы. Взамен во всех таких случаях должны использоваться, макрокоманды из locale зависимого файла заголовка ctype.h. Например:

         if (isalpha(c) && isupper(c)) { ... или
         if (isascii(c) && isupper(c))
    

    Хорошо написанная программа должна быть полностью 8-бит прозрачна. Например, отметка удалённого файла в MS-DOS кодом 0x0E5 — не очень хорошее решение. Ещё примеры плохих решений: знаменитая русская буква "Н"  в редакторе GoldEd или русская буква "р"  в Norton Commander...

  2. Форматы, методы сортировки, размеры листа бумаги. Программа использует strcoll() и strxfrm() вместо strcmp() для строк, использует time(), localtime(), и strftime() для работы со временем, и в заключение, использует localeconv() для правильного представления чисел и валюты.

  3. Видимый текст складывается в каталоги сообщений/. Программа должна локализовать весь видимый текст в специальных каталогах сообщений. Они содержат соответствия строк на английском и их переводы на другие языки. Выбор сообщений, соответствующих языку окружения, выполнен так, что полностью прозрачен и для программы и для пользователя. Чтобы использовать эти средства, программа должна вызвать gettext() (Sun/POSIX стандарт), или catgets() (X/Open стандарт). Подробнее см. раздел “Интернационализация”.

  4. EUC/Unicode поддержка. На этом уровне, программа не использует тип char. Взамен этого она использует wchar_t, который определяет объекты, достаточно большие, чтобы содержать символы Unicode. ANSI C определяет этот тип данных и соответствующий API.