Skip to content
FrameworkStyle

Internationalization

How Video.js translates player UI copy with English phrase keys, a global registry, and locale providers

Video.js ships in English by default. Non-English UI is opt-in: mount an i18n provider, then configure its active language. Video.js resolves the matching strings for control labels, ARIA text, tooltips, and error copy.

The default i18n path is automatic after you opt in. Built-in locale packs lazy-load on demand, and Chrome can fill missing languages or missing keys through the Browser Translation API when an on-device translation model is already available.

Components ask for strings by current English phrase (Play, Pause, Seek forward {seconds} seconds), so missing translations stay readable.

<html lang="es">
  <media-i18n>
    <video-player>
      <video-skin>
        <video src="..." playsinline></video>
      </video-skin>
    </video-player>
  </media-i18n>
</html>

Mount <media-i18n> and set its lang attribute, or let it inherit the nearest ancestor lang. Built-in packs then load automatically.

Registering packs yourself is only needed when you want custom copy, synchronous first paint, SSR, or CDN loading.

Phrase keys

Core controls expose current English phrases. PlayButtonCore.getLabel() returns 'Play'; the translator turns that into 'Play', 'Reproducir', or your override.

Phrase params are typed in TranslationParams. TypeScript catches missing {param} placeholders and wrong argument names at compile time.

Parametric strings use {placeholder} tokens, for example 'Seek forward {seconds} seconds' and '{duration} remaining'.

See Translation phrases for every current key and the player UI that uses it.

Global registry

registerI18n(locale, translations) merges strings into a process-wide map. English (en) is pre-registered when the i18n bundle loads, which is why the default player works without setup.

API Purpose
registerI18n Add or merge a locale layer
getI18nTranslations Read the merged map for a locale
hasRegisteredLocale Check whether a tag is in the registry
onI18nRegistryChange Subscribe to registry updates

Import from @videojs/html/i18n.

Opt into a language

Leave your app in English by default. To opt into another language, mount the framework provider around the translated player subtree:

  • Wrap the player in <media-i18n>.
  • Set <html lang="es"> or lang="es" on <media-i18n> to opt into Spanish.
  • Video.js lazy-loads the shipped Spanish pack when it is not already registered.

Use a separate <media-i18n> for standalone controls or players that need different languages on the same page.

If no pack exists, the provider keeps English until a registered pack, lazy-loaded pack, provider override, or supported browser translation layer supplies strings.

Locale resolution

<media-i18n> resolves its active language in order:

  1. Explicit: its lang attribute
  2. Ambient: nearest ancestor [lang]
  3. Fallback: English defaults

Changing <html lang> re-renders wired controls without remounting the player. An explicit provider lang overrides ambient language until you remove or update it.

BCP 47 fallback

Lookups walk a parent chain, not sibling locales. es-MX falls back to es, then en, not to es-419.

es-MX  →  es  →  en
zh-Hant-HK  →  zh-hant  →  zh  →  en

getI18nTranslations, lazy loadLocale, and providers all use the same chain via findLocaleKeys.

Merge priority

Later layers win over earlier ones:

Layer Source
1 (base) English defaults (en.ts)
2 Browser Translation API (Chrome, pre-installed model only)
3 registerI18n / CDN locale modules
4 Lazy built-in packs (loadLocale)

Built-in locale packs

Providers call loadLocale automatically for the active locale chain when a pack is not already registered, so app bundles can split locale packs into async chunks.

HTML locale files live under @videojs/html/i18n/locales/*.

To preload a shipped language without writing a registerI18n call, import its side-effect module:

import '@videojs/html/i18n/locales/es/register';

Import and register a pack manually when you need custom copy or want to preload a fixed language picker. See Register a locale.

CDN consumers load self-registering modules:

<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/video.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/locales/es.js"></script>

Browser translation

After lazy loading runs, Video.js can ask the browser to translate the English registry through the Browser Translation API. This currently works in Chrome when globalThis.Translator is available and the matching on-device model is already installed.

Video.js does not download translation models during normal provider resolution. If Chrome reports the model as unavailable, downloadable, or still downloading, controls keep using the registered, lazy-loaded, or English fallback strings.

Browser translation is a fallback, not a replacement for reviewed locale packs. Use shipped packs or your own registered strings for production-critical copy and SSR.

Common pitfalls

// ❌ Don't: old camelCase keys are ignored
registerI18n('es', { play: 'Reproducir' });

// ✅ Do
registerI18n('es', { Play: 'Reproducir' });
// ❌ Don't: parametric key without the placeholder
registerI18n('es', { 'Seek forward {seconds} seconds': 'Adelante 10 segundos' }); // TS error: missing {seconds}

// ✅ Do
registerI18n('es', { 'Seek forward {seconds} seconds': 'Adelantar {seconds} segundos' });

See also