Wie wir unsere Website mit Astro in 9 Sprachen lokalisiert haben
Wir haben in diesem Sprint das Locale-Routing für 9 Sprachen ausgeliefert. Hier ist die ehrliche Version, wie es dazu kam — einschließlich des Fehlers, der uns still und leise in der Produktion geschadet hat, bevor wir ihn bemerkten.
Die Entdeckung
Bereits in Sprint 1 hatten wir das erwartete i18n-Gerüst aufgebaut: Locale-JSON-Dateien für alle 9 Sprachen (Englisch, Spanisch, Arabisch, Deutsch, Französisch, Japanisch, Portugiesisch, Russisch, Chinesisch), einen getTranslation()-Helfer, getLocalePath() zum Erstellen locale-bewusster URLs und hreflang-Tags auf jeder Seite, die auf alle 9 Locale-Varianten verweisen.
Was wir nicht gebaut hatten: irgendwelche tatsächlichen Seiten unter diesen Locale-URLs.
Jede Seite in src/pages/ hatte const locale = "en" as const fest verdrahtet. Die Übersetzungs-Infrastruktur existierte und funktionierte — aber dahinter war nichts. Das bedeutete, dass BaseLayout.astro auf jeder einzelnen Seite hreflang-Tags wie https://proman365.com/es/pricing/ ausgab, für URLs, die in der Produktion einen 404 zurückgaben.
Das ist kein kosmetischer Fehler. Suchmaschinen nutzen hreflang-Tags, um zu verstehen, welche URLs Sprachvarianten voneinander sind. 404s im großen Stil anzukündigen ist ein aktives negatives SEO-Signal — es sagt Crawlern, dass die Metadaten deiner Website nicht der Realität entsprechen, was das Vertrauen in alle deine Metadaten untergräbt, nicht nur den defekten Teil.
Die Lösung: Template-Extraktion, nicht Duplizierung
Die naive Lösung wäre, jede Seite 8 Mal zu kopieren, einmal pro nicht-englischer Locale, und in jeder Kopie eine andere Locale-Konstante fest zu verdrahten. Das haben wir sofort verworfen — 8-fache Dateien bedeuten 8-fachen Verfall, sobald jemand eine Seite anfasst und die anderen 7 Kopien vergisst.
Der Ansatz, den wir stattdessen wählten: den Inhalt jeder Seite in eine Template-Komponente extrahieren, die locale als Prop entgegennimmt, und dieses Template dann von zwei Arten von Routen aus rendern.
Für die 12 Tier-1-Seiten (Startseite, Preise, Kontakt, Partner, Hilfe und die sechs Feature-Seiten) wurde jede von src/pages/*.astro nach src/page-templates/*.astro verschoben, wobei die fest verdrahtete Locale-Konstante ersetzt wurde durch:
interface Props {
locale: Locale;
}
const { locale } = Astro.props;
Die ursprüngliche englische Route wird zu einem dünnen Wrapper — ein paar Zeilen, die nur das Template importieren und mit locale="en" rendern. Eine neue Spiegelroute unter src/pages/[locale]/ rendert dasselbe Template für die 8 nicht-englischen Locales, unter Verwendung von getStaticPaths(), um eine Seite pro Locale zu erzeugen.
Das funktionierte sauber, weil die Logik jeder Tier-1-Seite — getTranslation(locale), Meta-Tag-Builder, Schema-Markup — bereits über diese eine locale-Konstante aus dem Sprint-1-Gerüst parametrisiert war. Wir haben i18n nicht nachträglich in diese Seiten eingebaut; wir haben endlich i18n verkabelt, das ungenutzt herumlag.
Das Ergebnis: 96 neue statische Seiten (12 Seiten × 8 Locales) aus dem Tier-1-Rollout, gefolgt von einem zweiten Durchgang, der dasselbe Muster auf die 64 Vergleichsseiten (8 Wettbewerber × 8 Locales) ausweitete.
hreflang hinter einem localized-Prop gaten
Das Beheben der Routing-Lücke behebt nicht automatisch das hreflang-Problem — die Tags müssen weiterhin nur das beanspruchen, was tatsächlich wahr ist. Wir haben BaseLayout und PageLayout ein localized?: boolean-Prop hinzugefügt, standardmäßig auf false.
Wenn localized false ist, gibt eine Seite genau zwei hreflang-Tags aus: en und x-default. Wenn es true ist, gibt sie den vollständigen Satz von 10 aus (alle 9 Locales plus x-default). Nur die Tier-1-Templates und die Vergleichsseiten übergeben localized={true}.
Die Standardeinstellung auf false war eine bewusste Sicherheitsentscheidung. Eine Seite muss sich aktiv dafür entscheiden, zu behaupten, dass sie 9 Sprachvarianten hat. Wenn morgen jemand eine neue Seite hinzufügt und vergisst, an Locales zu denken, schlägt es sicher fehl — nur englische Metadaten, keine weitere Runde von Phantom-URLs.
Was wir bewusst nicht lokalisiert haben
Nicht alles hat die Locale-Behandlung bekommen, und das war eine Entscheidung, kein Versehen:
- Der Blog bleibt auf unbestimmte Zeit nur auf Englisch. Das ist gängige Praxis bei SaaS-Marketing-Blogs, und maschinelle Übersetzung von Langform-Inhalten erzeugt schlechteren Text, als sie einfach nicht zu übersetzen. Sollten Analytics das irgendwann rechtfertigen, überdenken wir dedizierte Inhalte pro Locale.
- Rechtliche Seiten (Datenschutz, Nutzungsbedingungen, Cookie-Richtlinie) bleiben Englisch. Dieser Text ist von Termly generierter Rechtstext; ihn zu übersetzen ist keine Entscheidung, die wir einseitig treffen können.
- Die 404-Seite bleibt Englisch — statisches Hosting liefert einen einzigen 404 unabhängig vom angeforderten Pfad, sodass es keinen sauberen Weg gibt, sie ohne ein anderes Hosting-Modell zu lokalisieren.
- RSS war bereits nur Englisch und korrekt von hreflang ausgeschlossen; hier waren keine Änderungen nötig.
Die Checkliste zum Mitnehmen
Wenn du etwas Ähnliches auf einer statischen Astro-Website vorhast:
- Prüfe, bevor du baust. Kontrolliere, ob deine hreflang-Tags auf Seiten verweisen, die tatsächlich existieren. Wenn dein i18n-Gerüst älter ist als dein Routing, hast du diesen Fehler vielleicht schon.
- Extrahiere Templates, dupliziere keine Seiten. Ein Template pro Seite, dünne locale-spezifische Wrapper-Routen darüber. Verfall passiert, wenn überhaupt, auf der Wrapper-Ebene, und Wrapper sind billig synchron zu halten.
- Standardisiere Metadatenbehauptungen auf die engste wahre Aussage. Ein
localized-Flag, das standardmäßig „nein“ ist, bedeutet, dass neue Seiten nicht versehentlich zu viel behaupten können. - Entscheide bewusst, was unübersetzt bleibt, und halte es fest. Blog, Rechtstexte und Fehlerseiten sind vernünftige Kandidaten für den Ausschluss — aber mach es zu einer dokumentierten Entscheidung, nicht zu Schweigen.
- Übersetzung und Routing sind getrennte Probleme. Die Routen auszuliefern erfordert nicht, die Übersetzungen fertigzustellen — englischer Fallback-Text in einer echten Locale-Route ist immer noch ehrlicher als ein funktionierender Übersetzungsschlüssel ohne Route dahinter.
Wir haben noch manuelle Übersetzungsarbeit vor uns — viele Strings in den nicht-englischen Locale-Dateien sind englische Platzhalter, die auf einen menschlichen Übersetzer warten. Aber die URLs sind jetzt echt, und die Metadaten lügen nicht mehr. Das war das dringendere Problem.
Alex Rivera
Projektmanager und Workflow-Berater. Hilft Teams, Tools zu finden, die zu ihrer tatsächlichen Arbeitsweise passen.