diff --git a/site/src/content/docs/concepts/i18n.mdx b/site/src/content/docs/concepts/i18n.mdx
index e0b5393d..83ef2f80 100644
--- a/site/src/content/docs/concepts/i18n.mdx
+++ b/site/src/content/docs/concepts/i18n.mdx
@@ -23,13 +23,18 @@ Video.js translates control labels, ARIA text, tooltips, and error copy through
```
```tsx title="react"
-
-
-
-
-
-
-
+import { Provider, VideoSkin, Video } from '@videojs/react/video';
+
+// Set `` on the document (layout, _document, or index.html)
+export function App() {
+ return (
+
+
+
+
+
+ );
+}
```
Register a locale once (or rely on lazy-loaded built-in packs), set `lang`, and skins pick up translated strings automatically.
@@ -38,7 +43,7 @@ Register a locale once (or rely on lazy-loaded built-in packs), set `lang`, and
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.
+Keys are typed in `TranslationParams` — TypeScript catches missing `{param}` placeholders and wrong argument names at compile time.
Parametric strings use `{placeholder}` tokens, for example `seekForward: 'Seek forward {seconds} seconds'` and `timeRemainingPhrase: '{duration} remaining'`.
@@ -147,6 +152,7 @@ For zero flash-of-English on SSR or locale switches, pass `translations` directl
## See also
+- `Locale`, `Translations`, `Translator` — core types
- Register a custom locale
- Override individual keys
- Switch locale dynamically
diff --git a/site/src/content/docs/how-to/i18n-add-built-in-locale.mdx b/site/src/content/docs/how-to/i18n-add-built-in-locale.mdx
index 3cb383ef..50d87a4c 100644
--- a/site/src/content/docs/how-to/i18n-add-built-in-locale.mdx
+++ b/site/src/content/docs/how-to/i18n-add-built-in-locale.mdx
@@ -12,7 +12,7 @@ This guide is for **contributors** adding or updating shipped packs — app auth
### Prerequisites
-- Familiarity with Internationalization and `TranslationParams`
+- Familiarity with Internationalization and `TranslationParams`
- English defaults in `packages/core/src/core/i18n/locales/en.ts` as the source of keys
## 1. Add the locale file
diff --git a/site/src/content/docs/how-to/i18n-register-locale.mdx b/site/src/content/docs/how-to/i18n-register-locale.mdx
index 9e83c1d6..4ad0e77c 100644
--- a/site/src/content/docs/how-to/i18n-register-locale.mdx
+++ b/site/src/content/docs/how-to/i18n-register-locale.mdx
@@ -71,12 +71,12 @@ Preset skins use `Container`, which includes = {
+const es = {
play: 'Reproducir',
pause: 'Pausa',
mute: 'Silenciar',
unmute: 'Activar sonido',
-};
+} satisfies Partial;
export default es;
```
@@ -127,5 +127,6 @@ Load your module after the player script, before playback starts.
## What's next?
- Override individual keys
+- `Translations` type
- Switch locale at runtime
- `registerI18n` reference
diff --git a/site/src/content/docs/reference/built-in-locale.mdx b/site/src/content/docs/reference/built-in-locale.mdx
new file mode 100644
index 00000000..32e06117
--- /dev/null
+++ b/site/src/content/docs/reference/built-in-locale.mdx
@@ -0,0 +1,50 @@
+---
+title: BuiltInLocale
+description: Union of shipped BCP 47 locale tags with autocomplete in TypeScript
+---
+
+import DocsLink from '@/components/docs/DocsLink.astro';
+
+`BuiltInLocale` narrows `Locale` to tags with shipped translation packs in `@videojs/core/i18n/locales/*`. TypeScript suggests these tags when you call `registerI18n`, `loadLocale`, or import locale modules.
+
+## Import
+
+```ts
+import type { BuiltInLocale } from '@videojs/core/i18n';
+```
+
+Runtime lists are also exported:
+
+```ts
+import { BUILT_IN_LOCALES, LOCALE_ALIAS_TAGS, SHIPPED_LOCALE_TAGS } from '@videojs/core/i18n';
+```
+
+## Definition
+
+```ts
+type BuiltInLocale =
+ | (typeof BUILT_IN_LOCALES)[number]
+ | (typeof LOCALE_ALIAS_TAGS)[number];
+```
+
+`BUILT_IN_LOCALES` lists regional packs (`es`, `pt-BR`, `zh-CN`, …). `LOCALE_ALIAS_TAGS` adds shorthand tags (`pt`, `zh`) that resolve to regional packs through the lookup chain.
+
+## Lazy loading
+
+Providers call `loadLocale(tag)` for tags in `SHIPPED_LOCALE_TAGS` when a pack is not already registered. Explicit `registerI18n` or CDN locale modules skip the async gap on first paint.
+
+## Examples
+
+```ts
+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
+
+- `Locale`
+- Add a built-in locale (contributors)
+- Register a custom locale
diff --git a/site/src/content/docs/reference/create-i18n.mdx b/site/src/content/docs/reference/create-i18n.mdx
index 9a18ea27..9f13079c 100644
--- a/site/src/content/docs/reference/create-i18n.mdx
+++ b/site/src/content/docs/reference/create-i18n.mdx
@@ -15,7 +15,10 @@ import FrameworkCase from "@/components/docs/FrameworkCase.astro";
import { createI18n } from '@videojs/react/i18n';
const { I18nProvider, useTranslator } = createI18n({
- loadLocale: async (tag) => import(`./locales/${tag}.json`),
+ loadLocale: async (tag) => {
+ const mod = await import(`@videojs/react/i18n/locales/${tag}`);
+ return mod.default;
+ },
});
function App() {
diff --git a/site/src/content/docs/reference/create-translator.mdx b/site/src/content/docs/reference/create-translator.mdx
new file mode 100644
index 00000000..ce3c7f6e
--- /dev/null
+++ b/site/src/content/docs/reference/create-translator.mdx
@@ -0,0 +1,19 @@
+---
+title: createTranslator
+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";
+
+`createTranslator` wraps a `Translations` map and returns a `Translator`. Providers call this internally after merging registry, lazy, and prop layers — use it directly for custom UI outside built-in mixins.
+
+```ts
+import { createTranslator, getI18nTranslations } from '@videojs/core/i18n';
+
+const t = createTranslator(getI18nTranslations('fr'), 'fr');
+t('play');
+t('seekForward', { seconds: 5 });
+```
+
+
diff --git a/site/src/content/docs/reference/locale.mdx b/site/src/content/docs/reference/locale.mdx
new file mode 100644
index 00000000..957f126e
--- /dev/null
+++ b/site/src/content/docs/reference/locale.mdx
@@ -0,0 +1,41 @@
+---
+title: Locale
+description: BCP 47 language tag type for i18n registry and provider APIs
+---
+
+import DocsLink from '@/components/docs/DocsLink.astro';
+
+`Locale` is the BCP 47 tag type used by `registerI18n`, providers, and `Translator`. Shipped packs autocomplete as `BuiltInLocale`; any other tag remains valid at runtime.
+
+## Import
+
+```ts
+import type { Locale } from '@videojs/core/i18n';
+// or @videojs/html/i18n, @videojs/react/i18n
+```
+
+## Definition
+
+```ts
+type Locale = BuiltInLocale | (string & {});
+```
+
+The `(string & {})` pattern keeps custom tags (`'xx'`, `'en-US'`) type-safe without losing autocomplete for built-ins.
+
+## Resolution
+
+Providers and `getI18nTranslations` normalize tags and walk the parent chain (`es-MX` → `es` → `en`) via `localeLookupChain`. See Internationalization for explicit vs ambient resolution.
+
+## Examples
+
+```ts
+const es: Locale = 'es';
+const custom: Locale = 'en-US';
+const regional: Locale = 'pt-BR';
+```
+
+## Related
+
+- `BuiltInLocale`
+- `registerI18n`
+- Switch locale dynamically
diff --git a/site/src/content/docs/reference/register-i18n.mdx b/site/src/content/docs/reference/register-i18n.mdx
index c8edf5ca..feb2f389 100644
--- a/site/src/content/docs/reference/register-i18n.mdx
+++ b/site/src/content/docs/reference/register-i18n.mdx
@@ -8,7 +8,7 @@ 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 Internationalization.
+Import from `@videojs/core/i18n`, `@videojs/html/i18n`, or `@videojs/react/i18n`. Keys are opaque camelCase tokens (`play`, `pause`) — see Internationalization and `Translations`.
```ts
import { registerI18n } from '@videojs/react/i18n';
diff --git a/site/src/content/docs/reference/translation-params.mdx b/site/src/content/docs/reference/translation-params.mdx
new file mode 100644
index 00000000..6c3db844
--- /dev/null
+++ b/site/src/content/docs/reference/translation-params.mdx
@@ -0,0 +1,66 @@
+---
+title: TranslationParams
+description: Typed contract for translation keys and their placeholder arguments
+---
+
+import DocsLink from '@/components/docs/DocsLink.astro';
+
+`TranslationParams` maps every opaque translation key to its argument shape. Keys with `never` accept only `t('key')`. Keys with an object accept `t('key', { … })` with typed placeholder names.
+
+## Import
+
+```ts
+import type { TranslationParams } from '@videojs/core/i18n';
+```
+
+## Definition
+
+```ts
+type TranslationParams = {
+ play: never;
+ pause: never;
+ seekForward: { seconds: number | string };
+ timeRemainingPhrase: { duration: string };
+ // …
+};
+```
+
+English defaults and the full key list live in `packages/core/src/core/i18n/locales/en.ts`. The authoritative TypeScript map is `packages/core/src/core/i18n/types.ts`.
+
+## Parametric keys
+
+| Key | Placeholders | Example English value |
+| --- | --- | --- |
+| `seekForward` | `{seconds}` | `Seek forward {seconds} seconds` |
+| `seekBackward` | `{seconds}` | `Seek backward {seconds} seconds` |
+| `playbackRateAria` | `{rate}` | `Playback rate {rate}` |
+| `timeSliderValueTextRange` | `{current}`, `{duration}` | `{current} of {duration}` |
+| `timeRemainingPhrase` | `{duration}` | `{duration} remaining` |
+| `volumeSliderValueTextMuted` | `{percent}` | `{percent}, muted` |
+| `indicatorVolumeWithValue` | `{value}` | `Volume {value}` |
+
+All other keys are plain strings with no parameters.
+
+## Usage with Translator
+
+```ts
+const t: Translator = createTranslator(translations, 'es');
+
+t('play');
+t('seekForward', { seconds: 10 });
+t('timeRemainingPhrase', { duration: '1 minute' });
+```
+
+TypeScript rejects missing placeholders when defining `Translations` overlays:
+
+```ts
+registerI18n('es', {
+ seekForward: 'Adelantar', // error: missing {seconds}
+});
+```
+
+## Related
+
+- `Translations`
+- `Translator`
+- Internationalization — opaque keys overview
diff --git a/site/src/content/docs/reference/translations.mdx b/site/src/content/docs/reference/translations.mdx
new file mode 100644
index 00000000..172fdc38
--- /dev/null
+++ b/site/src/content/docs/reference/translations.mdx
@@ -0,0 +1,53 @@
+---
+title: Translations
+description: Partial map of opaque translation keys to localized strings
+---
+
+import DocsLink from '@/components/docs/DocsLink.astro';
+
+`Translations` is the shape for locale packs passed to `registerI18n`, the React `translations` prop on `I18nProvider`, and `createTranslator`. Every entry is optional — missing keys fall back through the BCP 47 chain to English.
+
+## Import
+
+```ts
+import type { Translations } from '@videojs/core/i18n';
+```
+
+## Definition
+
+```ts
+type Translations = {
+ [K in keyof TranslationParams]?: TranslationParams[K] extends never
+ ? string
+ : /* parametric keys must include required {placeholder} tokens */
+ string;
+};
+```
+
+Parametric values must include the same `{placeholder}` substrings as English (`{seconds}`, `{duration}`, …). TypeScript enforces this when you use `satisfies Partial`.
+
+## Examples
+
+```ts
+import type { Translations } from '@videojs/core/i18n';
+
+const es = {
+ play: 'Reproducir',
+ pause: 'Pausa',
+ seekForward: 'Adelantar {seconds} segundos',
+} satisfies Partial;
+
+registerI18n('es', es);
+```
+
+```tsx
+
+```
+
+Only supplied keys override lower layers. See Internationalization for merge priority.
+
+## Related
+
+- `TranslationParams`
+- `getI18nTranslations`
+- Override translation keys
diff --git a/site/src/content/docs/reference/translator.mdx b/site/src/content/docs/reference/translator.mdx
new file mode 100644
index 00000000..b752a988
--- /dev/null
+++ b/site/src/content/docs/reference/translator.mdx
@@ -0,0 +1,56 @@
+---
+title: Translator
+description: Typed function that resolves opaque translation keys to localized strings
+---
+
+import DocsLink from '@/components/docs/DocsLink.astro';
+
+`Translator` is the callable returned by `createTranslator` and `useTranslator`. It turns opaque keys from core controls into localized copy and interpolates `{placeholder}` tokens when params are required.
+
+## Import
+
+```ts
+import type { Translator } from '@videojs/core/i18n';
+```
+
+## Definition
+
+```ts
+type Translator = (
+ key: K,
+ ...args: TranslationParams[K] extends never ? [] : [params: TranslationParams[K]]
+) => string;
+```
+
+- Plain keys: `t('play')`
+- Parametric keys: `t('seekForward', { seconds: 10 })`
+
+Missing keys in the active map resolve to the key string (`'play'`) so partial locale packs degrade visibly during development.
+
+## Create manually
+
+```ts
+import { createTranslator, getI18nTranslations } from '@videojs/core/i18n';
+
+const t = createTranslator(getI18nTranslations('pt-BR'), 'pt-BR');
+t('pause'); // localized or key fallback
+```
+
+## React hook
+
+```tsx
+import { useTranslator } from '@videojs/react/i18n';
+
+function Label() {
+ const t = useTranslator();
+ return {t('play')};
+}
+```
+
+Control components resolve keys from core `getLabel()` through `resolveControlLabel` / `resolveControlAttrs` — you rarely call `t()` directly unless building custom UI.
+
+## Related
+
+- `createTranslator`
+- `useTranslator`
+- `TranslationParams`
diff --git a/site/src/docs.config.ts b/site/src/docs.config.ts
index ef8f4e43..0a94f67e 100644
--- a/site/src/docs.config.ts
+++ b/site/src/docs.config.ts
@@ -165,6 +165,18 @@ export const sidebar: Sidebar = [
{ slug: 'reference/get-i18n-translations', sidebarLabel: 'getI18nTranslations' },
{ slug: 'reference/has-registered-i18n', sidebarLabel: 'hasRegisteredI18n' },
{ slug: 'reference/on-i18n-registry-change', sidebarLabel: 'onI18nRegistryChange' },
+ { slug: 'reference/create-translator', sidebarLabel: 'createTranslator' },
+ {
+ 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' },
+ { slug: 'reference/translator', sidebarLabel: 'Translator' },
+ ],
+ },
{ slug: 'reference/feature-buffer' },
{ slug: 'reference/feature-controls' },
{ slug: 'reference/feature-error' },