From fff0ebe85de3f7bd36c92f0472047ee90fe32486 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Mon, 19 May 2025 17:02:32 +0300 Subject: [PATCH 001/136] fix(Player): workaround for binge watching --- src/routes/Player/Player.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 740ed46d4..ce4cd3c3e 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -103,7 +103,7 @@ const Player = ({ urlParams, queryParams }) => { video.setProp('extraSubtitlesOutlineColor', settings.subtitlesOutlineColor); }, [settings.subtitlesSize, settings.subtitlesOffset, settings.subtitlesTextColor, settings.subtitlesBackgroundColor, settings.subtitlesOutlineColor]); - const handleNextVideoNavigation = React.useCallback((deepLinks) => { + const handleNextVideoNavigation = (deepLinks) => { if (deepLinks.player) { isNavigating.current = true; window.location.replace(deepLinks.player); @@ -111,20 +111,16 @@ const Player = ({ urlParams, queryParams }) => { isNavigating.current = true; window.location.replace(deepLinks.metaDetailsStreams); } - }, []); - - const onEnded = React.useCallback(() => { - if (isNavigating.current) { - return; - } + }; + const onEnded = () => { ended(); - if (player.nextVideo !== null) { + if (window.playerNextVideo !== null) { onNextVideoRequested(); } else { window.history.back(); } - }, [player.nextVideo, onNextVideoRequested]); + }; const onError = React.useCallback((error) => { console.error('Player', error); @@ -229,14 +225,14 @@ const Player = ({ urlParams, queryParams }) => { nextVideoPopupDismissed.current = true; }, []); - const onNextVideoRequested = React.useCallback(() => { - if (player.nextVideo !== null) { + const onNextVideoRequested = () => { + if (window.playerNextVideo !== null) { nextVideo(); - const deepLinks = player.nextVideo.deepLinks; + const deepLinks = window.playerNextVideo.deepLinks; handleNextVideoNavigation(deepLinks); } - }, [player.nextVideo, handleNextVideoNavigation]); + }; const onVideoClick = React.useCallback(() => { if (video.state.paused !== null) { @@ -394,6 +390,12 @@ const Player = ({ urlParams, queryParams }) => { closeNextVideoPopup(); } } + if (player.nextVideo) { + // This is a workaround for the fact that when we call onEnded nextVideo from the player is already set to null since core unloads the stream + // we explicitly set it to a global variable so we can access it in the onEnded function + // this is not a good solution but it works for now + window.playerNextVideo = player.nextVideo; + } }, [player.nextVideo, video.state.time, video.state.duration]); React.useEffect(() => { @@ -429,6 +431,7 @@ const Player = ({ urlParams, queryParams }) => { defaultSubtitlesSelected.current = false; defaultAudioTrackSelected.current = false; nextVideoPopupDismissed.current = false; + isNavigating.current = false; }, [video.state.stream]); React.useEffect(() => { From b4c0ab551ecbb73ea074e035ea4b294b0755c15b Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 20 May 2025 15:09:50 +0300 Subject: [PATCH 002/136] fix(Player): binge watching --- src/routes/Player/Player.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index ce4cd3c3e..1ddb14599 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -103,7 +103,7 @@ const Player = ({ urlParams, queryParams }) => { video.setProp('extraSubtitlesOutlineColor', settings.subtitlesOutlineColor); }, [settings.subtitlesSize, settings.subtitlesOffset, settings.subtitlesTextColor, settings.subtitlesBackgroundColor, settings.subtitlesOutlineColor]); - const handleNextVideoNavigation = (deepLinks) => { + const handleNextVideoNavigation = React.useCallback((deepLinks) => { if (deepLinks.player) { isNavigating.current = true; window.location.replace(deepLinks.player); @@ -111,16 +111,23 @@ const Player = ({ urlParams, queryParams }) => { isNavigating.current = true; window.location.replace(deepLinks.metaDetailsStreams); } - }; + }, []); + + const onEnded = React.useCallback(() => { + if (isNavigating.current) { + return; + } - const onEnded = () => { ended(); if (window.playerNextVideo !== null) { - onNextVideoRequested(); + nextVideo(); + + const deepLinks = window.playerNextVideo.deepLinks; + handleNextVideoNavigation(deepLinks); } else { window.history.back(); } - }; + }, []); const onError = React.useCallback((error) => { console.error('Player', error); @@ -225,14 +232,14 @@ const Player = ({ urlParams, queryParams }) => { nextVideoPopupDismissed.current = true; }, []); - const onNextVideoRequested = () => { - if (window.playerNextVideo !== null) { + const onNextVideoRequested = React.useCallback(() => { + if (player.nextVideo !== null) { nextVideo(); - const deepLinks = window.playerNextVideo.deepLinks; + const deepLinks = player.nextVideo.deepLinks; handleNextVideoNavigation(deepLinks); } - }; + }, [player.nextVideo, handleNextVideoNavigation]); const onVideoClick = React.useCallback(() => { if (video.state.paused !== null) { @@ -431,7 +438,6 @@ const Player = ({ urlParams, queryParams }) => { defaultSubtitlesSelected.current = false; defaultAudioTrackSelected.current = false; nextVideoPopupDismissed.current = false; - isNavigating.current = false; }, [video.state.stream]); React.useEffect(() => { From 365294946a8a68d321bebe8cfb11f3f36301cc30 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 20 May 2025 15:12:29 +0300 Subject: [PATCH 003/136] chore(Player): add logs --- src/routes/Player/Player.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 1ddb14599..c0443ac74 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -114,6 +114,8 @@ const Player = ({ urlParams, queryParams }) => { }, []); const onEnded = React.useCallback(() => { + // here we need to explicitly check for isNavigating.current + // because the ended event can be calleb multiple times by MPV inside Shell if (isNavigating.current) { return; } From 38f7e5a0f805bc0192934e4d39fa53de984e5c20 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 20 May 2025 15:32:02 +0300 Subject: [PATCH 004/136] chore(Player): update logs --- src/routes/Player/Player.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index c0443ac74..7a4d5b035 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -115,7 +115,7 @@ const Player = ({ urlParams, queryParams }) => { const onEnded = React.useCallback(() => { // here we need to explicitly check for isNavigating.current - // because the ended event can be calleb multiple times by MPV inside Shell + // the ended event can be called multiple times by MPV inside Shell if (isNavigating.current) { return; } From 440713ee680fa034fe981e0119777480030464a3 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 20 May 2025 15:54:20 +0300 Subject: [PATCH 005/136] fix(Player): reset the flag back to false --- src/routes/Player/Player.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index 7a4d5b035..f1e0657d9 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -440,6 +440,9 @@ const Player = ({ urlParams, queryParams }) => { defaultSubtitlesSelected.current = false; defaultAudioTrackSelected.current = false; nextVideoPopupDismissed.current = false; + // we need a timeout here to make sure that previous page unloads and the new one loads + // avoiding race conditions and flickering + setTimeout(() => isNavigating.current = false, 1000); }, [video.state.stream]); React.useEffect(() => { From 28578e1deac922fe8e1e81d388ccc967d7304b30 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Wed, 21 May 2025 15:11:32 +0300 Subject: [PATCH 006/136] refactor(Player): reset global var --- src/routes/Player/Player.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/Player/Player.js b/src/routes/Player/Player.js index f1e0657d9..48218d126 100644 --- a/src/routes/Player/Player.js +++ b/src/routes/Player/Player.js @@ -404,6 +404,8 @@ const Player = ({ urlParams, queryParams }) => { // we explicitly set it to a global variable so we can access it in the onEnded function // this is not a good solution but it works for now window.playerNextVideo = player.nextVideo; + } else { + window.playerNextVideo = null; } }, [player.nextVideo, video.state.time, video.state.duration]); From 657f9cd29e5b9b0690aee8e899f796d9be6eb380 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 23 May 2025 16:34:41 +0300 Subject: [PATCH 007/136] feat: added scanner for missed translations --- .gitignore | 3 +- package-lock.json | 38 ++++++++++++++++ package.json | 4 +- scan-i18n-report.js | 107 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 scan-i18n-report.js diff --git a/.gitignore b/.gitignore index 71947782e..803977723 100755 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /yarn.lock /npm-debug.log .DS_Store -.prettierignore \ No newline at end of file +.prettierignore +i18n-report.csv \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 79e66d642..defb06643 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "mini-css-extract-plugin": "2.9.2", "postcss-loader": "8.1.1", "readdirp": "4.0.2", + "recast": "0.23.11", "terser-webpack-plugin": "5.3.10", "thread-loader": "^4.0.4", "ts-loader": "^9.5.1", @@ -4786,6 +4787,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -12497,6 +12511,23 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -13976,6 +14007,13 @@ "dev": true, "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinycolor2": { "version": "1.6.0", "dev": true, diff --git a/package.json b/package.json index 5ed9368ab..53682f6e6 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "start-prod": "webpack serve --mode production", "build": "webpack --mode production", "test": "jest", - "lint": "eslint src" + "lint": "eslint src", + "scan-translations": "node scan-i18n-report.js" }, "dependencies": { "@babel/runtime": "7.26.0", @@ -70,6 +71,7 @@ "mini-css-extract-plugin": "2.9.2", "postcss-loader": "8.1.1", "readdirp": "4.0.2", + "recast": "0.23.11", "terser-webpack-plugin": "5.3.10", "thread-loader": "^4.0.4", "ts-loader": "^9.5.1", diff --git a/scan-i18n-report.js b/scan-i18n-report.js new file mode 100644 index 000000000..3294d34e0 --- /dev/null +++ b/scan-i18n-report.js @@ -0,0 +1,107 @@ +const fs = require('fs'); +const path = require('path'); +const recast = require('recast'); +const babelParser = require('@babel/parser'); + +const directoryToScan = './src'; +const report = []; + +function toKey(str) { + return str + .toLowerCase() + .replace(/[^a-z0-9\s]/g, '') + .replace(/\s+/g, '_') + .slice(0, 40); +} + +function scanFile(filePath) { + try { + const code = fs.readFileSync(filePath, 'utf8'); + const ast = babelParser.parse(code, { + sourceType: 'module', + plugins: [ + 'jsx', + 'typescript', + 'classProperties', + 'objectRestSpread', + 'optionalChaining', + 'nullishCoalescingOperator', + ], + errorRecovery: true, + }); + + recast.types.visit(ast, { + // Text directly inside JSX + visitJSXText(path) { + const text = path.node.value.trim(); + if (text.length > 1 && /\w/.test(text)) { + const loc = path.node.loc?.start || { line: 0 }; + report.push({ + file: filePath, + line: loc.line, + string: text, + key: toKey(text), + }); + } + this.traverse(path); + }, + + // { "hello" } style + visitJSXExpressionContainer(path) { + const expr = path.node.expression; + + // Skip expressions that call t() + if ( + expr.type === 'CallExpression' && + expr.callee.type === 'Identifier' && + expr.callee.name === 't' + ) { + return false; + } + + // Find only { "text" } expressions + if (expr.type === 'StringLiteral') { + const parent = path.parentPath.node; + if (parent.type === 'JSXElement') { + const loc = path.node.loc?.start || { line: 0 }; + report.push({ + file: filePath, + line: loc.line, + string: expr.value, + key: toKey(expr.value), + }); + } + } + + this.traverse(path); + } + }); + + } catch (err) { + console.warn(`❌ Skipping ${filePath}: ${err.message}`); + } +} + +function walk(dir) { + fs.readdirSync(dir).forEach((file) => { + const fullPath = path.join(dir, file); + if (fs.statSync(fullPath).isDirectory()) { + walk(fullPath); + } else if (/\.(js|jsx|ts|tsx)$/.test(file)) { + console.log('Scanning file:', fullPath); + scanFile(fullPath); + } + }); +} + +function writeReport() { + const header = `File,Line,Hardcoded String,Suggested Key\n`; + const rows = report.map((row) => + `"${row.file}",${row.line},"${row.string.replace(/"/g, '""')}","${row.key}"` + ); + fs.writeFileSync('i18n-report.csv', header + rows.join('\n')); + console.log('✅ Report written to i18n-report.csv'); +} + +walk(directoryToScan); +writeReport(); From aba31c8ceb45a07209fb22588abc5b02850f7135 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 23 May 2025 17:05:01 +0300 Subject: [PATCH 008/136] fix(Addons): added missing translations --- .../AddonDetails/AddonDetails.js | 6 ++++-- .../AddonDetailsModal/AddonDetailsModal.js | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js b/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js index da5fde198..c6722f294 100644 --- a/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js +++ b/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); @@ -8,6 +9,7 @@ const { default: Image } = require('stremio/components/Image'); const styles = require('./styles'); const AddonDetails = ({ className, id, name, version, logo, description, types, transportUrl, official }) => { + const t = useTranslation(); const renderLogoFallback = React.useCallback(() => ( ), []); @@ -50,7 +52,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types, { Array.isArray(types) && types.length > 0 ?
- Supported types: + {`${t('ADDON_SUPPORTED_TYPES')}:`} { types.length === 1 ? @@ -66,7 +68,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types, { !official ?
-
Using third-party add-ons will always be subject to your responsibility and the governing law of the jurisdiction you are located.
+
{t('ADDON_DISCLAIMER')}
: null diff --git a/src/components/AddonDetailsModal/AddonDetailsModal.js b/src/components/AddonDetailsModal/AddonDetailsModal.js index 332eab364..2dcea3e3b 100644 --- a/src/components/AddonDetailsModal/AddonDetailsModal.js +++ b/src/components/AddonDetailsModal/AddonDetailsModal.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const ModalDialog = require('stremio/components/ModalDialog'); const { withCoreSuspender } = require('stremio/common/CoreSuspender'); @@ -43,6 +44,7 @@ function withRemoteAndLocalAddon(AddonDetails) { } const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { + const { t } = useTranslation(); const { core } = useServices(); const platform = usePlatform(); const addonDetails = useAddonDetails(transportUrl); @@ -145,17 +147,17 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { { addonDetails.selected === null ?
- Loading addon manifest + {t('ADDON_LOADING_MANIFEST')}
: addonDetails.remoteAddon === null || addonDetails.remoteAddon.content.type === 'Loading' ?
- Loading addon manifest from {addonDetails.selected.transportUrl} + {t('ADDON_LOADING_MANIFEST_FROM', { origin: addonDetails.selected.transportUrl})}
: addonDetails.remoteAddon.content.type === 'Err' && addonDetails.localAddon === null ?
- Failed to get addon manifest from {addonDetails.selected.transportUrl} + {t('ADDON_LOADING_MANIFEST_FAILED', {origin: addonDetails.selected.transportUrl})}
{addonDetails.remoteAddon.content.content.message}
: @@ -174,17 +176,18 @@ AddonDetailsModal.propTypes = { onCloseRequest: PropTypes.func }; -const AddonDetailsModalFallback = ({ onCloseRequest }) => ( - { + const { t } = useTranslation(); + return
- Loading addon manifest + {t('ADDON_LOADING_MANIFEST')}
-
-); +
; +}; AddonDetailsModalFallback.propTypes = AddonDetailsModal.propTypes; From f24ad7d069a0f4d5e9f8d897975780f4c569e75a Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 23 May 2025 17:17:30 +0300 Subject: [PATCH 009/136] fix(Addons): added missing translation keys --- src/routes/Addons/Addons.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 91bcbd27f..2ff1dad26 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -132,12 +132,12 @@ const Addons = ({ urlParams, queryParams }) => { installedAddons.selected !== null ? installedAddons.selectable.types.length === 0 ?
- No addons ware installed! + {t('NO_ADDONS')}
: installedAddons.catalog.length === 0 ?
- No addons ware installed for that type! + {t('NO_ADDONS_FOR_TYPE')}
:
From 2de0a517e0f354d37197ee39d35780e8ccb7ecbf Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 23 May 2025 17:43:06 +0300 Subject: [PATCH 010/136] fix(Multiselect): added missing translation keys --- src/components/Multiselect/Multiselect.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Multiselect/Multiselect.js b/src/components/Multiselect/Multiselect.js index 0e353eef4..c2bf855d1 100644 --- a/src/components/Multiselect/Multiselect.js +++ b/src/components/Multiselect/Multiselect.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); @@ -11,6 +12,7 @@ const useBinaryState = require('stremio/common/useBinaryState'); const styles = require('./styles'); const Multiselect = ({ className, mode, direction, title, disabled, dataset, options, renderLabelContent, renderLabelText, onOpen, onClose, onSelect, ...props }) => { + const { t } = useTranslation(); const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false); const filteredOptions = React.useMemo(() => { return Array.isArray(options) ? @@ -122,7 +124,7 @@ const Multiselect = ({ className, mode, direction, title, disabled, dataset, opt )) :
-
No options available
+
{t('NO_OPTIONS')}
}
From 5f81804b006daab2ae4ba9d78563a5970c2fbf19 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 23 May 2025 18:03:47 +0300 Subject: [PATCH 011/136] fix(MetaDetails): added missing translation keys --- src/components/Video/Video.js | 8 +++++--- .../MetaDetails/VideosList/SeasonsBar/SeasonsBar.js | 8 ++++---- .../SeasonsBarPlaceholder/SeasonsBarPlaceholder.js | 8 +++++--- src/routes/MetaDetails/VideosList/VideosList.js | 2 +- src/routes/Player/NextVideoPopup/NextVideoPopup.js | 8 +++++--- src/routes/Player/StatisticsMenu/StatisticsMenu.js | 12 +++++++----- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/components/Video/Video.js b/src/components/Video/Video.js index 94fbefcc7..92b223e0c 100644 --- a/src/components/Video/Video.js +++ b/src/components/Video/Video.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { t } = require('i18next'); @@ -13,6 +14,7 @@ const VideoPlaceholder = require('./VideoPlaceholder'); const styles = require('./styles'); const Video = ({ className, id, title, thumbnail, season, episode, released, upcoming, watched, progress, scheduled, seasonWatched, deepLinks, onMarkVideoAsWatched, onMarkSeasonAsWatched, ...props }) => { + const { t } = useTranslation(); const routeFocused = useRouteFocused(); const profile = useProfile(); const [menuOpen, , closeMenu, toggleMenu] = useBinaryState(false); @@ -112,7 +114,7 @@ const Video = ({ className, id, title, thumbnail, season, episode, released, upc : scheduled ?
- TBA + {t('TBA')}
: null @@ -121,7 +123,7 @@ const Video = ({ className, id, title, thumbnail, season, episode, released, upc { upcoming && !watched ?
-
Upcoming
+
{t('UPCOMING')}
: null @@ -130,7 +132,7 @@ const Video = ({ className, id, title, thumbnail, season, episode, released, upc watched ?
-
Watched
+
{t('CTX_WATCHED')}
: null diff --git a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js index 29637a24c..430b15aa9 100644 --- a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js +++ b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js @@ -56,9 +56,9 @@ const SeasonsBar = ({ className, seasons, season, onSelect }) => { return (
- { selectedOption={selectedSeason} onSelect={seasonOnSelect} /> -
diff --git a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBarPlaceholder/SeasonsBarPlaceholder.js b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBarPlaceholder/SeasonsBarPlaceholder.js index d767769ec..a46d6bda6 100644 --- a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBarPlaceholder/SeasonsBarPlaceholder.js +++ b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBarPlaceholder/SeasonsBarPlaceholder.js @@ -1,24 +1,26 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); const styles = require('./styles'); const SeasonsBarPlaceholder = ({ className }) => { + const { t } = useTranslation(); return (
-
Prev
+
{t('SEASON_PREV')}
-
Season 1
+
{`${t('SEASON')} 1`}
-
Next
+
{t('SEASON_NEXT')}
diff --git a/src/routes/MetaDetails/VideosList/VideosList.js b/src/routes/MetaDetails/VideosList/VideosList.js index a47b2f517..35012ddf0 100644 --- a/src/routes/MetaDetails/VideosList/VideosList.js +++ b/src/routes/MetaDetails/VideosList/VideosList.js @@ -122,7 +122,7 @@ const VideosList = ({ className, metaItem, libraryItem, season, seasonOnSelect,
{' -
No videos found for this meta!
+
{t('ERR_NO_VIDEOS_FOR_META')}
: diff --git a/src/routes/Player/NextVideoPopup/NextVideoPopup.js b/src/routes/Player/NextVideoPopup/NextVideoPopup.js index a02d2377e..1f6bcbbe6 100644 --- a/src/routes/Player/NextVideoPopup/NextVideoPopup.js +++ b/src/routes/Player/NextVideoPopup/NextVideoPopup.js @@ -7,8 +7,10 @@ const { default: Icon } = require('@stremio/stremio-icons/react'); const { CONSTANTS, useProfile } = require('stremio/common'); const { Button, Image } = require('stremio/components'); const styles = require('./styles'); +const { useTranslation } = require('react-i18next'); const NextVideoPopup = ({ className, metaItem, nextVideo, onDismiss, onNextVideoRequested }) => { + const { t } = useTranslation(); const profile = useProfile(); const blurPosterImage = profile.settings.hideSpoilers && metaItem.type === 'series'; const watchNowButtonRef = React.useRef(null); @@ -65,7 +67,7 @@ const NextVideoPopup = ({ className, metaItem, nextVideo, onDismiss, onNextVideo { typeof metaItem?.name === 'string' ?
- Next on { metaItem.name } + {t('PLAYER_NEXT_VIDEO_TITLE_SHORT')} { metaItem.name }
: null @@ -82,11 +84,11 @@ const NextVideoPopup = ({ className, metaItem, nextVideo, onDismiss, onNextVideo
diff --git a/src/routes/Player/StatisticsMenu/StatisticsMenu.js b/src/routes/Player/StatisticsMenu/StatisticsMenu.js index 69ee2bf8d..1678e0396 100644 --- a/src/routes/Player/StatisticsMenu/StatisticsMenu.js +++ b/src/routes/Player/StatisticsMenu/StatisticsMenu.js @@ -1,20 +1,22 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const classNames = require('classnames'); const PropTypes = require('prop-types'); const styles = require('./styles.less'); const StatisticsMenu = ({ className, peers, speed, completed, infoHash }) => { + const { t } = useTranslation(); return (
- Statistics + {t('PLAYER_STATISTICS')}
- Peers + {t('PLAYER_PEERS')}
{ peers } @@ -22,7 +24,7 @@ const StatisticsMenu = ({ className, peers, speed, completed, infoHash }) => {
- Speed + {t('PLAYER_SPEED')}
{ speed } MB/s @@ -30,7 +32,7 @@ const StatisticsMenu = ({ className, peers, speed, completed, infoHash }) => {
- Completed + {t('PLAYER_COMPLETED')}
{ Math.min(completed, 100) } % @@ -39,7 +41,7 @@ const StatisticsMenu = ({ className, peers, speed, completed, infoHash }) => {
- Info Hash + {t('PLAYER_INFO_HASH')}
{ infoHash } From 789173bb5be07cd38254d4c9bfd57e5676f61579 Mon Sep 17 00:00:00 2001 From: Botzy Date: Fri, 23 May 2025 18:18:35 +0300 Subject: [PATCH 012/136] fix(Intro): added missing translation keys --- src/routes/Intro/Intro.js | 20 ++++++++++---------- src/routes/Intro/styles.less | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 6af8f0167..3ba1c3610 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -299,10 +299,10 @@ const Intro = ({ queryParams }) => { {'
- Freedom to Stream + {t('WEBSITE_SLOGAN_NEW_NEW')}
- All the Video Content You Enjoy in One Place + {t('WEBSITE_SLOGAN_ALL')}
@@ -362,7 +362,7 @@ const Intro = ({ queryParams }) => { :
- +
} { @@ -372,22 +372,22 @@ const Intro = ({ queryParams }) => { null }
{ state.form === SIGNUP_FORM ? : null @@ -395,7 +395,7 @@ const Intro = ({ queryParams }) => { { state.form === LOGIN_FORM ? : null @@ -403,7 +403,7 @@ const Intro = ({ queryParams }) => { { state.form === SIGNUP_FORM ? : null @@ -421,7 +421,7 @@ const Intro = ({ queryParams }) => {
-
Authenticating...
+
{t('AUTHENTICATING')}
diff --git a/src/routes/Intro/styles.less b/src/routes/Intro/styles.less index 31a09d54c..32fc73d79 100644 --- a/src/routes/Intro/styles.less +++ b/src/routes/Intro/styles.less @@ -101,6 +101,10 @@ color: var(--primary-foreground-color); text-align: center; } + + .uppercase { + text-transform: uppercase; + } } .submit-button, .guest-login-button, .signup-form-button, .login-form-button { From 64c553d253a240cf34f4d4b303df441ca402bc34 Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 12:06:58 +0300 Subject: [PATCH 013/136] fix(Calendar): added missing translation keys --- src/routes/Calendar/Details/Details.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/Calendar/Details/Details.tsx b/src/routes/Calendar/Details/Details.tsx index cce550ee9..a03708572 100644 --- a/src/routes/Calendar/Details/Details.tsx +++ b/src/routes/Calendar/Details/Details.tsx @@ -1,6 +1,7 @@ // Copyright (C) 2017-2024 Smart code 203358507 import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import Icon from '@stremio/stremio-icons/react'; import { Button } from 'stremio/components'; import styles from './Details.less'; @@ -11,6 +12,7 @@ type Props = { }; const Details = ({ selected, items }: Props) => { + const { t } = useTranslation(); const videos = useMemo(() => { return items.find(({ date }) => date.day === selected?.day)?.items ?? []; }, [selected, items]); @@ -33,7 +35,7 @@ const Details = ({ selected, items }: Props) => { { !videos.length ?
- No new episodes for this day + {t('CALENDAR_NO_NEW_EPISODES')}
: null From 61fdf3113e78bcd41ae8411c731d9e7e39b04a58 Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 12:07:57 +0300 Subject: [PATCH 014/136] fix(Discover): added missing translation keys --- src/routes/Discover/Discover.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/routes/Discover/Discover.js b/src/routes/Discover/Discover.js index a1472d38e..ebefdf6c0 100644 --- a/src/routes/Discover/Discover.js +++ b/src/routes/Discover/Discover.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); @@ -14,6 +15,7 @@ const styles = require('./styles'); const SCROLL_TO_BOTTOM_THRESHOLD = 400; const Discover = ({ urlParams, queryParams }) => { + const { t } = useTranslation(); const { core } = useServices(); const [discover, loadNextPage] = useDiscover(urlParams, queryParams); const [selectInputs, hasNextPage] = useSelectableInputs(discover); @@ -120,9 +122,9 @@ const Discover = ({ urlParams, queryParams }) => { { discover.catalog !== null && !discover.catalog.installed ?
-
Addon is not installed. Install now?
+
{t('ERR_ADDON_NOT_INSTALLED')}
: @@ -133,7 +135,7 @@ const Discover = ({ urlParams, queryParams }) => {
{' -
No catalog selected!
+
{t('NO_CATALOG_SELECTED')}
: From db40abfad34d814bffcd50e6ff2e058f00338bc3 Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 12:12:39 +0300 Subject: [PATCH 015/136] fix(Library): added missing translation keys --- src/routes/Library/Library.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes/Library/Library.js b/src/routes/Library/Library.js index f6310470b..1a36457db 100644 --- a/src/routes/Library/Library.js +++ b/src/routes/Library/Library.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const NotFound = require('stremio/routes/NotFound'); @@ -47,6 +48,7 @@ function withModel(Library) { } const Library = ({ model, urlParams, queryParams }) => { + const { t } = useTranslation(); const profile = useProfile(); const notifications = useNotifications(); const [library, loadNextPage] = useLibrary(model, urlParams, queryParams); @@ -86,7 +88,7 @@ const Library = ({ model, urlParams, queryParams }) => { src={require('/images/empty.png')} alt={' '} /> -
{model === 'library' ? 'Library' : 'Continue Watching'} not loaded!
+
{model === 'library' ? t('LIBRARY_NOT_LOADED') : t('BOARD_CONTINUE_WATCHING_NOT_LOADED')}
: @@ -97,7 +99,7 @@ const Library = ({ model, urlParams, queryParams }) => { src={require('/images/empty.png')} alt={' '} /> -
Empty {model === 'library' ? 'Library' : 'Continue Watching'}
+
{model === 'library' ? t('LIBRARY_EMPTY') : t('BOARD_CONTINUE_WATCHING_EMPTY')}
:
From 7b57c0f508a622593ebd1aac1ebeb6c3fb210143 Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 12:24:35 +0300 Subject: [PATCH 016/136] fix(MetaDetails): added missing translation keys --- src/routes/MetaDetails/MetaDetails.js | 8 +++++--- src/routes/MetaDetails/StreamsList/StreamsList.js | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/routes/MetaDetails/MetaDetails.js b/src/routes/MetaDetails/MetaDetails.js index 8a50b59c1..da79df285 100644 --- a/src/routes/MetaDetails/MetaDetails.js +++ b/src/routes/MetaDetails/MetaDetails.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { useServices } = require('stremio/services'); @@ -14,6 +15,7 @@ const useMetaExtensionTabs = require('./useMetaExtensionTabs'); const styles = require('./styles'); const MetaDetails = ({ urlParams, queryParams }) => { + const { t } = useTranslation(); const { core } = useServices(); const metaDetails = useMetaDetails(urlParams); const [season, setSeason] = useSeason(urlParams, queryParams); @@ -129,20 +131,20 @@ const MetaDetails = ({ urlParams, queryParams }) => {
{' -
No meta was selected!
+
{t('ERR_NO_META_SELECTED')}
: metaDetails.metaItem === null ?
{' -
No addons were requested for this meta!
+
{t('ERR_NO_ADDONS_FOR_META')}
: metaDetails.metaItem.content.type === 'Err' ?
{' -
No metadata was found!
+
{t('ERR_NO_META_FOUND')}
: metaDetails.metaItem.content.type === 'Loading' ? diff --git a/src/routes/MetaDetails/StreamsList/StreamsList.js b/src/routes/MetaDetails/StreamsList/StreamsList.js index 82ba57a51..a9665688a 100644 --- a/src/routes/MetaDetails/StreamsList/StreamsList.js +++ b/src/routes/MetaDetails/StreamsList/StreamsList.js @@ -134,7 +134,7 @@ const StreamsList = ({ className, video, type, onEpisodeSearch, ...props }) => { : null } {' -
No addons were requested for streams!
+
{t('ERR_NO_ADDONS_FOR_STREAMS')}
: props.streams.every((streams) => streams.content.type === 'Err') ? From a25d23559f7ee836f12b5b06b41882e4a3ce3110 Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 14:23:39 +0300 Subject: [PATCH 017/136] fix(NotFound): added missing translation keys --- src/routes/NotFound/NotFound.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes/NotFound/NotFound.js b/src/routes/NotFound/NotFound.js index 323dfd866..d984496bc 100644 --- a/src/routes/NotFound/NotFound.js +++ b/src/routes/NotFound/NotFound.js @@ -1,15 +1,17 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const { HorizontalNavBar, Image } = require('stremio/components'); const styles = require('./styles'); const NotFound = () => { + const { t } = useTranslation(); return (
{ src={require('/images/empty.png')} alt={' '} /> -
Page not found!
+
{t('PAGE_NOT_FOUND')}
); From a7eb1801e3eed36b5240965efe412b094c90d13b Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 14:24:59 +0300 Subject: [PATCH 018/136] fix(Settings): added missing translation keys --- src/routes/Settings/Settings.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index d6fe9b7a4..43f714feb 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -184,20 +184,20 @@ const Settings = () => {
- App Version: {process.env.VERSION} + {`${t('SETTINGS_APP_VERSION')} : ${process.env.VERSION}`}
- Build Version: {process.env.COMMIT_HASH} + {`${t('SETTINGS_BUILD_VERSION')} : ${process.env.COMMIT_HASH}`}
{ streamingServer.settings !== null && streamingServer.settings.type === 'Ready' ? -
Server Version: {streamingServer.settings.content.serverVersion}
+
{`${t('SETTINGS_SERVER_VERSION')} : ${streamingServer.settings.content.serverVersion}`}
: null } { typeof shell?.transport?.props?.shellVersion === 'string' ? -
Shell Version: {shell.transport.props.shellVersion}
+
{`${t('SETTINGS_APP_VERSION')} : ${shell.transport.props.shellVersion}`}
: null } @@ -274,7 +274,7 @@ const Settings = () => {
@@ -310,7 +310,7 @@ const Settings = () => {
-
Trakt Scrobbling
+
{t('SETTINGS_TRAKT')}
- {`${t('SETTINGS_APP_VERSION')} : ${process.env.VERSION}`} + {`${t('SETTINGS_APP_VERSION')}: ${process.env.VERSION}`}
- {`${t('SETTINGS_BUILD_VERSION')} : ${process.env.COMMIT_HASH}`} + {`${t('SETTINGS_BUILD_VERSION')}: ${process.env.COMMIT_HASH}`}
{ streamingServer.settings !== null && streamingServer.settings.type === 'Ready' ? -
{`${t('SETTINGS_SERVER_VERSION')} : ${streamingServer.settings.content.serverVersion}`}
+
{`${t('SETTINGS_SERVER_VERSION')}: ${streamingServer.settings.content.serverVersion}`}
: null } { typeof shell?.transport?.props?.shellVersion === 'string' ? -
{`${t('SETTINGS_APP_VERSION')} : ${shell.transport.props.shellVersion}`}
+
{`${t('SETTINGS_APP_VERSION')}: ${shell.transport.props.shellVersion}`}
: null } From fe871f03f1e823e75cd4c5680f9a46090c63992c Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 17:55:17 +0300 Subject: [PATCH 020/136] fix: dropdown options and titles translations --- src/common/useTranslate.js | 2 +- src/routes/Addons/useSelectableInputs.js | 4 ++-- src/routes/Discover/useSelectableInputs.js | 4 ++-- src/routes/Settings/useStreamingServerSettingsInputs.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/useTranslate.js b/src/common/useTranslate.js index 7214a4a1e..fdfb2f3fe 100644 --- a/src/common/useTranslate.js +++ b/src/common/useTranslate.js @@ -9,7 +9,7 @@ const useTranslate = () => { const string = useCallback((key) => t(key), [t]); const stringWithPrefix = useCallback((value, prefix, fallback = null) => { - const key = `${prefix}${value}`; + const key = `${prefix || ''}${value}`; const defaultValue = fallback ?? value.charAt(0).toUpperCase() + value.slice(1); return t(key, { diff --git a/src/routes/Addons/useSelectableInputs.js b/src/routes/Addons/useSelectableInputs.js index af0bb35fc..e9d276d71 100644 --- a/src/routes/Addons/useSelectableInputs.js +++ b/src/routes/Addons/useSelectableInputs.js @@ -10,8 +10,8 @@ const mapSelectableInputs = (installedAddons, remoteAddons, t) => { .concat(installedAddons.selectable.catalogs) .map(({ name, deepLinks }) => ({ value: deepLinks.addons, - label: t.stringWithPrefix(name, 'ADDON_'), - title: t.stringWithPrefix(name, 'ADDON_'), + label: t.stringWithPrefix(name.toUpperCase(), 'ADDON_'), + title: t.stringWithPrefix(name.toUpperCase(), 'ADDON_'), })), selected: remoteAddons.selectable.catalogs .concat(installedAddons.selectable.catalogs) diff --git a/src/routes/Discover/useSelectableInputs.js b/src/routes/Discover/useSelectableInputs.js index 18d317507..66178c7ee 100644 --- a/src/routes/Discover/useSelectableInputs.js +++ b/src/routes/Discover/useSelectableInputs.js @@ -46,7 +46,7 @@ const mapSelectableInputs = (discover, t) => { } }; const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => ({ - title: t.stringWithPrefix(name, 'SELECT_'), + title: t.stringWithPrefix(name), isRequired: isRequired, options: options.map(({ value, deepLinks }) => ({ label: typeof value === 'string' ? t.stringWithPrefix(value) : t.string('NONE'), @@ -62,7 +62,7 @@ const mapSelectableInputs = (discover, t) => { value })), renderLabelText: options.some(({ selected, value }) => selected && value === null) ? - () => t.stringWithPrefix(name, 'SELECT_') + () => t.stringWithPrefix(name.toUpperCase(), 'SELECT_') : null, onSelect: (event) => { diff --git a/src/routes/Settings/useStreamingServerSettingsInputs.js b/src/routes/Settings/useStreamingServerSettingsInputs.js index 1d4eee066..4bebd4d0e 100644 --- a/src/routes/Settings/useStreamingServerSettingsInputs.js +++ b/src/routes/Settings/useStreamingServerSettingsInputs.js @@ -140,7 +140,7 @@ const useStreamingServerSettingsInputs = (streamingServer) => { return { options: Object.keys(TORRENT_PROFILES) .map((profileName) => ({ - label: profileName, + label: t('TORRENT_PROFILE_' + profileName.replace(' ', '_').toUpperCase()), value: JSON.stringify(TORRENT_PROFILES[profileName]) })) .concat( From 51a1da958b05f2ec1566d02d9af453d217deffb3 Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 17:55:47 +0300 Subject: [PATCH 021/136] fix: season number translation key --- src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js | 4 ++-- .../SeasonsBar/SeasonsBarPlaceholder/SeasonsBarPlaceholder.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js index 430b15aa9..d202aafb0 100644 --- a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js +++ b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBar.js @@ -13,7 +13,7 @@ const SeasonsBar = ({ className, seasons, season, onSelect }) => { const options = React.useMemo(() => { return seasons.map((season) => ({ value: String(season), - label: season > 0 ? `${t('SEASON')} ${season}` : t('SPECIAL') + label: season > 0 ? t('SEASON_NUMBER', { season }) : t('SPECIAL') })); }, [seasons]); const selectedSeason = React.useMemo(() => { @@ -63,7 +63,7 @@ const SeasonsBar = ({ className, seasons, season, onSelect }) => { 0 ? `${t('SEASON')} ${season}` : t('SPECIAL')} + title={season > 0 ? t('SEASON_NUMBER', { season }) : t('SPECIAL')} selectedOption={selectedSeason} onSelect={seasonOnSelect} /> diff --git a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBarPlaceholder/SeasonsBarPlaceholder.js b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBarPlaceholder/SeasonsBarPlaceholder.js index a46d6bda6..4013e0b64 100644 --- a/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBarPlaceholder/SeasonsBarPlaceholder.js +++ b/src/routes/MetaDetails/VideosList/SeasonsBar/SeasonsBarPlaceholder/SeasonsBarPlaceholder.js @@ -16,7 +16,7 @@ const SeasonsBarPlaceholder = ({ className }) => {
{t('SEASON_PREV')}
-
{`${t('SEASON')} 1`}
+
{t('SEASON_NUMBER', { season: 1 })}
From 3c249e5925e98e017a3d76bf0ce0569b1e2b2bb1 Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 18:20:18 +0300 Subject: [PATCH 022/136] fix hardcoded titles with no translations --- src/common/Toast/ToastItem/ToastItem.js | 4 +++- src/components/AddonDetailsModal/AddonDetailsModal.js | 4 ++-- src/components/ColorInput/ColorInput.js | 2 +- src/components/ModalDialog/ModalDialog.js | 4 +++- src/components/SharePrompt/SharePrompt.js | 2 +- src/components/Video/Video.js | 6 +++--- src/routes/Addons/Addons.js | 4 ++-- src/routes/Discover/Discover.js | 6 +++--- src/routes/Intro/PasswordResetModal/PasswordResetModal.js | 4 +++- .../DiscreteSelectInput/DiscreteSelectInput.js | 4 +++- src/routes/Settings/Settings.js | 8 ++++---- src/routes/Settings/URLsManager/URLsManager.tsx | 4 ++-- 12 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/common/Toast/ToastItem/ToastItem.js b/src/common/Toast/ToastItem/ToastItem.js index 94b5a98b4..3a317ab1b 100644 --- a/src/common/Toast/ToastItem/ToastItem.js +++ b/src/common/Toast/ToastItem/ToastItem.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { default: Icon } = require('@stremio/stremio-icons/react'); @@ -8,6 +9,7 @@ const { Button } = require('stremio/components'); const styles = require('./styles'); const ToastItem = ({ title, message, dataset, onSelect, onClose, ...props }) => { + const { t } = useTranslation(); const type = React.useMemo(() => { return ['success', 'alert', 'info', 'error'].includes(props.type) ? props.type @@ -74,7 +76,7 @@ const ToastItem = ({ title, message, dataset, onSelect, onClose, ...props }) => null }
- diff --git a/src/components/AddonDetailsModal/AddonDetailsModal.js b/src/components/AddonDetailsModal/AddonDetailsModal.js index 2dcea3e3b..372d6edd2 100644 --- a/src/components/AddonDetailsModal/AddonDetailsModal.js +++ b/src/components/AddonDetailsModal/AddonDetailsModal.js @@ -143,7 +143,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { return addonDetails.remoteAddon?.content.type === 'Ready' ? addonDetails.remoteAddon.content.content.manifest.background : null; }, [addonDetails.remoteAddon]); return ( - + { addonDetails.selected === null ?
@@ -180,7 +180,7 @@ const AddonDetailsModalFallback = ({ onCloseRequest }) => { const { t } = useTranslation(); return
diff --git a/src/components/ColorInput/ColorInput.js b/src/components/ColorInput/ColorInput.js index afa411f63..2a32542d8 100644 --- a/src/components/ColorInput/ColorInput.js +++ b/src/components/ColorInput/ColorInput.js @@ -82,7 +82,7 @@ const ColorInput = ({ className, value, dataset, onChange, ...props }) => { } { modalOpen ? - + : diff --git a/src/components/ModalDialog/ModalDialog.js b/src/components/ModalDialog/ModalDialog.js index 5e5907c5a..b07481f69 100644 --- a/src/components/ModalDialog/ModalDialog.js +++ b/src/components/ModalDialog/ModalDialog.js @@ -1,6 +1,7 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); +const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); const { useRouteFocused, useModalsContainer } = require('stremio-router'); @@ -10,6 +11,7 @@ const { Modal } = require('stremio-router'); const styles = require('./styles'); const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequest, background, ...props }) => { + const { t } = useTranslation(); const routeFocused = useRouteFocused(); const modalsContainer = useModalsContainer(); const modalContainerRef = React.useRef(null); @@ -60,7 +62,7 @@ const ModalDialog = ({ className, title, buttons, children, dataset, onCloseRequ
-
diff --git a/src/components/SharePrompt/SharePrompt.js b/src/components/SharePrompt/SharePrompt.js index 0a9843a02..af4393b8a 100644 --- a/src/components/SharePrompt/SharePrompt.js +++ b/src/components/SharePrompt/SharePrompt.js @@ -70,7 +70,7 @@ const SharePrompt = ({ className, url }) => { onClick={selectInputContent} tabIndex={-1} /> - diff --git a/src/components/Video/Video.js b/src/components/Video/Video.js index 92b223e0c..e0313b277 100644 --- a/src/components/Video/Video.js +++ b/src/components/Video/Video.js @@ -113,7 +113,7 @@ const Video = ({ className, id, title, thumbnail, season, episode, released, upc
: scheduled ? -
+
{t('TBA')}
: @@ -147,10 +147,10 @@ const Video = ({ className, id, title, thumbnail, season, episode, released, upc const renderMenu = React.useMemo(() => function renderMenu() { return (
- -
@@ -216,7 +216,7 @@ const Addons = ({ urlParams, queryParams }) => {
{ filtersModalOpen ? - + {selectInputs.map((selectInput, index) => ( { /> ))}
-
@@ -123,7 +123,7 @@ const Discover = ({ urlParams, queryParams }) => { discover.catalog !== null && !discover.catalog.installed ?
{t('ERR_ADDON_NOT_INSTALLED')}
-
@@ -204,7 +204,7 @@ const Discover = ({ urlParams, queryParams }) => {
{ inputsModalOpen ? - + {selectInputs.map(({ title, options, selected, renderLabelText, onSelect }, index) => ( { + const { t } = useTranslation(); const routeFocused = useRouteFocused(); const platform = usePlatform(); const [error, setError] = React.useState(''); @@ -45,7 +47,7 @@ const PasswordResetModal = ({ email, onCloseRequest }) => { } }, [routeFocused]); return ( - + { + const { t } = useTranslation(); const buttonOnClick = React.useCallback((event) => { if (typeof onChange === 'function') { onChange({ @@ -22,7 +24,7 @@ const DiscreteSelectInput = ({ className, value, label, disabled, dataset, onCha return (
{label}
-
+
diff --git a/src/routes/Settings/Settings.js b/src/routes/Settings/Settings.js index 032a4cf4a..7dcb24d4d 100644 --- a/src/routes/Settings/Settings.js +++ b/src/routes/Settings/Settings.js @@ -219,9 +219,9 @@ const Settings = () => { }} />
-
+
- {profile.auth === null ? 'Anonymous user' : profile.auth.user.email} + {profile.auth === null ? t('ANONYMOUS_USER') : profile.auth.user.email}
{ @@ -273,7 +273,7 @@ const Settings = () => {
-
@@ -312,7 +312,7 @@ const Settings = () => {
{t('SETTINGS_TRAKT')}
-
- - From ce54ac9aac58eda4a7bb14e14f5a37a5a74b043c Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 18:27:19 +0300 Subject: [PATCH 023/136] feat: update translations dependency --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index defb06643..aa19dd188 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#a6be0425573917c2e82b66d28968c1a4d444cb96", + "stremio-translations": "github:Stremio/stremio-translations#212e9e01226b1f2f24c9d0374556088b71d5f21a", "url": "0.11.4", "use-long-press": "^3.2.0" }, @@ -13404,9 +13404,9 @@ } }, "node_modules/stremio-translations": { - "version": "1.44.10", - "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#a6be0425573917c2e82b66d28968c1a4d444cb96", - "integrity": "sha512-77kVE/eos/SA16kzeK7TTWmqoLF0mLPCJXjITwVIVzMHr8XyBPZFOfmiVEg4M6W1W7qYqA+dHhzicyLs7hJhlw==", + "version": "1.44.12", + "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#212e9e01226b1f2f24c9d0374556088b71d5f21a", + "integrity": "sha512-z56Y3X/6Aery5GWuOmbLfeicQI9zSE5kodNxIvYU0ovAIHAcJeTAGoqeMhMq88IXm8n1XB/lqIcau8rTCvj0IQ==", "license": "MIT" }, "node_modules/string_decoder": { diff --git a/package.json b/package.json index 53682f6e6..564a01c05 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#a6be0425573917c2e82b66d28968c1a4d444cb96", + "stremio-translations": "github:Stremio/stremio-translations#212e9e01226b1f2f24c9d0374556088b71d5f21a", "url": "0.11.4", "use-long-press": "^3.2.0" }, From 18b70402a4aeef2d95614d1eea8dbefe72e53eeb Mon Sep 17 00:00:00 2001 From: Botzy Date: Tue, 27 May 2025 18:32:20 +0300 Subject: [PATCH 024/136] fix lint --- src/components/Video/Video.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Video/Video.js b/src/components/Video/Video.js index e0313b277..5e5d30ed8 100644 --- a/src/components/Video/Video.js +++ b/src/components/Video/Video.js @@ -4,7 +4,6 @@ const React = require('react'); const { useTranslation } = require('react-i18next'); const PropTypes = require('prop-types'); const classnames = require('classnames'); -const { t } = require('i18next'); const { useRouteFocused } = require('stremio-router'); const { default: Icon } = require('@stremio/stremio-icons/react'); const { Button, Image, Popup } = require('stremio/components'); From bafa6a7ad2ffa658240d0020895f6fafa204a394 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 28 May 2025 12:20:55 +0300 Subject: [PATCH 025/136] Feat: refactor the translations scanner into a test --- scan-i18n-report.js => i18nScan.test.js | 42 +++++++++++++------------ package.json | 2 +- 2 files changed, 23 insertions(+), 21 deletions(-) rename scan-i18n-report.js => i18nScan.test.js (74%) diff --git a/scan-i18n-report.js b/i18nScan.test.js similarity index 74% rename from scan-i18n-report.js rename to i18nScan.test.js index 3294d34e0..32754b1e4 100644 --- a/scan-i18n-report.js +++ b/i18nScan.test.js @@ -4,7 +4,6 @@ const recast = require('recast'); const babelParser = require('@babel/parser'); const directoryToScan = './src'; -const report = []; function toKey(str) { return str @@ -14,7 +13,7 @@ function toKey(str) { .slice(0, 40); } -function scanFile(filePath) { +function scanFile(filePath, report) { try { const code = fs.readFileSync(filePath, 'utf8'); const ast = babelParser.parse(code, { @@ -31,7 +30,6 @@ function scanFile(filePath) { }); recast.types.visit(ast, { - // Text directly inside JSX visitJSXText(path) { const text = path.node.value.trim(); if (text.length > 1 && /\w/.test(text)) { @@ -46,11 +44,9 @@ function scanFile(filePath) { this.traverse(path); }, - // { "hello" } style visitJSXExpressionContainer(path) { const expr = path.node.expression; - // Skip expressions that call t() if ( expr.type === 'CallExpression' && expr.callee.type === 'Identifier' && @@ -59,7 +55,6 @@ function scanFile(filePath) { return false; } - // Find only { "text" } expressions if (expr.type === 'StringLiteral') { const parent = path.parentPath.node; if (parent.type === 'JSXElement') { @@ -82,26 +77,33 @@ function scanFile(filePath) { } } -function walk(dir) { +function walk(dir, report) { fs.readdirSync(dir).forEach((file) => { const fullPath = path.join(dir, file); if (fs.statSync(fullPath).isDirectory()) { - walk(fullPath); + walk(fullPath, report); } else if (/\.(js|jsx|ts|tsx)$/.test(file)) { - console.log('Scanning file:', fullPath); - scanFile(fullPath); + // console.log('📄 Scanning file:', fullPath); + scanFile(fullPath, report); } }); } -function writeReport() { - const header = `File,Line,Hardcoded String,Suggested Key\n`; - const rows = report.map((row) => - `"${row.file}",${row.line},"${row.string.replace(/"/g, '""')}","${row.key}"` - ); - fs.writeFileSync('i18n-report.csv', header + rows.join('\n')); - console.log('✅ Report written to i18n-report.csv'); -} +describe('i18n hardcoded string scan', () => { + it('should print hardcoded strings with suggested keys', () => { + const report = []; + walk(directoryToScan, report); -walk(directoryToScan); -writeReport(); + if (report.length === 0) { + console.log('✅ No hardcoded strings found.'); + } else { + console.log('🚨 Hardcoded strings found:'); + report.forEach(row => { + console.log(`File: ${row.file}, Line: ${row.line}, String: "${row.string}", Suggested Key: ${row.key}`); + }); + } + + // Optional: expect no hardcoded strings if you want the test to fail in that case + // expect(report.length).toBe(0); + }); +}); diff --git a/package.json b/package.json index 564a01c05..40a319adc 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "webpack --mode production", "test": "jest", "lint": "eslint src", - "scan-translations": "node scan-i18n-report.js" + "scan-translations": "npx jest i18nScan.test.js" }, "dependencies": { "@babel/runtime": "7.26.0", From 66ad1ea59f856a9c0fd3e3a1742922e3ae5f0950 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 28 May 2025 12:25:55 +0300 Subject: [PATCH 026/136] fix: move scan translations test to test folder --- .gitignore | 3 +-- package.json | 2 +- i18nScan.test.js => tests/i18nScan.test.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) rename i18nScan.test.js => tests/i18nScan.test.js (98%) diff --git a/.gitignore b/.gitignore index 803977723..71947782e 100755 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ /yarn.lock /npm-debug.log .DS_Store -.prettierignore -i18n-report.csv \ No newline at end of file +.prettierignore \ No newline at end of file diff --git a/package.json b/package.json index 40a319adc..35bbaa641 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "webpack --mode production", "test": "jest", "lint": "eslint src", - "scan-translations": "npx jest i18nScan.test.js" + "scan-translations": "npx jest ./tests/i18nScan.test.js" }, "dependencies": { "@babel/runtime": "7.26.0", diff --git a/i18nScan.test.js b/tests/i18nScan.test.js similarity index 98% rename from i18nScan.test.js rename to tests/i18nScan.test.js index 32754b1e4..03d3156c8 100644 --- a/i18nScan.test.js +++ b/tests/i18nScan.test.js @@ -98,7 +98,7 @@ describe('i18n hardcoded string scan', () => { console.log('✅ No hardcoded strings found.'); } else { console.log('🚨 Hardcoded strings found:'); - report.forEach(row => { + report.forEach((row) => { console.log(`File: ${row.file}, Line: ${row.line}, String: "${row.string}", Suggested Key: ${row.key}`); }); } From 4f9cd612860d99a2a1ebbbd76c96b59a3f7cc80d Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 28 May 2025 13:36:21 +0300 Subject: [PATCH 027/136] fix(Discover): dropdown options translations --- src/common/useTranslate.js | 2 +- src/routes/Discover/useSelectableInputs.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/useTranslate.js b/src/common/useTranslate.js index fdfb2f3fe..7214a4a1e 100644 --- a/src/common/useTranslate.js +++ b/src/common/useTranslate.js @@ -9,7 +9,7 @@ const useTranslate = () => { const string = useCallback((key) => t(key), [t]); const stringWithPrefix = useCallback((value, prefix, fallback = null) => { - const key = `${prefix || ''}${value}`; + const key = `${prefix}${value}`; const defaultValue = fallback ?? value.charAt(0).toUpperCase() + value.slice(1); return t(key, { diff --git a/src/routes/Discover/useSelectableInputs.js b/src/routes/Discover/useSelectableInputs.js index 66178c7ee..97d9e7192 100644 --- a/src/routes/Discover/useSelectableInputs.js +++ b/src/routes/Discover/useSelectableInputs.js @@ -46,10 +46,10 @@ const mapSelectableInputs = (discover, t) => { } }; const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => ({ - title: t.stringWithPrefix(name), + title: t(name), isRequired: isRequired, options: options.map(({ value, deepLinks }) => ({ - label: typeof value === 'string' ? t.stringWithPrefix(value) : t.string('NONE'), + label: typeof value === 'string' ? t(value) : t.string('NONE'), value: JSON.stringify({ href: deepLinks.discover, value From d27f92fb01be8dbf97fa8e0e4e25115f5b8edc24 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 28 May 2025 16:30:04 +0300 Subject: [PATCH 028/136] fix(Discover): use correct translation func --- src/routes/Discover/useSelectableInputs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/Discover/useSelectableInputs.js b/src/routes/Discover/useSelectableInputs.js index 97d9e7192..89963cff3 100644 --- a/src/routes/Discover/useSelectableInputs.js +++ b/src/routes/Discover/useSelectableInputs.js @@ -46,10 +46,10 @@ const mapSelectableInputs = (discover, t) => { } }; const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => ({ - title: t(name), + title: t.string(name), isRequired: isRequired, options: options.map(({ value, deepLinks }) => ({ - label: typeof value === 'string' ? t(value) : t.string('NONE'), + label: typeof value === 'string' ? t.string(value) : t.string('NONE'), value: JSON.stringify({ href: deepLinks.discover, value From e704abcb03fa2f4505327d4324472e0394f8c4a1 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 28 May 2025 17:16:08 +0300 Subject: [PATCH 029/136] feat: make translation test fail if hardcoded strings are found --- tests/i18nScan.test.js | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/tests/i18nScan.test.js b/tests/i18nScan.test.js index 03d3156c8..5f2964a9a 100644 --- a/tests/i18nScan.test.js +++ b/tests/i18nScan.test.js @@ -88,22 +88,20 @@ function walk(dir, report) { } }); } +const report = []; -describe('i18n hardcoded string scan', () => { - it('should print hardcoded strings with suggested keys', () => { - const report = []; - walk(directoryToScan, report); +walk(directoryToScan, report); - if (report.length === 0) { - console.log('✅ No hardcoded strings found.'); - } else { - console.log('🚨 Hardcoded strings found:'); - report.forEach((row) => { - console.log(`File: ${row.file}, Line: ${row.line}, String: "${row.string}", Suggested Key: ${row.key}`); - }); - } - - // Optional: expect no hardcoded strings if you want the test to fail in that case - // expect(report.length).toBe(0); +if (report.length !== 0) { + describe.each(report)('Missing translation key', (entry) => { + it(`should not have "${entry.string}" in ${entry.file} at line ${entry.line}`, () => { + expect(entry.string).toBeFalsy(); + }); }); -}); +} else { + describe('Missing translation key', () => { + it('No hardcoded strings found', () => { + expect(true).toBe(true); // or just skip + }); + }); +} From d692d090410c57e5406270c22e3ec94fce7ee806 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 28 May 2025 17:17:34 +0300 Subject: [PATCH 030/136] fix: replace all remaining untranslated strings with translation keys --- .../AddonDetailsModal/AddonDetails/AddonDetails.js | 6 +++--- src/components/Checkbox/Checkbox.less | 1 + src/components/Checkbox/Checkbox.tsx | 1 - src/routes/Addons/Addon/Addon.js | 2 +- src/routes/Addons/Addons.js | 2 +- src/routes/Player/StatisticsMenu/StatisticsMenu.js | 2 +- src/routes/Settings/URLsManager/URLsManager.tsx | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js b/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js index c6722f294..78c744b96 100644 --- a/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js +++ b/src/components/AddonDetailsModal/AddonDetails/AddonDetails.js @@ -9,7 +9,7 @@ const { default: Image } = require('stremio/components/Image'); const styles = require('./styles'); const AddonDetails = ({ className, id, name, version, logo, description, types, transportUrl, official }) => { - const t = useTranslation(); + const { t } = useTranslation(); const renderLogoFallback = React.useCallback(() => ( ), []); @@ -26,7 +26,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types, {typeof name === 'string' && name.length > 0 ? name : id} { typeof version === 'string' && version.length > 0 ? - v. {version} + {t('ADDON_VERSION_SHORT', {version})} : null } @@ -43,7 +43,7 @@ const AddonDetails = ({ className, id, name, version, logo, description, types, { typeof transportUrl === 'string' && transportUrl.length > 0 ?
- URL: + {`${t('URL')}:`} {transportUrl}
: diff --git a/src/components/Checkbox/Checkbox.less b/src/components/Checkbox/Checkbox.less index a84244ce9..9276990b3 100644 --- a/src/components/Checkbox/Checkbox.less +++ b/src/components/Checkbox/Checkbox.less @@ -23,6 +23,7 @@ .link { font-size: 0.9rem; color: var(--primary-accent-color); + margin-left: 0.5rem; &:hover { text-decoration: underline; diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index da4ae33eb..b252006eb 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -80,7 +80,6 @@ const Checkbox = React.forwardRef(({ name, disabled, cl
{label} - {' '} { href && link ?
{ typeof version === 'string' && version.length > 0 ? -
v.{version}
+
{t('ADDON_VERSION_SHORT', {version})}
: null } diff --git a/src/routes/Addons/Addons.js b/src/routes/Addons/Addons.js index 17fc2a1b5..c33c22f9a 100644 --- a/src/routes/Addons/Addons.js +++ b/src/routes/Addons/Addons.js @@ -265,7 +265,7 @@ const Addons = ({ urlParams, queryParams }) => { {typeof sharedAddon.manifest.name === 'string' && sharedAddon.manifest.name.length > 0 ? sharedAddon.manifest.name : sharedAddon.manifest.id} { typeof sharedAddon.manifest.version === 'string' && sharedAddon.manifest.version.length > 0 ? - v. {sharedAddon.manifest.version} + {t('ADDON_VERSION_SHORT', { version: sharedAddon.manifest.version })} : null } diff --git a/src/routes/Player/StatisticsMenu/StatisticsMenu.js b/src/routes/Player/StatisticsMenu/StatisticsMenu.js index 1678e0396..b5f5232ea 100644 --- a/src/routes/Player/StatisticsMenu/StatisticsMenu.js +++ b/src/routes/Player/StatisticsMenu/StatisticsMenu.js @@ -27,7 +27,7 @@ const StatisticsMenu = ({ className, peers, speed, completed, infoHash }) => { {t('PLAYER_SPEED')}
- { speed } MB/s + {`${speed} ${t('MB_S')}`}
diff --git a/src/routes/Settings/URLsManager/URLsManager.tsx b/src/routes/Settings/URLsManager/URLsManager.tsx index b0907ed9e..b0d1245eb 100644 --- a/src/routes/Settings/URLsManager/URLsManager.tsx +++ b/src/routes/Settings/URLsManager/URLsManager.tsx @@ -30,7 +30,7 @@ const URLsManager = () => { return (
-
URL
+
{t('URL')}
{t('STATUS')}
From 8768b9ced30512c005dcc831efea5399ee2b9800 Mon Sep 17 00:00:00 2001 From: Botzy Date: Wed, 28 May 2025 19:02:28 +0300 Subject: [PATCH 031/136] added missing modal button labels and \input placeholder translation keys --- package-lock.json | 6 +++--- package.json | 2 +- .../AddonDetailsModal/AddonDetailsModal.js | 8 ++++---- src/components/ColorInput/ColorInput.js | 2 +- src/routes/Intro/Intro.js | 16 ++++++++-------- .../PasswordResetModal/PasswordResetModal.js | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa19dd188..854f2b0b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#212e9e01226b1f2f24c9d0374556088b71d5f21a", + "stremio-translations": "github:Stremio/stremio-translations#1f0b73ff5605b1afde18636f82b8fd45d299a426", "url": "0.11.4", "use-long-press": "^3.2.0" }, @@ -13405,8 +13405,8 @@ }, "node_modules/stremio-translations": { "version": "1.44.12", - "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#212e9e01226b1f2f24c9d0374556088b71d5f21a", - "integrity": "sha512-z56Y3X/6Aery5GWuOmbLfeicQI9zSE5kodNxIvYU0ovAIHAcJeTAGoqeMhMq88IXm8n1XB/lqIcau8rTCvj0IQ==", + "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#1f0b73ff5605b1afde18636f82b8fd45d299a426", + "integrity": "sha512-1jvv4VRWNiFl+GivV6fXpq9QSIpElMUzGMG3jFxkJqvvZeYZpUsK/D4Bd9lR0jBSPYhwpLMZgFoGHWx3ABg+Ig==", "license": "MIT" }, "node_modules/string_decoder": { diff --git a/package.json b/package.json index 35bbaa641..d1428b7a3 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "react-i18next": "^15.1.3", "react-is": "18.3.1", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#212e9e01226b1f2f24c9d0374556088b71d5f21a", + "stremio-translations": "github:Stremio/stremio-translations#1f0b73ff5605b1afde18636f82b8fd45d299a426", "url": "0.11.4", "use-long-press": "^3.2.0" }, diff --git a/src/components/AddonDetailsModal/AddonDetailsModal.js b/src/components/AddonDetailsModal/AddonDetailsModal.js index 372d6edd2..a89a68b77 100644 --- a/src/components/AddonDetailsModal/AddonDetailsModal.js +++ b/src/components/AddonDetailsModal/AddonDetailsModal.js @@ -51,7 +51,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { const modalButtons = React.useMemo(() => { const cancelButton = { className: styles['cancel-button'], - label: 'Cancel', + label: t('BUTTON_CANCEL'), props: { onClick: (event) => { if (typeof onCloseRequest === 'function') { @@ -69,7 +69,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { addonDetails.remoteAddon.content.content.manifest.behaviorHints.configurable ? { className: styles['configure-button'], - label: 'Configure', + label: t('ADDON_CONFIGURE'), props: { onClick: (event) => { platform.openExternal(transportUrl.replace('manifest.json', 'configure')); @@ -88,7 +88,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { const toggleButton = addonDetails.localAddon !== null ? { className: styles['uninstall-button'], - label: 'Uninstall', + label: t('ADDON_UNINSTALL'), props: { onClick: (event) => { core.transport.dispatch({ @@ -115,7 +115,7 @@ const AddonDetailsModal = ({ transportUrl, onCloseRequest }) => { { className: styles['install-button'], - label: 'Install', + label: t('ADDON_INSTALL'), props: { onClick: (event) => { core.transport.dispatch({ diff --git a/src/components/ColorInput/ColorInput.js b/src/components/ColorInput/ColorInput.js index 2a32542d8..5bedf7161 100644 --- a/src/components/ColorInput/ColorInput.js +++ b/src/components/ColorInput/ColorInput.js @@ -56,7 +56,7 @@ const ColorInput = ({ className, value, dataset, onChange, ...props }) => { }; return [ { - label: 'Select', + label: t('SELECT'), props: { 'data-autofocus': true, onClick: selectButtonOnClick diff --git a/src/routes/Intro/Intro.js b/src/routes/Intro/Intro.js index 3ba1c3610..48acadcd4 100644 --- a/src/routes/Intro/Intro.js +++ b/src/routes/Intro/Intro.js @@ -311,7 +311,7 @@ const Intro = ({ queryParams }) => { ref={emailRef} className={styles['credentials-text-input']} type={'email'} - placeholder={'Email'} + placeholder={t('EMAIL')} value={state.email} onChange={emailOnChange} onSubmit={emailOnSubmit} @@ -320,7 +320,7 @@ const Intro = ({ queryParams }) => { ref={passwordRef} className={styles['credentials-text-input']} type={'password'} - placeholder={'Password'} + placeholder={t('PASSWORD')} value={state.password} onChange={passwordOnChange} onSubmit={passwordOnSubmit} @@ -332,30 +332,30 @@ const Intro = ({ queryParams }) => { ref={confirmPasswordRef} className={styles['credentials-text-input']} type={'password'} - placeholder={'Confirm Password'} + placeholder={t('PASSWORD_CONFIRM')} value={state.confirmPassword} onChange={confirmPasswordOnChange} onSubmit={confirmPasswordOnSubmit} /> diff --git a/src/routes/Intro/PasswordResetModal/PasswordResetModal.js b/src/routes/Intro/PasswordResetModal/PasswordResetModal.js index 5158f0136..8c69c1489 100644 --- a/src/routes/Intro/PasswordResetModal/PasswordResetModal.js +++ b/src/routes/Intro/PasswordResetModal/PasswordResetModal.js @@ -25,13 +25,13 @@ const PasswordResetModal = ({ email, onCloseRequest }) => { return [ { className: styles['cancel-button'], - label: 'Cancel', + label: t('BUTTON_CANCEL'), props: { onClick: onCloseRequest } }, { - label: 'Send', + label: t('SEND'), props: { onClick: goToPasswordReset } From 015e770e42e818a77ba8189c2d3f70f8d2cc53aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 21:49:25 +0000 Subject: [PATCH 032/136] chore(deps): bump actions/github-script from 6 to 7 Bumps [actions/github-script](https://github.com/actions/github-script) from 6 to 7. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/auto_assign.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto_assign.yml b/.github/workflows/auto_assign.yml index 87e9c8f37..dfbddb6a4 100644 --- a/.github/workflows/auto_assign.yml +++ b/.github/workflows/auto_assign.yml @@ -14,7 +14,7 @@ jobs: # Auto assign PR to author - name: Auto Assign PR to Author if: github.event_name == 'pull_request' - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -33,7 +33,7 @@ jobs: - name: Label PRs and Issues env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From faee4166c3dd8e5dd4a4874f64ab64749b6fd2f5 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 3 Jun 2025 14:58:19 +0300 Subject: [PATCH 033/136] feat(MetaPreview): impl user item ratings --- src/components/MetaPreview/MetaPreview.js | 31 ++++++++++++++-- src/components/MetaPreview/useRating.ts | 44 +++++++++++++++++++++++ src/routes/MetaDetails/MetaDetails.js | 1 + src/types/models/MetaDetails.d.ts | 2 ++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/components/MetaPreview/useRating.ts diff --git a/src/components/MetaPreview/MetaPreview.js b/src/components/MetaPreview/MetaPreview.js index c0e9fb165..b1124e9ab 100644 --- a/src/components/MetaPreview/MetaPreview.js +++ b/src/components/MetaPreview/MetaPreview.js @@ -17,6 +17,7 @@ const ActionButton = require('./ActionButton'); const MetaLinks = require('./MetaLinks'); const MetaPreviewPlaceholder = require('./MetaPreviewPlaceholder'); const styles = require('./styles'); +const { default: useRating } = require('./useRating'); const ALLOWED_LINK_REDIRECTS = [ routesRegexp.search.regexp, @@ -24,8 +25,9 @@ const ALLOWED_LINK_REDIRECTS = [ routesRegexp.metadetails.regexp ]; -const MetaPreview = React.forwardRef(({ className, compact, name, logo, background, runtime, releaseInfo, released, description, deepLinks, links, trailerStreams, inLibrary, toggleInLibrary }, ref) => { +const MetaPreview = React.forwardRef(({ className, compact, name, logo, background, runtime, releaseInfo, released, description, deepLinks, links, trailerStreams, inLibrary, toggleInLibrary, metaDetails }, ref) => { const { t } = useTranslation(); + const { onLiked, onLoved, like } = useRating(metaDetails); const [shareModalOpen, openShareModal, closeShareModal] = useBinaryState(false); const linksGroups = React.useMemo(() => { return Array.isArray(links) ? @@ -220,6 +222,30 @@ const MetaPreview = React.forwardRef(({ className, compact, name, logo, backgrou : null } + { + !compact ? + + : + null + } + { + !compact ? + + : + null + } { typeof showHref === 'string' && compact ? { + const { core } = useServices(); + + const like = useMemo(() => { + return metaDetails.like !== null && metaDetails.like.type === 'Ready' ? metaDetails.like.content : null; + }, [metaDetails.like]); + const setRating = useCallback( + (status: string) => { + if (!metaDetails.metaItem || !metaDetails.metaItem.content) { + return; + } + core.transport.dispatch({ + action: 'MetaDetails', + args: { + action: 'Rate', + args: { + id: metaDetails?.metaItem.content.content?.id, + status: status, + }, + }, + }); + }, + [metaDetails], + ); + + const onLiked = () => { + setRating(like === 'liked' ? null : 'liked'); + }; + + const onLoved = () => { + setRating(like === 'loved' ? null : 'loved'); + }; + + return { + onLiked, + onLoved, + like, + }; +}; + +export default useRating; diff --git a/src/routes/MetaDetails/MetaDetails.js b/src/routes/MetaDetails/MetaDetails.js index 8a50b59c1..bb8789be0 100644 --- a/src/routes/MetaDetails/MetaDetails.js +++ b/src/routes/MetaDetails/MetaDetails.js @@ -166,6 +166,7 @@ const MetaDetails = ({ urlParams, queryParams }) => { trailerStreams={metaDetails.metaItem.content.content.trailerStreams} inLibrary={metaDetails.metaItem.content.content.inLibrary} toggleInLibrary={metaDetails.metaItem.content.content.inLibrary ? removeFromLibrary : addToLibrary} + metaDetails={metaDetails} /> } diff --git a/src/types/models/MetaDetails.d.ts b/src/types/models/MetaDetails.d.ts index c7eeafb1b..df615da3e 100644 --- a/src/types/models/MetaDetails.d.ts +++ b/src/types/models/MetaDetails.d.ts @@ -24,4 +24,6 @@ type MetaDetails = { content: Loadable }[], title: string | null, + like: Loadable | null, + sentLike: Loadable | null }; From ad680ca2a57f9cc4c3233ca9f6df7b6695090a51 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Tue, 3 Jun 2025 15:05:43 +0300 Subject: [PATCH 034/136] refactor(useRating): simplify --- src/components/MetaPreview/useRating.ts | 40 ++++++++++++------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/components/MetaPreview/useRating.ts b/src/components/MetaPreview/useRating.ts index d0ae46924..117417815 100644 --- a/src/components/MetaPreview/useRating.ts +++ b/src/components/MetaPreview/useRating.ts @@ -5,34 +5,32 @@ const useRating = (metaDetails: MetaDetails) => { const { core } = useServices(); const like = useMemo(() => { - return metaDetails.like !== null && metaDetails.like.type === 'Ready' ? metaDetails.like.content : null; + return metaDetails.like?.type === 'Ready' ? metaDetails.like.content : null; }, [metaDetails.like]); - const setRating = useCallback( - (status: string) => { - if (!metaDetails.metaItem || !metaDetails.metaItem.content) { - return; - } - core.transport.dispatch({ - action: 'MetaDetails', + + const setRating = useCallback((status: LoadableError | null) => { + const metaId = metaDetails.metaItem?.content?.content?.id; + if (!metaId) return; + + core.transport.dispatch({ + action: 'MetaDetails', + args: { + action: 'Rate', args: { - action: 'Rate', - args: { - id: metaDetails?.metaItem.content.content?.id, - status: status, - }, + id: metaId, + status, }, - }); - }, - [metaDetails], - ); + }, + }); + }, [metaDetails.metaItem?.content?.content?.id]); - const onLiked = () => { + const onLiked = useCallback(() => { setRating(like === 'liked' ? null : 'liked'); - }; + }, [like, setRating]); - const onLoved = () => { + const onLoved = useCallback(() => { setRating(like === 'loved' ? null : 'loved'); - }; + }, [like, setRating]); return { onLiked, From beb873e34ed3fe45fec7d1ce559856b3fb858e55 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Wed, 4 Jun 2025 14:56:11 +0300 Subject: [PATCH 035/136] refactor(MetaPreview): discover fix --- src/components/MetaPreview/MetaPreview.js | 50 +++++++++++------------ src/components/MetaPreview/useRating.ts | 6 +++ 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/components/MetaPreview/MetaPreview.js b/src/components/MetaPreview/MetaPreview.js index b1124e9ab..13b922736 100644 --- a/src/components/MetaPreview/MetaPreview.js +++ b/src/components/MetaPreview/MetaPreview.js @@ -27,7 +27,7 @@ const ALLOWED_LINK_REDIRECTS = [ const MetaPreview = React.forwardRef(({ className, compact, name, logo, background, runtime, releaseInfo, released, description, deepLinks, links, trailerStreams, inLibrary, toggleInLibrary, metaDetails }, ref) => { const { t } = useTranslation(); - const { onLiked, onLoved, like } = useRating(metaDetails); + const { onLiked, onLoved } = useRating(metaDetails); const [shareModalOpen, openShareModal, closeShareModal] = useBinaryState(false); const linksGroups = React.useMemo(() => { return Array.isArray(links) ? @@ -222,30 +222,6 @@ const MetaPreview = React.forwardRef(({ className, compact, name, logo, backgrou : null } - { - !compact ? - - : - null - } - { - !compact ? - - : - null - } { typeof showHref === 'string' && compact ? + : + null + } + { + !compact ? + + : + null + }
); diff --git a/src/components/MetaPreview/useRating.ts b/src/components/MetaPreview/useRating.ts index 117417815..b954aabec 100644 --- a/src/components/MetaPreview/useRating.ts +++ b/src/components/MetaPreview/useRating.ts @@ -2,6 +2,12 @@ import { useMemo, useCallback } from 'react'; import { useServices } from 'stremio/services'; const useRating = (metaDetails: MetaDetails) => { + if (!metaDetails) { + return { + onLiked: () => {}, + onLoved: () => {}, + }; + } const { core } = useServices(); const like = useMemo(() => { From 38d5290d91a8547b7a8124e10d20d857ec0ec5e4 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Thu, 5 Jun 2025 16:34:03 +0300 Subject: [PATCH 036/136] refactor(MetaPreview): usage of ratings --- .../MetaPreview/ActionButton/ActionButton.js | 9 +++-- .../MetaPreview/ActionButton/styles.less | 34 ++++++++++++++++ src/components/MetaPreview/MetaPreview.js | 23 +++++++---- src/components/MetaPreview/styles.less | 20 ++++++++++ src/components/MetaPreview/useRating.ts | 39 ++++++++++--------- src/routes/Discover/Discover.js | 2 + src/routes/MetaDetails/MetaDetails.js | 3 +- 7 files changed, 99 insertions(+), 31 deletions(-) diff --git a/src/components/MetaPreview/ActionButton/ActionButton.js b/src/components/MetaPreview/ActionButton/ActionButton.js index 520c0483b..0bda9a7f0 100644 --- a/src/components/MetaPreview/ActionButton/ActionButton.js +++ b/src/components/MetaPreview/ActionButton/ActionButton.js @@ -8,9 +8,9 @@ const { Button } = require('stremio/components'); const styles = require('./styles'); const { Tooltip } = require('stremio/common/Tooltips'); -const ActionButton = ({ className, icon, label, tooltip, ...props }) => { +const ActionButton = ({ className, icon, label, tooltip, showLabel = true, ...props }) => { return ( -
); diff --git a/src/components/MetaPreview/Ratings/Ratings.less b/src/components/MetaPreview/Ratings/Ratings.less index 199fb1b82..bc0a4e647 100644 --- a/src/components/MetaPreview/Ratings/Ratings.less +++ b/src/components/MetaPreview/Ratings/Ratings.less @@ -38,6 +38,14 @@ opacity: 1; } } + + &.disabled { + cursor: not-allowed; + + .icon { + opacity: 0.3; + } + } } } diff --git a/src/components/MetaPreview/Ratings/Ratings.tsx b/src/components/MetaPreview/Ratings/Ratings.tsx index 1669f3801..861776f32 100644 --- a/src/components/MetaPreview/Ratings/Ratings.tsx +++ b/src/components/MetaPreview/Ratings/Ratings.tsx @@ -1,6 +1,6 @@ // Copyright (C) 2017-2025 Smart code 203358507 -import React from 'react'; +import React, { useMemo } from 'react'; import useRating from './useRating'; import styles from './Ratings.less'; import Icon from '@stremio/stremio-icons/react'; @@ -19,13 +19,14 @@ type Props = { const Ratings = ({ metaId, like, className }: Props) => { const { onLiked, onLoved, liked, loved } = useRating(metaId, like); + const disabled = useMemo(() => like?.type !== 'Ready', [like]); return (
-
+
-
+
From 48851a62cb2ba995da6a299d7fc4f5586c7dfa96 Mon Sep 17 00:00:00 2001 From: "Timothy Z." Date: Fri, 6 Jun 2025 17:14:58 +0300 Subject: [PATCH 046/136] refactor(Ratings): disabled styles --- src/components/MetaPreview/Ratings/Ratings.less | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/MetaPreview/Ratings/Ratings.less b/src/components/MetaPreview/Ratings/Ratings.less index bc0a4e647..bc0ea05c8 100644 --- a/src/components/MetaPreview/Ratings/Ratings.less +++ b/src/components/MetaPreview/Ratings/Ratings.less @@ -41,10 +41,6 @@ &.disabled { cursor: not-allowed; - - .icon { - opacity: 0.3; - } } } } From d329139abe8f38c3124c86e4f6eaec0d30e375d1 Mon Sep 17 00:00:00 2001 From: Jordan Clarke Date: Sun, 8 Jun 2025 01:14:43 +0100 Subject: [PATCH 047/136] Fix for #916 - Added System priority and Alphabetic Ordering - First Commit Ever :) --- .../MultiselectMenu/Dropdown/Dropdown.less | 79 +++++++++++-------- .../MultiselectMenu/Dropdown/Dropdown.tsx | 41 +++++++++- 2 files changed, 84 insertions(+), 36 deletions(-) diff --git a/src/components/MultiselectMenu/Dropdown/Dropdown.less b/src/components/MultiselectMenu/Dropdown/Dropdown.less index 3bea17d22..71ca36e96 100644 --- a/src/components/MultiselectMenu/Dropdown/Dropdown.less +++ b/src/components/MultiselectMenu/Dropdown/Dropdown.less @@ -1,44 +1,55 @@ -// Copyright (C) 2017-2024 Smart code 203358507 + // Copyright (C) 2017-2024 Smart code 203358507 -@import (reference) '~stremio/common/screen-sizes.less'; + @import (reference) '~stremio/common/screen-sizes.less'; -@parent-height: 10rem; + @parent-height: 10rem; -.dropdown { - background: var(--modal-background-color); - display: none; - position: absolute; - width: 100%; - top: 100%; - left: 0; - z-index: 10; - box-shadow: var(--outer-glow); - border-radius: var(--border-radius); - overflow: hidden; - &.open { - display: block; - max-height: calc(3.3rem * 7); - overflow: auto; - } - .back-button { - display: flex; - align-items: center; - gap: 0 0.5rem; - padding: 0.75rem; - color: var(--primary-foreground-color); - .back-button-icon { - width: 1.5rem; - } - } -} - -@media (orientation: landscape) and (max-width: @xsmall) { .dropdown { + background: var(--modal-background-color); + display: none; + position: absolute; + width: 100%; + top: 100%; + left: 0; + z-index: 10; + box-shadow: var(--outer-glow); + border-radius: var(--border-radius); + overflow: hidden; + &.open { - max-height: calc(100dvh - var(--horizontal-nav-bar-size) - @parent-height); + display: block; + max-height: calc(3.3rem * 7); + overflow: auto; + } + + .back-button { + display: flex; + align-items: center; + gap: 0 0.5rem; + padding: 0.75rem; + color: var(--primary-foreground-color); + + .back-button-icon { + width: 1.5rem; + } + } + + .separator-after { + border-bottom: thin solid var(--overlay-color); } } -} \ No newline at end of file + + + + @media (orientation: landscape) and (max-width: @xsmall) { + .dropdown { + &.open { + max-height: calc(100dvh - var(--horizontal-nav-bar-size) - @parent-height); + } + } + } + + diff --git a/src/components/MultiselectMenu/Dropdown/Dropdown.tsx b/src/components/MultiselectMenu/Dropdown/Dropdown.tsx index 411ef6ab5..3aebce926 100644 --- a/src/components/MultiselectMenu/Dropdown/Dropdown.tsx +++ b/src/components/MultiselectMenu/Dropdown/Dropdown.tsx @@ -61,9 +61,45 @@ const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props : null } - {options - .filter((option: MultiselectMenuOption) => !option.hidden) + + {options + .filter((option: MultiselectMenuOption) => !option.hidden) + .sort((a, b) => { + + const userLocale = navigator.language || 'en'; + const userLangCode = userLocale.split('-')[0]; + + const getPriority = (option: MultiselectMenuOption) => { + + const value = String(option.value); + + // Check if the value matches user's language, if yes put at top of list + const matchesUser = value === userLocale || value.startsWith(userLangCode + '-'); + if (matchesUser) return 1; + + + // Check if it's English, put at the second position + const isEnglish = value.startsWith('en-') || value === 'en'; + if (isEnglish) return 2; + + return 3; // Everything else + }; + + const aPriority = getPriority(a); + const bPriority = getPriority(b); + + //Lowest number is ranked highest + if (aPriority !== bPriority) { + return aPriority - bPriority; + } + + // Same priority = alphabetical by label eg "english", "french" + return a.label.localeCompare(b.label); + }) .map((option: MultiselectMenuOption) => ( +
)) }
From bd5a8e988f5c514bf2689c438ee3facabbeaa853 Mon Sep 17 00:00:00 2001 From: Jordan Clarke Date: Sun, 8 Jun 2025 13:06:14 +0100 Subject: [PATCH 048/136] Improved Fix for Stremio#916 - Added System priority and Alphabetic Ordering on other dropdowns. --- .../MultiselectMenu/Dropdown/Dropdown.tsx | 75 ++++++++++++++++--- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/src/components/MultiselectMenu/Dropdown/Dropdown.tsx b/src/components/MultiselectMenu/Dropdown/Dropdown.tsx index 3aebce926..216261f79 100644 --- a/src/components/MultiselectMenu/Dropdown/Dropdown.tsx +++ b/src/components/MultiselectMenu/Dropdown/Dropdown.tsx @@ -17,6 +17,52 @@ type Props = { onSelect: (value: string | number) => void; }; + + + +const REVERSE_LANG_CODE_MAP: {[key:string]:string} = { + 'ar': 'ara', + 'bg': 'bul', + 'bn': 'ben', + 'ca': 'cat', + 'cs': 'ces', + 'da': 'dan', + 'de': 'deu', + 'el': 'ell', + 'en': 'eng', + 'eo': 'epo', + 'es': 'spa', + 'eu': 'eus', + 'fa': 'fas', + 'fr': 'fre', + 'he': 'heb', + 'hi': 'hin', + 'hr': 'hrv', + 'hu': 'hun', + 'id': 'ind', + 'it': 'ita', + 'ja': 'jpn', + 'ko': 'kor', + 'mk': 'mkd', + 'my': 'mya', + 'nb': 'nob', + 'nl': 'nld', + 'nn': 'nno', + 'pl': 'pol', + 'pt': 'por', + 'ru': 'rus', + 'sv': 'swe', + 'sl': 'slv', + 'sr': 'srp', + 'te': 'tel', + 'tr': 'tur', + 'uk': 'ukr', + 'vi': 'vie', + 'zh': 'zho' +}; + +// Usage: O(1) lookup + const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props) => { const { t } = useTranslation(); const optionsRef = useRef(new Map()); @@ -66,21 +112,28 @@ const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props .filter((option: MultiselectMenuOption) => !option.hidden) .sort((a, b) => { - const userLocale = navigator.language || 'en'; - const userLangCode = userLocale.split('-')[0]; + const userLocale2 = navigator.language || 'en-US'; + const userLocale:string = REVERSE_LANG_CODE_MAP[userLocale2.split('-')[0]] || 'eng'; // this is 3 leter code + // console.log(`USERBEFORE : ${userLocale2}, USERAFTER : ${userLocale}`) + + // const userLangCode = userLocale.split('-')[0]; + + // console.log(`VALUE : ${a.value}, LABEL : ${a.label}`); + + const getPriority = (option: MultiselectMenuOption) => { - - const value = String(option.value); - + + const value2 = String(option.value); + const value = value2.length == 3 ? value2 : (REVERSE_LANG_CODE_MAP[value2.split('-')[0]] || 'eng'); + + // console.log(`OPTION VALUE: ${value}`) // Check if the value matches user's language, if yes put at top of list - const matchesUser = value === userLocale || value.startsWith(userLangCode + '-'); - if (matchesUser) return 1; + if (value === userLocale) return 1; // Check if it's English, put at the second position - const isEnglish = value.startsWith('en-') || value === 'en'; - if (isEnglish) return 2; + if (value == 'eng') return 2; return 3; // Everything else }; @@ -98,8 +151,8 @@ const Dropdown = ({ level, setLevel, options, onSelect, value, menuOpen }: Props }) .map((option: MultiselectMenuOption) => (
+ key={`${String(option.label)}-${String(option.value)}`} + className={String(option.label) === 'English' ? styles['separator-after'] : ''}>