Deine Buttons, deine Navigation und deine Empty States sind auf Deutsch. Deine Produktnamen, Kategorien und Beschreibungen sind noch auf Englisch. Das ist kein Bug in deinem i18n-Setup, das ist seine Grenze. globalize.now ist KI-gestützte Lokalisierungsinfrastruktur: Es extrahiert hartcodierte Strings aus deiner Codebase, generiert die Keys und Übersetzungskataloge und synchronisiert sie bei jedem Git-Push. Es liest Code, keine Datenzeilen — und genau in den Zeilen steckt der Inhalt, für den deine Nutzer:innen gekommen sind.

Das zu beheben ist eine Schema-Entscheidung, keine Tooling-Entscheidung. Hier ist die Version, die trägt.

Warum ist die UI meiner Lovable-App übersetzt, der Datenbank-Content aber noch auf Englisch?

Weil ein String-Extraktor nur sehen kann, was in deinen Quelldateien geschrieben steht.

Wenn du eine Lovable-App lokalisierst, durchläuft ein Scanner deine Komponenten und zieht jedes gefundene Literal heraus: "Add to cart", "No results", "Sign in". Diese werden zu Message-IDs in einem Katalog, und der Katalog wird übersetzt und committet. Diese Pipeline ist deterministisch und deckt die Oberfläche vollständig ab.

Eine Zeile kann sie nicht abdecken. products.name = 'Trail Runner' stand nie in einer .tsx-Datei. Es kam über ein Formular, ein Seed-Skript oder einen Import herein und lebt in Postgres — dem in Lovable Cloud eingebauten Backend, das auf Supabase läuft, oder in einem selbst verbundenen Supabase-Projekt. Kein Code-Scanner wird es je lesen, und noch so viele Extraktionsläufe ändern daran nichts.

So landest du bei einer übersetzten Hülle um englischen Inhalt. Die Navigation sagt Warenkorb, das Produkt darin sagt Trail Runner, lightweight trail shoe for wet conditions. Nutzer:innen merken das sofort, und es wirkt schlimmer als eine unübersetzte App, weil es aussieht, als wäre es auf halbem Weg aufgegeben worden.

Was zählt in einer Lovable-App als Datenbank-Content?

Alles, was ein Nutzer oder ein Admin ohne Deployment ändern kann. In der Praxis, bei einem typischen Lovable-Build, ist das:

  • Namen und Beschreibungen von Produkten, Plänen oder Listings
  • Kategorie-, Tag- und Status-Labels, die aus einer Lookup-Tabelle gerendert werden
  • Marketing-Texte, die in einer pages- oder sections-Tabelle gespeichert sind
  • E-Mail- und Benachrichtigungsvorlagen
  • Enum-artige Werte, die du direkt anzeigst, wie status = 'pending_review'
  • Nutzergenerierter Content, der üblicherweise absichtlich in seiner Originalsprache belassen wird

Der letzte Punkt ist entscheidend. Übersetze nutzergenerierten Content standardmäßig nicht. Eine auf Spanisch verfasste Rezension sollte auf Spanisch bleiben. Das, was du tatsächlich lokalisierst, ist der Content, den du verfasst und ausliefierst.

Alles andere, die Hülle um diesen Content, bleibt in deinen Katalogen. Die beiden Bereiche sollten sich nie überschneiden. Wenn ein Label sowohl in einer Lookup-Tabelle als auch als hartcodierter String auftaucht, entscheide dich für einen Ort und lösche den anderen — sonst lieferst du zwei unterschiedliche deutsche Wörter für denselben Begriff aus.

Wie speicherst du Übersetzungen für Datenbank-Content in Supabase?

Nutze eine separate Übersetzungstabelle, die über die Row-ID und eine Locale-Spalte referenziert wird.

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);

Japanisch hinzuzufügen ist jetzt ein Insert, keine Migration. Row-Level-Security-Policies greifen an einer Tabelle an, statt an einer wachsenden Zahl von Spalten, und eine fehlende Übersetzung ist eine fehlende Zeile — leicht abzufragen und leicht zu alarmieren.

Die Alternative ist eine JSONB-Spalte pro übersetzbarem Feld:

alter table products
  add column name_i18n jsonb not null default '{}'::jsonb;

-- { "en": "Trail Runner", "de": "Trail Runner", "fr": "Coureur de sentier" }

Das lässt sich schneller prototypen und eignet sich für kleinen, weitgehend statischen Content. Es kostet dich pro-Sprache-Constraints, macht partielle Übersetzungen mühsam zu prüfen und wird teuer zu indexieren, sobald du nach dem lokalisierten Wert filterst oder sortierst.

Das dritte Muster, eine Spalte pro Sprache (name_en, name_de, name_fr), ist das, das du vermeiden solltest. Jede neue Sprache ist eine Schema-Migration auf jeder Tabelle, und bei einem Lovable-Projekt bedeutet das, den Agent wiederholt zu bitten, Produktionstabellen zu ändern. Wenn du das Muster ausführlicher nachlesen willst: Die öffentliche Postgres-Erweiterung pg_i18n implementiert den Übersetzungstabellen-Ansatz als Views mit eingebautem Fallback und ist einen Blick wert, bevor du dich auf eine Struktur festlegst.

Welches Muster solltest du wählen?

Die Übersetzungstabelle — außer der Content ist winzig und eingefroren.

Wähle JSONB nur dann, wenn du weniger als eine Handvoll übersetzbarer Felder hast, nicht danach filtern oder sortieren musst und niemand außer dir sie je bearbeiten wird. Alles andere — alles mit Admin-UI, alles, dem du Sprachen hinzufügen wirst, alles mit mehr als ein paar Hundert Zeilen — will die Tabelle.

Wie fragst du zur Renderzeit die richtige Sprache ab?

Mit einem expliziten Fallback, in der Datenbank, sodass die App sich nie darum kümmern muss.

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;
$$;

Dann trägt die Aufrufstelle nur ein Locale und sonst nichts:

const { data: products } = await supabase
  .rpc('products_for_locale', { p_locale: locale });

Zwei Regeln sorgen dafür, dass das den Kontakt mit echten Daten übersteht. Erstens: coalesce fällt zurück auf die Quellsprache, niemals auf einen leeren String — ein halb übersetzter Katalog sollte Englisch zeigen, keine leeren Karten. Zweitens: Das locale, das du übergibst, muss derselbe Wert sein, den dein Routen-Segment verwendet, und derselbe Wert, nach dem deine UI-Kataloge indiziert sind. Eine Locale-Liste, drei Konsumenten: Router, Kataloge, Datenbank.

Genau an diesem letzten Punkt driften die meisten Lovable-Projekte auseinander. Der Router kennt de, die Kataloge wurden für de-DE gebaut, und die Datenbank hat Zeilen, die mit german getaggt sind. Lege exakt das Set an BCP-47-Tags fest, das deine Locale-Routen verwenden, und richte alles andere danach aus. Falls du diese Routen noch nicht eingerichtet hast, decken die stackspezifischen Walkthroughs das für beide von Lovable generierten Stacks ab: den Vite-SPA-Build und den TanStack-Start-SSR-Standard.

Warum nicht einfach ein Runtime-Widget den Datenbank-Text übersetzen lassen?

Weil es auf der gerenderten Seite arbeitet, nicht auf deinen Daten — und dieser Unterschied zeigt sich an drei Stellen.

Ein JavaScript-Übersetzungs-Widget liest, was auch immer im DOM landet, und tauscht es im Nachhinein aus. Das erfasst tatsächlich auch Datenbank-Zeilen — das ist genau das, was Widgets können und Kataloge nicht, und deshalb wirken sie auf den ersten Blick wie eine vollständige Lösung.

Die Kosten sind struktureller Natur. Übersetzte Zeilen existieren nur in der Browser-Session, sodass Suchmaschinen, die die Seite crawlen, deine Quellsprache sehen; Weglots eigenes Helpcenter erklärt, dass ihre JavaScript-Integration keine SEO-Vorteile bringt, weil diese Übersetzungen clientseitig gerendert werden. Korrekturen leben im Dashboard des Anbieters statt in deiner Datenbank, sodass der eigentliche Wert eines Feldes über zwei Systeme verteilt ist. Und der Austausch passiert nach dem Rendering, weshalb so gebaute übersetzte Apps bei jeder Navigation kurz Englisch aufblitzen lassen.

Nichts davon spricht dagegen, den Ansatz für ein internes Tool grundsätzlich zu verwerfen. Es spricht dagegen, ein öffentliches, indexierbares, mehrsprachiges Produkt darauf aufzubauen.

Wie bleiben die beiden Ebenen synchron?

Indem sie sich eine Locale-Liste und einen Trigger teilen.

Die Code-Ebene läuft automatisch. Füge den lovable-i18n-Skill in deinem Lovable-Workspace hinzu und bitte Lovable, i18n einzurichten, oder installiere ihn lokal mit:

npx skills add globalize-now/globalize-skills

Ab da läuft die Extraktion und bei jedem Push öffnet sich ein Übersetzungs-Pull-Request: kein Dashboard-Schritt, kein manueller Durchgang durch Übersetzungsdateien. Das ist derselbe Loop, den wir in warum Lovable-Übersetzungen bei jedem Push kaputtgehen beschrieben haben.

Die Datenebene liegt bei dir, und sie braucht einen bewussten Hook: Immer wenn du eine Sprache hinzufügst, sollte dieselbe Liste, die deine Routen und Kataloge steuert, auch einen Backfill in deine Übersetzungstabelle antreiben. Bei einem Lovable-Projekt ist die praktische Umsetzung eine Seed-Funktion, die du den Agent einmal schreiben lässt und die die Locale-Liste liest und fehlende Zeilen anhand der Werte der Quellsprache einfügt. Fehlende Zeilen werden dann aufgefüllt, nicht erfunden, und coalesce deckt die Lücke in der Zwischenzeit ab.

Auch bei den Preisen gibt es keinen Ärger. Der Starter-Plan kostet 20 € pro Monat pro Workspace und beinhaltet ein monatliches Übersetzungsguthaben von 20 € (rund 200.000 Wörter). Verbrauch darüber hinaus wird zum veröffentlichten Preis pro Zeichen abgerechnet. Es gibt keine Gebühren pro Sitzplatz und keine Gebühren pro Sprache – die fünfte Sprache hinzuzufügen kostet also nur die dafür nötigen Zeichen und sonst nichts. Bei der Anmeldung gibt es ein Übersetzungsguthaben von 5 € ohne Kreditkarte. Mehr dazu, wie das in einen agentengebauten Stack passt, auf den Seiten für Vibe Coder und Entwickler.

Was geht kaputt, wenn ein Lokalisierungs-Anbieter dichtmacht?

Alles, was auf dessen Servern lag – und nichts von dem, was bei dir lag.

Das ist diesen Monat keine Hypothese mehr. Lovalingo, die Runtime-Übersetzungsschicht, auf die viele Lovable-Builder setzen, schließt am 31. August 2026 um 23:59 Uhr MESZ, bestätigt auf der eigenen Service-Closure-Seite. Neue Projekte und Abos sind bereits gestoppt, und für aktive Kund:innen werden Preservation-Exports vorbereitet. Interessant ist der eigene Rat des Anbieters: Er empfiehlt, parallel eine projekteigene Internationalisierung aufzubauen und Lovalingo erst zu entfernen, nachdem die Ablösung in einer Live-Umgebung die Produktionschecks besteht.

Das ist das ganze Argument in einem Satz – vom Anbieter selbst, dessen Abschaltung es beweist. Ein committeter Katalog und eine product_translations-Tabelle sind beides Dinge, die dir gehören; sie haben keinen Service-Status. Eine gemanagte Runtime-Schicht ist eine Abhängigkeit, die den Text deines Produkts rendert – und Abhängigkeiten enden irgendwann.

Wenn du gerade von einem Widget wegmigrierst, kümmere dich zuerst um die Datenschicht. Die UI-Strings werden in wenigen Minuten neu aus deinem Code extrahiert. Die Datenbank-Übersetzungen, falls sie ausschließlich im Dashboard eines Anbieters existiert haben, kommen nicht zurück. Unser Lovalingo-Vergleich zeigt, was du prüfen solltest, bevor du irgendetwas entfernst.

Die Code-Schicht ist der Teil, den du nicht von Hand pflegen solltest. globalize.now extrahiert deine Strings, generiert die Kataloge und öffnet bei jedem Push einen Übersetzungs-Pull-Request – so kannst du die Datenschicht einmal designen und dann vergessen.

globalize.now konvertiert hartcodierte App-Texte in übersetzungsreife Locale-Dateien und hält sie aktuell, während Sie veröffentlichen.

Probiere globalize.now kostenlos