First intl setup you should make (for React).
There are many things you should consider when you set up a frontend for production. One of these is internationalization. But, most of the cases you might think:
- Can I add it later without losing resources?
- What will be the benefits?
- Will adding that framework add much complexity?
- Becomes automatic testing even harder?
Therefore, I want to answer the above questions regarding first-intl
.
-
You should not wait to add
first-intl
. It is easier to extend your translations while developing than adding all those translations afterwards. -
You will benefit from more readable and robust source and test code.
-
In comparison to other frameworks
first-intl
will increase the overall complexity very slightly. -
Your tests wouldn't change at all. But you should consider to add some lines to your test configuration that will make your tests more readable and robust. Since your tests would only rely on message keys instead of whole translations.
pnpm add first-intl
yarn add first-intl
npm install --save first-intl
First let's define some translations inside a json file like so:
{
"header.logo.alt": "Welcome Logo",
"footer.navigation.home": "Home",
"account.back": "Revoke submission",
"account.remove": "Delete account",
"account.remove.info": "After deleting your account for {email}, you will be consequently distrusted.",
}
Then your code for any component would look like:
import { __, addIntlData } from 'first-intl';
import intlJson from './path/to/intl.json';
// first the required translations
// usually you would add your base translations inside the index.js
// but that is up to you
addIntlData(intlJson);
// you are ready to go
export const App = () => (
<div>
<header><img src="/path/to/img" alt={__string('header.logo.alt')} /></header>
<main>
<button>{__('account.remove')}</button>
{__({ id: 'account.remove.info', values: { email: '[email protected]' } }, info => <p>{info}</p>)}
</main>
<footer>
<a href="/path/to/home">{__('footer.navigation.home')}</a>
</footer>
</div>
);
To render directly strings which is required when you want to insert translations inside HTML attributes like "alt" or "placeholder" etc. you may also take the following function which basically ensures that you'll receive a string.
export const Header = () => (
<header><img src="/path/to/img" alt={__string('header.logo.alt')} />)</header>
);
You can additionally customize some internal behaviour by calling configure
:
import { configure } from 'first-intl';
// this is actually the default tracker
// you can track those error occurrences at your backend
// it is absolutely recommended to NOT ignore but fix those errors
// see further below for more detailed information
const myTracker = (errorDescription: string): void =>
window.console.error(errorDescription);
// this will replace the whole currently used intl data
const myIntlData = { 'abort': 'ματαίωση' };
// this will replace the default renderer which will be used if
// you use "__" without custom renderer
const myRenderer: (contents: string | React.ReactNode[]) => React.ReactNode = `<<YOUR_IMPLEMENTATION>>`;
// left keys won't affect your current configuration
configure({
tracker: myTracker,
intlData: myIntlData,
renderer: myRenderer,
})
The last thing to mention for production is the configured tracker. By default the dev console will display error occurrences. But you can configure whatever tracker you'd like. I recommend to track those occurrences to see if any of your users are encountering those errors.
What kind of errors will be tracked?
- If the provided key does not belong to any message in your current intl data. ('No translation for key: foo.bar')
- If you've specified values although the message do not use any placeholders. ('Ignoring specified values for: foo.bar')
- If you've specified more placeholders than necessary. ('Redundant placeholders for: foo.bar')
- If you've specified to few placeholders. ('Missing placeholder "phone" for: entered.phone')
For testing, I recommend to overwrite the behaviour of the intl function for all tests, inside your setupTests.js
import intlData from './your/intl-data.json';
import { __internal, configure, type Message } from 'first-intl';
// make your tests fail if they would produce any intl errors
configure({
tracker: (str: string) => {
throw new Error(str);
},
intlData,
});
// make all validations and render an informative string that does not contain translations
const oldRender = __internal.render;
__internal.render = (msg: Message | string, renderer: (arg: any) => any = (s) => s): any => {
// make all the validations
oldRender(msg, renderer);
// return rendered message without translated content (for stable test snapshots and assertions)
// you may use other serializers than "JSON.stringify" but for the start it is sufficient
return renderer(
typeof msg === 'string'
? `__('${msg}')`
: `__({ id: '${msg.id}'${msg.values ? ', values: ' + JSON.stringify(msg.values) : ''} })`
);
};
The made comparison is related to react-intl
. You'll benefit from more
development comfort and do not have to adjust your testing behaviour dramatically.
You can access even translations as strings easily. You are flexible in adding
new translations dynamically and are able to override existing translations
in favor to some switched language. It seems almost perfect to consider
first-intl
BUT you are not anymore able to switch all messages without
losing internal state of your React tree. This is the single most important aspect
why react-intl
and other alike frameworks are that complex. To switch all messages
with first-intl
and let your current UI take effect, you'd possibly need to change
a "key" property at your highest component in the tree that contains all translations.
This will destroy your current internal state and let's React rerender everything.
But I never had that requirement. It was always fine to lose your current state if the user switches the desired language. Most sites even deliver statically different content with even different URLs for different languages.
first-intl
being that minimalistic also misses functionality to translate e.g. dates
for the end user. You'd have to implement your own or install further packages to handle
this for you.