The Flexport trucking engineering team’s goal is to bring visibility and efficiency to the trucking portions of Flexport shipments. We’ve used Transmission’s trucking software suite to help achieve these goals by streamlining communication between our clients, our carrier partners, and the Transmission team.

As Flexport’s needs have grown, we’ve recognized an opportunity to expand trucking operations outside the US. A big piece of that has been updating Transmission to improve the Transmission apps’ shipment management tools for use in other countries. Doing so required changes to the Transmission mobile app for drivers:

Translations and date internationalization: We needed to provide English, Spanish, Chinese, Dutch, and German translations of the app.

We needed to provide English, Spanish, Chinese, Dutch, and German translations of the app. Phone numbers as usernames: For driver ease of use, our mobile app uses the driver’s phone number as the username. With drivers in countries outside the US, we needed to support international calling codes both in our validations and UI components.

In this post, we’ll explore how we supported these requirements.

The Transmission mobile app

The Transmission mobile app is a React Native app built with Expo, available for Android and iOS devices. The app is used by truck drivers and has two main functions:

Provide drivers with a FMCSA-compliant solution for managing their Hours of Service (HOS) with an Electronic Logging Device (ELD).

Allow drivers to provide Load Updates, pushing information about the status of the delivery to the driver’s dispatcher and the freight owner.

From the beginning, react-i18next was a pretty clear choice for the translation library. I18next was used with success in Flexport’s React web application for our customers, so we had initial confidence in it. Additionally, i18next allowed us to avoid depending on OS-level iOS and Android locale settings, which could cause issues if multiple drivers share a mobile device.

Transmission mobile home screen in Dutch

Adding the react-i18next library

We started by adding a few new files: an i18next config file, a few translated app phrases in the correct json format, and a transformation module. The library’s default translations json structure has a .js file for each supported language, with the structure looking like this:

An example of a translation with a namespace of “common”, a phrase_key of “cancel”, and two different supported locales:

We received feedback from our translators that it is easier to translate when all available translations for a given phrase are listed together. This resulted in a transformed hierarchy:

The new structure also gives us the ability to namespace our translation files, rather than keeping the default of all translations for a locale living in the same file.

The same example as above represented in the new format

The last step was to use the Translation component within our React Native screen components — slowly wrapping each bit of text with the i18next framework. Here is an example of translating a settings screen list item. We pass the “settings” namespace as the ns prop to the Translation component, then a translation function t is available for translating text in that namespace.

Settings screen with English and Dutch locales

Getting full translation coverage across the app

With the i18next library in place, the next step was to get full coverage of all visible text across the app. We found it difficult to track which text had been wrapped in the i18next library, so we created a special locale that replaces every alphabetic character with a box like this: ☐.

Then we ran the app in a simulator with the boxes locale enabled, and continuously found and wrapped the text in our app until everything was displaying as boxes!

The home screen with the boxes locale enabled

A process for professional translations

After we wrapped all the text in the app with the i18next translations library, we needed to get the actual translations. Initially we received translations from people within Flexport who spoke these languages, but as we scaled that turned out to be burdensome.

Using a professional translation service created two opportunities to use programming for leverage: (1) providing the original English strings to translate and (2) incorporating the translations back into the app in an efficient manner.

Since we were targeting five languages and weren’t expecting to be constantly changing the text in our app, we decided to take a scripted approach. We wrote two scripts: extractTranslations.js and integrateTranslations.js .

The first parses the translation JavaScript objects and extracts only the English translations, isolated into their own files split by namespace. With the goal being to provide original English strings to base our translations off of, so we know the text is being translated from a single context.

We use the second integrateTranslations.js script once we receive the completed translations. This script parses both the new completed translation json files and the existing translation files in our repository. Then it merges the contents of the objects, writes them back to the correct place in our repository, and runs prettier to correct the formatting.

With these two scripts, low developer effort is required to add new languages or new text to the app.

Phone number as username

The mobile app uses a driver’s phone number as username, so Flexport’s need to internationalize the Transmission suite resulted in extending Transmission to support international phone numbers. The mobile app login flow was modified to add a country picker to the username field.

We replaced the component on the mobile app login screen with a forked version of the react-native-phone-input to inject some style into the component, limit the countries available for selection, and add flow typing.

The react-native-phone-input package has frontend phone number validation using google-libphonenumber. Calling google-libphonenumber’s isValidNumber returns a boolean indicating if the string of numbers is a valid phone number. The validation helps decrease input error, and also lets us automatically transition a user to the password input after we’ve received a valid phone number.

Transmission mobile login screen with country picker open

Handling long translation strings

Particularly for German and Dutch, text strings were often significantly longer than their English source. For example, the initial German translation for “Prepull Yard” was “Zwischenlagerung auf Betriebshof”. The button we displayed this text on had significant wrap around, so we worked with our trucking team based in Amsterdam to find an appropriate, shorter blurb to describe the concept in German.