Snapshot for
@system-core/core0.12.x. Current docs live at /.
i18n
import { ... } from '@system-core/core/cms'
The core/cms/i18n sub-module is the single translation authority for the platform. It handles both CMS content translations and plugin-seeded UI strings through a unified namespace model.
Row Model
Translation rows are keyed by namespace + entityType + entityId + locale.
fields stores the translated key/value pairs as Record<string, string>.
| Namespace | entityType | entityId | Use |
|---|---|---|---|
content | Content model name (e.g. post) | Record ID | CMS content translations |
ui | ui-strings | namespace | Shared runtime UI strings |
ui:plugin-<id> | ui-strings | namespace | Capability-owned UI strings |
API
// List translation rows
listTranslations(filters?: TranslationFilters, deps: TranslationDeps): Promise<Translation[]>
// Upsert many keys in one call — used by plugin seed and bulk admin edit
bulkUpsertTranslations(input: BulkUpsertTranslationsInput, deps: TranslationDeps): Promise<Translation>
// Flatten all matching UI rows into one Record<key, string>
getUiStrings(input: GetUiStringsInput, deps: TranslationDeps): Promise<Record<string, string>>
// Single content translation helpers
getTranslation(entityType, entityId, locale, deps, namespace?): Promise<Translation | null>
getTranslatedFields(entityType, entityId, locale, deps): Promise<Record<string, string>>
deleteTranslation(entityType, entityId, locale, deps, namespace?): Promise<void>TranslationFilters
interface TranslationFilters {
locale?: Locale
namespace?: string // exact match
namespacePrefix?: string // prefix match — e.g. 'ui' matches 'ui' and 'ui:plugin-blog'
entityType?: string
entityId?: string
}BulkUpsertTranslationsInput
interface BulkUpsertTranslationsInput {
locale: Locale
namespace: string
entries: Record<string, string>
entityType?: string // defaults to 'ui-strings' for UI namespaces
entityId?: string // defaults to namespace for UI namespaces
status?: TranslationStatus
translatedBy?: string | null
reviewedBy?: string | null
}TranslationDeps vs TranslationStore
TranslationDeps is the minimal adapter contract that createPrismaDeps() satisfies.
TranslationStore extends it with the higher-level ops:
interface TranslationStore extends TranslationDeps {
count(filters?: TranslationFilters): Promise<number>
bulkUpsert(input: BulkUpsertTranslationsInput): Promise<Translation>
getUiStrings(input: GetUiStringsInput): Promise<Record<string, string>>
}Plugin Seed Translations
Capability plugins seed UI strings through installCapabilityPlugin:
export const backend = installCapabilityPlugin(capability, {
seedTranslations: {
en: {
'blog.list.title': 'Posts',
'blog.list.empty': 'No posts yet',
'blog.action.new': 'New Post'
},
fr: {
'blog.list.title': 'Articles',
'blog.list.empty': 'Aucun article',
'blog.action.new': 'Nouvel article'
}
}
})Conventional layout inside each plugin package:
src/
i18n/
en.json
fr.jsonSeeds are written once per locale per capability namespace. If a locale already has rows for a namespace, the seed is skipped — admin edits are never overwritten.
Public Runtime
createSystem() exposes resolved UI strings through:
system.uiStrings— flattenedRecord<string, string>for the system localepublicConfig.uiStrings— same, available on theGET /api/_system/public-configendpoint
The @maxnate-ui/system-core integration reads strings in this order:
options.uiStrings(inline override)GET /api/_system/public-configresponseGET /api/public/i18n/ui-strings(live refresh)
Admin Surface
The translation management page is at /admin/cms/translations. It supports:
- Locale filtering
- Namespace and namespace-prefix filtering
- Inline key/value editing
- Mark-for-review actions
Translation Status
type TranslationStatus = 'untranslated' | 'in_progress' | 'translated' | 'reviewed'