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,