Translate your application
International e-commerce websites often need to translate their UI into several languages to make sure that they reach a broader range of customers. This documentation will show how this works for static content in your application.
If you want to configure your application to support multiple languages, please refer to Configure multiple stores.
Front-Commerce manages your translations by using react-intl, a standard library in the React ecosystem. This library works by transforming your static values in React Components. These components will then fetch your translations and display them in your application.
Declare translations in your application
You can find the full set of components in the React Intl Components documentation.
For instance, let's see how to transform your values in react-intl
Components.
Strings
import { FormattedMessage } from "react-intl";
<FormattedMessage
id="My.Component.Id.messageId"
defaultMessage="Default translation"
/>;
To take full advantage of the translate
cli in Front-Commerce, a default
message is required for formatted messages. This will be used to extract the
messages for the default locale and ensure there are no dead translations, or
missing translations.
Dates
import { FormattedDate } from "react-intl";
<FormattedDate value={date} year="numeric" month="long" day="2-digit" />;
Prices
import { FormattedNumber } from "react-intl";
<FormattedNumber
value={value.amount}
style="currency"
currency={value.currency}
/>;
However, for this particular use case we have abstracted this in Front-Commerce
with the
theme/components/atoms/Price
component (and its variants: ProductPrice
, PriceOrFree
). We recommend you to
use it instead, so you could benefit from better integration with its related
GraphQL Type.
Without React Component
These components will be enough in most cases. However, the issue with these is that it wraps your string within a React Element, which may be troublesome when you actually need to handle the string itself.
For instance, this is the case when you want to add some label attributes to your DOM elements.
<span class="icon" aria-label="Icon title displayed for screen readers" />
Fortunately, this is correctly handled by react-intl
if you use
defineMessages
combined with the
useIntl
hook or the
injectIntl
HOC.
import { defineMessages, useIntl } from "react-intl";
const messages = defineMessages({
ariaLabel: {
id: "screen-reader-icon-title",
defaultMessage: "Icon title displayed for screen readers",
},
});
const MyComponentWithHook = (props) => {
const intl = useIntl();
return (
<span class="icon" aria-label={intl.formatMessage(messages.ariaLabel)} />
);
};
You should always define a defaultMessage
, and preferably in your default
locale which you will use with the translate
command. This will allow
FormatJS
to extract the existing messages for your application.
Translate what's in your components
Now that you have defined what should be translated in your application, you actually need to translate your application. This is a two-step process:
-
Run the following script that will fetch all your translatable strings in your application and gather them in a JSON file located in
translations/[locale].json
$ front-commerce translate <path> --locale <locale>
ProTip™The script has already been defined in the skeleton template, and can be used directly with
pnpm run translate
. You can change the default locale to accommodate your store. -
Translate the strings available in those files, and you are good to go 🙂
If your default locale is
en
and you want to add support forfr
you can create afr.json
in yourtranslations
folder and translate the strings for each key.ProTip™You can use tools like BabelEdit to help translate and keep your translations in sync.
Translations fallback
With react-intl
translations are usually grouped into a single file. In our
case, we would expect them to be in translations/[locale].json
. but we don't
want you to be troubled by translations that are handled by the core.
That's why Front-Commerce uses a mechanism called translations fallbacks. Instead of relying on a single file for translations, Front-Commerce will look out for translations that are defined by extensions
From your extension declared in front-commerce.config.js
If you are developing an extension, you will need to add the path to your
translations in the extension configuration file, for example lets say your
translations are located in <my-extension>/translations/[locale].json
.
You can then run the following command command to extract the translations from your extension:
$ pnpm run front-commerce translate <my-extension>/**/*.{js,jsx,ts,tsx} --locale <locale>
And then in your extension definition file you should add the translations
option:
import { defineExtension } from "@front-commerce/core";
export default defineExtension({
// ...
translations: `extensions/acme-extension/translations`, // path to your translations
});
This will extract the translations in your extension to the following file:
<my-extension>/translations/<locale>.json
It will also allow it to be injected into the generated translation file.
From your project
lang/[short-locale].json
orlang/[locale].json
[short-locale]
here means that we are only taking the first particle of the
locale
. E.g. if the locale was en-GB
, the short-locale would be en
. That's
how en-GB
would load translations from both en.json
and en-GB.json
files.
If a translation key is defined in multiple files, the last one (according to the above list) will be be used. This is especially useful if you want to change the core's translations.
You can see exactly which translation files are used by opening the files located in .front-commerce/compiled-lang/.
With react-intl
translations are usually grouped into a single file. In our
case, we would expect them to be in lang/[locale].json
, we don't want you to
be troubled by translations that are handled by the core.
Please keep in mind that when you run pnpm run translate
, new keys will be
added to lang/[locale].json
as the script only detects locales in the app
folder.
About dynamic content
react-intl
lets you translate your static content. But what about dynamic
content? Things like product title and description, CMS pages, etc.
This is done on the GraphQL side. The principle is that when a user is connected
to Front-Commerce, they will be assigned a storeViewCode
in their session (see
Configure multiple stores for
more details).
This code will then be used by your GraphQL loaders to retrieve the correct content from your backend. Learn more about GraphQL loaders.
Loaders and Meta function
The
FrontCommerceApp
instance exposes the intl
object which can be
used to translate strings in your loaders, which can then be used in in the meta
function.
import { json } from "@front-commerce/remix/node";
import type { LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { FrontCommerceApp } from "@front-commerce/remix";
import { defineMessage } from "react-intl";
const documentTitle = defineMessage({
id: "pages.my-route.document-title",
defaultMessage: "My Route",
});
export const loader = async ({ context }: LoaderFunctionArgs) => {
const app = new FrontCommerceApp(context.frontCommerce);
return json({
title: app.intl.formatMessage(messages.documentTitle),
});
};
export const meta: MetaFunction = (args) => {
// This will contain the translated title returned from the loader function.
return [{ title: args.data.title }];
};