From 373946ef3fd9427ff1788325de590eac75465db3 Mon Sep 17 00:00:00 2001 From: balibabu Date: Sun, 7 Apr 2024 17:41:29 +0800 Subject: [PATCH] change language #245 (#246) ### What problem does this PR solve? change language Issue link: #245 - [x] New Feature (non-breaking change which adds functionality) --- web/src/app.tsx | 32 +- .../components/chunk-method-modal/index.tsx | 39 +- web/src/components/max-token-number.tsx | 12 +- web/src/components/rename-modal/index.tsx | 8 +- web/src/global.ts | 1 - web/src/hooks/commonHooks.tsx | 4 + web/src/hooks/logicHooks.ts | 14 + web/src/hooks/userSettingHook.ts | 2 +- .../components/right-toolbar/index.tsx | 43 +- web/src/locales/config.ts | 4 +- web/src/locales/en.json | 122 ----- web/src/locales/en.ts | 433 ++++++++++++++++++ web/src/locales/zh.json | 23 - web/src/locales/zh.ts | 417 +++++++++++++++++ .../components/chunk-creating-modal/index.tsx | 16 +- .../components/chunk-toolbar/index.tsx | 21 +- .../components/knowledge-chunk/index.tsx | 4 +- .../components/knowledge-chunk/model.ts | 3 +- .../knowledge-upload-file/index.tsx | 17 +- .../components/knowledge-file/index.tsx | 4 +- .../components/knowledge-file/model.ts | 17 +- .../parsing-status-cell/index.tsx | 8 +- .../knowledge-setting/category-panel.tsx | 28 +- .../knowledge-setting/configuration.tsx | 6 +- .../components/knowledge-setting/model.ts | 5 +- web/src/pages/add-knowledge/index.tsx | 3 +- .../assistant-setting.tsx | 35 +- .../chat/chat-configuration-modal/index.tsx | 38 +- .../model-setting.tsx | 54 +-- .../prompt-engine.tsx | 32 +- web/src/pages/chat/chat-container/index.tsx | 6 +- web/src/pages/chat/index.tsx | 11 +- web/src/pages/chat/model.ts | 9 +- web/src/pages/login/index.tsx | 2 +- web/src/pages/login/model.ts | 7 +- web/src/pages/login/right-panel.tsx | 9 +- .../components/setting-title/index.tsx | 5 +- web/src/pages/user-setting/model.ts | 10 +- .../setting-model/api-key-modal/index.tsx | 14 +- .../user-setting/setting-model/index.tsx | 16 +- .../system-model-setting-modal/index.tsx | 27 +- .../user-setting/setting-password/index.tsx | 26 +- .../user-setting/setting-profile/index.tsx | 76 +-- .../pages/user-setting/setting-team/index.tsx | 8 +- web/src/pages/user-setting/sidebar/index.tsx | 43 +- web/src/utils/authorizationUtil.ts | 6 + web/src/utils/request.ts | 39 +- 47 files changed, 1301 insertions(+), 458 deletions(-) delete mode 100644 web/src/global.ts delete mode 100644 web/src/locales/en.json create mode 100644 web/src/locales/en.ts delete mode 100644 web/src/locales/zh.json create mode 100644 web/src/locales/zh.ts diff --git a/web/src/app.tsx b/web/src/app.tsx index 746b64995..e33201a94 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -1,7 +1,26 @@ -import { App, ConfigProvider } from 'antd'; -import { ReactNode } from 'react'; +import i18next from '@/locales/config'; +import { App, ConfigProvider, ConfigProviderProps } from 'antd'; +import enUS from 'antd/locale/en_US'; +import zhCN from 'antd/locale/zh_CN'; +import React, { ReactNode, useEffect, useState } from 'react'; +import storage from './utils/authorizationUtil'; + +type Locale = ConfigProviderProps['locale']; + +const RootProvider = ({ children }: React.PropsWithChildren) => { + const getLocale = (lng: string) => (lng === 'zh' ? zhCN : enUS); + + const [locale, setLocal] = useState(getLocale(storage.getLanguage())); + + i18next.on('languageChanged', function (lng: string) { + storage.setLanguage(lng); + setLocal(getLocale(lng)); + }); + + useEffect(() => { + i18next.changeLanguage(storage.getLanguage()); + }, [locale]); -export function rootContainer(container: ReactNode) { return ( - {container} + {children} ); +}; + +export function rootContainer(container: ReactNode) { + return {container}; } diff --git a/web/src/components/chunk-method-modal/index.tsx b/web/src/components/chunk-method-modal/index.tsx index f4ecbd958..7f592b546 100644 --- a/web/src/components/chunk-method-modal/index.tsx +++ b/web/src/components/chunk-method-modal/index.tsx @@ -134,12 +134,8 @@ const ChunkMethodModal: React.FC = ({ {showPages && ( <> -

Page Ranges:

- +

{t('pageRanges')}:

+ @@ -163,7 +159,7 @@ const ChunkMethodModal: React.FC = ({ rules={[ { required: true, - message: 'Missing start page number', + message: t('fromMessage'), }, ({ getFieldValue }) => ({ validator(_, value) { @@ -175,16 +171,14 @@ const ChunkMethodModal: React.FC = ({ return Promise.resolve(); } return Promise.reject( - new Error( - 'The current value must be greater than the previous to!', - ), + new Error(t('greaterThanPrevious')), ); }, }), ]} > = ({ rules={[ { required: true, - message: 'Missing end page number(excluded)', + message: t('toMessage'), }, ({ getFieldValue }) => ({ validator(_, value) { @@ -208,16 +202,14 @@ const ChunkMethodModal: React.FC = ({ return Promise.resolve(); } return Promise.reject( - new Error( - 'The current value must be greater than to!', - ), + new Error(t('greaterThan')), ); }, }), ]} > = ({ block icon={} > - Add page + {t('addPage')} @@ -246,12 +238,10 @@ const ChunkMethodModal: React.FC = ({ {showOne && ( @@ -265,14 +255,13 @@ const ChunkMethodModal: React.FC = ({ getFieldValue(['parser_config', 'layout_recognize']) && ( diff --git a/web/src/components/max-token-number.tsx b/web/src/components/max-token-number.tsx index 43618be4f..9913b035b 100644 --- a/web/src/components/max-token-number.tsx +++ b/web/src/components/max-token-number.tsx @@ -1,18 +1,18 @@ +import { useTranslate } from '@/hooks/commonHooks'; import { Flex, Form, InputNumber, Slider } from 'antd'; const MaxTokenNumber = () => { + const { t } = useTranslate('knowledgeConfiguration'); + return ( - + @@ -20,7 +20,7 @@ const MaxTokenNumber = () => { diff --git a/web/src/components/rename-modal/index.tsx b/web/src/components/rename-modal/index.tsx index 9802ff88f..afe5b7f67 100644 --- a/web/src/components/rename-modal/index.tsx +++ b/web/src/components/rename-modal/index.tsx @@ -1,3 +1,4 @@ +import { useTranslate } from '@/hooks/commonHooks'; import { Form, Input, Modal } from 'antd'; import { useEffect } from 'react'; import { IModalManagerChildrenProps } from '../modal-manager'; @@ -17,6 +18,7 @@ const RenameModal = ({ onOk, }: IProps) => { const [form] = Form.useForm(); + const { t } = useTranslate('common'); type FieldType = { name?: string; @@ -48,7 +50,7 @@ const RenameModal = ({ return ( - label="Name" + label={t('name')} name="name" - rules={[{ required: true, message: 'Please input name!' }]} + rules={[{ required: true, message: t('namePlaceholder') }]} > diff --git a/web/src/global.ts b/web/src/global.ts deleted file mode 100644 index d025e4a33..000000000 --- a/web/src/global.ts +++ /dev/null @@ -1 +0,0 @@ -import '@/locales/config'; diff --git a/web/src/hooks/commonHooks.tsx b/web/src/hooks/commonHooks.tsx index 4125cd7cd..03a16509b 100644 --- a/web/src/hooks/commonHooks.tsx +++ b/web/src/hooks/commonHooks.tsx @@ -119,3 +119,7 @@ export const useShowDeleteConfirm = () => { export const useTranslate = (keyPrefix: string) => { return useTranslation('translation', { keyPrefix }); }; + +export const useCommonTranslation = () => { + return useTranslation('translation', { keyPrefix: 'common' }); +}; diff --git a/web/src/hooks/logicHooks.ts b/web/src/hooks/logicHooks.ts index 42dea0ffd..23833db99 100644 --- a/web/src/hooks/logicHooks.ts +++ b/web/src/hooks/logicHooks.ts @@ -1,9 +1,11 @@ import { IKnowledgeFile } from '@/interfaces/database/knowledge'; import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; import { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useSetModalState } from './commonHooks'; import { useSetDocumentParser } from './documentHooks'; import { useOneNamespaceEffectsLoading } from './storeHooks'; +import { useSaveSetting } from './userSettingHook'; export const useChangeDocumentParser = (documentId: string) => { const setDocumentParser = useSetDocumentParser(); @@ -45,3 +47,15 @@ export const useSetSelectedRecord = () => { return { currentRecord, setRecord }; }; + +export const useChangeLanguage = () => { + const { i18n } = useTranslation(); + const saveSetting = useSaveSetting(); + + const changeLanguage = (lng: string) => { + i18n.changeLanguage(lng === 'Chinese' ? 'zh' : 'en'); + saveSetting({ language: lng }); + }; + + return changeLanguage; +}; diff --git a/web/src/hooks/userSettingHook.ts b/web/src/hooks/userSettingHook.ts index c6d5065a7..79f138121 100644 --- a/web/src/hooks/userSettingHook.ts +++ b/web/src/hooks/userSettingHook.ts @@ -84,7 +84,7 @@ export const useSaveSetting = () => { const dispatch = useDispatch(); const saveSetting = useCallback( - (userInfo: { new_password: string } | IUserInfo): number => { + (userInfo: { new_password: string } | Partial): number => { return dispatch({ type: 'settingModel/setting', payload: userInfo }); }, [dispatch], diff --git a/web/src/layouts/components/right-toolbar/index.tsx b/web/src/layouts/components/right-toolbar/index.tsx index e5df898f4..faca5c903 100644 --- a/web/src/layouts/components/right-toolbar/index.tsx +++ b/web/src/layouts/components/right-toolbar/index.tsx @@ -1,11 +1,19 @@ +import { ReactComponent as TranslationIcon } from '@/assets/svg/translation.svg'; +import { useTranslate } from '@/hooks/commonHooks'; import { GithubOutlined } from '@ant-design/icons'; -import { Space } from 'antd'; +import { Dropdown, MenuProps, Space } from 'antd'; import React from 'react'; import User from '../user'; + +import { useChangeLanguage } from '@/hooks/logicHooks'; import styled from './index.less'; -const Circle = ({ children }: React.PropsWithChildren) => { - return
{children}
; +const Circle = ({ children, ...restProps }: React.PropsWithChildren) => { + return ( +
+ {children} +
+ ); }; const handleGithubCLick = () => { @@ -13,17 +21,38 @@ const handleGithubCLick = () => { }; const RightToolBar = () => { + const { t } = useTranslate('common'); + const changeLanguage = useChangeLanguage(); + + const handleItemClick: MenuProps['onClick'] = ({ key }) => { + changeLanguage(key); + }; + + const items: MenuProps['items'] = [ + { + key: 'English', + label: {t('english')}, + }, + { type: 'divider' }, + { + key: 'Chinese', + label: {t('chinese')}, + }, + ]; + return (
+ + + + + {/* - - - - + */} diff --git a/web/src/locales/config.ts b/web/src/locales/config.ts index 311f5d200..17b7a7f2b 100644 --- a/web/src/locales/config.ts +++ b/web/src/locales/config.ts @@ -1,8 +1,8 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; -import translation_en from './en.json'; -import translation_zh from './zh.json'; +import translation_en from './en'; +import translation_zh from './zh'; const resources = { en: translation_en, diff --git a/web/src/locales/en.json b/web/src/locales/en.json deleted file mode 100644 index 027d7b7ec..000000000 --- a/web/src/locales/en.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "translation": { - "common": { - "delete": "Delete", - "deleteModalTitle": "Are you sure delete this item?", - "ok": "Yes", - "cancel": "No", - "total": "Total", - "rename": "Rename", - "name": "Name", - "namePlaceholder": "Please input name" - }, - "login": { - "login": "Sign in", - "signUp": "Sign up", - "loginDescription": "We’re so excited to see you again!", - "registerDescription": "Glad to have you on board!", - "emailLabel": "Email", - "emailPlaceholder": "Please input email", - "passwordLabel": "Password", - "passwordPlaceholder": "Please input password", - "rememberMe": "Remember me", - "signInTip": "Don’t have an account?", - "signUpTip": "Already have an account?", - "nicknameLabel": "Nickname", - "nicknamePlaceholder": "Please input nickname", - "register": "Create an account", - "continue": "Continue" - }, - "header": { - "knowledgeBase": "Knowledge Base", - "chat": "Chat", - "register": "Register", - "signin": "Sign in", - "home": "Home", - "setting": "用户设置", - "logout": "登出" - }, - "knowledgeList": { - "welcome": "Welcome back", - "description": "Which database are we going to use today?", - "createKnowledgeBase": "Create knowledge base", - "name": "Name", - "namePlaceholder": "Please input name!", - "doc": "Docs" - }, - "knowledgeDetails": { - "dataset": "Dataset", - "testing": "Retrieval testing", - "configuration": "Configuration", - "name": "Name", - "namePlaceholder": "Please input name!", - "doc": "Docs", - "datasetDescription": "Hey, don't forget to adjust the chunk after adding the dataset! 😉", - "addFile": "Add file", - "searchFiles": "Search your files", - "localFiles": "Local files", - "emptyFiles": "Create empty file", - "chunkNumber": "Chunk Number", - "uploadDate": "Upload Date", - "chunkMethod": "Chunk Method", - "enabled": "Enabled", - "action": "Action", - "parsingStatus": "Parsing Status", - "processBeginAt": "Process Begin At", - "processDuration": "Process Duration", - "progressMsg": "Progress Msg", - "testingDescription": "Final step! After success, leave the rest to Infiniflow AI.", - "topK": "Top K", - "topKTip": "For the computaion cost, not all the retrieved chunk will be computed vector cosine similarity with query. The bigger the 'Top K' is, the higher the recall rate is, the slower the retrieval speed is.", - "similarityThreshold": "Similarity threshold", - "similarityThresholdTip": "We use hybrid similarity score to evaluate distance between two lines of text. It's weighted keywords similarity and vector cosine similarity. If the similarity between query and chunk is less than this threshold, the chunk will be filtered out.", - "vectorSimilarityWeight": "Vector similarity weight", - "vectorSimilarityWeightTip": "We use hybrid similarity score to evaluate distance between two lines of text. It's weighted keywords similarity and vector cosine similarity. The sum of both weights is 1.0.", - "testText": "Test text", - "testTextPlaceholder": "Please input your question!", - "testingLabel": "Testing", - "similarity": "Hybrid Similarity", - "termSimilarity": "Term Similarity", - "vectorSimilarity": "Vector Similarity", - "hits": "Hits", - "view": "View", - "filesSelected": "Files Selected" - }, - "knowledgeConfiguration": { - "titleDescription": "Update your knowledge base details especially parsing method here.", - "name": "Knowledge base name", - "photo": "Knowledge base photo", - "description": "Description", - "language": "Language", - "languageMessage": "Please input your language!", - "languagePlaceholder": "Please input your language!", - "permissions": "Permissions", - "embeddingModel": "Embedding model", - "chunkTokenNumber": "Chunk token number", - "embeddingModelTip": "The embedding model used to embedding chunks. It's unchangable once the knowledgebase has chunks. You need to delete all the chunks if you want to change it.", - "permissionsTip": "If the permission is 'Team', all the team member can manipulate the knowledgebase.", - "chunkTokenNumberTip": "It determine the token number of a chunk approximately.", - "chunkMethodTip": "The instruction is at right.", - "upload": "Upload", - "english": "English", - "chinese": "Chinese", - "embeddingModelPlaceholder": "Please select a embedding model", - "chunkMethodPlaceholder": "Please select a chunk method", - "save": "Save", - "me": "Only me", - "team": "Team", - "cancel": "Cancel" - }, - "footer": { - "detail": "All rights reserved @ React" - }, - "layout": { - "file": "file", - "knowledge": "knowledge", - "chat": "chat" - }, - "setting": { - "btn": "en" - } - } -} diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts new file mode 100644 index 000000000..487b5c313 --- /dev/null +++ b/web/src/locales/en.ts @@ -0,0 +1,433 @@ +export default { + translation: { + common: { + delete: 'Delete', + deleteModalTitle: 'Are you sure delete this item?', + ok: 'Yes', + cancel: 'No', + total: 'Total', + rename: 'Rename', + name: 'Name', + save: 'Save', + namePlaceholder: 'Please input name', + next: 'Next', + create: 'Create', + edit: 'Edit', + upload: 'Upload', + english: 'English', + chinese: 'Chinese', + language: 'Language', + languageMessage: 'Please input your language!', + languagePlaceholder: 'select your language', + }, + login: { + login: 'Sign in', + signUp: 'Sign up', + loginDescription: 'We’re so excited to see you again!', + registerDescription: 'Glad to have you on board!', + emailLabel: 'Email', + emailPlaceholder: 'Please input email', + passwordLabel: 'Password', + passwordPlaceholder: 'Please input password', + rememberMe: 'Remember me', + signInTip: 'Don’t have an account?', + signUpTip: 'Already have an account?', + nicknameLabel: 'Nickname', + nicknamePlaceholder: 'Please input nickname', + register: 'Create an account', + continue: 'Continue', + title: 'Start building your smart assistants.', + description: + 'Sign up for free to explore top RAG technology. Create knowledge bases and AIs to empower your business.', + review: 'from 500+ reviews', + }, + header: { + knowledgeBase: 'Knowledge Base', + chat: 'Chat', + register: 'Register', + signin: 'Sign in', + home: 'Home', + setting: '用户设置', + logout: '登出', + }, + knowledgeList: { + welcome: 'Welcome back', + description: 'Which knowledge base are we going to use today?', + createKnowledgeBase: 'Create knowledge base', + name: 'Name', + namePlaceholder: 'Please input name!', + doc: 'Docs', + }, + knowledgeDetails: { + dataset: 'Dataset', + testing: 'Retrieval testing', + files: 'files', + configuration: 'Configuration', + name: 'Name', + namePlaceholder: 'Please input name!', + doc: 'Docs', + datasetDescription: + "Hey, don't forget to adjust the chunk after adding the dataset! 😉", + addFile: 'Add file', + searchFiles: 'Search your files', + localFiles: 'Local files', + emptyFiles: 'Create empty file', + chunkNumber: 'Chunk Number', + uploadDate: 'Upload Date', + chunkMethod: 'Chunk Method', + enabled: 'Enabled', + action: 'Action', + parsingStatus: 'Parsing Status', + processBeginAt: 'Process Begin At', + processDuration: 'Process Duration', + progressMsg: 'Progress Msg', + testingDescription: + 'Final step! After success, leave the rest to Infiniflow AI.', + topK: 'Top K', + topKTip: + "For the computaion cost, not all the retrieved chunk will be computed vector cosine similarity with query. The bigger the 'Top K' is, the higher the recall rate is, the slower the retrieval speed is.", + similarityThreshold: 'Similarity threshold', + similarityThresholdTip: + "We use hybrid similarity score to evaluate distance between two lines of text. It's weighted keywords similarity and vector cosine similarity. If the similarity between query and chunk is less than this threshold, the chunk will be filtered out.", + vectorSimilarityWeight: 'Vector similarity weight', + vectorSimilarityWeightTip: + "We use hybrid similarity score to evaluate distance between two lines of text. It's weighted keywords similarity and vector cosine similarity. The sum of both weights is 1.0.", + testText: 'Test text', + testTextPlaceholder: 'Please input your question!', + testingLabel: 'Testing', + similarity: 'Hybrid Similarity', + termSimilarity: 'Term Similarity', + vectorSimilarity: 'Vector Similarity', + hits: 'Hits', + view: 'View', + filesSelected: 'Files Selected', + upload: 'Upload', + runningStatus0: 'UNSTART', + runningStatus1: 'Parsing', + runningStatus2: 'CANCEL', + runningStatus3: 'SUCCESS', + runningStatus4: 'FAIL', + pageRanges: 'Page Ranges', + pageRangesTip: + 'page ranges: Define the page ranges that need to be parsed. The pages that not included in these ranges will be ignored.', + fromPlaceholder: 'from', + fromMessage: 'Missing start page number', + toPlaceholder: 'to', + toMessage: 'Missing end page number(excluded)', + layoutRecognize: 'Layout recognize', + layoutRecognizeTip: + 'Use visual models for layout analysis to better identify document structure, find where the titles, text blocks, images, and tables are. Without this feature, only the plain text of the PDF can be obtained.', + taskPageSize: 'Task page size', + taskPageSizeMessage: 'Please input your task page size!', + taskPageSizeTip: `If using layout recognize, the PDF file will be split into groups of successive. Layout analysis will be performed parallelly between groups to increase the processing speed. The 'Task page size' determines the size of groups. The larger the page size is, the lower the chance of splitting continuous text between pages into different chunks.`, + addPage: 'Add page', + greaterThan: 'The current value must be greater than to!', + greaterThanPrevious: + 'The current value must be greater than the previous to!', + selectFiles: 'Select files', + changeSpecificCategory: 'Change specific category', + uploadTitle: 'Click or drag file to this area to upload', + uploadDescription: + 'Support for a single or bulk upload. Strictly prohibited from uploading company data or other banned files.', + }, + knowledgeConfiguration: { + titleDescription: + 'Update your knowledge base details especially parsing method here.', + name: 'Knowledge base name', + photo: 'Knowledge base photo', + description: 'Description', + language: 'Language', + languageMessage: 'Please input your language!', + languagePlaceholder: 'Please input your language!', + permissions: 'Permissions', + embeddingModel: 'Embedding model', + chunkTokenNumber: 'Chunk token number', + chunkTokenNumberMessage: 'Chunk token number is required', + embeddingModelTip: + "The embedding model used to embedding chunks. It's unchangable once the knowledgebase has chunks. You need to delete all the chunks if you want to change it.", + permissionsTip: + "If the permission is 'Team', all the team member can manipulate the knowledgebase.", + chunkTokenNumberTip: + 'It determine the token number of a chunk approximately.', + chunkMethod: 'Chunk method', + chunkMethodTip: 'The instruction is at right.', + upload: 'Upload', + english: 'English', + chinese: 'Chinese', + embeddingModelPlaceholder: 'Please select a embedding model', + chunkMethodPlaceholder: 'Please select a chunk method', + save: 'Save', + me: 'Only me', + team: 'Team', + cancel: 'Cancel', + methodTitle: 'Chunking Method Description', + methodExamples: 'Examples', + methodExamplesDescription: + 'This visual guides is in order to make understanding easier for you.', + dialogueExamplesTitle: 'Dialogue Examples', + methodEmpty: + 'This will display a visual explanation of the knowledge base categories', + book: `

Supported file formats are DOCX, PDF, TXT.

+ Since a book is long and not all the parts are useful, if it's a PDF, + please setup the page ranges for every book in order eliminate negative effects and save computing time for analyzing.

`, + laws: `

Supported file formats are DOCX, PDF, TXT.

+ Legal documents have a very rigorous writing format. We use text feature to detect split point. +

+ The chunk granularity is consistent with 'ARTICLE', and all the upper level text will be included in the chunk. +

`, + manual: `

Only PDF is supported.

+ We assume manual has hierarchical section structure. We use the lowest section titles as pivots to slice documents. + So, the figures and tables in the same section will not be sliced apart, and chunk size might be large. +

`, + naive: `

Supported file formats are DOCX, EXCEL, PPT, IMAGE, PDF, TXT.

+

This method apply the naive ways to chunk files:

+

+

  • Successive text will be sliced into pieces using vision detection model.
  • +
  • Next, these successive pieces are merge into chunks whose token number is no more than 'Token number'.
  • `, + paper: `

    Only PDF file is supported.

    + If our model works well, the paper will be sliced by it's sections, like abstract, 1.1, 1.2, etc.

    + The benefit of doing this is that LLM can better summarize the content of relevant sections in the paper, + resulting in more comprehensive answers that help readers better understand the paper. + The downside is that it increases the context of the LLM conversation and adds computational cost, + so during the conversation, you can consider reducing the ‘topN’ setting.

    `, + presentation: `

    The supported file formats are PDF, PPTX.

    + Every page will be treated as a chunk. And the thumbnail of every page will be stored.

    + All the PPT files you uploaded will be chunked by using this method automatically, setting-up for every PPT file is not necessary.

    `, + qa: `

    EXCEL and CSV/TXT files are supported.

    + If the file is in excel format, there should be 2 columns question and answer without header. + And question column is ahead of answer column. + And it's O.K if it has multiple sheets as long as the columns are rightly composed.

    + + If it's in csv format, it should be UTF-8 encoded. Use TAB as delimiter to separate question and answer.

    + + All the deformed lines will be ignored. + Every pair of Q&A will be treated as a chunk.

    `, + resume: `

    The supported file formats are DOCX, PDF, TXT. +

    + The résumé comes in a variety of formats, just like a person’s personality, but we often have to organize them into structured data that makes it easy to search. +

    + Instead of chunking the résumé, we parse the résumé into structured data. As a HR, you can dump all the résumé you have, + the you can list all the candidates that match the qualifications just by talk with 'RAGFlow'. +

    + `, + table: `

    EXCEL and CSV/TXT format files are supported.

    + Here're some tips: +

      +
    • For csv or txt file, the delimiter between columns is TAB.
    • +
    • The first line must be column headers.
    • +
    • Column headers must be meaningful terms in order to make our LLM understanding. + It's good to enumerate some synonyms using slash '/' to separate, and even better to + enumerate values using brackets like 'gender/sex(male, female)'.

      + Here are some examples for headers:

        +
      1. supplier/vendor'TAB'color(yellow, red, brown)'TAB'gender/sex(male, female)'TAB'size(M,L,XL,XXL)
      2. +
      3. 姓名/名字'TAB'电话/手机/微信'TAB'最高学历(高中,职高,硕士,本科,博士,初中,中技,中专,专科,专升本,MPA,MBA,EMBA)
      4. +
      +

      +
    • +
    • Every row in table will be treated as a chunk.
    • +
    `, + picture: ` +

    Image files are supported. Video is coming soon.

    + If the picture has text in it, OCR is applied to extract the text as its text description. +

    + If the text extracted by OCR is not enough, visual LLM is used to get the descriptions. +

    `, + one: ` +

    Supported file formats are DOCX, EXCEL, PDF, TXT. +

    + For a document, it will be treated as an entire chunk, no split at all. +

    + If you want to summarize something that needs all the context of an article and the selected LLM's context length covers the document length, you can try this method. +

    `, + }, + chunk: { + chunk: 'Chunk', + bulk: 'Bulk', + selectAll: 'Select All', + enabledSelected: 'Enable Selected', + disabledSelected: 'Disable Selected', + deleteSelected: 'Delete Selected', + search: 'Search', + all: 'All', + enabled: 'Enabled', + disabled: 'Disabled', + keyword: 'Keyword', + function: 'Function', + chunkMessage: 'Please input value!', + }, + chat: { + assistantSetting: 'Assistant Setting', + promptEngine: 'Prompt Engine', + modelSetting: 'Model Setting', + chat: 'Chat', + newChat: 'New chat', + send: 'Send', + sendPlaceholder: 'Message Resume Assistant...', + chatConfiguration: 'Chat Configuration', + chatConfigurationDescription: + ' Here, dress up a dedicated assistant for your special knowledge bases! 💕', + assistantName: 'Assistant name', + namePlaceholder: 'e.g. Resume Jarvis', + assistantAvatar: 'Assistant avatar', + language: 'Language', + emptyResponse: 'Empty response', + emptyResponseTip: `If nothing is retrieved with user's question in the knowledgebase, it will use this as an answer. If you want LLM comes up with its own opinion when nothing is retrieved, leave this blank.`, + setAnOpener: 'Set an opener', + setAnOpenerInitial: `Hi! I'm your assistant, what can I do for you?`, + setAnOpenerTip: 'How do you want to welcome your clients?', + knowledgeBases: 'Knowledgebases', + knowledgeBasesMessage: 'Please select', + knowledgeBasesTip: 'Select knowledgebases associated.', + system: 'System', + systemInitialValue: `You are an intelligent assistant. Please summarize the content of the knowledge base to answer the question. Please list the data in the knowledge base and answer in detail. When all knowledge base content is irrelevant to the question, your answer must include the sentence "The answer you are looking for is not found in the knowledge base!" Answers need to consider chat history. + Here is the knowledge base: + {knowledge} + The above is the knowledge base.`, + systemMessage: 'Please input!', + systemTip: + 'Instructions you need LLM to follow when LLM answers questions, like charactor design, answer length and answer language etc.', + topN: 'Top N', + topNTip: `Not all the chunks whose similarity score is above the 'simialrity threashold' will be feed to LLMs. LLM can only see these 'Top N' chunks.`, + variable: 'Variable', + variableTip: `If you use dialog APIs, the varialbes might help you chat with your clients with different strategies. + The variables are used to fill-in the 'System' part in prompt in order to give LLM a hint. + The 'knowledge' is a very special variable which will be filled-in with the retrieved chunks. + All the variables in 'System' should be curly bracketed.`, + add: 'Add', + key: 'key', + optional: 'Optional', + operation: 'operation', + model: 'Model', + modelTip: 'Large language chat model', + modelMessage: 'Please select!', + freedom: 'Freedom', + improvise: 'Improvise', + precise: 'Precise', + balance: 'Balance', + freedomTip: `'Precise' means the LLM will be conservative and answer your question cautiously. 'Improvise' means the you want LLM talk much and freely. 'Balance' is between cautiously and freely.`, + temperature: 'Temperature', + temperatureMessage: 'Temperature is required', + temperatureTip: + 'This parameter controls the randomness of predictions by the model. A lower temperature makes the model more confident in its responses, while a higher temperature makes it more creative and diverse.', + topP: 'Top P', + topPMessage: 'Top P is required', + topPTip: + 'Also known as “nucleus sampling,” this parameter sets a threshold to select a smaller set of words to sample from. It focuses on the most likely words, cutting off the less probable ones.', + presencePenalty: 'Presence Penalty', + presencePenaltyMessage: 'Presence Penalty is required', + presencePenaltyTip: + 'This discourages the model from repeating the same information by penalizing words that have already appeared in the conversation.', + frequencyPenalty: 'Frequency Penalty', + frequencyPenaltyMessage: 'Frequency Penalty is required', + frequencyPenaltyTip: + 'Similar to the presence penalty, this reduces the model’s tendency to repeat the same words frequently.', + maxTokens: 'Max Tokens', + maxTokensMessage: 'Max Tokens is required', + maxTokensTip: + 'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).', + }, + setting: { + profile: 'Profile', + profileDescription: 'Update your photo and personal details here.', + password: 'Password', + passwordDescription: + 'Please enter your current password to change your password.', + model: 'Model Providers', + modelDescription: 'Manage your account settings and preferences here.', + team: 'Team', + logout: 'Log out', + username: 'Username', + usernameMessage: 'Please input your username!', + photo: 'Your photo', + photoDescription: 'This will be displayed on your profile.', + colorSchema: 'Color schema', + colorSchemaMessage: 'Please select your color schema!', + colorSchemaPlaceholder: 'select your color schema', + bright: 'Bright', + dark: 'Dark', + timezone: 'Timezone', + timezoneMessage: 'Please input your timezone!', + timezonePlaceholder: 'select your timezone', + email: 'Email address', + emailDescription: 'Once registered, E-mail cannot be changed.', + currentPassword: 'Current password', + currentPasswordMessage: 'Please input your password!', + newPassword: 'New password', + newPasswordMessage: 'Please input your password!', + newPasswordDescription: + 'Your new password must be more than 8 characters.', + confirmPassword: 'Confirm new password', + confirmPasswordMessage: 'Please confirm your password!', + confirmPasswordNonMatchMessage: + 'The new password that you entered do not match!', + cancel: 'Cancel', + addedModels: 'Added models', + modelsToBeAdded: 'Models to be added', + addTheModel: 'Add the model', + apiKey: 'API-Key', + apiKeyMessage: 'Please input api key!', + apiKeyTip: + 'The API key can be obtained by registering the corresponding LLM supplier.', + showMoreModels: 'Show more models', + baseUrl: 'Base-Url', + baseUrlTip: + 'If your API key is from OpenAI, just ignore it. Any other intermediate providers will give this base url with the API key.', + modify: 'Modify', + systemModelSettings: 'System Model Settings', + chatModel: 'Chat model', + chatModelTip: + 'The default chat LLM all the newly created knowledgebase will use.', + embeddingModel: 'Embedding model', + embeddingModelTip: + 'The default embedding model all the newly created knowledgebase will use.', + img2txtModel: 'Img2txt model', + img2txtModelTip: + 'The default multi-module model all the newly created knowledgebase will use. It can describe a picture or video.', + sequence2txtModel: 'Img2txt model', + sequence2txtModelTip: + 'The default ASR model all the newly created knowledgebase will use. Use this model to translate voices to corresponding text.', + workspace: 'Workspace', + upgrade: 'Upgrade', + }, + message: { + registered: 'Registered!', + logout: 'logout', + logged: 'logged!', + pleaseSelectChunk: 'Please select chunk!', + modified: 'Modified', + created: 'Created', + deleted: 'Deleted', + renamed: 'Renamed', + operated: 'Operated', + updated: 'Updated', + 200: 'The server successfully returns the requested data.', + 201: 'Create or modify data successfully.', + 202: 'A request has been queued in the background (asynchronous task).', + 204: 'Data deleted successfully.', + 400: 'There was an error in the request issued, and the server did not create or modify data.', + 401: 'The user does not have permissions (wrong token, username, password).', + 403: 'The user is authorized, but access is prohibited.', + 404: 'The request was made for a record that does not exist, and the server did not perform the operation.', + 406: 'The requested format is not available.', + 410: 'The requested resource has been permanently deleted and will not be available again.', + 422: 'When creating an object, a validation error occurred.', + 500: 'A server error occurred, please check the server.', + 502: 'Gateway error.', + 503: 'The service is unavailable and the server is temporarily overloaded or undergoing maintenance.', + 504: 'Gateway timeout.', + requestError: 'Request error', + networkAnomalyDescription: + 'There is an abnormality in your network and you cannot connect to the server.', + networkAnomaly: 'network anomaly', + hint: 'hint', + }, + footer: { + profile: 'All rights reserved @ React', + }, + layout: { + file: 'file', + knowledge: 'knowledge', + chat: 'chat', + }, + }, +}; diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json deleted file mode 100644 index a6fd83502..000000000 --- a/web/src/locales/zh.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "translation": { - "login": { "login": "登录" }, - "header": { - "register": "注册", - "signin": "登陆", - "home": "首页", - "setting": "user setting", - "logout": "logout" - }, - "footer": { - "detail": "版权所有 @ React" - }, - "layout": { - "file": "文件", - "knowledge": "知识库", - "chat": "聊天" - }, - "setting": { - "btn": "中文" - } - } -} diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts new file mode 100644 index 000000000..aa79895f6 --- /dev/null +++ b/web/src/locales/zh.ts @@ -0,0 +1,417 @@ +export default { + translation: { + common: { + delete: '删除', + deleteModalTitle: '确定删除吗?', + ok: '是', + cancel: '否', + total: '总共', + rename: '重命名', + name: '名称', + save: '保存', + namePlaceholder: '请输入名称', + next: '下一步', + create: '创建', + edit: '编辑', + upload: '上传', + english: '英文', + chinese: '中文', + language: '语言', + languageMessage: '请输入语言', + languagePlaceholder: '请选择语言', + }, + login: { + login: '登录', + signUp: '注册', + loginDescription: '很高兴再次见到您!', + registerDescription: '很高兴您加入!', + emailLabel: '邮箱', + emailPlaceholder: '请输入邮箱地址', + passwordLabel: '密码', + passwordPlaceholder: '请输入密码', + rememberMe: '记住我', + signInTip: '没有帐户?', + signUpTip: '已经有帐户?', + nicknameLabel: '名称', + nicknamePlaceholder: '请输入名称', + register: '创建账户', + continue: '继续', + title: '开始构建您的智能助手', + description: + '免费注册以探索顶级 RAG 技术。 创建知识库和人工智能来增强您的业务', + review: '来自 500 多条评论', + }, + header: { + knowledgeBase: '知识库', + chat: '聊天', + register: '注册', + signin: '登录', + home: '首页', + setting: '用户设置', + logout: '登出', + }, + knowledgeList: { + welcome: '欢迎回来', + description: '今天我们要使用哪个知识库?', + createKnowledgeBase: '创建知识库', + name: '名称', + namePlaceholder: '请输入名称', + doc: '文档', + }, + knowledgeDetails: { + dataset: '数据集', + testing: '检索测试', + configuration: '配置', + files: '文件', + name: '名称', + namePlaceholder: '请输入名称', + doc: '文档', + datasetDescription: '嘿,添加数据集后别忘了调整解析块! 😉', + addFile: '新增文件', + searchFiles: '搜索文件', + localFiles: '本地文件', + emptyFiles: '新建空文件', + chunkNumber: '模块数', + uploadDate: '上传日期', + chunkMethod: '解析方法', + enabled: '启用', + action: '动作', + parsingStatus: '解析状态', + processBeginAt: '流程开始于', + processDuration: '过程持续时间', + progressMsg: '进度消息', + testingDescription: '最后一步! 成功后,剩下的就交给Infiniflow AI吧。', + topK: 'Top K', + topKTip: + '对于计算成本,并非所有检索到的块都会计算与查询的向量余弦相似度。 Top K越大,召回率越高,检索速度越慢。', + similarityThreshold: '相似度阈值', + similarityThresholdTip: + '我们使用混合相似度得分来评估两行文本之间的距离。 它是加权关键词相似度和向量余弦相似度。 如果查询和块之间的相似度小于此阈值,则该块将被过滤掉。', + vectorSimilarityWeight: '向量相似度权重', + vectorSimilarityWeightTip: + '我们使用混合相似度得分来评估两行文本之间的距离。 它是加权关键词相似度和向量余弦相似度。 两个权重之和为 1.0。', + testText: '测试文本', + testTextPlaceholder: '请输入您的问题!', + testingLabel: '测试', + similarity: '混合相似度', + termSimilarity: '术语相似度', + vectorSimilarity: '向量相似度', + hits: '命中数', + view: '看法', + filesSelected: '选定的文件', + upload: '上传', + runningStatus0: '未启动', + runningStatus1: '解析中', + runningStatus2: '取消', + runningStatus3: '成功', + runningStatus4: '失败', + pageRanges: '页码范围', + pageRangesTip: + '页码范围:定义需要解析的页面范围。 不包含在这些范围内的页面将被忽略。', + fromPlaceholder: '从', + fromMessage: '缺少起始页码', + toPlaceholder: '到', + toMessage: '缺少结束页码(不包含)', + layoutRecognize: '布局识别', + layoutRecognizeTip: + '使用视觉模型进行布局分析,以更好地识别文档结构,找到标题、文本块、图像和表格的位置。 如果没有此功能,则只能获取 PDF 的纯文本。', + taskPageSize: '任务页面大小', + taskPageSizeMessage: '请输入您的任务页面大小!', + taskPageSizeTip: `如果使用布局识别,PDF 文件将被分成连续的组。 布局分析将在组之间并行执行,以提高处理速度。 “任务页面大小”决定组的大小。 页面大小越大,将页面之间的连续文本分割成不同块的机会就越低。`, + addPage: '新增页面', + greaterThan: '当前值必须大于起始值!', + greaterThanPrevious: '当前值必须大于之前的值!', + selectFiles: '选择文件', + changeSpecificCategory: '更改特定类别', + uploadTitle: '点击或拖拽文件至此区域即可上传', + uploadDescription: + '支持单次或批量上传。 严禁上传公司数据或其他违禁文件。', + }, + knowledgeConfiguration: { + titleDescription: '在这里更新您的知识库详细信息,尤其是解析方法。', + name: '知识库名称', + photo: '知识库图片', + description: '描述', + language: '语言', + languageMessage: '请输入语言', + languagePlaceholder: '请输入语言', + permissions: '权限', + embeddingModel: '嵌入模型', + chunkTokenNumber: '块令牌数', + chunkTokenNumberMessage: '块令牌数是必填项', + embeddingModelTip: + '用于嵌入块的嵌入模型。 一旦知识库有了块,它就无法更改。 如果你想改变它,你需要删除所有的块。', + permissionsTip: '如果权限是“团队”,则所有团队成员都可以操作知识库。', + chunkTokenNumberTip: '它大致确定了一个块的令牌数量。', + chunkMethod: '解析方法', + chunkMethodTip: '说明位于右侧。', + upload: '上传', + english: '英文', + chinese: '中文', + embeddingModelPlaceholder: '请选择嵌入模型', + chunkMethodPlaceholder: '请选择分块方法', + save: '保存', + me: '只有我', + team: '团队', + cancel: '取消', + methodTitle: '分块方法说明', + methodExamples: '示例', + methodExamplesDescription: '这个视觉指南是为了让您更容易理解。', + dialogueExamplesTitle: '对话示例', + methodEmpty: '这将显示知识库类别的可视化解释', + book: `

    支持的文件格式为DOCXPDFTXT

    + 由于一本书很长,并不是所有部分都有用,如果是 PDF, + 请为每本书设置页面范围,以消除负面影响并节省分析计算时间。

    `, + laws: `

    支持的文件格式为DOCXPDFTXT

    + 法律文件有非常严格的书写格式。 我们使用文本特征来检测分割点。 +

    + chunk的粒度与'ARTICLE'一致,所有上层文本都会包含在chunk中。 +

    `, + manual: `

    仅支持PDF

    + 我们假设手册具有分层部分结构。 我们使用最低的部分标题作为对文档进行切片的枢轴。 + 因此,同一部分中的图和表不会被分割,并且块大小可能会很大。 +

    `, + naive: `

    支持的文件格式为DOCX、EXCEL、PPT、IMAGE、PDF、TXT

    +

    此方法将简单的方法应用于块文件:

    +

    +

  • 系统将使用视觉检测模型将连续文本分割成多个片段。
  • +
  • 接下来,这些连续的片段被合并成令牌数不超过“令牌数”的块。
  • `, + paper: `

    仅支持PDF文件。

    + 如果我们的模型运行良好,论文将按其部分进行切片,例如摘要、1.1、1.2等。

    + 这样做的好处是LLM可以更好的概括论文中相关章节的内容, + 产生更全面的答案,帮助读者更好地理解论文。 + 缺点是它增加了 LLM 对话的背景并增加了计算成本, + 所以在对话过程中,你可以考虑减少‘topN’的设置。

    `, + presentation: `

    支持的文件格式为PDFPPTX

    + 每个页面都将被视为一个块。 并且每个页面的缩略图都会被存储。

    + 您上传的所有PPT文件都会使用此方法自动分块,无需为每个PPT文件进行设置。

    `, + qa: `支持

    EXCELCSV/TXT文件。

    + 如果文件是Excel格式,应该有2列问题和答案,没有标题。 + 问题栏位于答案栏之前。 + 如果有多个工作表也没关系,只要列的组合正确即可。

    + + 如果是 csv 格式,则应采用 UTF-8 编码。 使用 TAB 作为分隔符来分隔问题和答案。

    + + 所有变形的线都将被忽略。 + 每对问答都将被视为一个块。

    `, + resume: `

    支持的文件格式为DOCXPDFTXT。 +

    + 简历有多种格式,就像一个人的个性一样,但我们经常必须将它们组织成结构化数据,以便于搜索。 +

    + 我们不是将简历分块,而是将简历解析为结构化数据。 作为HR,你可以扔掉所有的简历, + 您只需与'RAGFlow'交谈即可列出所有符合资格的候选人。 +

    + `, + table: `支持

    EXCELCSV/TXT格式文件。

    + 以下是一些提示: +

      +
    • 对于 csv 或 txt 文件,列之间的分隔符为 TAB
    • +
    • 第一行必须是列标题。
    • +
    • 列标题必须是有意义的术语,以便我们的法学硕士能够理解。 + 列举一些同义词时最好使用斜杠'/'来分隔,甚至更好 + 使用方括号枚举值,例如 'gender/sex(male,female)'.

      + 以下是标题的一些示例:

        +
      1. 供应商/供货商'TAB'颜色(黄色、红色、棕色)'TAB'性别(男、女)'TAB'尺码(M、L、XL、XXL)
      2. +
      3. 姓名/名字'TAB'电话/手机/微信'TAB'最高学历(高中,职高,硕士,本科,博士,初中,中技,中 专,专科,专升本,MPA,MBA,EMBA)
      4. +
      +

      +
    • +
    • 表中的每一行都将被视为一个块。
    • +
    `, + picture: ` +

    支持图像文件。 视频即将推出。

    + 如果图片中有文字,则应用 OCR 提取文字作为其文字描述。 +

    + 如果OCR提取的文本不够,可以使用视觉LLM来获取描述。 +

    `, + one: ` +

    支持的文件格式为DOCX、EXCEL、PDF、TXT。 +

    + 对于一个文档,它将被视为一个完整的块,根本不会被分割。 +

    + 如果你要总结的东西需要一篇文章的全部上下文,并且所选LLM的上下文长度覆盖了文档长度,你可以尝试这种方法。 +

    `, + }, + chunk: { + chunk: '解析块', + bulk: '批量', + selectAll: '选择所有', + enabledSelected: '启用选定的', + disabledSelected: '禁用选定的', + deleteSelected: '删除选定的', + search: '搜索', + all: '所有', + enabled: '启用', + disabled: '禁用的', + keyword: '关键词', + function: '函数', + chunkMessage: '请输入值!', + }, + chat: { + assistantSetting: '助理设置', + promptEngine: '提示引擎', + modelSetting: '模型设置', + chat: '聊天', + newChat: '新建聊天', + send: '发送', + sendPlaceholder: '消息概要助手...', + chatConfiguration: '聊天配置', + chatConfigurationDescription: '在这里,为你的专业知识库装扮专属助手! 💕', + assistantName: '助理姓名', + namePlaceholder: '例如 贾维斯简历', + assistantAvatar: '助理头像', + language: '语言', + emptyResponse: '空回复', + emptyResponseTip: `如果在知识库中没有检索到用户的问题,它将使用它作为答案。 如果您希望 LLM 在未检索到任何内容时提出自己的意见,请将此留空。`, + setAnOpener: '设置开场白', + setAnOpenerInitial: `你好! 我是你的助理,有什么可以帮到你的吗?`, + setAnOpenerTip: '您想如何欢迎您的客户?', + knowledgeBases: '知识库', + knowledgeBasesMessage: '请选择', + knowledgeBasesTip: '选择关联的知识库。', + system: '系统', + systemInitialValue: `你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。 + 以下是知识库: + {knowledge} + 以上是知识库。`, + systemMessage: '请输入', + systemTip: + '当LLM回答问题时,你需要LLM遵循的说明,比如角色设计、答案长度和答案语言等。', + topN: 'Top N', + topNTip: `并非所有相似度得分高于“相似度阈值”的块都会被提供给法学硕士。 LLM 只能看到这些“Top N”块。`, + variable: '变量', + variableTip: `如果您使用对话 API,变量可能会帮助您使用不同的策略与客户聊天。 + 这些变量用于填写提示中的“系统”部分,以便给LLM一个提示。 + “知识”是一个非常特殊的变量,它将用检索到的块填充。 + “System”中的所有变量都应该用大括号括起来。`, + add: '新增', + key: '关键字', + optional: '可选的', + operation: '操作', + model: '模型', + modelTip: '大语言聊天模型', + modelMessage: '请选择', + freedom: '自由', + improvise: '即兴创作', + precise: '精确', + balance: '平衡', + freedomTip: `“精确”意味着法学硕士会保守并谨慎地回答你的问题。 “即兴发挥”意味着你希望法学硕士能够自由地畅所欲言。 “平衡”是谨慎与自由之间的平衡。`, + temperature: '温度', + temperatureMessage: '温度是必填项', + temperatureTip: + '该参数控制模型预测的随机性。 较低的温度使模型对其响应更有信心,而较高的温度则使其更具创造性和多样性。', + topP: 'Top P', + topPMessage: 'Top P 是必填项', + topPTip: + '该参数也称为“核心采样”,它设置一个阈值来选择较小的单词集进行采样。 它专注于最可能的单词,剔除不太可能的单词。', + presencePenalty: '出席处罚', + presencePenaltyMessage: '出席处罚是必填项', + presencePenaltyTip: + '这会通过惩罚对话中已经出现的单词来阻止模型重复相同的信息。', + frequencyPenalty: '频率惩罚', + frequencyPenaltyMessage: '频率惩罚是必填项', + frequencyPenaltyTip: + '与存在惩罚类似,这减少了模型频繁重复相同单词的倾向。', + maxTokens: '最大token数', + maxTokensMessage: '最大token数是必填项', + maxTokensTip: + '这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。', + }, + setting: { + profile: '概要', + profileDescription: '在此更新您的照片和个人详细信息。', + password: '密码', + passwordDescription: '请输入您当前的密码以更改您的密码。', + model: '模型提供商', + modelDescription: '在此管理您的帐户设置和首选项。', + team: '团队', + logout: '登出', + username: '用户名', + usernameMessage: '请输入用户名', + photo: '头像', + photoDescription: '这将显示在您的个人资料上。', + colorSchema: '主题', + colorSchemaMessage: '请选择您的主题!', + colorSchemaPlaceholder: '请选择您的主题!', + bright: '明亮', + dark: '暗色', + timezone: '时区', + timezoneMessage: '请选择时区', + timezonePlaceholder: '请选择时区', + email: '邮箱地址', + emailDescription: '一旦注册,电子邮件将无法更改。', + currentPassword: '当前密码', + currentPasswordMessage: '请输入当前密码', + newPassword: '新密码', + newPasswordMessage: '请输入新密码', + newPasswordDescription: '您的新密码必须超过 8 个字符。', + confirmPassword: '确认新密码', + confirmPasswordMessage: '请确认新密码', + confirmPasswordNonMatchMessage: '您输入的新密码不匹配!', + cancel: '取消', + addedModels: '添加了的模型', + modelsToBeAdded: '待添加的模型', + addTheModel: '添加模型', + apiKey: 'API-Key', + apiKeyMessage: '请输入 api key!', + apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。', + showMoreModels: '展示更多模型', + baseUrl: 'Base-Url', + baseUrlTip: + '如果您的 API 密钥来自 OpenAI,请忽略它。 任何其他中间提供商都会提供带有 API 密钥的基本 URL。', + modify: '修改', + systemModelSettings: '系统模型设置', + chatModel: '聊天模型', + chatModelTip: '所有新创建的知识库都会使用默认的聊天LLM。', + embeddingModel: '嵌入模型', + embeddingModelTip: '所有新创建的知识库都将使用的默认嵌入模型。', + img2txtModel: 'Img2txt模型', + img2txtModelTip: + '所有新创建的知识库都将使用默认的多模块模型。 它可以描述图片或视频。', + sequence2txtModel: 'Img2txt模型', + sequence2txtModelTip: + '所有新创建的知识库都将使用默认的 ASR 模型。 使用此模型将语音翻译为相应的文本。', + workspace: '工作空间', + upgrade: '升级', + }, + message: { + registered: '注册成功', + logout: '登出成功', + logged: '登录成功', + pleaseSelectChunk: '请选择解析块', + modified: '更新成功', + created: '创建成功', + deleted: '删除成功', + renamed: '重命名成功', + operated: '操作成功', + updated: '更新成功', + 200: '服务器成功返回请求的数据。', + 201: '新建或修改数据成功。', + 202: '一个请求已经进入后台排队(异步任务)。', + 204: '删除数据成功。', + 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', + 401: '用户没有权限(令牌、用户名、密码错误)。', + 403: '用户得到授权,但是访问是被禁止的。', + 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', + 406: '请求的格式不可得。', + 410: '请求的资源被永久删除,且不会再得到的。', + 422: '当创建一个对象时,发生一个验证错误。', + 500: '服务器发生错误,请检查服务器。', + 502: '网关错误。', + 503: '服务不可用,服务器暂时过载或维护。', + 504: '网关超时。', + requestError: '请求错误', + networkAnomalyDescription: '您的网络发生异常,无法连接服务器', + networkAnomaly: '网络异常', + hint: '提示', + }, + footer: { + profile: 'All rights reserved @ React', + }, + layout: { + file: 'file', + knowledge: 'knowledge', + chat: 'chat', + }, + }, +}; diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx index 15f84ac2d..93f2382ac 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-creating-modal/index.tsx @@ -3,6 +3,7 @@ import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; import { DeleteOutlined } from '@ant-design/icons'; import { Checkbox, Form, Input, Modal, Space } from 'antd'; import React, { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'umi'; import EditTag from '../edit-tag'; @@ -24,6 +25,7 @@ const ChunkCreatingModal: React.FC = ({ doc_id, chunkId }) => { const [keywords, setKeywords] = useState([]); const loading = useOneNamespaceEffectsLoading('chunkModel', ['create_chunk']); const { removeChunk } = useDeleteChunkByIds(); + const { t } = useTranslation(); const handleCancel = () => { dispatch({ @@ -87,7 +89,7 @@ const ChunkCreatingModal: React.FC = ({ doc_id, chunkId }) => { return ( = ({ doc_id, chunkId }) => { layout={'vertical'} > - label="Chunk" + label={t('chunk.chunk')} name="content" - rules={[{ required: true, message: 'Please input value!' }]} + rules={[{ required: true, message: t('chunk.chunkMessage') }]} >
    -

    Keyword*

    +

    {t('chunk.keyword')} *

    {chunkId && (
    -

    Function*

    +

    {t('chunk.function')} *

    - Enabled + {t('chunk.enabled')} - Delete + {t('common.delete')}
    diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx index fd02a1b10..ead23c48c 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx @@ -1,5 +1,6 @@ import { ReactComponent as FilterIcon } from '@/assets/filter.svg'; import { KnowledgeRouteKey } from '@/constants/knowledge'; +import { useTranslate } from '@/hooks/commonHooks'; import { useKnowledgeBaseId } from '@/hooks/knowledgeHook'; import { ArrowLeftOutlined, @@ -49,6 +50,7 @@ const ChunkToolBar = ({ const dispatch = useDispatch(); const knowledgeBaseId = useKnowledgeBaseId(); const [isShowSearchBox, setIsShowSearchBox] = useState(false); + const { t } = useTranslate('chunk'); const handleSelectAllCheck = useCallback( (e: any) => { @@ -95,7 +97,7 @@ const ChunkToolBar = ({ label: ( <> - Select All + {t('selectAll')} ), @@ -106,7 +108,7 @@ const ChunkToolBar = ({ label: ( - Enabled Selected + {t('enabledSelected')} ), }, @@ -115,7 +117,7 @@ const ChunkToolBar = ({ label: ( - Disabled Selected + {t('disabledSelected')} ), }, @@ -125,7 +127,7 @@ const ChunkToolBar = ({ label: ( - Delete Selected + {t('deleteSelected')} ), }, @@ -136,6 +138,7 @@ const ChunkToolBar = ({ handleDelete, handleEnabledClick, handleDisabledClick, + t, ]); const content = ( @@ -151,9 +154,9 @@ const ChunkToolBar = ({ const filterContent = ( - All - Enabled - Disabled + {t('all')} + {t('enabled')} + {t('disabled')} ); @@ -172,14 +175,14 @@ const ChunkToolBar = ({ {isShowSearchBox ? ( } allowClear onChange={handleSearchChange} diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx index d63e70e39..dc6029199 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx @@ -16,6 +16,7 @@ import { } from './hooks'; import { ChunkModelState } from './model'; +import { useTranslation } from 'react-i18next'; import styles from './index.less'; const Chunk = () => { @@ -33,6 +34,7 @@ const Chunk = () => { const documentInfo = useSelectDocumentInfo(); const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick(); const isPdf = documentInfo.type === 'pdf'; + const { t } = useTranslation(); const getChunkList = useFetchChunkList(); @@ -86,7 +88,7 @@ const Chunk = () => { [], ); const showSelectedChunkWarning = () => { - message.warning('Please select chunk!'); + message.warning(t('message.pleaseSelectChunk')); }; const handleRemoveChunk = useCallback(async () => { diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts b/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts index 5d9089a98..93187b15f 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/model.ts @@ -4,6 +4,7 @@ import kbService from '@/services/kbService'; import { message } from 'antd'; import { pick } from 'lodash'; // import { delay } from '@/utils/storeUtil'; +import i18n from '@/locales/config'; import { DvaModel } from 'umi'; export interface ChunkModelState extends BaseState { @@ -102,7 +103,7 @@ const model: DvaModel = { const { data } = yield call(kbService.switch_chunk, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified successfully !'); + message.success(i18n.t('message.modified')); } return retcode; }, diff --git a/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx b/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx index aaff97ccd..263457a75 100644 --- a/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx @@ -40,6 +40,7 @@ import classNames from 'classnames'; import { ReactElement, useCallback, useMemo, useRef, useState } from 'react'; import { Link, useNavigate } from 'umi'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; const { Dragger } = Upload; @@ -135,6 +136,7 @@ const KnowledgeUploadFile = () => { const documentList = useSelectDocumentList(); const runDocumentByIds = useRunDocument(); const uploadDocument = useUploadDocument(); + const { t } = useTranslate('knowledgeDetails'); const enabled = useMemo(() => { if (isUpload) { @@ -257,10 +259,10 @@ const KnowledgeUploadFile = () => {

    - Select files + {t('selectFiles')}

    - Change specific category + {t('changeSpecificCategory')}

    @@ -275,13 +277,8 @@ const KnowledgeUploadFile = () => { -

    - Click or drag file to this area to upload -

    -

    - Support for a single or bulk upload. Strictly prohibited from - uploading company data or other banned files. -

    +

    {t('uploadTitle')}

    +

    {t('uploadDescription')}

    @@ -292,7 +289,7 @@ const KnowledgeUploadFile = () => { disabled={!enabled} size="large" > - Next + {t('next', { keyPrefix: 'common' })}
    diff --git a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx index a520a90db..096b22de4 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx @@ -204,7 +204,9 @@ const KnowledgeFile = () => {

    {t('total', { keyPrefix: 'common' })}

    - {total} files + + {total} {t('files')} +
    = { const { data } = yield call(kbService.createKb, payload); const { retcode } = data; if (retcode === 0) { - message.success('Created!'); + message.success(i18n.t('message.created')); } }, *updateKf({ payload = {} }, { call }) { const { data } = yield call(kbService.updateKb, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); } }, *getKfDetail({ payload = {} }, { call }) { @@ -109,7 +110,7 @@ const model: DvaModel = { ); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); yield put({ type: 'getKfList', payload: { kb_id: payload.kb_id }, @@ -122,7 +123,7 @@ const model: DvaModel = { }); const { retcode } = data; if (retcode === 0) { - message.success('Deleted!'); + message.success(i18n.t('message.deleted')); yield put({ type: 'getKfList', payload: { kb_id: payload.kb_id }, @@ -137,7 +138,7 @@ const model: DvaModel = { ); const { retcode } = data; if (retcode === 0) { - message.success('rename success!'); + message.success(i18n.t('message.renamed')); yield put({ type: 'getKfList', @@ -156,7 +157,7 @@ const model: DvaModel = { payload: { kb_id: payload.kb_id }, }); - message.success('Created!'); + message.success(i18n.t('message.created')); } return retcode; }, @@ -173,7 +174,7 @@ const model: DvaModel = { payload: { kb_id: payload.knowledgeBaseId }, }); } - message.success('Operation successfully !'); + message.success(i18n.t('message.operated')); } return retcode; }, @@ -189,7 +190,7 @@ const model: DvaModel = { payload: { kb_id: payload.kb_id }, }); - message.success('Modified!'); + message.success(i18n.t('message.modified')); } return retcode; }, diff --git a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx index aa6c58659..d5ca83e88 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx @@ -4,6 +4,7 @@ import { useTranslate } from '@/hooks/commonHooks'; import { IKnowledgeFile } from '@/interfaces/database/knowledge'; import { CloseCircleOutlined } from '@ant-design/icons'; import { Badge, DescriptionsProps, Flex, Popover, Space, Tag } from 'antd'; +import { useTranslation } from 'react-i18next'; import reactStringReplace from 'react-string-replace'; import { useDispatch } from 'umi'; import { RunningStatus, RunningStatusMap } from '../constant'; @@ -80,11 +81,14 @@ export const ParsingStatusCell = ({ record }: IProps) => { const dispatch = useDispatch(); const text = record.run; const runningStatus = RunningStatusMap[text]; + const { t } = useTranslation(); const isRunning = isParserRunning(text); const OperationIcon = iconMap[text]; + const label = t(`knowledgeDetails.runningStatus${text}`); + const handleOperationIconClick = () => { dispatch({ type: 'kFModel/document_run', @@ -103,11 +107,11 @@ export const ParsingStatusCell = ({ record }: IProps) => { {isRunning ? ( - {runningStatus.label} + {label} {(record.progress * 100).toFixed(2)}% ) : ( - runningStatus.label + label )} diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx index 8b6168e81..d7f6a6c51 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx @@ -1,25 +1,27 @@ import SvgIcon from '@/components/svg-icon'; +import { useTranslate } from '@/hooks/commonHooks'; import { useSelectParserList } from '@/hooks/userSettingHook'; import { Col, Divider, Empty, Row, Typography } from 'antd'; import { useMemo } from 'react'; import styles from './index.less'; -import { ImageMap, TextMap } from './utils'; +import { ImageMap } from './utils'; const { Title, Text } = Typography; const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => { const parserList = useSelectParserList(); + const { t } = useTranslate('knowledgeConfiguration'); const item = useMemo(() => { const item = parserList.find((x) => x.value === chunkMethod); if (item) { return { title: item.label, - description: TextMap[item.value as keyof typeof TextMap]?.description, + description: t(item.value), }; } return { title: '', description: '' }; - }, [parserList, chunkMethod]); + }, [parserList, chunkMethod, t]); const imageList = useMemo(() => { if (chunkMethod in ImageMap) { @@ -33,18 +35,17 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => { {imageList.length > 0 ? ( <> - "{item.title}" Chunking Method Description + "{item.title}" {t('methodTitle')}

    - "{item.title}" Examples - - This visual guides is in order to make understanding easier - for you. - + + "{item.title}" {t('methodExamples')} + + {t('methodExamplesDescription')} {imageList.map((x) => ( @@ -56,15 +57,14 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => { ))} - {item.title} Dialogue Examples + + {item.title} {t('dialogueExamplesTitle')} + ) : ( -

    - This will display a visual explanation of the knowledge base - categories -

    +

    {t('methodEmpty')}

    )} diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx index b55164c17..e50156ebf 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx @@ -59,7 +59,7 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => { @@ -70,7 +70,7 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => { @@ -82,7 +82,7 @@ const ConfigurationForm = ({ form }: { form: FormInstance }) => { diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts index a24aea8f4..a2eb9e118 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts @@ -1,4 +1,5 @@ import { IKnowledge } from '@/interfaces/database/knowledge'; +import i18n from '@/locales/config'; import kbService from '@/services/kbService'; import { message } from 'antd'; import { DvaModel } from 'umi'; @@ -32,7 +33,7 @@ const model: DvaModel = { const { data } = yield call(kbService.createKb, payload); const { retcode } = data; if (retcode === 0) { - message.success('Created!'); + message.success(i18n.t('message.created')); } return data; }, @@ -41,7 +42,7 @@ const model: DvaModel = { const { retcode } = data; if (retcode === 0) { yield put({ type: 'getKbDetail', payload: { kb_id: payload.kb_id } }); - message.success('Updated!'); + message.success(i18n.t('message.updated')); } }, *getKbDetail({ payload = {} }, { call, put }) { diff --git a/web/src/pages/add-knowledge/index.tsx b/web/src/pages/add-knowledge/index.tsx index abe0e2e2b..68df60399 100644 --- a/web/src/pages/add-knowledge/index.tsx +++ b/web/src/pages/add-knowledge/index.tsx @@ -14,7 +14,6 @@ import { KnowledgeDatasetRouteKey, KnowledgeRouteKey, datasetRouteMap, - routeMap, } from './constant'; import styles from './index.less'; @@ -49,7 +48,7 @@ const KnowledgeAdding = () => { {t(`knowledgeDetails.${activeKey}`)} ) : ( - routeMap[activeKey] + t(`knowledgeDetails.${activeKey}`) ), }, ]; diff --git a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx index bca69b661..4dfdfe6cb 100644 --- a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx +++ b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx @@ -4,6 +4,7 @@ import { Form, Input, Select, Upload } from 'antd'; import classNames from 'classnames'; import { ISegmentedContentProps } from '../interface'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; const AssistantSetting = ({ show }: ISegmentedContentProps) => { @@ -12,6 +13,7 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => { label: x.name, value: x.id, })); + const { t } = useTranslate('chat'); const normFile = (e: any) => { if (Array.isArray(e)) { @@ -28,14 +30,14 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => { > @@ -46,44 +48,45 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => { > { diff --git a/web/src/pages/chat/chat-configuration-modal/index.tsx b/web/src/pages/chat/chat-configuration-modal/index.tsx index 91281e56c..8ddec396f 100644 --- a/web/src/pages/chat/chat-configuration-modal/index.tsx +++ b/web/src/pages/chat/chat-configuration-modal/index.tsx @@ -7,6 +7,7 @@ import { import { IDialog } from '@/interfaces/database/chat'; import { Divider, Flex, Form, Modal, Segmented, UploadFile } from 'antd'; import { SegmentedValue } from 'antd/es/segmented'; +import camelCase from 'lodash/camelCase'; import omit from 'lodash/omit'; import { useEffect, useRef, useState } from 'react'; import { variableEnabledFieldMap } from '../constants'; @@ -17,20 +18,9 @@ import { useFetchModelId } from './hooks'; import ModelSetting from './model-setting'; import PromptEngine from './prompt-engine'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; -enum ConfigurationSegmented { - AssistantSetting = 'Assistant Setting', - PromptEngine = 'Prompt Engine', - ModelSetting = 'Model Setting', -} - -const segmentedMap = { - [ConfigurationSegmented.AssistantSetting]: AssistantSetting, - [ConfigurationSegmented.ModelSetting]: ModelSetting, - [ConfigurationSegmented.PromptEngine]: PromptEngine, -}; - const layout = { labelCol: { span: 7 }, wrapperCol: { span: 17 }, @@ -47,6 +37,18 @@ const validateMessages = { }, }; +enum ConfigurationSegmented { + AssistantSetting = 'Assistant Setting', + PromptEngine = 'Prompt Engine', + ModelSetting = 'Model Setting', +} + +const segmentedMap = { + [ConfigurationSegmented.AssistantSetting]: AssistantSetting, + [ConfigurationSegmented.ModelSetting]: ModelSetting, + [ConfigurationSegmented.PromptEngine]: PromptEngine, +}; + interface IProps extends IModalManagerChildrenProps { initialDialog: IDialog; loading: boolean; @@ -63,11 +65,13 @@ const ChatConfigurationModal = ({ clearDialog, }: IProps) => { const [form] = Form.useForm(); + const [value, setValue] = useState( ConfigurationSegmented.AssistantSetting, ); const promptEngineRef = useRef>([]); const modelId = useFetchModelId(visible); + const { t } = useTranslate('chat'); const handleOk = async () => { const values = await form.validateFields(); @@ -115,10 +119,9 @@ const ChatConfigurationModal = ({
    - Chat Configuration + {t('chatConfiguration')}
    - Here, dress up a dedicated assistant for your special knowledge bases! - 💕 + {t('chatConfigurationDescription')}
    @@ -158,7 +161,10 @@ const ChatConfigurationModal = ({ size={'large'} value={value} onChange={handleSegmentedChange} - options={Object.values(ConfigurationSegmented)} + options={Object.values(ConfigurationSegmented).map((x) => ({ + label: t(camelCase(x)), + value: x, + }))} block /> diff --git a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx index 87390ab2b..6f2ec7745 100644 --- a/web/src/pages/chat/chat-configuration-modal/model-setting.tsx +++ b/web/src/pages/chat/chat-configuration-modal/model-setting.tsx @@ -5,16 +5,19 @@ import { } from '@/constants/knowledge'; import { Divider, Flex, Form, InputNumber, Select, Slider, Switch } from 'antd'; import classNames from 'classnames'; +import camelCase from 'lodash/camelCase'; import { useEffect } from 'react'; import { ISegmentedContentProps } from '../interface'; +import { useTranslate } from '@/hooks/commonHooks'; import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks'; import { variableEnabledFieldMap } from '../constants'; import styles from './index.less'; const ModelSetting = ({ show, form }: ISegmentedContentProps) => { + const { t } = useTranslate('chat'); const parameterOptions = Object.values(ModelVariableType).map((x) => ({ - label: x, + label: t(camelCase(x)), value: x, })); @@ -44,18 +47,18 @@ const ModelSetting = ({ show, form }: ISegmentedContentProps) => { })} > { loading={sendLoading} disabled={disabled} > - Send + {t('send')} } onPressEnter={handlePressEnter} diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx index 183914089..ba18215e7 100644 --- a/web/src/pages/chat/index.tsx +++ b/web/src/pages/chat/index.tsx @@ -35,6 +35,7 @@ import { useSelectFirstDialogOnMount, } from './hooks'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; const Chat = () => { @@ -71,6 +72,7 @@ const Chat = () => { } = useEditDialog(); const dialogLoading = useSelectDialogListLoading(); const conversationLoading = useSelectConversationListLoading(); + const { t } = useTranslate('chat'); useFetchDialogOnMount(dialogId, true); @@ -132,7 +134,8 @@ const Chat = () => { onClick: handleCreateTemporaryConversation, label: ( - New chat + + {t('newChat')} ), }, @@ -146,7 +149,7 @@ const Chat = () => { label: ( - Edit + {t('edit', { keyPrefix: 'common' })} ), }, @@ -157,7 +160,7 @@ const Chat = () => { label: ( - Delete chat + {t('delete', { keyPrefix: 'common' })} ), }, @@ -250,7 +253,7 @@ const Chat = () => { className={styles.chatTitle} > - Chat + {t('chat')} {conversationList.length} diff --git a/web/src/pages/chat/model.ts b/web/src/pages/chat/model.ts index 22bf7b48e..1e94be091 100644 --- a/web/src/pages/chat/model.ts +++ b/web/src/pages/chat/model.ts @@ -1,4 +1,5 @@ import { IConversation, IDialog, Message } from '@/interfaces/database/chat'; +import i18n from '@/locales/config'; import chatService from '@/services/chatService'; import { message } from 'antd'; import { DvaModel } from 'umi'; @@ -77,7 +78,9 @@ const model: DvaModel = { const { data } = yield call(chatService.setDialog, payload); if (data.retcode === 0) { yield put({ type: 'listDialog' }); - message.success(payload.dialog_id ? 'Modified!' : 'Created!'); + message.success( + i18n.t(`message.${payload.dialog_id ? 'modified' : 'created'}`), + ); } return data.retcode; }, @@ -85,7 +88,7 @@ const model: DvaModel = { const { data } = yield call(chatService.removeDialog, payload); if (data.retcode === 0) { yield put({ type: 'listDialog' }); - message.success('Deleted successfully !'); + message.success(i18n.t('message.deleted')); } return data.retcode; }, @@ -152,7 +155,7 @@ const model: DvaModel = { type: 'listConversation', payload: { dialog_id: payload.dialog_id }, }); - message.success('Deleted successfully !'); + message.success(i18n.t('message.deleted')); } return data.retcode; }, diff --git a/web/src/pages/login/index.tsx b/web/src/pages/login/index.tsx index 1de7e933b..98700dc9a 100644 --- a/web/src/pages/login/index.tsx +++ b/web/src/pages/login/index.tsx @@ -75,7 +75,7 @@ const Login = () => {
    -
    {title === 'login' ? t('login') : 'Create an account'}
    +
    {title === 'login' ? t('login') : t('register')}
    {title === 'login' ? t('loginDescription') diff --git a/web/src/pages/login/model.ts b/web/src/pages/login/model.ts index eaa92fb26..38865b190 100644 --- a/web/src/pages/login/model.ts +++ b/web/src/pages/login/model.ts @@ -1,4 +1,5 @@ import { Authorization } from '@/constants/authorization'; +import i18n from '@/locales/config'; import userService from '@/services/userService'; import authorizationUtil from '@/utils/authorizationUtil'; import { message } from 'antd'; @@ -31,7 +32,7 @@ const model: DvaModel = { const { retcode, data: res } = data; const authorization = response.headers.get(Authorization); if (retcode === 0) { - message.success('logged!'); + message.success(i18n.t('message.logged')); const token = res.access_token; const userInfo = { avatar: res.avatar, @@ -51,7 +52,7 @@ const model: DvaModel = { console.log(); const { retcode } = data; if (retcode === 0) { - message.success('Registered!'); + message.success(i18n.t('message.registered')); } return retcode; }, @@ -59,7 +60,7 @@ const model: DvaModel = { const { data } = yield call(userService.logout, payload); const { retcode } = data; if (retcode === 0) { - message.success('logout'); + message.success(i18n.t('message.logout')); } return retcode; }, diff --git a/web/src/pages/login/right-panel.tsx b/web/src/pages/login/right-panel.tsx index f40725670..964351325 100644 --- a/web/src/pages/login/right-panel.tsx +++ b/web/src/pages/login/right-panel.tsx @@ -3,11 +3,13 @@ import SvgIcon from '@/components/svg-icon'; import { Flex, Rate, Space, Typography } from 'antd'; import classNames from 'classnames'; +import { useTranslate } from '@/hooks/commonHooks'; import styles from './index.less'; const { Title, Text } = Typography; const LoginRightPanel = () => { + const { t } = useTranslate('login'); return (
    @@ -16,11 +18,10 @@ const LoginRightPanel = () => { level={1} className={classNames(styles.white, styles.loginTitle)} > - Start building your smart assisstants. + {t('title')} - Sign up for free to explore top RAG technology. Create knowledge bases - and AIs to empower your business. + {t('description')} @@ -34,7 +35,7 @@ const LoginRightPanel = () => { - from 500+ reviews + {t('review')} diff --git a/web/src/pages/user-setting/components/setting-title/index.tsx b/web/src/pages/user-setting/components/setting-title/index.tsx index 8ad153f3a..f5f8a7954 100644 --- a/web/src/pages/user-setting/components/setting-title/index.tsx +++ b/web/src/pages/user-setting/components/setting-title/index.tsx @@ -1,3 +1,4 @@ +import { useTranslate } from '@/hooks/commonHooks'; import { SettingOutlined } from '@ant-design/icons'; import { Button, Flex, Typography } from 'antd'; @@ -16,6 +17,8 @@ const SettingTitle = ({ clickButton, showRightButton = false, }: IProps) => { + const { t } = useTranslate('setting'); + return (
    @@ -24,7 +27,7 @@ const SettingTitle = ({
    {showRightButton && ( )}
    diff --git a/web/src/pages/user-setting/model.ts b/web/src/pages/user-setting/model.ts index 0caa12f33..5fffd8f4c 100644 --- a/web/src/pages/user-setting/model.ts +++ b/web/src/pages/user-setting/model.ts @@ -5,6 +5,7 @@ import { IThirdOAIModelCollection as IThirdAiModelCollection, } from '@/interfaces/database/llm'; import { IUserInfo } from '@/interfaces/database/userSetting'; +import i18n from '@/locales/config'; import userService from '@/services/userService'; import { message } from 'antd'; import { DvaModel } from 'umi'; @@ -47,7 +48,8 @@ const model: DvaModel = { const { data } = yield call(userService.setting, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); + yield put({ type: 'getUserInfo', }); @@ -89,7 +91,8 @@ const model: DvaModel = { const { data } = yield call(userService.set_tenant_info, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); + yield put({ type: 'getTenantInfo', }); @@ -137,7 +140,8 @@ const model: DvaModel = { const { data } = yield call(userService.set_api_key, payload); const { retcode } = data; if (retcode === 0) { - message.success('Modified!'); + message.success(i18n.t('message.modified')); + yield put({ type: 'my_llm' }); yield put({ type: 'factories_list' }); yield put({ diff --git a/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx b/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx index e4270e423..7a4850797 100644 --- a/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx @@ -1,4 +1,5 @@ import { IModalManagerChildrenProps } from '@/components/modal-manager'; +import { useTranslate } from '@/hooks/commonHooks'; import { Form, Input, Modal } from 'antd'; import { useEffect } from 'react'; @@ -24,6 +25,7 @@ const ApiKeyModal = ({ onOk, }: IProps) => { const [form] = Form.useForm(); + const { t } = useTranslate('setting'); const handleOk = async () => { const ret = await form.validateFields(); @@ -49,7 +51,7 @@ const ApiKeyModal = ({ return ( - label="Api-Key" + label={t('apiKey')} name="api_key" - tooltip="The API key can be obtained by registering the corresponding LLM supplier." - rules={[{ required: true, message: 'Please input api key!' }]} + tooltip={t('apiKeyTip')} + rules={[{ required: true, message: t('apiKeyMessage') }]} > {llmFactory === 'OpenAI' && ( - label="Base-Url" + label={t('baseUrl')} name="base_url" - tooltip="If your API key is from OpenAI, just ignore it. Any other intermediate providers will give this base url with the API key." + tooltip={t('baseUrlTip')} > diff --git a/web/src/pages/user-setting/setting-model/index.tsx b/web/src/pages/user-setting/setting-model/index.tsx index 4c8d2e8fe..03dd050d7 100644 --- a/web/src/pages/user-setting/setting-model/index.tsx +++ b/web/src/pages/user-setting/setting-model/index.tsx @@ -1,5 +1,5 @@ import { ReactComponent as MoreModelIcon } from '@/assets/svg/more-model.svg'; -import { useSetModalState } from '@/hooks/commonHooks'; +import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; import { LlmItem, useFetchLlmFactoryListOnMount, @@ -64,6 +64,7 @@ interface IModelCardProps { const ModelCard = ({ item, clickApiKey }: IModelCardProps) => { const { visible, switchVisible } = useSetModalState(); + const { t } = useTranslate('setting'); const handleApiKeyClick = () => { clickApiKey(item.name); @@ -94,7 +95,7 @@ const ModelCard = ({ item, clickApiKey }: IModelCardProps) => { @@ -140,6 +141,7 @@ const UserSettingModel = () => { hideSystemSettingModal, showSystemSettingModal, } = useSubmitSystemModelSetting(); + const { t } = useTranslate('setting'); const handleApiKeyClick = useCallback( (llmFactory: string) => { @@ -155,7 +157,7 @@ const UserSettingModel = () => { const items: CollapseProps['items'] = [ { key: '1', - label: 'Added models', + label: t('addedModels'), children: ( { }, { key: '2', - label: 'Models to be added', + label: t('modelsToBeAdded'), children: ( { @@ -208,8 +210,8 @@ const UserSettingModel = () => {
    diff --git a/web/src/pages/user-setting/setting-model/system-model-setting-modal/index.tsx b/web/src/pages/user-setting/setting-model/system-model-setting-modal/index.tsx index 1deb482e6..53ee20631 100644 --- a/web/src/pages/user-setting/setting-model/system-model-setting-modal/index.tsx +++ b/web/src/pages/user-setting/setting-model/system-model-setting-modal/index.tsx @@ -1,5 +1,6 @@ import { IModalManagerChildrenProps } from '@/components/modal-manager'; import { LlmModelType } from '@/constants/knowledge'; +import { useTranslate } from '@/hooks/commonHooks'; import { ISystemModelSettingSavingParams } from '@/hooks/llmHooks'; import { Form, Modal, Select } from 'antd'; import { useEffect } from 'react'; @@ -21,6 +22,7 @@ const SystemModelSettingModal = ({ const [form] = Form.useForm(); const { systemSetting: initialValues, allOptions } = useFetchSystemModelSettingOnMount(visible); + const { t } = useTranslate('setting'); const handleOk = async () => { const values = await form.validateFields(); @@ -35,7 +37,7 @@ const SystemModelSettingModal = ({ return (
    - - + diff --git a/web/src/pages/user-setting/setting-password/index.tsx b/web/src/pages/user-setting/setting-password/index.tsx index 919af726b..66e744ab1 100644 --- a/web/src/pages/user-setting/setting-password/index.tsx +++ b/web/src/pages/user-setting/setting-password/index.tsx @@ -5,6 +5,7 @@ import { Button, Divider, Form, Input, Space } from 'antd'; import SettingTitle from '../components/setting-title'; import { useValidateSubmittable } from '../hooks'; +import { useTranslate } from '@/hooks/commonHooks'; import parentStyles from '../index.less'; import styles from './index.less'; @@ -22,6 +23,7 @@ const UserSettingPassword = () => { const loading = useOneNamespaceEffectsLoading('settingModel', ['setting']); const { form, submittable } = useValidateSubmittable(); const saveSetting = useSaveSetting(); + const { t } = useTranslate('setting'); const onFinish = (values: any) => { const password = rsaPsw(values.password) as string; @@ -37,8 +39,8 @@ const UserSettingPassword = () => { return (
    { // requiredMark={'optional'} > - label="Current password" + label={t('currentPassword')} name="password" rules={[ { required: true, - message: 'Please input your password!', + message: t('currentPasswordMessage'), whitespace: true, }, ]} @@ -69,14 +71,14 @@ const UserSettingPassword = () => { - + noStyle name="new_password" rules={[ { required: true, - message: 'Please input your password!', + message: t('newPasswordMessage'), whitespace: true, }, ]} @@ -84,18 +86,18 @@ const UserSettingPassword = () => {

    - Your new password must be more than 8 characters. + {t('newPasswordDescription')}

    - label="Confirm new password" + label={t('confirmPassword')} name="confirm_password" dependencies={['new_password']} rules={[ { required: true, - message: 'Please confirm your password!', + message: t('confirmPasswordMessage'), whitespace: true, }, ({ getFieldValue }) => ({ @@ -104,7 +106,7 @@ const UserSettingPassword = () => { return Promise.resolve(); } return Promise.reject( - new Error('The new password that you entered do not match!'), + new Error(t('confirmPasswordNonMatchMessage')), ); }, }), @@ -120,14 +122,14 @@ const UserSettingPassword = () => { } > - + diff --git a/web/src/pages/user-setting/setting-profile/index.tsx b/web/src/pages/user-setting/setting-profile/index.tsx index 952dc7b16..627af9379 100644 --- a/web/src/pages/user-setting/setting-profile/index.tsx +++ b/web/src/pages/user-setting/setting-profile/index.tsx @@ -29,6 +29,8 @@ import { useValidateSubmittable, } from '../hooks'; +import { useTranslate } from '@/hooks/commonHooks'; +import { useChangeLanguage } from '@/hooks/logicHooks'; import parentStyles from '../index.less'; import styles from './index.less'; @@ -54,6 +56,8 @@ const UserSettingProfile = () => { const { form, submittable } = useValidateSubmittable(); const loading = useSelectUserInfoLoading(); useFetchUserInfo(); + const { t } = useTranslate('setting'); + const changeLanguage = useChangeLanguage(); const onFinish = async (values: any) => { const avatar = await getBase64FromUploadFileList(values.avatar); @@ -72,8 +76,8 @@ const UserSettingProfile = () => { return (
    @@ -91,12 +95,12 @@ const UserSettingProfile = () => { autoComplete="off" > - label="Username" + label={t('username')} name="nickname" rules={[ { required: true, - message: 'Please input your username!', + message: t('usernameMessage'), whitespace: true, }, ]} @@ -107,8 +111,8 @@ const UserSettingProfile = () => { label={
    - Your photo -
    This will be displayed on your profile.
    + {t('photo')} +
    {t('photoDescription')}
    } name="avatar" @@ -126,41 +130,53 @@ const UserSettingProfile = () => { > - label="Color schema" + label={t('colorSchema')} name="color_schema" + rules={[{ required: true, message: t('colorSchemaMessage') }]} + > + + + + + label={t('language', { keyPrefix: 'common' })} + name="language" rules={[ - { required: true, message: 'Please select your color schema!' }, + { + required: true, + message: t('languageMessage', { keyPrefix: 'common' }), + }, ]} > - + + - label="Language" - name="language" - rules={[{ required: true, message: 'Please input your language!' }]} - > - - - - - label="Timezone" + label={t('timezone')} name="timezone" - rules={[{ required: true, message: 'Please input your timezone!' }]} + rules={[{ required: true, message: t('timezoneMessage') }]} > - {TimezoneList.map((x) => (