The Unified Admin has built-in support for internationalization (i18n) of displayed text. Text can be supplied from:
+ * data fetched from APIs such as a Product’s name (see API Data) * messages specified in metadata such as a form field label or submit button’s text (see Metadata Text) * text controlled by the Admin App itself such as for interstitial pages, alerts, and other special components not driven by APIs or metadata.
Important
|
These translations are for display on the commerce-facing app rather than for the admin user’s benefit. To manage admin text translations like for admin menus and form field labels, see [Setting the App's Locale]. |
Data retrieved from APIs such as Product or Category data should supply their own translations based on the request’s Accept-Language
header.
This header’s value is derived from the selected locale on the entity view.
The values for this selector are driven by the resolved Application’s or Tenant’s allowedLocales
:
Tip
|
See Admin Translation Features for more details on managing an entity’s translations. |
Things like form field labels, submit button text, hints, and tooltips are defined using metadata. This also includes translations for them. These will show translated text using a different locale-selector than with API Data since they aren’t related to customer-facing data.
Tip
|
See [Setting the App's Locale] for details on that selector. |
The Metadata service will receive a request from the Admin that includes the desired locale and will return already-translated metadata if any translations are present.
Translations should be defined in message properties files in your MicroservicesDemo project under /services/metadata/src/main/resources/messages
.
This take advantage of Spring’s localization support.
For example, create a product.properties
for the default translations and a product_es.properties
for Spanish translations:
product.properties
product.fields.name=Name
product_es.properties
product.fields.name=Nombre
Then, in your metadata, refer to the properties by key instead of using static text:
Fields.string()
.name(ProductProps.NAME)
.label("product.fields.name") // <-- Spring will replace this with the localized text
.order(1000);
To make sure Spring knows about your properties files, make sure to register a MetadataMessagesBasename
bean that specifies the file paths in META-INF/spring.factories
.
package com.broadleafdemo.metadata.i18n;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import com.broadleafcommerce.metadata.i18n.MetadataMessagesBasename;
import java.util.Arrays;
public class DemoMetadataMessages implements MetadataMessagesBasename {
@Override
@NonNull
public String getBasename() {
return StringUtils.join(Arrays.asList(
"messages/demo-catalog",
"messages/demo-pricing",
"messages/demo-offer"),
",");
}
}
spring.factories
com.broadleafcommerce.metadata.i18n.MetadataMessagesBasename=\ com.broadleafdemo.metadata.i18n.DemoMetadataMessages
Static text is text that is a hard-coded part of the code bundle and is not provided by one of the various services or metadata. This includes text for the "Logout" header-menu item and the sandbox ribbon status. Translating this text is supported using the react-intl library from FormatJS. This is an abstraction layer on top of the browser’s built in JS Intl APIs.
Like with Metadata Text, the locale used to determine which translations for static text are shown is derived from the top-level selector in the header:
The values of this selector are determined by the following runtime property:
VITE_ALLOWED_LOCALES=en,en-US,es-MX,es-ES,fr,fr-CA
Static text is made translatable by defining a react-intl message descriptor. This contains an id for the message, a default translation, and, optionally, a description. The id is used to identify which message is which, allowing us to override text specifically for a given locale.
Important
|
This should be used when defining new, custom text. |
import { defineMessages } from 'react-intl';
export default defineMessages({
logoutLabel: {
// this is the ID that is used as a target for translations in different locales
id: 'LogoutButton.logout',
// it is a important to always give a defaultMessage
defaultMessage: 'Logout',
// optionally provide a description as helpful hint or documentation of the descriptor
description: 'This is used for representing the text within the logout button.'
}
});
As a convenience, we’ve also included a script in the Admin Starter (build-langs.js
) that can be run that compiles all of these message descriptors and produces a JSON file that can be sent off for translation of the default messages.
Just run yarn build:langs
.
Important
|
Make sure your message descriptors are defined in files with the following naming convention: *.messages.js|ts .
|
This will output an en.json
and index.json
file in /messages
.
Tip
|
To add new translations, just define files in /messages with a name like <locale>.json : fr.json , es.json , de.json .
The script will see these and add them to the index.json .
|
en.json
, auto-generated{
"LogoutButton.logout": "Logout"
}
es.json
, result of a translator service{
"LogoutButton.logout": "Cerrar Sesión"
}
index.json
, auto-generated{
"en": {
"LogoutButton.logout": "Logout"
},
"es": {
"LogoutButton.logout": "Cerrar Sesión"
}
}
This can be merged with the bundled messages from the Broadleaf components library to override the defaults and contribute new translations.
Just import the index.json
file into the App.jsx
:
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { AdminApp, AdminProvider, utils } from '@broadleaf/admin-components';
import baseMessages from '@broadleaf/admin-components/messages';
import customMessages from 'messages';
const messages = {...baseMessages, ...customMessages };
const browserSupportsHistory = 'pushState' in window.history;
const App = () => (
<Router
basename={utils.Environment.get('public.url')}
forceRefresh={!browserSupportsHistory}
>
<AdminProvider messages={messages}>
<AdminApp />
</AdminProvider>
</Router>
);
export default App;
Tip
|
build-langs.js is included in the Admin Starter, meaning you can customize it anyway you like: The code is yours.
|
Now that we have our message descriptor, we need to actually use that message descriptor. This step has a couple of options that depend on the component you are working with.
FormattedMessage
ComponentThe first option is to use the FormattedMessage
component provided by react-intl
:
import { FormattedMessage } from 'react-intl';
const logoutMessageDescriptor = {
// this is the ID that is used as a target for translations in different locales
id: 'LogoutButton.logout',
// it is a important to always give a defaultMessage
defaultMessage: 'Logout',
// optionally provide a description as helpful hint or documentation of the descriptor
description: 'This is used for representing the text within the logout button.'
};
const LogoutButton = ({ handleLogout }) => {
return (
<button onClick={handleLogout} type="button">
<FormattedMessage {...logoutMessageDescriptor}/>
</button>
);
}
useIntl
hookThe second option is to use the useIntl
hook:
import { useIntl } from 'react-intl';
const logoutMessageDescriptor = {
// this is the ID that is used as a target for translations in different locales
id: 'LogoutButton.logout',
// it is a important to always give a defaultMessage
defaultMessage: 'Logout',
// optionally provide a description as helpful hint or documentation of the descriptor
description: 'This is used for representing the text within the logout button.'
};
const LogoutButton = ({ handleLogout }) => {
const formatMessage = useIntl().formatMessage;
return (
<button onClick={handleLogout} type="button">
{formatMessage(logoutMessageDescriptor)}
</button>
);
}