From 8a50ef4ef2f6f6044d36ea2f4fe06e663083972e Mon Sep 17 00:00:00 2001 From: Haishan Date: Sun, 6 Dec 2020 14:57:59 +0800 Subject: [PATCH] feat: initial Chinese UI language support --- package.json | 5 ++ src/app.tsx | 1 + src/components/Config.module.css | 6 ++- src/components/Config.tsx | 51 +++++++++++++++------ src/components/Connections.tsx | 10 ++-- src/components/Home.tsx | 4 +- src/components/Logs.tsx | 8 ++-- src/components/Rules.tsx | 5 +- src/components/SideBar.module.css | 1 + src/components/SideBar.tsx | 8 ++-- src/components/TrafficChart.tsx | 12 +++-- src/components/TrafficNow.tsx | 14 +++--- src/components/proxies/Proxies.tsx | 9 ++-- src/components/proxies/Settings.tsx | 24 ++++++---- src/components/shared/Select.module.css | 1 + src/custom.d.ts | 4 ++ src/i18n/en.ts | 34 ++++++++++++++ src/i18n/zh.ts | 34 ++++++++++++++ src/misc/i18n.ts | 61 +++++++++++++++++++++++++ tsconfig.json | 4 +- webpack.config.js | 4 +- yarn.lock | 55 +++++++++++++++++++++- 22 files changed, 301 insertions(+), 54 deletions(-) create mode 100644 src/i18n/en.ts create mode 100644 src/i18n/zh.ts create mode 100644 src/misc/i18n.ts diff --git a/package.json b/package.json index 8f4dbe1..ee412b8 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,9 @@ "fontsource-roboto-mono": "^3.0.3", "framer-motion": "^2.9.5", "history": "^5.0.0", + "i18next": "^19.8.4", + "i18next-browser-languagedetector": "^6.0.1", + "i18next-http-backend": "^1.0.21", "immer": "^8.0.0", "invariant": "^2.2.4", "lodash-es": "^4.17.14", @@ -43,6 +46,7 @@ "react-dom": "0.0.0-experimental-94c0244ba", "react-feather": "^2.0.9", "react-helmet": "^6.1.0", + "react-i18next": "^11.7.4", "react-icons": "^3.10.0", "react-modal": "^3.12.1", "react-query": "^2.26.3", @@ -80,6 +84,7 @@ "@types/react-dom": "^17.0.0", "@types/react-helmet": "^6.1.0", "@types/react-modal": "^3.10.6", + "@types/react-table": "^7.0.25", "@types/react-tabs": "^2.3.2", "@typescript-eslint/eslint-plugin": "^4.9.0", "@typescript-eslint/parser": "^4.9.0", diff --git a/src/app.tsx b/src/app.tsx index b4cc818..5d2f226 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,4 +1,5 @@ import 'modern-normalize/modern-normalize.css'; +import './misc/i18n'; import React from 'react'; import ReactDOM from 'react-dom'; diff --git a/src/components/Config.module.css b/src/components/Config.module.css index d7fa40a..1f71765 100644 --- a/src/components/Config.module.css +++ b/src/components/Config.module.css @@ -11,7 +11,7 @@ .section { padding: 6px 15px 15px; @media (--breakpoint-not-small) { - padding: 10px 40px 40px; + padding: 0 40px 40px; } } @@ -28,3 +28,7 @@ .label { padding: 16px 0; } + +.narrow { + width: 360px; +} diff --git a/src/components/Config.tsx b/src/components/Config.tsx index 981eae1..4659e16 100644 --- a/src/components/Config.tsx +++ b/src/components/Config.tsx @@ -1,5 +1,8 @@ -import PropTypes from 'prop-types'; -import React from 'react'; +import * as 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 { getClashAPIConfig, @@ -67,12 +70,17 @@ const portFields = [ { key: 'redir-port', label: 'Redir Port' }, ]; -const mapState = (s) => ({ +const langOptions = [ + ['zh', '中文'], + ['en', 'English'], +]; + +const mapState = (s: State) => ({ configs: getConfigs(s), apiConfig: getClashAPIConfig(s), }); -const mapState2 = (s) => ({ +const mapState2 = (s: State) => ({ selectedChartStyleIndex: getSelectedChartStyleIndex(s), latencyTestUrl: getLatencyTestUrl(s), apiConfig: getClashAPIConfig(s), @@ -88,13 +96,21 @@ function ConfigContainer({ dispatch, configs, apiConfig }) { return ; } +type ConfigImplProps = { + dispatch: DispatchFn; + configs: ClashGeneralConfig; + selectedChartStyleIndex: number; + latencyTestUrl: string; + apiConfig: ClashAPIConfig; +}; + function ConfigImpl({ dispatch, configs, selectedChartStyleIndex, latencyTestUrl, apiConfig, -}) { +}: ConfigImplProps) { const [configState, setConfigStateInternal] = useState(configs); const refConfigs = useRef(configs); useEffect(() => { @@ -188,9 +204,11 @@ function ConfigImpl({ return typeof m === 'string' && m[0].toUpperCase() + m.slice(1); }, [configState.mode]); + const { t, i18n } = useTranslation(); + return (
- +
{portFields.map((f) => configState[f.key] !== undefined ? ( @@ -242,7 +260,7 @@ function ConfigImpl({
-
Chart Style
+
{t('chart_style')}
-
-
Latency Test URL
+
+
{t('latency_test_url')}
Action
+
+
{t('lang')}
+
+ { + return [o[0], t(o[1])]; + })} selected={appConfig.proxySortBy} onChange={handleProxySortByOnChange} /> @@ -52,7 +56,7 @@ function Settings({ appConfig }) {

- Hide unavailable proxies + {t('hide_unavail_proxies')}
- Automatically close old connections + {t('auto_close_conns')}
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; diff --git a/tsconfig.json b/tsconfig.json index b7f0ee5..caaea79 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { "baseUrl": ".", "allowSyntheticDefaultImports": true, @@ -23,6 +24,7 @@ "skipLibCheck": true, "strictNullChecks": false, "suppressImplicitAnyIndexErrors": true, - "types": ["jest"] + "types": ["jest"], + "resolveJsonModule": true } } diff --git a/webpack.config.js b/webpack.config.js index 9aaa9f4..067318d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -132,9 +132,7 @@ module.exports = { // https://github.com/webpack/webpack/issues/11467 { test: /\.m?js/, - resolve: { - fullySpecified: false, - }, + resolve: { fullySpecified: false }, }, { test: /\.[tj]sx?$/, diff --git a/yarn.lock b/yarn.lock index 258d196..ef5283b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1156,7 +1156,7 @@ dependencies: 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" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== @@ -1710,6 +1710,13 @@ dependencies: "@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": version "2.3.2" 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" 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: version "4.5.0" 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" 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: version "0.4.24" 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" 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": version "2.2.1" 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-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: version "3.11.0" 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" 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: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"