Zum Inhalt springen
duxt

Übersetzungen als eigene Collections ausliefern

Einer Quelle eine Locale-Dimension hinzufügen statt dem Content-Pfad ein Locale-Segment.

Kontext

Die Ebene übersetzte ihre Oberfläche in sieben Locales und lieferte allen denselben Satz Seiten aus: useDuxtPath() entfernte das Locale-Segment vor jedem Content-Lookup, /de-DE/guides/deploying und /guides/deploying lösten also auf dieselbe Datei auf. Content v3 kennt keine Locale — eine Collection ist ein Baum —, übersetzte Seiten brauchten also eine Entscheidung und keine Einstellung.

Was die vergleichbaren Generatoren tun, wurde nachgelesen statt angenommen. Starlight, VitePress, Docusaurus und MkDocs legen Übersetzungen allesamt in einen Ordner je Sprache; nur Starlight hat einen dokumentierten Rückfall für eine Seite, die einer Sprache fehlt. Ab einer gewissen Größe verlässt die Übersetzung das Werkzeug ganz: React betreibt de.react.dev als eigenes Repository, Vue eine ganze vuejs-translations-Organisation, weil Übersetzer nach eigenem Zeitplan und eigenem Review arbeiten. OpenCode baute einen Agenten, der die Doku in CI übersetzte, ließ ihn laufen und schaltete ihn ab; siebzehn Sprachen stehen seither still.

Die Kosten wurden gemessen, bevor die Form gewählt wurde: Builds über 1 … 200 Collections skalieren linear mit rund 2,2 s und 0,63 MB Datenbank je Collection, ohne Knick. Die Matrix hat keine Decke, die dem Entwurf die Hand führen würde.

Entscheidung

Eine Quelle bekommt eine locales-Liste, und ein Ref ebenso, aufgelöst wie status es bereits wird (ref.locales ?? source.locales). Eine Zeichenkette ist ein Ordner innerhalb des path der Quelle; ein Objekt verschiebt diese Sprache in einen eigenen Ordner, ein eigenes Repository oder einen eigenen Ref.

Die Standard-Locale ist der Baum in path selbst, ohne Ordner, das Hinzufügen des Schlüssels verschiebt also keine URL, die eine Seite bereits ausliefert.

Die Locale gehört nicht in den Content-Pfad. Sie gehört i18ns Routing, das sie dem Pfad ohnehin voranstellt. Original und Übersetzung liegen deshalb unter identischen Content-Pfaden in getrennten Collections.

Eine Seite, die einer Sprache fehlt, fällt entlang einer Kette zurück — die Locale, ihre Basissprache, eine Geschwisterregion, fallbackLocale aus vue-i18n, das unübersetzte Original — und dem Leser wird in einem Banner gesagt, welche Sprache er zu sehen bekommt.

Konsequenzen

Jeder Pfadvergleich im Theme bleibt unberührt: Navigation, Weiterleitungen, der Breadcrumb, die Bewertung der nächsten Seite im 404 und der Sprachumschalter arbeiten allesamt weiter auf einem Pfad, der nie eine Locale trug. Der Rückfall ist eine weitere Abfrage auf denselben Pfad statt einer Weiterleitung oder eines zweiten Auflösungsschemas.

useDuxtNavigation und die Suche folgen useDuxtCollection, beide wurden also locale-bewusst, ohne geändert zu werden.

Eine Seite, die nichts setzt, bekommt genau das, was sie hatte: eine Collection namens docs, einen Eintrag im Manifest, eine Abfrage je Seite.

Zwei Konfigurationen können nun so auseinandergehen, dass eine leere Seite dabei herauskommt statt eines Fehlers — content.config.ts löst die Standard-Locale ohne Zugriff auf die Nuxt-Konfiguration auf. Das duxt-Modul prüft deshalb sourceOptions.defaultLocale gegen i18n.defaultLocale und lässt den Build scheitern, wenn sie abweichen, statt das eine in das andere zu injizieren und Content die andere Antwort ausrechnen zu lassen.

Übersetzungen vervielfachen Collections, und der Build zahlt für jede linear. Die Zahl gehört in die Dokumentation, denn über die Matrix entscheidet ein Konsument.

Partials folgen den Seiten. _partials/ ist eine Collection JE SPRACHE, benannt wie die Seiten-Collections — duxt_partials für das Original, duxt_partials_de daneben —, und :partial{name} durchläuft dieselbe Fallback-Kette wie die Seite. Es muss dieselbe Kette sein: Dass eine Seite und die Blöcke, die sie einbindet, auf unterschiedliche Sprachen zurückfallen, ist genau der Weg zu einer halb übersetzten Seite, auf der nichts davon erzählt.

Die erste Fassung dieser Entscheidung teilte EINE unübersetzte Partials-Collection über alle Sprachen hinweg. Das war vertretbar, solange nichts ein Partial übersetzte, und hörte in dem Moment auf vertretbar zu sein, in dem ein Übersetzungsdurchlauf docs/de/_partials/ erzeugte — Dateien, die keine Collection las, und eine deutsche Seite, die einen englischen Installationshinweis rendert, ohne dass irgendetwas darauf hinweist.

Erwogene Alternativen

Ein Locale-Segment im Content-Präfix. Symmetrisch zu repo und version, und es hätte jeden Pfadvergleich im Theme gezwungen, Locales zu lernen — für eine URL, der i18n ohnehin ein Präfix voranstellt und die dann zweimal geschrieben stünde.

Ein Dateisuffix — installation.de-DE.md neben dem Original. Keine Vervielfachung von Collections, und es scheitert an dem Fall, den die großen Projekte tatsächlich haben: Es zwingt die Übersetzung in dasselbe Repository und denselben Ref wie das Original.

Ein 404 für eine fehlende Übersetzung. Was VitePress durch Auslassung tut. Es bestraft den Leser für eine Lücke, die der Schreibende hinterlassen hat, und es verbirgt vor allen anderen, dass die Übersetzung unvollständig ist.

War diese Seite hilfreich?