Skip to content

feat(packages): add i18n#1708

Draft
sampotts wants to merge 9 commits into
mainfrom
feat/i18n
Draft

feat(packages): add i18n#1708
sampotts wants to merge 9 commits into
mainfrom
feat/i18n

Conversation

@sampotts

@sampotts sampotts commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Refs #1589
Refs #1590

Summary

Restores the combined i18n foundation and locale-pack work from the two accidentally merged PRs after #1707 reverted them out of main.

feat/i18n will be the feature branch the other stacked PRs merge into and this PR will be used to merge the whole feature into main:

main
#1708 feat/i18n
#1591 feat/i18n-html
#1595 feat/i18n-react
#1593 feat/i18n-sandbox
#1600 docs/i18n


Note

Medium Risk
Large, user-facing change to labels and accessibility across controls and many locales; risk is mitigated by tests, codegen checks, and restoring previously reviewed design, but regressions in string keys or locale loading would affect all skins.

Overview
Restores the Video.js 10 i18n stack that was reverted from main: a new @videojs/core/i18n entry with a global registry, typed createTranslator, loadLocale lazy imports, 50+ shipped locale packs (v8 parity), and optional Chrome Translator API fallback (getBrowserTranslations / shouldAttemptBrowserTranslation).

Codegen & CI: generate-i18n-locales.ts runs on @videojs/core prebuild, regenerating locales/all.ts, load-locale.ts, and html/react locale re-export stubs; check-workspace gains an i18n locales consistency check. package.json adds ./i18n and ./i18n/locales/* exports and marks the registry as a side effect.

Player copy: English defaults live in locales/en.ts with an expanded key set (live/cast, sliders, indicators, error dialog, timeRemainingPhrase, etc.). UI cores (e.g. captions/cast and others in the diff) now expose opaque label keys via resolveOptionalControlLabel instead of hardcoded English; related tests and internal/design/i18n.md are updated to implemented status and document provider merge order, tooltips, and loadLocale.

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

@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 9df3884
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a38a6c7da32d60008c731f9
😎 Deploy Preview https://deploy-preview-1708--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.

@vercel

vercel Bot commented Jun 18, 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 22, 2026 3:07am

Request Review

@sampotts sampotts marked this pull request as draft June 18, 2026 23:01
@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.01 kB 44.70 kB +701 B +1.6% 🔺
/video/minimal-skin.tailwind 44.58 kB 45.25 kB +691 B +1.5% 🔺
/video/skin 44.28 kB 44.97 kB +699 B +1.5% 🔺
/video/skin.tailwind 44.98 kB 45.62 kB +657 B +1.4% 🔺
/audio/minimal-skin 36.62 kB 37.29 kB +680 B +1.8% 🔺
/audio/minimal-skin.tailwind 37.04 kB 37.63 kB +601 B +1.6% 🔺
/audio/skin 37.80 kB 38.50 kB +722 B +1.9% 🔺
/audio/skin.tailwind 38.25 kB 38.89 kB +656 B +1.7% 🔺
/live-video/minimal-skin 43.12 kB 43.80 kB +703 B +1.6% 🔺
/live-video/minimal-skin.tailwind 43.58 kB 44.23 kB +658 B +1.5% 🔺
/live-video/skin 43.08 kB 43.75 kB +690 B +1.6% 🔺
/live-video/skin.tailwind 43.58 kB 44.30 kB +733 B +1.6% 🔺
/live-audio/minimal-skin 29.73 kB 30.15 kB +430 B +1.4% 🔺
/live-audio/minimal-skin.tailwind 29.20 kB 29.64 kB +446 B +1.5% 🔺
/live-audio/skin 31.04 kB 31.50 kB +466 B +1.5% 🔺
/live-audio/skin.tailwind 30.66 kB 31.12 kB +474 B +1.5% 🔺
/ui/compounds 27.90 kB 28.60 kB +716 B +2.5% 🔺
/ui/time-slider 12.40 kB 12.74 kB +343 B +2.7% 🔺
/video (default) 44.32 kB 45.02 kB +720 B +1.6% 🔺
/video (default + hls) 183.79 kB 184.42 kB +647 B +0.3% 🔺
/video (minimal) 43.98 kB 44.72 kB +765 B +1.7% 🔺
/video (minimal + hls) 183.63 kB 184.21 kB +588 B +0.3% 🔺
/audio (default) 37.83 kB 38.49 kB +679 B +1.8% 🔺
/audio (minimal) 36.61 kB 37.26 kB +668 B +1.8% 🔺
Presets (7)
Entry Initial
/video (default) 45.02 kB
/video (default + hls) 184.42 kB
/video (minimal) 44.72 kB
/video (minimal + hls) 184.21 kB
/audio (default) 38.49 kB
/audio (minimal) 37.26 kB
/background 4.20 kB
Media (10)
Entry Initial
/media/background-video 1.14 kB
/media/container 1.72 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
/video/player 8.06 kB
/audio/player 5.38 kB
/background/player 3.93 kB
/live-video/player 7.63 kB
/live-audio/player 5.40 kB
Skins (30)
Entry Type Initial
/video/minimal-skin.css css 5.45 kB
/video/skin.css css 5.43 kB
/video/minimal-skin js 44.70 kB
/video/minimal-skin.tailwind js 45.25 kB
/video/skin js 44.97 kB
/video/skin.tailwind js 45.62 kB
/audio/minimal-skin.css css 3.60 kB
/audio/skin.css css 3.53 kB
/audio/minimal-skin js 37.29 kB
/audio/minimal-skin.tailwind js 37.63 kB
/audio/skin js 38.50 kB
/audio/skin.tailwind js 38.89 kB
/background/skin.css css 133 B
/background/skin js 1.14 kB
/live-video/minimal-skin.css css 5.45 kB
/live-video/skin.css css 5.43 kB
/live-video/minimal-skin js 43.80 kB
/live-video/minimal-skin.tailwind js 44.23 kB
/live-video/skin js 43.75 kB
/live-video/skin.tailwind js 44.30 kB
/live-audio/minimal-skin.css css 3.60 kB
/live-audio/skin.css css 3.53 kB
/live-audio/minimal-skin js 30.15 kB
/live-audio/minimal-skin.tailwind js 29.64 kB
/live-audio/skin js 31.50 kB
/live-audio/skin.tailwind js 31.12 kB
/global.css css 176 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 1.44 kB
UI Components (38)
Entry Initial
/ui/airplay-button 2.38 kB
/ui/alert-dialog 2.51 kB
/ui/alert-dialog-close 2.29 kB
/ui/alert-dialog-description 2.30 kB
/ui/alert-dialog-title 2.26 kB
/ui/buffering-indicator 2.26 kB
/ui/captions-button 2.44 kB
/ui/captions-radio-group 2.79 kB
/ui/cast-button 2.37 kB
/ui/compounds 2.98 kB
/ui/controls 2.73 kB
/ui/error-dialog 2.75 kB
/ui/fullscreen-button 2.40 kB
/ui/hotkey 2.41 kB
/ui/menu 2.77 kB
/ui/mute-button 2.31 kB
/ui/pip-button 2.37 kB
/ui/play-button 2.35 kB
/ui/playback-rate-button 2.42 kB
/ui/playback-rate-radio-group 2.83 kB
/ui/popover 2.68 kB
/ui/poster 2.26 kB
/ui/quality-radio-group 2.78 kB
/ui/seek-button 2.41 kB
/ui/seek-indicator 2.41 kB
/ui/seek-indicator-value 512 B
/ui/slider 2.78 kB
/ui/status-announcer 2.35 kB
/ui/status-indicator 2.42 kB
/ui/status-indicator-value 532 B
/ui/thumbnail 2.23 kB
/ui/time 2.74 kB
/ui/time-slider 2.76 kB
/ui/tooltip 2.72 kB
/ui/volume-indicator 2.42 kB
/ui/volume-indicator-fill 550 B
/ui/volume-indicator-value 535 B
/ui/volume-slider 2.76 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react

Path Base initial PR initial Diff % Lazy
/video/minimal-skin 36.81 kB 37.42 kB +619 B +1.6% 🔺
/video/minimal-skin.tailwind 42.58 kB 43.25 kB +687 B +1.6% 🔺
/video/skin 36.73 kB 37.39 kB +669 B +1.8% 🔺
/video/skin.tailwind 42.51 kB 43.15 kB +656 B +1.5% 🔺
/audio/minimal-skin 29.98 kB 30.61 kB +645 B +2.1% 🔺
/audio/minimal-skin.tailwind 31.76 kB 32.38 kB +637 B +2.0% 🔺
/audio/skin 29.91 kB 30.59 kB +691 B +2.3% 🔺
/audio/skin.tailwind 33.75 kB 34.46 kB +724 B +2.1% 🔺
/live-video/minimal-skin 32.56 kB 32.98 kB +431 B +1.3% 🔺
/live-video/minimal-skin.tailwind 38.22 kB 38.62 kB +400 B +1.0% 🔺
/live-video/skin 32.58 kB 33.00 kB +420 B +1.3% 🔺
/live-video/skin.tailwind 38.24 kB 38.66 kB +431 B +1.1% 🔺
/ui/time-slider 10.11 kB 10.43 kB +329 B +3.2% 🔺
/video (default) 36.83 kB 37.49 kB +675 B +1.8% 🔺
/video (default + hls) 175.18 kB 175.79 kB +619 B +0.3% 🔺
/video (minimal) 36.91 kB 37.57 kB +676 B +1.8% 🔺
/video (minimal + hls) 175.13 kB 175.89 kB +780 B +0.4% 🔺
/audio (default) 29.97 kB 30.65 kB +702 B +2.3% 🔺
/audio (minimal) 30.04 kB 30.72 kB +693 B +2.3% 🔺
Presets (7)
Entry Initial
/video (default) 37.49 kB
/video (default + hls) 175.79 kB
/video (minimal) 37.57 kB
/video (minimal + hls) 175.89 kB
/audio (default) 30.65 kB
/audio (minimal) 30.72 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.25 kB
/video/skin js 37.39 kB
/video/skin.tailwind js 43.15 kB
/audio/minimal-skin.css css 3.47 kB
/audio/skin.css css 3.39 kB
/audio/minimal-skin js 30.61 kB
/audio/minimal-skin.tailwind js 32.38 kB
/audio/skin js 30.59 kB
/audio/skin.tailwind js 34.46 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.98 kB
/live-video/minimal-skin.tailwind js 38.62 kB
/live-video/skin js 33.00 kB
/live-video/skin.tailwind js 38.66 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.18 kB
/ui/alert-dialog 2.21 kB
/ui/buffering-indicator 2.02 kB
/ui/captions-button 2.15 kB
/ui/captions-radio-group 2.05 kB
/ui/cast-button 2.17 kB
/ui/controls 2.00 kB
/ui/error-dialog 2.19 kB
/ui/fullscreen-button 2.15 kB
/ui/gesture 2.28 kB
/ui/hotkey 2.25 kB
/ui/live-button 2.06 kB
/ui/menu 2.37 kB
/ui/mute-button 2.17 kB
/ui/pip-button 2.16 kB
/ui/play-button 2.16 kB
/ui/playback-rate 2.00 kB
/ui/playback-rate-button 2.18 kB
/ui/popover 2.65 kB
/ui/poster 2.03 kB
/ui/quality 2.07 kB
/ui/seek-button 2.13 kB
/ui/seek-indicator 2.12 kB
/ui/slider 2.19 kB
/ui/status-announcer 2.06 kB
/ui/status-indicator 2.07 kB
/ui/thumbnail 2.01 kB
/ui/time 1.98 kB
/ui/time-slider 2.34 kB
/ui/tooltip 2.64 kB
/ui/volume-indicator 2.15 kB
/ui/volume-slider 2.24 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core

Path Base initial PR initial Diff % Lazy
. 9.08 kB 10.61 kB +1.53 kB +16.8% 🔴
/i18n 2.66 kB +49.22 kB 🆕
/i18n/base 1.28 kB 🆕
/i18n/locales/all 29.23 kB 🆕
/i18n/locales/ar 1.10 kB 🆕
/i18n/locales/az 1013 B 🆕
/i18n/locales/bg 1.13 kB 🆕
/i18n/locales/bn 1.16 kB 🆕
/i18n/locales/bs 929 B 🆕
/i18n/locales/ca 967 B 🆕
/i18n/locales/cs 957 B 🆕
/i18n/locales/cy 930 B 🆕
/i18n/locales/da 908 B 🆕
/i18n/locales/de 1003 B 🆕
/i18n/locales/el 1.33 kB 🆕
/i18n/locales/en 720 B 🆕
/i18n/locales/es 915 B 🆕
/i18n/locales/et 974 B 🆕
/i18n/locales/eu 927 B 🆕
/i18n/locales/fa 1.09 kB 🆕
/i18n/locales/fi 944 B 🆕
/i18n/locales/fr 989 B 🆕
/i18n/locales/gd 1022 B 🆕
/i18n/locales/gl 907 B 🆕
/i18n/locales/he 1.02 kB 🆕
/i18n/locales/hi 1.18 kB 🆕
/i18n/locales/hr 941 B 🆕
/i18n/locales/hu 1003 B 🆕
/i18n/locales/it 940 B 🆕
/i18n/locales/ja 1.07 kB 🆕
/i18n/locales/ko 1.03 kB 🆕
/i18n/locales/lv 986 B 🆕
/i18n/locales/mr 1.18 kB 🆕
/i18n/locales/nb 901 B 🆕
/i18n/locales/ne 1.17 kB 🆕
/i18n/locales/nl 916 B 🆕
/i18n/locales/nn 897 B 🆕
/i18n/locales/oc 992 B 🆕
/i18n/locales/pl 1.02 kB 🆕
/i18n/locales/pt 929 B 🆕
/i18n/locales/pt-BR 929 B 🆕
/i18n/locales/pt-PT 899 B 🆕
/i18n/locales/ro 968 B 🆕
/i18n/locales/ru 1.21 kB 🆕
/i18n/locales/sk 1.00 kB 🆕
/i18n/locales/sl 960 B 🆕
/i18n/locales/sr 922 B 🆕
/i18n/locales/sv 923 B 🆕
/i18n/locales/te 1.19 kB 🆕
/i18n/locales/th 1.19 kB 🆕
/i18n/locales/tr 999 B 🆕
/i18n/locales/uk 1.25 kB 🆕
/i18n/locales/vi 1005 B 🆕
/i18n/locales/zh 908 B 🆕
/i18n/locales/zh-CN 908 B 🆕
/i18n/locales/zh-TW 911 B 🆕
Entries (69)
Entry Initial Lazy
. 10.61 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.66 kB 49.22 kB
/i18n/base 1.28 kB
/i18n/locales/all 29.23 kB
/i18n/locales/ar 1.10 kB
/i18n/locales/az 1013 B
/i18n/locales/bg 1.13 kB
/i18n/locales/bn 1.16 kB
/i18n/locales/bs 929 B
/i18n/locales/ca 967 B
/i18n/locales/cs 957 B
/i18n/locales/cy 930 B
/i18n/locales/da 908 B
/i18n/locales/de 1003 B
/i18n/locales/el 1.33 kB
/i18n/locales/en 720 B
/i18n/locales/es 915 B
/i18n/locales/et 974 B
/i18n/locales/eu 927 B
/i18n/locales/fa 1.09 kB
/i18n/locales/fi 944 B
/i18n/locales/fr 989 B
/i18n/locales/gd 1022 B
/i18n/locales/gl 907 B
/i18n/locales/he 1.02 kB
/i18n/locales/hi 1.18 kB
/i18n/locales/hr 941 B
/i18n/locales/hu 1003 B
/i18n/locales/it 940 B
/i18n/locales/ja 1.07 kB
/i18n/locales/ko 1.03 kB
/i18n/locales/lv 986 B
/i18n/locales/mr 1.18 kB
/i18n/locales/nb 901 B
/i18n/locales/ne 1.17 kB
/i18n/locales/nl 916 B
/i18n/locales/nn 897 B
/i18n/locales/oc 992 B
/i18n/locales/pl 1.02 kB
/i18n/locales/pt 929 B
/i18n/locales/pt-BR 929 B
/i18n/locales/pt-PT 899 B
/i18n/locales/ro 968 B
/i18n/locales/ru 1.21 kB
/i18n/locales/sk 1.00 kB
/i18n/locales/sl 960 B
/i18n/locales/sr 922 B
/i18n/locales/sv 923 B
/i18n/locales/te 1.19 kB
/i18n/locales/th 1.19 kB
/i18n/locales/tr 999 B
/i18n/locales/uk 1.25 kB
/i18n/locales/vi 1005 B
/i18n/locales/zh 908 B
/i18n/locales/zh-CN 908 B
/i18n/locales/zh-TW 911 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

Path Base initial PR initial Diff % Lazy
/dom 2.26 kB 2.67 kB +423 B +18.3% 🔴
/time 478 B 930 B +452 B +94.6% 🔴
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 sampotts changed the title feat(core): restore i18n stack base feat(packages): add i18n Jun 18, 2026

@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 using default effort and found 2 potential issues.

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 3d1a240. Configure here.

"sideEffects": false,
"sideEffects": [
"**/i18n/registry.js"
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

sideEffects omits bundled i18n entry

Medium Severity

sideEffects only lists **/i18n/registry.js, but the published @videojs/core/i18n entry is a single i18n bundle that inlines registry.ts (including pre-registering en). Bundlers that honor sideEffects may tree-shake away registry initialization when consumers import named exports from @videojs/core/i18n, leaving translations without the English base layer.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3d1a240. Configure here.

if (!expectedPlatform.has(tag)) {
warnings.push(`Unexpected locale re-export packages/${pkg}/src/i18n/locales/${file}`);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Workspace check crashes missing locale dirs

Low Severity

Check 7 calls readdirSync on packages/html/src/i18n/locales and packages/react/src/i18n/locales without verifying those directories exist. If locale stubs were not generated yet, pnpm check:workspace throws instead of reporting a clear warning like the per-file existsSync checks above.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3d1a240. Configure here.

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.

1 participant