2

React Internationalization: Transifex Native Vs. react-i18next

 2 years ago
source link: https://hackernoon.com/react-internationalization-transifex-native-vs-react-i18next
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Site Color

hex

Text Color

Ad Color

hex

Text Color

Evergreen

Duotone

Mysterious

Classic

Sign Up to Save Your Colors

React Internationalization: Transifex Native Vs. react-i18next by@transifex

React Internationalization: Transifex Native Vs. react-i18next

Transifex Native is not just easier to use for internationalization, but it's also a great solution for localizing your product without having to bother with files, thanks to its automated push & pull function!

Listen to this story

Speed:
Read by:
voice-avatar
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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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):

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { tx } from '@transifex/native';

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

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
  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

react-i18next

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
function Paragraph() {
  const { t } = useTranslation();
  return <p>{t('a_key')}</p>;
}

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
function Paragraph() {
  const [count, setCount] = useState(2);
  const { t } = useTranslation();
  return (
    <>
  	<p>
    	  {t('You have {{count}} messages', { count })}
  	</p>
  	<p>
    	  <button onClick={() => setCount(count - 1)}>-</button>
    	  <button onClick={() => setCount(count + 1)}>+</button>
  	</p>
    </>
  );
}

And the following resources:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
const resources = {
  en: {
    translation: {
  	'You have {{count}} messages_one': 'You have one message',
  	'You have {{count}} messages_other': 'You have {{count}} messages',
    },
  },
  el: {
    translation: {
  	'You have {{count}} messages_one': 'Έχετε ένα μήνυμα',
  	'You have {{count}} messages_other': 'Έχετε {{count}} μηνύματα',
    },
  },
};

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".

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

'useTranslation' hook

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { useTranslation } from 'react-i18next';

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

'withTranslation' (Higher-Order Component)

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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)

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { Translation } from 'react-i18next';

function Paragraph() {
  return (
    <Translation>
  	{(t) => <p>{t('Some text')}</p>}
    </Translation>
  );
}

'I18nProvider' context

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { useT } from '@transifex/react';

function Paragraph() {
  const t = useT();
  return <p>{t('Some text')}</p>;
}

or by using the preferable 'T-component':

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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 '{{name}}' syntax, as demonstrated here:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { useTranslation } from 'react-i18next';
function Paragraph() {
  const { t } = useTranslation();
  return <p>{t('Hello {{name}}', { name: 'Bob' })}</p>;
}
0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { useTranslation, Trans } from 'react-i18next';

function Paragraph() {
  const { t } = useTranslation();
  return (
    <p>
      <Trans t={t}>
          Hello <strong>{{name}}</strong>
      </Trans>
    </p>
  );
}

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
const resources = {
  en: { translation: { messages_one: 'You have 1 message',
                       messages_other: 'You have {{count}} messages' },
  },
  el: { translation: { messages_one: 'Έχετε 1 μήνυμα',
                       messages_other: 'Έχετε {{count}} μηνύματα' } },
};
i18n.use(...).init({ ..., resources });

function Paragraph() {
  const count = 3;
  const { t } = useTranslation();
  return <p>{t('messages', { count })}</p>;
}
0 reactions
heart.png
light.png
money.png
thumbs-down.png
function Paragraph() {
  const count = 3;
  const { t } = useTranslation();
  return (
    <p>
      <Trans t={t} i18nkey="messages" count={count}>
        You have {{count}} messages
      </Trans>
    </p>
  );
}

Transifex Native

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
<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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Which you have to generate by hand.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Transifex Native

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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):

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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'.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Language Selection

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { LanguagePicker } from '@transifex/react';

function Languages() {
  return <LanguagePicker className="pretty" />;
}

Which will render a language selector dropdown.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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 {{count}} worlds', { count: 3 })}
  	</p>
    </>
  );
}

And use this configuration:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
module.exports = {
  useKeysAsDefaultValue: true,
  lexers: {
	js: ['JsxLexer'],
  },
};

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
{
  "Hello world 1": "Hello world 1",
  "Hello <1>world</1> 2": "Hello <1>world</1> 2",
  "Hello {{count}} worlds_one": "Hello {{count}} worlds",
  "Hello {{count}} worlds_other": "Hello {{count}} worlds"
}

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Transifex Native

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
export TRANSIFEX_TOKEN=...
export TRANSIFEX_SECRET=...
npx txjs-cli push src

After that:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
  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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
<T _str="Hello world" _tags="main" />

or by specifying tags during the extraction cli execution:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Migration Guide

Preparing the code

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

From:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { useTranslation } from 'react-i18next';

function Paragraph() {
  const { t } = useTranslation();
  return <p>{t('Some text')}</p>;
}
0 reactions
heart.png
light.png
money.png
thumbs-down.png
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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

From:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { useTranslation } from 'react-i18next';

function Paragraph() {
  const { t } = useTranslation();
  return <p>{t('Hello {{username}}', { username: 'Bob' })}</p>;
}
0 reactions
heart.png
light.png
money.png
thumbs-down.png
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 />'.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

From:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
import { useTranslation, Trans } from 'react-i18next';

function Paragraph() {
  const { t } = useTranslation();
  return (
    <Trans t={t}>
  	<p>Some <strong>bold</strong> text</p>
    </Trans>
  );
}
0 reactions
heart.png
light.png
money.png
thumbs-down.png
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):

0 reactions
heart.png
light.png
money.png
thumbs-down.png
# 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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
  1. Add the target languages to the new project
  2. Add both projects to the same TM group

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:

0 reactions
heart.png
light.png
money.png
thumbs-down.png
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.

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

Wrapping Up

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png

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

0 reactions
heart.png
light.png
money.png
thumbs-down.png
11
heart.pngheart.pngheart.pngheart.png
light.pnglight.pnglight.pnglight.png
boat.pngboat.pngboat.pngboat.png
money.pngmoney.pngmoney.pngmoney.png
by transifex @transifex.The #1 localization platform for developers.
Start your 15-day Free Trial
Customized Experience.|

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK