Skip to main content

i18n key conventions

This page documents the namespace rules every CRM-side t(...) call must follow. They exist so the locale files stay greppable and three parallel translation files don't drift over time.

The three rules

1. Module → entity → field/section

{module}.{entity}.{section}.{key}

Examples:

  • crm.contacts.fields.firstName — the form-field label.
  • crm.contacts.placeholders.firstName — the input placeholder.
  • crm.contacts.notifications.created — the success snackbar.
  • crm.contacts.deleteConfirmTitle — top-level keys are allowed when the value is page-global (no obvious section).

2. Detail pages live under <entity>Detail, not <entity>.detail

This is a legacy convention. The contact detail page reads from crm.contactDetail.*, NOT crm.contacts.detail.*. CRM audit F0 #23 flagged the hierarchy mismatch; we keep the legacy structure rather than rename 56 keys × 3 locales mid-refactor. New detail pages SHOULD continue the pattern ({entity}Detail) for consistency with the existing ones.

PageNamespace
crm/contacts/[id].vuecrm.contactDetail.*
crm/companies/[id].vuecrm.companyDetail.*
(future)crm.{entity}Detail.*

3. Notifications follow a strict 6-key shape

Every CRUD store dispatches one of:

{namespace}.notifications.created
{namespace}.notifications.createFailed
{namespace}.notifications.updated
{namespace}.notifications.updateFailed
{namespace}.notifications.deleted
{namespace}.notifications.deleteFailed

Domain-specific verbs (publish, archive, retry) get one pair each:

{namespace}.notifications.published / publishFailed
{namespace}.notifications.unpublished / unpublishFailed
{namespace}.notifications.archived / archiveFailed

Use useApiError(errorRef).notifySuccess(key) / notifyError(err, fallbackKey) so the i18n key always falls back to the server-provided message field, then the i18n key, then the fallback.

Adding a new locale string

  1. Write the source-language entry in hu.json (the editorial source of truth — translators read this).
  2. Mirror the same key + structure in en.json and de.json.
  3. If the key namespace doesn't exist yet, place it in the same parent block where related keys live (don't create a new top-level namespace unless you really need one).
  4. Run npm run test — the locale-parity test fails if you add a key to one file but forget the other two.

Common mistakes

  • Don't write t('Some hardcoded English'). The argument must be a dotted key path, NOT the literal text.
  • Don't introduce t('${someVar}.foo') — i18n keys must be static so the linter can detect missing translations. Use t('crm.statuses.' + status) only when the value side of the branch is short and the keys are explicitly enumerated.
  • Don't put placeholder values directly in the template (placeholder="Acme Inc."). Always go through t('...placeholder') — see crm.companies.placeholders.name for the canonical example.
  • Don't mix crm.contacts.title and crm.contacts.headerTitle to mean the same thing — pick one and use it everywhere.