mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
docs(site): add i18n concept, guides, and API reference pages
Documents opaque keys, registry APIs, React/HTML providers, and locale workflows for #1373. Extends api-docs-builder discovery for core/react i18n exports and adds matching reference MDX pages. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
---
|
||||
title: Internationalization (i18n)
|
||||
description: How Video.js translates player UI copy with opaque keys, a global registry, and locale providers
|
||||
frameworkTitle:
|
||||
html: Internationalization in HTML
|
||||
react: Internationalization in React
|
||||
---
|
||||
|
||||
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
|
||||
import Aside from '@/components/Aside.astro';
|
||||
import DocsLink from '@/components/docs/DocsLink.astro';
|
||||
|
||||
Video.js translates control labels, ARIA text, tooltips, and error copy through a single **global registry**. Components ask for strings by **opaque key** (`play`, `pause`, `seekForward`) — not by English text — so locale files stay stable when copy changes.
|
||||
|
||||
```html title="html"
|
||||
<html lang="es">
|
||||
<video-player>
|
||||
<video-skin>
|
||||
<video src="..." playsinline></video>
|
||||
</video-skin>
|
||||
</video-player>
|
||||
</html>
|
||||
```
|
||||
|
||||
```tsx title="react"
|
||||
<html lang="es">
|
||||
<Provider>
|
||||
<VideoSkin>
|
||||
<Video src="..." playsInline />
|
||||
</VideoSkin>
|
||||
</Provider>
|
||||
</html>
|
||||
```
|
||||
|
||||
Register a locale once (or rely on lazy-loaded built-in packs), set `lang`, and skins pick up translated strings automatically.
|
||||
|
||||
## Opaque keys
|
||||
|
||||
Core controls expose keys, not visible labels. `PlayButtonCore.getLabel()` returns `'play'`; the translator turns that into `'Play'`, `'Reproducir'`, or your override.
|
||||
|
||||
Keys are typed in `TranslationParams` — TypeScript catches missing `{param}` placeholders and wrong argument names at compile time. See `packages/core/src/core/i18n/types.ts` for the full key list.
|
||||
|
||||
Parametric strings use `{placeholder}` tokens, for example `seekForward: 'Seek forward {seconds} seconds'` and `timeRemainingPhrase: '{duration} remaining'`.
|
||||
|
||||
## Global registry
|
||||
|
||||
`registerI18n(locale, translations)` merges strings into a process-wide map. English (`en`) is pre-registered when `@videojs/core/i18n` loads.
|
||||
|
||||
| API | Purpose |
|
||||
| --- | --- |
|
||||
| <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> | Add or merge a locale layer |
|
||||
| <DocsLink slug="reference/get-i18n-translations">`getI18nTranslations`</DocsLink> | Read the merged map for a locale |
|
||||
| <DocsLink slug="reference/has-registered-i18n">`hasRegisteredI18n`</DocsLink> | Check whether a tag is in the registry |
|
||||
| <DocsLink slug="reference/on-i18n-registry-change">`onI18nRegistryChange`</DocsLink> | Subscribe to registry updates |
|
||||
|
||||
Import from `@videojs/html/i18n`, `@videojs/react/i18n`, or `@videojs/core/i18n` depending on your bundle.
|
||||
|
||||
## When you need a provider
|
||||
|
||||
**You usually don't** — if the active locale is available and packs are registered (or lazy-loaded), built-in players already wire providers:
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
|
||||
- `<video-player>`, `<audio-player>`, and skins include an i18n provider mixin.
|
||||
- Set `<html lang="es">` (or `lang` on an ancestor) and ship/register Spanish strings.
|
||||
|
||||
Use <DocsLink slug="reference/media-i18n-provider">`<media-i18n-provider>`</DocsLink> when you render **standalone** controls outside a player, or when one page hosts players in different languages.
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
- Preset skins render inside <DocsLink slug="reference/player-container">`Container`</DocsLink>, which mounts <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink> with `langRootRef` on the player shell.
|
||||
- Set `<html lang="es">` and register or lazy-load Spanish.
|
||||
|
||||
Wrap with an explicit <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink> when you need a forced locale, per-render overrides, or custom controls outside `Container`.
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
**You do need to register or lazy-load packs** before labels appear in the target language. `<html lang="fr">` alone does nothing if French strings were never registered and no built-in `fr` pack loads.
|
||||
|
||||
## Locale resolution
|
||||
|
||||
Providers resolve the active locale in order:
|
||||
|
||||
1. **Explicit** — `lang` on <DocsLink slug="reference/media-i18n-provider">`<media-i18n-provider>`</DocsLink> or `locale` on <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>
|
||||
2. **Ambient** — nearest ancestor `[lang]` (HTML) or `langRootRef` / `<html lang>` (React)
|
||||
3. **Fallback** — English defaults
|
||||
|
||||
Changing `<html lang>` re-renders wired controls without remounting the player. An explicit `locale` / `lang` on a provider overrides ambient `<html lang>` until you remove or update that override.
|
||||
|
||||
## 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 `localeLookupChain`.
|
||||
|
||||
## 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`) |
|
||||
| 5 (top) | React `translations` prop on `I18nProvider` |
|
||||
|
||||
## Built-in locale packs
|
||||
|
||||
Video.js ships ~50 locale files under `@videojs/core/i18n/locales/*`, re-exported from `@videojs/html/i18n/locales/*` and `@videojs/react/i18n/locales/*`. Providers call `loadLocale` automatically when a pack is not already registered.
|
||||
|
||||
CDN consumers load self-registering modules:
|
||||
|
||||
```html
|
||||
<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>
|
||||
```
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
```tsx
|
||||
// ❌ Don't — key is the English word; keys are opaque tokens
|
||||
registerI18n('es', { Play: 'Reproducir' });
|
||||
|
||||
// ✅ Do
|
||||
registerI18n('es', { play: 'Reproducir' });
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ❌ Don't — parametric key without the placeholder
|
||||
registerI18n('es', { seekForward: 'Adelante 10 segundos' }); // TS error: missing {seconds}
|
||||
|
||||
// ✅ Do
|
||||
registerI18n('es', { seekForward: 'Adelantar {seconds} segundos' });
|
||||
```
|
||||
|
||||
<Aside type="tip">
|
||||
For zero flash-of-English on SSR or locale switches, pass `translations` directly to `I18nProvider` instead of relying on async `loadLocale`. See <DocsLink slug="how-to/i18n-ssr">SSR with locale</DocsLink>.
|
||||
</Aside>
|
||||
|
||||
## See also
|
||||
|
||||
- <DocsLink slug="how-to/i18n-register-locale">Register a custom locale</DocsLink>
|
||||
- <DocsLink slug="how-to/i18n-override-translations">Override individual keys</DocsLink>
|
||||
- <DocsLink slug="how-to/i18n-switch-locale">Switch locale dynamically</DocsLink>
|
||||
- <DocsLink slug="how-to/i18n-ssr">SSR and hydration</DocsLink>
|
||||
- <DocsLink slug="how-to/i18n-add-built-in-locale">Add a built-in locale (contributors)</DocsLink>
|
||||
- <DocsLink slug="concepts/accessibility">Accessibility</DocsLink> — translated ARIA labels
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Add a built-in locale
|
||||
description: Ship a new translation pack in @videojs/core for contributors
|
||||
---
|
||||
|
||||
import Aside from '@/components/Aside.astro';
|
||||
import DocsLink from '@/components/docs/DocsLink.astro';
|
||||
|
||||
Built-in locales live in `packages/core/src/core/i18n/locales/`. The build generates lazy loaders, CDN chunks, and HTML/React re-exports from that directory.
|
||||
|
||||
This guide is for **contributors** adding or updating shipped packs — app authors should use <DocsLink slug="how-to/i18n-register-locale">Register a custom locale</DocsLink> instead.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Familiarity with <DocsLink slug="concepts/i18n">Internationalization</DocsLink> and `TranslationParams`
|
||||
- English defaults in `packages/core/src/core/i18n/locales/en.ts` as the source of keys
|
||||
|
||||
## 1. Add the locale file
|
||||
|
||||
Create `packages/core/src/core/i18n/locales/{tag}.ts` using the BCP 47 tag as the filename (`pt-BR.ts`, `zh-CN.ts`):
|
||||
|
||||
```ts title="packages/core/src/core/i18n/locales/xx.ts"
|
||||
import type { Translations } from '../types';
|
||||
|
||||
export default {
|
||||
play: '…',
|
||||
pause: '…',
|
||||
// all keys from en.ts — each entry optional but completeness is preferred
|
||||
} satisfies Partial<Translations>;
|
||||
```
|
||||
|
||||
Use **opaque keys** from `en.ts`, not English sentences as keys. Parametric strings must include the same `{placeholder}` tokens (TypeScript enforces this via `satisfies Partial<Translations>`).
|
||||
|
||||
Copy guidelines:
|
||||
|
||||
- `liveBadge` — sentence case (`Live`); skins uppercase via CSS where needed
|
||||
- `seek` — slider aria label (`Seek`), not legacy "Progress" wording
|
||||
- `timeRemainingPhrase` — `'{duration} remaining'` pattern with `{duration}` placeholder
|
||||
- Button and error strings — short aria-label style, aligned with V10 core label usage
|
||||
|
||||
## 2. Register the tag in built-in metadata
|
||||
|
||||
Add the tag to `BUILT_IN_LOCALES` in `packages/core/src/core/i18n/built-in-locales.ts`. Add alias tags (`pt`, `zh`) to `LOCALE_ALIAS_TAGS` only when intentional.
|
||||
|
||||
## 3. Regenerate loaders
|
||||
|
||||
From the repo root:
|
||||
|
||||
```bash
|
||||
pnpm -F @videojs/core build
|
||||
```
|
||||
|
||||
This runs `generate:locales` and updates:
|
||||
|
||||
- `packages/core/src/core/i18n/load-locale.ts`
|
||||
- `packages/core/src/core/i18n/locales/all.ts`
|
||||
- `packages/html/src/i18n/locales/*` and `packages/react/src/i18n/locales/*` re-exports
|
||||
- CDN locale stubs via `packages/html/scripts/build-cdn-locales.ts` when building HTML CDN output
|
||||
|
||||
Do not hand-edit generated files.
|
||||
|
||||
## 4. Test
|
||||
|
||||
```bash
|
||||
pnpm -F @videojs/core test src/core/i18n
|
||||
```
|
||||
|
||||
Add or extend locale tests if the tag introduces alias or loader edge cases.
|
||||
|
||||
## 5. CDN locale chunk
|
||||
|
||||
CDN builds emit `cdn/locales/{tag}.js` that import the pack and call `registerI18n`. Verify with:
|
||||
|
||||
```bash
|
||||
pnpm -F @videojs/html build:cdn
|
||||
```
|
||||
|
||||
<Aside type="note">
|
||||
Do not copy strings blindly from Video.js v8 `lang/` JSON — v10 uses different keys and aria-label semantics. Translate from `en.ts` keys and core `getLabel` usage.
|
||||
</Aside>
|
||||
|
||||
## What's next?
|
||||
|
||||
- Open a PR with the locale file and `built-in-locales.ts` change only (generated outputs come from CI/build)
|
||||
- <DocsLink slug="concepts/i18n">Internationalization concept</DocsLink>
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: Override translation keys
|
||||
description: Patch individual i18n strings without replacing an entire locale pack
|
||||
---
|
||||
|
||||
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
|
||||
import DocsLink from '@/components/docs/DocsLink.astro';
|
||||
|
||||
`registerI18n` **merges** — each call adds or replaces keys for that locale tag without wiping prior registrations. Use this to tweak shipped packs or A/B test copy.
|
||||
|
||||
Read <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for merge order and key naming.
|
||||
|
||||
## Override after a built-in pack
|
||||
|
||||
```ts
|
||||
import { registerI18n } from '@videojs/html/i18n';
|
||||
import es from '@videojs/html/i18n/locales/es';
|
||||
|
||||
registerI18n('es', es);
|
||||
registerI18n('es', { play: 'Comenzar' }); // only `play` changes
|
||||
```
|
||||
|
||||
Later registrations win for the same key. The rest of the `es` pack stays intact.
|
||||
|
||||
## React provider overrides
|
||||
|
||||
Pass `translations` on <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink> to scope overrides to one subtree. This layer sits **above** registry and lazy packs:
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
```tsx
|
||||
import { I18nProvider } from '@videojs/react/i18n';
|
||||
import { Provider, VideoSkin, Video } from '@videojs/react/video';
|
||||
|
||||
<Provider>
|
||||
<I18nProvider locale="es" translations={{ play: 'Comenzar' }}>
|
||||
<VideoSkin>
|
||||
<Video src="..." playsInline />
|
||||
</VideoSkin>
|
||||
</I18nProvider>
|
||||
</Provider>
|
||||
```
|
||||
|
||||
Nested providers inherit the parent locale when you only pass `translations`:
|
||||
|
||||
```tsx
|
||||
<I18nProvider locale="de">
|
||||
<I18nProvider translations={{ play: 'Override' }}>
|
||||
{/* locale stays `de`; only `play` differs */}
|
||||
</I18nProvider>
|
||||
</I18nProvider>
|
||||
```
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
## HTML reflected text
|
||||
|
||||
Use <DocsLink slug="reference/media-text">`<media-text>`</DocsLink> for static copy outside buttons:
|
||||
|
||||
```html
|
||||
<media-i18n-provider lang="es">
|
||||
<media-text key="play"></media-text>
|
||||
</media-i18n-provider>
|
||||
```
|
||||
|
||||
Override the key in the registry or set `lang` on the provider to a locale where you patched `play`.
|
||||
|
||||
## Parametric keys
|
||||
|
||||
Keep required placeholders when overriding:
|
||||
|
||||
```ts
|
||||
// ✅
|
||||
registerI18n('es', { seekForward: 'Adelantar {seconds} segundos' });
|
||||
|
||||
// ❌ TypeScript error — missing {seconds}
|
||||
registerI18n('es', { seekForward: 'Adelantar' });
|
||||
```
|
||||
|
||||
## What's next?
|
||||
|
||||
- <DocsLink slug="how-to/i18n-switch-locale">Switch locale dynamically</DocsLink>
|
||||
- <DocsLink slug="reference/get-i18n-translations">`getI18nTranslations`</DocsLink> — inspect the merged map
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: Register a custom locale
|
||||
description: Register translation packs for HTML, React, and CDN players
|
||||
---
|
||||
|
||||
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
|
||||
import Aside from '@/components/Aside.astro';
|
||||
import DocsLink from '@/components/docs/DocsLink.astro';
|
||||
|
||||
Register strings once with <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink>, then set `lang` on the page or pass `locale` to a provider. Read <DocsLink slug="concepts/i18n">Internationalization</DocsLink> first if you are new to opaque keys and the registry.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- A locale tag ([BCP 47](https://www.rfc-editor.org/rfc/rfc5646), e.g. `es`, `pt-BR`)
|
||||
- A partial translation map keyed by camelCase tokens (`play`, `pause`, …)
|
||||
|
||||
## Use a shipped pack
|
||||
|
||||
Built-in packs live in `@videojs/*/i18n/locales/*`. Import and register before the player renders:
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
|
||||
```ts title="main.ts"
|
||||
import '@videojs/html/video';
|
||||
import { registerI18n } from '@videojs/html/i18n';
|
||||
import es from '@videojs/html/i18n/locales/es';
|
||||
|
||||
registerI18n('es', es);
|
||||
```
|
||||
|
||||
```html title="index.html"
|
||||
<html lang="es">
|
||||
<video-player>
|
||||
<video-skin>
|
||||
<video src="..." playsinline></video>
|
||||
</video-skin>
|
||||
</video-player>
|
||||
</html>
|
||||
```
|
||||
|
||||
If you skip `registerI18n`, the built-in provider still **lazy-loads** `es` when `lang="es"` — explicit registration avoids the async gap on first paint.
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
```tsx title="app.tsx"
|
||||
import { registerI18n } from '@videojs/react/i18n';
|
||||
import es from '@videojs/react/i18n/locales/es';
|
||||
import { Provider, VideoSkin, Video } from '@videojs/react/video';
|
||||
|
||||
registerI18n('es', es);
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<Provider>
|
||||
<VideoSkin>
|
||||
<Video src="..." playsInline />
|
||||
</VideoSkin>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Preset skins use `Container`, which includes <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>. Set `<html lang="es">` or wrap with `<I18nProvider locale="es">`.
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
## Register your own pack
|
||||
|
||||
```ts title="my-es.ts"
|
||||
import type { Translations } from '@videojs/core/i18n';
|
||||
|
||||
const es: Partial<Translations> = {
|
||||
play: 'Reproducir',
|
||||
pause: 'Pausa',
|
||||
mute: 'Silenciar',
|
||||
unmute: 'Activar sonido',
|
||||
};
|
||||
|
||||
export default es;
|
||||
```
|
||||
|
||||
```ts
|
||||
import { registerI18n } from '@videojs/html/i18n'; // or @videojs/react/i18n
|
||||
import es from './my-es';
|
||||
|
||||
registerI18n('es', es);
|
||||
```
|
||||
|
||||
All keys are optional — missing keys fall back through the BCP 47 chain to English.
|
||||
|
||||
## CDN
|
||||
|
||||
Load the player and a locale chunk. Locale modules call `registerI18n` on import:
|
||||
|
||||
```html
|
||||
<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>
|
||||
|
||||
<html lang="es">
|
||||
<video-player>
|
||||
<video-skin>
|
||||
<video src="..." playsinline></video>
|
||||
</video-skin>
|
||||
</video-player>
|
||||
</html>
|
||||
```
|
||||
|
||||
<Aside type="note">
|
||||
CDN locale files import `registerI18n` from the shared `cdn/i18n.js` bundle so every chunk shares one registry instance.
|
||||
</Aside>
|
||||
|
||||
## Custom CDN locale
|
||||
|
||||
```js title="my-locale.js"
|
||||
import { registerI18n } from 'https://cdn.jsdelivr.net/npm/@videojs/html/cdn/i18n.js';
|
||||
|
||||
registerI18n('es', {
|
||||
play: 'Reproducir',
|
||||
pause: 'Pausa',
|
||||
});
|
||||
```
|
||||
|
||||
Load your module after the player script, before playback starts.
|
||||
|
||||
## What's next?
|
||||
|
||||
- <DocsLink slug="how-to/i18n-override-translations">Override individual keys</DocsLink>
|
||||
- <DocsLink slug="how-to/i18n-switch-locale">Switch locale at runtime</DocsLink>
|
||||
- <DocsLink slug="reference/register-i18n">`registerI18n` reference</DocsLink>
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: SSR and locale
|
||||
description: Render translated player UI on the server without a flash of English
|
||||
---
|
||||
|
||||
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
|
||||
import Aside from '@/components/Aside.astro';
|
||||
import DocsLink from '@/components/docs/DocsLink.astro';
|
||||
|
||||
Server-rendered pages should output the correct `lang` **and** supply translations on the first client render. Lazy `loadLocale` runs after hydration — without preloaded copy, controls briefly show English.
|
||||
|
||||
Read <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for provider resolution and merge order.
|
||||
|
||||
## Set `<html lang>`
|
||||
|
||||
Render the document language on the server:
|
||||
|
||||
```html
|
||||
<html lang="es">
|
||||
```
|
||||
|
||||
React providers use a server snapshot of `undefined` for ambient lang, then read `<html lang>` on hydration. Passing `locale` explicitly avoids any mismatch.
|
||||
|
||||
## Preload translations (React)
|
||||
|
||||
Import the locale pack on the server (or in a server component) and pass it to <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>:
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
```tsx
|
||||
import { I18nProvider } from '@videojs/react/i18n';
|
||||
import { Provider, VideoSkin, Video } from '@videojs/react/video';
|
||||
|
||||
export async function Player({ locale }: { locale: string }) {
|
||||
const { default: translations } = await import(`@videojs/react/i18n/locales/${locale}`);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<I18nProvider locale={locale} translations={translations}>
|
||||
<VideoSkin>
|
||||
<Video src="..." playsInline />
|
||||
</VideoSkin>
|
||||
</I18nProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`translations` on the first render skips the async lazy layer — labels match on server and client.
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
## Register on the server
|
||||
|
||||
For custom packs, call <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> in server bootstrap code before rendering players:
|
||||
|
||||
```ts
|
||||
import { registerI18n } from '@videojs/react/i18n';
|
||||
import es from './locales/es';
|
||||
|
||||
registerI18n('es', es);
|
||||
```
|
||||
|
||||
The registry is module singleton state — registration in server entry points carries into the client bundle when shared.
|
||||
|
||||
## next-intl and app routers
|
||||
|
||||
```tsx
|
||||
import { getLocale } from 'next-intl/server';
|
||||
|
||||
const locale = await getLocale();
|
||||
const { default: translations } = await import(`@videojs/react/i18n/locales/${locale}`);
|
||||
|
||||
<I18nProvider locale={locale} translations={translations}>
|
||||
...
|
||||
</I18nProvider>
|
||||
```
|
||||
|
||||
Match your app's locale negotiation — Video.js does not replace framework i18n; it consumes the tag you pass.
|
||||
|
||||
## HTML custom elements
|
||||
|
||||
SSR HTML players should emit `lang` on `<html>` or on <DocsLink slug="reference/media-i18n-provider">`<media-i18n-provider>`</DocsLink>. Register packs in the entry module loaded before custom elements upgrade:
|
||||
|
||||
```ts
|
||||
import '@videojs/html/video';
|
||||
import { registerI18n } from '@videojs/html/i18n';
|
||||
import es from '@videojs/html/i18n/locales/es';
|
||||
|
||||
registerI18n('es', es);
|
||||
```
|
||||
|
||||
<Aside type="caution">
|
||||
Do not rely on browser-only APIs (`getBrowserTranslations`) during SSR. Ship packs or register strings explicitly.
|
||||
</Aside>
|
||||
|
||||
## What's next?
|
||||
|
||||
- <DocsLink slug="how-to/i18n-switch-locale">Switch locale at runtime</DocsLink>
|
||||
- <DocsLink slug="how-to/i18n-register-locale">Register a custom locale</DocsLink>
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
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 re-resolves translations when the active locale changes. How you trigger that depends on the platform.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- <DocsLink slug="how-to/i18n-register-locale">Locale packs registered or lazy-loadable</DocsLink>
|
||||
|
||||
## Ambient `<html lang>`
|
||||
|
||||
The simplest switch — update the document language and let providers pick it up:
|
||||
|
||||
```ts
|
||||
document.documentElement.lang = 'fr';
|
||||
```
|
||||
|
||||
<Aside type="note">
|
||||
Ambient switching only applies when no provider sets an explicit locale. React `I18nProvider locale={…}`, HTML `<media-i18n-provider lang="…">`, and `element.lang` on a provider all override `<html lang>` until you remove or update that override.
|
||||
</Aside>
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
|
||||
Player elements and skins observe `lang` on `<html>` and ancestors. No remount required.
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
`Container`'s <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink> reads `langRootRef` from the player shell. Updating `<html lang>` is enough when you are not forcing `locale` on an outer provider.
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
## Explicit provider locale
|
||||
|
||||
<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-provider lang="es" id="provider">
|
||||
<video-player>...</video-player>
|
||||
</media-i18n-provider>
|
||||
|
||||
<script type="module">
|
||||
document.querySelector('#lang-picker').addEventListener('change', (e) => {
|
||||
document.getElementById('provider').lang = e.target.value;
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Or set `document.documentElement.lang` if the player inherits ambient language.
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
## Avoid flash while switching
|
||||
|
||||
Async lazy loads can briefly show English. Pre-import the pack and pass `translations`:
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
```tsx
|
||||
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>
|
||||
```
|
||||
|
||||
</FrameworkCase>
|
||||
|
||||
<Aside type="tip">
|
||||
For server-rendered first paint, see <DocsLink slug="how-to/i18n-ssr">SSR and hydration</DocsLink>.
|
||||
</Aside>
|
||||
|
||||
## CDN
|
||||
|
||||
Reload or navigate with an updated `locale` query parameter, or load a different locale module before playback. CDN shells typically full-reload on locale change because locale script tags are not hot-swappable.
|
||||
|
||||
## 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
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: createI18n
|
||||
description: Factory that creates an isolated React i18n context stack with I18nProvider and hooks
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
`createI18n` mirrors <DocsLink slug="reference/create-player">`createPlayer`</DocsLink>: it returns a dedicated `I18nProvider`, `useTranslator`, and `useLocale` for apps that need an isolated i18n stack. Most apps import the default exports from `@videojs/react/i18n` instead.
|
||||
|
||||
```tsx
|
||||
import { createI18n } from '@videojs/react/i18n';
|
||||
|
||||
const { I18nProvider, useTranslator } = createI18n({
|
||||
loadLocale: async (tag) => import(`./locales/${tag}.json`),
|
||||
});
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<I18nProvider locale="ja">
|
||||
<Controls />
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<UtilReference util="createI18n" />
|
||||
|
||||
</FrameworkCase>
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: getI18nTranslations
|
||||
description: Read the merged translation map for a locale using BCP 47 parent-chain fallback
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
|
||||
`getI18nTranslations` walks the BCP 47 lookup chain (`es-MX` → `es` → `en`) and merges registry layers into one map. Providers and `createTranslator` use this internally; call it directly when building custom UI outside the built-in mixins.
|
||||
|
||||
See <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for merge order and fallback rules.
|
||||
|
||||
```ts
|
||||
import { createTranslator, getI18nTranslations } from '@videojs/core/i18n';
|
||||
|
||||
const t = createTranslator(getI18nTranslations('pt-BR'), 'pt-BR');
|
||||
t('play'); // merged Portuguese string
|
||||
```
|
||||
|
||||
<UtilReference util="getI18nTranslations" />
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: hasRegisteredI18n
|
||||
description: Check whether an exact locale tag exists in the global i18n registry
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
|
||||
`hasRegisteredI18n` returns whether a normalized locale tag has an explicit registry layer from `registerI18n`. It does **not** indicate whether a lazy built-in pack exists — only registry entries count.
|
||||
|
||||
```ts
|
||||
import { hasRegisteredI18n, registerI18n } from '@videojs/html/i18n';
|
||||
|
||||
hasRegisteredI18n('fr'); // false until registered
|
||||
registerI18n('fr', { play: 'Lecture' });
|
||||
hasRegisteredI18n('fr'); // true
|
||||
```
|
||||
|
||||
<UtilReference util="hasRegisteredI18n" />
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: I18nProvider
|
||||
description: React provider that resolves locale and supplies a typed translator to descendants
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
`I18nProvider` resolves the active locale, merges registry and lazy pack layers, and exposes a translator through context. Preset skins mount it inside <DocsLink slug="reference/player-container">`Container`</DocsLink> with `langRootRef` on the player shell.
|
||||
|
||||
Wrap custom controls or force a locale explicitly:
|
||||
|
||||
```tsx
|
||||
import { I18nProvider } from '@videojs/react/i18n';
|
||||
|
||||
<I18nProvider locale="de" translations={{ play: 'Abspielen' }}>
|
||||
<MyControls />
|
||||
</I18nProvider>
|
||||
```
|
||||
|
||||
Omit `locale` to inherit the nearest `lang` attribute (via `langRootRef` or `<html lang>`). See <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for merge priority and SSR guidance.
|
||||
|
||||
<UtilReference util="I18nProvider" />
|
||||
|
||||
</FrameworkCase>
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: media-i18n-provider
|
||||
description: HTML custom element that resolves locale and supplies translations to descendants
|
||||
---
|
||||
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
|
||||
`<media-i18n-provider>` applies the i18n provider mixin to a standalone subtree. Built-in `<video-player>` and skins already include this mixin — use the element when rendering **controls outside a player** or when sibling players need different locales.
|
||||
|
||||
Register locale strings with <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink>, then set `lang` on the provider or an ancestor. See <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for opaque keys and fallback behavior.
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import '@videojs/html/i18n';
|
||||
import { registerI18n } from '@videojs/html/i18n';
|
||||
import es from '@videojs/html/i18n/locales/es';
|
||||
|
||||
registerI18n('es', es);
|
||||
</script>
|
||||
|
||||
<media-i18n-provider lang="es">
|
||||
<media-text key="play"></media-text>
|
||||
</media-i18n-provider>
|
||||
```
|
||||
|
||||
## Attributes
|
||||
|
||||
| Attribute | Description |
|
||||
| --- | --- |
|
||||
| `lang` | Forces the active BCP 47 locale for this subtree. Omit to inherit the nearest ancestor `[lang]`. |
|
||||
|
||||
## Related
|
||||
|
||||
- <DocsLink slug="reference/media-text">`<media-text>`</DocsLink> — renders a translated string by key
|
||||
- <DocsLink slug="how-to/i18n-register-locale">Register a custom locale</DocsLink>
|
||||
|
||||
</FrameworkCase>
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: media-text
|
||||
description: HTML custom element that renders a translated string by opaque key
|
||||
---
|
||||
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
<FrameworkCase frameworks={["html"]}>
|
||||
|
||||
`<media-text>` renders the translated value for a registry key inside a <DocsLink slug="reference/media-i18n-provider">`<media-i18n-provider>`</DocsLink> (or any ancestor with the i18n provider mixin). Use it for standalone labels, tooltips, or demos — skin controls resolve keys internally.
|
||||
|
||||
```html
|
||||
<media-i18n-provider lang="ja">
|
||||
<media-text key="play"></media-text>
|
||||
</media-i18n-provider>
|
||||
```
|
||||
|
||||
## Attributes
|
||||
|
||||
| Attribute | Description |
|
||||
| --- | --- |
|
||||
| `key` | Opaque translation key (`play`, `pause`, `timeRemainingPhrase`, …). |
|
||||
|
||||
Parametric keys are not yet supported on `<media-text>` — use `createTranslator` in script for interpolated strings.
|
||||
|
||||
</FrameworkCase>
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: onI18nRegistryChange
|
||||
description: Subscribe to global i18n registry mutations
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
|
||||
`onI18nRegistryChange` registers a callback that runs whenever any locale layer changes — for example after `registerI18n` or browser translation prefetch. Returns an unsubscribe function.
|
||||
|
||||
React `I18nProvider` uses this to invalidate translators when the registry updates.
|
||||
|
||||
<UtilReference util="onI18nRegistryChange" />
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: registerI18n
|
||||
description: Register or merge translation strings for a BCP 47 locale tag in the global i18n registry
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
|
||||
`registerI18n` merges a partial translation map into the process-wide registry for a locale tag. English defaults are registered when `@videojs/core/i18n` loads; call `registerI18n` for custom locales or to patch shipped packs before the player renders.
|
||||
|
||||
Import from `@videojs/core/i18n`, `@videojs/html/i18n`, or `@videojs/react/i18n`. Keys are opaque camelCase tokens (`play`, `pause`) — see <DocsLink slug="concepts/i18n">Internationalization</DocsLink>.
|
||||
|
||||
```ts
|
||||
import { registerI18n } from '@videojs/react/i18n';
|
||||
|
||||
registerI18n('es', {
|
||||
play: 'Reproducir',
|
||||
pause: 'Pausar',
|
||||
});
|
||||
```
|
||||
|
||||
<UtilReference util="registerI18n" />
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: useLocale
|
||||
description: React hook that returns the active BCP 47 locale from the nearest I18nProvider
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
`useLocale` returns the resolved BCP 47 tag from the nearest <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>, or `'en'` when none is mounted.
|
||||
|
||||
Use it when UI copy depends on the active locale outside the translator — for example formatting or caption language hooks.
|
||||
|
||||
```tsx
|
||||
import { useLocale } from '@videojs/react/i18n';
|
||||
|
||||
function LocaleBadge() {
|
||||
const locale = useLocale();
|
||||
return <span lang={locale}>{locale}</span>;
|
||||
}
|
||||
```
|
||||
|
||||
<UtilReference util="useLocale" />
|
||||
|
||||
</FrameworkCase>
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: useTranslator
|
||||
description: React hook that returns the typed translator for the nearest I18nProvider
|
||||
---
|
||||
|
||||
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
|
||||
import DocsLink from "@/components/docs/DocsLink.astro";
|
||||
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
|
||||
|
||||
<FrameworkCase frameworks={["react"]}>
|
||||
|
||||
`useTranslator` returns the translator from the nearest <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>. Control components pass opaque keys from core `getLabel()` — for example `t('play')` and `t('seekForward', { seconds: 10 })`.
|
||||
|
||||
When no provider is mounted, the hook falls back to English registry strings so standalone demos do not throw.
|
||||
|
||||
```tsx
|
||||
import { useTranslator } from '@videojs/react/i18n';
|
||||
|
||||
function PlayLabel() {
|
||||
const t = useTranslator();
|
||||
return <span>{t('play')}</span>;
|
||||
}
|
||||
```
|
||||
|
||||
<UtilReference util="useTranslator" />
|
||||
|
||||
</FrameworkCase>
|
||||
@@ -42,6 +42,7 @@ export const sidebar: Sidebar = [
|
||||
{ slug: 'concepts/ui-components' },
|
||||
{ slug: 'concepts/accessibility' },
|
||||
{ slug: 'concepts/cast', sidebarLabel: 'Google Cast' },
|
||||
{ slug: 'concepts/i18n', sidebarLabel: 'Internationalization' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -52,6 +53,11 @@ export const sidebar: Sidebar = [
|
||||
{ slug: 'how-to/customize-skins' },
|
||||
{ slug: 'how-to/build-your-own-component' },
|
||||
{ slug: 'how-to/self-host-the-player', frameworks: ['html'] },
|
||||
{ slug: 'how-to/i18n-register-locale', sidebarLabel: 'Register a locale' },
|
||||
{ slug: 'how-to/i18n-override-translations', sidebarLabel: 'Override translations' },
|
||||
{ slug: 'how-to/i18n-switch-locale', sidebarLabel: 'Switch locale' },
|
||||
{ slug: 'how-to/i18n-ssr', sidebarLabel: 'SSR with locale' },
|
||||
{ slug: 'how-to/i18n-add-built-in-locale', sidebarLabel: 'Add a built-in locale' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -105,6 +111,10 @@ export const sidebar: Sidebar = [
|
||||
frameworks: ['react'],
|
||||
contents: [
|
||||
{ slug: 'reference/create-player' },
|
||||
{ slug: 'reference/create-i18n' },
|
||||
{ slug: 'reference/i18n-provider' },
|
||||
{ slug: 'reference/use-translator' },
|
||||
{ slug: 'reference/use-locale' },
|
||||
{ slug: 'reference/use-player' },
|
||||
{ slug: 'reference/use-media' },
|
||||
{ slug: 'reference/use-store' },
|
||||
@@ -129,6 +139,8 @@ export const sidebar: Sidebar = [
|
||||
frameworks: ['html'],
|
||||
contents: [
|
||||
{ slug: 'reference/html-create-player', sidebarLabel: 'createPlayer' },
|
||||
{ slug: 'reference/media-i18n-provider', sidebarLabel: 'media-i18n-provider' },
|
||||
{ slug: 'reference/media-text', sidebarLabel: 'media-text' },
|
||||
{ slug: 'reference/player-controller' },
|
||||
{
|
||||
sidebarLabel: 'Advanced',
|
||||
@@ -149,6 +161,10 @@ export const sidebar: Sidebar = [
|
||||
llmsDescription: 'API reference for feature modules that provide player capabilities and state.',
|
||||
contents: [
|
||||
{ slug: 'reference/create-selector' },
|
||||
{ slug: 'reference/register-i18n', sidebarLabel: 'registerI18n' },
|
||||
{ slug: 'reference/get-i18n-translations', sidebarLabel: 'getI18nTranslations' },
|
||||
{ slug: 'reference/has-registered-i18n', sidebarLabel: 'hasRegisteredI18n' },
|
||||
{ slug: 'reference/on-i18n-registry-change', sidebarLabel: 'onI18nRegistryChange' },
|
||||
{ slug: 'reference/feature-buffer' },
|
||||
{ slug: 'reference/feature-controls' },
|
||||
{ slug: 'reference/feature-error' },
|
||||
|
||||
Reference in New Issue
Block a user