# General Translation Knowledge Base: Как перевести файлы PO и POT?
URL: https://generaltranslation.com/ru/kb/translate-po-files.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Заполните каждый msgstr, включая все формы множественного числа, которых требует заголовок Plural-Forms, а msgid, msgctxt и заполнители оставьте без изменений. Запустите msgfmt --check, чтобы проверить заголовки, количество форм множественного числа и заполнители в записях с флагом формата, а затем отдельно убедитесь, что не осталось пустых msgstr. CLI General Translation создаёт переведённый PO-файл для каждого языка на основе вашего POT-шаблона.
Category: Перевод
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

POT-файл (Portable Object Template) содержит исходные строки, извлечённые из вашего кода, а каждый PO-файл (Portable Object) сопоставляет эти строки с переводами на один язык. Чтобы выполнить перевод, нужно заполнить каждый `msgstr` — по одному на каждую форму множественного числа, используемую в языке, — а `msgid`, `msgctxt` и заполнители оставить в точности такими, какими они были извлечены. Затем каждый PO-файл компилируется в бинарный каталог `.mo`, который загружает ваша программа. Формат простой, но ошибки в нём легко пропустить: всё, что gettext не может использовать, он молча выводит в виде исходного текста.

## Откуда берутся файлы POT и PO

Любой стек на основе gettext выполняет одни и те же четыре шага — различаются лишь названия команд:

| Шаг                                                            | GNU gettext | Babel (Flask, FastAPI) |
| -------------------------------------------------------------- | ----------- | ---------------------- |
| Извлечь исходные строки в шаблон                               | `xgettext`  | `pybabel extract`      |
| Создать PO-файл для нового языка                               | `msginit`   | `pybabel init`         |
| Выполнить слияние изменений шаблона с существующими PO-файлами | `msgmerge`  | `pybabel update`       |
| Скомпилировать PO-файл в `.mo`                                 | `msgfmt`    | `pybabel compile`      |

Django объединяет первые три шага в команде `makemessages`, которая сразу записывает PO-файлы, а для компиляции использует `compilemessages`. Перевод выполняется между слиянием и компиляцией: шаблон не привязан к какому-либо языку, поэтому переводы хранятся только в PO-файлах. (Полный цикл gettext, в том числе поведение `msgmerge` при изменении исходной строки, описан в статье [General Translation и GNU gettext](/kb/general-translation-vs-gettext).)

## Из чего состоит запись PO

Ниже приведён польский PO-файл, который CLI General Translation сгенерировал из шаблона, извлечённого с помощью `xgettext` из небольшого скрипта на Python (заголовок сокращён):

```text title="po/pl.po"
msgid ""
msgstr ""
"Language: pl\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 == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;\n"

#: inbox.py:8
#. TRANSLATORS: Heading of the inbox page.
msgid "Inbox"
msgstr "Skrzynka odbiorcza"

#: inbox.py:9
#, python-format
msgid "You have %(num)d unread message."
msgid_plural "You have %(num)d unread messages."
msgstr[0] "Masz %(num)d nieprzeczytaną wiadomość."
msgstr[1] "Masz %(num)d nieprzeczytane wiadomości."
msgstr[2] "Masz %(num)d nieprzeczytanych wiadomości."

#: inbox.py:10
msgctxt "email action"
msgid "Archive"
msgstr "Archiwizuj"
```

В [руководстве GNU gettext](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) описана каждая часть:

* **`msgid` и `msgid_plural`** — это исходный текст, причём `msgid` также служит ключом для поиска. Никогда не редактируйте их в PO-файле: изменённый `msgid` не совпадёт ни с чем в коде, и пользователи увидят исходный текст.
* **`msgstr`** — это перевод, и это единственная строка, которую заполняете вы. У записи с множественным числом есть строки от `msgstr[0]` до номера, на единицу меньшего, чем число форм в языке.
* **`msgctxt`** разделяет одинаковые исходные строки с разным значением, например «Archive» как действие с письмом и как папку архива. Это поле появляется при вызове `pgettext`, для каждого контекста создаётся отдельный перевод, а само значение остаётся таким, каким было извлечено.
* **Извлечённые комментарии `#.`** — это заметки разработчиков (`xgettext --add-comments` извлекает их из кода), **ссылки `#:`** указывают на строки исходного кода, а **комментарии переводчика `# `** предназначены для того, кто переводит.
* **Флаги `#,`** бывают двух видов. `fuzzy` — флаг рабочего процесса: перевод может быть неверным и его должен проверить человек. Флаги формата, такие как `c-format` и `python-format`, проставляет `xgettext`; они указывают `msgfmt` сверять заполнители в исходном тексте и переводе.
* **Заголовок** — это запись с пустым `msgid`. Его поля `Language`, кодировка в `Content-Type` и `Plural-Forms` действуют для всего файла.

## Как Plural-Forms определяет количество переводов

`Plural-Forms` задаёт количество форм (`nplurals`) и C-подобное выражение, которое сопоставляет число `n` с индексом формы ([руководство GNU gettext: формы множественного числа](https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html)). В GNU gettext 1.0 `msginit` записывал `nplurals=1; plural=0;` для японского, `nplurals=2; plural=(n != 1);` для немецкого, `nplurals=2; plural=(n > 1);` для французского (где ноль относится к единственному числу) и три формы для польского и русского. Арабскому нужно шесть, однако для него тот же `msginit` оставлял в заголовке плейсхолдер `nplurals=INTEGER; plural=EXPRESSION;`, поэтому проверьте заголовок до того, как кто-либо возьмётся за перевод.

Когда польский файл скомпилирован, функция `ngettext` в Python выбирает разные слоты в зависимости от числа:

```text title="Output"
count  slot        output
1      msgstr[0]   Masz 1 nieprzeczytaną wiadomość.
3      msgstr[1]   Masz 3 nieprzeczytane wiadomości.
5      msgstr[2]   Masz 5 nieprzeczytanych wiadomości.
22     msgstr[1]   Masz 22 nieprzeczytane wiadomości.
```

В английском источнике две формы, поэтому при переводе запись за записью получаются лишь две польские строки, и в `msgstr[2]` не окажется ничего корректного для 5 или 12.

## Что ломается при переводе PO-файла

Все эти ошибки не мешают компиляции и не вызывают сбоев при запуске, поэтому и попадают в рабочую среду:

* **fuzzy-записи никогда не доходят до пользователей.** `msgfmt` не включает их в файл `.mo`, если не передать `--use-fuzzy`, а это в руководстве названо «как правило, неверным». Когда запись с множественным числом пометили как fuzzy, польская сборка вывела «You have 5 unread messages.» по-английски. Проверьте каждую fuzzy-запись, а затем снимите флаг.
* **Пустая ячейка форм множественного числа может вывести пустоту.** `msgfmt` скомпилировал запись с пустым `msgstr[2]` и посчитал её переведённой, а Python вывел пустую строку для количества 5. `--check` отклоняет такую запись, только если у неё есть флаг формата: тогда в пустой ячейке форм множественного числа не хватает заполнителя. Без флага запись проходит проверку.
* **Заполнители теряются или переименовываются.** Без `%(num)d` в переводе пропадает количество. `msgfmt --check` обнаруживает это только в записях с флагом формата.
* **Кавычки и экранирование — в стиле C.** Символ `"` внутри `msgstr` нужно записывать как `\"`; неэкранированная кавычка — синтаксическая ошибка, а перенос строки внутри строки записывается как `\n`.
* **В шаблонах вместо кодировки стоит плейсхолдер.** `xgettext` записывает `charset=CHARSET` в заголовок POT, и `msgfmt --check` отклоняет PO-файл, в котором это значение не заменено. `msginit` и `pybabel` указывают UTF-8; если инструмент переводит копию шаблона напрямую, сначала выполните для шаблона `msgconv --to-code=UTF-8`.

`msgfmt --check` сообщает о тех ошибках, которые способен обнаружить, а `--statistics` показывает, что попало в сборку:

```text title="msgfmt --check"
$ msgfmt --check -o /dev/null missing-placeholder.po
missing-placeholder.po:30: a format specification for argument 'num' doesn't exist in 'msgstr[2]'
msgfmt: found 1 fatal error

$ msgfmt --check -o /dev/null two-forms.po
two-forms.po:7: nplurals = 3...
two-forms.po:30: ...but some messages have only 2 plural forms
msgfmt: found 1 fatal error

$ msgfmt --check --statistics -o inbox.mo fuzzy.po
2 translated messages, 1 fuzzy translation.
```

Запускайте её в CI для каждого PO-файла и считайте любое ненулевое число fuzzy-записей незавершённой работой.

Ни один инструмент gettext не сообщает о записи с формами множественного числа, в которой не заполнена одна из ячеек форм множественного числа: `msgfmt --check` пропускает такую запись, если у неё нет флага формата, а `msgattrib --untranslated` её игнорирует. Короткий скрипт на [Babel](https://babel.pocoo.org/) (`pip install babel`) прерывает сборку при любом пустом переводе:

```python title="check_po.py"
import sys

from babel.messages.pofile import read_po

failed = False
for path in sys.argv[1:]:
    with open(path, "rb") as f:
        catalog = read_po(f)
    for message in catalog:
        if not message.id or message.fuzzy:
            continue  # заголовок и неточные (fuzzy) записи, которые msgfmt и так пропускает
        forms = message.string if isinstance(message.string, tuple) else (message.string,)
        if not all(forms):
            print(f"{path}:{message.lineno}: empty translation for {message.id!r}")
            failed = True
sys.exit(1 if failed else 0)
```

```text title="Output"
$ python check_po.py po/pl.po
po/pl.po:17: empty translation for ('You have %(num)d unread message.', 'You have %(num)d unread messages.')
```

## Способы перевода PO-файлов

| Подход                                                                                                            | Кто переводит                                      | Ячейки форм множественного числа                                         | Какой контекст получает переводчик                                                     | Что происходит при изменении шаблона                                                                                                                    |
| ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Вручную или в [Poedit](https://poedit.com/features/)                                                              | Вы или переводчик                                  | Poedit отмечает неверные формы множественного числа и пропущенные токены | Комментарии и ссылки в файле                                                           | Запустите `msgmerge`, затем проверьте fuzzy-записи                                                                                                      |
| [Weblate](https://docs.weblate.org/en/latest/formats/gettext.html) или другая система управления переводами (TMS) | Переводчики, при желании — с машинными подсказками | Поддерживаются; fuzzy соответствует статусу «Needs editing»              | Комментарии, ссылки, а также скриншоты или глоссарий TMS                               | PO-файлы обновляются через дополнение msgmerge или синхронизацию с репозиторием                                                                         |
| `msgpre` из GNU gettext                                                                                           | Локальная LLM через ollama, в виде черновика       | Черновик, который затем проверяет переводчик                             | То, что содержится в PO-файле                                                          | Повторный запуск для новых записей ([предварительный перевод в gettext 1.0](https://www.gnu.org/software/gettext/manual/html_node/Pretranslating.html)) |
| Скрипт поверх API машинного перевода или LLM                                                                      | Модель                                             | Ваш скрипт должен выводить все ячейки, которых требует заголовок         | То, что отправляет ваш скрипт                                                          | Перезапустите и самостоятельно проверьте результат                                                                                                      |
| [CLI General Translation](/docs/cli/reference/formats/po-pot-files)                                               | Сервис перевода General Translation                | Все ячейки для языка перевода, с проверкой заполнителей                  | `msgctxt`, комментарии, ссылки, флаги, а также ваш Glossary и пользовательские промпты | Повторно использует неизменённые переводы и переводит новые или изменённые записи                                                                       |

Предварительный машинный перевод в Poedit доступен только в версии Pro; редактирование, память переводов и проверки есть и в бесплатной версии. (Сравнение с полным циклом работы через TMS см. в статье [General Translation и Weblate](/kb/general-translation-vs-weblate).)

Используйте Poedit, если сами переводите на один-два языка. Выбирайте Weblate или другую [TMS](/kb/what-is-a-translation-management-system), если каталогами занимаются профессиональные переводчики, которым нужны назначение задач и очереди проверки. Скрипт или `msgpre` подходят, только если вы готовы сами отвечать за валидацию. Используйте General Translation, если PO-файлы должны переводиться на все языки при каждой сборке, а терминология — быть общей для всех ваших продуктов.

## Перевод PO-файлов с помощью General Translation

CLI от General Translation — `gt` — считывает POT-файл (или PO-файл на исходном языке) и создаёт переведённый PO-файл для каждой локали. Добавьте запись `pot` в `gt.config.json`. Эта конфигурация проверена на `gt` 2.26.11: встроенное в CLI разрешение путей сопоставляет `po/messages.pot` с файлами `po/de.po`, `po/pl.po` и `po/pt_BR.po`:

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["de", "pl", "pt_BR"],
  "customMapping": { "pt_BR": { "code": "pt-BR" } },
  "files": {
    "pot": {
      "include": ["po/messages.pot"],
      "transform": { "match": "^po/messages\\.pot$", "replace": "po/{locale}.po" },
      "transformationFormat": "PO"
    }
  }
}
```

* **`pot`** читает как PO-, так и POT-файлы (`POT` тоже подходит). Поддержка PO и POT появилась в `gt` 2.14.22.
* **`"transformationFormat": "PO"`** обязателен. Без него на выходе получится непереведённый шаблон.
* **[`transform`](/docs/cli/reference/config#locale-placeholders)** задаёт имя каждого выходного файла; `{locale}` — это локаль в том виде, в каком она указана в `locales`, а [`customMapping`](/docs/cli/reference/config#custom-mapping) сопоставляет gettext-код `pt_BR` со стандартным кодом `pt-BR`.
* **Для структуры папок Babel и Django** `transform` не нужен: укажите в `include` английский каталог с плейсхолдером `[locale]` в пути к папке, например `[locale]/LC_MESSAGES/*.po` внутри `translations/`, и CLI запишет каталог каждого языка рядом с ним, например `pl/LC_MESSAGES/messages.po`.

Затем запустите CLI между извлечением и компиляцией:

```bash
xgettext --language=Python --keyword=pgettext:1c,2 --add-comments=TRANSLATORS -o po/messages.pot app.py
msgconv --to-code=UTF-8 -o po/messages.pot po/messages.pot
npx gt translate
msgfmt --check -o locale/pl/LC_MESSAGES/messages.mo po/pl.po
```

Сначала настройте учётные данные с помощью [`npx gt login`](/docs/cli/reference/commands/login) и идентификатора проекта, а в CI — с помощью ключа API проекта ([учётные данные CLI](/docs/cli/guides/configuring#credentials)). Если в ваших PO-файлах уже есть проверенные переводы, импортируйте их с помощью [`npx gt upload`](/docs/cli/reference/commands/upload) перед первым запуском [`npx gt translate`](/docs/cli/reference/commands/translate).

Для каждой записи General Translation переводит текст `msgid`, передавая в качестве контекста её `msgctxt`, комментарии `#.`, ссылки `#:` и флаги. В выходном файле задаётся заголовок `Language`, а если в файле есть записи с множественными формами, добавляется правило `Plural-Forms` для языка перевода и заполняются все `msgstr[n]`, которых требует это правило: три для польского, шесть для арабского. Каждый перевод множественных форм проверяется на пустые слоты и на то, не пропущены ли в какой-либо форме заполнители, такие как `%(num)d`, `%s` или `{name}`. Комментарии, ссылки, флаги и `msgctxt` переносятся из шаблона, а для неизменённых записей при следующем запуске используются прежние переводы.

Поскольку источником служит `msgid`, этот подход лучше всего работает, когда в коде в качестве ключа используется исходный текст — так gettext работает по умолчанию. Рецензенты могут [редактировать записи в панели управления](/docs/platform/dashboard/guides/reviewing-translations), а Glossary и пользовательские промпты в [контекстной группе](/docs/platform/dashboard/guides/defining-context-for-translations) помогают переводить термины вроде «Archive» так же, как в вашем веб-приложении и документации. Если кто-то отредактировал PO-файл локально, синхронизируйте его с помощью [`npx gt save-local`](/docs/cli/reference/commands/save-local) перед следующим запуском. О настройке для конкретных фреймворков читайте в статьях [General Translation и фреймворк переводов Django](/kb/general-translation-vs-django-i18n), [General Translation, Babel и Flask-Babel](/kb/general-translation-vs-flask-babel) и [Как перевести приложение на Flask или FastAPI?](/kb/translate-python-app).

## Часто задаваемые вопросы

### Чем файл PO отличается от файла POT?

Файл POT — это шаблон: все `msgstr` в нём пусты, а в заголовке не указан язык. Файл PO — это копия для конкретного языка: в ней есть заголовок `Language`, правило `Plural-Forms` для этого языка и сами переводы.

### Нужен ли msgmerge, если PO-файлы записывает General Translation?

Для целевых языков — нет, если источником служит шаблон POT. При каждом запуске PO-файл каждого языка записывается заново на основе текущего шаблона: записи, удалённые из кода, исчезают, а новые появляются уже переведёнными, так что fuzzy-записей, которые пришлось бы разбирать вручную, не остаётся. В проектах со структурой Babel и Django английский каталог по-прежнему обновляется командой `pybabel update` или `makemessages`.

### Почему приложение по-прежнему показывает английский текст, хотя PO-файл уже переведён?

Обычно причина в одном из трёх: запись всё ещё помечена как `fuzzy`, файл `.mo` не был заново скомпилирован или развёрнут, либо `msgid` в PO-файле больше не совпадает со строкой в коде. Команда `msgfmt --statistics` показывает, сколько записей переведено и сколько помечено как fuzzy.

## Основные выводы

* Переводите только `msgstr`; `msgid`, `msgid_plural` и `msgctxt` служат ключами для поиска и остаются в том виде, в каком были извлечены.
* Заполняйте столько слотов `msgstr[n]`, сколько указано в `nplurals` в заголовке: один для японского, два для немецкого, три для польского, шесть для арабского.
* `msgfmt` отбрасывает fuzzy-записи, а пустую ячейку форм множественного числа компилирует без ошибок, и она ничего не выводит; `--check` выявляет это только в записях с флагом формата, поэтому для каждого PO-файла выполняйте `msgfmt --check --statistics` и отдельно проверяйте наличие пустых переводов.
* CLI General Translation переводит POT-шаблон в отдельный PO-файл для каждой локали с помощью `"pot"`, `"transformationFormat": "PO"` и `transform`, заполняя все ячейки форм множественного числа для языка перевода.

## Related questions

- [General Translation и GNU gettext: нужны ли оба?](https://generaltranslation.com/ru/kb/general-translation-vs-gettext.mdx): Оставьте GNU gettext для извлечения и рендеринга, а General Translation подключите для генерации и сопровождения PO-файлов. General Translation учитывает при генерации заметки для переводчиков и контекст сообщений, применяет общую терминологию и предоставляет рецензентам редактор в панели управления. Существующие вызовы gettext и скомпилированные каталоги остаются на месте.
- [General Translation и фреймворк переводов Django: нужны ли оба?](https://generaltranslation.com/ru/kb/general-translation-vs-django-i18n.mdx): Используйте фреймворк переводов Django вместе с General Translation. Django отвечает за извлечение сообщений, выбор языка и рендеринг. General Translation заполняет его PO-файлы, включая формы множественного числа, с учётом терминологии вашего продукта и проверки в панели управления. Ваши шаблоны и URL-шаблоны остаются прежними, а переводы обновляются вместе с кодом.
- [General Translation или Babel и Flask-Babel: что выбрать?](https://generaltranslation.com/ru/kb/general-translation-vs-flask-babel.mdx): Babel форматирует даты, числа и валюты для любой локали и управляет каталогами gettext, а Flask-Babel интегрирует его с Flask, но ни один из них не переводит ваши строки. CLI General Translation заполняет PO-файлы Babel для всех языков, включая формы множественного числа, поэтому вы можете и дальше использовать Flask-Babel, но больше не переводить каталоги вручную.
- [Как перевести приложение на Flask или FastAPI?](https://generaltranslation.com/ru/kb/translate-python-app.mdx): Отметьте строки, которые видит пользователь, определите локаль для каждого запроса (обычно по заголовку Accept-Language) и загрузите переводы для этой локали. В приложениях на Flask обычно используют Flask-Babel с каталогами gettext, а в приложениях на FastAPI Babel подключают через промежуточное ПО. CLI от General Translation заполняет эти каталоги, а экспериментальные SDK gt-flask и gt-fastapi переводят встроенные строки t() без всяких каталогов.
- [General Translation или Weblate: что выбрать?](https://generaltranslation.com/ru/kb/general-translation-vs-weblate.mdx): Weblate — это платформа локализации на основе Git, которую можно развернуть на собственном сервере. Она создана для перевода файлов репозитория силами сообщества или команды. General Translation встраивает генерацию переводов в разработку и релизы, опираясь на размеченный JSX и общий контекст продукта. Оставьте свои файлы gettext или перейдите на написание текста непосредственно в коде — тогда отпадёт и необходимость поддерживать каталог исходных сообщений.

## Previous and next questions

- Previous question: [Как перевести файлы .resx в приложении .NET?](https://generaltranslation.com/ru/kb/translate-resx-files.mdx)
- Next question: [Как перевести файлы локалей в формате YAML?](https://generaltranslation.com/ru/kb/translate-yaml-files.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/ru/kb/sitemap.md) for every article.
