i18n
Ybyra never hardcodes labels. All user-facing text is resolved through a translate function following a key convention.
Key Conventions
| Key Pattern | Example | Used For |
|---|---|---|
{domain}.fields.{field} | person.fields.name | Field labels |
{domain}.fields.{field}[{state}] | person.fields.name[error] | Field label with visual state |
{domain}.groups.{group} | person.groups.basic | Group labels |
common.actions.{action} | common.actions.create | Action labels (shared) |
Translate Contract
The translate function has a simple signature:
type TranslateContract = (key: string) => stringPass it to hooks:
const form = useDataForm({
schema: PersonSchema.provide(),
scope: Scope.add,
translate: (key) => i18n.t(key),
// ...
})Resolution Helpers
All framework packages provide the same helper functions for label resolution:
import { resolveFieldLabel, resolveGroupLabel, resolveActionLabel } from '@ybyra/react' // or '@ybyra/vue' or '@ybyra/svelte'
resolveFieldLabel(t, 'person', 'name', '')
// → t('person.fields.name')
resolveFieldLabel(t, 'person', 'name', 'error')
// → t('person.fields.name[error]')
resolveGroupLabel(t, 'person', 'basic')
// → t('person.groups.basic')
resolveActionLabel(t, 'person', 'create')
// → tries t('person.actions.create'), falls back to t('common.actions.create')Example Translation File
Translations are typically defined as TypeScript objects (see @ybyra/demo for a complete example):
export const ptBR = {
common: {
actions: {
add: "Adicionar",
create: "Criar",
update: "Atualizar",
cancel: "Cancelar",
destroy: "Excluir",
"create.success": "Registro criado com sucesso",
"update.success": "Registro atualizado com sucesso",
"destroy.confirm": "Deseja realmente excluir este registro?",
"destroy.success": "Registro excluído com sucesso",
},
table: {
columns: "Colunas",
previous: "Anterior",
next: "Próximo",
page: "Página {{page}} de {{total}}",
empty: "Nenhum registro encontrado",
},
dialog: { confirm: "Confirmar", cancel: "Cancelar" },
},
validation: {
required: "Campo obrigatório",
minLength: "Mínimo de {{value}} caracteres",
maxLength: "Máximo de {{value}} caracteres",
min: "Valor mínimo é {{value}}",
max: "Valor máximo é {{value}}",
},
person: {
fields: {
id: "ID",
name: "Nome",
email: "E-mail",
phone: "Telefone",
birthDate: "Data de Nascimento",
active: "Ativo",
street: "Rua",
city: "Cidade",
},
groups: {
basic: "Informações Básicas",
address: "Endereço",
},
},
}Field States
Fields can have visual states (set via the schema proxy). When a state is active, the label key includes the state:
schema.name.state = 'error'
// Label resolves from: person.fields.name[error]Define allowed states on a field:
text().states('error', 'warning', 'new')Component Contracts and i18n
All component contract methods (toast, dialog) receive i18n keys, not raw strings. The framework automatically resolves the key through the translate contract before displaying the message.
// ✅ Correct — uses i18n key
component.toast.success('common.actions.create.success')
component.dialog.confirm('common.actions.destroy.confirm')
// ❌ Wrong — raw strings won't be translated
component.toast.success('Record created successfully!')
component.dialog.confirm('Are you sure?')Common i18n Keys for Actions
| Key | Typical Translation |
|---|---|
common.actions.create.success | "Registro criado com sucesso" |
common.actions.create.invalid | "Formulário inválido" |
common.actions.update.success | "Registro atualizado com sucesso" |
common.actions.update.invalid | "Formulário inválido" |
common.actions.destroy.confirm | "Deseja realmente remover?" |
common.actions.destroy.success | "Registro removido com sucesso" |
common.dialog.confirm | "Confirmar" |
common.dialog.cancel | "Cancelar" |
TIP
You can define custom i18n keys for domain-specific actions. For example, a custom export action could use person.actions.export.confirm and person.actions.export.success.