🌐 React i18n TS Support

Introduction

The built-in type definitions of i18next are at your disposal. To get full intellisense and catch type errors before they happen, we can use as const to achieve full type safety for the t function.

Without this setup, the t function accepts any string as a key. That means a typo like t('welcom.title') instead of t('welcome.title') silently returns the key itself at runtime with no compile-time warning. With type safety in place, TypeScript flags unknown keys immediately in your editor.

Rename or delete a key in the JSON, and TypeScript catches every broken reference at build time.
Warning

Depending on the size of your translation sets and the machine you're rocking, the benefits of type safety and intellisense might slow down your development experience, something I noticed first hand with 4k+ translations when testing.

I do have some ideas for only indexing translation files by the current file system directory or page route but haven't figured out an elegant working solution. More to come on this if I figure out an elegant solution.

Ensure that the "strict" flag or "strictNullChecks" is set to true in your tsconfig compilerOptions.

If your project involves multiple i18next instances with different translation resources, you likely won't be able to use type-safe translations. This guide is for single language TS intellisense.

Translations setup

Install dependencies: npm install i18next react-i18next

Create your translations file en-us.jsonwith the default namespace key as "translation":

1{
2 "translation": {
3 "article": {
4 "title": "i18n TypeScript support",
5 "subheader": "The power of 'as const'"
6 }
7 }
8}

Create your file i18n.ts, this is a lightweight config example, but there are lots of additional options to support things like automatic language detection, please see i18n docs.

1import i18n from 'i18next';
2import { initReactI18next } from 'react-i18next';
3import * as en from './translations/en-us.json';
4
5export const defaultNS = 'translation';
6export const resources = {
7 en,
8} as const;
9
10i18n.use(initReactI18next).init({
11 resources,
12 defaultNS,
13 ns: [defaultNS],
14 fallbackLng: 'en',
15 debug: false,
16 returnNull: false,
17});
18
19const t = i18n.t.bind(i18n);
20export { t };
21
22export default i18n;

Declaration file

Expand TypeScript definitions for i18next through the use of Type Augmentation and Merging Interfaces. Begin by crafting a declaration file global.d.ts.

1import { resources, defaultNS } from './i18n';
2
3declare module 'i18next' {
4 interface CustomTypeOptions {
5 defaultNS: typeof defaultNS;
6 resources: (typeof resources)['en'];
7 returnNull: false;
8 }
9}

Translations hook

Now we're all set up, we can import the useTranslation hook inside our React components. TypeScript will now autocomplete translation keys and flag any that don't exist in your JSON file.

1import { FC } from 'react';
2import { useTranslation } from 'react-i18next';
3
4const Example: FC = () => {
5 const { t } = useTranslation();
6
7 return (
8 <p>{t('article.title')}</p>
9 );
10};
11
12export default Example;

Or outside a component:

1// from our i18n.ts setup file, not node_modules
2import { t } from './i18n';
3
4const dynamicConfig = {
5 id: 'title',
6 text: t('article.title')
7}

One thing worth knowing: if you rename or delete a key in your JSON file, TypeScript will immediately show an error on every call site that references that key. That's the payoff for this setup. Refactoring translations becomes safe because the compiler tells you exactly what broke.

Was this useful?

Thanks for reading! Time for one more?

const { userId } = useLocalSearchParams()
useEffect(() => {
getUserDetails(userId).then(setUser)
}, [userId])

đŸĻ¸ Expo & Expo Router

21 Jan 2024 â€ĸ 📖 30 min read â€ĸ Updated 16 Mar 2026
☕ Coffee needed

Expo, EAS & Expo Router: one codebase covering web, Android, and iOS, with managed workflows, OTA updates, and file-based routing.

0
0
0
TYPESCRIPT REACT NATIVE
// arrow: 'this' is always the class
logName = () => console.log(this.name)
// function: 'this' depends on caller
logName() { console.log(this.name) }

🏹 Regular Function vs Arrow Function

17 Aug 2023 â€ĸ 📖 9 min read â€ĸ Updated 15 Mar 2026
☕ Coffee needed

Learn the differences between normal functions and ES6 arrow functions.

0
0
0
JAVASCRIPT