Extensions cleanup (#257)
* [Cleanup] Add enums and types * Extract "default languages" from ISOLanguages The "default languages" will also be translated depending on which language the user selected
This commit is contained in:
@@ -15,9 +15,9 @@ import { Box, styled } from '@mui/system';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, useHistory } from 'react-router-dom';
|
import { Link, useHistory } from 'react-router-dom';
|
||||||
import { langCodeToName } from 'util/language';
|
|
||||||
import useLocalStorage from 'util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import { ISource } from 'typings';
|
import { ISource } from 'typings';
|
||||||
|
import { translateExtensionLanguage } from 'screens/util/Extensions';
|
||||||
|
|
||||||
const MobileWidthButtons = styled('div')(({ theme }) => ({
|
const MobileWidthButtons = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -99,7 +99,7 @@ const SourceCard: React.FC<IProps> = (props: IProps) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
{id !== '0' && (
|
{id !== '0' && (
|
||||||
<Typography variant="caption" display="block" gutterBottom>
|
<Typography variant="caption" display="block" gutterBottom>
|
||||||
{langCodeToName(lang)}
|
{translateExtensionLanguage(lang)}
|
||||||
{isNsfw && (
|
{isNsfw && (
|
||||||
<Typography variant="caption" display="inline" gutterBottom color="red">
|
<Typography variant="caption" display="inline" gutterBottom color="red">
|
||||||
{' 18+'}
|
{' 18+'}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import IconButton from '@mui/material/IconButton';
|
|||||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||||
import { List, ListItemSecondaryAction, ListItemText } from '@mui/material';
|
import { List, ListItemSecondaryAction, ListItemText } from '@mui/material';
|
||||||
import ListItem from '@mui/material/ListItem';
|
import ListItem from '@mui/material/ListItem';
|
||||||
import { langCodeToName } from 'util/language';
|
|
||||||
import cloneObject from 'util/cloneObject';
|
import cloneObject from 'util/cloneObject';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { translateExtensionLanguage } from 'screens/util/Extensions';
|
||||||
|
|
||||||
function removeAll(firstList: any[], secondList: any[]) {
|
function removeAll(firstList: any[], secondList: any[]) {
|
||||||
secondList.forEach((item) => {
|
secondList.forEach((item) => {
|
||||||
@@ -93,7 +93,7 @@ export default function LangSelect(props: IProps) {
|
|||||||
<List>
|
<List>
|
||||||
{allLangs.map((lang) => (
|
{allLangs.map((lang) => (
|
||||||
<ListItem key={lang}>
|
<ListItem key={lang}>
|
||||||
<ListItemText primary={langCodeToName(lang)} />
|
<ListItemText primary={translateExtensionLanguage(lang)} />
|
||||||
|
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -168,13 +168,16 @@
|
|||||||
"installing_file": "Installing Extension File..."
|
"installing_file": "Installing Extension File..."
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"all": "All"
|
"all": "All",
|
||||||
|
"other": "Other"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"label": {
|
"label": {
|
||||||
|
"installed": "Installed",
|
||||||
"installing": "installing",
|
"installing": "installing",
|
||||||
"obsolete": "obsolete",
|
"obsolete": "obsolete",
|
||||||
"uninstalling": "uninstalling",
|
"uninstalling": "uninstalling",
|
||||||
|
"update_pending": "Update pending",
|
||||||
"updating": "updating"
|
"updating": "updating"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -417,7 +420,8 @@
|
|||||||
"label": {
|
"label": {
|
||||||
"checkout": "Check out",
|
"checkout": "Check out",
|
||||||
"guide": "Local source guide"
|
"guide": "Local source guide"
|
||||||
}
|
},
|
||||||
|
"title": "Local source"
|
||||||
},
|
},
|
||||||
"title": "Source",
|
"title": "Source",
|
||||||
"title_one": "Source",
|
"title_one": "Source",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import React, { useContext, useEffect, useState, useMemo, useRef } from 'react';
|
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { fromEvent } from 'file-selector';
|
import { fromEvent } from 'file-selector';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
@@ -14,28 +14,37 @@ import NavbarContext from 'components/context/NavbarContext';
|
|||||||
import client, { useQuery } from 'util/client';
|
import client, { useQuery } from 'util/client';
|
||||||
import useLocalStorage from 'util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import LangSelect from 'components/navbar/action/LangSelect';
|
import LangSelect from 'components/navbar/action/LangSelect';
|
||||||
import { extensionDefaultLangs, langCodeToName, langSortCmp } from 'util/language';
|
import { extensionDefaultLangs, DefaultLanguage, langSortCmp } from 'util/language';
|
||||||
import { makeToaster } from 'components/util/Toast';
|
import { makeToaster } from 'components/util/Toast';
|
||||||
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
||||||
import AppbarSearch from 'components/util/AppbarSearch';
|
import AppbarSearch from 'components/util/AppbarSearch';
|
||||||
import { useQueryParam, StringParam } from 'use-query-params';
|
import { StringParam, useQueryParam } from 'use-query-params';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { Virtuoso } from 'react-virtuoso';
|
||||||
import { Typography, useMediaQuery, useTheme } from '@mui/material';
|
import { Typography, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import { IExtension } from 'typings';
|
import { IExtension } from 'typings';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
ExtensionState,
|
||||||
|
GroupedExtensions,
|
||||||
|
GroupedExtensionsResult,
|
||||||
|
isExtensionStateOrLanguage,
|
||||||
|
translateExtensionLanguage,
|
||||||
|
} from 'screens/util/Extensions';
|
||||||
|
|
||||||
const LANGUAGE = 0;
|
const LANGUAGE = 0;
|
||||||
const EXTENSIONS = 1;
|
const EXTENSIONS = 1;
|
||||||
|
|
||||||
const allLangs: string[] = [];
|
const allLangs: string[] = [];
|
||||||
|
|
||||||
interface GroupedExtension {
|
function groupExtensions(extensions: IExtension[]): GroupedExtensionsResult {
|
||||||
[key: string]: IExtension[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function groupExtensions(extensions: IExtension[]) {
|
|
||||||
allLangs.length = 0; // empty the array
|
allLangs.length = 0; // empty the array
|
||||||
const sortedExtenions: GroupedExtension = { installed: [], 'updates pending': [], all: [] };
|
const sortedExtenions: GroupedExtensions = {
|
||||||
|
[ExtensionState.INSTALLED]: [],
|
||||||
|
[ExtensionState.UPDATE_PENDING]: [],
|
||||||
|
[DefaultLanguage.ALL]: [],
|
||||||
|
[DefaultLanguage.OTHER]: [],
|
||||||
|
[DefaultLanguage.LOCAL_SOURCE]: [],
|
||||||
|
};
|
||||||
extensions.forEach((extension) => {
|
extensions.forEach((extension) => {
|
||||||
if (sortedExtenions[extension.lang] === undefined) {
|
if (sortedExtenions[extension.lang] === undefined) {
|
||||||
sortedExtenions[extension.lang] = [];
|
sortedExtenions[extension.lang] = [];
|
||||||
@@ -45,9 +54,9 @@ function groupExtensions(extensions: IExtension[]) {
|
|||||||
}
|
}
|
||||||
if (extension.installed) {
|
if (extension.installed) {
|
||||||
if (extension.hasUpdate) {
|
if (extension.hasUpdate) {
|
||||||
sortedExtenions['updates pending'].push(extension);
|
sortedExtenions[ExtensionState.UPDATE_PENDING].push(extension);
|
||||||
} else {
|
} else {
|
||||||
sortedExtenions.installed.push(extension);
|
sortedExtenions[ExtensionState.INSTALLED].push(extension);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sortedExtenions[extension.lang].push(extension);
|
sortedExtenions[extension.lang].push(extension);
|
||||||
@@ -55,15 +64,17 @@ function groupExtensions(extensions: IExtension[]) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
allLangs.sort(langSortCmp);
|
allLangs.sort(langSortCmp);
|
||||||
const result: [string, IExtension[]][] = [
|
const result: GroupedExtensionsResult<ExtensionState | DefaultLanguage | string> = [
|
||||||
['updates pending', sortedExtenions['updates pending']],
|
[ExtensionState.UPDATE_PENDING, sortedExtenions[ExtensionState.UPDATE_PENDING]],
|
||||||
['installed', sortedExtenions.installed],
|
[ExtensionState.INSTALLED, sortedExtenions[ExtensionState.INSTALLED]],
|
||||||
['all', sortedExtenions.all],
|
[DefaultLanguage.ALL, sortedExtenions[DefaultLanguage.ALL]],
|
||||||
|
[DefaultLanguage.OTHER, sortedExtenions[DefaultLanguage.OTHER]],
|
||||||
|
[DefaultLanguage.LOCAL_SOURCE, sortedExtenions[DefaultLanguage.LOCAL_SOURCE]],
|
||||||
];
|
];
|
||||||
|
|
||||||
const langExt: [string, IExtension[]][] = allLangs.map((lang) => [lang, sortedExtenions[lang]]);
|
const langExt: GroupedExtensionsResult = allLangs.map((lang) => [lang, sortedExtenions[lang]]);
|
||||||
|
|
||||||
return result.concat(langExt);
|
return (result as GroupedExtensionsResult).concat(langExt);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MangaExtensions() {
|
export default function MangaExtensions() {
|
||||||
@@ -106,7 +117,7 @@ export default function MangaExtensions() {
|
|||||||
() =>
|
() =>
|
||||||
groupExtensions(filteredExtensions)
|
groupExtensions(filteredExtensions)
|
||||||
.filter((group) => group[EXTENSIONS].length > 0)
|
.filter((group) => group[EXTENSIONS].length > 0)
|
||||||
.filter((group) => ['installed', 'updates pending', 'all', ...shownLangs].includes(group[LANGUAGE])),
|
.filter((group) => isExtensionStateOrLanguage(group[LANGUAGE]) || shownLangs.includes(group[LANGUAGE])),
|
||||||
[shownLangs, filteredExtensions],
|
[shownLangs, filteredExtensions],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -196,7 +207,7 @@ export default function MangaExtensions() {
|
|||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{langCodeToName(item)}
|
{translateExtensionLanguage(item)}
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ import React, { useContext, useEffect, useState } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { StringParam, useQueryParam } from 'use-query-params';
|
import { StringParam, useQueryParam } from 'use-query-params';
|
||||||
import client from 'util/client';
|
import client from 'util/client';
|
||||||
import { langCodeToName, langSortCmp, sourceDefualtLangs, sourceForcedDefaultLangs } from 'util/language';
|
import { langSortCmp, sourceDefualtLangs, sourceForcedDefaultLangs } from 'util/language';
|
||||||
import useLocalStorage from 'util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import { ISource } from 'typings';
|
import { ISource } from 'typings';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { translateExtensionLanguage } from 'screens/util/Extensions';
|
||||||
|
|
||||||
function sourceToLangList(sources: ISource[]) {
|
function sourceToLangList(sources: ISource[]) {
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
@@ -202,7 +203,7 @@ const SearchAll: React.FC = () => {
|
|||||||
sx={{ p: 3 }}
|
sx={{ p: 3 }}
|
||||||
>
|
>
|
||||||
<Typography variant="h5">{displayName}</Typography>
|
<Typography variant="h5">{displayName}</Typography>
|
||||||
<Typography variant="caption">{langCodeToName(lang)}</Typography>
|
<Typography variant="caption">{translateExtensionLanguage(lang)}</Typography>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
</Card>
|
</Card>
|
||||||
<MangaGrid
|
<MangaGrid
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import React, { useContext, useEffect } from 'react';
|
|||||||
import LangSelect from 'components/navbar/action/LangSelect';
|
import LangSelect from 'components/navbar/action/LangSelect';
|
||||||
import SourceCard from 'components/SourceCard';
|
import SourceCard from 'components/SourceCard';
|
||||||
import NavbarContext from 'components/context/NavbarContext';
|
import NavbarContext from 'components/context/NavbarContext';
|
||||||
import { sourceDefualtLangs, sourceForcedDefaultLangs, langCodeToName, langSortCmp } from 'util/language';
|
import { sourceDefualtLangs, sourceForcedDefaultLangs, langSortCmp } from 'util/language';
|
||||||
import useLocalStorage from 'util/useLocalStorage';
|
import useLocalStorage from 'util/useLocalStorage';
|
||||||
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
import LoadingPlaceholder from 'components/util/LoadingPlaceholder';
|
||||||
import { IconButton } from '@mui/material';
|
import { IconButton } from '@mui/material';
|
||||||
@@ -18,6 +18,7 @@ import { useHistory } from 'react-router-dom';
|
|||||||
import { useQuery } from 'util/client';
|
import { useQuery } from 'util/client';
|
||||||
import { ISource } from 'typings';
|
import { ISource } from 'typings';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { translateExtensionLanguage } from 'screens/util/Extensions';
|
||||||
|
|
||||||
function sourceToLangList(sources: ISource[]) {
|
function sourceToLangList(sources: ISource[]) {
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
@@ -104,7 +105,7 @@ export default function Sources() {
|
|||||||
shownLangs.indexOf(lang) !== -1 && (
|
shownLangs.indexOf(lang) !== -1 && (
|
||||||
<React.Fragment key={lang}>
|
<React.Fragment key={lang}>
|
||||||
<h1 key={lang} style={{ marginLeft: 25 }}>
|
<h1 key={lang} style={{ marginLeft: 25 }}>
|
||||||
{langCodeToName(lang)}
|
{translateExtensionLanguage(lang)}
|
||||||
</h1>
|
</h1>
|
||||||
{(list as ISource[])
|
{(list as ISource[])
|
||||||
.filter((source) => showNsfw || !source.isNsfw)
|
.filter((source) => showNsfw || !source.isNsfw)
|
||||||
|
|||||||
44
src/screens/util/Extensions.ts
Normal file
44
src/screens/util/Extensions.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { IExtension, TranslationKey } from 'typings';
|
||||||
|
import { DefaultLanguage, langCodeToName } from 'util/language';
|
||||||
|
import { t } from 'i18next';
|
||||||
|
|
||||||
|
export enum ExtensionState {
|
||||||
|
INSTALLED = 'INSTALLED',
|
||||||
|
UPDATE_PENDING = 'UPDATE_PENDING',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GroupedExtensionsResult<KEY extends string = string> = [KEY, IExtension[]][];
|
||||||
|
|
||||||
|
export type GroupedByExtensionState = {
|
||||||
|
[state in ExtensionState]: IExtension[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GroupedByLanguage = {
|
||||||
|
[language in DefaultLanguage]: IExtension[];
|
||||||
|
} & {
|
||||||
|
[language: string]: IExtension[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GroupedExtensions = GroupedByExtensionState & GroupedByLanguage;
|
||||||
|
|
||||||
|
export const extensionLanguageToTranslationKey: { [state in ExtensionState | DefaultLanguage]: TranslationKey } = {
|
||||||
|
[ExtensionState.INSTALLED]: 'extension.state.label.installed',
|
||||||
|
[ExtensionState.UPDATE_PENDING]: 'extension.state.label.update_pending',
|
||||||
|
[DefaultLanguage.ALL]: 'extension.language.all',
|
||||||
|
[DefaultLanguage.OTHER]: 'extension.language.other',
|
||||||
|
[DefaultLanguage.LOCAL_SOURCE]: 'source.local_source.title',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isExtensionStateOrLanguage = (languageCode: string): boolean =>
|
||||||
|
[
|
||||||
|
ExtensionState.INSTALLED,
|
||||||
|
ExtensionState.UPDATE_PENDING,
|
||||||
|
DefaultLanguage.ALL,
|
||||||
|
DefaultLanguage.OTHER,
|
||||||
|
DefaultLanguage.LOCAL_SOURCE,
|
||||||
|
].includes(languageCode as ExtensionState | DefaultLanguage);
|
||||||
|
|
||||||
|
export const translateExtensionLanguage = (languageCode: string): string =>
|
||||||
|
isExtensionStateOrLanguage(languageCode)
|
||||||
|
? (t(extensionLanguageToTranslationKey[languageCode as ExtensionState | DefaultLanguage]) as string)
|
||||||
|
: langCodeToName(languageCode);
|
||||||
@@ -6,15 +6,13 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
|
export enum DefaultLanguage {
|
||||||
|
ALL = 'all',
|
||||||
|
OTHER = 'other',
|
||||||
|
LOCAL_SOURCE = 'localsourcelang',
|
||||||
|
}
|
||||||
|
|
||||||
export const ISOLanguages = [
|
export const ISOLanguages = [
|
||||||
{ code: 'all', name: 'All', nativeName: 'All' },
|
|
||||||
{ code: 'installed', name: 'Installed', nativeName: 'Installed' },
|
|
||||||
{ code: 'updates pending', name: 'Updates pending', nativeName: 'Updates pending' },
|
|
||||||
|
|
||||||
{ code: 'other', name: 'other langs?', nativeName: 'Other' },
|
|
||||||
|
|
||||||
{ code: 'localsourcelang', name: 'Local source', nativeName: 'Local source' },
|
|
||||||
|
|
||||||
// full list: https://github.com/meikidd/iso-639-1/blob/master/src/data.js
|
// full list: https://github.com/meikidd/iso-639-1/blob/master/src/data.js
|
||||||
{ code: 'en', name: 'English', nativeName: 'English' },
|
{ code: 'en', name: 'English', nativeName: 'English' },
|
||||||
{ code: 'ca', name: 'Catalan; Valencian', nativeName: 'Català' },
|
{ code: 'ca', name: 'Catalan; Valencian', nativeName: 'Català' },
|
||||||
@@ -97,15 +95,15 @@ function defaultNativeLang() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function extensionDefaultLangs() {
|
export function extensionDefaultLangs() {
|
||||||
return [defaultNativeLang(), 'all'];
|
return [defaultNativeLang(), DefaultLanguage.ALL];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sourceDefualtLangs() {
|
export function sourceDefualtLangs() {
|
||||||
return [defaultNativeLang(), 'localsourcelang'];
|
return [defaultNativeLang(), DefaultLanguage.LOCAL_SOURCE];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sourceForcedDefaultLangs(): string[] {
|
export function sourceForcedDefaultLangs(): string[] {
|
||||||
return ['localsourcelang'];
|
return [DefaultLanguage.LOCAL_SOURCE];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const langSortCmp = (a: string, b: string) => {
|
export const langSortCmp = (a: string, b: string) => {
|
||||||
@@ -115,8 +113,8 @@ export const langSortCmp = (a: string, b: string) => {
|
|||||||
|
|
||||||
if (a === 'en') return -1;
|
if (a === 'en') return -1;
|
||||||
if (b === 'en') return 1;
|
if (b === 'en') return 1;
|
||||||
if (a === 'localSourceLang') return 1;
|
if (a === DefaultLanguage.LOCAL_SOURCE) return 1;
|
||||||
if (b === 'localSourceLang') return -1;
|
if (b === DefaultLanguage.LOCAL_SOURCE) return -1;
|
||||||
|
|
||||||
return aLang > bLang ? 1 : -1;
|
return aLang > bLang ? 1 : -1;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user