Wie man Übersetzungen aus Dateien in TanStack Start v1 lädt
Trennbare übersetzbare Inhalte vom Code
Problem
Das direkte Hardcoding von benutzerorientierten Zeichenketten in Komponenten erzeugt eine enge Kopplung zwischen Inhalt und Code. Bei jeder Änderung einer Zeichenkette oder Hinzufügung einer neuen Sprache müssen Entwickler Quelldateien lokalisieren und modifizieren und dann die Anwendung neu bereitstellen. Dieser Ansatz macht Übersetzungsworkflows von Entwicklungszyklen abhängig und verhindert, dass nicht-technische Teammitglieder Texte aktualisieren können. Mit zunehmender Anzahl unterstützter Sprachen wird die bedingte Logik zur Auswahl der richtigen Zeichenkette unhandlich und fehleranfällig. Das Ergebnis sind langsamere Iterationen, höhere Wartungskosten und ein Codebase, der mit übersetzbaren Inhalten überladen ist, die anderswo untergebracht werden sollten.
Lösung
Trennen Sie alle übersetzbaren Zeichenketten vom Anwendungscode, indem Sie sie in externen JSON-Dateien speichern, eine pro Locale. Definieren Sie einen stabilen Schlüssel für jede Nachricht und referenzieren Sie diese Schlüssel in Komponenten anstelle von literalem Text. Zur Laufzeit wird die entsprechende Übersetzungsdatei basierend auf dem Locale des Benutzers geladen und diese Nachrichten dem IntlProvider von react-intl bereitgestellt. Dies entkoppelt Inhalt vom Code: Übersetzer können direkt mit JSON-Dateien arbeiten, Textänderungen erfordern keine Codemodifikationen, und das Hinzufügen einer neuen Sprache bedeutet das Hinzufügen einer neuen Datei, ohne Komponenten zu berühren.
Schritte
1. Erstellen Sie Übersetzungsdateien für jedes Locale
Organisieren Sie Übersetzungen als JSON-Dateien in einem dedizierten Verzeichnis, mit einer Datei pro Locale, die Schlüssel-Wert-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 Schlüsseln, aber übersetzten Werten.
2. Übersetzungen mit einer Serverfunktion laden
Verwenden Sie eine Serverfunktion, um die Übersetzungsdatei basierend auf der angeforderten Locale von der Festplatte zu lesen. Dies stellt sicher, dass Übersetzungen serverseitig während des SSR geladen und auf dem Client während der Navigation 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 läuft nur auf dem Server und hält den Dateisystemzugriff sicher.
3. Einen Helfer erstellen, um die Locale des Benutzers zu bestimmen
Definieren Sie ein kleines Hilfsprogramm, das die Locale aus der Anfrage extrahiert oder auf einen Standardwert zurückgreift, wodurch es in verschiedenen Routen wiederverwendbar wird.
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 zuerst die Query-Parameter, dann den Accept-Language-Header und verwendet standardmäßig Englisch. Sie bietet eine einzige Quelle der Wahrheit für die Locale-Erkennung.
4. Nachrichten in einem Route-Loader laden
Verwenden Sie den Route-Loader, um Nachrichten für die aktuelle Locale vor dem Rendern zu laden und sie im Komponentenbaum verfügbar zu machen.
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 Serverfunktion auf, um Nachrichten abzurufen, und die Komponente greift über useLoaderData darauf zu. Dieses Muster funktioniert sowohl für SSR als auch für clientseitige Navigation.
5. Stellen Sie Nachrichten für react-intl bereit
Umschließen Sie Ihren Komponenten-Baum mit IntlProvider und übergeben Sie das geladene Gebietsschema und die Nachrichten, 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 macht das Gebietsschema und die Nachrichten für alle react-intl-Komponenten und Hooks verfügbar. Komponenten können jetzt Nachrichten anhand von Schlüsseln mit FormattedMessage oder useIntl referenzieren, und die korrekte Übersetzung wird basierend auf dem geladenen Gebietsschema gerendert.
6. Referenzieren Sie Nachrichten anhand von Schlüsseln in Komponenten
Verwenden Sie die FormattedMessage-Komponente oder den useIntl-Hook von react-intl, um übersetzte Zeichenketten anzuzeigen, indem Sie auf die in Ihren JSON-Dateien definierten Schlüssel 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 die übersetzte Zeichenkette inline, während useIntl().formatMessage eine Zeichenkette zur Verwendung in Attributen oder JavaScript-Logik zurückgibt. Beide akzeptieren values für die Interpolation und unterstützen die ICU-Nachrichtensyntax für Plurale und Formatierung.