Plugins
Form Builder

i18n

Override or localize the Form Builder admin strings, and translate the frontend.

Content vs UI strings

Two separate systems localize two separate things:

  • Content is what editors author on a form document: labels, placeholders, option labels, the response message. It is stored data, localized through Payload's own localization on the forms collection. Content fields are localized by default; the plugin option localizeContent: false opts a project out.
  • UI strings are the plugin's own words: admin authoring labels and frontend renderer strings ("Add row", validation messages, poll notices). These are typed translations, overridden via the translations option (admin) and the t prop (frontend), below.

Admin strings

Field type names, rule labels and messages, action labels, and the rest of the authoring UI are typed translations under the formBuilder: namespace. Several locales ship built in; override any of them, or add another locale, through the translations option:

payload.config.ts
import { formBuilder } from '@10x-media/form-builder'
import { keys } from '@10x-media/form-builder/i18n'

formBuilder({
  translations: {
    ja: { [keys.fieldTitle]: 'タイトル' /* ... */ },
    de: { [keys.fieldTitle]: 'Bezeichnung' }, // key-by-key override of the built-in German
  },
})

@10x-media/form-builder/i18n exports keys, the built-in translations map, the flat per-locale tables plus bundles, clientRuntimeKeys, and the TranslationKey / TranslationBundle / TranslationsOption types. A typo'd key fails to compile. Merge semantics and app-level overrides are shared across plugins: see Translations.

Frontend strings

The renderer is independent of the admin i18n: <Form> takes a t translator and a locale, which flow into every renderer and into value formatting:

<Form form={form} locale="de" t={(key) => myMessages[key] ?? key} />

locale stays in the browser. To have the submission stored in the visitor's content locale, and the confirmation and team emails rendered in it, also pass submissionLocale, one of your Payload localization codes. It is a separate prop because a formatting tag like en-US is usually not a content locale code; see Submission locale.

Falling back to the shipped bundles

<Form> and <Poll> resolve their own chrome (the submit / next / back labels, the failure banner, poll notices) and the client-side validation messages through t. A host that bridges t to its own i18n only sees the keys it references directly, so keys it never mirrors (the chrome, when you correctly pass no label props) fall through. With a ?? key-to-English fallback that leak is silent: a German visitor sees English with no error. Fall back to the plugin's own complete bundle instead, and English cannot leak:

import { makeTranslate } from '@10x-media/form-builder/react'

// Your overrides first, the shipped bundle for the locale next (never the raw key).
const fallback = makeTranslate(locale) // 'de' -> German, a locale we do not ship -> English
;<Form form={form} locale={locale} t={(key) => myOverrides[key] ?? fallback(key)} />

makeTranslate(locale) resolves the shipped bundle for that locale, falling back to English only for a locale the plugin does not ship. Pass a map for full control (makeTranslate(bundles[locale] ?? en)). @10x-media/form-builder/react and /i18n export every shipped locale table, bundles (Record<string, TranslationBundle>), and the TranslationBundle type for this.

Asserting mirror coverage

To fully own the frontend strings rather than fall back, clientRuntimeKeys lists every key the visitor runtime resolves, so a test can assert your mirror is complete instead of discovering a gap in production:

import { clientRuntimeKeys } from '@10x-media/form-builder/react'

for (const key of clientRuntimeKeys) expect(myMessages[key]).toBeDefined()

On this page