# General Translation Knowledge Base: Как перевести приложение на Flask или FastAPI?
URL: https://generaltranslation.com/ru/kb/translate-python-app.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Отметьте строки, которые видит пользователь, определите локаль для каждого запроса (обычно по заголовку Accept-Language) и загрузите переводы для этой локали. В приложениях на Flask обычно используют Flask-Babel с каталогами gettext, а в приложениях на FastAPI Babel подключают через промежуточное ПО. CLI от General Translation заполняет эти каталоги, а экспериментальные SDK gt-flask и gt-fastapi переводят встроенные строки t() без всяких каталогов.
Category: Интернационализация
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

Ни Flask, ни FastAPI не поддерживают переводы из коробки, поэтому понадобятся три вещи: способ отмечать строки, которые видит пользователь, способ выбирать язык для каждого запроса и переводы на все поддерживаемые языки. Проверенный подход — каталоги gettext из Babel; более новый оставляет строки встроенными в код и загружает переводы в формате JSON. В любом случае язык привязан к запросу, а не к процессу: один сервер одновременно обслуживает пользователей на разных языках.

## Что нужно переведённому веб-приложению на Python

* **Размеченные строки.** Вместо того чтобы возвращать «голую» строку, код вызывает функцию вроде `gettext()` или [`t()`](/docs/python/reference/functions/t), а в шаблонах используются `_()` или `{% trans %}`. Именно благодаря разметке инструмент может найти каждую строку и подобрать для неё перевод.
* **Локаль для каждого запроса.** Обычно она берётся из заголовка `Accept-Language` и может переопределяться префиксом URL, cookie-файлом или настройкой учётной записи. Flask хранит её в контексте запроса; FastAPI обрабатывает запросы конкурентно в одном цикле событий, поэтому локаль хранится в `contextvars.ContextVar`, значение которой задаёт каждый запрос.
* **Переводы для каждой локали.** gettext загружает скомпилированные каталоги `.mo`, а SDK General Translation — по одному JSON-файлу на локаль. В обоих случаях при отсутствии перевода без ошибки подставляется исходный текст, поэтому непереведённые строки просто отображаются на английском и не приводят к сбою.

## Вариант 1: каталоги gettext с помощью Babel

[Babel](https://babel.pocoo.org/en/latest/cmdline.html) извлекает размеченные строки в шаблон POT, создаёт и обновляет каталог PO для каждого языка и компилирует их в файлы `.mo`. Кроме того, он форматирует даты, числа и денежные суммы для любой локали.

### Flask и Flask-Babel

[Flask-Babel](https://python-babel.github.io/flask-babel/) (4.0.0) интегрирует Babel с запросами Flask и шаблонами Jinja. Достаточно передать ему функцию `locale_selector`, которая возвращает локаль для каждого запроса:

```python title="app.py"
from flask import Flask, render_template, request
from flask_babel import Babel, gettext, ngettext


def select_locale():
    # Выбираем из поставляемых языков лучше всего подходящий под заголовок Accept-Language
    return request.accept_languages.best_match(["en", "es", "pl"])


app = Flask(__name__)
babel = Babel(app, locale_selector=select_locale)


@app.route("/inbox/<int:count>")
def inbox(count):
    return {
        "title": gettext("Inbox"),
        # ngettext выбирает форму множественного числа для активной локали
        "unread": ngettext("You have %(num)d unread message.", "You have %(num)d unread messages.", count),
    }


@app.route("/")
def home():
    return render_template("home.html", name="Alice")
```

В шаблонах строки помечаются с помощью `_()`, например `{{ _("Welcome back, %(name)s!", name=name) }}`. Файл `babel.cfg` указывает `pybabel`, где искать строки, а для работы с каталогами используются четыре команды:

```bash
# babel.cfg содержит [python: **.py] и [jinja2: **/templates/**.html]
pybabel extract -F babel.cfg -o messages.pot .
pybabel init -i messages.pot -d translations -l es    # один раз для каждого языка
pybabel update -i messages.pot -d translations        # после каждого извлечения, для всех языков сразу
pybabel compile -d translations
```

С испанским каталогом, который сгенерировал CLI General Translation, запрос с заголовком `Accept-Language: es-MX,es;q=0.9` вернул «Bandeja de entrada» и «Tienes 5 mensajes sin leer.». Когда польский каталог был создан, но оставался пустым, запрос на польском вернул все строки на английском, причём без каких-либо предупреждений.

### FastAPI с Babel

У FastAPI нет официального аналога Flask-Babel. Есть сторонние пакеты, например [fastapi-babel](https://pypi.org/project/fastapi-babel/) и [starlette-babel](https://pypi.org/project/starlette-babel/), но можно и самостоятельно загружать каталоги Babel в промежуточном ПО. Этот вариант использует ту же директорию `translations` и хранит каталог каждого запроса в `ContextVar`:

```python title="main.py"
from contextvars import ContextVar
from functools import lru_cache

from babel.core import negotiate_locale
from babel.support import Translations
from fastapi import FastAPI, Request

SUPPORTED = ["en", "es", "pl"]
current = ContextVar("translations")


@lru_cache
def load(locale: str) -> Translations:
    # Читает файл translations/<locale>/LC_MESSAGES/messages.mo, скомпилированный pybabel
    return Translations.load("translations", [locale])


def _(message: str) -> str:
    return current.get().gettext(message)


def preferred_locales(header: str) -> list[str]:
    """Accept-Language tags, most preferred first, without q=0 entries."""
    weighted = []
    for part in header.split(","):
        tag, sep, param = part.strip().partition(";")
        try:
            q = float(param.strip().removeprefix("q=")) if sep else 1.0
        except ValueError:
            q = 0.0
        # q=0 означает «неприемлемо», поэтому такой тег пропускаем
        if tag and q > 0:
            weighted.append((q, tag.replace("-", "_")))
    # sorted() — стабильная сортировка, поэтому теги с одинаковым весом сохраняют порядок из заголовка
    return [tag for q, tag in sorted(weighted, key=lambda pair: -pair[0])]


app = FastAPI()


@app.middleware("http")
async def select_locale(request: Request, call_next):
    preferred = preferred_locales(request.headers.get("accept-language", ""))
    current.set(load(negotiate_locale(preferred, SUPPORTED) or "en"))
    return await call_next(request)


@app.get("/inbox/{count}")
def inbox(count: int):
    t = current.get()
    return {
        "title": _("Inbox"),
        "unread": t.ngettext("You have %(num)d unread message.", "You have %(num)d unread messages.", count) % {"num": count},
    }
```

При запуске под Uvicorn заголовок `Accept-Language: es-MX,es;q=0.9,en;q=0.8` вернул испанские строки, с `de,en;q=0.5` произошёл откат на английский, `en;q=0.5,es` вернул испанский, а `es;q=0,en` — английский. `negotiate_locale` сопоставляет `es_MX` с `es`. `preferred_locales` сортирует теги по весу `q` и отбрасывает теги с `q=0` — так помечается язык, который пользователь не принимает.

### Что при использовании Babel остаётся на вас

Babel управляет каталогами, но не пишет переводы. Каждая новая или изменённая строка ждёт, пока кто-нибудь заполнит её `msgstr` для каждого языка (для польского — все три ячейки форм множественного числа), затем выполнит `pybabel compile` и развернёт приложение. (О самом формате и о проверках, которые выявляют повреждённые записи, см. [Как перевести PO- и POT-файлы?](/kb/translate-po-files), а о возможностях каждого инструмента — [General Translation vs. Babel и Flask-Babel](/kb/general-translation-vs-flask-babel).) В приложениях на Django используется тот же паттерн, только со своими командами. (См. [General Translation vs. фреймворк переводов Django](/kb/general-translation-vs-django-i18n).)

## Выбор локали для каждого запроса

Заголовок `Accept-Language` содержит список языков пользователя с необязательными весами `q`. Ваша задача — сопоставить его с языками, которые поддерживает приложение, а во всех остальных случаях использовать язык по умолчанию. И `best_match()` из Werkzeug для `request.accept_languages`, и `negotiate_locale` из Babel при отсутствии точного совпадения переходят от регионального тега, например `es-MX`, к `es`. Явный выбор пользователя должен иметь приоритет над заголовком: это может быть префикс URL `/es/` для страниц, индексируемых поисковыми системами, или cookie-файл, который устанавливает переключатель языка. (Полный порядок сигналов см. в статье [Как определить язык браузера пользователя?](/kb/detect-user-language).) На серверах Node.js та же задача решается с помощью `AsyncLocalStorage` (см. [Как перевести приложение на Node.js?](/kb/translate-node-app)).

## Сравнение подходов

| Подход                                             | Как писать строки                                                               | Локаль для каждого запроса                    | Кто переводит                            | Статус                                                                     |
| -------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------- |
| Flask-Babel с каталогами PO                        | `gettext`, `ngettext`, Jinja `_()`                                              | `locale_selector`                             | Вы или переводчики, для каждого каталога | Стабильный                                                                 |
| Babel в промежуточном ПО FastAPI                   | `gettext` через `ContextVar`                                                    | Ваше промежуточное ПО                         | Вы или переводчики, для каждого каталога | Стабильный, но собирается вручную                                          |
| API машинного перевода для каждого запроса         | Необработанные строки отправляются во время выполнения                          | Вы передаёте язык перевода                    | API, при каждом запросе                  | Без кэширования каждый вызов увеличивает задержку и стоимость              |
| Каталоги Babel плюс CLI General Translation        | Как в Babel                                                                     | Как в Babel                                   | General Translation, до развёртывания    | CLI стабилен; Babel без изменений                                          |
| `gt-flask` или `gt-fastapi` от General Translation | Встроенные вызовы [`t()`](/docs/python/reference/functions/t) с сообщениями ICU | Встроенное сопоставление по `Accept-Language` | General Translation, до развёртывания    | Экспериментальный (0.1.0); загруженные переводы устаревают через 60 секунд |

Используйте Flask-Babel или Babel в промежуточном ПО FastAPI, если у вас уже есть каталоги или переводчики. Добавьте к этой настройке CLI General Translation, если хотите, чтобы при каждом изменении все каталоги заполнялись для всех языков без отдельного цикла перевода. Попробуйте `gt-flask` или `gt-fastapi` на прототипе, если удобнее держать строки прямо в коде, без файлов каталогов, и зафиксируйте версию. Вызывайте API машинного перевода во время запроса только для контента, который появляется лишь во время выполнения, например для пользовательских публикаций.

## Как General Translation переводит приложения на Flask и FastAPI

### Сохраните Babel и переведите каталоги

CLI от General Translation — `gt` — читает английский PO-каталог и записывает каталоги для остальных языков туда, где их ожидает Babel. Создайте английский каталог один раз командой `pybabel init -l en`, обновляйте его после каждого извлечения командой `pybabel update -l en`, а затем укажите путь к нему в `gt.config.json`:

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["es", "pl"],
  "files": {
    "pot": {
      "include": ["translations/[locale]/LC_MESSAGES/messages.po"],
      "transformationFormat": "PO"
    }
  }
}
```

По данным проверки на версии `gt` 2.26.11, CLI сопоставляет английский каталог в `translations/en/LC_MESSAGES` с соседними папками `es` и `pl`. Выполните [`npx gt translate`](/docs/cli/reference/commands/translate), а затем `pybabel compile -d translations`. General Translation заполняет все слоты множественного числа для каждого языка перевода и проверяет наличие заполнителей, таких как `%(num)d`, в каждой форме множественного числа. Glossary и пользовательские промпты в [контекстной группе](/docs/platform/dashboard/guides/defining-context-for-translations) помогают сохранять единую терминологию во всех ваших проектах. CLI работает на Node.js, поэтому на машине, где он запускается, должен быть установлен Node. Ваше приложение по-прежнему загружает скомпилированные каталоги и не зависит от General Translation во время выполнения.

### Или используйте gt-flask и gt-fastapi

Python SDK от General Translation — `gt-flask` и `gt-fastapi` (версия 0.1.0 на PyPI, из репозитория [gt-python](https://github.com/generaltranslation/gt-python)) — экспериментальные и могут меняться от релиза к релизу. Для них нужен Python 3.10 или новее. Строки остаются в коде в виде ICU-сообщений, которые передаются в [`t`](/docs/python/reference/functions/t), а значения передаются как именованные аргументы. [`initialize_gt`](/docs/python/reference/functions/initialize-gt) настраивает сопоставление по `Accept-Language`. Это приложение Flask загружает переводы из локальных JSON-файлов:

```python title="app.py"
import json
from pathlib import Path

from flask import Flask
from gt_flask import initialize_gt, t, get_locale


def load_translations(locale: str) -> dict[str, str]:
    # Файлы, которые gt CLI записывает в files.gt.output
    path = Path("translations") / f"{locale}.json"
    return json.loads(path.read_text()) if path.exists() else {}


app = Flask(__name__)
# В Flask передавайте locales здесь, чтобы переводы загружались при старте
initialize_gt(app, default_locale="en", locales=["es", "fr"], load_translations=load_translations)


@app.route("/")
def index():
    return {
        "message": t("Hello, {name}!", name="Alice"),
        "unread": t("{count, plural, one {You have # unread message.} other {You have # unread messages.}}", count=3),
        "locale": get_locale(),
    }
```

Для FastAPI импортируйте из `gt_fastapi`, создайте `FastAPI()` и используйте `@app.get`; здесь [`initialize_gt`](/docs/python/reference/functions/initialize-gt) добавляет HTTP-промежуточное ПО и загружает переводы в lifespan приложения, поэтому достаточно указать локали в `gt.config.json`. С файлом `translations/es.json`, сгенерированным CLI General Translation, оба приложения ответили так:

```text title="Output"
Accept-Language: es-MX,es;q=0.9   {"locale": "es", "message": "¡Hola, Alice!", "unread": "Tienes 3 mensajes sin leer."}
Accept-Language: de                {"locale": "en", "message": "Hello, Alice!", "unread": "You have 3 unread messages."}
```

Эти файлы создаёт CLI. Укажите `gt-flask` или `gt-fastapi` в `requirements.txt` или `pyproject.toml`, чтобы CLI смог обнаружить пакет, и задайте путь для выходных файлов в `gt.config.json`:

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["es", "fr"],
  "files": { "gt": { "output": "translations/[locale].json" } }
}
```

[`npx gt translate`](/docs/cli/reference/commands/translate) сканирует ваши файлы `.py` в поисках вызовов [`t()`](/docs/python/reference/functions/t) (пропуская виртуальные окружения, тесты и миграции), переводит их и записывает по одному файлу на каждую локаль. В этом приложении [`npx gt validate`](/docs/cli/reference/commands/validate) нашёл в `gt-flask` 2 записи для перевода, не обращаясь к API. Чтобы вместо этого загружать переводы из CDN General Translation, укажите `projectId` и `cacheUrl` в конфигурации (см. [Выбор способа доставки переводов](/docs/python/guides/configuring#delivery)). SDK не требуют ключа API, а благодаря локальным файлам во время выполнения не выполняется никаких сетевых запросов.

В версии 0.1.0 [`t()`](/docs/python/reference/functions/t) читает переводы из кэша в памяти, который устаревает через 60 секунд и не перезагружается при таком чтении. Поэтому после истечения срока кэша долго работающий сервер возвращает исходные строки, пока загрузчик не запустится снова. (См. [заметки о предзагрузке](/docs/python/guides/configuring#preload).) Пока это не исправят в одном из следующих релизов, используйте для трафика в рабочей среде каталоги Babel, заполняемые с помощью CLI, а `gt-flask` и `gt-fastapi` оставьте для прототипов.

## Частые вопросы

### Заменяет ли gt-flask форматирование дат и денежных значений из Flask-Babel?

Нет. В версии 0.1.0 `gt-flask` и `gt-fastapi` экспортируют только вспомогательные функции для перевода и работы с локалью ([`t`](/docs/python/reference/functions/t), [`get_locale`](/docs/python/reference/functions/get-locale), [`declare_var`](/docs/python/reference/functions/declare-var) и некоторые другие), поэтому для форматирования с учётом локали по-прежнему используйте `format_date` и `format_currency` из Babel.

### Как дать пользователям выбирать язык вместо использования Accept-Language?

Во Flask-Babel считывайте cookie-файл или сегмент URL в `locale_selector`. В SDK General Translation передайте функцию обратного вызова [`get_locale`](/docs/python/reference/functions/get-locale) в [`initialize_gt`](/docs/python/reference/functions/initialize-gt). Она заменяет сопоставление по заголовку, поэтому поддерживаемую локаль нужно возвращать самостоятельно. (См. [Определение локали запроса](/docs/python/guides/detecting-locale).)

### Как переводить содержимое базы данных или пользовательские публикации?

Каталоги и [`t()`](/docs/python/reference/functions/t) охватывают строки в вашем коде. Содержимое, которое появляется только во время выполнения, нужно переводить отдельным вызовом в момент его создания или запроса — например, через эндпоинт General Translation [Translate content](/docs/platform/openapi/reference/translation/translate-content) (`POST /v2/projects/{projectId}/translate`), — а результаты сохранять рядом с оригиналом.

## Ключевые выводы

* Flask и FastAPI нужны три дополнения: размеченные строки, локаль для каждого запроса и переводы для каждой локали; FastAPI хранит локаль в `ContextVar`.
* Во Flask всю работу выполняет Flask-Babel с помощью `locale_selector` и команд `pybabel extract`, [`init`](/docs/cli/reference/commands/init), `update` и `compile`; в FastAPI объект `Translations` из Babel загружается в промежуточном ПО.
* Если каталог пуст или отсутствует, приложение без предупреждений переключается на английский, поэтому остаётся лишь заполнить каждый `msgstr`.
* CLI General Translation заполняет PO-каталоги Babel при настройках `"pot"` и `"transformationFormat": "PO"`, включая слоты для форм множественного числа, а ваше приложение по-прежнему загружает скомпилированные каталоги.
* Экспериментальные пакеты `gt-flask` и `gt-fastapi` 0.1.0 от General Translation переводят встроенные строки [`t()`](/docs/python/reference/functions/t) из JSON-файлов, которые создаёт CLI; во Flask передайте `locales` в [`initialize_gt`](/docs/python/reference/functions/initialize-gt) и учтите 60-секундный срок жизни кэша, прежде чем запускать приложение в рабочей среде.

## Related questions

- [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, но больше не переводить каталоги вручную.
- [Как перевести файлы PO и POT?](https://generaltranslation.com/ru/kb/translate-po-files.mdx): Заполните каждый msgstr, включая все формы множественного числа, которых требует заголовок Plural-Forms, а msgid, msgctxt и заполнители оставьте без изменений. Запустите msgfmt --check, чтобы проверить заголовки, количество форм множественного числа и заполнители в записях с флагом формата, а затем отдельно убедитесь, что не осталось пустых msgstr. CLI General Translation создаёт переведённый PO-файл для каждого языка на основе вашего POT-шаблона.
- [Как определить язык браузера пользователя?](https://generaltranslation.com/ru/kb/detect-user-language.mdx): Прочитайте упорядоченный список языков браузера (navigator.languages в JavaScript или заголовок Accept-Language на сервере с сортировкой по q-значению) и сопоставьте его с поддерживаемыми локалями, предусмотрев резервный вариант. Safari и Chrome в режиме инкогнито сообщают только один язык, а геолокация по IP указывает на местоположение, а не на язык.
- [Как перевести приложение на Node.js?](https://generaltranslation.com/ru/kb/translate-node-app.mdx): Определяйте язык каждого запроса, обычно по заголовку Accept-Language, и возвращайте все строки на этом языке. Пакет gt-node от General Translation привязывает локаль к каждому запросу с помощью withGT и переводит строки с помощью getGT. Переводы для них генерирует команда npx gt translate --publish ещё до развертывания.
- [General Translation и фреймворк переводов Django: нужны ли оба?](https://generaltranslation.com/ru/kb/general-translation-vs-django-i18n.mdx): Используйте фреймворк переводов Django вместе с General Translation. Django отвечает за извлечение сообщений, выбор языка и рендеринг. General Translation заполняет его PO-файлы, включая формы множественного числа, с учётом терминологии вашего продукта и проверки в панели управления. Ваши шаблоны и URL-шаблоны остаются прежними, а переводы обновляются вместе с кодом.

## Previous and next questions

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

## Sitemap

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