From 473b8931f89c5bada9f7b01de177f59caca29615 Mon Sep 17 00:00:00 2001 From: 56 Date: Wed, 22 Jul 2020 20:04:58 +0800 Subject: [PATCH] feat: i18n --- dashboard/src/components/Editor/index.tsx | 19 +++-- dashboard/src/components/ITable/index.tsx | 10 ++- dashboard/src/components/Loading/index.tsx | 4 +- dashboard/src/locales/en_us.ts | 92 +++++++++++++++++++++- dashboard/src/locales/zh_cn.ts | 27 +++++-- dashboard/src/pages/App/Apps.tsx | 2 +- dashboard/src/pages/App/Cluster.tsx | 12 +-- dashboard/src/pages/App/NamespaceInfo.tsx | 22 ++++-- dashboard/src/pages/App/ReleaseModel.tsx | 18 +++-- dashboard/src/renders/namespace.tsx | 7 +- 10 files changed, 172 insertions(+), 41 deletions(-) diff --git a/dashboard/src/components/Editor/index.tsx b/dashboard/src/components/Editor/index.tsx index 788ec2e..5f905a1 100644 --- a/dashboard/src/components/Editor/index.tsx +++ b/dashboard/src/components/Editor/index.tsx @@ -1,4 +1,5 @@ import React, { ForwardRefRenderFunction, forwardRef } from 'react'; +import { TFunction } from 'i18next'; import { Button, Input, message } from 'antd'; import CodeMirror from 'codemirror'; import { CodeEditorProps } from '@src/components/Editor/Editor'; @@ -9,6 +10,7 @@ import YamlEditor from './YamlEditor'; import TomlEditor from './TomlEditor'; import styles from './index.module.scss'; +import { useTranslation } from 'react-i18next'; window.CodeMirror = CodeMirror; @@ -16,8 +18,8 @@ export { default as JsonEditor } from './JsonEditor'; export { default as YamlEditor } from './YamlEditor'; export { default as TomlEditor } from './TomlEditor'; -export const validateFormat = (value: string, format: NamespaceFormat): [boolean, string?] => { - if (!value) return [false, '配置不能为空']; +export const validateFormat = (value: string, format: NamespaceFormat, t: TFunction): [boolean, string?] => { + if (!value) return [false, t('form.creation.configuration.validation')]; if (format === NamespaceFormat.CUSTOM) return [true]; let validate; if (format === NamespaceFormat.JSON) validate = window.jsonlint.parse; @@ -46,6 +48,7 @@ const Editor: ForwardRefRenderFunction = ( { canControl, format, value, released, onSave, onRelease, ...props }, ref, ) => { + const { t } = useTranslation(); const renderEditor = () => { if (format === NamespaceFormat.CUSTOM) return ( @@ -74,24 +77,24 @@ const Editor: ForwardRefRenderFunction = ( type="primary" onClick={() => { const v = value || ''; - const [result, msg] = validateFormat(v, format); + const [result, msg] = validateFormat(v, format, t); if (result) onSave && onSave(v, format); - else message.error(`格式错误: ${msg}`); + else message.error(t('form.creation.format.validation.failure') + `: ${msg}`); }} > - 保存 + {t('form.creation.button.save')} )} diff --git a/dashboard/src/components/ITable/index.tsx b/dashboard/src/components/ITable/index.tsx index 1eb75d0..4fd5c91 100644 --- a/dashboard/src/components/ITable/index.tsx +++ b/dashboard/src/components/ITable/index.tsx @@ -1,7 +1,8 @@ import React, { useEffect, useState } from 'react'; -import { TableProps } from 'antd/lib/table'; +import { useTranslation } from 'react-i18next'; import { Button, Input, Table } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; +import { TableProps } from 'antd/lib/table'; import { usePropsValue } from '@src/hooks/usePropsValue'; @@ -22,6 +23,7 @@ export interface ITableProps extends TableProps { } const ITable = ({ showCreate, showSearch, ...props }: ITableProps): JSX.Element => { + const { t } = useTranslation(); const [key, setKey] = usePropsValue({ value: showSearch?.value, onChange: showSearch?.onChange }); const [data, setData] = useState([]); @@ -41,15 +43,15 @@ const ITable = ({ showCreate, showSearch, ...props }: IT
{!!showSearch && (
- + setKey(e.target.value)} style={{ marginLeft: 12, width: 300 }} - placeholder="输入关键字过滤" + placeholder={t('table.filter.placeholder')} addonAfter={ setKey('')}> - 清除 + {t('table.filter_clear_button')} } /> diff --git a/dashboard/src/components/Loading/index.tsx b/dashboard/src/components/Loading/index.tsx index cdde616..3664ec1 100644 --- a/dashboard/src/components/Loading/index.tsx +++ b/dashboard/src/components/Loading/index.tsx @@ -1,12 +1,14 @@ import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; import { Spin } from 'antd'; export interface LoadingProps {} const Loading: FC = () => { + const { t } = useTranslation(); return (
- +
); }; diff --git a/dashboard/src/locales/en_us.ts b/dashboard/src/locales/en_us.ts index 9e7f7c7..c927e21 100644 --- a/dashboard/src/locales/en_us.ts +++ b/dashboard/src/locales/en_us.ts @@ -6,7 +6,97 @@ const locale: Locale = { langs: ['en', 'en_us', 'en_US'], languageLabel: 'English', antLocale: enUS, - value: {}, + value: { + 'layout.title': '分布式配置中心', + 'layout.subTit': 'XConf', + 'layout.ownership': 'Micro China open source technology', + + 'label.released': 'released', + 'label.unreleased': 'unreleased', + 'label.loading': 'Try to load.', + + 'menus.apps': 'Applications', + 'menus.clusters': 'Clusters', + 'menus.github': 'Source', + + 'empty.desc': 'No description', + + 'card.app': 'My app', + 'card.app.create': 'Create App', + 'card.app.title': 'App', + 'card.cluster.create': 'Create Cluster', + 'card.cluster.title': 'Cluster', + 'card.namespace.title': 'Namespace', + 'card.namespace.create': 'Create Namespace', + + 'table.filter': 'Keyword', + 'table.filter.placeholder': 'Keyword filtering', + 'table.filter_clear_button': 'Clear', + 'table.columns.app': 'App', + 'table.columns.cluster': 'Cluster', + 'table.columns.namespace': 'Namespace', + 'table.columns.comment': 'Explain', + 'table.columns.desc': 'Description', + 'table.columns.status': 'Status', + 'table.columns.createdAt': 'CreatedAt', + 'table.columns.updatedAt': 'updatedAt', + 'table.columns.control': 'Control', + 'table.columns.control.view': 'View', + 'table.columns.control.history': 'History', + 'table.columns.control.import': 'Import', + 'table.columns.control.import.success': 'Import configuration success', + 'table.columns.control.import.failure': 'Import configuration failure', + 'table.columns.control.export': 'Export', + 'table.columns.control.rollback': 'Rollback', + 'table.columns.control.rollback.confirm': 'Confirm rollback', + 'table.columns.control.rollback.success': 'Rollback success', + 'table.columns.control.rollback.failure': 'Rollback failure', + 'table.columns.control.remove': 'Delete', + 'table.columns.control.remove.success': 'Delete success', + 'table.columns.control.remove.failure': 'Delete failure', + 'table.columns.control.remove.confirm.app': 'Do you want to delete this application?', + 'table.columns.control.remove.confirm.cluster': 'Do you want to delete this cluster?', + 'table.columns.control.remove.confirm.namespace': 'Do you want to delete this namespace?', + + 'form.creation.appName': 'App name', + 'form.creation.appName.validation': 'App name cannot be empty', + 'form.creation.appName.placeholder': 'Please enter the app name', + 'form.creation.clusterName': 'Cluster name', + 'form.creation.clusterName.validation': 'Cluster name cannot be empty', + 'form.creation.clusterName.placeholder': 'Please enter the cluster name', + 'form.creation.namespace': 'Namespace name', + 'form.creation.namespace.validation': 'Namespace name cannot be empty', + 'form.creation.namespace.placeholder': 'Please enter the namespace name', + 'form.creation.format': 'Format', + 'form.creation.format.validation': 'Format cannot be empty', + 'form.creation.format.validation.failure': 'Format error', + 'form.creation.status': 'Status', + 'form.creation.status.validation': 'Status cannot be empty', + 'form.creation.configuration': 'Configuration', + 'form.creation.configuration.validation': 'Configuration cannot be empty', + 'form.creation.tag': 'tag', + 'form.creation.tag.validation': 'Tag is required', + 'form.creation.tag.placeholder': 'Please enter the tag', + 'form.creation.comment': 'Note', + 'form.creation.comment.validation': 'Note cannot be empty', + 'form.creation.comment.placeholder': 'Please enter the note', + + 'form.creation.desc': 'Description', + 'form.creation.desc.placeholder': 'Please enter the description', + + 'form.creation.button.sure': 'Create', + 'form.creation.button.cancel': 'Cancel', + 'form.creation.button.save': 'Save', + 'form.creation.button.release': 'Release', + 'form.creation.app.success': 'Create application success', + 'form.creation.app.failure': 'Create application failure', + 'form.creation.cluster.success': 'Create cluster success', + 'form.creation.cluster.failure': 'Create cluster failure', + 'form.creation.namespace.failure': 'Create namespace failure', + 'form.creation.namespace.save.success': 'Save configuration success', + 'form.creation.namespace.save.failure': 'Save configuration failure', + 'form.creation.namespace.release.success': 'Release configuration success', + }, }; export default locale; diff --git a/dashboard/src/locales/zh_cn.ts b/dashboard/src/locales/zh_cn.ts index acdf595..f68859d 100644 --- a/dashboard/src/locales/zh_cn.ts +++ b/dashboard/src/locales/zh_cn.ts @@ -11,6 +11,10 @@ const locale: Locale = { 'layout.subTit': '分布式配置中心', 'layout.ownership': 'Micro China开源技术出品', + 'label.released': '已发布', + 'label.unreleased': '未发布', + 'label.loading': '努力加载中捏.', + 'menus.apps': '应用列表', 'menus.clusters': '集群列表', 'menus.github': '项目地址', @@ -26,6 +30,7 @@ const locale: Locale = { 'card.namespace.create': '创建新配置', 'table.filter': '关键字过滤', + 'table.filter.placeholder': '输入关键字过滤', 'table.filter_clear_button': '清除', 'table.columns.app': '应用', 'table.columns.cluster': '集群', @@ -50,35 +55,47 @@ const locale: Locale = { 'table.columns.control.remove.success': '删除成功', 'table.columns.control.remove.failure': '删除失败', 'table.columns.control.remove.confirm.app': '确认删除该应用?', - 'table.columns.control.remove.confirm.cluster': '确认删除该应用?', + 'table.columns.control.remove.confirm.cluster': '确认删除该集群?', 'table.columns.control.remove.confirm.namespace': '确认删除该空间?', 'form.creation.appName': '应用名', 'form.creation.appName.validation': '应用名不能为空', 'form.creation.appName.placeholder': '请输入应用名', - 'form.creation.clusterName': '集群名', 'form.creation.clusterName.validation': '集群名不能为空', 'form.creation.clusterName.placeholder': '请输入集群名', - 'form.creation.namespace': '配置名', 'form.creation.namespace.validation': '配置名不能为空', 'form.creation.namespace.placeholder': '请输入配置名', - 'form.creation.format': '格式', 'form.creation.format.validation': '格式不能为空', + 'form.creation.format.validation.failure': '格式错误', + 'form.creation.status': '状态', + 'form.creation.status.validation': '状态不能为空', + 'form.creation.configuration': '配置', + 'form.creation.configuration.validation': '配置不能为空', + 'form.creation.tag': 'tag', + 'form.creation.tag.validation': '必须填写tag', + 'form.creation.tag.placeholder': '请输入Tag', + 'form.creation.comment': '备注', + 'form.creation.comment.validation': '发布备注', + 'form.creation.comment.placeholder': '请输入发布备注', 'form.creation.desc': '描述', 'form.creation.desc.placeholder': '请输入描述内容', 'form.creation.button.sure': '创建', 'form.creation.button.cancel': '取消', + 'form.creation.button.save': '保存', + 'form.creation.button.release': '发布', 'form.creation.app.success': '创建应用成功', 'form.creation.app.failure': '创建应用失败', 'form.creation.cluster.success': '创建集群成功', 'form.creation.cluster.failure': '创建集群失败', - 'form.creation.namespace.success': '创建配置成功', 'form.creation.namespace.failure': '创建配置失败', + 'form.creation.namespace.save.success': '配置保存成功', + 'form.creation.namespace.save.failure': '配置保存失败', + 'form.creation.namespace.release.success': '配置发布成功', }, }; diff --git a/dashboard/src/pages/App/Apps.tsx b/dashboard/src/pages/App/Apps.tsx index 48fc52f..9c10045 100644 --- a/dashboard/src/pages/App/Apps.tsx +++ b/dashboard/src/pages/App/Apps.tsx @@ -38,7 +38,7 @@ const Apps: FC = () => { { title: t('table.columns.control'), key: 'control', - width: 120, + width: 130, render: (_, app) => (
{t('table.columns.control.view')} diff --git a/dashboard/src/pages/App/Cluster.tsx b/dashboard/src/pages/App/Cluster.tsx index 5997f65..5c376d5 100644 --- a/dashboard/src/pages/App/Cluster.tsx +++ b/dashboard/src/pages/App/Cluster.tsx @@ -34,7 +34,7 @@ const getFunction = (namespace: Namespace, t: TFunction, callback: () => void) = const getContent = (file: File) => { readFile(file, (value) => { - const [res, msg] = validateFormat(value, namespace.format); + const [res, msg] = validateFormat(value, namespace.format, t); if (res) uploadConfig(value); else message.error(t('table.columns.control.import.failure') + `: ${msg}`); }); @@ -80,17 +80,15 @@ const Cluster: FC = ({ appName, clusterName }) => { title: t('table.columns.status'), key: 'release', width: 100, - render: (_, namespace) => renderNamespaceRelease(namespace), + render: (_, namespace) => renderNamespaceRelease(namespace, t), }, { title: t('table.columns.control'), key: 'control', - width: 230, + width: 256, render: (_, namespace) => { return (
- 历史版本 - + + {t('table.columns.control.history')} + + {renderDeleteWithLinkButton({ label: t('table.columns.control.remove'), popLabel: t('table.columns.control.remove.confirm.namespace'), diff --git a/dashboard/src/pages/App/NamespaceInfo.tsx b/dashboard/src/pages/App/NamespaceInfo.tsx index 932169e..8e539ae 100644 --- a/dashboard/src/pages/App/NamespaceInfo.tsx +++ b/dashboard/src/pages/App/NamespaceInfo.tsx @@ -1,4 +1,5 @@ import React, { FC, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { Col, Form, Row, Tag, message } from 'antd'; import Editor from '@src/components/Editor'; @@ -17,21 +18,28 @@ const NamespaceInfo: FC = ({ namespace, canControl, callback const callbackRef = useRef(callback); callbackRef.current = callback; + const { t } = useTranslation(); + return (
- + {namespace.format} - - {renderNamespaceRelease(namespace)} + + {renderNamespaceRelease(namespace, t)} - + = ({ namespace, canControl, callback .then(() => { const callback = callbackRef.current; callback && callback(); - message.success('配置保存成功'); + message.success(t('form.creation.namespace.save.success')); }) - .catch((e) => message.error('保存失败:', e.message)); + .catch((e) => message.error(t('form.creation.namespace.save.failure') + ': ' + e.message)); }} onRelease={() => { ReleaseModel({ @@ -58,7 +66,7 @@ const NamespaceInfo: FC = ({ namespace, canControl, callback clusterName: namespace.clusterName, namespaceName: namespace.namespaceName, onOk: () => { - message.success('配置发布成功'); + message.success(t('form.creation.namespace.release.success')); const callback = callbackRef.current; callback && callback(); }, diff --git a/dashboard/src/pages/App/ReleaseModel.tsx b/dashboard/src/pages/App/ReleaseModel.tsx index 3b04f4f..b722381 100644 --- a/dashboard/src/pages/App/ReleaseModel.tsx +++ b/dashboard/src/pages/App/ReleaseModel.tsx @@ -1,4 +1,5 @@ import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; import moment from 'moment'; import { Button, Col, Form, Input, Modal, Row } from 'antd'; import { ModalProps } from 'antd/lib/modal'; @@ -16,6 +17,7 @@ export interface ReleaseModelProps extends Omit } const ReleaseModel: FC = ({ appName, clusterName, namespaceName, onOk, ...props }) => { + const { t } = useTranslation(); const [form] = Form.useForm(); const [releaseState, release] = useCapture({ fn: releaseConfig, @@ -38,21 +40,25 @@ const ReleaseModel: FC = ({ appName, clusterName, namespaceNa initialValues={{ tag: 'v' + moment().format('YYYY-MM-DD') }} onFinish={({ tag, comment }) => release({ appName, clusterName, namespaceName, tag, comment })} > - - + + - - + + diff --git a/dashboard/src/renders/namespace.tsx b/dashboard/src/renders/namespace.tsx index c7c77dd..e19a3ee 100644 --- a/dashboard/src/renders/namespace.tsx +++ b/dashboard/src/renders/namespace.tsx @@ -1,9 +1,10 @@ import React from 'react'; +import { TFunction } from 'i18next'; import { Namespace } from '@src/typings'; -export const renderNamespaceRelease = (namespace: Namespace) => { +export const renderNamespaceRelease = (namespace: Namespace, t: TFunction) => { const { label, color } = namespace.released - ? { label: '已发布', color: '#1890ff' } - : { label: '未发布', color: 'red' }; + ? { label: t('label.released'), color: '#1890ff' } + : { label: t('label.unreleased'), color: 'red' }; return {label}; };