mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-03-11 21:27:05 +00:00
Merge pull request #329 from Stremio/interface-language
Interface language
This commit is contained in:
commit
5705233705
35 changed files with 618 additions and 218 deletions
136
package-lock.json
generated
136
package-lock.json
generated
|
|
@ -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#ef047329f5bcb0a8f96008fca05c68b449b34cb1",
|
||||
"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#ef047329f5bcb0a8f96008fca05c68b449b34cb1",
|
||||
"integrity": "sha512-cG1GZIbCy2xWQpyYXJvlvnkWLtMBypL+BhMFJmC9LRHteAmJU++t37b5Wc6Sm1IbWoXqG508Tc9YfQ28bHTzaQ==",
|
||||
"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#ef047329f5bcb0a8f96008fca05c68b449b34cb1",
|
||||
"integrity": "sha512-cG1GZIbCy2xWQpyYXJvlvnkWLtMBypL+BhMFJmC9LRHteAmJU++t37b5Wc6Sm1IbWoXqG508Tc9YfQ28bHTzaQ==",
|
||||
"from": "stremio-translations@git+https://git@github.com/Stremio/stremio-translations.git#ef047329f5bcb0a8f96008fca05c68b449b34cb1"
|
||||
},
|
||||
"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==",
|
||||
|
|
|
|||
|
|
@ -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#ef047329f5bcb0a8f96008fca05c68b449b34cb1",
|
||||
"url": "0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
const onWindowFocus = () => {
|
||||
services.core.transport.dispatch({
|
||||
action: 'Ctx',
|
||||
|
|
@ -113,8 +130,16 @@ const App = () => {
|
|||
if (services.core.active) {
|
||||
onWindowFocus();
|
||||
window.addEventListener('focus', onWindowFocus);
|
||||
services.core.transport.on('CoreEvent', onCoreEvent);
|
||||
services.core.transport
|
||||
.getState('ctx')
|
||||
.then(onCtxState)
|
||||
.catch((e) => console.error(e));
|
||||
}
|
||||
return () => window.removeEventListener('focus', onWindowFocus);
|
||||
return () => {
|
||||
window.removeEventListener('focus', onWindowFocus);
|
||||
services.core.transport.off('CoreEvent', onCoreEvent);
|
||||
};
|
||||
}, [initialized]);
|
||||
return (
|
||||
<React.StrictMode>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const AColorPicker = require('a-color-picker');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Button = require('stremio/common/Button');
|
||||
const ModalDialog = require('stremio/common/ModalDialog');
|
||||
const useBinaryState = require('stremio/common/useBinaryState');
|
||||
|
|
@ -16,6 +17,7 @@ const parseColor = (value) => {
|
|||
};
|
||||
|
||||
const ColorInput = ({ className, value, dataset, onChange, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const [modalOpen, openModal, closeModal] = useBinaryState(false);
|
||||
const [tempValue, setTempValue] = React.useState(() => {
|
||||
return parseColor(value);
|
||||
|
|
@ -69,11 +71,11 @@ const ColorInput = ({ className, value, dataset, onChange, ...props }) => {
|
|||
setTempValue(parseColor(value));
|
||||
}, [value, modalOpen]);
|
||||
return (
|
||||
<Button title={isTransparent ? 'Transparent' : value} {...props} style={labelButtonStyle} className={classnames(className, styles['color-input-container'])} onClick={labelButtonOnClick}>
|
||||
<Button title={isTransparent ? t('BUTTON_COLOR_TRANSPARENT') : value} {...props} style={labelButtonStyle} className={classnames(className, styles['color-input-container'])} onClick={labelButtonOnClick}>
|
||||
{
|
||||
isTransparent ?
|
||||
<div className={styles['transparent-label-container']}>
|
||||
<div className={styles['transparent-label']}>Transparent</div>
|
||||
<div className={styles['transparent-label']}>{ t('BUTTON_COLOR_TRANSPARENT') }</div>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
|
|
|
|||
|
|
@ -4,29 +4,35 @@ const React = require('react');
|
|||
const { useServices } = require('stremio/services');
|
||||
const PropTypes = require('prop-types');
|
||||
const MetaItem = require('stremio/common/MetaItem');
|
||||
const { t } = require('i18next');
|
||||
|
||||
const OPTIONS = [
|
||||
{ label: 'Play', value: 'play' },
|
||||
{ label: 'Details', value: 'details' },
|
||||
{ label: 'Dismiss', value: 'dismiss' },
|
||||
{ label: 'Remove', value: 'remove' },
|
||||
{ label: 'LIBRARY_PLAY', value: 'play' },
|
||||
{ label: 'LIBRARY_DETAILS', value: 'details' },
|
||||
{ label: 'LIBRARY_RESUME_DISMISS', value: 'dismiss' },
|
||||
{ label: 'LIBRARY_REMOVE', value: 'remove' },
|
||||
];
|
||||
|
||||
const LibItem = ({ _id, removable, ...props }) => {
|
||||
const { core } = useServices();
|
||||
const options = React.useMemo(() => {
|
||||
return OPTIONS.filter(({ value }) => {
|
||||
switch (value) {
|
||||
case 'play':
|
||||
return props.deepLinks && typeof props.deepLinks.player === 'string';
|
||||
case 'details':
|
||||
return props.deepLinks && (typeof props.deepLinks.metaDetailsVideos === 'string' || typeof props.deepLinks.metaDetailsStreams === 'string');
|
||||
case 'dismiss':
|
||||
return typeof _id === 'string' && props.progress !== null && !isNaN(props.progress);
|
||||
case 'remove':
|
||||
return typeof _id === 'string' && removable;
|
||||
}
|
||||
});
|
||||
return OPTIONS
|
||||
.filter(({ value }) => {
|
||||
switch (value) {
|
||||
case 'play':
|
||||
return props.deepLinks && typeof props.deepLinks.player === 'string';
|
||||
case 'details':
|
||||
return props.deepLinks && (typeof props.deepLinks.metaDetailsVideos === 'string' || typeof props.deepLinks.metaDetailsStreams === 'string');
|
||||
case 'dismiss':
|
||||
return typeof _id === 'string' && props.progress !== null && !isNaN(props.progress);
|
||||
case 'remove':
|
||||
return typeof _id === 'string' && removable;
|
||||
}
|
||||
})
|
||||
.map((option) => ({
|
||||
...option,
|
||||
label: t(option.label)
|
||||
}));
|
||||
}, [_id, removable, props.progress, props.deepLinks]);
|
||||
const optionOnSelect = React.useCallback((event) => {
|
||||
if (typeof props.optionOnSelect === 'function') {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ const TABS = [
|
|||
{ id: 'board', label: 'Board', icon: 'ic_board', href: '#/' },
|
||||
{ id: 'discover', label: 'Discover', icon: 'ic_discover', href: '#/discover' },
|
||||
{ id: 'library', label: 'Library', icon: 'ic_library', href: '#/library' },
|
||||
{ id: 'settings', label: 'Settings', icon: 'ic_settings', href: '#/settings' },
|
||||
{ id: 'addons', label: 'Addons', icon: 'ic_addons', href: '#/addons' }
|
||||
{ id: 'settings', label: 'SETTINGS', icon: 'ic_settings', href: '#/settings' },
|
||||
{ id: 'addons', label: 'ADDONS', icon: 'ic_addons', href: '#/addons' }
|
||||
];
|
||||
|
||||
const MainNavBars = React.memo(({ className, route, query, children }) => {
|
||||
|
|
|
|||
|
|
@ -3,15 +3,19 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Button = require('stremio/common/Button');
|
||||
const styles = require('./styles');
|
||||
|
||||
const MetaLinks = ({ className, label, links }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={classnames(className, styles['meta-links-container'])}>
|
||||
{
|
||||
typeof label === 'string' && label.length > 0 ?
|
||||
<div className={styles['label-container']}>{label}</div>
|
||||
<div className={styles['label-container']}>
|
||||
{t(`LINKS_${label.toUpperCase()}`)}
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
|
@ -20,7 +24,7 @@ const MetaLinks = ({ className, label, links }) => {
|
|||
<div className={styles['links-container']}>
|
||||
{links.map(({ label, href }, index) => (
|
||||
<Button key={index} className={styles['link-container']} title={label} href={href}>
|
||||
{label}
|
||||
{ t(label) }
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const UrlUtils = require('url');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const Button = require('stremio/common/Button');
|
||||
const Image = require('stremio/common/Image');
|
||||
|
|
@ -24,6 +25,7 @@ const ALLOWED_LINK_REDIRECTS = [
|
|||
];
|
||||
|
||||
const MetaPreview = ({ className, compact, name, logo, background, runtime, releaseInfo, released, description, deepLinks, links, trailerStreams, inLibrary, toggleInLibrary }) => {
|
||||
const { t } = useTranslation();
|
||||
const [shareModalOpen, openShareModal, closeShareModal] = useBinaryState(false);
|
||||
const linksGroups = React.useMemo(() => {
|
||||
return Array.isArray(links) ?
|
||||
|
|
@ -192,7 +194,7 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
<ActionButton
|
||||
className={styles['action-button']}
|
||||
icon={inLibrary ? 'ic_removelib' : 'ic_addlib'}
|
||||
label={inLibrary ? 'Remove from Library' : 'Add to library'}
|
||||
label={inLibrary ? t('REMOVE_FROM_LIB') : t('ADD_TO_LIB')}
|
||||
tabIndex={compact ? -1 : 0}
|
||||
onClick={toggleInLibrary}
|
||||
/>
|
||||
|
|
@ -204,7 +206,7 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
<ActionButton
|
||||
className={styles['action-button']}
|
||||
icon={'ic_movies'}
|
||||
label={'Trailer'}
|
||||
label={t('TRAILER')}
|
||||
tabIndex={compact ? -1 : 0}
|
||||
href={trailerHref}
|
||||
/>
|
||||
|
|
@ -216,7 +218,7 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
<ActionButton
|
||||
className={styles['action-button']}
|
||||
icon={'ic_play'}
|
||||
label={'Show'}
|
||||
label={t('SHOW')}
|
||||
tabIndex={compact ? -1 : 0}
|
||||
href={showHref}
|
||||
/>
|
||||
|
|
@ -229,13 +231,13 @@ const MetaPreview = ({ className, compact, name, logo, background, runtime, rele
|
|||
<ActionButton
|
||||
className={styles['action-button']}
|
||||
icon={'ic_share'}
|
||||
label={'Share'}
|
||||
label={t('CTX_SHARE')}
|
||||
tabIndex={compact ? -1 : 0}
|
||||
onClick={openShareModal}
|
||||
/>
|
||||
{
|
||||
shareModalOpen ?
|
||||
<ModalDialog title={'Share'} onCloseRequest={closeShareModal}>
|
||||
<ModalDialog title={t('CTX_SHARE')} onCloseRequest={closeShareModal}>
|
||||
<SharePrompt
|
||||
className={styles['share-prompt']}
|
||||
url={linksGroups.get(CONSTANTS.SHARE_LINK_CATEGORY).href}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const React = require('react');
|
|||
const ReactIs = require('react-is');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const Button = require('stremio/common/Button');
|
||||
const CONSTANTS = require('stremio/common/CONSTANTS');
|
||||
|
|
@ -11,6 +12,7 @@ const MetaRowPlaceholder = require('./MetaRowPlaceholder');
|
|||
const styles = require('./styles');
|
||||
|
||||
const MetaRow = ({ className, title, message, items, itemComponent, deepLinks }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={classnames(className, styles['meta-row-container'])}>
|
||||
{
|
||||
|
|
@ -24,8 +26,8 @@ const MetaRow = ({ className, title, message, items, itemComponent, deepLinks })
|
|||
}
|
||||
{
|
||||
deepLinks && (typeof deepLinks.discover === 'string' || typeof deepLinks.library === 'string') ?
|
||||
<Button className={styles['see-all-container']} title={'SEE ALL'} href={deepLinks.discover || deepLinks.library} tabIndex={-1}>
|
||||
<div className={styles['label']}>SEE ALL</div>
|
||||
<Button className={styles['see-all-container']} title={t('BUTTON_SEE_ALL')} href={deepLinks.discover || deepLinks.library} tabIndex={-1}>
|
||||
<div className={styles['label']}>{ t('BUTTON_SEE_ALL') }</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_thin_right'} />
|
||||
</Button>
|
||||
:
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const Button = require('stremio/common/Button');
|
||||
const CONSTANTS = require('stremio/common/CONSTANTS');
|
||||
const styles = require('./styles');
|
||||
|
||||
const MetaRowPlaceholder = ({ className, title, deepLinks }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={classnames(className, styles['meta-row-placeholder-container'])}>
|
||||
<div className={styles['header-container']}>
|
||||
|
|
@ -17,8 +19,8 @@ const MetaRowPlaceholder = ({ className, title, deepLinks }) => {
|
|||
</div>
|
||||
{
|
||||
deepLinks && typeof deepLinks.discover === 'string' ?
|
||||
<Button className={styles['see-all-container']} title={'SEE ALL'} href={deepLinks.discover} tabIndex={-1}>
|
||||
<div className={styles['label']}>SEE ALL</div>
|
||||
<Button className={styles['see-all-container']} title={t('BUTTON_SEE_ALL')} href={deepLinks.discover} tabIndex={-1}>
|
||||
<div className={styles['label']}>{ t('BUTTON_SEE_ALL') }</div>
|
||||
<Icon className={styles['icon']} icon={'ic_arrow_thin_right'} />
|
||||
</Button>
|
||||
:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const useFullscreen = require('stremio/common/useFullscreen');
|
|||
const SearchBar = require('./SearchBar');
|
||||
const NavMenu = require('./NavMenu');
|
||||
const styles = require('./styles');
|
||||
const { t } = require('i18next');
|
||||
|
||||
const HorizontalNavBar = React.memo(({ className, route, query, title, backButton, searchBar, addonsButton, fullscreenButton, navMenu, ...props }) => {
|
||||
const backButtonOnClick = React.useCallback(() => {
|
||||
|
|
@ -54,7 +55,7 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
|||
<div className={styles['spacing']} />
|
||||
{
|
||||
addonsButton ?
|
||||
<Button className={styles['button-container']} href={'#/addons'} title={'Addons'} tabIndex={-1}>
|
||||
<Button className={styles['button-container']} href={'#/addons'} title={t('ADDONS')} tabIndex={-1}>
|
||||
<Icon className={styles['icon']} icon={'ic_addons'} />
|
||||
</Button>
|
||||
:
|
||||
|
|
@ -62,7 +63,7 @@ const HorizontalNavBar = React.memo(({ className, route, query, title, backButto
|
|||
}
|
||||
{
|
||||
fullscreenButton ?
|
||||
<Button className={styles['button-container']} title={fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'} tabIndex={-1} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||
<Button className={styles['button-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} tabIndex={-1} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||
<Icon className={styles['icon']} icon={fullscreen ? 'ic_exit_fullscreen' : 'ic_fullscreen'} />
|
||||
</Button>
|
||||
:
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { useServices } = require('stremio/services');
|
||||
const Button = require('stremio/common/Button');
|
||||
|
|
@ -13,6 +14,7 @@ const { withCoreSuspender } = require('stremio/common/CoreSuspender');
|
|||
const styles = require('./styles');
|
||||
|
||||
const NavMenuContent = ({ onClick }) => {
|
||||
const { t } = useTranslation();
|
||||
const { core } = useServices();
|
||||
const profile = useProfile();
|
||||
const { createTorrentFromMagnet } = useTorrent();
|
||||
|
|
@ -46,45 +48,45 @@ const NavMenuContent = ({ onClick }) => {
|
|||
}}
|
||||
/>
|
||||
<div className={styles['email-container']}>
|
||||
<div className={styles['email-label']}>{profile.auth === null ? 'Anonymous user' : profile.auth.user.email}</div>
|
||||
<div className={styles['email-label']}>{profile.auth === null ? t('ANONYMOUS_USER') : profile.auth.user.email}</div>
|
||||
</div>
|
||||
<Button className={styles['logout-button-container']} title={profile.auth === null ? 'Log in / Sign up' : 'Log out'} href={'#/intro'} onClick={logoutButtonOnClick}>
|
||||
<div className={styles['logout-label']}>{profile.auth === null ? 'Log in / Sign up' : 'Log out'}</div>
|
||||
<Button className={styles['logout-button-container']} title={profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')} href={'#/intro'} onClick={logoutButtonOnClick}>
|
||||
<div className={styles['logout-label']}>{profile.auth === null ? `${t('LOG_IN')} / ${t('SIGN_UP')}` : t('LOG_OUT')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['nav-menu-section']}>
|
||||
<Button className={styles['nav-menu-option-container']} title={fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||
<Button className={styles['nav-menu-option-container']} title={fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')} onClick={fullscreen ? exitFullscreen : requestFullscreen}>
|
||||
<Icon className={styles['icon']} icon={fullscreen ? 'ic_exit_fullscreen' : 'ic_fullscreen'} />
|
||||
<div className={styles['nav-menu-option-label']}>{fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}</div>
|
||||
<div className={styles['nav-menu-option-label']}>{fullscreen ? t('EXIT_FULLSCREEN') : t('ENTER_FULLSCREEN')}</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['nav-menu-section']}>
|
||||
<Button className={styles['nav-menu-option-container']} title={'Settings'} href={'#/settings'}>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('SETTINGS') } href={'#/settings'}>
|
||||
<Icon className={styles['icon']} icon={'ic_settings'} />
|
||||
<div className={styles['nav-menu-option-label']}>Settings</div>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('SETTINGS') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={'Addons'} href={'#/addons'}>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('ADDONS') } href={'#/addons'}>
|
||||
<Icon className={styles['icon']} icon={'ic_addons'} />
|
||||
<div className={styles['nav-menu-option-label']}>Addons</div>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('ADDONS') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={'Play Magnet Link'} onClick={onPlayMagnetLinkClick}>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('PLAY_URL_MAGNET_LINK') } onClick={onPlayMagnetLinkClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_magnet'} />
|
||||
<div className={styles['nav-menu-option-label']}>Play Magnet Link</div>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('PLAY_URL_MAGNET_LINK') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={'Help & Feedback'} href={'https://stremio.zendesk.com/'} target={'_blank'}>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('HELP_FEEDBACK') } href={'https://stremio.zendesk.com/'} target={'_blank'}>
|
||||
<Icon className={styles['icon']} icon={'ic_help'} />
|
||||
<div className={styles['nav-menu-option-label']}>Help & Feedback</div>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('HELP_FEEDBACK') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['nav-menu-section']}>
|
||||
<Button className={styles['nav-menu-option-container']} title={'Terms of Service'} href={'https://www.stremio.com/tos'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>Terms of Service</div>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('TERMS_OF_SERVICE') } href={'https://www.stremio.com/tos'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('TERMS_OF_SERVICE') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={'Privacy Policy'} href={'https://www.stremio.com/privacy'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>Privacy Policy</div>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('PRIVACY_POLICY') } href={'https://www.stremio.com/privacy'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('PRIVACY_POLICY') }</div>
|
||||
</Button>
|
||||
<Button className={styles['nav-menu-option-container']} title={'About Stremio'} href={'https://www.stremio.com/'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>About Stremio</div>
|
||||
<Button className={styles['nav-menu-option-container']} title={ t('ABOUT_STREMIO') } href={'https://www.stremio.com/'} target={'_blank'}>
|
||||
<div className={styles['nav-menu-option-label']}>{ t('ABOUT_STREMIO') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const Button = require('stremio/common/Button');
|
||||
|
|
@ -12,6 +13,7 @@ const { withCoreSuspender } = require('stremio/common/CoreSuspender');
|
|||
const styles = require('./styles');
|
||||
|
||||
const SearchBar = ({ className, query, active }) => {
|
||||
const { t } = useTranslation();
|
||||
const routeFocused = useRouteFocused();
|
||||
const { createTorrentFromMagnet } = useTorrent();
|
||||
const searchInputRef = React.useRef(null);
|
||||
|
|
@ -46,7 +48,7 @@ const SearchBar = ({ className, query, active }) => {
|
|||
ref={searchInputRef}
|
||||
className={styles['search-input']}
|
||||
type={'text'}
|
||||
placeholder={'Search or paste link'}
|
||||
placeholder={t('SEARCH_OR_PASTE_LINK')}
|
||||
defaultValue={query}
|
||||
tabIndex={-1}
|
||||
onChange={queryInputOnChange}
|
||||
|
|
@ -54,7 +56,7 @@ const SearchBar = ({ className, query, active }) => {
|
|||
/>
|
||||
:
|
||||
<div className={styles['search-input']}>
|
||||
<div className={styles['placeholder-label']}>Search or paste link</div>
|
||||
<div className={styles['placeholder-label']}>{ t('SEARCH_OR_PASTE_LINK') }</div>
|
||||
</div>
|
||||
}
|
||||
<Button className={styles['submit-button-container']} tabIndex={-1} onClick={queryInputOnSubmit}>
|
||||
|
|
@ -70,16 +72,19 @@ SearchBar.propTypes = {
|
|||
active: PropTypes.bool
|
||||
};
|
||||
|
||||
const SearchBarFallback = ({ className }) => (
|
||||
<label className={classnames(className, styles['search-bar-container'])}>
|
||||
<div className={styles['search-input']}>
|
||||
<div className={styles['placeholder-label']}>Search or paste link</div>
|
||||
</div>
|
||||
<Button className={styles['submit-button-container']} tabIndex={-1}>
|
||||
<Icon className={styles['icon']} icon={'ic_search_link'} />
|
||||
</Button>
|
||||
</label>
|
||||
);
|
||||
const SearchBarFallback = ({ className }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<label className={classnames(className, styles['search-bar-container'])}>
|
||||
<div className={styles['search-input']}>
|
||||
<div className={styles['placeholder-label']}>{ t('SEARCH_OR_PASTE_LINK') }</div>
|
||||
</div>
|
||||
<Button className={styles['submit-button-container']} tabIndex={-1}>
|
||||
<Icon className={styles['icon']} icon={'ic_search_link'} />
|
||||
</Button>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
SearchBarFallback.propTypes = SearchBar.propTypes;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const NavTabButton = require('./NavTabButton');
|
||||
const styles = require('./styles');
|
||||
|
||||
const VerticalNavBar = React.memo(({ className, selected, tabs }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<nav className={classnames(className, styles['vertical-nav-bar-container'])}>
|
||||
{
|
||||
|
|
@ -19,7 +21,7 @@ const VerticalNavBar = React.memo(({ className, selected, tabs }) => {
|
|||
href={tab.href}
|
||||
logo={tab.logo}
|
||||
icon={tab.icon}
|
||||
label={tab.label}
|
||||
label={t(tab.label)}
|
||||
onClick={tab.onClick}
|
||||
/>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { useRouteFocused } = require('stremio-router');
|
||||
const { useServices } = require('stremio/services');
|
||||
|
|
@ -11,6 +12,7 @@ const TextInput = require('stremio/common/TextInput');
|
|||
const styles = require('./styles');
|
||||
|
||||
const SharePrompt = ({ className, url }) => {
|
||||
const { t } = useTranslation();
|
||||
const { core } = useServices();
|
||||
const inputRef = React.useRef(null);
|
||||
const routeFocused = useRouteFocused();
|
||||
|
|
@ -62,7 +64,7 @@ const SharePrompt = ({ className, url }) => {
|
|||
/>
|
||||
<Button className={styles['copy-button']} title={'Copy to clipboard'} onClick={copyToClipboard}>
|
||||
<Icon className={styles['icon']} icon={'ic_link'} />
|
||||
<div className={styles['label']}>Copy</div>
|
||||
<div className={styles['label']}>{ t('COPY') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ const React = require('react');
|
|||
const { useServices } = require('stremio/services');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Button = require('stremio/common/Button');
|
||||
const useProfile = require('stremio/common/useProfile');
|
||||
const { withCoreSuspender } = require('stremio/common/CoreSuspender');
|
||||
const styles = require('./styles');
|
||||
|
||||
const StreamingServerWarning = ({ className }) => {
|
||||
const { t } = useTranslation();
|
||||
const { core } = useServices();
|
||||
const profile = useProfile();
|
||||
const onLaterClick = React.useCallback(() => {
|
||||
|
|
@ -48,12 +50,12 @@ const StreamingServerWarning = ({ className }) => {
|
|||
|
||||
return (
|
||||
<div className={classnames(className, styles['warning-container'])}>
|
||||
<div className={styles['warning-statement']}>Streaming server is not available.</div>
|
||||
<Button className={styles['warning-button']} title={'Later'} onClick={onLaterClick} tabIndex={-1}>
|
||||
<div className={styles['warning-label']}>Later</div>
|
||||
<div className={styles['warning-statement']}>{ t('SETTINGS_SERVER_UNAVAILABLE') }</div>
|
||||
<Button className={styles['warning-button']} title={t('WARNING_STREAMING_SERVER_LATER')} onClick={onLaterClick} tabIndex={-1}>
|
||||
<div className={styles['warning-label']}>{ t('WARNING_STREAMING_SERVER_LATER') }</div>
|
||||
</Button>
|
||||
<Button className={styles['warning-button']} title={'Dismiss'} onClick={onDismissClick} tabIndex={-1}>
|
||||
<div className={styles['warning-label']}>Dismiss</div>
|
||||
<Button className={styles['warning-button']} title={t('WARNING_STREAMING_SERVER_DISMISS')} onClick={onDismissClick} tabIndex={-1}>
|
||||
<div className={styles['warning-label']}>{ t('WARNING_STREAMING_SERVER_DISMISS') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,8 +27,10 @@ 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 translateOption = require('./translateOption');
|
||||
const useAnimationFrame = require('./useAnimationFrame');
|
||||
const useBinaryState = require('./useBinaryState');
|
||||
const useFullscreen = require('./useFullscreen');
|
||||
|
|
@ -70,8 +72,10 @@ module.exports = {
|
|||
withCoreSuspender,
|
||||
useCoreSuspender,
|
||||
getVisibleChildrenRange,
|
||||
interfaceLanguages,
|
||||
languageNames,
|
||||
routesRegexp,
|
||||
translateOption,
|
||||
useAnimationFrame,
|
||||
useBinaryState,
|
||||
useFullscreen,
|
||||
|
|
|
|||
150
src/common/interfaceLanguages.json
Normal file
150
src/common/interfaceLanguages.json
Normal 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"]
|
||||
}
|
||||
]
|
||||
15
src/common/translateOption.js
Normal file
15
src/common/translateOption.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
const { t } = require('i18next');
|
||||
|
||||
const translateOption = (option, translateKeyPrefix = '') => {
|
||||
const translateKey = `${translateKeyPrefix}${option}`;
|
||||
const translateValue = t(translateKey, {
|
||||
defaultValue: t(translateKey.toUpperCase(), {
|
||||
defaultValue: null
|
||||
})
|
||||
});
|
||||
return translateValue ?? option.charAt(0).toUpperCase() + option.slice(1);
|
||||
};
|
||||
|
||||
module.exports = translateOption;
|
||||
18
src/index.js
18
src/index.js
|
|
@ -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 />);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { Button, Image } = require('stremio/common');
|
||||
const styles = require('./styles');
|
||||
|
||||
const Addon = ({ className, id, name, version, logo, description, types, installed, onToggle, onShare, dataset }) => {
|
||||
const { t } = useTranslation();
|
||||
const toggleButtonOnClick = React.useCallback((event) => {
|
||||
if (typeof onToggle === 'function') {
|
||||
onToggle({
|
||||
|
|
@ -82,12 +84,12 @@ const Addon = ({ className, id, name, version, logo, description, types, install
|
|||
}
|
||||
</div>
|
||||
<div className={styles['buttons-container']}>
|
||||
<Button className={installed ? styles['uninstall-button-container'] : styles['install-button-container']} title={installed ? 'Uninstall' : 'Install'} tabIndex={-1} onClick={toggleButtonOnClick}>
|
||||
<div className={styles['label']}>{installed ? 'Uninstall' : 'Install'}</div>
|
||||
<Button className={installed ? styles['uninstall-button-container'] : styles['install-button-container']} title={installed ? t('ADDON_UNINSTALL') : t('ADDON_INSTALL')} tabIndex={-1} onClick={toggleButtonOnClick}>
|
||||
<div className={styles['label']}>{installed ? t('ADDON_UNINSTALL') : t('ADDON_INSTALL')}</div>
|
||||
</Button>
|
||||
<Button className={styles['share-button-container']} title={'SHARE ADDON'} tabIndex={-1} onClick={shareButtonOnClick}>
|
||||
<Button className={styles['share-button-container']} title={t('SHARE_ADDON')} tabIndex={-1} onClick={shareButtonOnClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_share'} />
|
||||
<div className={styles['label']}>SHARE ADDON</div>
|
||||
<div className={styles['label']}>{ t('SHARE_ADDON') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { AddonDetailsModal, Button, Image, Multiselect, MainNavBars, TextInput, SearchBar, SharePrompt, ModalDialog, useBinaryState, withCoreSuspender } = require('stremio/common');
|
||||
const Addon = require('./Addon');
|
||||
|
|
@ -13,6 +14,7 @@ const useSelectableInputs = require('./useSelectableInputs');
|
|||
const styles = require('./styles');
|
||||
|
||||
const Addons = ({ urlParams, queryParams }) => {
|
||||
const { t } = useTranslation();
|
||||
const installedAddons = useInstalledAddons(urlParams);
|
||||
const remoteAddons = useRemoteAddons(urlParams);
|
||||
const [addonDetailsTransportUrl, setAddonDetailsTransportUrl] = useAddonDetailsTransportUrl(urlParams, queryParams);
|
||||
|
|
@ -29,13 +31,13 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
return [
|
||||
{
|
||||
className: styles['cancel-button'],
|
||||
label: 'Cancel',
|
||||
label: t('BUTTON_CANCEL'),
|
||||
props: {
|
||||
onClick: closeAddAddonModal
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Add',
|
||||
label: t('ADDON_ADD'),
|
||||
props: {
|
||||
onClick: addAddonOnSubmit
|
||||
}
|
||||
|
|
@ -78,9 +80,9 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
<MainNavBars className={styles['addons-container']} route={'addons'}>
|
||||
<div className={styles['addons-content']}>
|
||||
<div className={styles['selectable-inputs-container']}>
|
||||
<Button className={styles['add-button-container']} title={'Add addon'} onClick={openAddAddonModal}>
|
||||
<Button className={styles['add-button-container']} title={t('ADD_ADDON')} onClick={openAddAddonModal}>
|
||||
<Icon className={styles['icon']} icon={'ic_plus'} />
|
||||
<div className={styles['add-button-label']}>Add addon</div>
|
||||
<div className={styles['add-button-label']}>{ t('ADD_ADDON') }</div>
|
||||
</Button>
|
||||
{selectInputs.map((selectInput, index) => (
|
||||
<Multiselect
|
||||
|
|
@ -92,7 +94,7 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
<div className={styles['spacing']} />
|
||||
<SearchBar
|
||||
className={styles['search-bar']}
|
||||
title={'Search addons'}
|
||||
title={t('ADDON_SEARCH')}
|
||||
value={search}
|
||||
onChange={searchInputOnChange}
|
||||
/>
|
||||
|
|
@ -192,15 +194,15 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
addAddonModalOpen ?
|
||||
<ModalDialog
|
||||
className={styles['add-addon-modal-container']}
|
||||
title={'Add addon'}
|
||||
title={t('ADD_ADDON')}
|
||||
buttons={addAddonModalButtons}
|
||||
onCloseRequest={closeAddAddonModal}>
|
||||
<div className={styles['notice']}>You can add an addon via an external link, which will appear under Installed addons.</div>
|
||||
<div className={styles['notice']}>{ t('ADD_ADDON_DESCRIPTION') }</div>
|
||||
<TextInput
|
||||
ref={addAddonUrlInputRef}
|
||||
className={styles['addon-url-input']}
|
||||
type={'text'}
|
||||
placeholder={'Paste addon URL'}
|
||||
placeholder={t('PASTE_ADDON_URL')}
|
||||
autoFocus={true}
|
||||
onSubmit={addAddonOnSubmit}
|
||||
/>
|
||||
|
|
@ -212,7 +214,7 @@ const Addons = ({ urlParams, queryParams }) => {
|
|||
sharedAddon !== null ?
|
||||
<ModalDialog
|
||||
className={styles['share-modal-container']}
|
||||
title={'Share Addon'}
|
||||
title={t('SHARE_ADDON')}
|
||||
onCloseRequest={clearSharedAddon}>
|
||||
<div className={styles['title-container']}>
|
||||
<Image
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { t } = require('i18next');
|
||||
const { translateOption } = require('stremio/common');
|
||||
|
||||
const mapSelectableInputs = (installedAddons, remoteAddons) => {
|
||||
const catalogSelect = {
|
||||
title: 'Select catalog',
|
||||
title: t('SELECT_CATALOG'),
|
||||
options: remoteAddons.selectable.catalogs
|
||||
.concat(installedAddons.selectable.catalogs)
|
||||
.map(({ name, deepLinks }) => ({
|
||||
value: deepLinks.addons,
|
||||
label: name,
|
||||
title: name
|
||||
label: translateOption(name, 'ADDON_'),
|
||||
title: translateOption(name, 'ADDON_'),
|
||||
})),
|
||||
selected: remoteAddons.selectable.catalogs
|
||||
.concat(installedAddons.selectable.catalogs)
|
||||
|
|
@ -20,7 +22,7 @@ const mapSelectableInputs = (installedAddons, remoteAddons) => {
|
|||
() => {
|
||||
const selectableCatalog = remoteAddons.selectable.catalogs
|
||||
.find(({ id }) => id === remoteAddons.selected.request.path.id);
|
||||
return selectableCatalog ? selectableCatalog.name : remoteAddons.selected.request.path.id;
|
||||
return selectableCatalog ? translateOption(selectableCatalog.name, 'ADDON_') : remoteAddons.selected.request.path.id;
|
||||
}
|
||||
:
|
||||
null,
|
||||
|
|
@ -29,16 +31,16 @@ const mapSelectableInputs = (installedAddons, remoteAddons) => {
|
|||
}
|
||||
};
|
||||
const typeSelect = {
|
||||
title: 'Select type',
|
||||
title: t('SELECT_TYPE'),
|
||||
options: installedAddons.selected !== null ?
|
||||
installedAddons.selectable.types.map(({ type, deepLinks }) => ({
|
||||
value: deepLinks.addons,
|
||||
label: type !== null ? type : 'All'
|
||||
label: type !== null ? translateOption(type, 'TYPE_') : t('TYPE_ALL')
|
||||
}))
|
||||
:
|
||||
remoteAddons.selectable.types.map(({ type, deepLinks }) => ({
|
||||
value: deepLinks.addons,
|
||||
label: type
|
||||
label: translateOption(type, 'TYPE_')
|
||||
})),
|
||||
selected: installedAddons.selected !== null ?
|
||||
installedAddons.selectable.types
|
||||
|
|
@ -51,12 +53,12 @@ const mapSelectableInputs = (installedAddons, remoteAddons) => {
|
|||
renderLabelText: () => {
|
||||
return installedAddons.selected !== null ?
|
||||
installedAddons.selected.request.type === null ?
|
||||
'All'
|
||||
t('TYPE_ALL')
|
||||
:
|
||||
installedAddons.selected.request.type
|
||||
translateOption(installedAddons.selected.request.type, 'TYPE_')
|
||||
:
|
||||
remoteAddons.selected !== null ?
|
||||
remoteAddons.selected.request.path.type
|
||||
translateOption(remoteAddons.selected.request.path.type, 'TYPE_')
|
||||
:
|
||||
typeSelect.title;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
const React = require('react');
|
||||
const classnames = require('classnames');
|
||||
const debounce = require('lodash.debounce');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { MainNavBars, MetaRow, LibItem, MetaItem, StreamingServerWarning, useStreamingServer, withCoreSuspender, getVisibleChildrenRange } = require('stremio/common');
|
||||
const useBoard = require('./useBoard');
|
||||
const useContinueWatchingPreview = require('./useContinueWatchingPreview');
|
||||
|
|
@ -11,6 +12,7 @@ const styles = require('./styles');
|
|||
const THRESHOLD = 5;
|
||||
|
||||
const Board = () => {
|
||||
const { t } = useTranslation();
|
||||
const streamingServer = useStreamingServer();
|
||||
const continueWatchingPreview = useContinueWatchingPreview();
|
||||
const [board, loadBoardRows] = useBoard();
|
||||
|
|
@ -42,7 +44,7 @@ const Board = () => {
|
|||
continueWatchingPreview.libraryItems.length > 0 ?
|
||||
<MetaRow
|
||||
className={classnames(styles['board-row'], styles['continue-watching-row'], 'animation-fade-in')}
|
||||
title={'Continue Watching'}
|
||||
title={t('BOARD_CONTINUE_WATCHING')}
|
||||
items={continueWatchingPreview.libraryItems}
|
||||
itemComponent={LibItem}
|
||||
deepLinks={continueWatchingPreview.deepLinks}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { translateOption } = require('stremio/common');
|
||||
|
||||
const mapSelectableInputs = (discover) => {
|
||||
const mapSelectableInputs = (discover, t) => {
|
||||
const typeSelect = {
|
||||
title: 'Select type',
|
||||
title: t('SELECT_TYPE'),
|
||||
options: discover.selectable.types
|
||||
.map(({ type, deepLinks }) => ({
|
||||
value: deepLinks.discover,
|
||||
label: type
|
||||
label: translateOption(type, 'TYPE_')
|
||||
})),
|
||||
selected: discover.selectable.types
|
||||
.filter(({ selected }) => selected)
|
||||
.map(({ deepLinks }) => deepLinks.discover),
|
||||
renderLabelText: discover.selected !== null ?
|
||||
() => discover.selected.request.path.type
|
||||
() => translateOption(discover.selected.request.path.type, 'TYPE_')
|
||||
:
|
||||
null,
|
||||
onSelect: (event) => {
|
||||
|
|
@ -22,7 +24,7 @@ const mapSelectableInputs = (discover) => {
|
|||
}
|
||||
};
|
||||
const catalogSelect = {
|
||||
title: 'Select catalog',
|
||||
title: t('SELECT_CATALOG'),
|
||||
options: discover.selectable.catalogs
|
||||
.map(({ name, addon, deepLinks }) => ({
|
||||
value: deepLinks.discover,
|
||||
|
|
@ -45,10 +47,10 @@ const mapSelectableInputs = (discover) => {
|
|||
}
|
||||
};
|
||||
const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => ({
|
||||
title: `Select ${name}`,
|
||||
title: translateOption(name, 'SELECT_'),
|
||||
isRequired: isRequired,
|
||||
options: options.map(({ value, deepLinks }) => ({
|
||||
label: typeof value === 'string' ? value : 'None',
|
||||
label: typeof value === 'string' ? translateOption(value) : t('NONE'),
|
||||
value: JSON.stringify({
|
||||
href: deepLinks.discover,
|
||||
value
|
||||
|
|
@ -61,7 +63,7 @@ const mapSelectableInputs = (discover) => {
|
|||
value
|
||||
})),
|
||||
renderLabelText: options.some(({ selected, value }) => selected && value === null) ?
|
||||
() => `Select ${name}`
|
||||
() => translateOption(name, 'SELECT_')
|
||||
:
|
||||
null,
|
||||
onSelect: (event) => {
|
||||
|
|
@ -73,8 +75,9 @@ const mapSelectableInputs = (discover) => {
|
|||
};
|
||||
|
||||
const useSelectableInputs = (discover) => {
|
||||
const { t } = useTranslation();
|
||||
const selectableInputs = React.useMemo(() => {
|
||||
return mapSelectableInputs(discover);
|
||||
return mapSelectableInputs(discover, t);
|
||||
}, [discover.selected, discover.selectable]);
|
||||
return selectableInputs;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { translateOption } = require('stremio/common');
|
||||
|
||||
const mapSelectableInputs = (library) => {
|
||||
const mapSelectableInputs = (library, t) => {
|
||||
const typeSelect = {
|
||||
title: 'Select type',
|
||||
title: t('SELECT_TYPE'),
|
||||
options: library.selectable.types
|
||||
.map(({ type, deepLinks }) => ({
|
||||
value: deepLinks.library,
|
||||
label: type === null ? 'All' : type
|
||||
label: type === null ? t('TYPE_ALL') : translateOption(type, 'TYPE_')
|
||||
})),
|
||||
selected: library.selectable.types
|
||||
.filter(({ selected }) => selected)
|
||||
|
|
@ -18,11 +20,11 @@ const mapSelectableInputs = (library) => {
|
|||
}
|
||||
};
|
||||
const sortSelect = {
|
||||
title: 'Select sort',
|
||||
title: t('SELECT_SORT'),
|
||||
options: library.selectable.sorts
|
||||
.map(({ sort, deepLinks }) => ({
|
||||
value: deepLinks.library,
|
||||
label: sort
|
||||
label: translateOption(sort, 'SORT_')
|
||||
})),
|
||||
selected: library.selectable.sorts
|
||||
.filter(({ selected }) => selected)
|
||||
|
|
@ -49,8 +51,9 @@ const mapSelectableInputs = (library) => {
|
|||
};
|
||||
|
||||
const useSelectableInputs = (library) => {
|
||||
const { t } = useTranslation();
|
||||
const selectableInputs = React.useMemo(() => {
|
||||
return mapSelectableInputs(library);
|
||||
return mapSelectableInputs(library, t);
|
||||
}, [library]);
|
||||
return selectableInputs;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { Button, Image, Multiselect } = require('stremio/common');
|
||||
const { useServices } = require('stremio/services');
|
||||
|
|
@ -12,6 +13,7 @@ const styles = require('./styles');
|
|||
const ALL_ADDONS_KEY = 'ALL';
|
||||
|
||||
const StreamsList = ({ className, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const { core } = useServices();
|
||||
const [selectedAddon, setSelectedAddon] = React.useState(ALL_ADDONS_KEY);
|
||||
const onAddonSelected = React.useCallback((event) => {
|
||||
|
|
@ -55,8 +57,8 @@ const StreamsList = ({ className, ...props }) => {
|
|||
options: [
|
||||
{
|
||||
value: ALL_ADDONS_KEY,
|
||||
label: 'All',
|
||||
title: 'All'
|
||||
label: t('ALL_ADDONS'),
|
||||
title: t('ALL_ADDONS')
|
||||
},
|
||||
...Object.keys(streamsByAddon).map((transportUrl) => ({
|
||||
value: transportUrl,
|
||||
|
|
@ -80,7 +82,7 @@ const StreamsList = ({ className, ...props }) => {
|
|||
props.streams.every((streams) => streams.content.type === 'Err') ?
|
||||
<div className={styles['message-container']}>
|
||||
<Image className={styles['image']} src={require('/images/empty.png')} alt={' '} />
|
||||
<div className={styles['label']}>No streams were found!</div>
|
||||
<div className={styles['label']}>{t('NO_STREAM')}</div>
|
||||
</div>
|
||||
:
|
||||
filteredStreams.length === 0 ?
|
||||
|
|
@ -115,9 +117,9 @@ const StreamsList = ({ className, ...props }) => {
|
|||
</div>
|
||||
</React.Fragment>
|
||||
}
|
||||
<Button className={styles['install-button-container']} title={'Install Addons'} href={'#/addons'}>
|
||||
<Button className={styles['install-button-container']} title={t('ADDON_CATALOGUE_MORE')} href={'#/addons'}>
|
||||
<Icon className={styles['icon']} icon={'ic_addons'} />
|
||||
<div className={styles['label']}>Install Addons</div>
|
||||
<div className={styles['label']}>{ t('ADDON_CATALOGUE_MORE') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { t } = require('i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { Button, Multiselect } = require('stremio/common');
|
||||
const SeasonsBarPlaceholder = require('./SeasonsBarPlaceholder');
|
||||
|
|
@ -12,7 +13,7 @@ const SeasonsBar = ({ className, seasons, season, onSelect }) => {
|
|||
const options = React.useMemo(() => {
|
||||
return seasons.map((season) => ({
|
||||
value: String(season),
|
||||
label: season > 0 ? `Season ${season}` : 'Specials'
|
||||
label: season > 0 ? `${t('SEASON')} ${season}` : t('SPECIAL')
|
||||
}));
|
||||
}, [seasons]);
|
||||
const selected = React.useMemo(() => {
|
||||
|
|
@ -53,7 +54,7 @@ const SeasonsBar = ({ className, seasons, season, onSelect }) => {
|
|||
</Button>
|
||||
<Multiselect
|
||||
className={styles['seasons-popup-label-container']}
|
||||
title={season > 0 ? `Season ${season}` : 'Specials'}
|
||||
title={season > 0 ? `${t('SEASON')} ${season}` : t('SPECIAL')}
|
||||
direction={'bottom-left'}
|
||||
options={options}
|
||||
selected={selected}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { t } = require('i18next');
|
||||
const Image = require('stremio/common/Image');
|
||||
const SearchBar = require('stremio/common/SearchBar');
|
||||
const SeasonsBar = require('./SeasonsBar');
|
||||
|
|
@ -62,7 +63,7 @@ const VideosList = ({ className, metaItem, season, seasonOnSelect }) => {
|
|||
!metaItem || metaItem.content.type === 'Loading' ?
|
||||
<React.Fragment>
|
||||
<SeasonsBar.Placeholder className={styles['seasons-bar']} />
|
||||
<SearchBar.Placeholder className={styles['search-bar']} title={'Search videos'} />
|
||||
<SearchBar.Placeholder className={styles['search-bar']} title={t('SEARCH_VIDEOS')} />
|
||||
<div className={styles['videos-scroll-container']}>
|
||||
<Video.Placeholder />
|
||||
<Video.Placeholder />
|
||||
|
|
@ -92,7 +93,7 @@ const VideosList = ({ className, metaItem, season, seasonOnSelect }) => {
|
|||
}
|
||||
<SearchBar
|
||||
className={styles['search-bar']}
|
||||
title={'Search videos'}
|
||||
title={t('SEARCH_VIDEOS')}
|
||||
value={search}
|
||||
onChange={searchInputOnChange}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ const SeekBar = require('./SeekBar');
|
|||
const VolumeSlider = require('./VolumeSlider');
|
||||
const styles = require('./styles');
|
||||
const { useBinaryState } = require('stremio/common');
|
||||
const { t } = require('i18next');
|
||||
|
||||
const ControlBar = ({
|
||||
className,
|
||||
|
|
@ -131,18 +132,18 @@ const ControlBar = ({
|
|||
onSeekRequested={onSeekRequested}
|
||||
/>
|
||||
<div className={styles['control-bar-buttons-container']}>
|
||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': typeof paused !== 'boolean' })} title={paused ? 'Play' : 'Pause'} tabIndex={-1} onClick={onPlayPauseButtonClick}>
|
||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': typeof paused !== 'boolean' })} title={paused ? t('PLAYER_PLAY') : t('PLAYER_PAUSE')} tabIndex={-1} onClick={onPlayPauseButtonClick}>
|
||||
<Icon className={styles['icon']} icon={typeof paused !== 'boolean' || paused ? 'ic_play' : 'ic_pause'} />
|
||||
</Button>
|
||||
{
|
||||
nextVideo !== null ?
|
||||
<Button className={classnames(styles['control-bar-button'])} title={'Next Video'} tabIndex={-1} onClick={onNextVideoButtonClick}>
|
||||
<Button className={classnames(styles['control-bar-button'])} title={t('PLAYER_NEXT_VIDEO')} tabIndex={-1} onClick={onNextVideoButtonClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_play_next'} />
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': typeof muted !== 'boolean' })} title={muted ? 'Unmute' : 'Mute'} tabIndex={-1} onClick={onMuteButtonClick}>
|
||||
<Button className={classnames(styles['control-bar-button'], { 'disabled': typeof muted !== 'boolean' })} title={muted ? t('PLAYER_UNMUTE') : t('PLAYER_MUTE')} tabIndex={-1} onClick={onMuteButtonClick}>
|
||||
<Icon
|
||||
className={styles['icon']}
|
||||
icon={
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Option = require('./Option');
|
||||
const styles = require('./styles');
|
||||
|
||||
const RATES = Array.from(Array(8).keys(), (n) => n * 0.25 + 0.25).reverse();
|
||||
|
||||
const SpeedMenu = ({ className, playbackSpeed, onPlaybackSpeedChanged }) => {
|
||||
const { t } = useTranslation();
|
||||
const onMouseDown = React.useCallback((event) => {
|
||||
event.nativeEvent.speedMenuClosePrevented = true;
|
||||
}, []);
|
||||
|
|
@ -20,7 +22,7 @@ const SpeedMenu = ({ className, playbackSpeed, onPlaybackSpeedChanged }) => {
|
|||
return (
|
||||
<div className={classnames(className, styles['speed-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<div className={styles['title']}>
|
||||
Playback Speed
|
||||
{ t('PLAYBACK_SPEED') }
|
||||
</div>
|
||||
<div className={styles['options-container']}>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const classnames = require('classnames');
|
|||
const { Button, CONSTANTS, comparatorWithPriorities, languageNames } = require('stremio/common');
|
||||
const DiscreteSelectInput = require('./DiscreteSelectInput');
|
||||
const styles = require('./styles');
|
||||
const { t } = require('i18next');
|
||||
|
||||
const ORIGIN_PRIORITIES = {
|
||||
'EMBEDDED': 2,
|
||||
|
|
@ -172,10 +173,10 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
null
|
||||
}
|
||||
<div className={styles['languages-container']}>
|
||||
<div className={styles['languages-header']}>Subtitles Languages</div>
|
||||
<div className={styles['languages-header']}>{ t('PLAYER_SUBTITLES_LANGUAGES') }</div>
|
||||
<div className={styles['languages-list']}>
|
||||
<Button title={'Off'} className={classnames(styles['language-option'], { 'selected': selectedSubtitlesLanguage === null })} onClick={subtitlesLanguageOnClick}>
|
||||
<div className={styles['language-label']}>Off</div>
|
||||
<Button title={t('OFF')} className={classnames(styles['language-option'], { 'selected': selectedSubtitlesLanguage === null })} onClick={subtitlesLanguageOnClick}>
|
||||
<div className={styles['language-label']}>{ t('OFF') }</div>
|
||||
{
|
||||
selectedSubtitlesLanguage === null ?
|
||||
<div className={styles['icon']} />
|
||||
|
|
@ -197,7 +198,7 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className={styles['variants-container']}>
|
||||
<div className={styles['variants-header']}>Subtitles Variants</div>
|
||||
<div className={styles['variants-header']}>{ t('PLAYER_SUBTITLES_VARIANTS') }</div>
|
||||
{
|
||||
subtitlesTracksForLanguage.length > 0 ?
|
||||
<div className={styles['variants-list']}>
|
||||
|
|
@ -216,7 +217,7 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
:
|
||||
<div className={styles['no-variants-container']}>
|
||||
<div className={styles['no-variants-label']}>
|
||||
Subtitles are disabled
|
||||
{ t('PLAYER_SUBTITLES_DISABLED') }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -225,14 +226,14 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
<div className={styles['settings-header']}>Subtitles Settings</div>
|
||||
<DiscreteSelectInput
|
||||
className={styles['discrete-input']}
|
||||
label={'Delay'}
|
||||
label={t('DELAY')}
|
||||
value={typeof props.selectedExtraSubtitlesTrackId === 'string' && props.extraSubtitlesDelay !== null && !isNaN(props.extraSubtitlesDelay) ? `${(props.extraSubtitlesDelay / 1000).toFixed(2)}s` : '--'}
|
||||
disabled={typeof props.selectedExtraSubtitlesTrackId !== 'string' || props.extraSubtitlesDelay === null || isNaN(props.extraSubtitlesDelay)}
|
||||
onChange={onSubtitlesDelayChanged}
|
||||
/>
|
||||
<DiscreteSelectInput
|
||||
className={styles['discrete-input']}
|
||||
label={'Size'}
|
||||
label={t('SIZE')}
|
||||
value={
|
||||
typeof props.selectedSubtitlesTrackId === 'string' ?
|
||||
props.subtitlesSize !== null && !isNaN(props.subtitlesSize) ? `${props.subtitlesSize}%` : '--'
|
||||
|
|
@ -255,7 +256,7 @@ const SubtitlesMenu = React.memo((props) => {
|
|||
/>
|
||||
<DiscreteSelectInput
|
||||
className={styles['discrete-input']}
|
||||
label={'Vertical position'}
|
||||
label={t('PLAYER_SUBTITLES_VERTICAL_POSIITON')}
|
||||
value={
|
||||
typeof props.selectedSubtitlesTrackId === 'string' ?
|
||||
props.subtitlesOffset !== null && !isNaN(props.subtitlesOffset) ? `${props.subtitlesOffset}%` : '--'
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const React = require('react');
|
|||
const PropTypes = require('prop-types');
|
||||
const classnames = require('classnames');
|
||||
const debounce = require('lodash.debounce');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const Icon = require('@stremio/stremio-icons/dom');
|
||||
const { Image, MainNavBars, MetaRow, MetaItem, withCoreSuspender, getVisibleChildrenRange } = require('stremio/common');
|
||||
const useSearch = require('./useSearch');
|
||||
|
|
@ -12,6 +13,7 @@ const styles = require('./styles');
|
|||
const THRESHOLD = 100;
|
||||
|
||||
const Search = ({ queryParams }) => {
|
||||
const { t } = useTranslation();
|
||||
const [search, loadSearchRows] = useSearch(queryParams);
|
||||
const query = React.useMemo(() => {
|
||||
return search.selected !== null ?
|
||||
|
|
@ -50,11 +52,11 @@ const Search = ({ queryParams }) => {
|
|||
<div className={classnames(styles['search-hints-container'], 'animation-fade-in')}>
|
||||
<div className={styles['search-hint-container']}>
|
||||
<Icon className={styles['icon']} icon={'ic_movies'} />
|
||||
<div className={styles['label']}>Search for movies, series, YouTube and TV channels</div>
|
||||
<div className={styles['label']}>{ t('SEARCH_EXPLANATION_CONTENT') }</div>
|
||||
</div>
|
||||
<div className={styles['search-hint-container']}>
|
||||
<Icon className={styles['icon']} icon={'ic_actor'} />
|
||||
<div className={styles['label']}>Search for actors, directors and writers</div>
|
||||
<div className={styles['label']}>{ t('SEARCH_EXPLANATION_PEOPLE') }</div>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
|
|
@ -65,7 +67,7 @@ const Search = ({ queryParams }) => {
|
|||
src={require('/images/empty.png')}
|
||||
alt={' '}
|
||||
/>
|
||||
<div className={styles['message-label']}>No addons were requested for catalogs!</div>
|
||||
<div className={styles['message-label']}>{ t('STREMIO_TV_SEARCH_NO_ADDONS') }</div>
|
||||
</div>
|
||||
:
|
||||
search.catalogs.map((catalog, index) => {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
@ -18,6 +19,7 @@ const STREAMING_SECTION = 'streaming';
|
|||
const SHORTCUTS_SECTION = 'shortcuts';
|
||||
|
||||
const Settings = () => {
|
||||
const { t } = useTranslation();
|
||||
const { core } = useServices();
|
||||
const { routeFocused } = useRouteFocused();
|
||||
const profile = useProfile();
|
||||
|
|
@ -176,17 +178,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>
|
||||
|
|
@ -199,7 +201,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']}
|
||||
|
|
@ -218,22 +220,22 @@ const Settings = () => {
|
|||
</div>
|
||||
{
|
||||
profile.auth !== null ?
|
||||
<Button className={styles['logout-button-container']} title={'Log out'} href={'#/intro'} onClick={logoutButtonOnClick}>
|
||||
<div className={styles['logout-label']}>Log out</div>
|
||||
<Button className={styles['logout-button-container']} title={ t('LOG_OUT') } href={'#/intro'} onClick={logoutButtonOnClick}>
|
||||
<div className={styles['logout-label']}>{ t('LOG_OUT') }</div>
|
||||
</Button>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<Button className={styles['user-panel-container']} title={'User panel'} target={'_blank'} href={'https://www.stremio.com/acc-settings'}>
|
||||
<div className={styles['user-panel-label']}>User Panel</div>
|
||||
<div className={styles['user-panel-label']}>{ t('USER_PANEL') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
{
|
||||
profile.auth === null ?
|
||||
<div className={styles['option-container']}>
|
||||
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Log in / Sign up'} href={'#/intro'} onClick={logoutButtonOnClick}>
|
||||
<div className={styles['label']}>Log in / Sign up</div>
|
||||
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={`${t('LOG_IN')} / ${t('SIGN_UP')}`} href={'#/intro'} onClick={logoutButtonOnClick}>
|
||||
<div className={styles['label']}>{ t('LOG_IN') } / { t('SIGN_UP') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
:
|
||||
|
|
@ -241,11 +243,10 @@ const Settings = () => {
|
|||
}
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Interface language</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_INTERFACE_LANGUAGE') }</div>
|
||||
</div>
|
||||
<Multiselect
|
||||
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
|
||||
disabled={true}
|
||||
tabIndex={-1}
|
||||
{...interfaceLanguageSelect}
|
||||
/>
|
||||
|
|
@ -257,27 +258,27 @@ const Settings = () => {
|
|||
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Authenticate'} disabled={profile.auth === null} tabIndex={-1} onClick={toggleTraktOnClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_trakt'} />
|
||||
<div className={styles['label']}>
|
||||
{ profile.auth !== null && profile.auth.user !== null && profile.auth.user.trakt !== null ? 'Log out' : 'Authenticate' }
|
||||
{ profile.auth !== null && profile.auth.user !== null && profile.auth.user.trakt !== null ? t('LOG_OUT') : t('SETTINGS_TRAKT_AUTHENTICATE') }
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Calendar</div>
|
||||
<div className={styles['label']}>{ t('Calendar') }</div>
|
||||
</div>
|
||||
<Button className={classnames(styles['option-input-container'], styles['button-container'])} title={'Subscribe'} disabled={!(profile.auth && profile.auth.user && profile.auth.user._id)} tabIndex={-1} onClick={subscribeCalendarOnClick}>
|
||||
<Icon className={styles['icon']} icon={'ic_calendar'} />
|
||||
<div className={styles['label']}>Subscribe</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_CALENDAR_SUBSCRIBE') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={'Export user data'} tabIndex={-1} onClick={exportDataOnClick}>
|
||||
<div className={styles['label']}>Export user data</div>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={t('SETTINGS_DATA_EXPORT')} tabIndex={-1} onClick={exportDataOnClick}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_DATA_EXPORT') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={'Contact support'} target={'_blank'} href={'https://stremio.zendesk.com/hc/en-us'}>
|
||||
<div className={styles['label']}>Contact support</div>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={t('SETTINGS_SUPPORT')} target={'_blank'} href={'https://stremio.zendesk.com/hc/en-us'}>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SUPPORT') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
|
|
@ -286,21 +287,21 @@ const Settings = () => {
|
|||
</Button>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={'Terms of Service'} target={'_blank'} href={'https://www.stremio.com/tos'}>
|
||||
<div className={styles['label']}>Terms of Service</div>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={t('TERMS_OF_SERVICE')} target={'_blank'} href={'https://www.stremio.com/tos'}>
|
||||
<div className={styles['label']}>{ t('TERMS_OF_SERVICE') }</div>
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={'Privacy Policy'} target={'_blank'} href={'https://www.stremio.com/privacy'}>
|
||||
<div className={styles['label']}>Privacy Policy</div>
|
||||
<Button className={classnames(styles['option-input-container'], styles['link-container'])} title={t('PRIVACY_POLICY')} target={'_blank'} href={'https://www.stremio.com/privacy'}>
|
||||
<div className={styles['label']}>{ t('PRIVACY_POLICY') }</div>
|
||||
</Button>
|
||||
</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>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SUBTITLES_LANGUAGE') }</div>
|
||||
</div>
|
||||
<Multiselect
|
||||
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
|
||||
|
|
@ -309,7 +310,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Subtitles size</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SUBTITLES_SIZE') }</div>
|
||||
</div>
|
||||
<Multiselect
|
||||
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
|
||||
|
|
@ -318,7 +319,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Subtitles text color</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SUBTITLES_COLOR') }</div>
|
||||
</div>
|
||||
<ColorInput
|
||||
className={classnames(styles['option-input-container'], styles['color-input-container'])}
|
||||
|
|
@ -327,7 +328,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Subtitles background color</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SUBTITLES_COLOR_BACKGROUND') }</div>
|
||||
</div>
|
||||
<ColorInput
|
||||
className={classnames(styles['option-input-container'], styles['color-input-container'])}
|
||||
|
|
@ -336,7 +337,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Subtitles outline color</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SUBTITLES_COLOR_OUTLINE') }</div>
|
||||
</div>
|
||||
<ColorInput
|
||||
className={classnames(styles['option-input-container'], styles['color-input-container'])}
|
||||
|
|
@ -345,7 +346,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Audio Language</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_DEFAULT_AUDIO_TRACK') }</div>
|
||||
</div>
|
||||
<Multiselect
|
||||
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
|
||||
|
|
@ -354,7 +355,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Rewind & Fast-forward duration</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_REWIND_FAST_FORWARD_DURATION') }</div>
|
||||
</div>
|
||||
<Multiselect
|
||||
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
|
||||
|
|
@ -363,7 +364,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Auto-play next episode</div>
|
||||
<div className={styles['label']}>{ t('AUTO_PLAY') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
|
|
@ -372,7 +373,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Next video popup duration</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_NEXT_VIDEO_POPUP_DURATION') }</div>
|
||||
</div>
|
||||
<Multiselect
|
||||
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
|
||||
|
|
@ -382,7 +383,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Play in background</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_PLAY_IN_BACKGROUND') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
|
|
@ -393,7 +394,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Play in external player</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_PLAY_IN_EXTERNAL_PLAYER') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
|
|
@ -404,7 +405,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Hardware-accelerated decoding</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_HWDEC') }</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
className={classnames(styles['option-input-container'], styles['checkbox-container'])}
|
||||
|
|
@ -415,15 +416,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']}>
|
||||
|
|
@ -432,10 +433,10 @@ const Settings = () => {
|
|||
'NotLoaded'
|
||||
:
|
||||
streamingServer.settings.type === 'Ready' ?
|
||||
'Online'
|
||||
t('SETTINGS_SERVER_STATUS_ONLINE')
|
||||
:
|
||||
streamingServer.settings.type === 'Error' ?
|
||||
`Error: (${streamingServer.settings.content})`
|
||||
`${t('SETTINGS_SERVER_STATUS_ERROR')}: (${streamingServer.settings.content})`
|
||||
:
|
||||
streamingServer.settings.type
|
||||
}
|
||||
|
|
@ -457,7 +458,7 @@ const Settings = () => {
|
|||
cacheSizeSelect !== null ?
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Cache size</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SERVER_CACHE_SIZE') }</div>
|
||||
</div>
|
||||
<Multiselect
|
||||
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
|
||||
|
|
@ -471,7 +472,7 @@ const Settings = () => {
|
|||
torrentProfileSelect !== null ?
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Torrent profile</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SERVER_TORRENT_PROFILE') }</div>
|
||||
</div>
|
||||
<Multiselect
|
||||
className={classnames(styles['option-input-container'], styles['multiselect-container'])}
|
||||
|
|
@ -483,42 +484,42 @@ 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>
|
||||
<kbd>{ t('SETTINGS_SHORTCUT_SPACE') }</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Seek Forward</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_SEEK_FORWARD') }</div>
|
||||
</div>
|
||||
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
|
||||
<kbd>→</kbd>
|
||||
<div className={styles['label']}>or</div>
|
||||
<kbd>⇧ Shift</kbd>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_OR') }</div>
|
||||
<kbd>⇧ { t('SETTINGS_SHORTCUT_SHIFT') }</kbd>
|
||||
<div className={styles['label']}>+</div>
|
||||
<kbd>→</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Seek Backward</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_SEEK_BACKWARD') }</div>
|
||||
</div>
|
||||
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
|
||||
<kbd>←</kbd>
|
||||
<div className={styles['label']}>or</div>
|
||||
<kbd>⇧ Shift</kbd>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_OR') }</div>
|
||||
<kbd>⇧ { t('SETTINGS_SHORTCUT_SHIFT') }</kbd>
|
||||
<div className={styles['label']}>+</div>
|
||||
<kbd>←</kbd>
|
||||
</div>
|
||||
</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>
|
||||
|
|
@ -526,7 +527,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>
|
||||
|
|
@ -534,7 +535,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Toggle Subtitles Menu</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_MENU_SUBTITLES') }</div>
|
||||
</div>
|
||||
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
|
||||
<kbd>S</kbd>
|
||||
|
|
@ -542,7 +543,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Toggle Info Menu</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_MENU_INFO') }</div>
|
||||
</div>
|
||||
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
|
||||
<kbd>I</kbd>
|
||||
|
|
@ -550,7 +551,7 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Toggle Fullscreen</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_FULLSCREEN') }</div>
|
||||
</div>
|
||||
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
|
||||
<kbd>F</kbd>
|
||||
|
|
@ -558,17 +559,17 @@ const Settings = () => {
|
|||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Navigate Between Menus</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_NAVIGATE_MENUS') }</div>
|
||||
</div>
|
||||
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
|
||||
<kbd>1</kbd>
|
||||
<div className={styles['label']}>to</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_TO') }</div>
|
||||
<kbd>5</kbd>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['option-container']}>
|
||||
<div className={styles['option-name-container']}>
|
||||
<div className={styles['label']}>Go to Search</div>
|
||||
<div className={styles['label']}>{ t('SETTINGS_SHORTCUT_GO_TO_SEARCH') }</div>
|
||||
</div>
|
||||
<div className={classnames(styles['option-input-container'], styles['shortcut-container'])}>
|
||||
<kbd>0</kbd>
|
||||
|
|
@ -576,10 +577,10 @@ 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>
|
||||
<kbd>{ t('SETTINGS_SHORTCUT_ESC') }</kbd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -589,7 +590,7 @@ const Settings = () => {
|
|||
configureServerUrlModalOpen ?
|
||||
<ModalDialog
|
||||
className={styles['configure-server-url-modal-container']}
|
||||
title={'Configure streaming server url'}
|
||||
title={t('SETTINGS_SERVER_CONFIGURE_TITLE')}
|
||||
buttons={configureServerUrlModalButtons}
|
||||
onCloseRequest={closeConfigureServerUrlModal}>
|
||||
<TextInput
|
||||
|
|
@ -598,7 +599,7 @@ const Settings = () => {
|
|||
className={styles['server-url-input']}
|
||||
type={'text'}
|
||||
defaultValue={streamingServerUrlInput.value}
|
||||
placeholder={'Enter a streaming server url'}
|
||||
placeholder={t('SETTINGS_SERVER_CONFIGURE_INPUT')}
|
||||
onSubmit={configureServerUrlOnSubmit}
|
||||
/>
|
||||
</ModalDialog>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
// Copyright (C) 2017-2022 Smart code 203358507
|
||||
|
||||
const React = require('react');
|
||||
const { useTranslation } = require('react-i18next');
|
||||
const { useServices } = require('stremio/services');
|
||||
const { CONSTANTS, languageNames } = require('stremio/common');
|
||||
const { CONSTANTS, interfaceLanguages, languageNames } = require('stremio/common');
|
||||
|
||||
const useProfileSettingsInputs = (profile) => {
|
||||
const { t } = useTranslation();
|
||||
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',
|
||||
|
|
@ -134,11 +138,11 @@ const useProfileSettingsInputs = (profile) => {
|
|||
const seekTimeDurationSelect = React.useMemo(() => ({
|
||||
options: CONSTANTS.SEEK_TIME_DURATIONS.map((size) => ({
|
||||
value: `${size}`,
|
||||
label: `${size / 1000} seconds`
|
||||
label: `${size / 1000} ${t('SECONDS')}`
|
||||
})),
|
||||
selected: [`${profile.settings.seekTimeDuration}`],
|
||||
renderLabelText: () => {
|
||||
return `${profile.settings.seekTimeDuration / 1000} seconds`;
|
||||
return `${profile.settings.seekTimeDuration / 1000} ${t('SECONDS')}`;
|
||||
},
|
||||
onSelect: (event) => {
|
||||
core.transport.dispatch({
|
||||
|
|
@ -156,14 +160,14 @@ const useProfileSettingsInputs = (profile) => {
|
|||
const nextVideoPopupDurationSelect = React.useMemo(() => ({
|
||||
options: CONSTANTS.NEXT_VIDEO_POPUP_DURATIONS.map((duration) => ({
|
||||
value: `${duration}`,
|
||||
label: duration === 0 ? 'Disabled' : `${duration / 1000} seconds`
|
||||
label: duration === 0 ? 'Disabled' : `${duration / 1000} ${t('SECONDS')}`
|
||||
})),
|
||||
selected: [`${profile.settings.nextVideoNotificationDuration}`],
|
||||
renderLabelText: () => {
|
||||
return profile.settings.nextVideoNotificationDuration === 0 ?
|
||||
'Disabled'
|
||||
:
|
||||
`${profile.settings.nextVideoNotificationDuration / 1000} seconds`;
|
||||
`${profile.settings.nextVideoNotificationDuration / 1000} ${t('SECONDS')}`;
|
||||
},
|
||||
onSelect: (event) => {
|
||||
core.transport.dispatch({
|
||||
|
|
|
|||
Loading…
Reference in a new issue