Vos boutons, votre navigation et vos états vides sont en allemand. Les noms de produits, les catégories et les descriptions sont toujours en anglais. Ce n’est pas un bug de votre configuration i18n, c’est sa limite. globalize.now est une infrastructure de localisation propulsée par l’IA : elle extrait les chaînes codées en dur de votre codebase, génère les clés et les catalogues de locales, puis les synchronise à chaque Git push. Elle lit le code, pas les lignes, alors que le contenu recherché par vos utilisateurs se trouve dans les lignes.
Corriger cela relève d’une décision de schéma, pas d’un choix d’outil. Voici le modèle qui tient la route.
Pourquoi l’interface de mon app Lovable est-elle traduite alors que le contenu de la base de données est toujours en anglais ?
Parce qu’un extracteur de chaînes ne peut voir que ce qui est écrit dans vos fichiers source.
Lorsque vous localisez une app Lovable, un scanner parcourt vos composants et extrait chaque chaîne littérale qu’il trouve : "Add to cart", "No results", "Sign in". Elles deviennent des identifiants de message dans un catalogue, puis le catalogue est traduit et commité. Ce pipeline est déterministe et couvre entièrement l’interface.
Il ne peut pas couvrir une ligne. products.name = 'Trail Runner' ne se trouvait jamais dans un fichier .tsx. Cette donnée est arrivée via un formulaire, un script de seed ou un import, et vit dans Postgres : le backend intégré de Lovable Cloud, qui s’appuie sur Supabase, ou un projet Supabase que vous avez connecté vous-même. Aucun scanner de code ne la lira, et relancer l’extraction autant de fois que nécessaire n’y changera rien.
Vous vous retrouvez donc avec une interface traduite autour d’un contenu anglais. La navigation affiche Warenkorb, tandis que le produit affiche Trail Runner, lightweight trail shoe for wet conditions. Les utilisateurs le remarquent immédiatement, et le résultat paraît pire qu’une app non traduite : on dirait qu’elle a été abandonnée en cours de route.
Qu’est-ce qui compte comme contenu de base de données dans une app Lovable ?
Tout ce qu’un utilisateur ou un administrateur peut modifier sans déploiement. En pratique, dans une app Lovable classique, cela comprend :
- Noms et descriptions de produits, de forfaits ou d’annonces
- Libellés de catégories, de tags et de statuts rendus depuis une table de référence
- Contenu marketing stocké dans une table
pagesousections - Modèles d’e-mails et de notifications
- Valeurs de type enum affichées directement, comme
status = 'pending_review' - Contenu généré par les utilisateurs, généralement conservé volontairement dans sa langue d’origine
Le dernier point est important. Ne traduisez pas le contenu généré par les utilisateurs par défaut. Un avis rédigé en espagnol doit rester en espagnol. Le contenu que vous localisez réellement est celui que vous rédigez et livrez.
Tout le reste — l’interface autour de ce contenu — reste dans vos catalogues. Les deux ensembles ne doivent jamais se chevaucher. Si un libellé apparaît à la fois dans une table de référence et dans une chaîne codée en dur, choisissez un seul emplacement et supprimez l’autre. Sinon, vous livrerez deux mots allemands différents pour le même concept.
Comment stocker les traductions du contenu de base de données dans Supabase ?
Utilisez une table de traduction distincte, indexée par l’identifiant de la ligne et une colonne de locale.
create table product_translations (
product_id uuid not null references products(id) on delete cascade,
locale text not null,
name text not null,
description text,
primary key (product_id, locale)
);
create index product_translations_locale_idx on product_translations (locale);
Ajouter le japonais devient alors un insert, pas une migration. Les politiques de sécurité au niveau des lignes s’appliquent à une seule table au lieu de s’étendre à un ensemble croissant de colonnes. Une traduction manquante correspond à une ligne manquante : facile à détecter dans une requête et facile à signaler.
L’alternative consiste à utiliser une colonne JSONB par champ traduisible :
alter table products
add column name_i18n jsonb not null default '{}'::jsonb;
-- { "en": "Trail Runner", "de": "Trail Runner", "fr": "Coureur de sentier" }
Cette approche est plus rapide à prototyper et convient aux petits contenus essentiellement statiques. En contrepartie, elle impose des contraintes par langue, rend les traductions partielles difficiles à auditer et devient coûteuse à indexer dès que vous filtrez ou triez sur la valeur localisée.
Le troisième modèle — une colonne par langue (name_en, name_de, name_fr) — est celui à éviter. Chaque nouvelle langue devient une migration de schéma sur chaque table et, dans un projet Lovable, cela signifie demander régulièrement à l’agent de modifier des tables de production. Si vous voulez approfondir ce modèle, l’extension Postgres publique pg_i18n implémente l’approche par table de traduction sous forme de vues avec fallback intégré. Elle mérite d’être consultée avant d’arrêter votre schéma.
Quel modèle choisir ?
La table de traduction, sauf si le contenu est minuscule et figé.
Choisissez JSONB uniquement si vous avez moins de quelques champs traduisibles, si vous n’avez pas besoin de filtrer ou de trier dessus et si personne d’autre que vous ne les modifiera. Pour tout le reste — présence d’une interface d’administration, ajout prévu de langues ou volume supérieur à quelques centaines de lignes — choisissez la table.
Comment interroger la bonne langue au moment du rendu ?
Avec un fallback explicite, dans la base de données, afin que l’app n’ait jamais à s’en préoccuper.
create or replace function products_for_locale(p_locale text)
returns table (id uuid, slug text, name text, description text)
language sql stable as $$
select
p.id,
p.slug,
coalesce(t.name, base.name) as name,
coalesce(t.description, base.description) as description
from products p
left join product_translations base
on base.product_id = p.id and base.locale = 'en'
left join product_translations t
on t.product_id = p.id and t.locale = p_locale;
$$;
Le point d’appel ne transporte alors qu’une locale :
const { data: products } = await supabase
.rpc('products_for_locale', { p_locale: locale });
Deux règles permettent à ce modèle de tenir face aux données réelles. Premièrement, coalesce vers la langue source, jamais vers une chaîne vide : un catalogue partiellement traduit doit afficher l’anglais, pas des cartes vides. Deuxièmement, la locale transmise doit être la même valeur que celle utilisée par votre segment de route et par les clés de vos catalogues d’interface. Une seule liste de locales, trois consommateurs : routeur, catalogues, base de données.
C’est sur ce dernier point que la plupart des projets Lovable dérivent. Le routeur connaît de, les catalogues ont été construits pour de-DE et la base de données contient des lignes marquées german. Choisissez l’ensemble exact de tags BCP 47 utilisés par vos routes de locales et alignez tout le reste dessus. Si ces routes ne sont pas encore configurées, les guides par stack couvrent les deux stacks générées par Lovable : le build SPA Vite et le défaut SSR TanStack Start.
Pourquoi ne pas simplement laisser un widget de runtime traduire le texte de la base de données ?
Parce qu’il agit sur la page rendue, pas sur vos données, et cette différence se manifeste à trois endroits.
Un widget de traduction JavaScript lit le texte qui arrive dans le DOM, puis le remplace après coup. Il capture effectivement les lignes de la base de données : c’est la seule chose que les widgets font et que les catalogues ne font pas, ce qui explique pourquoi ils semblent d’abord être une solution complète.
Les coûts sont structurels. Les lignes traduites n’existent que dans la session du navigateur : les moteurs de recherche qui explorent la page voient donc la langue source. Le centre d’aide de Weglot indique lui-même que son intégration JavaScript n’apporte pas de bénéfice SEO, puisque les traductions sont rendues côté client. Les corrections vivent dans le dashboard du fournisseur plutôt que dans votre base de données : la valeur réelle d’un champ est donc répartie entre deux systèmes. Enfin, le remplacement intervient après le rendu initial, ce qui explique pourquoi les apps traduites de cette manière affichent brièvement l’anglais à chaque navigation.
Rien de tout cela ne justifie de rejeter complètement cette approche pour un outil interne. En revanche, c’est une bonne raison de ne pas bâtir dessus un produit public, indexable et multilingue.
Comment les deux couches restent-elles synchronisées ?
En partageant une seule liste de locales et un seul déclencheur.
La couche de code est automatique. Ajoutez la compétence lovable-i18n à votre espace de travail Lovable et demandez à Lovable de configurer l’i18n, ou installez-la localement avec :
npx skills add globalize-now/globalize-skills
À partir de là, l’extraction s’exécute et une pull request de traduction s’ouvre à chaque push : aucune étape dans un dashboard, aucune passe manuelle sur les fichiers de traduction. C’est la même boucle que celle décrite dans pourquoi les traductions Lovable cassent à chaque push.
La couche de données vous appartient et nécessite un point d’intégration explicite : chaque fois que vous ajoutez une locale, la même liste que celle qui alimente vos routes et vos catalogues doit lancer un backfill dans votre table de traductions. Dans un projet Lovable, la solution pratique consiste à demander une fois à l’agent d’écrire une fonction de seed qui lit la liste des locales et insère les lignes manquantes à partir des valeurs dans la langue source. Les lignes manquantes sont ainsi complétées, pas inventées, et coalesce comble l’écart en attendant.
La tarification ne vous met pas non plus de bâtons dans les roues. Le forfait Starter coûte 20 € par mois et par espace de travail et inclut 20 € de crédit de traduction par mois — environ 200 000 mots —, puis l’utilisation au-delà est facturée au tarif publié par caractère. Aucun frais par siège ni par langue : ajouter une cinquième langue coûte uniquement les caractères qu’elle consomme. L’inscription inclut 5 € de crédit de traduction, sans carte bancaire. Pour en savoir plus sur l’intégration à une stack construite avec un agent, consultez les pages vibe coders et développeurs.
Que se passe-t-il quand un fournisseur de localisation ferme ses portes ?
Tout ce qui était stocké sur ses serveurs, et rien de ce qui était stocké sur les vôtres.
Ce n’est pas une hypothèse théorique ce mois-ci. Lovalingo, la couche de traduction runtime adoptée par de nombreux créateurs Lovable, fermera le 31 août 2026 à 23 h 59 CEST, comme l’indique sa propre page de fermeture du service. Les nouveaux projets et abonnements sont déjà suspendus, et des exports de sauvegarde sont en préparation pour les clients actifs. Le point intéressant de ses propres recommandations : le fournisseur demande aux clients de mettre en place une internationalisation gérée par le projet en parallèle, puis de supprimer Lovalingo uniquement après avoir vérifié le remplacement en production, dans un environnement réel.
Voilà tout l’argument résumé en une phrase par le fournisseur dont le départ en est la preuve. Un catalogue versionné et une table product_translations sont à vous ; ils n’ont pas de statut de service. Une couche runtime gérée est une dépendance qui affiche les textes de votre produit, et les dépendances finissent par disparaître.
Si vous migrez actuellement depuis un widget, commencez par la couche de données. Les chaînes de l’interface seront réextraites de votre code en quelques minutes. Les traductions en base de données, si elles n’ont jamais existé que dans le dashboard d’un fournisseur, ne reviendront pas. Notre comparatif Lovalingo détaille les points à vérifier avant toute suppression.
La couche de code est précisément celle que vous ne devriez pas gérer à la main. globalize.now extrait vos chaînes, génère les catalogues et ouvre une pull request de traduction à chaque push, ce qui vous laisse libre de concevoir la couche de données une fois pour toutes.
globalize.now transforme le texte codé en dur de votre application en fichiers de traduction prêts à l'emploi et les maintient à jour au fur et à mesure de vos déploiements.
Essayer globalize.now gratuitement