Subscribe Us

React Internationalization: Transifex Native Vs. react-i18next

image
transifex Hacker Noon profile picture

transifex

The #1 localization platform for developers.

In this document, we will showcase the capabilities of each framework and see how they map to each other and provide a foundation for how one would go about migrating from react-i18next to Transifex Native.

Initialization

Both frameworks require you to initialize a global object that will serve as a “gateway” between your application and the ability to localize it. In react-i18next, this looks like this (example taken from the official website):

import i18n from "i18next";
import { initReactI18next } from "react-i18next"; const resources = {
  en: {
    translation: {
  'Hello world': 'Hello world',
    },
  },
  el: {
    translation: {
  'Hello world': 'Καλημέρα κόσμε',
    },
  },
}; i18n
  .use(initReactI18next)
  .init({
    resources,
    lng: 'en',
    interpolation: { escapeValue: false },
  });

This snippet doesn’t tell the whole story. The translated content (the ‘resources’ field) can originate from a variety of sources and can also be added after the initialization. Also, the ‘init’ method accepts many more arguments and allows you to customize the experience to a great extent.

With Transifex Native, you lose some of this flexibility, given that the source of truth for your content is always the content on transifex.com, and in return, you end up with the simpler:

import { tx } from '@transifex/native'; tx.init({
  token: '...',
});

There are other initialization options in Transifex Native that allow you to:

  1. Limit the content you will receive from Transifex with the ‘filterTags’ field
  2. What to do when a translation is missing with the ‘missingPolicy’ field
  3. What to do when the translation cannot be rendered (because, for example, the translator made a formatting mistake) with the ‘errorPolicy’ field

Keys

react-i18next

react-i18next is key-based. This means that if your component code looks like this:

function Paragraph() {
  const { t } = useTranslation();
  return <p>{t('a_key')}</p>;
}

Then you need to have the exact same key in your ‘resources’:

const resources = {
  en: { translation: {
  'a_key': 'Hello world', },
  },
  el: { translation: {
  'a_key': 'Καλημέρα κόσμε', },
  },
};

If the key is not found in the resources, then the key itself will be rendered in the application. For this reason, it makes sense to use the source string itself as the key:

const resources = {
  en: { translation: {
  'Hello world': 'Hello world', },
  },
  el: { translation: {
  'Hello world': 'Καλημέρα κόσμε', },
  },
}; // i18n.use(...).init(...) function Paragraph() {
  const { t } = useTranslation();
  return <p>{t('Hello world')}</p>;
}

However, this gets a little difficult to manage if you have plurals. Let’s assume you have the following component:

function Paragraph() {
  const [count, setCount] = useState(2);
  const { t } = useTranslation();
  return (
    <>
  <p>
      {t('You have  messages', { count })}
  </p>
  <p>
      <button onClick={() => setCount(count - 1)}>-</button>
      <button onClick={() => setCount(count + 1)}>+</button>
  </p>
    </>
  );
}

And the following resources:

const resources = {
  en: {
    translation: {
  'You have  messages_one': 'You have one message',
  'You have  messages_other': 'You have  messages',
    },
  },
  el: {
    translation: {
  'You have  messages_one': 'Έχετε ένα μήνυμα',
  'You have  messages_other': 'Έχετε  μηνύματα',
    },
  },
};

Then the application will work as expected. When the ‘count’ becomes 1, the application will render “You have one message”. If the keys are not in the resources, however, or if they are not formatted properly, you”ll get “You have 1 messages”.

Transifex Native

With Transifex Native, the default behaviour is to not worry about keys at all. The string itself becomes the key. In case of plurals, the fact that the strings are formatted with ICU MessageFormat means that it contains all the information needed to render in both the source and target languages:

function Paragraph() {
  const [count, setCount] = useState(2);
  return (
    <>
  <p>
      <T
          _str="{count, plural, one {You have one message} other {You have # messages}}"
          count={count} />
  </p>
  <p>
        <button onClick={() => setCount(count - 1)}>-</button>
        <button onClick={() => setCount(count + 1)}>+</button>
  </p>
    </>
  );
}

If you want to use keys, however, probably because you are using Transifex Native with a design tool like Figma or Sketch, You can supply the ‘_key’ prop:

function Paragraph() {
  return <T _key="a_key" _str="Hello world" />;
}

In both cases, the source language is editable by using the “edit source strings” feature of transifex.com.

t-function

react-i18next

Most of the translation methods offered by react-i18next boil down to offering you a javascript function called ‘t’. Using this function will render the argument in the correct language and make sure the component will re-render when the selected language changes.

‘useTranslation’ hook

import { useTranslation } from 'react-i18next'; function Paragraph() {
  const { t } = useTranslation();
  return <p>{t('Some text')}</p>;
}

‘withTranslation’ (Higher-Order Component)

import { withTranslation } from 'react-i18next'; function _Paragraph({ t }) {
  return <p>{t('Some text')}</p>;
} const Paragraph = withTranslation()(_Paragraph); function App() {
  return <Paragraph />;
}

‘Translation’ (Render Prop)

import { Translation } from 'react-i18next'; function Paragraph() {
  return (
    <Translation>
  {(t) => <p>{t('Some text')}</p>}
    </Translation>
  );
}

‘I18nProvider’ context

import { useContext } from 'react';
import { I18nContext, I18nextProvider } from 'react-i18next'; i18n.use(...).init({...}); ReactDOM.render(
  <I18nextProvider i18n={i18n}>
    <App />
  </I18nextProvider>,
  document.getElementById('root'),
); function App() {
  return <Paragraph />;
} function Paragraph() {
  const { i18n: { t } } = useContext(I18nContext);
  return <p>{t('Some text')}</p>;
}

Transifex Native

With Transifex Native you can achieve the same result by using the ‘useT’ hook:

import { useT } from '@transifex/react'; function Paragraph() {
  const t = useT();
  return <p>{t('Some text')}</p>;
}

or by using the preferable ‘T-component’:

import { T } from '@transifex/react'; function Paragraph() {
  return <p><T _str="Some text" /></p>;
}

Interpolation and Plurals

react-i18next

react-i18next allows you to interpolate dynamic values into your translations by using the ‘’ syntax, as demonstrated here:

import { useTranslation } from 'react-i18next';
function Paragraph() {
  const { t } = useTranslation();
  return <p>{t('Hello ', { name: 'Bob' })}</p>;
}

or

import { useTranslation, Trans } from 'react-i18next'; function Paragraph() { const { t } = useTranslation(); return ( <p> <Trans t={t}> Hello <strong></strong> </Trans> </p> );
}

In order to support plurals, you have to pay very close attention to your keys:

const resources = { en: { translation: { messages_one: 'You have 1 message', messages_other: 'You have  messages' }, }, el: { translation: { messages_one: 'Έχετε 1 μήνυμα', messages_other: 'Έχετε  μηνύματα' } },
};
i18n.use(...).init({ ..., resources }); function Paragraph() { const count = 3; const { t } = useTranslation(); return <p>{t('messages', { count })}</p>;
}

Or

function Paragraph() { const count = 3; const { t } = useTranslation(); return ( <p> <Trans t={t} i18nkey="messages" count={count}> You have  messages </Trans> </p> );
}

Transifex Native

Transifex Native uses ICU MessageFormat natively (pun intended). This gives you a solid background to work with interpolation:

import { T } from '@transifex/react'; function Paragraph() { return <p><T _str="Hello {name}" name="Bob" /></p>;
}
ICU MessageFormat also offers you industry standard capabilities for plurals:
function Messages() { const count = 3; return ( <p> <T _str="{cnt, plural, one {You have 1 message} other {You have # messages}}" cnt={count} /> </p> );
}

And also for select statements and number formatting:

<T _str="It's a {gender, select, male {boy} female {girl}}" gender={gender} />
<T _str="Today is {today, date}" today={today} />

Translation with Formatted Text

react-i18next

When you want to translate HTML content, react-i18next offers the ‘<Trans>’ component:

import { useTranslation, Trans } from 'react-i18next'; function Paragraph() { const { t } = useTranslation(); return ( <Trans t={t}> <p>Some <strong>bold</strong> text</p> </Trans> );
}

In order for this to work, the source text in i18n’s resources must have the form

<1>Some <2>bold</2> text</1>

Which you have to generate by hand.

Transifex Native

With Transifex Native you have the option to use the ‘UT’-component:

import { UT } from '@transifex/react'; function Paragraph() { return <UT _str="<p>Some <strong>bold</strong> text</p>" />;
}

Or to interpolate the outer template with react elements (that can be also translated):

import { T } from '@transifex/react';
function Paragraph() { return ( <T _str="<p>Some {strong} text</p>" strong={<strong><T _str="bold" /></strong>} /> );
}

With both ways, what your translators will be asked to work on will have the exact same form as the argument you used for ‘_str’.

Language Selection

Both frameworks allow you to dynamically set the currently active language. With react-i18next you can do:

function Languages() { return ( <> <button onClick={() => i18n.changeLanguage('en')}>English</button> <button onClick={() => i18n.changeLanguage('el')}>Greek</button> </> );
}

And with Transifex Native you can do the similar:

function Languages() { return ( <> <button onClick={() => tx.setCurrentLocale('en')}>English</button> <button onClick={() => tx.setCurrentLocale('el')}>Greek</button> </> );
}

What Transifex Native can offer you above this is due to the fact that transifex.com is your source-of-truth not only for your translation content but also for your available languages. Taking this into account, you can do:

import { useLanguages } from '@transifex/react'; function Languages() { const languages = useLanguages(); return ( <> {languages.map(({ code, name }) => ( <button key={code} onClick={() => tx.setCurrentLocale(code)}> {name} </button> ))} </> );
}

Or the more direct:

import { LanguagePicker } from '@transifex/react'; function Languages() { return <LanguagePicker className="pretty" />;
}

Which will render a language selector dropdown.

After this, languages added on transifex.com will be reflected in your application without requiring you to publish a new release.

String Extraction

react-i18next

There are some third-party tools to help with extracting strings from your code in the i18next ecosystem. One that can work with react applications is i18next-parser. Assuming you have the following application:

export default function App() { const { t } = useTranslation(); return ( <> <p> {t('Hello world 1')} </p> <p> <Trans t={t}> Hello <strong>world</strong> 2 </Trans> </p> <p> {t('Hello  worlds', { count: 3 })} </p> </> );
}

And use this configuration:

module.exports = { useKeysAsDefaultValue: true, lexers: { js: ['JsxLexer'], },
};

Then, if you run i18next-parser, you will end up with the following resource file:

{ "Hello world 1": "Hello world 1", "Hello <1>world</1> 2": "Hello <1>world</1> 2", "Hello  worlds_one": "Hello  worlds", "Hello  worlds_other": "Hello  worlds"
}

΅΅΅Which is a good starting point and even takes care of the unintuitive key generation for the ‘<Trans>’ component and of plurals.

After that, you will of course have to worry about how to generate translated versions of these files (hint: you should upload them to Transifex) and how to import these files into the application when it’s running.

Transifex Native

With Transifex Native, you don’t have to worry about files at all. You simply have to run:

export TRANSIFEX_TOKEN=...
export TRANSIFEX_SECRET=...
npx txjs-cli push src

After that:

  1. Your content will be available on transifex.com for your translators to work on
  2. Any ready translations will be available to your application during runtime

Namespaces / Lazy loading

react-i18next and i18next in general offers extensive support for compartmentalizing your translated content and, via plugins, loading these compartmentalized namespaces into your application via HTTP after the initial booting of the application.

Transifex Native has limited support for namespacing via tags. You can add a tag to a translatable string with the ‘_tags’ property:

<T _str="Hello world" _tags="main" />

or by specifying tags during the extraction cli execution:

npx txjs-cli push --append-tags=helpdesk src/helpdesk
npx txjs-cli push --append-tags=dashboard src/dashboard

Then, when initializing the ‘tx’ object, you can specify which tags you want to filter against:

tx.init({ token: ..., filterTags: 'android' });

This is useful in case you are using the same transifex.com project for different platforms and you want each platform to only pull translations that are relevant.

As of now, we don’t support lazy loading of translations but we have plans to implement this in the near future.

Preparing the code

For minimal inconvenience, you should replace the invocation of react-i18next’s ‘t’ function with Transifex Native’s ‘t’ function.

From:

import { useTranslation } from 'react-i18next'; function Paragraph() { const { t } = useTranslation(); return <p>{t('Some text')}</p>;
}

to

import { useT } from '@transifex/react'; function Paragraph() { const t = useT(); return <p>{t('Some text')}</p>;
}

However, it may be preferable to use the T-component:

import { T} from '@transifex/react'; function Paragraph() { return <p><T _str="Some text" /></p>;
}

Simple variable interpolation is done with single curly brackets instead of double.

From:

import { useTranslation } from 'react-i18next'; function Paragraph() { const { t } = useTranslation(); return <p>{t('Hello ', { username: 'Bob' })}</p>;
}

to:

import { T } from '@transifex/react'; function Paragraph() { return <p><T _str="Hello {username}" username="Bob" /></p>;
}

For formatted content, your best bet is to replace ‘<Trans>’ with <UT />’.

From:

import { useTranslation, Trans } from 'react-i18next'; function Paragraph() { const { t } = useTranslation(); return ( <Trans t={t}> <p>Some <strong>bold</strong> text</p> </Trans> );
}

To:

import { UT } from '@transifex/react'; function Paragraph() { return <UT _str="<p>Some <strong>bold</strong> text</p>" />;
}

Migrating the translated content

First, you’ll have to upload your current translations to transifex.com on a file-based project. After creating the project, you can use the transifex-client to help with uploading the resources (one namespace per resource):

# Install the client
wget https://github.com/transifex/cli/releases/download/v0.3.0/tx-linux-amd64.tar.gz
tar xf tx-linux-amd64.tar.gz tx
rm tx-linux-amd64.tar.gz
mv tx ~/bin # Set up the mapping to transifex.com
tx init
tx add \ --organization=... \ --project=... \ --resource=translations \ --file-filter=locales/translations/<lang>.json \ --type=KEYVALUEJSON \ locales/translations/en.json
tx add \ --organization=... \ --project=... \ --resource=helpdesk \ --file-filter=locales/helpdesk/<lang>.json \ --type=KEYVALUEJSON \ locales/helpdesk/en.json # Push the content to transifex
tx push --source --translation --all

Next, you need to create a Native project.

image

Make sure you keep the public and secret tokens for later.

Before pushing the content to the new project, you need to:

  1. Add the target languages to the new project
  2. Add both projects to the same TM group
    image

    Now in your local project, use the extraction cli to push content to the native project in transifex.com (use tags for namespacing as discussed above:

npm install --save-dev @transifex/cli
npx txjs-cli push --token=... --secret=... src

Because we put both projects in the same TM group, existing translations will be used to fill up the new ones. In case you had to make minor changes to some of the strings while migrating the code, take a look in the editor to fill these in. Even if they were not filled up automatically, partial TM matches should make this job easy.

Finally, make sure you initialize the ‘tx’ object when your application boots up and you should be good to go!

Once everything is up and running, you can delete the old file-based project.

Wrapping Up

Wanna share a quick React localization guide with a fellow developer? Find it on this page!

image

This post was co-authored by Konstantinos Bairaktaris and Mike Giannakopoulos

Find Transifex on our: Website, Facebook, Twitter, and LinkedIn.

Tags



React Internationalization: Transifex Native Vs. react-i18next
Source: Trends Pinoy

Post a Comment

0 Comments