Skip to content

feat(core): add built-in locale packs and lazy loadLocale#1590

Merged
luwes merged 9 commits into
mainfrom
feat/i18n-locale-packs
Jun 18, 2026
Merged

feat(core): add built-in locale packs and lazy loadLocale#1590
luwes merged 9 commits into
mainfrom
feat/i18n-locale-packs

Conversation

@sampotts

@sampotts sampotts commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Refs #222
Closes #1368
Closes #1369

Summary

Ships v8-parity built-in locale packs (~50 languages) with codegen for lazy loadLocale, platform re-exports, CDN locale stubs, and workspace consistency checks.

Changes

  • 50 locale .ts packs + all.ts aggregator in @videojs/core/i18n/locales
  • built-in-locales.ts, load-locale.ts (codegen), and generate-i18n-locales.ts script
  • HTML/React generated locale re-exports and CDN locale build script
  • check:workspace i18n locale consistency check
Stack info

PR 2 of 5 — stacks on #1589 (core foundation).

Testing

pnpm -F @videojs/core build
pnpm -F @videojs/core test src/core/i18n/tests/load-locale.test.ts
pnpm check:workspace

Made with Cursor


Note

Medium Risk
Large additive i18n surface (many locale files and generated outputs); loadLocale and lookup-chain behavior affect runtime translations but are covered by tests and mostly data/codegen.

Overview
Adds v8-parity built-in locale packs (~50 languages) in @videojs/core, with a single source of truth in built-in-locales.ts and codegen (generate-i18n-locales.ts) that runs on prebuild to refresh load-locale.ts, locales/all.ts, and generated re-exports under html and react.

loadLocale is exported from @videojs/core/i18n: it lazy-imports shipped packs when nothing is already registered, walking localeLookupChain (regional tags, casing, Unicode extensions). canonicalLocaleRegistryKey is now public for that normalization.

Build & tooling: tsdown bundles every locale entry; check:workspace gains an i18n locales check; HTML gets a CDN locale stub generator. Types widen BuiltInLocale beyond en. Tests cover loadLocale behavior.

Reviewed by Cursor Bugbot for commit fe7b46e. 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 18, 2026 7:11pm

Request Review

Comment thread packages/core/src/core/i18n/load-locale.ts Outdated
Comment thread packages/core/src/core/i18n/load-locale.ts Outdated
Comment thread packages/html/scripts/build-cdn-locales.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 be7c182. Configure here.

Comment thread packages/core/src/core/i18n/load-locale.ts Outdated
@sampotts sampotts force-pushed the feat/i18n-locale-packs branch from fe7b46e to f579d71 Compare June 18, 2026 04:37
Base automatically changed from feat/i18n-core to main June 18, 2026 18:55
@netlify

netlify Bot commented Jun 18, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

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

sampotts and others added 9 commits June 18, 2026 12:03
Use a parametric timeRemainingPhrase key so locales control full remaining
time word order; formatDuration accepts formatRemaining instead of translate.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 7139b92)
Ship v8-parity locale packs with codegen for loadLocale, platform
re-exports, CDN locale stubs, and workspace consistency checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 6fc0451)
Map lowercase BCP 47 tags from localeLookupChain to canonical loader keys
so regional packs like pt-BR and zh-TW load correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit a9eced9)
Use sentence case for liveBadge (skins uppercase via CSS), replace legacy
Video.js Progress mappings with seek slider labels, and add timeRemainingPhrase
to every shipped locale.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit d669ff4)
Replace legacy compound live-button strings, wrong exit-fullscreen labels,
and noun-only caption toggles with short aria-label copy aligned to how
LiveButtonCore and CaptionsButtonCore use each key.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 9abcdc9)
Use canonicalLocaleRegistryKey for lazy loader lookup so tags like
zh-CN-u-nu-hans match shipped packs the same way as the registry.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 6e7c966)
Generated CDN locale files import registerI18n from this module, which
maps to the shared i18n.js bundle via the CDN build plugin.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 5319847)
Locale copy fixes live in the locale pack files directly; the one-off
override catalog is not needed in the repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 5643f9f)
Regional tags such as es-MX now lazy-load the nearest shipped base pack
instead of requiring an exact loader match.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit f579d71)
@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html — no changes
Presets (7)
Entry Size
/video (default) 44.89 kB
/video (default + hls) 184.36 kB
/video (minimal) 44.55 kB
/video (minimal + hls) 184.06 kB
/audio (default) 38.40 kB
/audio (minimal) 37.17 kB
/background 4.22 kB
Media (10)
Entry Size
/media/background-video 1.07 kB
/media/container 1.72 kB
/media/dash-video 242.74 kB
/media/hls-video 141.10 kB
/media/mux-audio 163.66 kB
/media/mux-video 163.68 kB
/media/native-hls-video 9.02 kB
/media/simple-hls-audio-only 16.94 kB
/media/simple-hls-video 18.77 kB
/media/vimeo-video 12.29 kB
Players (5)
Entry Size
/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.39 kB
Skins (30)
Entry Type Size
/video/minimal-skin.css css 5.45 kB
/video/skin.css css 5.43 kB
/video/minimal-skin js 44.57 kB
/video/minimal-skin.tailwind js 45.16 kB
/video/skin js 44.88 kB
/video/skin.tailwind js 45.49 kB
/audio/minimal-skin.css css 3.60 kB
/audio/skin.css css 3.53 kB
/audio/minimal-skin js 37.17 kB
/audio/minimal-skin.tailwind js 37.56 kB
/audio/skin js 38.34 kB
/audio/skin.tailwind js 38.72 kB
/background/skin.css css 133 B
/background/skin js 1.16 kB
/live-video/minimal-skin.css css 5.45 kB
/live-video/skin.css css 5.43 kB
/live-video/minimal-skin js 43.68 kB
/live-video/minimal-skin.tailwind js 44.09 kB
/live-video/skin js 43.64 kB
/live-video/skin.tailwind js 44.12 kB
/live-audio/minimal-skin.css css 3.60 kB
/live-audio/skin.css css 3.53 kB
/live-audio/minimal-skin js 30.17 kB
/live-audio/minimal-skin.tailwind js 29.65 kB
/live-audio/skin js 31.48 kB
/live-audio/skin.tailwind js 31.06 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 Size
/ui/airplay-button 3.66 kB
/ui/alert-dialog 1.32 kB
/ui/alert-dialog-close 524 B
/ui/alert-dialog-description 483 B
/ui/alert-dialog-title 405 B
/ui/buffering-indicator 2.33 kB
/ui/captions-button 3.76 kB
/ui/captions-radio-group 3.36 kB
/ui/cast-button 3.73 kB
/ui/compounds 8.75 kB
/ui/controls 2.38 kB
/ui/error-dialog 3.47 kB
/ui/fullscreen-button 3.65 kB
/ui/hotkey 2.19 kB
/ui/menu 5.05 kB
/ui/mute-button 3.69 kB
/ui/pip-button 3.69 kB
/ui/play-button 3.74 kB
/ui/playback-rate-button 3.72 kB
/ui/playback-rate-radio-group 3.15 kB
/ui/popover 2.00 kB
/ui/poster 2.35 kB
/ui/quality-radio-group 2.31 kB
/ui/seek-button 3.71 kB
/ui/seek-indicator 3.88 kB
/ui/seek-indicator-value 235 B
/ui/slider 1.56 kB
/ui/status-announcer 3.62 kB
/ui/status-indicator 3.74 kB
/ui/status-indicator-value 237 B
/ui/thumbnail 3.30 kB
/ui/time 3.17 kB
/ui/time-slider 4.04 kB
/ui/tooltip 2.14 kB
/ui/volume-indicator 3.94 kB
/ui/volume-indicator-fill 176 B
/ui/volume-indicator-value 166 B
/ui/volume-slider 2.96 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react — no changes
Presets (7)
Entry Size
/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 Size
/media/background-video 575 B
/media/dash-video 241.13 kB
/media/hls-video 139.78 kB
/media/mux-audio 162.29 kB
/media/mux-video 162.29 kB
/media/native-hls-video 7.41 kB
/media/simple-hls-audio-only 15.37 kB
/media/simple-hls-video 17.18 kB
/media/vimeo-video 10.58 kB
Skins (27)
Entry Type Size
/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.10 kB
/audio/minimal-skin.css css 3.47 kB
/audio/skin.css css 3.39 kB
/audio/minimal-skin js 30.56 kB
/audio/minimal-skin.tailwind js 32.36 kB
/audio/skin js 30.54 kB
/audio/skin.tailwind js 34.36 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.92 kB
/live-video/minimal-skin.tailwind js 38.51 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.86 kB
/live-audio/skin js 22.04 kB
/live-audio/skin.tailwind js 25.02 kB
UI Components (32)
Entry Size
/ui/airplay-button 3.08 kB
/ui/alert-dialog 1.25 kB
/ui/buffering-indicator 2.55 kB
/ui/captions-button 3.11 kB
/ui/captions-radio-group 2.83 kB
/ui/cast-button 2.76 kB
/ui/controls 2.52 kB
/ui/error-dialog 2.72 kB
/ui/fullscreen-button 2.75 kB
/ui/gesture 2.20 kB
/ui/hotkey 2.39 kB
/ui/live-button 3.33 kB
/ui/menu 6.79 kB
/ui/mute-button 2.75 kB
/ui/pip-button 3.10 kB
/ui/play-button 2.79 kB
/ui/playback-rate 2.85 kB
/ui/playback-rate-button 2.73 kB
/ui/popover 2.48 kB
/ui/poster 2.40 kB
/ui/quality 3.03 kB
/ui/seek-button 2.72 kB
/ui/seek-indicator 2.31 kB
/ui/slider 3.62 kB
/ui/status-announcer 2.49 kB
/ui/status-indicator 2.18 kB
/ui/thumbnail 2.75 kB
/ui/time 3.27 kB
/ui/time-slider 4.28 kB
/ui/tooltip 2.43 kB
/ui/volume-indicator 2.19 kB
/ui/volume-slider 3.63 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core

Path Base PR Diff %
/i18n 1.76 kB 29.34 kB +27.58 kB +1569.9% 🔴
/i18n/locales/all 26.96 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/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 🆕
Entries (67)
Entry Size
. 10.05 kB
/dom 17.11 kB
/dom/media/custom-media-element 2.04 kB
/dom/media/dash 236.79 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.55 kB
/dom/media/simple-hls-audio-only 14.79 kB
/dom/media/vimeo 9.86 kB
/i18n 29.34 kB
/i18n/locales/all 26.96 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 Size
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Size
. 1.39 kB
/html 696 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Size
/array 104 B
/dom 2.63 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 232 B
/style 190 B
/time 930 B
/number 158 B
📦 @videojs/spf — no changes
Entries (4)
Entry Size
. 4.45 kB
/dom 6.33 kB
/hls 15.44 kB
/background-looping-video 12.97 kB

ℹ️ How to interpret

All sizes are standalone totals (minified + brotli).

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

Run pnpm size locally to check current sizes.

@luwes luwes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

👍

@luwes luwes merged commit 9170a58 into main Jun 18, 2026
26 checks passed
@luwes luwes deleted the feat/i18n-locale-packs branch June 18, 2026 19:16
@luwes luwes mentioned this pull request Jun 18, 2026
@sampotts sampotts restored the feat/i18n-locale-packs branch June 18, 2026 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Feature: CDN Build for i18n and Locales Feature: Built-in Locale Packs

2 participants