Introduction
In this blog post, we will explore the process of integrating a basic translation functionality into your React application. However, it's important to clarify that this approach falls short of the capabilities offered by third-party plugins like react-i18next. If your requirements are confined to the following items, then the insights shared here can be quite valuable.
- Basic translation support
- Willing to craft your custom translation solution
- Simply interested to know how these translation libraries work under the hood
When I examine the source code of well-known third-party libraries, I often find them intricate and convoluted. Understanding them proves challenging, almost as if they're composed in a different programming language altogether. The complexity far surpasses what I would have done to address a similar issue. so, here is my simple take on solving it.
Key features of this library
Dynamic values
- To support dynamic values inside the translated content, provide the dynamic values object as the second argument to the
t
helper as shown below.
Examples:
{
"greeting": "Hello, ${name}"
}
import { useTranslation } from './translations'
export const App = () => {
const { t } = useTranslation()
return <h1>{t('greeting', { name: 'Vinodh' })}</h1>
}
Default translation fallback
- If the translation key is missing in the selected locale JSON then the value from the default locale is returned as a fallback.
Examples:
en.json
{
"greeting": "Hello, ${name}",
"learnMore": "Click on the Vite and React logos to learn more"
}
fr.json
{
"greeting": "Bonjour, ${name}"
}
Component
import { useTranslation } from './translations'
export const App = () => {
const { t } = useTranslation()
return (
<>
<h1>{t('greeting', { name: 'Vinodh' })}</h1>
<p>{t('learnMore')}</p>
</>
)
}
Result
- The compiled HTML for the French translation will be something like this.
<h1>Bonjour, Vinodh</h1>
<p>Click on the Vite and React logos to learn more</p>
As the 'learnMore' key is not present in the fr.json
, the corresponding default locale en.json
value is returned.
Pluralization
- Pluralization is supported using a special property called
count
. - To make it work, you need to provide both zero, one, and many values to your JSON files.
Examples:
{
"items": {
"zero": "Your cart is empty",
"one": "You have one item in your cart",
"many": "You have {count} items in your cart"
}
}
import { useState } from 'react'
import { useTranslation } from './translations'
export const Cart = () => {
const { t } = useTranslation()
const [itemsCount, setItemsCount] = useState(0)
return (
<>
<h1>{t('items', { count: itemsCount })}</h1>
<button onClick={() => setItemsCount((count) => count + 1)}>Add item</button>
</>
)
}
HTML support:
- Supports HTML sanitization by default.
- To support HTML tags, add them in the translation params alone and explicitly set the "escapeValue" translation param to true.
- HTML tags inside the translations JSON are not supported as it will make the JSON vulnerable to security attacks.
Examples:
{
"greeting": "Hello, ${name}. Your role is ${role}"
}
- Using the
t
helper
import { useTranslation } from './translations'
export const App = () => {
const { t } = useTranslation()
const role = 'Admin'
return (
<>
{t(
'greeting',
{
name: '<span className="font-medium">Vinodh</span>',
role: `<span className="text-gray">${role}</span>`,
},
{
escapeValue: false,
}
)}
</>
)
}
- To simplify this usage, the library has a custom component that escapes the param values by default internally.
<RenderTranslationWithHtml
tKey={'greeting'}
params={{
name: '<span className="ca-font-medium">Vinodh</span>',
role: `<span className="ca-font-medium">${role}</span>`
}}
/>,
Now that we know what we are going to build, let's start with our app setup.
App setup
Let's quickly create a react app using Vite bundler.
pnpm create vite i18n-app --template react-ts
cd i18n-app
pnpm install && pnpm update
pnpm run dev
Your app should be available at http://localhost:5173/
.
PS: If you want to create a react app with complete bells and whistles then check out this Create a react app using Vite](https://dev.to/vinomanick/modern-way-to-create-a-react-app-using-vite-part-1-32ol) blog.
Translation files
- Create a
locales
folder that is going to have all our translation JSONs and create the translation JSONs for 'English' and 'French' language.
mkdir locales
touch locales/en.json
touch locales/fr.json
- Update the
en.json
with the following content.
{
"app": {
"heading": "Translation supported app"
}
}
- Update the
fr.json
with the following content.
{
"app": {
"heading": "Application prise en charge par la traduction"
}
}
- Create an
index.ts
file that is going to export a translation map that contains the supported locales and its corresponding dynamic import loader.
touch locales/index.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
export const TRANSLATIONS_MAPPING: Record<string, () => Promise<any>> = {
en: () => import('./en.json'),
fr: () => import('./fr.json'),
}
Translations Library
- Create a
translations
folder inside the src to keep all our translation-related code.
mkdir src/translations
- Create an interface file to store all our common interfaces.
mkdir src/translations/translations.interfaces.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface TranslationsObject {
[key: string]: any;
}
export interface TranslationsMap {
[x: string]: () => Promise<TranslationsObject>;
}
export interface TranslateParams {
count?: number;
[key: string]: any;
}
export interface TranslateOptions {
escapeValue?: boolean;
}
Setting up the context
We are going to need a React context to store our translations object which can be used in all the child components.
touch src/translations/translations-context.tsx
import { createContext } from 'react'
import { TranslationsObject } from './translations.interfaces'
interface TContext {
locale: string;
isLoading: boolean;
setLocale: (language: string) => void;
translations: TranslationsObject;
defaultLocale: string;
}
export const TranslationsContext =
createContext <
TContext >
{
locale: '',
isLoading: true,
setLocale: () => null,
translations: {},
defaultLocale: '',
}
Our basic setup is done for the translations library, let's start building our Translation provider and "useTranslation" hook in the next part.
Top comments (0)