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:
parent
3fe4cbc677
commit
d28ad8805a
11 changed files with 260 additions and 0 deletions
44
examples/with-i18n-rosetta/README.md
Normal file
44
examples/with-i18n-rosetta/README.md
Normal 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)).
|
6
examples/with-i18n-rosetta/components/title.js
Normal file
6
examples/with-i18n-rosetta/components/title.js
Normal 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>
|
||||
}
|
7
examples/with-i18n-rosetta/hooks/use-i18n.js
Normal file
7
examples/with-i18n-rosetta/hooks/use-i18n.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { useContext } from 'react'
|
||||
import { I18nContext } from '../lib/i18n'
|
||||
|
||||
export default function useI18n() {
|
||||
const i18n = useContext(I18nContext)
|
||||
return i18n
|
||||
}
|
57
examples/with-i18n-rosetta/lib/i18n.js
Normal file
57
examples/with-i18n-rosetta/lib/i18n.js
Normal 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>
|
||||
)
|
||||
}
|
10
examples/with-i18n-rosetta/locales/de.json
Normal file
10
examples/with-i18n-rosetta/locales/de.json
Normal 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."
|
||||
}
|
||||
}
|
10
examples/with-i18n-rosetta/locales/en.json
Normal file
10
examples/with-i18n-rosetta/locales/en.json
Normal 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."
|
||||
}
|
||||
}
|
13
examples/with-i18n-rosetta/next.config.js
Normal file
13
examples/with-i18n-rosetta/next.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
module.exports = {
|
||||
experimental: {
|
||||
redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/',
|
||||
permanent: true,
|
||||
destination: '/en',
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
16
examples/with-i18n-rosetta/package.json
Normal file
16
examples/with-i18n-rosetta/package.json
Normal 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"
|
||||
}
|
46
examples/with-i18n-rosetta/pages/[lng]/index.js
Normal file
46
examples/with-i18n-rosetta/pages/[lng]/index.js
Normal 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
|
10
examples/with-i18n-rosetta/pages/_app.js
Normal file
10
examples/with-i18n-rosetta/pages/_app.js
Normal 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>
|
||||
)
|
||||
}
|
41
examples/with-i18n-rosetta/pages/dashboard.js
Normal file
41
examples/with-i18n-rosetta/pages/dashboard.js
Normal 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
|
Loading…
Reference in a new issue