Internationalization
We make use of react-i18next, a powerful internationalization framework for React / React Native which is based on i18next. In certain projects we also use:
In order to understand how this repository implements i18n, it is important to understand how these libraries work. The remainder of this document assumes a working knowledge of these libraries.
Adding i18n to your project
- add i18next-parser.config.js to project root.
const i18nextParserConfigBase = require('../../i18next-parser.config.base') // this file is located in the workspace root
module.exports = {
...i18nextParserConfigBase,
input: ['src/**/*.{js,jsx,ts,tsx}']
}
- add extract-translations command to targets object and implicitDependencies to base object in project.json. You must replace PROJECT_PATH with your project path e.g.
apps/journeys
.
{
...
"implicitDependencies": ["locales"],
"targets": {
"extract-translations": {
"executor": "@nx/workspace:run-commands",
"options": {
"commands": [
{
"command": "npx i18next --config PROJECT_PATH/i18next-parser.config.js"
}
]
}
}
...
}
}
- update your components to make use of useTranslation hooks, appWithTranslation HOCs, Trans components etc in order to indicate to the parser that there is a string available for translation.
It is very important that you include a namespace. This namespace should be a dashed version of your project path. For example apps/journeys
would become apps-journeys
or libs/journeys/ui
would become libs-journeys-ui
.
import { useTranslation } from 'next-i18next'
function Component() {
const { t } = useTranslation('libs-journeys-ui') // must include namespace
return <div>{t('hello world')}</div>
}
- run nx command
extract-translations
for the project you are working on. This should pull all of the strings requiring translations into a namespaced file for examplelocales/en/libs-journeys-ui.json
ready for translation. This needs to be committed and kept up to date. This is tested in CI so will fail if not correctly updated.
Next.js configuration
- update input in i18next-parser.config.js located in the project root
const i18nextParserConfigBase = require('../../i18next-parser.config.base')
module.exports = {
...i18nextParserConfigBase,
input: ['src/**/*.{js,jsx,ts,tsx}', 'pages/**/*.{js,jsx,ts,tsx}']
}
- add next-i18next.config.js to the project root
const path = require('path')
/**
* @type {import('next-i18next').UserConfig}
**/
const i18nConfig = {
i18n: {
defaultLocale: 'en',
locales: ['en'],
localePath: path.resolve('./libs/locales')
}
}
module.exports = i18nConfig
- update next.config.js
const { i18n } = require('./next-i18next.config')
const nextConfig = {
i18n,
...
}
- update pages/_app.tsx to use appWithTranslation HOC
import { appWithTranslation } from 'next-i18next'
import i18nConfig from '../next-i18next.config'
// your _app component
function CustomApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default appWithTranslation(JourneysApp, i18nConfig)
- add async function on your page-level components, via either getStaticProps or getServerSideProps (depending on your use case)
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import i18nConfig from '../next-i18next.config' // project root
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(
context.locale ?? 'en',
['apps-watch'], // namespaces your components make use of
i18nConfig
))
},
};
}
- (Optional) Add a middleware to detect user's preferred language by creating a
middleware.ts
file in the project root. This middleware will:
- Detect and manage user language preferences using browser settings and cookies
- Handle redirects for language-specific routes
- Use cookie fingerprinting to version language preferences
Basic implementation example:
import { NextRequest, NextResponse } from 'next/server'
const DEFAULT_LOCALE = 'en'
const SUPPORTED_LOCALES = ['en', 'es', 'fr'] // Add your supported locales
// Fingerprint is used to version cookies and may be referenced by other components
// (e.g., LanguageSwitcher)
const COOKIE_FINGERPRINT = '00001' // Increment when updating cookie logic
export function middleware(req: NextRequest): NextResponse | undefined {
// Skip internal paths and static files
if (req.nextUrl.pathname.startsWith('/_next') || req.nextUrl.pathname.includes('/api/') || /\.(.*)$/.test(req.nextUrl.pathname)) {
return
}
// Get stored language preference with fingerprint
const localeCookie = req.cookies.get('NEXT_LOCALE')?.value
const [fingerprint, storedLocale] = localeCookie?.split('---') ?? []
// If no valid fingerprinted cookie exists, get browser preference
const preferredLanguage = (fingerprint === COOKIE_FINGERPRINT ? storedLocale : null) ?? req.headers.get('accept-language')?.split(',')[0] ?? DEFAULT_LOCALE
// Redirect to appropriate language path if needed
if (preferredLanguage !== req.nextUrl.locale) {
const response = NextResponse.redirect(new URL(`/${preferredLanguage}${req.nextUrl.pathname}${req.nextUrl.search}`, req.url))
response.cookies.set('NEXT_LOCALE', `${COOKIE_FINGERPRINT}---${preferredLanguage}`)
return response
}
return NextResponse.next()
}
Important Notes:
- Update
SUPPORTED_LOCALES
with your project's supported languages- Increment the
COOKIE_FINGERPRINT
when updating cookie logic to invalidate old preferences
- Note that this fingerprint may be referenced in other components like
LanguageSwitcher
- For more information, read about Cookie Versioning
- Adjust the locale detection logic based on your needs
- Consider adding language fallbacks for regional variants (e.g., 'en-US' → 'en')