Skip to content

i18n

The player ships a lightweight i18n layer. All player-generated strings (error messages, phase labels, accessibility announcements) are routable through a pluggable ITranslator adapter.

Key namespaces

Translation keys are namespaced:

  • core.* — network, auth, policy, media, DRM, state, and a11y announcements. These live in the kit’s built-in en bundle.
  • plugin.<id>.* — plugin-owned strings. Each plugin registers its own bundle when it loads.

Examples of real core keys:

KeyDefault English string
core.network.offlineNo internet connection.
core.network.timeoutThe connection timed out. Trying again…
core.auth.forbiddenYour account doesn’t have access to this content.
core.policy.autoplayBlockedTap or click anywhere to start playback.
core.media.unsupportedThis format is not supported by your browser.
core.drm.licenseFailedCould not get a license for this content.
core.a11y.playingPlaying {title}
core.a11y.pausedPaused
plugin.tab-leader.lostPlayback paused — another tab is now playing.

The full list is in packages/nomercy-player-kit/src/i18n/en.ts.

Configuration

The translations option uses a locale-nested shape: the outer key is a BCP-47 language tag, and the inner record maps translation keys to strings. The kit’s English bundle is always merged underneath as the final fallback — you do not need to reproduce it.

TypeScript
import { defaultTranslations, nlTranslations } from '@nomercy-entertainment/nomercy-player-core';

player.setup({
language: 'nl',
translations: {
...defaultTranslations, // spread in the built-in English bundle as fallback
nl: nlTranslations, // complete built-in Dutch bundle
},
});

defaultTranslations is exported from @nomercy-entertainment/nomercy-player-core and contains { en: { ...allCoreKeys } }. Spreading it ensures English strings are always available when a locale key is missing. The kit also ships nlTranslations, a complete Dutch bundle covering every core key.

For a locale without a built-in bundle, supply the keys yourself. Anything you leave out falls back to English:

TypeScript
player.setup({
language: 'de',
translations: {
...defaultTranslations,
de: {
'core.policy.autoplayBlocked': 'Zum Starten irgendwo klicken.',
'core.network.offline': 'Keine Internetverbindung.',
},
},
});

language

TypeScript
language?: string

Initial language code (BCP-47, e.g. 'en', 'nl', 'ja-JP'). Default: navigator.language.

translations

TypeScript
translations?: Record<string, Record<string, string>>

Inline translation bundles keyed by BCP-47 language tag. Each inner record maps translation keys to translated strings for that locale. The kit’s English bundle is always merged underneath as the final fallback.

Supports variable interpolation with {key} syntax:

TypeScript
// This core key uses variable interpolation:
// 'core.a11y.playing': 'Playing {title}'

player.t('core.a11y.playing', { title: 'Sintel' });
// → 'Playing Sintel'

loadTranslations

TypeScript
loadTranslations?: (lang: string) => Promise<Record<string, string> | undefined>

Async loader called during setup() (if language is set) or when language(lang) is called at runtime.

Runtime language switching

TypeScript
await player.language('nl');

player.language(); // 'nl'

language('nl') calls loadTranslations('nl'), merges the result into the active table, and emits a language event:

TypeScript
player.on('language', ({ lang }) => {
document.documentElement.lang = lang;
});

Adding a single key override

Override one key without replacing the whole bundle:

TypeScript
import { defaultTranslations } from '@nomercy-entertainment/nomercy-player-core';

player.setup({
language: 'en',
translations: {
...defaultTranslations,
en: {
...defaultTranslations.en,
'core.policy.autoplayBlocked': 'Click to watch',
},
},
});

Translate a key

TypeScript
player.t('core.network.timeout');
// → 'The connection timed out. Trying again…'

player.t('core.a11y.playing', { title: 'Sintel' });
// → 'Playing Sintel'

Variable substitution uses {key} syntax. Unrecognized keys fall back to the key string itself so the player never silently hides text.

Translation events

EventPayloadFires when
language{ lang: string }Language changes

Custom translator adapter

Replace the entire i18n backend with your own ITranslator implementation:

TypeScript
import type { ITranslator } from '@nomercy-entertainment/nomercy-player-core';
import i18n from 'i18next';

class I18nextAdapter implements ITranslator {
t(key: string, vars?: Record<string, string>): string {
return i18n.t(key, vars);
}

language(): string;
language(lang: string): Promise<void>;
language(lang?: string): string | Promise<void> {
if (lang === undefined) return i18n.language;

return i18n.changeLanguage(lang);
}
}

player.setup({
translator: new I18nextAdapter(),
});

Vite plugin

The published packages are self-contained: their translation bundles are resolved at package build time into plain lazy imports, so installing and importing them needs no special Vite configuration at all.

The Vite plugin exists for one case: your own source code calling translationsFromGlob('./i18n/*.ts') with a literal string, for example in a plugin you author yourself. Vite’s static analyser cannot see through the helper, so the plugin rewrites the call into the import.meta.glob form during your build:

TypeScript
// vite.config.ts
import { nomercyTranslationsPlugin } from '@nomercy-entertainment/nomercy-player-core/vite-plugin';

export default {
plugins: [
nomercyTranslationsPlugin(),
],
};

The rewrite stays lazy: only the active language bundle is fetched at runtime. Passing inline translations objects works without the plugin.