Zum Inhalt springen

Umstieg auf MessageFormat 2 von svelte-i18n und i18next

Lass svelte-dialect migrate über deine Übersetzungsdateien laufen, und sie liegen danach als MessageFormat 2 vor. Was der Konverter nicht in gültiges MF2 überführen kann, bleibt unverändert und kommt mit einer Warnung. Diese Seite begleitet eine Migration von Anfang bis Ende, mit echter Ausgabe des Befehls.

Für wen das ist #

Für Apps, deren Übersetzungen ICU-MessageFormat-Strings sind (so arbeiten svelte-i18n und formatjs) oder i18next-JSON mit {{name}}-Platzhaltern und key_one / key_other-Plurals. MessageFormat 2 ist der Unicode-Nachfolger von ICU MessageFormat. Plurals, Selects und Zahlenformatierung bleiben erhalten, nur die Syntax ist eine andere. Die CLI schreibt die Message-Dateien um, und Schritt 4 ordnet die Aufrufe zur Laufzeit zu.

1. Installation #

Installiere svelte-dialect neben deiner bisherigen Bibliothek. Der Konverter liest und schreibt nur Message-Dateien, beide können also während des Umstiegs im Projekt bleiben.

npm install svelte-dialect

Dateistruktur #

Die CLI erwartet src/lib/messages/<locale>/<namespace>.json, dieselbe Struktur, die auch die Laufzeit liest. Bei i18next hat locales/<lng>/<ns>.json diese Form bereits. svelte-i18n hält eine en.json pro Sprache; verschiebe jede in einen Ordner, dann wird der Dateiname zum Namespace. Locale-Ordner heißen wie BCP-47-Tags, pt-BR und zh-Hant funktionieren also unverändert. Ein Ordner namens zh_CN wird übersprungen; der Befehl warnt und bittet dich, ihn in zh-CN umzubenennen.

src/lib/messages/
  en/common.json   # was en.json
  ru/common.json   # was ru.json

Eine <locale>.json, die direkt im Message-Verzeichnis liegt, wird mit einer Warnung übersprungen. Bleibt dadurch nichts zu migrieren, endet der Befehl mit Exit-Code 1:

$ npx svelte-dialect migrate
  src/lib/messages/en.json: skipped — it sits directly in the messages directory. Move it to en/common.json (any namespace name works)
  src/lib/messages/ru.json: skipped — it sits directly in the messages directory. Move it to ru/common.json (any namespace name works)
svelte-dialect migrate: 0 messages converted, 2 warnings
svelte-dialect migrate: nothing was migrated — move each <locale>.json into <locale>/<namespace>.json
$ echo $?
1

2. Den Konverter ausführen #

Committe zuerst, damit der Diff zeigt, was sich geändert hat. Dann ein Probelauf, der jede Änderung als Vorher-Nachher-Paar ausgibt und nichts schreibt:

npx svelte-dialect migrate --dry-run

Ohne --dry-run werden die Dateien geschrieben. --messages <dir> setzt ein anderes Verzeichnis und lässt sich wiederholen; Standard ist src/lib/messages. Eine Datei wird (als JSON mit Tab-Einrückung) nur dann neu geschrieben, wenn sich mindestens eine ihrer Messages geändert hat. Warnungen gehen nach stderr. Der Exit-Code ist 0, außer du setzt --strict oder das Verzeichnis enthält nur flache <locale>.json-Dateien. Ein zweiter Lauf ändert nichts, du kannst ihn also nach manuellen Korrekturen erneut starten. Alle Flags und Exit-Codes stehen in der CLI-Referenz.

Der Konverter kompiliert jedes Ergebnis mit dem MF2-Parser, bevor er es behält. Lässt sich das Ergebnis nicht parsen oder enthält die Quelle etwas, das der Konverter nicht kennt, bleibt der ursprüngliche String in der Datei, und eine Warnung nennt Datei und Schlüssel.

ICU MessageFormat (svelte-i18n, formatjs) #

Messages in svelte-i18n sind ICU-Strings. Nimm diese Datei:

src/lib/messages/en/common.json
{
	"greeting": "Hello, {name}!",
	"cart": {
		"items": "{count, plural, =0 {Your cart is empty} one {# item in your cart} other {# items in your cart}}"
	},
	"invite": "{gender, select, female {She invited you} male {He invited you} other {They invited you}}"
}
$ npx svelte-dialect migrate --dry-run
svelte-dialect migrate: en/common.json — 3 converted, 0 unchanged, 0 warnings
  greeting
  - Hello, {name}!
  + Hello, {$name}!
  cart.items
  - {count, plural, =0 {Your cart is empty} one {# item in your cart} other {# items in your cart}}
  + .input {$count :number}
  + .match $count
  + 0 {{Your cart is empty}}
  + one {{{$count} item in your cart}}
  + * {{{$count} items in your cart}}
  invite
  - {gender, select, female {She invited you} male {He invited you} other {They invited you}}
  + .input {$gender :string}
  + .match $gender
  + female {{She invited you}}
  + male {{He invited you}}
  + * {{They invited you}}
svelte-dialect migrate: 3 messages would change, 0 warnings (dry run, nothing written)

Der Plural wird zu einer einzigen Message mit .match. Die Zweige werden zu Varianten, =0 zum exakten Schlüssel 0, other zum Auffangfall * und # zu {$count}. Ein Select funktioniert genauso, mit :string statt :number.

i18next #

i18next verteilt einen Plural auf benachbarte Schlüssel. Der Konverter fasst sie pro Locale zu einer Message zusammen und behält die Plural-Kategorien, die du geschrieben hast; die russische Datei unten bekommt daher vier Formen:

src/lib/messages/en/common.json
{
	"welcome": "Welcome, {{name}}!",
	"item_one": "{{count}} item",
	"item_other": "{{count}} items",
	"inbox_zero": "Inbox zero",
	"inbox_one": "One message",
	"inbox_other": "{{count}} messages",
	"rank_ordinal_one": "{{count}}st place",
	"rank_ordinal_two": "{{count}}nd place",
	"rank_ordinal_few": "{{count}}rd place",
	"rank_ordinal_other": "{{count}}th place"
}
src/lib/messages/ru/common.json
{
	"welcome": "Добро пожаловать, {{name}}!",
	"item_one": "{{count}} товар",
	"item_few": "{{count}} товара",
	"item_many": "{{count}} товаров",
	"item_other": "{{count}} товара"
}
$ npx svelte-dialect migrate --dry-run
svelte-dialect migrate: en/common.json — 4 converted, 0 unchanged, 1 warnings
  welcome
  - Welcome, {{name}}!
  + Welcome, {$name}!
  item
  - item_*
  + .input {$count :number}
  + .match $count
  + one {{{$count} item}}
  + * {{{$count} items}}
  inbox
  - inbox_*
  + .input {$count :number}
  + .match $count
  + 0 {{Inbox zero}}
  + one {{One message}}
  + * {{{$count} messages}}
  rank_ordinal
  - rank_ordinal_*
  + .input {$count :number select=ordinal}
  + .match $count
  + one {{{$count}st place}}
  + two {{{$count}nd place}}
  + few {{{$count}rd place}}
  + * {{{$count}th place}}
  common.json: rank_ordinal: i18next ordinal plural converted to ":number select=ordinal" — call t('common:rank_ordinal', { count }) instead of t('common:rank', { count, ordinal: true })
svelte-dialect migrate: ru/common.json — 2 converted, 0 unchanged, 0 warnings
  welcome
  - Добро пожаловать, {{name}}!
  + Добро пожаловать, {$name}!
  item
  - item_*
  + .input {$count :number}
  + .match $count
  + one {{{$count} товар}}
  + few {{{$count} товара}}
  + many {{{$count} товаров}}
  + * {{{$count} товара}}
svelte-dialect migrate: 6 messages would change, 1 warnings (dry run, nothing written)

item_one und item_other wurden zu item. Ein _zero-Schlüssel steht bei i18next für genau count 0. Der Konverter macht es genauso: inbox_zero wird zur exakten Variante 0 und nicht zur Kategorie zero, die die meisten Sprachen nicht haben. Ordinal-Plurals sind anders aufgebaut: rank_ordinal_one, rank_ordinal_two und die übrigen werden zu einer Message namens rank_ordinal, die mit select=ordinal auswählt, sodass 2 als 2nd erscheint. i18next hat sie mit t('rank', {count, ordinal: true}) gefunden; der neue Aufruf lautet t('common:rank_ordinal', {count}), und die Warnung sagt das auch. Ein normales rank_one / rank_other daneben wird weiterhin zu rank.

Was konvertiert wird #

Der Konverter hat einen eigenen Parser für den Teil von ICU und i18next, der in echten Katalogen vorkommt: Argumente, number, date und time, plural, selectordinal, select, # und Apostroph-Escaping, die doppelten geschweiften Klammern von i18next sowie HTML-Tags ohne Attribute. Die gesamte ICU-Grammatik deckt er nicht ab.

VorherNachherHinweise
Hello, {name}!Hello, {$name}!Einfache Platzhalter. {{name}} aus i18next wird genauso konvertiert.
{{- html}}{$html}Der unescapte Platzhalter von i18next. MF2 escaped kein HTML in Werten, es gibt also nichts abzuschalten.
Score: {score, number, integer}Score: {$score :integer}Die number-Stile integer und percent, das Skeleton ::percent und currency werden auf die gleichnamige Funktion abgebildet. Das gilt auch für die i18next-Formate number, integer, currency, percent, date, time, datetime und unit.
Due {d, date, short}Due {$d :date length=short}Die Stile short, medium und long werden zur Option length. full hat keine eigene Länge, deshalb kommt der Wochentag dazu: fields=year-month-day-weekday zusammen mit length=long.
At {d, time, medium}At {$d :time precision=second}Die Funktion :time kennt keine Stile. short ist ihr Standard, medium ergänzt Sekunden mit precision=second, und long und full ergänzen den Namen der Zeitzone mit timeZoneStyle=short und timeZoneStyle=long.
{n, number, ::percent} done{$n :percent} done::percent ist das einzige Zahlen-Skeleton, das :percent abdeckt. Jedes andere Skeleton bekommt weiterhin eine Warnung.
Read <b>this</b> firstRead {#b}this{/b} firstTags ohne Attribute werden zu MF2-Markup, <br/> eingeschlossen, und eine Warnung listet sie auf. Tags mit Attributen bleiben Text, siehe Schritt 3.
Type '{'name'}' to insert a nameType \{name\} to insert a nameDas Apostroph-Quoting von ICU wird zu einem Backslash.
{count, plural, one {{gender, select, male {He has one hat} other {They have one hat}}} other {{gender, select, male {He has # hats} other {They have # hats}}}}.input {$count :number} .input {$gender :string} .match $count $gender one male {{He has one hat}} one * {{They have one hat}} * male {{He has {$count} hats}} * * {{They have {$count} hats}}Ein Select innerhalb eines Plurals wird zu einem einzigen .match über beide Variablen verflacht, mit einer Variante pro Kombination, höchstens 64 Varianten.

3. Beheben, was der Konverter markiert hat #

Diese Datei enthält die Probleme, die der Konverter nicht allein lösen kann. Die Ausgabe stammt aus einem echten Lauf:

src/lib/messages/en/common.json
{
	"total": "Total: {$total :currency}",
	"price": "Total: {$price :currency}",
	"place": ".input {$place :number select=ordinal}\n.match $place\none {{You finished {$place}st}}\ntwo {{You finished {$place}nd}}\nfew {{You finished {$place}rd}}\n* {{You finished {$place}th}}",
	"views": "{$views :number} views",
	"spell": "{n, spellout} items",
	"people": "{n, plural, offset:1 =0 {Nobody} other {# people}}",
	"shout": "{$name}",
	"friend": "A friend",
	"friend_male": "A boyfriend",
	"friend_female": "A girlfriend",
	"back": "Back to $t(welcome)",
	"docs": "Read the <a href=\"/docs\">docs</a>"
}
$ npx svelte-dialect migrate
svelte-dialect migrate: en/common.json — 5 converted, 7 unchanged, 10 warnings
  common.json: total: "total" became :currency, which needs a code — write {$total :currency currency=EUR} or pass one at runtime
  common.json: price: "price" became :currency, which needs a code — write {$price :currency currency=EUR} or pass one at runtime
  common.json: place: "place" used ICU selectordinal — converted to ":number select=ordinal"; MF2 ordinal categories can differ, review the arms
  common.json: views: style "::compact-short" dropped from "{views, number, ::compact-short}"
  common.json: spell: unsupported ICU type "spellout"
  common.json: people: plural offset is not supported
  common.json: shout: format "uppercase" dropped from "{{name, uppercase}}" — "name" is now a plain {$name}
  common.json: back: i18next nesting "$t(…)" has no MF2 equivalent — pass the other translation in as a parameter, or split the message
  common.json: docs: HTML <a href="/docs"> left as text — markup carries no attributes. Write {#name}…{/name}, render it with the Rich component and set the attributes in its snippet
  common.json: friend: "friend_male", "friend_female" look like i18next context variants — MF2 handles context with a .match on a parameter; ignore this if they are separate messages
svelte-dialect migrate: 5 messages converted, 10 warnings

Jede Warnung und was du damit tun kannst:

WarnungWas zu tun ist
"total" became :currency, which needs a codeMF2 kann eine Währung ohne Code nicht formatieren, und weder ICU noch i18next haben einen geliefert. Trage den Code in die Message ein, etwa {$total :currency currency=EUR}. Dieselbe Message ergibt dann €1,234.50 auf Englisch und 1.234,50 € auf Deutsch.
"place" used ICU selectordinalDie Message ist konvertiert, die Zweige wurden unverändert übernommen. Gleiche sie mit den Ordinal-Kategorien jeder Locale ab, die du auslieferst. Englisch nutzt one, two, few und den Auffangfall, andere Sprachen haben andere Kategorien.
style "::compact-short" dropped from "{views, number, ::compact-short}"Der Konverter bildet die ICU-Stile für Datum und Zeit sowie den Stil percent ab. Was er nicht abbilden kann, wird verworfen, der Wert wird also mit den Standardwerten formatiert: eigene Datumsmuster wie dd/MM/yyyy und Zahlen-Skeletons wie ::compact-short. Bei einem Datum setzt du die Felder von Hand wieder ein, zum Beispiel {$due :date fields=month-day}. Das :number von messageformat hat keine kompakte Notation, es gibt also keine Option zum Ergänzen. Formatiere den Wert im Code mit formatNumber und übergib den fertigen String.
unsupported ICU type "spellout" plural offset is not supportedDiese Strings bleiben im ICU-Format, das kein gültiges MF2 ist, zur Laufzeit erscheint also der Schlüssel statt des Textes. Schreibe sie von Hand um. Bei einem offset berechnest du die angepasste Zahl im Code und übergibst sie als eigenen Parameter.
format "uppercase" dropped from "{{name, uppercase}}"Das Format wird verworfen, der Wert bleibt eine einfache Variable, hier {$name}. Wandle den Wert im Code um, bevor du ihn übergibst, oder registriere eine Funktion über die Option functions und rufe sie in der Message auf.
i18next nesting "$t(…)" has no MF2 equivalentMF2-Messages können sich nicht gegenseitig einbinden. Der Text bleibt, wie er war, und der Aufruf $t(…) erscheint wörtlich. Übergib die andere Übersetzung als Parameter oder schreib die Wörter aus, wie unten gezeigt.
HTML <a href="/docs"> left as textMF2-Markup hat keine Attribute. Schreibe {#a}docs{/a} in die Message, rendere sie mit der Rich-Komponente und setze das href im Snippet. Tags ohne Attribute sind schon konvertiert, die Warnung listet sie auf.
"friend_male", "friend_female" look like i18next context variantsDie Varianten bleiben getrennte Schlüssel, und nichts schlägt sie nach, weil MF2 kein context-Argument hat. Führe sie zu einer Message zusammen, die anhand eines Parameters auswählt, wie unten gezeigt. Sind die Schlüssel unabhängige Messages, die zufällig ein Präfix teilen, kannst du die Warnung ignorieren.
i18next ordinal plural converted to ":number select=ordinal"In der Datei ist nichts zu ändern. Stelle die Aufrufe von t('rank', {count, ordinal: true}) auf t('common:rank_ordinal', {count}) um. Die Zweige wurden unverändert übernommen, gleiche sie mit den Ordinal-Kategorien jeder Locale ab.

Kontext, Verschachtelung und Links von Hand #

Kontext. friend_male und friend_female bleiben getrennte Schlüssel. In MF2 wählt eine einzige Message anhand einer Variable aus, der Kontext wird also ein gewöhnlicher Parameter:

src/lib/messages/en/common.json
{
	"friend": ".input {$context :string}\n.match $context\nmale {{A boyfriend}}\nfemale {{A girlfriend}}\n* {{A friend}}"
}

t('common:friend', { context: 'male' }) // → "A boyfriend"

Verschachtelung. MF2-Messages können sich nicht gegenseitig einbinden, übergib die andere Übersetzung also als Parameter oder schreib die Wörter aus:

src/lib/messages/en/common.json
{ "back": "Back to {$welcome}" }

t('common:back', { welcome: t('common:welcome', { name }) })

Links. MF2 hat ein eigenes Markup, {#link}docs{/link}, das die Rich-Komponente über Snippets rendert. Dort gehört das href hin.

CI bei Warnungen scheitern lassen #

Standardmäßig ändern Warnungen den Exit-Code nicht. Mit --strict endet der Befehl mit 1, sobald er eine Warnung ausgegeben hat, und kann so eine Pipeline absichern. Zusammen mit --dry-run prüft er einen Branch, ohne Dateien zu schreiben. Unten siehst du einen zweiten Lauf über die Datei von oben, nachdem der erste konvertiert hat, was er konnte:

$ npx svelte-dialect migrate --strict
svelte-dialect migrate: en/common.json — 0 converted, 12 unchanged, 5 warnings
  common.json: spell: unsupported ICU type "spellout"
  common.json: people: plural offset is not supported
  common.json: back: i18next nesting "$t(…)" has no MF2 equivalent — pass the other translation in as a parameter, or split the message
  common.json: docs: HTML <a href="/docs"> left as text — markup carries no attributes. Write {#name}…{/name}, render it with the Rich component and set the attributes in its snippet
  common.json: friend: "friend_male", "friend_female" look like i18next context variants — MF2 handles context with a .match on a parameter; ignore this if they are separate messages
svelte-dialect migrate: 0 messages converted, 5 warnings
svelte-dialect migrate: --strict: 5 warnings, exiting with 1
$ echo $?
1

Warnungen zu Strings, die weiterhin unkonvertiert sind, kommen bei jedem Lauf wieder. Eine Warnung zur Konvertierung selbst, etwa der fehlende Währungscode, erscheint nur einmal, in dem Lauf, der die Message konvertiert hat. Willst du alles sehen, starte --strict --dry-run vor dem ersten echten Lauf.

4. Die Aufrufe zur Laufzeit ersetzen #

Die CLI fasst nur Message-Dateien an, deine Komponenten rufen also weiterhin die alte Bibliothek auf. svelte-dialect hat keine Stores. t, getLocale und die Formatter sind einfache Funktionen, die einer Locale-Rune folgen; in Templates entfällt das Präfix $, und sie aktualisieren sich trotzdem beim Wechsel der Locale.

Jeder Schlüssel trägt seinen Namespace, wie in t('common:cart.items'). Der Dateiname ist der Namespace, und verschachtelte Objekte werden zu Punkt-Pfaden verflacht. Ein Schlüssel ohne Doppelpunkt wird nicht nachgeschlagen, und t gibt ihn unverändert zurück.

Von svelte-i18n #

svelte-i18n konfiguriert einen globalen Store. svelte-dialect baut seine Funktionen einmal in src/lib/i18n.ts, und du importierst sie von dort.

src/lib/i18n.ts
import { init, register, getLocaleFromNavigator } from 'svelte-i18n';

register('en', () => import('./locales/en.json'));
register('ru', () => import('./locales/ru.json'));

init({ fallbackLocale: 'en', initialLocale: getLocaleFromNavigator() });
src/lib/i18n.ts
import { createDialect } from 'svelte-dialect/kit';

export const { t, loadTranslations, getLocale, setLocale, locales } = createDialect({
	messages: import.meta.glob('./messages/**/*.json', { eager: true }),
	defaultLocale: 'en',
	locales: ['en', 'ru']
});

Füge in SvelteKit die Hooks aus Einrichtung hinzu. Der handle-Hook bestimmt die Locale auf dem Server.

In Komponenten wird $_ zu t, der values-Wrapper verschwindet, und der Schlüssel bekommt seinen Namespace:

<script>
	import { _, locale } from 'svelte-i18n';
</script>

<h1>{$_('cart.items', { values: { count: 3 } })}</h1>
<button onclick={() => locale.set('ru')}>RU</button>
<script lang="ts">
	import { t, setLocale } from '#lib/i18n.js';
</script>

<h1>{t('common:cart.items', { count: 3 })}</h1>
<button onclick={() => setLocale('ru')}>RU</button>

Die übrige API:

svelte-i18nsvelte-dialectHinweise
init({ fallbackLocale, initialLocale })createDialect({ defaultLocale, locales })defaultLocale ist die Rückfall-Locale. Ein Schlüssel, der in der aktiven Locale fehlt, fällt auf Varianten derselben Sprache zurück, dann auf sie.
addMessages('en', dict)messages: import.meta.glob('./messages/**/*.json', { eager: true })Wird zur Build-Zeit eingebunden.
register('en', () => import('./en.json'))messages: import.meta.glob('./messages/**/*.json')Ohne eager ist jeder Namespace ein eigener Chunk. Die Option loader holt die Daten stattdessen aus einer API.
await waitLocale()await loadTranslations('common')Nur für Lazy- oder per Fetch geladene Namespaces nötig. Liefert t und lädt beim Wechsel der Locale neu.
$_('a.b', { values: { n } })t('common:a.b', { n })$t und $format sind dieselbe Funktion. Die Werte sind direkt das zweite Argument.
$localegetLocale()Reaktiv in Templates und Effects, kein Store nötig.
locale.set('de')setLocale('de')Bei aktivem URL-Routing navigiert setLocale zur URL mit Präfix. Übergib { reload: false }, um die Navigation selbst zu übernehmen.
$localeslocalesDie wählbaren Locales.
$number(n), $date(d), $time(d)formatNumber(n, options), formatDate(d, options)Beide nutzen Intl. $time ist formatDate mit Stunden- und Minutenoptionen, und benannte Formate wie medium werden zu Options-Objekten oder MF2-Optionen wie length=long.
getLocaleFromNavigator(), getLocaleFromPathname()strategy: ['url', 'cookie', 'navigator', 'default']Die Quellen werden der Reihe nach geprüft. Für getLocaleFromHostname, getLocaleFromQueryString und getLocaleFromHash gibt es keine eingebaute Entsprechung: Lies den Wert selbst aus und rufe setLocale auf.
$isLoading(keine)loadTranslations gibt ein Promise zurück. Warte in einer load-Funktion darauf, statt einen Ladeindikator zu zeigen.
$json('list.items')(keine)Messages sind Strings. Arrays und andere Nicht-String-Werte werden beim Laden des Katalogs verworfen, halte Listendaten also im Code oder nimm einen Schlüssel pro Eintrag.
$_('key', { default: '…' })(keine)Ein fehlender Schlüssel fällt entlang der Locale-Kette zurück und wird zuletzt als der Schlüssel selbst ausgegeben. check fängt statische Schlüssel vorher ab.

Von i18next #

Die Aufrufe sind näher dran, denn t('ns:key', params) sieht schon gleich aus. Schreibe zuerst die Einrichtung um, dann die Stellen, die an t vorbeigehen:

i18nextsvelte-dialectHinweise
i18next.init({ lng, fallbackLng, resources })createDialect({ defaultLocale, locales, messages })messages nimmt dieselben Dateien, die du schon hast, in der Struktur aus Schritt 1.
i18next.t('ns:key', { count })t('ns:key', { count })Dieselben Konventionen für ns:key und Punkt-Pfade.
i18next.t('rank', { count, ordinal: true })t('common:rank_ordinal', { count })Das ordinal-Flag wird zu einem Schlüssel-Suffix. Für jede konvertierte Ordinal-Gruppe gibt die Migration eine Warnung aus.
i18next.t('key')t('translation:key')Einen Standard-Namespace gibt es nicht. i18next nennt seinen translation, schreibe den Namespace also in jeden Schlüssel oder benenne die Datei um.
await i18next.changeLanguage('de')setLocale('de')Synchron. Lazy-Namespaces laden von selbst nach.
i18next.loadNamespaces('ns')await loadTranslations('common')Nur für Lazy- oder per Fetch geladene Namespaces nötig.
t('friend', { context: 'male' })t('common:friend', { context: 'male' })Nach der manuellen Konvertierung aus Schritt 3 ist context ein gewöhnlicher Parameter. Nenne ihn so, wie die Variable heißt, nach der die Message auswählt.
i18next.dir()getTextDirection()Gibt ltr oder rtl zurück.
i18next.getFixedT(null, 'ns')(keine)Schlüssel tragen immer ihren Namespace. Wenn du eine Abkürzung willst, verpacke t in eine eigene Funktion.
interpolation.escapeValue(keine)i18next escaped HTML in eingesetzten Werten standardmäßig. svelte-dialect tut das nicht, weil Svelte den gerenderten Text selbst escaped. Escape die Werte selbst, bevor du die Ausgabe von t() an {@html} gibst.

5. Das Ergebnis prüfen #

Erzeuge die Schlüsseltypen aus den migrierten Dateien, und die Parameterformen kommen mit: Aus der MF2-Quelle geht hervor, dass count eine Zahl und gender ein String ist. Führe dann check aus, das jeden t('…')-Aufruf in deinem Code mit den Katalogen vergleicht. Hier enthält der Code einen Tippfehler, common:cart.itms, und die russische Datei hat nur eine der drei Messages:

$ npx svelte-dialect typegen
svelte-dialect typegen: 3 keys → src/lib/i18n-keys.d.ts

$ npx svelte-dialect check
Missing:
  common:cart.items — missing in ru
  common:cart.itms — missing in en, ru
  common:invite — missing in ru
4 keys used, 3 missing
// src/lib/i18n-keys.d.ts (generated)
export type MessageParams = {
	'common:cart.items': { count: number | string };
	'common:greeting': { name: unknown };
	'common:invite': { gender: string };
};

check beendet sich mit 1, wenn ein verwendeter Schlüssel in irgendeiner Locale fehlt, und passt damit gut in einen CI-Schritt. Beide Befehle sind unter Typisierte Schlüssel & CLI beschrieben. Lies außerdem ein paar konvertierte Plurals in jeder Sprache durch. Der Konverter behält die Kategorien, die du geschrieben hast, und kann nicht wissen, welche deine Sprache braucht.

Weiter #

Message-Dateien behandelt die MF2-Syntax, in der deine Dateien jetzt geschrieben sind. Messages laden ersetzt register und Loader, und Locale in der URL ersetzt getLocaleFromPathname. Im Vergleich stellt svelte-dialect neben die anderen i18n-Bibliotheken für Svelte.