feat: support custom locale language

support custom locale language and custom support languages.

Change-Id: Ia235cb83b65c8530449f52d9ea575c3d6f603f04
This commit is contained in:
Jingwei.Zhang 2023-08-08 13:17:29 +08:00
parent dd00305998
commit b36d5593e6
10 changed files with 142 additions and 23 deletions

View File

@ -139,6 +139,7 @@
"globals": true, "globals": true,
"request": true, "request": true,
"METRICDICT": true, "METRICDICT": true,
"globalCSS": true "globalCSS": true,
"GLOBAL_VARIABLES": true
} }
} }

View File

@ -1,3 +1,10 @@
host: 0.0.0.0 host: 0.0.0.0
port: 8088 port: 8088
server: http://localhost server: http://localhost
globalVariables:
defaultLanguage: en
supportLanguages: # use value in i18n.js
- en
- zh-hans
- ko-kr

View File

@ -2,7 +2,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const { merge } = require('lodash'); const { merge, extend, has } = require('lodash');
const root = (dir) => const root = (dir) =>
`${path.resolve(__dirname, './')}/${dir}`.replace(/(\/+)/g, '/'); `${path.resolve(__dirname, './')}/${dir}`.replace(/(\/+)/g, '/');
@ -22,16 +22,43 @@ const getServerConfig = (key) => {
const tryFile = root('./local_config.yaml'); const tryFile = root('./local_config.yaml');
if (fs.existsSync(tryFile)) { if (fs.existsSync(tryFile)) {
// merge local_config // merge local_config
const local_config = loadYaml(tryFile); const localConfig = loadYaml(tryFile);
if (typeof local_config === 'object') { if (typeof localConfig === 'object') {
merge(config, local_config); merge(config, localConfig);
} }
} }
return key ? config[key] : config; 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 = { module.exports = {
getServerConfig, getServerConfig,
root, root,
getGlobalVariables,
}; };

View File

@ -17,6 +17,7 @@ const { normalize, resolve } = require('path');
// const path = require("path"); // const path = require("path");
// const CleanWebpackPlugin = require('clean-webpack-plugin'); // const CleanWebpackPlugin = require('clean-webpack-plugin');
const moment = require('moment'); const moment = require('moment');
const { getGlobalVariables } = require('./utils');
const root = (path) => resolve(__dirname, `../${path}`); const root = (path) => resolve(__dirname, `../${path}`);
const version = moment().unix(); const version = moment().unix();
@ -109,7 +110,12 @@ module.exports = {
client: root('src/client'), 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; module.exports.version = version;

View File

@ -32,4 +32,10 @@ module.exports = {
moduleDirectories: ['node_modules', 'src'], moduleDirectories: ['node_modules', 'src'],
testPathIgnorePatterns: ['node_modules', '.cache', 'test/e2e', 'config'], testPathIgnorePatterns: ['node_modules', '.cache', 'test/e2e', 'config'],
setupFiles: ['<rootDir>/test/unit/setup-tests.js'], setupFiles: ['<rootDir>/test/unit/setup-tests.js'],
globals: {
GLOBAL_VARIABLES: {
defaultLanguage: 'en',
supportLanguages: ['en', 'zh-hans'],
},
},
}; };

View File

@ -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.

View File

@ -103,6 +103,21 @@ export class AvatarDropdown extends React.Component {
return <span style={{ float: 'right' }}>{btns}</span>; return <span style={{ float: 'right' }}>{btns}</span>;
} }
renderLanguageMenuItem() {
if (SUPPORT_LOCALES.length <= 1) {
return null;
}
return (
<Menu.Item
key="language"
className={`${styles['no-hover']} ${styles['menu-item']}`}
>
<span>{t('Switch Language')}</span>
{this.renderLanguageSwitch()}
</Menu.Item>
);
}
render() { render() {
if (!this.user) { if (!this.user) {
return ( return (
@ -136,13 +151,7 @@ export class AvatarDropdown extends React.Component {
</Button> </Button>
</Menu.Item> </Menu.Item>
<Menu.Divider /> <Menu.Divider />
<Menu.Item {this.renderLanguageMenuItem()}
key="language"
className={`${styles['no-hover']} ${styles['menu-item']}`}
>
<span>{t('Switch Language')}</span>
{this.renderLanguageSwitch()}
</Menu.Item>
<Menu.Item key="password" className={styles['menu-item']}> <Menu.Item key="password" className={styles['menu-item']}>
<ItemActionButtons <ItemActionButtons
actions={{ moreActions: [{ action: Password }] }} actions={{ moreActions: [{ action: Password }] }}

View File

@ -22,6 +22,10 @@ import styles from './index.less';
const { getLocale, setLocale, SUPPORT_LOCALES } = i18n; const { getLocale, setLocale, SUPPORT_LOCALES } = i18n;
const SelectLang = (props) => { const SelectLang = (props) => {
if (SUPPORT_LOCALES.length <= 1) {
return null;
}
const { className } = props; const { className } = props;
const selectedLang = getLocale(); const selectedLang = getLocale();

View File

@ -14,35 +14,69 @@
import moment from 'moment'; import moment from 'moment';
import 'moment/locale/zh-cn'; import 'moment/locale/zh-cn';
import _ from 'lodash'; import _, { isArray } from 'lodash';
import cookie from 'utils/cookie'; import cookie from 'utils/cookie';
import SLI18n from 'utils/translate'; import SLI18n from 'utils/translate';
import { setLocalStorageItem } from 'utils/local-storage'; import { setLocalStorageItem } from 'utils/local-storage';
import locales from '../locales'; import locales from '../locales';
// shortName: the i18n name in the api // shortName: the i18n name in the api
// icon: the icon of switch language in ui // icon: the icon of switch language in ui
const SUPPORT_LOCALES = [ const SUPPORT_LOCALES_ALL = [
{ {
name: 'English', name: 'English',
value: 'en', value: 'en',
shortName: 'en', shortName: 'en',
icon: 'en', icon: 'en',
momentName: 'en',
}, },
{ {
name: '简体中文', name: '简体中文',
value: 'zh-hans', value: 'zh-hans',
shortName: 'zh', shortName: 'zh',
icon: 'zh', icon: 'zh',
momentName: 'zhCN',
}, },
{ {
name: '한글', name: '한글',
value: 'ko-kr', value: 'ko-kr',
shortName: 'ko', shortName: 'ko',
icon: '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(); const intl = new SLI18n();
let currentLocals = null; let currentLocals = null;
@ -63,9 +97,11 @@ const getLocale = () => {
localStorageLocaleKey: 'lang', 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 })) { if (!_.find(SUPPORT_LOCALES, { value: currentLocale })) {
currentLocale = 'en'; currentLocale = defaultLanguage;
} }
if (!currentLocals) { if (!currentLocals) {
@ -93,7 +129,7 @@ const loadLocales = () => {
const setLocale = (lang) => { const setLocale = (lang) => {
setLocaleToStorage(lang); setLocaleToStorage(lang);
cookie('lang', lang); cookie('lang', lang);
moment.locale(lang); moment.locale(getMomentName(lang));
window.location.reload(); window.location.reload();
return lang; return lang;
}; };
@ -104,7 +140,7 @@ const init = () => {
const lang = getLocale(); const lang = getLocale();
if (lang === 'zh-hans') { if (lang === 'zh-hans') {
moment.locale('zh', { moment.locale('zh-cn', {
relativeTime: { relativeTime: {
s: '1秒', s: '1秒',
ss: '%d秒', ss: '%d秒',
@ -140,6 +176,7 @@ const t = (key, options) => intl.get(key, options);
t.html = (key, options) => intl.getHTML(key, options); t.html = (key, options) => intl.getHTML(key, options);
loadLocales(); loadLocales();
init();
window.t = t; window.t = t;
export default { export default {

View File

@ -17,12 +17,13 @@ import ReactDOM from 'react-dom';
import { createBrowserHistory } from 'history'; import { createBrowserHistory } from 'history';
import { syncHistoryWithStore } from 'mobx-react-router'; import { syncHistoryWithStore } from 'mobx-react-router';
import { ConfigProvider } from 'antd'; 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 globalRootStore from 'stores/root';
import PageLoading from 'components/PageLoading'; import PageLoading from 'components/PageLoading';
import metricDict from 'resources/prometheus/metricDict'; import metricDict from 'resources/prometheus/metricDict';
import variables from 'styles/variables.less'; 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 i18n from './i18n';
import App from './App'; import App from './App';
@ -33,8 +34,19 @@ window.globalCSS = variables;
const store = globalRootStore; const store = globalRootStore;
const browserHistory = createBrowserHistory(); const browserHistory = createBrowserHistory();
const history = syncHistoryWithStore(browserHistory, store.routing); 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) => { const render = (component) => {
ReactDOM.render( ReactDOM.render(