feat: i18n supports

This commit is contained in:
GyDi 2022-03-12 23:07:45 +08:00
parent a393b8b122
commit 4991f7ff39
No known key found for this signature in database
GPG Key ID: 1C95E0D3467B3084
19 changed files with 254 additions and 60 deletions

View File

@ -23,8 +23,10 @@
"ahooks": "^3.1.13",
"axios": "^0.26.0",
"dayjs": "^1.10.8",
"i18next": "^21.6.14",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-i18next": "^11.15.6",
"react-router-dom": "^6.2.2",
"react-virtuoso": "^2.7.0",
"recoil": "^0.6.1",

View File

@ -1,6 +1,6 @@
use crate::log_if_err;
use crate::{
core::Clash,
log_if_err,
utils::{config, dirs, sysopt::SysProxyConfig},
};
use anyhow::{bail, Result};
@ -12,6 +12,9 @@ use tauri::{async_runtime::Mutex, utils::platform::current_exe};
/// ### `verge.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct VergeConfig {
// i18n
pub language: Option<String>,
/// `light` or `dark`
pub theme_mode: Option<String>,
@ -188,6 +191,9 @@ impl Verge {
/// so call the save_file at the end is savely
pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> {
// only change it
if patch.language.is_some() {
self.config.language = patch.language;
}
if patch.theme_mode.is_some() {
self.config.theme_mode = patch.theme_mode;
}

View File

@ -21,6 +21,7 @@ items: ~
/// template for `verge.yaml`
pub const VERGE_CONFIG: &[u8] = b"# Defaulf Config For Clash Verge
language: en
theme_mode: light
theme_blur: false
traffic_graph: true

View File

@ -1,5 +1,6 @@
import useSWR, { useSWRConfig } from "swr";
import { useSetRecoilState } from "recoil";
import { useTranslation } from "react-i18next";
import {
ListItemText,
TextField,
@ -21,15 +22,16 @@ interface Props {
}
const SettingClash = ({ onError }: Props) => {
const { t } = useTranslation();
const { mutate } = useSWRConfig();
const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
const { data: versionData } = useSWR("getVersion", getVersion);
const {
ipv6 = false,
"allow-lan": allowLan = false,
"log-level": logLevel = "silent",
"mixed-port": mixedPort = 0,
ipv6,
"allow-lan": allowLan,
"log-level": logLevel,
"mixed-port": mixedPort,
} = clashConfig ?? {};
const setGlobalClashPort = useSetRecoilState(atomClashPort);
@ -64,11 +66,11 @@ const SettingClash = ({ onError }: Props) => {
: versionData?.version || "-";
return (
<SettingList title="Clash Setting">
<SettingList title={t("Clash Setting")}>
<SettingItem>
<ListItemText primary="Allow Lan" />
<ListItemText primary={t("Allow Lan")} />
<GuardState
value={allowLan}
value={allowLan ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
@ -80,9 +82,9 @@ const SettingClash = ({ onError }: Props) => {
</SettingItem>
<SettingItem>
<ListItemText primary="IPv6" />
<ListItemText primary={t("IPv6")} />
<GuardState
value={ipv6}
value={ipv6 ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
@ -94,9 +96,9 @@ const SettingClash = ({ onError }: Props) => {
</SettingItem>
<SettingItem>
<ListItemText primary="Log Level" />
<ListItemText primary={t("Log Level")} />
<GuardState
value={logLevel}
value={logLevel ?? "info"}
onCatch={onError}
onFormat={(e: any) => e.target.value}
onChange={(e) => onChangeData({ "log-level": e })}
@ -113,9 +115,9 @@ const SettingClash = ({ onError }: Props) => {
</SettingItem>
<SettingItem>
<ListItemText primary="Mixed Port" />
<ListItemText primary={t("Mixed Port")} />
<GuardState
value={mixedPort!}
value={mixedPort ?? 0}
onCatch={onError}
onFormat={(e: any) => +e.target.value?.replace(/\D+/, "")}
onChange={(e) => onChangeData({ "mixed-port": e })}
@ -127,7 +129,7 @@ const SettingClash = ({ onError }: Props) => {
</SettingItem>
<SettingItem>
<ListItemText primary="Clash core" />
<ListItemText primary={t("Clash core")} />
<Typography sx={{ py: 1 }}>{clashVer}</Typography>
</SettingItem>
</SettingList>

View File

@ -1,4 +1,5 @@
import useSWR, { useSWRConfig } from "swr";
import { useTranslation } from "react-i18next";
import { Box, ListItemText, Switch, TextField } from "@mui/material";
import { getVergeConfig, patchVergeConfig } from "../../services/cmds";
import { SettingList, SettingItem } from "./setting";
@ -11,15 +12,16 @@ interface Props {
}
const SettingSystem = ({ onError }: Props) => {
const { t } = useTranslation();
const { mutate } = useSWRConfig();
const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig);
const {
enable_tun_mode = false,
enable_auto_launch = false,
enable_system_proxy = false,
system_proxy_bypass = "",
enable_proxy_guard = false,
enable_tun_mode,
enable_auto_launch,
enable_system_proxy,
system_proxy_bypass,
enable_proxy_guard,
} = vergeConfig ?? {};
const onSwitchFormat = (_e: any, value: boolean) => value;
@ -28,11 +30,11 @@ const SettingSystem = ({ onError }: Props) => {
};
return (
<SettingList title="System Setting">
<SettingList title={t("System Setting")}>
<SettingItem>
<ListItemText primary="Tun Mode" />
<ListItemText primary={t("Tun Mode")} />
<GuardState
value={enable_tun_mode}
value={enable_tun_mode ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
@ -44,9 +46,9 @@ const SettingSystem = ({ onError }: Props) => {
</SettingItem>
<SettingItem>
<ListItemText primary="Auto Launch" />
<ListItemText primary={t("Auto Launch")} />
<GuardState
value={enable_auto_launch}
value={enable_auto_launch ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
@ -61,13 +63,13 @@ const SettingSystem = ({ onError }: Props) => {
<ListItemText
primary={
<Box sx={{ display: "flex", alignItems: "center" }}>
System Proxy
{t("System Proxy")}
<SysproxyTooltip />
</Box>
}
/>
<GuardState
value={enable_system_proxy}
value={enable_system_proxy ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
@ -83,9 +85,9 @@ const SettingSystem = ({ onError }: Props) => {
{enable_system_proxy && (
<SettingItem>
<ListItemText primary="Proxy Guard" />
<ListItemText primary={t("Proxy Guard")} />
<GuardState
value={enable_proxy_guard}
value={enable_proxy_guard ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
@ -99,7 +101,7 @@ const SettingSystem = ({ onError }: Props) => {
{enable_system_proxy && (
<SettingItem>
<ListItemText primary="Proxy Bypass" />
<ListItemText primary={t("Proxy Bypass")} />
<GuardState
value={system_proxy_bypass ?? ""}
onCatch={onError}

View File

@ -1,5 +1,13 @@
import useSWR, { useSWRConfig } from "swr";
import { IconButton, ListItemText, Switch, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import {
IconButton,
ListItemText,
MenuItem,
Select,
Switch,
Typography,
} from "@mui/material";
import {
getVergeConfig,
openAppDir,
@ -18,11 +26,11 @@ interface Props {
}
const SettingVerge = ({ onError }: Props) => {
const { t } = useTranslation();
const { mutate } = useSWRConfig();
const { data: vergeConfig } = useSWR("getVergeConfig", getVergeConfig);
const { theme_mode = "light", theme_blur = false, traffic_graph } =
vergeConfig ?? {};
const { theme_mode, theme_blur, traffic_graph, language } = vergeConfig ?? {};
const onSwitchFormat = (_e: any, value: boolean) => value;
const onChangeData = (patch: Partial<CmdType.VergeConfig>) => {
@ -30,9 +38,9 @@ const SettingVerge = ({ onError }: Props) => {
};
return (
<SettingList title="Verge Setting">
<SettingList title={t("Verge Setting")}>
<SettingItem>
<ListItemText primary="Theme Mode" />
<ListItemText primary={t("Theme Mode")} />
<GuardState
value={theme_mode === "dark"}
valueProps="checked"
@ -48,9 +56,9 @@ const SettingVerge = ({ onError }: Props) => {
</SettingItem>
<SettingItem>
<ListItemText primary="Theme Blur" />
<ListItemText primary={t("Theme Blur")} />
<GuardState
value={theme_blur}
value={theme_blur ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
@ -62,7 +70,7 @@ const SettingVerge = ({ onError }: Props) => {
</SettingItem>
<SettingItem>
<ListItemText primary="Traffic Graph" />
<ListItemText primary={t("Traffic Graph")} />
<GuardState
value={traffic_graph ?? true}
valueProps="checked"
@ -76,21 +84,37 @@ const SettingVerge = ({ onError }: Props) => {
</SettingItem>
<SettingItem>
<ListItemText primary="Open App Dir" />
<ListItemText primary={t("Language")} />
<GuardState
value={language ?? "en"}
onCatch={onError}
onFormat={(e: any) => e.target.value}
onChange={(e) => onChangeData({ language: e })}
onGuard={(e) => patchVergeConfig({ language: e })}
>
<Select size="small" sx={{ width: 100 }}>
<MenuItem value="zh"></MenuItem>
<MenuItem value="en">English</MenuItem>
</Select>
</GuardState>
</SettingItem>
<SettingItem>
<ListItemText primary={t("Open App Dir")} />
<IconButton color="inherit" size="small" onClick={openAppDir}>
<ArrowForward />
</IconButton>
</SettingItem>
<SettingItem>
<ListItemText primary="Open Logs Dir" />
<ListItemText primary={t("Open Logs Dir")} />
<IconButton color="inherit" size="small" onClick={openLogsDir}>
<ArrowForward />
</IconButton>
</SettingItem>
<SettingItem>
<ListItemText primary="Version" />
<ListItemText primary={t("Version")} />
<Typography sx={{ py: "6px" }}>v{version}</Typography>
</SettingItem>
</SettingList>

42
src/locales/en.json Normal file
View File

@ -0,0 +1,42 @@
{
"Label-Proxies": "Proxies",
"Label-Profiles": "Profiles",
"Label-Connections": "Connections",
"Label-Logs": "Logs",
"Label-Settings": "Settings",
"Connections": "Connections",
"Logs": "Logs",
"Clear": "Clear",
"Proxies": "Proxies",
"Proxy Groups": "Proxy Groups",
"rule": "rule",
"global": "global",
"direct": "direct",
"Profiles": "Profiles",
"Profile URL": "Profile URL",
"Import": "Import",
"New": "New",
"Settings": "Settings",
"Clash Setting": "Clash Setting",
"System Setting": "System Setting",
"Verge Setting": "Verge Setting",
"Allow Lan": "Allow Lan",
"IPv6": "IPv6",
"Log Level": "Log Level",
"Mixed Port": "Mixed Port",
"Clash core": "Clash core",
"Tun Mode": "Tun Mode",
"Auto Launch": "Auto Launch",
"System Proxy": "System Proxy",
"Proxy Guard": "Proxy Guard",
"Proxy Bypass": "Proxy Bypass",
"Theme Mode": "Theme Mode",
"Theme Blur": "Theme Blur",
"Traffic Graph": "Traffic Graph",
"Language": "Language",
"Open App Dir": "Open App Dir",
"Open Logs Dir": "Open Logs Dir",
"Version": "Version"
}

42
src/locales/zh.json Normal file
View File

@ -0,0 +1,42 @@
{
"Label-Proxies": "代 理",
"Label-Profiles": "配 置",
"Label-Connections": "连 接",
"Label-Logs": "日 志",
"Label-Settings": "设 置",
"Connections": "连接",
"Logs": "日志",
"Clear": "清除",
"Proxies": "代理",
"Proxy Groups": "代理组",
"rule": "规则",
"global": "全局",
"direct": "直连",
"Profiles": "配置",
"Profile URL": "配置文件链接",
"Import": "导入",
"New": "新建",
"Settings": "设置",
"Clash Setting": "Clash 设置",
"System Setting": "系统设置",
"Verge Setting": "Verge 设置",
"Allow Lan": "局域网连接",
"IPv6": "IPv6",
"Log Level": "日志等级",
"Mixed Port": "端口设置",
"Clash core": "Clash 内核",
"Tun Mode": "Tun 模式",
"Auto Launch": "开机自启",
"System Proxy": "系统代理",
"Proxy Guard": "系统代理守卫",
"Proxy Bypass": "Proxy Bypass",
"Theme Mode": "暗夜模式",
"Theme Blur": "背景模糊",
"Traffic Graph": "流量图显",
"Language": "语言设置",
"Open App Dir": "应用目录",
"Open Logs Dir": "日志目录",
"Version": "版本"
}

View File

@ -7,6 +7,7 @@ import { RecoilRoot } from "recoil";
import { BrowserRouter } from "react-router-dom";
import Layout from "./pages/_layout";
import enhance from "./services/enhance";
import "./services/i18n";
enhance.setup();

View File

@ -1,5 +1,7 @@
import i18next from "i18next";
import useSWR, { SWRConfig, useSWRConfig } from "swr";
import { useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Route, Routes } from "react-router-dom";
import { alpha, createTheme, List, Paper, ThemeProvider } from "@mui/material";
import { listen } from "@tauri-apps/api/event";
@ -16,6 +18,7 @@ import UpdateButton from "../components/layout/update-button";
const isMacos = navigator.userAgent.includes("Mac OS X");
const Layout = () => {
const { t } = useTranslation();
const { mutate } = useSWRConfig();
const { data } = useSWR("getVergeConfig", getVergeConfig);
@ -37,6 +40,12 @@ const Layout = () => {
});
}, []);
useEffect(() => {
if (data?.language) {
i18next.changeLanguage(data.language);
}
}, [data?.language]);
const theme = useMemo(() => {
// const background = mode === "light" ? "#f5f5f5" : "#000";
const selectColor = mode === "light" ? "#f5f5f5" : "#d5d5d5";
@ -87,7 +96,7 @@ const Layout = () => {
<List className="the-menu" data-windrag>
{routers.map((router) => (
<LayoutItem key={router.label} to={router.link}>
{router.label}
{t(router.label)}
</LayoutItem>
))}
</List>

View File

@ -6,27 +6,27 @@ import ConnectionsPage from "./connections";
export const routers = [
{
label: "Proxies",
label: "Label-Proxies",
link: "/",
ele: ProxiesPage,
},
{
label: "Profiles",
label: "Label-Profiles",
link: "/profile",
ele: ProfilesPage,
},
{
label: "Connections",
label: "Label-Connections",
link: "/connections",
ele: ConnectionsPage,
},
{
label: "Logs",
label: "Label-Logs",
link: "/logs",
ele: LogsPage,
},
{
label: "Settings",
label: "Label-Settings",
link: "/settings",
ele: SettingsPage,
},

View File

@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import { Paper } from "@mui/material";
import { Virtuoso } from "react-virtuoso";
import { useTranslation } from "react-i18next";
import { ApiType } from "../services/types";
import { getInfomation } from "../services/api";
import BasePage from "../components/base/base-page";
@ -8,6 +9,8 @@ import ConnectionItem from "../components/connection/connection-item";
const ConnectionsPage = () => {
const initConn = { uploadTotal: 0, downloadTotal: 0, connections: [] };
const { t } = useTranslation();
const [conn, setConn] = useState<ApiType.Connections>(initConn);
useEffect(() => {
@ -27,7 +30,7 @@ const ConnectionsPage = () => {
}, []);
return (
<BasePage title="Connections" contentStyle={{ height: "100%" }}>
<BasePage title={t("Connections")} contentStyle={{ height: "100%" }}>
<Paper sx={{ boxShadow: 2, height: "100%" }}>
<Virtuoso
data={conn.connections}

View File

@ -1,16 +1,18 @@
import { useRecoilState } from "recoil";
import { Button, Paper } from "@mui/material";
import { Virtuoso } from "react-virtuoso";
import { useTranslation } from "react-i18next";
import { atomLogData } from "../services/states";
import BasePage from "../components/base/base-page";
import LogItem from "../components/log/log-item";
const LogPage = () => {
const { t } = useTranslation();
const [logData, setLogData] = useRecoilState(atomLogData);
return (
<BasePage
title="Logs"
title={t("Logs")}
contentStyle={{ height: "100%" }}
header={
<Button
@ -19,7 +21,7 @@ const LogPage = () => {
variant="contained"
onClick={() => setLogData([])}
>
Clear
{t("Clear")}
</Button>
}
>

View File

@ -2,6 +2,7 @@ import useSWR, { useSWRConfig } from "swr";
import { useLockFn } from "ahooks";
import { useEffect, useMemo, useState } from "react";
import { Box, Button, Grid, TextField } from "@mui/material";
import { useTranslation } from "react-i18next";
import {
getProfiles,
patchProfile,
@ -19,6 +20,7 @@ import ProfileItem from "../components/profile/profile-item";
import ProfileMore from "../components/profile/profile-more";
const ProfilePage = () => {
const { t } = useTranslation();
const { mutate } = useSWRConfig();
const [url, setUrl] = useState("");
@ -175,12 +177,12 @@ const ProfilePage = () => {
});
return (
<BasePage title="Profiles">
<BasePage title={t("Profiles")}>
<Box sx={{ display: "flex", mb: 2.5 }}>
<TextField
id="clas_verge_profile_url"
name="profile_url"
label="Profile URL"
label={t("Profile URL")}
size="small"
fullWidth
value={url}
@ -193,10 +195,10 @@ const ProfilePage = () => {
onClick={onImport}
sx={{ mr: 1 }}
>
Import
{t("Import")}
</Button>
<Button variant="contained" onClick={() => setDialogOpen(true)}>
New
{t("New")}
</Button>
</Box>

View File

@ -1,6 +1,7 @@
import useSWR, { useSWRConfig } from "swr";
import { useEffect } from "react";
import { useLockFn } from "ahooks";
import { useTranslation } from "react-i18next";
import { Button, ButtonGroup, List, Paper } from "@mui/material";
import { getClashConfig, updateConfigs } from "../services/api";
import { patchClashConfig } from "../services/cmds";
@ -10,6 +11,7 @@ import ProxyGroup from "../components/proxy/proxy-group";
import ProxyGlobal from "../components/proxy/proxy-global";
const ProxyPage = () => {
const { t } = useTranslation();
const { mutate } = useSWRConfig();
const { data: proxiesData } = useSWR("getProxies", getProxies);
const { data: clashConfig } = useSWR("getClashConfig", getClashConfig);
@ -45,7 +47,7 @@ const ProxyPage = () => {
return (
<BasePage
contentStyle={pageStyle}
title={showGroup ? "Proxy Groups" : "Proxies"}
title={showGroup ? t("Proxy Groups") : t("Proxies")}
header={
<ButtonGroup size="small">
{modeList.map((mode) => (
@ -55,7 +57,7 @@ const ProxyPage = () => {
onClick={() => onChangeMode(mode)}
sx={{ textTransform: "capitalize" }}
>
{mode}
{t(mode)}
</Button>
))}
</ButtonGroup>

View File

@ -1,4 +1,5 @@
import { Paper } from "@mui/material";
import { useTranslation } from "react-i18next";
import Notice from "../components/base/base-notice";
import BasePage from "../components/base/base-page";
import SettingVerge from "../components/setting/setting-verge";
@ -6,12 +7,14 @@ import SettingClash from "../components/setting/setting-clash";
import SettingSystem from "../components/setting/setting-system";
const SettingPage = () => {
const onError = (error: any) => {
error && Notice.error(error.toString());
const { t } = useTranslation();
const onError = (err: any) => {
Notice.error(err?.message || err.toString());
};
return (
<BasePage title="Settings">
<BasePage title={t("Settings")}>
<Paper sx={{ borderRadius: 1, boxShadow: 2, mb: 3 }}>
<SettingClash onError={onError} />
</Paper>

17
src/services/i18n.ts Normal file
View File

@ -0,0 +1,17 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import en from "../locales/en.json";
import zh from "../locales/zh.json";
const resources = {
en: { translation: en },
zh: { translation: zh },
};
i18n.use(initReactI18next).init({
resources,
lng: "en",
interpolation: {
escapeValue: false,
},
});

View File

@ -120,6 +120,7 @@ export namespace CmdType {
}
export interface VergeConfig {
language?: string;
theme_mode?: "light" | "dark";
theme_blur?: boolean;
traffic_graph?: boolean;

View File

@ -227,7 +227,7 @@
"@babel/plugin-syntax-jsx" "^7.16.7"
"@babel/types" "^7.17.0"
"@babel/runtime@^7.13.10", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7":
"@babel/runtime@^7.13.10", "@babel/runtime@^7.14.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7":
version "7.17.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
@ -1291,6 +1291,18 @@ hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
dependencies:
react-is "^16.7.0"
html-escaper@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
html-parse-stringify@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
dependencies:
void-elements "3.1.0"
human-signals@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
@ -1301,6 +1313,13 @@ husky@^7.0.0:
resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535"
integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==
i18next@^21.6.14:
version "21.6.14"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.6.14.tgz#2bc199fba7f4da44b5952d7df0a3814a6e5c3943"
integrity sha512-XL6WyD+xlwQwbieXRlXhKWoLb/rkch50/rA+vl6untHnJ+aYnkQ0YDZciTWE78PPhOpbi2gR0LTJCJpiAhA+uQ==
dependencies:
"@babel/runtime" "^7.17.2"
ignore@^5.1.4:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
@ -1653,6 +1672,15 @@ react-dom@^17.0.2:
object-assign "^4.1.1"
scheduler "^0.20.2"
react-i18next@^11.15.6:
version "11.15.6"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.15.6.tgz#693430fbee5ac7d0774bd88683575d62adb24afb"
integrity sha512-OUWcFdNgIA9swVx3JGIreuquglAinpRwB/HYrCprTN+s9BQDt9LSiY7x5DGc2JzVpwqtpoTV7oRUTOxEPNyUPw==
dependencies:
"@babel/runtime" "^7.14.5"
html-escaper "^2.0.2"
html-parse-stringify "^3.0.1"
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -1901,6 +1929,11 @@ vite@^2.8.6:
optionalDependencies:
fsevents "~2.3.2"
void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
web-streams-polyfill@^3.0.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965"