So laden Sie Übersetzungen aus Dateien in TanStack Start v1
Trennen Sie übersetzbare Inhalte vom Code
Problem
Das direkte Hardcodieren von benutzerseitigen Strings in Komponenten erzeugt eine enge Kopplung zwischen Inhalt und Code. Jedes Mal, wenn sich ein String ändert oder eine neue Sprache hinzugefügt wird, müssen Entwickler Quelldateien lokalisieren und modifizieren und anschließend die Anwendung neu deployen. Dieser Ansatz macht Übersetzungs-Workflows von Engineering-Zyklen abhängig und verhindert, dass nicht-technische Teammitglieder Texte aktualisieren können. Mit zunehmender Anzahl unterstützter Sprachen wird die bedingte Logik zur Auswahl des richtigen Strings unhandlich und fehleranfällig. Das Ergebnis sind langsamere Iterationen, höhere Wartungskosten und eine Codebasis, die mit übersetzbaren Inhalten überladen ist, die anderswo gespeichert werden sollten.
Lösung
Trennen Sie alle übersetzbaren Strings vom Anwendungscode, indem Sie diese in externen JSON-Dateien speichern – eine pro Locale. Definieren Sie einen stabilen Key für jede Nachricht und referenzieren Sie diese Keys in Komponenten anstelle von literalem Text. Laden Sie zur Laufzeit die entsprechende Übersetzungsdatei basierend auf der Locale des Benutzers und stellen Sie diese Nachrichten dem IntlProvider von react-intl bereit. Dies entkoppelt Inhalt vom Code: Übersetzer können direkt mit JSON-Dateien arbeiten, Textänderungen erfordern keine Code-Modifikationen, und das Hinzufügen einer neuen Sprache bedeutet lediglich das Hinzufügen einer neuen Datei, ohne Komponenten anzufassen.
Schritte
1. Erstellen Sie Übersetzungsdateien für jede Locale
Organisieren Sie Übersetzungen als JSON-Dateien in einem dedizierten Verzeichnis, mit einer Datei pro Locale, die Key-Value-Paare für alle Nachrichten enthält.
{
"welcome": "Welcome back",
"greeting": "Hello, {name}",
"itemCount": "{count, plural, =0 {No items} one {One item} other {# items}}"
}
Speichern Sie dies als app/translations/en.json. Erstellen Sie parallele Dateien für andere Locales, wie app/translations/es.json und app/translations/fr.json, mit denselben Keys, aber übersetzten Werten.
2. Übersetzungen mithilfe einer Server-Funktion laden
Verwenden Sie eine Server-Funktion, um die Übersetzungsdatei basierend auf der angeforderten Locale von der Festplatte zu lesen. Dadurch wird sichergestellt, dass Übersetzungen während des SSR serverseitig geladen und bei der Navigation auf dem Client abgerufen werden.
import { createServerFn } from "@tanstack/react-start";
import * as fs from "node:fs";
export const getMessages = createServerFn({ method: "GET" }).handler(
async ({ request }) => {
const url = new URL(request.url);
const locale = url.searchParams.get("locale") || "en";
const filePath = `app/translations/${locale}.json`;
const content = await fs.promises.readFile(filePath, "utf-8");
return JSON.parse(content);
},
);
Diese Funktion liest die JSON-Datei für die angegebene Locale und gibt das geparste Messages-Objekt zurück. Sie wird ausschließlich auf dem Server ausgeführt, wodurch der Dateisystemzugriff sicher bleibt.
3. Hilfsfunktion zur Ermittlung der Benutzer-Locale erstellen
Definieren Sie ein kleines Utility, das die Locale aus der Anfrage extrahiert oder auf einen Standardwert zurückfällt, sodass es über mehrere Routen hinweg wiederverwendbar ist.
export function getLocaleFromRequest(request: Request): string {
const url = new URL(request.url);
const localeParam = url.searchParams.get("locale");
if (localeParam) return localeParam;
const acceptLanguage = request.headers.get("accept-language");
if (acceptLanguage) {
const match = acceptLanguage.split(",")[0].split("-")[0];
return match || "en";
}
return "en";
}
Diese Funktion prüft zunächst Query-Parameter, dann den Accept-Language-Header und verwendet standardmäßig Englisch. Sie bietet eine zentrale Quelle für die Locale-Erkennung.
4. Messages in einem Route-Loader laden
Verwenden Sie den Route-Loader, um Messages für die aktuelle Locale vor dem Rendering abzurufen und sie dem Komponentenbaum zur Verfügung zu stellen.
import { createFileRoute } from "@tanstack/react-router";
import { getMessages, getLocaleFromRequest } from "../lib/i18n";
export const Route = createFileRoute("/")({
loader: async ({ context }) => {
const locale = getLocaleFromRequest(context.request);
const messages = await getMessages({ data: { locale } });
return { locale, messages };
},
component: HomePage,
});
function HomePage() {
const { locale, messages } = Route.useLoaderData();
return (
<div>
<p>{messages.welcome}</p>
</div>
);
}
Der Loader ruft die Server-Funktion auf, um Messages abzurufen, und die Komponente greift über useLoaderData darauf zu. Dieses Muster funktioniert sowohl für SSR als auch für clientseitige Navigation.
5. Messages für react-intl bereitstellen
Umschließen Sie Ihren Komponentenbaum mit IntlProvider und übergeben Sie die geladene Locale und Messages, damit alle untergeordneten Komponenten auf Übersetzungen zugreifen können.
import { IntlProvider } from "react-intl";
function HomePage() {
const { locale, messages } = Route.useLoaderData();
return (
<IntlProvider locale={locale} messages={messages}>
<AppContent />
</IntlProvider>
);
}
function AppContent() {
return (
<div>
<FormattedMessage id="welcome" />
</div>
);
}
IntlProvider stellt die Locale und Messages allen react-intl-Komponenten und -Hooks zur Verfügung. Komponenten können nun mithilfe von FormattedMessage oder useIntl auf Messages über ihren Schlüssel verweisen, und die korrekte Übersetzung wird basierend auf der geladenen Locale gerendert.
6. Nachrichten per Schlüssel in Komponenten referenzieren
Verwenden Sie die FormattedMessage-Komponente oder den useIntl-Hook von react-intl, um übersetzte Strings anzuzeigen und dabei auf die in Ihren JSON-Dateien definierten Schlüssel zu verweisen.
import { FormattedMessage, useIntl } from "react-intl";
function UserGreeting({ name }: { name: string }) {
const intl = useIntl();
const title = intl.formatMessage({ id: "greeting" }, { name });
return (
<div>
<h1 title={title}>
<FormattedMessage id="greeting" values={{ name }} />
</h1>
<p>
<FormattedMessage id="itemCount" values={{ count: 5 }} />
</p>
</div>
);
}
FormattedMessage rendert den übersetzten String inline, während useIntl().formatMessage einen String zur Verwendung in Attributen oder JavaScript-Logik zurückgibt. Beide akzeptieren values für Interpolation und unterstützen die ICU-Nachrichtensyntax für Plurale und Formatierung.