This is an automated email from the ASF dual-hosted git repository.
lzljs3620320 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-paimon-webui.git
The following commit(s) were added to refs/heads/main by this push:
new 9b0ecf1 [Feature][UI] support i18n (#28)
9b0ecf1 is described below
commit 9b0ecf1cf7c0ad352d29c79050d66f1cbc64275a
Author: zhazhapi <[email protected]>
AuthorDate: Wed Aug 16 14:37:09 2023 +0800
[Feature][UI] support i18n (#28)
---
paimon-web-ui/package.json | 4 ++
.../src/components/ChangeI18nBtn/index.tsx | 74 ++++++++++++++++++++++
.../src/components/Dropdown/CatalogDropdown.tsx | 5 +-
paimon-web-ui/src/components/Search/index.tsx | 4 +-
paimon-web-ui/src/config/menu.tsx | 7 +-
paimon-web-ui/src/locales/en/translation.json | 20 ++++++
paimon-web-ui/src/locales/i18n.ts | 63 ++++++++++++++++++
paimon-web-ui/src/locales/zh-CN/translation.json | 20 ++++++
paimon-web-ui/src/main.tsx | 8 ++-
paimon-web-ui/src/pages/Layout/Header/index.tsx | 34 ++++++----
.../LeftContent/components/CatalogTree/index.tsx | 4 +-
.../Metadata/components/LeftContent/index.tsx | 5 +-
.../components/MainContent/Table/index.tsx | 18 ++++--
.../components/TreeComponent/index.tsx | 4 +-
14 files changed, 244 insertions(+), 26 deletions(-)
diff --git a/paimon-web-ui/package.json b/paimon-web-ui/package.json
index 7fe6537..5916f82 100644
--- a/paimon-web-ui/package.json
+++ b/paimon-web-ui/package.json
@@ -18,12 +18,16 @@
"@semi-bot/semi-theme-figma": "^0.1.6",
"axios": "^1.4.0",
"http-proxy-middleware": "^2.0.6",
+ "i18next": "^23.4.4",
+ "i18next-browser-languagedetector": "^7.1.0",
+ "i18next-http-backend": "^2.2.1",
"less": "^4.1.3",
"monaco-editor": "^0.40.0",
"react": "^18.2.0",
"react-bootstrap": "^2.8.0",
"react-dom": "^18.2.0",
"react-icons": "^4.10.1",
+ "react-i18next": "^13.1.0",
"zustand": "^4.4.1"
},
"devDependencies": {
diff --git a/paimon-web-ui/src/components/ChangeI18nBtn/index.tsx
b/paimon-web-ui/src/components/ChangeI18nBtn/index.tsx
new file mode 100644
index 0000000..f599e95
--- /dev/null
+++ b/paimon-web-ui/src/components/ChangeI18nBtn/index.tsx
@@ -0,0 +1,74 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License. */
+
+import { useMemo, useState } from "react";
+import {Button, Tooltip} from "@douyinfe/semi-ui";
+import {IconLanguage} from "@douyinfe/semi-icons";
+import i18n from "i18next";
+
+
+const ChangeI18nBtn = () => {
+
+ const [preBtnName, setPreBtnName] = useState<string>()
+ const [changeName, setChangeName] = useState<string>()
+
+ useMemo(() => {
+ switch (i18n.language) {
+ case 'zh-CN':
+ setPreBtnName('EN')
+ setChangeName('Switch to English')
+ break
+ case 'en':
+ setPreBtnName('中文')
+ setChangeName('切换中文')
+ }
+ return ''
+ }, [])
+
+ const switchEnCh = () => {
+ if(preBtnName === 'EN') {
+ i18n.changeLanguage('en')
+ setPreBtnName('中文')
+ setChangeName('切换中文')
+ }
+ if (preBtnName === '中文') {
+ i18n.changeLanguage('zh-CN')
+ setPreBtnName('EN')
+ setChangeName('Switch to English')
+ }
+ }
+
+
+ return (
+ <Tooltip content={changeName}
+ position='bottom'
+ >
+ <Button
+ theme="borderless"
+ icon={<IconLanguage size="extra-large" />}
+ style={{
+ color: 'var(--semi-color-text-2)',
+ marginRight: '12px',
+ }}
+ aria-label={changeName}
+ onClick={switchEnCh}
+ >{preBtnName}</Button>
+ </Tooltip>
+ )
+}
+
+export default ChangeI18nBtn
diff --git a/paimon-web-ui/src/components/Dropdown/CatalogDropdown.tsx
b/paimon-web-ui/src/components/Dropdown/CatalogDropdown.tsx
index f533505..edeaf72 100644
--- a/paimon-web-ui/src/components/Dropdown/CatalogDropdown.tsx
+++ b/paimon-web-ui/src/components/Dropdown/CatalogDropdown.tsx
@@ -19,6 +19,7 @@ import {Select} from '@douyinfe/semi-ui';
import React from "react";
import {CataLog} from
"@pages/Playground/components/TabMenuSidebar/components/SiderTab";
import {IconRefresh} from "@douyinfe/semi-icons";
+import { useTranslation } from 'react-i18next';
/**
* 下拉菜单组件props
@@ -32,6 +33,8 @@ type CatalogDropdownProps = {
const CatalogDropdown: React.FC<CatalogDropdownProps> = (props) => {
+ const { t } = useTranslation()
+
const {catalogList, catalogChange,
reLoadCatalog,reloadCatalogListCallBack} = props;
@@ -62,7 +65,7 @@ const CatalogDropdown: React.FC<CatalogDropdownProps> =
(props) => {
<Select
suffix={<IconRefresh onClick={reloadCatalogListCallBack}
title={"Refresh"} spin={reLoadCatalog}/>}
loading={reLoadCatalog} onChange={handleCatalogChange}
- placeholder="Select Catalog"
+ placeholder={t('playground.selectCatalog')}
optionList={renderCatalogList()}
showClear
style={{display: "flex", flexGrow: 1}}
diff --git a/paimon-web-ui/src/components/Search/index.tsx
b/paimon-web-ui/src/components/Search/index.tsx
index f5351db..956ce19 100644
--- a/paimon-web-ui/src/components/Search/index.tsx
+++ b/paimon-web-ui/src/components/Search/index.tsx
@@ -17,10 +17,12 @@ under the License. */
import { Input } from '@douyinfe/semi-ui';
import { IconFilter } from '@douyinfe/semi-icons';
+import { useTranslation } from 'react-i18next';
const Search = () => {
+ const { t } = useTranslation()
return(
- <Input placeholder='Filter' suffix={<IconFilter />}></Input>
+ <Input placeholder={t('playground.filter')} suffix={<IconFilter
/>}></Input>
)
}
diff --git a/paimon-web-ui/src/config/menu.tsx
b/paimon-web-ui/src/config/menu.tsx
index 3a912cb..9025e72 100644
--- a/paimon-web-ui/src/config/menu.tsx
+++ b/paimon-web-ui/src/config/menu.tsx
@@ -18,8 +18,9 @@ under the License. */
export interface MenuItem {
itemKey: string
text: string
+ name: string
icon?: React.ReactNode
- path?: string
+ path: string
items?: MenuItem[]
component?: React.ComponentType<any>
}
@@ -28,21 +29,25 @@ const MENU_CONFIG: MenuItem[] = [
{
itemKey: '1',
text: 'Playground',
+ name: 'playground',
path: '/playground'
},
{
itemKey: '2',
text: 'Metadata',
+ name: 'metadata',
path: '/metadata'
},
{
itemKey: '3',
text: 'CDC Ingestion',
+ name: 'cdcingestion',
path: '/system'
},
{
itemKey: '4',
text: 'System',
+ name: 'system',
path: '/system'
}
]
diff --git a/paimon-web-ui/src/locales/en/translation.json
b/paimon-web-ui/src/locales/en/translation.json
new file mode 100644
index 0000000..e4277d5
--- /dev/null
+++ b/paimon-web-ui/src/locales/en/translation.json
@@ -0,0 +1,20 @@
+{
+ "common": {
+ "filter": "Filter"
+ },
+ "header": {
+ "playground": "Playground",
+ "metadata": "Metadata",
+ "cdcingestion": "CDC Ingestion",
+ "system": "System"
+ },
+ "playground": {
+ "selectCatalog": "Select Catalog"
+ },
+ "metadata": {
+ "catalog": "Catalog",
+ "tableInfo": "TableInfo",
+ "details": "Details",
+ "files": "Files"
+ }
+}
diff --git a/paimon-web-ui/src/locales/i18n.ts
b/paimon-web-ui/src/locales/i18n.ts
new file mode 100644
index 0000000..eeacd7b
--- /dev/null
+++ b/paimon-web-ui/src/locales/i18n.ts
@@ -0,0 +1,63 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License. */
+
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+
+import Backend from 'i18next-http-backend';
+import LanguageDetector from 'i18next-browser-languagedetector';
+import en from '@src/locales/en/translation.json';
+import zh from '@src/locales/zh-CN/translation.json';
+
+// don't want to use this?
+// have a look at the Quick start guide
+// for passing in lng and translations on init
+
+const resources = {
+ "en": {
+ translation: en
+ },
+ "zh-CN": {
+ translation: zh
+ }
+}
+
+i18n
+ // load translation using http -> see /public/locales (i.e.
https://github.com/i18next/react-i18next/tree/master/example/react/public/locales)
+ // learn more: https://github.com/i18next/i18next-http-backend
+ // want your translations to be loaded from a professional CDN? =>
https://github.com/locize/react-tutorial#step-2---use-the-locize-cdn
+ .use(Backend)
+ // detect user language
+ // learn more: https://github.com/i18next/i18next-browser-languageDetector
+ .use(LanguageDetector)
+ // pass the i18n instance to react-i18next.
+ .use(initReactI18next)
+ // init i18next
+ // for all options read:
https://www.i18next.com/overview/configuration-options
+ .init({
+ fallbackLng: 'zh-CN',
+ debug: true,
+ interpolation: {
+ escapeValue: false, // not needed for react as it escapes by
default
+ },
+ resources
+ });
+
+
+export default i18n;
+
+
diff --git a/paimon-web-ui/src/locales/zh-CN/translation.json
b/paimon-web-ui/src/locales/zh-CN/translation.json
new file mode 100644
index 0000000..a144468
--- /dev/null
+++ b/paimon-web-ui/src/locales/zh-CN/translation.json
@@ -0,0 +1,20 @@
+{
+ "common": {
+ "filter": "过滤"
+ },
+ "header": {
+ "playground": "运行信息",
+ "metadata": "元数据",
+ "cdcingestion": "CDC集成",
+ "system": "系统"
+ },
+ "playground": {
+ "selectCatalog": "选择Catalog"
+ },
+ "metadata": {
+ "catalog": "元数据",
+ "tableInfo": "表信息",
+ "details": "详细",
+ "files": "文件"
+ }
+}
diff --git a/paimon-web-ui/src/main.tsx b/paimon-web-ui/src/main.tsx
index e6c1cfd..8fbb2ef 100644
--- a/paimon-web-ui/src/main.tsx
+++ b/paimon-web-ui/src/main.tsx
@@ -15,13 +15,15 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License. */
-import React from 'react'
+// import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './app/App.tsx'
import './index.less'
+import '@src/locales/i18n.ts'
+
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
- <React.StrictMode>
+ // <React.StrictMode>
<App />
- </React.StrictMode>,
+ // </React.StrictMode>,
)
diff --git a/paimon-web-ui/src/pages/Layout/Header/index.tsx
b/paimon-web-ui/src/pages/Layout/Header/index.tsx
index 3491008..05b5440 100644
--- a/paimon-web-ui/src/pages/Layout/Header/index.tsx
+++ b/paimon-web-ui/src/pages/Layout/Header/index.tsx
@@ -16,13 +16,16 @@ specific language governing permissions and limitations
under the License. */
import {Avatar, Button, Layout, Nav} from '@douyinfe/semi-ui';
-import { IconMoon, IconGithubLogo, IconLanguage, IconSun } from
'@douyinfe/semi-icons';
+import { IconMoon, IconGithubLogo, IconSun } from '@douyinfe/semi-icons';
import useThemeSwitcher from '@src/utils/mode'
import paimonLogo from '@src/assets/logo/favicon_blue.svg'
import paimonWhiteLogo from '@src/assets/logo/favicon_white.svg'
import {useNavigate} from "react-router";
import {useMemo, useState} from "react";
import menuList from "@config/menu.tsx";
+import ChangeI18nBtn from "@components/ChangeI18nBtn";
+import {useTranslation} from "react-i18next";
+import {Link} from "react-router-dom";
const { Header } = Layout
@@ -57,6 +60,8 @@ const HeaderRoot = ()=> {
setOpenKeys([...data.openKeys])
}
+ const { t } = useTranslation()
+
return(
<Header style={{ backgroundColor: 'var(--semi-color-bg-1)'}}>
<div>
@@ -72,7 +77,7 @@ const HeaderRoot = ()=> {
selectedKeys={selectedKeys}
onSelect={onSelect}
onOpenChange={onOpenChange}
- items={navList}
+ // items={navList}
footer={
<div>
<Button
@@ -92,14 +97,7 @@ const HeaderRoot = ()=> {
marginRight: '12px',
}}
/>
- <Button
- theme="borderless"
- icon={<IconLanguage size="extra-large" />}
- style={{
- color: 'var(--semi-color-text-2)',
- marginRight: '12px',
- }}
- />
+ <ChangeI18nBtn />
<Avatar
color="orange"
size="small"
@@ -108,7 +106,21 @@ const HeaderRoot = ()=> {
</Avatar>
</div>
}
- />
+ >
+ {
+ navList.map( nav => {
+ return (
+ <Link
+ key={nav.name}
+ style={{ textDecoration: "none" }}
+ to={nav.path}
+ >
+ <Nav.Item itemKey={nav.name} text={t('header.'
+ nav.name)} />
+ </Link>
+ )
+ })
+ }
+ </Nav>
</div>
</Header>
)
diff --git
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/index.tsx
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/index.tsx
index e3fdd71..61746f1 100644
---
a/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/index.tsx
+++
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/components/CatalogTree/index.tsx
@@ -19,9 +19,11 @@ import {Input, Tree} from '@douyinfe/semi-ui';
import { IconSearch, IconFile } from "@douyinfe/semi-icons";
import {useEffect, useState} from "react";
import styles from "./catalog-tree.module.less"
+import { useTranslation } from 'react-i18next';
import { useCatalogStore } from "@src/store/catalogStore.ts";
const CatalogTree = () => {
+ const { t } = useTranslation()
type TreeDataItem = {
label: string;
@@ -73,7 +75,7 @@ const CatalogTree = () => {
<Tree
filterTreeNode
treeData={treeData}
- searchPlaceholder={"Filter"}
+ searchPlaceholder={t('common.filter')}
searchRender={({ prefix, ...restProps }) => (
<Input suffix={<IconSearch
className={styles['catalog-tree-input-icon']}/>} {...restProps}
className={styles['catalog-tree-input']}></Input>
)}
diff --git a/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx
b/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx
index b03a999..1bada6f 100644
--- a/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx
+++ b/paimon-web-ui/src/pages/Metadata/components/LeftContent/index.tsx
@@ -21,6 +21,8 @@ import { useState } from "react";
import CatalogModalForm from
"@pages/Metadata/components/LeftContent/components/CatalogModalForm";
import {useCatalogStore} from "@src/store/catalogStore.ts";
import styles from "./left-content.module.less";
+import { useTranslation } from 'react-i18next';
+
const MetadataSidebar = () => {
@@ -76,10 +78,11 @@ const MetadataSidebar = () => {
});
};
+ const { t } = useTranslation()
return(
<div className={styles.container}>
<div className={styles['add-catalog-container']}>
- <span>Catalog</span>
+ <span>{t('metadata.catalog')}</span>
<IconPlus className={styles.iconPlus}
onClick={handleOpenModal}/>
</div>
<CatalogTree/>
diff --git
a/paimon-web-ui/src/pages/Metadata/components/RightContent/components/MainContent/Table/index.tsx
b/paimon-web-ui/src/pages/Metadata/components/RightContent/components/MainContent/Table/index.tsx
index dad9c11..4c33c3b 100644
---
a/paimon-web-ui/src/pages/Metadata/components/RightContent/components/MainContent/Table/index.tsx
+++
b/paimon-web-ui/src/pages/Metadata/components/RightContent/components/MainContent/Table/index.tsx
@@ -17,12 +17,15 @@ under the License. */
import { useState } from 'react';
import {TabPane, Tabs} from '@douyinfe/semi-ui';
+import { useTranslation } from 'react-i18next';
const TableTab = () => {
+ const { t } = useTranslation()
+
const [tabList, setTabList] = useState([
- { tab: 'TableInfo', itemKey: '1', content: "info", closable: true },
- { tab: 'Details', itemKey: '2', content: "info", closable: true },
- { tab: 'Files', itemKey: '3', content: "info", closable: true }
+ { tab: 'TableInfo', name: 'tableInfo', itemKey: '1', content: "info",
closable: true },
+ { tab: 'Details', name: 'details', itemKey: '2', content: "info",
closable: true },
+ { tab: 'Files', name: 'files', itemKey: '3', content: "info",
closable: true }
]);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -35,9 +38,12 @@ const TableTab = () => {
return (
<Tabs type="line" defaultActiveKey="1" onTabClose={close}>
- {tabList.map((t) => (
- <TabPane closable={t.closable} tab={<span>{t.tab}</span>}
itemKey={t.itemKey} key={t.itemKey}>
- {t.content}
+ {tabList.map((tab) => (
+ <TabPane closable={tab.closable}
+ tab={<span>{t(`metadata.${tab.name}`)}</span>}
+ itemKey={tab.itemKey}
+ key={tab.itemKey}>
+ {tab.content}
</TabPane>
))}
</Tabs>
diff --git
a/paimon-web-ui/src/pages/Playground/components/TabMenuSidebar/components/TreeComponent/index.tsx
b/paimon-web-ui/src/pages/Playground/components/TabMenuSidebar/components/TreeComponent/index.tsx
index 70cc832..06279d1 100644
---
a/paimon-web-ui/src/pages/Playground/components/TabMenuSidebar/components/TreeComponent/index.tsx
+++
b/paimon-web-ui/src/pages/Playground/components/TabMenuSidebar/components/TreeComponent/index.tsx
@@ -20,8 +20,10 @@ import { Input } from '@douyinfe/semi-ui';
import { IconFilter } from '@douyinfe/semi-icons';
import { IconFile } from '@douyinfe/semi-icons';
import styles from "./tree-component.module.less";
+import { useTranslation } from 'react-i18next';
const TreeNode = () => {
+ const { t } = useTranslation()
const treeData = [
{
label: 'paimon',
@@ -164,7 +166,7 @@ const TreeNode = () => {
<Tree
filterTreeNode
treeData={treeData}
- searchPlaceholder={"Filter"}
+ searchPlaceholder={t('common.filter')}
searchRender={({ prefix, ...restProps }) => (
<Input suffix={<IconFilter/>} {...restProps}></Input>
)}