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' },