mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Add next-i18next as the solution to integrate i18next into next.js (#5761)
* replace all i18next related examples with the recommended next-i18next module * update readme
This commit is contained in:
parent
c5630d3bc5
commit
09a8960f1a
|
@ -1,4 +0,0 @@
|
|||
|
||||
# with-i18next example
|
||||
|
||||
The `with-i18next` example is maintained within the `react-i18next` repository and [can be found here](https://github.com/i18next/react-i18next/tree/master/example/nextjs).
|
5
examples/with-next-i18next/README.md
Normal file
5
examples/with-next-i18next/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# with-next-i18next - internationalization using i18next
|
||||
|
||||
The next-i18next module is the simplest way to add internationalization based on i18next to your next.js application providing all the benefits of react-i18next.
|
||||
|
||||
Learn more on [next-i18next](https://github.com/isaachinman/next-i18next).
|
|
@ -1,82 +0,0 @@
|
|||
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-react-i18next)
|
||||
|
||||
# Internationalization with [react-i18next](https://github.com/i18next/react-i18next).
|
||||
|
||||
## How to use
|
||||
|
||||
### Using `create-next-app`
|
||||
|
||||
Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example with-react-i18next with-react-i18next-app
|
||||
# or
|
||||
yarn create next-app --example with-react-i18next with-react-i18next-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
||||
Download the example:
|
||||
|
||||
```bash
|
||||
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-react-i18next
|
||||
cd with-react-i18next
|
||||
```
|
||||
|
||||
Install it and run:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
# or
|
||||
yarn
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
|
||||
|
||||
```bash
|
||||
now
|
||||
```
|
||||
|
||||
### Testing the app
|
||||
|
||||
auto detecting user language: [http://localhost:3000](http://localhost:3000)
|
||||
|
||||
german: [http://localhost:3000/?lng=de](http://localhost:3000/?lng=de)
|
||||
|
||||
english: [http://localhost:3000/?lng=en](http://localhost:3000/?lng=en)
|
||||
|
||||
## The idea behind the example
|
||||
|
||||
This example app shows how to integrate [react-i18next](https://github.com/i18next/react-i18next) with [Next](https://github.com/zeit/next.js).
|
||||
|
||||
**Plus:**
|
||||
|
||||
- Routing and separating translations into multiple files (lazy load them on client routing)
|
||||
- Child components (pure or using translation hoc)
|
||||
|
||||
### Features of this example app
|
||||
|
||||
- Server-side language negotiation
|
||||
- Full control and usage of i18next on express server using [i18next-express-middleware](https://github.com/i18next/i18next-express-middleware) which asserts no async request collisions resulting in wrong language renderings
|
||||
- Support for save missing features to get untranslated keys automatically created `locales/{lng}/{namespace}.missing.json` -> never miss to translate a key
|
||||
- Proper pass down on translations via initialProps
|
||||
- Taking advantage of multiple translation files including lazy loading on client (no need to load all translations upfront)
|
||||
- Use express to also serve translations for clientside
|
||||
- In contrast to react-intl the translations are visible both during development and in production
|
||||
|
||||
### learn more
|
||||
|
||||
- [next.js](https://github.com/zeit/next.js)
|
||||
- [react-i18next repository](https://github.com/i18next/react-i18next)
|
||||
- [react-i18next documentation](https://react.i18next.com)
|
||||
|
||||
**Translation features:**
|
||||
|
||||
- [i18next repository](https://github.com/i18next/i18next)
|
||||
- [i18next documentation](https://www.i18next.com)
|
||||
|
||||
**Translation management:**
|
||||
|
||||
- [locize](http://locize.com)
|
|
@ -1,12 +0,0 @@
|
|||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
export default function ComponentWithTrans () {
|
||||
return (
|
||||
<p>
|
||||
<Trans i18nKey='common:transComponent'>
|
||||
Alternatively, you can use <code>Trans</code> component.
|
||||
</Trans>
|
||||
</p>
|
||||
)
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import React from 'react'
|
||||
import { withNamespaces } from 'react-i18next'
|
||||
|
||||
function MyComponent ({ t }) {
|
||||
return <p>{t('extendedComponent')}</p>
|
||||
}
|
||||
|
||||
const Extended = withNamespaces('common')(MyComponent)
|
||||
|
||||
export default Extended
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { withRouter } from 'next/router'
|
||||
|
||||
import i18n from '../i18n'
|
||||
|
||||
const LanguageSwitch = ({ router }) => (
|
||||
<ul>
|
||||
<li>
|
||||
<Link href={`${router.pathname}`}>
|
||||
<a onClick={() => i18n.changeLanguage('en')}>en</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href={`${router.pathname}?lng=de`}>
|
||||
<a onClick={() => i18n.changeLanguage('de')}>de</a>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
|
||||
export default withRouter(LanguageSwitch)
|
|
@ -1,5 +0,0 @@
|
|||
import React from 'react'
|
||||
|
||||
export default function PureComponent ({ t }) {
|
||||
return <p>{t('common:pureComponent')}</p>
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
const i18n = require('i18next')
|
||||
const XHR = require('i18next-xhr-backend')
|
||||
const LanguageDetector = require('i18next-browser-languagedetector')
|
||||
|
||||
const options = {
|
||||
fallbackLng: 'en',
|
||||
load: 'languageOnly', // we only provide en, de -> no region specific locals like en-US, de-DE
|
||||
|
||||
// have a common namespace used around the full app
|
||||
ns: ['common'],
|
||||
defaultNS: 'common',
|
||||
|
||||
debug: false, // process.env.NODE_ENV !== 'production',
|
||||
saveMissing: true,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react!!
|
||||
formatSeparator: ',',
|
||||
format: (value, format, lng) => {
|
||||
if (format === 'uppercase') return value.toUpperCase()
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for browser use xhr backend to load translations and browser lng detector
|
||||
if (process.browser) {
|
||||
i18n
|
||||
.use(XHR)
|
||||
// .use(Cache)
|
||||
.use(LanguageDetector)
|
||||
}
|
||||
|
||||
// initialize if not already initialized
|
||||
if (!i18n.isInitialized) i18n.init(options)
|
||||
|
||||
// a simple helper to getInitialProps passed on loaded i18n data
|
||||
i18n.getInitialProps = (req, namespaces) => {
|
||||
if (!namespaces) namespaces = i18n.options.defaultNS
|
||||
if (typeof namespaces === 'string') namespaces = [namespaces]
|
||||
|
||||
req.i18n.toJSON = () => {} // do not serialize i18next instance to prevent circular references on the client
|
||||
|
||||
const initialI18nStore = {}
|
||||
req.i18n.languages.forEach((l) => {
|
||||
initialI18nStore[l] = {}
|
||||
namespaces.forEach((ns) => {
|
||||
initialI18nStore[l][ns] = (req.i18n.services.resourceStore.data[l] || {})[ns] || {}
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
i18n: req.i18n, // use the instance on req - fixed language on request (avoid issues in race conditions with lngs of different users)
|
||||
initialI18nStore,
|
||||
initialLanguage: req.i18n.language
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = i18n
|
|
@ -1,25 +0,0 @@
|
|||
import { withNamespaces } from 'react-i18next'
|
||||
import i18n from '../i18n'
|
||||
|
||||
export const withI18next = (namespaces = ['common']) => ComposedComponent => {
|
||||
const Extended = withNamespaces(namespaces, { wait: process.browser })(
|
||||
ComposedComponent
|
||||
)
|
||||
|
||||
Extended.getInitialProps = async (ctx) => {
|
||||
const composedInitialProps = ComposedComponent.getInitialProps
|
||||
? await ComposedComponent.getInitialProps(ctx)
|
||||
: {}
|
||||
|
||||
const i18nInitialProps = ctx.req
|
||||
? i18n.getInitialProps(ctx.req, namespaces)
|
||||
: {}
|
||||
|
||||
return {
|
||||
...composedInitialProps,
|
||||
...i18nInitialProps
|
||||
}
|
||||
}
|
||||
|
||||
return Extended
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"integrates_react-i18next": "Dieses Beispiel integriert react-i18next für einfache Übersetzung.",
|
||||
"pureComponent": "Entweder t Funktion an Komponente via props weiterreichen.",
|
||||
"extendedComponent": "Oder die Komponente erneut mit dem translate hoc erweiteren.",
|
||||
"transComponent": "Sonst können Sie auch die Komponente <1>Trans</1> verwenden."
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"welcome": "Willkommen zu next.js",
|
||||
"sample_test": "test words for de",
|
||||
"sample_button": "fire in the wind for de",
|
||||
"link": {
|
||||
"gotoPage2": "Zur Seite 2",
|
||||
"gotoPage3": "Zur Seite 3 (no hoc)"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"welcomePage2": "Dies ist die 2te Seite",
|
||||
"link": {
|
||||
"gotoPage1": "Zur Seite 1"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"integrates_react-i18next": "This example integrates react-i18next for simple internationalization.",
|
||||
"pureComponent": "You can either pass t function to child components.",
|
||||
"extendedComponent": "Or wrap your component using the translate hoc provided by react-i18next.",
|
||||
"transComponent": "Alternatively, you can use <1>Trans</1> component."
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"welcome": "welcome to next.js",
|
||||
"sample_test": "test words for en",
|
||||
"sample_button": "fire in the wind for en",
|
||||
"link": {
|
||||
"gotoPage2": "Go to page 2",
|
||||
"gotoPage3": "Go to page 3 (no hoc)"
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"welcomePage2": "this is page 2",
|
||||
"link": {
|
||||
"gotoPage1": "Back to page 1"
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"name": "react-i18next-nextjs-example",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
"start": "NODE_ENV=production node server.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "4.16.3",
|
||||
"i18next": "11.9.0",
|
||||
"i18next-browser-languagedetector": "2.2.3",
|
||||
"i18next-express-middleware": "1.4.0",
|
||||
"i18next-node-fs-backend": "2.1.0",
|
||||
"i18next-xhr-backend": "1.5.1",
|
||||
"next": "latest",
|
||||
"react": "^16.5.2",
|
||||
"react-dom": "^16.5.2",
|
||||
"react-i18next": "8.0.6"
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import React from 'react'
|
||||
import App, { Container } from 'next/app'
|
||||
import { NamespacesConsumer, I18nextProvider } from 'react-i18next'
|
||||
import initialI18nInstance from '../i18n'
|
||||
import LanguageSwitch from '../components/LanguageSwitch'
|
||||
|
||||
export default class MyApp extends App {
|
||||
render () {
|
||||
const { Component, pageProps } = this.props
|
||||
const { i18n, initialI18nStore, initialLanguage } = pageProps || {}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<I18nextProvider
|
||||
i18n={i18n || initialI18nInstance}
|
||||
initialI18nStore={initialI18nStore}
|
||||
initialLanguage={initialLanguage}
|
||||
>
|
||||
<React.Fragment>
|
||||
<NamespacesConsumer ns='common' wait>
|
||||
{t => <h1>{t('common:integrates_react-i18next')}</h1>}
|
||||
</NamespacesConsumer>
|
||||
<LanguageSwitch />
|
||||
|
||||
<Component {...pageProps} />
|
||||
</React.Fragment>
|
||||
</I18nextProvider>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
import PureComponent from '../components/PureComponent'
|
||||
import ExtendedComponent from '../components/ExtendedComponent'
|
||||
import ComponentWithTrans from '../components/ComponentWithTrans'
|
||||
import { withI18next } from '../lib/withI18next'
|
||||
|
||||
const Test = ({ t }) => (
|
||||
<div>
|
||||
<h1>{t('welcome')}</h1>
|
||||
<p>{t('common:integrates_react-i18next')}</p>
|
||||
<p>{t('sample_test')}</p>
|
||||
<div>
|
||||
<button>{t('sample_button')}</button>
|
||||
</div>
|
||||
<PureComponent t={t} />
|
||||
<ExtendedComponent />
|
||||
<ComponentWithTrans />
|
||||
<Link href='/page2'>
|
||||
<a>{t('link.gotoPage2')}</a>
|
||||
</Link>
|
||||
<br />
|
||||
<Link href='/page3'>
|
||||
<a>{t('link.gotoPage3')}</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default withI18next(['home', 'common'])(Test)
|
|
@ -1,22 +0,0 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
import PureComponent from '../components/PureComponent'
|
||||
import ExtendedComponent from '../components/ExtendedComponent'
|
||||
import ComponentWithTrans from '../components/ComponentWithTrans'
|
||||
import { withI18next } from '../lib/withI18next'
|
||||
|
||||
const Page2 = ({ t }) => (
|
||||
<div>
|
||||
<h1>{t('welcomePage2')}</h1>
|
||||
<p>{t('common:integrates_react-i18next')}</p>
|
||||
<PureComponent t={t} />
|
||||
<ExtendedComponent />
|
||||
<ComponentWithTrans />
|
||||
<Link href='/'>
|
||||
<a>{t('link.gotoPage1')}</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default withI18next(['page2', 'common'])(Page2)
|
|
@ -1,14 +0,0 @@
|
|||
// a page not using i18next - no hoc - not t function
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Hello Page 3</h1>
|
||||
<Link href='/'>
|
||||
<a>back</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
const express = require('express')
|
||||
const path = require('path')
|
||||
const next = require('next')
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const app = next({ dev })
|
||||
const handle = app.getRequestHandler()
|
||||
|
||||
const i18nextMiddleware = require('i18next-express-middleware')
|
||||
const Backend = require('i18next-node-fs-backend')
|
||||
const i18n = require('./i18n')
|
||||
|
||||
// init i18next with serverside settings
|
||||
// using i18next-express-middleware
|
||||
i18n
|
||||
.use(Backend)
|
||||
.use(i18nextMiddleware.LanguageDetector)
|
||||
.init({
|
||||
fallbackLng: 'en',
|
||||
preload: ['en', 'de'], // preload all langages
|
||||
ns: ['common', 'home', 'page2'], // need to preload all the namespaces
|
||||
backend: {
|
||||
loadPath: path.join(__dirname, '/locales/{{lng}}/{{ns}}.json'),
|
||||
addPath: path.join(__dirname, '/locales/{{lng}}/{{ns}}.missing.json')
|
||||
}
|
||||
}, () => {
|
||||
// loaded translations we can bootstrap our routes
|
||||
app.prepare()
|
||||
.then(() => {
|
||||
const server = express()
|
||||
|
||||
// enable middleware for i18next
|
||||
server.use(i18nextMiddleware.handle(i18n))
|
||||
|
||||
// serve locales for client
|
||||
server.use('/locales', express.static(path.join(__dirname, '/locales')))
|
||||
|
||||
// missing keys
|
||||
server.post('/locales/add/:lng/:ns', i18nextMiddleware.missingKeyHandler(i18n))
|
||||
|
||||
// use next.js
|
||||
server.get('*', (req, res) => handle(req, res))
|
||||
|
||||
server.listen(3000, (err) => {
|
||||
if (err) throw err
|
||||
console.log('> Ready on http://localhost:3000')
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue