diff --git a/build/scripts/check-workspace.mjs b/build/scripts/check-workspace.mjs
index fc12f8da..bfba6d6a 100644
--- a/build/scripts/check-workspace.mjs
+++ b/build/scripts/check-workspace.mjs
@@ -11,7 +11,6 @@
* 4. Package metadata — non-private packages have required fields
* 5. Release-please config — every versioned package is registered
* 6. Define imports — no bare side-effect imports from relative paths
- * 7. i18n locales — tag lists match locale files and generated stubs
*/
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
@@ -325,89 +324,6 @@ function checkDefineImports() {
return { ok: warnings.length === 0, warnings };
}
-// ── Check 7: i18n locale consistency ─────────────────────────────────────────
-
-const GENERATED_I18N_HEADER = '/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */';
-
-function parseLocaleTagArray(source, exportName) {
- const match = source.match(new RegExp(`export const ${exportName} = \\[([\\s\\S]*?)\\] as const`));
- if (!match) {
- throw new Error(`Could not parse ${exportName} from built-in-locales.ts`);
- }
- return [...match[1].matchAll(/'([^']+)'/g)].map((m) => m[1]);
-}
-
-function checkI18nLocales() {
- const warnings = [];
- const builtInPath = join(PACKAGES_DIR, 'core/src/core/i18n/built-in-locales.ts');
- const builtInSource = readText(builtInPath);
- const builtInLocales = parseLocaleTagArray(builtInSource, 'BUILT_IN_LOCALES');
- const aliasTags = parseLocaleTagArray(builtInSource, 'LOCALE_ALIAS_TAGS');
- const shippedTags = [...builtInLocales, ...aliasTags];
-
- const coreLocalesDir = join(PACKAGES_DIR, 'core/src/core/i18n/locales');
- const coreFiles = readdirSync(coreLocalesDir)
- .filter((file) => file.endsWith('.ts'))
- .map((file) => file.slice(0, -3));
-
- const expectedCore = new Set(['all', 'en', ...builtInLocales, ...aliasTags]);
-
- for (const tag of builtInLocales) {
- if (!coreFiles.includes(tag)) {
- warnings.push(`BUILT_IN_LOCALES tag "${tag}" has no packages/core/src/core/i18n/locales/${tag}.ts`);
- }
- }
-
- for (const tag of aliasTags) {
- if (!coreFiles.includes(tag)) {
- warnings.push(`LOCALE_ALIAS_TAGS tag "${tag}" has no packages/core/src/core/i18n/locales/${tag}.ts`);
- }
- }
-
- for (const file of coreFiles) {
- if (!expectedCore.has(file)) {
- warnings.push(
- `Unexpected locale file packages/core/src/core/i18n/locales/${file}.ts (not in built-in-locales.ts)`
- );
- }
- }
-
- const allPath = join(coreLocalesDir, 'all.ts');
- if (!readText(allPath).startsWith(GENERATED_I18N_HEADER)) {
- warnings.push(
- 'packages/core/src/core/i18n/locales/all.ts is not generated — run pnpm -F @videojs/core generate:locales'
- );
- }
-
- for (const pkg of ['html', 'react']) {
- const localesDir = join(PACKAGES_DIR, `${pkg}/src/i18n/locales`);
- const expectedPlatform = new Set(['all', 'en', ...shippedTags]);
-
- for (const tag of expectedPlatform) {
- const filePath = join(localesDir, `${tag}.ts`);
- if (!existsSync(filePath)) {
- warnings.push(`Missing generated re-export packages/${pkg}/src/i18n/locales/${tag}.ts`);
- continue;
- }
- if (!readText(filePath).startsWith(GENERATED_I18N_HEADER)) {
- warnings.push(
- `packages/${pkg}/src/i18n/locales/${tag}.ts is not generated — run pnpm -F @videojs/core generate:locales`
- );
- }
- }
-
- for (const file of readdirSync(localesDir)) {
- if (!file.endsWith('.ts')) continue;
- const tag = file.slice(0, -3);
- if (!expectedPlatform.has(tag)) {
- warnings.push(`Unexpected locale re-export packages/${pkg}/src/i18n/locales/${file}`);
- }
- }
- }
-
- return { ok: warnings.length === 0, warnings };
-}
-
// ── Main ────────────────────────────────────────────────────────────────────
const checks = [
@@ -418,7 +334,6 @@ const checks = [
{ name: 'Release-please config', fn: checkReleasePleaseConfig },
{ name: 'Bundled docs publishing', fn: checkBundledDocs },
{ name: 'Define imports', fn: checkDefineImports },
- { name: 'i18n locales', fn: checkI18nLocales },
];
let failed = 0;
diff --git a/internal/design/i18n.md b/internal/design/i18n.md
index 5c1f1174..121c933e 100644
--- a/internal/design/i18n.md
+++ b/internal/design/i18n.md
@@ -1,5 +1,5 @@
---
-status: implemented
+status: draft
date: 2026-03-25
---
@@ -162,10 +162,10 @@ const { default: translations } = await import(`@videojs/react/i18n/locales/${lo
**Dynamic switching**
-Changing locale loads the shipped pack automatically via `loadLocale` (or pass `translations` / call `registerI18n` for zero-flash SSR):
+Just flip `locale` — the provider lazy-loads the built-in pack for the new locale:
```tsx
-const [locale, setLocale] = useState('es');
+const [locale, setLocale] = useState('en');
@@ -176,7 +176,7 @@ const [locale, setLocale] = useState('es');
```
-For zero-flash switching, pre-import the locale and pass `translations` directly:
+Or for zero-flash switching, pre-import the locale and pass `translations` directly:
```tsx
const [{ locale, translations }, setLocale] = useState({ locale: 'en', translations: undefined });
@@ -215,55 +215,27 @@ Keys are opaque camelCase identifiers. The English string is the value in `en.ts
| `replay` | `'Replay'` | — | `PlayButtonCore` |
| `mute` | `'Mute'` | — | `MuteButtonCore` |
| `unmute` | `'Unmute'` | — | `MuteButtonCore` |
-| `seekForward` | `'Seek forward {seconds} seconds'` | `{seconds}` | `SeekButtonCore` |
-| `seekBackward` | `'Seek backward {seconds} seconds'` | `{seconds}` | `SeekButtonCore` |
+| `seek` | `'Seek'` | — | `TimeSliderCore` (aria-label) |
+| `volume` | `'Volume'` | — | `VolumeSliderCore` (aria-label) |
+| `muted` | `'muted'` | — | `VolumeSliderCore` (aria-valuetext suffix) |
| `enterFullscreen` | `'Enter fullscreen'` | — | `FullscreenButtonCore` |
| `exitFullscreen` | `'Exit fullscreen'` | — | `FullscreenButtonCore` |
| `enableCaptions` | `'Enable captions'` | — | `CaptionsButtonCore` |
| `disableCaptions` | `'Disable captions'` | — | `CaptionsButtonCore` |
| `enterPictureInPicture` | `'Enter picture-in-picture'` | — | `PipButtonCore` |
| `exitPictureInPicture` | `'Exit picture-in-picture'` | — | `PipButtonCore` |
-| `playingLive` | `'Playing live'` | — | `LiveButtonCore` |
-| `seekToLiveEdge` | `'Seek to live edge'` | — | `LiveButtonCore` |
-| `liveBadge` | `'Live'` | — | Live badge / time display |
-| `startCasting` | `'Start casting'` | — | `CastButtonCore` |
-| `stopCasting` | `'Stop casting'` | — | `CastButtonCore` |
-| `connectingCast` | `'Connecting'` | — | `CastButtonCore` |
-| `seek` | `'Seek'` | — | `TimeSliderCore` (aria-label) |
-| `volume` | `'Volume'` | — | `VolumeSliderCore` (aria-label) |
-| `timeCurrent` | `'Current time'` | — | `TimeCore` |
-| `timeDuration` | `'Duration'` | — | `TimeCore` |
-| `timeRemaining` | `'Remaining'` | — | `TimeCore` |
-| `remainingTimeSuffix` | `'remaining'` | — | `formatDuration` (negative time suffix) |
-| `playbackRateAria` | `'Playback rate {rate}'` | `{rate}` | `PlaybackRateButtonCore`, playback-rate menu |
-| `timeSliderValueTextRange` | `'{current} of {duration}'` | `{current}`, `{duration}` | `TimeSliderCore` (aria-valuetext) |
-| `volumeSliderValueTextMuted` | `'{percent}, muted'` | `{percent}` | `VolumeSliderCore` (aria-valuetext when muted) |
-| `indicatorMuted` | `'Muted'` | — | Input feedback (status / announcer) |
-| `indicatorVolume` | `'Volume'` | — | Input feedback (status indicator label) |
-| `indicatorVolumeWithValue` | `'Volume {value}'` | `{value}` | Input feedback (status announcer) |
-| `indicatorCaptionsOn` | `'Captions on'` | — | Input feedback |
-| `indicatorCaptionsOff` | `'Captions off'` | — | Input feedback |
-| `indicatorPaused` | `'Paused'` | — | Input feedback |
-| `indicatorPlaying` | `'Playing'` | — | Input feedback |
-| `indicatorFullscreen` | `'Enter fullscreen'` | — | Input feedback |
-| `indicatorExitFullscreen` | `'Exit fullscreen'` | — | Input feedback |
-| `indicatorPictureInPicture` | `'Picture in picture'` | — | Input feedback |
-| `indicatorExitPictureInPicture` | `'Exit picture in picture'` | — | Input feedback |
-| `mediaErrorAborted` | `'You aborted the media playback'` | — | Error dialog |
-| `mediaErrorNetwork` | `'A network error caused…'` | — | Error dialog |
-| `mediaErrorDecode` | `'A media error caused playback…'` | — | Error dialog |
-| `mediaErrorSrcNotSupported` | `'An unsupported error occurred…'` | — | Error dialog |
-| `mediaErrorEncrypted` | `'The media is encrypted…'` | — | Error dialog |
-| `mediaErrorCustom` | `''` | — | Error dialog (custom errors use literal message) |
-| `errorDialogTitle` | `'Something went wrong.'` | — | Error dialog |
-| `errorDialogDismiss` | `'OK'` | — | Error dialog |
-| `mediaErrorFallback` | `'An error occurred. Please try again.'` | — | Error dialog |
+| `currentTime` | `'Current time'` | — | `TimeCore` |
+| `duration` | `'Duration'` | — | `TimeCore` |
+| `remaining` | `'Remaining'` | — | `TimeCore` |
+| `seekForward` | `'Seek forward {seconds} seconds'` | `{seconds}` | `SeekButtonCore` |
+| `seekBackward` | `'Seek backward {seconds} seconds'` | `{seconds}` | `SeekButtonCore` |
+| `playbackRate` | `'Playback rate {rate}'` | `{rate}` | `PlaybackRateButtonCore` |
+| `timePosition` | `'{current} of {duration}'` | `{current}`, `{duration}` | `TimeSliderCore` (aria-valuetext) |
+| `remaining` | `'remaining'` | — | `formatDuration` (negative time suffix) |
-> `timeSliderValueTextRange` params are already-formatted time phrases from `Intl.DurationFormat`, not raw numbers.
+> `timePosition` params are already-formatted time phrases from `Intl.DurationFormat`, not raw numbers.
-> `Intl.DurationFormat` handles duration unit labels; `Intl.NumberFormat` handles percent formatting. Only `remainingTimeSuffix` and `volumeSliderValueTextMuted` need translation keys for suffixes `Intl` cannot express.
-
-> Full key list and param contracts: `packages/core/src/core/i18n/types.ts` (`TranslationParams`).
+> `Intl.DurationFormat` handles all duration unit labels; `Intl.NumberFormat` handles percent formatting. Only `muted` and `remaining` are translation keys because `Intl` has no concept of those suffixes.
## Architecture
@@ -291,15 +263,82 @@ Keys are opaque camelCase identifiers. The English string is the value in `en.ts
### Core types
-Authoritative definitions: `packages/core/src/core/i18n/types.ts` and `built-in-locales.ts`.
+```ts
+// @videojs/core/i18n/types.ts
-- **`BuiltInLocale`** — autocomplete for shipped packs (`BUILT_IN_LOCALES`: 50 tags + `LOCALE_ALIAS_TAGS`: `pt`, `zh`).
-- **`Locale`** — `BuiltInLocale | (string & {})` for any BCP 47 tag.
-- **`TranslationParams`** — per-key param contract (`never` = no params).
-- **`Translations`** — optional overlay; parametric values must contain required `{placeholder}` substrings (`Contains<>` helper).
-- **`Translator`** — typed `(key, params?) => string` from `createTranslator`.
+export type BuiltInLocale =
+ | 'ar' | 'de' | 'es' | 'fr' | 'it' | 'ja'
+ | 'ko' | 'nl' | 'pl' | 'pt' | 'ru' | 'tr' | 'zh';
-The `Contains` helper makes locale files compile-error when they forget a `{param}` placeholder. `Translator`'s signature catches two classes of mistake at call sites:
+/** Any BCP 47 tag; named built-in locales autocomplete in editors. */
+export type Locale = BuiltInLocale | (string & {});
+
+/** Helper — string type that must contain a given literal substring. */
+type Contains = `${string}${Needle}${string}`;
+
+/** Per-key parameter contract — maps each key to the exact params it requires (or `never`). */
+export interface TranslationParams {
+ play: never;
+ pause: never;
+ replay: never;
+ mute: never;
+ unmute: never;
+ seek: never;
+ volume: never;
+ muted: never;
+ enterFullscreen: never;
+ exitFullscreen: never;
+ enableCaptions: never;
+ disableCaptions: never;
+ enterPictureInPicture: never;
+ exitPictureInPicture: never;
+ currentTime: never;
+ duration: never;
+ remaining: never;
+ seekForward: { seconds: string | number };
+ seekBackward: { seconds: string | number };
+ playbackRate: { rate: string | number };
+ timePosition: { current: string | number; duration: string | number };
+}
+
+/** All player translation keys. All keys are optional — missing keys fall back to English.
+ * Value types enforce the `{param}` placeholders required by `TranslationParams`. */
+export interface Translations {
+ play?: string;
+ pause?: string;
+ replay?: string;
+ mute?: string;
+ unmute?: string;
+ seek?: string;
+ volume?: string;
+ muted?: string;
+ enterFullscreen?: string;
+ exitFullscreen?: string;
+ enableCaptions?: string;
+ disableCaptions?: string;
+ enterPictureInPicture?: string;
+ exitPictureInPicture?: string;
+ currentTime?: string;
+ duration?: string;
+ remaining?: string;
+ seekForward?: Contains<'{seconds}'>;
+ seekBackward?: Contains<'{seconds}'>;
+ playbackRate?: Contains<'{rate}'>;
+ timePosition?: Contains<'{current}'> & Contains<'{duration}'>;
+}
+
+type SimpleKeys = { [K in keyof TranslationParams]: TranslationParams[K] extends never ? K : never }[keyof TranslationParams];
+type ParamKeys = Exclude;
+
+/** Callable translator. Keys with params require the exact params object; keys without params take no second argument. */
+export type Translator = {
+ (key: SimpleKeys): string;
+ (key: K, params: TranslationParams[K]): string;
+ readonly locale?: Locale;
+};
+```
+
+The `Contains` helper makes locale files compile-error when they forget a `{param}` placeholder. `Translator`'s overloaded signature catches two classes of mistake at call sites:
```ts
t('play'); // ✓ no params
@@ -458,28 +497,38 @@ export function createI18n() {
);
const locale = explicitLocale ?? ambientLocale;
- // Lazy-load built-in overlays, then browser fallback when no pack exists.
+ // Lazy-load built-in pack when locale is set
+ const [builtIn, setBuiltIn] = useState>({});
useEffect(() => {
- const seq = ++lazySeqRef.current;
- const locale = resolvedLocale;
- void (async () => {
- const mergedLazy = await mergeLocaleOverlays(locale, loadLocale, localeLookupChain);
- if (seq !== lazySeqRef.current) return;
- if (shouldAttemptBrowserTranslation(locale, mergedLazy)) {
- const browser = await getBrowserTranslations(locale);
- if (Object.keys(browser).length) registerI18n(locale, browser);
- }
- if (seq !== lazySeqRef.current) return;
- setLazyLayer(mergedLazy);
- })();
- }, [resolvedLocale]);
+ if (!locale) { setBuiltIn({}); return; }
+ const tags = [...new Set([locale, locale.split('-')[0]])];
+ const load = (i = 0): Promise =>
+ import(`@videojs/core/i18n/locales/${tags[i]}`)
+ .then(m => { registerI18n(tags[i], m.default); setBuiltIn(m.default); })
+ .catch(() => i + 1 < tags.length ? load(i + 1) : setBuiltIn({}));
+ load();
+ }, [locale]);
- // Priority: registry (incl. browser registerI18n) < lazy built-in < consumer translations
- const translations = useMemo(
- () => ({ ...getI18nTranslations(resolvedLocale), ...lazyLayer, ...translationsProp }),
- [resolvedLocale, lazyLayer, translationsProp, registryEpoch]
+ // Browser Translation API — background fallback, pre-installed model only.
+ // Enumerates the full key set from en.ts (no consumer input needed).
+ const [browserTranslated, setBrowserTranslated] = useState>({});
+ useEffect(() => {
+ if (!locale) { setBrowserTranslated({}); return; }
+ let cancelled = false;
+ getBrowserTranslations(locale).then(result => {
+ if (!cancelled) setBrowserTranslated(result);
+ });
+ return () => { cancelled = true; };
+ }, [locale]);
+
+ // Priority: browser API < registry/built-in < consumer translations
+ const translator = useMemo(
+ () => createTranslator(
+ { ...browserTranslated, ...getI18nTranslations(locale ?? 'en'), ...builtIn, ...translations },
+ locale
+ ),
+ [browserTranslated, builtIn, translations, locale]
);
- const translator = useMemo(() => createTranslator(translations, resolvedLocale), [translations, resolvedLocale]);
return {children};
}
@@ -598,29 +647,33 @@ const ProviderMixin = >(base: Base) =>
if (changed.has('lang')) this.#refresh();
}
- async #resetLazyAndLoad(): Promise {
- const localeSnapshot = resolveProviderLocale(this);
- this.#lazyResetStartedForLocale = localeSnapshot;
- this.#lazySeq += 1;
- const seq = this.#lazySeq;
- this.#lazyLayer = {};
- void (async () => {
- const merged = await mergeLocaleOverlays(localeSnapshot, loadLocale, localeLookupChain);
- if (seq !== this.#lazySeq) return;
- if (shouldAttemptBrowserTranslation(localeSnapshot, merged)) {
- const browser = await getBrowserTranslations(localeSnapshot);
- if (Object.keys(browser).length) registerI18n(localeSnapshot, browser);
+ async #refresh(): Promise {
+ const locale = this.#effectiveLocale;
+ if (!locale) { this.#updateProvider(undefined); return; }
+
+ if (!hasRegisteredI18n(locale)) {
+ const tags = [...new Set([locale, locale.split('-')[0]])];
+ for (const tag of tags) {
+ try {
+ const { default: data } = await import(`@videojs/core/i18n/locales/${tag}`);
+ registerI18n(tag, data);
+ break;
+ } catch { /* try next tag */ }
}
- if (seq !== this.#lazySeq) return;
- this.#lazyLayer = merged;
- this.requestUpdate();
- })();
+ }
+
+ // Browser Translation API — background, pre-installed model only
+ if (!hasRegisteredI18n(locale) && TRANSLATION_KEYS.length) {
+ getBrowserTranslations(locale, TRANSLATION_KEYS).then(result => {
+ if (Object.keys(result).length) registerI18n(locale, result);
+ });
+ }
+
+ this.#updateProvider(locale);
}
- #publish(): void {
- const locale = resolveProviderLocale(this);
- const translations = { ...getI18nTranslations(locale), ...this.#lazyLayer };
- this.#i18nProvider.setValue({ translator: createTranslator(translations, locale), locale });
+ #updateProvider(locale: Locale | undefined): void {
+ this.#provider.setValue(createTranslator(getI18nTranslations(locale ?? 'en'), locale));
}
};
```
@@ -644,24 +697,10 @@ const TextMixin = >(base: Base) =>
};
```
-### HTML: Built-in skin tooltips (trigger sync)
-
-Built-in skins use **empty** `` elements linked to controls via `commandfor`. Tooltip text is not authored in the skin template — `TooltipElement` copies the trigger button's translated label at runtime (`getLabel()` / `getResolvedLabel()` via `I18nController`), keeping tooltip copy and `aria-label` in sync.
+`` is for any dynamic translated content that isn't already wired to a component state (free-floating tooltip copy, dialog bodies, custom labels in ejected skins). Built-in button elements already auto-forward their `aria-label` from the registry via `I18nController` — no `` wrapping required for those.
```html
-
-
-
-```
-
-Changing text inside `` in a built-in skin has no effect. To customize tooltip and `aria-label`, set `label` on the control (e.g. ``) or override the component's core label key.
-
-### HTML: `` (ejected / custom skins only)
-
-`` renders translated copy for static shadow DOM strings **not** owned by a component's `getLabel()` — standalone help text, dialog bodies, or tooltips in **ejected** skins without a linked trigger. Built-in button labels and built-in tooltips do not use ``.
-
-```html
-
+
@@ -752,39 +791,36 @@ The Browser Translation API path uses the native `Intl.Locale` minimization wher
### Browser Translation API
-The [Translator API](https://developer.chrome.com/docs/ai/translator-api) (WICG draft, Chrome 138+) provides on-device text translation. Video.js uses it as a background fallback for locales with no registered or lazy-loaded pack. Implementation lives in `@videojs/core/i18n` (`getBrowserTranslations`, `shouldAttemptBrowserTranslation`).
+The [Translator API](https://developer.chrome.com/docs/ai/translator-api) (WICG draft, Chrome 138+ origin trial) provides on-device text translation. Video.js uses it as a background fallback for locales with no registered pack.
-Only activates when `Translator.availability()` returns `'available'` — model already present, no network cost. `'downloadable'` / `'downloading'` / `'unavailable'` are silently skipped in production providers.
+Only activates when `Translator.availability()` returns `'available'` — model already present, no network cost. `'downloadable'` / `'downloading'` / `'unavailable'` are silently skipped.
-**Sandbox exception.** The dev sandbox may call `getBrowserTranslations` with `downloadIfNeeded: true` for browser-only locale tags (Chrome Translator API languages without a shipped pack). This is for demo coverage only — not production behavior.
+Since keys are opaque, the browser API translates the *English values* from `en.ts`, then maps the results back to keys:
-Providers call `shouldAttemptBrowserTranslation(locale, lazyLayer)` after `mergeLocaleOverlays` — skip when locale is English, when lazy built-ins loaded, or when any non-`en` tag in `localeLookupChain(locale)` is already registered (covers `es-MX` → `es`).
+```ts
+const en = getI18nTranslations('en');
+const englishValues = keys.map(k => en[k] ?? k);
+const translated = await Promise.all(englishValues.map(v => translator.translate(v)));
+return Object.fromEntries(keys.map((k, i) => [k, translated[i]]));
+```
-Since keys are opaque, the browser API translates the *English values* from `en.ts`, then maps the results back to keys. Results are registered via `registerI18n` (triggering `onI18nRegistryChange`) and cached per target language at module level.
+Results are cached by locale at module level — repeated mounts of the same skin do not re-trigger translation.
**Priority merge:**
```
English defaults (en.ts — always present)
↑
-Browser API (registerI18n after async translate; pre-installed model only)
+Browser API (auto-translated, background, pre-installed only)
↑
-Registry pack (registerI18n / CDN locale modules)
+Registry pack (registerI18n / built-in locale)
↑
-Lazy built-in (`loadLocale` overlay from `@videojs/core/i18n`)
- ↑
-Consumer prop (React translations — always wins)
+Consumer prop (translations — always wins)
```
-### `loadLocale`
-
-`loadLocale(tag)` in `@videojs/core/i18n` lazy-imports shipped locale packs by exact BCP 47 tag. It skips tags already present in the registry (including `en`) so explicit `registerI18n` overrides are preserved. Default `createI18n()` providers call it via `mergeLocaleOverlays` when the locale changes — `` or `` loads Spanish without a prior `registerI18n` call. Override with `createI18n({ loadLocale })` in tests or custom apps.
-
-Explicit `registerI18n`, CDN locale modules, and React `translations` still merge on top and remain the preferred path for SSR (zero flash) and CDN.
-
### Intl API integration
-**`Intl.DurationFormat`** — drives `formatDuration`. Handles unit labels, pluralization, and locale-specific ordering automatically (baseline: Chrome 122+, Firefox 127+, Safari 18+). The remaining-time suffix uses `remainingTimeSuffix` via `formatOptions.translate` since `Intl.DurationFormat` has no concept of remaining time.
+**`Intl.DurationFormat`** — drives `formatDuration`. Handles unit labels, pluralization, and locale-specific ordering automatically (baseline: Chrome 122+, Firefox 127+, Safari 18+). The `remaining` suffix still uses `translate?.('remaining')` since `Intl.DurationFormat` has no concept of remaining time.
**`Intl.NumberFormat`** with `style: 'percent'` — formats volume values. No translation key needed.
@@ -852,7 +888,7 @@ registerI18n('es', { play: 'Reproducir', pause: 'Pausa', /* … */ });
- *Side-effect locale imports* — `import '@videojs/core/i18n/locales/es'` auto-registers. Media Chrome uses this pattern (`import 'media-chrome/lang/es.js'`). Simple for CDN, but bundlers may include unwanted locales, and there's no explicit registration call for overrides.
- *Lazy-load only inside provider* — each provider independently loads its pack. Multiple players for the same locale each trigger a separate network request.
-**Rationale.** Module-level singleton means each locale is loaded once per page regardless of how many players are mounted. For CDN, locale files are self-registering ES modules — they import `registerI18n` from the same module URL, sharing the registry instance with no global namespace touch. For bundlers, consumers may call `registerI18n` explicitly or rely on default provider `loadLocale` lazy imports. Pass React `translations` for SSR zero-flash.
+**Rationale.** Module-level singleton means each locale is loaded once per page regardless of how many players are mounted. For CDN, locale files are self-registering ES modules — they import `registerI18n` from the same module URL, sharing the registry instance with no global namespace touch. For bundlers, consumers call `registerI18n` explicitly after importing the JSON. `` can still lazy-load built-in packs as a convenience fallback (triggered only when the locale is not already registered).
### Standalone providers — skins are i18n-unaware
@@ -888,27 +924,16 @@ registerI18n('es', { play: 'Reproducir', pause: 'Pausa', /* … */ });
**Rationale.** The factory pattern allows consumers to create independent provider trees if needed (e.g., two players with different languages and fully isolated contexts). It also keeps the provider and hooks as a cohesive unit — impossible to import `useTranslator` without the matching context.
-### Built-in tooltips: trigger sync, not ``
+### `` for template strings
-**Decision.** Shipped video/audio/live skins use empty `` + `commandfor`; `TooltipElement` syncs translated text from the linked control's label.
-
-**Alternatives considered.**
-
-- *`` inside each tooltip* — duplicates the control's translation key; two sources of truth for the same string.
-- *`` self-translating* — couples tooltip semantics to i18n; translation key becomes a tooltip API concern.
-- *Hardcoded English in skin templates* — original v9 pattern; breaks locale switching.
-
-**Rationale.** Control cores already expose opaque label keys; `MediaButtonElement` and `TooltipElement` both resolve through `I18nController`. One key → one translated string for `aria-label` and tooltip.
-
-### `` for ejected skin template strings
-
-**Decision.** A `` element renders translated text inside shadow DOM templates where no component owns the phrase.
+**Decision.** A `` element renders translated text inside shadow DOM templates.
**Alternatives considered.**
+- *`` self-translating* — tooltip knows its translation key. Couples tooltip semantics to i18n; translation key becomes a tooltip API concern.
- *Skin re-renders full template on locale change* — not viable; shadow DOM templates are static HTML cloned once on element creation.
-**Rationale.** `` is the minimal reactive primitive for ad-hoc translated copy in custom/ejected skins. It subscribes to `i18nContext` independently and updates only `textContent` — no parent re-render needed.
+**Rationale.** `` is the minimal reactive primitive for translated text inside static shadow DOM. It subscribes to `i18nContext` independently and updates only `textContent` — no parent re-render needed. The `key` attribute is the only i18n contract in ejected skin templates, making them easy to audit and override.
### `{param}` interpolation, not ICU message format
@@ -920,7 +945,7 @@ registerI18n('es', { play: 'Reproducir', pause: 'Pausa', /* … */ });
**Decision.** `Intl.DurationFormat` for time phrases, `Intl.NumberFormat` with `style: 'percent'` for volume, and `Intl.PluralRules` via `pluralize` for plural selection.
-**Rationale.** `Intl.DurationFormat` became baseline in 2024 and handles unit labels, pluralization, and locale-specific ordering — zero translation keys. `Intl.NumberFormat` handles percent symbols and digit forms. Only `remainingTimeSuffix` and `volumeSliderValueTextMuted` need translation keys because `Intl` has no concept of those suffixes.
+**Rationale.** `Intl.DurationFormat` became baseline in 2024 and handles unit labels, pluralization, and locale-specific ordering — zero translation keys. `Intl.NumberFormat` handles percent symbols and digit forms. Only `remaining` and `muted` need translation keys because `Intl` has no concept of those suffixes.
### Browser Translation API — pre-installed model only
@@ -954,7 +979,7 @@ VJS v8's `addLanguage` is the closest precedent. This design replaces English-as
**`en.ts` bundle footprint.** `en.ts` is imported as a module-level side effect of `@videojs/core/i18n`. Consumers who never use i18n should not import this module — they will not pay the cost if they use only `@videojs/core/ui` or similar.
-**When to use ``.** Prefer control `label` + trigger-synced tooltips for anything tied to a button. Use `` only for standalone copy in ejected skins (help text, custom dialog bodies) where no component exposes `getLabel()`.
+**Tooltip keys in ejected skins.** `` inside `` is unusual markup compared to hardcoded text. This is an acceptable tradeoff — the `key` attribute makes the translation contract explicit and the element is thin enough to not be confusing.
## Descoped
@@ -966,33 +991,32 @@ VJS v8's `addLanguage` is the closest precedent. This design replaces English-as
- **Translator perf optimizations** — `createTranslator` re-parses `{param}` templates on every call via `String.prototype.replace`. Future work, not required for v1:
- *Run-time* — parse each template once on first use, cache tokens keyed by source string, reuse thereafter (similar to i18next's format cache).
- *Build-time* — AOT-compile locale entries into functions (`seekForward: ({ seconds }) => \`Seek forward ${seconds} seconds\``); inline static keys with dead-code elimination for the active locale (similar to`babel-plugin-formatjs` / `lingui`).
-- **Custom translator implementations** — a consumer-supplied `translate(key) → string` override (e.g. to delegate to `i18next` or FormatJS). Deferred follow-up; use `registerI18n` or React `translations` prop today.
+- **Custom translator implementations** — swapping `createTranslator` for a consumer-supplied translator (e.g., to delegate to `i18next` or FormatJS). The current `Translator` type is a callable, so this is feasible, but no public extension point is wired up.
## File Structure
```
packages/
-├── core/src/core/i18n/
+├── core/src/i18n/
│ ├── types.ts ← BuiltInLocale, Locale, Translations, Translator
│ ├── translator.ts ← createTranslator
-│ ├── browser-translation.ts ← getBrowserTranslations, shouldAttemptBrowserTranslation
-│ ├── load-locale.ts ← loadLocale (codegen lazy import map)
│ ├── registry.ts ← registerI18n, getI18nTranslations, onI18nRegistryChange
│ ├── index.ts ← re-exports
│ └── locales/
│ ├── en.ts ← English defaults (pre-registered at module init)
-│ ├── ar.ts … zh.ts ← built-in locale packs (default export each)
-│ ├── all.ts ← aggregated map `{ all, localeTags }` (generated; loads every pack)
+│ ├── ar.ts … zh.ts ← built-in locale packs
│ └── *.cdn.ts ← CDN self-registering entry points (one per locale)
│
├── react/src/i18n/
│ ├── create-i18n.tsx ← createI18n
-│ ├── locales/ ← generated re-exports of core/locales/*.ts
+│ ├── browser-translation.ts
+│ ├── locales/ ← re-exports of core/locales/*.ts
│ └── index.ts ← public entry: registerI18n, I18nProvider, useTranslator, useLocale, Translations, Translator, Locale
│
└── html/src/i18n/
├── create-i18n.ts ← createI18n → { context, I18nController, ProviderMixin, TextMixin }
- ├── locales/ ← generated re-exports of core/locales/*.ts
+ ├── browser-translation.ts
+ ├── locales/ ← re-exports of core/locales/*.ts
├── define/
│ ├── media-i18n-provider.ts ← ProviderMixin(ReactiveElement) + customElements.define
│ └── media-text.ts ← TextMixin(ReactiveElement) + customElements.define
@@ -1005,8 +1029,6 @@ packages/utils/src/time/
└── format.ts ← formatDuration, TimeFormatOptions, TimeTranslate
```
-**Adding a locale:** author `packages/core/src/core/i18n/locales/{tag}.ts`, append the tag to `built-in-locales.ts`, then run `pnpm -F @videojs/core generate:locales` (also runs on `@videojs/core` prebuild). That regenerates `locales/all.ts` and html/react re-export stubs.
-
### Modified files
| File | Change |
@@ -1015,6 +1037,5 @@ packages/utils/src/time/
| `packages/core/src/core/ui/playback-rate-button/playback-rate-button-core.ts` | Same |
| `packages/utils/src/time/format.ts` | Add `TimeTranslate`, `TimeFormatOptions`, optional `translate` param |
| `packages/html/src/ui/media-button-element.ts` | Add `I18nController`; apply `t()` in `update()` |
-| `packages/html/src/ui/tooltip/tooltip-element.ts` | Sync tooltip text from trigger `getResolvedLabel()` |
-| `packages/core/src/core/i18n/browser-translation.ts` | Browser Translation API fallback + `shouldAttemptBrowserTranslation` |
+| `packages/html/src/define/video/skin.ts` | Replace hardcoded tooltip strings with `` children |
| `packages/react/src/i18n/index.ts` | Export `{ I18nProvider, useTranslator, useLocale }` from `createI18n()` |
diff --git a/packages/core/package.json b/packages/core/package.json
index 9f272a76..9b2e4f43 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -15,9 +15,7 @@
"player",
"videojs"
],
- "sideEffects": [
- "**/i18n/registry.js"
- ],
+ "sideEffects": false,
"exports": {
".": {
"types": "./dist/dev/index.d.ts",
@@ -38,16 +36,6 @@
"types": "./dist/dev/media/predicate.d.ts",
"development": "./dist/dev/media/predicate.js",
"default": "./dist/default/media/predicate.js"
- },
- "./i18n": {
- "types": "./dist/dev/i18n.d.ts",
- "development": "./dist/dev/i18n.js",
- "default": "./dist/default/i18n.js"
- },
- "./i18n/locales/*": {
- "types": "./dist/dev/i18n/locales/*.d.ts",
- "development": "./dist/dev/i18n/locales/*.js",
- "default": "./dist/default/i18n/locales/*.js"
}
},
"main": "dist/default/index.js",
@@ -57,8 +45,6 @@
"dist"
],
"scripts": {
- "generate:locales": "node --import tsx ./scripts/generate-i18n-locales.ts",
- "prebuild": "pnpm run generate:locales",
"build": "tsdown",
"build:watch": "tsdown --watch ./src --no-clean",
"dev": "pnpm run build:watch",
diff --git a/packages/core/scripts/generate-i18n-locales.ts b/packages/core/scripts/generate-i18n-locales.ts
deleted file mode 100644
index a9095513..00000000
--- a/packages/core/scripts/generate-i18n-locales.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { readdirSync, unlinkSync, writeFileSync } from 'node:fs';
-import { dirname, resolve } from 'node:path';
-import { fileURLToPath } from 'node:url';
-
-import { BUILT_IN_LOCALES, LOCALE_ALIAS_TAGS, SHIPPED_LOCALE_TAGS } from '../src/core/i18n/built-in-locales.ts';
-
-const GENERATED_HEADER = '/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */\n';
-
-const coreRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
-const coreLocalesDir = resolve(coreRoot, 'src/core/i18n/locales');
-const htmlLocalesDir = resolve(coreRoot, '../html/src/i18n/locales');
-const reactLocalesDir = resolve(coreRoot, '../react/src/i18n/locales');
-
-const PLATFORM_LOCALE_TAGS = ['en', ...SHIPPED_LOCALE_TAGS] as const;
-
-function importBinding(tag: string): string {
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(tag) ? tag : tag.replace(/-/g, '_');
-}
-
-function objectEntry(tag: string): string {
- const binding = importBinding(tag);
- if (tag === binding) {
- return ` ${tag},`;
- }
- return ` '${tag}': ${binding},`;
-}
-
-function generateLoadLocaleTs(): string {
- const tags = [...BUILT_IN_LOCALES, ...LOCALE_ALIAS_TAGS];
- const entries = tags.map((tag) => ` '${tag}': () => import('./locales/${tag}'),`).join('\n');
- const normalizedEntries = tags
- .map((tag) => ` '${tag.trim().replaceAll('_', '-').toLowerCase()}': '${tag}',`)
- .join('\n');
-
- return `${GENERATED_HEADER}import { canonicalLocaleRegistryKey, hasRegisteredI18n, localeLookupChain } from './registry';
-import type { Translations } from './types';
-
-const loaders = {
-${entries}
-} as const satisfies Record Promise<{ default: Partial }>>;
-
-const loaderTagByNormalized = {
-${normalizedEntries}
-} as const satisfies Record;
-
-/** Lazy-import a shipped locale pack when the tag is not already in the registry. */
-export async function loadLocale(tag: string): Promise | undefined> {
- if (hasRegisteredI18n(tag)) return undefined;
- for (const chainTag of localeLookupChain(tag)) {
- if (hasRegisteredI18n(chainTag)) return undefined;
- const loaderTag =
- loaderTagByNormalized[canonicalLocaleRegistryKey(chainTag) as keyof typeof loaderTagByNormalized];
- const load = loaderTag ? loaders[loaderTag] : undefined;
- if (load) return (await load()).default;
- }
- return undefined;
-}
-`;
-}
-
-function generateCoreAllTs(): string {
- const importTags = ['en', ...BUILT_IN_LOCALES, ...LOCALE_ALIAS_TAGS];
- const imports = importTags.map((tag) => `import ${importBinding(tag)} from './${tag}';`).join('\n');
-
- const objectLines = [' en,', ...BUILT_IN_LOCALES.map(objectEntry), ...LOCALE_ALIAS_TAGS.map(objectEntry)].join('\n');
-
- return `${GENERATED_HEADER}import type { Translations } from '../types';
-${imports}
-
-/** Every built-in locale pack keyed by BCP 47 tag (includes \`en\` and shorthand aliases \`pt\` / \`zh\`). */
-export const all = {
-${objectLines}
-} as const satisfies Record>;
-
-export type LocaleTag = keyof typeof all;
-
-/** BCP 47 tags for every pack in {@link all}. */
-export const localeTags = Object.keys(all) as LocaleTag[];
-`;
-}
-
-function generatePlatformDefaultReExport(tag: string): string {
- return `${GENERATED_HEADER}export { default } from '@videojs/core/i18n/locales/${tag}';
-`;
-}
-
-function generatePlatformAllReExport(): string {
- return `${GENERATED_HEADER}export { all, type LocaleTag, localeTags } from '@videojs/core/i18n/locales/all';
-`;
-}
-
-function writeGenerated(path: string, content: string): void {
- writeFileSync(path, content.endsWith('\n') ? content : `${content}\n`);
-}
-
-function syncPlatformLocaleDir(dir: string): void {
- const expected = new Set([...PLATFORM_LOCALE_TAGS, 'all'].map((tag) => `${tag}.ts`));
-
- for (const tag of PLATFORM_LOCALE_TAGS) {
- writeGenerated(resolve(dir, `${tag}.ts`), generatePlatformDefaultReExport(tag));
- }
-
- writeGenerated(resolve(dir, 'all.ts'), generatePlatformAllReExport());
-
- for (const file of readdirSync(dir)) {
- if (!file.endsWith('.ts') || expected.has(file)) {
- continue;
- }
- unlinkSync(resolve(dir, file));
- }
-}
-
-writeGenerated(resolve(coreLocalesDir, 'all.ts'), generateCoreAllTs());
-writeGenerated(resolve(coreRoot, 'src/core/i18n/load-locale.ts'), generateLoadLocaleTs());
-syncPlatformLocaleDir(htmlLocalesDir);
-syncPlatformLocaleDir(reactLocalesDir);
-
-console.log('[generate-i18n-locales] Updated core all.ts, load-locale.ts, and html/react locale re-exports');
diff --git a/packages/core/src/core/i18n/browser-translation.ts b/packages/core/src/core/i18n/browser-translation.ts
deleted file mode 100644
index 06011888..00000000
--- a/packages/core/src/core/i18n/browser-translation.ts
+++ /dev/null
@@ -1,168 +0,0 @@
-import en from './locales/en';
-import { getI18nTranslations, hasRegisteredI18n, localeLookupChain } from './registry';
-import type { Locale, Translations } from './types';
-
-type BrowserTranslatorAvailability = 'available' | 'downloadable' | 'downloading' | 'unavailable';
-
-interface BrowserTranslatorInstance {
- translate(text: string): Promise;
-}
-
-interface BrowserTranslatorMonitor {
- addEventListener(type: 'downloadprogress', listener: (event: { loaded: number }) => void): void;
-}
-
-interface BrowserTranslatorConstructor {
- availability(options: { sourceLanguage: string; targetLanguage: string }): Promise;
- create(options: {
- sourceLanguage: string;
- targetLanguage: string;
- monitor?: (monitor: BrowserTranslatorMonitor) => void;
- }): Promise;
-}
-
-export interface GetBrowserTranslationsOptions {
- /**
- * When true, call `Translator.create()` for `downloadable` / `downloading` (may download the
- * on-device model). Defaults to false — production providers only use pre-installed models.
- */
- downloadIfNeeded?: boolean;
- /** Invoked when a model download starts and when `Translator.create()` resolves. */
- onModelDownload?: {
- start?: (targetLanguage: string) => void;
- finish?: (targetLanguage: string) => void;
- };
-}
-
-const NAMED_PLACEHOLDER = /\{([^{}]+)\}/g;
-const INDEX_PLACEHOLDER = /\{\s*(\d+)\s*\}/g;
-
-/**
- * Replaces `{seconds}` with `{0}`, `{1}`, … so the Browser Translation API sees one full
- * sentence (grammar/word order preserved) while opaque numeric slots are left alone.
- */
-function maskNamedPlaceholders(source: string): { masked: string; slots: readonly string[] } {
- const slots: string[] = [];
- const masked = source.replace(NAMED_PLACEHOLDER, (_, name: string) => {
- slots.push(name);
- return `{${slots.length - 1}}`;
- });
- return { masked, slots };
-}
-
-function restoreNamedPlaceholders(translated: string, slots: readonly string[]): string {
- return translated.replace(INDEX_PLACEHOLDER, (match, index: string) => {
- const name = slots[Number(index)];
- return name !== undefined ? `{${name}}` : match;
- });
-}
-
-async function translateProtectingPlaceholders(translator: BrowserTranslatorInstance, value: string): Promise {
- const { masked, slots } = maskNamedPlaceholders(value);
- if (slots.length === 0) {
- return translator.translate(value);
- }
- const translated = await translator.translate(masked);
- return restoreNamedPlaceholders(translated, slots);
-}
-
-const cache = new Map>();
-
-function isEnglishLocaleTag(tag: string): boolean {
- return tag === 'en' || tag.startsWith('en-');
-}
-
-function getBrowserTranslator(): BrowserTranslatorConstructor | undefined {
- if (!('Translator' in globalThis)) return undefined;
- return (globalThis as typeof globalThis & { Translator: BrowserTranslatorConstructor }).Translator;
-}
-
-/** First non-English tag in the lookup chain used as the browser translation target. */
-export function resolveBrowserTranslationTarget(locale: string): string | undefined {
- for (const tag of localeLookupChain(locale)) {
- if (!isEnglishLocaleTag(tag)) return tag;
- }
- return undefined;
-}
-
-/** Whether to invoke the Browser Translation API for this locale after lazy built-in loading. */
-export function shouldAttemptBrowserTranslation(locale: Locale, loadedLazyTags: readonly string[]): boolean {
- const target = resolveBrowserTranslationTarget(locale);
- if (!target) return false;
- if (loadedLazyTags.some((tag) => !isEnglishLocaleTag(tag))) return false;
-
- return !localeLookupChain(locale).some((tag) => !isEnglishLocaleTag(tag) && hasRegisteredI18n(tag));
-}
-
-/**
- * Translates English registry values via the on-device Browser Translation API when a pre-installed
- * model is available. Results are cached per target language tag.
- */
-export async function getBrowserTranslations(
- locale: string,
- options?: GetBrowserTranslationsOptions
-): Promise> {
- const target = resolveBrowserTranslationTarget(locale);
- if (!target) return {};
-
- const cached = cache.get(target);
- if (cached) return cached;
-
- const Translator = getBrowserTranslator();
- if (!Translator) return {};
-
- const downloadIfNeeded = options?.downloadIfNeeded ?? false;
-
- const availability = await Translator.availability({
- sourceLanguage: 'en',
- targetLanguage: target,
- });
- if (availability === 'unavailable') return {};
- if (!downloadIfNeeded && availability !== 'available') return {};
-
- const needsDownload = downloadIfNeeded && (availability === 'downloadable' || availability === 'downloading');
- let downloadStarted = false;
- const notifyDownloadStart = (): void => {
- if (!needsDownload || downloadStarted) return;
- downloadStarted = true;
- options?.onModelDownload?.start?.(target);
- };
-
- notifyDownloadStart();
-
- const english = getI18nTranslations('en');
- const keys = Object.keys(en) as (keyof Translations)[];
- const translator = await Translator.create({
- sourceLanguage: 'en',
- targetLanguage: target,
- ...(downloadIfNeeded
- ? {
- monitor(monitor: BrowserTranslatorMonitor) {
- monitor.addEventListener('downloadprogress', notifyDownloadStart);
- },
- }
- : {}),
- });
-
- if (downloadStarted) {
- options?.onModelDownload?.finish?.(target);
- }
-
- const entries = await Promise.all(
- keys.map(async (key) => {
- const value = english[key] ?? en[key];
- if (!value) return [key, ''] as const;
- const translated = await translateProtectingPlaceholders(translator, value);
- return [key, translated] as const;
- })
- );
-
- const result = Object.fromEntries(entries) as Partial;
- cache.set(target, result);
- return result;
-}
-
-/** Clears the browser translation cache (test isolation). */
-export function resetBrowserTranslationCacheForTesting(): void {
- cache.clear();
-}
diff --git a/packages/core/src/core/i18n/built-in-locales.ts b/packages/core/src/core/i18n/built-in-locales.ts
deleted file mode 100644
index 4adac1ac..00000000
--- a/packages/core/src/core/i18n/built-in-locales.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/** All non-English locale packs shipped with Video.js (parity with video.js v8 `lang/`). */
-export const BUILT_IN_LOCALES = [
- 'ar',
- 'az',
- 'bs',
- 'bg',
- 'bn',
- 'ca',
- 'cs',
- 'cy',
- 'da',
- 'de',
- 'el',
- 'es',
- 'et',
- 'eu',
- 'fa',
- 'fi',
- 'fr',
- 'gd',
- 'gl',
- 'he',
- 'hi',
- 'hr',
- 'hu',
- 'it',
- 'ja',
- 'ko',
- 'lv',
- 'mr',
- 'nb',
- 'nl',
- 'nn',
- 'ne',
- 'oc',
- 'pl',
- 'pt-BR',
- 'pt-PT',
- 'ro',
- 'ru',
- 'sk',
- 'sl',
- 'sr',
- 'sv',
- 'te',
- 'th',
- 'tr',
- 'uk',
- 'vi',
- 'zh-CN',
- 'zh-TW',
-] as const;
-
-/** Shorthand tags (`pt`, `zh`) that resolve to regional packs via the i18n lookup chain. */
-export const LOCALE_ALIAS_TAGS = ['pt', 'zh'] as const;
-
-/** Non-English tags with shipped translation packs (built-ins + aliases). */
-export const SHIPPED_LOCALE_TAGS = [...BUILT_IN_LOCALES, ...LOCALE_ALIAS_TAGS] as const;
diff --git a/packages/core/src/core/i18n/index.ts b/packages/core/src/core/i18n/index.ts
deleted file mode 100644
index 233df16d..00000000
--- a/packages/core/src/core/i18n/index.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-export type { GetBrowserTranslationsOptions } from './browser-translation';
-export {
- getBrowserTranslations,
- resetBrowserTranslationCacheForTesting,
- resolveBrowserTranslationTarget,
- shouldAttemptBrowserTranslation,
-} from './browser-translation';
-export { BUILT_IN_LOCALES, LOCALE_ALIAS_TAGS, SHIPPED_LOCALE_TAGS } from './built-in-locales';
-export { loadLocale } from './load-locale';
-export { default as translations } from './locales/en';
-export {
- canonicalLocaleRegistryKey,
- getI18nTranslations,
- hasRegisteredI18n,
- localeLookupChain,
- onI18nRegistryChange,
- registerI18n,
- resetI18nRegistryForTesting,
-} from './registry';
-export { resolveTranslationPhrase } from './resolve-translation-phrase';
-export { createTranslator } from './translator';
-export type * from './types';
diff --git a/packages/core/src/core/i18n/load-locale.ts b/packages/core/src/core/i18n/load-locale.ts
deleted file mode 100644
index 92613b35..00000000
--- a/packages/core/src/core/i18n/load-locale.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-import { canonicalLocaleRegistryKey, hasRegisteredI18n, localeLookupChain } from './registry';
-import type { Translations } from './types';
-
-const loaders = {
- ar: () => import('./locales/ar'),
- az: () => import('./locales/az'),
- bs: () => import('./locales/bs'),
- bg: () => import('./locales/bg'),
- bn: () => import('./locales/bn'),
- ca: () => import('./locales/ca'),
- cs: () => import('./locales/cs'),
- cy: () => import('./locales/cy'),
- da: () => import('./locales/da'),
- de: () => import('./locales/de'),
- el: () => import('./locales/el'),
- es: () => import('./locales/es'),
- et: () => import('./locales/et'),
- eu: () => import('./locales/eu'),
- fa: () => import('./locales/fa'),
- fi: () => import('./locales/fi'),
- fr: () => import('./locales/fr'),
- gd: () => import('./locales/gd'),
- gl: () => import('./locales/gl'),
- he: () => import('./locales/he'),
- hi: () => import('./locales/hi'),
- hr: () => import('./locales/hr'),
- hu: () => import('./locales/hu'),
- it: () => import('./locales/it'),
- ja: () => import('./locales/ja'),
- ko: () => import('./locales/ko'),
- lv: () => import('./locales/lv'),
- mr: () => import('./locales/mr'),
- nb: () => import('./locales/nb'),
- nl: () => import('./locales/nl'),
- nn: () => import('./locales/nn'),
- ne: () => import('./locales/ne'),
- oc: () => import('./locales/oc'),
- pl: () => import('./locales/pl'),
- 'pt-BR': () => import('./locales/pt-BR'),
- 'pt-PT': () => import('./locales/pt-PT'),
- ro: () => import('./locales/ro'),
- ru: () => import('./locales/ru'),
- sk: () => import('./locales/sk'),
- sl: () => import('./locales/sl'),
- sr: () => import('./locales/sr'),
- sv: () => import('./locales/sv'),
- te: () => import('./locales/te'),
- th: () => import('./locales/th'),
- tr: () => import('./locales/tr'),
- uk: () => import('./locales/uk'),
- vi: () => import('./locales/vi'),
- 'zh-CN': () => import('./locales/zh-CN'),
- 'zh-TW': () => import('./locales/zh-TW'),
- pt: () => import('./locales/pt'),
- zh: () => import('./locales/zh'),
-} as const satisfies Record Promise<{ default: Partial }>>;
-
-const loaderTagByNormalized = {
- ar: 'ar',
- az: 'az',
- bs: 'bs',
- bg: 'bg',
- bn: 'bn',
- ca: 'ca',
- cs: 'cs',
- cy: 'cy',
- da: 'da',
- de: 'de',
- el: 'el',
- es: 'es',
- et: 'et',
- eu: 'eu',
- fa: 'fa',
- fi: 'fi',
- fr: 'fr',
- gd: 'gd',
- gl: 'gl',
- he: 'he',
- hi: 'hi',
- hr: 'hr',
- hu: 'hu',
- it: 'it',
- ja: 'ja',
- ko: 'ko',
- lv: 'lv',
- mr: 'mr',
- nb: 'nb',
- nl: 'nl',
- nn: 'nn',
- ne: 'ne',
- oc: 'oc',
- pl: 'pl',
- 'pt-br': 'pt-BR',
- 'pt-pt': 'pt-PT',
- ro: 'ro',
- ru: 'ru',
- sk: 'sk',
- sl: 'sl',
- sr: 'sr',
- sv: 'sv',
- te: 'te',
- th: 'th',
- tr: 'tr',
- uk: 'uk',
- vi: 'vi',
- 'zh-cn': 'zh-CN',
- 'zh-tw': 'zh-TW',
- pt: 'pt',
- zh: 'zh',
-} as const satisfies Record;
-
-/** Lazy-import a shipped locale pack when the tag is not already in the registry. */
-export async function loadLocale(tag: string): Promise | undefined> {
- if (hasRegisteredI18n(tag)) return undefined;
- for (const chainTag of localeLookupChain(tag)) {
- if (hasRegisteredI18n(chainTag)) return undefined;
- const loaderTag = loaderTagByNormalized[canonicalLocaleRegistryKey(chainTag) as keyof typeof loaderTagByNormalized];
- const load = loaderTag ? loaders[loaderTag] : undefined;
- if (load) return (await load()).default;
- }
- return undefined;
-}
diff --git a/packages/core/src/core/i18n/locales/all.ts b/packages/core/src/core/i18n/locales/all.ts
deleted file mode 100644
index 040f7b53..00000000
--- a/packages/core/src/core/i18n/locales/all.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-import type { Translations } from '../types';
-import ar from './ar';
-import az from './az';
-import bg from './bg';
-import bn from './bn';
-import bs from './bs';
-import ca from './ca';
-import cs from './cs';
-import cy from './cy';
-import da from './da';
-import de from './de';
-import el from './el';
-import en from './en';
-import es from './es';
-import et from './et';
-import eu from './eu';
-import fa from './fa';
-import fi from './fi';
-import fr from './fr';
-import gd from './gd';
-import gl from './gl';
-import he from './he';
-import hi from './hi';
-import hr from './hr';
-import hu from './hu';
-import it from './it';
-import ja from './ja';
-import ko from './ko';
-import lv from './lv';
-import mr from './mr';
-import nb from './nb';
-import ne from './ne';
-import nl from './nl';
-import nn from './nn';
-import oc from './oc';
-import pl from './pl';
-import pt from './pt';
-import pt_BR from './pt-BR';
-import pt_PT from './pt-PT';
-import ro from './ro';
-import ru from './ru';
-import sk from './sk';
-import sl from './sl';
-import sr from './sr';
-import sv from './sv';
-import te from './te';
-import th from './th';
-import tr from './tr';
-import uk from './uk';
-import vi from './vi';
-import zh from './zh';
-import zh_CN from './zh-CN';
-import zh_TW from './zh-TW';
-
-/** Every built-in locale pack keyed by BCP 47 tag (includes `en` and shorthand aliases `pt` / `zh`). */
-export const all = {
- en,
- ar,
- az,
- bs,
- bg,
- bn,
- ca,
- cs,
- cy,
- da,
- de,
- el,
- es,
- et,
- eu,
- fa,
- fi,
- fr,
- gd,
- gl,
- he,
- hi,
- hr,
- hu,
- it,
- ja,
- ko,
- lv,
- mr,
- nb,
- nl,
- nn,
- ne,
- oc,
- pl,
- 'pt-BR': pt_BR,
- 'pt-PT': pt_PT,
- ro,
- ru,
- sk,
- sl,
- sr,
- sv,
- te,
- th,
- tr,
- uk,
- vi,
- 'zh-CN': zh_CN,
- 'zh-TW': zh_TW,
- pt,
- zh,
-} as const satisfies Record>;
-
-export type LocaleTag = keyof typeof all;
-
-/** BCP 47 tags for every pack in {@link all}. */
-export const localeTags = Object.keys(all) as LocaleTag[];
diff --git a/packages/core/src/core/i18n/locales/ar.ts b/packages/core/src/core/i18n/locales/ar.ts
deleted file mode 100644
index 81552d4f..00000000
--- a/packages/core/src/core/i18n/locales/ar.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'تشغيل',
- pause: 'إيقاف',
- replay: 'إعادة التشغيل',
- mute: 'كتم',
- unmute: 'إلغاء الكتم',
- seekForward: 'التخطي للأمام {seconds}',
- seekBackward: 'الرجوع للخلف {seconds}',
- enterFullscreen: 'ملء الشاشة',
- exitFullscreen: 'الخروج من وضع ملء الشاشة',
- enableCaptions: 'تفعيل التسميات التوضيحية',
- disableCaptions: 'إيقاف التسميات التوضيحية',
- enterPictureInPicture: 'صورة داخل صورة',
- exitPictureInPicture: 'الخروج من وضع صورة داخل صورة',
- playingLive: 'بث مباشر',
- seekToLiveEdge: 'الانتقال إلى البث المباشر',
- liveBadge: 'مباشر',
- startCasting: 'بدء الإرسال',
- stopCasting: 'إيقاف الإرسال',
- connectingCast: 'جارٍ الاتصال',
- seek: 'تقديم',
- volume: 'مستوى الصوت',
- timeCurrent: 'الوقت الحالي',
- timeDuration: 'المدة',
- timeRemaining: 'الوقت المتبقي',
- timeRemainingPhrase: 'متبقٍ {duration}',
- playbackRateAria: 'سرعة التشغيل {rate}',
- timeSliderValueTextRange: '{current} من {duration}',
- volumeSliderValueTextMuted: '{percent}، مكتوم',
- indicatorMuted: 'صامت',
- indicatorVolume: 'مستوى الصوت',
- indicatorVolumeWithValue: 'مستوى الصوت {value}',
- indicatorCaptionsOn: 'الترجمة مفعّلة',
- indicatorCaptionsOff: 'الترجمة متوقفة',
- indicatorPaused: 'متوقف مؤقتاً',
- indicatorPlaying: 'قيد التشغيل',
- indicatorFullscreen: 'ملء الشاشة',
- indicatorExitFullscreen: 'الخروج من ملء الشاشة',
- indicatorPictureInPicture: 'صورة داخل صورة',
- indicatorExitPictureInPicture: 'الخروج من صورة داخل صورة',
- mediaErrorAborted: 'لقد ألغيت تشغيل الفيديو',
- mediaErrorNetwork: 'تسبب خطأ في الشبكة بفشل تحميل الفيديو بالكامل.',
- mediaErrorDecode:
- 'تم إيقاف تشغيل الفيديو بسبب عدم صلاحية الفيديو أو لأن الفيديو المستخدم يستخدم ميزات غير مدعومة من متصفحك.',
- mediaErrorSrcNotSupported:
- 'لا يمكن تحميل الفيديو بسبب فشل في الخادم أو الشبكة ، أو بسبب عدم إمكانية قراءة تنسيق الفيديو.',
- mediaErrorEncrypted: 'الوسائط مشفرة وليس لدينا الرموز اللازمة لفك شفرتها.',
- mediaErrorCustom: '',
- errorDialogTitle: 'حدث خطأ ما.',
- errorDialogDismiss: 'أغلق',
- mediaErrorFallback: 'حدث خطأ. يُرجى المحاولة مرة أخرى.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/az.ts b/packages/core/src/core/i18n/locales/az.ts
deleted file mode 100644
index cfe32fb1..00000000
--- a/packages/core/src/core/i18n/locales/az.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Oynat',
- pause: 'Pauza',
- replay: 'Yenidən oynat',
- mute: 'Səssizi qoş',
- unmute: 'Səssizi söndür',
- seekForward: '{seconds} saniyə qabağa keçin',
- seekBackward: '{seconds} saniyə geriyə keçin',
- enterFullscreen: 'Tam ekran',
- exitFullscreen: 'Tam ekrandan çıx',
- enableCaptions: 'Altyazıları aktiv et',
- disableCaptions: 'Altyazıları söndür',
- enterPictureInPicture: 'Şəkil içində şəkil rejimi',
- exitPictureInPicture: 'Şəkil içində şəkil rejimindən çıxın',
- playingLive: 'Canlı yayımda',
- seekToLiveEdge: 'Canlı yayıma keç',
- liveBadge: 'Canlı',
- startCasting: 'Yayımı başlat',
- stopCasting: 'Yayımı durdur',
- connectingCast: 'Qoşulur',
- seek: 'Sürüşdür',
- volume: 'Səs Səviyyəsi',
- timeCurrent: 'Cari Vaxt',
- timeDuration: 'Müddət',
- timeRemaining: 'Qalan vaxt',
- timeRemainingPhrase: 'Qalan {duration}',
- playbackRateAria: 'Oynatma sürəti {rate}',
- timeSliderValueTextRange: '{current} / {duration}',
- volumeSliderValueTextMuted: '{percent}, səssiz',
- indicatorMuted: 'Səssiz',
- indicatorVolume: 'Səs',
- indicatorVolumeWithValue: 'Səs {value}',
- indicatorCaptionsOn: 'Başlıqlar aktiv',
- indicatorCaptionsOff: 'Başlıqlar söndürülüb',
- indicatorPaused: 'Dayandırılıb',
- indicatorPlaying: 'Oynadılır',
- indicatorFullscreen: 'Tam ekran',
- indicatorExitFullscreen: 'Tam ekrandan çıx',
- indicatorPictureInPicture: 'Şəkil içində şəkil',
- indicatorExitPictureInPicture: 'Şəkil içində şəkildən çıxın',
- mediaErrorAborted: 'Siz medianın oxudulmasını dayandırdınız',
- mediaErrorNetwork: 'Şəbəkə xətası səbəbindən medianın endirilməsi yarıda qaldı.',
- mediaErrorDecode:
- 'Media faylının korlanması səbəbilə və ya media faylın brauzerinizin dəstəkləmədiyi funksiyalardan istifadə etdiyinə görə medianın oxudulması dayandırılıb.',
- mediaErrorSrcNotSupported: 'Yükləmə xətası.',
- mediaErrorEncrypted: 'Media faylı şifrələnib və onun şifrəsini açmaq üçün açarlar yoxdur.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Bir şey yanlış getdi.',
- errorDialogDismiss: 'Bağla',
- mediaErrorFallback: 'Xəta baş verdi. Yenidən cəhd edin.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/bg.ts b/packages/core/src/core/i18n/locales/bg.ts
deleted file mode 100644
index b5f92a89..00000000
--- a/packages/core/src/core/i18n/locales/bg.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Възпроизвеждане',
- pause: 'Пауза',
- replay: 'Повтори',
- mute: 'Без звук',
- unmute: 'Със звук',
- seekForward: 'Превъртане напред с {seconds} секунди',
- seekBackward: 'Превъртане назад с {seconds} секунди',
- enterFullscreen: 'Цял екран',
- exitFullscreen: 'Спиране на цял екран',
- enableCaptions: 'Включи субтитри',
- disableCaptions: 'Изключи субтитри',
- enterPictureInPicture: 'Картина в картина',
- exitPictureInPicture: 'Изход от картина в картина',
- playingLive: 'На живо',
- seekToLiveEdge: 'Към живото предаване',
- liveBadge: 'На живо',
- startCasting: 'Започни излъчване',
- stopCasting: 'Спри излъчването',
- connectingCast: 'Свързване',
- seek: 'Превъртане',
- volume: 'Сила на звука',
- timeCurrent: 'Текущо време',
- timeDuration: 'Продължителност',
- timeRemaining: 'Оставащо време',
- timeRemainingPhrase: 'Остават {duration}',
- playbackRateAria: 'Скорост на възпроизвеждане {rate}',
- timeSliderValueTextRange: '{current} от {duration}',
- volumeSliderValueTextMuted: '{percent}, без звук',
- indicatorMuted: 'Без звук',
- indicatorVolume: 'Сила на звука',
- indicatorVolumeWithValue: 'Сила на звука {value}',
- indicatorCaptionsOn: 'Субтитри включени',
- indicatorCaptionsOff: 'Субтитри изключени',
- indicatorPaused: 'На пауза',
- indicatorPlaying: 'Възпроизвеждане',
- indicatorFullscreen: 'Цял екран',
- indicatorExitFullscreen: 'Изход от цял екран',
- indicatorPictureInPicture: 'Картина в картина',
- indicatorExitPictureInPicture: 'Изход от картина в картина',
- mediaErrorAborted: 'Спряхте възпроизвеждането на видеото',
- mediaErrorNetwork: 'Грешка в мрежата провали изтеглянето на видеото.',
- mediaErrorDecode:
- 'Възпроизвеждането на видеото беше прекъснато заради проблем с файла или защото видеото използва опции които браузърът Ви не поддържа.',
- mediaErrorSrcNotSupported:
- 'Видеото не може да бъде заредено заради проблем със сървъра или мрежата или защото този формат не е поддържан.',
- mediaErrorEncrypted: 'Медията е шифрована и няма ключове за дешифриране.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Нещо се обърка.',
- errorDialogDismiss: 'OK',
- mediaErrorFallback: 'Възникна грешка. Моля, опитайте отново.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/bn.ts b/packages/core/src/core/i18n/locales/bn.ts
deleted file mode 100644
index e9e18f02..00000000
--- a/packages/core/src/core/i18n/locales/bn.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'প্লে করুন',
- pause: 'বিরাম',
- replay: 'রিপ্লে করুন',
- mute: 'মিউট',
- unmute: 'আনমিউট',
- seekForward: '{seconds} সেকেন্ড এগিয়ে যান',
- seekBackward: '{seconds} সেকেন্ড পিছিয়ে যান',
- enterFullscreen: 'পূর্ণ স্ক্রীন',
- exitFullscreen: 'পূর্ণ স্ক্রীন থেকে বেরিয়ে আসুন',
- enableCaptions: 'ক্যাপশন',
- disableCaptions: 'ক্যাপশন বন্ধ করুন',
- enterPictureInPicture: 'পিকচার-ইন-পিকচার',
- exitPictureInPicture: 'পিকচার-ইন-পিকচার থেকে প্রস্থান করুন',
- playingLive: 'লাইভ চলছে',
- seekToLiveEdge: 'লাইভে যান',
- liveBadge: 'লাইভ',
- startCasting: 'কাস্টিং শুরু করুন',
- stopCasting: 'কাস্টিং বন্ধ করুন',
- connectingCast: 'সংযুক্ত হচ্ছে',
- seek: 'অনুসন্ধান',
- volume: 'ভলিউম লেভেল',
- timeCurrent: 'বর্তমান সময়',
- timeDuration: 'ব্যাপ্তিকাল',
- timeRemaining: 'অবশিষ্ট সময়',
- timeRemainingPhrase: 'বাকি {duration}',
- playbackRateAria: 'প্লেব্যাক রেট {rate}',
- timeSliderValueTextRange: '{duration} এর মধ্যে {current}',
- volumeSliderValueTextMuted: '{percent}, নিঃশব্দ',
- indicatorMuted: 'নিঃশব্দ',
- indicatorVolume: 'ভলিউম',
- indicatorVolumeWithValue: 'ভলিউম {value}',
- indicatorCaptionsOn: 'ক্যাপশন চালু',
- indicatorCaptionsOff: 'ক্যাপশন বন্ধ',
- indicatorPaused: 'বিরাম',
- indicatorPlaying: 'চলছে',
- indicatorFullscreen: 'পূর্ণ স্ক্রীন',
- indicatorExitFullscreen: 'পূর্ণ স্ক্রীন থেকে বেরিয়ে আসুন',
- indicatorPictureInPicture: 'পিকচার ইন পিকচার',
- indicatorExitPictureInPicture: 'পিকচার ইন পিকচার থেকে বেরিয়ে আসুন',
- mediaErrorAborted: 'আপনি মিডিয়া প্লেব্যাক বাতিল করেছেন',
- mediaErrorNetwork: 'একটি নেটওয়ার্ক ত্রুটির কারণে মিডিয়া ডাউনলোড আংশিকভাবে ব্যর্থ হয়েছে৷',
- mediaErrorDecode:
- 'মিডিয়া প্লেব্যাক একটি সমস্যার কারণে বা মিডিয়া ব্যবহার করা বৈশিষ্ট্যগুলি আপনার ব্রাউজার সমর্থন করে না বলে বাতিল করা হয়েছে৷',
- mediaErrorSrcNotSupported: 'মিডিয়া লোড করা যায়নি, হয় সার্ভার বা নেটওয়ার্ক ব্যর্থ হওয়ার কারণে বা ফর্ম্যাটটি সমর্থিত নয়।',
- mediaErrorEncrypted: 'মিডিয়া এনক্রিপ্ট করা হয়েছে এবং এটি ডিক্রিপ্ট করার সমাধান আমাদের কাছে নেই।',
- mediaErrorCustom: '',
- errorDialogTitle: 'কিছু একটা ভুল হয়েছে।',
- errorDialogDismiss: 'বন্ধ করুন',
- mediaErrorFallback: 'একটি ত্রুটি ঘটেছে। আবার চেষ্টা করুন।',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/bs.ts b/packages/core/src/core/i18n/locales/bs.ts
deleted file mode 100644
index 7cf73bea..00000000
--- a/packages/core/src/core/i18n/locales/bs.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Pusti',
- pause: 'Pauza',
- replay: 'Ponovi',
- mute: 'Prigušen',
- unmute: 'Ne-prigušen',
- seekForward: 'Premotaj naprijed {seconds} sekundi',
- seekBackward: 'Premotaj nazad {seconds} sekundi',
- enterFullscreen: 'Puni ekran',
- exitFullscreen: 'Izađi iz cijelog ekrana',
- enableCaptions: 'Uključi titlove',
- disableCaptions: 'Isključi titlove',
- enterPictureInPicture: 'Slika u slici',
- exitPictureInPicture: 'Izlaz iz slike u slici',
- playingLive: 'Reprodukcija uživo',
- seekToLiveEdge: 'Idi na live',
- liveBadge: 'Uživo',
- startCasting: 'Pokreni emitovanje',
- stopCasting: 'Zaustavi emitovanje',
- connectingCast: 'Povezivanje',
- seek: 'Premotavanje',
- volume: 'Glasnoća',
- timeCurrent: 'Trenutno vrijeme',
- timeDuration: 'Vrijeme trajanja',
- timeRemaining: 'Preostalo vrijeme',
- timeRemainingPhrase: 'Preostalo {duration}',
- playbackRateAria: 'Stopa reprodukcije {rate}',
- timeSliderValueTextRange: '{current} od {duration}',
- volumeSliderValueTextMuted: '{percent}, isključen zvuk',
- indicatorMuted: 'Isključen zvuk',
- indicatorVolume: 'Glasnoća',
- indicatorVolumeWithValue: 'Glasnoća {value}',
- indicatorCaptionsOn: 'Titlovi uključeni',
- indicatorCaptionsOff: 'Titlovi isključeni',
- indicatorPaused: 'Pauzirano',
- indicatorPlaying: 'Reprodukcija',
- indicatorFullscreen: 'Puni ekran',
- indicatorExitFullscreen: 'Izlaz iz punog ekrana',
- indicatorPictureInPicture: 'Slika u slici',
- indicatorExitPictureInPicture: 'Izlaz iz slike u slici',
- mediaErrorAborted: 'Isključili ste reprodukciju videa.',
- mediaErrorNetwork: 'Video se prestao preuzimati zbog greške na mreži.',
- mediaErrorDecode: 'Reprodukcija videa je zaustavljenja zbog greške u formatu ili zbog verzije vašeg pretraživača.',
- mediaErrorSrcNotSupported: 'Video se ne može reproducirati zbog servera, greške u mreži ili je format ne podržan.',
- mediaErrorEncrypted: 'Medij je šifriran i nema ključeva za dešifriranje.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Nešto je pošlo po krivu.',
- errorDialogDismiss: 'OK',
- mediaErrorFallback: 'Došlo je do greške. Pokušajte ponovo.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/ca.ts b/packages/core/src/core/i18n/locales/ca.ts
deleted file mode 100644
index 7b344080..00000000
--- a/packages/core/src/core/i18n/locales/ca.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Reproduir',
- pause: 'Pausa',
- replay: 'Repetir',
- mute: 'Silenciar',
- unmute: 'Activar el so',
- seekForward: 'Salta endavant {seconds} segons',
- seekBackward: 'Salta enrere {seconds} segons',
- enterFullscreen: 'Pantalla completa',
- exitFullscreen: 'Sortir de pantalla completa',
- enableCaptions: 'Activa subtítols',
- disableCaptions: 'Desactiva subtítols',
- enterPictureInPicture: 'Imatge en imatge',
- exitPictureInPicture: 'Sortir de la imatge en imatge',
- playingLive: 'Reproducció en directe',
- seekToLiveEdge: 'Anar al directe',
- liveBadge: 'En directe',
- startCasting: 'Comença a emetre',
- stopCasting: 'Atura la transmissió',
- connectingCast: 'Connectant',
- seek: 'Desplaçament',
- volume: 'Nivell de volum',
- timeCurrent: 'Temps actual',
- timeDuration: 'Durada',
- timeRemaining: 'Temps restant',
- timeRemainingPhrase: 'Queden {duration}',
- playbackRateAria: 'Velocitat de reproducció {rate}',
- timeSliderValueTextRange: '{current} de {duration}',
- volumeSliderValueTextMuted: '{percent}, silenciat',
- indicatorMuted: 'Silenciat',
- indicatorVolume: 'Volum',
- indicatorVolumeWithValue: 'Volum {value}',
- indicatorCaptionsOn: 'Llegendes activades',
- indicatorCaptionsOff: 'Llegendes desactivades',
- indicatorPaused: 'En pausa',
- indicatorPlaying: 'Reproduint',
- indicatorFullscreen: 'Pantalla completa',
- indicatorExitFullscreen: 'Surt de pantalla completa',
- indicatorPictureInPicture: 'Imatge en imatge',
- indicatorExitPictureInPicture: 'Surt de la imatge en imatge',
- mediaErrorAborted: 'Has interromput la reproducció del contingut',
- mediaErrorNetwork: 'Un error de xarxa ha interromput la descàrrega del contingut.',
- mediaErrorDecode:
- "La reproducció del contingut s'ha interromput a causa d'un problema de corrupció o perquè el contingut fa servir funcions que el teu navegador no suporta.",
- mediaErrorSrcNotSupported:
- "No s'ha pogut carregar el contingut, ja sigui perquè el servidor o la xarxa han fallat o perquè el format no està suportat.",
- mediaErrorEncrypted: 'El contingut està xifrat i no disposem de les claus per desxifrar-lo.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Alguna cosa ha anat malament.',
- errorDialogDismiss: 'Tancar',
- mediaErrorFallback: "S'ha produït un error. Torneu-ho a intentar.",
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/cs.ts b/packages/core/src/core/i18n/locales/cs.ts
deleted file mode 100644
index ea649535..00000000
--- a/packages/core/src/core/i18n/locales/cs.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Přehrát',
- pause: 'Pozastavit',
- replay: 'Přehrát znovu',
- mute: 'Ztlumit',
- unmute: 'Zrušit ztlumení',
- seekForward: 'Posunout vpřed o {seconds} sekund',
- seekBackward: 'Posunout zpět o {seconds} sekund',
- enterFullscreen: 'Celá obrazovka',
- exitFullscreen: 'Ukončit režim celé obrazovky',
- enableCaptions: 'Zapnout titulky',
- disableCaptions: 'Vypnout titulky',
- enterPictureInPicture: 'Obraz v obraze',
- exitPictureInPicture: 'Ukončit obraz v obraze',
- playingLive: 'Přehrává se živě',
- seekToLiveEdge: 'Přejít na živé vysílání',
- liveBadge: 'Živě',
- startCasting: 'Začít přenášet',
- stopCasting: 'Zastavit přenos',
- connectingCast: 'Připojování',
- seek: 'Posun',
- volume: 'Hlasitost',
- timeCurrent: 'Aktuální čas',
- timeDuration: 'Doba trvání',
- timeRemaining: 'Zbývající čas',
- timeRemainingPhrase: 'Zbývá {duration}',
- playbackRateAria: 'Rychlost přehrávání {rate}',
- timeSliderValueTextRange: '{current} z {duration}',
- volumeSliderValueTextMuted: '{percent}, ztlumeno',
- indicatorMuted: 'Ztlumeno',
- indicatorVolume: 'Hlasitost',
- indicatorVolumeWithValue: 'Hlasitost {value}',
- indicatorCaptionsOn: 'Popisky zapnuty',
- indicatorCaptionsOff: 'Popisky vypnuty',
- indicatorPaused: 'Pozastaveno',
- indicatorPlaying: 'Přehrávání',
- indicatorFullscreen: 'Celá obrazovka',
- indicatorExitFullscreen: 'Ukončit celou obrazovku',
- indicatorPictureInPicture: 'Obraz v obraze',
- indicatorExitPictureInPicture: 'Ukončit obraz v obraze',
- mediaErrorAborted: 'Přehrávání videa bylo přerušeno.',
- mediaErrorNetwork: 'Video nemohlo být načteno kvůli chybě v síti.',
- mediaErrorDecode: 'Váš prohlížeč nepodporuje tento formát videa.',
- mediaErrorSrcNotSupported:
- 'Video nemohlo být načteno, buď kvůli chybě serveru, sítě nebo proto, že daný formát není podporován.',
- mediaErrorEncrypted: 'Chyba při dešifrování videa.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Něco se pokazilo.',
- errorDialogDismiss: 'Zavřít',
- mediaErrorFallback: 'Došlo k chybě. Zkuste to prosím znovu.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/cy.ts b/packages/core/src/core/i18n/locales/cy.ts
deleted file mode 100644
index 92262e4f..00000000
--- a/packages/core/src/core/i18n/locales/cy.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Chwarae',
- pause: 'Oedi',
- replay: 'Ailchwarae',
- mute: 'Pylu',
- unmute: 'Dad-bylu',
- seekForward: 'Neidla ymlaen {seconds} eiliad',
- seekBackward: 'Neidla yn ôl {seconds} eiliad',
- enterFullscreen: 'Sgrîn Lawn',
- exitFullscreen: 'Gadael sgrîn lawn',
- enableCaptions: 'Galluogi capsiynau',
- disableCaptions: 'Analluogi capsiynau',
- enterPictureInPicture: 'Llun mewn llun',
- exitPictureInPicture: 'Gadael llun mewn llun',
- playingLive: 'Yn chwarae’n fyw',
- seekToLiveEdge: 'Mynd i’r ymyl byw',
- liveBadge: 'Yn fyw',
- startCasting: 'Dechrau bwrw',
- stopCasting: 'Stopio bwrw',
- connectingCast: 'Cysylltu',
- seek: 'Chwilio',
- volume: 'Lefel Sain',
- timeCurrent: 'Amser Cyfredol',
- timeDuration: 'Parhad',
- timeRemaining: 'Amser ar ôl',
- timeRemainingPhrase: '{duration} yn weddill',
- playbackRateAria: 'Cyfradd Chwarae {rate}',
- timeSliderValueTextRange: '{current} o {duration}',
- volumeSliderValueTextMuted: '{percent}, mud',
- indicatorMuted: 'Mud',
- indicatorVolume: 'Sain',
- indicatorVolumeWithValue: 'Sain {value}',
- indicatorCaptionsOn: 'Capsiynau ymlaen',
- indicatorCaptionsOff: 'Capsiynau i ffwrdd',
- indicatorPaused: 'Wedi oedi',
- indicatorPlaying: 'Yn chwarae',
- indicatorFullscreen: 'Sgrîn lawn',
- indicatorExitFullscreen: 'Gadael sgrîn lawn',
- indicatorPictureInPicture: 'Llun mewn llun',
- indicatorExitPictureInPicture: 'Gadael llun mewn llun',
- mediaErrorAborted: 'Atalwyd y fideo gennych',
- mediaErrorNetwork: 'Mae gwall rhwydwaith wedi achosi methiant lawrlwytho.',
- mediaErrorDecode:
- "Atalwyd y fideo oherwydd problem llygredd data neu oherwydd nid yw'ch porwr yn cefnogi nodweddion penodol o'r fideo.",
- mediaErrorSrcNotSupported:
- "Ni lwythodd y fideo, oherwydd methiant gweinydd neu rwydwaith, neu achos nid yw'r system yn cefnogi'r fformat.",
- mediaErrorEncrypted: "Mae'r fideo wedi ei amgryptio ac nid oes allweddion gennym.",
- mediaErrorCustom: '',
- errorDialogTitle: "Aeth rhywbeth o'i le.",
- errorDialogDismiss: 'Cau',
- mediaErrorFallback: 'Digwyddodd gwall. Ceisiwch eto.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/da.ts b/packages/core/src/core/i18n/locales/da.ts
deleted file mode 100644
index 996fd302..00000000
--- a/packages/core/src/core/i18n/locales/da.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Afspil',
- pause: 'Pause',
- replay: 'Afspil igen',
- mute: 'Uden lyd',
- unmute: 'Med lyd',
- seekForward: 'Spring {seconds} sekunder frem',
- seekBackward: 'Spring {seconds} sekunder tilbage',
- enterFullscreen: 'Fuldskærm',
- exitFullscreen: 'Luk fuldskærm',
- enableCaptions: 'Aktivér undertekster',
- disableCaptions: 'Deaktiver undertekster',
- enterPictureInPicture: 'Billede-i-billede',
- exitPictureInPicture: 'Afslut billede-i-billede',
- playingLive: 'Afspiller live',
- seekToLiveEdge: 'Gå til live',
- liveBadge: 'Live',
- startCasting: 'Start afsendelse',
- stopCasting: 'Stop afsendelse',
- connectingCast: 'Forbinder',
- seek: 'Spol',
- volume: 'Lydstyrke',
- timeCurrent: 'Aktuel tid',
- timeDuration: 'Varighed',
- timeRemaining: 'Resterende tid',
- timeRemainingPhrase: '{duration} tilbage',
- playbackRateAria: 'Afspilningsrate {rate}',
- timeSliderValueTextRange: '{current} af {duration}',
- volumeSliderValueTextMuted: '{percent}, lydløs',
- indicatorMuted: 'Lydløs',
- indicatorVolume: 'Lydstyrke',
- indicatorVolumeWithValue: 'Lydstyrke {value}',
- indicatorCaptionsOn: 'Undertekster til',
- indicatorCaptionsOff: 'Undertekster fra',
- indicatorPaused: 'Pauseret',
- indicatorPlaying: 'Afspiller',
- indicatorFullscreen: 'Fuldskærm',
- indicatorExitFullscreen: 'Luk fuldskærm',
- indicatorPictureInPicture: 'Billede i billede',
- indicatorExitPictureInPicture: 'Afslut billede i billede',
- mediaErrorAborted: 'Du afbrød videoafspilningen.',
- mediaErrorNetwork: 'En netværksfejl fik download af videoen til at fejle.',
- mediaErrorDecode:
- 'Videoafspilningen blev afbrudt på grund af ødelagte data eller fordi videoen benyttede faciliteter som din browser ikke understøtter.',
- mediaErrorSrcNotSupported:
- 'Videoen kunne ikke indlæses, enten fordi serveren eller netværket fejlede, eller fordi formatet ikke er understøttet.',
- mediaErrorEncrypted: 'Mediet er krypteret, og der er ingen nøgler til at dekryptere det.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Noget gik galt.',
- errorDialogDismiss: 'OK',
- mediaErrorFallback: 'Der opstod en fejl. Prøv igen.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/de.ts b/packages/core/src/core/i18n/locales/de.ts
deleted file mode 100644
index 6afe2809..00000000
--- a/packages/core/src/core/i18n/locales/de.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Wiedergabe',
- pause: 'Pause',
- replay: 'Erneut abspielen',
- mute: 'Stumm schalten',
- unmute: 'Ton einschalten',
- seekForward: '{seconds} Sekunden vorwärts',
- seekBackward: '{seconds} Sekunden zurück',
- enterFullscreen: 'Vollbild',
- exitFullscreen: 'Vollbildmodus beenden',
- enableCaptions: 'Untertitel einschalten',
- disableCaptions: 'Untertitel ausschalten',
- enterPictureInPicture: 'Bild-im-Bild-Modus',
- exitPictureInPicture: 'Bild-im-Bild-Modus beenden',
- playingLive: 'Wird live wiedergegeben',
- seekToLiveEdge: 'Zum Live-Rand springen',
- liveBadge: 'Live',
- startCasting: 'Übertragung starten',
- stopCasting: 'Übertragung beenden',
- connectingCast: 'Verbinden',
- seek: 'Spule',
- volume: 'Lautstärke',
- timeCurrent: 'Aktueller Zeitpunkt',
- timeDuration: 'Dauer',
- timeRemaining: 'Verbleibende Zeit',
- timeRemainingPhrase: 'Noch {duration}',
- playbackRateAria: 'Wiedergabegeschwindigkeit {rate}',
- timeSliderValueTextRange: '{current} von {duration}',
- volumeSliderValueTextMuted: '{percent}, stummgeschaltet',
- indicatorMuted: 'Stummgeschaltet',
- indicatorVolume: 'Lautstärke',
- indicatorVolumeWithValue: 'Lautstärke {value}',
- indicatorCaptionsOn: 'Untertitel ein',
- indicatorCaptionsOff: 'Untertitel aus',
- indicatorPaused: 'Pausiert',
- indicatorPlaying: 'Wird wiedergegeben',
- indicatorFullscreen: 'Vollbild',
- indicatorExitFullscreen: 'Vollbild beenden',
- indicatorPictureInPicture: 'Bild-in-Bild',
- indicatorExitPictureInPicture: 'Bild-in-Bild beenden',
- mediaErrorAborted: 'Sie haben die Videowiedergabe abgebrochen.',
- mediaErrorNetwork: 'Der Videodownload ist aufgrund eines Netzwerkfehlers fehlgeschlagen.',
- mediaErrorDecode:
- 'Die Videowiedergabe wurde entweder wegen eines Problems mit einem beschädigten Video oder wegen verwendeten Funktionen, die vom Browser nicht unterstützt werden, abgebrochen.',
- mediaErrorSrcNotSupported:
- 'Das Video konnte nicht geladen werden, da entweder ein Server- oder Netzwerkfehler auftrat oder das Format nicht unterstützt wird.',
- mediaErrorEncrypted: 'Die Entschlüsselungsschlüssel für den verschlüsselten Medieninhalt sind nicht verfügbar.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Etwas ist schiefgelaufen.',
- errorDialogDismiss: 'Schließen',
- mediaErrorFallback: 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/el.ts b/packages/core/src/core/i18n/locales/el.ts
deleted file mode 100644
index a1a47b89..00000000
--- a/packages/core/src/core/i18n/locales/el.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Aναπαραγωγή',
- pause: 'Παύση',
- replay: 'Επανάληψη',
- mute: 'Σίγαση',
- unmute: 'Kατάργηση σίγασης',
- seekForward: 'Μεταβείτε μπροστά {seconds} δευτερόλεπτα',
- seekBackward: 'Μεταβείτε πίσω {seconds} δευτερόλεπτα',
- enterFullscreen: 'Πλήρης οθόνη',
- exitFullscreen: 'Έξοδος από πλήρη οθόνη',
- enableCaptions: 'Ενεργοποίηση υποτίτλων',
- disableCaptions: 'Απενεργοποίηση υποτίτλων',
- enterPictureInPicture: 'Εικόνα-μέσα-σε-Εικόνα',
- exitPictureInPicture: 'Έξοδος από την Εικόνα-μέσα-σε-Εικόνα',
- playingLive: 'Αναπαραγωγή ζωντανά',
- seekToLiveEdge: 'Μετάβαση στο ζωντανό',
- liveBadge: 'Ζωντανά',
- startCasting: 'Έναρξη μετάδοσης',
- stopCasting: 'Διακοπή μετάδοσης',
- connectingCast: 'Σύνδεση',
- seek: 'Μετακίνηση',
- volume: 'Επίπεδο Ήχου',
- timeCurrent: 'Τρέχων χρόνος',
- timeDuration: 'Συνολικός χρόνος',
- timeRemaining: 'Υπολοιπόμενος χρόνος',
- timeRemainingPhrase: 'Απομένουν {duration}',
- playbackRateAria: 'Ρυθμός αναπαραγωγής {rate}',
- timeSliderValueTextRange: '{current} από {duration}',
- volumeSliderValueTextMuted: '{percent}, σε σίγαση',
- indicatorMuted: 'Σε σίγαση',
- indicatorVolume: 'Ένταση',
- indicatorVolumeWithValue: 'Ένταση {value}',
- indicatorCaptionsOn: 'Λεζάντες ενεργές',
- indicatorCaptionsOff: 'Λεζάντες ανενεργές',
- indicatorPaused: 'Σε παύση',
- indicatorPlaying: 'Αναπαραγωγή',
- indicatorFullscreen: 'Πλήρης οθόνη',
- indicatorExitFullscreen: 'Έξοδος από πλήρη οθόνη',
- indicatorPictureInPicture: 'Εικόνα μέσα σε εικόνα',
- indicatorExitPictureInPicture: 'Έξοδος από εικόνα μέσα σε εικόνα',
- mediaErrorAborted: 'Ακυρώσατε την αναπαραγωγή',
- mediaErrorNetwork: 'Ένα σφάλμα δικτύου προκάλεσε την αποτυχία μεταφόρτωσης του αρχείου προς αναπαραγωγή.',
- mediaErrorDecode:
- 'Η αναπαραγωγή ακυρώθηκε είτε λόγω κατεστραμμένου αρχείου, είτε γιατί το αρχείο απαιτεί λειτουργίες που δεν υποστηρίζονται από το πρόγραμμα περιήγησης που χρησιμοποιείτε.',
- mediaErrorSrcNotSupported:
- 'Το αρχείο προς αναπαραγωγή δεν ήταν δυνατό να φορτωθεί είτε γιατί υπήρξε σφάλμα στον διακομιστή ή το δίκτυο, είτε γιατί ο τύπος του αρχείου δεν υποστηρίζεται.',
- mediaErrorEncrypted:
- 'Το αρχείο προς αναπαραγωγή είναι κρυπτογραφημένo και δεν υπάρχουν τα απαραίτητα κλειδιά αποκρυπτογράφησης.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Κάτι πήγε στραβά.',
- errorDialogDismiss: 'Κλείσιμο',
- mediaErrorFallback: 'Παρουσιάστηκε σφάλμα. Δοκιμάστε ξανά.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/en.ts b/packages/core/src/core/i18n/locales/en.ts
deleted file mode 100644
index fd48078e..00000000
--- a/packages/core/src/core/i18n/locales/en.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import type { Translations } from '../types';
-
-/** Default English layer — registered when `@videojs/core/i18n/registry` (or this package entry) is loaded. */
-export default {
- play: 'Play',
- pause: 'Pause',
- replay: 'Replay',
- mute: 'Mute',
- unmute: 'Unmute',
- seekForward: 'Seek forward {seconds} seconds',
- seekBackward: 'Seek backward {seconds} seconds',
- enterFullscreen: 'Enter fullscreen',
- exitFullscreen: 'Exit fullscreen',
- enableCaptions: 'Enable captions',
- disableCaptions: 'Disable captions',
- enterPictureInPicture: 'Enter picture-in-picture',
- exitPictureInPicture: 'Exit picture-in-picture',
- playingLive: 'Playing live',
- seekToLiveEdge: 'Seek to live edge',
- liveBadge: 'Live',
- startCasting: 'Start casting',
- stopCasting: 'Stop casting',
- connectingCast: 'Connecting',
- seek: 'Seek',
- volume: 'Volume',
- timeCurrent: 'Current time',
- timeDuration: 'Duration',
- timeRemaining: 'Remaining',
- timeRemainingPhrase: '{duration} remaining',
- playbackRateAria: 'Playback rate {rate}',
- timeSliderValueTextRange: '{current} of {duration}',
- volumeSliderValueTextMuted: '{percent}, muted',
- indicatorMuted: 'Muted',
- indicatorVolume: 'Volume',
- indicatorVolumeWithValue: 'Volume {value}',
- indicatorCaptionsOn: 'Captions on',
- indicatorCaptionsOff: 'Captions off',
- indicatorPaused: 'Paused',
- indicatorPlaying: 'Playing',
- indicatorFullscreen: 'Fullscreen',
- indicatorExitFullscreen: 'Exit fullscreen',
- indicatorPictureInPicture: 'Picture in picture',
- indicatorExitPictureInPicture: 'Exit picture in picture',
- mediaErrorAborted: 'You aborted the media playback',
- mediaErrorNetwork: 'A network error caused the media download to fail.',
- mediaErrorDecode:
- 'A media error caused playback to be aborted. The media could be corrupt or your browser does not support this format.',
- mediaErrorSrcNotSupported:
- 'An unsupported error occurred. The server or network failed, or your browser does not support this format.',
- mediaErrorEncrypted: 'The media is encrypted and there are no keys to decrypt it.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Something went wrong.',
- errorDialogDismiss: 'OK',
- mediaErrorFallback: 'An error occurred. Please try again.',
-} as const satisfies Translations;
diff --git a/packages/core/src/core/i18n/locales/es.ts b/packages/core/src/core/i18n/locales/es.ts
deleted file mode 100644
index e407f4ed..00000000
--- a/packages/core/src/core/i18n/locales/es.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Reproducir',
- pause: 'Pausa',
- replay: 'Volver a reproducir',
- mute: 'Desactivar el sonido',
- unmute: 'Activar el sonido',
- seekForward: 'Avanza {seconds} segundos',
- seekBackward: 'Retrocede {seconds} segundos',
- enterFullscreen: 'Pantalla completa',
- exitFullscreen: 'Salir de pantalla completa',
- enableCaptions: 'Activar subtítulos',
- disableCaptions: 'Desactivar subtítulos',
- enterPictureInPicture: 'Imagen sobre imagen',
- exitPictureInPicture: 'Salir de imagen sobre imagen',
- playingLive: 'Reproduciendo en directo',
- seekToLiveEdge: 'Ir al directo',
- liveBadge: 'Directo',
- startCasting: 'Iniciar transmisión',
- stopCasting: 'Detener transmisión',
- connectingCast: 'Conectando',
- seek: 'Buscar',
- volume: 'Nivel de volumen',
- timeCurrent: 'Tiempo reproducido',
- timeDuration: 'Duración total',
- timeRemaining: 'Tiempo restante',
- timeRemainingPhrase: 'Quedan {duration}',
- playbackRateAria: 'Velocidad de reproducción {rate}',
- timeSliderValueTextRange: '{current} de {duration}',
- volumeSliderValueTextMuted: '{percent}, silenciado',
- indicatorMuted: 'Silenciado',
- indicatorVolume: 'Volumen',
- indicatorVolumeWithValue: 'Volumen {value}',
- indicatorCaptionsOn: 'Subtítulos activados',
- indicatorCaptionsOff: 'Subtítulos desactivados',
- indicatorPaused: 'En pausa',
- indicatorPlaying: 'Reproduciendo',
- indicatorFullscreen: 'Pantalla completa',
- indicatorExitFullscreen: 'Salir de pantalla completa',
- indicatorPictureInPicture: 'Imagen en imagen',
- indicatorExitPictureInPicture: 'Salir de imagen en imagen',
- mediaErrorAborted: 'Ha interrumpido la reproducción del vídeo.',
- mediaErrorNetwork: 'Un error de red ha interrumpido la descarga del vídeo.',
- mediaErrorDecode:
- 'La reproducción de vídeo se ha interrumpido por un problema de corrupción de datos o porque el vídeo precisa funciones que su navegador no ofrece.',
- mediaErrorSrcNotSupported:
- 'No se ha podido cargar el vídeo debido a un fallo de red o del servidor o porque el formato es incompatible.',
- mediaErrorEncrypted: 'El material audiovisual está cifrado y no tenemos las claves para descifrarlo.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Algo ha salido mal.',
- errorDialogDismiss: 'Cerrar',
- mediaErrorFallback: 'Se ha producido un error. Inténtalo de nuevo.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/et.ts b/packages/core/src/core/i18n/locales/et.ts
deleted file mode 100644
index 8397523d..00000000
--- a/packages/core/src/core/i18n/locales/et.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Esita',
- pause: 'Paus',
- replay: 'Esita uuesti',
- mute: 'Vaigista',
- unmute: 'Lõpeta vaigistus',
- seekForward: 'Liigu edasi {seconds} sekundit',
- seekBackward: 'Liigu tagasi {seconds} sekundit',
- enterFullscreen: 'Täisekraan',
- exitFullscreen: 'Välju täisekraanist',
- enableCaptions: 'Lülita subtiitrid sisse',
- disableCaptions: 'Lülita subtiitrid välja',
- enterPictureInPicture: 'Pilt pildis',
- exitPictureInPicture: 'Välju funktsioonist pilt pildis',
- playingLive: 'Mängib reaalajas',
- seekToLiveEdge: 'Mine otseülekande äärele',
- liveBadge: 'Otse',
- startCasting: 'Alusta ülekandmist',
- stopCasting: 'Lõpeta ülekandmine',
- connectingCast: 'Ühendumine',
- seek: 'Kerimine',
- volume: 'Helitugevuse tase',
- timeCurrent: 'Praegune aeg',
- timeDuration: 'Kestus',
- timeRemaining: 'Järelejäänud aeg',
- timeRemainingPhrase: 'Jäänud {duration}',
- playbackRateAria: 'Taasesituse kiirus {rate}',
- timeSliderValueTextRange: '{current} / {duration}',
- volumeSliderValueTextMuted: '{percent}, vaigistatud',
- indicatorMuted: 'Vaigistatud',
- indicatorVolume: 'Helitugevus',
- indicatorVolumeWithValue: 'Helitugevus {value}',
- indicatorCaptionsOn: 'Pealdised sees',
- indicatorCaptionsOff: 'Pealdised väljas',
- indicatorPaused: 'Pausitud',
- indicatorPlaying: 'Esitamine',
- indicatorFullscreen: 'Täisekraan',
- indicatorExitFullscreen: 'Välju täisekraanist',
- indicatorPictureInPicture: 'Pilt pildis',
- indicatorExitPictureInPicture: 'Välju funktsioonist pilt pildis',
- mediaErrorAborted: 'Katkestasid taasesituse',
- mediaErrorNetwork: 'Võrguvea tõttu nurjus meediumifaili allalaadimine poole pealt.',
- mediaErrorDecode:
- 'Meediumifaili taasesitamine katkestati, kuna fail on rikutud või see kasutab funktsiooni, mida sinu brauser ei toeta.',
- mediaErrorSrcNotSupported:
- 'Seda meediumifaili ei õnnestunud laadida, kuna serveris või võrgus esines tõrge või kuna vormingut ei toetata.',
- mediaErrorEncrypted: 'See meediumifail on krüpteeritud ja meil pole dekrüpteerimiseks vajalikku võtit.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Midagi läks valesti.',
- errorDialogDismiss: 'Sule',
- mediaErrorFallback: 'Esines viga. Palun proovige uuesti.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/eu.ts b/packages/core/src/core/i18n/locales/eu.ts
deleted file mode 100644
index 454806c3..00000000
--- a/packages/core/src/core/i18n/locales/eu.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Hasi',
- pause: 'Gelditu',
- replay: 'Berriz hasi',
- mute: 'Ixildu',
- unmute: 'Soinua jarri',
- seekForward: 'Joan aurrera {seconds} segundo',
- seekBackward: 'Joan atzera {seconds} segundo',
- enterFullscreen: 'Pantaila osoa',
- exitFullscreen: 'Irten pantaila osotik',
- enableCaptions: 'Aktibatu azpitituluak',
- disableCaptions: 'Desaktibatu azpitituluak',
- enterPictureInPicture: 'Irudiz-irudi',
- exitPictureInPicture: 'Irten irudiz-irudiztik',
- playingLive: 'Zuzenean erreproduzitzen',
- seekToLiveEdge: 'Zuzeneko ertzeraino joan',
- liveBadge: 'Zuzenean',
- startCasting: 'Hasi emankizuna',
- stopCasting: 'Gelditu emankizuna',
- connectingCast: 'Konektatzen',
- seek: 'Bilatu',
- volume: 'Bolumen maila',
- timeCurrent: 'Uneko denbora',
- timeDuration: 'Iraupena',
- timeRemaining: 'Gelditzen den denbora',
- timeRemainingPhrase: 'Geratzen den {duration}',
- playbackRateAria: 'Abiadura {rate}',
- timeSliderValueTextRange: '{current} / {duration}',
- volumeSliderValueTextMuted: '{percent}, isilarazia',
- indicatorMuted: 'Isilarazia',
- indicatorVolume: 'Bolumena',
- indicatorVolumeWithValue: 'Bolumena {value}',
- indicatorCaptionsOn: 'Oharrak aktibo',
- indicatorCaptionsOff: 'Oharrak ez aktibo',
- indicatorPaused: 'Geldituta',
- indicatorPlaying: 'Erreproduzitzen',
- indicatorFullscreen: 'Pantaila osoa',
- indicatorExitFullscreen: 'Irten pantaila osotik',
- indicatorPictureInPicture: 'Irudiz irudi',
- indicatorExitPictureInPicture: 'Irten irudiz irudiztik',
- mediaErrorAborted: 'Bertan behera utzi duzu',
- mediaErrorNetwork: 'Sare errore batek deskargak huts egitea eragin du.',
- mediaErrorDecode:
- 'Bertan behera gelditu da fitxategia ondo ez dagoelako edo zure nabigatzailean erabili ezin diren ezaugarriak dituelako.',
- mediaErrorSrcNotSupported:
- 'Media ezin izan da kargatu, zerbitzariak edo sareak huts egin duelako edo formatu horretako media erabili ezin delako.',
- mediaErrorEncrypted: 'Media zifratuta dago eta ez ditugu beharrezko gakoak.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Zerbait gaizki joan da.',
- errorDialogDismiss: 'Itxi',
- mediaErrorFallback: 'Errore bat gertatu da. Saiatu berriro.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/fa.ts b/packages/core/src/core/i18n/locales/fa.ts
deleted file mode 100644
index 97ec0173..00000000
--- a/packages/core/src/core/i18n/locales/fa.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'پخش',
- pause: 'توقف',
- replay: 'پخش مجدد',
- mute: 'بیصدا',
- unmute: 'صدادار',
- seekForward: '{seconds} ثانیه بعد',
- seekBackward: '{seconds} ثانیه قبل',
- enterFullscreen: 'تمامصفحه',
- exitFullscreen: 'غیر تمامصفحه',
- enableCaptions: 'فعالسازی زیرنویس',
- disableCaptions: 'غیرفعالسازی زیرنویس',
- enterPictureInPicture: 'تصویر در تصویر',
- exitPictureInPicture: 'خروج از حالت تصویر در تصویر',
- playingLive: 'پخش زنده',
- seekToLiveEdge: 'رفتن به پخش زنده',
- liveBadge: 'زنده',
- startCasting: 'شروع پخش به تلویزیون',
- stopCasting: 'توقف پخش به تلویزیون',
- connectingCast: 'در حال اتصال',
- seek: 'جستجو',
- volume: 'سطح صدا',
- timeCurrent: 'زمان فعلی',
- timeDuration: 'مدت',
- timeRemaining: 'زمان باقیمانده',
- timeRemainingPhrase: '{duration} باقیمانده',
- playbackRateAria: 'سرعت پخش {rate}',
- timeSliderValueTextRange: '{current} از {duration}',
- volumeSliderValueTextMuted: '{percent}, بیصدا',
- indicatorMuted: 'بیصدا',
- indicatorVolume: 'صدا',
- indicatorVolumeWithValue: 'صدا {value}',
- indicatorCaptionsOn: 'توضیحات روشن',
- indicatorCaptionsOff: 'توضیحات خاموش',
- indicatorPaused: 'متوقف شده',
- indicatorPlaying: 'در حال پخش',
- indicatorFullscreen: 'تمامصفحه',
- indicatorExitFullscreen: 'خروج از تمامصفحه',
- indicatorPictureInPicture: 'تصویر در تصویر',
- indicatorExitPictureInPicture: 'خروج از حالت تصویر در تصویر',
- mediaErrorAborted: 'شما پخش رسانه را قطع نمودید',
- mediaErrorNetwork: 'وقوع مشکلی در شبکه باعث اختلال در دانلود رسانه شد.',
- mediaErrorDecode: 'پخش رسانه بهعلت اشکال در آن یا عدم پشتیبانی مرورگر شما قطع شد.',
- mediaErrorSrcNotSupported:
- 'رسانه قابل بارگیری نیست. ممکن است مشکلی در شبکه یا سرور رخ داده باشد یا قالب رسانه در دستگاه شما پشتیبانی نشود.',
- mediaErrorEncrypted: 'این رسانه رمزنگاری شدهاست و کلیدهای رمزگشایی آن موجود نیست.',
- mediaErrorCustom: '',
- errorDialogTitle: 'مشکلی پیش آمد.',
- errorDialogDismiss: 'بستن',
- mediaErrorFallback: 'خطایی رخ داد. لطفاً دوباره امتحان کنید.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/fi.ts b/packages/core/src/core/i18n/locales/fi.ts
deleted file mode 100644
index 82608f20..00000000
--- a/packages/core/src/core/i18n/locales/fi.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Toista',
- pause: 'Keskeytä toisto',
- replay: 'Toista uudelleen',
- mute: 'Mykistä',
- unmute: 'Poista mykistys',
- seekForward: 'Hyppää eteenpäin {seconds} sekuntia',
- seekBackward: 'Hyppää taaksepäin {seconds} sekuntia',
- enterFullscreen: 'Koko näytön tila',
- exitFullscreen: 'Poistu koko näytön tilasta',
- enableCaptions: 'Ota tekstitykset käyttöön',
- disableCaptions: 'Poista tekstitykset käytöstä',
- enterPictureInPicture: 'Kuva kuvassa -tila',
- exitPictureInPicture: 'Poistu kuva kuvassa -tilasta',
- playingLive: 'Toistetaan livenä',
- seekToLiveEdge: 'Siirry liveen',
- liveBadge: 'Live',
- startCasting: 'Aloita lähetys',
- stopCasting: 'Lopeta lähetys',
- connectingCast: 'Yhdistetään',
- seek: 'Kelaa',
- volume: 'Äänenvoimakkuus',
- timeCurrent: 'Tämänhetkinen aika',
- timeDuration: 'Kokonaiskesto',
- timeRemaining: 'Jäljellä oleva aika',
- timeRemainingPhrase: '{duration} jäljellä',
- playbackRateAria: 'Toistonopeus {rate}',
- timeSliderValueTextRange: '{current} / {duration}',
- volumeSliderValueTextMuted: '{percent}, mykistetty',
- indicatorMuted: 'Mykistetty',
- indicatorVolume: 'Äänenvoimakkuus',
- indicatorVolumeWithValue: 'Äänenvoimakkuus {value}',
- indicatorCaptionsOn: 'Tekstitys päällä',
- indicatorCaptionsOff: 'Tekstitys pois',
- indicatorPaused: 'Keskeytetty',
- indicatorPlaying: 'Toistetaan',
- indicatorFullscreen: 'Koko näyttö',
- indicatorExitFullscreen: 'Poistu koko näytöltä',
- indicatorPictureInPicture: 'Kuva kuvassa',
- indicatorExitPictureInPicture: 'Poistu kuva kuvassa -tilasta',
- mediaErrorAborted: 'Olet keskeyttänyt videotoiston',
- mediaErrorNetwork: 'Verkkovirhe keskeytti videon latauksen.',
- mediaErrorDecode:
- 'Videon toisto keskeytyi, koska videotiedosto on vioittunut tai käyttää toimintoja, joita selaimesi ei tue.',
- mediaErrorSrcNotSupported:
- 'Videon lataus ei onnistunut joko palvelin- tai verkkovirheestä tai väärästä formaatista johtuen.',
- mediaErrorEncrypted: 'Media on salattu eikä siihen ole purkuavaimia.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Jotain meni pieleen.',
- errorDialogDismiss: 'OK',
- mediaErrorFallback: 'Tapahtui virhe. Yritä uudelleen.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/fr.ts b/packages/core/src/core/i18n/locales/fr.ts
deleted file mode 100644
index af807742..00000000
--- a/packages/core/src/core/i18n/locales/fr.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Lecture',
- pause: 'Pause',
- replay: 'Revoir',
- mute: 'Mettre en sourdine',
- unmute: 'Activer le son',
- seekForward: 'Avancer de {seconds} secondes',
- seekBackward: 'Reculer de {seconds} secondes',
- enterFullscreen: 'Plein écran',
- exitFullscreen: 'Quitter le plein écran',
- enableCaptions: 'Activer les sous-titres',
- disableCaptions: 'Désactiver les sous-titres',
- enterPictureInPicture: "Image dans l'image",
- exitPictureInPicture: "Quitter le mode image dans l'image",
- playingLive: 'Lecture en direct',
- seekToLiveEdge: 'Aller au direct',
- liveBadge: 'En direct',
- startCasting: 'Démarrer la diffusion',
- stopCasting: 'Arrêter la diffusion',
- connectingCast: 'Connexion',
- seek: 'Barre de lecture',
- volume: 'Niveau de volume',
- timeCurrent: 'Temps actuel',
- timeDuration: 'Durée',
- timeRemaining: 'Temps restant',
- timeRemainingPhrase: 'Il reste {duration}',
- playbackRateAria: 'Vitesse de lecture {rate}',
- timeSliderValueTextRange: '{current} de {duration}',
- volumeSliderValueTextMuted: '{percent}, son coupé',
- indicatorMuted: 'Son coupé',
- indicatorVolume: 'Niveau de volume',
- indicatorVolumeWithValue: 'Niveau de volume {value}',
- indicatorCaptionsOn: 'Sous-titres activés',
- indicatorCaptionsOff: 'Sous-titres désactivés',
- indicatorPaused: 'En pause',
- indicatorPlaying: 'Lecture en cours',
- indicatorFullscreen: 'Plein écran',
- indicatorExitFullscreen: 'Quitter le plein écran',
- indicatorPictureInPicture: 'Image dans l’image',
- indicatorExitPictureInPicture: 'Quitter l’image dans l’image',
- mediaErrorAborted: 'Vous avez interrompu la lecture de la vidéo.',
- mediaErrorNetwork: 'Une erreur de réseau a interrompu le téléchargement de la vidéo.',
- mediaErrorDecode:
- "La lecture de la vidéo a été interrompue à cause d'un problème de corruption ou parce que la vidéo utilise des fonctionnalités non prises en charge par votre navigateur.",
- mediaErrorSrcNotSupported:
- "Cette vidéo n'a pas pu être chargée, soit parce que le serveur ou le réseau a échoué ou parce que le format n'est pas reconnu.",
- mediaErrorEncrypted: "Le média est chiffré et nous n'avons pas les clés pour le déchiffrer.",
- mediaErrorCustom: '',
- errorDialogTitle: 'Une erreur s’est produite.',
- errorDialogDismiss: 'Fermer',
- mediaErrorFallback: 'Une erreur s’est produite. Veuillez réessayer.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/gd.ts b/packages/core/src/core/i18n/locales/gd.ts
deleted file mode 100644
index ab0eb468..00000000
--- a/packages/core/src/core/i18n/locales/gd.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Cluich',
- pause: 'Cuir ’na stad',
- replay: 'Cluich a-rithist',
- mute: 'Mùch',
- unmute: 'Dì-mhùch',
- seekForward: 'Gluais air adhart {seconds} diog',
- seekBackward: 'Gluais air ais {seconds} diog',
- enterFullscreen: 'Làn-sgrìn',
- exitFullscreen: 'Fàg modh làn-sgrìn',
- enableCaptions: 'Cuir capsaidean air',
- disableCaptions: 'Toir capsaidean dheth',
- enterPictureInPicture: 'Dealbh beag anns a’ dealbh mhòr',
- exitPictureInPicture: 'Fàg dealbh beag anns a’ dealbh mhòr',
- playingLive: 'A’ cluich beò',
- seekToLiveEdge: 'Tèarmann gu beò',
- liveBadge: 'Beò',
- startCasting: 'Tòisich air tar-chur',
- stopCasting: 'Cuir stad air tar-chur',
- connectingCast: 'A’ ceangal',
- seek: 'Lorg',
- volume: 'Àirde na fuaime',
- timeCurrent: 'An ùine làithreach',
- timeDuration: 'Faide',
- timeRemaining: 'An ùine air fhàgail',
- timeRemainingPhrase: '{duration} air fhàgail',
- playbackRateAria: 'Reat cluich {rate}',
- timeSliderValueTextRange: '{current} à {duration}',
- volumeSliderValueTextMuted: '{percent}, air mùchadh',
- indicatorMuted: 'Air mùchadh',
- indicatorVolume: 'Àirde na fuaime',
- indicatorVolumeWithValue: 'Àirde na fuaime {value}',
- indicatorCaptionsOn: 'Caipseanan air',
- indicatorCaptionsOff: 'Caipseanan dheth',
- indicatorPaused: 'Air stad',
- indicatorPlaying: 'A’ cluich',
- indicatorFullscreen: 'Làn-sgrìn',
- indicatorExitFullscreen: 'Fàg làn-sgrìn',
- indicatorPictureInPicture: 'Dealbh beag anns a’ dealbh mhòr',
- indicatorExitPictureInPicture: 'Fàg dealbh beag',
- mediaErrorAborted: 'Sguir thu de chluich a’ mheadhain',
- mediaErrorNetwork: 'Cha deach leinn an còrr dhen mheadhan a luchdadh a-nuas ri linn mearachd lìonraidh.',
- mediaErrorDecode:
- 'Sguir sinn de chluich a’ mheadhain – dh’fhaoidte gu bheil e coirbte no gu bheil gleus aig a’ mheadhan nach cuir am brabhsair taic ris.',
- mediaErrorSrcNotSupported:
- 'Cha b’ urrainn dhuinn am meadhan a luchdadh – dh’fhaoidte gun do dh’fhàillig leis an fhrithealaiche no an lìonra no nach cuir sinn taic ris an fhòrmat.',
- mediaErrorEncrypted: 'Tha am meadhan crioptaichte ’s chan eil iuchair dì-chrioptachaidh againn dha.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Chaidh rudeigin ceàrr.',
- errorDialogDismiss: 'Dùin',
- mediaErrorFallback: 'Thachair mearachd. Feuch ris a-rithist.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/gl.ts b/packages/core/src/core/i18n/locales/gl.ts
deleted file mode 100644
index c015ca27..00000000
--- a/packages/core/src/core/i18n/locales/gl.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Reproducir',
- pause: 'Pausa',
- replay: 'Repetir',
- mute: 'Silenciar',
- unmute: 'Son activado',
- seekForward: 'Avanzar {seconds} segundos',
- seekBackward: 'Retroceder {seconds} segundos',
- enterFullscreen: 'Pantalla completa',
- exitFullscreen: 'Saír da pantalla completa',
- enableCaptions: 'Activar subtítulos',
- disableCaptions: 'Desactivar subtítulos',
- enterPictureInPicture: 'Imaxe en imaxe',
- exitPictureInPicture: 'Saír de imaxe en imaxe',
- playingLive: 'Reproducindo en directo',
- seekToLiveEdge: 'Ir ao directo',
- liveBadge: 'En directo',
- startCasting: 'Iniciar emisión',
- stopCasting: 'Deter emisión',
- connectingCast: 'Conectando',
- seek: 'Buscar',
- volume: 'Nivel do volume',
- timeCurrent: 'Tempo reproducido',
- timeDuration: 'Duración',
- timeRemaining: 'Tempo restante',
- timeRemainingPhrase: 'Quedan {duration}',
- playbackRateAria: 'Velocidade de reprodución {rate}',
- timeSliderValueTextRange: '{current} de {duration}',
- volumeSliderValueTextMuted: '{percent}, silenciado',
- indicatorMuted: 'Silenciado',
- indicatorVolume: 'Nivel do volume',
- indicatorVolumeWithValue: 'Nivel do volume {value}',
- indicatorCaptionsOn: 'Subtítulos activados',
- indicatorCaptionsOff: 'Subtítulos desactivados',
- indicatorPaused: 'En pausa',
- indicatorPlaying: 'Reproducindo',
- indicatorFullscreen: 'Pantalla completa',
- indicatorExitFullscreen: 'Saír da pantalla completa',
- indicatorPictureInPicture: 'Imaxe en imaxe',
- indicatorExitPictureInPicture: 'Saír de imaxe en imaxe',
- mediaErrorAborted: 'Vostede interrompeu a reprodución do medio.',
- mediaErrorNetwork: 'Un erro de rede interrompeu a descarga do medio.',
- mediaErrorDecode:
- 'Interrompeuse a reprodución do medio por mor dun problema de estragamento dos datos ou porque o medio precisa funcións que o seu navegador non ofrece.',
- mediaErrorSrcNotSupported:
- 'Non foi posíbel cargar o medio por mor dun fallo de rede ou do servidor ou porque o formato non é compatíbel.',
- mediaErrorEncrypted: 'O medio está cifrado e non temos as chaves para descifralo.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Algo saíu mal.',
- errorDialogDismiss: 'Pechar',
- mediaErrorFallback: 'Produciuse un erro. Por favor, ténteo de novo.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/he.ts b/packages/core/src/core/i18n/locales/he.ts
deleted file mode 100644
index a46dd4aa..00000000
--- a/packages/core/src/core/i18n/locales/he.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'נַגֵּן',
- pause: 'השהה',
- replay: 'נַגֵּן שוב',
- mute: 'השתק',
- unmute: 'בטל השתקה',
- seekForward: 'דילוג קדימה {seconds} שניות',
- seekBackward: 'דילוג אחורה {seconds} שניות',
- enterFullscreen: 'מסך מלא',
- exitFullscreen: 'יציאה ממסך מלא',
- enableCaptions: 'הפעל כתוביות',
- disableCaptions: 'כבה כתוביות',
- enterPictureInPicture: 'תמונה בתוך תמונה',
- exitPictureInPicture: 'יציאה מתמונה בתוך תמונה',
- playingLive: 'משדר חי',
- seekToLiveEdge: 'עבור לשידור חי',
- liveBadge: 'שידור חי',
- startCasting: 'התחל שידור',
- stopCasting: 'עצור שידור',
- connectingCast: 'מתחבר',
- seek: 'חיפוש',
- volume: 'רמת ווליום',
- timeCurrent: 'זמן נוכחי',
- timeDuration: 'זמן כולל',
- timeRemaining: 'זמן נותר',
- timeRemainingPhrase: 'נותרו {duration}',
- playbackRateAria: 'קצב ניגון {rate}',
- timeSliderValueTextRange: '{current} מתוך {duration}',
- volumeSliderValueTextMuted: '{percent}, מושתק',
- indicatorMuted: 'מושתק',
- indicatorVolume: 'עוצמת קול',
- indicatorVolumeWithValue: 'עוצמת קול {value}',
- indicatorCaptionsOn: 'כיתובים פועלים',
- indicatorCaptionsOff: 'כיתובים כבויים',
- indicatorPaused: 'מושהה',
- indicatorPlaying: 'מתנגן',
- indicatorFullscreen: 'מסך מלא',
- indicatorExitFullscreen: 'יציאה ממסך מלא',
- indicatorPictureInPicture: 'תמונה בתוך תמונה',
- indicatorExitPictureInPicture: 'יציאה מתמונה בתוך תמונה',
- mediaErrorAborted: 'ביטלת את השמעת המדיה',
- mediaErrorNetwork: 'שגיאת רשת גרמה להורדת המדיה להיכשל באמצע.',
- mediaErrorDecode: 'השמעת המדיה בוטלה בשל בעית השחטת מידע או מכיוון שהמדיה עשתה שימוש בתכונות שהדפדפן שלך לא תמך בהן.',
- mediaErrorSrcNotSupported: 'לא ניתן לטעון את המדיה, או מכיוון שהרשת או השרת כשלו או מכיוון שהפורמט אינו נתמך.',
- mediaErrorEncrypted: 'המדיה מוצפנת ואין בידינו את המפתח כדי לפענח אותה.',
- mediaErrorCustom: '',
- errorDialogTitle: 'אירעה שגיאה.',
- errorDialogDismiss: 'סְגוֹר',
- mediaErrorFallback: 'אירעה שגיאה. אנא נסה שוב.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/hi.ts b/packages/core/src/core/i18n/locales/hi.ts
deleted file mode 100644
index 080efdb5..00000000
--- a/packages/core/src/core/i18n/locales/hi.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'चलाएँ',
- pause: 'रोकें',
- replay: 'फिर से चलाएँ',
- mute: 'म्यूट करें',
- unmute: 'अनम्यूट करें',
- seekForward: '{seconds} सेकंड आगे बढ़ें',
- seekBackward: '{seconds} सेकंड पीछे जाएं',
- enterFullscreen: 'फ़ुल स्क्रीन',
- exitFullscreen: 'फ़ुल स्क्रीन से बाहर निकलें',
- enableCaptions: 'कैप्शन चालू करें',
- disableCaptions: 'कैप्शन बंद करें',
- enterPictureInPicture: 'पिक्चर-इन-पिक्चर',
- exitPictureInPicture: 'पिक्चर-इन-पिक्चर से बाहर निकलें',
- playingLive: 'लाइव चल रहा है',
- seekToLiveEdge: 'लाइव पर जाएँ',
- liveBadge: 'लाइव',
- startCasting: 'कास्टिंग शुरू करें',
- stopCasting: 'कास्टिंग बंद करें',
- connectingCast: 'कनेक्ट हो रहा है',
- seek: 'खोजें',
- volume: 'वॉल्यूम स्तर',
- timeCurrent: 'वर्तमान समय',
- timeDuration: 'अवधि',
- timeRemaining: 'शेष समय',
- timeRemainingPhrase: '{duration} शेष',
- playbackRateAria: 'चलाने की दर {rate}',
- timeSliderValueTextRange: '{duration} में से {current}',
- volumeSliderValueTextMuted: '{percent}, म्यूट',
- indicatorMuted: 'म्यूट',
- indicatorVolume: 'वॉल्यूम',
- indicatorVolumeWithValue: 'वॉल्यूम {value}',
- indicatorCaptionsOn: 'कैप्शन चालू',
- indicatorCaptionsOff: 'कैप्शन बंद',
- indicatorPaused: 'रोका गया',
- indicatorPlaying: 'चल रहा है',
- indicatorFullscreen: 'पूर्ण स्क्रीन',
- indicatorExitFullscreen: 'पूर्ण स्क्रीन से बाहर',
- indicatorPictureInPicture: 'पिक्चर में पिक्चर',
- indicatorExitPictureInPicture: 'पिक्चर में पिक्चर से बाहर',
- mediaErrorAborted: 'आपने मीडिया प्लेबैक को रोक दिया',
- mediaErrorNetwork: 'एक नेटवर्क त्रुटि के कारण मीडिया डाउनलोड आंशिक रूप से विफल हो गया।',
- mediaErrorDecode:
- 'मीडिया प्लेबैक निरस्त कर दिया गया, कारण: दूषण की समस्या या मीडिया ने उन सुविधाओं का उपयोग किया था जिनका आपके ब्राउज़र ने समर्थन नहीं किया।',
- mediaErrorSrcNotSupported:
- 'मीडिया लोड नहीं किया जा सका, या तो सर्वर या नेटवर्क विफल होने के कारण या प्रारूप समर्थित नहीं होने के कारण।',
- mediaErrorEncrypted: 'मीडिया एन्क्रिप्टेड है और हमारे पास इसे डिक्रिप्ट करने की चाबी नहीं है।',
- mediaErrorCustom: '',
- errorDialogTitle: 'कुछ गलत हुआ।',
- errorDialogDismiss: 'बंद करें',
- mediaErrorFallback: 'एक त्रुटि हुई। कृपया पुनः प्रयास करें।',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/hr.ts b/packages/core/src/core/i18n/locales/hr.ts
deleted file mode 100644
index b7dd41be..00000000
--- a/packages/core/src/core/i18n/locales/hr.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Pusti',
- pause: 'Pauza',
- replay: 'Ponovi',
- mute: 'Prigušen',
- unmute: 'Ne-prigušen',
- seekForward: 'Preskoči naprijed {seconds} sekundi',
- seekBackward: 'Preskoči unatrag {seconds} sekundi',
- enterFullscreen: 'Puni ekran',
- exitFullscreen: 'Izađi iz cijelog zaslona',
- enableCaptions: 'Uključi titlove',
- disableCaptions: 'Isključi titlove',
- enterPictureInPicture: 'Slika u slici',
- exitPictureInPicture: 'Izađi iz slike u slici',
- playingLive: 'Reprodukcija uživo',
- seekToLiveEdge: 'Prijeđi na live',
- liveBadge: 'Uživo',
- startCasting: 'Pokreni emitiranje',
- stopCasting: 'Zaustavi emitiranje',
- connectingCast: 'Povezivanje',
- seek: 'Premotavanje',
- volume: 'Glasnoća',
- timeCurrent: 'Trenutno vrijeme',
- timeDuration: 'Vrijeme trajanja',
- timeRemaining: 'Preostalo vrijeme',
- timeRemainingPhrase: 'Preostalo {duration}',
- playbackRateAria: 'Stopa reprodukcije {rate}',
- timeSliderValueTextRange: '{current} od {duration}',
- volumeSliderValueTextMuted: '{percent}, utišano',
- indicatorMuted: 'Utišano',
- indicatorVolume: 'Glasnoća',
- indicatorVolumeWithValue: 'Glasnoća {value}',
- indicatorCaptionsOn: 'Titlovi uključeni',
- indicatorCaptionsOff: 'Titlovi isključeni',
- indicatorPaused: 'Pauzirano',
- indicatorPlaying: 'Reproducira se',
- indicatorFullscreen: 'Cijeli zaslon',
- indicatorExitFullscreen: 'Izađi iz cijelog zaslona',
- indicatorPictureInPicture: 'Slika u slici',
- indicatorExitPictureInPicture: 'Izađi iz slike u slici',
- mediaErrorAborted: 'Isključili ste reprodukciju videa.',
- mediaErrorNetwork: 'Video se prestao preuzimati zbog greške na mreži.',
- mediaErrorDecode: 'Reprodukcija videa je zaustavljenja zbog greške u formatu ili zbog verzije vašeg pretraživača.',
- mediaErrorSrcNotSupported: 'Video se ne može reproducirati zbog servera, greške u mreži ili je format ne podržan.',
- mediaErrorEncrypted: 'Medij je šifriran i nema ključeva za dešifriranje.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Nešto je pošlo po zlu.',
- errorDialogDismiss: 'Zatvori',
- mediaErrorFallback: 'Došlo je do pogreške. Pokušajte ponovo.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/hu.ts b/packages/core/src/core/i18n/locales/hu.ts
deleted file mode 100644
index 4de33efa..00000000
--- a/packages/core/src/core/i18n/locales/hu.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Lejátszás',
- pause: 'Szünet',
- replay: 'Visszajátszás',
- mute: 'Némítás',
- unmute: 'Némítás kikapcsolva',
- seekForward: 'Ugrás előre {seconds} másodpercet',
- seekBackward: 'Ugrás vissza {seconds} másodpercet',
- enterFullscreen: 'Teljes képernyő',
- exitFullscreen: 'Kilépés a teljes képernyős módból',
- enableCaptions: 'Feliratok bekapcsolása',
- disableCaptions: 'Feliratok kikapcsolása',
- enterPictureInPicture: 'Kép a képben',
- exitPictureInPicture: 'Kilépés kép a képben módból',
- playingLive: 'Élő adás',
- seekToLiveEdge: 'Ugrás az élő adáshoz',
- liveBadge: 'Élő',
- startCasting: 'Vetítés indítása',
- stopCasting: 'Vetítés leállítása',
- connectingCast: 'Csatlakozás',
- seek: 'Teke',
- volume: 'Hangerő',
- timeCurrent: 'Aktuális időpont',
- timeDuration: 'Hossz',
- timeRemaining: 'Hátralévő idő',
- timeRemainingPhrase: '{duration} van hátra',
- playbackRateAria: 'Lejátszási sebesség {rate}',
- timeSliderValueTextRange: '{current} / {duration}',
- volumeSliderValueTextMuted: '{percent}, némítva',
- indicatorMuted: 'Némítva',
- indicatorVolume: 'Hangerő',
- indicatorVolumeWithValue: 'Hangerő {value}',
- indicatorCaptionsOn: 'Feliratok bekapcsolva',
- indicatorCaptionsOff: 'Feliratok kikapcsolva',
- indicatorPaused: 'Szüneteltetve',
- indicatorPlaying: 'Lejátszás',
- indicatorFullscreen: 'Teljes képernyő',
- indicatorExitFullscreen: 'Kilépés teljes képernyőből',
- indicatorPictureInPicture: 'Kép a képben',
- indicatorExitPictureInPicture: 'Kilépés kép a képben módból',
- mediaErrorAborted: 'Leállította a lejátszást',
- mediaErrorNetwork: 'Hálózati hiba miatt a videó részlegesen töltődött le.',
- mediaErrorDecode:
- 'A lejátszás adatsérülés miatt leállt, vagy a videó egyes tulajdonságait a böngészője nem támogatja.',
- mediaErrorSrcNotSupported:
- 'A videó nem tölthető be hálózati vagy kiszolgálói hiba miatt, vagy a formátuma nem támogatott.',
- mediaErrorEncrypted: 'A média titkosítva van és nincsenek kulcsok a visszafejtéshez.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Valami hiba történt.',
- errorDialogDismiss: 'Bezárás',
- mediaErrorFallback: 'Hiba történt. Kérjük, próbálja újra.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/it.ts b/packages/core/src/core/i18n/locales/it.ts
deleted file mode 100644
index 44368738..00000000
--- a/packages/core/src/core/i18n/locales/it.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Riproduci',
- pause: 'Pausa',
- replay: 'Riproduci di nuovo',
- mute: 'Disattiva l’audio',
- unmute: 'Attiva l’audio',
- seekForward: 'Avanti {seconds} secondi',
- seekBackward: 'Indietro {seconds} secondi',
- enterFullscreen: 'Schermo intero',
- exitFullscreen: 'Chiudi Schermo intero',
- enableCaptions: 'Attiva sottotitoli',
- disableCaptions: 'Disattiva sottotitoli',
- enterPictureInPicture: 'Picture-in-Picture',
- exitPictureInPicture: 'Esci dalla modalità Picture-in-Picture',
- playingLive: 'Riproduzione in diretta',
- seekToLiveEdge: 'Vai al live',
- liveBadge: 'In diretta',
- startCasting: 'Avvia trasmissione',
- stopCasting: 'Interrompi trasmissione',
- connectingCast: 'Connessione',
- seek: 'Scorrimento',
- volume: 'Livello del volume',
- timeCurrent: 'Orario attuale',
- timeDuration: 'Durata',
- timeRemaining: 'Tempo rimanente',
- timeRemainingPhrase: 'Restano {duration}',
- playbackRateAria: 'Velocità di riproduzione {rate}',
- timeSliderValueTextRange: '{current} di {duration}',
- volumeSliderValueTextMuted: '{percent}, audio disattivato',
- indicatorMuted: 'Audio disattivato',
- indicatorVolume: 'Livello del volume',
- indicatorVolumeWithValue: 'Livello del volume {value}',
- indicatorCaptionsOn: 'Sottotitoli attivi',
- indicatorCaptionsOff: 'Sottotitoli disattivi',
- indicatorPaused: 'In pausa',
- indicatorPlaying: 'In riproduzione',
- indicatorFullscreen: 'Schermo intero',
- indicatorExitFullscreen: 'Esci da schermo intero',
- indicatorPictureInPicture: 'Picture-in-picture',
- indicatorExitPictureInPicture: 'Esci dalla modalità Picture-in-picture',
- mediaErrorAborted: 'La riproduzione del contenuto multimediale è stata interrotta.',
- mediaErrorNetwork: 'Il download del contenuto multimediale è stato interrotto a causa di un problema rete.',
- mediaErrorDecode:
- 'La riproduzione del contenuto multimediale è stata interrotta a causa di un file danneggiato o per l’utilizzo di impostazioni non supportate dal browser.',
- mediaErrorSrcNotSupported:
- 'Il contenuto multimediale non può essere caricato a causa di un errore nel server o nella rete o perché il formato non viene supportato.',
- mediaErrorEncrypted: 'Il contenuto multimediale è criptato e non disponiamo delle chiavi per decifrarlo.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Qualcosa è andato storto.',
- errorDialogDismiss: 'Chiudi',
- mediaErrorFallback: 'Si è verificato un errore. Riprova.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/ja.ts b/packages/core/src/core/i18n/locales/ja.ts
deleted file mode 100644
index 64e72ac8..00000000
--- a/packages/core/src/core/i18n/locales/ja.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: '再生',
- pause: '一時停止',
- replay: 'もう一度見る',
- mute: 'ミュート',
- unmute: 'サウンドをオン',
- seekForward: '{seconds}秒進む',
- seekBackward: '{seconds}秒戻る',
- enterFullscreen: 'フルスクリーン',
- exitFullscreen: '全画面表示を終了',
- enableCaptions: '字幕を表示',
- disableCaptions: '字幕を非表示',
- enterPictureInPicture: 'ピクチャーインピクチャー',
- exitPictureInPicture: 'ピクチャーインピクチャー機能の終了',
- playingLive: 'ライブ再生中',
- seekToLiveEdge: 'ライブ位置へ移動',
- liveBadge: 'ライブ',
- startCasting: 'キャスト開始',
- stopCasting: 'キャスト停止',
- connectingCast: '接続中',
- seek: 'シーク',
- volume: 'ボリュームレベル',
- timeCurrent: '現在の時間',
- timeDuration: '長さ',
- timeRemaining: '残りの時間',
- timeRemainingPhrase: '残り {duration}',
- playbackRateAria: '再生レート {rate}',
- timeSliderValueTextRange: '{duration}の{current}',
- volumeSliderValueTextMuted: '{percent}、ミュート',
- indicatorMuted: 'ミュート',
- indicatorVolume: '音量',
- indicatorVolumeWithValue: '音量 {value}',
- indicatorCaptionsOn: '字幕オン',
- indicatorCaptionsOff: '字幕オフ',
- indicatorPaused: '一時停止',
- indicatorPlaying: '再生中',
- indicatorFullscreen: '全画面表示',
- indicatorExitFullscreen: '全画面表示解除',
- indicatorPictureInPicture: 'ピクチャーインピクチャー表示',
- indicatorExitPictureInPicture: 'ピクチャーインピクチャー表示解除',
- mediaErrorAborted: '動画再生を中止しました',
- mediaErrorNetwork: 'ネットワーク エラーにより動画のダウンロードが途中で失敗しました',
- mediaErrorDecode:
- '破損の問題、またはお使いのブラウザがサポートしていない機能が動画に使用されていたため、動画の再生が中止されました',
- mediaErrorSrcNotSupported:
- 'サーバーまたはネットワークのエラー、またはフォーマットがサポートされていないため、動画をロードできませんでした',
- mediaErrorEncrypted: 'メディアは暗号化されており、解読するためのキーがありません。',
- mediaErrorCustom: '',
- errorDialogTitle: '問題が発生しました。',
- errorDialogDismiss: '閉じる',
- mediaErrorFallback: 'エラーが発生しました。再度お試しください。',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/ko.ts b/packages/core/src/core/i18n/locales/ko.ts
deleted file mode 100644
index 51d97bb6..00000000
--- a/packages/core/src/core/i18n/locales/ko.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: '재생',
- pause: '일시중지',
- replay: '다시 재생',
- mute: '음소거',
- unmute: '소리 활성화하기',
- seekForward: '{seconds}초 앞으로',
- seekBackward: '{seconds}초 뒤로',
- enterFullscreen: '전체 화면',
- exitFullscreen: '전체 화면 해제',
- enableCaptions: '자막 켜기',
- disableCaptions: '자막 끄기',
- enterPictureInPicture: '화면 속 화면',
- exitPictureInPicture: '화면 속 화면 종료',
- playingLive: '라이브 재생 중',
- seekToLiveEdge: '라이브 지점으로 이동',
- liveBadge: '라이브',
- startCasting: '전송 시작',
- stopCasting: '전송 중지',
- connectingCast: '연결 중',
- seek: '탐색',
- volume: '볼륨 레벨',
- timeCurrent: '현재 시간',
- timeDuration: '지정 기간',
- timeRemaining: '남은 시간',
- timeRemainingPhrase: '{duration} 남음',
- playbackRateAria: '재생 속도 {rate}',
- timeSliderValueTextRange: '{duration} 중 {current}',
- volumeSliderValueTextMuted: '{percent}, 음소거',
- indicatorMuted: '음소거',
- indicatorVolume: '볼륨',
- indicatorVolumeWithValue: '볼륨 {value}',
- indicatorCaptionsOn: '자막 켜짐',
- indicatorCaptionsOff: '자막 꺼짐',
- indicatorPaused: '일시정지',
- indicatorPlaying: '재생 중',
- indicatorFullscreen: '전체 화면',
- indicatorExitFullscreen: '전체 화면 종료',
- indicatorPictureInPicture: '화면 속 화면',
- indicatorExitPictureInPicture: '화면 속 화면 종료',
- mediaErrorAborted: '비디오 재생을 취소했습니다.',
- mediaErrorNetwork: '네트워크 오류로 인하여 비디오 일부를 다운로드하지 못 했습니다.',
- mediaErrorDecode:
- '비디오 재생이 취소됐습니다. 비디오가 손상되었거나 비디오가 사용하는 기능을 브라우저에서 지원하지 않는 것 같습니다.',
- mediaErrorSrcNotSupported:
- '비디오를 로드할 수 없습니다. 서버 혹은 네트워크 오류 때문이거나 지원되지 않는 형식 때문일 수 있습니다.',
- mediaErrorEncrypted: '미디어는 암호화되어 있으며 이를 해독할 키를 갖고 있지 않습니다.',
- mediaErrorCustom: '',
- errorDialogTitle: '문제가 발생했습니다.',
- errorDialogDismiss: '닫기',
- mediaErrorFallback: '오류가 발생했습니다. 다시 시도해 주세요.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/lv.ts b/packages/core/src/core/i18n/locales/lv.ts
deleted file mode 100644
index 082957d7..00000000
--- a/packages/core/src/core/i18n/locales/lv.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Atskaņot',
- pause: 'Pauzēt',
- replay: 'Atkārtot',
- mute: 'Izslēgt skaņu',
- unmute: 'Ieslēgt skaņu',
- seekForward: 'Pārtīt uz priekšu {seconds} sekundes',
- seekBackward: 'Pārtīt atpakaļ {seconds} sekundes',
- enterFullscreen: 'Pilnekrāna režīms',
- exitFullscreen: 'Iziet no pilnekrāna režīma',
- enableCaptions: 'Ieslēgt parakstus',
- disableCaptions: 'Izslēgt parakstus',
- enterPictureInPicture: 'Attēls attēlā',
- exitPictureInPicture: 'Iziet no attēls attēlā',
- playingLive: 'Tiešraide',
- seekToLiveEdge: 'Pāriet uz tiešraidi',
- liveBadge: 'Tiešraide',
- startCasting: 'Sākt pārraidīšanu',
- stopCasting: 'Beigt pārraidīšanu',
- connectingCast: 'Savienošanās',
- seek: 'Meklēt',
- volume: 'Skaļums',
- timeCurrent: 'Esošais laiks',
- timeDuration: 'Ilgums',
- timeRemaining: 'Atlikušais laiks',
- timeRemainingPhrase: 'Atlicis {duration}',
- playbackRateAria: 'Atskaņošanas ātrums {rate}',
- timeSliderValueTextRange: '{current} no {duration}',
- volumeSliderValueTextMuted: '{percent}, izslēgts',
- indicatorMuted: 'Skaņa izslēgta',
- indicatorVolume: 'Skaļums',
- indicatorVolumeWithValue: 'Skaļums {value}',
- indicatorCaptionsOn: 'Paraksti ieslēgti',
- indicatorCaptionsOff: 'Paraksti izslēgti',
- indicatorPaused: 'Pauzēts',
- indicatorPlaying: 'Atskaņo',
- indicatorFullscreen: 'Pilnekrāna režīms',
- indicatorExitFullscreen: 'Iziet no pilnekrāna',
- indicatorPictureInPicture: 'Attēls attēlā',
- indicatorExitPictureInPicture: 'Iziet no attēls attēlā',
- mediaErrorAborted: 'Atskaņošana atcelta',
- mediaErrorNetwork: 'Tīkla kļūdas dēļ, multivides lejupielāde neizdevās.',
- mediaErrorDecode: 'Atskaņošana tika pārtraukta tīkla kļūmes dēļ vai pārlūkprogrammas iespēju trūkuma dēļ.',
- mediaErrorSrcNotSupported:
- 'Neizdevās ielādēt multividi, iespējams severa, vai tīkla kļūmes dēļ, vai neatbalstīta formāta dēļ.',
- mediaErrorEncrypted: 'Multividi nevar atskaņot, jo tas ir kriptēts un nav pieejama dekriptēšanas atslēga.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Kaut kas nogāja greizi.',
- errorDialogDismiss: 'Aizvērt',
- mediaErrorFallback: 'Radās kļūda. Lūdzu, mēģiniet vēlreiz.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/mr.ts b/packages/core/src/core/i18n/locales/mr.ts
deleted file mode 100644
index 035970cb..00000000
--- a/packages/core/src/core/i18n/locales/mr.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'वाजवा',
- pause: 'थांबा',
- replay: 'पुन्हा वाजवा',
- mute: 'म्यूट करा',
- unmute: 'अनम्यूट करा',
- seekForward: 'पुढे जा {seconds} सेकंद',
- seekBackward: 'मागे जा {seconds} सेकंद',
- enterFullscreen: 'संपूर्ण पडदा',
- exitFullscreen: 'संपूर्ण पडद्यातून बाहेर पडा',
- enableCaptions: 'मथळे',
- disableCaptions: 'मथळे बंद',
- enterPictureInPicture: 'पिक्चर-इन-पिक्चर',
- exitPictureInPicture: 'पिक्चर-इन-पिक्चरमधून बाहेर पडा',
- playingLive: 'थेट प्रसारण सुरू आहे',
- seekToLiveEdge: 'थेट प्रसारणाकडे जा',
- liveBadge: 'थेट प्रसारण',
- startCasting: 'कास्टिंग सुरू करा',
- stopCasting: 'कास्टिंग थांबवा',
- connectingCast: 'कनेक्ट होत आहे',
- seek: 'शोध',
- volume: 'आवाज पातळी',
- timeCurrent: 'वर्तमान वेळ',
- timeDuration: 'कालावधी',
- timeRemaining: 'उर्वरित वेळ',
- timeRemainingPhrase: '{duration} उर्वरित',
- playbackRateAria: 'प्लेबॅक दर {rate}',
- timeSliderValueTextRange: '{duration} पैकी {current}',
- volumeSliderValueTextMuted: '{percent}, म्यूट केलेले',
- indicatorMuted: 'म्यूट केलेले',
- indicatorVolume: 'आवाज',
- indicatorVolumeWithValue: 'आवाज {value}',
- indicatorCaptionsOn: 'मथळे चालू',
- indicatorCaptionsOff: 'मथळे बंद',
- indicatorPaused: 'थांबलेले',
- indicatorPlaying: 'वाजत आहे',
- indicatorFullscreen: 'संपूर्ण पडदा',
- indicatorExitFullscreen: 'संपूर्ण पडद्यातून बाहेर',
- indicatorPictureInPicture: 'पिक्चरमध्ये पिक्चर',
- indicatorExitPictureInPicture: 'पिक्चरमध्ये पिक्चरमधून बाहेर',
- mediaErrorAborted: 'तुम्ही मीडिया प्लेबॅक रद्द केला',
- mediaErrorNetwork: 'नेटवर्क त्रुटीमुळे मीडिया डाउनलोड अर्ध्यात अयशस्वी झाला.',
- mediaErrorDecode:
- 'मीडिया प्लेबॅक भ्रष्टाचाराच्या समस्येमुळे किंवा मीडियाने वापरलेल्या वैशिष्ट्यांमुळे तुमचा ब्राउझर सपोर्ट करत नसल्यामुळे रद्द करण्यात आला.',
- mediaErrorSrcNotSupported:
- 'मीडिया लोड करता आला नाही, एकतर सर्व्हर किंवा नेटवर्क अयशस्वी झाल्यामुळे किंवा फॉरमॅट समर्थित नसल्यामुळे.',
- mediaErrorEncrypted: 'मीडिया एन्क्रिप्ट केलेला आहे आणि तो डिक्रिप्ट करण्यासाठी आमच्याकडे कळा नाहीत.',
- mediaErrorCustom: '',
- errorDialogTitle: 'काहीतरी चुकले.',
- errorDialogDismiss: 'बंद',
- mediaErrorFallback: 'एक त्रुटी आली. कृपया पुन्हा प्रयत्न करा.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/nb.ts b/packages/core/src/core/i18n/locales/nb.ts
deleted file mode 100644
index 8fd25cb6..00000000
--- a/packages/core/src/core/i18n/locales/nb.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Spill',
- pause: 'Pause',
- replay: 'Spill om igjen',
- mute: 'Lyd av',
- unmute: 'Lyd på',
- seekForward: 'Hopp frem {seconds} sekunder',
- seekBackward: 'Hopp tilbake {seconds} sekunder',
- enterFullscreen: 'Fullskjerm',
- exitFullscreen: 'Lukk fullskjerm',
- enableCaptions: 'Slå på teksting',
- disableCaptions: 'Slå av teksting',
- enterPictureInPicture: 'Bilde-i-bilde',
- exitPictureInPicture: 'Avslutt bilde-i-bilde',
- playingLive: 'Spiller live',
- seekToLiveEdge: 'Gå til live',
- liveBadge: 'Direkte',
- startCasting: 'Start sending',
- stopCasting: 'Stopp sending',
- connectingCast: 'Kobler til',
- seek: 'Spol',
- volume: 'Volumnivå',
- timeCurrent: 'Aktuell tid',
- timeDuration: 'Varighet',
- timeRemaining: 'Gjenstående tid',
- timeRemainingPhrase: '{duration} igjen',
- playbackRateAria: 'Avspillingshastighet {rate}',
- timeSliderValueTextRange: '{current} av {duration}',
- volumeSliderValueTextMuted: '{percent}, dempet',
- indicatorMuted: 'Dempet',
- indicatorVolume: 'Volum',
- indicatorVolumeWithValue: 'Volum {value}',
- indicatorCaptionsOn: 'Teksting på',
- indicatorCaptionsOff: 'Teksting av',
- indicatorPaused: 'Satt på pause',
- indicatorPlaying: 'Spiller',
- indicatorFullscreen: 'Fullskjerm',
- indicatorExitFullscreen: 'Avslutt fullskjerm',
- indicatorPictureInPicture: 'Bilde i bilde',
- indicatorExitPictureInPicture: 'Avslutt bilde i bilde',
- mediaErrorAborted: 'Du avbrøt avspillingen.',
- mediaErrorNetwork: 'En nettverksfeil avbrøt nedlasting av videoen.',
- mediaErrorDecode:
- 'Videoavspillingen ble avbrudt på grunn av ødelagte data eller fordi videoen ville gjøre noe som nettleseren din ikke har støtte for.',
- mediaErrorSrcNotSupported:
- 'Videoen kunne ikke lastes ned, på grunn av nettverksfeil eller serverfeil, eller fordi formatet ikke er støttet.',
- mediaErrorEncrypted: 'Mediefilen er kryptert og vi mangler nøkler for å dekryptere den.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Noe gikk galt.',
- errorDialogDismiss: 'Lukk',
- mediaErrorFallback: 'En feil oppstod. Vennligst prøv igjen.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/ne.ts b/packages/core/src/core/i18n/locales/ne.ts
deleted file mode 100644
index 09617ecc..00000000
--- a/packages/core/src/core/i18n/locales/ne.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'चलाउनु',
- pause: 'रोक्नु',
- replay: 'फेरि चलाउनु',
- mute: 'म्यूट गर्नुहोस्',
- unmute: 'अनम्यूट गर्नुहोस्',
- seekForward: '{seconds} सेकेन्ड अगाडि सार्नुहोस्',
- seekBackward: '{seconds} सेकेन्ड पछाडि सार्नुहोस्',
- enterFullscreen: 'पूर्ण स्क्रिन',
- exitFullscreen: 'पूर्ण स्क्रिनबाट बाहिर निस्कनुहोस्',
- enableCaptions: 'क्याप्शन',
- disableCaptions: 'क्याप्शन बंद',
- enterPictureInPicture: 'पिक्चर-इन-पिक्चर',
- exitPictureInPicture: 'पिक्चर-इन-पिक्चरबाट बाहिर निस्कनुहोस्',
- playingLive: 'लाइभ चलिरहेको छ',
- seekToLiveEdge: 'लाइभमा जानुहोस्',
- liveBadge: 'लाइव',
- startCasting: 'कास्टिंग सुरू गर्नुहोस्',
- stopCasting: 'कास्टिंग रोक्नुहोस्',
- connectingCast: 'जडान हुँदैछ',
- seek: 'खोज',
- volume: 'वॉल्यूम स्तर',
- timeCurrent: 'हालको समय',
- timeDuration: 'अवधि',
- timeRemaining: 'बाँकी समय',
- timeRemainingPhrase: '{duration} बाँकी',
- playbackRateAria: 'प्लेब्याक दर {rate}',
- timeSliderValueTextRange: '{duration} मध्ये {current}',
- volumeSliderValueTextMuted: '{percent}, म्यूट',
- indicatorMuted: 'म्यूट',
- indicatorVolume: 'भोल्युम',
- indicatorVolumeWithValue: 'भोल्युम {value}',
- indicatorCaptionsOn: 'क्याप्शन चालू',
- indicatorCaptionsOff: 'क्याप्शन बंद',
- indicatorPaused: 'रोकिएको',
- indicatorPlaying: 'चलिरहेको',
- indicatorFullscreen: 'पूर्ण स्क्रिन',
- indicatorExitFullscreen: 'पूर्ण स्क्रिनबाट बाहिर',
- indicatorPictureInPicture: 'पिक्चर इन पिक्चर',
- indicatorExitPictureInPicture: 'पिक्चर इन पिक्चरबाट बाहिर',
- mediaErrorAborted: 'तपाईंले मिडिया प्लेब्याक रद्द गर्नुभयो',
- mediaErrorNetwork: 'नेटवर्क त्रुटिले मिडिया डाउनलोडलाई आधा मार्गमा असफल गर्यो।',
- mediaErrorDecode: 'मिडिया प्लेब्याक अवरुद्ध गरियो, कारण मिडिया दूषित भयो वा तपाईंको ब्राउजरले समर्थन नगरेको सुविधाहरू प्रयोग गर्यो।',
- mediaErrorSrcNotSupported: 'मिडिया लोड गर्न सकिएन, नेटवर्क वा सर्भर विफल भयो वा त्यसको प्रारूप समर्थित छैन।',
- mediaErrorEncrypted: 'मिडिया एन्क्रिप्ट गरिएको छ र हामीसँग डिक्रिप्ट गर्ने कुञ्जीहरू छैनन्।',
- mediaErrorCustom: '',
- errorDialogTitle: 'केही गलत भयो।',
- errorDialogDismiss: 'बन्द गर्नुहोस्',
- mediaErrorFallback: 'एउटा त्रुटि भयो। कृपया पुन: प्रयास गर्नुहोस्।',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/nl.ts b/packages/core/src/core/i18n/locales/nl.ts
deleted file mode 100644
index 976bd4f7..00000000
--- a/packages/core/src/core/i18n/locales/nl.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Afspelen',
- pause: 'Pauzeren',
- replay: 'Opnieuw afspelen',
- mute: 'Dempen',
- unmute: 'Dempen uit',
- seekForward: '{seconds} seconden vooruit',
- seekBackward: '{seconds} seconden terug',
- enterFullscreen: 'Volledig scherm',
- exitFullscreen: 'Volledig scherm sluiten',
- enableCaptions: 'Ondertiteling inschakelen',
- disableCaptions: 'Ondertiteling uitschakelen',
- enterPictureInPicture: 'Picture-in-Picture',
- exitPictureInPicture: 'Picture-in-Picture uit',
- playingLive: 'Speelt live',
- seekToLiveEdge: 'Ga naar live',
- liveBadge: 'Live',
- startCasting: 'Casten starten',
- stopCasting: 'Casten stoppen',
- connectingCast: 'Verbinden',
- seek: 'Spoelen',
- volume: 'Geluidsniveau',
- timeCurrent: 'Huidige tijd',
- timeDuration: 'Tijdsduur',
- timeRemaining: 'Resterende tijd',
- timeRemainingPhrase: 'Nog {duration}',
- playbackRateAria: 'Afspeelsnelheid {rate}',
- timeSliderValueTextRange: '{current} van {duration}',
- volumeSliderValueTextMuted: '{percent}, gedempt',
- indicatorMuted: 'Gedempt',
- indicatorVolume: 'Geluidsniveau',
- indicatorVolumeWithValue: 'Geluidsniveau {value}',
- indicatorCaptionsOn: 'Ondertiteling aan',
- indicatorCaptionsOff: 'Ondertiteling uit',
- indicatorPaused: 'Gepauzeerd',
- indicatorPlaying: 'Wordt afgespeeld',
- indicatorFullscreen: 'Volledig scherm',
- indicatorExitFullscreen: 'Volledig scherm verlaten',
- indicatorPictureInPicture: 'Beeld-in-beeld',
- indicatorExitPictureInPicture: 'Beeld-in-beeld verlaten',
- mediaErrorAborted: 'U heeft het afspelen van de media afgebroken',
- mediaErrorNetwork: 'Een netwerkfout heeft ervoor gezorgd dat het downloaden van de media is mislukt.',
- mediaErrorDecode:
- 'Het afspelen van de media werd afgebroken vanwege een corruptieprobleem of omdat de uw browser de gebruikte mediafuncties niet ondersteund.',
- mediaErrorSrcNotSupported:
- 'De media kon niet worden geladen, doordat de server of het netwerk faalde of doordat het formaat niet wordt ondersteund.',
- mediaErrorEncrypted: 'De media is gecodeerd en we hebben niet de sleutels om het te decoderen.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Er is iets misgegaan.',
- errorDialogDismiss: 'Sluiten',
- mediaErrorFallback: 'Er is een fout opgetreden. Probeer het opnieuw.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/nn.ts b/packages/core/src/core/i18n/locales/nn.ts
deleted file mode 100644
index 335c3a78..00000000
--- a/packages/core/src/core/i18n/locales/nn.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Spel',
- pause: 'Pause',
- replay: 'Spel om att',
- mute: 'Lyd av',
- unmute: 'Lyd på',
- seekForward: 'Hopp fram {seconds} sekund',
- seekBackward: 'Hopp tilbake {seconds} sekund',
- enterFullscreen: 'Fullskjerm',
- exitFullscreen: 'Stenga fullskjerm',
- enableCaptions: 'Slå på teksting',
- disableCaptions: 'Slå av teksting',
- enterPictureInPicture: 'Bilete-i-bilete',
- exitPictureInPicture: 'Avslutt bilete-i-bilete',
- playingLive: 'Spelar live',
- seekToLiveEdge: 'Hopp til live',
- liveBadge: 'Direkte',
- startCasting: 'Start sending',
- stopCasting: 'Stopp sending',
- connectingCast: 'Koplar til',
- seek: 'Spol',
- volume: 'Volumnivå',
- timeCurrent: 'Aktuell tid',
- timeDuration: 'Varigheit',
- timeRemaining: 'Tid attende',
- timeRemainingPhrase: '{duration} att',
- playbackRateAria: 'Avspelingshastigheit {rate}',
- timeSliderValueTextRange: '{current} av {duration}',
- volumeSliderValueTextMuted: '{percent}, dempa',
- indicatorMuted: 'Dempa',
- indicatorVolume: 'Volum',
- indicatorVolumeWithValue: 'Volum {value}',
- indicatorCaptionsOn: 'Teksting på',
- indicatorCaptionsOff: 'Teksting av',
- indicatorPaused: 'Satt på pause',
- indicatorPlaying: 'Spelar',
- indicatorFullscreen: 'Fullskjerm',
- indicatorExitFullscreen: 'Stenga fullskjerm',
- indicatorPictureInPicture: 'Bilete i bilete',
- indicatorExitPictureInPicture: 'Avslutt bilete i bilete',
- mediaErrorAborted: 'Du avbraut avspelinga.',
- mediaErrorNetwork: 'Ein nettverksfeil avbraut nedlasting av videoen.',
- mediaErrorDecode:
- 'Videoavspelinga blei broten på grunn av øydelagde data eller av di videoen ville gjera noe som nettlesaren din ikkje stodar.',
- mediaErrorSrcNotSupported:
- 'Videoen kunne ikkje lastas ned, på grunn av ein nettverksfeil eller serverfeil, eller av di formatet ikkje er stoda.',
- mediaErrorEncrypted: 'Mediefila er kryptert og vi manglar nyklar for å dekryptere ho.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Noko gjekk gale.',
- errorDialogDismiss: 'Lukk',
- mediaErrorFallback: 'Det oppstod ein feil. Ver venleg prøv igjen.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/oc.ts b/packages/core/src/core/i18n/locales/oc.ts
deleted file mode 100644
index a27210b2..00000000
--- a/packages/core/src/core/i18n/locales/oc.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Lectura',
- pause: 'Pausa',
- replay: 'Tornar legir',
- mute: 'Copar lo son',
- unmute: 'Restablir lo son',
- seekForward: 'Avançar de {seconds} segondas',
- seekBackward: 'Recular de {seconds} segondas',
- enterFullscreen: 'Ecran complèt',
- exitFullscreen: 'Sortir del ecran complèt',
- enableCaptions: 'Activar los subtítols',
- disableCaptions: 'Desactivar los subtítols',
- enterPictureInPicture: 'Vidèo incrustada',
- exitPictureInPicture: 'Sortir de la vidèo incrustada',
- playingLive: 'Lectura dirècta',
- seekToLiveEdge: 'Anar al dirècte',
- liveBadge: 'Dirècte',
- startCasting: 'Anar en dirècte',
- stopCasting: 'Aturar la difusion',
- connectingCast: 'Connexion en cors',
- seek: 'Desfilament',
- volume: 'Nivèl del volum',
- timeCurrent: 'Durada passada',
- timeDuration: 'Durada',
- timeRemaining: 'Temps restant',
- timeRemainingPhrase: 'Demòra {duration}',
- playbackRateAria: 'Velocitat de lectura {rate}',
- timeSliderValueTextRange: '{current} sus {duration}',
- volumeSliderValueTextMuted: '{percent}, silenciat',
- indicatorMuted: 'Silenciat',
- indicatorVolume: 'Volum',
- indicatorVolumeWithValue: 'Volum {value}',
- indicatorCaptionsOn: 'Legendas activadas',
- indicatorCaptionsOff: 'Legendas desactivadas',
- indicatorPaused: 'En pausa',
- indicatorPlaying: 'En lectura',
- indicatorFullscreen: 'Ecran complèt',
- indicatorExitFullscreen: "Sortir de l'ecran complèt",
- indicatorPictureInPicture: 'Vidèo incrustada',
- indicatorExitPictureInPicture: 'Sortir de la vidèo incrustada',
- mediaErrorAborted: 'Avètz copat la lectura del mèdia.',
- mediaErrorNetwork: 'Una error de ret a provocat un fracàs del telecargament.',
- mediaErrorDecode:
- "La lectura del mèdia es copada a causa d'un problèma de corrupcion o perque lo mèdia utiliza de foncionalitats pas suportadas pel navigador.",
- mediaErrorSrcNotSupported:
- 'Lo mèdia a pas pogut èsser cargat, siá perque lo servidor o lo ret a fracassat siá perque lo format es pas compatible.',
- mediaErrorEncrypted: 'Lo mèdia es chifrat e avèm pas las claus per lo deschifrar.',
- mediaErrorCustom: '',
- errorDialogTitle: "Quaucarèn s'es mal passat.",
- errorDialogDismiss: 'Tampar',
- mediaErrorFallback: "Una error s'es produsida. Provatz d'un autre còp.",
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/pl.ts b/packages/core/src/core/i18n/locales/pl.ts
deleted file mode 100644
index 1f300b0c..00000000
--- a/packages/core/src/core/i18n/locales/pl.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Odtwórz',
- pause: 'Wstrzymaj',
- replay: 'Odtwórz ponownie',
- mute: 'Wycisz',
- unmute: 'Wyłącz wyciszenie',
- seekForward: 'Przewiń do przodu o {seconds} s',
- seekBackward: 'Przewiń do tyłu o {seconds} s',
- enterFullscreen: 'Pełny ekran',
- exitFullscreen: 'Wyjdź z trybu pełnoekranowego',
- enableCaptions: 'Włącz napisy',
- disableCaptions: 'Wyłącz napisy',
- enterPictureInPicture: 'Obraz w obrazie',
- exitPictureInPicture: 'Wyjdź z trybu obraz w obrazie',
- playingLive: 'Odtwarzanie na żywo',
- seekToLiveEdge: 'Przejdź na transmisję na żywo',
- liveBadge: 'Na żywo',
- startCasting: 'Rozpocznij przesyłanie',
- stopCasting: 'Zatrzymaj przesyłanie',
- connectingCast: 'Łączenie',
- seek: 'Przewijanie',
- volume: 'Poziom głośności',
- timeCurrent: 'Aktualny czas',
- timeDuration: 'Czas trwania',
- timeRemaining: 'Pozostały czas',
- timeRemainingPhrase: 'Pozostało {duration}',
- playbackRateAria: 'Prędkość odtwarzania {rate}',
- timeSliderValueTextRange: '{current} z {duration}',
- volumeSliderValueTextMuted: '{percent}, wyciszono',
- indicatorMuted: 'Wyciszono',
- indicatorVolume: 'Głośność',
- indicatorVolumeWithValue: 'Głośność {value}',
- indicatorCaptionsOn: 'Napisy włączone',
- indicatorCaptionsOff: 'Napisy wyłączone',
- indicatorPaused: 'Wstrzymano',
- indicatorPlaying: 'Odtwarzanie',
- indicatorFullscreen: 'Pełny ekran',
- indicatorExitFullscreen: 'Wyjdź z pełnego ekranu',
- indicatorPictureInPicture: 'Obraz w obrazie',
- indicatorExitPictureInPicture: 'Wyjdź z obrazu w obrazie',
- mediaErrorAborted: 'Odtwarzanie zostało przerwane',
- mediaErrorNetwork: 'Błąd sieci spowodował częściowe niepowodzenie pobierania materiału wideo.',
- mediaErrorDecode:
- 'Odtwarzanie materiału wideo zostało przerwane z powodu uszkodzonego pliku wideo lub z powodu użycia funkcji multimediów nieobsługiwanych przez Twoją przeglądarkę.',
- mediaErrorSrcNotSupported:
- 'Materiał wideo nie może zostać załadowany, ponieważ wystąpił problem z serwerem lub siecią albo format materiału wideo nie jest obsługiwany',
- mediaErrorEncrypted: 'Materiał jest zaszyfrowany, a nie mamy kluczy do jego odszyfrowania.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Coś poszło nie tak.',
- errorDialogDismiss: 'Zamknij',
- mediaErrorFallback: 'Wystąpił błąd. Spróbuj ponownie.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/pt-BR.ts b/packages/core/src/core/i18n/locales/pt-BR.ts
deleted file mode 100644
index 48f12d1b..00000000
--- a/packages/core/src/core/i18n/locales/pt-BR.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Tocar',
- pause: 'Pausar',
- replay: 'Tocar novamente',
- mute: 'Mudo',
- unmute: 'Ativar o som',
- seekForward: 'Avançar {seconds} segundos',
- seekBackward: 'Retroceder {seconds} segundos',
- enterFullscreen: 'Tela Cheia',
- exitFullscreen: 'Sair da tela cheia',
- enableCaptions: 'Ativar legendas',
- disableCaptions: 'Desativar legendas',
- enterPictureInPicture: 'Picture-in-Picture',
- exitPictureInPicture: 'Sair de Picture-in-Picture',
- playingLive: 'Reproduzindo ao vivo',
- seekToLiveEdge: 'Ir para o ao vivo',
- liveBadge: 'Ao vivo',
- startCasting: 'Iniciar transmissão',
- stopCasting: 'Parar transmissão',
- connectingCast: 'Conectando',
- seek: 'Buscar',
- volume: 'Nível de volume',
- timeCurrent: 'Tempo',
- timeDuration: 'Duração',
- timeRemaining: 'Tempo Restante',
- timeRemainingPhrase: 'Restam {duration}',
- playbackRateAria: 'Velocidade {rate}',
- timeSliderValueTextRange: '{current} de {duration}',
- volumeSliderValueTextMuted: '{percent}, silenciado',
- indicatorMuted: 'Silenciado',
- indicatorVolume: 'Nível de volume',
- indicatorVolumeWithValue: 'Nível de volume {value}',
- indicatorCaptionsOn: 'Legendas ativadas',
- indicatorCaptionsOff: 'Legendas desativadas',
- indicatorPaused: 'Pausado',
- indicatorPlaying: 'Reproduzindo',
- indicatorFullscreen: 'Tela cheia',
- indicatorExitFullscreen: 'Sair da tela cheia',
- indicatorPictureInPicture: 'Picture-in-picture',
- indicatorExitPictureInPicture: 'Sair do picture-in-picture',
- mediaErrorAborted: 'Você parou a execução do vídeo.',
- mediaErrorNetwork: 'Um erro na rede causou falha durante o download da mídia.',
- mediaErrorDecode:
- 'A reprodução foi interrompida devido à um problema de mídia corrompida ou porque a mídia utiliza funções que seu navegador não suporta.',
- mediaErrorSrcNotSupported:
- 'A mídia não pode ser carregada, por uma falha de rede ou servidor ou o formato não é suportado.',
- mediaErrorEncrypted: 'A mídia está criptografada e não temos as chaves para descriptografar.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Algo deu errado.',
- errorDialogDismiss: 'Fechar',
- mediaErrorFallback: 'Ocorreu um erro. Tente novamente.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/pt-PT.ts b/packages/core/src/core/i18n/locales/pt-PT.ts
deleted file mode 100644
index 71df5de8..00000000
--- a/packages/core/src/core/i18n/locales/pt-PT.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Reproduzir',
- pause: 'Pausar',
- replay: 'Reiniciar',
- mute: 'Desativar som',
- unmute: 'Ativar som',
- seekForward: 'Avançar {seconds} segundos',
- seekBackward: 'Recuar {seconds} segundos',
- enterFullscreen: 'Ecrã inteiro',
- exitFullscreen: 'Sair de ecrã inteiro',
- enableCaptions: 'Ativar legendas',
- disableCaptions: 'Desativar legendas',
- enterPictureInPicture: 'Imagem em imagem',
- exitPictureInPicture: 'Sair de imagem em imagem',
- playingLive: 'A reproduzir em direto',
- seekToLiveEdge: 'Ir para o em direto',
- liveBadge: 'Em direto',
- startCasting: 'Iniciar transmissão',
- stopCasting: 'Parar transmissão',
- connectingCast: 'A ligar',
- seek: 'Procurar',
- volume: 'Nível de volume',
- timeCurrent: 'Tempo Atual',
- timeDuration: 'Duração',
- timeRemaining: 'Tempo Restante',
- timeRemainingPhrase: 'Restam {duration}',
- playbackRateAria: 'Velocidade de reprodução {rate}',
- timeSliderValueTextRange: '{current} de {duration}',
- volumeSliderValueTextMuted: '{percent}, sem som',
- indicatorMuted: 'Sem som',
- indicatorVolume: 'Nível de volume',
- indicatorVolumeWithValue: 'Nível de volume {value}',
- indicatorCaptionsOn: 'Legendas ativas',
- indicatorCaptionsOff: 'Legendas desativadas',
- indicatorPaused: 'Em pausa',
- indicatorPlaying: 'A reproduzir',
- indicatorFullscreen: 'Ecrã inteiro',
- indicatorExitFullscreen: 'Sair de ecrã inteiro',
- indicatorPictureInPicture: 'Imagem em imagem',
- indicatorExitPictureInPicture: 'Sair de imagem em imagem',
- mediaErrorAborted: 'Parou a reprodução do vídeo.',
- mediaErrorNetwork: 'Um erro na rede fez o vídeo falhar parcialmente.',
- mediaErrorDecode:
- 'A reprodução foi interrompida por um problema com o vídeo ou porque o formato não é compatível com o seu navegador.',
- mediaErrorSrcNotSupported:
- 'O vídeo não pode ser carregado, ou porque houve um problema na rede ou no servidor, ou porque o formato do vídeo não é compatível.',
- mediaErrorEncrypted: 'O vídeo está encriptado e não há uma chave para o desencriptar.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Algo correu mal.',
- errorDialogDismiss: 'Fechar',
- mediaErrorFallback: 'Ocorreu um erro. Por favor tente novamente.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/pt.ts b/packages/core/src/core/i18n/locales/pt.ts
deleted file mode 100644
index 8607aa03..00000000
--- a/packages/core/src/core/i18n/locales/pt.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Alias for `lang="pt"` — same strings as `pt-BR`. */
-export { default } from './pt-BR';
diff --git a/packages/core/src/core/i18n/locales/ro.ts b/packages/core/src/core/i18n/locales/ro.ts
deleted file mode 100644
index a8d18527..00000000
--- a/packages/core/src/core/i18n/locales/ro.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Redare',
- pause: 'Pauză',
- replay: 'Reluare',
- mute: 'Fără sunet',
- unmute: 'Cu sunet',
- seekForward: 'Salt înainte {seconds} secunde',
- seekBackward: 'Salt înapoi {seconds} secunde',
- enterFullscreen: 'Ecran complet',
- exitFullscreen: 'Ieșire ecran complet',
- enableCaptions: 'Activează subtitrările',
- disableCaptions: 'Dezactivează subtitrările',
- enterPictureInPicture: 'Imagine în imagine',
- exitPictureInPicture: 'Închidere imagine în imagine',
- playingLive: 'Redare în direct',
- seekToLiveEdge: 'Salt la direct',
- liveBadge: 'În direct',
- startCasting: 'Pornire transmisie',
- stopCasting: 'Oprire transmisie',
- connectingCast: 'Se conectează',
- seek: 'Derulare',
- volume: 'Nivel volum',
- timeCurrent: 'Ora curentă',
- timeDuration: 'Durată',
- timeRemaining: 'Timp rămas',
- timeRemainingPhrase: 'Mai rămân {duration}',
- playbackRateAria: 'Rată de redare {rate}',
- timeSliderValueTextRange: '{current} din {duration}',
- volumeSliderValueTextMuted: '{percent}, mut',
- indicatorMuted: 'Mut',
- indicatorVolume: 'Volum',
- indicatorVolumeWithValue: 'Volum {value}',
- indicatorCaptionsOn: 'Subtitrări activate',
- indicatorCaptionsOff: 'Subtitrări dezactivate',
- indicatorPaused: 'Pauză',
- indicatorPlaying: 'Se redă',
- indicatorFullscreen: 'Ecran complet',
- indicatorExitFullscreen: 'Ieșire ecran complet',
- indicatorPictureInPicture: 'Imagine în imagine',
- indicatorExitPictureInPicture: 'Închidere imagine în imagine',
- mediaErrorAborted: 'Ați abandonat redarea media',
- mediaErrorNetwork: 'O eroare de rețea a provocat eșecul descărcării conținutului media în timpul procesului.',
- mediaErrorDecode:
- 'Redarea media a fost întreruptă din cauza conținutului corupt sau din cauza faptului că acest conținut media folosește funcții pe care browserul dvs. nu le acceptă.',
- mediaErrorSrcNotSupported:
- 'Conținutul media nu a putut fi încărcat, fie pentru că serverul sau rețeaua a eșuat, fie pentru că formatul nu este acceptat.',
- mediaErrorEncrypted: 'Conținutul media este criptat și nu avem cheile pentru decriptare.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Ceva a mers greșit.',
- errorDialogDismiss: 'Închidere',
- mediaErrorFallback: 'A apărut o eroare. Vă rugăm să încercați din nou.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/ru.ts b/packages/core/src/core/i18n/locales/ru.ts
deleted file mode 100644
index 455e6694..00000000
--- a/packages/core/src/core/i18n/locales/ru.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Воспроизвести',
- pause: 'Приостановить',
- replay: 'Воспроизвести снова',
- mute: 'Без звука',
- unmute: 'Со звуком',
- seekForward: 'На {seconds} секунд вперед',
- seekBackward: 'На {seconds} секунд назад',
- enterFullscreen: 'Полноэкранный режим',
- exitFullscreen: 'Выйти из полноэкранного режима',
- enableCaptions: 'Включить субтитры',
- disableCaptions: 'Отключить субтитры',
- enterPictureInPicture: 'Картинка в картинке',
- exitPictureInPicture: 'Закрыть картинку в картинке',
- playingLive: 'Прямой эфир',
- seekToLiveEdge: 'Перейти к прямому эфиру',
- liveBadge: 'Прямой эфир',
- startCasting: 'Начать трансляцию',
- stopCasting: 'Остановить трансляцию',
- connectingCast: 'Подключение',
- seek: 'Перемотка',
- volume: 'Уровень громкости',
- timeCurrent: 'Текущее время',
- timeDuration: 'Продолжительность',
- timeRemaining: 'Оставшееся время',
- timeRemainingPhrase: 'Осталось {duration}',
- playbackRateAria: 'Скорость воспроизведения {rate}',
- timeSliderValueTextRange: '{current} из {duration}',
- volumeSliderValueTextMuted: '{percent}, без звука',
- indicatorMuted: 'Без звука',
- indicatorVolume: 'Громкость',
- indicatorVolumeWithValue: 'Громкость {value}',
- indicatorCaptionsOn: 'Субтитры включены',
- indicatorCaptionsOff: 'Субтитры выключены',
- indicatorPaused: 'На паузе',
- indicatorPlaying: 'Воспроизведение',
- indicatorFullscreen: 'Полноэкранный режим',
- indicatorExitFullscreen: 'Выйти из полноэкранного режима',
- indicatorPictureInPicture: 'Картинка в картинке',
- indicatorExitPictureInPicture: 'Выйти из режима «картинка в картинке»',
- mediaErrorAborted: 'Вы прервали воспроизведение видео',
- mediaErrorNetwork: 'Ошибка сети вызвала сбой во время загрузки.',
- mediaErrorDecode:
- 'Воспроизведение прервано из-за повреждения либо в связи с тем, что видео использует функции, неподдерживаемые вашим браузером.',
- mediaErrorSrcNotSupported:
- 'Не удалось загрузить видео из-за сетевого или серверного сбоя либо неподдерживаемого формата видео.',
- mediaErrorEncrypted: 'Видео зашифровано, а у нас нет ключей для его расшифровки.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Что-то пошло не так.',
- errorDialogDismiss: 'Закрыть',
- mediaErrorFallback: 'Произошла ошибка. Попробуйте снова.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/sk.ts b/packages/core/src/core/i18n/locales/sk.ts
deleted file mode 100644
index bf063686..00000000
--- a/packages/core/src/core/i18n/locales/sk.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Prehrať',
- pause: 'Pozastaviť',
- replay: 'Prehrať znova',
- mute: 'Stlmiť',
- unmute: 'Zrušiť stlmenie',
- seekForward: 'Posunúť dopredu o {seconds} s',
- seekBackward: 'Posunúť dozadu o {seconds} s',
- enterFullscreen: 'Režim celej obrazovky',
- exitFullscreen: 'Zavrieť režim celej obrazovky',
- enableCaptions: 'Zapnúť titulky',
- disableCaptions: 'Vypnúť titulky',
- enterPictureInPicture: 'Obraz v obraze',
- exitPictureInPicture: 'Zavrieť obraz v obraze',
- playingLive: 'Prehráva sa naživo',
- seekToLiveEdge: 'Prejsť na živé vysielanie',
- liveBadge: 'Naživo',
- startCasting: 'Spustiť prenos',
- stopCasting: 'Zastaviť prenos',
- connectingCast: 'Pripájam',
- seek: 'Posun',
- volume: 'Úroveň hlasitosti',
- timeCurrent: 'Aktuálny čas',
- timeDuration: 'Čas trvania',
- timeRemaining: 'Zostávajúci čas',
- timeRemainingPhrase: 'Zostáva {duration}',
- playbackRateAria: 'Rýchlosť prehrávania {rate}',
- timeSliderValueTextRange: '{current} z {duration}',
- volumeSliderValueTextMuted: '{percent}, stlmené',
- indicatorMuted: 'Stlmené',
- indicatorVolume: 'Hlasitosť',
- indicatorVolumeWithValue: 'Hlasitosť {value}',
- indicatorCaptionsOn: 'Popisky zapnuté',
- indicatorCaptionsOff: 'Popisky vypnuté',
- indicatorPaused: 'Pozastavené',
- indicatorPlaying: 'Prehráva sa',
- indicatorFullscreen: 'Celá obrazovka',
- indicatorExitFullscreen: 'Zavrieť celú obrazovku',
- indicatorPictureInPicture: 'Obraz v obraze',
- indicatorExitPictureInPicture: 'Zavrieť obraz v obraze',
- mediaErrorAborted: 'Prerušili ste prehrávanie',
- mediaErrorNetwork: 'Sťahovanie súboru bolo zrušené pre chybu na sieti.',
- mediaErrorDecode:
- 'Prehrávanie súboru bolo prerušené pre poškodené dáta, alebo súbor používa vlastnosti, ktoré váš prehliadač nepodporuje.',
- mediaErrorSrcNotSupported:
- 'Súbor sa nepodarilo načítať pre chybu servera, sieťového pripojenia, alebo je formát súboru nepodporovaný.',
- mediaErrorEncrypted: 'Súbor je zašifrovaný a nie je k dispozícii kľúč na rozšifrovanie.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Niečo sa pokazilo.',
- errorDialogDismiss: 'Zatvoriť',
- mediaErrorFallback: 'Vyskytla sa chyba. Skúste to znova.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/sl.ts b/packages/core/src/core/i18n/locales/sl.ts
deleted file mode 100644
index 9806c815..00000000
--- a/packages/core/src/core/i18n/locales/sl.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Predvajaj',
- pause: 'Začasno ustavi',
- replay: 'Predvajaj ponovno',
- mute: 'Izključi zvok',
- unmute: 'Vključi zvok',
- seekForward: 'Preskoči naprej {seconds} sekund',
- seekBackward: 'Preskoči nazaj {seconds} sekund',
- enterFullscreen: 'Celozaslonski prikaz',
- exitFullscreen: 'Izhod iz celozaslonskega prikaza',
- enableCaptions: 'Vklopi podnapise',
- disableCaptions: 'Izklopi podnapise',
- enterPictureInPicture: 'Slika v sliki',
- exitPictureInPicture: 'Izhod iz slike v sliki',
- playingLive: 'Predvajanje v živo',
- seekToLiveEdge: 'Skoči na live',
- liveBadge: 'V živo',
- startCasting: 'Začni predvajanje na zaslonu',
- stopCasting: 'Ustavi predvajanje na zaslonu',
- connectingCast: 'Povezovanje',
- seek: 'Premikanje',
- volume: 'Raven glasnosti',
- timeCurrent: 'Trenutni čas',
- timeDuration: 'Trajanje',
- timeRemaining: 'Preostali čas',
- timeRemainingPhrase: 'Preostane {duration}',
- playbackRateAria: 'Hitrost predvajanja {rate}',
- timeSliderValueTextRange: '{current} od {duration}',
- volumeSliderValueTextMuted: '{percent}, izklopljeno',
- indicatorMuted: 'Izklopljeno',
- indicatorVolume: 'Glasnost',
- indicatorVolumeWithValue: 'Glasnost {value}',
- indicatorCaptionsOn: 'Zvočni zapis vklopljen',
- indicatorCaptionsOff: 'Zvočni zapis izklopljen',
- indicatorPaused: 'Začasno ustavljeno',
- indicatorPlaying: 'Predvaja',
- indicatorFullscreen: 'Celozaslonski prikaz',
- indicatorExitFullscreen: 'Izhod iz celozaslonskega prikaza',
- indicatorPictureInPicture: 'Slika v sliki',
- indicatorExitPictureInPicture: 'Izhod iz slike v sliki',
- mediaErrorAborted: 'Prekinili ste predvajanje.',
- mediaErrorNetwork: 'Prenos multimedijske datoteke ni uspel zaradi napake v omrežju.',
- mediaErrorDecode:
- 'Predvajanje datoteke je bilo prekinjeno zaradi napak v datoteki ali ker uporablja funkcije, ki jih brskalnik ne podpira.',
- mediaErrorSrcNotSupported:
- 'Multimedijske datoteke ni bilo mogoče naložiti zaradi napake na strežniku oziroma omrežju ali ker ta oblika ni podprta.',
- mediaErrorEncrypted: 'Datoteka je šifrirana in predvajalnik nima ključev za njeno dešifriranje.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Nekaj je šlo narobe.',
- errorDialogDismiss: 'Zapri',
- mediaErrorFallback: 'Prišlo je do napake. Poskusite znova.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/sr.ts b/packages/core/src/core/i18n/locales/sr.ts
deleted file mode 100644
index 227dabc8..00000000
--- a/packages/core/src/core/i18n/locales/sr.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Pusti',
- pause: 'Pauza',
- replay: 'Ponovi',
- mute: 'Utišaj',
- unmute: 'Poništi utišavanje',
- seekForward: 'Premotaj unapred {seconds} sekundi',
- seekBackward: 'Premotaj unazad {seconds} sekundi',
- enterFullscreen: 'Pun ekran',
- exitFullscreen: 'Izađi iz punog ekrana',
- enableCaptions: 'Uključi titlove',
- disableCaptions: 'Isključi titlove',
- enterPictureInPicture: 'Slika u slici',
- exitPictureInPicture: 'Izađi iz slike u slici',
- playingLive: 'Reprodukcija uživo',
- seekToLiveEdge: 'Idi na live',
- liveBadge: 'Uživo',
- startCasting: 'Počni emitovanje',
- stopCasting: 'Zaustavi emitovanje',
- connectingCast: 'Povezivanje',
- seek: 'Premotavanje',
- volume: 'Jačina zvuka',
- timeCurrent: 'Trenutno vreme',
- timeDuration: 'Vreme trajanja',
- timeRemaining: 'Preostalo vreme',
- timeRemainingPhrase: 'Preostalo {duration}',
- playbackRateAria: 'Stopa reprodukcije {rate}',
- timeSliderValueTextRange: '{current} od {duration}',
- volumeSliderValueTextMuted: '{percent}, utišano',
- indicatorMuted: 'Utišano',
- indicatorVolume: 'Jačina zvuka',
- indicatorVolumeWithValue: 'Jačina zvuka {value}',
- indicatorCaptionsOn: 'Titlovi uključeni',
- indicatorCaptionsOff: 'Titlovi isključeni',
- indicatorPaused: 'Pauzirano',
- indicatorPlaying: 'Reprodukuje se',
- indicatorFullscreen: 'Pun ekran',
- indicatorExitFullscreen: 'Izađi iz punog ekrana',
- indicatorPictureInPicture: 'Slika u slici',
- indicatorExitPictureInPicture: 'Izađi iz slike u slici',
- mediaErrorAborted: 'Isključili ste reprodukciju videa.',
- mediaErrorNetwork: 'Video se prestao preuzimati zbog greške na mreži.',
- mediaErrorDecode: 'Reprodukcija videa je zaustavljena zbog greške u formatu ili zbog verzije vašeg pretraživača.',
- mediaErrorSrcNotSupported: 'Video se ne može reproducirati zbog servera, greške u mreži ili format nije podržan.',
- mediaErrorEncrypted: 'Medij je šifrovan i nema ključeva za dešifrovanje.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Nešto je pošlo po zlu.',
- errorDialogDismiss: 'Zatvori',
- mediaErrorFallback: 'Došlo je do greške. Molimo pokušajte ponovo.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/sv.ts b/packages/core/src/core/i18n/locales/sv.ts
deleted file mode 100644
index d0545785..00000000
--- a/packages/core/src/core/i18n/locales/sv.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Spela',
- pause: 'Pausa',
- replay: 'Spela upp igen',
- mute: 'Ljud av',
- unmute: 'Ljud på',
- seekForward: 'Hoppa framåt {seconds} sekunder',
- seekBackward: 'Hoppa bakåt {seconds} sekunder',
- enterFullscreen: 'Fullskärm',
- exitFullscreen: 'Avsluta fullskärm',
- enableCaptions: 'Aktivera textning',
- disableCaptions: 'Inaktivera textning',
- enterPictureInPicture: 'Bild-i-bild',
- exitPictureInPicture: 'Avsluta bild-i-bild',
- playingLive: 'Spelar live',
- seekToLiveEdge: 'Gå till live',
- liveBadge: 'Live',
- startCasting: 'Starta casting',
- stopCasting: 'Stoppa casting',
- connectingCast: 'Ansluter',
- seek: 'Spola',
- volume: 'Volymnivå',
- timeCurrent: 'Aktuell tid',
- timeDuration: 'Total tid',
- timeRemaining: 'Återstående tid',
- timeRemainingPhrase: '{duration} kvar',
- playbackRateAria: 'Uppspelningshastighet {rate}',
- timeSliderValueTextRange: '{current} av {duration}',
- volumeSliderValueTextMuted: '{percent}, tystat',
- indicatorMuted: 'Tystat',
- indicatorVolume: 'Volym',
- indicatorVolumeWithValue: 'Volym {value}',
- indicatorCaptionsOn: 'Text på',
- indicatorCaptionsOff: 'Text av',
- indicatorPaused: 'Pausad',
- indicatorPlaying: 'Spelar',
- indicatorFullscreen: 'Fullskärm',
- indicatorExitFullscreen: 'Avsluta fullskärm',
- indicatorPictureInPicture: 'Bild i bild',
- indicatorExitPictureInPicture: 'Avsluta bild i bild',
- mediaErrorAborted: 'Du har avbrutit videouppspelningen.',
- mediaErrorNetwork: 'Ett nätverksfel gjorde att nedladdningen av videon avbröts.',
- mediaErrorDecode:
- 'Uppspelningen avbröts på grund av att videon är skadad, eller också för att videon använder funktioner som din webbläsare inte stöder.',
- mediaErrorSrcNotSupported:
- 'Det gick inte att ladda videon, antingen på grund av ett server- eller nätverksfel, eller för att formatet inte stöds.',
- mediaErrorEncrypted: 'Mediat är krypterat och vi har inte nycklarna för att dekryptera det.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Något gick fel.',
- errorDialogDismiss: 'Stäng',
- mediaErrorFallback: 'Ett fel uppstod. Försök igen.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/te.ts b/packages/core/src/core/i18n/locales/te.ts
deleted file mode 100644
index ae233f01..00000000
--- a/packages/core/src/core/i18n/locales/te.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'ప్లే',
- pause: 'పాజ్',
- replay: 'రీప్లే',
- mute: 'మ్యూట్',
- unmute: 'అన్మ్యూట్ చేయండి',
- seekForward: '{seconds} సెకన్లు ముందుకు',
- seekBackward: '{seconds} సెకన్లు వెనుకకు',
- enterFullscreen: 'పూర్తి స్క్రీన్',
- exitFullscreen: 'పూర్తి స్క్రీన్ నుండి నిష్క్రమించండి',
- enableCaptions: 'శీర్షికలు',
- disableCaptions: 'శీర్షికలు ఆఫ్ చేయండి',
- enterPictureInPicture: 'పిక్చర్-ఇన్-పిక్చర్',
- exitPictureInPicture: 'పిక్చర్-ఇన్-పిక్చర్ నుండి నిష్క్రమించండి',
- playingLive: 'లైవ్లో ప్లే అవుతోంది',
- seekToLiveEdge: 'లైవ్కు వెళ్లండి',
- liveBadge: 'లైవ్',
- startCasting: 'కాస్టింగ్ ప్రారంభించండి',
- stopCasting: 'కాస్టింగ్ ఆపండి',
- connectingCast: 'కనెక్ట్ అవుతోంది',
- seek: 'శోధించు',
- volume: 'వాల్యూమ్ స్థాయి',
- timeCurrent: 'ప్రస్తుత సమయం',
- timeDuration: 'వ్యవధి',
- timeRemaining: 'మిగిలిన సమయం',
- timeRemainingPhrase: '{duration} మిగిలి',
- playbackRateAria: 'ప్లేబ్యాక్ రేట్ {rate}',
- timeSliderValueTextRange: '{current} యొక్క {duration}',
- volumeSliderValueTextMuted: '{percent}, మ్యూట్ చేయబడింది',
- indicatorMuted: 'మ్యూట్ చేయబడింది',
- indicatorVolume: 'వాల్యూమ్',
- indicatorVolumeWithValue: 'వాల్యూమ్ {value}',
- indicatorCaptionsOn: 'శీర్షికలు ఆన్',
- indicatorCaptionsOff: 'శీర్షికలు ఆఫ్',
- indicatorPaused: 'పాజ్ చేయబడింది',
- indicatorPlaying: 'ప్లే అవుతోంది',
- indicatorFullscreen: 'పూర్తి స్క్రీన్',
- indicatorExitFullscreen: 'పూర్తి స్క్రీన్ నుండి నిష్క్రమించండి',
- indicatorPictureInPicture: 'పిక్చర్ ఇన్ పిక్చర్',
- indicatorExitPictureInPicture: 'పిక్చర్ ఇన్ పిక్చర్ నుండి నిష్క్రమించండి',
- mediaErrorAborted: 'మీరు మీడియా ప్లేబ్యాక్ను రద్దు చేశారు',
- mediaErrorNetwork: 'నెట్వర్క్ లోపం వలన మీడియా డౌన్లోడ్ విఫలమైంది.',
- mediaErrorDecode: 'అవినీతి సమస్య కారణంగా లేదా మీ బ్రౌజర్ మద్దతు ఇవ్వని లక్షణాలను మీడియా ఉపయోగించినందున మీడియా ప్లేబ్యాక్ నిలిపివేయబడింది.',
- mediaErrorSrcNotSupported: 'సర్వర్ లేదా నెట్వర్క్ విఫలమైనందున లేదా ఫార్మాట్కు మద్దతు లేనందున మీడియాను లోడ్ చేయడం సాధ్యం కాలేదు.',
- mediaErrorEncrypted: 'మీడియా గుప్తీకరించబడింది మరియు దానిని డీక్రిప్ట్ చేయడానికి మాకు కీలు లేవు.',
- mediaErrorCustom: '',
- errorDialogTitle: 'ఏదో తప్పు జరిగింది.',
- errorDialogDismiss: 'మూసివేయండి',
- mediaErrorFallback: 'ఒక లోపం సంభవించింది. దయచేసి మళ్ళీ ప్రయత్నించండి.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/th.ts b/packages/core/src/core/i18n/locales/th.ts
deleted file mode 100644
index 85b1609a..00000000
--- a/packages/core/src/core/i18n/locales/th.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'เล่น',
- pause: 'หยุดชั่วคราว',
- replay: 'เล่นซ้ำ',
- mute: 'ปิดเสียง',
- unmute: 'ยกเลิกการปิดเสียง',
- seekForward: 'ข้ามไปข้างหน้า {seconds} วินาที',
- seekBackward: 'ข้ามไปข้างหลัง {seconds} วินาที',
- enterFullscreen: 'แบบเต็มหน้าจอ',
- exitFullscreen: 'ออกจากเต็มหน้าจอ',
- enableCaptions: 'เปิดคำบรรยาย',
- disableCaptions: 'ปิดคำบรรยาย',
- enterPictureInPicture: 'การเล่นภาพควบคู่',
- exitPictureInPicture: 'ออกจากการเล่นภาพควบคู่',
- playingLive: 'กำลังถ่ายทอดสด',
- seekToLiveEdge: 'ไปยังจุดถ่ายทอดสด',
- liveBadge: 'ถ่ายทอดสด',
- startCasting: 'เริ่มแคสต์',
- stopCasting: 'หยุดแคสต์',
- connectingCast: 'กำลังเชื่อมต่อ',
- seek: 'ค้นหา',
- volume: 'ระดับเสียง',
- timeCurrent: 'เวลาปัจจุบัน',
- timeDuration: 'ระยะเวลา',
- timeRemaining: 'เวลาที่เหลือ',
- timeRemainingPhrase: 'เหลือ {duration}',
- playbackRateAria: 'อัตราการเล่น {rate}',
- timeSliderValueTextRange: '{current} ของ {duration}',
- volumeSliderValueTextMuted: '{percent}, ปิดเสียง',
- indicatorMuted: 'ปิดเสียงแล้ว',
- indicatorVolume: 'ระดับเสียง',
- indicatorVolumeWithValue: 'ระดับเสียง {value}',
- indicatorCaptionsOn: 'เปิดคำอธิบายภาพ',
- indicatorCaptionsOff: 'ปิดคำอธิบายภาพ',
- indicatorPaused: 'หยุดชั่วคราว',
- indicatorPlaying: 'กำลังเล่น',
- indicatorFullscreen: 'เต็มหน้าจอ',
- indicatorExitFullscreen: 'ออกจากเต็มหน้าจอ',
- indicatorPictureInPicture: 'ภาพซ้อนภาพ',
- indicatorExitPictureInPicture: 'ออกจากภาพซ้อนภาพ',
- mediaErrorAborted: 'คุณยกเลิกการเล่นสื่อแล้ว',
- mediaErrorNetwork: 'ข้อผิดพลาดของเครือข่ายทำให้การดาวน์โหลดสื่อไม่สำเร็จเป็นบางส่วน',
- mediaErrorDecode: 'การเล่นสื่อถูกยกเลิกเนื่องจากปัญหาเกี่ยวกับความเสียหาย หรือเนื่องจากสื่อใช้ฟีเจอร์ที่เบราว์เซอร์ของคุณไม่รองรับ',
- mediaErrorSrcNotSupported: 'ไม่สามารถโหลดสื่อได้ โดยอาจเป็นเพราะเซิร์ฟเวอร์หรือเครือข่ายล้มเหลว หรือเพราะรูปแบบไม่ได้รับการรองรับ',
- mediaErrorEncrypted: 'สื่อถูกเข้ารหัสลับแล้ว และเราไม่มีคีย์ที่จะถอดรหัสลับดังกล่าว',
- mediaErrorCustom: '',
- errorDialogTitle: 'เกิดข้อผิดพลาด',
- errorDialogDismiss: 'ปิด',
- mediaErrorFallback: 'เกิดข้อผิดพลาด กรุณาลองอีกครั้ง',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/tr.ts b/packages/core/src/core/i18n/locales/tr.ts
deleted file mode 100644
index d13ce240..00000000
--- a/packages/core/src/core/i18n/locales/tr.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Oynat',
- pause: 'Duraklat',
- replay: 'Yeniden Oynat',
- mute: 'Sessiz',
- unmute: 'Sesi Aç',
- seekForward: '{seconds} saniye ileri sar',
- seekBackward: '{seconds} saniye geri sar',
- enterFullscreen: 'Tam Ekran',
- exitFullscreen: 'Tam Ekrandan Çık',
- enableCaptions: 'Altyazıları aç',
- disableCaptions: 'Altyazıları kapat',
- enterPictureInPicture: 'Mini oynatıcı',
- exitPictureInPicture: 'Mini oynatıcıdan çık',
- playingLive: 'Canlı oynatılıyor',
- seekToLiveEdge: 'Canlıya git',
- liveBadge: 'Canlı',
- startCasting: 'Yansıtmayı başlat',
- stopCasting: 'Yansıtmayı durdur',
- connectingCast: 'Bağlanıyor',
- seek: 'Ara',
- volume: 'Ses Düzeyi',
- timeCurrent: 'Süre',
- timeDuration: 'Toplam Süre',
- timeRemaining: 'Kalan Süre',
- timeRemainingPhrase: '{duration} kaldı',
- playbackRateAria: 'Oynatma Hızı {rate}',
- timeSliderValueTextRange: '{current} / {duration}',
- volumeSliderValueTextMuted: '{percent}, sessiz',
- indicatorMuted: 'Sessiz',
- indicatorVolume: 'Ses',
- indicatorVolumeWithValue: 'Ses {value}',
- indicatorCaptionsOn: 'Altyazılar açık',
- indicatorCaptionsOff: 'Altyazılar kapalı',
- indicatorPaused: 'Duraklatıldı',
- indicatorPlaying: 'Oynatılıyor',
- indicatorFullscreen: 'Tam ekran',
- indicatorExitFullscreen: 'Tam ekrandan çık',
- indicatorPictureInPicture: 'Resim içinde resim',
- indicatorExitPictureInPicture: 'Resim içinde resimden çık',
- mediaErrorAborted: 'Medyayı oynatmayı iptal ettiniz',
- mediaErrorNetwork: 'Medya indirme işleminin kısmen başarısız olmasına neden olan bir ağ sorunu oluştu.',
- mediaErrorDecode:
- 'Medya oynatma, bir bozulma sorunu nedeniyle veya medya, tarayıcınızın desteklemediği özellikleri kullandığı için durduruldu.',
- mediaErrorSrcNotSupported: 'Sunucu veya ağ hatasından ya da biçim desteklenmediğinden medya yüklenemedi.',
- mediaErrorEncrypted: 'Medya, şifrelenmiş bir kaynaktan geliyor ve oynatmak için gerekli anahtar bulunamadı.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Bir şeyler ters gitti.',
- errorDialogDismiss: 'Kapat',
- mediaErrorFallback: 'Bir hata oluştu. Lütfen tekrar deneyin.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/uk.ts b/packages/core/src/core/i18n/locales/uk.ts
deleted file mode 100644
index d06df670..00000000
--- a/packages/core/src/core/i18n/locales/uk.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Відтворити',
- pause: 'Призупинити',
- replay: 'Відтворити знову',
- mute: 'Без звуку',
- unmute: 'Зі звуком',
- seekForward: 'Перемотати вперед на {seconds} с',
- seekBackward: 'Перемотати назад на {seconds} с',
- enterFullscreen: 'Повноекранний режим',
- exitFullscreen: 'Вийти з повноекранного режиму',
- enableCaptions: 'Увімкнути субтитри',
- disableCaptions: 'Вимкнути субтитри',
- enterPictureInPicture: 'Зображення в зображенні',
- exitPictureInPicture: 'Вийти із режиму зображення в зображенні',
- playingLive: 'Прямий ефір',
- seekToLiveEdge: 'Перейти до прямого ефіру',
- liveBadge: 'На живо',
- startCasting: 'Почати трансляцію',
- stopCasting: 'Зупинити трансляцію',
- connectingCast: 'Підключення',
- seek: 'Перемотка',
- volume: 'Рівень гучності',
- timeCurrent: 'Поточний час',
- timeDuration: 'Тривалість',
- timeRemaining: 'Час, що залишився',
- timeRemainingPhrase: 'Залишилось {duration}',
- playbackRateAria: 'Швидкість відтворення {rate}',
- timeSliderValueTextRange: '{current} з {duration}',
- volumeSliderValueTextMuted: '{percent}, вимкнено',
- indicatorMuted: 'Вимкнено',
- indicatorVolume: 'Гучність',
- indicatorVolumeWithValue: 'Гучність {value}',
- indicatorCaptionsOn: 'Підписи увімкнено',
- indicatorCaptionsOff: 'Підписи вимкнено',
- indicatorPaused: 'На паузі',
- indicatorPlaying: 'Відтворення',
- indicatorFullscreen: 'Повноекранний режим',
- indicatorExitFullscreen: 'Вийти з повноекранного режиму',
- indicatorPictureInPicture: 'Зображення в зображенні',
- indicatorExitPictureInPicture: 'Вийти із режиму зображення в зображенні',
- mediaErrorAborted: 'Ви припинили відтворення відео',
- mediaErrorNetwork: 'Помилка мережі викликала збій під час завантаження відео.',
- mediaErrorDecode:
- "Відтворення відео було припинено через пошкодження або у зв'язку з тим, що відео використовує функції, які не підтримуються вашим браузером.",
- mediaErrorSrcNotSupported:
- 'Неможливо завантажити відео через мережевий чи серверний збій або формат не підтримується.',
- mediaErrorEncrypted: 'Відео в зашифрованому вигляді, і ми не маємо ключі для розшифровки.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Щось пішло не так.',
- errorDialogDismiss: 'Закрити',
- mediaErrorFallback: 'Сталася помилка. Будь ласка, спробуйте ще раз.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/vi.ts b/packages/core/src/core/i18n/locales/vi.ts
deleted file mode 100644
index 9c7c4db8..00000000
--- a/packages/core/src/core/i18n/locales/vi.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: 'Phát',
- pause: 'Tạm dừng',
- replay: 'Phát lại',
- mute: 'Tắt tiếng',
- unmute: 'Bật âm thanh',
- seekForward: 'Tua tới {seconds} giây',
- seekBackward: 'Tua lại {seconds} giây',
- enterFullscreen: 'Toàn màn hình',
- exitFullscreen: 'Thoát toàn màn hình',
- enableCaptions: 'Bật phụ đề',
- disableCaptions: 'Tắt chú thích',
- enterPictureInPicture: 'Màn hình trong màn hình',
- exitPictureInPicture: 'Thoát màn hình trong màn hình',
- playingLive: 'Đang phát trực tiếp',
- seekToLiveEdge: 'Tua tới trực tiếp',
- liveBadge: 'Trực tiếp',
- startCasting: 'Bắt đầu truyền phát',
- stopCasting: 'Dừng truyền phát',
- connectingCast: 'Đang kết nối',
- seek: 'Tua',
- volume: 'Mức âm lượng',
- timeCurrent: 'Thời gian hiện tại',
- timeDuration: 'Độ dài',
- timeRemaining: 'Thời gian còn lại',
- timeRemainingPhrase: 'Còn {duration}',
- playbackRateAria: 'Tỉ lệ phát lại {rate}',
- timeSliderValueTextRange: '{current} của {duration}',
- volumeSliderValueTextMuted: '{percent}, đã tắt tiếng',
- indicatorMuted: 'Đã tắt tiếng',
- indicatorVolume: 'Âm lượng',
- indicatorVolumeWithValue: 'Âm lượng {value}',
- indicatorCaptionsOn: 'Bật chú thích',
- indicatorCaptionsOff: 'Tắt chú thích',
- indicatorPaused: 'Đã tạm dừng',
- indicatorPlaying: 'Đang phát',
- indicatorFullscreen: 'Toàn màn hình',
- indicatorExitFullscreen: 'Thoát toàn màn hình',
- indicatorPictureInPicture: 'Màn hình trong màn hình',
- indicatorExitPictureInPicture: 'Thoát màn hình trong màn hình',
- mediaErrorAborted: 'Bạn đã hủy việc phát lại media.',
- mediaErrorNetwork: 'Một lỗi mạng dẫn đến việc tải media bị lỗi.',
- mediaErrorDecode: 'Phát media đã bị hủy do một sai lỗi hoặc media sử dụng những tính năng trình duyệt không hỗ trợ.',
- mediaErrorSrcNotSupported: 'Video không tải được, mạng hay server có lỗi hoặc định dạng không được hỗ trợ.',
- mediaErrorEncrypted: 'Media đã được mã hóa và chúng tôi không có khóa để giải mã.',
- mediaErrorCustom: '',
- errorDialogTitle: 'Đã xảy ra lỗi.',
- errorDialogDismiss: 'Đóng',
- mediaErrorFallback: 'Đã xảy ra lỗi. Vui lòng thử lại.',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/zh-CN.ts b/packages/core/src/core/i18n/locales/zh-CN.ts
deleted file mode 100644
index 2b19c8df..00000000
--- a/packages/core/src/core/i18n/locales/zh-CN.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: '播放',
- pause: '暂停',
- replay: '重新播放',
- mute: '静音',
- unmute: '开启音效',
- seekForward: '快进 {seconds} 秒',
- seekBackward: '快退 {seconds} 秒',
- enterFullscreen: '全屏',
- exitFullscreen: '退出全屏',
- enableCaptions: '开启字幕',
- disableCaptions: '关闭字幕',
- enterPictureInPicture: '画中画',
- exitPictureInPicture: '退出画中画',
- playingLive: '正在直播',
- seekToLiveEdge: '跳转到直播',
- liveBadge: '直播',
- startCasting: '开始投屏',
- stopCasting: '停止投屏',
- connectingCast: '正在连接',
- seek: '定位',
- volume: '音量',
- timeCurrent: '当前时间',
- timeDuration: '时长',
- timeRemaining: '剩余时间',
- timeRemainingPhrase: '剩余 {duration}',
- playbackRateAria: '播放速度 {rate}',
- timeSliderValueTextRange: '{current},总时长 {duration}',
- volumeSliderValueTextMuted: '{percent},已静音',
- indicatorMuted: '已静音',
- indicatorVolume: '音量',
- indicatorVolumeWithValue: '音量 {value}',
- indicatorCaptionsOn: '字幕已开启',
- indicatorCaptionsOff: '字幕已关闭',
- indicatorPaused: '已暂停',
- indicatorPlaying: '正在播放',
- indicatorFullscreen: '全屏',
- indicatorExitFullscreen: '退出全屏',
- indicatorPictureInPicture: '画中画',
- indicatorExitPictureInPicture: '退出画中画',
- mediaErrorAborted: '视频播放被终止',
- mediaErrorNetwork: '网络错误导致视频下载中途失败。',
- mediaErrorDecode: '由于视频文件损坏或是该视频使用了你的浏览器不支持的功能,播放终止。',
- mediaErrorSrcNotSupported: '视频因格式不支持或者服务器或网络的问题无法加载。',
- mediaErrorEncrypted: '视频已加密,无法解密。',
- mediaErrorCustom: '',
- errorDialogTitle: '出现问题。',
- errorDialogDismiss: '关闭',
- mediaErrorFallback: '发生错误,请重试。',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/zh-TW.ts b/packages/core/src/core/i18n/locales/zh-TW.ts
deleted file mode 100644
index b95767aa..00000000
--- a/packages/core/src/core/i18n/locales/zh-TW.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { Translations } from '../types';
-
-export default {
- play: '播放',
- pause: '暫停',
- replay: '重播',
- mute: '靜音',
- unmute: '開啟音效',
- seekForward: '快轉 {seconds} 秒',
- seekBackward: '倒轉 {seconds} 秒',
- enterFullscreen: '全螢幕',
- exitFullscreen: '退出全螢幕',
- enableCaptions: '開啟字幕',
- disableCaptions: '關閉字幕',
- enterPictureInPicture: '子母畫面',
- exitPictureInPicture: '離開子母畫面',
- playingLive: '正在直播',
- seekToLiveEdge: '跳轉至直播',
- liveBadge: '直播',
- startCasting: '開始投屏',
- stopCasting: '停止投屏',
- connectingCast: '連線中',
- seek: '定位',
- volume: '音量',
- timeCurrent: '目前時間',
- timeDuration: '總共時間',
- timeRemaining: '剩餘時間',
- timeRemainingPhrase: '剩餘 {duration}',
- playbackRateAria: '播放速率 {rate}',
- timeSliderValueTextRange: '{current},總時長 {duration}',
- volumeSliderValueTextMuted: '{percent},已靜音',
- indicatorMuted: '已靜音',
- indicatorVolume: '音量',
- indicatorVolumeWithValue: '音量 {value}',
- indicatorCaptionsOn: '字幕已開啟',
- indicatorCaptionsOff: '字幕已關閉',
- indicatorPaused: '已暫停',
- indicatorPlaying: '正在播放',
- indicatorFullscreen: '全螢幕',
- indicatorExitFullscreen: '退出全螢幕',
- indicatorPictureInPicture: '子母畫面',
- indicatorExitPictureInPicture: '離開子母畫面',
- mediaErrorAborted: '影片播放已終止',
- mediaErrorNetwork: '網路錯誤導致影片下載失敗。',
- mediaErrorDecode: '由於影片檔案損毀或是該影片使用了您的瀏覽器不支援的功能,已終止播放媒體。',
- mediaErrorSrcNotSupported: '因格式不支援、伺服器或網路的問題無法載入媒體。',
- mediaErrorEncrypted: '媒體已加密,無法解密。',
- mediaErrorCustom: '',
- errorDialogTitle: '發生問題。',
- errorDialogDismiss: '關閉',
- mediaErrorFallback: '發生錯誤,請重試。',
-} as const satisfies Partial;
diff --git a/packages/core/src/core/i18n/locales/zh.ts b/packages/core/src/core/i18n/locales/zh.ts
deleted file mode 100644
index 6bf47c16..00000000
--- a/packages/core/src/core/i18n/locales/zh.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Alias for `lang="zh"` — same strings as `zh-CN`. */
-export { default } from './zh-CN';
diff --git a/packages/core/src/core/i18n/registry.ts b/packages/core/src/core/i18n/registry.ts
deleted file mode 100644
index 18635060..00000000
--- a/packages/core/src/core/i18n/registry.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import en from './locales/en';
-import type { Translations } from './types';
-
-const registry = new Map>();
-const subscribers = new Set<() => void>();
-
-function notify(): void {
- for (const cb of subscribers) {
- cb();
- }
-}
-
-function normalizeLocaleTag(tag: string): string {
- return tag.trim().replaceAll('_', '-').toLowerCase();
-}
-
-/** Strip unicode locale extension sequences (`-u-…`) before any private-use `-x-` block. */
-function stripUnicodeExtensions(tag: string): string {
- const xIdx = tag.indexOf('-x-');
- const beforePrivateUse = xIdx === -1 ? tag : tag.slice(0, xIdx);
- const uIdx = beforePrivateUse.indexOf('-u-');
- if (uIdx === -1) {
- return tag;
- }
- return tag.slice(0, uIdx) + (xIdx === -1 ? '' : tag.slice(xIdx));
-}
-
-/** Registry map key: normalized tag with unicode extensions removed (same base as {@link localeLookupChain}). */
-export function canonicalLocaleRegistryKey(locale: string): string {
- return stripUnicodeExtensions(normalizeLocaleTag(locale));
-}
-
-/**
- * Most-specific-first BCP 47 lookup tags (normalized). Always ends with `en` when missing from the truncated chain.
- *
- * @example `es-419-u-nu-latn` → `['es-419', 'es', 'en']`
- */
-export function localeLookupChain(locale: string): string[] {
- const base = canonicalLocaleRegistryKey(locale);
- if (!base) {
- return ['en'];
- }
-
- const segments = base.split('-').filter(Boolean);
- const chain: string[] = [];
-
- for (let len = segments.length; len >= 1; len--) {
- chain.push(segments.slice(0, len).join('-'));
- }
-
- const out: string[] = [];
- const seen = new Set();
- for (const tag of chain) {
- if (!seen.has(tag)) {
- seen.add(tag);
- out.push(tag);
- }
- }
- if (!seen.has('en')) {
- out.push('en');
- }
-
- return out;
-}
-
-function mergeLookupChain(chain: string[]): Translations {
- const merged: Partial = {};
- for (let i = chain.length - 1; i >= 0; i--) {
- const tag = chain[i]!;
- const layer = registry.get(tag);
- if (layer) {
- Object.assign(merged, layer);
- }
- }
- return merged as Translations;
-}
-
-export function registerI18n(locale: string, translations: Partial): void {
- const tag = canonicalLocaleRegistryKey(locale);
- const existing = registry.get(tag) ?? {};
- registry.set(tag, { ...existing, ...translations });
- notify();
-}
-
-export function getI18nTranslations(locale: string): Translations {
- return mergeLookupChain(localeLookupChain(locale));
-}
-
-export function onI18nRegistryChange(callback: () => void): () => void {
- subscribers.add(callback);
- return () => {
- subscribers.delete(callback);
- };
-}
-
-export function hasRegisteredI18n(locale: string): boolean {
- return registry.has(canonicalLocaleRegistryKey(locale));
-}
-
-/** Restores the registry to built-in English only (test isolation). */
-export function resetI18nRegistryForTesting(): void {
- registry.clear();
- subscribers.clear();
- registry.set('en', { ...en });
-}
-
-registry.set('en', { ...en });
diff --git a/packages/core/src/core/i18n/resolve-translation-phrase.ts b/packages/core/src/core/i18n/resolve-translation-phrase.ts
deleted file mode 100644
index b7b1949d..00000000
--- a/packages/core/src/core/i18n/resolve-translation-phrase.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import type { TranslationKeyOrString, Translator } from './types';
-
-/** Resolves a {@link TranslationKeyOrString} with optional template params via a translator. */
-export function resolveTranslationPhrase(
- translator: Translator,
- phrase: TranslationKeyOrString,
- params?: Record
-): string {
- const translate = translator as (key: string, params?: unknown) => string;
- return params !== undefined ? translate(phrase, params) : translate(phrase);
-}
diff --git a/packages/core/src/core/i18n/tests/browser-translation.test.ts b/packages/core/src/core/i18n/tests/browser-translation.test.ts
deleted file mode 100644
index 1eca0635..00000000
--- a/packages/core/src/core/i18n/tests/browser-translation.test.ts
+++ /dev/null
@@ -1,196 +0,0 @@
-import { afterEach, describe, expect, it, vi } from 'vitest';
-
-import {
- getBrowserTranslations,
- resetBrowserTranslationCacheForTesting,
- resolveBrowserTranslationTarget,
- shouldAttemptBrowserTranslation,
-} from '../browser-translation';
-import { registerI18n, resetI18nRegistryForTesting } from '../registry';
-
-type MockAvailability = 'available' | 'downloadable' | 'unavailable';
-
-function installMockTranslator(
- options: { availability?: MockAvailability; translate?: (text: string) => string | Promise } = {}
-) {
- const availability = options.availability ?? 'available';
- const translate = options.translate ?? ((text: string) => `translated:${text}`);
-
- const Translator = {
- availability: vi.fn(async () => availability),
- create: vi.fn(async () => ({
- translate: vi.fn(async (text: string) => translate(text)),
- })),
- };
-
- Object.defineProperty(globalThis, 'Translator', {
- configurable: true,
- writable: true,
- value: Translator,
- });
-
- return Translator;
-}
-
-function removeMockTranslator(): void {
- Reflect.deleteProperty(globalThis, 'Translator');
-}
-
-describe('resolveBrowserTranslationTarget', () => {
- it('returns the first non-en tag in the lookup chain', () => {
- expect(resolveBrowserTranslationTarget('fr-CA')).toBe('fr-ca');
- expect(resolveBrowserTranslationTarget('en')).toBeUndefined();
- expect(resolveBrowserTranslationTarget('en-US')).toBeUndefined();
- expect(resolveBrowserTranslationTarget('en-GB')).toBeUndefined();
- });
-});
-
-describe('shouldAttemptBrowserTranslation', () => {
- afterEach(() => {
- resetI18nRegistryForTesting();
- });
-
- it('skips English', () => {
- expect(shouldAttemptBrowserTranslation('en', [])).toBe(false);
- expect(shouldAttemptBrowserTranslation('en-US', [])).toBe(false);
- });
-
- it('skips when a non-English lazy built-in tag loaded', () => {
- expect(shouldAttemptBrowserTranslation('xx', ['xx'])).toBe(false);
- expect(shouldAttemptBrowserTranslation('es', ['en', 'es'])).toBe(false);
- });
-
- it('attempts when only English lazy tags loaded', () => {
- expect(shouldAttemptBrowserTranslation('xx', ['en'])).toBe(true);
- });
-
- it('skips when a non-en tag in the chain is registered', () => {
- registerI18n('es', { play: 'Ir' });
- expect(shouldAttemptBrowserTranslation('es-MX', [])).toBe(false);
- });
-
- it('attempts when locale has no pack', () => {
- expect(shouldAttemptBrowserTranslation('xx', [])).toBe(true);
- });
-});
-
-describe('getBrowserTranslations', () => {
- afterEach(() => {
- resetI18nRegistryForTesting();
- resetBrowserTranslationCacheForTesting();
- removeMockTranslator();
- });
-
- it('returns empty when Translator API is missing', async () => {
- removeMockTranslator();
- await expect(getBrowserTranslations('fr')).resolves.toEqual({});
- });
-
- it('returns empty when availability is not available', async () => {
- const translator = installMockTranslator({ availability: 'downloadable' });
- await expect(getBrowserTranslations('fr')).resolves.toEqual({});
- expect(translator.create).not.toHaveBeenCalled();
- });
-
- it('translates en values and maps them back to keys', async () => {
- installMockTranslator({
- translate: (text) => (text === 'Play' ? 'Jouer' : `translated:${text}`),
- });
-
- const result = await getBrowserTranslations('fr');
- expect(result.play).toBe('Jouer');
- expect(result.pause).toBe('translated:Pause');
- });
-
- it('preserves {param} placeholders in translated strings', async () => {
- installMockTranslator({
- translate: (text) => `FR:${text}`,
- });
-
- const result = await getBrowserTranslations('fr');
- expect(result.seekForward).toBe('FR:Seek forward {seconds} seconds');
- });
-
- it('masks named placeholders as numeric slots for whole-string translation', async () => {
- const translatedInputs: string[] = [];
- installMockTranslator({
- translate: (text) => {
- translatedInputs.push(text);
- if (text === 'Seek backward {0} seconds') {
- return 'Mencari mundur {0} detik';
- }
- if (text === 'Seek forward {0} seconds') {
- return 'Mencari maju {0} detik';
- }
- return text;
- },
- });
-
- const result = await getBrowserTranslations('fr');
- expect(translatedInputs).toContain('Seek backward {0} seconds');
- expect(translatedInputs.some((text) => text.includes('{seconds}'))).toBe(false);
- expect(result.seekBackward).toBe('Mencari mundur {seconds} detik');
- expect(result.seekForward).toBe('Mencari maju {seconds} detik');
- });
-
- it('restores slots when the translator adds spaces inside braces', async () => {
- installMockTranslator({
- translate: (text) => (text === 'Playback rate {0}' ? 'Kecepatan pemutaran { 0 }' : text),
- });
-
- const result = await getBrowserTranslations('fr');
- expect(result.playbackRateAria).toBe('Kecepatan pemutaran {rate}');
- });
-
- it('interpolates seek seconds after browser translation', async () => {
- installMockTranslator({
- translate: (text) => (text === 'Seek forward {0} seconds' ? 'Mencari maju {0} detik' : text),
- });
-
- const { createTranslator } = await import('../translator');
- const result = await getBrowserTranslations('fr');
- const t = createTranslator({ ...result, play: 'Play' } as import('../types').Translations, 'fr');
-
- expect(t('seekForward', { seconds: 10 })).toBe('Mencari maju 10 detik');
- });
-
- it('caches results per target language', async () => {
- const translator = installMockTranslator();
- await getBrowserTranslations('fr');
- await getBrowserTranslations('fr');
-
- expect(translator.create).toHaveBeenCalledTimes(1);
- });
-
- it('downloads and translates when downloadIfNeeded is true', async () => {
- const onStart = vi.fn();
- const onFinish = vi.fn();
- const translator = installMockTranslator({
- availability: 'downloadable',
- translate: (text) => (text === 'Play' ? 'Main' : text),
- });
-
- const result = await getBrowserTranslations('id', {
- downloadIfNeeded: true,
- onModelDownload: { start: onStart, finish: onFinish },
- });
- expect(translator.create).toHaveBeenCalledTimes(1);
- expect(result.play).toBe('Main');
- expect(onStart).toHaveBeenCalledWith('id');
- expect(onFinish).toHaveBeenCalledWith('id');
- });
-
- it('does not invoke download callbacks when the model is already available', async () => {
- const onStart = vi.fn();
- const onFinish = vi.fn();
- installMockTranslator();
-
- await getBrowserTranslations('fr', {
- downloadIfNeeded: true,
- onModelDownload: { start: onStart, finish: onFinish },
- });
-
- expect(onStart).not.toHaveBeenCalled();
- expect(onFinish).not.toHaveBeenCalled();
- });
-});
diff --git a/packages/core/src/core/i18n/tests/load-locale.test.ts b/packages/core/src/core/i18n/tests/load-locale.test.ts
deleted file mode 100644
index afb74e0e..00000000
--- a/packages/core/src/core/i18n/tests/load-locale.test.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import { loadLocale } from '../load-locale';
-import { registerI18n, resetI18nRegistryForTesting } from '../registry';
-
-describe('loadLocale', () => {
- it('returns undefined for unknown tags', async () => {
- await expect(loadLocale('xx-unknown')).resolves.toBeUndefined();
- });
-
- it('skips tags already registered via registerI18n', async () => {
- registerI18n('es', { play: 'Custom' });
- await expect(loadLocale('es')).resolves.toBeUndefined();
- resetI18nRegistryForTesting();
- });
-
- it('loads shipped locale packs by tag', async () => {
- const es = await loadLocale('es');
- expect(es?.play).toBe('Reproducir');
- });
-
- it('loads alias tags', async () => {
- const pt = await loadLocale('pt');
- expect(pt?.play).toBeTruthy();
- });
-
- it('loads regional tags regardless of casing', async () => {
- const ptBr = await loadLocale('pt-br');
- const zhTw = await loadLocale('zh-TW');
- expect(ptBr?.play).toBeTruthy();
- expect(zhTw?.play).toBeTruthy();
- });
-
- it('loads regional tags via the locale lookup chain', async () => {
- const esMx = await loadLocale('es-MX');
- expect(esMx?.play).toBe('Reproducir');
- });
-
- it('loads packs when unicode locale extensions are present', async () => {
- const zhCn = await loadLocale('zh-CN-u-nu-hans');
- expect(zhCn?.play).toBeTruthy();
- });
-});
diff --git a/packages/core/src/core/i18n/tests/registry.test.ts b/packages/core/src/core/i18n/tests/registry.test.ts
deleted file mode 100644
index b88fdf87..00000000
--- a/packages/core/src/core/i18n/tests/registry.test.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { beforeEach, describe, expect, it, vi } from 'vitest';
-
-import en from '../locales/en';
-import {
- getI18nTranslations,
- hasRegisteredI18n,
- localeLookupChain,
- onI18nRegistryChange,
- registerI18n,
- resetI18nRegistryForTesting,
-} from '../registry';
-
-describe('i18n registry', () => {
- beforeEach(() => {
- resetI18nRegistryForTesting();
- });
-
- it('merges multiple registerI18n calls for one locale', () => {
- registerI18n('es', { play: 'Ir' });
- registerI18n('es', { pause: 'Pausa' });
- const es = getI18nTranslations('es');
- expect(es.play).toBe('Ir');
- expect(es.pause).toBe('Pausa');
- });
-
- it('inherits from English for keys missing in the locale', () => {
- registerI18n('es', { play: 'Ir' });
- const es = getI18nTranslations('es');
- expect(es.mute).toBe(en.mute);
- });
-
- it('resolves es-419-u-nu-latn to the es layer before English', () => {
- registerI18n('es', { play: 'ES-generic' });
- const merged = getI18nTranslations('es-419-u-nu-latn');
- expect(merged.play).toBe('ES-generic');
- });
-
- it('registers unicode-extension locales under the same key as localeLookupChain', () => {
- registerI18n('es-u-nu-latn', { play: 'Latin-numerals ES' });
- expect(hasRegisteredI18n('es-u-nu-latn')).toBe(true);
- expect(getI18nTranslations('es-u-nu-latn').play).toBe('Latin-numerals ES');
- });
-
- it('does not strip -u- subtags inside a private-use extension', () => {
- registerI18n('en-x-u-k0', { play: 'Private U' });
- registerI18n('en-x-u-k1', { play: 'Private U2' });
- expect(getI18nTranslations('en-x-u-k0').play).toBe('Private U');
- expect(getI18nTranslations('en-x-u-k1').play).toBe('Private U2');
- });
-
- it('walks zh-Hant-HK → zh-Hant → zh → en', () => {
- expect(localeLookupChain('zh-Hant-HK')).toEqual(['zh-hant-hk', 'zh-hant', 'zh', 'en']);
- registerI18n('zh', { play: 'ZH' });
- registerI18n('zh-Hant', { pause: 'Hant' });
- registerI18n('zh-Hant-HK', { replay: 'HK' });
- const merged = getI18nTranslations('zh-Hant-HK');
- expect(merged.replay).toBe('HK');
- expect(merged.pause).toBe('Hant');
- expect(merged.play).toBe('ZH');
- expect(merged.mute).toBe(en.mute);
- });
-
- it('truncates en-GB-scotland toward en-GB then en', () => {
- expect(localeLookupChain('en-GB-scotland')).toEqual(['en-gb-scotland', 'en-gb', 'en']);
- registerI18n('en-GB', { play: 'GB' });
- const merged = getI18nTranslations('en-GB-scotland');
- expect(merged.play).toBe('GB');
- });
-
- it('does not use a sibling locale tag when only the parent matches (es-AR vs es-MX)', () => {
- registerI18n('es-MX', { play: 'MX' });
- registerI18n('es', { play: 'ES' });
- const merged = getI18nTranslations('es-AR');
- expect(merged.play).toBe('ES');
- });
-
- it('reports hasRegisteredI18n with normalized tags', () => {
- expect(hasRegisteredI18n('en')).toBe(true);
- expect(hasRegisteredI18n('fr')).toBe(false);
- registerI18n('fr', { play: 'Lire' });
- expect(hasRegisteredI18n('FR')).toBe(true);
- });
-
- it('onI18nRegistryChange unsubscribe stops notifications', () => {
- const spy = vi.fn();
- const off = onI18nRegistryChange(spy);
- registerI18n('de', { play: 'Los' });
- expect(spy).toHaveBeenCalledOnce();
- off();
- registerI18n('de', { pause: 'Pause' });
- expect(spy).toHaveBeenCalledOnce();
- });
-});
diff --git a/packages/core/src/core/i18n/tests/translator.test.ts b/packages/core/src/core/i18n/tests/translator.test.ts
deleted file mode 100644
index 272d08a4..00000000
--- a/packages/core/src/core/i18n/tests/translator.test.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import { createTranslator } from '../translator';
-
-describe('createTranslator', () => {
- it('resolves a simple key', () => {
- const t = createTranslator({ play: 'Start' }, 'en');
- expect(t('play')).toBe('Start');
- });
-
- it('interpolates {param} placeholders', () => {
- const t = createTranslator({ seekForward: 'Jump {seconds} s' }, 'en');
- expect(t('seekForward', { seconds: 10 })).toBe('Jump 10 s');
- });
-
- it('falls back to the key when no translation is defined', () => {
- const t = createTranslator({}, 'en');
- expect(t('play')).toBe('play');
- });
-
- it('keeps tokens that are not supplied as params', () => {
- const t = createTranslator({ play: 'Hi {name}' }, 'en');
- expect(t('play')).toBe('Hi {name}');
- });
-
- it('coerces numeric params to strings', () => {
- const t = createTranslator({ playbackRateAria: 'Speed {rate}' }, 'en');
- expect(t('playbackRateAria', { rate: 1.25 })).toBe('Speed 1.25');
- });
-});
diff --git a/packages/core/src/core/i18n/translator.ts b/packages/core/src/core/i18n/translator.ts
deleted file mode 100644
index d86a121a..00000000
--- a/packages/core/src/core/i18n/translator.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type { Locale, TranslationParams, Translations, Translator } from './types';
-
-const PLACEHOLDER = /\{([^{}]+)\}/g;
-
-function interpolate(template: string, params?: Record): string {
- if (!params) return template;
- return template.replace(PLACEHOLDER, (match, name: string) => {
- if (Object.hasOwn(params, name)) {
- return String(params[name as keyof typeof params]);
- }
- return match;
- });
-}
-
-/** Builds a typed translator from a resolved translation map (typically from {@link getI18nTranslations}). */
-export function createTranslator(translations: Translations, locale: Locale): Translator {
- void locale;
-
- const translate = (key: keyof TranslationParams, params?: unknown): string => {
- const raw = translations[key];
- if (raw === undefined) {
- return String(key);
- }
- return interpolate(raw, params as Record | undefined);
- };
-
- return translate as Translator;
-}
diff --git a/packages/core/src/core/i18n/types.ts b/packages/core/src/core/i18n/types.ts
deleted file mode 100644
index 75c76ed6..00000000
--- a/packages/core/src/core/i18n/types.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import type { BUILT_IN_LOCALES, LOCALE_ALIAS_TAGS } from './built-in-locales';
-
-/** Matches strings that include the literal substring `needle` (for example a `{param}` token). */
-export type Contains = `${string}${Needle}${string}`;
-
-export type BuiltInLocale = (typeof BUILT_IN_LOCALES)[number] | (typeof LOCALE_ALIAS_TAGS)[number];
-
-/** BCP 47 language tag; built-ins are narrowed for autocomplete. */
-export type Locale = BuiltInLocale | (string & {});
-
-/** Per-key argument contract: `never` means the translator only accepts the key. */
-export type TranslationParams = {
- play: never;
- pause: never;
- replay: never;
- mute: never;
- unmute: never;
- seekForward: { seconds: number | string };
- seekBackward: { seconds: number | string };
- enterFullscreen: never;
- exitFullscreen: never;
- enableCaptions: never;
- disableCaptions: never;
- enterPictureInPicture: never;
- exitPictureInPicture: never;
- playingLive: never;
- seekToLiveEdge: never;
- liveBadge: never;
- startCasting: never;
- stopCasting: never;
- connectingCast: never;
- seek: never;
- volume: never;
- timeCurrent: never;
- timeDuration: never;
- timeRemaining: never;
- timeRemainingPhrase: { duration: string };
- playbackRateAria: { rate: number | string };
- timeSliderValueTextRange: { current: string; duration: string };
- volumeSliderValueTextMuted: { percent: number | string };
- indicatorMuted: never;
- indicatorVolume: never;
- indicatorVolumeWithValue: { value: string };
- indicatorCaptionsOn: never;
- indicatorCaptionsOff: never;
- indicatorPaused: never;
- indicatorPlaying: never;
- indicatorFullscreen: never;
- indicatorExitFullscreen: never;
- indicatorPictureInPicture: never;
- indicatorExitPictureInPicture: never;
- mediaErrorAborted: never;
- mediaErrorNetwork: never;
- mediaErrorDecode: never;
- mediaErrorSrcNotSupported: never;
- mediaErrorEncrypted: never;
- mediaErrorCustom: never;
- errorDialogTitle: never;
- errorDialogDismiss: never;
- mediaErrorFallback: never;
-};
-
-/**
- * Either a known translation id from {@link TranslationParams}, or any other string the platform may
- * use as copy (custom overlay key, literal text, etc.).
- */
-export type TranslationKeyOrString = keyof TranslationParams | (string & {});
-
-/** Placeholder shape for each key that accepts `t(key, params)`. Omitting a key here is a type error when defining strings. */
-type ParametricTranslations = {
- seekForward: Contains<'{seconds}'>;
- seekBackward: Contains<'{seconds}'>;
- playbackRateAria: Contains<'{rate}'>;
- timeSliderValueTextRange: Contains<'{current}'> & Contains<'{duration}'>;
- timeRemainingPhrase: Contains<'{duration}'>;
- volumeSliderValueTextMuted: Contains<'{percent}'>;
- indicatorVolumeWithValue: Contains<'{value}'>;
-};
-
-/** Player copy keyed by camelCase tokens; all entries are optional overlay keys. */
-export type Translations = {
- [K in keyof TranslationParams]?: TranslationParams[K] extends never
- ? string
- : K extends keyof ParametricTranslations
- ? ParametricTranslations[K]
- : never;
-};
-
-export type Translator = (
- key: K,
- ...args: TranslationParams[K] extends never ? [] : [params: TranslationParams[K]]
-) => string;
diff --git a/packages/core/src/core/index.ts b/packages/core/src/core/index.ts
index 865d3079..7fbacec7 100644
--- a/packages/core/src/core/index.ts
+++ b/packages/core/src/core/index.ts
@@ -18,11 +18,9 @@ export * from './ui/controls/controls-core';
export * from './ui/controls/controls-data-attrs';
export * from './ui/error-dialog/error-dialog-core';
export * from './ui/error-dialog/error-dialog-data-attrs';
-export * from './ui/error-dialog/error-dialog-i18n';
export * from './ui/fullscreen-button/fullscreen-button-core';
export * from './ui/fullscreen-button/fullscreen-button-data-attrs';
export * from './ui/input-feedback/indicator-lifecycle';
-export * from './ui/input-feedback/labels';
export * from './ui/input-feedback/seek-indicator-core';
export * from './ui/input-feedback/seek-indicator-data-attrs';
export * from './ui/input-feedback/status';
@@ -56,8 +54,6 @@ export * from './ui/poster/poster-core';
export * from './ui/poster/poster-data-attrs';
export * from './ui/quality-radio-group/quality-radio-group-core';
export * from './ui/quality-radio-group/quality-radio-group-data-attrs';
-export * from './ui/resolve-control-attrs';
-export * from './ui/resolve-optional-control-label';
export * from './ui/seek-button/seek-button-core';
export * from './ui/seek-button/seek-button-data-attrs';
export * from './ui/slider/slider-core';
diff --git a/packages/core/src/core/ui/captions-button/captions-button-core.ts b/packages/core/src/core/ui/captions-button/captions-button-core.ts
index 0ac42c14..7bb009b3 100644
--- a/packages/core/src/core/ui/captions-button/captions-button-core.ts
+++ b/packages/core/src/core/ui/captions-button/captions-button-core.ts
@@ -1,15 +1,15 @@
import { createState } from '@videojs/store';
import { isCaptionOrSubtitleTrack } from '@videojs/utils/dom';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaTextTrackState } from '../../media/state';
-import { resolveOptionalControlLabel } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export interface CaptionsButtonProps {
/** Custom label for the button. */
- label?: TranslationKeyOrString | ((state: CaptionsButtonState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: CaptionsButtonState) => string) | undefined;
/** Whether the button is disabled. */
disabled?: boolean | undefined;
/** When true with multiple tracks, pointer activation opens a menu instead of toggling. React sets this automatically inside `Menu.Trigger`. */
@@ -44,11 +44,17 @@ export class CaptionsButtonCore {
this.#props = defaults(props, CaptionsButtonCore.defaultProps);
}
- getLabel(state: CaptionsButtonState): TranslationKeyOrString {
- const custom = resolveOptionalControlLabel(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: CaptionsButtonState): string {
+ const { label } = this.#props;
- return state.subtitlesShowing ? 'disableCaptions' : 'enableCaptions';
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ return state.subtitlesShowing ? 'Disable captions' : 'Enable captions';
}
getAttrs(state: CaptionsButtonState) {
diff --git a/packages/core/src/core/ui/captions-button/tests/captions-button-core.test.ts b/packages/core/src/core/ui/captions-button/tests/captions-button-core.test.ts
index 1835c82d..14a45189 100644
--- a/packages/core/src/core/ui/captions-button/tests/captions-button-core.test.ts
+++ b/packages/core/src/core/ui/captions-button/tests/captions-button-core.test.ts
@@ -69,12 +69,12 @@ describe('CaptionsButtonCore', () => {
describe('getLabel', () => {
it('returns Enable captions when captions are disabled', () => {
const core = new CaptionsButtonCore();
- expect(core.getLabel(createState({ subtitlesShowing: false }))).toBe('enableCaptions');
+ expect(core.getLabel(createState({ subtitlesShowing: false }))).toBe('Enable captions');
});
it('returns Disable captions when captions are enabled', () => {
const core = new CaptionsButtonCore();
- expect(core.getLabel(createState({ subtitlesShowing: true }))).toBe('disableCaptions');
+ expect(core.getLabel(createState({ subtitlesShowing: true }))).toBe('Disable captions');
});
it('returns custom string label', () => {
@@ -94,7 +94,7 @@ describe('CaptionsButtonCore', () => {
it('returns aria-label', () => {
const core = new CaptionsButtonCore();
const attrs = core.getAttrs(createState({ subtitlesShowing: false }));
- expect(attrs['aria-label']).toBe('enableCaptions');
+ expect(attrs['aria-label']).toBe('Enable captions');
});
it('sets aria-disabled when disabled', () => {
diff --git a/packages/core/src/core/ui/cast-button/cast-button-core.ts b/packages/core/src/core/ui/cast-button/cast-button-core.ts
index 8d91dc09..c694d5f9 100644
--- a/packages/core/src/core/ui/cast-button/cast-button-core.ts
+++ b/packages/core/src/core/ui/cast-button/cast-button-core.ts
@@ -1,15 +1,15 @@
import { createState } from '@videojs/store';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaRemotePlaybackState, RemotePlaybackConnectionState } from '../../media/state';
import type { MediaFeatureAvailability } from '../../media/types';
-import { resolveOptionalControlLabel } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export interface CastButtonProps {
/** Custom label for the button. */
- label?: TranslationKeyOrString | ((state: CastButtonState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: CastButtonState) => string) | undefined;
/** Whether the button is disabled. */
disabled?: boolean | undefined;
}
@@ -42,13 +42,19 @@ export class CastButtonCore {
this.#props = defaults(props, CastButtonCore.defaultProps);
}
- getLabel(state: CastButtonState): TranslationKeyOrString {
- const custom = resolveOptionalControlLabel(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: CastButtonState): string {
+ const { label } = this.#props;
- if (state.castState === 'connected') return 'stopCasting';
- if (state.castState === 'connecting') return 'connectingCast';
- return 'startCasting';
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ if (state.castState === 'connected') return 'Stop casting';
+ if (state.castState === 'connecting') return 'Connecting';
+ return 'Start casting';
}
getAttrs(state: CastButtonState) {
diff --git a/packages/core/src/core/ui/cast-button/tests/cast-button-core.test.ts b/packages/core/src/core/ui/cast-button/tests/cast-button-core.test.ts
index a308cadb..fc0b9781 100644
--- a/packages/core/src/core/ui/cast-button/tests/cast-button-core.test.ts
+++ b/packages/core/src/core/ui/cast-button/tests/cast-button-core.test.ts
@@ -78,17 +78,17 @@ describe('CastButtonCore', () => {
describe('getLabel', () => {
it('returns Start casting when disconnected', () => {
const core = new CastButtonCore();
- expect(core.getLabel(createState({ castState: 'disconnected' }))).toBe('startCasting');
+ expect(core.getLabel(createState({ castState: 'disconnected' }))).toBe('Start casting');
});
it('returns Stop casting when connected', () => {
const core = new CastButtonCore();
- expect(core.getLabel(createState({ castState: 'connected' }))).toBe('stopCasting');
+ expect(core.getLabel(createState({ castState: 'connected' }))).toBe('Stop casting');
});
it('returns Connecting when connecting', () => {
const core = new CastButtonCore();
- expect(core.getLabel(createState({ castState: 'connecting' }))).toBe('connectingCast');
+ expect(core.getLabel(createState({ castState: 'connecting' }))).toBe('Connecting');
});
it('returns custom string label', () => {
@@ -108,7 +108,7 @@ describe('CastButtonCore', () => {
it('returns aria-label', () => {
const core = new CastButtonCore();
const attrs = core.getAttrs(createState());
- expect(attrs['aria-label']).toBe('startCasting');
+ expect(attrs['aria-label']).toBe('Start casting');
});
it('sets aria-disabled when disabled', () => {
diff --git a/packages/core/src/core/ui/error-dialog/error-dialog-i18n.ts b/packages/core/src/core/ui/error-dialog/error-dialog-i18n.ts
deleted file mode 100644
index 949e1d84..00000000
--- a/packages/core/src/core/ui/error-dialog/error-dialog-i18n.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import type { TranslationKeyOrString, TranslationParams } from '../../i18n/types';
-import { MediaError } from '../../media/media-error';
-
-export type MediaErrorTranslationKey = Extract<
- keyof TranslationParams,
- | 'mediaErrorAborted'
- | 'mediaErrorNetwork'
- | 'mediaErrorDecode'
- | 'mediaErrorSrcNotSupported'
- | 'mediaErrorEncrypted'
- | 'mediaErrorCustom'
->;
-
-const MEDIA_ERROR_CODE_TO_KEY: Record = {
- [MediaError.MEDIA_ERR_ABORTED]: 'mediaErrorAborted',
- [MediaError.MEDIA_ERR_NETWORK]: 'mediaErrorNetwork',
- [MediaError.MEDIA_ERR_DECODE]: 'mediaErrorDecode',
- [MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED]: 'mediaErrorSrcNotSupported',
- [MediaError.MEDIA_ERR_ENCRYPTED]: 'mediaErrorEncrypted',
- [MediaError.MEDIA_ERR_CUSTOM]: 'mediaErrorCustom',
-};
-
-const STANDARD_CODE_UA_MESSAGES: Partial> = {
- [MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED]: ['Failed to open media'],
-};
-
-function isStandardMediaErrorCode(code: number): boolean {
- return (
- code === MediaError.MEDIA_ERR_ABORTED ||
- code === MediaError.MEDIA_ERR_NETWORK ||
- code === MediaError.MEDIA_ERR_DECODE ||
- code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED ||
- code === MediaError.MEDIA_ERR_ENCRYPTED
- );
-}
-
-export function getMediaErrorTranslationKey(code: number): MediaErrorTranslationKey | undefined {
- return MEDIA_ERROR_CODE_TO_KEY[code];
-}
-
-export function getErrorDialogTitleLabel(): TranslationKeyOrString {
- return 'errorDialogTitle';
-}
-
-export function getErrorDialogDismissLabel(): TranslationKeyOrString {
- return 'errorDialogDismiss';
-}
-
-/**
- * Resolves dialog body copy: registry keys for known {@link MediaError} defaults, literal text for
- * custom messages, otherwise the generic fallback key.
- */
-export function resolveErrorDialogDescription(
- error: (Pick & { context?: MediaError['context'] }) | null | undefined,
- cachedMessage?: string | null
-): TranslationKeyOrString {
- if (error) {
- const key = getMediaErrorTranslationKey(error.code);
- const message = error.message?.trim();
- if (message) {
- const defaultForCode = MediaError.defaultMessages[error.code];
- if (key && defaultForCode && message === defaultForCode) {
- return key;
- }
- const uaVariants = STANDARD_CODE_UA_MESSAGES[error.code];
- if (key && isStandardMediaErrorCode(error.code) && !error.context && uaVariants?.includes(message)) {
- return key;
- }
- return message;
- }
- if (key) return key;
- }
-
- const cached = cachedMessage?.trim();
- if (cached) return cached;
-
- return 'mediaErrorFallback';
-}
diff --git a/packages/core/src/core/ui/error-dialog/tests/error-dialog-i18n.test.ts b/packages/core/src/core/ui/error-dialog/tests/error-dialog-i18n.test.ts
deleted file mode 100644
index 73f0e170..00000000
--- a/packages/core/src/core/ui/error-dialog/tests/error-dialog-i18n.test.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { describe, expect, it } from 'vitest';
-import { MediaError } from '../../../media/media-error';
-import {
- getErrorDialogDismissLabel,
- getErrorDialogTitleLabel,
- getMediaErrorTranslationKey,
- resolveErrorDialogDescription,
-} from '../error-dialog-i18n';
-
-describe('getMediaErrorTranslationKey', () => {
- it('maps standard MediaError codes to registry keys', () => {
- expect(getMediaErrorTranslationKey(MediaError.MEDIA_ERR_NETWORK)).toBe('mediaErrorNetwork');
- expect(getMediaErrorTranslationKey(MediaError.MEDIA_ERR_ABORTED)).toBe('mediaErrorAborted');
- });
-});
-
-describe('getErrorDialogTitleLabel', () => {
- it('returns the error dialog title key', () => {
- expect(getErrorDialogTitleLabel()).toBe('errorDialogTitle');
- });
-});
-
-describe('getErrorDialogDismissLabel', () => {
- it('returns the dismiss button key', () => {
- expect(getErrorDialogDismissLabel()).toBe('errorDialogDismiss');
- });
-});
-
-describe('resolveErrorDialogDescription', () => {
- it('returns a registry key when the message matches the default for the code', () => {
- const error = new MediaError(undefined, MediaError.MEDIA_ERR_NETWORK);
- expect(resolveErrorDialogDescription(error, null)).toBe('mediaErrorNetwork');
- });
-
- it('returns custom message text when context is provided', () => {
- const error = new MediaError('Custom failure', MediaError.MEDIA_ERR_NETWORK, true, 'hls');
- expect(resolveErrorDialogDescription(error, null)).toBe('Custom failure');
- });
-
- it('returns custom message text on standard codes without context', () => {
- const error = new MediaError('App network failure', MediaError.MEDIA_ERR_NETWORK);
- expect(resolveErrorDialogDescription(error, null)).toBe('App network failure');
- });
-
- it('returns a registry key for browser-specific messages on standard codes', () => {
- const error = new MediaError('Failed to open media', MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
- expect(resolveErrorDialogDescription(error, null)).toBe('mediaErrorSrcNotSupported');
- });
-
- it('falls back to cached message then generic key', () => {
- expect(resolveErrorDialogDescription(null, 'Cached')).toBe('Cached');
- expect(resolveErrorDialogDescription(null, null)).toBe('mediaErrorFallback');
- });
-});
diff --git a/packages/core/src/core/ui/fullscreen-button/fullscreen-button-core.ts b/packages/core/src/core/ui/fullscreen-button/fullscreen-button-core.ts
index 81772f93..216e9ec2 100644
--- a/packages/core/src/core/ui/fullscreen-button/fullscreen-button-core.ts
+++ b/packages/core/src/core/ui/fullscreen-button/fullscreen-button-core.ts
@@ -1,14 +1,14 @@
import { createState } from '@videojs/store';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaFullscreenState } from '../../media/state';
-import { resolveOptionalControlLabel } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export interface FullscreenButtonProps {
/** Custom label for the button. */
- label?: TranslationKeyOrString | ((state: FullscreenButtonState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: FullscreenButtonState) => string) | undefined;
/** Whether the button is disabled. */
disabled?: boolean | undefined;
}
@@ -41,11 +41,17 @@ export class FullscreenButtonCore {
this.#props = defaults(props, FullscreenButtonCore.defaultProps);
}
- getLabel(state: FullscreenButtonState): TranslationKeyOrString {
- const custom = resolveOptionalControlLabel(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: FullscreenButtonState): string {
+ const { label } = this.#props;
- return state.fullscreen ? 'exitFullscreen' : 'enterFullscreen';
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ return state.fullscreen ? 'Exit fullscreen' : 'Enter fullscreen';
}
getAttrs(state: FullscreenButtonState) {
diff --git a/packages/core/src/core/ui/fullscreen-button/tests/fullscreen-button-core.test.ts b/packages/core/src/core/ui/fullscreen-button/tests/fullscreen-button-core.test.ts
index fe77b6a3..8cc51c43 100644
--- a/packages/core/src/core/ui/fullscreen-button/tests/fullscreen-button-core.test.ts
+++ b/packages/core/src/core/ui/fullscreen-button/tests/fullscreen-button-core.test.ts
@@ -48,12 +48,12 @@ describe('FullscreenButtonCore', () => {
describe('getLabel', () => {
it('returns Enter fullscreen when not fullscreen', () => {
const core = new FullscreenButtonCore();
- expect(core.getLabel(createState({ fullscreen: false }))).toBe('enterFullscreen');
+ expect(core.getLabel(createState({ fullscreen: false }))).toBe('Enter fullscreen');
});
it('returns Exit fullscreen when fullscreen', () => {
const core = new FullscreenButtonCore();
- expect(core.getLabel(createState({ fullscreen: true }))).toBe('exitFullscreen');
+ expect(core.getLabel(createState({ fullscreen: true }))).toBe('Exit fullscreen');
});
it('returns custom string label', () => {
@@ -73,7 +73,7 @@ describe('FullscreenButtonCore', () => {
it('returns aria-label', () => {
const core = new FullscreenButtonCore();
const attrs = core.getAttrs(createState());
- expect(attrs['aria-label']).toBe('enterFullscreen');
+ expect(attrs['aria-label']).toBe('Enter fullscreen');
});
it('sets aria-disabled when disabled', () => {
diff --git a/packages/core/src/core/ui/input-feedback/labels.ts b/packages/core/src/core/ui/input-feedback/labels.ts
deleted file mode 100644
index b1829bcc..00000000
--- a/packages/core/src/core/ui/input-feedback/labels.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Translator } from '../../i18n/types';
-import type { InputIndicatorLabels } from './status';
-
-/** Maps i18n indicator keys to {@link InputIndicatorLabels} for status / volume feedback. */
-export function createInputIndicatorLabels(translator: Translator): InputIndicatorLabels {
- return {
- muted: translator('indicatorMuted'),
- volume: translator('indicatorVolume'),
- volumeWithValue: (value) => translator('indicatorVolumeWithValue', { value }),
- captionsOn: translator('indicatorCaptionsOn'),
- captionsOff: translator('indicatorCaptionsOff'),
- paused: translator('indicatorPaused'),
- playing: translator('indicatorPlaying'),
- fullscreen: translator('indicatorFullscreen'),
- exitFullscreen: translator('indicatorExitFullscreen'),
- pictureInPicture: translator('indicatorPictureInPicture'),
- exitPictureInPicture: translator('indicatorExitPictureInPicture'),
- };
-}
diff --git a/packages/core/src/core/ui/input-feedback/status.ts b/packages/core/src/core/ui/input-feedback/status.ts
index ae1f620a..aa6d346d 100644
--- a/packages/core/src/core/ui/input-feedback/status.ts
+++ b/packages/core/src/core/ui/input-feedback/status.ts
@@ -56,7 +56,6 @@ export interface MediaSnapshot {
export interface InputIndicatorLabels {
muted: string;
volume: string;
- volumeWithValue: (value: string) => string;
captionsOn: string;
captionsOff: string;
paused: string;
@@ -77,7 +76,6 @@ export interface StatusDetails {
export const DEFAULT_INPUT_INDICATOR_LABELS: InputIndicatorLabels = {
muted: 'Muted',
volume: 'Volume',
- volumeWithValue: (value) => `Volume ${value}`,
captionsOn: 'Captions on',
captionsOff: 'Captions off',
paused: 'Paused',
@@ -156,7 +154,7 @@ export function deriveAnnouncerLabel(
if (!details) return null;
if (isVolumeIndicatorAction(event.action)) {
- return details.status === 'volume-off' ? labels.muted : labels.volumeWithValue(details.value ?? '');
+ return details.status === 'volume-off' ? labels.muted : `${labels.volume} ${details.value}`;
}
return details.label;
diff --git a/packages/core/src/core/ui/input-feedback/tests/labels.test.ts b/packages/core/src/core/ui/input-feedback/tests/labels.test.ts
deleted file mode 100644
index f26d63d9..00000000
--- a/packages/core/src/core/ui/input-feedback/tests/labels.test.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { describe, expect, it } from 'vitest';
-import { createTranslator, type Translations } from '../../../i18n';
-
-import { createInputIndicatorLabels } from '../labels';
-
-describe('createInputIndicatorLabels', () => {
- it('maps indicator translation keys to input feedback labels', () => {
- const labels = createInputIndicatorLabels(
- createTranslator(
- {
- indicatorMuted: 'Muet',
- indicatorVolume: 'Volume',
- indicatorVolumeWithValue: 'Volume {value}',
- indicatorCaptionsOn: 'Sous-titres activés',
- indicatorCaptionsOff: 'Sous-titres désactivés',
- indicatorPaused: 'En pause',
- indicatorPlaying: 'Lecture en cours',
- indicatorFullscreen: 'Plein écran',
- indicatorExitFullscreen: 'Quitter le plein écran',
- indicatorPictureInPicture: 'Image dans l’image',
- indicatorExitPictureInPicture: 'Quitter l’image dans l’image',
- } satisfies Translations,
- 'fr'
- )
- );
-
- expect(labels.captionsOn).toBe('Sous-titres activés');
- expect(labels.captionsOff).toBe('Sous-titres désactivés');
- expect(labels.paused).toBe('En pause');
- expect(labels.volumeWithValue('80%')).toBe('Volume 80%');
- });
-});
diff --git a/packages/core/src/core/ui/input-feedback/volume-indicator-core.ts b/packages/core/src/core/ui/input-feedback/volume-indicator-core.ts
index 0709c70b..e43e1ffb 100644
--- a/packages/core/src/core/ui/input-feedback/volume-indicator-core.ts
+++ b/packages/core/src/core/ui/input-feedback/volume-indicator-core.ts
@@ -7,15 +7,12 @@ import {
deriveVolumeStatus,
type IndicatorVolumeLevel,
type InputActionEvent,
- type InputIndicatorLabels,
isVolumeIndicatorAction,
type MediaSnapshot,
predictVolumeActionOutcome,
} from './status';
-export interface VolumeIndicatorProps extends IndicatorCoreProps {
- labels?: Partial | undefined;
-}
+export interface VolumeIndicatorProps extends IndicatorCoreProps {}
export interface VolumeIndicatorState extends IndicatorLifecycleState {
level: IndicatorVolumeLevel | null;
@@ -69,12 +66,7 @@ export class VolumeIndicatorCore {
const current = this.state.current;
const prediction = predictVolumeActionOutcome(event, snapshot);
- const details = deriveVolumeStatus(
- event,
- snapshot,
- { ...DEFAULT_INPUT_INDICATOR_LABELS, ...this.#props.labels },
- prediction
- );
+ const details = deriveVolumeStatus(event, snapshot, DEFAULT_INPUT_INDICATOR_LABELS, prediction);
const boundary = getVolumeBoundary(event, prediction.snapshotVolume, prediction.nextVolume);
const repeatedBoundary = boundary !== null && current[boundary] === true;
diff --git a/packages/core/src/core/ui/live-button/live-button-core.ts b/packages/core/src/core/ui/live-button/live-button-core.ts
index 1d3049c5..af1d47f3 100644
--- a/packages/core/src/core/ui/live-button/live-button-core.ts
+++ b/packages/core/src/core/ui/live-button/live-button-core.ts
@@ -1,14 +1,14 @@
import { createState } from '@videojs/store';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaBufferState, MediaLiveState, MediaTimeState } from '../../media/state';
-import { resolveOptionalControlLabel } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export interface LiveButtonProps {
/** Custom label for the button. */
- label?: TranslationKeyOrString | ((state: LiveButtonState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: LiveButtonState) => string) | undefined;
/** Whether the button is disabled. */
disabled?: boolean | undefined;
}
@@ -82,12 +82,18 @@ export class LiveButtonCore {
this.#props = defaults(props, LiveButtonCore.defaultProps);
}
- getLabel(state: LiveButtonState): TranslationKeyOrString {
- const custom = resolveOptionalControlLabel(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: LiveButtonState): string {
+ const { label } = this.#props;
- if (state.liveEdge) return 'playingLive';
- return 'seekToLiveEdge';
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ if (state.liveEdge) return 'Playing live';
+ return 'Seek to live edge';
}
getAttrs(state: LiveButtonState) {
diff --git a/packages/core/src/core/ui/live-button/tests/live-button-core.test.ts b/packages/core/src/core/ui/live-button/tests/live-button-core.test.ts
index 29d41f12..d69ea2ba 100644
--- a/packages/core/src/core/ui/live-button/tests/live-button-core.test.ts
+++ b/packages/core/src/core/ui/live-button/tests/live-button-core.test.ts
@@ -148,12 +148,12 @@ describe('LiveButtonCore', () => {
describe('getLabel', () => {
it('returns "Seek to live edge" when behind live', () => {
const core = new LiveButtonCore();
- expect(core.getLabel(createState({ live: true, liveEdge: false }))).toBe('seekToLiveEdge');
+ expect(core.getLabel(createState({ live: true, liveEdge: false }))).toBe('Seek to live edge');
});
it('returns "Playing live" when at live edge', () => {
const core = new LiveButtonCore();
- expect(core.getLabel(createState({ live: true, liveEdge: true }))).toBe('playingLive');
+ expect(core.getLabel(createState({ live: true, liveEdge: true }))).toBe('Playing live');
});
it('returns custom string label', () => {
diff --git a/packages/core/src/core/ui/mute-button/mute-button-core.ts b/packages/core/src/core/ui/mute-button/mute-button-core.ts
index dbc328cf..769314a6 100644
--- a/packages/core/src/core/ui/mute-button/mute-button-core.ts
+++ b/packages/core/src/core/ui/mute-button/mute-button-core.ts
@@ -1,16 +1,16 @@
import { createState } from '@videojs/store';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaVolumeState } from '../../media/state';
-import { resolveOptionalControlLabel } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export type VolumeLevel = 'off' | 'low' | 'medium' | 'high';
export interface MuteButtonProps {
/** Custom label for the button. */
- label?: TranslationKeyOrString | ((state: MuteButtonState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: MuteButtonState) => string) | undefined;
/** Whether the button is disabled. */
disabled?: boolean | undefined;
}
@@ -49,11 +49,17 @@ export class MuteButtonCore {
this.#props = defaults(props, MuteButtonCore.defaultProps);
}
- getLabel(state: MuteButtonState): TranslationKeyOrString {
- const custom = resolveOptionalControlLabel(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: MuteButtonState): string {
+ const { label } = this.#props;
- return state.muted ? 'unmute' : 'mute';
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ return state.muted ? 'Unmute' : 'Mute';
}
getAttrs(state: MuteButtonState) {
diff --git a/packages/core/src/core/ui/mute-button/tests/mute-button-core.test.ts b/packages/core/src/core/ui/mute-button/tests/mute-button-core.test.ts
index 2b7eee4c..d1005565 100644
--- a/packages/core/src/core/ui/mute-button/tests/mute-button-core.test.ts
+++ b/packages/core/src/core/ui/mute-button/tests/mute-button-core.test.ts
@@ -80,14 +80,14 @@ describe('MuteButtonCore', () => {
});
describe('getLabel', () => {
- it('returns mute when unmuted', () => {
+ it('returns Mute when unmuted', () => {
const core = new MuteButtonCore();
- expect(core.getLabel(createState({ muted: false }))).toBe('mute');
+ expect(core.getLabel(createState({ muted: false }))).toBe('Mute');
});
- it('returns unmute when muted', () => {
+ it('returns Unmute when muted', () => {
const core = new MuteButtonCore();
- expect(core.getLabel(createState({ muted: true }))).toBe('unmute');
+ expect(core.getLabel(createState({ muted: true }))).toBe('Unmute');
});
it('returns custom string label', () => {
@@ -107,7 +107,7 @@ describe('MuteButtonCore', () => {
it('returns aria-label', () => {
const core = new MuteButtonCore();
const attrs = core.getAttrs(createState({ muted: false }));
- expect(attrs['aria-label']).toBe('mute');
+ expect(attrs['aria-label']).toBe('Mute');
});
it('sets aria-disabled when disabled', () => {
diff --git a/packages/core/src/core/ui/pip-button/pip-button-core.ts b/packages/core/src/core/ui/pip-button/pip-button-core.ts
index 7dd18de6..837bd396 100644
--- a/packages/core/src/core/ui/pip-button/pip-button-core.ts
+++ b/packages/core/src/core/ui/pip-button/pip-button-core.ts
@@ -1,14 +1,14 @@
import { createState } from '@videojs/store';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaPictureInPictureState } from '../../media/state';
-import { resolveOptionalControlLabel } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export interface PiPButtonProps {
/** Custom label for the button. */
- label?: TranslationKeyOrString | ((state: PiPButtonState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: PiPButtonState) => string) | undefined;
/** Whether the button is disabled. */
disabled?: boolean | undefined;
}
@@ -41,11 +41,17 @@ export class PiPButtonCore {
this.#props = defaults(props, PiPButtonCore.defaultProps);
}
- getLabel(state: PiPButtonState): TranslationKeyOrString {
- const custom = resolveOptionalControlLabel(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: PiPButtonState): string {
+ const { label } = this.#props;
- return state.pip ? 'exitPictureInPicture' : 'enterPictureInPicture';
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ return state.pip ? 'Exit picture-in-picture' : 'Enter picture-in-picture';
}
getAttrs(state: PiPButtonState) {
diff --git a/packages/core/src/core/ui/pip-button/tests/pip-button-core.test.ts b/packages/core/src/core/ui/pip-button/tests/pip-button-core.test.ts
index 2492c859..5abddf09 100644
--- a/packages/core/src/core/ui/pip-button/tests/pip-button-core.test.ts
+++ b/packages/core/src/core/ui/pip-button/tests/pip-button-core.test.ts
@@ -48,12 +48,12 @@ describe('PiPButtonCore', () => {
describe('getLabel', () => {
it('returns Enter picture-in-picture when not in PiP', () => {
const core = new PiPButtonCore();
- expect(core.getLabel(createState({ pip: false }))).toBe('enterPictureInPicture');
+ expect(core.getLabel(createState({ pip: false }))).toBe('Enter picture-in-picture');
});
it('returns Exit picture-in-picture when in PiP', () => {
const core = new PiPButtonCore();
- expect(core.getLabel(createState({ pip: true }))).toBe('exitPictureInPicture');
+ expect(core.getLabel(createState({ pip: true }))).toBe('Exit picture-in-picture');
});
it('returns custom string label', () => {
@@ -73,7 +73,7 @@ describe('PiPButtonCore', () => {
it('returns aria-label', () => {
const core = new PiPButtonCore();
const attrs = core.getAttrs(createState());
- expect(attrs['aria-label']).toBe('enterPictureInPicture');
+ expect(attrs['aria-label']).toBe('Enter picture-in-picture');
});
it('sets aria-disabled when disabled', () => {
diff --git a/packages/core/src/core/ui/play-button/play-button-core.ts b/packages/core/src/core/ui/play-button/play-button-core.ts
index b5d95842..c091644a 100644
--- a/packages/core/src/core/ui/play-button/play-button-core.ts
+++ b/packages/core/src/core/ui/play-button/play-button-core.ts
@@ -1,14 +1,14 @@
import { createState } from '@videojs/store';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaPlaybackState } from '../../media/state';
-import { resolveOptionalControlLabel } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export interface PlayButtonProps {
/** Custom label for the button. */
- label?: TranslationKeyOrString | ((state: PlayButtonState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: PlayButtonState) => string) | undefined;
/** Whether the button is disabled. */
disabled?: boolean | undefined;
}
@@ -39,12 +39,18 @@ export class PlayButtonCore {
this.#props = defaults(props, PlayButtonCore.defaultProps);
}
- getLabel(state: PlayButtonState): TranslationKeyOrString {
- const custom = resolveOptionalControlLabel(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: PlayButtonState): string {
+ const { label } = this.#props;
- if (state.ended) return 'replay';
- return state.paused ? 'play' : 'pause';
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ if (state.ended) return 'Replay';
+ return state.paused ? 'Play' : 'Pause';
}
getAttrs(state: PlayButtonState) {
diff --git a/packages/core/src/core/ui/play-button/tests/play-button-core.test.ts b/packages/core/src/core/ui/play-button/tests/play-button-core.test.ts
index cc41c750..47e15959 100644
--- a/packages/core/src/core/ui/play-button/tests/play-button-core.test.ts
+++ b/packages/core/src/core/ui/play-button/tests/play-button-core.test.ts
@@ -65,19 +65,19 @@ describe('PlayButtonCore', () => {
});
describe('getLabel', () => {
- it('returns play when paused', () => {
+ it('returns Play when paused', () => {
const core = new PlayButtonCore();
- expect(core.getLabel(createState({ paused: true }))).toBe('play');
+ expect(core.getLabel(createState({ paused: true }))).toBe('Play');
});
- it('returns pause when playing', () => {
+ it('returns Pause when playing', () => {
const core = new PlayButtonCore();
- expect(core.getLabel(createState({ paused: false }))).toBe('pause');
+ expect(core.getLabel(createState({ paused: false }))).toBe('Pause');
});
- it('returns replay when ended', () => {
+ it('returns Replay when ended', () => {
const core = new PlayButtonCore();
- expect(core.getLabel(createState({ ended: true }))).toBe('replay');
+ expect(core.getLabel(createState({ ended: true }))).toBe('Replay');
});
it('returns custom string label', () => {
@@ -94,7 +94,7 @@ describe('PlayButtonCore', () => {
it('falls back to default when function returns empty', () => {
const core = new PlayButtonCore({ label: () => '' });
- expect(core.getLabel(createState({ paused: true }))).toBe('play');
+ expect(core.getLabel(createState({ paused: true }))).toBe('Play');
});
});
@@ -102,7 +102,7 @@ describe('PlayButtonCore', () => {
it('returns aria-label', () => {
const core = new PlayButtonCore();
const attrs = core.getAttrs(createState({ paused: true }));
- expect(attrs['aria-label']).toBe('play');
+ expect(attrs['aria-label']).toBe('Play');
});
it('sets aria-disabled when disabled', () => {
diff --git a/packages/core/src/core/ui/playback-rate-button/playback-rate-button-core.ts b/packages/core/src/core/ui/playback-rate-button/playback-rate-button-core.ts
index a9116039..71cd4c35 100644
--- a/packages/core/src/core/ui/playback-rate-button/playback-rate-button-core.ts
+++ b/packages/core/src/core/ui/playback-rate-button/playback-rate-button-core.ts
@@ -1,14 +1,14 @@
import { createState } from '@videojs/store';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaPlaybackRateState } from '../../media/state';
-import { createOptionalControlLabelCache } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export interface PlaybackRateButtonProps {
/** Custom label for the button. */
- label?: TranslationKeyOrString | ((state: PlaybackRateButtonState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: PlaybackRateButtonState) => string) | undefined;
/** Whether the button is disabled. */
disabled?: boolean | undefined;
/** When true, pointer activation opens a menu instead of cycling. React sets this automatically inside `Menu.Trigger`. */
@@ -33,7 +33,6 @@ export class PlaybackRateButtonCore {
#props = { ...PlaybackRateButtonCore.defaultProps };
#media: MediaPlaybackRateState | null = null;
- readonly #customLabel = createOptionalControlLabelCache();
constructor(props?: PlaybackRateButtonProps) {
if (props) this.setProps(props);
@@ -41,19 +40,19 @@ export class PlaybackRateButtonCore {
setProps(props: PlaybackRateButtonProps): void {
this.#props = defaults(props, PlaybackRateButtonCore.defaultProps);
- this.#customLabel.invalidate();
}
- getLabel(state: PlaybackRateButtonState): TranslationKeyOrString {
- const custom = this.#customLabel.resolve(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: PlaybackRateButtonState): string {
+ const { label } = this.#props;
- return 'playbackRateAria';
- }
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
- getLabelParams(state: PlaybackRateButtonState): { rate: number } | undefined {
- if (this.#customLabel.resolve(this.#props.label, state) !== undefined) return undefined;
- return { rate: state.rate };
+ return `Playback rate ${state.rate}`;
}
getAttrs(state: PlaybackRateButtonState) {
diff --git a/packages/core/src/core/ui/playback-rate-button/tests/playback-rate-button-core.test.ts b/packages/core/src/core/ui/playback-rate-button/tests/playback-rate-button-core.test.ts
index 78a31496..cc281116 100644
--- a/packages/core/src/core/ui/playback-rate-button/tests/playback-rate-button-core.test.ts
+++ b/packages/core/src/core/ui/playback-rate-button/tests/playback-rate-button-core.test.ts
@@ -36,12 +36,12 @@ describe('PlaybackRateButtonCore', () => {
describe('getLabel', () => {
it('returns default label with rate', () => {
const core = new PlaybackRateButtonCore();
- expect(core.getLabel(createState({ rate: 1.5 }))).toBe('playbackRateAria');
+ expect(core.getLabel(createState({ rate: 1.5 }))).toBe('Playback rate 1.5');
});
it('returns default label for rate 1', () => {
const core = new PlaybackRateButtonCore();
- expect(core.getLabel(createState({ rate: 1 }))).toBe('playbackRateAria');
+ expect(core.getLabel(createState({ rate: 1 }))).toBe('Playback rate 1');
});
it('returns custom string label', () => {
@@ -60,19 +60,7 @@ describe('PlaybackRateButtonCore', () => {
const core = new PlaybackRateButtonCore({
label: () => '',
});
- expect(core.getLabel(createState({ rate: 1.5 }))).toBe('playbackRateAria');
- });
- });
-
- describe('getLabelParams', () => {
- it('returns rate for default label', () => {
- const core = new PlaybackRateButtonCore();
- expect(core.getLabelParams(createState({ rate: 1.5 }))).toEqual({ rate: 1.5 });
- });
-
- it('returns undefined when custom label is set', () => {
- const core = new PlaybackRateButtonCore({ label: 'Speed' });
- expect(core.getLabelParams(createState())).toBeUndefined();
+ expect(core.getLabel(createState({ rate: 1.5 }))).toBe('Playback rate 1.5');
});
});
@@ -80,7 +68,7 @@ describe('PlaybackRateButtonCore', () => {
it('returns aria-label', () => {
const core = new PlaybackRateButtonCore();
const attrs = core.getAttrs(createState({ rate: 1.5 }));
- expect(attrs['aria-label']).toBe('playbackRateAria');
+ expect(attrs['aria-label']).toBe('Playback rate 1.5');
});
it('sets aria-disabled when disabled', () => {
diff --git a/packages/core/src/core/ui/playback-rate-radio-group/playback-rate-radio-group-core.ts b/packages/core/src/core/ui/playback-rate-radio-group/playback-rate-radio-group-core.ts
index 95377aef..e582c99a 100644
--- a/packages/core/src/core/ui/playback-rate-radio-group/playback-rate-radio-group-core.ts
+++ b/packages/core/src/core/ui/playback-rate-radio-group/playback-rate-radio-group-core.ts
@@ -1,15 +1,14 @@
import { createState } from '@videojs/store';
import { defaults } from '@videojs/utils/object';
-import { isUndefined } from '@videojs/utils/predicate';
+import { isFunction, isUndefined } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaPlaybackRateState } from '../../media/state';
-import { createOptionalControlLabelCache } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export interface PlaybackRateRadioGroupProps {
/** Custom label for the options group. */
- label?: TranslationKeyOrString | ((state: PlaybackRateRadioGroupState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: PlaybackRateRadioGroupState) => string) | undefined;
/** Custom formatter for visible playback rate labels. */
formatRate?: ((rate: number) => string) | undefined;
/** Whether playback rate selection is disabled. */
@@ -44,7 +43,6 @@ export class PlaybackRateRadioGroupCore {
#props = { ...PlaybackRateRadioGroupCore.defaultProps };
#media: MediaPlaybackRateState | null = null;
- readonly #customLabel = createOptionalControlLabelCache();
constructor(props?: PlaybackRateRadioGroupProps) {
if (props) this.setProps(props);
@@ -52,18 +50,19 @@ export class PlaybackRateRadioGroupCore {
setProps(props: PlaybackRateRadioGroupProps): void {
this.#props = defaults(props, PlaybackRateRadioGroupCore.defaultProps);
- this.#customLabel.invalidate();
}
- getLabel(state: PlaybackRateRadioGroupState): TranslationKeyOrString {
- const custom = this.#customLabel.resolve(this.#props.label, state);
- if (custom !== undefined) return custom;
- return 'playbackRateAria';
- }
+ getLabel(state: PlaybackRateRadioGroupState): string {
+ const { label } = this.#props;
- getLabelParams(state: PlaybackRateRadioGroupState): { rate: number } | undefined {
- if (this.#customLabel.resolve(this.#props.label, state) !== undefined) return undefined;
- return { rate: state.rate };
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ return `Playback rate ${state.rate}`;
}
getRateLabel(rate: number): string {
diff --git a/packages/core/src/core/ui/playback-rate-radio-group/tests/playback-rate-radio-group-core.test.ts b/packages/core/src/core/ui/playback-rate-radio-group/tests/playback-rate-radio-group-core.test.ts
index 832d9331..3dd48ef8 100644
--- a/packages/core/src/core/ui/playback-rate-radio-group/tests/playback-rate-radio-group-core.test.ts
+++ b/packages/core/src/core/ui/playback-rate-radio-group/tests/playback-rate-radio-group-core.test.ts
@@ -62,7 +62,7 @@ describe('PlaybackRateRadioGroupCore', () => {
describe('getLabel', () => {
it('returns default label with rate', () => {
const core = new PlaybackRateRadioGroupCore();
- expect(core.getLabel(createState({ rate: 1.5 }))).toBe('playbackRateAria');
+ expect(core.getLabel(createState({ rate: 1.5 }))).toBe('Playback rate 1.5');
});
it('returns custom string label', () => {
@@ -78,18 +78,6 @@ describe('PlaybackRateRadioGroupCore', () => {
});
});
- describe('getLabelParams', () => {
- it('returns rate for default label', () => {
- const core = new PlaybackRateRadioGroupCore();
- expect(core.getLabelParams(createState({ rate: 2 }))).toEqual({ rate: 2 });
- });
-
- it('returns undefined when custom label is set', () => {
- const core = new PlaybackRateRadioGroupCore({ label: 'Speed' });
- expect(core.getLabelParams(createState())).toBeUndefined();
- });
- });
-
describe('getRateLabel', () => {
it('formats rate labels by default', () => {
const core = new PlaybackRateRadioGroupCore();
@@ -109,7 +97,7 @@ describe('PlaybackRateRadioGroupCore', () => {
it('returns aria-label', () => {
const core = new PlaybackRateRadioGroupCore();
const attrs = core.getAttrs(createState({ rate: 1.5 }));
- expect(attrs['aria-label']).toBe('playbackRateAria');
+ expect(attrs['aria-label']).toBe('Playback rate 1.5');
});
it('sets aria-disabled when disabled', () => {
diff --git a/packages/core/src/core/ui/resolve-control-attrs.ts b/packages/core/src/core/ui/resolve-control-attrs.ts
deleted file mode 100644
index 86f0932a..00000000
--- a/packages/core/src/core/ui/resolve-control-attrs.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { resolveTranslationPhrase } from '../i18n/resolve-translation-phrase';
-import type { Translator } from '../i18n/types';
-import type { TranslationKeyOrString } from './types';
-
-export interface ControlAttrsResolvable {
- getAttrs?(state: State): object;
- getLabelParams?(state: State): Record | undefined;
- getValueTextParams?(state: State): Record | undefined;
-}
-
-export interface ControlLabelResolvable {
- getLabel(state: State): TranslationKeyOrString;
- getLabelParams?(state: State): Record | undefined;
-}
-
-/** Resolves `getLabel` and optional {@link ControlLabelResolvable.getLabelParams}. */
-export function resolveControlLabel(
- translator: Translator,
- core: ControlLabelResolvable,
- state: State
-): string {
- return resolveTranslationPhrase(translator, core.getLabel(state), core.getLabelParams?.(state));
-}
-
-/** Resolves `aria-label` / `aria-valuetext` keys from {@link ControlAttrsResolvable.getAttrs}. */
-export function resolveControlAttrs(
- translator: Translator,
- core: ControlAttrsResolvable,
- state: State
-): Record {
- const raw = (core.getAttrs?.(state) ?? {}) as Record;
- const out: Record = {};
-
- for (const [key, value] of Object.entries(raw)) {
- if (value === undefined) {
- out[key] = undefined;
- continue;
- }
-
- if (key === 'aria-label' && typeof value === 'string') {
- out[key] = resolveTranslationPhrase(translator, value as TranslationKeyOrString, core.getLabelParams?.(state));
- continue;
- }
-
- if (key === 'aria-valuetext' && typeof value === 'string') {
- out[key] = resolveTranslationPhrase(
- translator,
- value as TranslationKeyOrString,
- core.getValueTextParams?.(state)
- );
- continue;
- }
-
- out[key] = String(value);
- }
-
- return out;
-}
diff --git a/packages/core/src/core/ui/resolve-optional-control-label.ts b/packages/core/src/core/ui/resolve-optional-control-label.ts
deleted file mode 100644
index 47d1d18e..00000000
--- a/packages/core/src/core/ui/resolve-optional-control-label.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { isFunction } from '@videojs/utils/predicate';
-
-import type { TranslationKeyOrString } from './types';
-
-/**
- * Resolves a user-provided control `label` prop ({@link TranslationKeyOrString} or state callback).
- * Returns `undefined` when the caller should use its built-in default (registry id, slider default, etc.).
- */
-export function resolveOptionalControlLabel(
- label: TranslationKeyOrString | ((state: State) => TranslationKeyOrString) | undefined,
- state: State
-): TranslationKeyOrString | undefined {
- if (isFunction(label)) {
- const custom = label(state);
- return custom ? custom : undefined;
- }
- if (label) return label;
- return undefined;
-}
-
-/** Per-instance cache so `getLabel` / `getLabelParams` share one resolution per state snapshot. */
-export function createOptionalControlLabelCache(): {
- resolve(
- label: TranslationKeyOrString | ((state: State) => TranslationKeyOrString) | undefined,
- state: State
- ): TranslationKeyOrString | undefined;
- invalidate(): void;
-} {
- let cache: {
- state: State;
- label: TranslationKeyOrString | ((state: State) => TranslationKeyOrString) | undefined;
- custom: TranslationKeyOrString | undefined;
- } | null = null;
-
- return {
- resolve(label, state) {
- if (cache?.state !== state || cache.label !== label) {
- cache = { state, label, custom: resolveOptionalControlLabel(label, state) };
- }
- return cache.custom;
- },
- invalidate() {
- cache = null;
- },
- };
-}
diff --git a/packages/core/src/core/ui/seek-button/seek-button-core.ts b/packages/core/src/core/ui/seek-button/seek-button-core.ts
index b856bce9..c4062016 100644
--- a/packages/core/src/core/ui/seek-button/seek-button-core.ts
+++ b/packages/core/src/core/ui/seek-button/seek-button-core.ts
@@ -1,16 +1,16 @@
import { createState } from '@videojs/store';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaTimeState } from '../../media/state';
-import { createOptionalControlLabelCache } from '../resolve-optional-control-label';
-import type { ButtonState, TranslationKeyOrString } from '../types';
+import type { ButtonState } from '../types';
export interface SeekButtonProps {
/** Seconds to seek. Positive = forward, negative = backward. Default `30`. */
seconds?: number | undefined;
/** Custom label for the button. */
- label?: TranslationKeyOrString | ((state: SeekButtonState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: SeekButtonState) => string) | undefined;
/** Whether the button is disabled. */
disabled?: boolean | undefined;
}
@@ -39,7 +39,6 @@ export class SeekButtonCore {
#props = { ...SeekButtonCore.defaultProps };
#media: MediaTimeState | null = null;
- readonly #customLabel = createOptionalControlLabelCache();
constructor(props?: SeekButtonProps) {
if (props) this.setProps(props);
@@ -47,19 +46,20 @@ export class SeekButtonCore {
setProps(props: SeekButtonProps): void {
this.#props = defaults(props, SeekButtonCore.defaultProps);
- this.#customLabel.invalidate();
}
- getLabel(state: SeekButtonState): TranslationKeyOrString {
- const custom = this.#customLabel.resolve(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: SeekButtonState): string {
+ const { label } = this.#props;
- return state.direction === 'backward' ? 'seekBackward' : 'seekForward';
- }
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
- getLabelParams(state: SeekButtonState): { seconds: number } | undefined {
- if (this.#customLabel.resolve(this.#props.label, state) !== undefined) return undefined;
- return { seconds: Math.abs(this.#props.seconds) };
+ const abs = Math.abs(this.#props.seconds);
+ return state.direction === 'backward' ? `Seek backward ${abs} seconds` : `Seek forward ${abs} seconds`;
}
getAttrs(state: SeekButtonState) {
diff --git a/packages/core/src/core/ui/seek-button/tests/seek-button-core.test.ts b/packages/core/src/core/ui/seek-button/tests/seek-button-core.test.ts
index 505a3c39..334a0f6d 100644
--- a/packages/core/src/core/ui/seek-button/tests/seek-button-core.test.ts
+++ b/packages/core/src/core/ui/seek-button/tests/seek-button-core.test.ts
@@ -75,18 +75,18 @@ describe('SeekButtonCore', () => {
describe('getLabel', () => {
it('returns forward label for forward direction', () => {
const core = new SeekButtonCore({ seconds: 30 });
- expect(core.getLabel(createState({ direction: 'forward' }))).toBe('seekForward');
+ expect(core.getLabel(createState({ direction: 'forward' }))).toBe('Seek forward 30 seconds');
});
it('returns backward label for backward direction', () => {
const core = new SeekButtonCore({ seconds: -10 });
- expect(core.getLabel(createState({ direction: 'backward' }))).toBe('seekBackward');
+ expect(core.getLabel(createState({ direction: 'backward' }))).toBe('Seek backward 10 seconds');
});
it('uses absolute value in backward label', () => {
const core = new SeekButtonCore({ seconds: -30 });
const label = core.getLabel(createState({ direction: 'backward' }));
- expect(label).toBe('seekBackward');
+ expect(label).toBe('Seek backward 30 seconds');
expect(label).not.toContain('-');
});
@@ -105,40 +105,7 @@ describe('SeekButtonCore', () => {
it('falls back to default when function returns empty', () => {
const core = new SeekButtonCore({ seconds: 10, label: () => '' });
- expect(core.getLabel(createState({ direction: 'forward' }))).toBe('seekForward');
- });
- });
-
- describe('getLabelParams', () => {
- it('returns seconds for default forward label', () => {
- const core = new SeekButtonCore({ seconds: 30 });
- expect(core.getLabelParams(createState({ direction: 'forward' }))).toEqual({ seconds: 30 });
- });
-
- it('returns positive seconds for backward seek', () => {
- const core = new SeekButtonCore({ seconds: -10 });
- expect(core.getLabelParams(createState({ direction: 'backward' }))).toEqual({ seconds: 10 });
- });
-
- it('returns undefined when custom label is set', () => {
- const core = new SeekButtonCore({ label: 'Skip' });
- expect(core.getLabelParams(createState())).toBeUndefined();
- });
-
- it('shares one label callback invocation with getLabel for the same state', () => {
- let calls = 0;
- const core = new SeekButtonCore({
- seconds: 30,
- label: () => {
- calls += 1;
- return calls === 1 ? 'custom' : '';
- },
- });
- const state = createState({ direction: 'forward' });
-
- expect(core.getLabel(state)).toBe('custom');
- expect(core.getLabelParams(state)).toBeUndefined();
- expect(calls).toBe(1);
+ expect(core.getLabel(createState({ direction: 'forward' }))).toBe('Seek forward 10 seconds');
});
});
@@ -146,7 +113,7 @@ describe('SeekButtonCore', () => {
it('returns aria-label', () => {
const core = new SeekButtonCore({ seconds: 30 });
const attrs = core.getAttrs(createState({ direction: 'forward' }));
- expect(attrs['aria-label']).toBe('seekForward');
+ expect(attrs['aria-label']).toBe('Seek forward 30 seconds');
});
it('sets aria-disabled when disabled', () => {
diff --git a/packages/core/src/core/ui/slider/slider-core.ts b/packages/core/src/core/ui/slider/slider-core.ts
index 78eb3d8f..cc81afbc 100644
--- a/packages/core/src/core/ui/slider/slider-core.ts
+++ b/packages/core/src/core/ui/slider/slider-core.ts
@@ -1,13 +1,12 @@
import { clamp, roundToStep } from '@videojs/utils/number';
import { defaults } from '@videojs/utils/object';
+import { isFunction } from '@videojs/utils/predicate';
import type { NonNullableObject } from '@videojs/utils/types';
-import { resolveOptionalControlLabel } from '../resolve-optional-control-label';
-import type { TranslationKeyOrString } from '../types';
/** Configuration shared by all slider variants. */
export interface SliderProps {
/** Custom label for the slider. */
- label?: TranslationKeyOrString | ((state: SliderState) => TranslationKeyOrString) | undefined;
+ label?: string | ((state: SliderState) => string) | undefined;
/** Step increment for value changes (arrow keys). */
step?: number | undefined;
/** Large step increment (Page Up/Down keys). */
@@ -123,8 +122,17 @@ export class SliderCore {
};
}
- getLabel(state: SliderState): TranslationKeyOrString {
- return resolveOptionalControlLabel(this.#props.label, state) ?? '';
+ getLabel(state: SliderState): string {
+ const { label } = this.#props;
+
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ return '';
}
getAttrs(state: SliderState) {
diff --git a/packages/core/src/core/ui/tests/resolve-control-attrs.test.ts b/packages/core/src/core/ui/tests/resolve-control-attrs.test.ts
deleted file mode 100644
index 91f32f13..00000000
--- a/packages/core/src/core/ui/tests/resolve-control-attrs.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import { createTranslator, translations } from '../../i18n';
-import { resolveControlAttrs } from '../resolve-control-attrs';
-
-describe('resolveControlAttrs', () => {
- const translator = createTranslator(translations, 'en');
-
- it('resolves aria-label and aria-valuetext with params', () => {
- const core = {
- getAttrs: () => ({
- role: 'slider',
- 'aria-label': 'volume',
- 'aria-valuetext': 'volumeSliderValueTextMuted',
- }),
- getValueTextParams: () => ({ percent: '50%' }),
- };
-
- expect(resolveControlAttrs(translator, core, {})).toEqual({
- role: 'slider',
- 'aria-label': 'Volume',
- 'aria-valuetext': '50%, muted',
- });
- });
-
- it('resolves time slider range valuetext', () => {
- const core = {
- getAttrs: () => ({
- 'aria-valuetext': 'timeSliderValueTextRange',
- }),
- getValueTextParams: () => ({ current: '1 minute', duration: '5 minutes' }),
- };
-
- expect(resolveControlAttrs(translator, core, {})['aria-valuetext']).toBe('1 minute of 5 minutes');
- });
-});
diff --git a/packages/core/src/core/ui/tests/resolve-optional-control-label.test.ts b/packages/core/src/core/ui/tests/resolve-optional-control-label.test.ts
deleted file mode 100644
index 4a354172..00000000
--- a/packages/core/src/core/ui/tests/resolve-optional-control-label.test.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import { createOptionalControlLabelCache, resolveOptionalControlLabel } from '../resolve-optional-control-label';
-
-describe('resolveOptionalControlLabel', () => {
- const state = { x: 1 as const };
-
- it('returns undefined when label is missing', () => {
- expect(resolveOptionalControlLabel(undefined, state)).toBeUndefined();
- });
-
- it('returns undefined for empty string label', () => {
- expect(resolveOptionalControlLabel('', state)).toBeUndefined();
- });
-
- it('returns string label when set', () => {
- expect(resolveOptionalControlLabel('Custom', state)).toBe('Custom');
- });
-
- it('returns callback result when truthy', () => {
- expect(resolveOptionalControlLabel(() => 'From state', state)).toBe('From state');
- expect(resolveOptionalControlLabel((s) => `x=${s.x}`, state)).toBe('x=1');
- });
-
- it('returns undefined when callback returns empty string', () => {
- expect(resolveOptionalControlLabel(() => '', state)).toBeUndefined();
- });
-});
-
-describe('createOptionalControlLabelCache', () => {
- const state = { x: 1 as const };
-
- it('reuses resolution for the same state snapshot', () => {
- let calls = 0;
- const cache = createOptionalControlLabelCache();
- const label = () => {
- calls += 1;
- return calls === 1 ? 'first' : '';
- };
-
- expect(cache.resolve(label, state)).toBe('first');
- expect(cache.resolve(label, state)).toBe('first');
- expect(calls).toBe(1);
- });
-
- it('invalidates cached resolution', () => {
- const cache = createOptionalControlLabelCache();
-
- expect(cache.resolve('A', state)).toBe('A');
- cache.invalidate();
- expect(cache.resolve('B', state)).toBe('B');
- });
-
- it('re-resolves when label changes for the same state snapshot', () => {
- const cache = createOptionalControlLabelCache();
-
- expect(cache.resolve('A', state)).toBe('A');
- expect(cache.resolve('B', state)).toBe('B');
- });
-});
diff --git a/packages/core/src/core/ui/time-slider/tests/time-slider-core.test.ts b/packages/core/src/core/ui/time-slider/tests/time-slider-core.test.ts
index 5d114bf4..a8e91a41 100644
--- a/packages/core/src/core/ui/time-slider/tests/time-slider-core.test.ts
+++ b/packages/core/src/core/ui/time-slider/tests/time-slider-core.test.ts
@@ -1,4 +1,3 @@
-import { formatDuration } from '@videojs/utils/time';
import { describe, expect, it, vi } from 'vitest';
import type { MediaBufferState, MediaTimeState } from '../../../media/state';
@@ -34,7 +33,7 @@ describe('TimeSliderCore', () => {
describe('defaultProps', () => {
it('has expected defaults', () => {
expect(TimeSliderCore.defaultProps).toEqual({
- label: '',
+ label: 'Seek',
step: 1,
largeStep: 10,
orientation: 'horizontal',
@@ -157,12 +156,8 @@ describe('TimeSliderCore', () => {
const state = core.getState();
const attrs = core.getAttrs(state);
- expect(attrs['aria-label']).toBe('seek');
- expect(attrs['aria-valuetext']).toBe('timeSliderValueTextRange');
- expect(core.getValueTextParams(state)).toEqual({
- current: formatDuration(90),
- duration: formatDuration(300),
- });
+ expect(attrs['aria-label']).toBe('Seek');
+ expect(attrs['aria-valuetext']).toBe('1 minute, 30 seconds of 5 minutes');
expect(attrs.role).toBe('slider');
});
@@ -183,11 +178,7 @@ describe('TimeSliderCore', () => {
const state = core.getState();
const attrs = core.getAttrs(state);
- expect(attrs['aria-valuetext']).toBe('timeSliderValueTextRange');
- expect(core.getValueTextParams(state)).toEqual({
- current: formatDuration(0),
- duration: formatDuration(0),
- });
+ expect(attrs['aria-valuetext']).toBe('0 seconds of 0 seconds');
});
it('announces drag position in valuetext during drag', () => {
@@ -197,12 +188,9 @@ describe('TimeSliderCore', () => {
const state = core.getState();
const attrs = core.getAttrs(state);
+ // pointerPercent is 50 → 150s → "2 minutes, 30 seconds of 5 minutes"
expect(attrs['aria-valuenow']).toBe(150);
- expect(attrs['aria-valuetext']).toBe('timeSliderValueTextRange');
- expect(core.getValueTextParams(state)).toEqual({
- current: formatDuration(150),
- duration: formatDuration(300),
- });
+ expect(attrs['aria-valuetext']).toBe('2 minutes, 30 seconds of 5 minutes');
});
});
diff --git a/packages/core/src/core/ui/time-slider/time-slider-core.ts b/packages/core/src/core/ui/time-slider/time-slider-core.ts
index 8a8f63dd..2620180a 100644
--- a/packages/core/src/core/ui/time-slider/time-slider-core.ts
+++ b/packages/core/src/core/ui/time-slider/time-slider-core.ts
@@ -1,10 +1,9 @@
import { defaults } from '@videojs/utils/object';
-import { formatDuration, type TimeFormatOptions } from '@videojs/utils/time';
+import { formatTimeAsPhrase } from '@videojs/utils/time';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaBufferState, MediaTimeState } from '../../media/state';
import { SliderCore, type SliderProps, type SliderState } from '../slider/slider-core';
-import type { TranslationKeyOrString } from '../types';
export interface TimeSliderProps extends SliderProps {
/** @internal Derived from `currentTime` — not user-settable. */
@@ -15,8 +14,6 @@ export interface TimeSliderProps extends SliderProps {
max?: number | undefined;
/** Leading+trailing throttle (ms) for `onValueChange` during drag. */
changeThrottle?: number | undefined;
- /** Options for `formatDuration` when building the slider thumb `aria-valuetext`. */
- formatOptions?: TimeFormatOptions | undefined;
}
export interface TimeSliderState extends SliderState, Pick {
@@ -26,13 +23,13 @@ export interface TimeSliderState extends SliderState, Pick> = {
+ static override readonly defaultProps: NonNullableObject = {
...SliderCore.defaultProps,
- label: '',
+ label: 'Seek',
changeThrottle: 100,
};
- #props: TimeSliderProps = { ...TimeSliderCore.defaultProps };
+ #props = { ...TimeSliderCore.defaultProps };
#media: (MediaTimeState & MediaBufferState) | null = null;
constructor(props?: TimeSliderProps) {
@@ -71,38 +68,23 @@ export class TimeSliderCore extends SliderCore {
};
}
- override getLabel(state: SliderState): TranslationKeyOrString {
- return super.getLabel(state) || 'seek';
- }
-
- #announceValue(state: TimeSliderState): number {
- return state.dragging ? this.rawValueFromPercent(state.pointerPercent) : state.value;
- }
-
- getValueText(state: TimeSliderState): TranslationKeyOrString {
- return Number.isFinite(state.duration) ? 'timeSliderValueTextRange' : this.getValueTextParams(state).current;
- }
-
- getValueTextParams(state: TimeSliderState): { current: string; duration: string } | { current: string } {
- const formatOptions = this.#props.formatOptions;
- const current = formatDuration(this.#announceValue(state), formatOptions);
- if (!Number.isFinite(state.duration)) {
- return { current };
- }
- return {
- current,
- duration: formatDuration(state.duration, formatOptions),
- };
+ override getLabel(state: SliderState): string {
+ return super.getLabel(state) || 'Seek';
}
override getAttrs(state: TimeSliderState) {
const base = super.getAttrs(state);
- const announceValue = this.#announceValue(state);
+
+ // During drag, announce the pointer position the user would seek to.
+ const announceValue = state.dragging ? this.rawValueFromPercent(state.pointerPercent) : state.value;
+ const currentPhrase = formatTimeAsPhrase(announceValue);
+ const durationPhrase = formatTimeAsPhrase(state.duration);
+ const valuetext = durationPhrase ? `${currentPhrase} of ${durationPhrase}` : currentPhrase;
return {
...base,
'aria-valuenow': announceValue,
- 'aria-valuetext': this.getValueText(state),
+ 'aria-valuetext': valuetext,
};
}
}
diff --git a/packages/core/src/core/ui/time/tests/time-core.test.ts b/packages/core/src/core/ui/time/tests/time-core.test.ts
index 136b8489..7122ede5 100644
--- a/packages/core/src/core/ui/time/tests/time-core.test.ts
+++ b/packages/core/src/core/ui/time/tests/time-core.test.ts
@@ -1,4 +1,3 @@
-import { formatDuration } from '@videojs/utils/time';
import { describe, expect, it } from 'vitest';
import type { MediaTimeState } from '../../../media/state';
@@ -41,7 +40,7 @@ describe('TimeCore', () => {
expect(state.seconds).toBe(90);
expect(state.negative).toBe(false);
expect(state.text).toBe('1:30');
- expect(state.phrase).toBe(formatDuration(90));
+ expect(state.phrase).toBe('1 minute, 30 seconds');
expect(state.datetime).toBe('PT1M30S');
});
@@ -54,7 +53,7 @@ describe('TimeCore', () => {
expect(state.seconds).toBe(300);
expect(state.negative).toBe(false);
expect(state.text).toBe('5:00');
- expect(state.phrase).toBe(formatDuration(300));
+ expect(state.phrase).toBe('5 minutes');
expect(state.datetime).toBe('PT5M');
});
@@ -67,7 +66,7 @@ describe('TimeCore', () => {
expect(state.seconds).toBe(-210); // 90 - 300
expect(state.negative).toBe(true);
expect(state.text).toBe('3:30');
- expect(state.phrase).toBe(formatDuration(90 - 300));
+ expect(state.phrase).toBe('3 minutes, 30 seconds remaining');
expect(state.datetime).toBe('PT3M30S');
});
@@ -104,21 +103,21 @@ describe('TimeCore', () => {
const core = new TimeCore({ type: 'current' });
core.setMedia(createMediaState());
const state = core.getState();
- expect(core.getLabel(state)).toBe('timeCurrent');
+ expect(core.getLabel(state)).toBe('Current time');
});
it('returns default label for duration', () => {
const core = new TimeCore({ type: 'duration' });
core.setMedia(createMediaState());
const state = core.getState();
- expect(core.getLabel(state)).toBe('timeDuration');
+ expect(core.getLabel(state)).toBe('Duration');
});
it('returns default label for remaining', () => {
const core = new TimeCore({ type: 'remaining' });
core.setMedia(createMediaState());
const state = core.getState();
- expect(core.getLabel(state)).toBe('timeRemaining');
+ expect(core.getLabel(state)).toBe('Remaining');
});
it('returns custom string label', () => {
@@ -146,8 +145,8 @@ describe('TimeCore', () => {
const state = core.getState();
const attrs = core.getAttrs(state);
- expect(attrs['aria-label']).toBe('timeCurrent');
- expect(attrs['aria-valuetext']).toBe(formatDuration(90));
+ expect(attrs['aria-label']).toBe('Current time');
+ expect(attrs['aria-valuetext']).toBe('1 minute, 30 seconds');
});
it('includes remaining suffix in valuetext', () => {
@@ -156,22 +155,8 @@ describe('TimeCore', () => {
const state = core.getState();
const attrs = core.getAttrs(state);
- expect(attrs['aria-label']).toBe('timeRemaining');
- expect(attrs['aria-valuetext']).toBe(formatDuration(90 - 300));
- });
-
- it('uses formatRemaining for remaining phrase when provided', () => {
- const core = new TimeCore({
- type: 'remaining',
- formatOptions: {
- locale: 'en',
- formatRemaining: (duration) => `quedan ${duration}`,
- },
- });
- core.setMedia(createMediaState({ currentTime: 60, duration: 120 }));
- const state = core.getState();
-
- expect(state.phrase.startsWith('quedan ')).toBe(true);
+ expect(attrs['aria-label']).toBe('Remaining');
+ expect(attrs['aria-valuetext']).toBe('3 minutes, 30 seconds remaining');
});
});
});
diff --git a/packages/core/src/core/ui/time/time-core.ts b/packages/core/src/core/ui/time/time-core.ts
index db5eb331..818c402e 100644
--- a/packages/core/src/core/ui/time/time-core.ts
+++ b/packages/core/src/core/ui/time/time-core.ts
@@ -1,10 +1,9 @@
import { defaults } from '@videojs/utils/object';
-import { formatDuration, formatTime, secondsToIsoDuration, type TimeFormatOptions } from '@videojs/utils/time';
+import { isFunction } from '@videojs/utils/predicate';
+import { formatTime, formatTimeAsPhrase, secondsToIsoDuration } from '@videojs/utils/time';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaTimeState } from '../../media/state';
-import { resolveOptionalControlLabel } from '../resolve-optional-control-label';
-import type { TranslationKeyOrString } from '../types';
/** Time display type. */
export type TimeType = 'current' | 'duration' | 'remaining';
@@ -15,9 +14,7 @@ export interface TimeProps {
/** Symbol prepended to remaining time. */
negativeSign?: string | undefined;
/** Custom label for accessibility. */
- label?: TranslationKeyOrString | ((state: TimeState) => TranslationKeyOrString) | undefined;
- /** Options for `formatDuration` when building spoken-duration copy (`phrase` state and screen readers), not digital clock text. */
- formatOptions?: TimeFormatOptions | undefined;
+ label?: string | ((state: TimeState) => string) | undefined;
}
export interface TimeState {
@@ -35,22 +32,20 @@ export interface TimeState {
datetime: string;
}
-const DEFAULT_LABEL_KEYS: Record = {
- current: 'timeCurrent',
- duration: 'timeDuration',
- remaining: 'timeRemaining',
+const DEFAULT_LABELS: Record = {
+ current: 'Current time',
+ duration: 'Duration',
+ remaining: 'Remaining',
};
-type TimeCoreResolvedProps = NonNullableObject> & Pick;
-
export class TimeCore {
- static readonly defaultProps: NonNullableObject> = {
+ static readonly defaultProps: NonNullableObject = {
type: 'current',
negativeSign: '-',
label: '',
};
- #props: TimeCoreResolvedProps = { ...TimeCore.defaultProps };
+ #props = { ...TimeCore.defaultProps };
#media: MediaTimeState | null = null;
constructor(props?: TimeProps) {
@@ -58,7 +53,7 @@ export class TimeCore {
}
setProps(props: TimeProps): void {
- this.#props = defaults(props, TimeCore.defaultProps) as TimeCoreResolvedProps;
+ this.#props = defaults(props, TimeCore.defaultProps);
}
setMedia(media: MediaTimeState): void {
@@ -87,15 +82,15 @@ export class TimeCore {
}
#getPhrase(): string {
- const { type, formatOptions } = this.#props;
+ const { type } = this.#props;
const seconds = this.#getSeconds();
if (type === 'remaining') {
// Use negative to trigger "remaining" suffix
- return formatDuration(seconds < 0 ? seconds : -Math.abs(seconds), formatOptions);
+ return formatTimeAsPhrase(seconds < 0 ? seconds : -Math.abs(seconds));
}
- return formatDuration(seconds, formatOptions);
+ return formatTimeAsPhrase(seconds);
}
#getDatetime(): string {
@@ -103,11 +98,17 @@ export class TimeCore {
return secondsToIsoDuration(Math.abs(seconds));
}
- getLabel(state: TimeState): TranslationKeyOrString {
- const custom = resolveOptionalControlLabel(this.#props.label, state);
- if (custom !== undefined) return custom;
+ getLabel(state: TimeState): string {
+ const { label } = this.#props;
- return DEFAULT_LABEL_KEYS[this.#props.type];
+ if (isFunction(label)) {
+ const customLabel = label(state);
+ if (customLabel) return customLabel;
+ } else if (label) {
+ return label;
+ }
+
+ return DEFAULT_LABELS[this.#props.type];
}
getAttrs(state: TimeState) {
diff --git a/packages/core/src/core/ui/types.ts b/packages/core/src/core/ui/types.ts
index 0dfa7ecb..308bcefb 100644
--- a/packages/core/src/core/ui/types.ts
+++ b/packages/core/src/core/ui/types.ts
@@ -1,14 +1,5 @@
import type { State } from '@videojs/store';
-import type { TranslationKeyOrString } from '../i18n/types';
-
-export type { TranslationKeyOrString };
-
-/** DOM attrs where `aria-label` is a {@link TranslationKeyOrString} resolved by the platform layer. */
-export type TranslationAriaLabelAttrs = {
- 'aria-label'?: TranslationKeyOrString;
-};
-
export type StateAttrMap = {
[Key in keyof State]?: string;
};
@@ -26,14 +17,14 @@ export interface MediaUIComponent
}
export interface ButtonState {
- label: TranslationKeyOrString;
+ label: string;
}
/** Constraint for media button cores that provide a label derived from state. */
export interface MediaButtonComponent
extends MediaUIComponent {
readonly state: State;
- getLabel(state: ComponentState): TranslationKeyOrString;
+ getLabel(state: ComponentState): string;
}
/** Extracts the media state parameter type from a core's `setMedia` method. */
diff --git a/packages/core/src/core/ui/volume-slider/tests/volume-slider-core.test.ts b/packages/core/src/core/ui/volume-slider/tests/volume-slider-core.test.ts
index cf595d33..08403362 100644
--- a/packages/core/src/core/ui/volume-slider/tests/volume-slider-core.test.ts
+++ b/packages/core/src/core/ui/volume-slider/tests/volume-slider-core.test.ts
@@ -1,4 +1,3 @@
-import { formatVolumePercent } from '@videojs/utils/time';
import { describe, expect, it, vi } from 'vitest';
import type { MediaVolumeState } from '../../../media/state';
@@ -31,7 +30,7 @@ describe('VolumeSliderCore', () => {
describe('defaultProps', () => {
it('has expected defaults', () => {
expect(VolumeSliderCore.defaultProps).toEqual({
- label: '',
+ label: 'Volume',
step: 1,
largeStep: 10,
wheelStep: 5,
@@ -141,9 +140,8 @@ describe('VolumeSliderCore', () => {
const state = core.getState();
const attrs = core.getAttrs(state);
- expect(attrs['aria-label']).toBe('volume');
- expect(attrs['aria-valuetext']).toBe(formatVolumePercent(0.75));
- expect(core.getValueTextParams(state)).toEqual({ percent: formatVolumePercent(0.75) });
+ expect(attrs['aria-label']).toBe('Volume');
+ expect(attrs['aria-valuetext']).toBe('75 percent');
expect(attrs.role).toBe('slider');
});
@@ -154,8 +152,7 @@ describe('VolumeSliderCore', () => {
const state = core.getState();
const attrs = core.getAttrs(state);
- expect(attrs['aria-valuetext']).toBe('volumeSliderValueTextMuted');
- expect(core.getValueTextParams(state)).toEqual({ percent: formatVolumePercent(0.5) });
+ expect(attrs['aria-valuetext']).toBe('50 percent, muted');
});
it('rounds value in valuetext', () => {
@@ -165,8 +162,7 @@ describe('VolumeSliderCore', () => {
const state = core.getState();
const attrs = core.getAttrs(state);
- expect(attrs['aria-valuetext']).toBe(formatVolumePercent(0.333));
- expect(core.getValueTextParams(state)).toEqual({ percent: formatVolumePercent(0.333) });
+ expect(attrs['aria-valuetext']).toBe('33 percent');
});
it('uses custom label', () => {
@@ -186,8 +182,7 @@ describe('VolumeSliderCore', () => {
const state = core.getState();
const attrs = core.getAttrs(state);
- expect(attrs['aria-valuetext']).toBe('volumeSliderValueTextMuted');
- expect(core.getValueTextParams(state)).toEqual({ percent: formatVolumePercent(0) });
+ expect(attrs['aria-valuetext']).toBe('0 percent, muted');
});
});
diff --git a/packages/core/src/core/ui/volume-slider/volume-slider-core.ts b/packages/core/src/core/ui/volume-slider/volume-slider-core.ts
index f8865233..0e77e7b9 100644
--- a/packages/core/src/core/ui/volume-slider/volume-slider-core.ts
+++ b/packages/core/src/core/ui/volume-slider/volume-slider-core.ts
@@ -1,10 +1,8 @@
import { defaults } from '@videojs/utils/object';
-import { formatVolumePercent } from '@videojs/utils/time';
import type { NonNullableObject } from '@videojs/utils/types';
import type { MediaVolumeState } from '../../media/state';
import type { MediaFeatureAvailability } from '../../media/types';
import { SliderCore, type SliderProps, type SliderState } from '../slider/slider-core';
-import type { TranslationKeyOrString } from '../types';
export interface VolumeSliderProps extends SliderProps {
/** Step increment for wheel scrolling. */
@@ -25,12 +23,11 @@ export interface VolumeSliderState extends SliderState, Pick = {
...SliderCore.defaultProps,
- label: '',
+ label: 'Volume',
wheelStep: 5,
};
#media: MediaVolumeState | null = null;
- #formatLocale: string | string[] | undefined;
constructor(props?: VolumeSliderProps) {
super();
@@ -45,11 +42,6 @@ export class VolumeSliderCore extends SliderCore {
this.#media = media;
}
- /** @internal Platform adapters set the active i18n locale for `aria-valuetext` percent formatting. */
- setFormatLocale(locale: string | string[] | undefined): void {
- this.#formatLocale = locale;
- }
-
getState(): VolumeSliderState {
const media = this.#media!;
const { volume, muted } = media;
@@ -75,24 +67,17 @@ export class VolumeSliderCore extends SliderCore {
return range > 0 ? (props.wheelStep / range) * 100 : 0;
}
- override getLabel(state: SliderState): TranslationKeyOrString {
- return super.getLabel(state) || 'volume';
- }
-
- getValueText(state: VolumeSliderState): TranslationKeyOrString {
- return state.muted ? 'volumeSliderValueTextMuted' : this.getValueTextParams(state).percent;
- }
-
- getValueTextParams(state: VolumeSliderState): { percent: string } {
- return { percent: formatVolumePercent(state.value / 100, this.#formatLocale) };
+ override getLabel(state: SliderState): string {
+ return super.getLabel(state) || 'Volume';
}
override getAttrs(state: VolumeSliderState) {
const base = super.getAttrs(state);
+ const valuetext = `${Math.round(state.value)} percent${state.muted ? ', muted' : ''}`;
return {
...base,
- 'aria-valuetext': this.getValueText(state),
+ 'aria-valuetext': valuetext,
};
}
}
diff --git a/packages/core/src/dom/media/native-hls/errors.ts b/packages/core/src/dom/media/native-hls/errors.ts
index f403982b..df59e385 100644
--- a/packages/core/src/dom/media/native-hls/errors.ts
+++ b/packages/core/src/dom/media/native-hls/errors.ts
@@ -49,9 +49,7 @@ export function NativeHlsMediaErrorsMixin= MediaError.MEDIA_ERR_ABORTED && code <= MediaError.MEDIA_ERR_ENCRYPTED;
- const error = new MediaError(useCanonicalMessage ? undefined : native.message, code, true);
+ const error = new MediaError(native.message, native.code, true);
this.#error = error;
this.dispatchEvent(new ErrorEvent('error', { error, message: error.message }));
diff --git a/packages/core/src/dom/media/native-hls/tests/errors.test.ts b/packages/core/src/dom/media/native-hls/tests/errors.test.ts
index 6526f8fd..5b0e4719 100644
--- a/packages/core/src/dom/media/native-hls/tests/errors.test.ts
+++ b/packages/core/src/dom/media/native-hls/tests/errors.test.ts
@@ -42,20 +42,7 @@ describe('NativeHlsMediaErrorsMixin', () => {
expect(event.error).toBeInstanceOf(MediaError);
expect(event.error.code).toBe(MediaError.MEDIA_ERR_NETWORK);
expect(event.error.fatal).toBe(true);
- expect(event.error.message).toBe(MediaError.defaultMessages[MediaError.MEDIA_ERR_NETWORK]);
- });
-
- it('normalizes browser-specific messages for standard error codes', () => {
- const { host, video } = setup();
-
- const handler = vi.fn();
- host.addEventListener('error', handler);
-
- fireNativeError(video, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, 'Failed to open media');
-
- const event = handler.mock.calls[0]![0] as ErrorEvent;
- expect(event.error.code).toBe(MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
- expect(event.error.message).toBe(MediaError.defaultMessages[MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED]);
+ expect(event.error.message).toBe('network failure');
});
it('uses default message when native error has no message', () => {
@@ -76,11 +63,10 @@ describe('NativeHlsMediaErrorsMixin', () => {
expect(host.error).toBeNull();
- fireNativeError(video, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, 'Failed to open media');
+ fireNativeError(video, MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, 'unsupported');
expect(host.error).toBeInstanceOf(MediaError);
expect(host.error!.code).toBe(MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED);
- expect(host.error!.message).toBe(MediaError.defaultMessages[MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED]);
});
it('stops propagation of the native error event', () => {
diff --git a/packages/core/src/dom/media/native-hls/tests/native-hls-custom-media.test.ts b/packages/core/src/dom/media/native-hls/tests/native-hls-custom-media.test.ts
index 49de37de..a38f9c07 100644
--- a/packages/core/src/dom/media/native-hls/tests/native-hls-custom-media.test.ts
+++ b/packages/core/src/dom/media/native-hls/tests/native-hls-custom-media.test.ts
@@ -29,6 +29,6 @@ describe('NativeHlsMedia', () => {
expect(event).toBeInstanceOf(ErrorEvent);
expect(event.error).toBeInstanceOf(MediaError);
expect(event.error.code).toBe(MediaError.MEDIA_ERR_NETWORK);
- expect(event.error.message).toBe(MediaError.defaultMessages[MediaError.MEDIA_ERR_NETWORK]);
+ expect(event.error.message).toBe('network failure');
});
});
diff --git a/packages/core/tsdown.config.ts b/packages/core/tsdown.config.ts
index 1a63d1f0..a7331e2b 100644
--- a/packages/core/tsdown.config.ts
+++ b/packages/core/tsdown.config.ts
@@ -2,21 +2,12 @@ import type { UserConfig } from 'tsdown';
import { defineConfig } from 'tsdown';
import { type PackageBuildMode, packageBuildConfig, packageBuildModes } from '../../build/tsdown.ts';
import packageJson from './package.json' with { type: 'json' };
-import { SHIPPED_LOCALE_TAGS } from './src/core/i18n/built-in-locales.ts';
-
-const localeEntries = Object.fromEntries([
- ['i18n/locales/all', './src/core/i18n/locales/all.ts'],
- ['i18n/locales/en', './src/core/i18n/locales/en.ts'],
- ...SHIPPED_LOCALE_TAGS.map((tag) => [`i18n/locales/${tag}`, `./src/core/i18n/locales/${tag}.ts`]),
-]);
const createConfig = (mode: PackageBuildMode): UserConfig => ({
...packageBuildConfig(mode, 'neutral'),
entry: {
index: './src/core/index.ts',
'media/predicate': './src/core/media/predicate.ts',
- i18n: './src/core/i18n/index.ts',
- ...localeEntries,
dom: './src/dom/index.ts',
'dom/media/media-host/index': './src/dom/media/media-host.ts',
'dom/media/custom-media-element/index': './src/dom/media/custom-media-element/index.ts',
diff --git a/packages/html/scripts/build-cdn-locales.ts b/packages/html/scripts/build-cdn-locales.ts
deleted file mode 100644
index 2d4bd159..00000000
--- a/packages/html/scripts/build-cdn-locales.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { mkdirSync, writeFileSync } from 'node:fs';
-import { dirname, resolve } from 'node:path';
-import { fileURLToPath } from 'node:url';
-
-import { SHIPPED_LOCALE_TAGS } from '../../core/src/core/i18n/built-in-locales.ts';
-
-const REGISTRY = '@videojs/html/cdn/i18n-registry';
-const outDir = resolve(dirname(fileURLToPath(import.meta.url)), '../src/cdn/locales');
-const tags = SHIPPED_LOCALE_TAGS;
-
-mkdirSync(outDir, { recursive: true });
-
-for (const tag of tags) {
- const body = `import { registerI18n } from '${REGISTRY}';
-import translations from '@videojs/core/i18n/locales/${tag}';
-
-registerI18n('${tag}', translations);
-`;
- writeFileSync(resolve(outDir, `${tag}.ts`), body);
-}
diff --git a/packages/html/src/cdn/i18n-registry.ts b/packages/html/src/cdn/i18n-registry.ts
deleted file mode 100644
index 3227819b..00000000
--- a/packages/html/src/cdn/i18n-registry.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { registerI18n } from '@videojs/core/i18n';
diff --git a/packages/html/src/cdn/locales/.gitignore b/packages/html/src/cdn/locales/.gitignore
deleted file mode 100644
index 6461deec..00000000
--- a/packages/html/src/cdn/locales/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.ts
diff --git a/packages/html/src/i18n/locales/all.ts b/packages/html/src/i18n/locales/all.ts
deleted file mode 100644
index e1016b19..00000000
--- a/packages/html/src/i18n/locales/all.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { all, type LocaleTag, localeTags } from '@videojs/core/i18n/locales/all';
diff --git a/packages/html/src/i18n/locales/ar.ts b/packages/html/src/i18n/locales/ar.ts
deleted file mode 100644
index 0e389db2..00000000
--- a/packages/html/src/i18n/locales/ar.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ar';
diff --git a/packages/html/src/i18n/locales/az.ts b/packages/html/src/i18n/locales/az.ts
deleted file mode 100644
index 6d46a490..00000000
--- a/packages/html/src/i18n/locales/az.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/az';
diff --git a/packages/html/src/i18n/locales/bg.ts b/packages/html/src/i18n/locales/bg.ts
deleted file mode 100644
index 118cef65..00000000
--- a/packages/html/src/i18n/locales/bg.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/bg';
diff --git a/packages/html/src/i18n/locales/bn.ts b/packages/html/src/i18n/locales/bn.ts
deleted file mode 100644
index 76d645b5..00000000
--- a/packages/html/src/i18n/locales/bn.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/bn';
diff --git a/packages/html/src/i18n/locales/bs.ts b/packages/html/src/i18n/locales/bs.ts
deleted file mode 100644
index ef50bd94..00000000
--- a/packages/html/src/i18n/locales/bs.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/bs';
diff --git a/packages/html/src/i18n/locales/ca.ts b/packages/html/src/i18n/locales/ca.ts
deleted file mode 100644
index 050a7265..00000000
--- a/packages/html/src/i18n/locales/ca.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ca';
diff --git a/packages/html/src/i18n/locales/cs.ts b/packages/html/src/i18n/locales/cs.ts
deleted file mode 100644
index 924e7107..00000000
--- a/packages/html/src/i18n/locales/cs.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/cs';
diff --git a/packages/html/src/i18n/locales/cy.ts b/packages/html/src/i18n/locales/cy.ts
deleted file mode 100644
index 9c0bbd4a..00000000
--- a/packages/html/src/i18n/locales/cy.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/cy';
diff --git a/packages/html/src/i18n/locales/da.ts b/packages/html/src/i18n/locales/da.ts
deleted file mode 100644
index 9f3fd735..00000000
--- a/packages/html/src/i18n/locales/da.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/da';
diff --git a/packages/html/src/i18n/locales/de.ts b/packages/html/src/i18n/locales/de.ts
deleted file mode 100644
index 67a9d0c5..00000000
--- a/packages/html/src/i18n/locales/de.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/de';
diff --git a/packages/html/src/i18n/locales/el.ts b/packages/html/src/i18n/locales/el.ts
deleted file mode 100644
index 0e812607..00000000
--- a/packages/html/src/i18n/locales/el.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/el';
diff --git a/packages/html/src/i18n/locales/en.ts b/packages/html/src/i18n/locales/en.ts
deleted file mode 100644
index 17569535..00000000
--- a/packages/html/src/i18n/locales/en.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/en';
diff --git a/packages/html/src/i18n/locales/es.ts b/packages/html/src/i18n/locales/es.ts
deleted file mode 100644
index 592a8e3b..00000000
--- a/packages/html/src/i18n/locales/es.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/es';
diff --git a/packages/html/src/i18n/locales/et.ts b/packages/html/src/i18n/locales/et.ts
deleted file mode 100644
index 2cdaa205..00000000
--- a/packages/html/src/i18n/locales/et.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/et';
diff --git a/packages/html/src/i18n/locales/eu.ts b/packages/html/src/i18n/locales/eu.ts
deleted file mode 100644
index 3aedfc0b..00000000
--- a/packages/html/src/i18n/locales/eu.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/eu';
diff --git a/packages/html/src/i18n/locales/fa.ts b/packages/html/src/i18n/locales/fa.ts
deleted file mode 100644
index cad46b97..00000000
--- a/packages/html/src/i18n/locales/fa.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/fa';
diff --git a/packages/html/src/i18n/locales/fi.ts b/packages/html/src/i18n/locales/fi.ts
deleted file mode 100644
index c72f17ed..00000000
--- a/packages/html/src/i18n/locales/fi.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/fi';
diff --git a/packages/html/src/i18n/locales/fr.ts b/packages/html/src/i18n/locales/fr.ts
deleted file mode 100644
index c71d6575..00000000
--- a/packages/html/src/i18n/locales/fr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/fr';
diff --git a/packages/html/src/i18n/locales/gd.ts b/packages/html/src/i18n/locales/gd.ts
deleted file mode 100644
index 4a9cdef7..00000000
--- a/packages/html/src/i18n/locales/gd.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/gd';
diff --git a/packages/html/src/i18n/locales/gl.ts b/packages/html/src/i18n/locales/gl.ts
deleted file mode 100644
index 313cea85..00000000
--- a/packages/html/src/i18n/locales/gl.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/gl';
diff --git a/packages/html/src/i18n/locales/he.ts b/packages/html/src/i18n/locales/he.ts
deleted file mode 100644
index c108668f..00000000
--- a/packages/html/src/i18n/locales/he.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/he';
diff --git a/packages/html/src/i18n/locales/hi.ts b/packages/html/src/i18n/locales/hi.ts
deleted file mode 100644
index 53b36476..00000000
--- a/packages/html/src/i18n/locales/hi.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/hi';
diff --git a/packages/html/src/i18n/locales/hr.ts b/packages/html/src/i18n/locales/hr.ts
deleted file mode 100644
index bd7eab16..00000000
--- a/packages/html/src/i18n/locales/hr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/hr';
diff --git a/packages/html/src/i18n/locales/hu.ts b/packages/html/src/i18n/locales/hu.ts
deleted file mode 100644
index a163f809..00000000
--- a/packages/html/src/i18n/locales/hu.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/hu';
diff --git a/packages/html/src/i18n/locales/it.ts b/packages/html/src/i18n/locales/it.ts
deleted file mode 100644
index fd1233ed..00000000
--- a/packages/html/src/i18n/locales/it.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/it';
diff --git a/packages/html/src/i18n/locales/ja.ts b/packages/html/src/i18n/locales/ja.ts
deleted file mode 100644
index 4e36df11..00000000
--- a/packages/html/src/i18n/locales/ja.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ja';
diff --git a/packages/html/src/i18n/locales/ko.ts b/packages/html/src/i18n/locales/ko.ts
deleted file mode 100644
index 548d7bf6..00000000
--- a/packages/html/src/i18n/locales/ko.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ko';
diff --git a/packages/html/src/i18n/locales/lv.ts b/packages/html/src/i18n/locales/lv.ts
deleted file mode 100644
index c59135b4..00000000
--- a/packages/html/src/i18n/locales/lv.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/lv';
diff --git a/packages/html/src/i18n/locales/mr.ts b/packages/html/src/i18n/locales/mr.ts
deleted file mode 100644
index df74d3da..00000000
--- a/packages/html/src/i18n/locales/mr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/mr';
diff --git a/packages/html/src/i18n/locales/nb.ts b/packages/html/src/i18n/locales/nb.ts
deleted file mode 100644
index 87bc17cf..00000000
--- a/packages/html/src/i18n/locales/nb.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/nb';
diff --git a/packages/html/src/i18n/locales/ne.ts b/packages/html/src/i18n/locales/ne.ts
deleted file mode 100644
index 7910ce89..00000000
--- a/packages/html/src/i18n/locales/ne.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ne';
diff --git a/packages/html/src/i18n/locales/nl.ts b/packages/html/src/i18n/locales/nl.ts
deleted file mode 100644
index 9cc416cc..00000000
--- a/packages/html/src/i18n/locales/nl.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/nl';
diff --git a/packages/html/src/i18n/locales/nn.ts b/packages/html/src/i18n/locales/nn.ts
deleted file mode 100644
index 02a3df21..00000000
--- a/packages/html/src/i18n/locales/nn.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/nn';
diff --git a/packages/html/src/i18n/locales/oc.ts b/packages/html/src/i18n/locales/oc.ts
deleted file mode 100644
index c19d4111..00000000
--- a/packages/html/src/i18n/locales/oc.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/oc';
diff --git a/packages/html/src/i18n/locales/pl.ts b/packages/html/src/i18n/locales/pl.ts
deleted file mode 100644
index e4c4e758..00000000
--- a/packages/html/src/i18n/locales/pl.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/pl';
diff --git a/packages/html/src/i18n/locales/pt-BR.ts b/packages/html/src/i18n/locales/pt-BR.ts
deleted file mode 100644
index 1c82e0f0..00000000
--- a/packages/html/src/i18n/locales/pt-BR.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/pt-BR';
diff --git a/packages/html/src/i18n/locales/pt-PT.ts b/packages/html/src/i18n/locales/pt-PT.ts
deleted file mode 100644
index 248f58ee..00000000
--- a/packages/html/src/i18n/locales/pt-PT.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/pt-PT';
diff --git a/packages/html/src/i18n/locales/pt.ts b/packages/html/src/i18n/locales/pt.ts
deleted file mode 100644
index 99e488b4..00000000
--- a/packages/html/src/i18n/locales/pt.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/pt';
diff --git a/packages/html/src/i18n/locales/ro.ts b/packages/html/src/i18n/locales/ro.ts
deleted file mode 100644
index 55fd4cc0..00000000
--- a/packages/html/src/i18n/locales/ro.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ro';
diff --git a/packages/html/src/i18n/locales/ru.ts b/packages/html/src/i18n/locales/ru.ts
deleted file mode 100644
index 535f4973..00000000
--- a/packages/html/src/i18n/locales/ru.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ru';
diff --git a/packages/html/src/i18n/locales/sk.ts b/packages/html/src/i18n/locales/sk.ts
deleted file mode 100644
index 418d05bc..00000000
--- a/packages/html/src/i18n/locales/sk.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/sk';
diff --git a/packages/html/src/i18n/locales/sl.ts b/packages/html/src/i18n/locales/sl.ts
deleted file mode 100644
index e8f07397..00000000
--- a/packages/html/src/i18n/locales/sl.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/sl';
diff --git a/packages/html/src/i18n/locales/sr.ts b/packages/html/src/i18n/locales/sr.ts
deleted file mode 100644
index da2c2d73..00000000
--- a/packages/html/src/i18n/locales/sr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/sr';
diff --git a/packages/html/src/i18n/locales/sv.ts b/packages/html/src/i18n/locales/sv.ts
deleted file mode 100644
index ed0d042f..00000000
--- a/packages/html/src/i18n/locales/sv.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/sv';
diff --git a/packages/html/src/i18n/locales/te.ts b/packages/html/src/i18n/locales/te.ts
deleted file mode 100644
index 4bcc9314..00000000
--- a/packages/html/src/i18n/locales/te.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/te';
diff --git a/packages/html/src/i18n/locales/th.ts b/packages/html/src/i18n/locales/th.ts
deleted file mode 100644
index 5e24e370..00000000
--- a/packages/html/src/i18n/locales/th.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/th';
diff --git a/packages/html/src/i18n/locales/tr.ts b/packages/html/src/i18n/locales/tr.ts
deleted file mode 100644
index 135a7f68..00000000
--- a/packages/html/src/i18n/locales/tr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/tr';
diff --git a/packages/html/src/i18n/locales/uk.ts b/packages/html/src/i18n/locales/uk.ts
deleted file mode 100644
index f191b229..00000000
--- a/packages/html/src/i18n/locales/uk.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/uk';
diff --git a/packages/html/src/i18n/locales/vi.ts b/packages/html/src/i18n/locales/vi.ts
deleted file mode 100644
index e34b456a..00000000
--- a/packages/html/src/i18n/locales/vi.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/vi';
diff --git a/packages/html/src/i18n/locales/zh-CN.ts b/packages/html/src/i18n/locales/zh-CN.ts
deleted file mode 100644
index 712195e9..00000000
--- a/packages/html/src/i18n/locales/zh-CN.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/zh-CN';
diff --git a/packages/html/src/i18n/locales/zh-TW.ts b/packages/html/src/i18n/locales/zh-TW.ts
deleted file mode 100644
index c61e3979..00000000
--- a/packages/html/src/i18n/locales/zh-TW.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/zh-TW';
diff --git a/packages/html/src/i18n/locales/zh.ts b/packages/html/src/i18n/locales/zh.ts
deleted file mode 100644
index b561ec25..00000000
--- a/packages/html/src/i18n/locales/zh.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/zh';
diff --git a/packages/html/src/ui/playback-rate-radio-group/playback-rate-radio-group-element.ts b/packages/html/src/ui/playback-rate-radio-group/playback-rate-radio-group-element.ts
index 4d46f671..dfd6e810 100644
--- a/packages/html/src/ui/playback-rate-radio-group/playback-rate-radio-group-element.ts
+++ b/packages/html/src/ui/playback-rate-radio-group/playback-rate-radio-group-element.ts
@@ -1,5 +1,5 @@
import { PlaybackRateRadioGroupCore, PlaybackRateRadioGroupDataAttrs } from '@videojs/core';
-import { applyElementProps, applyStateDataAttrs, logMissingFeature, selectPlaybackRate } from '@videojs/core/dom';
+import { applyStateDataAttrs, logMissingFeature, selectPlaybackRate } from '@videojs/core/dom';
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
import { playerContext } from '../../player/context';
@@ -54,11 +54,8 @@ export class PlaybackRateRadioGroupElement extends MenuRadioGroupElement {
this.value = this.#core.getRateValue(state.rate);
if (!this.hasAttribute('aria-label') && !this.hasAttribute('aria-labelledby')) {
- this.setAttribute('aria-label', this.#core.getLabel(state));
+ this.setAttribute('aria-label', 'Playback rate');
}
- applyElementProps(this, {
- 'aria-disabled': state.disabled ? 'true' : undefined,
- });
this.#syncContent(state);
}
diff --git a/packages/html/src/ui/playback-rate-radio-group/tests/playback-rate-radio-group-element.test.ts b/packages/html/src/ui/playback-rate-radio-group/tests/playback-rate-radio-group-element.test.ts
index 5cdbe94a..6d268de2 100644
--- a/packages/html/src/ui/playback-rate-radio-group/tests/playback-rate-radio-group-element.test.ts
+++ b/packages/html/src/ui/playback-rate-radio-group/tests/playback-rate-radio-group-element.test.ts
@@ -155,7 +155,7 @@ afterEach(() => {
describe('PlaybackRateRadioGroupElement', () => {
it('renders radio items from the available playback rates', async () => {
- const { menu, options, trigger } = setup({ playbackRates: [1, 1.25, 1.5], playbackRate: 1.25 });
+ const { menu, trigger } = setup({ playbackRates: [1, 1.25, 1.5], playbackRate: 1.25 });
await waitForMenu(menu, trigger);
@@ -165,8 +165,6 @@ describe('PlaybackRateRadioGroupElement', () => {
await waitForAssertion(() => {
expect(items.map((item) => item.getAttribute('aria-checked'))).toEqual(['false', 'true', 'false']);
});
- expect(options.getAttribute('aria-label')).toBe('playbackRateAria');
- expect(options.getAttribute('data-rate')).toBe('1.25');
});
it('renders radio items from a template', async () => {
@@ -208,7 +206,7 @@ describe('PlaybackRateButtonElement', () => {
await trigger.updateComplete;
expect(trigger.getAttribute('role')).toBe('button');
- expect(trigger.getAttribute('aria-label')).toBe('playbackRateAria');
+ expect(trigger.getAttribute('aria-label')).toBe('Playback rate 2');
expect(trigger.getAttribute('data-rate')).toBe('2');
});
diff --git a/packages/html/src/ui/time-slider/tests/time-slider-element.test.ts b/packages/html/src/ui/time-slider/tests/time-slider-element.test.ts
index c247362e..64aa8977 100644
--- a/packages/html/src/ui/time-slider/tests/time-slider-element.test.ts
+++ b/packages/html/src/ui/time-slider/tests/time-slider-element.test.ts
@@ -27,7 +27,7 @@ describe('TimeSliderElement', () => {
it('initializes with default property values', () => {
const slider = createElement(TimeSliderElement);
- expect(slider.label).toBe('');
+ expect(slider.label).toBe('Seek');
expect(slider.changeThrottle).toBe(100);
expect(slider.step).toBe(1);
expect(slider.largeStep).toBe(10);
diff --git a/packages/html/src/ui/time-slider/time-slider-element.ts b/packages/html/src/ui/time-slider/time-slider-element.ts
index 87de0ae9..069a64e3 100644
--- a/packages/html/src/ui/time-slider/time-slider-element.ts
+++ b/packages/html/src/ui/time-slider/time-slider-element.ts
@@ -30,7 +30,7 @@ export class TimeSliderElement extends MediaElement {
orientation: { type: String },
disabled: { type: Boolean },
thumbAlignment: { type: String, attribute: 'thumb-alignment' },
- } satisfies PropertyDeclarationMap>;
+ } satisfies PropertyDeclarationMap>;
label = TimeSliderCore.defaultProps.label;
changeThrottle = TimeSliderCore.defaultProps.changeThrottle;
diff --git a/packages/html/src/ui/time/time-element.ts b/packages/html/src/ui/time/time-element.ts
index fcfe2a1a..43b7babe 100644
--- a/packages/html/src/ui/time/time-element.ts
+++ b/packages/html/src/ui/time/time-element.ts
@@ -13,7 +13,7 @@ export class TimeElement extends MediaElement {
type: { type: String },
negativeSign: { type: String, attribute: 'negative-sign' },
label: { type: String },
- } satisfies PropertyDeclarationMap>;
+ } satisfies PropertyDeclarationMap;
type: TimeType = TimeCore.defaultProps.type;
negativeSign = TimeCore.defaultProps.negativeSign;
diff --git a/packages/html/src/ui/volume-slider/tests/volume-slider-element.test.ts b/packages/html/src/ui/volume-slider/tests/volume-slider-element.test.ts
index 421f673e..c1eb1a3a 100644
--- a/packages/html/src/ui/volume-slider/tests/volume-slider-element.test.ts
+++ b/packages/html/src/ui/volume-slider/tests/volume-slider-element.test.ts
@@ -26,7 +26,7 @@ describe('VolumeSliderElement', () => {
it('initializes with default property values', () => {
const slider = createElement(VolumeSliderElement);
- expect(slider.label).toBe('');
+ expect(slider.label).toBe('Volume');
expect(slider.step).toBe(1);
expect(slider.largeStep).toBe(10);
expect(slider.orientation).toBe('horizontal');
diff --git a/packages/react/src/i18n/locales/all.ts b/packages/react/src/i18n/locales/all.ts
deleted file mode 100644
index e1016b19..00000000
--- a/packages/react/src/i18n/locales/all.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { all, type LocaleTag, localeTags } from '@videojs/core/i18n/locales/all';
diff --git a/packages/react/src/i18n/locales/ar.ts b/packages/react/src/i18n/locales/ar.ts
deleted file mode 100644
index 0e389db2..00000000
--- a/packages/react/src/i18n/locales/ar.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ar';
diff --git a/packages/react/src/i18n/locales/az.ts b/packages/react/src/i18n/locales/az.ts
deleted file mode 100644
index 6d46a490..00000000
--- a/packages/react/src/i18n/locales/az.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/az';
diff --git a/packages/react/src/i18n/locales/bg.ts b/packages/react/src/i18n/locales/bg.ts
deleted file mode 100644
index 118cef65..00000000
--- a/packages/react/src/i18n/locales/bg.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/bg';
diff --git a/packages/react/src/i18n/locales/bn.ts b/packages/react/src/i18n/locales/bn.ts
deleted file mode 100644
index 76d645b5..00000000
--- a/packages/react/src/i18n/locales/bn.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/bn';
diff --git a/packages/react/src/i18n/locales/bs.ts b/packages/react/src/i18n/locales/bs.ts
deleted file mode 100644
index ef50bd94..00000000
--- a/packages/react/src/i18n/locales/bs.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/bs';
diff --git a/packages/react/src/i18n/locales/ca.ts b/packages/react/src/i18n/locales/ca.ts
deleted file mode 100644
index 050a7265..00000000
--- a/packages/react/src/i18n/locales/ca.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ca';
diff --git a/packages/react/src/i18n/locales/cs.ts b/packages/react/src/i18n/locales/cs.ts
deleted file mode 100644
index 924e7107..00000000
--- a/packages/react/src/i18n/locales/cs.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/cs';
diff --git a/packages/react/src/i18n/locales/cy.ts b/packages/react/src/i18n/locales/cy.ts
deleted file mode 100644
index 9c0bbd4a..00000000
--- a/packages/react/src/i18n/locales/cy.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/cy';
diff --git a/packages/react/src/i18n/locales/da.ts b/packages/react/src/i18n/locales/da.ts
deleted file mode 100644
index 9f3fd735..00000000
--- a/packages/react/src/i18n/locales/da.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/da';
diff --git a/packages/react/src/i18n/locales/de.ts b/packages/react/src/i18n/locales/de.ts
deleted file mode 100644
index 67a9d0c5..00000000
--- a/packages/react/src/i18n/locales/de.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/de';
diff --git a/packages/react/src/i18n/locales/el.ts b/packages/react/src/i18n/locales/el.ts
deleted file mode 100644
index 0e812607..00000000
--- a/packages/react/src/i18n/locales/el.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/el';
diff --git a/packages/react/src/i18n/locales/en.ts b/packages/react/src/i18n/locales/en.ts
deleted file mode 100644
index 17569535..00000000
--- a/packages/react/src/i18n/locales/en.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/en';
diff --git a/packages/react/src/i18n/locales/es.ts b/packages/react/src/i18n/locales/es.ts
deleted file mode 100644
index 592a8e3b..00000000
--- a/packages/react/src/i18n/locales/es.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/es';
diff --git a/packages/react/src/i18n/locales/et.ts b/packages/react/src/i18n/locales/et.ts
deleted file mode 100644
index 2cdaa205..00000000
--- a/packages/react/src/i18n/locales/et.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/et';
diff --git a/packages/react/src/i18n/locales/eu.ts b/packages/react/src/i18n/locales/eu.ts
deleted file mode 100644
index 3aedfc0b..00000000
--- a/packages/react/src/i18n/locales/eu.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/eu';
diff --git a/packages/react/src/i18n/locales/fa.ts b/packages/react/src/i18n/locales/fa.ts
deleted file mode 100644
index cad46b97..00000000
--- a/packages/react/src/i18n/locales/fa.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/fa';
diff --git a/packages/react/src/i18n/locales/fi.ts b/packages/react/src/i18n/locales/fi.ts
deleted file mode 100644
index c72f17ed..00000000
--- a/packages/react/src/i18n/locales/fi.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/fi';
diff --git a/packages/react/src/i18n/locales/fr.ts b/packages/react/src/i18n/locales/fr.ts
deleted file mode 100644
index c71d6575..00000000
--- a/packages/react/src/i18n/locales/fr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/fr';
diff --git a/packages/react/src/i18n/locales/gd.ts b/packages/react/src/i18n/locales/gd.ts
deleted file mode 100644
index 4a9cdef7..00000000
--- a/packages/react/src/i18n/locales/gd.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/gd';
diff --git a/packages/react/src/i18n/locales/gl.ts b/packages/react/src/i18n/locales/gl.ts
deleted file mode 100644
index 313cea85..00000000
--- a/packages/react/src/i18n/locales/gl.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/gl';
diff --git a/packages/react/src/i18n/locales/he.ts b/packages/react/src/i18n/locales/he.ts
deleted file mode 100644
index c108668f..00000000
--- a/packages/react/src/i18n/locales/he.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/he';
diff --git a/packages/react/src/i18n/locales/hi.ts b/packages/react/src/i18n/locales/hi.ts
deleted file mode 100644
index 53b36476..00000000
--- a/packages/react/src/i18n/locales/hi.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/hi';
diff --git a/packages/react/src/i18n/locales/hr.ts b/packages/react/src/i18n/locales/hr.ts
deleted file mode 100644
index bd7eab16..00000000
--- a/packages/react/src/i18n/locales/hr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/hr';
diff --git a/packages/react/src/i18n/locales/hu.ts b/packages/react/src/i18n/locales/hu.ts
deleted file mode 100644
index a163f809..00000000
--- a/packages/react/src/i18n/locales/hu.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/hu';
diff --git a/packages/react/src/i18n/locales/it.ts b/packages/react/src/i18n/locales/it.ts
deleted file mode 100644
index fd1233ed..00000000
--- a/packages/react/src/i18n/locales/it.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/it';
diff --git a/packages/react/src/i18n/locales/ja.ts b/packages/react/src/i18n/locales/ja.ts
deleted file mode 100644
index 4e36df11..00000000
--- a/packages/react/src/i18n/locales/ja.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ja';
diff --git a/packages/react/src/i18n/locales/ko.ts b/packages/react/src/i18n/locales/ko.ts
deleted file mode 100644
index 548d7bf6..00000000
--- a/packages/react/src/i18n/locales/ko.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ko';
diff --git a/packages/react/src/i18n/locales/lv.ts b/packages/react/src/i18n/locales/lv.ts
deleted file mode 100644
index c59135b4..00000000
--- a/packages/react/src/i18n/locales/lv.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/lv';
diff --git a/packages/react/src/i18n/locales/mr.ts b/packages/react/src/i18n/locales/mr.ts
deleted file mode 100644
index df74d3da..00000000
--- a/packages/react/src/i18n/locales/mr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/mr';
diff --git a/packages/react/src/i18n/locales/nb.ts b/packages/react/src/i18n/locales/nb.ts
deleted file mode 100644
index 87bc17cf..00000000
--- a/packages/react/src/i18n/locales/nb.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/nb';
diff --git a/packages/react/src/i18n/locales/ne.ts b/packages/react/src/i18n/locales/ne.ts
deleted file mode 100644
index 7910ce89..00000000
--- a/packages/react/src/i18n/locales/ne.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ne';
diff --git a/packages/react/src/i18n/locales/nl.ts b/packages/react/src/i18n/locales/nl.ts
deleted file mode 100644
index 9cc416cc..00000000
--- a/packages/react/src/i18n/locales/nl.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/nl';
diff --git a/packages/react/src/i18n/locales/nn.ts b/packages/react/src/i18n/locales/nn.ts
deleted file mode 100644
index 02a3df21..00000000
--- a/packages/react/src/i18n/locales/nn.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/nn';
diff --git a/packages/react/src/i18n/locales/oc.ts b/packages/react/src/i18n/locales/oc.ts
deleted file mode 100644
index c19d4111..00000000
--- a/packages/react/src/i18n/locales/oc.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/oc';
diff --git a/packages/react/src/i18n/locales/pl.ts b/packages/react/src/i18n/locales/pl.ts
deleted file mode 100644
index e4c4e758..00000000
--- a/packages/react/src/i18n/locales/pl.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/pl';
diff --git a/packages/react/src/i18n/locales/pt-BR.ts b/packages/react/src/i18n/locales/pt-BR.ts
deleted file mode 100644
index 1c82e0f0..00000000
--- a/packages/react/src/i18n/locales/pt-BR.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/pt-BR';
diff --git a/packages/react/src/i18n/locales/pt-PT.ts b/packages/react/src/i18n/locales/pt-PT.ts
deleted file mode 100644
index 248f58ee..00000000
--- a/packages/react/src/i18n/locales/pt-PT.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/pt-PT';
diff --git a/packages/react/src/i18n/locales/pt.ts b/packages/react/src/i18n/locales/pt.ts
deleted file mode 100644
index 99e488b4..00000000
--- a/packages/react/src/i18n/locales/pt.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/pt';
diff --git a/packages/react/src/i18n/locales/ro.ts b/packages/react/src/i18n/locales/ro.ts
deleted file mode 100644
index 55fd4cc0..00000000
--- a/packages/react/src/i18n/locales/ro.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ro';
diff --git a/packages/react/src/i18n/locales/ru.ts b/packages/react/src/i18n/locales/ru.ts
deleted file mode 100644
index 535f4973..00000000
--- a/packages/react/src/i18n/locales/ru.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/ru';
diff --git a/packages/react/src/i18n/locales/sk.ts b/packages/react/src/i18n/locales/sk.ts
deleted file mode 100644
index 418d05bc..00000000
--- a/packages/react/src/i18n/locales/sk.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/sk';
diff --git a/packages/react/src/i18n/locales/sl.ts b/packages/react/src/i18n/locales/sl.ts
deleted file mode 100644
index e8f07397..00000000
--- a/packages/react/src/i18n/locales/sl.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/sl';
diff --git a/packages/react/src/i18n/locales/sr.ts b/packages/react/src/i18n/locales/sr.ts
deleted file mode 100644
index da2c2d73..00000000
--- a/packages/react/src/i18n/locales/sr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/sr';
diff --git a/packages/react/src/i18n/locales/sv.ts b/packages/react/src/i18n/locales/sv.ts
deleted file mode 100644
index ed0d042f..00000000
--- a/packages/react/src/i18n/locales/sv.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/sv';
diff --git a/packages/react/src/i18n/locales/te.ts b/packages/react/src/i18n/locales/te.ts
deleted file mode 100644
index 4bcc9314..00000000
--- a/packages/react/src/i18n/locales/te.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/te';
diff --git a/packages/react/src/i18n/locales/th.ts b/packages/react/src/i18n/locales/th.ts
deleted file mode 100644
index 5e24e370..00000000
--- a/packages/react/src/i18n/locales/th.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/th';
diff --git a/packages/react/src/i18n/locales/tr.ts b/packages/react/src/i18n/locales/tr.ts
deleted file mode 100644
index 135a7f68..00000000
--- a/packages/react/src/i18n/locales/tr.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/tr';
diff --git a/packages/react/src/i18n/locales/uk.ts b/packages/react/src/i18n/locales/uk.ts
deleted file mode 100644
index f191b229..00000000
--- a/packages/react/src/i18n/locales/uk.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/uk';
diff --git a/packages/react/src/i18n/locales/vi.ts b/packages/react/src/i18n/locales/vi.ts
deleted file mode 100644
index e34b456a..00000000
--- a/packages/react/src/i18n/locales/vi.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/vi';
diff --git a/packages/react/src/i18n/locales/zh-CN.ts b/packages/react/src/i18n/locales/zh-CN.ts
deleted file mode 100644
index 712195e9..00000000
--- a/packages/react/src/i18n/locales/zh-CN.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/zh-CN';
diff --git a/packages/react/src/i18n/locales/zh-TW.ts b/packages/react/src/i18n/locales/zh-TW.ts
deleted file mode 100644
index c61e3979..00000000
--- a/packages/react/src/i18n/locales/zh-TW.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/zh-TW';
diff --git a/packages/react/src/i18n/locales/zh.ts b/packages/react/src/i18n/locales/zh.ts
deleted file mode 100644
index b561ec25..00000000
--- a/packages/react/src/i18n/locales/zh.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Generated by packages/core/scripts/generate-i18n-locales.ts — do not edit. */
-export { default } from '@videojs/core/i18n/locales/zh';
diff --git a/packages/react/src/ui/playback-rate-button/tests/playback-rate-button.test.tsx b/packages/react/src/ui/playback-rate-button/tests/playback-rate-button.test.tsx
index 581d0610..da0a67ff 100644
--- a/packages/react/src/ui/playback-rate-button/tests/playback-rate-button.test.tsx
+++ b/packages/react/src/ui/playback-rate-button/tests/playback-rate-button.test.tsx
@@ -49,10 +49,10 @@ describe('PlaybackRateButton', () => {
const button = document.querySelector('[data-testid="button"]');
await waitFor(() => {
- expect(document.querySelector('[data-testid="popup"] span')?.textContent).toBe('playbackRateAria');
+ expect(document.querySelector('[data-testid="popup"] span')?.textContent).toBe('Playback rate 1');
expect(document.querySelector('[data-testid="popup"] kbd')?.textContent).toBe('>');
});
- expect(button?.getAttribute('aria-label')).toBe('playbackRateAria');
+ expect(button?.getAttribute('aria-label')).toBe('Playback rate 1');
expect(button?.getAttribute('aria-keyshortcuts')).toBe('>');
});
});
diff --git a/packages/react/src/ui/playback-rate/tests/use-playback-rate-options.test.tsx b/packages/react/src/ui/playback-rate/tests/use-playback-rate-options.test.tsx
index 71c74b0a..01c02e00 100644
--- a/packages/react/src/ui/playback-rate/tests/use-playback-rate-options.test.tsx
+++ b/packages/react/src/ui/playback-rate/tests/use-playback-rate-options.test.tsx
@@ -72,7 +72,7 @@ describe('usePlaybackRateOptions', () => {
const trigger = screen.getByTestId('trigger');
- expect(trigger.getAttribute('aria-label')).toBe('playbackRateAria');
+ expect(trigger.getAttribute('aria-label')).toBe('Playback rate 1.5');
expect(trigger.getAttribute('data-rate')).toBe('1.5');
});
diff --git a/packages/react/src/ui/time-slider/tests/time-slider.test.tsx b/packages/react/src/ui/time-slider/tests/time-slider.test.tsx
index 0dcd365c..d16cdfba 100644
--- a/packages/react/src/ui/time-slider/tests/time-slider.test.tsx
+++ b/packages/react/src/ui/time-slider/tests/time-slider.test.tsx
@@ -178,7 +178,7 @@ describe('TimeSlider compound', () => {
const thumb = container.querySelector('[data-testid="thumb"]');
expect(thumb?.getAttribute('role')).toBe('slider');
- expect(thumb?.getAttribute('aria-label')).toBe('seek');
+ expect(thumb?.getAttribute('aria-label')).toBe('Seek');
});
it('SliderValue displays formatted time', () => {
diff --git a/packages/react/src/ui/volume-slider/tests/volume-slider.test.tsx b/packages/react/src/ui/volume-slider/tests/volume-slider.test.tsx
index 2cfec2e3..9972029e 100644
--- a/packages/react/src/ui/volume-slider/tests/volume-slider.test.tsx
+++ b/packages/react/src/ui/volume-slider/tests/volume-slider.test.tsx
@@ -184,7 +184,7 @@ describe('VolumeSlider compound', () => {
const thumb = container.querySelector('[data-testid="thumb"]');
expect(thumb?.getAttribute('role')).toBe('slider');
- expect(thumb?.getAttribute('aria-label')).toBe('volume');
+ expect(thumb?.getAttribute('aria-label')).toBe('Volume');
});
it('SliderValue formats as percentage', () => {
diff --git a/packages/utils/src/dom/effective-locale.ts b/packages/utils/src/dom/effective-locale.ts
deleted file mode 100644
index 05ff0f6d..00000000
--- a/packages/utils/src/dom/effective-locale.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { isUndefined } from '../predicate';
-
-/** Resolves locale: explicit non-empty value → ambient `lang` → {@link fallback}. */
-export function effectiveLocale(
- explicitLocale: string | undefined,
- ambientLang: string | undefined,
- fallback = 'en'
-): string {
- if (!isUndefined(explicitLocale) && String(explicitLocale).trim() !== '') {
- return explicitLocale;
- }
- if (!isUndefined(ambientLang) && ambientLang.trim() !== '') {
- return ambientLang;
- }
- return fallback;
-}
diff --git a/packages/utils/src/dom/index.ts b/packages/utils/src/dom/index.ts
index 6fcecdb0..0f4289b4 100644
--- a/packages/utils/src/dom/index.ts
+++ b/packages/utils/src/dom/index.ts
@@ -1,7 +1,6 @@
export { animationFrame } from './animation-frame';
export { namedNodeMapToObject, serializeAttributes } from './attributes';
export { isRTL } from './direction';
-export { effectiveLocale } from './effective-locale';
export { type OnEventOptions, onEvent, resolveEventTarget } from './event';
export { idleCallback } from './idle-callback';
export {
@@ -13,9 +12,6 @@ export {
isInteractiveTarget,
} from './interactive';
export { listen } from './listen';
-export { localeFromDomLang } from './locale-from-dom-lang';
-export { mergeLocaleOverlays } from './merge-locale-overlays';
-export { nearestLang } from './nearest-lang';
export { isMacOS } from './platform';
export { tryHidePopover, tryShowPopover } from './popover';
export { isHTMLAudioElement, isHTMLMediaElement, isHTMLVideoElement } from './predicates';
@@ -29,7 +25,6 @@ export {
} from './shadow-styles';
export { getSlottedElement, querySlot } from './slotted';
export { applyStyles, resolveCSSLength } from './style';
-export { subscribeAmbientLang } from './subscribe-ambient-lang';
export { supportsAnchorPositioning, supportsAnimationFrame, supportsIdleCallback } from './supports';
export { createTemplate, renderTemplate } from './template';
export {
diff --git a/packages/utils/src/dom/locale-from-dom-lang.ts b/packages/utils/src/dom/locale-from-dom-lang.ts
deleted file mode 100644
index d5d3c597..00000000
--- a/packages/utils/src/dom/locale-from-dom-lang.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { isUndefined } from '../predicate';
-
-/**
- * Normalizes a raw `lang` string (e.g. from {@link nearestLang}): empty or whitespace-only →
- * `undefined`, otherwise the trimmed value.
- */
-export function localeFromDomLang(raw: string | undefined): string | undefined {
- if (isUndefined(raw) || raw.trim() === '') {
- return undefined;
- }
- return raw.trim();
-}
diff --git a/packages/utils/src/dom/merge-locale-overlays.ts b/packages/utils/src/dom/merge-locale-overlays.ts
deleted file mode 100644
index 9c0bd116..00000000
--- a/packages/utils/src/dom/merge-locale-overlays.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Loads overlay layers for each tag in {@link localeLookupChain}, least-specific first, then merges
- * most-specific-last (same semantics as the core i18n registry).
- */
-export async function mergeLocaleOverlays(
- locale: string,
- load: (tag: string) => Promise | undefined>,
- localeLookupChain: (locale: string) => string[]
-): Promise<{ merged: Partial; loadedTags: string[] }> {
- const chain = localeLookupChain(locale);
- const layers = await Promise.all(chain.map((tag) => load(tag)));
- const loadedTags: string[] = [];
- const merged: Partial = {};
- for (let i = 0; i < chain.length; i++) {
- const layer = layers[i];
- if (layer && Object.keys(layer).length > 0) {
- loadedTags.push(chain[i]!);
- }
- }
- for (let i = chain.length - 1; i >= 0; i--) {
- const layer = layers[i];
- if (layer) {
- Object.assign(merged, layer);
- }
- }
- return { merged, loadedTags };
-}
diff --git a/packages/utils/src/dom/nearest-lang.ts b/packages/utils/src/dom/nearest-lang.ts
deleted file mode 100644
index 10ee34ad..00000000
--- a/packages/utils/src/dom/nearest-lang.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-function readLang(node: Element): string | undefined {
- const fromAttribute = node.getAttribute('lang')?.trim();
- if (fromAttribute) {
- return fromAttribute;
- }
- if ('lang' in node && typeof node.lang === 'string') {
- const fromProperty = node.lang.trim();
- if (fromProperty) {
- return fromProperty;
- }
- }
- return undefined;
-}
-
-/** First non-empty `lang` on `start` or an ancestor (HTML language inheritance). */
-export function nearestLang(start: Element | null): string | undefined {
- if (!start || typeof document === 'undefined') {
- return undefined;
- }
- let node: Element | null = start;
- while (node) {
- const lang = readLang(node);
- if (lang) {
- return lang;
- }
- node = node.parentElement;
- }
- return undefined;
-}
diff --git a/packages/utils/src/dom/subscribe-ambient-lang.ts b/packages/utils/src/dom/subscribe-ambient-lang.ts
deleted file mode 100644
index aadbf31d..00000000
--- a/packages/utils/src/dom/subscribe-ambient-lang.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * Subscribes to DOM updates that can change inherited `lang`: any `lang` attribute edit,
- * or subtree structural changes under `` (which can move nodes between labeled ancestors).
- */
-export function subscribeAmbientLang(onStoreChange: () => void): () => void {
- if (typeof document === 'undefined') {
- return () => {};
- }
- let disconnected = false;
- let queued = false;
- const flush = (): void => {
- queued = false;
- if (disconnected) {
- return;
- }
- onStoreChange();
- };
- const schedule = (): void => {
- if (!queued) {
- queued = true;
- queueMicrotask(flush);
- }
- };
- const root = document.documentElement;
- const observer = new MutationObserver(schedule);
- observer.observe(root, {
- subtree: true,
- attributes: true,
- attributeFilter: ['lang'],
- childList: true,
- });
- return () => {
- disconnected = true;
- observer.disconnect();
- queued = false;
- };
-}
diff --git a/packages/utils/src/dom/tests/effective-locale.test.ts b/packages/utils/src/dom/tests/effective-locale.test.ts
deleted file mode 100644
index 77e24417..00000000
--- a/packages/utils/src/dom/tests/effective-locale.test.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import { effectiveLocale } from '../effective-locale';
-
-describe('effectiveLocale', () => {
- it('prefers explicit locale over ambient', () => {
- expect(effectiveLocale('fr', 'de')).toBe('fr');
- });
-
- it('uses ambient when explicit is undefined', () => {
- expect(effectiveLocale(undefined, 'es-MX')).toBe('es-MX');
- });
-
- it('falls back to en when both missing or blank', () => {
- expect(effectiveLocale(undefined, undefined)).toBe('en');
- expect(effectiveLocale(' ', undefined)).toBe('en');
- expect(effectiveLocale(undefined, ' ')).toBe('en');
- });
-
- it('respects custom fallback', () => {
- expect(effectiveLocale(undefined, undefined, 'xx')).toBe('xx');
- });
-});
diff --git a/packages/utils/src/dom/tests/locale-from-dom-lang.test.ts b/packages/utils/src/dom/tests/locale-from-dom-lang.test.ts
deleted file mode 100644
index 00a56d2c..00000000
--- a/packages/utils/src/dom/tests/locale-from-dom-lang.test.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import { localeFromDomLang } from '../locale-from-dom-lang';
-
-describe('localeFromDomLang', () => {
- it('returns undefined for missing or blank', () => {
- expect(localeFromDomLang(undefined)).toBeUndefined();
- expect(localeFromDomLang('')).toBeUndefined();
- expect(localeFromDomLang(' ')).toBeUndefined();
- });
-
- it('returns trimmed language tag', () => {
- expect(localeFromDomLang(' fr ')).toBe('fr');
- expect(localeFromDomLang('de-DE')).toBe('de-DE');
- });
-});
diff --git a/packages/utils/src/dom/tests/merge-locale-overlays.test.ts b/packages/utils/src/dom/tests/merge-locale-overlays.test.ts
deleted file mode 100644
index 4f1ca367..00000000
--- a/packages/utils/src/dom/tests/merge-locale-overlays.test.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { describe, expect, it } from 'vitest';
-
-import { mergeLocaleOverlays } from '../merge-locale-overlays';
-
-describe('mergeLocaleOverlays', () => {
- it('merges layers least-specific to most-specific', async () => {
- const chain = (_locale: string) => ['es', 'en'];
- const load = async (tag: string): Promise> | undefined> =>
- tag === 'en' ? { a: 'en-a', b: 'en-b' } : tag === 'es' ? { b: 'es-b', c: 'es-c' } : undefined;
-
- const { merged, loadedTags } = await mergeLocaleOverlays('es', load, chain);
- expect(merged).toEqual({ a: 'en-a', b: 'es-b', c: 'es-c' });
- expect(loadedTags).toEqual(['es', 'en']);
- });
-
- it('skips undefined layers', async () => {
- const chain = () => ['xx', 'en'];
- const load = async (tag: string) => (tag === 'en' ? { k: 'v' } : undefined);
- const { merged, loadedTags } = await mergeLocaleOverlays('xx', load, chain);
- expect(merged).toEqual({ k: 'v' });
- expect(loadedTags).toEqual(['en']);
- });
-});
diff --git a/packages/utils/src/dom/tests/nearest-lang.test.ts b/packages/utils/src/dom/tests/nearest-lang.test.ts
deleted file mode 100644
index d4724ea4..00000000
--- a/packages/utils/src/dom/tests/nearest-lang.test.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { afterEach, describe, expect, it } from 'vitest';
-
-import { nearestLang } from '../nearest-lang';
-
-describe('nearestLang', () => {
- afterEach(() => {
- document.body.innerHTML = '';
- document.documentElement.removeAttribute('lang');
- });
-
- it('returns undefined for null start', () => {
- expect(nearestLang(null)).toBeUndefined();
- });
-
- it('reads lang on the start element', () => {
- const el = document.createElement('div');
- el.setAttribute('lang', 'fr');
- document.body.appendChild(el);
- expect(nearestLang(el)).toBe('fr');
- });
-
- it('walks ancestors and prefers closest lang', () => {
- const outer = document.createElement('section');
- outer.setAttribute('lang', 'de');
- const inner = document.createElement('div');
- inner.setAttribute('lang', 'fr');
- outer.appendChild(inner);
- document.body.appendChild(outer);
- expect(nearestLang(inner)).toBe('fr');
- });
-
- it('inherits from an ancestor when start has no lang', () => {
- const outer = document.createElement('section');
- outer.setAttribute('lang', 'de');
- const inner = document.createElement('div');
- outer.appendChild(inner);
- document.body.appendChild(outer);
- expect(nearestLang(inner)).toBe('de');
- });
-
- it('ignores empty lang and continues walking', () => {
- const outer = document.createElement('section');
- outer.setAttribute('lang', 'de');
- const inner = document.createElement('div');
- inner.setAttribute('lang', ' ');
- outer.appendChild(inner);
- document.body.appendChild(outer);
- expect(nearestLang(inner)).toBe('de');
- });
-
- it('reads lang IDL property on html when set via documentElement.lang', () => {
- document.documentElement.lang = 'fr';
- const inner = document.createElement('div');
- document.body.appendChild(inner);
- expect(nearestLang(inner)).toBe('fr');
- });
-});
diff --git a/packages/utils/src/dom/tests/subscribe-ambient-lang.test.ts b/packages/utils/src/dom/tests/subscribe-ambient-lang.test.ts
deleted file mode 100644
index 0aae1f68..00000000
--- a/packages/utils/src/dom/tests/subscribe-ambient-lang.test.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { afterEach, describe, expect, it, vi } from 'vitest';
-
-import { subscribeAmbientLang } from '../subscribe-ambient-lang';
-
-describe('subscribeAmbientLang', () => {
- afterEach(() => {
- document.documentElement.removeAttribute('lang');
- vi.restoreAllMocks();
- });
-
- it('invokes callback when html lang changes', async () => {
- const spy = vi.fn();
- const off = subscribeAmbientLang(spy);
- document.documentElement.setAttribute('lang', 'de');
- await Promise.resolve();
- await Promise.resolve();
- expect(spy).toHaveBeenCalled();
- off();
- });
-
- it('invokes callback when html lang property changes', async () => {
- const spy = vi.fn();
- const off = subscribeAmbientLang(spy);
- document.documentElement.lang = 'fr';
- await Promise.resolve();
- await Promise.resolve();
- expect(spy).toHaveBeenCalled();
- off();
- });
-
- it('unsubscribe stops notifications', async () => {
- const spy = vi.fn();
- const off = subscribeAmbientLang(spy);
- off();
- spy.mockClear();
- document.documentElement.setAttribute('lang', 'fr');
- await Promise.resolve();
- await Promise.resolve();
- expect(spy).not.toHaveBeenCalled();
- });
-});
diff --git a/packages/utils/src/time/format.ts b/packages/utils/src/time/format.ts
index 5cf829f5..849abc7d 100644
--- a/packages/utils/src/time/format.ts
+++ b/packages/utils/src/time/format.ts
@@ -1,79 +1,11 @@
import { isNumber } from '../predicate/predicate';
-export type TimeFormatOptions = {
- /** BCP 47 tag(s) for {@link Intl.DurationFormat} (and percent formatting where applicable). */
- locale?: string | string[];
- /** Called only when `seconds` is negative; formats the localized remaining-time phrase for the duration body. */
- formatRemaining?: (duration: string) => string;
- /** Passed to `Intl.DurationFormat`; defaults to `"long"`. */
- style?: 'long' | 'short' | 'narrow' | 'digital';
-};
-
const UNIT_LABELS = [
{ singular: 'hour', plural: 'hours' },
{ singular: 'minute', plural: 'minutes' },
{ singular: 'second', plural: 'seconds' },
] as const;
-type DurationFormatConstructor = new (
- locales?: string | string[],
- options?: { style?: TimeFormatOptions['style'] }
-) => { format: (duration: object) => string };
-
-const DurationFormat = (Intl as typeof Intl & { DurationFormat?: DurationFormatConstructor }).DurationFormat;
-
-const percentFormatters = new Map();
-const durationFormatters = new Map>>();
-
-function localeCacheKey(locale?: string | string[]): string {
- if (locale === undefined) return '';
- return Array.isArray(locale) ? locale.join('\0') : locale;
-}
-
-function isEnglishLocale(locale?: string | string[]): boolean {
- const tag = Array.isArray(locale) ? locale[0] : locale;
- if (!tag) return true;
- return tag === 'en' || tag.startsWith('en-');
-}
-
-function getPercentFormatter(locale?: string | string[]): Intl.NumberFormat | undefined {
- const key = localeCacheKey(locale);
- let formatter = percentFormatters.get(key);
- if (!formatter) {
- try {
- formatter = new Intl.NumberFormat(locale, { style: 'percent', maximumFractionDigits: 0 });
- percentFormatters.set(key, formatter);
- } catch {
- return undefined;
- }
- }
- return formatter;
-}
-
-function formatVolumePercentFallback(fraction: number): string {
- const percent = Math.round(Math.min(1, Math.max(0, fraction)) * 100);
- return `${percent}%`;
-}
-
-function getDurationFormatter(
- locale?: string | string[],
- style: NonNullable = 'long'
-): InstanceType> | undefined {
- if (!DurationFormat) return undefined;
-
- const key = `${localeCacheKey(locale)}\0${style}`;
- let formatter = durationFormatters.get(key);
- if (!formatter) {
- try {
- formatter = new DurationFormat(locale, { style });
- durationFormatters.set(key, formatter);
- } catch {
- return undefined;
- }
- }
- return formatter;
-}
-
function isValidTime(value: number): boolean {
return isNumber(value) && Number.isFinite(value);
}
@@ -186,69 +118,3 @@ export function secondsToIsoDuration(seconds: number): string {
return duration;
}
-
-/**
- * Human-readable duration using {@link Intl.DurationFormat} when available.
- *
- * Negative `seconds` denote remaining time: the absolute value is formatted, then wrapped in a
- * localized phrase via {@link TimeFormatOptions.formatRemaining}; otherwise `{duration} remaining`.
- */
-export function formatDuration(seconds: number, options?: TimeFormatOptions): string {
- if (!isValidTime(seconds)) {
- return '';
- }
-
- const negative = seconds < 0;
- const positiveSeconds = Math.abs(seconds);
- const totalSeconds = Math.floor(positiveSeconds);
- const hours = Math.floor(totalSeconds / 3600);
- const minutes = Math.floor((totalSeconds % 3600) / 60);
- const secondsPart = totalSeconds % 60;
-
- const record: Partial<{ hours: number; minutes: number; seconds: number }> = {};
- if (hours > 0) record.hours = hours;
- if (minutes > 0) record.minutes = minutes;
- if (secondsPart > 0 || (hours === 0 && minutes === 0)) record.seconds = secondsPart;
-
- let body: string;
- try {
- const durationFormatter = getDurationFormatter(options?.locale, options?.style ?? 'long');
- if (durationFormatter) {
- body = durationFormatter.format(record);
- } else {
- body = formatTimeAsPhrase(positiveSeconds);
- }
- } catch {
- body = formatTimeAsPhrase(positiveSeconds);
- }
-
- // Some ICU builds return an empty string for a zero-length duration; fall back to the phrase formatter.
- if (!body.trim()) {
- body = formatTimeAsPhrase(positiveSeconds);
- }
-
- if (negative) {
- const formatRemaining = options?.formatRemaining;
- if (formatRemaining) return formatRemaining(body);
- if (isEnglishLocale(options?.locale)) return `${body} remaining`;
- return body;
- }
-
- return body;
-}
-
-/** Format a volume fraction (0–1) with {@link Intl.NumberFormat} `style: "percent"`. */
-export function formatVolumePercent(fraction: number, locale?: string | string[]): string {
- const value = !isNumber(fraction) || !Number.isFinite(fraction) ? 0 : Math.min(1, Math.max(0, fraction));
-
- try {
- const formatter = getPercentFormatter(locale) ?? getPercentFormatter(undefined);
- if (formatter) {
- return formatter.format(value);
- }
- } catch {
- // fall through to simple percent string
- }
-
- return formatVolumePercentFallback(value);
-}
diff --git a/packages/utils/src/time/tests/format.test.ts b/packages/utils/src/time/tests/format.test.ts
index 040b1a54..a05077f9 100644
--- a/packages/utils/src/time/tests/format.test.ts
+++ b/packages/utils/src/time/tests/format.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
-import { formatDuration, formatTime, formatTimeAsPhrase, formatVolumePercent, secondsToIsoDuration } from '../format';
+import { formatTime, formatTimeAsPhrase, secondsToIsoDuration } from '../format';
describe('formatTime', () => {
it('formats seconds only', () => {
@@ -99,80 +99,6 @@ describe('formatTimeAsPhrase', () => {
});
});
-describe('formatDuration', () => {
- it('formats positive duration', () => {
- expect(formatDuration(90)).toContain('1');
- expect(formatDuration(90)).toMatch(/minute/i);
- expect(formatDuration(90)).toMatch(/30/);
- expect(formatDuration(300)).toMatch(/5/);
- expect(formatDuration(300)).toMatch(/minute/i);
- });
-
- it('adds remaining suffix for negative seconds', () => {
- expect(formatDuration(-30)).toMatch(/30/);
- expect(formatDuration(-30)).toMatch(/remaining$/i);
- });
-
- it('uses formatRemaining only for negative durations', () => {
- expect(formatDuration(-30, { formatRemaining: (duration) => `quedan ${duration}` })).toMatch(/^quedan /);
- expect(formatDuration(-30, { formatRemaining: (duration) => `quedan ${duration}` })).toMatch(/30/);
- expect(formatDuration(90, { formatRemaining: () => 'should-not-appear' })).toBe(formatDuration(90));
- });
-
- it('omits English remaining suffix for non-English locales without formatRemaining', () => {
- const formatted = formatDuration(-30, { locale: 'es' });
- expect(formatted).toMatch(/30/);
- expect(formatted).not.toMatch(/remaining$/i);
- });
-
- it('uses Intl.DurationFormat when supported; otherwise matches formatTimeAsPhrase', () => {
- const DurationFormatConstructor = (Intl as typeof Intl & { DurationFormat?: unknown }).DurationFormat;
- const hasDurationFormat = typeof DurationFormatConstructor === 'function';
- const phrase = formatTimeAsPhrase(125);
- if (hasDurationFormat) {
- const en = formatDuration(125, { locale: 'en' });
- const de = formatDuration(125, { locale: 'de' });
- expect(en.length).toBeGreaterThan(0);
- expect(de.length).toBeGreaterThan(0);
- expect(en).not.toBe(de);
- } else {
- expect(formatDuration(125, { locale: 'en' })).toBe(phrase);
- expect(formatDuration(125, { locale: 'ja' })).toBe(phrase);
- }
- });
-
- it('handles invalid values', () => {
- expect(formatDuration(NaN)).toBe('');
- expect(formatDuration(Infinity)).toBe('');
- });
-
- it('falls back to formatTimeAsPhrase when locale is invalid', () => {
- const phrase = formatTimeAsPhrase(90);
- expect(formatDuration(90, { locale: 'not-a-valid-bcp47-tag!!!' })).toBe(phrase);
- });
-});
-
-describe('formatVolumePercent', () => {
- it('uses Intl percent style', () => {
- expect(formatVolumePercent(0.75)).toMatch(/75/);
- expect(formatVolumePercent(0.75)).toMatch(/%/);
- });
-
- it('clamps to 0–100%', () => {
- expect(formatVolumePercent(-1)).toBe(formatVolumePercent(0));
- expect(formatVolumePercent(2)).toBe(formatVolumePercent(1));
- });
-
- it('handles invalid fraction', () => {
- expect(formatVolumePercent(Number.NaN)).toMatch(/0/);
- expect(formatVolumePercent(Number.NaN)).toMatch(/%/);
- });
-
- it('falls back when locale is invalid', () => {
- expect(formatVolumePercent(0.75, 'not-a-invalid-bcp47-tag!!!')).toBe('75%');
- });
-});
-
describe('secondsToIsoDuration', () => {
it('formats seconds only', () => {
expect(secondsToIsoDuration(0)).toBe('PT0S');
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 88b2b1ec..22f4c3b1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4951,6 +4951,9 @@ packages:
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
engines: {node: '>= 0.4'}
+ get-tsconfig@4.13.6:
+ resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
+
get-tsconfig@4.14.0:
resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
@@ -13110,6 +13113,10 @@ snapshots:
es-errors: 1.3.0
get-intrinsic: 1.3.0
+ get-tsconfig@4.13.6:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
get-tsconfig@4.14.0:
dependencies:
resolve-pkg-maps: 1.0.0
@@ -16082,7 +16089,7 @@ snapshots:
tsx@4.21.0:
dependencies:
esbuild: 0.27.3
- get-tsconfig: 4.14.0
+ get-tsconfig: 4.13.6
optionalDependencies:
fsevents: 2.3.3