Skip to content

How to Work on Localized Client Webapp

The React-based client web app that powers our learning platform is built using Gatsby. It is translated into various world languages using react-i18next and i18next.

You can learn more about setting up the client application locally for development by following our local setup guide here. By default, the application is available only in English.

Once you have set up the project locally you should be able to follow this documentation to run the client in the language of your choice from the list of available languages.

This could be helpful when you are working on a feature that specifically targets something that involves localization, and requires you to validate for instance a button’s label in a different language.

Let’s understand how the i18n frameworks and tooling work.

English source files and each language’s links.json are maintained in client/i18n/locales. Non-English versions of intro.json, translations.json, meta-tags.json, and motivation.json are maintained in the i18n-curriculum repository, which is included as a submodule at curriculum/i18n-curriculum.

The tracked files are organized as follows, relative to the repository root:

  • Directoryclient
    • Directoryi18n
      • config-for-tests.ts
      • config.js
      • Directorylocales
        • Directoryarabic
          • links.json
        • Directoryenglish
          • intro.json
          • links.json
          • meta-tags.json
          • motivation.json
          • translations.json
        • Directory…/
      • locales.test.ts
      • schema-validation.ts
  • Directorycurriculum
    • Directoryi18n-curriculum
      • Directoryclient
        • Directoryarabic
          • intro.json
          • meta-tags.json
          • motivation.json
          • translations.json
        • Directory…/
  • Directorypackages
    • Directoryshared
      • Directorysrc
        • Directoryconfig
          • i18n.ts

During setup, client/tools/create-i18n.ts copies the four non-English JSON files for CLIENT_LOCALE from the submodule into client/i18n/locales/<language>/. These copies are Git-ignored and overwritten when setup runs again. Setup also generates trending.json and search-bar.json.

Non-English content for the following four files comes through the translation pipeline via the i18n-curriculum submodule. Their English source files remain in the main repository.

Non-English files supplied through the submodule:

  • The translations.json file contains the majority of the text that appears on the user interface elements. The keys are used in the codebase to get the correct text for whatever language is set. English is the fallback when a translation is missing.

  • The intro.json file contains the key-value pairs for the introduction text on the certification pages.

  • The motivation.json files are not required to have the same quotes, compliments, or array length. Just the same JSON structure.

  • The meta-tags.json file contains the information for our website’s meta tag information.

Files maintained in the main repository for every language:

  • The links.json file contains URLs used by the client, including links to localized resources.

Update localized links in client/i18n/locales/<language>/links.json. For help with content supplied through the submodule, ask in the contributors chat room.

Testing the Client App in a World Language

Section titled “Testing the Client App in a World Language”

You can test the client app in any language available in the list of availableLangs here.

export const availableLangs = {
client: [
Languages.English,
Languages.Espanol,
Languages.Chinese,
Languages.ChineseTraditional,
Languages.Italian,
Languages.Portuguese,
Languages.Ukrainian,
Languages.Japanese,
Languages.German,
Languages.Swahili,
Languages.Korean,
Languages.Arabic
],
curriculum: [
...
]
};

From the repository root, initialize the translation submodule. This is required for a non-English client even if you keep the curriculum in English:

Terminal window
git submodule update --init curriculum/i18n-curriculum

Set CLIENT_LOCALE in your .env file to the language’s value in the Languages enum, for example CLIENT_LOCALE=arabic. CURRICULUM_LOCALE controls the challenge content separately and can remain english when you are testing only the UI.

Run pnpm run clean-and-develop to apply the locale setting and copy the translation files.

For a language that is not yet configured, prepare these additional files before starting the client:

  1. Add the language to the Languages enum and availableLangs.client in packages/shared/src/config/i18n.ts. Follow the comments to update the language codes and display name. Add right-to-left languages to rtlLangs; use hiddenLangs to keep a language out of the selector until launch.
  2. Copy the English links.json to client/i18n/locales/<language>/links.json and update URLs where localized resources exist.
  3. Ensure curriculum/i18n-curriculum/client/<language>/ contains intro.json, translations.json, meta-tags.json, and motivation.json. For local testing before translations exist, copy those four English files there. Changes intended for publication in that directory belong in the separate i18n-curriculum repository.
  4. Add an entry to algoliaIndices in client/src/utils/algolia-locale-setup.ts. Use the English entry’s values if the language has no news publication. Setup reads this entry when generating the search placeholder.
  5. If the language does not yet have a trending file on the CDN, copy the generated client/i18n/locales/english/trending.json from your English setup to client/i18n/locales/<language>/trending.json. The download script uses this local file as a fallback in development. Production requires a build/universal/trending/<language>.yaml file in the CDN repository.

Then set CLIENT_LOCALE and run pnpm run clean-and-develop as described above. Enabling production deployments and publishing a localized curriculum require additional work beyond testing the client UI locally.

If you are working on a feature or a bug for the client web app, say for example adding new UI items on the settings page, you should follow the guidelines below. They will help you prepare the components for localization into all the supported world languages.

import { useTranslation } from 'react-i18next';
// in the render method:
const { t } = useTranslation();
// call the "t" function with a key from the JSON file:
<p>{t('key')}</p>; // more details below
import { withTranslation } from 'react-i18next';
// withTranslation adds the "t" function to props:
const { t } = this.props;
// call the "t" function with a key from the JSON file:
<h1>{t('key')}</h1> // more details below
// export without redux:
export default withTranslation()(Component);
// or with redux:
export default connect(...)(withTranslation()(Component));
// in the component:
<p>{t('p1')}</p>
// in the JSON file:
{
"p1": "My paragraph"
}
// output:
<p>My paragraph</p>
// in the component:
const username = 'moT';
<p>{t('welcome', { username: username })}</p>
// in the JSON file:
{
"welcome": "Welcome {{username}}"
}
// output:
<p>Welcome moT</p>

The above example passes an object to the t function with a username variable. The variable will be used in the JSON value where {{username}} is.

The general rule is to use the “t” function when you can. But there’s a Trans component for when that isn’t enough, usually when you have elements embedded in the text. You can use the Trans component with any type of react component.

// in the component:
import { Trans } from 'react-i18next'
<p>
<Trans>fcc.greeting</Trans>
</p>
// in the JSON file:
{
"fcc": {
"greeting": "Welcome to <strong>freeCodeCamp</strong>"
}
}
// output:
<p>Welcome to <strong>freeCodeCamp</strong></p>

You can place the key inside the component tags like in the above example if the text contains “simple” tags with no attributes. br, strong, i, and p are the default, but that list can be expanded in the i18n config.

Other times, you will want to have certain text inside another element, an anchor tag is a good example:

// in the component:
<p>
<Trans i18nKey='check-forum'>
<a href='https://forum.freecodecamp.org/'>placeholder</a>
</Trans>
</p>
// in the JSON file:
{
"check-forum": "Check out <0>our forum</0>."
}
// output:
<p>Check out <a href='https://forum.freecodecamp.org/'>our forum</a></p>

In the above example, the key is set in the attributes of the Trans component. The <0> and </0> in the JSON represent the first child of the component, in this case, the anchor element. If there were more children, they would just count up from there using the same syntax. You can find the children of a component in the react dev tools by inspecting it. placeholder is simply there because the linter complains about empty <a> elements.

// in the component:
const email = 'team@freecodecamp.org';
<p>
<Trans i18nKey='fcc.email'>
<a href={`mailto:${email}`}>
{{ email }}
</a>
</Trans>
</p>
// in the JSON file:
{
"fcc": {
"email": "Send us an email at: <0>{{email}}</0>"
}
}
// output:
<p>Send us an email at: <a href='mailto:team@freecodecamp.org'>team@freecodecamp.org</a></p>

The i18nKey prop selects the translation. Trans reads the nested {{ email }} object to supply the value for {{email}} in the JSON string. The <0> tags place the translated text inside the anchor. A separate email prop on Trans is not needed.

To change English client text, open the relevant .json file in client/i18n/locales/english/, find the key used in the React component, and update its value. Search the codebase for that key to check that the change makes sense everywhere it is used. For translations, use the workflow described in File Structure; do not edit the generated non-English copies, since setup overwrites them.

Run pnpm run clean-and-develop to apply the change.

If the text you want to add to the client exists in the relevant .json file, use the existing key. Otherwise, create a new key.

The English file is the “source of truth” for all of the .json files sharing the same name. Add new UI text to client/i18n/locales/english/translations.json, or to the appropriate English file for another translation namespace.

Keep related keys together in the English source file. Put punctuation, spacing, and quotes in the JSON strings so translators can adjust the complete text.

Run pnpm run clean-and-develop to apply the change.

After you’ve committed your changes, check here for how to open a Pull Request.