feat: implement interface language

This commit is contained in:
Tim 2022-12-14 21:27:38 +01:00
parent 0771cd7fd7
commit fc0dff4ec2
8 changed files with 355 additions and 30 deletions

136
package-lock.json generated
View file

@ -22,6 +22,7 @@
"eventemitter3": "4.0.7",
"filter-invalid-dom-props": "2.1.0",
"hat": "0.0.3",
"i18next": "^22.4.3",
"langs": "^2.0.0",
"lodash.debounce": "4.0.8",
"lodash.intersection": "4.4.0",
@ -32,8 +33,10 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-focus-lock": "2.9.1",
"react-i18next": "^12.1.1",
"react-is": "18.2.0",
"spatial-navigation-polyfill": "git+https://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"stremio-translations": "git+https://git@github.com/Stremio/stremio-translations.git#e47383bbbb3da8708bb727acd73a093604cdf5c8",
"url": "0.11.0"
},
"devDependencies": {
@ -7009,6 +7012,14 @@
"node": ">= 12"
}
},
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/html-webpack-plugin": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
@ -7153,6 +7164,39 @@
"node": ">=10.17.0"
}
},
"node_modules/i18next": {
"version": "22.4.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.3.tgz",
"integrity": "sha512-rnAabD3+i/rMzdg85Eq4VkZjy0Uxe33J1069IQ4R6+cpcM+wL4lWMRClfSweINA0QEfqzSdsfsyLO7SnGAF4fg==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"dependencies": {
"@babel/runtime": "^7.20.6"
}
},
"node_modules/i18next/node_modules/@babel/runtime": {
"version": "7.20.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
"integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
"dependencies": {
"regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -11920,6 +11964,27 @@
}
}
},
"node_modules/react-i18next": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.1.tgz",
"integrity": "sha512-mFdieOI0LDy84q3JuZU6Aou1DoWW2fhapcTGeBS8+vWSJuViuoCLQAMYSb0QoHhXS8B0WKUOPpx4cffAP7r/aA==",
"dependencies": {
"@babel/runtime": "^7.14.5",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 19.0.0",
"react": ">= 16.8.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@ -11982,9 +12047,9 @@
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/regenerator-transform": {
"version": "0.14.5",
@ -12781,6 +12846,12 @@
"node": ">= 0.6"
}
},
"node_modules/stremio-translations": {
"version": "1.43.15",
"resolved": "git+https://git@github.com/Stremio/stremio-translations.git#e47383bbbb3da8708bb727acd73a093604cdf5c8",
"integrity": "sha512-33qlTmLytSgTAf0fFO242pq2Ir52xREhMs0AGCBmaft4maceuM1cRf/lRJswwu/yMkEBH92C2hGkLGpd9qtw2A==",
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@ -13702,6 +13773,14 @@
"resolved": "https://registry.npmjs.org/video-name-parser/-/video-name-parser-1.4.6.tgz",
"integrity": "sha512-ZdeYjh8X4ms1EzjY/UoiTZ6JWbi8SYyOPGY0jESSLq2BAmdc5sZHi+F8J19Qz0y7H1WSpaltojsCkO1p2dH4YA=="
},
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vtt.js": {
"version": "0.13.0",
"resolved": "git+ssh://git@github.com/jaruba/vtt.js.git#e4f5f5603730866bacb174a93f51b734c9f29e6a",
@ -20111,6 +20190,14 @@
}
}
},
"html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"requires": {
"void-elements": "3.1.0"
}
},
"html-webpack-plugin": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz",
@ -20212,6 +20299,24 @@
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true
},
"i18next": {
"version": "22.4.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.3.tgz",
"integrity": "sha512-rnAabD3+i/rMzdg85Eq4VkZjy0Uxe33J1069IQ4R6+cpcM+wL4lWMRClfSweINA0QEfqzSdsfsyLO7SnGAF4fg==",
"requires": {
"@babel/runtime": "^7.20.6"
},
"dependencies": {
"@babel/runtime": {
"version": "7.20.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
"integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
"requires": {
"regenerator-runtime": "^0.13.11"
}
}
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -23691,6 +23796,15 @@
"use-sidecar": "^1.1.2"
}
},
"react-i18next": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.1.tgz",
"integrity": "sha512-mFdieOI0LDy84q3JuZU6Aou1DoWW2fhapcTGeBS8+vWSJuViuoCLQAMYSb0QoHhXS8B0WKUOPpx4cffAP7r/aA==",
"requires": {
"@babel/runtime": "^7.14.5",
"html-parse-stringify": "^3.0.1"
}
},
"react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@ -23741,9 +23855,9 @@
}
},
"regenerator-runtime": {
"version": "0.13.9",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"regenerator-transform": {
"version": "0.14.5",
@ -24393,6 +24507,11 @@
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
"dev": true
},
"stremio-translations": {
"version": "git+https://git@github.com/Stremio/stremio-translations.git#e47383bbbb3da8708bb727acd73a093604cdf5c8",
"integrity": "sha512-33qlTmLytSgTAf0fFO242pq2Ir52xREhMs0AGCBmaft4maceuM1cRf/lRJswwu/yMkEBH92C2hGkLGpd9qtw2A==",
"from": "stremio-translations@git+https://git@github.com/Stremio/stremio-translations.git#e47383bbbb3da8708bb727acd73a093604cdf5c8"
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@ -25067,6 +25186,11 @@
"resolved": "https://registry.npmjs.org/video-name-parser/-/video-name-parser-1.4.6.tgz",
"integrity": "sha512-ZdeYjh8X4ms1EzjY/UoiTZ6JWbi8SYyOPGY0jESSLq2BAmdc5sZHi+F8J19Qz0y7H1WSpaltojsCkO1p2dH4YA=="
},
"void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="
},
"vtt.js": {
"version": "git+ssh://git@github.com/jaruba/vtt.js.git#e4f5f5603730866bacb174a93f51b734c9f29e6a",
"integrity": "sha512-RXV60lPGrmjuRcV/jRuydLC2thMaMlmK4Vc3DtBmVSotFA3986sgW0H5AH9IUmHzQo4bFR2gELYLcfwVh7Dqow==",

View file

@ -25,6 +25,7 @@
"eventemitter3": "4.0.7",
"filter-invalid-dom-props": "2.1.0",
"hat": "0.0.3",
"i18next": "^22.4.3",
"langs": "^2.0.0",
"lodash.debounce": "4.0.8",
"lodash.intersection": "4.4.0",
@ -35,8 +36,10 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-focus-lock": "2.9.1",
"react-i18next": "^12.1.1",
"react-is": "18.2.0",
"spatial-navigation-polyfill": "git+https://git@github.com/Stremio/spatial-navigation.git#64871b1422466f5f45d24ebc8bbd315b2ebab6a6",
"stremio-translations": "git+https://git@github.com/Stremio/stremio-translations.git#e47383bbbb3da8708bb727acd73a093604cdf5c8",
"url": "0.11.0"
},
"devDependencies": {

View file

@ -2,6 +2,7 @@
require('spatial-navigation-polyfill');
const React = require('react');
const { useTranslation } = require('react-i18next');
const { Router } = require('stremio-router');
const { Core, Shell, Chromecast, DragAndDrop, KeyboardShortcuts, ServicesProvider } = require('stremio/services');
const { NotFound } = require('stremio/routes');
@ -13,6 +14,7 @@ const routerViewsConfig = require('./routerViewsConfig');
const styles = require('./styles');
const App = () => {
const { i18n } = useTranslation();
const onPathNotMatch = React.useCallback(() => {
return NotFound;
}, []);
@ -90,6 +92,21 @@ const App = () => {
};
}, []);
React.useEffect(() => {
const onCoreEvent = ({ event, args }) => {
switch (event) {
case 'SettingsUpdated': {
if (args && args.settings && typeof args.settings.interfaceLanguage === 'string') {
i18n.changeLanguage(args.settings.interfaceLanguage);
}
break;
}
}
};
const onCtxState = (state) => {
if (state && state.profile && state.profile.settings && typeof state.profile.settings.interfaceLanguage === 'string') {
i18n.changeLanguage(state.profile.settings.interfaceLanguage);
}
};
if (services.core.active) {
services.core.transport.dispatch({
action: 'Ctx',
@ -109,7 +126,15 @@ const App = () => {
action: 'SyncLibraryWithAPI'
}
});
services.core.transport.on('CoreEvent', onCoreEvent);
services.core.transport
.getState('ctx')
.then(onCtxState)
.catch((e) => console.error(e));
}
return () => {
services.core.transport.off('CoreEvent', onCoreEvent);
};
}, [initialized]);
return (
<React.StrictMode>

View file

@ -27,6 +27,7 @@ const comparatorWithPriorities = require('./comparatorWithPriorities');
const CONSTANTS = require('./CONSTANTS');
const { withCoreSuspender, useCoreSuspender } = require('./CoreSuspender');
const getVisibleChildrenRange = require('./getVisibleChildrenRange');
const interfaceLanguages = require('./interfaceLanguages');
const languageNames = require('./languageNames');
const routesRegexp = require('./routesRegexp');
const useAnimationFrame = require('./useAnimationFrame');
@ -70,6 +71,7 @@ module.exports = {
withCoreSuspender,
useCoreSuspender,
getVisibleChildrenRange,
interfaceLanguages,
languageNames,
routesRegexp,
useAnimationFrame,

View file

@ -0,0 +1,150 @@
[
{
"name": "العربية",
"codes": ["ar-AR", "ara"]
},
{
"name": "български език",
"codes": ["bg-BG", "bul"]
},
{
"name": "català",
"codes": ["ca-CA", "cat"]
},
{
"name": "čeština",
"codes": ["cs-CZ", "ces"]
},
{
"name": "dansk",
"codes": ["da-DK", "dan"]
},
{
"name": "Deutsch",
"codes": ["de-DE", "deu"]
},
{
"name": "ελληνικά",
"codes": ["el-GR", "ell"]
},
{
"name": "English",
"codes": ["en-US", "eng"]
},
{
"name": "Esperanto",
"codes": ["eo-EO", "epo"]
},
{
"name": "español",
"codes": ["es-ES", "spa"]
},
{
"name": "euskara",
"codes": ["eu-ES", "eus"]
},
{
"name": "فارسی",
"codes": ["fa-IR", "fas"]
},
{
"name": "Français",
"codes": ["fr-FR", "fre"]
},
{
"name": "עברית",
"codes": ["he-IL", "heb"]
},
{
"name": "हिन्दी",
"codes": ["hi-IN", "hin"]
},
{
"name": "hrvatski jezik",
"codes": ["hr-HR", "hrv"]
},
{
"name": "magyar",
"codes": ["hu-HU", "hun"]
},
{
"name": "Bahasa Indonesia",
"codes": ["id-ID", "ind"]
},
{
"name": "italiano",
"codes": ["it-IT", "ita"]
},
{
"name": "македонски јазик",
"codes": ["mk-MK", "mkd"]
},
{
"name": "ဗမာစာ",
"codes": ["my-BM", "mya"]
},
{
"name": "Norsk bokmål",
"codes": ["nb-NO", "nob"]
},
{
"name": "Nederlands",
"codes": ["nl-NL", "nld"]
},
{
"name": "Norsk nynorsk",
"codes": ["nn-NO", "nno"]
},
{
"name": "język polski",
"codes": ["pl-PL", "pol"]
},
{
"name": "português Brazil",
"codes": ["pt-BR", "por"]
},
{
"name": "português",
"codes": ["pt-PT", "por"]
},
{
"name": "русский язык",
"codes": ["ru-RU", "rus"]
},
{
"name": "Svenska",
"codes": ["sv-SE", "swe"]
},
{
"name": "slovenski jezik",
"codes": ["sl-SL", "slv"]
},
{
"name": "српски језик",
"codes": ["sr-RS", "srp"]
},
{
"name": "తెలుగు",
"codes": ["te-IN", "tel"]
},
{
"name": "Türkçe",
"codes": ["tr-TR", "tur"]
},
{
"name": "українська мова",
"codes": ["uk-UA", "ukr"]
},
{
"name": "中文(中华人民共和国)",
"codes": ["zh-CN", "zho"]
},
{
"name": "中文(香港特别行政區)",
"codes": ["zh-HK", "zho"]
},
{
"name": "中文(台灣)",
"codes": ["zh-TW", "zho"]
}
]

View file

@ -13,8 +13,26 @@ if (browser?.platform?.type === 'desktop') {
const React = require('react');
const ReactDOM = require('react-dom/client');
const i18n = require('i18next');
const { initReactI18next } = require('react-i18next');
const stremioTranslations = require('stremio-translations');
const App = require('./App');
const translations = Object.fromEntries(Object.entries(stremioTranslations()).map(([key, value]) => [key, {
translation: value
}]));
i18n
.use(initReactI18next)
.init({
resources: translations,
lng: 'en-US',
fallbackLng: 'en-US',
interpolation: {
escapeValue: false
}
});
const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(<App />);

View file

@ -3,6 +3,7 @@
const React = require('react');
const classnames = require('classnames');
const throttle = require('lodash.throttle');
const { useTranslation } = require('react-i18next');
const Icon = require('@stremio/stremio-icons/dom');
const { useRouteFocused } = require('stremio-router');
const { useServices } = require('stremio/services');
@ -17,6 +18,7 @@ const STREAMING_SECTION = 'streaming';
const SHORTCUTS_SECTION = 'shortcuts';
const Settings = () => {
const { t } = useTranslation();
const { core } = useServices();
const { routeFocused } = useRouteFocused();
const profile = useProfile();
@ -138,17 +140,17 @@ const Settings = () => {
<MainNavBars className={styles['settings-container']} route={'settings'}>
<div className={classnames(styles['settings-content'], 'animation-fade-in')}>
<div className={styles['side-menu-container']}>
<Button className={classnames(styles['side-menu-button'], { [styles['selected']]: selectedSectionId === GENERAL_SECTION })} title={'General'} data-section={GENERAL_SECTION} onClick={sideMenuButtonOnClick}>
General
<Button className={classnames(styles['side-menu-button'], { [styles['selected']]: selectedSectionId === GENERAL_SECTION })} title={ t('SETTINGS_NAV_GENERAL') } data-section={GENERAL_SECTION} onClick={sideMenuButtonOnClick}>
{ t('SETTINGS_NAV_GENERAL') }
</Button>
<Button className={classnames(styles['side-menu-button'], { [styles['selected']]: selectedSectionId === PLAYER_SECTION })} title={'Player'} data-section={PLAYER_SECTION} onClick={sideMenuButtonOnClick}>
Player
<Button className={classnames(styles['side-menu-button'], { [styles['selected']]: selectedSectionId === PLAYER_SECTION })} title={ t('SETTINGS_NAV_PLAYER') }data-section={PLAYER_SECTION} onClick={sideMenuButtonOnClick}>
{ t('SETTINGS_NAV_PLAYER') }
</Button>
<Button className={classnames(styles['side-menu-button'], { [styles['selected']]: selectedSectionId === STREAMING_SECTION })} title={'Streaming server'} data-section={STREAMING_SECTION} onClick={sideMenuButtonOnClick}>
Streaming server
<Button className={classnames(styles['side-menu-button'], { [styles['selected']]: selectedSectionId === STREAMING_SECTION })} title={ t('SETTINGS_NAV_STREAMING') } data-section={STREAMING_SECTION} onClick={sideMenuButtonOnClick}>
{ t('SETTINGS_NAV_STREAMING') }
</Button>
<Button className={classnames(styles['side-menu-button'], { [styles['selected']]: selectedSectionId === SHORTCUTS_SECTION })} title={'Shortcuts'} data-section={SHORTCUTS_SECTION} onClick={sideMenuButtonOnClick}>
Shortcuts
<Button className={classnames(styles['side-menu-button'], { [styles['selected']]: selectedSectionId === SHORTCUTS_SECTION })} title={ t('SETTINGS_NAV_SHORTCUTS') } data-section={SHORTCUTS_SECTION} onClick={sideMenuButtonOnClick}>
{ t('SETTINGS_NAV_SHORTCUTS') }
</Button>
<div className={styles['spacing']} />
<div className={styles['version-info-label']} title={process.env.VERSION}>App Version: {process.env.VERSION}</div>
@ -161,7 +163,7 @@ const Settings = () => {
</div>
<div ref={sectionsContainerRef} className={styles['sections-container']} onScroll={sectionsContainerOnScorll}>
<div ref={generalSectionRef} className={styles['section-container']}>
<div className={styles['section-title']}>General</div>
<div className={styles['section-title']}>{ t('SETTINGS_NAV_GENERAL') }</div>
<div className={classnames(styles['option-container'], styles['user-info-option-container'])}>
<div
className={styles['avatar-container']}
@ -207,7 +209,6 @@ const Settings = () => {
</div>
<Multiselect
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
disabled={true}
tabIndex={-1}
{...interfaceLanguageSelect}
/>
@ -266,7 +267,7 @@ const Settings = () => {
</div>
</div>
<div ref={playerSectionRef} className={styles['section-container']}>
<div className={styles['section-title']}>Player</div>
<div className={styles['section-title']}>{ t('SETTINGS_NAV_PLAYER') }</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>Subtitles language</div>
@ -384,15 +385,15 @@ const Settings = () => {
</div>
</div>
<div ref={streamingServerSectionRef} className={styles['section-container']}>
<div className={styles['section-title']}>Streaming Server</div>
<div className={styles['section-title']}>{ t('SETTINGS_NAV_STREAMING') }</div>
<div className={styles['option-container']}>
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Reload'} onClick={reloadStreamingServer}>
<div className={styles['label']}>Reload</div>
<div className={styles['label']}>{ t('RELOAD') }</div>
</Button>
</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>Status</div>
<div className={styles['label']}>{ t('STATUS') }</div>
</div>
<div className={classnames(styles['option-input-container'], styles['info-container'])}>
<div className={styles['label']}>
@ -452,10 +453,10 @@ const Settings = () => {
}
</div>
<div ref={shortcutsSectionRef} className={styles['section-container']}>
<div className={styles['section-title']}>Shortcuts</div>
<div className={styles['section-title']}>{ t('SETTINGS_NAV_SHORTCUTS') }</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>Play / Pause</div>
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_PLAY_PAUSE') }</div>
</div>
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
<kbd>Space</kbd>
@ -487,7 +488,7 @@ const Settings = () => {
</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>Volume Up</div>
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_VOLUME_UP') }</div>
</div>
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
<kbd></kbd>
@ -495,7 +496,7 @@ const Settings = () => {
</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>Volume Down</div>
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_VOLUME_DOWN') }</div>
</div>
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
<kbd></kbd>
@ -545,7 +546,7 @@ const Settings = () => {
</div>
<div className={styles['option-container']}>
<div className={styles['option-name-container']}>
<div className={styles['label']}>Close Menu or Modal</div>
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_EXIT_BACK') }</div>
</div>
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
<kbd>Esc</kbd>

View file

@ -2,17 +2,19 @@
const React = require('react');
const { useServices } = require('stremio/services');
const { CONSTANTS, languageNames } = require('stremio/common');
const { CONSTANTS, interfaceLanguages, languageNames } = require('stremio/common');
const useProfileSettingsInputs = (profile) => {
const { core } = useServices();
// TODO combine those useMemo in one
const interfaceLanguageSelect = React.useMemo(() => ({
options: Object.keys(languageNames).map((code) => ({
value: code,
label: languageNames[code]
options: interfaceLanguages.map(({ name, codes }) => ({
value: codes[0],
label: name,
})),
selected: [profile.settings.interfaceLanguage],
selected: [
interfaceLanguages.find(({ codes }) => codes[1] === profile.settings.interfaceLanguage)?.codes?.[0] || profile.settings.interfaceLanguage
],
onSelect: (event) => {
core.transport.dispatch({
action: 'Ctx',