i18n
Anhanga 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:
typescript
type TranslateContract = (key: string) => stringPass it to hooks:
typescript
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:
typescript
import { resolveFieldLabel, resolveGroupLabel, resolveActionLabel } from '@anhanga/react' // or '@anhanga/vue' or '@anhanga/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 @anhanga/demo for a complete example):
typescript
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:
typescript
schema.name.state = 'error'
// Label resolves from: person.fields.name[error]Define allowed states on a field:
typescript
text().states('error', 'warning', 'new')