feat(packages): opaque translation keys in core ui with duration formatting#1555
feat(packages): opaque translation keys in core ui with duration formatting#1555sampotts wants to merge 16 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
ef7f395 to
47d2618
Compare
4837266 to
f964c98
Compare
d45a4c8 to
3e63671
Compare
2855ad9 to
2404b8f
Compare
8570f9d to
e587e78
Compare
b511158 to
8d7adef
Compare
8d7adef to
ee57311
Compare
ee57311 to
bae55cd
Compare
…atting Use TranslationKeyOrString for control labels, centralize optional label resolution, and expose formatOptions for formatDuration-based copy across time controls and sliders; extend utils time helpers used by core and tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Use Number.isFinite for duration in time slider aria-valuetext so duration 0 still formats. Fix formatOptions JSDoc for api-docs schema. Cache optional control labels so impure label callbacks run once per state. Inline duration decomposition in formatDuration (remove redundant toDurationRecord filtering). Co-authored-by: Cursor <cursoragent@cursor.com>
Match formatVolumePercent(0.333) in the valuetext rounding test so the expectation follows the value passed through getAttrs. Co-authored-by: Cursor <cursoragent@cursor.com>
Return volumeSliderValueText* and timeSliderValueText* keys with params instead of mixing localized Intl output with hardcoded English connectors. Co-authored-by: Cursor <cursoragent@cursor.com>
Wire resolveControlAttrs through HTML and React adapters so aria-label and aria-valuetext translation keys receive label and valuetext params. Co-authored-by: Cursor <cursoragent@cursor.com>
Wire LiveButton through resolveControlAttrs like other media buttons. Update html/react tests to expect resolved English aria-label strings. Co-authored-by: Cursor <cursoragent@cursor.com>
Wire PlaybackRateMenuElement, TimeElement, and Time.Value through resolveControlAttrs so translation keys resolve before reaching the DOM. Co-authored-by: Cursor <cursoragent@cursor.com>
Reuse NumberFormat and DurationFormat instances keyed by locale and style to avoid reconstructing them on every slider update. Co-authored-by: Cursor <cursoragent@cursor.com>
Fall back to formatTimeAsPhrase or a simple percent string when DurationFormat/NumberFormat construction or format throws. Co-authored-by: Cursor <cursoragent@cursor.com>
Re-resolve when label changes for the same state snapshot so cache hits do not return a stale custom label. Co-authored-by: Cursor <cursoragent@cursor.com>
…ters Pass active i18n locale into formatDuration and formatVolumePercent so aria-valuetext announcements match translated control labels. Co-authored-by: Cursor <cursoragent@cursor.com>
Use setFormatLocale on VolumeSliderCore instead of a public prop so typecheck and api-docs schema validation stay clean. Co-authored-by: Cursor <cursoragent@cursor.com>
Pass active i18n locale into TimeCore formatOptions so spoken duration and aria-valuetext match translated control labels. Co-authored-by: Cursor <cursoragent@cursor.com>
Add remainingTimeSuffix registry key and wire formatOptions.translate from i18n adapters so spoken remaining time does not mix languages. Co-authored-by: Cursor <cursoragent@cursor.com>
Use Intl-formatted values directly for non-range time and unmuted volume aria-valuetext, and drop unused playbackRateMultiplier, timeSliderValueTextCurrent, and volumeSliderValueText translation keys. Co-authored-by: Cursor <cursoragent@cursor.com>
englishTranslations was renamed to translations when en locale moved to a default export; the stale import broke typecheck and runtime test setup. Co-authored-by: Cursor <cursoragent@cursor.com>
bae55cd to
9ba7cc3
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bae55cd. Configure here.
| const DurationFormat = (Intl as typeof Intl & { DurationFormat?: DurationFormatConstructor }).DurationFormat; | ||
|
|
||
| const percentFormatters = new Map<string, Intl.NumberFormat>(); | ||
| const durationFormatters = new Map<string, InstanceType<NonNullable<typeof DurationFormat>>>(); |
There was a problem hiding this comment.
Module-level formatter caches leak memory unboundedly
Low Severity
percentFormatters and durationFormatters are module-level Map instances that grow unboundedly. Every unique locale string (or locale+style combination) adds an entry that is never evicted. In applications where locale values are dynamically constructed or vary widely, these maps become a memory leak. A bounded LRU cache or WeakRef-based approach would be safer for long-lived processes.
Reviewed by Cursor Bugbot for commit bae55cd. Configure here.
| } | ||
|
|
||
| getValueTextParams(state: VolumeSliderState): { percent: string } { | ||
| return { percent: formatVolumePercent(state.value / 100, this.#formatLocale) }; |
There was a problem hiding this comment.
Redundant getValueTextParams call for non-muted volume valuetext
Low Severity
When not muted, getValueText returns the already-formatted percent string (not a translation key), yet resolveControlAttrs still calls getValueTextParams and passes those params to resolveTranslationPhrase. The translator doesn't find the formatted string as a key and returns it verbatim, making the params call wasteful. A dedicated non-parametric path or returning the value text key consistently (like the muted case) would be cleaner and avoid the unnecessary formatVolumePercent double-call.
Reviewed by Cursor Bugbot for commit bae55cd. Configure here.
|
Consolidating i18n work into a single feature branch ( |


Closes #1366
Summary
Core media controls now treat labels and
aria-labelvalues as translation tokens (TranslationKeyOrString) so the HTML i18n layer can resolve them consistently. Optional label props shareresolveOptionalControlLabel, and time / time-slider exposeformatOptionsforformatDuration-based accessibility copy.Changes
@videojs/utils/timewith tests; core expectations aligned on registry keys and parametric labels (seek, playback rate).phraseFormattoformatOptionswhere it configures spoken-duration output (aria-valuetext/ phrase text), distinct from clock display.Testing
pnpm -F @videojs/core build && pnpm typecheckpnpm -F @videojs/core testpnpm -F @videojs/react testMade with Cursor
Note
Medium Risk
Touches many core UI controls and their HTML/React adapters by changing
label/aria-*to flow through translation keys and new param resolution, which can cause regressions in accessibility text or tooltip copy if any adapter misses the new resolver. Formatting changes now depend onIntl.DurationFormat/Intl.NumberFormatbehavior and locale wiring.Overview
Core UI controls now return opaque i18n keys (new
TranslationKeyOrString) forlabelandaria-labelinstead of hardcoded English strings, and shared helpers (resolveOptionalControlLabel,resolveControlAttrs,resolveControlLabel,resolveTranslationPhrase) were introduced to consistently resolve optional custom labels and parametric phrases.Buttons/menus that need template params (notably seek and playback-rate) now expose
getLabelParams, and sliders/time components switch theiraria-valuetextto translation tokens plus params, with locale-aware formatting added via new@videojs/utils/timehelpers (formatDuration,formatVolumePercent) and newformatOptionsplumbed throughTimeCore/TimeSliderCore.HTML and React adapters were updated to instantiate an i18n controller/hook and apply resolved attributes/tooltips, and English translations/types were adjusted (new
remainingTimeSuffix, updatedvolumeSliderValueTextMuted, removed unused keys) with tests updated/added accordingly.Reviewed by Cursor Bugbot for commit 9ba7cc3. Bugbot is set up for automated code reviews on this repo. Configure here.