Библиотека сайта rus-linux.net
Что значит "программа правильно написана" ?
Давайте рассмотрим теперь, как писать "правильные" с точки зрения локализации программы. Основное внимание уделим стандартам.
Но и у стандартов есть свои проблемы. Пожтому - немного критики.
"Правильно" написанная
программа с использованием POSIX locale не должна
зависить от способа кодирования ("кодировки")
символов. Такая программа нигде не привязана к
7-ми битности ASCII символов, и пользуется
стандатрными библиотечными (API)
функциями locale : isalpha(),
isupper(), isxxx() и
tolower()/
toupper() не полагаясь, что Upper=Lower+0x20
и т.д. Конструкции, подобные этой также недопустимы :
if (c >= 'A' && c <= 'Z') { ...
Пользуйтесь :
if (isalpha(c) && isupper(c)) { ... или if (isascii(c) && isupper(c))
Как правило, национальные алфавиты расположены начиная с кода 0x80, поэтому для совместимости со старыми реализациями locale можно объявить все символы как unsigned char, например ключом компилятора (для gcc -funsigned-char) или явно.
Хорошо написанная программа должна
быть польностью 8-бит прозрачна. Например отметка удаленного
файла в MS-DOS кодом 0x0E5
- не очень правильное
решение. Также как и знаменитая русская буква
"Н" в редакторе GoldEd или русская буква
"р" в Norton Commander...
Во-вторых, программа должна явно начинаться с вызова setlocale(LC_ALL,"") (такая форма вызова означает, что всем категориям локализации одновременно будет присвоено значение переменной окружения LANG). До вызова этой функции (или совсем без него) программа работает в локализации POSIX (С) и с набором символов ASCII. То есть, не бывает "никакой" локализации.
ПРИМЕЧАHИЕ: Во FreeBSD можно вылечить
некоторые программы, в которых забыт вызов setlocale()
путем задания строки окружения : $ export ENABLE_STARTUP_LOCALE="" тогда setlocale(LC_ALL,"") будет вызываться автоматически при старте программ (без их перекомпиляции). В Linux libc такого нет (пока?) и по умолчанию всегда включено "C" или "POSIX". Однако можно пере-собрать Linux libc, указав другое значение по умолчанию. Но следует отметить, что такое решение будет противоречить стандарту, по которому программа стартует в POSIX (до первого вызова setlocale()). |
Для получения locale-зависимой информации следует пользоватся данными структуры lconv, которые можно получить вызовом функции localeconv() . Для получения детальной информации по категориям локализации (описанным в файле <langinfo.h> ) можно пользовться функцией nl_langinfo(). (Эта функция не входит в POSIX, но входит в XPG, и большинство систем ее имеют).
Для сравнения строк символов следует пользоваться функциями strcoll() и strxfrm() вместо strcmp().
Для полной поддержки NLS весь вывод сообщений пользователю должен происходить c использованием функций NLS и должен быть создан каталог сообщений (message catalog) для данной программы (и данного языка).
А в заключение, неплохо бы иметь man на разных языках. :-)
Локализация и POSIX.
Если мы говорим о файле, то в идеологии POSIX, файл - это просто плоская последовательность байтов. Внутреннее содержимое не стандартизовано никак. Поэтому невозможно определить, какую информацию содержит файл, а если он содержит текстовую информацию -- в какой она кодировке. То же самое можно сказать о потоках : stdin/stdout -- это потоки байтов (кодов). Кодировка (т.е. соответствие символ-код (CES)) полностью потеряна и нигде не указывается. И POSIX вовсе не гарантирует, что CES stdin будет совпадать с текущей локализацией, заданной через LANG=.
Точно тактая же ситуация с терминальным вводом/выводом : кодировка терминала совершенно неизвестна приложению.
К сожалению, в стандарте POSIX поддержка сharset не имеет полностью идеологически стройной и ясной концепции. Понятие charset существует только для locale API и тех функций, которые зависят от locale. Определено поле charset в наименованияx locale (ru_RU.KOI8-R и ru_RU.ISO_8859-5). Но с другой стороны, это поле опционально и может опускаться (ru_RU). См. localedef ключ -f .
Более того, в "чистом" POSIX вообще невозможно узнать имя Charset после setlocale(). Единственный способ, узнать Charset текущей locale - это воспользоваться не-POSIX функцией XPG (SVID, Unix98) : nl_langinfo(CODESET) (определенной в файле <langinfo.h> ). Тогда текущий charset можно получить так :
#include <locale.h> #include <langinfo.h> ... setlocale(LC_ALL,""); printf ("Current charset = %s\n",nl_langinfo(CODESET)); |
Надо ли говорить, что некоторые UNIX (например FreeBSD) не имеют этих XPG-extensions и не имеют функции nl_langinfo() как таковой вообще. (Что очень странно, поскольку в том же POSIX определена утилита locale c keyword-ом codeset которая "как-то" это имя определяет...) Например, популярная система FreeBSD до сих пор не имеет функции nl_langinfo() (как впрочем и утилиты locale, увы !).
Довольно значительное число ошибок
происходит из за того, что в языке С
определен тип переменных char (хотя
точнее было бы назвать его : byte). Поэтому,
если мы задаем строку (массив char), в
которой употребляются символы не ASCII (с кодами
>128) : char string[]="Проверка";
-- результат совершенно
непредсказуем и непереносим. Также вызывает
удивление существование (и синтаксическая
корректность) типов signed / unsigned char (unsigned
short int -- понятно)... Если вы планируете работу
вашей программы в многоязычном окружении,
неплохо бы предусмотреть атрибут charset у
строк символов char *. Или полностью
переходить на UNICODE (wchar_t) в
качестве внутренней кодировки.
Содержание "Locale AS IT IS"