diff --git a/.eslintrc b/.eslintrc index 9dd1c002..a8d1f8cf 100644 --- a/.eslintrc +++ b/.eslintrc @@ -139,6 +139,7 @@ "globals": true, "request": true, "METRICDICT": true, - "globalCSS": true + "globalCSS": true, + "GLOBAL_VARIABLES": true } } diff --git a/config/config.yaml b/config/config.yaml index 2a6cae9e..aa5b1344 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,3 +1,10 @@ host: 0.0.0.0 port: 8088 server: http://localhost + +globalVariables: + defaultLanguage: en + supportLanguages: # use value in i18n.js + - en + - zh-hans + - ko-kr diff --git a/config/utils.js b/config/utils.js index fe6b0960..8a67e75b 100644 --- a/config/utils.js +++ b/config/utils.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const yaml = require('js-yaml'); -const { merge } = require('lodash'); +const { merge, extend, has } = require('lodash'); const root = (dir) => `${path.resolve(__dirname, './')}/${dir}`.replace(/(\/+)/g, '/'); @@ -22,16 +22,43 @@ const getServerConfig = (key) => { const tryFile = root('./local_config.yaml'); if (fs.existsSync(tryFile)) { // merge local_config - const local_config = loadYaml(tryFile); - if (typeof local_config === 'object') { - merge(config, local_config); + const localConfig = loadYaml(tryFile); + if (typeof localConfig === 'object') { + merge(config, localConfig); } } return key ? config[key] : config; }; +const getObjectConfig = (key) => { + // parse config yaml + const config = loadYaml(root('./config.yaml')) || {}; + if (!has(config, key)) { + return {}; + } + const defaultConfig = config[key]; + + const result = defaultConfig; + const tryFile = root('./local_config.yaml'); + if (fs.existsSync(tryFile)) { + // merge local_config + const localConfig = loadYaml(tryFile); + extend(result, localConfig[key] || {}); + } + + return result; +}; + +const getGlobalVariables = () => { + const variables = getObjectConfig('globalVariables') || {}; + // eslint-disable-next-line no-console + console.log('globalVariables', variables, JSON.stringify(variables)); + return JSON.stringify(variables); +}; + module.exports = { getServerConfig, root, + getGlobalVariables, }; diff --git a/config/webpack.common.js b/config/webpack.common.js index 62803569..1b503288 100644 --- a/config/webpack.common.js +++ b/config/webpack.common.js @@ -17,6 +17,7 @@ const { normalize, resolve } = require('path'); // const path = require("path"); // const CleanWebpackPlugin = require('clean-webpack-plugin'); const moment = require('moment'); +const { getGlobalVariables } = require('./utils'); const root = (path) => resolve(__dirname, `../${path}`); const version = moment().unix(); @@ -109,7 +110,12 @@ module.exports = { client: root('src/client'), }, }, - plugins: [new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)], + plugins: [ + new webpack.DefinePlugin({ + GLOBAL_VARIABLES: getGlobalVariables(), + }), + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + ], }; module.exports.version = version; diff --git a/jest.config.js b/jest.config.js index c3261807..21811309 100644 --- a/jest.config.js +++ b/jest.config.js @@ -32,4 +32,10 @@ module.exports = { moduleDirectories: ['node_modules', 'src'], testPathIgnorePatterns: ['node_modules', '.cache', 'test/e2e', 'config'], setupFiles: ['/test/unit/setup-tests.js'], + globals: { + GLOBAL_VARIABLES: { + defaultLanguage: 'en', + supportLanguages: ['en', 'zh-hans'], + }, + }, }; diff --git a/releasenotes/notes/Support-Custom-Local-Language-84ad3016c2469a51.yaml b/releasenotes/notes/Support-Custom-Local-Language-84ad3016c2469a51.yaml new file mode 100644 index 00000000..b2826aaf --- /dev/null +++ b/releasenotes/notes/Support-Custom-Local-Language-84ad3016c2469a51.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Support custom local language: + + * The globalVariables in the config.yaml support default language and all support languages. + + * Use globalVariables in the local_config.yaml to support custom local languages and custom support languages. + + * If only one support language, the switch language icon will been auto hidden. diff --git a/src/components/Layout/GlobalHeader/AvatarDropdown.jsx b/src/components/Layout/GlobalHeader/AvatarDropdown.jsx index 2ffc8bda..3a501cfe 100644 --- a/src/components/Layout/GlobalHeader/AvatarDropdown.jsx +++ b/src/components/Layout/GlobalHeader/AvatarDropdown.jsx @@ -103,6 +103,21 @@ export class AvatarDropdown extends React.Component { return {btns}; } + renderLanguageMenuItem() { + if (SUPPORT_LOCALES.length <= 1) { + return null; + } + return ( + + {t('Switch Language')} + {this.renderLanguageSwitch()} + + ); + } + render() { if (!this.user) { return ( @@ -136,13 +151,7 @@ export class AvatarDropdown extends React.Component { - - {t('Switch Language')} - {this.renderLanguageSwitch()} - + {this.renderLanguageMenuItem()} { + if (SUPPORT_LOCALES.length <= 1) { + return null; + } + const { className } = props; const selectedLang = getLocale(); diff --git a/src/core/i18n.js b/src/core/i18n.js index ad3d22a1..f3074341 100644 --- a/src/core/i18n.js +++ b/src/core/i18n.js @@ -14,35 +14,69 @@ import moment from 'moment'; import 'moment/locale/zh-cn'; -import _ from 'lodash'; +import _, { isArray } from 'lodash'; import cookie from 'utils/cookie'; import SLI18n from 'utils/translate'; import { setLocalStorageItem } from 'utils/local-storage'; + import locales from '../locales'; // shortName: the i18n name in the api // icon: the icon of switch language in ui -const SUPPORT_LOCALES = [ +const SUPPORT_LOCALES_ALL = [ { name: 'English', value: 'en', shortName: 'en', icon: 'en', + momentName: 'en', }, { name: '简体中文', value: 'zh-hans', shortName: 'zh', icon: 'zh', + momentName: 'zhCN', }, { name: '한글', value: 'ko-kr', shortName: 'ko', icon: 'ko', + momentName: 'ko', }, ]; +const getDefaultLanguageInConfig = () => { + const { defaultLanguage } = GLOBAL_VARIABLES; + const defaultLang = defaultLanguage || 'en'; + const inSupport = SUPPORT_LOCALES_ALL.find((it) => it.value === defaultLang); + return inSupport ? defaultLang : 'en'; +}; + +const getSupportLanguagesInConfig = () => { + const { supportLanguages } = GLOBAL_VARIABLES; + const defaultLang = getDefaultLanguageInConfig(); + const defaultSupportLanguages = [defaultLang]; + if (!supportLanguages || !isArray(supportLanguages)) { + return defaultSupportLanguages; + } + if (!supportLanguages.includes(defaultLang)) { + return [...supportLanguages, defaultLang]; + } + return supportLanguages; +}; + +const SUPPORT_LOCALES = SUPPORT_LOCALES_ALL.filter((it) => { + const supportLanguages = getSupportLanguagesInConfig(); + return supportLanguages.includes(it.value); +}); + +const getMomentName = (locale) => { + const item = SUPPORT_LOCALES_ALL.find((it) => it.value === locale); + return (item || {}).momentName || 'en'; +}; + const intl = new SLI18n(); let currentLocals = null; @@ -63,9 +97,11 @@ const getLocale = () => { localStorageLocaleKey: 'lang', }); - // If not found, the default is English + const { defaultLanguage } = GLOBAL_VARIABLES; + + // If not found, the default language is set in config.yaml if (!_.find(SUPPORT_LOCALES, { value: currentLocale })) { - currentLocale = 'en'; + currentLocale = defaultLanguage; } if (!currentLocals) { @@ -93,7 +129,7 @@ const loadLocales = () => { const setLocale = (lang) => { setLocaleToStorage(lang); cookie('lang', lang); - moment.locale(lang); + moment.locale(getMomentName(lang)); window.location.reload(); return lang; }; @@ -104,7 +140,7 @@ const init = () => { const lang = getLocale(); if (lang === 'zh-hans') { - moment.locale('zh', { + moment.locale('zh-cn', { relativeTime: { s: '1秒', ss: '%d秒', @@ -140,6 +176,7 @@ const t = (key, options) => intl.get(key, options); t.html = (key, options) => intl.getHTML(key, options); loadLocales(); +init(); window.t = t; export default { diff --git a/src/core/index.jsx b/src/core/index.jsx index 429a53e3..62281e01 100644 --- a/src/core/index.jsx +++ b/src/core/index.jsx @@ -17,12 +17,13 @@ import ReactDOM from 'react-dom'; import { createBrowserHistory } from 'history'; import { syncHistoryWithStore } from 'mobx-react-router'; import { ConfigProvider } from 'antd'; -import zhCN from 'antd/es/locale/zh_CN'; -import enUS from 'antd/es/locale/en_US'; import globalRootStore from 'stores/root'; import PageLoading from 'components/PageLoading'; import metricDict from 'resources/prometheus/metricDict'; import variables from 'styles/variables.less'; +import zhCN from 'antd/es/locale/zh_CN'; +import enUS from 'antd/es/locale/en_US'; +import koKR from 'antd/es/locale/ko_KR'; import i18n from './i18n'; import App from './App'; @@ -33,8 +34,19 @@ window.globalCSS = variables; const store = globalRootStore; const browserHistory = createBrowserHistory(); const history = syncHistoryWithStore(browserHistory, store.routing); -const lang = i18n.getLocale(); -const localeProvider = lang === 'en' ? enUS : zhCN; + +const antdLanguageMap = { + en: enUS, + 'zh-hans': zhCN, + 'ko-kr': koKR, +}; + +const getAntdLocale = (locale) => { + const lang = locale || i18n.getLocale(); + return antdLanguageMap[lang] || enUS; +}; + +const localeProvider = getAntdLocale(i18n.getLocale()); const render = (component) => { ReactDOM.render(