---
title: Switch locale dynamically
description: Change the player language at runtime in HTML and React
---

import FrameworkCase from '@/components/docs/FrameworkCase.astro';
import Aside from '@/components/Aside.astro';
import DocsLink from '@/components/docs/DocsLink.astro';

Video.js starts in English and re-resolves translations when you opt into another active locale.

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

```ts
document.documentElement.lang = 'fr';
```

<Aside type="note">
<FrameworkCase frameworks={["html"]}>

Ambient switching only applies when `<media-i18n>` has no explicit `lang` attribute.

</FrameworkCase>

<FrameworkCase frameworks={["react"]}>

Ambient switching only applies when `I18nProvider` has no explicit `locale` prop.

</FrameworkCase>
</Aside>

<FrameworkCase frameworks={["html"]}>

Mounted `<media-i18n>` providers observe `lang` on `<html>` and ancestors. No remount required.

</FrameworkCase>

<FrameworkCase frameworks={["react"]}>

An <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink> without `locale` reads `<html lang>`. Updating it is enough when you are not forcing an explicit locale.

</FrameworkCase>

## Explicit provider language

<FrameworkCase frameworks={["react"]}>

```tsx
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.

</FrameworkCase>

<FrameworkCase frameworks={["html"]}>

```html
<media-i18n lang="es" id="provider">
  <video-player>...</video-player>
</media-i18n>

<script type="module">
  document.querySelector('#lang-picker').addEventListener('change', (e) => {
    document.getElementById('provider').lang = e.target.value;
  });
</script>
```

Or set `document.documentElement.lang` when the mounted `<media-i18n>` inherits ambient language.

</FrameworkCase>

## 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.

<FrameworkCase frameworks={["react"]}>

```tsx
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>
```

</FrameworkCase>

<FrameworkCase frameworks={["html"]}>

```ts
import '@videojs/html/i18n/locales/es/register';
import '@videojs/html/i18n/locales/fr/register';
```

```html
<media-i18n id="provider">
  <video-player>...</video-player>
</media-i18n>

<script type="module">
  document.getElementById('provider').lang = 'fr';
</script>
```

</FrameworkCase>

<FrameworkCase frameworks={["react"]}>

### Prefetch before switching

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

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

const loaders = {
  es: () => import('@videojs/react/i18n/locales/es'),
  fr: () => import('@videojs/react/i18n/locales/fr'),
};

async function switchTo(next: keyof typeof loaders) {
  const { default: translations } = await loaders[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`

Pass `translations` on <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink> when overrides should apply to one subtree only, or when you want copy colocated with the switch handler:

```tsx
const loaders = {
  es: () => import('@videojs/react/i18n/locales/es'),
  fr: () => import('@videojs/react/i18n/locales/fr'),
};

async function switchTo(next: string) {
  const load = loaders[next as keyof typeof loaders];
  if (!load) throw new Error(`Unsupported locale: ${next}`);
  const { default: translations } = await load();
  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 |

<Aside type="note">
`registerI18n` merges into the global registry. Repeated calls add or replace keys for the same tag, they do not wipe other locales. Registry updates notify mounted providers via `onI18nRegistryChange`, so late registrations still refresh wired controls.
</Aside>

<Aside type="tip">
For server-rendered first paint, see <DocsLink slug="how-to/i18n-ssr">SSR and hydration</DocsLink>.
</Aside>

</FrameworkCase>

## Browser translation fallback

If a switched locale has no registered or shipped pack, Video.js can use Chrome's [Browser Translation API](https://developer.mozilla.org/en-US/docs/Web/API/Translator) 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?

- <DocsLink slug="how-to/i18n-ssr">SSR with locale</DocsLink>
- <DocsLink slug="reference/use-locale">`useLocale`</DocsLink>: Read the active locale in React
