DEV Community

Bartłomiej Stefański
Bartłomiej Stefański

Posted on • Originally published at bstefanski.com on

Simple, pragmatic and performant i18n solution for JavaScript applications

There're great libraries for i18n that support absolutely everything like interpolation with React components, server-side rendering, and code splitting (or maybe I should call it - JSON splitting). This is one example of such library - react-i18next.

The biggest drawback and issue, something that keeps me from using it is its bundle size. Assuming you're using gzip for compression like everyone else, it will take 20kB+ from your bundle. For some people/teams it's an acceptable amount, but for me, I don't believe it to be a good trade-off.

i18next package size summaryi18next package size summary

react-i18next package size summaryreact-i18next package size summary

And that's why I decided to write my implementation that has great DX (supports dotted paths with autocompletion) and is easy to scale/maintain. I used the js-cookie library to get and parse the cookie with legible & declarative API.

The example is done specifically for Next.js, but you can seamlessly port it to any other library/framework.


// i18n.ts 

import Cookies from "js-cookie";

import get from "lodash/get";

import { en } from "./en";

type Locales = "en";

const defaultTranslations: Record<Locales, Partial<typeof en>> = {

 en,

};

export const t = (key: Join<PathsToStringProps<typeof en>, ".">, translations = defaultTranslations) => {

const locale = Cookies.get("NEXT\_LOCALE") as Locales;

return get(translations[locale] || translations["en"], key);

};

type PathsToStringProps<T> = T extends string

? []

: {

[K in Extract<keyof T, string>]: [K, ...PathsToStringProps<T[K]>];

}[Extract<keyof T, string>];

type Join<T extends string[], D extends string> = T extends []

? never

: T extends [infer F]

? F

: T extends [infer F, ...infer R]

? F extends string

? `${F}${D}${Join<Extract<R, string[]>, D>}`

: never

: string;

Enter fullscreen mode Exit fullscreen mode

This is how the translations file looks like


// en.ts 

export const en = {

 ctaSection: {

 title: "Some value for demo purposes",

// ...The rest of the items, removed for brevity 

}

}

Enter fullscreen mode Exit fullscreen mode

And this is how you use it:

An example of how you could use the "t" functionAn example of how you could use the "t" function

You don't have to worry about the performance unless the file exceeds a few hundred lines. After that, you can use dynamic imports and split the translations file into smaller chunks.

Top comments (0)