@nextvm/i18n
Type-safe translation keys with per-player locale, fallback chain, and build-time validation. Works across server messages, client notifications, and NUI interfaces.
Install
pnpm add @nextvm/i18ndefineLocale
import { defineLocale } from '@nextvm/i18n'
export default defineLocale({
'shop.title': 'Weapon Shop',
'shop.buy': 'Buy {weapon} for ${price}?',
'shop.insufficient_funds': 'Not enough money.',
} as const)defineLocale is a typed identity function. It preserves literal key types so other locales can be type-checked against the base.
I18nService
import { I18nService } from '@nextvm/i18n'
const i18n = new I18nService({
defaultLocale: 'en',
onMissing: (key, locale) => log.warn('missing', { key, locale }),
})
i18n.registerTranslations('shop', 'en', enLocale)
i18n.registerTranslations('shop', 'de', deLocale)
i18n.setPlayerLocale(source, 'de')
const msg = i18n.t(source, 'shop.buy', { weapon: 'Pistol', price: 500 })
// → 'Pistol für 500€ kaufen?'Fallback chain
If a key is missing in the player's locale, the resolver tries:
- The player's locale
- The server's default locale
- The hard-coded
enbaseline - The raw key
Each fallback fires the onMissing callback.
SameKeys helper
The SameKeys<Base, Other> type fails compilation if the second locale is missing keys from the base:
import { defineLocale, type SameKeys } from '@nextvm/i18n'
const en = defineLocale({ 'a': 'A', 'b': 'B' } as const)
const de: SameKeys<typeof en, typeof de> = defineLocale({
'a': 'A-de',
// ❌ TS error: 'b' is missing
} as const)TranslatableNotification
For server → client notifications, build the payload with the key + params instead of a resolved string. The client resolves it in its own locale:
import type { TranslatableNotification } from '@nextvm/i18n'
const notif: TranslatableNotification = {
key: 'shop.buy',
params: { weapon: 'Pistol', price: 500 },
type: 'info',
}Interpolation
Both {name} and ${name} placeholders are supported:
i18n.translate('greeting', 'en', { name: 'Tom' })
// 'Hello {name}' → 'Hello Tom'
// 'Welcome ${name}' → 'Welcome Tom'Missing parameters leave the placeholder intact (so you can detect the bug rather than ship undefined).
See also
- i18n concept
- [com/nextvm-official/nextvm/tree/main/docs/concept)