Skip to content

Commit

Permalink
refactor: Use NextJs i18n config API (#955)
Browse files Browse the repository at this point in the history
  • Loading branch information
isaachinman authored Feb 21, 2021
1 parent ce3ca48 commit 2ad6af6
Show file tree
Hide file tree
Showing 10 changed files with 93 additions and 42 deletions.
57 changes: 49 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ If you are using next-i18next in production, please consider [sponsoring the pac

## What is this?

While NextJs [provides internationalised routing directly](https://nextjs.org/docs/advanced-features/i18n-routing), it does not handle any management of translation content, or the actual translation process itself. All NextJs does is keep your locales and URLs in sync.

Thus, `next-i18next` provides the remaining functionality – management of translation content, and components/hooks to translate your React components.

`next-i18next` is a plugin for [Next.js](https://nextjs.org/) projects that allows you to get translations up and running quickly and easily, while fully supporting SSR, multiple [namespaces](https://www.i18next.com/principles/namespaces) with codesplitting, etc.

While `next-i18next` uses [i18next](https://www.i18next.com/) and [react-i18next](https://github.com/i18next/react-i18next) under the hood, users of `next-i18next` simply need to include their translation content as JSON files and don't have to worry about much else.
Expand Down Expand Up @@ -44,15 +48,34 @@ If you want to structure your translations/namespaces in a custom way, you will

### 3. Project setup

First, you'll need to configure your internationalised routing [via NextJs directly](https://nextjs.org/docs/advanced-features/i18n-routing). An example `next.config.js` might look like this:
First, create a `next-i18next.config.js` file in the root of your project. The syntax for the nested `i18n` object [comes from NextJs directly](https://nextjs.org/docs/advanced-features/i18n-routing).

#### `next-i18next.config.js`

```js
const i18n = {
defaultLocale: 'en',
locales: ['en', 'de'],
}

module.exports = {
...i18n
}
```

#### `next.config.js`

This tells `next-i18next` what your `defaultLocale` and other locales are, so that it can preload translations on the server, etc.

Second, simply pass the `i18n` object into your `next.config.js` file, to enable localised URL routing:

```js
const { i18n } = require('./next-i18next.config')

module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
i18n,
}

```

There are three functions that `next-i18next` exports, which you will need to use to translate your project:
Expand All @@ -69,6 +92,8 @@ const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />
export default appWithTranslation(MyApp)
```

The `appWithTranslation` HOC is primarily responsible for adding a `I18nextProvider`.

#### serverSideTranslations

This is an async function that you need to include on your page-level components, via either [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation) or [`getServerSideProps`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering) (depending on your use case):
Expand All @@ -83,11 +108,13 @@ export const getStaticProps = async ({ locale }) => ({
})
```

Note that `serverSideTranslations` must be imported from `next-i18next/serverSideTranslations` – this is a separate module that contains NodeJs-specific code.
Note that `serverSideTranslations` must be imported from `next-i18next/serverSideTranslations` – this is a separate module that contains NodeJs-specific code. Also, note that `serverSideTranslations` is not compatible with `getInitialProps`, as it _only_ can execute in a server environment, whereas `getInitialProps` is called on the client side when navigating between pages.

The `serverSideTranslations` HOC is primarily responsible for passing translations and configuration options into pages, as props.

### useTranslation

This is the hook which you'll actually use to do the translation itself. The `useTranslation` hook [comes from `react-i18next`](https://react.i18next.com/latest/usetranslation-hook):
This is the hook which you'll actually use to do the translation itself. The `useTranslation` hook [comes from `react-i18next`](https://react.i18next.com/latest/usetranslation-hook), but can be imported from `next-i18next` directly:

```tsx
import { useTranslation } from 'next-i18next'
Expand Down Expand Up @@ -116,7 +143,7 @@ Note: `useTranslation` provides namespaces to the component that you use it in.

### 5. Advanced configuration

If you need to modify more advanced configuration options, you can add a `next-i18next-config.js` file to the root of your project. That file should have a default export. For example:
If you need to modify more advanced configuration options, you can add a `next-i18next.config.js` file to the root of your project. That file should have a default export. For example:

```js
const path = require('path')
Expand All @@ -126,6 +153,20 @@ module.exports = {
}
```

#### Options

| Key | Default value |
| ------------- | ------------- |
| `defaultNS` | `'common'` |
| `defaultLanguage` | `'en'` |
| `locales` (required) | `['en']` |
| `localeExtension` | `'json'` |
| `localePath` (required) | `'/public/static/locales'` |
| `localeStructure` | `'{{lng}}/{{ns}}'` |
| `strictMode` | `true` |
| `use` (for plugins) | `[]` |
| `customDetectors` | `[]` |

All other [i18next options](https://www.i18next.com/overview/configuration-options) can be passed in as well.

## Contributors
Expand Down
6 changes: 6 additions & 0 deletions examples/simple/next-i18next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
}
}
7 changes: 3 additions & 4 deletions examples/simple/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const { i18n } = require('./next-i18next.config')

module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'de'],
},
i18n,
}
21 changes: 13 additions & 8 deletions src/config/createConfig.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { defaultConfig } from './defaultConfig'
import { Config } from '../../types'
import { InternalConfig, UserConfig } from '../../types'

const deepMergeObjects = ['backend', 'detection']

export const createConfig = (userConfig: Config): Config => {
export const createConfig = (userConfig: UserConfig): InternalConfig => {
/*
Initial merge of default and user-provided config
*/
const { i18n: userI18n, ...userConfigStripped } = userConfig
const { i18n: defaultI18n, ...defaultConfigStripped } = defaultConfig
const combinedConfig = {
...defaultConfig,
...userConfig,
} as Config
...defaultConfigStripped,
...userConfigStripped,
...defaultI18n,
...userI18n,
}

const {
lng,
locales,
localeExtension,
localePath,
localeStructure,
Expand All @@ -24,11 +29,11 @@ export const createConfig = (userConfig: Config): Config => {
* https://github.com/isaachinman/next-i18next/pull/851#discussion_r503113620
*/
if (lng === 'cimode') {
return combinedConfig as Config
return combinedConfig as InternalConfig
}

if (!process.browser) {
combinedConfig.preload = [lng]
combinedConfig.preload = locales

const hasCustomBackend = userConfig.use && userConfig.use.find((b) => b.type === 'backend')

Expand Down Expand Up @@ -100,5 +105,5 @@ export const createConfig = (userConfig: Config): Config => {
}
})

return combinedConfig
return combinedConfig as InternalConfig
}
7 changes: 4 additions & 3 deletions src/config/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ const LOCALE_STRUCTURE = '{{lng}}/{{ns}}'
const LOCALE_EXTENSION = 'json'

export const defaultConfig = {
defaultLocale: DEFAULT_LOCALE,
locales: LOCALES,
i18n: {
defaultLocale: DEFAULT_LOCALE,
locales: LOCALES,
},

load: 'currentOnly',
localePath: LOCALE_PATH,
Expand All @@ -26,7 +28,6 @@ export const defaultConfig = {
},
strictMode: true,
errorStackTraceLimit: 0,
shallowRender: false,
get initImmediate(): boolean {
return process.browser
}
Expand Down
4 changes: 2 additions & 2 deletions src/createClient/browser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import i18n from 'i18next'
import i18nextHTTPBackend from 'i18next-http-backend/cjs'

import { Config, CreateClientReturn, InitPromise } from '../../types'
import { InternalConfig, CreateClientReturn, InitPromise } from '../../types'

export default (config: Config): CreateClientReturn => {
export default (config: InternalConfig): CreateClientReturn => {
const instance = i18n.createInstance(config)

let initPromise: InitPromise
Expand Down
4 changes: 2 additions & 2 deletions src/createClient/node.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import i18n from 'i18next'
import i18nextFSBackend from 'i18next-fs-backend/cjs'

import { Config, CreateClientReturn, InitPromise } from '../../types'
import { InternalConfig, CreateClientReturn, InitPromise } from '../../types'

export default (config: Config): CreateClientReturn => {
export default (config: InternalConfig): CreateClientReturn => {
const instance = i18n.createInstance(config)
let initPromise: InitPromise

Expand Down
5 changes: 3 additions & 2 deletions src/serverSideTranslations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import path from 'path'

import { createConfig } from './config/createConfig'
import createClient from './createClient'
import { Config, SSRConfig } from '../types'
import { UserConfig, SSRConfig } from '../types'

const DEFAULT_CONFIG_PATH = './next-i18next.config.js'

export const serverSideTranslations = async (
initialLocale: string,
namespacesRequired: string[] = [],
configOverride: Config = null,
configOverride: UserConfig = null,
): Promise<SSRConfig> => {
let userConfig = configOverride

Expand All @@ -23,6 +23,7 @@ export const serverSideTranslations = async (
...userConfig,
lng: initialLocale,
})

const { i18n, initPromise } = createClient({
...config,
lng: initialLocale,
Expand Down
4 changes: 2 additions & 2 deletions src/utils/consoleMessage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable no-console */

import { Config } from "../../types"
import { InternalConfig } from "../../types"

type MessageType = 'error' | 'info' | 'warn'

Expand All @@ -20,7 +20,7 @@ const logMessage = (messageType: MessageType, message: string) => {
}
}

export const consoleMessage = (messageType: MessageType, message: string, config: Config): void => {
export const consoleMessage = (messageType: MessageType, message: string, config: InternalConfig): void => {

const { errorStackTraceLimit, strictMode } = config

Expand Down
20 changes: 9 additions & 11 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,28 @@ import {
import { InitOptions, i18n, TFunction as I18NextTFunction } from 'i18next'
import { appWithTranslation } from './src';

export type InitConfig = {
strictMode?: boolean;
type NextJsI18NConfig = {
defaultLocale: string;
locales: string[];
}

export type UserConfig = {
i18n: NextJsI18NConfig;
localeExtension?: string;
localePath?: string;
localeStructure?: string;
locales: string[];
strictMode?: boolean;
use?: any[];
shallowRender?: boolean;
} & InitOptions

export type Config = {
export type InternalConfig = Omit<UserConfig, 'i18n'> & NextJsI18NConfig & {
errorStackTraceLimit: number
fallbackLng: boolean;
supportedLngs: string[];
// temporal backwards compatibility WHITELIST REMOVAL
whitelist: string[];
// end temporal backwards compatibility WHITELIST REMOVAL
preload: string[];
} & InitConfig

export type NextI18NextInternals = {
config: Config;
i18n: I18n;
}

export type UseTranslation = typeof useTranslation
Expand All @@ -53,7 +51,7 @@ export type SSRConfig = {
_nextI18Next: {
initialI18nStore: any;
initialLocale: string;
userConfig: Config;
userConfig: UserConfig;
};
}

Expand Down

0 comments on commit 2ad6af6

Please sign in to comment.