docs(site): i18n updates after review

This commit is contained in:
Sam Potts
2026-07-13 08:42:43 +10:00
parent c8e1bd81e5
commit 02c7480e93
26 changed files with 696 additions and 305 deletions
+26
View File
@@ -0,0 +1,26 @@
# Adding a built-in locale
Built-in locales live in `locales/`. The locale build generates lazy loaders, CDN chunks, and HTML/React re-exports from that directory.
1. Add `locales/<tag>.ts` using a BCP 47 filename such as `pt-BR.ts` or `zh-CN.ts`.
```ts
import type { Translations } from '../types';
export default {
Play: '...',
Pause: '...',
} satisfies Partial<Translations>;
```
Use `locales/en.ts` as the source of phrases. Parametric strings must keep their placeholders.
2. Add the tag to `LOCALES` in `locales.ts`.
3. Run `pnpm -F @videojs/core build` to regenerate locale loaders and HTML/React re-exports. Do not edit generated files.
4. Run `pnpm -F @videojs/core test src/core/i18n` and add coverage for locale aliases or loader behavior when needed.
5. Run `pnpm -F @videojs/html build:cdn` to verify the generated CDN locale chunk.
Do not copy Video.js v8 locale JSON blindly. V10 uses different phrase keys and ARIA-label semantics.
+173 -36
View File
@@ -7,17 +7,23 @@ 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 current English phrase (`Play`, `Pause`, `Seek forward {seconds} seconds`), so missing translations stay readable.
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](https://developer.mozilla.org/en-US/docs/Web/API/Translator) 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.
<FrameworkCase frameworks={["html"]}>
```html
<html lang="es">
<video-player>
<video-skin>
<video src="..." playsinline></video>
</video-skin>
</video-player>
<media-i18n>
<video-player>
<video-skin>
<video src="..." playsinline></video>
</video-skin>
</video-player>
</media-i18n>
</html>
```
@@ -26,15 +32,18 @@ Video.js translates control labels, ARIA text, tooltips, and error copy through
<FrameworkCase frameworks={["react"]}>
```tsx
import { I18nProvider } from '@videojs/react/i18n';
import { Provider, VideoSkin, Video } from '@videojs/react/video';
// Set `<html lang="es">` on the document (layout, _document, or index.html)
export function App() {
return (
<Provider>
<VideoSkin>
<Video src="..." playsInline />
</VideoSkin>
<I18nProvider>
<VideoSkin>
<Video src="..." playsInline />
</VideoSkin>
</I18nProvider>
</Provider>
);
}
@@ -42,7 +51,19 @@ export function App() {
</FrameworkCase>
Register a locale once (or rely on lazy-loaded built-in packs), set `lang`, and skins pick up translated strings automatically.
<FrameworkCase frameworks={["html"]}>
Mount `<media-i18n>` and set its `lang` attribute, or let it inherit the nearest ancestor `lang`. Built-in packs then load automatically.
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
Mount `I18nProvider` and pass its `locale` prop to force a language, or omit it to inherit `lang`. Built-in packs then load automatically.
</FrameworkCase>
Registering packs yourself is only needed when you want custom copy, synchronous first paint, SSR, or CDN loading.
## Phrase keys
@@ -52,9 +73,11 @@ Phrase params are typed in <DocsLink slug="reference/translation-params">`Transl
Parametric strings use `{placeholder}` tokens, for example `'Seek forward {seconds} seconds'` and `'{duration} remaining'`.
See <DocsLink slug="reference/translation-phrases">Translation phrases</DocsLink> 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.
`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 |
| --- | --- |
@@ -63,41 +86,69 @@ Parametric strings use `{placeholder}` tokens, for example `'Seek forward {secon
| <DocsLink slug="reference/has-registered-locale">`hasRegisteredLocale`</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` or `@videojs/react/i18n` depending on your framework.
## 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">`<media-i18n>`</DocsLink> when you render **standalone** controls outside a player, or when one page hosts players in different languages.
Import from `@videojs/html/i18n`.
</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`.
Import from `@videojs/react/i18n`.
</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.
## 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:
<FrameworkCase frameworks={["html"]}>
- Wrap the player in <DocsLink slug="reference/media-i18n">`<media-i18n>`</DocsLink>.
- 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.
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
- Mount <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink> inside `Provider`, around the preset skin.
- Omit the `locale` prop to inherit `<html lang="es">`, or pass `locale="es"` directly.
- Video.js lazy-loads the shipped Spanish pack when it is not already registered.
Pass the `locale` prop to force a language or `translations` for per-render overrides.
</FrameworkCase>
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
Providers resolve the active locale in order:
<FrameworkCase frameworks={["html"]}>
1. **Explicit**: `lang` on <DocsLink slug="reference/media-i18n">`<media-i18n>`</DocsLink> or `locale` on <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>
2. **Ambient**: nearest ancestor `[lang]` (HTML) or `langRootRef` / `<html lang>` (React)
`<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 `locale` or `lang` on a provider overrides ambient `<html lang>` until you remove or update that override.
Changing `<html lang>` re-renders wired controls without remounting the player. An explicit provider `lang` overrides ambient language until you remove or update it.
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
`I18nProvider` resolves its active language in order:
1. **Explicit**: its `locale` prop
2. **Ambient**: `langRootRef` or `<html lang>`
3. **Fallback**: English defaults
Changing `<html lang>` re-renders wired controls without remounting the player. An explicit `locale` prop overrides ambient language until you remove or update it.
</FrameworkCase>
## BCP 47 fallback
@@ -114,17 +165,66 @@ zh-Hant-HK → zh-hant → zh → en
Later layers win over earlier ones:
<FrameworkCase frameworks={["html"]}>
| 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` |
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
| 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) | `translations` prop on `I18nProvider` |
</FrameworkCase>
## Built-in locale packs
Video.js ships locale files under `@videojs/html/i18n/locales/*` and `@videojs/react/i18n/locales/*`. Providers call `loadLocale` automatically when a pack is not already registered.
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.
<FrameworkCase frameworks={["html"]}>
HTML locale files live under `@videojs/html/i18n/locales/*`.
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
React locale files live under `@videojs/react/i18n/locales/*`.
</FrameworkCase>
To preload a shipped language without writing a `registerI18n` call, import its side-effect module:
<FrameworkCase frameworks={["html"]}>
```ts
import '@videojs/html/i18n/locales/es/register';
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import '@videojs/react/i18n/locales/es/register';
```
</FrameworkCase>
Import and register a pack manually when you need custom copy or want to preload a fixed language picker. See <DocsLink slug="how-to/i18n-register-locale">Register a locale</DocsLink>.
<FrameworkCase frameworks={["html"]}>
CDN consumers load self-registering modules:
@@ -133,9 +233,19 @@ CDN consumers load self-registering modules:
<script type="module" src="https://cdn.jsdelivr.net/npm/@videojs/html/cdn/locales/es.js"></script>
```
</FrameworkCase>
## Browser translation
After lazy loading runs, Video.js can ask the browser to translate the English registry through the [Browser Translation API](https://developer.mozilla.org/en-US/docs/Web/API/Translator). 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
```tsx
```ts
// ❌ Don't: old camelCase keys are ignored
registerI18n('es', { play: 'Reproducir' });
@@ -143,7 +253,7 @@ registerI18n('es', { play: 'Reproducir' });
registerI18n('es', { Play: 'Reproducir' });
```
```tsx
```ts
// ❌ Don't: parametric key without the placeholder
registerI18n('es', { 'Seek forward {seconds} seconds': 'Adelante 10 segundos' }); // TS error: missing {seconds}
@@ -151,15 +261,42 @@ registerI18n('es', { 'Seek forward {seconds} seconds': 'Adelante 10 segundos' })
registerI18n('es', { 'Seek forward {seconds} seconds': 'Adelantar {seconds} segundos' });
```
<FrameworkCase frameworks={["react"]}>
<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>.
For zero flash of English on SSR or locale switches, pass `translations` directly to `I18nProvider` or register the locale before render. See <DocsLink slug="how-to/i18n-ssr">SSR with locale</DocsLink>.
</Aside>
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
<Aside type="tip">
For zero flash of English on SSR or locale switches, register the locale before render. See <DocsLink slug="how-to/i18n-ssr">SSR with locale</DocsLink>.
</Aside>
</FrameworkCase>
## See also
- <DocsLink slug="reference/locale">`Locale`</DocsLink>, <DocsLink slug="reference/translations">`Translations`</DocsLink>, <DocsLink slug="reference/translator">`Translator`</DocsLink>: I18n types
<FrameworkCase frameworks={["html"]}>
- <DocsLink slug="reference/media-i18n">`<media-i18n>`</DocsLink>, <DocsLink slug="reference/locale">`Locale`</DocsLink>, <DocsLink slug="reference/translations">`Translations`</DocsLink>, <DocsLink slug="reference/translator">`Translator`</DocsLink>: I18n APIs
- <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="concepts/accessibility">Accessibility</DocsLink>: Translated ARIA labels
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
- <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>, <DocsLink slug="reference/locale">`Locale`</DocsLink>, <DocsLink slug="reference/translations">`Translations`</DocsLink>, <DocsLink slug="reference/translator">`Translator`</DocsLink>: I18n APIs
- <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="concepts/accessibility">Accessibility</DocsLink>: Translated ARIA labels
</FrameworkCase>
@@ -1,85 +0,0 @@
---
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 <DocsLink slug="reference/translation-params">`TranslationParams`</DocsLink>
- English defaults in `packages/core/src/core/i18n/locales/en.ts` as the source of phrases
## 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 phrases from en.ts; completeness is preferred
} satisfies Partial<Translations>;
```
Use the current English phrases from `en.ts`. Parametric strings must include the same `{placeholder}` tokens (TypeScript enforces this via `satisfies Partial<Translations>`).
Copy guidelines:
- `Live`: sentence case; skins uppercase via CSS where needed
- `Seek`: slider aria label, not legacy "Progress" wording
- `'{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 `LOCALES` in `packages/core/src/core/i18n/locales.ts`.
## 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>
@@ -8,26 +8,40 @@ import DocsLink from '@/components/docs/DocsLink.astro';
`registerI18n` **merges**. Each call adds or replaces phrases 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 phrase naming.
Read <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for merge order and <DocsLink slug="reference/translation-phrases">Translation phrases</DocsLink> for the supported keys.
## Override after a built-in pack
<FrameworkCase frameworks={["html"]}>
```ts
import { registerI18n } from '@videojs/html/i18n';
import es from '@videojs/html/i18n/locales/es';
import '@videojs/html/i18n/locales/es/register';
registerI18n('es', es);
registerI18n('es', { Play: 'Comenzar' }); // only `Play` changes
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import { registerI18n } from '@videojs/react/i18n';
import '@videojs/react/i18n/locales/es/register';
registerI18n('es', { Play: 'Comenzar' }); // only `Play` changes
```
</FrameworkCase>
Later registrations win for the same phrase. The rest of the `es` pack stays intact.
<FrameworkCase frameworks={["react"]}>
## 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';
@@ -53,6 +67,8 @@ Nested providers inherit the parent locale when you only pass `translations`:
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
## HTML reflected text
Use <DocsLink slug="reference/media-text">`<media-text>`</DocsLink> for static copy outside buttons:
@@ -65,6 +81,8 @@ Use <DocsLink slug="reference/media-text">`<media-text>`</DocsLink> for static c
Override the phrase in the registry or set `lang` on the provider to a locale where you patched `Play`.
</FrameworkCase>
## Parametric phrases
Keep required placeholders when overriding:
@@ -7,62 +7,75 @@ 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 phrase keys and the registry.
Video.js is English by default. Built-in packs lazy-load automatically for app bundles; use <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> when you have custom strings, need synchronous first paint, or load locales from the CDN.
<FrameworkCase frameworks={["html"]}>
To opt into another language, mount `<media-i18n>` and set its `lang` attribute or an ancestor `lang` attribute.
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
To opt into another language, mount `I18nProvider`. Pass its `locale` prop to force a tag, or omit it to inherit `lang`.
</FrameworkCase>
Read <DocsLink slug="concepts/i18n">Internationalization</DocsLink> first if you are new to phrase 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 current English phrases (`Play`, `Pause`, …)
## Use a shipped pack
## Use a shipped pack synchronously
Built-in packs live in `@videojs/*/i18n/locales/*`. Import and register before the player renders:
Built-in packs include side-effect modules that register a language 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);
import '@videojs/html/i18n/locales/es/register';
```
```html title="index.html"
<html lang="es">
<video-player>
<video-skin>
<video src="..." playsinline></video>
</video-skin>
</video-player>
<media-i18n>
<video-player>
<video-skin>
<video src="..." playsinline></video>
</video-skin>
</video-player>
</media-i18n>
</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.
Without the side-effect import, `<media-i18n>` lazy-loads `es` when it resolves `lang="es"`. The import makes the first paint synchronous.
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```tsx title="app.tsx"
import { registerI18n } from '@videojs/react/i18n';
import es from '@videojs/react/i18n/locales/es';
import { I18nProvider } from '@videojs/react/i18n';
import '@videojs/react/i18n/locales/es/register';
import { Provider, VideoSkin, Video } from '@videojs/react/video';
registerI18n('es', es);
export function App() {
return (
<Provider>
<VideoSkin>
<Video src="..." playsInline />
</VideoSkin>
<I18nProvider locale="es">
<VideoSkin>
<Video src="..." playsInline />
</VideoSkin>
</I18nProvider>
</Provider>
);
}
```
Preset skins use `Container`, which includes <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>. Set `<html lang="es">` or wrap with `<I18nProvider locale="es">`.
Wrap the player subtree with <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>. Omit `locale` to inherit `<html lang="es">`.
</FrameworkCase>
@@ -102,29 +115,48 @@ export default es;
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
```ts
import { registerI18n } from '@videojs/html/i18n'; // or @videojs/react/i18n
import { registerI18n } from '@videojs/html/i18n';
import es from './my-es';
registerI18n('es', es);
```
All keys are optional. Missing keys fall back through the BCP 47 chain to English.
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import { registerI18n } from '@videojs/react/i18n';
import es from './my-es';
registerI18n('es', es);
```
</FrameworkCase>
All keys are optional. Missing keys follow the <DocsLink slug="concepts/i18n">locale fallback chain</DocsLink> through a built-in pack, browser-translated copy in supported Chrome builds, or English.
<FrameworkCase frameworks={["html"]}>
## CDN
Load the player and a locale chunk. Locale modules call `registerI18n` on import:
Load the player and a locale chunk. CDN locale modules call <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> 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>
<media-i18n>
<video-player>
<video-skin>
<video src="..." playsinline></video>
</video-skin>
</video-player>
</media-i18n>
</html>
```
@@ -143,7 +175,9 @@ registerI18n('es', {
});
```
Load your module after the player script, before playback starts.
Load your module after the player script and before playback starts.
</FrameworkCase>
## What's next?
+54 -12
View File
@@ -7,7 +7,7 @@ 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.
Video.js is English by default. Server-rendered pages that opt into another language should mount the relevant provider, output the correct `lang`, and supply translations on the first client render. Lazy `loadLocale` and browser translation fallback run after hydration, so controls briefly show English without preloaded copy.
Read <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for provider resolution and merge order.
@@ -19,14 +19,18 @@ Render the document language on the server:
<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.
<FrameworkCase frameworks={["react"]}>
## Preload translations (React)
React providers use a server snapshot of `undefined` for ambient lang, then read `<html lang>` on hydration. Passing the `locale` prop explicitly avoids any mismatch.
Import the locale pack on the server (or in a server component) and pass it to <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>:
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
## Preload translations
Import the locale pack on the server (or in a server component) and pass it to <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>:
```tsx
import { I18nProvider } from '@videojs/react/i18n';
import { Provider, VideoSkin, Video } from '@videojs/react/video';
@@ -46,13 +50,44 @@ export async function Player({ locale }: { locale: string }) {
}
```
`translations` on the first render skips the async lazy layer. Labels match on server and client.
`translations` on the first render skips the async lazy layer and Chrome-only browser translation fallback. 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:
For a shipped pack, import its side-effect module in server bootstrap code before rendering players:
<FrameworkCase frameworks={["html"]}>
```ts
import '@videojs/html/i18n/locales/es/register';
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import '@videojs/react/i18n/locales/es/register';
```
</FrameworkCase>
For a custom pack, call <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> instead:
<FrameworkCase frameworks={["html"]}>
```ts
import { registerI18n } from '@videojs/html/i18n';
import es from './locales/es';
registerI18n('es', es);
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import { registerI18n } from '@videojs/react/i18n';
@@ -61,8 +96,12 @@ import es from './locales/es';
registerI18n('es', es);
```
</FrameworkCase>
The registry is module singleton state. Registration in server entry points carries into the client bundle when shared.
<FrameworkCase frameworks={["react"]}>
## next-intl and app routers
```tsx
@@ -78,22 +117,25 @@ const { default: translations } = await import(`@videojs/react/i18n/locales/${lo
Match your app's locale negotiation. Video.js does not replace framework i18n; it consumes the tag you pass.
</FrameworkCase>
<FrameworkCase frameworks={["html"]}>
## HTML custom elements
SSR HTML players should emit `lang` on `<html>` or on <DocsLink slug="reference/media-i18n">`<media-i18n>`</DocsLink>. Register packs in the entry module loaded before custom elements upgrade:
SSR HTML players should emit `lang` and wrap the player with <DocsLink slug="reference/media-i18n">`<media-i18n>`</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);
import '@videojs/html/i18n/locales/es/register';
```
<Aside type="caution">
Do not rely on browser-only APIs (`getBrowserTranslations`) during SSR. Ship packs or register strings explicitly.
Do not rely on browser-only translation APIs during SSR. Ship packs or register strings explicitly.
</Aside>
</FrameworkCase>
## What's next?
- <DocsLink slug="how-to/i18n-switch-locale">Switch locale at runtime</DocsLink>
@@ -7,11 +7,11 @@ 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.
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
- <DocsLink slug="how-to/i18n-register-locale">Locale packs registered or lazy-loadable</DocsLink>
- A target locale from a shipped lazy-loaded pack, a registered custom pack, or a Chrome browser translation fallback
## Ambient `<html lang>`
@@ -22,22 +22,32 @@ document.documentElement.lang = 'fr';
```
<Aside type="note">
Ambient switching only applies when no provider sets an explicit locale. React `I18nProvider locale={…}`, HTML `<media-i18n 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.
Ambient switching only applies when `<media-i18n>` has no explicit `lang` attribute.
</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.
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>
## Explicit provider locale
<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"]}>
@@ -84,27 +94,23 @@ Changing `locale` triggers lazy `loadLocale` for built-in packs.
</script>
```
Or set `document.documentElement.lang` if the player inherits ambient language.
Or set `document.documentElement.lang` when the mounted `<media-i18n>` inherits ambient language.
</FrameworkCase>
## Avoid flash while switching
Async lazy loads can briefly show English. Preload copy before the active locale changes with one of three common patterns:
Async lazy loads and browser translation fallback can briefly show English. Preload copy before the active locale changes with one of three common patterns:
### Pre-register at bootstrap (React and HTML)
### Preload at bootstrap
<DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> writes to a global registry that providers read synchronously. Register every locale you plan to switch between once at startup; `loadLocale` skips tags already in the registry.
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 { registerI18n } from '@videojs/react/i18n';
import es from '@videojs/react/i18n/locales/es';
import fr from '@videojs/react/i18n/locales/fr';
registerI18n('es', es);
registerI18n('fr', fr);
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}>
@@ -117,12 +123,8 @@ registerI18n('fr', fr);
<FrameworkCase frameworks={["html"]}>
```ts
import { registerI18n } from '@videojs/html/i18n';
import es from '@videojs/html/i18n/locales/es';
import fr from '@videojs/html/i18n/locales/fr';
registerI18n('es', es);
registerI18n('fr', fr);
import '@videojs/html/i18n/locales/es/register';
import '@videojs/html/i18n/locales/fr/register';
```
```html
@@ -137,7 +139,9 @@ registerI18n('fr', fr);
</FrameworkCase>
### Prefetch before switching (React)
<FrameworkCase frameworks={["react"]}>
### Prefetch before switching
When you cannot register every locale up front, import and register immediately before updating `locale`:
@@ -174,7 +178,7 @@ async function switchTo(next: string) {
| Approach | Scope | Best for |
| --- | --- | --- |
| `registerI18n` at bootstrap | Global, all providers | Language picker with a fixed set of locales |
| 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 |
@@ -186,9 +190,13 @@ async function switchTo(next: string) {
For server-rendered first paint, see <DocsLink slug="how-to/i18n-ssr">SSR and hydration</DocsLink>.
</Aside>
## CDN
</FrameworkCase>
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.
## 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?
@@ -1,44 +0,0 @@
---
title: BuiltInLocale
description: Union of shipped BCP 47 locale tags with autocomplete in TypeScript
---
import DocsLink from '@/components/docs/DocsLink.astro';
`BuiltInLocale` narrows <DocsLink slug="reference/locale">`Locale`</DocsLink> to tags with shipped translation packs. TypeScript suggests these tags when you call `registerI18n`, `loadLocale`, or import locale modules.
## App imports
```ts
import es from '@videojs/html/i18n/locales/es';
// or @videojs/react/i18n/locales/es
```
## Definition
```ts
type BuiltInLocale = (typeof LOCALES)[number];
```
`LOCALES` lists shipped packs (`es`, `pt-BR`, `zh-CN`, …). Bare language tags such as `pt` and `zh` are handled by the normal locale lookup chain when there is a matching registered or loaded layer.
## Lazy loading
Providers call `loadLocale(tag)` for tags in the resolved locale chain when a pack is not already registered. Explicit `registerI18n` or CDN locale modules skip the async gap on first paint.
## Examples
```ts
import { registerI18n } from '@videojs/react/i18n';
import es from '@videojs/react/i18n/locales/es';
registerI18n('es', es); // 'es' autocompletes as BuiltInLocale
```
Custom app locales (`'xx'`) use `Locale` but are not members of `BuiltInLocale`.
## Related
- <DocsLink slug="reference/locale">`Locale`</DocsLink>
- <DocsLink slug="how-to/i18n-add-built-in-locale">Add a built-in locale (contributors)</DocsLink>
- <DocsLink slug="how-to/i18n-register-locale">Register a custom locale</DocsLink>
@@ -1,21 +1,38 @@
---
title: createI18n
description: Factory that creates a React i18n provider with custom loading options
description: Factory that creates framework i18n helpers with custom loading options
---
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={["html"]}>
`createI18n` returns a context-bound `ProviderMixin`, `TextMixin`, and `I18nController` for custom elements. Use it when you need a custom locale loader or custom i18n elements. Most apps use `<media-i18n>` and `<media-text>` from `@videojs/html/i18n`.
```ts
import { ReactiveElement } from '@videojs/element';
import { createI18n } from '@videojs/html/i18n';
const { ProviderMixin, TextMixin } = createI18n();
class LocalizedRoot extends ProviderMixin(ReactiveElement) {}
class LocalizedText extends TextMixin(ReactiveElement) {}
```
<UtilReference util="createI18n" />
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
`createI18n` returns an `I18nProvider`, `useTranslator`, and `useLocale` wired to the shared React i18n context used by the stock skins and controls. Use it when you need options such as a custom locale loader. Most apps import the default exports from `@videojs/react/i18n` instead.
`createI18n` returns an `I18nProvider`, `useTranslator`, and `useLocale` wired to the shared React i18n context used by the stock skins and controls. Use it when you need options such as a custom locale loader. Most apps use the default exports from `@videojs/react/i18n`.
```tsx
import { createI18n } from '@videojs/react/i18n';
const { I18nProvider, useTranslator } = createI18n({
loadLocale: async (tag) => {
loader: async (tag) => {
const mod = await import(`@videojs/react/i18n/locales/${tag}`);
return mod.default;
},
@@ -5,16 +5,32 @@ description: Build a typed translator from a resolved translation map
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
`createTranslator` wraps a <DocsLink slug="reference/translations">`Translations`</DocsLink> map and returns a <DocsLink slug="reference/translator">`Translator`</DocsLink>. Providers call this internally after merging registry, lazy, and prop layers. Use it directly for custom UI outside built-in mixins.
`createTranslator` wraps a <DocsLink slug="reference/translations">`Translations`</DocsLink> map and returns a <DocsLink slug="reference/translator">`Translator`</DocsLink>. Providers call this internally after merging registry, lazy-loaded built-in packs, browser-translated fallback copy, and provider layers. Use it directly for custom UI outside built-in controls.
<FrameworkCase frameworks={["html"]}>
```ts
import { createTranslator, getI18nTranslations } from '@videojs/html/i18n';
// or @videojs/react/i18n
const t = createTranslator(getI18nTranslations('fr'), 'fr');
t('Play');
t('Seek forward {seconds} seconds', { seconds: 5 });
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import { createTranslator, getI18nTranslations } from '@videojs/react/i18n';
const t = createTranslator(getI18nTranslations('fr'), 'fr');
t('Play');
t('Seek forward {seconds} seconds', { seconds: 5 });
```
</FrameworkCase>
<UtilReference util="createTranslator" />
@@ -5,17 +5,32 @@ description: Read the merged translation map for a locale using BCP 47 parent-ch
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.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.
`getI18nTranslations` walks the BCP 47 lookup chain (`es-MX` → `es` → `en`) and merges registry layers into one map. Providers combine this map with lazy-loaded built-in packs and provider overrides. Call it directly when building custom UI outside built-in controls.
See <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for merge order and fallback rules.
<FrameworkCase frameworks={["html"]}>
```ts
import { createTranslator, getI18nTranslations } from '@videojs/html/i18n';
// or @videojs/react/i18n
const t = createTranslator(getI18nTranslations('pt-BR'), 'pt-BR');
t('Play'); // merged Portuguese string
t('Play');
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import { createTranslator, getI18nTranslations } from '@videojs/react/i18n';
const t = createTranslator(getI18nTranslations('pt-BR'), 'pt-BR');
t('Play');
```
</FrameworkCase>
<UtilReference util="getI18nTranslations" />
@@ -5,8 +5,11 @@ description: Check whether an exact locale tag exists in the global i18n registr
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
`hasRegisteredLocale` 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.
`hasRegisteredLocale` returns whether a normalized locale tag has an explicit registry layer from <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink>. It does **not** indicate whether a lazy built-in pack exists. Only registry entries count.
<FrameworkCase frameworks={["html"]}>
```ts
import { hasRegisteredLocale, registerI18n } from '@videojs/html/i18n';
@@ -16,4 +19,18 @@ registerI18n('fr', { Play: 'Lecture' });
hasRegisteredLocale('fr'); // true
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import { hasRegisteredLocale, registerI18n } from '@videojs/react/i18n';
hasRegisteredLocale('fr'); // false until registered
registerI18n('fr', { Play: 'Lecture' });
hasRegisteredLocale('fr'); // true
```
</FrameworkCase>
<UtilReference util="hasRegisteredLocale" />
@@ -9,7 +9,7 @@ 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.
`I18nProvider` resolves the active locale, lazy-loads built-in packs, merges registry and provider layers, and exposes a translator through context. Wrap it around a player or custom controls that should translate.
Wrap custom controls or force a locale explicitly:
@@ -21,7 +21,7 @@ import { I18nProvider } from '@videojs/react/i18n';
</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.
Omit `locale` to inherit the nearest `lang` attribute (via `langRootRef` or `<html lang>`). English is the fallback when no non-English locale is active or no translation layer supplies a string. See <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for merge priority, browser translation fallback, and SSR guidance.
<UtilReference util="I18nProvider" />
+31 -14
View File
@@ -4,38 +4,55 @@ description: BCP 47 language tag type for i18n registry and provider APIs
---
import DocsLink from '@/components/docs/DocsLink.astro';
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
`Locale` is the BCP 47 tag type used by <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink>, providers, and <DocsLink slug="reference/translator">`Translator`</DocsLink>. Shipped packs autocomplete as <DocsLink slug="reference/built-in-locale">`BuiltInLocale`</DocsLink>; any other tag remains valid at runtime.
`Locale` is the BCP 47 tag type used by <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> and provider APIs. Shipped tags autocomplete, and any other BCP 47 tag remains valid at runtime.
## Import
<FrameworkCase frameworks={["html"]}>
```ts
import type { Locale } from '@videojs/html/i18n';
// or @videojs/react/i18n
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import type { Locale } from '@videojs/react/i18n';
```
</FrameworkCase>
## Definition
```ts
type Locale = BuiltInLocale | (string & {});
type Locale = (typeof LOCALES)[number] | (string & {});
```
The `(string & {})` pattern keeps custom tags (`'xx'`, `'en-US'`) type-safe without losing autocomplete for built-ins.
The `(string & {})` pattern keeps custom tags (`'xx'`, `'en-US'`) type-safe without losing autocomplete for shipped tags.
## Provider use
<FrameworkCase frameworks={["html"]}>
Set a `lang` attribute on <DocsLink slug="reference/media-i18n">`<media-i18n>`</DocsLink>; HTML providers do not have a `locale` attribute.
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
Pass a `Locale` to the `locale` prop on <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>, or omit the prop to inherit `lang`.
</FrameworkCase>
## Resolution
Providers and `getI18nTranslations` normalize tags and walk the parent chain (`es-MX` → `es` → `en`) via `findLocaleKeys`. See <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for explicit vs ambient resolution.
## Examples
```ts
const es: Locale = 'es';
const custom: Locale = 'en-US';
const regional: Locale = 'pt-BR';
```
Providers and `getI18nTranslations` normalize tags and walk the parent chain (`es-MX` → `es` → `en`) via `findLocaleKeys`. See <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for explicit and ambient resolution.
## Related
- <DocsLink slug="reference/built-in-locale">`BuiltInLocale`</DocsLink>
- <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink>
- <DocsLink slug="how-to/i18n-switch-locale">Switch locale dynamically</DocsLink>
@@ -8,17 +8,14 @@ import FrameworkCase from "@/components/docs/FrameworkCase.astro";
<FrameworkCase frameworks={["html"]}>
`<media-i18n>` 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.
`<media-i18n>` applies the i18n provider mixin to a subtree. Wrap a player, standalone controls, or one of several players that need different locales with this element. Without it, HTML controls use English defaults.
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 phrase keys and fallback behavior.
Set `lang` on the provider or an ancestor. Built-in packs lazy-load automatically; register locale strings with <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> when you need custom copy or synchronous first paint. See <DocsLink slug="reference/translation-phrases">Translation phrases</DocsLink> for supported keys.
```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);
import '@videojs/html/i18n/locales/es/register';
</script>
<media-i18n lang="es">
@@ -18,7 +18,7 @@ import FrameworkCase from "@/components/docs/FrameworkCase.astro";
## Text content
The initial text content is the English translation phrase (`Play`, `Pause`, `{duration} remaining`, …). If the phrase is not registered for the active locale, `<media-text>` keeps the original text.
The initial text content is the English translation phrase (`Play`, `Pause`, `{duration} remaining`, …). See <DocsLink slug="reference/translation-phrases">Translation phrases</DocsLink> for supported keys. If the phrase is not registered for the active locale, `<media-text>` keeps the original text.
Parametric phrases are not yet supported on `<media-text>` — use `createTranslator` in script for interpolated strings.
@@ -4,9 +4,14 @@ description: Subscribe to global i18n registry mutations
---
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
`onI18nRegistryChange` registers a callback that runs whenever any locale layer changes, for example after `registerI18n` or browser translation prefetch. Returns an unsubscribe function.
<FrameworkCase frameworks={["react"]}>
React `I18nProvider` uses this to invalidate translators when the registry updates.
</FrameworkCase>
<UtilReference util="onI18nRegistryChange" />
@@ -5,10 +5,26 @@ description: Register or merge translation strings for a BCP 47 locale tag in th
import UtilReference from "@/components/docs/api-reference/UtilReference.astro";
import DocsLink from "@/components/docs/DocsLink.astro";
import FrameworkCase from "@/components/docs/FrameworkCase.astro";
`registerI18n` merges a partial translation map into the process-wide registry for a locale tag. English defaults are registered by the i18n bundle. Call `registerI18n` for custom locales or to patch shipped packs before the player renders.
`registerI18n` merges a partial translation map into the process-wide registry for a locale tag. English defaults are registered by the i18n bundle. Built-in non-English packs lazy-load automatically; call `registerI18n` for custom locales, CDN locale modules, or patched shipped packs before the provider renders.
Import from `@videojs/html/i18n` or `@videojs/react/i18n`. Keys are the current English phrases (`Play`, `Pause`). See <DocsLink slug="concepts/i18n">Internationalization</DocsLink> and <DocsLink slug="reference/translations">`Translations`</DocsLink>.
Keys are the current English phrases (`Play`, `Pause`). See <DocsLink slug="reference/translation-phrases">Translation phrases</DocsLink> and <DocsLink slug="reference/translations">`Translations`</DocsLink>.
<FrameworkCase frameworks={["html"]}>
```ts
import { registerI18n } from '@videojs/html/i18n';
registerI18n('es', {
Play: 'Reproducir',
Pause: 'Pausar',
});
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import { registerI18n } from '@videojs/react/i18n';
@@ -19,4 +35,6 @@ registerI18n('es', {
});
```
</FrameworkCase>
<UtilReference util="registerI18n" />
@@ -4,16 +4,30 @@ description: Typed contract for translation phrases and their placeholder argume
---
import DocsLink from '@/components/docs/DocsLink.astro';
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
`TranslationParams` maps every current English translation phrase to its argument shape. Phrases with `never` accept only `t('Phrase')`. Phrases with an object accept `t('Phrase {value}', { })` with typed placeholder names.
`TranslationParams` maps every current English translation phrase to its argument shape. Phrases with `never` accept only `t('Phrase')`. Phrases with an object accept `t('Phrase {value}', { ... })` with typed placeholder names.
See <DocsLink slug="reference/translation-phrases">Translation phrases</DocsLink> for the complete key catalog and where each phrase appears.
## Import
<FrameworkCase frameworks={["html"]}>
```ts
import type { TranslationParams } from '@videojs/html/i18n';
// or @videojs/react/i18n
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import type { TranslationParams } from '@videojs/react/i18n';
```
</FrameworkCase>
## Definition
```ts
@@ -22,7 +36,7 @@ type TranslationParams = {
Pause: never;
'Seek forward {seconds} seconds': { seconds: number | string };
'{duration} remaining': { duration: string };
//
// ...
};
```
@@ -39,10 +53,11 @@ English defaults and the full phrase list live in `packages/core/src/core/i18n/l
| `{duration} remaining` | `{duration}` |
| `{percent}, muted` | `{percent}` |
| `Volume {value}` | `{value}` |
| `Auto ({label})` | `{label}` |
All other phrases are plain strings with no parameters.
## Usage with Translator
## Usage
```ts
const t: Translator = createTranslator(translations, 'es');
@@ -52,16 +67,10 @@ t('Seek forward {seconds} seconds', { seconds: 10 });
t('{duration} remaining', { duration: '1 minute' });
```
TypeScript rejects missing placeholders when defining <DocsLink slug="reference/translations">`Translations`</DocsLink> overlays:
```ts
registerI18n('es', {
'Seek forward {seconds} seconds': 'Adelantar', // error: missing {seconds}
});
```
TypeScript rejects missing placeholders when defining <DocsLink slug="reference/translations">`Translations`</DocsLink> overlays.
## Related
- <DocsLink slug="reference/translations">`Translations`</DocsLink>
- <DocsLink slug="reference/translator">`Translator`</DocsLink>
- <DocsLink slug="concepts/i18n">Internationalization</DocsLink>: Phrase keys overview
- <DocsLink slug="reference/translation-phrases">Translation phrases</DocsLink>
@@ -0,0 +1,76 @@
---
title: Translation phrases
description: English i18n phrase keys and the player UI that uses them
---
import DocsLink from '@/components/docs/DocsLink.astro';
Video.js uses the English phrases below as translation keys. They are defined in `packages/core/src/core/i18n/locales/en.ts` and typed by <DocsLink slug="reference/translation-params">`TranslationParams`</DocsLink>. Both HTML and React controls use the same keys.
Use these keys with <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> or React `I18nProvider` `translations` overrides.
## Playback controls
| Phrase | Used by |
| --- | --- |
| `Play`, `Pause`, `Replay` | Play button and its tooltip |
| `Mute`, `Unmute` | Mute button and its tooltip |
| `Seek forward {seconds} seconds`, `Seek backward {seconds} seconds` | Seek buttons and their tooltips |
| `Enter fullscreen`, `Exit fullscreen` | Fullscreen button and input feedback |
| `Enter picture-in-picture`, `Exit picture-in-picture` | Picture-in-picture button |
| `Enable captions`, `Disable captions` | Captions button and captions selection control |
| `Playing live`, `Seek to live edge`, `Live` | Live button |
| `Start casting`, `Stop casting`, `Connecting` | Cast button; `Connecting` also appears on the AirPlay button |
## Time and volume
| Phrase | Used by |
| --- | --- |
| `Seek` | Time slider aria label |
| `Volume` | Volume slider aria label and input feedback |
| `Current time`, `Duration`, `Remaining` | Time display aria labels |
| `{duration} remaining` | Remaining time display |
| `{duration}. Show elapsed time.`, `{duration}. Show duration.`, `{duration}. Show remaining time.` | Toggleable time display aria labels |
| `Playback rate {rate}` | Playback-rate button and radio group |
| `{current} of {duration}` | Time slider value text |
| `{percent}, muted` | Muted volume slider value text |
| `Muted`, `Volume {value}` | Volume input feedback |
## Playback feedback
| Phrase | Used by |
| --- | --- |
| `Captions on`, `Captions off` | Input feedback after captions changes |
| `Paused`, `Playing` | Input feedback after playback changes |
| `Fullscreen`, `Exit fullscreen` | Input feedback after fullscreen changes |
| `Picture in picture`, `Exit picture in picture` | Input feedback after picture-in-picture changes |
## Errors
| Phrase | Used by |
| --- | --- |
| `You stopped media playback before it finished.` | Media error dialog for `MEDIA_ERR_ABORTED` |
| `This media could not be loaded due to a network or server issue.` | Media error dialog for `MEDIA_ERR_NETWORK` |
| `This media could not be played. It may be corrupted, or your browser may not support its format.` | Media error dialog for `MEDIA_ERR_DECODE` |
| `This media could not be loaded. It may be unavailable, or your browser may not support its format.` | Media error dialog for `MEDIA_ERR_SRC_NOT_SUPPORTED` |
| `This media could not be played because it could not be decrypted.` | Media error dialog for `MEDIA_ERR_ENCRYPTED` |
| `Something went wrong.`, `An unexpected error occurred.` | Generic media error dialog title and fallback description |
| `OK` | Error dialog confirmation button |
## Settings and track menus
| Phrase | Used by |
| --- | --- |
| `Settings` | Video settings menu trigger |
| `Quality`, `Auto`, `Auto ({label})` | Quality menu and automatic-quality option |
| `Audio` | Audio-track menu and fallback track label |
| `Default` | Reserved default-option label; no stock control currently emits it |
| `Speed`, `Playback rate` | Playback-rate menu and radio group |
| `Captions`, `Subtitles`, `Off` | Captions menu and track options |
| `Back` | Nested settings-menu back button |
## Related
- <DocsLink slug="reference/translation-params">`TranslationParams`</DocsLink>
- <DocsLink slug="reference/translations">`Translations`</DocsLink>
- <DocsLink slug="how-to/i18n-override-translations">Override translations</DocsLink>
@@ -4,16 +4,36 @@ description: Partial map of English translation phrases to localized strings
---
import DocsLink from '@/components/docs/DocsLink.astro';
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
`Translations` is the shape for locale packs passed to <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink>, the React `translations` prop on <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>, and <DocsLink slug="reference/create-translator">`createTranslator`</DocsLink>. Every entry is optional. Missing keys fall back through the BCP 47 chain to English.
`Translations` is the shape for locale packs passed to <DocsLink slug="reference/register-i18n">`registerI18n`</DocsLink> and <DocsLink slug="reference/create-translator">`createTranslator`</DocsLink>. Every entry is optional. Missing keys fall back through the BCP 47 chain, lazy-loaded built-in packs, supported browser translation fallback, and English.
<FrameworkCase frameworks={["react"]}>
`Translations` is also the type for the `translations` prop on <DocsLink slug="reference/i18n-provider">`I18nProvider`</DocsLink>.
</FrameworkCase>
See <DocsLink slug="reference/translation-phrases">Translation phrases</DocsLink> for every supported key.
## Import
<FrameworkCase frameworks={["html"]}>
```ts
import type { Translations } from '@videojs/html/i18n';
// or @videojs/react/i18n
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import type { Translations } from '@videojs/react/i18n';
```
</FrameworkCase>
## Definition
```ts
@@ -25,13 +45,14 @@ type Translations = {
};
```
Parametric values must include the same `{placeholder}` substrings as English (`{seconds}`, `{duration}`, ). TypeScript enforces this when you use `satisfies Partial<Translations>`.
Parametric values must include the same `{placeholder}` substrings as English (`{seconds}`, `{duration}`, and so on). TypeScript enforces this when you use `satisfies Partial<Translations>`.
## Examples
<FrameworkCase frameworks={["html"]}>
```ts
import type { Translations } from '@videojs/html/i18n';
// or @videojs/react/i18n
import { registerI18n, type Translations } from '@videojs/html/i18n';
const es = {
Play: 'Reproducir',
@@ -42,10 +63,24 @@ const es = {
registerI18n('es', es);
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```tsx
<I18nProvider locale="de" translations={{ Play: 'Abspielen' }} />
import { I18nProvider, type Translations } from '@videojs/react/i18n';
const translations = {
Play: 'Abspielen',
} satisfies Partial<Translations>;
<I18nProvider locale="de" translations={translations}>
<Player />
</I18nProvider>;
```
</FrameworkCase>
Only supplied keys override lower layers. See <DocsLink slug="concepts/i18n">Internationalization</DocsLink> for merge priority.
## Related
+41 -4
View File
@@ -4,16 +4,34 @@ description: Typed function that resolves English translation phrases to localiz
---
import DocsLink from '@/components/docs/DocsLink.astro';
import FrameworkCase from '@/components/docs/FrameworkCase.astro';
`Translator` is the callable returned by <DocsLink slug="reference/create-translator">`createTranslator`</DocsLink> and <DocsLink slug="reference/use-translator">`useTranslator`</DocsLink>. It turns current English phrases from core controls into localized copy and interpolates `{placeholder}` tokens when params are required.
`Translator` is the callable returned by <DocsLink slug="reference/create-translator">`createTranslator`</DocsLink>. It turns current English phrases from core controls into localized copy and interpolates `{placeholder}` tokens when params are required.
<FrameworkCase frameworks={["react"]}>
`useTranslator` also returns a `Translator` from the nearest `I18nProvider`.
</FrameworkCase>
## Import
<FrameworkCase frameworks={["html"]}>
```ts
import type { Translator } from '@videojs/html/i18n';
// or @videojs/react/i18n
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import type { Translator } from '@videojs/react/i18n';
```
</FrameworkCase>
## Definition
```ts
@@ -30,14 +48,30 @@ Missing phrases in the active map resolve to the source English (`'Play'`) so pa
## Create manually
<FrameworkCase frameworks={["html"]}>
```ts
import { createTranslator, getI18nTranslations } from '@videojs/html/i18n';
// or @videojs/react/i18n
const t = createTranslator(getI18nTranslations('pt-BR'), 'pt-BR');
t('Pause'); // localized or English fallback
t('Pause');
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
```ts
import { createTranslator, getI18nTranslations } from '@videojs/react/i18n';
const t = createTranslator(getI18nTranslations('pt-BR'), 'pt-BR');
t('Pause');
```
</FrameworkCase>
<FrameworkCase frameworks={["react"]}>
## React hook
```tsx
@@ -49,6 +83,8 @@ function Label() {
}
```
</FrameworkCase>
Control components resolve phrases from core `getLabel()` through their framework adapters. You rarely call `t()` directly unless building custom UI.
## Related
@@ -56,3 +92,4 @@ Control components resolve phrases from core `getLabel()` through their framewor
- <DocsLink slug="reference/create-translator">`createTranslator`</DocsLink>
- <DocsLink slug="reference/use-translator">`useTranslator`</DocsLink>
- <DocsLink slug="reference/translation-params">`TranslationParams`</DocsLink>
- <DocsLink slug="reference/translation-phrases">Translation phrases</DocsLink>
@@ -18,7 +18,7 @@ import { useTranslator } from '@videojs/react/i18n';
function PlayLabel() {
const t = useTranslator();
return <span>{t('play')}</span>;
return <span>{t('Play')}</span>;
}
```
+1 -6
View File
@@ -17,11 +17,6 @@ export const sidebar: Sidebar = [
},
],
},
{
sidebarLabel: 'Contributing',
devOnly: true,
contents: [{ slug: 'how-to/i18n-add-built-in-locale', sidebarLabel: 'Add a built-in locale' }],
},
{
sidebarLabel: 'Getting started',
// May change when we revisit this section's boundary with Concepts (#1105)
@@ -198,11 +193,11 @@ export const sidebar: Sidebar = [
{ slug: 'reference/has-registered-locale', sidebarLabel: 'hasRegisteredLocale' },
{ slug: 'reference/on-i18n-registry-change', sidebarLabel: 'onI18nRegistryChange' },
{ slug: 'reference/create-translator', sidebarLabel: 'createTranslator' },
{ slug: 'reference/translation-phrases', sidebarLabel: 'Translation phrases' },
{
sidebarLabel: 'Types',
defaultOpen: false,
contents: [
{ slug: 'reference/built-in-locale', sidebarLabel: 'BuiltInLocale' },
{ slug: 'reference/locale', sidebarLabel: 'Locale' },
{ slug: 'reference/translation-params', sidebarLabel: 'TranslationParams' },
{ slug: 'reference/translations', sidebarLabel: 'Translations' },
+1
View File
@@ -3,6 +3,7 @@
.astro-code {
font-family: var(--font-mono), monospace;
font-variant-ligatures: none;
color: var(--color-manila-light);
}
/* reinforcing shared.codeBlock */
+1 -1
View File
@@ -6,7 +6,7 @@ import type { ShikiTransformer } from 'shiki';
*
* Shiki should only highlight the text; the code container's background and
* scrolling are owned by `CodeFrame` and the `.astro-code` rules. Token colors
* live on the inner spans, so removing the pre's style leaves them untouched.
* live on the inner spans, while `.astro-code` supplies the plaintext color.
*
* Astro adds its built-in `pre` transformer before user transformers, so this
* one runs last and sees the fully-assembled style to remove.