📖 Go Deeper » We’ve covered both react-intl and i18next in detail in our article Best Libraries for React I18n. Check out that article for deeper dive into these libraries with source code.
An Intuitive Syntax
One of the most important pros of LinguiJS is its elegant and intuitive syntax. Compare a React component written with react-intl and (functionally) the same component written with Lingui.
With LinguiWhile more verbose, the second component, written using Lingui, is much more readable: we can tell exactly where the markup is and where the content is. Since the translation messages are embedded right in your views—in the source language, of course—we can understand the context of our views without digging through translations files. Lingui will render the text between the
<Trans>tags as a translated message based on the currently active locale. The library achieves achieves this with Lingui macros, which transform
<Trans>tags into the common ICU MessageFormat (the same format used by react-intl), and places them in translation files for our translators to work with. We can actually have rich text, including HTML and JSX, within our translation messages as well. Lingui will convert that text into ICU messages that remember where markup tags are, without bogging down our translators with the tag meanings. If you’ve worked on internationalized apps before, you know that rich text with tags within translation messages can be a source of headaches for developers and translators alike. Lingui macros just make these messages a breeze to work with. We’ll dive into this in more detail when we build a demo app with Lingui a bit later.
A Small Footprint
We developers know how our third-party libraries can add up to megabytes going down the pipe to our users’ browsers if we’re not careful. So the cost-benefit analysis of each library we choose has to include the library’s size. Now, consider the following size comparison, with package versions at time of writing.
react-intl (v3.2.0) 12.7 kB gzipped
i18next (v17.0.13) 10.2 kB gzipped
react-i18next (v10.12.2) 5.2 kB gzipped
15.4 kB gzipped
@linguijs/core (v2.3.3) 1.4 kB gzipped
@linguijs/react (v2.8.3) 2.5 kB gzipped
3.9 kB gzipped
It’s obvious that LinguiJS gives us massive savings in kilobytes.
🔗 Resource » All kB sizes provided above are from Bundlephobia.
Worth mentioning is that, according to the Lingui documentation, the library is compatible with both JSON and the PO translation file formats.
✋🏽 Heads Up » While I have worked with the JSON format in a LinguiJS project, I haven’t tried the PO format with the library myself. PO files are very common for localization, of course. If you do try the PO format, let us know how that worked out for you in the comments below.
@lingui/cli development package. Just install the package and start running the CLI commands to work with translation files. We’ll look at how to do that in a minute.
No Built-in Translation File Loading
Unlike i18next, LinguiJS doesn’t have a first-party HTTP translation file loader. So if you want to use Lingui and load your all translation files dynamically, you will have to write your own translation loader. Generally, this is pretty easy, and, of course it’s a non-issue if you’re just bundling your translations into your main app bundle. However, you should be aware of the lack of first-party loader here. We’ll write our own little translation file loader for our Lingui demo app shortly.
Newer, Smaller Community
react-intl and i18next have established communities, and you can usually quickly find solutions for most of the common hurdles you’ll encounter as you work with them. Lingui is a newer library with a smaller community. This may mean that at times you won’t find answers to problems you encounter with the library right away. You may need to interact with the Lingui community to get some help. From my cursory viewing, the community does look quite active and helpful. And it can actually be nice to be part of an up-and-coming library community like Lingui’s. So it’s up to you to decide if “new and small” is a pro or a con for you. It should also be mentioned that the core functions of the library and its documentation seemed robust to me as I worked with the library.
Working with Lingui: A Little Demo App
OK, let’s get our hands dirty with some code and see what working with LinguiJS is really like. We’ll build a little demo React app that shows a list of users belonging to an imaginary forum. This will allow us to test out Lingui’s translation workflow, working with messages, the CLI, plurals, and date formatting. Let’s get started.
Our app in its finished form
I’ve already built a starter project that we can use to add Lingui to. It’s been bootstrapped with Create React App, and uses the Bulma CSS framework for basic styling. You can get the starter project by cloning the Git repo for this demo app and checking out the commit with the
Installing the NPM Packages
Using that as our starting point, we can install LinguiJS through NPM.This installs the necessary packages, with latest stable versions at time of writing, to develop with Lingui.
- @lingui/cli — command-line tools used to work with translation messages
- @lingui/macro — transpilation functions that allow us to use intuitive translation messages in our JSX
- @lingui/react — components that hook Lingui into our React app (this package pulls in
@lingui/core, which you would need to install explicitly on a non-React project)
- @babel/core and babel-core@bridge — requirements for backwards-compatibility for projects using Babel 6
✋🏽 Heads Up » While installing the NPM packages you might get a warning about
babel-plugin-macrosbeing an unmet peer dependency of
@lingui/macro. I’ve found Lingui to work just fine without
babel-plugin-macros, but you can choose to install the package as a dev dependency if you want to get rid of that warning.
With these in place, let’s add a
.linguirc file to our project root to configure Lingui.
src/localesdirectory. Our source/development locale will be English. Lingui will scan everything under our
srcdirectory for translation messages to extract into translation files.
Adding Our Lingui CLI Scripts
To make our lives easier as we develop, let’s add three NPM scripts to our
compilewill be used to manage our translation messages through the command-line.
Adding Our Initial Locales
In fact, let’s use the
add-locale command to tell Lingui which locales our app will initially support.
src/localesdirectory and put empty message files in it for English and French.
Connecting Lingui to React
Now let’s wire up our React app to use Lingui. Here’s the most basic setup in
Appcomponent, and all its children, with an underlying context that allows access to the active locale’s translation messages. We generally don’t have to worry about the details under the hood here, but we do need to make sure that our root component is wrapped with the library’s
I18nProvider, like we’ve done above.
✋🏽 Heads Up » We’ve hard-coded English as the active locale here, but we’ll make that dynamic a bit later.
That’s it for installation. With LinguiJS now in place, let’s start using it to localize our app.
Our UI text is currently hard-coded into your components.Let’s start using Lingui’s React macros to make that text dynamic and translatable. We just wrap our text with Lingui’s
Transmacro. Notice that if we save and reload our app in the browser after this change, everything looks the same. Let’s add the French translation so we can see how our app will start looking to our French users. We’ll run the extraction CLI command to have Lingui scan our
srcdirectory and pull out any
Transed text. If everything went well, we’ll see a message showing that 1 message is in our translation catalog, and we’re missing 1 French translation.
✋🏽 Heads Up » If you get an error that says that no locales have been defined when running
extract, make sure you’ve added your locales using
add-localesfirst (see above).
Let’s add the translation. When we open
src/locales/fr/messages.json, we’ll see our untranslated message.
"translation"value is the one we need to fill in. (Sorry about the translation if it’s off: I’m using Google Translate). Notice that we keep our dynamic, interpolated value,
messages.jsfile in each of our
index.jsfile to do just that. If we save and reload our app, we’ll see our header appear in French.
So the basic Lingui translation workflow is:
- Wrap translated text in
extractto update translation files
- Add translations to
compileto generate performant runtime catalogs
Notice that our active locale remains hard-coded. Another issue in our current setup is that we would have to front-load all catalogs for all translations in our main app bundle. This may be fine if our translation files are small. However, we’ll add dynamic catalog loading and locale-switching, making our app more flexible and performant, when we build a language switcher in a few minutes. First, let’s take a look at how we work with plurals when using Lingui.
You may have noticed that we’re not exactly handling plurals in our current UI.
Our posts counter is always showing the many form, even when we have one
Our count strings are hard-coded. We need to set up plurals for our source language, English, as well as the other locales our app supports. Of course, different languages have different rules for pluralization. Thankfully, the ICU Message format that Lingui supports handles these cases gracefully. So let’s get to the code. We’ll first update our UI to use Lingui’s
Pluralhas a nice additional feature: it will support exact count messages that override general plural forms. We’re using that in our code to show a message for our zero count. We want to show a special message for zero, so we use the non-standard
_0prop to override standard language rules here. This saves a conditional and makes our code a bit cleaner. Note that we can use any number after the underscore, so if we wanted a special message for 42 we could use the
_42prop. Alright, with our
Pluralmacro in place let’s extract our messages again with the
extractCLI command. Once we do, we’ll find that our
/src/locales/fr/messages.jsonfile has a couple of new entries for us to translate. Our messages are in the ICU format, and to translate them we simply copy the English into our French
translations, making any changes to the plural forms we want to. After we
compile, we should see the French versions of our plurals appear in our app. (Our source English text will, of course, also show the correct plural forms dynamically when our active locale is English).
The one, the many
Let’s take a look at dates and numbers in Lingui before building a language-switcher to round out our demo. We’ll deal with the “Last seen” date that we’re showing for our users, which is currently just outputting the date string as it is in our data.
That is ugly, dawg
All we have to do is wrap our entire string in a
Trans component, and pass the date value to a
DateFormattakes a string or
Dateobject as a
DateFormatthat will get relayed to
Intl.DateFormatas its options parameter. This allows us to easily format our dates so that they show the weekday, hour, and minute our given user was last seen.
Our dates formatted in the English locale
To translate our date, we go through our familiar workflow: after
extracting our messages we open our
src/locales/fr/messages.json file and fill in the French translation.
DateFormatwill handle, in the appropriate position in our translation. After we compile and switch our language to French, we can see our underlying libraries, Lingui and Intl, doing the heavy lifting of localizing our date format for us.
And notice how we can simply nest our
DateFormat macro within our
Trans macro. This feels like working with any other React component and is incredibly intuitive.
A Simple Language Switcher & Dynamic Loading of Message Catalogs
As promised, it’s time to build a simple language switcher so that our locale is not hard-coded into our root component’s source code. When our user switches the app’s language, we’ll give Lingui’s
I18nProvider the newly selected language, as well as dynamically load in that locale’s message catalog. Before we start coding, we need to install the Lingui Webpack loader, which will load in catalogs dynamically for us.
index.jsfile to manage
catalogsbits of state that we pass back and forth to our
Appcomponent. We use Lingui’s Webpack loader to dynamically import the catalog for the current locale.
✋🏽 Heads Up » You may need to install the Babel dynamic import plugins to enable support for
import()called dynamically like we have in our
loadMessagesfunction. If you’re working along with our demo app and started with our starter app provided earlier, there’s no need to install anything: our demo is built with Create React App, which includes the dynamic import plugins. Check out the Lingui documentation for more details.
Now let’s update our
App component to work with the
onLanguageChange props, and to pass those to our new
LanguageSelectoritself, which is just a glorified
<select>control. That’s it. Now when our user selects a language, our app will respond by loading in the language’s message catalog and updating its translated messages.
Take your pick
📖 Go Deeper » This is just a basic look at dynamic catalog loading. If you want to explore this feature with regards to saving the compilation step when developing with Lingui, take a look at Lingui’s Webpack loader documentation. If you want an in-depth look at dynamic loading of catalogs in a React app, check out the Lingui guide, Dynamic loading of message catalogs. 🔗 Resource » You can get the code for the complete demo app we built in this article on Github.
- Building an Awesome Magazine App with i18n in React
And That’s a Wrap
That’s all for our little foray into the world of the up-and-coming LinguiJS library. We hope you’ve seen how intuitive and clean this library is, and how it’s built with developer happiness and simplicity in mind. Speaking of developer happiness, if you’re looking for a pro localization platform for your websites and apps, check out Phrase. Phrase is built for developers, product managers, and translators, and has a flexible API, powerful web console, and many advanced i18n features: OTA translations, branching, machine translations, and more. Take a look at all of Phrase’s features, and sign up for a free 14-day trial.