Skip to content
Form, Please

Localization

Form, Please has no locale setting. Each part of the form gets its text from a different source:

  • Field labels and other definition text come from your definition. Read the locale from the runtime context.
  • Validation messages come from the schema library. Configure its messages.
  • Array action labels and the file button label come from the slots and controls of the form kit. Give the kit an i18n object.

Translate labels with context

Put the locale in the kit context. A label function gets the context in its second argument. When you give useForm a new context object, Form, Please resolves the labels again. Memoize the object, so that it changes only when the locale changes.

type Locale = "en" | "fr"
 
type LocaleContext = {
	readonly locale: Locale
}
 
const labels = {
	en: { email: "Email", newsletter: "Send product news" },
	fr: { email: "E-mail", newsletter: "Recevoir les nouveautés" },
} as const
 
const signupSchema = z.object({
	email: z.string(),
	newsletter: z.boolean(),
})
 
const localeKit = nativeFormKit.forContext<LocaleContext>()
 
const signupDefinition = localeKit.defineForm(signupSchema, (ui) => [
	ui.field("email", {
		control: "text",
		label: (_values, { context }) => labels[context.locale].email,
	}),
	ui.field("newsletter", {
		control: "checkbox",
		label: (_values, { context }) => labels[context.locale].newsletter,
	}),
])
 
function SignupForm({ locale }: { readonly locale: Locale }) {
	const context = useMemo(() => ({ locale }), [locale])
	const form = localeKit.useForm(signupDefinition, {
		defaultValues: { email: "", newsletter: false },
		context,
	})
 
	return <localeKit.AutoForm form={form} />
}

A custom control gets the same context in its props, so it can read context.locale. Read Resolver values for the arguments of property functions.

Translate validation messages

Form, Please displays the messages that the Standard Schema returns. Configure the schema library once, before form validation runs. Do not configure it during a component render, because the configuration is global.

A message that you give to one schema or one check has priority over the global messages.

Zod

Load a Zod locale with z.config(). Add customError to replace the message of one issue type. Return undefined to use the locale message.

z.config(z.locales.fr())
 
z.config({
	customError: (issue) => {
		if (issue.code === "too_small" && issue.origin === "string") {
			return `Saisissez au moins ${issue.minimum} caractères`
		}
 
		return undefined
	},
})

Valibot

Set the language with setGlobalConfig. Then set the messages for that language with setGlobalMessage and setSpecificMessage.

v.setGlobalConfig({ lang: "fr" })
v.setGlobalMessage("Valeur non valide", "fr")
v.setSpecificMessage(
	v.minLength,
	(issue) => `Saisissez au moins ${issue.requirement} caractères`,
	"fr",
)

Yup

Call setLocale before you create the schemas. Yup reads the default message when a schema method runs, so a schema created earlier keeps the old message.

setLocale({
	mixed: { required: "Ce champ est obligatoire" },
	string: { min: ({ min }) => `Saisissez au moins ${min} caractères` },
})

Translate the form kit labels

The default slots and the Material UI preset use different i18n key names for the same actions. Their callbacks also get different arguments.

LabelcreateDefaultSlots keycreateMuiFormKit keyEnglish default
Append an array itemarrayAddaddItemAdd item
Remove an array itemarrayRemoveremoveItemRemove item 1
Move an array item uparrayMoveUpmoveItemUpMove item 1 up
Move an array item downarrayMoveDownmoveItemDownMove item 1 down
Choose a file—chooseFileChoose file
  • A createDefaultSlots value is a string or a callback. The arrayAdd callback gets the array label. The item callbacks get the zero-based index and the one-based position.
  • A createMuiFormKit value for an item action is a callback that gets the one-based position as a number. addItem and chooseFile are strings.
  • All keys are optional. A missing key keeps the English default.

Default slots

nativeFormKit uses the English default slots. To translate them, create the kit with createFormKit, createNativeControls, and createDefaultSlots.

const frenchSlots = createDefaultSlots({
	i18n: {
		arrayAdd: ({ label }) => {
			if (typeof label === "string") return `Ajouter : ${label}`
			return "Ajouter un élément"
		},
		arrayRemove: ({ position }) => `Supprimer l'élément ${position}`,
		arrayMoveUp: ({ position }) => `Monter l'élément ${position}`,
		arrayMoveDown: ({ position }) => `Descendre l'élément ${position}`,
	},
})
 
const frenchNativeKit = createFormKit({
	controls: createNativeControls(),
	slots: frenchSlots,
})

Read Default slots for the i18n types.

Material UI preset

Give createMuiFormKit an i18n object.

const frenchMuiKit = createMuiFormKit({
	i18n: {
		addItem: "Ajouter un élément",
		removeItem: (position) => `Supprimer l'élément ${position}`,
		moveItemUp: (position) => `Monter l'élément ${position}`,
		moveItemDown: (position) => `Descendre l'élément ${position}`,
		chooseFile: "Choisir un fichier",
	},
})

Read Material UI preset for the preset options.

Next steps