Skip to content

Commit aeb7251

Browse files
Merge pull request #3195 from nextcloud/enh/noid/plyr-speed-l10n
fix(plyr): add l10n to speed selections
2 parents 883008a + 89531d9 commit aeb7251

7 files changed

Lines changed: 112 additions & 37 deletions

File tree

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

css/viewer-init.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* extracted by css-entry-points-plugin */
2-
@import './init-BV7L6N6D.chunk.css';
2+
@import './init-D8SK1jbH.chunk.css';
33
@import './previewUtils-DsspQ6dL.chunk.css';
44
@import './NcIconSvgWrapper-Bui9PhAS-3xIBDiQU.chunk.css';
55
@import './NcActionButton-Dc3ra1Np.chunk.css';

js/viewer-init.mjs

Lines changed: 31 additions & 31 deletions
Large diffs are not rendered by default.

js/viewer-init.mjs.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Audios.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import '@skjnldsv/vue-plyr/dist/vue-plyr.css'
3737
3838
import logger from '../services/logger.js'
3939
import { preloadMedia } from '../services/mediaPreloader'
40-
import { plyrTranslations } from '../utils/plyrTranslations'
40+
import { localizeSpeedLabels, plyrTranslations } from '../utils/plyrTranslations'
4141
4242
const VuePlyr = () => import(/* webpackChunkName: 'plyr' */'@skjnldsv/vue-plyr')
4343
@@ -53,6 +53,7 @@ export default {
5353
data() {
5454
return {
5555
fallback: false,
56+
speedListenerBound: false,
5657
}
5758
},
5859
@@ -111,6 +112,15 @@ export default {
111112
control.addEventListener('mouseenter', this.disableSwipe)
112113
control.addEventListener('mouseleave', this.enableSwipe)
113114
})
115+
116+
// The Plyr menu is only in the DOM once the controls are mounted (see
117+
// above), so wire up the speed-label localization here rather than in
118+
// mounted(). Register the rate-change listener once.
119+
if (!this.speedListenerBound && this.$refs.plyr?.player) {
120+
this.$refs.plyr.player.on('ratechange', this.localizeSpeed)
121+
this.speedListenerBound = true
122+
}
123+
this.localizeSpeed()
114124
},
115125
116126
beforeDestroy() {
@@ -122,6 +132,11 @@ export default {
122132
},
123133
124134
methods: {
135+
localizeSpeed() {
136+
// Defer so we run after Plyr's own label/badge update for this event.
137+
this.$nextTick(() => localizeSpeedLabels(this.$el))
138+
},
139+
125140
donePlaying() {
126141
this.$refs.audio.autoplay = false
127142
this.$refs.audio.load()

src/components/Videos.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import logger from '../services/logger.js'
4848
import { findLivePhotoPeerFromName } from '../utils/livePhotoUtils'
4949
import { getPreviewIfAny } from '../utils/previewUtils'
5050
import { preloadMedia } from '../services/mediaPreloader.js'
51-
import { plyrTranslations } from '../utils/plyrTranslations'
51+
import { localizeSpeedLabels, plyrTranslations } from '../utils/plyrTranslations'
5252
5353
const VuePlyr = () => import(/* webpackChunkName: 'plyr' */'@skjnldsv/vue-plyr')
5454
@@ -66,6 +66,7 @@ export default {
6666
return {
6767
isFullscreenButtonVisible: false,
6868
fallback: false,
69+
speedListenerBound: false,
6970
}
7071
},
7172
@@ -138,6 +139,15 @@ export default {
138139
control.addEventListener('mouseenter', this.disableSwipe)
139140
control.addEventListener('mouseleave', this.enableSwipe)
140141
})
142+
143+
// The Plyr menu is only in the DOM once the controls are mounted (see
144+
// above), so wire up the speed-label localization here rather than in
145+
// mounted(). Register the rate-change listener once.
146+
if (!this.speedListenerBound && this.$refs.plyr?.player) {
147+
this.$refs.plyr.player.on('ratechange', this.localizeSpeed)
148+
this.speedListenerBound = true
149+
}
150+
this.localizeSpeed()
141151
},
142152
143153
beforeDestroy() {
@@ -149,6 +159,11 @@ export default {
149159
},
150160
151161
methods: {
162+
localizeSpeed() {
163+
// Defer so we run after Plyr's own label/badge update for this event.
164+
this.$nextTick(() => localizeSpeedLabels(this.$el))
165+
},
166+
152167
hideHeaderAndFooter() {
153168
// work arround to get the state of the fullscreen button, aria-selected attribute is not reliable
154169
this.isFullscreenButtonVisible = !this.isFullscreenButtonVisible

src/utils/plyrTranslations.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import { translate as t } from '@nextcloud/l10n'
6+
import { getCanonicalLocale, translate as t } from '@nextcloud/l10n'
77

88
/**
99
* Plyr `i18n` option so the player controls and menus
@@ -49,3 +49,48 @@ export const plyrTranslations: Record<string, string> = {
4949
enabled: t('viewer', 'Enabled'),
5050
advertisement: t('viewer', 'Ad'),
5151
}
52+
53+
/**
54+
* Plyr renders speed values via a plain template literal (`` `${speed}×` ``),
55+
* which always uses a `.` decimal separator regardless of locale — so German
56+
* shows `1.5×` instead of `1,5×`. Plyr exposes no hook for this, so we
57+
* re-format the rendered labels using the user's locale.
58+
*
59+
* The speed panel's id always ends in `-speed`. Each entry is a
60+
* `button[role="menuitemradio"]` carrying the numeric speed in its `value`
61+
* attribute (the source of truth). The home-pane `.plyr__menu__value` badge
62+
* shows the current speed as a bare `<number>×` string, which we match by
63+
* pattern so we never touch the quality/captions badges.
64+
*
65+
* @param root the Plyr root element to localize labels within
66+
*/
67+
export function localizeSpeedLabels(root: ParentNode): void {
68+
const formatter = new Intl.NumberFormat(getCanonicalLocale())
69+
const speedLabel = (value: number): string =>
70+
value === 1 ? t('viewer', 'Normal') : `${formatter.format(value)}×`
71+
72+
// Speed submenu radio items, scoped to the speed panel so we don't touch
73+
// quality (e.g. "1080") or captions entries.
74+
const items = root.querySelectorAll<HTMLButtonElement>(
75+
'.plyr__menu__container [id$="-speed"] [role="menuitemradio"]',
76+
)
77+
items.forEach((item) => {
78+
const value = Number.parseFloat(item.value)
79+
if (Number.isNaN(value)) {
80+
return
81+
}
82+
const label = item.querySelector('span')
83+
if (label) {
84+
label.textContent = speedLabel(value)
85+
}
86+
})
87+
88+
// Home-pane badge showing the current speed (e.g. "Speed: 1,5×"). Match the
89+
// bare "<number>×" form so quality/captions badges are left untouched.
90+
root.querySelectorAll<HTMLElement>('.plyr__menu__value').forEach((badge) => {
91+
const match = /^(\d+(?:\.\d+)?)×$/.exec((badge.textContent ?? '').trim())
92+
if (match) {
93+
badge.textContent = `${formatter.format(Number.parseFloat(match[1]))}×`
94+
}
95+
})
96+
}

0 commit comments

Comments
 (0)