Localization
Overview
Some component properties can be localized. In order for a component property to be localizable, the following conditions must be met - the property must be of type string and the notLocalize method has not been called for this property.
Here is an example of a component where the name
property is localized, but the value
property is not:
import {define, string} from '@react-form-builder/core'
interface MyComponentProps {
name: string
value: string
}
const MyComponent = (props: MyComponentProps) => {
return <span>
{props.name}: {props.value}
</span>
}
export const myComponent = define(MyComponent)
.name('MyComponent')
.type('MyComponent')
.props({
name: string,
value: string.notLocalize
})
Example of localization
The form builder uses the Fluent library to localize component properties. We recommend that you follow this link and familiarize yourself with the capabilities of this library.
For the example, we will create a form to display the data as in the example on the Fluent website. The form will have a field for entering the username, fields for entering the number of photos and selecting the gender. At the bottom of the form there will be two static fields to display localized data.
You can simply copy the JSON below and upload it to the public demo.
{
"version": "1",
"form": {
"key": "Screen",
"type": "Screen",
"props": {},
"children": [
{
"key": "userName",
"type": "RsInput",
"props": {
"label": {
"value": "User name"
}
}
},
{
"key": "photoCount",
"type": "RsNumberFormat",
"props": {
"label": {
"value": "Photo count"
}
}
},
{
"key": "userGender",
"type": "RsRadioGroup",
"props": {
"name": {
"value": "Gender"
},
"label": {
"value": "Gender"
},
"items": {
"value": [
{
"value": "male",
"label": "Male"
},
{
"value": "female",
"label": "Female"
},
{
"value": "unspecified",
"label": "Unspecified"
}
]
}
}
},
{
"key": "RsStaticContent 1",
"type": "RsStaticContent",
"props": {
"content": {
"value": "Greetings",
"computeType": "localization"
}
}
},
{
"key": "RsStaticContent 2",
"type": "RsStaticContent",
"props": {
"content": {
"value": "shared photos",
"computeType": "localization"
}
}
}
]
},
"localization": {
"en-US": {
"RsStaticContent_1": {
"component": {
"content": "Hello, {$userName}!"
}
},
"RsStaticContent_2": {
"component": {
"content": "{$userName} {$photoCount ->\n [one] added a new photo\n *[other] added {$photoCount} new photos\n } to {$userGender ->\n [male] his stream\n [female] her stream\n *[other] their stream\n }."
}
}
},
"de-DE": {
"RsStaticContent_1": {
"component": {
"content": "Hallo, {$userName}!"
}
}
},
"fr-FR": {
"RsStaticContent_1": {
"component": {
"content": "Bonjour, {$userName}!"
}
}
}
},
"languages": [
{
"code": "en",
"dialect": "US",
"name": "English",
"description": "American English",
"bidi": "ltr"
},
{
"code": "de",
"dialect": "DE",
"name": "Deutsch",
"description": "German",
"bidi": "ltr"
},
{
"code": "fr",
"dialect": "FR",
"name": "Français",
"description": "French",
"bidi": "ltr"
}
],
"defaultLanguage": "en-US"
}
Click on the "Preview" button to test the form. The form language switch is set to "English", which means that localization for English will be applied. The default language for the form is also set to English, see line 127 in JSON.
Let's enter the data into the form. In the "User name" field enter the value "Mark", in the "Photo count" field set the value "3", and in the "Gender" field select "Male".
As you enter data, you will see how the value in the text at the bottom of the form changes. After entering the data, you should get a form like this:
Great, let's now switch the language to German in the language switcher on the toolbar. Notice that only the welcome text has changed. This is because only the welcome text is translated into 3 languages - English, German and French (lines 80, 92, 99 in JSON respectively).
Switch the language of the form to French, and the welcome text will change as well.
You can also try switching the form language to other languages and see what happens. If there is no localization for some language - the localization for the default language will work.
Localization editing
Click the "Edit" button to return to editing the form. In the panel on the left you can switch to the "Settings" section, where the form settings are located. In the "Language settings" section you can see the default language settings.
Now select the "RsStaticContent 2" component on the form. In the right pane you can see a blue globe icon next to the "Content" property - this means that the property is defined using localization.
Click on the blue globe icon. And switch to English in the language switcher under the code editor. This language switch changes the language for the localization editor. On the right is a panel with test form data, where you can change the data and check how this or that localization will work.
Switch the language under the code editor to German and paste the text "Vielen Dank!" as in the screenshot below. On the right side you can click the "Run" button to get the localization result. Or select the "Autorun" checkbox to have the localization result recalculated automatically when the data changes. To save the data - click the "Save" button.
Also if you need to access data within nested objects, you can do so using dot notation. This method allows you to retrieve values at deeper levels by specifying the full path.
Click the "Close" button to close the localization editor. Change the language of the form in the switch above, as on the screenshot - the text will change to the one we just wrote.
Custom localization
If you want to use your localization system, you can plug it in. To do this, you need to pass the localization function through the localize property. The parameters are passed to the function input:
- localization language.
- component settings.
Your function should return a Record
with the localized values of the component's properties. If the function does not localize the
component properties, it should return an empty object. A simple example of a localization function:
function localizer(componentStore: ComponentStore, language: Language): Record<string, any> {
return componentStore.key === 'comment'
? {'placeholder': `${language?.fullCode || 'default language'}`}
: {}
}
This function localizes only the placeholder
property and only on a component with the comment
key. The placeholder
property is set to
the full code of the selected language. You can pass the current language through
the language property.
Additional localization functions
In addition to localization properties and functions mentioned above, there are several functions that allow you to customize specific aspects of localization. For example, such functions can determine the direction of text, which is crucial for certain languages.
withViewerWrapper
The withViewerWrapper allows you to add a
wrapper to the form viewer. This wrapper can be utilized to modify various localization properties and provide a customized experience
for different languages or locales. The wrapper component accepts properties of
type FormViewerWrapperComponentProps. You can define
your own wrapper for your component library and pass it to the withViewerWrapper
function.
One practical example of using the withViewerWrapper
function is by implementing
the RsLocalizationWrapper for React Suite
components, i.e.:
view.withViewerWrapper(RsLocalizationWrapper)
This wrapper allows you to configure localization settings for components. For example, this is how the RsLocalizationWrapper
component
implementation looks like:
export const RsLocalizationWrapper = ({language, children}: FormViewerWrapperComponentProps) => {
return <CustomProvider rtl={language.bidi === 'rtl'}
locale={rSuiteLocales[language.fullCode] ?? defaultComponentsLocale}>
{children}
</CustomProvider>
}
Let's take a closer look at what this code does. The RsLocalizationWrapper
function returns
the CustomProvider component, setting the rtl
property to true
if
the language has the bidi property set to rtl
, and also sets the locale
for the components. The children
are the child components to be wrapped.
You can write a similar wrapper if you use MUI components, for example.
withCssLoader
The withCssLoader allows you to apply a CSS loader to the component based on the BiDi (Bidirectional) layout. This enables you to load specific CSS styles based on the text direction and provides flexibility in customizing the component's appearance.
Here are examples of how the withCssLoader function can be implemented:
view
.withCssLoader(BiDi.LTR, ltrCssLoader)
.withCssLoader(BiDi.RTL, rtlCssLoader)
.withCssLoader('common', formEngineRsuiteCssLoader)
The first loader, ltrCssLoader, provides a CSS loader for left-to-right (LTR) text direction. This loader will be applied to the component when the text direction is LTR. Similarly, on the next line is a CSS loader for right-to-left (RTL) text direction, rtlCssLoader. This loader will be applied when the text direction is RTL. Finally, the last line specifies a CSS loader named formEngineRsuiteCssLoader, which is loaded regardless of the text direction.
The following snippet showcases how the loaders can be implemented:
/**
* Represents a relationship attribute value used in HTML.
*/
export type Rel = 'stylesheet' | string
/**
* Loads a resource into the document head asynchronously.
* @param id the identifier of the resource.
* @param href represents a URL to the resource.
* @param rel the relationship of the resource to the document.
* @returns the promise that resolves when the resource has been loaded successfully.
*/
export const loadResource = (id: string, href: string, rel: Rel) => {
return new Promise<void>((resolve, reject) => {
if (document.getElementById(id)) return resolve()
const link = document.createElement('link')
link.id = id
link.rel = rel
link.href = href
link.onload = () => {
resolve()
}
link.onerror = reject
document.head.appendChild(link)
})
}
/**
* Unloads a resource from the DOM based on its ID.
* @param id the ID of the resource to unload.
*/
export const unloadResource = (id: string) => {
const link = document.getElementById(id)
link?.parentNode?.removeChild(link)
}
export const ltrCssLoader = async () => {
const href = (await import('../public/css/rsuite-no-reset.min.css?url')).default
await loadResource(resourceIds[BiDi.LTR], href, 'stylesheet')
unloadResource(resourceIds[BiDi.RTL])
}
export const rtlCssLoader = async () => {
const href = (await import('../public/css/rsuite-no-reset-rtl.min.css?url')).default
await loadResource(resourceIds[BiDi.RTL], href, 'stylesheet')
unloadResource(resourceIds[BiDi.LTR])
}
export const formEngineRsuiteCssLoader = async () => {
const href = (await import('../public/css/formengine-rsuite.css?url')).default
await loadResource('form-engine-css', href, 'stylesheet')
}
In the example above, the ltrCssLoader
function loads the Left-To-Right (LTR) CSS for the React Suite library. It imports the CSS file and
uses the loadResource
function to load the CSS dynamically. Similarly, the rtlCssLoader
function loads the Right-to-Left (RTL) CSS for
the React Suite library. It imports the RTL CSS file and uses the loadResource
function to load it. The formEngineRsuiteCssLoader
function loads custom styles for the FormEngine over the React Suite library.
Setting the form language
To set the language of the form, you can pass the current language through the language property:
<FormViewer
language={language}
{...otherProps}
/>
Conclusion
We have shown how localization of component properties works in Form Builder. Localization works the same way as computed properties work, except that the Fluent language is used for localization. We also showed that localization works reactively when data changes.