Documenting how we handle translations in the code
We use react-intl to manage our translations. They are extracted from the code to src/lang/${locale}.json files using the npm run build:langs command (CI will notify you if the translation files are outdated). Don't translate the strings directly in the files, we use Crowdin to manage our translatations.
Good practices
Use "select" when a value has a limited number of options
Example
<FormattedMessagedefaultMessage="{action, select, delete {Delete this} archive {Archive this} other {Do something with this}}"values={{ action:'delete' }}/>// => "Delete this"<FormattedMessagedefaultMessage="{action, select, delete {Delete this} archive {Archive this} other {Do something with this}}"values={{ action:'eat' }}/>// => "Do something with this"
defaultMessage string breakdown:
action variable name
select keyword
delete and archive possible values
other all other values will use this key
An exception to this rule: very common enums or the ones with many possible values should be implemented as a separate file listing all values because:
Re-usability
A map of translations is easier to read than a long select string with tons of options
Don't assume word's order stays the same in other languages
The order of the words may change from a language to another. For this reason we must always pass the values to be replaced in values so their order can later be changed.
Example
// Bad<div> <FormattedMessageid="str"defaultMessage="Pending approval from " /> <Linkroute={`/${host.slug}`}>{host.name} </Link></div>// Good<div> <FormattedMessagedefaultMessage="Pending approval from {host}"values={{ host: <Linkroute={`/${host.slug}`}>{host.name} </Link> }} /></div>
Don't split strings
Splitting a string is alway problematic, because translators loose the context: the strings may not be next to each others when they'll be translated.
// Bad<FormattedMessagedefaultMessage="Do you want to {createSomething} in this list?"values={{createSomething: ( <blink> <FormattedMessagedefaultMessage="create something"/> </blink> ) }}/>// Good<FormattedMessagedefaultMessage="Do you want to <blink>create something</blink> in this list?"values={{createSomething:functionBlinkComponent(msg) {return (<blink>{msg}</blink>) } }}/>
Use I18nFormatters to format rich text (bold, italic...etc)
Provide an ID when the translation depends on the context
In many Latin languages, the translation for a string like "Created at" will depend on the context because of the feminine/masculine forms. The best way to provide this context is to set an ID on the string:
The FormattedMessage component is the main way to translate strings. To use it, you just need to add the following import:
import { FormattedMessage } from'react-intl'
Then you just add the component with an unique id and a defaultMessage.
For VSCode users, you can use the following snippet to make your life easier:
{"Formatted Message (react-intl)": {"scope":"javascript","prefix":"formatted-message","body":"<FormattedMessage id=\"$TM_FILENAME_BASE.$0\" defaultMessage=\"$1\"/>","description":"Put the given string in a FormattedMessage" }}