Internationalization
Internationalization
A workspace’s text lives in message catalogs under src/i18n/. The base catalog is the source of truth, and every other locale mirrors its keys.
// src/i18n/+messages.ts (templates/field-operations, excerpt)
import { messages } from '@norbital-ai/bolt';
export default messages({
'app.field_ops_contractor.title': 'Contractor Workspace',
'app.field_ops_contractor.header_title': 'Contractor jobs'
// …
});
// src/i18n/+zh.messages.ts: the same keys or fewer, never more
export default messages({
'app.field_ops_contractor.title': '承包商工作区',
'app.field_ops_contractor.header_title': '承包商任务'
// …
}); Authored files
src/i18n/+messages.ts is messages({ … }), a flat map of dot-namespaced keys to strings, and there is exactly one when src/i18n/ exists. src/i18n/+<locale>.messages.ts holds another locale; its keys must be a subset of the base, or it is a type error. Keep {placeholders} identical in every locale.
Messages in pages
bolt.t(key, vars?) is typed from the base catalog, so an unknown key is a type error. bolt.locale is the viewer’s locale — their stored choice, then their browser languages, then the workspace locale — and the shell owns the language toggle.
Messages in declarations
A Msg in a declaration — an app or page title, a field label or help, a notification title — is a catalog key. Outside catalog mode, in a workspace with no src/i18n/, it may be a literal string.
The raw-text rule
With a catalog, every user-facing string in a page or record view comes from bolt.t(…): a raw text node, or a literal passed to a prop that renders, is a missing catalog entry and fails bolt check. Identifiers, URLs, seed data and model descriptions are not user-facing copy.
Key conventions
app.<app>.title,app.<app>.descriptionandapp.<app>.tab_<name>for app copycomponent.<name>for labels shared across pages- plurals as
{count}, with separate keys where the languages differ