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:
schroda
2023-04-21 00:27:42 +02:00
committed by GitHub
parent c81189a9ed
commit 0a56a2f6d4
8 changed files with 101 additions and 42 deletions

View File

@@ -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+'}

View File

@@ -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

View File

@@ -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",

View File

@@ -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>
); );
} }

View File

@@ -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

View File

@@ -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)

View 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);

View File

@@ -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;
}; };