Skip to content

@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

bash
pnpm add @nextvm/i18n

defineLocale

typescript
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

typescript
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:

  1. The player's locale
  2. The server's default locale
  3. The hard-coded en baseline
  4. 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:

typescript
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:

typescript
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:

typescript
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)

Released under the LGPL-3.0 License.