Reden wir über Internationalisierung (i18n) für Remix...
Wenn es um JavaScript-Lokalisierung geht, ist eines der beliebtesten Frameworks i18next und eines der bekanntesten Remix-Module für i18next ist remix-i18next.
Es wurde im Oktober 2021 von Sergio Xalambrí erstellt.
Es gibt auch einen zweiten Teil dieses Blogposts, der sich mehr auf einen kontinuierlichen Lokalisierungsworkflow konzentriert.
i18next wurde Ende 2011 erstellt. Es ist älter als die meisten Bibliotheken, die Sie heutzutage verwenden, einschliesslich der wichtigsten Frontend-Technologien (React, Angular, Vue, ...). ➡️ nachhaltig
Basierend darauf, wie lange i18next bereits Open Source verfügbar ist, gibt es keinen echten i18n-Fall, welcher nicht mit i18next gelöst werden könnte. ➡️ reif
i18next kann in jeder Umgebung mit Javascript (und einigen Nicht-Javascript - .net, elm, iOS, Android, Ruby, ...) verwendet werden, mit jedem UI-Framework, mit jedem i18n-Format, ... die Möglichkeiten sind endlos. ➡️ erweiterbar
Es gibt viele Funktionen und Möglichkeiten, die Sie mit i18next im Vergleich zu anderen regulären i18n-Frameworks erhalten. ➡️ reich
Hier finden Sie weitere Informationen darüber, warum i18next so besonders ist und wie es funktioniert.
Fangen wir an...
Voraussetzungen
Stellen Sie sicher, dass Sie Node.js und npm installiert haben. Es ist am besten, wenn Sie etwas Erfahrung mit einfachem HTML, JavaScript und grundlegendem React und Remix haben, bevor Sie mit remix-i18next loslegen.
Einstieg
Nehmen Sie Ihr eigenes Remix-Projekt oder verwenden Sie diese Beispiel-App hier.
1 2 3 4
git clone -b start [email protected]:locize/locize-remix-i18next-example.git cd locize-remix-i18next-example npm i npm run dev
Wir werden die App anpassen, um die Sprache gemäss den Vorlieben des Benutzers zu erkennen.
Und wir werden einen Sprachumschalter erstellen, um den Inhalt zwischen verschiedenen Sprachen zu ändern.
Lassen Sie uns einige i18next-Abhängigkeiten installieren:
exportdefaultnew RemixI18Next({ detection: { // This is the list of languages your application supports supportedLanguages: i18nextOptions.supportedLngs, // This is the language you want to use in case the user language is not // listed above fallbackLanguage: i18nextOptions.fallbackLng, }, // This is the configuration for i18next used when translating messages server // side only i18next: { backend: { loadPath: resolve('./public/locales/{{lng}}/{{ns}}.json') }, }, // The backend you want to use to load the translations // Tip: You could pass `resources` to the `i18next` configuration and avoid // a backend here backend: Backend, })
Bereiten Sie einige Ordner wie folgt vor:
Passen Sie nun in Ihrer entry.client.jsx den Code wie folgt an:
// initialize i18next using initReactI18next and configuring it if (!i18next.isInitialized) // prevent i18next to be initialized multiple times i18next .use(initReactI18next) // Tell i18next to use the react-i18next plugin .use(LanguageDetector) // Setup a client-side language detector .use(Backend) // Setup your backend .init({ ...i18nextOptions, backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' }, // This function detects the namespaces your routes rendered while SSR use // and pass them here to load the translations ns: getInitialNamespaces(), detection: { // Here only enable htmlTag detection, we'll detect the language only // server-side with remix-i18next, by using the `<html lang>` attribute // we can communicate to the client the language detected server-side order: ['htmlTag'], // Because we only use htmlTag, there's no reason to cache the language // on the browser, so we disable it caches: [], } }) .then(() => { // then hydrate your app wrapped in the I18nextProvider return hydrate( <I18nextProvideri18n={i18next}> <RemixBrowser /> </I18nextProvider>, document ) })
Und passen Sie in Ihrer entry.server.jsx den Code so an:
exportdefaultasyncfunctionhandleRequest( request, statusCode, headers, context ) { // First, we create a new instance of i18next so every request will have a // completely unique instance and not share any state const instance = createInstance()
// Then we could detect locale from the request const lng = await i18n.getLocale(request) // And here we detect what namespaces the routes about to render want to use const ns = i18n.getRouteNamespaces(context)
// First, we create a new instance of i18next so every request will have a // completely unique instance and not share any state. await instance .use(initReactI18next) // Tell our instance to use react-i18next .use(Backend) // Setup our backend.init({ .init({ ...i18nextOptions, // use the same configuration as in your client side. lng, // The locale we detected above ns, // The namespaces the routes about to render want to use backend: { loadPath: resolve("./public/locales/{{lng}}/{{ns}}.json"), } })
// Then you can render your app wrapped in the I18nextProvider as in the // entry.client file const markup = renderToString( <I18nextProvideri18n={instance}> <RemixServercontext={context}url={request.url} /> </I18nextProvider> );
exportconst loader = async ({ request }) => { const locale = await remixI18n.getLocale(request) const t = await remixI18n.getFixedT(request, 'common') const title = t('headTitle') return json({ locale, title }) }
exportconst handle = { // In the handle export, we could add a i18n key with namespaces our route // will need to load. This key can be a single string or an array of strings. i18n: ['common'] };
exportfunctionmeta({ data }) { return { title: data.title } }
exportdefaultfunctionApp() { const { i18n } = useTranslation() const { locale } = useLoaderData() // This hook will change the i18n instance language to the current locale // detected by the loader, this way, when we do something to change the // language, this locale will change and i18next will load the correct // translation files useChangeLanguage(locale)
Wir sind bereit, mit der Verwendung der t-Funktion zu beginnen.
In Ihren Pages-Dateien können Sie jetzt mit respond-i18next auf die t-Funktion zugreifen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
import { useTranslation } from'react-i18next'
exportconst handle = { // In the handle export, we could add a i18n key with namespaces our route // will need to load. This key can be a single string or an array of strings. i18n: ['index'] };
exportdefaultfunctionIndex() { const { t, ready, i18n } = useTranslation('index') if (!ready) return<Loading />// i18next may not be ready when changing route with <Link> return ( <> <div>{t('title')}</div> </> ) }
Fügen Sie die Schlüssel zu Ihren Übersetzungen hinzu, z. B. public/locales/en/index.json:
1 2 3
{ "title": "Welcome to Remix" }
Sie können dies für alle Ihre Seiten und Komponenten tun:
// Component using the Trans component functionMyComponent({ t }) { return ( <Transt={t}i18nKey="description.part1"> To get started, edit <code>src/App.js</code> and save to reload. </Trans> ) }
exportconst handle = { // In the handle export, we could add a i18n key with namespaces our route // will need to load. This key can be a single string or an array of strings. i18n: ['index'] };
exportdefaultfunctionIndex() { const { t, ready, i18n } = useTranslation('index') if (!ready) return<Loading />// i18next may not be ready when changing route with <Link>
Dies sieht aus wie die normale Verwendung von react-i18next.
Da wir hier kein Suspense verwenden, stellen Sie einfach sicher, dass Sie das ready-Flag überprüfen, bevor Sie die t-Funktion aufrufen. Die Übersetzungen werden verzögert geladen, sobald Sie auf der Clientseite zu einer anderen Seite navigieren.
Wir können auch Dinge wie den Seitentitel übersetzen.
Da remix-i18next Text innerhalb von Loadern oder Aktionen übersetzen kann, können wir dies beispielsweise in unserer root.jsx tun:
exportconst loader = async ({ request }) => { const locale = await remixI18n.getLocale(request) const t = await remixI18n.getFixedT(request, 'common') const title = t('headTitle') return json({ locale, title }) }
exportconst handle = { // In the handle export, we could add a i18n key with namespaces our route // will need to load. This key can be a single string or an array of strings. i18n: ['common'] };
exportfunctionmeta({ data }) { return { title: data.title } }
exportdefaultfunctionApp() { const { i18n } = useTranslation() const { locale } = useLoaderData() // This hook will change the i18n instance language to the current locale // detected by the loader, this way, when we do something to change the // language, this locale will change and i18next will load the correct // translation files useChangeLanguage(locale)
exportconst handle = { // In the handle export, we could add a i18n key with namespaces our route // will need to load. This key can be a single string or an array of strings. i18n: ['index'] };
// Component using the Trans component functionMyComponent({ t }) { return ( <Transt={t}i18nKey="description.part1"> To get started, edit <code>src/App.js</code> and save to reload. </Trans> ) }
exportdefaultfunctionIndex() { const { lngs } = useLoaderData() const { t, ready, i18n } = useTranslation('index') if (!ready) return<Loading />// i18next may not be ready when changing route with <Link>
exportdefaultnew RemixI18Next({ detection: { // persist language selection in cookie cookie: i18nCookie, // This is the list of languages your application supports supportedLanguages: i18nextOptions.supportedLngs, // This is the language you want to use in case the user language is not // listed above fallbackLanguage: i18nextOptions.fallbackLng, }, // This is the configuration for i18next used when translating messages server // side only i18next: { backend: { loadPath: resolve('./public/locales/{{lng}}/{{ns}}.json') }, }, // The backend you want to use to load the translations // Tip: You could pass `resources` to the `i18next` configuration and avoid // a backend here backend: Backend, })
exportconst handle = { // In the handle export, we could add a i18n key with namespaces our route // will need to load. This key can be a single string or an array of strings. i18n: ['common'] };
exportfunctionmeta({ data }) { return { title: data.title } }
exportdefaultfunctionApp() { const { i18n } = useTranslation() const { locale } = useLoaderData() // This hook will change the i18n instance language to the current locale // detected by the loader, this way, when we do something to change the // language, this locale will change and i18next will load the correct // translation files useChangeLanguage(locale)
Verbinden Sie sich mit einem grossartigen Übersetzungsmanagementsystem und verwalten Sie Ihre Übersetzungen ausserhalb Ihres Codes.
Lassen Sie uns die Übersetzungsdateien mit locize synchronisieren.
Dies kann bei Bedarf oder auf dem CI-Server oder vor der Bereitstellung der App erfolgen.
Verwenden Sie den Befehl locize sync, um Ihr lokales Repository (public/locales) mit dem zu synchronisieren, was auf locize veröffentlicht wird.
Alternativ können Sie auch den Befehl locize download verwenden, um die veröffentlichten locize-Übersetzungen immer in Ihr lokales Repository (public/locales) herunterzuladen, bevor Sie Ihre App bündeln.