Если C-программа с поддержкой перевода через gettext показывает интерфейс на английском, хотя системная локаль выставлена на ru_RU.UTF-8, дело почти наверняка в одной непрописанной строке: setlocale(LC_ALL, ""). Без этого вызова в main() libc игнорирует переменные окружения LANG и LC_ALL, и программа работает в локали по умолчанию. Разбираемся, почему так происходит, как это чинить на Arch Linux, и в чём отличие «каракулей» от непереведённого интерфейса.
Локаль (locale) — это набор правил, по которым программа определяет язык, формат дат, сортировку строк, разделители чисел. В Linux локаль задаётся переменными окружения:
LANG — основная локаль по умолчаниюLC_ALL — переопределяет всёLC_MESSAGES — язык сообщений и переводовПроверить текущую локаль в Arch проще всего:
locale
В выводе увидите что-то вроде LANG=ru_RU.UTF-8. Эта переменная живёт в /etc/locale.conf, а список доступных локалей прописан в /etc/locale.gen.
GNU gettext — стандартный механизм интернационализации в C-программах. Схема простая:
_() или gettext().xgettext вытаскивает эти строки в .po-файл (человекочитаемый каталог переводов)..po-файлы нужными языками.msgfmt компилирует .po в бинарные .mo-файлы..mo через gettext().Вот ключевой момент: gettext() определяет, какой .mo-файл загрузить, по текущей локали. Но текущая локаль задаётся вызовом setlocale(). Если его нет, libc не знает, какой язык показывать, и возвращает дефолтный (английский).
Вот как выглядит минимальный пример с gettext:
#include <libintl.h>
#include <locale.h>
int main(void) {
setlocale(LC_ALL, "");
bindtextdomain("myapp", LOCALEDIR);
textdomain("myapp");
printf("%s\n", _("Hello, world!"));
return 0;
}
Строка setlocale(LC_ALL, "") говорит libc: «прочитай переменные окружения и настрой локаль по ним». Без неё libc работает в «американском» режиме по умолчанию. Переменная LANG=ru_RU.UTF-8 остаётся без внимания, и gettext загружает английский .mo.
Это не баг и не особенность конкретного дистрибутива. Так работает стандарт POSIX: программы не обязаны читать переменные окружения автоматически. Разработчик должен явно вызвать setlocale().
Klavaro — клавиатурный тренажёр с открытым кодом. В оригинальном Klavaro переводы через gettext есть, но setlocale() в main() не вызывается. Результат: даже если в Arch стоит русская локаль, интерфейс всегда на английском.
В форке Klavaro эту проблему исправили добавлением трёх строк в начало main():
setlocale(LC_ALL, "");
bindtextdomain("klavaro", LOCALEDIR);
textdomain("klavaro");
После этого программа стала считывать LC_MESSAGES из окружения и загружать русские переводы. Простая правка, а результат кардинальный.
locale
Все переменные должны быть ru_RU.UTF-8 или хотя бы LANG=ru_RU.UTF-8. Если LANG=C или LANG=en_US.UTF-8 — программам нечего читать.
cat /etc/locale.conf
Там должна быть строка:
LANG=ru_RU.UTF-8
Откройте /etc/locale.gen и раскомментируйте нужную строку (уберите # в начале):
ru_RU.UTF-8 UTF-8
Затем сгенерируйте:
sudo locale-gen
После locale-gen переменные подхватятся автоматически при следующем входе в систему. Для текущей сессии можно выставить вручную:
export LANG=ru_RU.UTF-8
Подробнее о приоритетах LC_*-переменных — в ArchWiki: Locale и Environment variables.
Часто путают два разных сценария:
setlocale() или отсутствующих .mo-файлах.Если в терминале вместо русских букв появляются пустые квадраты, шрифт просто не содержит нужных глифов. Решение — поставить Noto Fonts:
sudo pacman -S noto-fonts noto-fonts-cjk noto-fonts-extra
После установки пересоздайте кэш шрифтов:
fc-cache -fv
Проверьте шрифт по умолчанию:
fc-match monospace
Должен показать Noto Sans Mono или аналог с поддержкой кириллицы. Это касается и терминальных прог, и GUI. Например, в i3wm без правильного шрифта в конфиге bar тоже будут пустые ячейки вместо символов.
LC_ALL вместо LANG?Да, но LC_ALL имеет наивысший приоритет и переопределяет всё. В setlocale(LC_ALL, "") программа берёт LC_ALL, если задан, иначе LANG. Для большинства задач достаточно правильно выставить LANG в /etc/locale.conf.
bindtextdomain()?Указывает путь к каталогу с .mo-файлами. Стандартная иерархия: /usr/share/locale/<язык>/LC_MESSAGES/<домен>.mo. Без этой строки gettext не найдёт переводы.
C или POSIX?Это намеренно. Серверные программы работают с текстовыми протоколами, где локаль может сломать парсинг. C — безопасный минимум.
Посмотрите, есть ли .mo-файл:
find /usr/share/locale -name "*.mo" | grep <название_программы>
Например, для gettext-домена klavaro:
find /usr/share/locale -name "klavaro.mo"
Конечно:
LANG=en_US.UTF-8 ./myapp
Это полезно для отладки переводов или тестирования.
Одна строка setlocale(LC_ALL, "") в начале main() — и C-программа начинает читать системную локаль. Без неё gettext просто не знает, какой язык подставить. В форке Klavaro именно это и исправили. Если интерфейс показывает не тот язык, проверяйте по порядку: locale, /etc/locale.conf, locale-gen. А если вместо букв квадратики — дело в шрифтах. Установите Noto Fonts, и проблема уйдёт.
Задай вопрос в чате — отвечаем быстро, по делу и без воды.
Комментарии