Comparison with react-intl¶

react-intl is definitely the most popular and widely-used i18n library for React. LinguiJS is in many ways very similar: both libraries use the same syntax for (messages ICU MessageFormat) and they also have very similar API.

Here’s an example from react-intl docs:

< FormattedMessage id = "welcome" defaultMessage = { `Hello {name}, you have {unreadCount, number} {unreadCount, plural, one {message} other {messages} }` } values = {{ name : < b >{ name }</ b >, unreadCount }} />

Looking at the low-level API of LinguiJS, there isn’t much difference:

< Trans id = "welcome" defaults = { `Hello {name}, you have {unreadCount, number} {unreadCount, plural, one {message} other {messages} }` } values = {{ name : < b >{ name }</ b >, unreadCount }} />

There’s really no reason to reinvent the wheel when both libs are build on top of the same message syntax. The story doesn’t end here, though.

Translations with rich-text markup¶ Suppose we have the following text: < p > Read the < a href = "/docs > documentation </ a > . </ p > In react-intl, this would be translated as: < FormattedMessage id = 'msg.docs' defaultMessage = 'Read the {link}.' values = {{ link : < a href = "/docs" > documentation </ a > }} /> which isn’t enough, because word documentation is left untranslated. The correct solution would be: < FormattedMessage id = 'msg.docs' defaultMessage = 'Read the {link}.' values = {{ link : ( < a href = "/docs" > < FormattedMessage id = "msg.docs.link" defaultMessage = "documentation" /> </ a > ) }} /> but even this solution is sub-optimal, because the translator has to translate two separate messages: { "msg.docs" : "Read the {link}." , "msg.docs.link" : "documentation" } This is very fragile and error prone because phrases usually can’t be translated word by word. LinguiJS extends ICU MessageFormat with tags. The example above would be: < Trans id = 'msg.docs' defaults = 'Read the <0>documentation</0>.' components = {[ < a href = "/docs" /> ]} /> and the translator gets the message in one piece: Read the <0>documentation</0> . However, let’s go yet another level deeper.

Macros for component-based message syntax¶ LinguiJS provides macros @lingui/macro which automatically generates a message syntax. Let’s go back to the previous example: < p > Read the < a href = "/docs > documentation </ a > . </ p > All we need to do is to wrap the message in a <Trans> macro: < p > < Trans id = "msg.docs" > Read the < a href = "/docs > documentation </ a > . </ Trans > </ p > The macro then parses the <Trans> macro childrens and generates defaults and components props automaticaly in the form described in the previous section. This is extremly useful when adding i18n to an existing project. All we need to do is to wrap all messages in <Trans> macro. Let’s compare it with react-intl solution to see the difference: < p > < FormattedMessage id = 'msg.docs' defaultMessage = 'Read the {link}.' values = {{ name : ( < a href = "/docs" > < FormattedMessage id = "msg.docs.link" defaultMessage = "documentation" /> </ a > ) }} /> </ p > Note It’ also worth mentioning that the message IDs are completely optional. LinguiJS is unopinionated in this way and perfectly works with messages as IDs as well: < p > < Trans > Read the < a href = "/docs > documentation </ a > . </ Trans > </ p > The message ID is Read the <0>documentation</0>. instead of msg.id . Both solutions have pros and cons and the library lets you choose the one which works best for you.

Plurals¶ Another very common linguistic feature is pluralization. Let’s take a look at the original example from react-intl docs: < FormattedMessage id = "welcome" defaultMessage = { `Hello {name}, you have {unreadCount, number} {unreadCount, plural, one {message} other {messages} }` } values = {{ name : < b >{ name }</ b >, unreadCount }} /> Using LinguiJS macros, we could combine <Trans>, <Plural> and <NumberFormat> components: < Trans id = "welcome" > Hello < b >{ name }</ b >, you have < NumberFormat value = { number } /> < Plural one = "message" other = "messages" /> </ Trans > and the final message would be very similar: < Trans id = "welcome" defaults = { `Hello <0>{name}</0>, you have {unreadCount, number} {unreadCount, plural, one {message} other {messages} }` } values = {{ name , unreadCount }} /> The only difference is the <0> tag included in the message, as LinguiJS can handle components in both variables and the message itself. Note It’s good to mention here that this isn’t the best example of using plurals. Make your translators happy and move plurals to the top of the message: < Plural id = "welcome" value = { number } one = {<> Hello < b >{ name }</ b >, you have < NumberFormat value = { number } /> message .</>} other = {<> Hello < b >{ name }</ b >, you have < NumberFormat value = { number } /> messages .</>} /> Even though both variants are syntactically valid in ICU MessageFormat, the second one is easier for translating, because (again) the translator gets the phrase in one piece.

Text attributes¶ Components can’t be used in some contexts, e.g. to translate text attributes. Whereas react-intl provides JS methods (e.g: formatMessage ) which return plain strings, LinguiJS offers its core library for such translations. And it also provides macros for these usecases! Here are a few short examples: < a title = { i18n . _ ( t `The title of ${ name } ` )}>{ name }</ a > < img alt = { i18n . _ ( plural ({ value : count , one : "flag" , other : "flags" }))} src = "..." /> Custom IDs are supported as well: < a title = { i18n . _ ( t ( "link.title" ) `The title of ${ name } ` }>{ name }</ a > < img alt = { i18n . _ ( plural ( "img.alt" , { value : count , one : "flag" , other : "flags" }))} src = "..." /> Note To inject i18n object into props, you need to use HOC withI18n() . It’s very similar to injectIntl from react-intl. Alternatively, you can also use <I18n> render prop component.

External message catalog¶ Let’s say our app has been internationalized and we now want to send the messages to the translator. react-intl comes with the Babel plugin which extracts messages from individual files, but it’s up to you to merge them into one file which you can send to translators. LinguiJS provides handy CLI which extracts messages and merges them with any existing translations. Again, the story doesn’t end here.

Compiling messages¶ The biggest and slowest part of i18n libraries are message parsers and formatters. LinguiJS compiles messages from MessageFormat syntax into JS functions which only accept values for interpolation (e.g. components, variables, etc). This makes the final bundle smaller and makes the library faster. The compiled catalogs are also bundled with locale data like plurals, so it’s not necessary to load them manually.

Disadvantages of LinguiJS¶ react-intl has been around for some time and it’s definitely more popular, more used and a lot of production sites are running it. The community is larger and it’s much easier to find help on StackOverflow and other sites. LinguiJS is a very new library and the community is very small at the moment. It’s not tested on many production sites. On the other hand, LinguiJS’s testing suite is very large and all examples are incorporated into the integration testing suite to make sure everything is working fine. Last but not least, LinguiJS is actively maintained. Bugs are fixed regularly and new features are constantly coming in. Work is currently progressing on webpack code splitting, so that only messages related to the code in the chunk are loaded.

Summary¶ both libraries use the same MessageFormat syntax

similar API (easy to port from one to the other) On top of that, LinguiJS: supports rich-text messages

provides macros to simplify writing messages using MessageFormat syntax

provides a CLI for extracting and compiling messages

is very small (<5kb gzipped) and fast

works also in vanilla JS (without React)

is actively maintained On the other hand, react-intl: is the most popular and used i18n lib in React

is used in many production websites (stability)

has lots of resources available online