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