Appearance
Internationalization (i18n)
Vorm has built-in support for internationalization. All text in your forms — labels, placeholders, help text, and validation messages — can be reactive and automatically update when the locale changes.
Key Features
- Reactive text — Labels, placeholders, and helpText update when locale changes
- No re-validation — Language changes update messages without re-running validation
- No
computed()needed — Plain functions work directly - FormContext access — Dynamic text based on form state
- Built-in translations — English and German included for validators
ReactiveString
All text properties in Vorm accept a ReactiveString:
ts
type ReactiveString =
| string // Static: "Username"
| Ref<string> // Vue Ref: ref('Username')
| ComputedRef<string> // Vue Computed
| (() => string) // Function
| ((ctx: FormContext) => string); // Function with form contextSimple Function (Recommended)
No computed() wrapper needed:
ts
const schema: VormSchema = [
{
name: 'username',
label: () => locale.value === 'en' ? 'Username' : 'Benutzername',
placeholder: () => locale.value === 'en' ? 'Enter username' : 'Benutzername eingeben',
}
];With Vue I18n
ts
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const schema: VormSchema = [
{
name: 'username',
label: () => t('form.username'),
placeholder: () => t('form.username.placeholder'),
}
];With Nuxt I18n
ts
const { t } = useI18n();
const schema: VormSchema = [
{
name: 'email',
label: () => t('form.email'),
validation: [
{ rule: 'required' },
{ rule: 'email' }
]
}
];FormContext
Functions can receive a FormContext parameter for dynamic text based on form state:
ts
interface FormContext {
formData: Record<string, any>;
readonly errors: Record<string, string | null>;
readonly isValid: boolean;
readonly isDirty: boolean;
readonly isTouched: boolean;
readonly touched: Record<string, boolean>;
readonly dirty: Record<string, boolean>;
}Dynamic Placeholder
ts
{
name: 'email',
placeholder: (ctx) => ctx.formData.username
? `${ctx.formData.username}@example.com`
: 'your@email.com',
}Dynamic Help Text
ts
{
name: 'password',
helpText: (ctx) => ctx.formData.email
? `Password for ${ctx.formData.email}`
: 'At least 8 characters',
}Dynamic Validation Message
ts
{
name: 'age',
validation: [{
rule: between(18, 100),
message: (ctx) => `Age must be 18-100 (you entered: ${ctx.formData.age || 'nothing'})`
}]
}Validation Messages
Built-in i18n Keys
All built-in validators return message keys that are automatically resolved:
| Validator | Key | Params |
|---|---|---|
required | vorm.validation.required | - |
email | vorm.validation.email | - |
minLength | vorm.validation.minLength | [min] |
maxLength | vorm.validation.maxLength | [max] |
min | vorm.validation.min | [min] |
max | vorm.validation.max | [max] |
between | vorm.validation.between | [min, max] |
step | vorm.validation.step | [step] |
pattern | vorm.validation.pattern | - |
matchField | vorm.validation.matchField | [fieldName] |
url | vorm.validation.url | - |
integer | vorm.validation.integer | - |
alpha | vorm.validation.alpha | - |
Default Messages
Vorm includes default translations:
English:
This field is required.
Please enter a valid email address.
This field must be at least {0} characters.
...German:
Dieses Feld ist erforderlich.
Bitte geben Sie eine gültige E-Mail-Adresse ein.
Dieses Feld muss mindestens {0} Zeichen lang sein.
...Custom i18n Messages
Override or add translations:
ts
const vorm = useVorm(schema, {
i18n: {
'vorm.validation.required': 'Campo obligatorio',
'vorm.validation.minLength': 'Mínimo {0} caracteres',
'my.custom.key': 'Custom message',
}
});Message Interpolation
Messages use {0}, {1}, etc. for parameter interpolation:
"This field must be at least {0} characters."
// With minLength(5) → "This field must be at least 5 characters."
"Value must be between {0} and {1}."
// With between(18, 65) → "Value must be between 18 and 65."How It Works
Two-Layer Error System
Vorm uses a clever two-layer system to update messages without re-validation:
- Storage Layer — Stores error metadata (message reference + params)
- Display Layer — Resolves messages reactively to strings
When locale changes:
- Display layer re-computes
- Messages update with new locale
- Validation does NOT re-run
This means users can switch languages without losing their validation state or triggering unnecessary API calls.
Complete Example
vue
<script setup lang="ts">
import { ref } from 'vue';
import { useVorm, type VormSchema, minLength, between } from 'vorm-vue';
const locale = ref('en');
const schema: VormSchema = [
{
name: 'username',
type: 'text',
label: () => locale.value === 'en' ? 'Username' : 'Benutzername',
placeholder: () => locale.value === 'en' ? 'Enter username' : 'Benutzername eingeben',
validation: [
{ rule: 'required' },
{ rule: minLength(3) }
]
},
{
name: 'email',
type: 'email',
label: () => locale.value === 'en' ? 'Email' : 'E-Mail',
placeholder: (ctx) => ctx.formData.username
? `${ctx.formData.username}@example.com`
: (locale.value === 'en' ? 'your@email.com' : 'ihre@email.de'),
validation: [
{ rule: 'required' },
{ rule: 'email' }
]
},
{
name: 'age',
type: 'number',
label: () => locale.value === 'en' ? 'Age' : 'Alter',
helpText: (ctx) => ctx.formData.age
? (locale.value === 'en' ? `You are ${ctx.formData.age} years old` : `Sie sind ${ctx.formData.age} Jahre alt`)
: (locale.value === 'en' ? 'Enter your age' : 'Geben Sie Ihr Alter ein'),
validation: [
{ rule: between(18, 120) }
]
}
];
const vorm = useVorm(schema);
</script>
<template>
<div>
<button @click="locale = 'en'">English</button>
<button @click="locale = 'de'">Deutsch</button>
<VormProvider :vorm="vorm">
<AutoVorm />
</VormProvider>
</div>
</template>