Skip to content
FrameworkStyle

Switch locale dynamically

Change the player language at runtime in HTML and React

Video.js starts in English and re-resolves translations when you opt into another active locale. How you trigger that depends on the platform.

Prerequisites

  • A target locale from a shipped lazy-loaded pack, a registered custom pack, or a Chrome browser translation fallback

Ambient <html lang>

The simplest switch updates the document language and lets providers pick it up:

document.documentElement.lang = 'fr';

An I18nProvider without locale reads <html lang>. Updating it is enough when you are not forcing an explicit locale.

Explicit provider language

import { useState } from 'react';
import { I18nProvider } from '@videojs/react/i18n';
import { Provider, VideoSkin, Video } from '@videojs/react/video';

function App() {
  const [locale, setLocale] = useState<'en' | 'es' | 'fr'>('en');

  return (
    <>
      <button type="button" onClick={() => setLocale('es')}>
        Español
      </button>
      <Provider>
        <I18nProvider locale={locale}>
          <VideoSkin>
            <Video src="..." playsInline />
          </VideoSkin>
        </I18nProvider>
      </Provider>
    </>
  );
}

Changing locale triggers lazy loadLocale for built-in packs.

Avoid flash while switching

Async lazy loads and browser translation fallback can briefly show English. Preload copy before the active locale changes with one of three common patterns:

Preload at bootstrap

Side-effect locale modules register shipped packs before the provider renders. Preload every language in a fixed picker to avoid the async gap.

import '@videojs/react/i18n/locales/es/register';
import '@videojs/react/i18n/locales/fr/register';

// Switching locale is instant: no remount, no lazy-load gap
<I18nProvider locale={userLocale}>
  <VideoSkin>...</VideoSkin>
</I18nProvider>

Prefetch before switching

When you cannot register every locale up front, import and register immediately before updating locale:

import { registerI18n } from '@videojs/react/i18n';

async function switchTo(next: 'es' | 'fr') {
  const { default: translations } = await import(`@videojs/react/i18n/locales/${next}`);
  registerI18n(next, translations);
  setLocale(next);
}

<I18nProvider locale={locale}>
  ...
</I18nProvider>

You still pay the import cost once per locale, but later switches to the same tag stay synchronous.

Scoped overrides with translations (React)

Pass translations on I18nProvider when overrides should apply to one subtree only, or when you want copy colocated with the switch handler:

async function switchTo(next: string) {
  const { default: translations } = await import(`@videojs/react/i18n/locales/${next}`);
  setState({ locale: next, translations });
}

<I18nProvider locale={state.locale} translations={state.translations}>
  ...
</I18nProvider>
Approach Scope Best for
Side-effect locale imports Global, all providers Language picker with a fixed set of shipped locales
registerI18n before switch Global, all providers On-demand prefetch before changing locale
I18nProvider translations Single provider subtree Scoped overrides, SSR first paint

Browser translation fallback

If a switched locale has no registered or shipped pack, Video.js can use Chrome’s Browser Translation API when the browser exposes globalThis.Translator and already has the target model installed. This is automatic after lazy loading is attempted.

Do not rely on it for instant switching. Preload or register reviewed packs when the language picker should update without a flash of English.

What’s next?