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-inenbundle.plugin.<id>.*— plugin-owned strings. Each plugin registers its own bundle when it loads.
Examples of real core keys:
| Key | Default English string |
|---|---|
core.network.offline | No internet connection. |
core.network.timeout | The connection timed out. Trying again… |
core.auth.forbidden | Your account doesn’t have access to this content. |
core.policy.autoplayBlocked | Tap or click anywhere to start playback. |
core.media.unsupported | This format is not supported by your browser. |
core.drm.licenseFailed | Could not get a license for this content. |
core.a11y.playing | Playing {title} |
core.a11y.paused | Paused |
plugin.tab-leader.lost | Playback 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.
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:
player.setup({
language: 'de',
translations: {
...defaultTranslations,
de: {
'core.policy.autoplayBlocked': 'Zum Starten irgendwo klicken.',
'core.network.offline': 'Keine Internetverbindung.',
},
},
});
language
language?: string
Initial language code (BCP-47, e.g. 'en', 'nl', 'ja-JP'). Default: navigator.language.
translations
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:
// This core key uses variable interpolation:
// 'core.a11y.playing': 'Playing {title}'
player.t('core.a11y.playing', { title: 'Sintel' });
// → 'Playing Sintel'
loadTranslations
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
await player.language('nl');
player.language(); // 'nl'
language('nl') calls loadTranslations('nl'), merges the result into the active table, and emits a language event:
player.on('language', ({ lang }) => {
document.documentElement.lang = lang;
});
Adding a single key override
Override one key without replacing the whole bundle:
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
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
| Event | Payload | Fires when |
|---|---|---|
language | { lang: string } | Language changes |
Custom translator adapter
Replace the entire i18n backend with your own ITranslator implementation:
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:
// 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.