Allseitig optimierte Next.js-Übersetzungen (ein next-i18next Leitfaden)
en

Das Schreiben von Next.js-Code verwischt die Grenzen zwischen Client- und Serverseite.
Der Code wird einmal geschrieben und dann je nach Bedarf als SSG (Static-Site Generation), SSR (Server-Side Rendering) oder CSR (Client-Side Rendering) etc. ausgeführt.

Also auch die Internationalisierung, oder?

Wie kann man Next.js-Apps optimieren, um am besten mit Übersetzungen auf Serverseite und auf Clientseite mit next-i18next zu arbeiten?

Nehmen wir das Beispiel von next-i18next. Während next-i18next i18next und react-i18next unter der Haube verwendet, müssen Benutzer von next-i18next einfach ihre Übersetzungsinhalte als JSON-Dateien einbinden und müssen sich um nichts weiter kümmern.

Standardmässig gibt es eine next-i18next-Konfiguration, welche die Übersetzungen aus der lokalen Verzeichnisstruktur lädt und die Seiten serverseitig rendert.
Das ist ok, es funktioniert und ist für SEO etc. optimiert, aber wir könnten noch mehr tun.

Was wäre, wenn wir die SEO-optimierte Website mit immer aktuellen Übersetzungen versorgen könnten, ohne dass Sie Ihre App erneut bereitstellen müssen?

Wir werden 2 verschiedene Setups besprechen: Eines mit einem aktiven Backend und ein anderes ein vollständig statisch generiertes.

Das grundlegende Ziel ist immer das gleiche: Wir wollen, dass alles in allen Sprachen SEO-optimiert ist und unseren Nutzern immer die neusten Übersetzungen zur Verfügung stellt.

Beispiel mit einem Backend-Server

Einen Backend-Server zu haben, bedeutet nicht, dass Sie gezwungen sind, Ihren eigenen Server zu betreiben. Es kann auch eine PaaS- oder serverless Lösung sein, wie Vercel oder Netlify usw.

Ok, fangen wir mit dem Default an:
Sie haben die normale next-i18next Einrichtungsanleitung befolgt und jetzt sind Ihre Übersetzungen mehr oder weniger so organisiert:

1
2
3
4
5
6
7
.
└── public
└── locales
├── en
| └── common.json
└── de
└── common.json

Lassen Sie uns jetzt eine Verbindung zu einem grossartigen Übersetzungsverwaltungssystem herstellen und Ihre Übersetzungen ausserhalb Ihres Codes verwalten.

Lassen Sie uns die Übersetzungsdateien mit locize synchronisieren. Dies kann bei Bedarf oder auf dem CI-Server oder vor der Bereitstellung der App erfolgen.

Was zu tun ist, um diesen Schritt zu erreichen:

  1. in locize: Anmeldung unter https://locize.app/register und login
  2. in locize: ein neues Projekt erstellen
  3. in locize: Fügen Sie alle Ihre zusätzlichen Sprachen hinzu (dies kann auch über die API erfolgen)
  4. installiere das locize-cli (npm i locize-cli)

Benutzen Sie die locize-cli

Verwenden Sie den Befehl locize sync, um Ihr lokales Repository (public/locales) mit dem zu synchronisieren, was auf locize veröffentlicht wurde.

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.

Aber Sie sprachen davon, immer aktuelle Übersetzungen zu haben, ohne Ihre App erneut bereitstellen zu müssen?

Ja, passen wir uns dem an:

Wir werden das i18next-locize-backend-Plugin verwenden, aber nur auf Client-Seite.

Zusammen mit einigen anderen i18next-Abhängigkeiten:

npm install i18next-locize-backend i18next-chained-backend i18next-localstorage-backend

Und wir passen die Datei next-i18next.config.js an:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// next-i18next.config.js
const LocizeBackend = require('i18next-locize-backend/cjs')
const ChainedBackend= require('i18next-chained-backend').default
const LocalStorageBackend = require('i18next-localstorage-backend').default

const isBrowser = typeof window !== 'undefined'

module.exports = {
// debug: true,
i18n: {
defaultLocale: 'en',
locales: ['en', 'de', 'it'],
},
backend: {
backendOptions: [{
expirationTime: 60 * 60 * 1000 // 1 hour
}, {
projectId: 'd3b405cf-2532-46ae-adb8-99e88d876733',
version: 'latest'
}],
backends: isBrowser ? [LocalStorageBackend, LocizeBackend] : [],
},
serializeConfig: false,
use: isBrowser ? [ChainedBackend] : []
}

Und entfernen Sie dann die serverSideTranslation zu getStaticProps oder getServerSideProps (abhängig von Ihrem Fall) in den Komponenten auf Seitenebene.

1
2
3
4
5
6
7
8
9
//
// Ohne die Funktion getStaticProps oder getServerSideProps,
// werden die Übersetzungen über das konfigurierte i18next-Backend geladen.
//
// export const getStaticProps = async ({ locale }) => {
// return {
// props: await serverSideTranslations(locale, ['common', 'footer'])
// }
// }

Das ist es! Überprüfen wir das Ergebnis:

Das vom Server zurückgegebene HTML sieht korrekt übersetzt aus. Das ist also gut für Suchmaschinen optimiert.

Und auf der Clientseite werden die aktuellen Übersetzungen direkt vom locize CDN abgerufen.

🙀 Das bedeutet, dass Sie Übersetzungen korrigieren können, ohne Ihren Code ändern oder Ihre App erneut bereitstellen zu müssen. 🤩

🧑‍💻 Den Code finden Sie hier.

Zusätzlicher Hinweis:

Wenn Sie Caching für Ihre Locize-Version konfiguriert haben, benötigen Sie das i18next-localstorage-backend und i18next-chained-backend-Plugin nicht unbedingt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// next-i18next.config.js
const LocizeBackend = require('i18next-locize-backend/cjs')

const isBrowser = typeof window !== 'undefined'

module.exports = {
// debug: true,
i18n: {
defaultLocale: 'en',
locales: ['en', 'de', 'it'],
},
backend: isBrowser ? {
projectId: 'd3b405cf-2532-46ae-adb8-99e88d876733',
version: 'production'
} : undefined,
serializeConfig: false,
use: isBrowser ? [LocizeBackend] : []
}

Alternative Verwendung:

Falls Sie das Ready-Flag verwenden und eine Warnung wie diese sehen: Expected server HTML to contains a matching text node for..., hat dies folgenden Grund:

Der Server hat den korrekten Übersetzungstext gerendert, aber der Client muss die Übersetzungen immer noch verzögert laden und zeigt eine andere Benutzeroberfläche an. Dies bedeutet, dass es eine Hydratationsfehlanpassung gibt.

Dies kann verhindert werden, indem die getServerSideProps- oder getStaticProps-Funktion beibehalten wird, aber die reloadResources-Funktionalität von i18next genutzt wird.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const LazyReloadPage = () => {

const { t, i18n } = useTranslation(['lazy-reload-page', 'footer'], { bindI18n: 'languageChanged loaded' })
// bindI18n: loaded is needed because of the reloadResources call
// if all pages use the reloadResources mechanism, the bindI18n option can also be defined in next-i18next.config.js
useEffect(() => {
i18n.reloadResources(i18n.resolvedLanguage, ['lazy-reload-page', 'footer'])
}, [])

return (
<>
<main>
<Header heading={t('h1')} title={t('title')} />
<Link href='/'>
<button
type='button'
>
{t('back-to-home')}
</button>
</Link>
</main>
<Footer />
</>
)
}

export const getStaticProps = async ({ locale }) => ({
props: {
...await serverSideTranslations(locale, ['lazy-reload-page', 'footer']),
},
})

export default LazyReloadPage

Auf diese Weise entfällt auch die Ready-Prüfung, da die direkt vom Server bereitgestellten Übersetzungen verwendet werden. Und sobald die Übersetzungen neu geladen werden, werden neue Übersetzungen angezeigt.

Beispiel für eine statische Website

Bei diesem Beispiel brauchen wir nur einen statischen Webserver wie GitHub Pages oder ähnliches.

Es ist ziemlich dasselbe wie bei obigem Beispiel, aber es gibt ein paar Kleinigkeiten, die wir zusätzlich beachten müssen.

Um mit der Static-Site-Generierung (SSG) zu arbeiten, müssen wir den Befehl next export verwenden, aber...

Error: i18n support is not compatible with next export. See here for more info on deploying: https://nextjs.org/docs/deployment

Dies passiert, wenn Sie die Funktion internationalisiertes Routing verwenden und versuchen, einen statischen HTML-Export, indem Sie next export ausführen. Nun, diese Funktion erfordert einen Node.js-Server oder eine dynamische Logik, die während des Build-Prozesses nicht berechnet werden kann, deshalb ist sie nicht unterstützt.

Es gibt einen dedizierten Artikel mit einer Lösung für dieses Next.js-Problem. Folge zuerst dieser Anleitung!

Gemacht? Dann machen wir hier weiter:

Es ist dieselbe next-i18next.config.js-Konfiguration wie im vorherigen Beispiel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// next-i18next.config.js
const LocizeBackend = require('i18next-locize-backend/cjs')
const ChainedBackend= require('i18next-chained-backend').default
const LocalStorageBackend = require('i18next-localstorage-backend').default

// If you've configured caching for your locize version, you may not need the i18next-localstorage-backend and i18next-chained-backend plugin.
// https://docs.locize.com/more/caching

const isBrowser = typeof window !== 'undefined'

module.exports = {
// debug: true,
i18n: {
defaultLocale: 'en',
locales: ['en', 'de', 'it'],
},
backend: {
backendOptions: [{
expirationTime: 60 * 60 * 1000 // 1 hour
}, {
projectId: 'd3b405cf-2532-46ae-adb8-99e88d876733',
version: 'latest'
}],
backends: isBrowser ? [LocalStorageBackend, LocizeBackend] : [],
},
serializeConfig: false,
use: isBrowser ? [ChainedBackend] : []
}

Erweitern Sie die makeStaticProps-Funktion mit Optionen (emptyI18nStoreStore):

1
2
3
4
5
6
7
8
9
10
11
12
export function makeStaticProps(ns = [], opt = {}) {
return async function getStaticProps(ctx) {
const props = await getI18nProps(ctx, ns)
if (opt.emptyI18nStoreStore) {
// let the client fetch the translations
props._nextI18Next.initialI18nStore = null
}
return {
props
}
}
}

...und entsprechend verwenden:

1
2
const getStaticProps = makeStaticProps(['common', 'footer'], { emptyI18nStoreStore: true })
export { getStaticPaths, getStaticProps }

Das ist es! Überprüfen wir das Ergebnis:

Das generierte statische HTML sieht korrekt übersetzt aus. Das ist also gut für Suchmaschinen optimiert.

Und auf der Client-Seite werden die aktuellen Übersetzungen direkt aus dem locize CDN abgerufen.

🙀 Das bedeutet, dass Sie Übersetzungen korrigieren können, ohne Ihren Code ändern oder Ihre App erneut bereitstellen zu müssen. Und ohne einen aktiven Server zu besitzen. 🤩

🧑‍💻 Der Code kann here gefunden werden.

Kontinuierliche Lokalisierung

Da wir jetzt mit as smart Übersetzungsmanagementsystem "verbunden“ sind, können wir versuchen, sein volles Potenzial auszuschöpfen.

fehlende Übersetzungen speichern

Ich möchte, dass neu hinzugefügte Schlüssel im Code automatisch gespeichert werden, um zu lokalisieren.

Dein Wunsch ist mir Befehl!

Erweitern Sie die next-i18next config mit dem locize api-key und setzen Sie saveMissing: true:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// next-i18next.config.js
const LocizeBackend = require('i18next-locize-backend/cjs')

const isBrowser = typeof window !== 'undefined'

module.exports = {
// debug: true,
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
backend: {
projectId: 'd3b405cf-2532-46ae-adb8-99e88d876733',
apiKey: '14bbe1fa-6ffc-40f5-9226-7462aa4a042f',
version: 'latest'
},
serializeConfig: false,
use: isBrowser ? [LocizeBackend] : [],
saveMissing: true // do not set saveMissing to true for production and also not when using the chained backend
}

Jedes Mal, wenn Sie einen neuen Schlüssel verwenden, wird dieser zu locize gesendet, d.h.:

1
<div>{t('new.key', 'this will be added automatically')}</div>

resultiert in locize wie folgt:

missing key

👀 aber es gibt noch mehr...

Dank des Plugins locize-lastused können Sie in locize, Schlüssel welche verwendet oder nicht mehr verwendet werden, finden und filtern.

Mit Hilfe des Plugins locize können Sie Ihre App im locize InContext Editor verwenden.

Zusätzlich mit Hilfe des Auto-MachineTranslation-Workflows und der Verwendung der saveMissing-Funktionalität werden während der Entwicklung der App nicht nur neue Schlüssel zur automatischen Lokalisierung hinzugefügt, sondern auch automatisch per maschineller Übersetzung in die Zielsprachen übersetzt.

Sehen Sie sich dieses Video an, um zu sehen, wie der Arbeitsablauf der automatischen maschinellen Übersetzung aussieht!

npm install locize-lastused locize

und zwar so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// next-i18next.config.js
const LocizeBackend = require('i18next-locize-backend/cjs')

const isBrowser = typeof window !== 'undefined'

const locizeOptions = {
projectId: 'd3b405cf-2532-46ae-adb8-99e88d876733',
apiKey: '14bbe1fa-6ffc-40f5-9226-7462aa4a042f',
version: 'latest'
}

module.exports = {
// debug: true,
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
backend: locizeOptions,
locizeLastUsed: locizeOptions,
serializeConfig: false,
use: isBrowser ? [LocizeBackend, require('locize').locizePlugin, require('locize-lastused/cjs')] : [], // do not use locize-lastused on production
saveMissing: true // do not set saveMissing to true for production and also not when using the chained backend
}

Automatische maschinelle Übersetzung:

missing key automatisch

Filter für zuletzt verwendete Übersetzungen:

i18next last used

InContext-Editor:

i18next inkontext

📦 Bereiten wir uns auf die Produktion vor 🚀

Now, we prepare the app for going to production.

Jetzt bereiten wir die App für den Produktionsstart vor (https://docs.locize.com/guides-tips-and-tricks/going-production).

Erstellen Sie zuerst in locize eine dedizierte Version für die Produktion. Aktivieren Sie die automatische Veröffentlichung für diese Version nicht, sondern veröffentlichen Sie sie manuell oder über die API oder über die CLI. Schliesslich aktivieren Sie auch Cache-Control max-age​ für diese Produktionsversion.

Passen wir die Datei next-i18next.config.js noch einmal an:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// next-i18next.config.js
const LocizeBackend = require('i18next-locize-backend/cjs')

const isBrowser = typeof window !== 'undefined'

const locizeOptions = {
projectId: 'd3b405cf-2532-46ae-adb8-99e88d876733',
apiKey: '14bbe1fa-6ffc-40f5-9226-7462aa4a042f',
version: 'latest'
}

module.exports = {
// debug: true,
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
backend: locizeOptions,
locizeLastUsed: locizeOptions,
serializeConfig: false,
use: isBrowser ? [LocizeBackend, require('locize').locizePlugin, require('locize-lastused/cjs')] : [], // do not use locize-lastused on production
saveMissing: true // do not set saveMissing to true for production and also not when using the chained backend
}

Während der Entwicklung werden Sie nun weiterhin fehlende Schlüssel speichern und die lastused Funktionalität nutzen. => npm run dev

Und in der Produktionsumgebung sind saveMissing und lastused deaktiviert, und auch der API-Schlüssel wird nicht verfügbar gemacht. => npm run build && npm start

Caching:

i18next Caching

Versionen zusammenführen:

Version überschreiben

🧑‍💻 Den vollständigen Code finden Sie hier.

Sehen Sie sich auch den Teil zur Code-Integration in diesem YouTube-Video.

Es gibt auch ein i18next-Crashkurs-Video.

🎉🥳 Herzlichen Glückwunsch 🎊🎁

Genial! Dank an next-i18next, i18next, react-i18next und locize ist Ihr kontinuierlicher Lokalisierungs-Workflow einsatzbereit.

Wenn Sie also Ihr i18n-Thema auf die nächste Ebene bringen möchten, lohnt es sich, die Übersetzungs-Management Platform - locize auszuprobieren.

Die Gründer von locize sind auch die Schöpfer von i18next. Mit der Nutzung von locize unterstützen Sie also direkt die Zukunft von i18next.

👍

Share