Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
0944d2e
Set up internationalization
MattiasBuelens May 6, 2025
c110d1f
Add lang to DefaultUI
MattiasBuelens May 6, 2025
5ee3c0c
Internationalize PlayButton
MattiasBuelens May 6, 2025
4ccf996
Internationalize TimeDisplay
MattiasBuelens Jan 21, 2026
daf0d66
Internationalize TimeRange
MattiasBuelens Jan 21, 2026
60cdde6
Use `Intl.DurationFormat` types from `es2025.intl`
MattiasBuelens May 12, 2026
c0db7d9
Add missing exports
MattiasBuelens May 12, 2026
1bea8fe
Document `Locale` interface
MattiasBuelens May 12, 2026
f12dbed
Restructure
MattiasBuelens May 12, 2026
d27ecc2
Use `willUpdate()` to update ARIA labels
MattiasBuelens May 12, 2026
4889bad
Add `Button.ariaLabel`
MattiasBuelens May 12, 2026
a973851
Internationalize AirPlayButton and ChromecastButton
MattiasBuelens May 12, 2026
758a71e
Fix BadNetworkModeButton
MattiasBuelens May 12, 2026
eefd89a
Export THEOlive components
MattiasBuelens May 12, 2026
13c6d1a
Internationalize menu buttons
MattiasBuelens May 12, 2026
167c606
Internationalize LiveButton
MattiasBuelens May 12, 2026
af09454
Internationalize FullscreenButton
MattiasBuelens May 12, 2026
361fe86
Internationalize MuteButton
MattiasBuelens May 12, 2026
57304e5
Internationalize SeekButton
MattiasBuelens May 12, 2026
bcd2737
Internationalize ad components
MattiasBuelens May 13, 2026
6c14536
Use narrow duration format for ad components
MattiasBuelens May 13, 2026
46e0c20
Extract `toDuration` helper
MattiasBuelens May 13, 2026
029174d
Rework `AdDisplay` state
MattiasBuelens May 13, 2026
82f7f1e
Push language down to all localized components
MattiasBuelens May 13, 2026
e35fc3e
Rework default duration formatters
MattiasBuelens May 13, 2026
ad6dfb3
Internationalize VolumeRange
MattiasBuelens May 13, 2026
0704627
Add TODO
MattiasBuelens May 13, 2026
9d966b4
Add `Locale.formatPercentage`
MattiasBuelens May 13, 2026
761e413
Move stuff around
MattiasBuelens May 13, 2026
c4b0899
Don't export `DurationFormatter`
MattiasBuelens May 13, 2026
e3a5d6c
Document `Duration`
MattiasBuelens May 13, 2026
fb23f27
Internationalize PlaybackRateMenu and PlaybackRateDisplay
MattiasBuelens May 21, 2026
a5197cb
Internationalize LanguageMenu
MattiasBuelens May 21, 2026
7804d17
Internationalize SettingsMenu
MattiasBuelens May 22, 2026
8a56b5d
Internationalize headings in TextTrackStyleMenu
MattiasBuelens May 22, 2026
2e264f7
Tweak Locale types
MattiasBuelens May 22, 2026
d5657ab
Internationalize style values in TextTrackStyleMenu and TextTrackStyl…
MattiasBuelens May 22, 2026
20009ef
Deduplicate style options
MattiasBuelens May 22, 2026
7abbf36
Export types
MattiasBuelens May 22, 2026
db2d226
Internationalize TextTrackOffRadioButton
MattiasBuelens May 22, 2026
1d6f198
Internationalize TextTrackStyleResetButton
MattiasBuelens May 22, 2026
b369dfc
Internationalize QualityRadioButton
MattiasBuelens May 22, 2026
5a6503c
Internationalize AutomaticQualitySelector and BadNetworkModeSelector
MattiasBuelens May 22, 2026
889ae01
Internationalize ActiveQualityDisplay
MattiasBuelens May 22, 2026
4b617a4
Use "Automatic" label only when QualityRadioButton has no associated …
MattiasBuelens May 22, 2026
2bb9b81
Simplify bandwidth formatter
MattiasBuelens May 22, 2026
6d6c2a3
Fix `lang` attribute not pushed through to shadow DOM children
MattiasBuelens May 22, 2026
868935f
Internationalize ErrorDisplay
MattiasBuelens May 22, 2026
524ba3f
Internationalize TimeDisplay and TimeRange
MattiasBuelens May 22, 2026
53860ec
Fix spelling
MattiasBuelens May 22, 2026
0037c54
Re-export internationalization APIs in React UI
MattiasBuelens May 22, 2026
e372dd3
Fix TypeDoc
MattiasBuelens May 22, 2026
bf19b69
Add `lang` to props in React UI
MattiasBuelens May 22, 2026
cbceeff
Update changelog
MattiasBuelens May 22, 2026
7b82a29
Localize language names in track radio buttons
MattiasBuelens May 28, 2026
0afbd00
Compute track label during render
MattiasBuelens May 28, 2026
76128b8
Remove unused `lazy` helper
MattiasBuelens May 28, 2026
e472846
Ignore default track label if it exactly matches the non-localized la…
MattiasBuelens May 28, 2026
45bab7c
Prefer a non-localized track label over a raw language code
MattiasBuelens May 28, 2026
dcb50bf
Small fix
MattiasBuelens May 28, 2026
b6ec61a
Add Dutch locale to examples
MattiasBuelens May 28, 2026
641420d
Add source select to React example
MattiasBuelens May 28, 2026
f0d027e
Add language select to examples
MattiasBuelens May 28, 2026
315f1b0
Internationalize THEOliveDefaultUI
MattiasBuelens May 28, 2026
d11d17a
Internationalize ChromecastDisplay
MattiasBuelens May 28, 2026
834256d
Add guide for localization
MattiasBuelens May 28, 2026
db622f1
Add icons to docs
MattiasBuelens May 28, 2026
73f6793
Run Prettier
MattiasBuelens May 28, 2026
80e5788
Add a doctype
MattiasBuelens May 28, 2026
fe00caf
Highlight `<html>` in example code
MattiasBuelens May 28, 2026
6cc70fb
Add remark around updating translations
MattiasBuelens May 28, 2026
e3eca7b
Copy localization guide to React docs
MattiasBuelens May 28, 2026
97c0fd5
Update guide for React
MattiasBuelens May 28, 2026
c63f2ad
Add icon to migration guide
MattiasBuelens May 28, 2026
5fc39f3
Remove a space
MattiasBuelens May 28, 2026
1adb04f
Use "prop" for React
MattiasBuelens May 28, 2026
2168433
Fix typo
MattiasBuelens May 29, 2026
79d974a
Internationalize `TimeDisplay.ariaLabel`
MattiasBuelens May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/gold-pants-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@theoplayer/react-ui': minor
'@theoplayer/web-ui': minor
---

Added localization support. Use `addLocale()` to register a locale, and set the `lang` attribute on the UI to apply it.
1 change: 1 addition & 0 deletions docs/guides/custom-component.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
description: Build custom components that integrate with the player, and which you can use in your own custom UI.
sidebar_position: 2
sidebar_custom_props: { 'icon': '🧩' }
---

# Making a custom component
Expand Down
1 change: 1 addition & 0 deletions docs/guides/custom-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ title: Making a custom UI
permalink: /guides/custom-ui
description: Build a custom player from scratch, starting from a basic player and gradually adding more features.
sidebar_position: 1
sidebar_custom_props: { 'icon': '🎨' }
---

Although the default UI was designed to support a variety of usage scenarios, you may still run into a case that it doesn't handle very well. Perhaps you want to move some buttons around, or add like and dislike buttons to the control bar, or perhaps integrate a text chat component inside your player. In these situations, you may want to build a custom player UI to create a truly unique experience for your viewers.
Expand Down
96 changes: 96 additions & 0 deletions docs/guides/localization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
---
layout: page
title: Localization
permalink: /guides/localization
description: Localize your UI to support different languages.
sidebar_position: 3
sidebar_custom_props: { 'icon': '🌍' }
---

The Open Video UI for Web can be localized to different languages,
enabling you to reach audiences from different regions of the world.

Localization works by [registering one or more locales](#register-a-locale)
and then [selecting one of the registered locales using the `lang` attribute](#select-a-language).

## Register a locale

A locale is a JavaScript object mapping translation IDs to translated messages.
You can register a locale with the `addLocale` function:

```javascript title="French locale"
import { addLocale } from '@theoplayer/web-ui';

addLocale('fr', {
playAria: 'lire',
pauseAria: 'pauser',
replayAria: 'revoir'
// ...
});
```

Some messages may need to be formatted with one or more values, based on the configuration or active state of the
player. For those messages, the translation ID maps to a JavaScript function that takes those values as arguments
and returns the formatted translation.

```javascript title="French locale (continued)"
addLocale('fr', {
// ...
seekForwardAria: (offset) => `avancer de ${offset}`,
seekBackwardAria: (offset) => `reculer de ${offset}`
// ...
});
```

Refer to the [`Locale` interface definition](https://theoplayer.github.io/web-ui/api/interfaces/Locale.html)
in the API references for the complete list of translatable messages.

## Select a language

The UI automatically selects the locale based on the `lang` attribute of the `<theoplayer-ui>`
(or `<theoplayer-default-ui>`) element, or from the closest parent element with such an attribute.

The value of the `lang` attribute must exactly match the locale name as it was passed to `addLocale`.

```html title="Setting the language on the UI"
<theoplayer-default-ui lang="fr" source='{"sources":{"src":"https://example.com/stream.m3u8"}}'></theoplayer-default-ui>
```

You can also put the `lang` attribute on any parent element. For example, if the entire page is in French, you could put
the attribute on the `<html>` element:

```html title="Setting the language on the HTML document"
<!doctype html>
<!-- highlight-next-line -->
<html lang="fr">
<head>
<title>Ma page</title>
</head>
<body>
<theoplayer-default-ui source='{"sources":{"src":"https://example.com/stream.m3u8"}}'></theoplayer-default-ui>
</body>
</html>
```

## Remarks

### Update translations when upgrading Open Video UI

Newer versions of the Open Video UI for Web may add new messages that need to be translated.
We follow [semantic versioning](https://semver.org/), so new messages can only be added in _major_ or _minor_ versions.

When using custom translations in your app, we recommend pinning the `@theoplayer/web-ui` dependency
in your app's `package.json` to a specific minor version using a tilde constraint (`~`).
Avoid using a caret constraint (`^`), since this may cause upgrading past your currently selected minor version.

```json title="package.json"
{
"dependencies": {
"@theoplayer/web-ui": "~2.2.0"
}
}
```

When you decide to upgrade Open Video UI to the latest version, make sure to also update your translations.
Check the history for [`i18n/Locale.ts`](https://github.com/THEOplayer/web-ui/commits/main/src/i18n/Locale.ts)
to see whether any messages were added or changed since the previous version.
1 change: 1 addition & 0 deletions docs/guides/migrating-to-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ layout: page
title: Migrating to Open Video UI for Web 2.x
permalink: /guides/migrating-to-v2
sidebar_position: 10
sidebar_custom_props: { 'icon': '⬆️' }
---

This article will guide you through updating to Open Video UI for Web version 2 (from version 1),
Expand Down
79 changes: 79 additions & 0 deletions docs/react/guides/localization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
layout: page
title: Localization
slug: /react/guides/localization
description: Localize your UI to support different languages.
sidebar_position: 1
sidebar_custom_props: { 'icon': '🌍' }
---

The Open Video UI for React can be localized to different languages,
enabling you to reach audiences from different regions of the world.

Localization works by [registering one or more locales](#register-a-locale)
and then [selecting one of the registered locales using the `lang` prop](#select-a-language).

## Register a locale

A locale is a JavaScript object mapping translation IDs to translated messages.
You can register a locale with the `addLocale` function:

```javascript title="French locale"
import { addLocale } from '@theoplayer/react-ui';

addLocale('fr', {
playAria: 'lire',
pauseAria: 'pauser',
replayAria: 'revoir'
// ...
});
```

Some messages may need to be formatted with one or more values, based on the configuration or active state of the
player. For those messages, the translation ID maps to a JavaScript function that takes those values as arguments
and returns the formatted translation.

```javascript title="French locale (continued)"
addLocale('fr', {
// ...
seekForwardAria: (offset) => `avancer de ${offset}`,
seekBackwardAria: (offset) => `reculer de ${offset}`
// ...
});
```

Refer to the [`Locale` interface definition](https://theoplayer.github.io/web-ui/react-api/interfaces/Locale.html)
in the API references for the complete list of translatable messages.

## Select a language

The UI automatically selects the locale based on the `lang` prop of the `<UIContainer>` (or `<DefaultUI>`) component.

The value of the `lang` prop must exactly match the locale name as it was passed to `addLocale`.

```jsx title="Setting the language on the UI"
<DefaultUI lang="fr" source={{ sources: { src: 'https://example.com/stream.m3u8' } }} />
```

## Remarks

### Update translations when upgrading Open Video UI

Newer versions of the Open Video UI for React may add new messages that need to be translated.
We follow [semantic versioning](https://semver.org/), so new messages can only be added in _major_ or _minor_ versions.

When using custom translations in your app, we recommend pinning the `@theoplayer/react-ui` dependency
in your app's `package.json` to a specific minor version using a tilde constraint (`~`).
Avoid using a caret constraint (`^`), since this may cause upgrading past your currently selected minor version.

```json title="package.json"
{
"dependencies": {
"@theoplayer/react-ui": "~2.2.0"
}
}
```

When you decide to upgrade Open Video UI to the latest version, make sure to also update your translations.
Check the history for [`i18n/Locale.ts`](https://github.com/THEOplayer/web-ui/commits/main/src/i18n/Locale.ts)
to see whether any messages were added or changed since the previous version.
2 changes: 2 additions & 0 deletions docs/react/guides/migrating-to-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
layout: page
title: Migrating to Open Video UI for React 2.x
slug: /react/guides/migrating-to-v2
sidebar_position: 10
sidebar_custom_props: { 'icon': '⬆️' }
---

This article will guide you through updating to Open Video UI for React version 2 (from version 1),
Expand Down
14 changes: 14 additions & 0 deletions examples/default-ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
<script async src="https://ga.jspm.io/npm:es-module-shims@2.6.2/dist/es-module-shims.js" crossorigin="anonymous"></script>
<script type="module">
import * as THEOplayerUI from '@theoplayer/web-ui';
import './locale/nl.js';

self.THEOplayerUI = THEOplayerUI;
</script>
<!-- Legacy browsers -->
Expand All @@ -70,6 +72,15 @@ <h1>Default UI</h1>
</select>
</label>
</div>
<div>
<label style="user-select: none">
Language:
<select id="language-select">
<option value="en" selected>English</option>
<option value="nl">Nederlands (Dutch)</option>
</select>
</label>
</div>
<script>
const ui = document.querySelector('theoplayer-default-ui');
const sources = {
Expand Down Expand Up @@ -112,6 +123,9 @@ <h1>Default UI</h1>
document.querySelector('#source-select').addEventListener('change', (e) => {
ui.source = sources[e.target.value];
});
document.querySelector('#language-select').addEventListener('change', (e) => {
ui.lang = e.target.value;
});
</script>
</body>
</html>
92 changes: 92 additions & 0 deletions examples/locale/nl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { addLocale } from '@theoplayer/web-ui';

/**
* Dutch (Nederlands)
*/
addLocale('nl', {
playAria: 'afspelen',
pauseAria: 'pauzeren',
replayAria: 'opnieuw afspelen',
muteAria: 'dempen',
unmuteAria: 'dempen opheffen',
volumeAria: 'volume',
seekAria: 'zoeken',
seekForwardAria: (offset) => `spring voorwaarts met ${offset}`,
seekBackwardAria: (offset) => `spring achterwaarts met ${offset}`,
live: 'LIVE',
seekToLiveAria: 'spring naar live',
fullscreenAria: 'volledig scherm',
fullscreenExitAria: 'volledig scherm sluiten',
airplayAria: 'start met afspelen op AirPlay',
airplayConnectedAria: 'stop met afspelen op AirPlay',
chromecastAria: 'start met afspelen op Chromecast',
chromecastConnectedAria: 'stop met afspelen op Chromecast',
chromecastHeading: 'Aan het afspelen op',
chromecastDefaultReceiverName: 'Chromecast',
timeOfTotalAria: (currentTime, totalDuration) => `${currentTime} van ${totalDuration}`,
playbackTimeAria: 'afspeeltijd',
unknownTimeAria: 'video niet ingeladen, onbekende tijd',
adText: 'Advertentie',
adBreakText: (currentAd, totalAds) => `Advertentie ${currentAd} van ${totalAds}`,
adClickThroughText: 'Adverteerder bezoeken',
adCountdownText: (remainingDuration) => `Video wordt hervat in ${remainingDuration}`,
adSkipButtonText: 'Overslaan',
adSkipCountdownText: (remainingDuration) => `Overslaan in ${remainingDuration}`,
liveStreamLoading: 'Laden...',
liveStreamOffline: `De livestream is nog niet gestart`,
closeMenuAria: 'menu sluiten',
openLanguageMenuAria: 'taalmenu openen',
languageMenuHeading: 'Taal',
audioMenuHeading: 'Audio',
subtitleMenuHeading: 'Ondertitels',
subtitleOff: 'Uit',
openPlaybackRateMenuAria: 'afspeelsnelheidmenu openen',
playbackRateMenuHeading: 'Afspeelsnelheid',
formatPlaybackRate: (rate) => (rate === 1 ? 'Normaal' : `${rate}x`),
openSettingsMenuAria: 'instellingenmenu openen',
settingsMenuHeading: 'Instellingen',
qualityMenuHeading: 'Kwaliteit',
textTrackStyleMenuHeading: 'Opties voor ondertitels',
textTrackStyleFontFamily: 'Lettertype',
textTrackStyleFontColor: 'Tekstkleur',
textTrackStyleFontOpacity: 'Ondoorzichtigheid van tekst',
textTrackStyleFontSize: 'Lettergrootte',
textTrackStyleBackgroundColor: 'Achtergrondkleur',
textTrackStyleBackgroundOpacity: 'Ondoorzichtigheid van achtergrond',
textTrackStyleWindowColor: 'Vensterkleur',
textTrackStyleWindowOpacity: 'Ondoorzichtigheid van venster',
textTrackStyleEdgeStyle: 'Randstijl van tekens',
textTrackStyleDefaultLabel: 'Standaard',
textTrackStyleCustomLabel: 'Aangepast',
textTrackStyleResetLabel: 'Opnieuw instellen',
fontFamilyLabels: {
'Monospace Serif': 'Serif met gelijke tekenbreedte',
'Proportional Serif': 'Proportionele serif',
'Monospace Sans': 'Sans-serif met gelijke tekenbreedte',
'Proportional Sans': 'Proportionele sans-serif'
},
colorLabels: {
White: 'Wit',
Yellow: 'Geel',
Green: 'Groen',
Cyan: 'Cyaan',
Blue: 'Blauw',
Magenta: 'Magenta',
Red: 'Rood',
Black: 'Zwart'
},
edgeStyleLabels: {
none: 'Geen',
dropshadow: 'Slagschaduw',
raised: 'Verhoogd',
depressed: 'Verlaagd',
uniform: 'Uniform'
},
automaticQualityLabel: 'Automatisch',
unknownQualityLabel: 'Onbekend',
highQualityLabel: 'Hoge kwaliteit',
lowQualityLabel: 'Lage kwaliteit',
errorHeading: 'Er is een fout opgetreden',
openBadNetworkModeMenuAria: 'menu voor slecht netwerk openen',
formatRemainingDuration: (duration) => `${duration} resterend`
});
Loading