Simple intro to React and Globalization

Overview

A main goal is to have a ready-to-use and simple “guide” that others can use for setting up a React project for translation.  There are many different methods and stacks in the computer world, especially in the javascript world, but the React Intl stack seems the most-used and easiest to use.

Here are the base overview steps:

  • Prepare the project imports.

  • Create the top level wrapper-component which the lower components can use to access the translation strings.

  • Find suitable strings in the code and use one of two different techniques for the lookup (the technique is based whether the target code is JSX or javascript).

  • Use the BabelEdit gui tool to make the translations tags, per-language substitutions, and possibly the “accept” or “track” (especially important when off shore teams are doing the work).

In all, it is not difficult and the code remains readable; in fact, once accustomed, it is barely more work to continue writing the code in a translatable manner.

One caveat not yet resolved is pre-processing the source files to tease-out the strings. The React “extract-messages” script WILL find JSX-style language tags and create file(s) for the ‘defaultMessage’ value, but it will NOT find the javascript equivalent syntax and I ran out of time. In the end, this will help optimize the process by finding ALL translation points (and using the defaultMessage attribute on each allows completely deferring the actual translation step until completely necessary (and for a non-english target).

Prep The Environment

Perform the following two npm installs:

npm install –save-dev @babel/core @babel/cli  babel-plugin-react-intl

npm -s install react-intl

 

The first one installs a plugin that can/will run through the source files looking for translation inserts. The output is a script of all the “defaultMessage” values which can be a handy way at completely identifying all candidate strings.

The second one is the run-time ‘react-intl’ library.

Then visit and install BabelEdit if you wish to use the gui tool for maintaining the json translation files:

https://www.codeandweb.com/babeledit

 

Top level wrapper

The overall approach is that the entry-point of the application will load the key translation library code and then load all the translation files that it can find and (if a language hasn’t been translated, at run time it will use the defaultMessage values which is essentially as-if the software hasn’t been translated at all).  It then looks at the browser current language setting and automatically activates the appropriate translation file. Alternatively, some applications will offer the user a ‘Pull Down’ to select their desired language and often will store/remember that in a cookie or local storage location.

 

The index.js file imports react-intl, then/any existing language files, and then builds a union as follows:

import { IntlProvider } from ‘react-intl’;

import messages_es from ‘./translations/es.json’;

import messages_en from ‘./translations/en.json’;

 

const messages = {

   es: messages_es,

   en: messages_en,

};

 

The translation files are typically stored in the folder “./src/translations” and one per target language domain, two-character lower case letter style by convention.   These can be created by hand (simple json), but the BabelEdit tool greatly helps manage that process.

 

The last “introductory step” is to determine which language to make active (per browser setting) and then wrap the top level app application in the ‘IntlProvider’, as follows:

const language = navigator.language.split(/[-_]/)[0]; // language without region code

ReactDOM.render(

   <IntlProvider locale={language} messages={messages[language]}>

       <App />

   </IntlProvider>,

   document.getElementById(‘root’),

);

 

The language files themselves are trivial K-V pairs of some random ID you invent and the desired translation.  For example, the current one for Spanish is:

 

{

   “app.btn.login”: “Iniciar sesión”,

   “app.btn.signup”: “Regístrate”,

   “app.tagline”: “Descubre la mejor experiencia en redes sociales”

}

 

The general style is to use a pseudo-hierarchy convention for the ID, where often the first part is the component or container, then maybe the html-type, then something unique — its just a string.  Depending on how the rest of the system is implemented if an ID can not be found in a translation file, then the ID itself is used, so often it makes sense to have it be human-readable and sensible “just in case”.  However, most code SHOULD provide a “defaultMessage” which will be used if the ID can not be found (more on this below). In fact, there really is no reason to create an english translation file if all the defaults are correctly set using english.

 

In spite of how simple the translation files can be, there are many options and layered tools (like BabelEdit) add-on their own wrappers to help translators (give them clue, for example, is ‘to’ 2 or ‘too’?)

 

The Substitution Code

There are two main styles for doing the run-time string replacement. One is easier if the code segment is ‘jsx’ land; and one better for javascript.

In either case, the running code segment needs to get access to that top level wrapper component that holds the currently-active translation, and there are different approaches.  But let’s first start with a jsx approach.

Typically your class/library file will import as follows;

import { FormattedMessage} from ‘react-intl’;

There are two main jsx-style components, one is “FormattedMesssage” and the other is “FormatedHTMLMessage”; use the html one if inserting code that has html tags versus simple string.  Note: This paper does NOT describe all the colorful ways that substitution can use live data inserts, single vs plural detection, currency/money detection, and fancy right-to-left handling; there are many documents describing such.

Let’s cut to a very simple case.  A portion of a page/component wants to give the user a welcome message, and of course we’d like that translated appropriately.  Look at this example:

                               <h2>

                                   <FormattedMessage

                                       id=‘app.comeexplore’

                                       defaultMessage=‘Come explore the latest of what others are sharing.’

                                   />

                               </h2>

Trivial.  And readable. And w/o any translations files built, this will display *something* natural and readable because of the defaultMessage placeholder.  And this will work inside any code/component that is “under” the top level one holding the translation wrapper (much like useContext).

Next up is javascript code.  Sometimes javascript code generates jsx code or has event handlers (which is javascript inside of a jsx/element wrapper).  There is a nearly identical run-time function call, called formatMessage() which takes the same properties. The challenge is sometimes getting access to that API.

IF the component was defined as a HOC and thus returned a wrapped version of itself using the “injectIntl” HOC wrapper, THEN the component can access “this.props.format Message()” (or, props.formatMessage() as fit).  Otherwise, the module needs to import “useIntl” from react-intl and can then call that method the same way (e.g. useIntl().formatMessage(…)) or similar factoring. Otherwise, the code appears very similar, as shown here:

 

                       <Button

                           label={intl.formatMessage({ id: ‘app.btn.login’, defaultMessage: ‘Log In’ })} //’Log In’

                           type=‘outline’

                           size=‘large’

                           onClick={() => updateAuthModal({ route: ‘login’ })}

 

                       />

Leave a Comment

Your email address will not be published. Required fields are marked *