i18n: The Type-Safe Approach Type: blog post Language: en-US Canonical URL: https://simonboisset.com/en/blog/i18n-type-safe-approach Published: July 19, 2024 Summary: Internationalization (i18n) is a crucial aspect of modern web development. This article explores how to implement a type-safe i18n solution using the typed-l... Internationalization (i18n) is a crucial aspect of modern web development. This article explores how to implement a type-safe i18n solution using the typed-locale library in a React application. Introduction to typed-locale typed-locale is a lightweight, type-safe internationalization library designed to work with TypeScript. It provides an API for managing translations with type safety for both keys and variables. Setting Up the Project Let's create a new React project using Vite with TypeScript: npm create vite@latest my-i18n-app -- --template react-ts cd my-i18n-app npm install Now, install typed-locale: npm install typed-locale Defining Translations Create a new file called translations.ts in the src folder: // src/translations.ts import { InferTranslation, plural } from "typed-locale"; export const en = { greeting: "Hello, {{name}}!", itemCount: plural({ none: "You have no items.", one: "You have one item.", other: "You have {{count}} items.", }), nav: { home: "Home", about: "About", contact: "Contact", }, } as const; export type Translation = InferTranslation ; export const fr: Translation = { greeting: "Bonjour, {{name}} !", itemCount: plural({ none: "Vous n'avez aucun article.", one: "Vous avez un article.", other: "Vous avez {{count}} articles.", }), nav: { home: "Accueil", about: "À propos", contact: "Contact", }, }; Creating the Translator Now, let's create a custom hook to use our translations. Create a new file called useTranslator.ts: // src/useTranslator.ts import { createTranslatorFromDictionary } from "typed-locale"; import { useMemo } from "react"; import { en, fr, Translation } from "./translations"; const dictionary = { en, fr }; export const useTranslator = (locale: keyof typeof dictionary) => { return useMemo( () => createTranslatorFromDictionary ({ dictionary, locale, defaultLocale: "en", }), [locale] ); }; Using the Translator in Components Now, let's use our translator in a React component. Update your App.tsx: // src/App.tsx import React, { useState } from "react"; import { useTranslator } from "./useTranslator"; const App: React.FC = () => { const [locale, setLocale] = useState ("en"); const [itemCount, setItemCount] = useState(0); const translator = useTranslator(locale); return ( setLocale(e.target.value as "en" | "fr")} > English Français {translator((t) => t.nav.home)} {translator((t) => t.nav.about)} {translator((t) => t.nav.contact)} {translator((t) => t.greeting, { name: "World" })} {translator((t) => t.itemCount, { count: itemCount })} setItemCount(itemCount + 1)}>Add Item setItemCount(Math.max(0, itemCount - 1))}> Remove Item ); }; export default App; Type Safety Features typed-locale provides several type safety features: Autocomplete for translation keys: The IDE provides autocomplete suggestions for all available translation keys. Type checking for variables: TypeScript catches incorrect variable usage: // This will cause a TypeScript error translator((t) => t.greeting, { wrongVariable: "World" }); Nested translations: The type system understands nested translations, allowing translator(t => t.nav.home). Pluralization: The itemCount translation demonstrates how typed-locale handles pluralization, automatically selecting the correct plural form based on the count value. Technical Advantages The type-safe approach using typed-locale offers several technical advantages: Compile-time error detection: Translation-related errors are caught during compilation rather than at runtime. Improved developer experience: Autocomplete for translation keys enhances productivity. Refactoring support: TypeScript's refactoring tools work seamlessly with the translation keys. Prevents key typos: The callback approach eliminates string typos in translation keys. Small bundle size: At 1kB, typed-locale has minimal impact on application size. Framework agnostic: While this example uses React, typed-locale can be used with any JavaScript framework or vanilla JS. Conclusion Implementing i18n with a type-safe approach using typed-locale provides a robust solution for managing translations in TypeScript projects. By leveraging the type system, developers can create more reliable internationalized applications while maintaining code quality and productivity. Related links Blog index: https://simonboisset.com/en/blog Website: https://simonboisset.com/en