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>
             )}

Reply via email to