Rosetta i18n example (#11841)

* add rosetta example

* add comment

* add example for ssr

* add debug example, rerender when i18n keys are added

* output active locale, fix interpolation, fix cases for SSR and CSR

* add useful comments

* address pr issues

* update name in readme

* improve wording

* rename folder

* fix prop typo, add redirects

* load specific i18n json file in getStatisProps
Dashboard dont use any ssr technique

* use ext

* improve example

* Updated example

Co-authored-by: Luis Alvarez <luis@zeit.co>
This commit is contained in:
Dustin Deus 2020-04-21 19:49:18 +02:00 committed by GitHub
parent 3fe4cbc677
commit d28ad8805a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 260 additions and 0 deletions

View file

@ -0,0 +1,44 @@
# rosetta example
This example uses [rosetta](https://github.com/lukeed/rosetta), react hooks and context to provide a SSR, SSG, CSR compatible i18n solution.
In `next.config.js` you can configure the fallback language.
## Deploy your own
Deploy the example using [Vercel](https://vercel.com):
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-i18n-rosetta)
## How to use
### Using `create-next-app`
Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
```bash
npm init next-app --example with-i18n-rosetta with-i18n-rosetta
# or
yarn create next-app --example with-i18n-rosetta with-i18n-rosetta
```
### Download manually
Download the example:
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-i18n-rosetta
cd with-i18n-rosetta
```
Install it and run:
```bash
npm install
npm run dev
# or
yarn
yarn dev
```
Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).

View file

@ -0,0 +1,6 @@
import useI18n from '../hooks/use-i18n'
export default function Title({ username }) {
const i18n = useI18n()
return <h1>{i18n.t('intro.welcome', { username })}</h1>
}

View file

@ -0,0 +1,7 @@
import { useContext } from 'react'
import { I18nContext } from '../lib/i18n'
export default function useI18n() {
const i18n = useContext(I18nContext)
return i18n
}

View file

@ -0,0 +1,57 @@
import { createContext, useState, useRef, useEffect } from 'react'
import rosetta from 'rosetta'
// import rosetta from 'rosetta/debug';
const i18n = rosetta()
export const defaultLanguage = 'en'
export const languages = ['de', 'en']
export const contentLanguageMap = { de: 'de-DE', en: 'en-US' }
export const I18nContext = createContext()
// default language
i18n.locale(defaultLanguage)
export default function I18n({ children, locale, lngDict }) {
const [activeDict, setActiveDict] = useState(() => lngDict)
const activeLocaleRef = useRef(locale || defaultLanguage)
const [, setTick] = useState(0)
const firstRender = useRef(true)
// for initial SSR render
if (locale && firstRender.current === true) {
firstRender.current = false
i18n.locale(locale)
i18n.set(locale, activeDict)
}
useEffect(() => {
if (locale) {
i18n.locale(locale)
i18n.set(locale, activeDict)
activeLocaleRef.current = locale
// force rerender
setTick(tick => tick + 1)
}
}, [locale, activeDict])
const i18nWrapper = {
activeLocale: activeLocaleRef.current,
t: (...args) => i18n.t(...args),
locale: (l, dict) => {
i18n.locale(l)
activeLocaleRef.current = l
if (dict) {
i18n.set(l, dict)
setActiveDict(dict)
} else {
setTick(tick => tick + 1)
}
},
}
return (
<I18nContext.Provider value={i18nWrapper}>{children}</I18nContext.Provider>
)
}

View file

@ -0,0 +1,10 @@
{
"intro": {
"welcome": "Willkommen, {{username}}!",
"text": "Ich hoffe, du findest das nützlich.",
"description": "Das Beispiel zeigt, wie man die Sprache für SSG und SSG optimierte Seiten wechselt."
},
"dashboard": {
"description": "Das Beispiel zeigt, wie man die Sprache nur Frontendseitig verändert. Nützlich für Dashboards wo SEO nicht relevant ist."
}
}

View file

@ -0,0 +1,10 @@
{
"intro": {
"welcome": "Welcome, {{username}}!",
"text": "I hope you find this useful.",
"description": "This example demonstrate how to change the language for SSG and SSR optimized pages."
},
"dashboard": {
"description": "This example demonstrate how to change the language on client side only. Useful for dashboards because they don't require SEO."
}
}

View file

@ -0,0 +1,13 @@
module.exports = {
experimental: {
redirects() {
return [
{
source: '/',
permanent: true,
destination: '/en',
},
]
},
},
}

View file

@ -0,0 +1,16 @@
{
"name": "with-rosetta",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"rosetta": "1.0.0"
},
"license": "ISC"
}

View file

@ -0,0 +1,46 @@
import Link from 'next/link'
import Head from 'next/head'
import Title from '../../components/title'
import useI18n from '../../hooks/use-i18n'
import { languages, contentLanguageMap } from '../../lib/i18n'
const HomePage = () => {
const i18n = useI18n()
return (
<div>
<Head>
<meta
httpEquiv="content-language"
content={contentLanguageMap[i18n.activeLocale]}
/>
</Head>
<Title username="Peter" />
<h2>{i18n.t('intro.text')}</h2>
<h3>{i18n.t('intro.description')}</h3>
<div>Current locale: {i18n.activeLocale}</div>
<Link href="/de">
<a>Use client-side routing to change language to 'de'</a>
</Link>
</div>
)
}
export async function getStaticProps({ params }) {
const { default: lngDict = {} } = await import(
`../../locales/${params.lng}.json`
)
return {
props: { lng: params.lng, lngDict },
}
}
export async function getStaticPaths() {
return {
paths: languages.map(l => ({ params: { lng: l } })),
fallback: false,
}
}
export default HomePage

View file

@ -0,0 +1,10 @@
import React from 'react'
import I18n from '../lib/i18n'
export default function MyApp({ Component, pageProps }) {
return (
<I18n lngDict={pageProps.lngDict} locale={pageProps.lng}>
<Component {...pageProps} />
</I18n>
)
}

View file

@ -0,0 +1,41 @@
import { useEffect } from 'react'
import Head from 'next/head'
import useI18n from '../hooks/use-i18n'
import Title from '../components/title'
import { contentLanguageMap } from '../lib/i18n'
import EN from '../locales/en.json'
import DE from '../locales/de.json'
const Dashboard = () => {
const i18n = useI18n()
useEffect(() => {
i18n.locale('en', EN)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<div>
<Head>
<meta
httpEquiv="content-language"
content={contentLanguageMap[i18n.activeLocale]}
/>
</Head>
<Title username="Peter" />
<h2>{i18n.t('intro.text')}</h2>
<h3>{i18n.t('dashboard.description')}</h3>
<div>Current locale: {i18n.activeLocale}</div>
<a
href="#"
onClick={() => {
i18n.locale('de', DE)
}}
>
Change language client-side to 'de'
</a>
</div>
)
}
export default Dashboard