# General Translation Knowledge Base: Comment traduire une application Flask ou FastAPI ?
URL: https://generaltranslation.com/fr/kb/translate-python-app.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Marquez les chaînes visibles par l’utilisateur, déterminez le paramètre régional de chaque requête (généralement à partir de l’en-tête Accept-Language), puis chargez les traductions correspondantes. Les applications Flask s’appuient généralement sur Flask-Babel et des catalogues gettext, tandis que les applications FastAPI intègrent Babel dans un middleware. La CLI de General Translation remplit ces catalogues, et ses SDK expérimentaux gt-flask et gt-fastapi traduisent les chaînes t() intégrées au code, sans catalogue.
Category: Internationalisation
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

Ni Flask ni FastAPI ne gèrent la traduction nativement : vous devez donc ajouter trois éléments, à savoir un moyen de marquer les chaînes visibles par l’utilisateur, un moyen de choisir la langue de chaque requête et des traductions pour chaque langue proposée. L’approche classique repose sur les catalogues gettext de Babel ; une approche plus récente conserve les chaînes intégrées au code et charge les traductions au format JSON. Dans les deux cas, la langue est propre à la requête, et non au processus, car un même serveur répond simultanément à des utilisateurs parlant de nombreuses langues.

## Ce dont une application web Python traduite a besoin

* **Des chaînes marquées.** Le code appelle une fonction telle que `gettext()` ou [`t()`](/docs/python/reference/functions/t) au lieu de renvoyer une chaîne de caractères brute, et les modèles utilisent `_()` ou `{% trans %}`. C&#39;est ce marquage qui permet à un outil de repérer chaque chaîne et d&#39;en retrouver la traduction.
* **Un paramètre régional par requête.** Il provient généralement de l&#39;en-tête `Accept-Language`, éventuellement remplacé par un préfixe d&#39;URL, un cookie ou un réglage du compte. Flask le conserve dans le contexte de la requête ; FastAPI traite les requêtes de manière concurrente sur une seule boucle d&#39;événements, si bien que le paramètre régional est stocké dans une `contextvars.ContextVar` définie par chaque requête.
* **Des traductions pour chaque paramètre régional.** gettext charge des catalogues `.mo` compilés ; les SDK General Translation chargent un fichier JSON par paramètre régional. Dans les deux cas, une traduction manquante est remplacée par le texte source, sans erreur : les chaînes non traduites s&#39;affichent en anglais au lieu de provoquer un plantage.

## Option 1 : catalogues gettext avec Babel

[Babel](https://babel.pocoo.org/en/latest/cmdline.html) extrait les chaînes marquées vers un modèle POT, crée et met à jour un catalogue PO par langue, puis compile ces catalogues en fichiers `.mo`. Il met également en forme les dates, les nombres et les devises pour n&#39;importe quel paramètre régional.

### Flask avec Flask-Babel

[Flask-Babel](https://python-babel.github.io/flask-babel/) (4.0.0) intègre Babel aux requêtes et aux modèles Jinja de Flask. Il vous suffit de lui fournir un `locale_selector` qui renvoie le paramètre régional de chaque requête :

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


def select_locale():
    # Meilleure correspondance entre l'en-tête Accept-Language et les langues prises en charge
    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 sélectionne la forme du pluriel adaptée à la locale active
        "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")
```

Dans les modèles, les chaînes sont marquées avec `_()`, par exemple `{{ _("Welcome back, %(name)s!", name=name) }}`. Un fichier `babel.cfg` indique à `pybabel` où chercher, et quatre commandes suffisent à gérer les catalogues :

```bash
# babel.cfg contient [python: **.py] et [jinja2: **/templates/**.html]
pybabel extract -F babel.cfg -o messages.pot .
pybabel init -i messages.pot -d translations -l es    # une fois par langue
pybabel update -i messages.pot -d translations        # après chaque extraction, pour toutes les langues
pybabel compile -d translations
```

Avec le catalogue espagnol généré par la CLI de General Translation, une requête avec `Accept-Language: es-MX,es;q=0.9` a renvoyé « Bandeja de entrada » et « Tienes 5 mensajes sin leer. » En revanche, avec un catalogue polonais créé mais vide, une requête en polonais a renvoyé toutes les chaînes de caractères en anglais, sans le moindre avertissement.

### FastAPI avec Babel

FastAPI ne propose aucun équivalent officiel de Flask-Babel. Il existe des paquets tiers comme [fastapi-babel](https://pypi.org/project/fastapi-babel/) et [starlette-babel](https://pypi.org/project/starlette-babel/), mais vous pouvez aussi charger vous-même les catalogues Babel dans un middleware. Cette version lit le même répertoire `translations` et stocke le catalogue de chaque requête dans une `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:
    # Lit translations/<locale>/LC_MESSAGES/messages.mo, compilé avec 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 signifie « non acceptable » : on l'ignore
        if tag and q > 0:
            weighted.append((q, tag.replace("-", "_")))
    # sorted() est stable : les tags de même poids conservent l'ordre de l'en-tête
    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},
    }
```

Sous Uvicorn, `Accept-Language: es-MX,es;q=0.9,en;q=0.8` a renvoyé les chaînes en espagnol, `de,en;q=0.5` s&#39;est rabattu sur l&#39;anglais, `en;q=0.5,es` a renvoyé l&#39;espagnol et `es;q=0,en` a renvoyé l&#39;anglais. `negotiate_locale` fait correspondre `es_MX` à `es`. `preferred_locales` trie les tags selon leur poids `q` et écarte ceux à `q=0`, qui désigne une langue que l&#39;utilisateur n&#39;accepte pas.

### Ce que l&#39;approche Babel laisse à votre charge

Babel gère les catalogues, mais ne rédige pas les traductions. Chaque chaîne de caractères nouvelle ou modifiée attend que quelqu&#39;un renseigne son `msgstr` dans chaque langue, y compris les trois formes plurielles du polonais, puis qu&#39;on exécute `pybabel compile` et qu&#39;on déploie. (Consultez [How do I translate PO and POT files?](/kb/translate-po-files) pour en savoir plus sur le format et sur les vérifications qui repèrent les entrées défectueuses, et [General Translation vs. Babel and Flask-Babel](/kb/general-translation-vs-flask-babel) pour savoir ce que couvre chaque outil.) Les applications Django suivent la même approche, avec leurs propres commandes. (Consultez [General Translation vs. Django&#39;s translation framework](/kb/general-translation-vs-django-i18n).)

## Choisir le paramètre régional de chaque requête

`Accept-Language` liste les langues de l&#39;utilisateur, avec des poids `q` facultatifs, et votre rôle consiste à les mettre en correspondance avec les langues que vous proposez, en prévoyant une valeur par défaut pour tous les autres cas. `best_match()` de Werkzeug appliqué à `request.accept_languages` et `negotiate_locale` de Babel se rabattent tous deux d&#39;un tag régional comme `es-MX` sur `es`. Donnez la priorité à un choix explicite plutôt qu&#39;à l&#39;en-tête : un préfixe d&#39;URL `/es/` pour les pages indexées par les moteurs de recherche, ou un cookie défini par le sélecteur de langue. (Consultez [Comment détecter la langue du navigateur d&#39;un utilisateur ?](/kb/detect-user-language) pour connaître l&#39;ordre complet des signaux.) Côté Node.js, les serveurs résolvent le même problème avec `AsyncLocalStorage`. (Consultez [Comment traduire une application Node.js ?](/kb/translate-node-app).)

## Comparer les approches

| Approche                                            | Écriture des chaînes                                                      | Paramètre régional par requête                    | Qui rédige les traductions                | État                                                                       |
| --------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------- |
| Flask-Babel avec des catalogues PO                  | `gettext`, `ngettext`, Jinja `_()`                                        | `locale_selector`                                 | Vous ou des traducteurs, par catalogue    | Stable                                                                     |
| Babel dans un middleware FastAPI                    | `gettext` via une `ContextVar`                                            | Votre middleware                                  | Vous ou des traducteurs, par catalogue    | Stable, assemblé par vos soins                                             |
| API de traduction automatique à chaque requête      | Chaînes brutes envoyées à l&#39;exécution                                 | Vous transmettez la langue cible                  | L&#39;API, à chaque requête               | Ajoute de la latence et un coût à chaque appel, sauf mise en cache         |
| Catalogues Babel avec la CLI de General Translation | Comme avec Babel                                                          | Comme avec Babel                                  | General Translation, avant le déploiement | La CLI est stable ; Babel reste inchangé                                   |
| `gt-flask` ou `gt-fastapi` de General Translation   | [`t()`](/docs/python/reference/functions/t) intégré avec des messages ICU | Mise en correspondance `Accept-Language` intégrée | General Translation, avant le déploiement | Expérimental (0.1.0) ; les traductions chargées expirent après 60 secondes |

Utilisez Flask-Babel, ou Babel dans un middleware FastAPI, si vous disposez déjà de catalogues ou de traducteurs. Ajoutez la CLI de General Translation à cette configuration si vous voulez que chaque catalogue soit rempli dans toutes les langues à chaque modification, sans passer par un cycle de traduction. Essayez `gt-flask` ou `gt-fastapi` sur un prototype si vous préférez garder les chaînes directement dans le code, sans fichiers de catalogue, et figez la version. Ne faites appel à une API de traduction automatique au moment de la requête que pour le contenu qui n&#39;existe qu&#39;à l&#39;exécution, comme les publications des utilisateurs.

## Comment General Translation traduit les applications Flask et FastAPI

### Conserver Babel et traduire les catalogues

La CLI de General Translation, `gt`, lit un catalogue PO en anglais et génère les catalogues des autres langues à l&#39;emplacement attendu par Babel. Créez une seule fois le catalogue anglais avec `pybabel init -l en`, mettez-le à jour après chaque extraction avec `pybabel update -l en`, puis faites-y pointer `gt.config.json` :

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

Testée avec `gt` 2.26.11, la CLI associe le catalogue anglais situé dans `translations/en/LC_MESSAGES` aux dossiers `es` et `pl` qui se trouvent au même niveau. Exécutez [`npx gt translate`](/docs/cli/reference/commands/translate), puis `pybabel compile -d translations`. General Translation remplit chaque forme plurielle pour chaque langue cible et vérifie, dans chaque forme du pluriel, la présence des espaces réservés tels que `%(num)d`. Un glossaire et des instructions personnalisées définis dans un [groupe de contexte](/docs/platform/dashboard/guides/defining-context-for-translations) garantissent une terminologie cohérente avec vos autres projets. La CLI s&#39;exécute sur Node.js : la machine qui la lance doit donc disposer de Node. Votre application continue de charger des catalogues compilés, sans aucune dépendance à General Translation à l&#39;exécution.

### Ou utilisez gt-flask et gt-fastapi

Les SDK Python de General Translation, `gt-flask` et `gt-fastapi` (0.1.0 sur PyPI, issus du dépôt [gt-python](https://github.com/generaltranslation/gt-python)), sont expérimentaux et peuvent évoluer d&#39;une version à l&#39;autre. Ils nécessitent Python 3.10 ou une version ultérieure. Les chaînes restent dans votre code sous forme de messages ICU passés à [`t`](/docs/python/reference/functions/t), les valeurs étant fournies en arguments par mot-clé, tandis que [`initialize_gt`](/docs/python/reference/functions/initialize-gt) configure la mise en correspondance de l&#39;en-tête `Accept-Language`. Cette application Flask charge les traductions depuis des fichiers JSON locaux :

```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]:
    # Fichiers écrits par la CLI gt dans files.gt.output
    path = Path("translations") / f"{locale}.json"
    return json.loads(path.read_text()) if path.exists() else {}


app = Flask(__name__)
# Avec Flask, transmettez les locales ici pour charger les traductions au démarrage
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(),
    }
```

Pour FastAPI, importez depuis `gt_fastapi`, créez `FastAPI()` et utilisez `@app.get`ﾠ; [`initialize_gt`](/docs/python/reference/functions/initialize-gt) y ajoute un middleware HTTP et charge les traductions pendant le cycle de vie (lifespan) de l&#39;application, si bien que les locales définies dans `gt.config.json` suffisent. Avec le fichier `translations/es.json` généré par la CLI de General Translation, les deux applications ont renvoyé la réponse suivanteﾠ:

```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."}
```

C&#39;est la CLI qui génère ces fichiers. Ajoutez `gt-flask` ou `gt-fastapi` à `requirements.txt` ou à `pyproject.toml` pour que la CLI le détecte, puis définissez le chemin de sortie dans `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) analyse vos fichiers `.py` à la recherche d&#39;appels à [`t()`](/docs/python/reference/functions/t) (en ignorant les environnements virtuels, les tests et les migrations), les traduit, puis écrit un fichier par paramètre régional. Sur cette application, [`npx gt validate`](/docs/cli/reference/commands/validate) a détecté 2 entrées traduisibles pour `gt-flask` sans appeler l&#39;API. Pour charger plutôt les traductions depuis le CDN de General Translation, définissez `projectId` et `cacheUrl` dans la configuration (voir [Choisir comment les traductions sont fournies](/docs/python/guides/configuring#delivery)). Les SDK ne nécessitent aucune clé API ; avec des fichiers locaux, aucun appel réseau n&#39;est effectué à l&#39;exécution.

Dans la version 0.1.0, [`t()`](/docs/python/reference/functions/t) lit les traductions depuis un cache en mémoire qui expire au bout de 60 secondes et n&#39;est pas rechargé sur ce chemin de lecture : un serveur qui tourne longtemps renvoie donc les chaînes sources dès l&#39;expiration du cache, jusqu&#39;à la prochaine exécution du loader. (Voir les [notes sur le préchargement](/docs/python/guides/configuring#preload).) Tant qu&#39;une nouvelle livraison n&#39;aura pas corrigé ce comportement, utilisez des catalogues Babel remplis par la CLI pour le trafic de production, et réservez `gt-flask` et `gt-fastapi` aux prototypes.

## Questions fréquentes

### gt-flask remplace-t-il le formatage des dates et des valeurs monétaires de Flask-Babel ?

Non. Dans la version 0.1.0, `gt-flask` et `gt-fastapi` exportent des utilitaires de traduction et de gestion des paramètres régionaux ([`t`](/docs/python/reference/functions/t), [`get_locale`](/docs/python/reference/functions/get-locale), [`declare_var`](/docs/python/reference/functions/declare-var) et quelques autres). Continuez donc à utiliser `format_date` et `format_currency` de Babel pour le formatage selon les paramètres régionaux.

### Comment permettre aux utilisateurs de choisir une langue au lieu d&#39;utiliser Accept-Language ?

Avec Flask-Babel, lisez un cookie ou un segment d&#39;URL dans votre `locale_selector`. Avec les SDK General Translation, passez un callback [`get_locale`](/docs/python/reference/functions/get-locale) à [`initialize_gt`](/docs/python/reference/functions/initialize-gt) ; il remplace la mise en correspondance basée sur l&#39;en-tête : c&#39;est donc à vous de renvoyer un paramètre régional pris en charge. (Voir [Détecter les paramètres régionaux de la requête](/docs/python/guides/detecting-locale).)

### Comment traduire le contenu d&#39;une base de données ou les publications des utilisateurs ?

Les catalogues et [`t()`](/docs/python/reference/functions/t) prennent en charge les chaînes présentes dans votre code. Le contenu qui n&#39;existe qu&#39;à l&#39;exécution doit faire l&#39;objet d&#39;un appel de traduction au moment où il est créé ou demandé, par exemple via l&#39;endpoint [Translate content](/docs/platform/openapi/reference/translation/translate-content) de General Translation (`POST /v2/projects/{projectId}/translate`). Les résultats sont ensuite stockés avec le contenu original.

## Points clés

* Flask et FastAPI nécessitent trois ajouts : des chaînes marquées, un paramètre régional par requête et des traductions pour chaque paramètre régional ; FastAPI conserve le paramètre régional dans une `ContextVar`.
* Avec Flask, Flask-Babel s&#39;appuie sur un `locale_selector` et sur les commandes `pybabel extract`, [`init`](/docs/cli/reference/commands/init), `update` et `compile` ; FastAPI charge les `Translations` de Babel dans un middleware.
* Un catalogue vide ou manquant bascule silencieusement sur l&#39;anglais : il reste donc à renseigner chaque `msgstr`.
* La CLI de General Translation remplit les catalogues PO de Babel avec `"pot"` et `"transformationFormat": "PO"`, formes plurielles comprises, et votre application continue de charger les catalogues compilés.
* Les paquets expérimentaux `gt-flask` et `gt-fastapi` 0.1.0 de General Translation traduisent les chaînes [`t()`](/docs/python/reference/functions/t) intégrées à partir des fichiers JSON générés par la CLI ; avec Flask, passez `locales` à [`initialize_gt`](/docs/python/reference/functions/initialize-gt), et tenez compte de l&#39;expiration du cache au bout de 60 secondes avant de passer en production.

## Related questions

- [General Translation ou Babel et Flask-Babel : lequel choisir ?](https://generaltranslation.com/fr/kb/general-translation-vs-flask-babel.mdx): Babel formate les dates, les nombres et les devises pour n’importe quel paramètre régional et gère les catalogues gettext, tandis que Flask-Babel l’intègre à Flask, mais aucun des deux ne traduit vos chaînes. La CLI de General Translation remplit les fichiers PO de Babel pour chaque langue, formes du pluriel comprises : vous pouvez ainsi conserver Flask-Babel et ne plus traduire vos catalogues à la main.
- [Comment traduire des fichiers PO et POT ?](https://generaltranslation.com/fr/kb/translate-po-files.mdx): Remplissez chaque msgstr, y compris chaque forme du pluriel exigée par l’en-tête Plural-Forms, et laissez msgid, msgctxt et les espaces réservés inchangés. Exécutez msgfmt --check pour contrôler les en-têtes, le nombre de formes du pluriel et les espaces réservés des entrées dotées d’une option de format, puis vérifiez séparément qu’aucun msgstr n’est vide. La CLI de General Translation génère un fichier PO traduit pour chaque langue à partir de votre modèle POT.
- [Comment détecter la langue du navigateur d’un utilisateur ?](https://generaltranslation.com/fr/kb/detect-user-language.mdx): Lisez la liste ordonnée des langues du navigateur (navigator.languages en JavaScript ou l’en-tête Accept-Language côté serveur), triée par valeur q, puis mettez-la en correspondance avec vos paramètres régionaux pris en charge, en prévoyant une solution de secours. Safari et la navigation privée de Chrome n’indiquent qu’une seule langue, et la géolocalisation par IP révèle l’emplacement, pas la langue.
- [Comment traduire une application Node.js ?](https://generaltranslation.com/fr/kb/translate-node-app.mdx): Détectez la langue de chaque requête, généralement à partir de son en-tête Accept-Language, et renvoyez toutes les chaînes dans cette langue. Le package gt-node de General Translation lie le paramètre régional à chaque requête avec withGT et traduit les chaînes avec getGT, à partir des traductions générées par npx gt translate --publish avant le déploiement.
- [General Translation ou le framework de traduction de Django : avez-vous besoin des deux ?](https://generaltranslation.com/fr/kb/general-translation-vs-django-i18n.mdx): Utilisez conjointement le framework de traduction de Django et General Translation. Django gère l’extraction des messages, la sélection de la langue et l’affichage. General Translation remplit ses fichiers PO, formes du pluriel comprises, avec la terminologie de votre produit et une relecture dans le tableau de bord. Vos modèles et vos motifs d’URL restent inchangés, tandis que les traductions évoluent au rythme de votre code.

## Previous and next questions

- Previous question: [Comment traduire les titres SEO et les méta-descriptions ?](https://generaltranslation.com/fr/kb/translate-seo-metadata.mdx)
- Next question: [General Translation ou Lingo.dev : lequel choisir ?](https://generaltranslation.com/fr/kb/general-translation-vs-lingo-dev.mdx)

## Sitemap

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