Form Translations & Localization

Most form builders force a choice: either duplicate your entire form for each language, or accept that only basic labels get translated while error messages, placeholders, and validation feedback stay in English. Form.io takes a different approach. The form schema defines structure and behavior. Translations live separately, loaded at render time through the i18next framework. You build one form. Your application decides which language to display.

This matters for enterprise applications serving global user bases. A single form definition in a government benefits portal might need to render in English, Spanish, Chinese, Arabic, and Vietnamese. A multinational corporation’s HR system might serve employees across dozens of countries. Managing separate form schemas for each language creates maintenance nightmares. When business logic changes, you update one schema instead of twenty.

How Translation Works in Form.io

Form.io’s JavaScript renderer uses the i18next internationalization framework. When you create a form, you pass translation dictionaries through the i18n configuration option. The renderer looks up every translatable string against the active language dictionary before displaying it.

Formio.createForm(document.getElementById('formio'), 'https://yourproject.form.io/yourform', {
  language: 'es',
  i18n: {
    es: {
      'First Name': 'Nombre de pila',
      'Last Name': 'Apellido',
      'Enter your first name': 'Ponga su primer nombre',
      'Submit': 'Enviar',
      required: '{{field}} es requerido.',
      invalid_email: '{{field}} debe ser un correo electrónico válido.'
    },
    zh: {
      'First Name': '名字',
      'Last Name': '姓',
      'Submit': '提交',
      required: '{{field}} 是必需的。'
    }
  }
}).then(function(form) {
  window.setLanguage = function(lang) {
    form.language = lang;
  };
});

The language property sets the initial display language. The i18n object contains translation dictionaries keyed by language code. When you call form.language = 'zh', the renderer immediately re-renders all translatable content using the Chinese dictionary. No page reload required.

What Gets Translated

Translation applies to every user-facing string the renderer displays. This includes field labels, placeholder text, tooltips, help text, button labels, wizard navigation buttons like “Next” and “Previous”, and all validation error messages. The form schema itself remains unchanged. The renderer intercepts strings at display time and swaps them for translated equivalents.

System-level validation messages use template syntax with double curly braces. The string required: '{{field}} es requerido.' interpolates the field label into the error message. If your form has a field labeled “Email Address” and the user leaves it empty, the Spanish error displays “Email Address es requerido.” You can also translate the field label itself, so the complete message reads in the target language.

Form.io’s default English messages cover common validation scenarios: required fields, email format validation, minimum and maximum values, pattern matching, date validation, and length constraints. You override any of these by including the key in your translation dictionary. The i18n.json file in the formio.js library contains the complete list of default keys.

Static vs Dynamic Translation Loading

The example above embeds translation dictionaries directly in application code. This works for applications with a small, fixed set of languages where translations rarely change. The tradeoff is that adding or modifying translations requires a code deployment.

Form.io supports a more flexible pattern using Resources. You create a Language resource within your project containing a Select field for language codes and a Data Map component for key-value translation pairs. Each submission to this resource represents one language’s complete translation dictionary. Your application fetches translations via API at runtime.

var languages = {en: true};
window.setLanguage = function(lang) {
  if (languages[lang]) {
    return form.language = lang;
  }
  
  Formio.fetch('https://yourproject.form.io/language/submission?data.language=' + lang)
    .then(function(resp) { return resp.json(); })
    .then(function(result) {
      languages[lang] = true;
      form.addLanguage(lang, result[0].data.translations);
      form.language = lang;
    });
};

This pattern enables non-developers to manage translations through the Form.io portal. A translator logs in, opens the Language resource, and edits the Spanish submission to fix a typo or add a new term. The change takes effect immediately for all users without touching application code. For organizations with dedicated localization teams, this separation of concerns matters.

The Translations documentation walks through the complete Resource-based setup, including access control configuration to ensure anonymous users can read translations while restricting who can modify them.

Right-to-Left Language Support

Languages like Arabic and Hebrew read right-to-left and require layout mirroring. Form.io handles this through the HTML dir attribute. When switching to an RTL language, your application sets document.body.setAttribute("dir", "rtl"). The CSS frameworks Form.io supports respond to this attribute by mirroring layouts appropriately.

window.setLanguage = function(lang) {
  form.language = lang;
  document.body.setAttribute("dir", lang === "ar" || lang === "he" ? "rtl" : "ltr");
};

The renderer itself does not automatically detect RTL languages. Your application maintains the list of RTL language codes and applies the direction attribute during language switching. This gives you control over edge cases where you might want to override default behavior.

Form layouts with multiple columns may need additional CSS adjustments for RTL presentation. Single-column forms and center-aligned layouts typically require no modifications beyond the direction attribute. Complex layouts warrant testing in target RTL languages before deployment.

Custom Error Message Translation

Validation errors follow the same translation pattern as labels. Define keys in your translation dictionary that match the error type, and the renderer substitutes the translated message. This includes both Form.io’s built-in validations and custom validation messages you define in the form builder.

When you write custom validation logic in a component’s settings, you return an error string if validation fails:

valid = input === 'example' ? true : 'mycustomerror';
That error string becomes a translation key. Add it to your dictionaries:
javascript
i18n: {
  en: { 'mycustomerror': 'Your input must be "example"' },
  es: { 'mycustomerror': 'Su entrada debe ser "example"' }
}

The renderer looks up mycustomerror in the active language dictionary and displays the appropriate message. This keeps validation logic in the form schema while allowing localized error presentation.

Translation Coverage and Fallbacks

i18next provides fallback behavior when a translation key is missing. If the active language dictionary lacks a translation for a particular string, the renderer falls back to the default language (typically English). This prevents untranslated keys from displaying as raw technical identifiers.

Partial translation coverage is common during initial localization efforts. You might translate all field labels and critical error messages first, then fill in placeholder text and help content later. The fallback mechanism ensures the form remains usable even with incomplete translations.

For applications requiring complete translation coverage, implement validation in your deployment process that checks translation dictionaries against the full set of form strings. The form schema contains all translatable strings as properties of component definitions. A script can extract these strings and verify that each target language dictionary contains matching keys.

When Not to Use Dynamic Translation

Dynamic translation assumes your form structure stays constant across languages. This holds for most enterprise applications but breaks down in specific scenarios.

Regulatory requirements sometimes mandate different form fields for different jurisdictions. A financial disclosure form in the European Union might require fields that do not exist in the United States version. These are not translation differences but structural differences. Build separate form schemas for each regulatory context rather than trying to handle this through translation.

Significantly different input formats can also justify separate forms. A name field in Western contexts typically uses first name and last name. East Asian naming conventions often work better with a single full name field with family name first. If your target audiences have fundamentally different data entry expectations, separate form schemas may serve users better than a translated single schema.

The general principle: use dynamic translation when you want the same data structure presented in different languages. Use separate form schemas when you need different data structures for different audiences.

Portal Translation for Self-Hosted Deployments

Organizations running self-hosted Form.io deployments can also translate the Developer Portal interface itself. The Portal Base Project includes a Language resource with the same structure used for form translations. Administrators add submissions for each supported language, providing translations for portal navigation, buttons, and system messages.

Portal translation is part of the Security Compliance module. Contact Form.io for the complete translation key list and module access.

Integration with Frontend Frameworks

The translation system works identically whether you embed forms in Angular, React, Vue, or vanilla JavaScript applications. The i18n configuration passes through framework-specific wrappers to the underlying renderer. If your application already uses i18next for general localization, you can share translation infrastructure between your application UI and embedded Form.io forms.

For React applications using react-i18next, you can initialize Form.io with the same i18next instance your application uses. Angular applications using ngx-translate would maintain separate translation systems, passing Form.io-specific translations through the form options.

The formio.js examples include working demonstrations of translation switching across multiple languages, including survey components with translatable questions and response options.

Related Resources