diff --git a/package-lock.json b/package-lock.json index 8fbf11e17..7b6facbf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "react-i18next": "^12.1.1", "react-is": "18.2.0", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#13c8241ca262541813ce0e2df4ff3e289fbd391b", + "stremio-translations": "github:Stremio/stremio-translations#f5587521902320be9b97ecf5e1c5c38d1aa847ff", "url": "0.11.0", "use-long-press": "^3.1.5" }, @@ -13600,8 +13600,9 @@ } }, "node_modules/stremio-translations": { - "version": "1.44.4", - "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#13c8241ca262541813ce0e2df4ff3e289fbd391b", + "version": "1.44.5", + "resolved": "git+ssh://git@github.com/Stremio/stremio-translations.git#f5587521902320be9b97ecf5e1c5c38d1aa847ff", + "integrity": "sha512-vE23dpAIJLBHwGarwNh7uXFU7JxMkGWbP5Oi8LJbsiQHgrsSHD/v7mcXLDxVgCpPTM4D/SbfcJnJkAKXwllezw==", "license": "MIT" }, "node_modules/string_decoder": { @@ -26006,8 +26007,9 @@ "dev": true }, "stremio-translations": { - "version": "git+ssh://git@github.com/Stremio/stremio-translations.git#13c8241ca262541813ce0e2df4ff3e289fbd391b", - "from": "stremio-translations@github:Stremio/stremio-translations#13c8241ca262541813ce0e2df4ff3e289fbd391b" + "version": "git+ssh://git@github.com/Stremio/stremio-translations.git#f5587521902320be9b97ecf5e1c5c38d1aa847ff", + "integrity": "sha512-vE23dpAIJLBHwGarwNh7uXFU7JxMkGWbP5Oi8LJbsiQHgrsSHD/v7mcXLDxVgCpPTM4D/SbfcJnJkAKXwllezw==", + "from": "stremio-translations@github:Stremio/stremio-translations#f5587521902320be9b97ecf5e1c5c38d1aa847ff" }, "string_decoder": { "version": "1.1.1", diff --git a/package.json b/package.json index 7584c1383..6a4ca2a46 100755 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "react-i18next": "^12.1.1", "react-is": "18.2.0", "spatial-navigation-polyfill": "github:Stremio/spatial-navigation#64871b1422466f5f45d24ebc8bbd315b2ebab6a6", - "stremio-translations": "github:Stremio/stremio-translations#13c8241ca262541813ce0e2df4ff3e289fbd391b", + "stremio-translations": "github:Stremio/stremio-translations#f5587521902320be9b97ecf5e1c5c38d1aa847ff", "url": "0.11.0", "use-long-press": "^3.1.5" }, diff --git a/src/common/MetaRow/MetaRow.js b/src/common/MetaRow/MetaRow.js index 5d06d9615..5a99c585a 100644 --- a/src/common/MetaRow/MetaRow.js +++ b/src/common/MetaRow/MetaRow.js @@ -4,39 +4,47 @@ const React = require('react'); const ReactIs = require('react-is'); const PropTypes = require('prop-types'); const classnames = require('classnames'); -const { useTranslation } = require('react-i18next'); const { default: Icon } = require('@stremio/stremio-icons/react'); const Button = require('stremio/common/Button'); const CONSTANTS = require('stremio/common/CONSTANTS'); +const useTranslate = require('stremio/common/useTranslate'); const MetaRowPlaceholder = require('./MetaRowPlaceholder'); const styles = require('./styles'); -const MetaRow = ({ className, title, message, items, itemComponent, deepLinks }) => { - const { t } = useTranslation(); +const MetaRow = ({ className, title, catalog, message, itemComponent }) => { + const t = useTranslate(); + + const catalogTitle = React.useMemo(() => { + return title ?? t.catalogTitle(catalog); + }, [title, catalog, t.catalogTitle]); + + const items = React.useMemo(() => { + return catalog?.items ?? catalog?.content?.content; + }, [catalog]); + + const href = React.useMemo(() => { + return catalog?.deepLinks?.discover ?? catalog?.deepLinks?.library; + }, [catalog]); + return (
- { - (typeof title === 'string' && title.length > 0) || (deepLinks && (typeof deepLinks.discover === 'string' || typeof deepLinks.library === 'string')) ? -
- { - typeof title === 'string' && title.length > 0 ? -
{title}
- : - null - } - { - deepLinks && (typeof deepLinks.discover === 'string' || typeof deepLinks.library === 'string') ? - - : - null - } -
- : - null - } +
+ { + typeof catalogTitle === 'string' && catalogTitle.length > 0 ? +
{catalogTitle}
+ : + null + } + { + href ? + + : + null + } +
{ typeof message === 'string' && message.length > 0 ?
{message}
@@ -69,14 +77,33 @@ MetaRow.propTypes = { className: PropTypes.string, title: PropTypes.string, message: PropTypes.string, - items: PropTypes.arrayOf(PropTypes.shape({ - posterShape: PropTypes.string - })), + catalog: PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string, + type: PropTypes.string, + addon: PropTypes.shape({ + manifest: PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string, + }), + }), + content: PropTypes.shape({ + content: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.arrayOf(PropTypes.shape({ + posterShape: PropTypes.string, + })), + ]), + }), + items: PropTypes.arrayOf(PropTypes.shape({ + posterShape: PropTypes.string, + })), + deepLinks: PropTypes.shape({ + discover: PropTypes.string, + library: PropTypes.string, + }), + }), itemComponent: PropTypes.elementType, - deepLinks: PropTypes.shape({ - discover: PropTypes.string, - library: PropTypes.string - }) }; module.exports = MetaRow; diff --git a/src/common/index.js b/src/common/index.js index 5d8d65cbe..a6aafb76c 100644 --- a/src/common/index.js +++ b/src/common/index.js @@ -32,7 +32,6 @@ const getVisibleChildrenRange = require('./getVisibleChildrenRange'); const interfaceLanguages = require('./interfaceLanguages.json'); const languageNames = require('./languageNames.json'); const routesRegexp = require('./routesRegexp'); -const translateOption = require('./translateOption'); const useAnimationFrame = require('./useAnimationFrame'); const useBinaryState = require('./useBinaryState'); const useFullscreen = require('./useFullscreen'); @@ -43,6 +42,7 @@ const useOnScrollToBottom = require('./useOnScrollToBottom'); const useProfile = require('./useProfile'); const useStreamingServer = require('./useStreamingServer'); const useTorrent = require('./useTorrent'); +const useTranslate = require('./useTranslate'); const platform = require('./platform'); const EventModal = require('./EventModal'); @@ -83,7 +83,6 @@ module.exports = { interfaceLanguages, languageNames, routesRegexp, - translateOption, useAnimationFrame, useBinaryState, useFullscreen, @@ -94,6 +93,7 @@ module.exports = { useProfile, useStreamingServer, useTorrent, + useTranslate, platform, EventModal, }; diff --git a/src/common/translateOption.js b/src/common/translateOption.js deleted file mode 100644 index bbe0a9ebc..000000000 --- a/src/common/translateOption.js +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (C) 2017-2023 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; diff --git a/src/common/useTranslate.js b/src/common/useTranslate.js new file mode 100644 index 000000000..f253e5129 --- /dev/null +++ b/src/common/useTranslate.js @@ -0,0 +1,41 @@ +const { useCallback } = require('react'); +const { useTranslation } = require('react-i18next'); + +const useTranslate = () => { + const { t } = useTranslation(); + + const string = useCallback((key) => t(key), [t]); + + const stringWithPrefix = useCallback((value, prefix, fallback = null) => { + const key = `${prefix}${value}`; + const defaultValue = fallback ?? value.charAt(0).toUpperCase() + value.slice(1); + + return t(key, { + defaultValue, + }); + }, [t]); + + const catalogTitle = useCallback(({ addon, id, name, type } = {}, withType = true) => { + if (addon && id && name) { + const partialKey = `${addon.manifest.id}/${id}`; + const translatedName = stringWithPrefix(partialKey, 'CATALOG_', name); + + if (type && withType) { + const translatedType = stringWithPrefix(type, 'TYPE_'); + return `${translatedName} - ${translatedType}`; + } + + return translatedName; + } + + return null; + }, [stringWithPrefix]); + + return { + string, + stringWithPrefix, + catalogTitle, + }; +}; + +module.exports = useTranslate; diff --git a/src/routes/Addons/useSelectableInputs.js b/src/routes/Addons/useSelectableInputs.js index a48cd2ac9..af0bb35fc 100644 --- a/src/routes/Addons/useSelectableInputs.js +++ b/src/routes/Addons/useSelectableInputs.js @@ -1,18 +1,17 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); -const { t } = require('i18next'); -const { translateOption } = require('stremio/common'); +const { useTranslate } = require('stremio/common'); -const mapSelectableInputs = (installedAddons, remoteAddons) => { +const mapSelectableInputs = (installedAddons, remoteAddons, t) => { const catalogSelect = { - title: t('SELECT_CATALOG'), + title: t.string('SELECT_CATALOG'), options: remoteAddons.selectable.catalogs .concat(installedAddons.selectable.catalogs) .map(({ name, deepLinks }) => ({ value: deepLinks.addons, - label: translateOption(name, 'ADDON_'), - title: translateOption(name, 'ADDON_'), + label: t.stringWithPrefix(name, 'ADDON_'), + title: t.stringWithPrefix(name, 'ADDON_'), })), selected: remoteAddons.selectable.catalogs .concat(installedAddons.selectable.catalogs) @@ -22,7 +21,7 @@ const mapSelectableInputs = (installedAddons, remoteAddons) => { () => { const selectableCatalog = remoteAddons.selectable.catalogs .find(({ id }) => id === remoteAddons.selected.request.path.id); - return selectableCatalog ? translateOption(selectableCatalog.name, 'ADDON_') : remoteAddons.selected.request.path.id; + return selectableCatalog ? t.stringWithPrefix(selectableCatalog.name, 'ADDON_') : remoteAddons.selected.request.path.id; } : null, @@ -31,16 +30,16 @@ const mapSelectableInputs = (installedAddons, remoteAddons) => { } }; const typeSelect = { - title: t('SELECT_TYPE'), + title: t.string('SELECT_TYPE'), options: installedAddons.selected !== null ? installedAddons.selectable.types.map(({ type, deepLinks }) => ({ value: deepLinks.addons, - label: type !== null ? translateOption(type, 'TYPE_') : t('TYPE_ALL') + label: type !== null ? t.stringWithPrefix(type, 'TYPE_') : t.string('TYPE_ALL') })) : remoteAddons.selectable.types.map(({ type, deepLinks }) => ({ value: deepLinks.addons, - label: translateOption(type, 'TYPE_') + label: t.stringWithPrefix(type, 'TYPE_') })), selected: installedAddons.selected !== null ? installedAddons.selectable.types @@ -53,12 +52,12 @@ const mapSelectableInputs = (installedAddons, remoteAddons) => { renderLabelText: () => { return installedAddons.selected !== null ? installedAddons.selected.request.type === null ? - t('TYPE_ALL') + t.string('TYPE_ALL') : - translateOption(installedAddons.selected.request.type, 'TYPE_') + t.stringWithPrefix(installedAddons.selected.request.type, 'TYPE_') : remoteAddons.selected !== null ? - translateOption(remoteAddons.selected.request.path.type, 'TYPE_') + t.stringWithPrefix(remoteAddons.selected.request.path.type, 'TYPE_') : typeSelect.title; }, @@ -70,8 +69,9 @@ const mapSelectableInputs = (installedAddons, remoteAddons) => { }; const useSelectableInputs = (installedAddons, remoteAddons) => { + const t = useTranslate(); const selectableInputs = React.useMemo(() => { - return mapSelectableInputs(installedAddons, remoteAddons); + return mapSelectableInputs(installedAddons, remoteAddons, t); }, [installedAddons, remoteAddons]); return selectableInputs; }; diff --git a/src/routes/Board/Board.js b/src/routes/Board/Board.js index 4c0702eda..510406381 100644 --- a/src/routes/Board/Board.js +++ b/src/routes/Board/Board.js @@ -46,9 +46,8 @@ const Board = () => { : null @@ -60,10 +59,8 @@ const Board = () => { ); } @@ -72,9 +69,8 @@ const Board = () => { ); } @@ -83,8 +79,7 @@ const Board = () => { ); } diff --git a/src/routes/Discover/useSelectableInputs.js b/src/routes/Discover/useSelectableInputs.js index 6bba2c8ef..18d317507 100644 --- a/src/routes/Discover/useSelectableInputs.js +++ b/src/routes/Discover/useSelectableInputs.js @@ -1,22 +1,21 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); -const { useTranslation } = require('react-i18next'); -const { translateOption } = require('stremio/common'); +const { useTranslate } = require('stremio/common'); const mapSelectableInputs = (discover, t) => { const typeSelect = { - title: t('SELECT_TYPE'), + title: t.string('SELECT_TYPE'), options: discover.selectable.types .map(({ type, deepLinks }) => ({ value: deepLinks.discover, - label: translateOption(type, 'TYPE_') + label: t.stringWithPrefix(type, 'TYPE_') })), selected: discover.selectable.types .filter(({ selected }) => selected) .map(({ deepLinks }) => deepLinks.discover), renderLabelText: discover.selected !== null ? - () => translateOption(discover.selected.request.path.type, 'TYPE_') + () => t.stringWithPrefix(discover.selected.request.path.type, 'TYPE_') : null, onSelect: (event) => { @@ -24,11 +23,11 @@ const mapSelectableInputs = (discover, t) => { } }; const catalogSelect = { - title: t('SELECT_CATALOG'), + title: t.string('SELECT_CATALOG'), options: discover.selectable.catalogs - .map(({ name, addon, deepLinks }) => ({ + .map(({ id, name, addon, deepLinks }) => ({ value: deepLinks.discover, - label: name, + label: t.catalogTitle({ addon, id, name }), title: `${name} (${addon.manifest.name})` })), selected: discover.selectable.catalogs @@ -38,7 +37,7 @@ const mapSelectableInputs = (discover, t) => { () => { const selectableCatalog = discover.selectable.catalogs .find(({ id }) => id === discover.selected.request.path.id); - return selectableCatalog ? selectableCatalog.name : discover.selected.request.path.id; + return selectableCatalog ? t.catalogTitle(selectableCatalog, false) : discover.selected.request.path.id; } : null, @@ -47,10 +46,10 @@ const mapSelectableInputs = (discover, t) => { } }; const extraSelects = discover.selectable.extra.map(({ name, isRequired, options }) => ({ - title: translateOption(name, 'SELECT_'), + title: t.stringWithPrefix(name, 'SELECT_'), isRequired: isRequired, options: options.map(({ value, deepLinks }) => ({ - label: typeof value === 'string' ? translateOption(value) : t('NONE'), + label: typeof value === 'string' ? t.stringWithPrefix(value) : t.string('NONE'), value: JSON.stringify({ href: deepLinks.discover, value @@ -63,7 +62,7 @@ const mapSelectableInputs = (discover, t) => { value })), renderLabelText: options.some(({ selected, value }) => selected && value === null) ? - () => translateOption(name, 'SELECT_') + () => t.stringWithPrefix(name, 'SELECT_') : null, onSelect: (event) => { @@ -75,7 +74,7 @@ const mapSelectableInputs = (discover, t) => { }; const useSelectableInputs = (discover) => { - const { t } = useTranslation(); + const t = useTranslate(); const selectableInputs = React.useMemo(() => { return mapSelectableInputs(discover, t); }, [discover.selected, discover.selectable]); diff --git a/src/routes/Library/useSelectableInputs.js b/src/routes/Library/useSelectableInputs.js index 49eb94a1f..ef956814b 100644 --- a/src/routes/Library/useSelectableInputs.js +++ b/src/routes/Library/useSelectableInputs.js @@ -1,16 +1,15 @@ // Copyright (C) 2017-2023 Smart code 203358507 const React = require('react'); -const { useTranslation } = require('react-i18next'); -const { translateOption } = require('stremio/common'); +const { useTranslate } = require('stremio/common'); const mapSelectableInputs = (library, t) => { const typeSelect = { - title: t('SELECT_TYPE'), + title: t.string('SELECT_TYPE'), options: library.selectable.types .map(({ type, deepLinks }) => ({ value: deepLinks.library, - label: type === null ? t('TYPE_ALL') : translateOption(type, 'TYPE_') + label: type === null ? t.string('TYPE_ALL') : t.stringWithPrefix(type, 'TYPE_') })), selected: library.selectable.types .filter(({ selected }) => selected) @@ -20,11 +19,11 @@ const mapSelectableInputs = (library, t) => { } }; const sortSelect = { - title: t('SELECT_SORT'), + title: t.string('SELECT_SORT'), options: library.selectable.sorts .map(({ sort, deepLinks }) => ({ value: deepLinks.library, - label: translateOption(sort, 'SORT_') + label: t.stringWithPrefix(sort, 'SORT_') })), selected: library.selectable.sorts .filter(({ selected }) => selected) @@ -51,7 +50,7 @@ const mapSelectableInputs = (library, t) => { }; const useSelectableInputs = (library) => { - const { t } = useTranslation(); + const t = useTranslate(); const selectableInputs = React.useMemo(() => { return mapSelectableInputs(library, t); }, [library]); diff --git a/src/types/types.d.ts b/src/types/types.d.ts index 258e48098..18c769a07 100644 --- a/src/types/types.d.ts +++ b/src/types/types.d.ts @@ -54,7 +54,9 @@ type BehaviorHints = { type PosterShape = 'square' | 'landscape' | 'poster' | null; type Catalog = { - title?: string, + label?: string, + name?: string, + type?: string, content: T, installed?: boolean, deepLinks?: D,