diff --git a/package.json b/package.json index e013883..a22e99e 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,9 @@ "html-webpack-plugin": "4.0.0-beta.5", "http-proxy-middleware": "^0.20.0", "husky": "^4.2.5", + "i18next": "^21.6.16", + "i18next-browser-languagedetector": "^6.1.4", + "i18next-http-backend": "^1.4.0", "identity-obj-proxy": "3.0.0", "invariant": "^2.2.4", "is-wsl": "^1.1.0", @@ -76,6 +79,7 @@ "react-dom": "^16.12.0", "react-highlight-words": "^0.18.0", "react-hotkeys": "^2.0.0", + "react-i18next": "^11.16.7", "react-lazy-load-image-component": "^1.3.2", "react-load-script": "^0.0.6", "react-monaco-editor": "^0.36.0", diff --git a/public/locales/en-US/application.json b/public/locales/en-US/application.json new file mode 100644 index 0000000..1081e50 --- /dev/null +++ b/public/locales/en-US/application.json @@ -0,0 +1,19 @@ +{ + "login": { + "email": "Email", + "password": "Password", + "captcha": "CAPTCHA", + "signIn": "Sign in", + "signUp": "Sign up", + "signUpAccount": "Sign up", + "useFIDO2": "Use Hardware Authenticator", + "usePassword": "Use Password", + "forgetPassword": "Forgot password?", + "2FA": "2FA Verification", + "input2FACode": "Please enter the six-digit 2FA verification code", + "browserNotSupport": "Not supported by current browser or environment", + "success": "Sign in successful", + "title": "Sign in {{title}}", + "continue": "Continue" + } +} \ No newline at end of file diff --git a/public/locales/en-US/common.json b/public/locales/en-US/common.json new file mode 100644 index 0000000..b6e6d7a --- /dev/null +++ b/public/locales/en-US/common.json @@ -0,0 +1,18 @@ +{ + "pageNotFound": "Page not found", + "unknownError": "Unknown error", + "errLoadingSiteConfig": "Unable to load site configuration: ", + "newVersionRefresh": "A new version of the current page is available and ready to be refreshed.", + "errorDetails": "Error details", + "renderError": "There is an error in the page rendering, please try refreshing this page.", + "errors": { + "40020": "Wrong password or email address", + "40017": "This account has been blocked", + "40018": "This account is not activated", + "40019": "This feature is not enabled", + "40001": "Wrong format of input parameters ({{message}})", + "40021": "User not found", + "40022": "Verification code not correct", + "40023": "Login session not exist" + } +} \ No newline at end of file diff --git a/public/locales/zh-CN/application.json b/public/locales/zh-CN/application.json new file mode 100644 index 0000000..2b49274 --- /dev/null +++ b/public/locales/zh-CN/application.json @@ -0,0 +1,19 @@ +{ + "login": { + "email": "电子邮箱", + "password": "密码", + "captcha": "验证码", + "signIn": "登录", + "signUp": "注册", + "signUpAccount": "注册账号", + "useFIDO2": "使用外部验证器登录", + "usePassword": "使用密码登录", + "forgetPassword": "忘记密码", + "2FA": "二步验证", + "input2FACode": "请输入六位二步验证代码", + "browserNotSupport": "当前浏览器或环境不支持", + "success": "登录成功", + "title": "登录 {{title}}", + "continue": "下一步" + } +} \ No newline at end of file diff --git a/public/locales/zh-CN/common.json b/public/locales/zh-CN/common.json new file mode 100644 index 0000000..b2df006 --- /dev/null +++ b/public/locales/zh-CN/common.json @@ -0,0 +1,18 @@ +{ + "pageNotFound": "页面不存在", + "unknownError": "未知错误", + "errLoadingSiteConfig": "无法加载站点配置:", + "errorDetails": "错误详情", + "newVersionRefresh": "当前页面有新版本可用,准备刷新。", + "renderError": "页面渲染出现错误,请尝试刷新此页面。", + "errors": { + "40020": "用户邮箱或密码错误", + "40017": "该账号已被封禁", + "40018": "该账号未激活", + "40019": "此功能未启用", + "40001": "输入参数格式有误 ({{message}})", + "40021": "用户不存在", + "40022": "验证代码不正确", + "40023": "登录会话不存在" + } +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 3c528ef..b83265c 100644 --- a/src/App.js +++ b/src/App.js @@ -33,6 +33,7 @@ import PageLoading from "./component/Placeholder/PageLoading"; import CodeViewer from "./component/Viewer/Code"; import MusicPlayer from "./component/FileManager/MusicPlayer"; import EpubViewer from "./component/Viewer/Epub"; +import { useTranslation } from "react-i18next"; const PDFViewer = React.lazy(() => import(/* webpackChunkName: "pdf" */ "./component/Viewer/PDF") @@ -42,6 +43,7 @@ export default function App() { const themeConfig = useSelector((state) => state.siteConfig.theme); const isLogin = useSelector((state) => state.viewUpdate.isLogin); const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); + const { t } = useTranslation(); const theme = React.useMemo(() => { themeConfig.palette.type = prefersDarkMode ? "dark" : "light"; @@ -61,6 +63,13 @@ export default function App() { : themeConfig.palette.primary.main, }, }, + overrides: { + MuiButton: { + root: { + textTransform: "none", + }, + }, + }, }); changeThemeColor( themeConfig.palette.type === "dark" @@ -164,11 +173,19 @@ export default function App() { - + - + @@ -215,7 +232,7 @@ export default function App() { - + diff --git a/src/component/Common/ICPFooter.js b/src/component/Common/ICPFooter.js index 056eaaf..d0390b4 100644 --- a/src/component/Common/ICPFooter.js +++ b/src/component/Common/ICPFooter.js @@ -30,7 +30,6 @@ export const ICPFooter = () => { } return (
- {`备案号: `} ({ layout: { @@ -87,6 +92,8 @@ function useQuery() { } function LoginForm() { + const { t } = useTranslation(); + const [email, setEmail] = useState(""); const [pwd, setPwd] = useState(""); const [loading, setLoading] = useState(false); @@ -95,7 +102,9 @@ function LoginForm() { const [faCode, setFACode] = useState(""); const loginCaptcha = useSelector((state) => state.siteConfig.loginCaptcha); - const registerEnabled = useSelector((state) => state.siteConfig.registerEnabled); + const registerEnabled = useSelector( + (state) => state.siteConfig.registerEnabled + ); const title = useSelector((state) => state.siteConfig.title); const authn = useSelector((state) => state.siteConfig.authn); @@ -143,7 +152,7 @@ function LoginForm() { SetSessionStatus(true); history.push("/home"); - ToggleSnackbar("top", "right", "登录成功", "success"); + ToggleSnackbar("top", "right", t("login.success"), "success"); localStorage.removeItem("siteConfigCache"); }; @@ -151,7 +160,12 @@ function LoginForm() { const authnLogin = (e) => { e.preventDefault(); if (!navigator.credentials) { - ToggleSnackbar("top", "right", "当前浏览器或环境不支持", "warning"); + ToggleSnackbar( + "top", + "right", + t("login.browserNotSupport"), + "warning" + ); return; } @@ -261,13 +275,13 @@ function LoginForm() { - 登录 {title} + {t("login.title", { title })} {!useAuthn && (
- 电子邮箱 + {t("login.email")} - 密码 + {t("login.password")} - 登录 + {t("login.signIn")} )} @@ -315,7 +329,7 @@ function LoginForm() {
- 电子邮箱 + {t("login.email")} - 下一步 + {t("login.continue")} )}
- 忘记密码 + + {t("login.forgetPassword")} +
- { registerEnabled && 注册账号 } + {registerEnabled && ( + + {t("login.signUpAccount")} + + )}
@@ -368,7 +388,7 @@ function LoginForm() { marginRight: 8, }} /> - 使用外部验证器登录 + {t("login.useFIDO2")} )} {useAuthn && ( @@ -378,7 +398,7 @@ function LoginForm() { marginRight: 8, }} /> - 使用密码登录 + {t("login.usePassword")} )} @@ -392,12 +412,12 @@ function LoginForm() { - 二步验证 + {t("login.2FA")}
- 请输入六位二步验证代码 + {t("login.input2FACode")} - 继续登录 + {t("login.continue")} {" "} {" "} diff --git a/src/component/Placeholder/ErrorBoundary.js b/src/component/Placeholder/ErrorBoundary.js index 2311df4..ae4bab9 100644 --- a/src/component/Placeholder/ErrorBoundary.js +++ b/src/component/Placeholder/ErrorBoundary.js @@ -1,5 +1,6 @@ import React from "react"; import { withStyles } from "@material-ui/core"; +import { withTranslation } from "react-i18next"; const styles = { h1: { @@ -18,7 +19,6 @@ class ErrorBoundary extends React.Component { } static getDerivedStateFromError() { - // 更新 state 使下一次渲染能够显示降级后的 UI return { hasError: true }; } @@ -30,20 +30,17 @@ class ErrorBoundary extends React.Component { } render() { - const { classes } = this.props; + const { classes, t } = this.props; if (this.state.hasError) { - // 你可以自定义降级后的 UI 并渲染 return ( <>

:(

-

- 页面渲染出现错误,请尝试刷新此页面。 -

+

{t("renderError")}

{this.state.error && this.state.errorInfo && this.state.errorInfo.componentStack && (
- 错误详情 + {t("errorDetails")}
                                     {this.state.error.toString()}
                                 
@@ -62,4 +59,4 @@ class ErrorBoundary extends React.Component { } } -export default withStyles(styles)(ErrorBoundary); +export default withTranslation(["common"])(withStyles(styles)(ErrorBoundary)); diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..47ed3be --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,21 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; + +import Backend from "i18next-http-backend"; +import LanguageDetector from "i18next-browser-languagedetector"; + +i18n.use(Backend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + fallbackLng: "en-US", + debug: true, + ns: ["common", "application"], + load: "currentOnly", + defaultNS: "application", + interpolation: { + escapeValue: false, + }, + }); + +export default i18n; diff --git a/src/index.js b/src/index.js index d411ac2..dd9e9e5 100644 --- a/src/index.js +++ b/src/index.js @@ -11,16 +11,16 @@ import { UpdateSiteConfig } from "./middleware/Init"; import ErrorBoundary from "./component/Placeholder/ErrorBoundary"; import { createBrowserHistory } from "history"; import { ConnectedRouter, routerMiddleware } from "connected-react-router"; +import i18next from "./i18n"; const Admin = React.lazy(() => import("./Admin")); if (window.location.hash !== "") { window.location.href = window.location.hash.split("#")[1]; } - serviceWorker.register({ onUpdate: (registration) => { - alert("当前页面有新版本可用,准备刷新。"); + alert(i18next.t("newVersionRefresh", { ns: "common" })); if (registration && registration.waiting) { registration.waiting.postMessage({ type: "SKIP_WAITING" }); } @@ -41,21 +41,21 @@ const store = createStore(cloureveApp(history), reduxEnhance); UpdateSiteConfig(store); ReactDOM.render( - - - - - - + + + + + + - - - - - - - - - , + + + + + + + + + , document.getElementById("root") ); diff --git a/src/middleware/Api.ts b/src/middleware/Api.ts index 267eb23..f1daf1c 100644 --- a/src/middleware/Api.ts +++ b/src/middleware/Api.ts @@ -1,5 +1,6 @@ import axios from "axios"; import Auth from "./Auth"; +import i18next from "../i18n"; export const baseURL = "/api/v3"; @@ -34,7 +35,16 @@ class AppError extends Error { constructor(message: string | undefined, public code: any, error: any) { super(message); this.code = code; - this.message = message || "未知错误"; + if (i18next.exists(`errors.${code}`, { ns: "common" })) { + this.message = i18next.t(`errors.${code}`, { + ns: "common", + message, + }); + } else { + this.message = + message || i18next.t("unknownError", { ns: "common" }); + } + this.message += error ? " " + error : ""; this.stack = new Error().stack; } @@ -49,13 +59,13 @@ instance.interceptors.response.use( response.rawData.code !== 0 && response.rawData.code !== 203 ) { - // 登录过期 + // Login expired if (response.rawData.code === 401) { Auth.signout(); window.location.href = "/login"; } - // 非管理员 + // Non-admin if (response.rawData.code === 40008) { window.location.href = "/home"; } diff --git a/src/middleware/Init.js b/src/middleware/Init.js index fc4e943..8aa4585 100644 --- a/src/middleware/Init.js +++ b/src/middleware/Init.js @@ -6,6 +6,7 @@ import { setSiteConfig, toggleSnackbar, } from "../redux/explorer"; +import i18next from "../i18n"; const initUserConfig = (siteConfig) => { if (siteConfig.user !== undefined && !siteConfig.user.anonymous) { @@ -81,7 +82,8 @@ export async function UpdateSiteConfig(store) { toggleSnackbar( "top", "right", - "无法加载站点配置:" + error.message, + i18next.t("errLoadingSiteConfig", { ns: "common" }) + + error.message, "error" ) ); diff --git a/yarn.lock b/yarn.lock index 097f977..7bcd6a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1173,6 +1173,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.2": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" + integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.16.7", "@babel/template@^7.4.0", "@babel/template@^7.6.0", "@babel/template@^7.8.6": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -3599,6 +3606,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-fetch@3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" + integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== + dependencies: + node-fetch "2.6.7" + cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -5642,7 +5656,7 @@ html-entities@^1.3.1: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== -html-escaper@^2.0.0: +html-escaper@^2.0.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== @@ -5660,6 +5674,13 @@ html-minifier@^3.5.20: relateurl "0.2.x" uglify-js "3.4.x" +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" + html-webpack-plugin@4.0.0-beta.5: version "4.0.0-beta.5" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.5.tgz#2c53083c1151bfec20479b1f8aaf0039e77b5513" @@ -5777,6 +5798,27 @@ hyphenate-style-name@^1.0.3: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== +i18next-browser-languagedetector@^6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.4.tgz#7b087c5edb6f6acd38ef54ede2160ab9cde0108f" + integrity sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg== + dependencies: + "@babel/runtime" "^7.14.6" + +i18next-http-backend@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/i18next-http-backend/-/i18next-http-backend-1.4.0.tgz#e6abef0615316e00837798d2385c13395096f963" + integrity sha512-wsvx7E/CT1pHmBM99Vu57YLJpsrHbVjxGxf25EIJ/6oTjsvCkZZ6c3SA4TejcK5jIHfv9oLxQX8l+DFKZHZ0Gg== + dependencies: + cross-fetch "3.1.5" + +i18next@^21.6.16: + version "21.6.16" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.6.16.tgz#8cff8c3ba2ffaf8438a8c83fe284083f15cf3941" + integrity sha512-xJlzrVxG9CyAGsbMP1aKuiNr1Ed2m36KiTB7hjGMG2Zo4idfw3p9THUEu+GjBwIgEZ7F11ZbCzJcfv4uyfKNuw== + dependencies: + "@babel/runtime" "^7.17.2" + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -7806,6 +7848,13 @@ node-ensure@^0.0.0: resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc= +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -9564,6 +9613,15 @@ react-hotkeys@^2.0.0: dependencies: prop-types "^15.6.1" +react-i18next@^11.16.7: + version "11.16.7" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.16.7.tgz#8d0680b7f4c8e43f59996336b7183ad576a28df7" + integrity sha512-7yotILJLnKfvUfrl/nt9eK9vFpVFjZPLWAwBzWL6XppSZZEvlmlKk0GBGDCAPfLfs8oND7WAbry8wGzdoiW5Nw== + dependencies: + "@babel/runtime" "^7.14.5" + html-escaper "^2.0.2" + html-parse-stringify "^3.0.1" + react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -11252,6 +11310,11 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + ts-pnp@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.4.tgz#ae27126960ebaefb874c6d7fa4729729ab200d90" @@ -11596,6 +11659,11 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +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= + w3c-hr-time@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" @@ -11644,6 +11712,11 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -11785,6 +11858,14 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^6.4.1: version "6.5.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"