Internationalization
Patterns for building multilingual forms and workflows with RilayKit — localized labels, validation messages, and dynamic form generation.
RilayKit has no built-in i18n system. It is headless and schema-first, so any i18n library (react-intl, next-intl, i18next, etc.) plugs in with the patterns below.
Labels, placeholders, validation messages, and step titles are all data in the config — swap them for localized strings and the rendering logic never changes.
Localized Validation Messages
Built-in validators accept a custom message. Pass your translation function's output, and rebuild the config when the locale changes — messages are baked in at creation time:
import { useMemo } from 'react';
import { required, email } from '@rilaykit/core';
import { form } from '@rilaykit/forms';
import { Form, FormField } from '@rilaykit/forms/react';
import { useTranslation } from 'react-i18next'; // or your i18n library
function LocalizedContactForm() {
const { t, i18n } = useTranslation();
const contactForm = useMemo(() => form.create(rilay, 'contact')
.add({
id: 'email',
type: 'input',
props: { label: t('form.email') },
validation: { validate: [required(t('validation.required')), email(t('validation.email'))] },
}),
[i18n.language] // Rebuild when locale changes
);
return (
<Form formConfig={contactForm} onSubmit={handleSubmit}>
<FormField fieldId="email" />
</Form>
);
}Localized Component Props
All component props flow through the schema, so translated strings go straight into props — labels, placeholders, option lists, helper text, aria attributes:
import { form } from '@rilaykit/forms';
const signupForm = form.create(rilay, 'signup')
.add({
id: 'country',
type: 'select',
props: {
label: t('form.country'),
placeholder: t('form.selectCountry'),
options: [
{ value: 'us', label: t('countries.us') },
{ value: 'fr', label: t('countries.fr') },
],
},
});Server-Driven Localized Forms
Form configs are plain serializable data, so the server can resolve translations, reorder fields, or add region-specific fields per locale before sending the config down:
// server: generate config with the right locale
const formConfig = generateFormConfig(locale);
return Response.json(formConfig);import { form } from '@rilaykit/forms';
// client: deserialize and render
const config = await fetch('/api/form?locale=fr').then(r => r.json());
const contactForm = form.create(rilay, 'contact').fromJSON(config);Localized Workflow Steps
The same pattern applies to workflows. Each step wraps a form config; localize step titles and nested field labels through the same translation function, memoized on the locale:
import { useMemo } from 'react';
import { required } from '@rilaykit/core';
import { form } from '@rilaykit/forms';
import { flow } from '@rilaykit/workflow';
import { useTranslation } from 'react-i18next';
function useLocalizedOnboarding() {
const { t, i18n } = useTranslation();
return useMemo(() => flow.create(rilay, 'onboarding', t('onboarding.title'))
.step({
id: 'account',
title: t('onboarding.steps.account'),
formConfig: form.create(rilay, 'account').add({
id: 'email',
type: 'input',
props: { label: t('form.email') },
validation: { validate: [required(t('validation.required'))] },
}),
}),
[i18n.language]
);
}RTL Support
RTL is purely a CSS/HTML concern handled by your renderers. dir="auto" lets the browser pick the direction per field; for full RTL layouts set dir="rtl" on a parent or <html> element:
import type { ComponentRenderContext } from '@rilaykit/core';
function Input({ id, props, field }: ComponentRenderContext<{ label: string }>) {
return (
<div dir="auto">
<label htmlFor={id}>{props.label}</label>
<input
id={id}
value={(field?.value as string) ?? ''}
onChange={(e) => field?.onChange(e.target.value)}
/>
</div>
);
}Best Practices
- Rebuild configs on locale change —
useMemokeyed on the locale. - Keep validation messages in your i18n files, and use the library's pluralization ("minimum 1 character" vs "minimum 8 characters") instead of string concatenation.
- Use server-driven configs for SEO-critical forms so search engines see localized content.
- Test with RTL locales (Arabic, Hebrew) if you support them.
Accessibility
Patterns for building accessible forms and workflows with RilayKit's headless architecture — ARIA attributes, focus management, and keyboard navigation.
Debugging
Tools and techniques for debugging RilayKit forms and workflows — monitoring adapters, state inspection, and common troubleshooting patterns.