1
0
Fork 0
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:
Jan Mühlemann 2018-11-28 20:39:54 +01:00 committed by Tim Neutkens
parent c5630d3bc5
commit 09a8960f1a
21 changed files with 5 additions and 432 deletions

View file

@ -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).

View 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).

View file

@ -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)

View file

@ -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>
)
}

View file

@ -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

View file

@ -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)

View file

@ -1,5 +0,0 @@
import React from 'react'
export default function PureComponent ({ t }) {
return <p>{t('common:pureComponent')}</p>
}

View file

@ -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

View file

@ -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
}

View file

@ -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."
}

View file

@ -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)"
}
}

View file

@ -1,6 +0,0 @@
{
"welcomePage2": "Dies ist die 2te Seite",
"link": {
"gotoPage1": "Zur Seite 1"
}
}

View file

@ -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."
}

View file

@ -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)"
}
}

View file

@ -1,6 +0,0 @@
{
"welcomePage2": "this is page 2",
"link": {
"gotoPage1": "Back to page 1"
}
}

View file

@ -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"
}
}

View file

@ -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>
)
}
}

View file

@ -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)

View file

@ -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)

View file

@ -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>
)
}

View file

@ -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')
})
})
})