Product SiteDocumentation Site

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

10.1. Работа с Gettext
10.2. Сборка пакета с поддержкой переводов
10.3. Переводы с помощью autotools
При разработке ПО для широкой аудитории необходимо проводить интернационализацию и локализацию продукта. Одной из основных задач локализации является перевод текстов с поддержкой множественных форм. Локализация базовой библиотеки GLibC в ALT содержится в отдельном пакете — glibc-locales, все остальные программные продукты используют для хранения локализованных объектов файлы в подкаталогах /usr/share/locale с указанием кода локали, класса объектов и т. н. домена.

10.1. Работа с Gettext

Рассмотрим программу, последовательно считающую овец:
@user: in-place/src/sheepcounter.c
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>
#include <libintl.h>
#include <locale.h>

#define _(STRING) gettext(STRING)

int main(int argc, char *argv[])
{
       int count;

       setlocale (LC_ALL, "");
       bindtextdomain (PACKAGE, LOCALE_PATH);
       textdomain (PACKAGE);

       /* Simple text */
       puts(_("Let's count sheeps together!\n"));
       puts(_("How many sheeps do you want to count? "));

       scanf("%d", &count);

#ifndef NUMBERS
       while (count >= 10) {
               puts(_("Oh, I know only digits, not numbers, try again: "));
               scanf("%d", &count);
       }
#endif

       for(int i = 1; i <= count; i++)
               /* Plural example */
               printf(ngettext("We counted %d sheep\n", "We counted %d sheeps\n", i), i);

       return 0;
}
Для работы с переводами используется специальный набор инструментов GNU gettext. С помощью специальных функций-обёрток gettext и ngettext из библиотеки locale.h обозначаются элементы, которые необходимо будет локализовать. Для элементов, в которых возможна множественная форма, указывается также параметр, от которого она будет зависеть.
В GNU gettext идентификатором сообщения является само сообщение — строка на исходном языке. Переводы объединены в т. н. домены, внутри которых идентификаторы уникальны, а сами сообщения принадлежат одному культурному контексту. Несколько приложений могут пользоваться одним доменом переводов; главное, чтобы исходные строки-идентификаторы были равны. Одно приложение может пользоваться несколькими доменами — например, в ситуации, когда одно и то же словосочетание необходимо в одной и той же локали перевести по-разному в зависимости от контекста. В большинстве случаев название домена совпадает с названием запускаемой программы или пакета, в состав которого она входит.
Соответствие домена и директории для поиска переводов устанавливаются функцией bindtextdomain(), при этом файл с переводом определяется поиском в этой директории по шаблону <директория>/<код_локали>/<класс_объекта>/<домен>.mo. Сам домен устанавливается функцией textdomain().
Для генерации шаблона перевода (pot — po template) используется утилита xgettext, которой указывается название функции-обёртки, входной и выходной файлы: xgettext -k_ -c src/sheepcounter.c -o po/sheepcounter.pot:
@user: sheepcounter-pkg/po/sheepcounter.pot
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-24 17:22+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#. Simple text
#: src/sheepcounter.c:19
msgid "Let's count sheeps together!\n"
msgstr ""

#: src/sheepcounter.c:20
msgid "How many sheeps do you want to count? "
msgstr ""

#: src/sheepcounter.c:26
msgid "Oh, I know only digits, not numbers, try again: "
msgstr ""

#. Plural example
#: src/sheepcounter.c:33
#, c-format
msgid "We counted %d sheep\n"
msgid_plural "We counted %d sheeps\n"
msgstr[0] ""
msgstr[1] ""
Шаблон содержит все элементы, требующие локализации. Для создания первичного (пустого) текстового файла с переводами используется утилита msginit с указанием локали: msginit -i po/sheepcounter.pot -o po/ru.po -l ru_RU.UTF-8. При наличии уже написанного перевода (например, при внесении каких-то изменений в исходный текст программы, требующих дополнительной локализации) шаблон сначала обновляется, а затем на его основе с помощью утилиты msgmerge обновляется и старый перевод: msgmerge -U po/ru.po po/sheepcounter.pot. Новые сообщения оказываются с пустым переводом, перевод исчезнувших сообщений комментируется, но не удаляется, а переводы старых, но слегка изменившихся сообщений, помечаются флагом fuzzy — «нечёткий».
Текстовый файл перевода помимо сообщений содержит мета-данные о конкретном переводе на заданном языке, которые включают формулу обработки множественных форм (в разных языках их разное количество и разные правила соответствия). Пустые и fuzzy-переводы самостоятельно исправляет в .po-файле автор:
@user: sheepcounter-pkg/po/ru.po
# Russian translations for PACKAGE package
# Английские переводы для пакета PACKAGE.
# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#  <user@usamg1tvm>, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-24 17:22+0300\n"
"PO-Revision-Date: 2025-07-24 17:34+0300\n"
"Last-Translator:  <user@usamg1tvm>\n"
"Language-Team: Russian <gnu@d07.ru>\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"

#. Simple text
#: src/sheepcounter.c:19
msgid "Let's count sheeps together!\n"
msgstr "Давай считать овец вместе!\n"

#: src/sheepcounter.c:20
msgid "How many sheeps do you want to count? "
msgstr "Сколько овец ты хочешь посчитать? "

#: src/sheepcounter.c:26
msgid "Oh, I know only digits, not numbers, try again: "
msgstr "Оу, я знаю только цифры, не числа, напиши что-то другое: "

#. Plural example
#: src/sheepcounter.c:33
#, c-format
msgid "We counted %d sheep\n"
msgid_plural "We counted %d sheeps\n"
msgstr[0] "Мы посчитали %d овцу\n"
msgstr[1] "Мы посчитали %d овцы\n"
msgstr[2] "Мы посчитали %d овец\n"
Для компиляции перевода в файл с доменом используется утилита msgfmt. Предварительно необходимо создать и указать поддиректории для расположения файла перевода:
mkdir -p ru/LC_MESSAGES/
msgfmt po/ru.po -o ru/LC_MESSAGES/sheepcounter.mo
Скомпилируем программу и проверим её работу. Заметим, что макросы PACKAGE и LOCALE_PATH в самой программе не определены — мы можем задать их напрямую из командной строки. В нашем случае переводы искать надо прямо в текущем каталоге, а домен называется sheepcounter:
@user
[user@VM in-place]$ cc -DPACKAGE='"sheepcounter"' -D LOCALE_PATH='"."' src/sheepcounter.c -o sheepcounter
[user@VM in-place]$ ./sheepcounter
Давай считать овец вместе!

Сколько овец ты хочешь посчитать?
10
Оу, я знаю только цифры, не числа, напиши что-то другое:
5
Мы посчитали 1 овцу
Мы посчитали 2 овцы
Мы посчитали 3 овцы
Мы посчитали 4 овцы
Мы посчитали 5 овец
[user@VM in-place]$ LC_MESSAGES=en_US.UTF-8 ./sheepcounter
Let's count sheeps together!

How many sheeps do you want to count?
4
We counted 1 sheep
We counted 2 sheeps
We counted 3 sheeps
We counted 4 sheeps
[user@VM in-place]$