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
Section titled “File Structure”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.jsonfile 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.jsonfile contains the key-value pairs for the introduction text on the certification pages. -
The
motivation.jsonfiles are not required to have the same quotes, compliments, or array length. Just the same JSON structure. -
The
meta-tags.jsonfile contains the information for our website’s meta tag information.
Files maintained in the main repository for every language:
- The
links.jsonfile 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:
git submodule update --init curriculum/i18n-curriculumSet 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.
Testing a New Language
Section titled “Testing a New Language”For a language that is not yet configured, prepare these additional files before starting the client:
- Add the language to the
Languagesenum andavailableLangs.clientinpackages/shared/src/config/i18n.ts. Follow the comments to update the language codes and display name. Add right-to-left languages tortlLangs; usehiddenLangsto keep a language out of the selector until launch. - Copy the English
links.jsontoclient/i18n/locales/<language>/links.jsonand update URLs where localized resources exist. - Ensure
curriculum/i18n-curriculum/client/<language>/containsintro.json,translations.json,meta-tags.json, andmotivation.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. - Add an entry to
algoliaIndicesinclient/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. - If the language does not yet have a trending file on the CDN, copy the generated
client/i18n/locales/english/trending.jsonfrom your English setup toclient/i18n/locales/<language>/trending.json. The download script uses this local file as a fallback in development. Production requires abuild/universal/trending/<language>.yamlfile 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.
How to Structure Components
Section titled “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
Section titled “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 belowClass Component
Section titled “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
Section titled “Translate Using the “t” Function”Basic Translation
Section titled “Basic Translation”// in the component:<p>{t('p1')}</p>
// in the JSON file:{ "p1": "My paragraph"}
// output:<p>My paragraph</p>With Dynamic Data
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “With a Variable”// 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.
Changing Text
Section titled “Changing Text”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.
Adding Text
Section titled “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. 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.
Proposing a Pull Request (PR)
Section titled “Proposing a Pull Request (PR)”After you’ve committed your changes, check here for how to open a Pull Request.