feat: initial Chinese UI language support
This commit is contained in:
parent
a8c6cd23ce
commit
8a50ef4ef2
22 changed files with 301 additions and 54 deletions
|
@ -33,6 +33,9 @@
|
||||||
"fontsource-roboto-mono": "^3.0.3",
|
"fontsource-roboto-mono": "^3.0.3",
|
||||||
"framer-motion": "^2.9.5",
|
"framer-motion": "^2.9.5",
|
||||||
"history": "^5.0.0",
|
"history": "^5.0.0",
|
||||||
|
"i18next": "^19.8.4",
|
||||||
|
"i18next-browser-languagedetector": "^6.0.1",
|
||||||
|
"i18next-http-backend": "^1.0.21",
|
||||||
"immer": "^8.0.0",
|
"immer": "^8.0.0",
|
||||||
"invariant": "^2.2.4",
|
"invariant": "^2.2.4",
|
||||||
"lodash-es": "^4.17.14",
|
"lodash-es": "^4.17.14",
|
||||||
|
@ -43,6 +46,7 @@
|
||||||
"react-dom": "0.0.0-experimental-94c0244ba",
|
"react-dom": "0.0.0-experimental-94c0244ba",
|
||||||
"react-feather": "^2.0.9",
|
"react-feather": "^2.0.9",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
|
"react-i18next": "^11.7.4",
|
||||||
"react-icons": "^3.10.0",
|
"react-icons": "^3.10.0",
|
||||||
"react-modal": "^3.12.1",
|
"react-modal": "^3.12.1",
|
||||||
"react-query": "^2.26.3",
|
"react-query": "^2.26.3",
|
||||||
|
@ -80,6 +84,7 @@
|
||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^17.0.0",
|
||||||
"@types/react-helmet": "^6.1.0",
|
"@types/react-helmet": "^6.1.0",
|
||||||
"@types/react-modal": "^3.10.6",
|
"@types/react-modal": "^3.10.6",
|
||||||
|
"@types/react-table": "^7.0.25",
|
||||||
"@types/react-tabs": "^2.3.2",
|
"@types/react-tabs": "^2.3.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.9.0",
|
"@typescript-eslint/eslint-plugin": "^4.9.0",
|
||||||
"@typescript-eslint/parser": "^4.9.0",
|
"@typescript-eslint/parser": "^4.9.0",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'modern-normalize/modern-normalize.css';
|
import 'modern-normalize/modern-normalize.css';
|
||||||
|
import './misc/i18n';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
.section {
|
.section {
|
||||||
padding: 6px 15px 15px;
|
padding: 6px 15px 15px;
|
||||||
@media (--breakpoint-not-small) {
|
@media (--breakpoint-not-small) {
|
||||||
padding: 10px 40px 40px;
|
padding: 0 40px 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,3 +28,7 @@
|
||||||
.label {
|
.label {
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.narrow {
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import PropTypes from 'prop-types';
|
import * as React from 'react';
|
||||||
import React from 'react';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Select from 'src/components/shared/Select';
|
||||||
|
import { ClashGeneralConfig, DispatchFn, State } from 'src/store/types';
|
||||||
|
import { ClashAPIConfig } from 'src/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getClashAPIConfig,
|
getClashAPIConfig,
|
||||||
|
@ -67,12 +70,17 @@ const portFields = [
|
||||||
{ key: 'redir-port', label: 'Redir Port' },
|
{ key: 'redir-port', label: 'Redir Port' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const mapState = (s) => ({
|
const langOptions = [
|
||||||
|
['zh', '中文'],
|
||||||
|
['en', 'English'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const mapState = (s: State) => ({
|
||||||
configs: getConfigs(s),
|
configs: getConfigs(s),
|
||||||
apiConfig: getClashAPIConfig(s),
|
apiConfig: getClashAPIConfig(s),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapState2 = (s) => ({
|
const mapState2 = (s: State) => ({
|
||||||
selectedChartStyleIndex: getSelectedChartStyleIndex(s),
|
selectedChartStyleIndex: getSelectedChartStyleIndex(s),
|
||||||
latencyTestUrl: getLatencyTestUrl(s),
|
latencyTestUrl: getLatencyTestUrl(s),
|
||||||
apiConfig: getClashAPIConfig(s),
|
apiConfig: getClashAPIConfig(s),
|
||||||
|
@ -88,13 +96,21 @@ function ConfigContainer({ dispatch, configs, apiConfig }) {
|
||||||
return <Config configs={configs} />;
|
return <Config configs={configs} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConfigImplProps = {
|
||||||
|
dispatch: DispatchFn;
|
||||||
|
configs: ClashGeneralConfig;
|
||||||
|
selectedChartStyleIndex: number;
|
||||||
|
latencyTestUrl: string;
|
||||||
|
apiConfig: ClashAPIConfig;
|
||||||
|
};
|
||||||
|
|
||||||
function ConfigImpl({
|
function ConfigImpl({
|
||||||
dispatch,
|
dispatch,
|
||||||
configs,
|
configs,
|
||||||
selectedChartStyleIndex,
|
selectedChartStyleIndex,
|
||||||
latencyTestUrl,
|
latencyTestUrl,
|
||||||
apiConfig,
|
apiConfig,
|
||||||
}) {
|
}: ConfigImplProps) {
|
||||||
const [configState, setConfigStateInternal] = useState(configs);
|
const [configState, setConfigStateInternal] = useState(configs);
|
||||||
const refConfigs = useRef(configs);
|
const refConfigs = useRef(configs);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -188,9 +204,11 @@ function ConfigImpl({
|
||||||
return typeof m === 'string' && m[0].toUpperCase() + m.slice(1);
|
return typeof m === 'string' && m[0].toUpperCase() + m.slice(1);
|
||||||
}, [configState.mode]);
|
}, [configState.mode]);
|
||||||
|
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ContentHeader title="Config" />
|
<ContentHeader title={t('Config')} />
|
||||||
<div className={s0.root}>
|
<div className={s0.root}>
|
||||||
{portFields.map((f) =>
|
{portFields.map((f) =>
|
||||||
configState[f.key] !== undefined ? (
|
configState[f.key] !== undefined ? (
|
||||||
|
@ -242,7 +260,7 @@ function ConfigImpl({
|
||||||
|
|
||||||
<div className={s0.section}>
|
<div className={s0.section}>
|
||||||
<div>
|
<div>
|
||||||
<div className={s0.label}>Chart Style</div>
|
<div className={s0.label}>{t('chart_style')}</div>
|
||||||
<Selection2
|
<Selection2
|
||||||
OptionComponent={TrafficChartSample}
|
OptionComponent={TrafficChartSample}
|
||||||
optionPropsList={propsList}
|
optionPropsList={propsList}
|
||||||
|
@ -250,8 +268,8 @@ function ConfigImpl({
|
||||||
onChange={selectChartStyleIndex}
|
onChange={selectChartStyleIndex}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ maxWidth: 360 }}>
|
<div className={s0.narrow}>
|
||||||
<div className={s0.label}>Latency Test URL</div>
|
<div className={s0.label}>{t('latency_test_url')}</div>
|
||||||
<SelfControlledInput
|
<SelfControlledInput
|
||||||
name="latencyTestUrl"
|
name="latencyTestUrl"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -263,12 +281,17 @@ function ConfigImpl({
|
||||||
<div className={s0.label}>Action</div>
|
<div className={s0.label}>Action</div>
|
||||||
<Button label="Switch backend" onClick={openAPIConfigModal} />
|
<Button label="Switch backend" onClick={openAPIConfigModal} />
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className={s0.label}>{t('lang')}</div>
|
||||||
|
<div className={s0.narrow}>
|
||||||
|
<Select
|
||||||
|
options={langOptions}
|
||||||
|
selected={i18n.language}
|
||||||
|
onChange={(e) => i18n.changeLanguage(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'propTypes' does not exist on type '(prop... Remove this comment to see the full error message
|
|
||||||
Config.propTypes = {
|
|
||||||
configs: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import './Connections.css';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Pause, Play, X as IconClose } from 'react-feather';
|
import { Pause, Play, X as IconClose } from 'react-feather';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
|
||||||
import { ConnectionItem } from 'src/api/connections';
|
import { ConnectionItem } from 'src/api/connections';
|
||||||
import { State } from 'src/store/types';
|
import { State } from 'src/store/types';
|
||||||
|
@ -176,9 +177,12 @@ function Conn({ apiConfig }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return connAPI.fetchData(apiConfig, read);
|
return connAPI.fetchData(apiConfig, read);
|
||||||
}, [apiConfig, read]);
|
}, [apiConfig, read]);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ContentHeader title="Connections" />
|
<ContentHeader title={t('Connections')} />
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -189,14 +193,14 @@ function Conn({ apiConfig }) {
|
||||||
>
|
>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab>
|
<Tab>
|
||||||
<span>Active</span>
|
<span>{t('Active')}</span>
|
||||||
<span className={s.connQty}>
|
<span className={s.connQty}>
|
||||||
{/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */}
|
{/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */}
|
||||||
<ConnQty qty={filteredConns.length} />
|
<ConnQty qty={filteredConns.length} />
|
||||||
</span>
|
</span>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab>
|
<Tab>
|
||||||
<span>Closed</span>
|
<span>{t('Closed')}</span>
|
||||||
<span className={s.connQty}>
|
<span className={s.connQty}>
|
||||||
{/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */}
|
{/* @ts-expect-error ts-migrate(2786) FIXME: 'ConnQty' cannot be used as a JSX component. */}
|
||||||
<ConnQty qty={filteredClosedConns.length} />
|
<ConnQty qty={filteredClosedConns.length} />
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ContentHeader from './ContentHeader';
|
import ContentHeader from './ContentHeader';
|
||||||
import s0 from './Home.module.css';
|
import s0 from './Home.module.css';
|
||||||
|
@ -7,9 +8,10 @@ import TrafficChart from './TrafficChart';
|
||||||
import TrafficNow from './TrafficNow';
|
import TrafficNow from './TrafficNow';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ContentHeader title="Overview" />
|
<ContentHeader title={t('Overview')} />
|
||||||
<div className={s0.root}>
|
<div className={s0.root}>
|
||||||
<div>
|
<div>
|
||||||
<TrafficNow />
|
<TrafficNow />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { areEqual, FixedSizeList as List } from 'react-window';
|
import { areEqual, FixedSizeList as List } from 'react-window';
|
||||||
|
|
||||||
import { fetchLogs } from '../api/logs';
|
import { fetchLogs } from '../api/logs';
|
||||||
|
@ -73,10 +74,11 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
||||||
fetchLogs({ ...apiConfig, logLevel }, appendLogInternal);
|
fetchLogs({ ...apiConfig, logLevel }, appendLogInternal);
|
||||||
}, [apiConfig, logLevel, appendLogInternal]);
|
}, [apiConfig, logLevel, appendLogInternal]);
|
||||||
const [refLogsContainer, containerHeight] = useRemainingViewPortHeight();
|
const [refLogsContainer, containerHeight] = useRemainingViewPortHeight();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ContentHeader title="Logs" />
|
<ContentHeader title={t('Logs')} />
|
||||||
<LogSearch />
|
<LogSearch />
|
||||||
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message */}
|
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message */}
|
||||||
<div ref={refLogsContainer} style={{ paddingBottom }}>
|
<div ref={refLogsContainer} style={{ paddingBottom }}>
|
||||||
|
@ -89,7 +91,7 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
|
||||||
<div className={s0.logPlaceholderIcon}>
|
<div className={s0.logPlaceholderIcon}>
|
||||||
<SvgYacd width={200} height={200} />
|
<SvgYacd width={200} height={200} />
|
||||||
</div>
|
</div>
|
||||||
<div>No logs yet, hang tight...</div>
|
<div>{t('no_logs')}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={s0.logsWrapper}>
|
<div className={s0.logsWrapper}>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RotateCw } from 'react-feather';
|
import { RotateCw } from 'react-feather';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { queryCache, useQuery } from 'react-query';
|
import { queryCache, useQuery } from 'react-query';
|
||||||
import { areEqual, VariableSizeList } from 'react-window';
|
import { areEqual, VariableSizeList } from 'react-window';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
@ -114,10 +115,12 @@ function Rules({ apiConfig }) {
|
||||||
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ rules: RuleItem[]; provider: {... Remove this comment to see the full error message
|
// @ts-expect-error ts-migrate(2345) FIXME: Argument of type '{ rules: RuleItem[]; provider: {... Remove this comment to see the full error message
|
||||||
const getItemSize = getItemSizeFactory({ rules, provider });
|
const getItemSize = getItemSizeFactory({ rules, provider });
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={s.header}>
|
<div className={s.header}>
|
||||||
<ContentHeader title="Rules" />
|
<ContentHeader title={t('Rules')} />
|
||||||
<TextFilter />
|
<TextFilter />
|
||||||
</div>
|
</div>
|
||||||
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message */}
|
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message */}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.root {
|
.root {
|
||||||
background: var(--color-bg-sidebar);
|
background: var(--color-bg-sidebar);
|
||||||
|
min-width: 150px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Tooltip from '@reach/tooltip';
|
||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Info } from 'react-feather';
|
import { Info } from 'react-feather';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
FcAreaChart,
|
FcAreaChart,
|
||||||
FcDocument,
|
FcDocument,
|
||||||
|
@ -85,6 +86,7 @@ const pages = [
|
||||||
];
|
];
|
||||||
|
|
||||||
function SideBar({ dispatch, theme }) {
|
function SideBar({ dispatch, theme }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const switchThemeHooked = useCallback(() => {
|
const switchThemeHooked = useCallback(() => {
|
||||||
dispatch(switchTheme());
|
dispatch(switchTheme());
|
||||||
|
@ -99,13 +101,13 @@ function SideBar({ dispatch, theme }) {
|
||||||
to={to}
|
to={to}
|
||||||
isActive={location.pathname === to}
|
isActive={location.pathname === to}
|
||||||
iconId={iconId}
|
iconId={iconId}
|
||||||
labelText={labelText}
|
labelText={t(labelText)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className={s.footer}>
|
<div className={s.footer}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label="theme"
|
label={t('theme')}
|
||||||
aria-label={
|
aria-label={
|
||||||
'switch to ' + (theme === 'light' ? 'dark' : 'light') + ' theme'
|
'switch to ' + (theme === 'light' ? 'dark' : 'light') + ' theme'
|
||||||
}
|
}
|
||||||
|
@ -117,7 +119,7 @@ function SideBar({ dispatch, theme }) {
|
||||||
{theme === 'light' ? <MoonA /> : <Sun />}
|
{theme === 'light' ? <MoonA /> : <Sun />}
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label="about">
|
<Tooltip label={t('about')}>
|
||||||
<Link to="/about" className={s.iconWrapper}>
|
<Link to="/about" className={s.iconWrapper}>
|
||||||
<Info size={20} />
|
<Info size={20} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { useMemo } from 'react';
|
import * as React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { fetchData } from '../api/traffic';
|
import { fetchData } from '../api/traffic';
|
||||||
import useLineChart from '../hooks/useLineChart';
|
import useLineChart from '../hooks/useLineChart';
|
||||||
|
@ -10,6 +11,8 @@ import {
|
||||||
import { getClashAPIConfig, getSelectedChartStyleIndex } from '../store/app';
|
import { getClashAPIConfig, getSelectedChartStyleIndex } from '../store/app';
|
||||||
import { connect } from './StateProvider';
|
import { connect } from './StateProvider';
|
||||||
|
|
||||||
|
const { useMemo } = React;
|
||||||
|
|
||||||
const chartWrapperStyle = {
|
const chartWrapperStyle = {
|
||||||
// make chartjs chart responsive
|
// make chartjs chart responsive
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
@ -26,6 +29,7 @@ export default connect(mapState)(TrafficChart);
|
||||||
function TrafficChart({ apiConfig, selectedChartStyleIndex }) {
|
function TrafficChart({ apiConfig, selectedChartStyleIndex }) {
|
||||||
const Chart = chartJSResource.read();
|
const Chart = chartJSResource.read();
|
||||||
const traffic = fetchData(apiConfig);
|
const traffic = fetchData(apiConfig);
|
||||||
|
const { t } = useTranslation();
|
||||||
const data = useMemo(
|
const data = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
labels: traffic.labels,
|
labels: traffic.labels,
|
||||||
|
@ -33,18 +37,18 @@ function TrafficChart({ apiConfig, selectedChartStyleIndex }) {
|
||||||
{
|
{
|
||||||
...commonDataSetProps,
|
...commonDataSetProps,
|
||||||
...chartStyles[selectedChartStyleIndex].up,
|
...chartStyles[selectedChartStyleIndex].up,
|
||||||
label: 'Up',
|
label: t('Up'),
|
||||||
data: traffic.up,
|
data: traffic.up,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...commonDataSetProps,
|
...commonDataSetProps,
|
||||||
...chartStyles[selectedChartStyleIndex].down,
|
...chartStyles[selectedChartStyleIndex].down,
|
||||||
label: 'Down',
|
label: t('Down'),
|
||||||
data: traffic.down,
|
data: traffic.down,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
[traffic, selectedChartStyleIndex]
|
[traffic, selectedChartStyleIndex, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
useLineChart(Chart, 'trafficChart', data, traffic);
|
useLineChart(Chart, 'trafficChart', data, traffic);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import * as connAPI from '../api/connections';
|
import * as connAPI from '../api/connections';
|
||||||
import { fetchData } from '../api/traffic';
|
import { fetchData } from '../api/traffic';
|
||||||
|
@ -15,28 +16,29 @@ const mapState = (s) => ({
|
||||||
export default connect(mapState)(TrafficNow);
|
export default connect(mapState)(TrafficNow);
|
||||||
|
|
||||||
function TrafficNow({ apiConfig }) {
|
function TrafficNow({ apiConfig }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { upStr, downStr } = useSpeed(apiConfig);
|
const { upStr, downStr } = useSpeed(apiConfig);
|
||||||
const { upTotal, dlTotal, connNumber } = useConnection(apiConfig);
|
const { upTotal, dlTotal, connNumber } = useConnection(apiConfig);
|
||||||
return (
|
return (
|
||||||
<div className={s0.TrafficNow}>
|
<div className={s0.TrafficNow}>
|
||||||
<div className="sec">
|
<div className="sec">
|
||||||
<div>Upload</div>
|
<div>{t('Upload')}</div>
|
||||||
<div>{upStr}</div>
|
<div>{upStr}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sec">
|
<div className="sec">
|
||||||
<div>Download</div>
|
<div>{t('Download')}</div>
|
||||||
<div>{downStr}</div>
|
<div>{downStr}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sec">
|
<div className="sec">
|
||||||
<div>Upload Total</div>
|
<div>{t('Upload Total')}</div>
|
||||||
<div>{upTotal}</div>
|
<div>{upTotal}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sec">
|
<div className="sec">
|
||||||
<div>Download Total</div>
|
<div>{t('Download Total')}</div>
|
||||||
<div>{dlTotal}</div>
|
<div>{dlTotal}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sec">
|
<div className="sec">
|
||||||
<div>Active Connections</div>
|
<div>{t('Active Connections')}</div>
|
||||||
<div>{connNumber}</div>
|
<div>{connNumber}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Tooltip from '@reach/tooltip';
|
import Tooltip from '@reach/tooltip';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Zap } from 'react-feather';
|
import { Zap } from 'react-feather';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { getClashAPIConfig } from '../../store/app';
|
import { getClashAPIConfig } from '../../store/app';
|
||||||
import {
|
import {
|
||||||
|
@ -80,6 +81,8 @@ function Proxies({
|
||||||
proxies: { closeModalClosePrevConns, closePrevConnsAndTheModal },
|
proxies: { closeModalClosePrevConns, closePrevConnsAndTheModal },
|
||||||
} = useStoreActions();
|
} = useStoreActions();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BaseModal
|
<BaseModal
|
||||||
|
@ -89,12 +92,12 @@ function Proxies({
|
||||||
<Settings />
|
<Settings />
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
<div className={s0.topBar}>
|
<div className={s0.topBar}>
|
||||||
<ContentHeader title="Proxies" />
|
<ContentHeader title={t('Proxies')} />
|
||||||
<div className={s0.topBarRight}>
|
<div className={s0.topBarRight}>
|
||||||
<div className={s0.textFilterContainer}>
|
<div className={s0.textFilterContainer}>
|
||||||
<TextFilter />
|
<TextFilter />
|
||||||
</div>
|
</div>
|
||||||
<Tooltip label="settings">
|
<Tooltip label={t('settings')}>
|
||||||
<Button kind="minimal" onClick={() => setIsSettingsModalOpen(true)}>
|
<Button kind="minimal" onClick={() => setIsSettingsModalOpen(true)}>
|
||||||
<Equalizer size={16} />
|
<Equalizer size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -120,7 +123,7 @@ function Proxies({
|
||||||
<Fab
|
<Fab
|
||||||
icon={isTestingLatency ? <ColorZap /> : <Zap width={16} height={16} />}
|
icon={isTestingLatency ? <ColorZap /> : <Zap width={16} height={16} />}
|
||||||
onClick={requestDelayAllFn}
|
onClick={requestDelayAllFn}
|
||||||
text="Test Latency"
|
text={t('Test Latency')}
|
||||||
position={fabPosition}
|
position={fabPosition}
|
||||||
/>
|
/>
|
||||||
<BaseModal
|
<BaseModal
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Select from 'src/components/shared/Select';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getAutoCloseOldConns,
|
getAutoCloseOldConns,
|
||||||
getHideUnavailableProxies,
|
getHideUnavailableProxies,
|
||||||
getProxySortBy,
|
getProxySortBy,
|
||||||
} from '../../store/app';
|
} from '../../store/app';
|
||||||
import Select from '../shared/Select';
|
|
||||||
import { connect, useStoreActions } from '../StateProvider';
|
import { connect, useStoreActions } from '../StateProvider';
|
||||||
import Switch from '../SwitchThemed';
|
import Switch from '../SwitchThemed';
|
||||||
import s from './Settings.module.css';
|
import s from './Settings.module.css';
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
['Natural', 'Original order in config file'],
|
['Natural', 'order_natural'],
|
||||||
['LatencyAsc', 'By latency from small to big'],
|
['LatencyAsc', 'order_latency_asc'],
|
||||||
['LatencyDesc', 'By latency from big to small'],
|
['LatencyDesc', 'order_latency_desc'],
|
||||||
['NameAsc', 'By name alphabetically (A-Z)'],
|
['NameAsc', 'order_name_asc'],
|
||||||
['NameDesc', 'By name alphabetically (Z-A)'],
|
['NameDesc', 'order_name_desc'],
|
||||||
];
|
];
|
||||||
|
|
||||||
const { useCallback } = React;
|
const { useCallback } = React;
|
||||||
|
@ -38,13 +39,16 @@ function Settings({ appConfig }) {
|
||||||
},
|
},
|
||||||
[updateAppConfig]
|
[updateAppConfig]
|
||||||
);
|
);
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={s.labeledInput}>
|
<div className={s.labeledInput}>
|
||||||
<span>Sorting in group</span>
|
<span>{t('sort_in_grp')}</span>
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
options={options}
|
options={options.map((o) => {
|
||||||
|
return [o[0], t(o[1])];
|
||||||
|
})}
|
||||||
selected={appConfig.proxySortBy}
|
selected={appConfig.proxySortBy}
|
||||||
onChange={handleProxySortByOnChange}
|
onChange={handleProxySortByOnChange}
|
||||||
/>
|
/>
|
||||||
|
@ -52,7 +56,7 @@ function Settings({ appConfig }) {
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className={s.labeledInput}>
|
<div className={s.labeledInput}>
|
||||||
<span>Hide unavailable proxies</span>
|
<span>{t('hide_unavail_proxies')}</span>
|
||||||
<div>
|
<div>
|
||||||
<Switch
|
<Switch
|
||||||
name="hideUnavailableProxies"
|
name="hideUnavailableProxies"
|
||||||
|
@ -62,7 +66,7 @@ function Settings({ appConfig }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.labeledInput}>
|
<div className={s.labeledInput}>
|
||||||
<span>Automatically close old connections</span>
|
<span>{t('auto_close_conns')}</span>
|
||||||
<div>
|
<div>
|
||||||
<Switch
|
<Switch
|
||||||
name="autoCloseOldConns"
|
name="autoCloseOldConns"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.select {
|
.select {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
line-height: 1.5;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
|
|
4
src/custom.d.ts
vendored
4
src/custom.d.ts
vendored
|
@ -7,6 +7,10 @@ declare module '*.module.css' {
|
||||||
export default classes;
|
export default classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
i18n: any;
|
||||||
|
}
|
||||||
|
|
||||||
// webpack definePlugin replacing variables
|
// webpack definePlugin replacing variables
|
||||||
declare const __VERSION__: string;
|
declare const __VERSION__: string;
|
||||||
declare const __DEV__: string;
|
declare const __DEV__: string;
|
||||||
|
|
34
src/i18n/en.ts
Normal file
34
src/i18n/en.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
export const data = {
|
||||||
|
Overview: 'Overview',
|
||||||
|
Proxies: 'Proxies',
|
||||||
|
Rules: 'Rules',
|
||||||
|
Conns: 'Conns',
|
||||||
|
Config: 'Config',
|
||||||
|
Logs: 'Logs',
|
||||||
|
Upload: 'Upload',
|
||||||
|
Download: 'Download',
|
||||||
|
'Upload Total': 'Upload Total',
|
||||||
|
'Download Total': 'Download Total',
|
||||||
|
'Active Connections': 'Active Connections',
|
||||||
|
Up: 'Up',
|
||||||
|
Down: 'Down',
|
||||||
|
'Test Latency': 'Test Latency',
|
||||||
|
settings: 'settings',
|
||||||
|
sort_in_grp: 'Sorting in group',
|
||||||
|
hide_unavail_proxies: 'Hide unavailable proxies',
|
||||||
|
auto_close_conns: 'Automatically close old connections',
|
||||||
|
order_natural: 'Original order in config file',
|
||||||
|
order_latency_asc: 'By latency from small to big',
|
||||||
|
order_latency_desc: 'By latency from big to small',
|
||||||
|
order_name_asc: 'By name alphabetically (A-Z)',
|
||||||
|
order_name_desc: 'By name alphabetically (Z-A)',
|
||||||
|
Connections: 'Connections',
|
||||||
|
Active: 'Active',
|
||||||
|
Closed: 'Closed',
|
||||||
|
theme: 'theme',
|
||||||
|
about: 'about',
|
||||||
|
no_logs: 'No logs yet, hang tight...',
|
||||||
|
chart_style: 'Chart Style',
|
||||||
|
latency_test_url: 'Latency Test URL',
|
||||||
|
lang: 'Language',
|
||||||
|
};
|
34
src/i18n/zh.ts
Normal file
34
src/i18n/zh.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
export const data = {
|
||||||
|
Overview: '概述',
|
||||||
|
Proxies: '代理',
|
||||||
|
Rules: '规则',
|
||||||
|
Conns: '连接',
|
||||||
|
Config: '配置',
|
||||||
|
Logs: '日志',
|
||||||
|
Upload: '上传',
|
||||||
|
Download: '下载',
|
||||||
|
'Upload Total': '上传总量',
|
||||||
|
'Download Total': '下载总量',
|
||||||
|
'Active Connections': '活动连接',
|
||||||
|
Up: '上传',
|
||||||
|
Down: '下载',
|
||||||
|
'Test Latency': '延迟测速',
|
||||||
|
settings: '设置',
|
||||||
|
sort_in_grp: '代理组条目排序',
|
||||||
|
hide_unavail_proxies: '隐藏不可用代理',
|
||||||
|
auto_close_conns: '切换代理时自动断开旧连接',
|
||||||
|
order_natural: '原 config 文件中的排序',
|
||||||
|
order_latency_asc: '按延迟从小到大',
|
||||||
|
order_latency_desc: '按延迟从大到小',
|
||||||
|
order_name_asc: '按名称字母排序 (A-Z)',
|
||||||
|
order_name_desc: '按名称字母排序 (Z-A)',
|
||||||
|
Connections: '连接',
|
||||||
|
Active: '活动',
|
||||||
|
Closed: '已断开',
|
||||||
|
theme: '主题',
|
||||||
|
about: '关于',
|
||||||
|
no_logs: '暂无日志...',
|
||||||
|
chart_style: '流量图样式',
|
||||||
|
latency_test_url: '延迟测速 URL',
|
||||||
|
lang: '语言',
|
||||||
|
};
|
61
src/misc/i18n.ts
Normal file
61
src/misc/i18n.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import i18next from 'i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import HttpBackend from 'i18next-http-backend';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
|
const allLocales = {
|
||||||
|
zh: import('src/i18n/zh'),
|
||||||
|
en: import('src/i18n/en'),
|
||||||
|
};
|
||||||
|
|
||||||
|
type BackendRequestCallback = (
|
||||||
|
err: null,
|
||||||
|
result: { status: number; data: any }
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
i18next
|
||||||
|
.use(HttpBackend)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.init({
|
||||||
|
debug: process.env.NODE_ENV === 'development',
|
||||||
|
// resources,
|
||||||
|
backend: {
|
||||||
|
loadPath: '/__{{lng}}/{{ns}}.json',
|
||||||
|
request: function (
|
||||||
|
_options: any,
|
||||||
|
url: string,
|
||||||
|
_payload: any,
|
||||||
|
callback: BackendRequestCallback
|
||||||
|
) {
|
||||||
|
let p: PromiseLike<{ data: any }>;
|
||||||
|
|
||||||
|
switch (url) {
|
||||||
|
case '/__zh/translation.json':
|
||||||
|
p = allLocales.zh;
|
||||||
|
break;
|
||||||
|
case '/__en/translation.json':
|
||||||
|
default:
|
||||||
|
p = allLocales.en;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p) {
|
||||||
|
p.then((mod) => {
|
||||||
|
callback(null, { status: 200, data: mod.data });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
supportedLngs: ['en', 'zh'],
|
||||||
|
fallbackLng: 'en',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
window.i18n = i18next;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default i18next;
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strictNullChecks": false,
|
"strictNullChecks": false,
|
||||||
"suppressImplicitAnyIndexErrors": true,
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
"types": ["jest"]
|
"types": ["jest"],
|
||||||
|
"resolveJsonModule": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,9 +132,7 @@ module.exports = {
|
||||||
// https://github.com/webpack/webpack/issues/11467
|
// https://github.com/webpack/webpack/issues/11467
|
||||||
{
|
{
|
||||||
test: /\.m?js/,
|
test: /\.m?js/,
|
||||||
resolve: {
|
resolve: { fullySpecified: false },
|
||||||
fullySpecified: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.[tj]sx?$/,
|
test: /\.[tj]sx?$/,
|
||||||
|
|
55
yarn.lock
55
yarn.lock
|
@ -1156,7 +1156,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/runtime@^7.12.5":
|
"@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1":
|
||||||
version "7.12.5"
|
version "7.12.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
|
||||||
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
|
||||||
|
@ -1710,6 +1710,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-table@^7.0.25":
|
||||||
|
version "7.0.25"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-table/-/react-table-7.0.25.tgz#79efba1c58149d75b3c030634ed36215b7ba8390"
|
||||||
|
integrity sha512-MLWxIiFKIW2CjcB8yQ5LfLNyVfwXfIYm2yrQfTkroK5tJ3Ai+Xzq73EQcdKWQvi/nLk431v2WV0cf30VQV+5Ow==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-tabs@^2.3.2":
|
"@types/react-tabs@^2.3.2":
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-tabs/-/react-tabs-2.3.2.tgz#99fb6866bbc6912d44f7bbc99eca03fbbd217960"
|
resolved "https://registry.yarnpkg.com/@types/react-tabs/-/react-tabs-2.3.2.tgz#99fb6866bbc6912d44f7bbc99eca03fbbd217960"
|
||||||
|
@ -4427,6 +4434,13 @@ html-minifier-terser@^5.0.1:
|
||||||
relateurl "^0.2.7"
|
relateurl "^0.2.7"
|
||||||
terser "^4.6.3"
|
terser "^4.6.3"
|
||||||
|
|
||||||
|
html-parse-stringify2@2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a"
|
||||||
|
integrity sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=
|
||||||
|
dependencies:
|
||||||
|
void-elements "^2.0.1"
|
||||||
|
|
||||||
html-webpack-plugin@^4.5.0:
|
html-webpack-plugin@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz#625097650886b97ea5dae331c320e3238f6c121c"
|
resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz#625097650886b97ea5dae331c320e3238f6c121c"
|
||||||
|
@ -4486,6 +4500,27 @@ human-signals@^1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
|
||||||
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
|
||||||
|
|
||||||
|
i18next-browser-languagedetector@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.0.1.tgz#83654bc87302be2a6a5a75146ffea97b4ca268cf"
|
||||||
|
integrity sha512-3H+OsNQn3FciomUU0d4zPFHsvJv4X66lBelXk9hnIDYDsveIgT7dWZ3/VvcSlpKk9lvCK770blRZ/CwHMXZqWw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.5.5"
|
||||||
|
|
||||||
|
i18next-http-backend@^1.0.21:
|
||||||
|
version "1.0.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-1.0.21.tgz#cee901b3527dad5165fa91de973b6aa6404c1c37"
|
||||||
|
integrity sha512-UDeHoV2B+31Gr++0KFAVjM5l+SEwePpF6sfDyaDq5ennM9QNJ78PBEMPStwkreEm4h5C8sT7M1JdNQrLcU1Wdg==
|
||||||
|
dependencies:
|
||||||
|
node-fetch "2.6.1"
|
||||||
|
|
||||||
|
i18next@^19.8.4:
|
||||||
|
version "19.8.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.8.4.tgz#447718f2a26319b8debdbcc6fbc1a9761be7316b"
|
||||||
|
integrity sha512-FfVPNWv+felJObeZ6DSXZkj9QM1Ivvh7NcFCgA8XPtJWHz0iXVa9BUy+QY8EPrCLE+vWgDfV/sc96BgXVo6HAA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.12.0"
|
||||||
|
|
||||||
iconv-lite@0.4.24:
|
iconv-lite@0.4.24:
|
||||||
version "0.4.24"
|
version "0.4.24"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
|
@ -5357,6 +5392,11 @@ no-case@^3.0.3:
|
||||||
lower-case "^2.0.1"
|
lower-case "^2.0.1"
|
||||||
tslib "^1.10.0"
|
tslib "^1.10.0"
|
||||||
|
|
||||||
|
node-fetch@2.6.1:
|
||||||
|
version "2.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||||
|
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||||
|
|
||||||
"node-libs-browser@^1.0.0 || ^2.0.0":
|
"node-libs-browser@^1.0.0 || ^2.0.0":
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
|
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
|
||||||
|
@ -6422,6 +6462,14 @@ react-helmet@^6.1.0:
|
||||||
react-fast-compare "^3.1.1"
|
react-fast-compare "^3.1.1"
|
||||||
react-side-effect "^2.1.0"
|
react-side-effect "^2.1.0"
|
||||||
|
|
||||||
|
react-i18next@^11.7.4:
|
||||||
|
version "11.7.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.7.4.tgz#6c0142e15652d8dd80cd7d857e36efe2e9d4d09a"
|
||||||
|
integrity sha512-Aq0+QVW7NMYuAtk0Stcwp4jWeNTd1p5XefAfBPcjs/4c/2duG3v3G3zdtn8fC8L4EyA/coKLwdULHI+lYTbF8w==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.3.1"
|
||||||
|
html-parse-stringify2 "2.0.1"
|
||||||
|
|
||||||
react-icons@^3.10.0:
|
react-icons@^3.10.0:
|
||||||
version "3.11.0"
|
version "3.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.11.0.tgz#2ca2903dfab8268ca18ebd8cc2e879921ec3b254"
|
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.11.0.tgz#2ca2903dfab8268ca18ebd8cc2e879921ec3b254"
|
||||||
|
@ -7682,6 +7730,11 @@ vm-browserify@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
||||||
|
|
||||||
|
void-elements@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
|
||||||
|
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
|
||||||
|
|
||||||
warning@^4.0.3:
|
warning@^4.0.3:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||||
|
|
Loading…
Reference in a new issue