Skip to content

Commit 09ce6b4

Browse files
Add localization support (#54)
1 parent ceadcf2 commit 09ce6b4

24 files changed

Lines changed: 456 additions & 73 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
> - 🏠 Internal
1010
> - 💅 Polish
1111
12+
## Unreleased
13+
14+
* 🚀 Added localization support.
15+
* See `res/values/strings.xml` for the full list of translatable strings, which you can override in your app's `strings.xml`.
16+
* For more information, see [Localize your app on Android Developers](https://developer.android.com/guide/topics/resources/localization).
17+
1218
## v1.10.0 (2025-04-02)
1319

1420
* 🚀 Added support for THEOplayer 9.0. ([#61](https://github.com/THEOplayer/android-ui/pulls/61))
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources xmlns:tools="http://schemas.android.com/tools" tools:locale="nl">
3+
<string name="app_name">THEOplayer Open Video UI voor Android Demo</string>
4+
<string name="theoplayer_ui_btn_play">Afspelen</string>
5+
<string name="theoplayer_ui_btn_pause">Pauzeren</string>
6+
<string name="theoplayer_ui_btn_replay">Opnieuw spelen</string>
7+
<string name="theoplayer_ui_btn_chromecast_start">Starten met casten</string>
8+
<string name="theoplayer_ui_btn_chromecast_stop">Stop casting</string>
9+
<string name="theoplayer_ui_btn_fullscreen_enter">Volledig scherm</string>
10+
<string name="theoplayer_ui_btn_fullscreen_exit">Volledig scherm sluiten</string>
11+
<string name="theoplayer_ui_btn_menu_close">Sluiten</string>
12+
<string name="theoplayer_ui_btn_back">Terug</string>
13+
<string name="theoplayer_ui_btn_mute">Dempen</string>
14+
<string name="theoplayer_ui_btn_unmute">Dempen opheffen</string>
15+
<plurals name="theoplayer_ui_btn_seek_forward">
16+
<item quantity="one">Spring %1$d seconde vooruit</item>
17+
<item quantity="other">Spring %1$d seconden vooruit</item>
18+
</plurals>
19+
<plurals name="theoplayer_ui_btn_seek_backward">
20+
<item quantity="one">Spring %1$d seconde achteruit</item>
21+
<item quantity="other">Spring %1$d seconden achteruit</item>
22+
</plurals>
23+
<string name="theoplayer_ui_btn_live">LIVE</string>
24+
<string name="theoplayer_ui_chromecast_playing_on_receiver">Speelt op %1$s</string>
25+
<string name="theoplayer_ui_chromecast_playing_on_receiver_expanded_first_line">Speelt op</string>
26+
<string name="theoplayer_ui_chromecast_playing_on_unknown_receiver">Speelt op Chromecast</string>
27+
<string name="theoplayer_ui_chromecast_playing_on_unknown_receiver_expanded_first_line">Speelt op</string>
28+
<string name="theoplayer_ui_chromecast_playing_on_unknown_receiver_expanded_second_line">Chromecast</string>
29+
<string name="theoplayer_ui_menu_language">Taal</string>
30+
<string name="theoplayer_ui_menu_settings">Instellingen</string>
31+
<string name="theoplayer_ui_menu_audio">Audio</string>
32+
<string name="theoplayer_ui_menu_subtitles">Ondertitels</string>
33+
<string name="theoplayer_ui_menu_playback_rate">Afspeelsnelheid</string>
34+
<string name="theoplayer_ui_menu_quality">Kwaliteit</string>
35+
<string name="theoplayer_ui_audio_none">Geen</string>
36+
<string name="theoplayer_ui_subtitles_off">Uit</string>
37+
<string name="theoplayer_ui_playback_rate_normal">Normaal</string>
38+
<string name="theoplayer_ui_quality_automatic">Automatisch</string>
39+
<string name="theoplayer_ui_quality_automatic_with_height">Automatisch (%1$dp)</string>
40+
<string name="theoplayer_ui_track_unknown">Onbekend</string>
41+
<string name="theoplayer_ui_error_title">Fout</string>
42+
</resources>
Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,53 @@
1-
<resources>
1+
<resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:locale="en">
22
<string name="app_name">THEOplayer Open Video UI for Android Demo</string>
3+
4+
<!-- Copied from Open Video UI for Android -->
5+
<string name="theoplayer_ui_btn_play">Play</string>
6+
<string name="theoplayer_ui_btn_pause">Pause</string>
7+
<string name="theoplayer_ui_btn_replay">Replay</string>
8+
<string name="theoplayer_ui_btn_chromecast_start">Start casting</string>
9+
<string name="theoplayer_ui_btn_chromecast_stop">Stop casting</string>
10+
<string name="theoplayer_ui_btn_fullscreen_enter">Enter fullscreen</string>
11+
<string name="theoplayer_ui_btn_fullscreen_exit">Exit fullscreen</string>
12+
<string name="theoplayer_ui_btn_menu_close">Close</string>
13+
<string name="theoplayer_ui_btn_back">Back</string>
14+
<string name="theoplayer_ui_btn_mute">Mute</string>
15+
<string name="theoplayer_ui_btn_unmute">Unmute</string>
16+
<plurals name="theoplayer_ui_btn_seek_forward">
17+
<item quantity="one">Seek forward by <xliff:g example="5" id="seek_offset">%1$d</xliff:g> second</item>
18+
<item quantity="other">Seek forward by <xliff:g example="5" id="seek_offset">%1$d</xliff:g> seconds</item>
19+
</plurals>
20+
<plurals name="theoplayer_ui_btn_seek_backward">
21+
<item quantity="one">Seek backward by <xliff:g example="5" id="seek_offset">%1$d</xliff:g> second</item>
22+
<item quantity="other">Seek backward by <xliff:g example="5" id="seek_offset">%1$d</xliff:g> seconds</item>
23+
</plurals>
24+
<string name="theoplayer_ui_btn_live">LIVE</string>
25+
<string name="theoplayer_ui_current_time" translatable="false"><xliff:g example="01:23" id="current_time">%1$s</xliff:g></string>
26+
<string name="theoplayer_ui_current_time_remaining" translatable="false"><xliff:g example="-01:23" id="remaining_time">%1$s</xliff:g></string>
27+
<string name="theoplayer_ui_current_time_with_duration" translatable="false"><xliff:g example="01:23" id="current_time">%1$s</xliff:g> / <xliff:g example="60:00" id="duration">%2$s</xliff:g></string>
28+
<string name="theoplayer_ui_current_time_remaining_with_duration" translatable="false"><xliff:g example="-01:23" id="remaining_time">%1$s</xliff:g> / <xliff:g example="60:00" id="duration">%2$s</xliff:g></string>
29+
<string name="theoplayer_ui_chromecast_playing_on_receiver">Playing on <xliff:g example="Living Room" id="receiver_name">%1$s</xliff:g></string>
30+
<string name="theoplayer_ui_chromecast_playing_on_receiver_expanded_first_line">Playing on</string>
31+
<string name="theoplayer_ui_chromecast_playing_on_receiver_expanded_second_line" translatable="false"><xliff:g example="Living Room" id="receiver_name">%1$s</xliff:g></string>
32+
<string name="theoplayer_ui_chromecast_playing_on_unknown_receiver">Playing on Chromecast</string>
33+
<string name="theoplayer_ui_chromecast_playing_on_unknown_receiver_expanded_first_line">Playing on</string>
34+
<string name="theoplayer_ui_chromecast_playing_on_unknown_receiver_expanded_second_line">Chromecast</string>
35+
<string name="theoplayer_ui_menu_language">Language</string>
36+
<string name="theoplayer_ui_menu_settings">Settings</string>
37+
<string name="theoplayer_ui_menu_audio">Audio</string>
38+
<string name="theoplayer_ui_menu_subtitles">Subtitles</string>
39+
<string name="theoplayer_ui_menu_playback_rate">Playback speed</string>
40+
<string name="theoplayer_ui_menu_quality">Quality</string>
41+
<string name="theoplayer_ui_audio_none">None</string>
42+
<string name="theoplayer_ui_subtitles_off">Off</string>
43+
<string name="theoplayer_ui_playback_rate_normal">Normal</string>
44+
<string name="theoplayer_ui_playback_rate_format" translatable="false">#.##x</string>
45+
<string name="theoplayer_ui_quality_automatic">Automatic</string>
46+
<string name="theoplayer_ui_quality_with_height" translatable="false"><xliff:g example="720" id="quality_height">%1$d</xliff:g>p</string>
47+
<string name="theoplayer_ui_quality_automatic_with_height">Automatic (<xliff:g example="720" id="quality_height">%1$d</xliff:g>p)</string>
48+
<string name="theoplayer_ui_bandwidth_format_10mbps" translatable="false">#Mbps</string>
49+
<string name="theoplayer_ui_bandwidth_format_1mbps" translatable="false">#.#Mbps</string>
50+
<string name="theoplayer_ui_bandwidth_format_kbps" translatable="false">#kbps</string>
51+
<string name="theoplayer_ui_track_unknown">Unknown</string>
52+
<string name="theoplayer_ui_error_title">An error occurred</string>
353
</resources>

docs/guides/localization.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
sidebar_position: 3
3+
---
4+
5+
# Localization
6+
7+
The Open Video UI for Android can be localized to different languages,
8+
enabling you to reach audiences from different regions of the world.
9+
10+
Localization works
11+
by [providing alternative resources](https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources)
12+
in one or more languages. You can choose to [change the text of the default language](#change-default-language)
13+
to target a single audience, or [add alternative languages](#add-alternative-languages) to target many audiences.
14+
15+
## Changing the default language {#change-default-language}
16+
17+
By default, the Open Video UI ships with English texts only. If your app targets a non-English speaking audience,
18+
you can override these texts with translations for another language.
19+
20+
1. Copy the English string resources
21+
from [`res/values/strings.xml`](https://github.com/THEOplayer/android-ui/blob/main/ui/src/main/res/values/strings.xml)
22+
to your app's resources for the default locale (i.e. `res/values/strings.xml`).
23+
2. Change the resource values to the translated texts.
24+
For example, to change the title of the "Language" menu, you would change the contents
25+
of the `theoplayer_ui_menu_language` resource:
26+
27+
```xml title="res/values/strings.xml"
28+
<resources>
29+
<string name="theoplayer_ui_menu_language">Langue</string> <!-- translated to French -->
30+
</resources>
31+
```
32+
33+
:::tip
34+
You can also use
35+
the [Translations Editor in Android Studio](https://developer.android.com/studio/write/translations-editor)
36+
to edit these values.
37+
:::
38+
39+
3. Build and run your app. The translated texts should now appear in your player UI.
40+
41+
## Add alternative languages {#add-alternative-languages}
42+
43+
If your app targets many audiences speaking different languages, you can add multiple translations using
44+
locale-specific resources.
45+
46+
1. Copy the English string resources
47+
from [`res/values/strings.xml`](https://github.com/THEOplayer/android-ui/blob/main/ui/src/main/res/values/strings.xml)
48+
to your app's resources for the default locale (i.e. `res/values/strings.xml`).
49+
2. Add a new string resources file for a new locale.
50+
For example, for French, create the file: `res/values-fr/strings.xml`.
51+
3. Add translated versions for each English string resource to the new resources file.
52+
For example, to translate the title of the "Language" menu, you would add an entry for `theoplayer_ui_menu_language`:
53+
```xml title="res/values-fr/strings.xml"
54+
<resources>
55+
<string name="theoplayer_ui_menu_language">Langue</string>
56+
</resources>
57+
```
58+
59+
## Remarks
60+
61+
### Update translations when upgrading Open Video UI
62+
63+
Newer versions of the Open Video UI for Android may add new string resources that need to be translated.
64+
65+
When using custom translations in your app, we recommend pinning the `com.theoplayer.android-ui:android-ui` dependency
66+
in your app's `build.gradle` to a specific version. Avoid using `+` in the dependency's version range.
67+
68+
```groovy title="build.gradle"
69+
dependencies {
70+
implementation "com.theoplayer.android-ui:android-ui:1.9.0"
71+
}
72+
```
73+
74+
When you decide to upgrade Open Video UI to the latest version, make sure to also update your translations.
75+
Check the history for [`res/values/strings.xml`](https://github.com/THEOplayer/android-ui/commits/main/ui/src/main/res/values/strings.xml)
76+
to see whether any string resources were added or changed since the previous version.
77+
78+
## More information
79+
80+
- [Localize your app on Android Developers](https://developer.android.com/guide/topics/resources/localization)
81+
- [Support different languages and cultures on Android Developers](https://developer.android.com/training/basics/supporting-devices/languages)

ui/src/main/java/com/theoplayer/android/ui/ChromecastButton.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import androidx.compose.material.icons.rounded.CastConnected
77
import androidx.compose.material3.Icon
88
import androidx.compose.runtime.Composable
99
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.res.stringResource
1011
import androidx.compose.ui.unit.dp
1112
import com.theoplayer.android.api.cast.chromecast.PlayerCastState
1213

@@ -31,19 +32,19 @@ fun ChromecastButton(
3132
availableIcon: @Composable () -> Unit = {
3233
Icon(
3334
Icons.Rounded.Cast,
34-
contentDescription = "Start casting"
35+
contentDescription = stringResource(R.string.theoplayer_ui_btn_chromecast_start)
3536
)
3637
},
3738
connectingIcon: @Composable () -> Unit = {
3839
Icon(
3940
Icons.Rounded.Cast,
40-
contentDescription = "Stop casting"
41+
contentDescription = stringResource(R.string.theoplayer_ui_btn_chromecast_stop)
4142
)
4243
},
4344
connectedIcon: @Composable () -> Unit = {
4445
Icon(
4546
Icons.Rounded.CastConnected,
46-
contentDescription = "Stop casting"
47+
contentDescription = stringResource(R.string.theoplayer_ui_btn_chromecast_stop)
4748
)
4849
}
4950
) {

ui/src/main/java/com/theoplayer/android/ui/ChromecastDisplay.kt

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import androidx.compose.material3.Text
1515
import androidx.compose.runtime.Composable
1616
import androidx.compose.ui.Alignment
1717
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.res.stringResource
1819
import androidx.compose.ui.unit.dp
1920
import com.theoplayer.android.api.cast.chromecast.PlayerCastState
2021

@@ -68,7 +69,13 @@ fun ChromecastDisplayCompact(
6869
)
6970
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
7071
Text(
71-
text = "Playing on ${player.castReceiverName ?: "Chromecast"}"
72+
text = player.castReceiverName?.let {
73+
stringResource(
74+
R.string.theoplayer_ui_chromecast_playing_on_receiver,
75+
it
76+
)
77+
}
78+
?: stringResource(R.string.theoplayer_ui_chromecast_playing_on_unknown_receiver)
7279
)
7380
}
7481
}
@@ -101,11 +108,23 @@ fun ChromecastDisplayExpanded(
101108
}
102109
Column {
103110
Text(
104-
text = "Playing on",
111+
text = player.castReceiverName?.let {
112+
stringResource(
113+
R.string.theoplayer_ui_chromecast_playing_on_receiver_expanded_first_line,
114+
it
115+
)
116+
}
117+
?: stringResource(R.string.theoplayer_ui_chromecast_playing_on_unknown_receiver_expanded_first_line),
105118
style = MaterialTheme.typography.bodyLarge
106119
)
107120
Text(
108-
text = player.castReceiverName ?: "Chromecast",
121+
text = player.castReceiverName?.let {
122+
stringResource(
123+
R.string.theoplayer_ui_chromecast_playing_on_receiver_expanded_second_line,
124+
it
125+
)
126+
}
127+
?: stringResource(R.string.theoplayer_ui_chromecast_playing_on_unknown_receiver_expanded_second_line),
109128
style = MaterialTheme.typography.headlineSmall
110129
)
111130
}

ui/src/main/java/com/theoplayer/android/ui/CurrentTimeDisplay.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.theoplayer.android.ui
33
import androidx.compose.material3.Text
44
import androidx.compose.runtime.Composable
55
import androidx.compose.ui.Modifier
6+
import androidx.compose.ui.res.stringResource
67

78
/**
89
* A text display that shows the player's current time.
@@ -29,12 +30,26 @@ fun CurrentTimeDisplay(
2930
currentTime
3031
}
3132

32-
val text = StringBuilder()
33-
text.append(formatTime(time, duration))
34-
if (showDuration) {
35-
text.append(" / ")
36-
text.append(formatTime(duration))
33+
val text = if (showDuration) {
34+
stringResource(
35+
if (showRemaining) {
36+
R.string.theoplayer_ui_current_time_remaining_with_duration
37+
} else {
38+
R.string.theoplayer_ui_current_time_with_duration
39+
},
40+
formatTime(time, duration, preferNegative = showRemaining),
41+
formatTime(duration)
42+
)
43+
} else {
44+
stringResource(
45+
if (showRemaining) {
46+
R.string.theoplayer_ui_current_time_remaining
47+
} else {
48+
R.string.theoplayer_ui_current_time
49+
},
50+
formatTime(time, duration, preferNegative = showRemaining),
51+
)
3752
}
3853

39-
Text(modifier = modifier, text = text.toString())
54+
Text(modifier = modifier, text = text)
4055
}

ui/src/main/java/com/theoplayer/android/ui/DefaultUI.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ fun DefaultUI(
9090
}
9191
Spacer(modifier = Modifier.weight(1f))
9292
LanguageMenuButton()
93+
SettingsMenuButton()
9394
ChromecastButton()
9495
}
9596
}

ui/src/main/java/com/theoplayer/android/ui/ErrorDisplay.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import androidx.compose.material3.Text
1414
import androidx.compose.runtime.Composable
1515
import androidx.compose.ui.Alignment
1616
import androidx.compose.ui.Modifier
17+
import androidx.compose.ui.res.stringResource
1718
import androidx.compose.ui.unit.dp
1819

1920
/**
@@ -43,7 +44,7 @@ fun ErrorDisplay(
4344
contentAlignment = Alignment.CenterStart
4445
) {
4546
Text(
46-
text = "An error occurred",
47+
text = stringResource(R.string.theoplayer_ui_error_title),
4748
style = MaterialTheme.typography.headlineMedium
4849
)
4950
}

ui/src/main/java/com/theoplayer/android/ui/FullscreenButton.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import androidx.compose.material.icons.rounded.FullscreenExit
77
import androidx.compose.material3.Icon
88
import androidx.compose.runtime.Composable
99
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.res.stringResource
1011
import androidx.compose.ui.unit.dp
1112

1213
/**
@@ -25,13 +26,13 @@ fun FullscreenButton(
2526
enter: @Composable () -> Unit = {
2627
Icon(
2728
Icons.Rounded.Fullscreen,
28-
contentDescription = "Enter fullscreen"
29+
contentDescription = stringResource(R.string.theoplayer_ui_btn_fullscreen_enter)
2930
)
3031
},
3132
exit: @Composable () -> Unit = {
3233
Icon(
3334
Icons.Rounded.FullscreenExit,
34-
contentDescription = "Exit fullscreen"
35+
contentDescription = stringResource(R.string.theoplayer_ui_btn_fullscreen_exit)
3536
)
3637
}
3738
) {

0 commit comments

Comments
 (0)