Let’s get started with a simple example using the internationalization capabilities of this framework. To set up a project with i18n, we’ll follow these steps:
BaseI18nController
class. We will need to override the load
method to tell the controller where to get the translations from, and which translations to use.I18nProvider
component (default export) at the top level of the application and give the controller object to this component. This will give the application the ability to utilize the controller and the i18n components to achieve internationalization throughout the app.I18nSection
and I18nText
to render translatable text in the application. We’ll use these components to tell the controller which translations we need and where to pull them from.Project Fluent designed a simple, powerful syntax for writing translations for internationalization. This project makes use of that syntax, and expects translations to use that syntax. Again, if you are not familiar with the ftl syntax, please take the time to read about it here.
Now, let’s put that syntax to use:
// en/global.js
export default `
welcome-message = Welcome, { $user }!`
// en/accounts.js
export default `
welcome-message = Welcome to the accounts page, { $user }!`
// es/global.js
export default `
welcome-message = ¡Bienvenido, { $user }!`
// es/accounts.js
export default `
welcome-message =
¡Bienvenido a la página de cuentas, { $user }!`
Notice that we put translations in two separate folders; one for English and one for Spanish. We also made global.js
and accounts.js
. This is because of how the i18n framework utilizes sections for organization and efficiency. We’ll discuss that more later. For now, let’s move on to extending the BaseI18nController
class.
BaseI18nController
In order to use the internationalization features offered by Cactus Framework, we’ll need to extend the BaseI18nController
class, so we can override the load()
method. This method will need to load ftl translations based on language and section and return that content so that it can be used by the controller.
Let’s see what this might look like:
// i18nController.js
import { BaseI18nController } from '@repay/cactus-i18n'
class I18nController extends BaseI18nController {
async load(sectionInfo) {
const { lang, section } = sectionInfo
// load ftl translations from the source
const { default: ftl } = await import(`./locales/${lang}/${section}.ftl`)
return { resources: [ftl] }
}
}
const controller = new I18nController({ defaultLang: 'en', supportedLangs: ['en', 'es'] })
export default controller
In this example, we assume that the translations are stored locally in a folder called locales
. We use the language and section to load the translations needed, and then return them. Now that we’ve extended the BaseI18nController
class, we can start using <I18nProvider />
.
<I18nProvider />
The I18nProvider
component is designed to be a top-level wrapper component that can provide all of its children (the rest of the application) with the means to make internationalization happen. Let’s see how this would look:
// index.js
import I18nProvider from '@repay/cactus-i18n'
import i18nController from './i18nController.js'
const mainComponent = () => {
return (
<I18nProvider controller={i18nController} lang="en">
...
</I18nProvider>
)
}
Here, we make <I18nProvider />
the top-level component in our app, and we provide it with an instantiated object of the I18nController
class that we set up earlier. Providing this component with the controller using the controller
prop will allow us to use i18n features. Now, let’s start using some other tools to display some text.
Now that our setup is complete, we can add on to the work we did in the last step to render some translations. We’ll use two extra components: I18nSection
and I18nText
. I18nSection
can be used to tell the controller which section to load (In this case, we have either “global” or “accounts”). If you don’t use I18nSection
to specify a section to choose a translation from, the controller will use the section specified by the I18nProvider
(default “global”). Let’s add on to what we did in the previous step with some examples.
// index.js
...
const mainComponent = () => {
return (
<I18nProvider controller={i18nController} lang="en">
<I18nText get="welcome-message" args={{ user: "CS Human" }} />
</I18nProvider>
)
} // Welcome, CS Human!
Notice that we told I18nText
that we wanted to get the welcome-message
translation, but we didn’t use an I18nSection
component. This means the controller will look for the translation in the default section “global”. We also made use of the args
prop to pass an object that contains the value for the user
variable in the translation. But what if we had used a section? Let’s check it out:
// index.js
...
const mainComponent = () => {
return (
<I18nProvider controller={i18nController} lang="en">
<I18nSection name="accounts">
<I18nText get="welcome-message" args={{ user: "CS Human" }} />
</I18nSection>
</I18nProvider>
)
} // Welcome to the accounts page, CS Human!
Notice that we used the same get
value on I18nText
, but by making the I18nText
component as a child of I18nSection
, we told the controller that we should be loading translations for the “accounts” section, instead of the default. What if we wanted to load translations for a different language, though? To do so, we’ll just need to change the lang
prop on I18nProvider
:
// index.js
...
const mainComponent = () => {
return (
<I18nProvider controller={i18nController} lang="es">
<I18nSection name="accounts">
<I18nText get="welcome-message" args={{ user: "CS Human" }} />
</I18nSection>
</I18nProvider>
)
} // ¡Bienvenido a la página de cuentas, CS Human!
At this point, we have successfully set up a small example with the ability to render different translations for different sections. If you want to see a working example, you can check out the standard implementation here. It uses a lot of these same ideas. Additionally, we have much more detailed API docs on each tool available with internationalization here.