Skip to content

feat(html): add i18n provider and wire controls#1591

Open
sampotts wants to merge 10 commits into
feat/i18nfrom
feat/i18n-html
Open

feat(html): add i18n provider and wire controls#1591
sampotts wants to merge 10 commits into
feat/i18nfrom
feat/i18n-html

Conversation

@sampotts

@sampotts sampotts commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Refs #222
Closes #1365
Closes #1367

Summary

Adds the HTML i18n layer with ambient <html lang> inheritance, browser translation fallback, default loadLocale, and translated labels across web component controls.

Changes

  • createI18n()I18nController, ProviderMixin, TextMixin, <media-i18n-provider>, <media-text>
  • Controls auto-forward translated aria-labels and tooltips via I18nController
  • Error dialog uses core i18n helpers; CDN i18n entry point and build plugin
  • Package exports for @videojs/html/i18n and @videojs/html/cdn/i18n
Stack info

PR 3 of 5 — stacks on locale packs PR.

Testing

pnpm -F @videojs/html build
pnpm -F @videojs/html test src/i18n

Made with Cursor


Note

Medium Risk
Touches many player UI and accessibility strings across skins and controls; async locale/browser loading adds edge cases, though covered by new tests.

Overview
Adds an HTML i18n layer on top of @videojs/core/i18n: createI18n() exposes Lit context, I18nController, provider/text mixins, and registers <media-i18n-provider> / <media-text>. Locale resolution follows explicit lang, ambient <html lang>, lazy shipped locale packs, registry overlays, and optional browser translation (with sequence guards on async loads).

Players and SkinElement are wrapped with I18nProviderMixin so skins and controls share a translator. UI elements consume I18nController and use resolveControlAttrs / resolveControlLabel for translated aria-labels, tooltips, error-dialog copy, time remaining phrases, and input-indicator labels. Skin templates drop hardcoded error-dialog strings in favor of runtime-filled elements.

Packaging & CDN: new @videojs/html/i18n and locale export paths, build:cdn runs locale generation plus cdnI18nExternalPlugin (shared i18n.js on jsDelivr in prod, relative in dev), and the video CDN entry pulls in define/i18n.

Reviewed by Cursor Bugbot for commit 85e56ff. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented May 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Jun 19, 2026 5:31am

Request Review

Comment thread packages/html/src/define/video/skin.ts
Comment thread packages/html/src/ui/playback-rate-menu/playback-rate-menu-trigger-element.ts Outdated
Comment thread build/plugins/cdn-i18n-external-plugin.ts
Comment thread packages/html/src/i18n/create-i18n.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2ecd3b9. Configure here.

Comment thread packages/html/src/ui/tooltip/tooltip-element.ts Outdated
@sampotts sampotts force-pushed the feat/i18n-locale-packs branch from fe7b46e to f579d71 Compare June 18, 2026 04:37
@luwes luwes force-pushed the feat/i18n-locale-packs branch from 472666f to f6ce653 Compare June 18, 2026 19:10
Base automatically changed from feat/i18n-locale-packs to main June 18, 2026 19:16
@sampotts sampotts changed the base branch from main to feat/i18n-locale-packs June 18, 2026 21:37
@sampotts sampotts changed the base branch from feat/i18n-locale-packs to main June 18, 2026 21:41
@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 99096db
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a346ac28214550008c27e36
😎 Deploy Preview https://deploy-preview-1591--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html

Path Base initial PR initial Diff % Lazy
/video/minimal-skin 44.62 kB 48.22 kB +3.60 kB +8.1% +44.53 kB 🔺
/video/minimal-skin.tailwind 45.16 kB 48.75 kB +3.60 kB +8.0% +44.53 kB 🔺
/video/player 8.06 kB 10.94 kB +2.87 kB +35.6% +44.53 kB 🔴
/video/skin 44.91 kB 48.55 kB +3.64 kB +8.1% +44.53 kB 🔺
/video/skin.tailwind 45.55 kB 49.20 kB +3.65 kB +8.0% +44.53 kB 🔺
/audio/minimal-skin 37.21 kB 40.70 kB +3.48 kB +9.4% +44.53 kB 🔺
/audio/minimal-skin.tailwind 37.59 kB 41.14 kB +3.55 kB +9.4% +44.53 kB 🔺
/audio/player 5.38 kB 8.31 kB +2.92 kB +54.3% +44.53 kB 🔴
/audio/skin 38.42 kB 41.86 kB +3.43 kB +8.9% +44.53 kB 🔺
/audio/skin.tailwind 38.75 kB 42.33 kB +3.58 kB +9.2% +44.53 kB 🔺
/background/player 3.93 kB 6.88 kB +2.94 kB +74.8% +44.53 kB 🔴
/live-video/minimal-skin 43.70 kB 47.37 kB +3.67 kB +8.4% +44.53 kB 🔺
/live-video/minimal-skin.tailwind 44.14 kB 47.78 kB +3.64 kB +8.2% +44.53 kB 🔺
/live-video/player 7.63 kB 10.52 kB +2.89 kB +37.8% +44.53 kB 🔴
/live-video/skin 43.67 kB 47.30 kB +3.64 kB +8.3% +44.53 kB 🔺
/live-video/skin.tailwind 44.23 kB 47.86 kB +3.63 kB +8.2% +44.53 kB 🔺
/live-audio/minimal-skin 30.15 kB 33.69 kB +3.55 kB +11.8% +44.53 kB 🔴
/live-audio/minimal-skin.tailwind 29.64 kB 33.19 kB +3.55 kB +12.0% +44.53 kB 🔴
/live-audio/player 5.40 kB 8.31 kB +2.91 kB +53.9% +44.53 kB 🔴
/live-audio/skin 31.50 kB 35.06 kB +3.56 kB +11.3% +44.53 kB 🔴
/live-audio/skin.tailwind 31.12 kB 34.66 kB +3.54 kB +11.4% +44.53 kB 🔴
/ui/airplay-button 9.36 kB 12.71 kB +3.35 kB +35.8% +44.53 kB 🔴
/ui/captions-button 9.67 kB 12.99 kB +3.32 kB +34.3% +44.53 kB 🔴
/ui/cast-button 9.38 kB 12.76 kB +3.39 kB +36.1% +44.53 kB 🔴
/ui/compounds 28.52 kB 32.02 kB +3.50 kB +12.3% +44.53 kB 🔴
/ui/controls 7.56 kB 7.56 kB +1 B +0.0% +44.53 kB 🔴
/ui/error-dialog 9.13 kB 12.44 kB +3.31 kB +36.3% +44.53 kB 🔴
/ui/fullscreen-button 9.36 kB 12.71 kB +3.35 kB +35.8% +44.53 kB 🔴
/ui/menu 17.35 kB 17.37 kB +14 B +0.1% +44.53 kB 🔴
/ui/mute-button 9.37 kB 12.75 kB +3.38 kB +36.1% +44.53 kB 🔴
/ui/pip-button 9.36 kB 12.74 kB +3.38 kB +36.1% +44.53 kB 🔴
/ui/play-button 9.38 kB 12.70 kB +3.32 kB +35.4% +44.53 kB 🔴
/ui/playback-rate-button 9.67 kB 13.04 kB +3.37 kB +34.9% +44.53 kB 🔴
/ui/poster 7.07 kB 10.51 kB +3.44 kB +48.7% +44.53 kB 🔴
/ui/seek-button 9.50 kB 12.85 kB +3.35 kB +35.3% +44.53 kB 🔴
/ui/slider 5.14 kB 5.13 kB -4 B -0.1% +44.53 kB 🔴
/ui/status-announcer 9.96 kB 13.25 kB +3.30 kB +33.1% +44.53 kB 🔴
/ui/status-indicator 10.87 kB 14.16 kB +3.30 kB +30.3% +44.53 kB 🔴
/ui/time 8.21 kB 11.56 kB +3.34 kB +40.7% +44.53 kB 🔴
/ui/time-slider 12.74 kB 15.70 kB +2.96 kB +23.2% +44.53 kB 🔴
/ui/tooltip 7.08 kB 10.43 kB +3.34 kB +47.2% +44.53 kB 🔴
/ui/volume-indicator 10.88 kB 14.17 kB +3.29 kB +30.2% +44.53 kB 🔴
/ui/volume-slider 10.85 kB 13.80 kB +2.95 kB +27.2% +44.53 kB 🔴
/skin-element 1.44 kB 5.22 kB +3.79 kB +263.7% +44.53 kB 🔴
/video (default) 44.92 kB 48.58 kB +3.67 kB +8.2% +44.53 kB 🔺
/video (default + hls) 184.43 kB 187.78 kB +3.36 kB +1.8% +44.53 kB 🔺
/video (minimal) 44.58 kB 48.23 kB +3.65 kB +8.2% +44.53 kB 🔺
/video (minimal + hls) 184.27 kB 187.39 kB +3.12 kB +1.7% +44.53 kB 🔺
/audio (default) 38.38 kB 41.95 kB +3.57 kB +9.3% +44.53 kB 🔺
/audio (minimal) 37.21 kB 40.70 kB +3.50 kB +9.4% +44.53 kB 🔺
/background 4.20 kB 7.14 kB +2.94 kB +70.0% +44.53 kB 🔴
Presets (7)
Entry Initial Lazy
/video (default) 48.58 kB 44.53 kB
/video (default + hls) 187.78 kB 44.53 kB
/video (minimal) 48.23 kB 44.53 kB
/video (minimal + hls) 187.39 kB 44.53 kB
/audio (default) 41.95 kB 44.53 kB
/audio (minimal) 40.70 kB 44.53 kB
/background 7.14 kB 44.53 kB
Media (10)
Entry Initial
/media/background-video 1.14 kB
/media/container 1.71 kB
/media/dash-video 242.62 kB
/media/hls-video 141.27 kB
/media/mux-audio 163.78 kB
/media/mux-video 163.64 kB
/media/native-hls-video 9.07 kB
/media/simple-hls-audio-only 16.99 kB
/media/simple-hls-video 18.78 kB
/media/vimeo-video 12.32 kB
Players (5)
Entry Initial Lazy
/video/player 10.94 kB 44.53 kB
/audio/player 8.31 kB 44.53 kB
/background/player 6.88 kB 44.53 kB
/live-video/player 10.52 kB 44.53 kB
/live-audio/player 8.31 kB 44.53 kB
Skins (30)
Entry Type Initial Lazy
/video/minimal-skin.css css 5.47 kB
/video/skin.css css 5.45 kB
/video/minimal-skin js 48.22 kB 44.53 kB
/video/minimal-skin.tailwind js 48.75 kB 44.53 kB
/video/skin js 48.55 kB 44.53 kB
/video/skin.tailwind js 49.20 kB 44.53 kB
/audio/minimal-skin.css css 3.61 kB
/audio/skin.css css 3.53 kB
/audio/minimal-skin js 40.70 kB 44.53 kB
/audio/minimal-skin.tailwind js 41.14 kB 44.53 kB
/audio/skin js 41.86 kB 44.53 kB
/audio/skin.tailwind js 42.33 kB 44.53 kB
/background/skin.css css 133 B
/background/skin js 1.14 kB
/live-video/minimal-skin.css css 5.47 kB
/live-video/skin.css css 5.45 kB
/live-video/minimal-skin js 47.37 kB 44.53 kB
/live-video/minimal-skin.tailwind js 47.78 kB 44.53 kB
/live-video/skin js 47.30 kB 44.53 kB
/live-video/skin.tailwind js 47.86 kB 44.53 kB
/live-audio/minimal-skin.css css 3.61 kB
/live-audio/skin.css css 3.53 kB
/live-audio/minimal-skin js 33.69 kB 44.53 kB
/live-audio/minimal-skin.tailwind js 33.19 kB 44.53 kB
/live-audio/skin js 35.06 kB 44.53 kB
/live-audio/skin.tailwind js 34.66 kB 44.53 kB
/global.css css 183 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 5.22 kB 44.53 kB
UI Components (38)
Entry Initial
/ui/airplay-button 2.41 kB
/ui/alert-dialog 2.79 kB
/ui/alert-dialog-close 2.28 kB
/ui/alert-dialog-description 2.25 kB
/ui/alert-dialog-title 2.25 kB
/ui/buffering-indicator 2.54 kB
/ui/captions-button 2.49 kB
/ui/captions-radio-group 3.04 kB
/ui/cast-button 2.44 kB
/ui/compounds 3.03 kB
/ui/controls 2.81 kB
/ui/error-dialog 2.83 kB
/ui/fullscreen-button 2.41 kB
/ui/hotkey 2.40 kB
/ui/menu 2.85 kB
/ui/mute-button 2.42 kB
/ui/pip-button 2.45 kB
/ui/play-button 2.44 kB
/ui/playback-rate-button 2.57 kB
/ui/playback-rate-radio-group 2.99 kB
/ui/popover 2.94 kB
/ui/poster 2.34 kB
/ui/quality-radio-group 3.00 kB
/ui/seek-button 2.45 kB
/ui/seek-indicator 2.62 kB
/ui/seek-indicator-value 386 B
/ui/slider 2.83 kB
/ui/status-announcer 2.35 kB
/ui/status-indicator 2.49 kB
/ui/status-indicator-value 410 B
/ui/thumbnail 2.42 kB
/ui/time 2.80 kB
/ui/time-slider 2.86 kB
/ui/tooltip 2.81 kB
/ui/volume-indicator 2.48 kB
/ui/volume-indicator-fill 460 B
/ui/volume-indicator-value 474 B
/ui/volume-slider 2.83 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react — no changes
Presets (7)
Entry Initial
/video (default) 37.38 kB
/video (default + hls) 175.59 kB
/video (minimal) 37.50 kB
/video (minimal + hls) 175.83 kB
/audio (default) 30.57 kB
/audio (minimal) 30.62 kB
/background 754 B
Media (9)
Entry Initial
/media/background-video 575 B
/media/dash-video 241.23 kB
/media/hls-video 139.89 kB
/media/mux-audio 162.20 kB
/media/mux-video 162.30 kB
/media/native-hls-video 7.41 kB
/media/simple-hls-audio-only 15.38 kB
/media/simple-hls-video 17.18 kB
/media/vimeo-video 10.58 kB
Skins (27)
Entry Type Initial
/tailwind.css css 228 B
/video/minimal-skin.css css 5.37 kB
/video/skin.css css 5.34 kB
/video/minimal-skin js 37.42 kB
/video/minimal-skin.tailwind js 43.10 kB
/video/skin js 37.39 kB
/video/skin.tailwind js 43.09 kB
/audio/minimal-skin.css css 3.47 kB
/audio/skin.css css 3.39 kB
/audio/minimal-skin js 30.52 kB
/audio/minimal-skin.tailwind js 32.32 kB
/audio/skin js 30.48 kB
/audio/skin.tailwind js 34.33 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 5.37 kB
/live-video/skin.css css 5.34 kB
/live-video/minimal-skin js 32.91 kB
/live-video/minimal-skin.tailwind js 38.53 kB
/live-video/skin js 32.94 kB
/live-video/skin.tailwind js 38.58 kB
/live-audio/minimal-skin.css css 3.47 kB
/live-audio/skin.css css 3.39 kB
/live-audio/minimal-skin js 21.98 kB
/live-audio/minimal-skin.tailwind js 24.84 kB
/live-audio/skin js 22.02 kB
/live-audio/skin.tailwind js 25.01 kB
UI Components (32)
Entry Initial
/ui/airplay-button 2.15 kB
/ui/alert-dialog 2.13 kB
/ui/buffering-indicator 1.99 kB
/ui/captions-button 2.12 kB
/ui/captions-radio-group 2.06 kB
/ui/cast-button 2.13 kB
/ui/controls 2.00 kB
/ui/error-dialog 2.22 kB
/ui/fullscreen-button 2.13 kB
/ui/gesture 2.21 kB
/ui/hotkey 2.17 kB
/ui/live-button 2.05 kB
/ui/menu 2.29 kB
/ui/mute-button 2.16 kB
/ui/pip-button 2.18 kB
/ui/play-button 2.27 kB
/ui/playback-rate 1.99 kB
/ui/playback-rate-button 2.15 kB
/ui/popover 2.67 kB
/ui/poster 1.99 kB
/ui/quality 2.05 kB
/ui/seek-button 2.13 kB
/ui/seek-indicator 2.12 kB
/ui/slider 2.23 kB
/ui/status-announcer 2.01 kB
/ui/status-indicator 2.10 kB
/ui/thumbnail 1.93 kB
/ui/time 1.93 kB
/ui/time-slider 2.28 kB
/ui/tooltip 2.60 kB
/ui/volume-indicator 2.08 kB
/ui/volume-slider 2.21 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core — no changes
Entries (68)
Entry Initial Lazy
. 10.50 kB
/dom 17.01 kB
/dom/media/custom-media-element 2.09 kB
/dom/media/dash 236.88 kB
/dom/media/google-cast 4.03 kB
/dom/media/hls 135.73 kB
/dom/media/media-host 1.25 kB
/dom/media/media-played-ranges 576 B
/dom/media/mux 151.26 kB
/dom/media/native-hls 3.07 kB
/dom/media/simple-hls 16.54 kB
/dom/media/simple-hls-audio-only 14.75 kB
/dom/media/vimeo 9.86 kB
/media/predicate 573 B
/i18n 2.55 kB 44.53 kB
/i18n/locales/all 26.94 kB
/i18n/locales/ar 1017 B
/i18n/locales/az 914 B
/i18n/locales/bg 1.02 kB
/i18n/locales/bn 1.04 kB
/i18n/locales/bs 837 B
/i18n/locales/ca 878 B
/i18n/locales/cs 865 B
/i18n/locales/cy 829 B
/i18n/locales/da 821 B
/i18n/locales/de 916 B
/i18n/locales/el 1.20 kB
/i18n/locales/en 645 B
/i18n/locales/es 829 B
/i18n/locales/et 882 B
/i18n/locales/eu 828 B
/i18n/locales/fa 1008 B
/i18n/locales/fi 856 B
/i18n/locales/fr 896 B
/i18n/locales/gd 917 B
/i18n/locales/gl 816 B
/i18n/locales/he 940 B
/i18n/locales/hi 1.06 kB
/i18n/locales/hr 846 B
/i18n/locales/hu 913 B
/i18n/locales/it 856 B
/i18n/locales/ja 997 B
/i18n/locales/ko 960 B
/i18n/locales/lv 886 B
/i18n/locales/mr 1.07 kB
/i18n/locales/nb 814 B
/i18n/locales/ne 1.06 kB
/i18n/locales/nl 834 B
/i18n/locales/nn 801 B
/i18n/locales/oc 904 B
/i18n/locales/pl 951 B
/i18n/locales/pt 836 B
/i18n/locales/pt-BR 836 B
/i18n/locales/pt-PT 807 B
/i18n/locales/ro 882 B
/i18n/locales/ru 1.10 kB
/i18n/locales/sk 929 B
/i18n/locales/sl 861 B
/i18n/locales/sr 827 B
/i18n/locales/sv 825 B
/i18n/locales/te 1.10 kB
/i18n/locales/th 1.08 kB
/i18n/locales/tr 906 B
/i18n/locales/uk 1.13 kB
/i18n/locales/vi 903 B
/i18n/locales/zh 810 B
/i18n/locales/zh-CN 810 B
/i18n/locales/zh-TW 821 B
🏷️ @videojs/element — no changes
Entries (2)
Entry Initial
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Initial
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Initial
/array 104 B
/dom 2.67 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 231 B
/style 190 B
/time 930 B
/number 158 B
📦 @videojs/spf — no changes
Entries (4)
Entry Initial
. 4.45 kB
/dom 6.33 kB
/hls 15.44 kB
/background-looping-video 12.97 kB

ℹ️ How to interpret

JS sizes are initial static graph totals (minified + brotli). Lazy dynamic chunks are shown separately when present.

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current initial sizes.

sampotts and others added 10 commits June 19, 2026 15:25
Add createI18n with ambient lang inheritance, browser translation fallback,
default loadLocale, I18nController/TextMixin, and translated labels across HTML UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
Wrap store provider mixins with I18nProviderMixin so ambient lang inheritance
and lazy locale loading apply to default skins without manual wrappers.

Co-authored-by: Cursor <cursoragent@cursor.com>
CDN create-i18n imports loadLocale from the i18n bundle; re-export it so
sandbox and CDN builds resolve the symbol.

Co-authored-by: Cursor <cursoragent@cursor.com>
Compose i18n provider onto SkinElement, resolve playback-rate tooltips via
getResolvedLabel, and normalize CDN i18n entry paths on Windows.

Co-authored-by: Cursor <cursoragent@cursor.com>
Re-check lazy-load sequence after getBrowserTranslations resolves so a
superseded locale cannot register browser translations globally.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep tooltip copy visible before media state attaches by translating the
trigger label when getResolvedLabel returns undefined.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use formatRemaining with the parametric timeRemainingPhrase template instead
of the removed remainingTimeSuffix key.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds integration coverage that play button aria-label updates when
document.documentElement.lang changes and the i18n provider has no
explicit lang attribute.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use mergeLocaleOverlays loadedTags instead of merged overlay so
English-only lazy merges no longer block browser translation fallback.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Feature: Wire I18nController into UI Elements and Move Error Dialog Defaults to Core Feature: HTML i18n Layer (@videojs/html/i18n)

1 participant