@nimpl/i18n
(Former next-translation )
i18n library designed primarily with server components in mind and maximum optimization (due to the transfer of logic to the assembly stage and/or server side)
Installation
npm i @nimpl/i18n
Why one more library?
Server components are a recent feature in React. Existing translation libraries are not yet well-optimized for them. If they support it, then only by disabling Next.js static optimization.
This library is an attempt to create a highly optimized solution exclusively using the current capabilities of React.js, Next.js and node.js.
Usage
getTranslation
Server getter for adding translations to React Server Components and outside React DOM.
import getTranslation from '@nimpl/i18n/getTranslation';
const ServerComponent: React.FC = () => {
const { t } = getTranslation();
return (
<div>
{/* Welcome to Next Translation */}
{t('header.nav.home')}
</div>
)
}
You can also specify the desired language and translations will be added specifically for it:
const { t } = getTranslation({ language: 'fr' });
To add translations outside the React DOM, you can also use getTranslation
, but always passing the desired language.
import createTranslation from '@nimpl/i18n/createTranslation'
// ...
export async function generateMetadata({ params }: { params: { lang: string } }) {
const { t } = await getTranslation({ language: params.lang });
return {
title: t('homePage.meta.title'),
}
}
You can pass opts
as the second argument of t
.
Now you can pass a query there to inject variable strings inside translations. In the translation, the variable should be isolated by two curly braces ({{example}} page title
)
const Component: React.FC = () => {
return (
<div>
{/* Price starts from {{price}} */}
{t('pricing.tariffs.solo', { query: { price: '16.90' } })}
</div>
)
}
useTranslation
Hook for adding translations to React Client Components.
"use client";
import useTranslation from '@nimpl/i18n/useTranslation';
const ClientComponent: React.FC = () => {
const { t } = useTranslation();
return (
<div>
{/* Welcome to Next Translation */}
{t('header.nav.home')}
</div>
)
}
You can pass opts
as the second argument of t
.
Now you can pass a query there to inject variable strings inside translations.
const Component: React.FC = () => {
return (
<div>
{/* Price starts from ${{price}} */}
{t('pricing.tariffs.solo', { query: { price: '16.90' } })}
</div>
)
}
I18nTransmitter
For client-side translations to work, translations are passed in parent server components.
The I18nTransmitter
is used for this. It takes an array of keys to namespaces or specific keys.
import I18nTransmitter from '@nimpl/i18n/I18nTransmitter';
import ClientComponent from './ClientComponent';
const ServerComponent: React.FC = () => (
<I18nTransmitter terms={['header.nav']}>
<ClientComponent />
</I18nTransmitter>
)
I18nTransmitter
separately.Also you can inject query to client terms on the server side. For example, when they depend on the server environment or when you get values from a database on the server.
Just add an array value instead of a string in terms arr, where the second element will be the query object.
import I18nTransmitter from '@nimpl/i18n/I18nTransmitter';
import ClientComponent from './ClientComponent';
const ServerComponent: React.FC = () => (
<I18nTransmitter terms={[['home.welcome', { stage: process.env.GITHUB_REF === 'main' ? 'production' : 'test' }]]}>
<ClientComponent />
</I18nTransmitter>
)
You can also specify the desired language and translations will be added specifically for it:
<I18nTransmitter terms={[['home.welcome']]} language="fr">
<ClientComponent />
</I18nTransmitter>
ServerTranslation
A component for adding complex translations in React Server Components. Unlike getter, it supports adding components inside translations.
import ServerTranslation from '@nimpl/i18n/ServerTranslation';
const ServerComponent: React.FC = () => (
<ServerTranslation
term='intro.description' // We have {{number}} tariffs. Read more about pricings <link>special section</link>
components={{
link: <a href='#' />
}}
query={{ number: 5 }}
/>
)
You can also specify the desired language and translations will be added specifically for it:
<ServerTranslation
term='intro.description'
language="fr"
/>
ClientTranslation
A component for adding complex translations in React Client Components. Unlike hooks, it supports adding components inside translations.
"use client";
import ClientTranslation from '@nimpl/i18n/ClientTranslation';
const ClientComponent: React.FC = () => (
<ClientTranslation
term='intro.description' // We have {{number}} tariffs. Read more about pricings <link>special section</link>
components={{
link: <a href='#' />
}}
query={{ number: 5 }}
/>
)
Configuration
Configuration file
Create nimpl-i18n.js
file in the app root directory
const fs = require("fs/promises");
/** @type {import('@nimpl/i18n/configuration/types').Config} */
module.exports = {
load: async (language) => {
const data = await fs.readFile(`./translates/${language}.json`);
const dictionary = JSON.parse(data);
return dictionary;
},
getLanguage: async ({ params }) => params.language,
languages: ['en', 'de', 'fr'],
};
Options
load [required]
An asynchronous function that describes the logic of loading translations. Should return an object containing translations.
Function will receive 1 argument - language
- current language;
languages [required]
Array of allowed languages. Languages outside of this array will be ignored.
getLanguage [required]
Функция для определения текущего языка. Принимает аргумент в виде объекта с ключами:
pathname
- current route pathname;
params
- current route segment params;
Configure app
If you can transfer the package configuration file to the server in such a way that it remains readable - the instructions above will be enough.
However, if there is no way to transfer the file or the hosting has internal blocks (for example, Vercel considers this file unused and does not read it) - you need to wrap next.config.js
in withI18n
const { default: i18n } = require('@nimpl/i18n/i18n');
const i18nWrapper = i18n();
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = i18nWrapper(nextConfig);
App Organization
Pages structure
app
[lang]
page-name
page.tsx
page.tsx
layout.tsx
(root)
page-name
page.tsx
page.tsx
layout.tsx
Why so?
You can only create one root layout. In the root (/app
) we don't know the language, so we can't add a lang
attribute there on the server side.
Therefore, the only way to do it is to create a root layout for localized pages in the [lang]
folder.
type RootLayoutProps = { children: React.ReactNode; params: { lang: string } }
export default function RootLayout({ children, params }: RootLayoutProps) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
)
}
Additional
Please consider giving a star if you like it, it shows that the package is useful and helps continue work on this and other packages.
Create issues with wishes, ideas, difficulties, etc. All of them will definitely be considered and thought over.
Development
Read about working on the package and making changes on the contribution page