Translate your Remix.run app with Lingui
Earlier this year we were on the lookout for a new i18n library for our projects at work. We were using i18next for a long time, but we were looking for something more modern and flexible. We found Lingui, a library that offers a different approach to i18n in Javascript projects. I was so excited about it that I decided to contribute to the lingui repository to add a Remix.run example.
In this article, we will show you how to use Lingui in your Remix.run project with Vite.
Why Lingui?
Or rather "Why not i18next?".
One of the reason I wanted to try something else is that I was not happy with the developer experience of i18next. I found it hard to use and not very flexible, especially for complex translations (e.g. text with variables & components in it). I am always surprised that almost all educational content out there is about i18next. I was sure there was a better experience and a new project is always a good opportunity to try something new.
I found Lingui, a library that offers a different approach to internationalization in Javascript projects. It's a modern library that is easy to use and has a great developer experience. It's also very flexible and can be used in any Javascript project.
After reading the docs, I knew this was the right tool. It's exactly how I want to deal with translations in my Javascript projects.
Key features of Lingui
Here's some of the key features of Lingui that make the difference in my opinion:
- No more keys: The hardest thing in programming is naming things. On i18next, the docs uses keys to identify translations. On Lingui's docs, you will see that they use the actual text as the key. This is a game changer for me. No need to think about key names, just write the text and you're done.
- Built-in extraction: A simple CLI command to extract messages from your code and save them in a
.po
file, for free. - Easy Rich Text: Lingui supports rich text translations out of the box. You can use HTML tags or components in your translations and it will work.
- Contextualized translations: You can add context to your translations to avoid ambiguity. This is very useful when you have multiple translations for the same text.
There are many more features, but to me these are the most important ones.
Setup Lingui in your Remix.run project
Okay let's get started and install Lingui in your project.
_10pnpm add @lingui/core @lingui/react @lingui/macro @lingui/detect-locale_10pnpm add -D @lingui/conf @lingui/cli @lingui/vite-plugin vite-plugin-babel-macros
Vite configuration
Let's start with the vite config. Add the following plugins to your vite.config.js
:
Lingui configuration
Now let's create a configuration file for Lingui. Create a lingui.config.ts
file at the root of your project:
With this in place you can setup the extraction command in your package.json
:
You can make sure everything is working by running the extract command:
_12> pnpm lingui:extract_12_12Catalog statistics:_12┌──────────┬─────────────┬─────────┐_12│ Language │ Total count │ Missing │_12├──────────┼─────────────┼─────────┤_12│ en │ 0 │ 0 │_12│ fr │ 0 │ 0 │_12└──────────┴─────────────┴─────────┘_12_12(use "lingui extract" to update catalogs with new messages)_12(use "lingui compile" to compile catalogs for production)
This command should have generated two .po
files in your app/locales
folder.
Load translations in your app
Now that we have our translations extracted, we can load them in our app.
Create a function in your project that'll load the translations catalogs.
Language detection
Out of the box, lingui offers a locale detection helper but that only works on the frontend. In Remix I want the server to be responsible for detecting the locale, just like you'd do with remix-i18next
So the easiest way I found was to copy and adapt the remix-i18n
library.
You can use your own logic for this, but if you want to use the same approach, you can copy the following files into your lingui module:
And the following lingui.server.ts
Initialize Lingui in your app
We now have all we need to start setting up lingui in Remix.
We'll set it up in our entry.server.tsx
& entry.client.tsx
files. If you don't have them, you can create them at the root of your app
folder or use the npx remix reveal
command to generate them.
Use Lingui in your components
Now that lingui is setup and your translations loaded you're ready to start using it in your components.
In a Page component
In a route loader
Translate your content
Run the extraction command once more when you have added a few translations in your components and translate them. You can use any tool or even just use your code editor to add missing translations.
When it's done, try to navigate to your app and pass a lng
query parameter to see the page in different languages.
http://localhost:3000/?lng=fr
Pass the locale to the client
As our server is responsible to detect & store the locale, we need to pass it to the client.
You can do this by returning the locale in the root.tsx
loader data.
In the root component you can then use this locale to set the html lang attribute.
Remember this is used to load the correct catalog in our entry.client.tsx
file.
Change the locale
You can change the language using a form and an action or use a Link with a query parameter.
I prefer the Link approach as it just works out of the box. In your app, you just need to add a link to change the language.
_10<Link to="?lng=fr" reloadDocument>French</Link>_10<Link to="?lng=en" reloadDocument>English</Link>
You can also use the form approach, but you'll need to handle the locale change in an action. You'll catch the language passed into the form and use it to update the locale cookie.
Conclusion
You're now ready to use Lingui everywhere in your Remix app.
I invite you to read the docs for the common gotchas you'll encounter in Remix/React.