Localization and Internationalization
FormEngine Core provides comprehensive support for localization and internationalization, allowing you to create forms that can be displayed in multiple languages. This guide covers all aspects of localization in FormEngine, from basic property localization to advanced validation error translation.
Types of Localization
FormEngine supports two main types of localization:
- Component Properties Localization - Localization of component properties such as labels, placeholders, tooltips, and other text properties.
- Validation Error Localization - Localization of validation error messages for form validation rules.
Localization in JSON Forms
Localization data is stored directly within the form JSON, making forms self-contained and easily distributable across different language environments.
Basic Structure
A localized form includes three key properties:
{
"form": {
// Form definition
},
"localization": {
"en-US": {
// English translations
},
"de-DE": {
// German translations
}
},
"languages": [
{
"code": "en",
"dialect": "US",
"name": "English",
"description": "American English",
"bidi": "ltr"
},
{
"code": "de",
"dialect": "DE",
"name": "Deutsch",
"description": "German",
"bidi": "ltr"
}
],
"defaultLanguage": "en-US"
}
The localization Property
The localization property contains translations organized
by language code and component. Each language code (like en-US, de-DE) maps to an object containing translations for specific components:
{
"localization": {
"en-US": {
"componentKey1": {
"component": {
"label": "Enter your name",
"helperText": "Type here..."
}
},
"componentKey2": {
"component": {
"label": "Email address"
}
}
},
"de-DE": {
"componentKey1": {
"component": {
"label": "Geben Sie Ihren Namen ein",
"helperText": "Hier eingeben..."
}
},
"componentKey2": {
"component": {
"label": "E-Mail-Adresse"
}
}
}
}
}
Variables in Localized Strings
Localized strings can include variables that are replaced at runtime with actual values. Variables are enclosed in curly braces with a
dollar sign prefix: {$variableName}.
Example:
{
"localization": {
"en-US": {
"componentKey": {
"component": {
"content": "Welcome, {$userName}! You have {$messageCount} new messages."
}
}
}
}
}
At runtime, {$userName} and {$messageCount} will be replaced with actual values provided to the component. This allows for dynamic
content within localized strings.
Variable names are case-sensitive and should match the property names available in the component's data context.
The languages Property
The languages array defines which languages are available for the form. Each language includes:
code: Language code (e.g., "en")dialect: Regional dialect (e.g., "US")name: Language name in its native scriptdescription: Language descriptionbidi: Text direction ("ltr" for left-to-right, "rtl" for right-to-left)
The defaultLanguage Property
The defaultLanguage property specifies which language
should be used by default when the form loads. Must match one of the language codes in the localization object.
Localizing Component Properties
Component properties can be localized by
setting computeType to localization in the property
definition. This tells FormEngine to look up the actual value from the localization data.
When a property has computeType set to localization, FormEngine will look up the actual value from the localization object based on
the component key and
current language:
{
"key": "emailInput",
"type": "RsInput",
"props": {
"label": {
"computeType": "localization"
},
"helperText": {
"computeType": "localization"
}
}
}
With this approach, the actual values for label and helperText will be retrieved from:
localization["en-US"]["emailInput"]["component"]["label"](for English)localization["de-DE"]["emailInput"]["component"]["label"](for German)- etc.
Example: Basic Component Localization
{
"form": {
"key": "Screen",
"type": "Screen",
"children": [
{
"key": "langSelect",
"type": "MuiSelect",
"props": {
"items": {
"value": [
{
"value": "en-US",
"label": "American English"
},
{
"value": "es-ES",
"label": "Español"
}
]
},
"label": {
"computeType": "localization"
}
},
"events": {
"onChange": [
{
"name": "onLangChange",
"type": "custom"
}
]
}
},
{
"key": "nameInput",
"type": "MuiTextField",
"props": {
"label": {
"computeType": "localization"
},
"helperText": {
"computeType": "localization"
}
}
}
]
},
"localization": {
"en-US": {
"langSelect": {
"component": {
"label": "Language"
}
},
"nameInput": {
"component": {
"label": "Full Name",
"helperText": "Enter your full name"
}
}
},
"es-ES": {
"nameInput": {
"component": {
"label": "Nombre Completo",
"helperText": "Introduce tu nombre completo"
}
},
"langSelect": {
"component": {
"label": "Idioma"
}
}
}
},
"languages": [
{
"code": "en",
"dialect": "US",
"name": "English",
"description": "American English",
"bidi": "ltr"
},
{
"code": "es",
"dialect": "ES",
"name": "Español",
"description": "Spanish",
"bidi": "ltr"
}
],
"defaultLanguage": "en-US"
}
Live example
function App() { const form = useMemo(() => ({ "form": { "key": "Screen", "type": "Screen", "children": [ { "key": "langSelect", "type": "MuiSelect", "props": { "items": { "value": [ { "value": "en-US", "label": "American English" }, { "value": "es-ES", "label": "Español" } ] }, "label": { "computeType": "localization" } }, "events": { "onChange": [ { "name": "onLangChange", "type": "custom" } ] } }, { "key": "nameInput", "type": "MuiTextField", "props": { "label": { "computeType": "localization" }, "helperText": { "computeType": "localization" } } } ] }, "localization": { "en-US": { "langSelect": { "component": { "label": "Language" } }, "nameInput": { "component": { "label": "Full Name", "helperText": "Enter your full name" } } }, "es-ES": { "nameInput": { "component": { "label": "Nombre Completo", "helperText": "Introduce tu nombre completo" } }, "langSelect": { "component": { "label": "Idioma" } } } }, "languages": [ { "code": "en", "dialect": "US", "name": "English", "description": "American English", "bidi": "ltr" }, { "code": "es", "dialect": "ES", "name": "Español", "description": "Spanish", "bidi": "ltr" } ], "defaultLanguage": "en-US" }), []) const getForm = useCallback(() => JSON.stringify(form), [form]) const [language, setLanguage] = useState('en-US') const actions = useMemo(() => ({ onLangChange: (e)=> { setLanguage(e.args[0] ?? 'en-US') } }), []) const initialData = useMemo(() => ({langSelect: 'en-US'}), []) return <FormViewer initialData={initialData} view={muiView} getForm={getForm} actions={actions} language={language} /> }
Example: Multiple Components with Localization
{
"tooltipType": "MuiTooltip",
"form": {
"key": "Screen",
"type": "Screen",
"children": [
{
"key": "langSelect",
"type": "MuiSelect",
"props": {
"items": {
"value": [
{
"value": "en-US",
"label": "American English"
},
{
"value": "fr-FR",
"label": "Français"
}
]
},
"label": {
"computeType": "localization"
}
},
"events": {
"onChange": [
{
"name": "onLangChange",
"type": "custom"
}
]
}
},
{
"key": "header",
"type": "MuiTypography",
"props": {
"children": {
"computeType": "localization"
}
}
},
{
"key": "email",
"type": "MuiTextField",
"props": {
"label": {
"computeType": "localization"
},
"helperText": {
"computeType": "localization"
}
},
"tooltipProps": {
"title": {
"computeType": "localization"
}
}
},
{
"key": "submitButton",
"type": "MuiButton",
"props": {
"children": {
"computeType": "localization"
}
}
}
]
},
"localization": {
"en-US": {
"header": {
"component": {
"children": "Registration Form"
}
},
"email": {
"component": {
"label": "Email Address",
"helperText": "user@example.com"
},
"tooltip": {
"title": "We'll never share your email with anyone else"
}
},
"submitButton": {
"component": {
"children": "Submit Registration"
}
},
"langSelect": {
"component": {
"label": "Language"
}
}
},
"fr-FR": {
"header": {
"component": {
"children": "Formulaire d'Inscription"
}
},
"email": {
"component": {
"label": "Adresse Email",
"helperText": "utilisateur@exemple.fr"
},
"tooltip": {
"title": "Nous ne partagerons jamais votre email avec qui que ce soit"
}
},
"submitButton": {
"component": {
"children": "Soumettre l'Inscription"
}
},
"langSelect": {
"component": {
"label": "Idioma"
}
}
}
},
"languages": [
{
"code": "en",
"dialect": "US",
"name": "English",
"description": "American English",
"bidi": "ltr"
},
{
"code": "fr",
"dialect": "FR",
"name": "Français",
"description": "French",
"bidi": "ltr"
}
],
"defaultLanguage": "en-US"
}
Live example
function App() { const form = useMemo(() => ({ "tooltipType": "MuiTooltip", "form": { "key": "Screen", "type": "Screen", "children": [ { "key": "langSelect", "type": "MuiSelect", "props": { "items": { "value": [ { "value": "en-US", "label": "American English" }, { "value": "fr-FR", "label": "Français" } ] }, "label": { "computeType": "localization" } }, "events": { "onChange": [ { "name": "onLangChange", "type": "custom" } ] } }, { "key": "header", "type": "MuiTypography", "props": { "children": { "computeType": "localization" } } }, { "key": "email", "type": "MuiTextField", "props": { "label": { "computeType": "localization" }, "helperText": { "computeType": "localization" } }, "tooltipProps": { "title": { "computeType": "localization" } } }, { "key": "submitButton", "type": "MuiButton", "props": { "children": { "computeType": "localization" } } } ] }, "localization": { "en-US": { "header": { "component": { "children": "Registration Form" } }, "email": { "component": { "label": "Email Address", "helperText": "user@example.com" }, "tooltip": { "title": "We'll never share your email with anyone else" } }, "submitButton": { "component": { "children": "Submit Registration" } }, "langSelect": { "component": { "label": "Language" } } }, "fr-FR": { "header": { "component": { "children": "Formulaire d'Inscription" } }, "email": { "component": { "label": "Adresse Email", "helperText": "utilisateur@exemple.fr" }, "tooltip": { "title": "Nous ne partagerons jamais votre email avec qui que ce soit" } }, "submitButton": { "component": { "children": "Soumettre l'Inscription" } }, "langSelect": { "component": { "label": "Idioma" } } } }, "languages": [ { "code": "en", "dialect": "US", "name": "English", "description": "American English", "bidi": "ltr" }, { "code": "fr", "dialect": "FR", "name": "Français", "description": "French", "bidi": "ltr" } ], "defaultLanguage": "en-US" }), []) const getForm = useCallback(() => JSON.stringify(form), [form]) const [language, setLanguage] = useState('en-US') const actions = useMemo(() => ({ onLangChange: (e)=> { setLanguage(e.args[0] ?? 'en-US') } }), []) const initialData = useMemo(() => ({langSelect: 'en-US'}), []) return <FormViewer initialData={initialData} view={muiView} getForm={getForm} actions={actions} language={language} /> }
Localizing Validation Errors
Validation error messages can also be localized. Each validator can have its error message specified in the localization data.
Example: Validation Error Localization
{
"tooltipType": "MuiTooltip",
"errorType": "MuiErrorWrapper",
"form": {
"key": "Screen",
"type": "Screen",
"children": [
{
"key": "langSelect",
"type": "MuiSelect",
"props": {
"items": {
"value": [
{
"value": "en-US",
"label": "American English"
},
{
"value": "de-DE",
"label": "Deutsch"
}
]
},
"label": {
"computeType": "localization"
}
},
"events": {
"onChange": [
{
"name": "onLangChange",
"type": "custom"
}
]
}
},
{
"key": "email",
"type": "MuiTextField",
"props": {
"label": {
"value": "Email"
}
},
"schema": {
"validations": [
{
"key": "email"
},
{
"key": "required"
}
]
}
},
{
"key": "age",
"type": "MuiTextField",
"props": {
"label": {
"computeType": "localization"
}
},
"schema": {
"validations": [
{
"key": "code",
"args": {
"code": " return parseInt(value) >= 18"
}
}
]
}
},
{
"key": "validateButton",
"type": "MuiButton",
"props": {
"children": {
"computeType": "localization"
}
},
"events": {
"onClick": [
{
"name": "validate",
"type": "common"
}
]
}
}
]
},
"localization": {
"en-US": {
"langSelect": {
"component": {
"label": "Language"
}
},
"email": {
"validator-email": {
"message": "Please enter a valid email address"
},
"validator-required": {
"message": "Email is required"
}
},
"age": {
"validator-code": {
"message": "You must be at least 18 years old"
},
"component": {
"label": "Age"
}
},
"validateButton": {
"component": {
"children": "Validate"
}
}
},
"de-DE": {
"langSelect": {
"component": {
"label": "Sprache"
}
},
"email": {
"validator-email": {
"message": "Bitte geben Sie eine gültige E-Mail-Adresse ein"
},
"validator-required": {
"message": "E-Mail ist erforderlich"
}
},
"age": {
"validator-code": {
"message": "Sie müssen mindestens 18 Jahre alt sein"
},
"component": {
"label": "Alter"
}
},
"validateButton": {
"component": {
"children": "Validieren"
}
}
}
},
"languages": [
{
"code": "en",
"dialect": "US",
"name": "English",
"description": "American English",
"bidi": "ltr"
},
{
"code": "de",
"dialect": "DE",
"name": "Deutsch",
"description": "German",
"bidi": "ltr"
}
],
"defaultLanguage": "en-US"
}
The validator type in the localization object uses the format validator-{validatorKey} (e.g., validator-email, validator-required).
Live example
function App() { const form = useMemo(() => ({ "tooltipType": "MuiTooltip", "errorType": "MuiErrorWrapper", "form": { "key": "Screen", "type": "Screen", "children": [ { "key": "langSelect", "type": "MuiSelect", "props": { "items": { "value": [ { "value": "en-US", "label": "American English" }, { "value": "de-DE", "label": "Deutsch" } ] }, "label": { "computeType": "localization" } }, "events": { "onChange": [ { "name": "onLangChange", "type": "custom" } ] } }, { "key": "email", "type": "MuiTextField", "props": { "label": { "value": "Email" } }, "schema": { "validations": [ { "key": "email" }, { "key": "required" } ] } }, { "key": "age", "type": "MuiTextField", "props": { "label": { "computeType": "localization" } }, "schema": { "validations": [ { "key": "code", "args": { "code": " return parseInt(value) >= 18" } } ] } }, { "key": "validateButton", "type": "MuiButton", "props": { "children": { "computeType": "localization" } }, "events": { "onClick": [ { "name": "validate", "type": "common" } ] } } ] }, "localization": { "en-US": { "langSelect": { "component": { "label": "Language" } }, "email": { "validator-email": { "message": "Please enter a valid email address" }, "validator-required": { "message": "Email is required" } }, "age": { "validator-code": { "message": "You must be at least 18 years old" }, "component": { "label": "Age" } }, "validateButton": { "component": { "children": "Validate" } } }, "de-DE": { "langSelect": { "component": { "label": "Sprache" } }, "email": { "validator-email": { "message": "Bitte geben Sie eine gültige E-Mail-Adresse ein" }, "validator-required": { "message": "E-Mail ist erforderlich" } }, "age": { "validator-code": { "message": "Sie müssen mindestens 18 Jahre alt sein" }, "component": { "label": "Alter" } }, "validateButton": { "component": { "children": "Validieren" } } } }, "languages": [ { "code": "en", "dialect": "US", "name": "English", "description": "American English", "bidi": "ltr" }, { "code": "de", "dialect": "DE", "name": "Deutsch", "description": "German", "bidi": "ltr" } ], "defaultLanguage": "en-US" }), []) const getForm = useCallback(() => JSON.stringify(form), [form]) const [language, setLanguage] = useState('en-US') const actions = useMemo(() => ({ onLangChange: (e)=> { setLanguage(e.args[0] ?? 'en-US') } }), []) const initialData = useMemo(() => ({langSelect: 'en-US'}), []) return <FormViewer initialData={initialData} view={muiView} getForm={getForm} actions={actions} language={language} /> }
Validation error messages are not automatically translated when the language is changed, but they change after calling the validate
action.
Localizing Tooltips and Modal Windows
FormEngine also supports localization of tooltips and modal windows, allowing you to provide translated help text and modal content for different languages.
Tooltip Localization
Tooltips can be localized using the tooltip type in the localization object. Component tooltip properties are typically defined in the
tooltipProps section of a component.
Example: Tooltip Localization
{
"tooltipType": "MuiTooltip",
"errorType": "MuiErrorWrapper",
"form": {
"key": "Screen",
"type": "Screen",
"children": [
{
"key": "langSelect",
"type": "MuiSelect",
"props": {
"items": {
"value": [
{
"value": "en-US",
"label": "American English"
},
{
"value": "de-DE",
"label": "Deutsch"
},
{
"value": "fr-FR",
"label": "Français"
}
]
},
"label": {
"computeType": "localization"
}
},
"events": {
"onChange": [
{
"name": "onLangChange",
"type": "custom"
}
]
}
},
{
"key": "email",
"type": "MuiTextField",
"props": {
"label": {
"value": "Email"
}
},
"tooltipProps": {
"title": {
"computeType": "localization"
}
}
}
]
},
"localization": {
"en-US": {
"langSelect": {
"component": {
"label": "Language"
}
},
"email": {
"tooltip": {
"title": "Enter your email address for notifications"
}
}
},
"de-DE": {
"langSelect": {
"component": {
"label": "Sprache"
}
},
"email": {
"tooltip": {
"title": "Geben Sie Ihre E-Mail-Adresse für Benachrichtigungen ein"
}
}
},
"fr-FR": {
"langSelect": {
"component": {
"label": "Langue"
}
},
"email": {
"tooltip": {
"title": "Entrez votre adresse e-mail pour les notifications"
}
}
}
},
"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"
}
The tooltip type in the localization object uses the format tooltip (e.g., tooltip.title).
Live example
function App() { const form = useMemo(() => ({ "tooltipType": "MuiTooltip", "errorType": "MuiErrorWrapper", "form": { "key": "Screen", "type": "Screen", "children": [ { "key": "langSelect", "type": "MuiSelect", "props": { "items": { "value": [ { "value": "en-US", "label": "American English" }, { "value": "de-DE", "label": "Deutsch" }, { "value": "fr-FR", "label": "Français" } ] }, "label": { "computeType": "localization" } }, "events": { "onChange": [ { "name": "onLangChange", "type": "custom" } ] } }, { "key": "email", "type": "MuiTextField", "props": { "label": { "value": "Email" } }, "tooltipProps": { "title": { "computeType": "localization" } } } ] }, "localization": { "en-US": { "langSelect": { "component": { "label": "Language" } }, "email": { "tooltip": { "title": "Enter your email address for notifications" } } }, "de-DE": { "langSelect": { "component": { "label": "Sprache" } }, "email": { "tooltip": { "title": "Geben Sie Ihre E-Mail-Adresse für Benachrichtigungen ein" } } }, "fr-FR": { "langSelect": { "component": { "label": "Langue" } }, "email": { "tooltip": { "title": "Entrez votre adresse e-mail pour les notifications" } } } }, "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" }), []) const getForm = useCallback(() => JSON.stringify(form), [form]) const [language, setLanguage] = useState('en-US') const actions = useMemo(() => ({ onLangChange: (e)=> { setLanguage(e.args[0] ?? 'en-US') } }), []) const initialData = useMemo(() => ({langSelect: 'en-US'}), []) return <FormViewer initialData={initialData} view={muiView} getForm={getForm} actions={actions} language={language} /> }
Modal Window Localization
Modal windows can be localized using the modal type in the localization object. Modal content typically comes from templates defined in the form, and these templates can contain localized content.
{
"key": "confirmationModal",
"type": "Modal",
"props": {
"modalTemplate": {
"value": "Template:confirmation-dialog"
}
},
"modal": {
"props": {
"cancelButton": {
"computeType": "localization"
},
"confirmButton": {
"computeType": "localization"
},
"title": {
"computeType": "localization"
}
}
}
}
Example: Modal Window Localization
Below is an example of a simple dialog box for which localization will be applied:
import type {DialogProps} from '@mui/material'
import {Button, Dialog, DialogActions, DialogContent, DialogTitle} from '@mui/material'
import {boolean, define, oneOf} from '@react-form-builder/core'
import type {SyntheticEvent} from 'react'
import {useCallback} from 'react'
export interface MyDialogProps extends DialogProps {
handleClose?: () => void
cancelButton?: string
confirmButton?: string
title?: string
}
const MyDialog = (props: MyDialogProps) => {
const {children, cancelButton, confirmButton, handleClose, onClose, title, ...rest} = props
const close = useCallback((e: SyntheticEvent, reason: 'backdropClick' | 'escapeKeyDown') => {
handleClose?.()
onClose?.(e, reason)
}, [handleClose, onClose])
const closeDialog = useCallback(() => {
handleClose?.()
}, [handleClose])
return <Dialog {...rest} onClose={close}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
{children}
</DialogContent>
<DialogActions>
<Button onClick={closeDialog}>{cancelButton}</Button>
<Button onClick={closeDialog}>{confirmButton}</Button>
</DialogActions>
</Dialog>
}
export const myDialog = define(MyDialog, 'MyDialog')
.props({
open: boolean.default(false),
fullWidth: boolean.default(false),
scroll: oneOf('paper', 'body').default('paper'),
})
.componentRole('modal')
.build()
Live example
function App() { const MyDialog = (props) => { const {children, cancelButton, confirmButton, handleClose, onClose, title, ...rest} = props const close = useCallback((e, reason) => { handleClose?.() onClose?.(e, reason) }, [handleClose, onClose]) const closeDialog = useCallback(() => { handleClose?.() }, [handleClose]) return <Dialog {...rest} onClose={close}> <DialogTitle>{title}</DialogTitle> <DialogContent> {children} </DialogContent> <DialogActions> <Button onClick={closeDialog}>{cancelButton}</Button> <Button onClick={closeDialog}>{confirmButton}</Button> </DialogActions> </Dialog> } const myDialog = define(MyDialog, 'MyDialog') .props({ open: boolean.default(false), fullWidth: boolean.default(false), scroll: oneOf('paper', 'body').default('paper'), }) .componentRole('modal') .build() const confirmationDialog = useMemo(() => ({ "form": { "key": "Screen", "type": "Screen", "children": [ { "key": "content", "type": "MuiTypography", "props": { "children": { "computeType": "localization" } } } ] }, "localization": { "en-US": { "content": { "component": { "children": "Are you sure you want to proceed with this action?" } } }, "es-ES": { "content": { "component": { "children": "¿Está seguro de que desea proceder con esta acción?" } } } }, "languages": [ { "code": "en", "dialect": "US", "name": "English", "description": "American English", "bidi": "ltr" }, { "code": "es", "dialect": "ES", "name": "Español", "description": "Spanish", "bidi": "ltr" } ], "defaultLanguage": "en-US" }), []) const form = useMemo(() => ({ "modalType": "MyDialog", "form": { "key": "Screen", "type": "Screen", "children": [ { "key": "confirmationModal", "type": "Modal", "props": { "modalTemplate": { "value": "Template:confirmation-dialog" } }, "modal": { "props": { "cancelButton": { "computeType": "localization" }, "confirmButton": { "computeType": "localization" }, "title": { "computeType": "localization" } } } }, { "key": "langSelect", "type": "MuiSelect", "props": { "items": { "value": [ { "value": "en-US", "label": "American English" }, { "value": "es-ES", "label": "Español" } ] }, "label": { "computeType": "localization" } }, "events": { "onChange": [ { "name": "onLangChange", "type": "custom" } ] } }, { "key": "showModalButton", "type": "MuiButton", "props": { "children": { "computeType": "localization" } }, "events": { "onClick": [ { "name": "openModal", "type": "common", "args": { "modalKey": "confirmationModal" } } ] } } ] }, "localization": { "en-US": { "langSelect": { "component": { "label": "Language" } }, "showModalButton": { "component": { "children": "Show Confirmation" } }, "confirmationModal": { "modal": { "title": "Confirm Action", "confirmButton": "Yes, proceed", "cancelButton": "Cancel" } } }, "es-ES": { "langSelect": { "component": { "label": "Idioma" } }, "showModalButton": { "component": { "children": "Mostrar Confirmación" } }, "confirmationModal": { "modal": { "title": "Confirmar Acción", "confirmButton": "Sí, proceder", "cancelButton": "Cancelar" } } } }, "languages": [ { "code": "en", "dialect": "US", "name": "English", "description": "American English", "bidi": "ltr" }, { "code": "es", "dialect": "ES", "name": "Español", "description": "Spanish", "bidi": "ltr" } ], "defaultLanguage": "en-US" }), []) const getForm = useCallback((name) => { const data = name === 'confirmation-dialog' ? confirmationDialog : form return JSON.stringify(data) }, [confirmationDialog, form]) muiView.define(myDialog.model) const [language, setLanguage] = useState<LanguageFullCode>('en-US') const actions = useMemo(() => ({ onLangChange: (e) => { setLanguage(e.args[0] ?? 'en-US') } }), []) const initialData = useMemo(() => ({langSelect: 'en-US'}), []) return <FormViewer initialData={initialData} view={muiView} getForm={getForm} actions={actions} language={language} /> }
Setting Form Language
The form language can be controlled using the language property on the FormViewer component. This property accepts a language code in
the format {code}-{dialect} (e.g., en-US, de-DE).
Example: React Component with Language Selection
import {view as muiView} from '@react-form-builder/components-material-ui'
import {FormViewer, type LanguageFullCode} from '@react-form-builder/core'
import {useCallback, useMemo, useState} from 'react'
function App() {
const form = useMemo(() => ({
"form": {
"key": "Screen",
"type": "Screen",
}
}), [])
const getForm = useCallback(() => JSON.stringify(form), [form])
const [language, setLanguage] = useState<LanguageFullCode>('en-US')
return (
<div>
<div>
<label>Select Language: </label>
<select value={language} onChange={(e) => setLanguage(e.target.value as LanguageFullCode)}>
<option value="en-US">English</option>
<option value="de-DE">German</option>
<option value="fr-FR">French</option>
<option value="es-ES">Spanish</option>
</select>
</div>
<FormViewer
getForm={getForm}
view={muiView}
language={language}
/>
</div>
)
}
Live example
function App() { const form = useMemo(() => ({ "form": { "key": "Screen", "type": "Screen", "children": [ { "key": "langSelect", "type": "MuiSelect", "props": { "disabled": { "value": true }, "items": { "value": [ { "value": "en-US", "label": "American English" }, { "value": "de-DE", "label": "Deutsch" }, { "value": "fr-FR", "label": "Français" }, { "value": "es-ES", "label": "Español" } ] }, "label": { "computeType": "localization" } } } ] }, "localization": { "en-US": { "langSelect": { "component": { "label": "Language" } } }, "de-DE": { "langSelect": { "component": { "label": "Sprache" } } }, "fr-FR": { "langSelect": { "component": { "label": "Langue" } } }, "es-ES": { "langSelect": { "component": { "label": "Idioma" } } } }, "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" }, { "code": "es", "dialect": "ES", "name": "Español", "description": "Spanish", "bidi": "ltr" } ], "defaultLanguage": "en-US" }), []) const getForm = useCallback(() => JSON.stringify(form), [form]) const [language, setLanguage] = useState('en-US') return ( <div> <div> <label>Select Language: </label> <select value={language} onChange={(e) => setLanguage(e.target.value)}> <option value="en-US">English</option> <option value="de-DE">German</option> <option value="fr-FR">French</option> <option value="es-ES">Spanish</option> </select> </div> <FormViewer getForm={getForm} view={muiView} language={language} /> </div> ) }
Fallback Localization Mechanism
FormEngine provides a robust fallback mechanism to ensure users always see meaningful text even when translations are incomplete or missing.
How Fallback Works
When a user requests a specific language (e.g., fr-CA - Canadian French), FormEngine follows this fallback chain:
- Requested language exact match - Looks for exact translation in the requested language full code (e.g.,
fr-CA) - Requested language base match - Falls back to the base language code if the specific dialect is not found (e.g.,
frforfr-CA) - Form default language - Uses the form's
defaultLanguagespecified in the JSON - Global default language - Falls back to the global default (
en-US) if form default is not available - [NOT LOCALIZED] helperText - Shows
[NOT LOCALIZED]for missing translations (not component key or property name)
This fallback mechanism applies to all localization types: component properties, validation errors, tooltips, and modal content.
Example: Fallback Chain in Action
Consider a form with these language definitions:
{
"localization": {
"en-US": {
"emailField": {
"component": {
"label": "Email Address",
"helperText": "Enter your email"
}
}
},
"fr": {
"emailField": {
"component": {
"label": "Adresse Email"
}
}
}
},
"languages": [
{
"code": "en",
"dialect": "US",
"name": "English",
"description": "American English",
"bidi": "ltr"
},
{
"code": "fr",
"dialect": "FR",
"name": "Français",
"description": "French",
"bidi": "ltr"
}
],
"defaultLanguage": "en-US"
}
When a user requests fr-CA (Canadian French):
- FormEngine looks for
fr-CAtranslations - Not found - Falls back to
fr(French) - Finds"label": "Adresse Email"buthelperTextis missing - Falls back to form default
en-US- Uses"helperText": "Enter your email"for the missing translation - Result:
labelin French,helperTextin English
Live example
function App() { const form = useMemo(() => ({ "form": { "key": "Screen", "type": "Screen", "children": [ { "key": "langSelect", "type": "MuiSelect", "props": { "items": { "value": [ { "value": "en-US", "label": "American English" }, { "value": "fr-CA", "label": "Français (Canadian)" } ] }, "label": { "computeType": "localization" } }, "events": { "onChange": [ { "name": "onLangChange", "type": "custom" } ] } }, { "key": "email", "type": "MuiTextField", "props": { "label": { "computeType": "localization" }, "helperText": { "computeType": "localization" } } } ] }, "localization": { "en-US": { "langSelect": { "component": { "label": "Language" } }, "email": { "component": { "label": "Email Address", "helperText": "Enter your full name" } } }, "fr-FR": { "email": { "component": { "label": "Adresse Email", } }, "langSelect": { "component": { "label": "Idioma" } } } }, "languages": [ { "code": "en", "dialect": "US", "name": "English", "description": "American English", "bidi": "ltr" }, { "code": "fr", "dialect": "FR", "name": "Français", "description": "French", "bidi": "ltr" }, { "code": "fr", "dialect": "CA", "name": "Français canadien", "description": "Canadian French", "bidi": "ltr" } ], "defaultLanguage": "en-US" }), []) const getForm = useCallback(() => JSON.stringify(form), [form]) const [language, setLanguage] = useState('en-US') const actions = useMemo(() => ({ onLangChange: (e)=> { setLanguage(e.args[0] ?? 'en-US') } }), []) const initialData = useMemo(() => ({langSelect: 'en-US'}), []) return <FormViewer initialData={initialData} view={muiView} getForm={getForm} actions={actions} language={language} /> }
Best Practices for Fallback
- Always provide complete translations for your default language - This ensures a complete user experience even when other languages are incomplete
- Use base language translations for regional dialects - Provide
frtranslations that work for all French dialects (fr-FR,fr-CA,fr-BE, etc.) - Test with incomplete translations - Verify your form works correctly when some translations are missing
- Define all languages in the
languagesarray - Ensure every language used inlocalizationhas a corresponding entry in thelanguagesarray - Use consistent language codes - If you define
fr-FRinlanguages, usefr-FR(notfr) in thelocalizationobject keys
External Localization with localize Function
For more advanced scenarios where you need to load translations from external sources or integrate with existing translation systems, you can use the localize property on FormViewerProps.
The ComponentLocalizer Type
The localize property accepts a ComponentLocalizer function
with the following signature:
type ComponentLocalizer = (
componentStore: ComponentStore,
language: Language
) => Record<string, any>;
componentStore: Contains information about the component being localizedlanguage: The current language selected for the form- Returns: An object with localized property values for the component
Example: External Translation Service Integration
import {view as muiView} from '@react-form-builder/components-material-ui'
import {type ComponentLocalizer, FormViewer, type FormViewerProps, type LanguageFullCode} from '@react-form-builder/core'
import {useCallback, useMemo, useState} from 'react'
function App() {
const form = useMemo(() => ({
"form": {
"key": "Screen",
"type": "Screen",
"children": [
{
"key": "langSelect",
"type": "MuiSelect",
"props": {
"items": {
"value": [
{
"value": "en-US",
"label": "American English"
},
{
"value": "es-ES",
"label": "Español"
}
]
},
"label": {
"computeType": "localization"
}
},
"events": {
"onChange": [
{
"name": "onLangChange",
"type": "custom"
}
]
}
},
{
"key": "nameInput",
"type": "MuiTextField",
"props": {
"label": {
"computeType": "localization"
},
"helperText": {
"computeType": "localization"
}
}
}
]
},
"localization": {
"en-US": {
"langSelect": {
"component": {
"label": "Language"
}
},
},
"es-ES": {
"langSelect": {
"component": {
"label": "Idioma"
}
}
}
},
"languages": [
{
"code": "en",
"dialect": "US",
"name": "English",
"description": "American English",
"bidi": "ltr"
},
{
"code": "es",
"dialect": "ES",
"name": "Español",
"description": "Spanish",
"bidi": "ltr"
}
],
"defaultLanguage": "en-US"
}), [])
const getForm = useCallback(() => JSON.stringify(form), [form])
const [language, setLanguage] = useState<LanguageFullCode>('en-US')
const actions: FormViewerProps['actions'] = useMemo(() => ({
onLangChange: (e) => {
setLanguage(e.args[0] ?? 'en-US')
}
}), [])
const initialData = useMemo(() => ({langSelect: 'en-US'}), [])
const translate = useCallback((componentKey: string, langFullCode: LanguageFullCode) => {
if (componentKey === 'nameInput') {
if (langFullCode === 'es-ES') {
return {
label: 'Nombre Completo',
helperText: 'Introduce tu nombre completo'
// Add any other properties that need localization
}
}
return {
label: 'Full Name',
helperText: 'Enter your full name'
// Add any other properties that need localization
}
}
return {}
}, [])
const customLocalizer: ComponentLocalizer = useCallback((componentStore, language) => {
const componentKey = componentStore.key
// Use your external translation service
return translate(componentKey, language.fullCode)
}, [translate])
return <FormViewer
initialData={initialData}
view={muiView}
getForm={getForm}
actions={actions}
language={language}
localize={customLocalizer}
/>
}
Live example
function App() { const form = useMemo(() => ({ "form": { "key": "Screen", "type": "Screen", "children": [ { "key": "langSelect", "type": "MuiSelect", "props": { "items": { "value": [ { "value": "en-US", "label": "American English" }, { "value": "es-ES", "label": "Español" } ] }, "label": { "computeType": "localization" } }, "events": { "onChange": [ { "name": "onLangChange", "type": "custom" } ] } }, { "key": "nameInput", "type": "MuiTextField", "props": { "label": { "computeType": "localization" }, "helperText": { "computeType": "localization" } } } ] }, "localization": { "en-US": { "langSelect": { "component": { "label": "Language" } }, }, "es-ES": { "langSelect": { "component": { "label": "Idioma" } } } }, "languages": [ { "code": "en", "dialect": "US", "name": "English", "description": "American English", "bidi": "ltr" }, { "code": "es", "dialect": "ES", "name": "Español", "description": "Spanish", "bidi": "ltr" } ], "defaultLanguage": "en-US" }), []) const getForm = useCallback(() => JSON.stringify(form), [form]) const [language, setLanguage] = useState('en-US') const actions = useMemo(() => ({ onLangChange: (e) => { setLanguage(e.args[0] ?? 'en-US') } }), []) const initialData = useMemo(() => ({langSelect: 'en-US'}), []) const translate = useCallback((componentKey, langFullCode) => { if (componentKey === 'nameInput') { if (langFullCode === 'es-ES') { return { label: 'Nombre Completo', helperText: 'Introduce tu nombre completo' // Add any other properties that need localization } } return { label: 'Full Name', helperText: 'Enter your full name' // Add any other properties that need localization } } return {} }, []) const customLocalizer = useCallback((componentStore, language) => { const componentKey = componentStore.key // Use your external translation service return translate(componentKey, language.fullCode) }, [translate]) return <FormViewer initialData={initialData} view={muiView} getForm={getForm} actions={actions} language={language} localize={customLocalizer} /> }
In this example, you can also observe hybrid mode. The language selection component is translated into JSON, and the name input component is translated using an external function.
Best Practices
- Always provide a default language - Ensure your form has a sensible default language for users who don't have a language preference set.
- Use descriptive component keys - Component keys like
emailFieldorsubmitButtonmake localization easier to manage than generic keys likeinput1orbutton2. - Keep localization data organized - Group related translations together and consider maintaining separate translation files for large forms.
- Test bidirectional text support - For RTL languages like Arabic or Hebrew, ensure your form layout handles text direction correctly.
- Consider text expansion - Some languages require more space for the same meaning. Design your forms with flexible layouts to accommodate longer text.
- Fallback strategy - Implement a fallback strategy for missing translations, either to a default language or to the component key itself.
Summary
FormEngine Core provides a robust localization system that supports:
- Inline JSON localization for self-contained forms
- Component property localization for labels, placeholders, tooltips, and more
- Validation error localization for user-friendly error messages in multiple languages
- Language switching via the
languageproperty - External localization integration through the
localizefunction
Whether you need simple multi-language support or complex translation system integration, FormEngine's localization features provide the flexibility to meet your internationalization requirements.