Skip to content
freeCodeCamp.org

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.

File Structure

Most of the files for translating the platform are located in the client/i18n folder. Each language has a directory that contains JSON files with the translations.

Terminal window
config
└── i18n.ts
...
client/i18n
β”œβ”€β”€ configForTests.js
β”œβ”€β”€ config.js
β”œβ”€β”€ locales
β”‚Β Β  β”œβ”€β”€ chinese
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ intro.json
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ links.json
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ meta-tags.json
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ motivation.json
β”‚Β Β  β”‚Β Β  └── translations.json
... ...
β”‚Β Β  β”œβ”€β”€ dothraki
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ intro.json
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ links.json
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ meta-tags.json
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ motivation.json
β”‚Β Β  β”‚Β Β  └── translations.json
... ...
β”‚Β Β  β”œβ”€β”€ english
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ intro.json
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ links.json
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ meta-tags.json
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ motivation.json
β”‚Β Β  β”‚Β Β  └── translations.json
β”‚Β Β  └── espanol
β”‚Β Β  β”œβ”€β”€ intro.json
β”‚Β Β  β”œβ”€β”€ links.json
β”‚Β Β  β”œβ”€β”€ meta-tags.json
β”‚Β Β  β”œβ”€β”€ motivation.json
β”‚Β Β  └── translations.json
β”œβ”€β”€ locales.test.js
β”œβ”€β”€ schema-validation.js
└── validate-keys.ts

Some of these files are translated on our translation platform (Crowdin) and some are translated or created via PRs on GitHub.

Files translated on our translation platform:

  • 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. This file needs to have the same keys in all languages.

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

    If you want to add/update translations for the keys please read this guide here.

Files NOT translated on our translations platform:

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

    Changes to these files are typically done by the staff team. If you see something out of the ordinary we recommend you reach us in the contributors chat room.

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.Arabic
],
...
};

If you are testing a new language, create a folder with the language name as the title next to the other languages and copy the JSON files from another language into your new folder.

Add the new language to the Languages enum and the client array at the top of the shared/config/i18n.ts file.

Next, follow the instructions in the comments in the same file to add/update the rest of the variables as needed.

Finally, set the CLIENT_LOCALE variable in your .env file to the string of the locale you want to build from the Languages enum in the above file.

How to Structure Components

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.

Functional Component

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

Class Component

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

Translate Using the β€œt” Function

Basic Translation

// in the component:
<p>{t('p1')}</p>
// in the JSON file:
{
"p1": "My paragraph"
}
// output:
<p>My paragraph</p>

With Dynamic Data

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

Translate with the Trans Component

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.

Basic Elements Nested

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

Complex Elements Nested

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.

With a Variable

// in the component:
const email = '[email protected]';
<p>
<Trans email={email} 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:[email protected]'>[email protected]</a><p>

In the above example, the key and a variable are set in the attributes of the Trans component. {{ email }} needs to be somewhere in the Trans component as well, it doesn’t matter where.

Changing Text

To change text on the client side of things, go to the relevant .json file, find the key that is being used in the React component, and change the value to the new text you want. You should search the codebase for that key to make sure it isn’t being used elsewhere. Or, if it is, that the changes make sense in all places.

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

Adding Text

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. If you need to add a new key, add it there. Then, add the key to all of the translations.json files.

It would be nice to keep the keys in the same order across all the files as well. Also, try to put all punctuation, spacing, quotes, etc. in the JSON files and not in the components or server files.

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

Proposing a Pull Request (PR)

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

Helpful Documentation