mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
adds with react-i18next example (#2558)
* adds with react-i18next example * lint example code * remove unneeded .babelrc
This commit is contained in:
parent
ed733dc14d
commit
e7d91bf692
51
examples/with-react-i18next/README.md
Normal file
51
examples/with-react-i18next/README.md
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# Getting started
|
||||||
|
|
||||||
|
Example with [react-i18next](https://github.com/i18next/react-i18next).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm install
|
||||||
|
# npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**open:**
|
||||||
|
|
||||||
|
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)
|
14
examples/with-react-i18next/components/ExtendedComponent.js
Normal file
14
examples/with-react-i18next/components/ExtendedComponent.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { translate } from 'react-i18next'
|
||||||
|
|
||||||
|
function MyComponennt ({ t }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{t('extendedComponent')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Extended = translate('common')(MyComponennt)
|
||||||
|
|
||||||
|
export default Extended
|
10
examples/with-react-i18next/components/PureComponent.js
Normal file
10
examples/with-react-i18next/components/PureComponent.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
// pure component just getting t function by props
|
||||||
|
export default function PureComponent ({ t }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{t('common:pureComponent')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
59
examples/with-react-i18next/i18n.js
Normal file
59
examples/with-react-i18next/i18n.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
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: true,
|
||||||
|
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.defautlNS
|
||||||
|
if (typeof namespaces === 'string') namespaces = [namespaces]
|
||||||
|
|
||||||
|
req.i18n.toJSON = () => null // do not serialize i18next instance and send to 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
|
5
examples/with-react-i18next/locales/de/common.json
Normal file
5
examples/with-react-i18next/locales/de/common.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"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."
|
||||||
|
}
|
6
examples/with-react-i18next/locales/de/home.json
Normal file
6
examples/with-react-i18next/locales/de/home.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"welcome": "Willkommen zu next.js",
|
||||||
|
"link": {
|
||||||
|
"gotoPage2": "Zur Seite 2"
|
||||||
|
}
|
||||||
|
}
|
6
examples/with-react-i18next/locales/de/page2.json
Normal file
6
examples/with-react-i18next/locales/de/page2.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"welcomePage2": "Dies ist die 2te Seite",
|
||||||
|
"link": {
|
||||||
|
"gotoPage1": "Zur Seite 1"
|
||||||
|
}
|
||||||
|
}
|
5
examples/with-react-i18next/locales/en/common.json
Normal file
5
examples/with-react-i18next/locales/en/common.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"integrates_react-i18next": "this sample 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."
|
||||||
|
}
|
6
examples/with-react-i18next/locales/en/home.json
Normal file
6
examples/with-react-i18next/locales/en/home.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"welcome": "welcome to next.js",
|
||||||
|
"link": {
|
||||||
|
"gotoPage2": "Go to page 2"
|
||||||
|
}
|
||||||
|
}
|
6
examples/with-react-i18next/locales/en/page2.json
Normal file
6
examples/with-react-i18next/locales/en/page2.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"welcomePage2": "this is page 2",
|
||||||
|
"link": {
|
||||||
|
"gotoPage1": "Back to page 1"
|
||||||
|
}
|
||||||
|
}
|
25
examples/with-react-i18next/package.json
Normal file
25
examples/with-react-i18next/package.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"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.15.3",
|
||||||
|
"i18next": "8.4.2",
|
||||||
|
"i18next-browser-languagedetector": "2.0.0",
|
||||||
|
"i18next-express-middleware": "1.0.5",
|
||||||
|
"i18next-node-fs-backend": "1.0.0",
|
||||||
|
"i18next-xhr-backend": "1.4.2",
|
||||||
|
"next": "2.4.4",
|
||||||
|
"react": "15.6.1",
|
||||||
|
"react-dom": "15.6.1",
|
||||||
|
"react-i18next": "4.6.3"
|
||||||
|
}
|
||||||
|
}
|
30
examples/with-react-i18next/pages/index.js
Normal file
30
examples/with-react-i18next/pages/index.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { translate } from 'react-i18next'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
import PureComponent from '../components/PureComponent'
|
||||||
|
import ExtendedComponent from '../components/ExtendedComponent'
|
||||||
|
|
||||||
|
function Home ({ t, initialI18nStore }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{t('welcome')}
|
||||||
|
<p>{t('common:integrates_react-i18next')}</p>
|
||||||
|
<PureComponent t={t} />
|
||||||
|
<ExtendedComponent />
|
||||||
|
<Link href='/page2'><a>{t('link.gotoPage2')}</a></Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Extended = translate(['home', 'common'], { i18n, wait: process.browser })(Home)
|
||||||
|
|
||||||
|
// Passing down initial translations
|
||||||
|
// use req.i18n instance on serverside to avoid overlapping requests set the language wrong
|
||||||
|
Extended.getInitialProps = async ({ req }) => {
|
||||||
|
if (req && !process.browser) return i18n.getInitialProps(req, ['home', 'common'])
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Extended
|
30
examples/with-react-i18next/pages/page2.js
Normal file
30
examples/with-react-i18next/pages/page2.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { translate } from 'react-i18next'
|
||||||
|
import i18n from '../i18n'
|
||||||
|
|
||||||
|
import PureComponent from '../components/PureComponent'
|
||||||
|
import ExtendedComponent from '../components/ExtendedComponent'
|
||||||
|
|
||||||
|
function Page2 ({ t, initialI18nStore }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{t('welcomePage2')}
|
||||||
|
<p>{t('common:integrates_react-i18next')}</p>
|
||||||
|
<PureComponent t={t} />
|
||||||
|
<ExtendedComponent />
|
||||||
|
<Link href='/'><a>{t('link.gotoPage1')}</a></Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Extended = translate(['page2', 'common'], { i18n, wait: process.browser })(Page2)
|
||||||
|
|
||||||
|
// Passing down initial translations
|
||||||
|
// use req.i18n instance on serverside to avoid overlapping requests set the language wrong
|
||||||
|
Extended.getInitialProps = async ({ req }) => {
|
||||||
|
if (req && !process.browser) return i18n.getInitialProps(req, ['page2', 'common'])
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Extended
|
48
examples/with-react-i18next/server.js
Normal file
48
examples/with-react-i18next/server.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
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({
|
||||||
|
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