Perf: i18n ns (#1441)

* i18n

* fix: handle
This commit is contained in:
Archer
2024-05-10 18:41:41 +08:00
committed by GitHub
parent f351d4ea68
commit 26f4c92124
27 changed files with 1705 additions and 1595 deletions

41
.vscode/i18n-ally-custom-framework.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
# .vscode/i18n-ally-custom-framework.yml
# An array of strings which contain Language Ids defined by VS Code
# You can check available language ids here: https://code.visualstudio.com/docs/languages/identifiers
languageIds:
- javascript
- typescript
- javascriptreact
- typescriptreact
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
# You should unescape RegEx strings in order to fit in the YAML file
# To help with this, you can use https://www.freeformatter.com/json-escape.html
usageMatchRegex:
# The following example shows how to detect `t("your.i18n.keys")`
# the `{key}` will be placed by a proper keypath matching regex,
# you can ignore it and use your own matching rules as well
- "[^\\w\\d]t\\(['\"`]({key})['\"`]"
- "[^\\w\\d]commonT\\(['\"`]({key})['\"`]"
# 支持 appT("your.i18n.keys")
- "[^\\w\\d]appT\\(['\"`]({key})['\"`]"
# 支持 datasetT("your.i18n.keys")
- "[^\\w\\d]datasetT\\(['\"`]({key})['\"`]"
# A RegEx to set a custom scope range. This scope will be used as a prefix when detecting keys
# and works like how the i18next framework identifies the namespace scope from the
# useTranslation() hook.
# You should unescape RegEx strings in order to fit in the YAML file
# To help with this, you can use https://www.freeformatter.com/json-escape.html
scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]"
# An array of strings containing refactor templates.
# The "$1" will be replaced by the keypath specified.
# Optional: uncomment the following two lines to use
# refactorTemplates:
# - i18n.get("$1")
# If set to true, only enables this custom framework (will disable all built-in frameworks)
monopoly: true

View File

@@ -28,6 +28,7 @@
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"pdfjs-dist": "4.0.269", "pdfjs-dist": "4.0.269",
"react": "18.2.0", "react": "18.2.0",
"use-context-selector": "^1.4.4",
"react-day-picker": "^8.7.1", "react-day-picker": "^8.7.1",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-i18next": "13.5.0", "react-i18next": "13.5.0",

3
pnpm-lock.yaml generated
View File

@@ -301,6 +301,9 @@ importers:
react-i18next: react-i18next:
specifier: 13.5.0 specifier: 13.5.0
version: 13.5.0(i18next@23.10.0)(react-dom@18.2.0)(react@18.2.0) version: 13.5.0(i18next@23.10.0)(react-dom@18.2.0)(react@18.2.0)
use-context-selector:
specifier: ^1.4.4
version: 1.4.4(react-dom@18.2.0)(react@18.2.0)(scheduler@0.23.0)
devDependencies: devDependencies:
'@types/lodash': '@types/lodash':
specifier: ^4.14.191 specifier: ^4.14.191

View File

@@ -0,0 +1,45 @@
{
"AI Advanced Settings": "AI Advanced Settings",
"AI Settings": "AI Settings",
"Advance App TestTip": "Current app may be in advanced orchestration mode\nTo switch to【Simple Mode】please click the save button on the left",
"App Detail": "App Details",
"Apps Share": "Apps Share",
"Basic Settings": "Basic Settings",
"Chat Debug": "Chat Debug",
"Chat Logs Tips": "Logs will record online, shared and API (chatId required) conversation records for this app",
"Chat logs": "Chat Logs",
"Confirm Del App Tip": "Confirm to delete this app and all its chat records?",
"Connection is invalid": "Connection is invalid",
"Connection type is different": "Connection type is different",
"Copy Module Config": "Copy Config",
"Dataset Quote Template": "Knowledge Base QA Mode",
"Export Config Successful": "Config copied, please check for important data",
"Export Configs": "Export Configs",
"Feedback Count": "User Feedback",
"Import Configs": "Import Configs",
"Import Configs Failed": "Failed to import configs, please ensure configs are valid!",
"Input Field Settings": "Input Field Settings",
"Logs Empty": "No logs yet~",
"Logs Message Total": "Total Messages",
"Logs Source": "Source",
"Logs Time": "Time",
"Logs Title": "Title",
"Mark Count": "Marked Answer Count",
"My Apps": "My Apps",
"Output Field Settings": "Output Field Settings",
"Paste Config": "Paste Config",
"To Chat": "Go to Chat",
"To Settings": "View Details",
"Variable Key Repeat Tip": "Variable key is duplicate",
"module": {
"Combine Modules": "Combine Modules",
"Custom Title Tip": "This title will be displayed during the conversation",
"My Modules": "My Modules",
"No Modules": "No modules yet~",
"System Module": "System Module",
"type": "\"{{type}}\" type\n{{description}}"
},
"modules": {
"Title is required": "Module name cannot be empty"
}
}

File diff suppressed because it is too large Load Diff

View File

View File

@@ -0,0 +1,44 @@
{
"AI Settings": "AI 配置",
"Advance App TestTip": "当前应用可能为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键",
"App Detail": "应用详情",
"Apps Share": "应用分享",
"Basic Settings": "基本信息",
"Chat Debug": "调试预览",
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录",
"Chat logs": "对话日志",
"Confirm Del App Tip": "确认删除该应用及其所有聊天记录?",
"Connection is invalid": "连接无效",
"Connection type is different": "连接的类型不一致",
"Copy Module Config": "复制配置",
"Dataset Quote Template": "知识库问答模式",
"Export Config Successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
"Export Configs": "导出配置",
"Feedback Count": "用户反馈",
"Import Configs": "导入配置",
"Import Configs Failed": "导入配置失败,请确保配置正常!",
"Input Field Settings": "输入字段编辑",
"Logs Empty": "还没有日志噢~",
"Logs Message Total": "消息总数",
"Logs Source": "来源",
"Logs Time": "时间",
"Logs Title": "标题",
"Mark Count": "标注答案数量",
"My Apps": "我的应用",
"Output Field Settings": "输出字段编辑",
"Paste Config": "粘贴配置",
"To Chat": "前去对话",
"To Settings": "查看详情",
"Variable Key Repeat Tip": "变量 key 重复",
"module": {
"Combine Modules": "组合模块",
"Custom Title Tip": "该标题名字会展示在对话过程中",
"My Modules": "",
"No Modules": "还没有模块~",
"System Module": "系统模块",
"type": "\"{{type}}\"类型\n{{description}}"
},
"modules": {
"Title is required": "模块名不能为空"
}
}

View File

@@ -8,51 +8,8 @@
"Running": "运行中", "Running": "运行中",
"UnKnow": "未知", "UnKnow": "未知",
"Warning": "提示", "Warning": "提示",
"app": { "New Create": "新建",
"AI Advanced Settings": "AI 高级配置",
"AI Settings": "AI 配置",
"Advance App TestTip": "当前应用可能为高级编排模式\n如需切换为【简易模式】请点击左侧保存按键",
"App Detail": "应用详情",
"Apps Share": "应用分享",
"Basic Settings": "基本信息",
"Chat Debug": "调试预览",
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录",
"Chat logs": "对话日志",
"Confirm Del App Tip": "确认删除该应用及其所有聊天记录?",
"Connection is invalid": "连接无效",
"Connection type is different": "连接的类型不一致",
"Copy Module Config": "复制配置",
"Dataset Quote Template": "知识库问答模式",
"Export Config Successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
"Export Configs": "导出配置",
"Feedback Count": "用户反馈",
"Import Configs": "导入配置",
"Import Configs Failed": "导入配置失败,请确保配置正常!",
"Input Field Settings": "输入字段编辑",
"Logs Empty": "还没有日志噢~",
"Logs Message Total": "消息总数",
"Logs Source": "来源",
"Logs Time": "时间",
"Logs Title": "标题",
"Mark Count": "标注答案数量",
"My Apps": "我的应用",
"Output Field Settings": "输出字段编辑",
"Paste Config": "粘贴配置",
"To Chat": "前去对话",
"To Settings": "查看详情",
"Variable Key Repeat Tip": "变量 key 重复",
"module": {
"Combine Modules": "组合模块",
"Custom Title Tip": "该标题名字会展示在对话过程中",
"My Modules": "",
"No Modules": "还没有模块~",
"System Module": "系统模块",
"type": "\"{{type}}\"类型\n{{description}}"
},
"modules": {
"Title is required": "模块名不能为空"
}
},
"common": { "common": {
"Action": "操作", "Action": "操作",
"Add": "添加", "Add": "添加",
@@ -113,7 +70,6 @@
"Name": "名称", "Name": "名称",
"Name Can": "名称不能为空", "Name Can": "名称不能为空",
"Name is empty": "名称不能为空", "Name is empty": "名称不能为空",
"New Create": "新建",
"Next Step": "下一步", "Next Step": "下一步",
"No more data": "没有更多了~", "No more data": "没有更多了~",
"Not open": "未开启", "Not open": "未开启",
@@ -933,10 +889,10 @@
"params": "Params" "params": "Params"
}, },
"input": { "input": {
"Add Branch": "添加分支",
"Add Input": "添加入参", "Add Input": "添加入参",
"Input Number": "入参: {{length}}", "Input Number": "入参: {{length}}",
"add": "添加条件", "add": "添加条件",
"Add Branch": "添加分支",
"description": { "description": {
"Background": "你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。", "Background": "你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。",
"HTTP Dynamic Input": "接收前方节点的输出值作为变量这些变量可以被HTTP请求参数使用。", "HTTP Dynamic Input": "接收前方节点的输出值作为变量这些变量可以被HTTP请求参数使用。",

View File

View File

@@ -1,19 +1,18 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react'; import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context'; import { WorkflowContext } from '../context';
import { useI18n } from '@/web/context/I18n';
type Props = { type Props = {
onClose: () => void; onClose: () => void;
}; };
const ImportSettings = ({ onClose }: Props) => { const ImportSettings = ({ onClose }: Props) => {
const { t } = useTranslation(); const { appT } = useI18n();
const { toast } = useToast(); const { toast } = useToast();
const initData = useContextSelector(WorkflowContext, (v) => v.initData); const initData = useContextSelector(WorkflowContext, (v) => v.initData);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
@@ -23,11 +22,11 @@ const ImportSettings = ({ onClose }: Props) => {
w={'600px'} w={'600px'}
onClose={onClose} onClose={onClose}
iconSrc="/imgs/modal/params.svg" iconSrc="/imgs/modal/params.svg"
title={t('app.Import Configs')} title={appT('Import Configs')}
> >
<ModalBody> <ModalBody>
<Textarea <Textarea
placeholder={t('app.Paste Config') || 'app.Paste Config'} placeholder={appT('Paste Config')}
defaultValue={value} defaultValue={value}
rows={16} rows={16}
onChange={(e) => setValue(e.target.value)} onChange={(e) => setValue(e.target.value)}
@@ -46,7 +45,7 @@ const ImportSettings = ({ onClose }: Props) => {
onClose(); onClose();
} catch (error) { } catch (error) {
toast({ toast({
title: t('app.Import Configs Failed') title: appT('Import Configs Failed')
}); });
} }
}} }}

View File

@@ -28,6 +28,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context'; import { WorkflowContext } from '../context';
import { useCreation } from 'ahooks'; import { useCreation } from 'ahooks';
import { useI18n } from '@/web/context/I18n';
type ModuleTemplateListProps = { type ModuleTemplateListProps = {
isOpen: boolean; isOpen: boolean;
@@ -251,6 +252,8 @@ const RenderList = React.memo(function RenderList({
setCurrentParent setCurrentParent
}: RenderListProps) { }: RenderListProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();
const { x, y, zoom } = useViewport(); const { x, y, zoom } = useViewport();
const { setLoading } = useSystemStore(); const { setLoading } = useSystemStore();
@@ -323,7 +326,7 @@ const RenderList = React.memo(function RenderList({
const Render = useMemo(() => { const Render = useMemo(() => {
return templates.length === 0 ? ( return templates.length === 0 ? (
<EmptyTip text={t('app.module.No Modules')} /> <EmptyTip text={appT('module.No Modules')} />
) : ( ) : (
<Box flex={'1 0 0'} overflow={'overlay'} px={'20px'}> <Box flex={'1 0 0'} overflow={'overlay'} px={'20px'}>
<Box mx={'auto'}> <Box mx={'auto'}>

View File

@@ -203,16 +203,16 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
if (connectingEdge?.handleId && !connectingEdge.handleId?.includes('source')) return false; if (connectingEdge?.handleId && !connectingEdge.handleId?.includes('source')) return false;
// From same source node // From same source node and same handle
if ( if (
connectedEdges.some( connectedEdges.some(
(item) => item.source === connectingEdge?.nodeId && item.target === nodeId (item) => item.sourceHandle === connectingEdge?.handleId && item.target === nodeId
) )
) )
return false; return false;
return true; return true;
}, [connectedEdges, connectingEdge?.handleId, connectingEdge?.nodeId, edges, node, nodeId]); }, [connectedEdges, connectingEdge?.handleId, edges, node, nodeId]);
const RenderHandle = useMemo(() => { const RenderHandle = useMemo(() => {
return ( return (

View File

@@ -22,6 +22,7 @@ import { storeNode2FlowNode } from '@/web/core/workflow/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context'; import { WorkflowContext } from '../../../context';
import { useI18n } from '@/web/context/I18n';
type Props = FlowNodeItemType & { type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string; children?: React.ReactNode | React.ReactNode[] | string;
@@ -38,6 +39,8 @@ type Props = FlowNodeItemType & {
const NodeCard = (props: Props) => { const NodeCard = (props: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const { toast } = useToast(); const { toast } = useToast();
const { const {
@@ -66,7 +69,7 @@ const NodeCard = (props: Props) => {
// custom title edit // custom title edit
const { onOpenModal: onOpenCustomTitleModal, EditModal: EditTitleModal } = useEditTitle({ const { onOpenModal: onOpenCustomTitleModal, EditModal: EditTitleModal } = useEditTitle({
title: t('common.Custom Title'), title: t('common.Custom Title'),
placeholder: t('app.module.Custom Title Tip') || '' placeholder: appT('module.Custom Title Tip') || ''
}); });
const showToolHandle = useMemo( const showToolHandle = useMemo(
@@ -105,7 +108,7 @@ const NodeCard = (props: Props) => {
onSuccess: (e) => { onSuccess: (e) => {
if (!e) { if (!e) {
return toast({ return toast({
title: t('app.modules.Title is required'), title: appT('modules.Title is required'),
status: 'warning' status: 'warning'
}); });
} }
@@ -132,8 +135,8 @@ const NodeCard = (props: Props) => {
</Box> </Box>
); );
}, [ }, [
nodeId,
showToolHandle, showToolHandle,
nodeId,
avatar, avatar,
t, t,
name, name,
@@ -143,7 +146,8 @@ const NodeCard = (props: Props) => {
intro, intro,
onOpenCustomTitleModal, onOpenCustomTitleModal,
onChangeNode, onChangeNode,
toast toast,
appT
]); ]);
return ( return (

View File

@@ -135,7 +135,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
}) })
} }
> >
{t('common.New Create')} {t('New Create')}
</Button> </Button>
</Box> </Box>
</Box> </Box>

View File

@@ -6,6 +6,7 @@ import { appWithTranslation } from 'next-i18next';
import QueryClientContext from '@/web/context/QueryClient'; import QueryClientContext from '@/web/context/QueryClient';
import ChakraUIContext from '@/web/context/ChakraUI'; import ChakraUIContext from '@/web/context/ChakraUI';
import I18nContextProvider from '@/web/context/I18n';
import { useInitApp } from '@/web/context/useInitApp'; import { useInitApp } from '@/web/context/useInitApp';
import '@/web/styles/reset.scss'; import '@/web/styles/reset.scss';
@@ -34,11 +35,13 @@ function App({ Component, pageProps }: AppProps) {
{scripts?.map((item, i) => <Script key={i} strategy="lazyOnload" {...item}></Script>)} {scripts?.map((item, i) => <Script key={i} strategy="lazyOnload" {...item}></Script>)}
<QueryClientContext> <QueryClientContext>
<ChakraUIContext> <I18nContextProvider>
<Layout> <ChakraUIContext>
<Component {...pageProps} /> <Layout>
</Layout> <Component {...pageProps} />
</ChakraUIContext> </Layout>
</ChakraUIContext>
</I18nContextProvider>
</QueryClientContext> </QueryClientContext>
</> </>
); );

View File

@@ -26,6 +26,7 @@ import { formatTime2HM } from '@fastgpt/global/common/string/time';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context'; import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context';
import { useInterval, useUpdateEffect } from 'ahooks'; import { useInterval, useUpdateEffect } from 'ahooks';
import { useI18n } from '@/web/context/I18n';
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings')); const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
const PublishHistories = dynamic( const PublishHistories = dynamic(
@@ -56,6 +57,8 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const theme = useTheme(); const theme = useTheme();
const { toast } = useToast(); const { toast } = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const { copyData } = useCopyData(); const { copyData } = useCopyData();
const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({ const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({
content: t('core.app.Publish Confirm') content: t('core.app.Publish Confirm')
@@ -177,10 +180,10 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
null, null,
2 2
), ),
t('app.Export Config Successful') appT('Export Config Successful')
); );
} }
}, [copyData, flowData2StoreDataAndCheck, t]); }, [appT, copyData, flowData2StoreDataAndCheck]);
// effect // effect
useBeforeunload({ useBeforeunload({
@@ -254,12 +257,12 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
} }
menuList={[ menuList={[
{ {
label: t('app.Import Configs'), label: appT('Import Configs'),
icon: 'common/importLight', icon: 'common/importLight',
onClick: onOpenImport onClick: onOpenImport
}, },
{ {
label: t('app.Export Configs'), label: appT('Export Configs'),
icon: 'export', icon: 'export',
onClick: onExportWorkflow onClick: onExportWorkflow
} }
@@ -316,6 +319,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
isV2Workflow, isV2Workflow,
t, t,
saveLabel, saveLabel,
appT,
onOpenImport, onOpenImport,
onExportWorkflow, onExportWorkflow,
openConfigPublish, openConfigPublish,

View File

@@ -33,9 +33,12 @@ import { usePagination } from '@fastgpt/web/hooks/usePagination';
import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker'; import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
import { formatChatValue2InputType } from '@/components/ChatBox/utils'; import { formatChatValue2InputType } from '@/components/ChatBox/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useI18n } from '@/web/context/I18n';
const Logs = ({ appId }: { appId: string }) => { const Logs = ({ appId }: { appId: string }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();
const [dateRange, setDateRange] = useState<DateRangeType>({ const [dateRange, setDateRange] = useState<DateRangeType>({
@@ -73,10 +76,10 @@ const Logs = ({ appId }: { appId: string }) => {
{isPc && ( {isPc && (
<> <>
<Box fontWeight={'bold'} fontSize={['md', 'xl']} mb={2}> <Box fontWeight={'bold'} fontSize={['md', 'xl']} mb={2}>
{t('app.Chat logs')} {appT('Chat logs')}
</Box> </Box>
<Box color={'myGray.500'} fontSize={'sm'}> <Box color={'myGray.500'} fontSize={'sm'}>
{t('app.Chat Logs Tips')},{' '} {appT('Chat Logs Tips')},{' '}
<Box <Box
as={'span'} as={'span'}
mr={2} mr={2}
@@ -97,11 +100,11 @@ const Logs = ({ appId }: { appId: string }) => {
<Thead> <Thead>
<Tr> <Tr>
<Th>{t('core.app.logs.Source And Time')}</Th> <Th>{t('core.app.logs.Source And Time')}</Th>
<Th>{t('app.Logs Title')}</Th> <Th>{appT('Logs Title')}</Th>
<Th>{t('app.Logs Message Total')}</Th> <Th>{appT('Logs Message Total')}</Th>
<Th>{t('app.Feedback Count')}</Th> <Th>{appT('Feedback Count')}</Th>
<Th>{t('core.app.feedback.Custom feedback')}</Th> <Th>{t('core.app.feedback.Custom feedback')}</Th>
<Th>{t('app.Mark Count')}</Th> <Th>{appT('Mark Count')}</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
@@ -176,7 +179,7 @@ const Logs = ({ appId }: { appId: string }) => {
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}> <Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} /> <MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}> <Box mt={2} color={'myGray.500'}>
{t('app.Logs Empty')} {appT('Logs Empty')}
</Box> </Box>
</Flex> </Flex>
)} )}

View File

@@ -15,11 +15,14 @@ import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import TagsEditModal from './TagsEditModal'; import TagsEditModal from './TagsEditModal';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useI18n } from '@/web/context/I18n';
const InfoModal = dynamic(() => import('../InfoModal')); const InfoModal = dynamic(() => import('../InfoModal'));
const AppCard = ({ appId }: { appId: string }) => { const AppCard = ({ appId }: { appId: string }) => {
const router = useRouter(); const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const { toast } = useToast(); const { toast } = useToast();
const { appDetail } = useAppStore(); const { appDetail } = useAppStore();
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
@@ -27,7 +30,7 @@ const AppCard = ({ appId }: { appId: string }) => {
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>(); const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();
const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({ const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({
content: t('app.Confirm Del App Tip'), content: appT('Confirm Del App Tip'),
type: 'delete' type: 'delete'
}); });

View File

@@ -21,6 +21,7 @@ import { UseFormReturn } from 'react-hook-form';
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { useAppStore } from '@/web/core/app/store/useAppStore'; import { useAppStore } from '@/web/core/app/store/useAppStore';
import { form2AppWorkflow } from '@/web/core/app/utils'; import { form2AppWorkflow } from '@/web/core/app/utils';
import { useI18n } from '@/web/context/I18n';
const ChatTest = ({ const ChatTest = ({
editForm, editForm,
@@ -30,6 +31,8 @@ const ChatTest = ({
appId: string; appId: string;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const ChatBoxRef = useRef<ComponentRef>(null); const ChatBoxRef = useRef<ComponentRef>(null);
const { appDetail } = useAppStore(); const { appDetail } = useAppStore();
@@ -114,7 +117,7 @@ const ChatTest = ({
> >
<Flex px={[2, 5]}> <Flex px={[2, 5]}>
<Box fontSize={['md', 'xl']} fontWeight={'bold'} flex={1}> <Box fontSize={['md', 'xl']} fontWeight={'bold'} flex={1}>
{t('app.Chat Debug')} {appT('Chat Debug')}
</Box> </Box>
<MyTooltip label={t('core.chat.Restart')}> <MyTooltip label={t('core.chat.Restart')}>
<IconButton <IconButton
@@ -160,7 +163,7 @@ const ChatTest = ({
whiteSpace={'pre-wrap'} whiteSpace={'pre-wrap'}
textAlign={'center'} textAlign={'center'}
> >
<Box>{t('app.Advance App TestTip')}</Box> <Box>{appT('Advance App TestTip')}</Box>
</Flex> </Flex>
)} )}
</Flex> </Flex>

View File

@@ -29,6 +29,7 @@ import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Ic
import { TTSTypeEnum } from '@/constants/app'; import { TTSTypeEnum } from '@/constants/app';
import { getSystemVariables } from '@/web/core/app/utils'; import { getSystemVariables } from '@/web/core/app/utils';
import { useUpdate } from 'ahooks'; import { useUpdate } from 'ahooks';
import { useI18n } from '@/web/context/I18n';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal')); const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
@@ -61,6 +62,8 @@ const EditForm = ({
const theme = useTheme(); const theme = useTheme();
const router = useRouter(); const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const { publishApp, appDetail } = useAppStore(); const { publishApp, appDetail } = useAppStore();
const { allDatasets } = useDatasetStore(); const { allDatasets } = useDatasetStore();
@@ -200,7 +203,7 @@ const EditForm = ({
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<MyIcon name={'core/app/simpleMode/ai'} w={'20px'} /> <MyIcon name={'core/app/simpleMode/ai'} w={'20px'} />
<Box ml={2} flex={1}> <Box ml={2} flex={1}>
{t('app.AI Settings')} {appT('AI Settings')}
</Box> </Box>
</Flex> </Flex>
<Flex alignItems={'center'} mt={5}> <Flex alignItems={'center'} mt={5}>

View File

@@ -17,6 +17,7 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { useAppStore } from '@/web/core/app/store/useAppStore'; import { useAppStore } from '@/web/core/app/store/useAppStore';
import Head from 'next/head'; import Head from 'next/head';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useI18n } from '@/web/context/I18n';
const FlowEdit = dynamic(() => import('./components/FlowEdit'), { const FlowEdit = dynamic(() => import('./components/FlowEdit'), {
loading: () => <Loading /> loading: () => <Loading />
@@ -34,6 +35,8 @@ enum TabEnum {
const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const router = useRouter(); const router = useRouter();
const theme = useTheme(); const theme = useTheme();
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
@@ -74,10 +77,10 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
id: TabEnum.publish, id: TabEnum.publish,
icon: 'support/outlink/shareLight' icon: 'support/outlink/shareLight'
}, },
{ label: t('app.Chat logs'), id: TabEnum.logs, icon: 'core/app/logsLight' }, { label: appT('Chat logs'), id: TabEnum.logs, icon: 'core/app/logsLight' },
{ label: t('core.Start chat'), id: TabEnum.startChat, icon: 'core/chat/chatLight' } { label: t('core.Start chat'), id: TabEnum.startChat, icon: 'core/chat/chatLight' }
], ],
[feConfigs?.hide_app_flow, t] [appT, feConfigs?.hide_app_flow, t]
); );
const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]); const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]);
@@ -150,7 +153,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
borderRadius={'50%'} borderRadius={'50%'}
aria-label={''} aria-label={''}
/> />
{t('app.My Apps')} {appT('My Apps')}
</Flex> </Flex>
</Box> </Box>
{/* phone tab */} {/* phone tab */}
@@ -193,7 +196,7 @@ export async function getServerSideProps(context: any) {
const currentTab = context?.query?.currentTab || TabEnum.simpleEdit; const currentTab = context?.query?.currentTab || TabEnum.simpleEdit;
return { return {
props: { currentTab, ...(await serviceSideProps(context)) } props: { currentTab, ...(await serviceSideProps(context, ['app'])) }
}; };
} }

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useState, useEffect } from 'react'; import React, { useCallback } from 'react';
import { Box, Grid, Flex, IconButton, Button, useDisclosure } from '@chakra-ui/react'; import { Box, Grid, Flex, IconButton, Button, useDisclosure } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
@@ -7,7 +7,6 @@ import { delModelById } from '@/web/core/app/api';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import PageContainer from '@/components/PageContainer'; import PageContainer from '@/components/PageContainer';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
@@ -16,10 +15,12 @@ import CreateModal from './component/CreateModal';
import { useAppStore } from '@/web/core/app/store/useAppStore'; import { useAppStore } from '@/web/core/app/store/useAppStore';
import PermissionIconText from '@/components/support/permission/IconText'; import PermissionIconText from '@/components/support/permission/IconText';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { useI18n } from '@/web/context/I18n';
const MyApps = () => { const MyApps = () => {
const { toast } = useToast(); const { toast } = useToast();
const { t } = useTranslation(); const { appT, commonT } = useI18n();
const router = useRouter(); const router = useRouter();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const { myApps, loadMyApps } = useAppStore(); const { myApps, loadMyApps } = useAppStore();
@@ -62,10 +63,10 @@ const MyApps = () => {
<PageContainer isLoading={isFetching} insertProps={{ px: [5, '48px'] }}> <PageContainer isLoading={isFetching} insertProps={{ px: [5, '48px'] }}>
<Flex pt={[4, '30px']} alignItems={'center'} justifyContent={'space-between'}> <Flex pt={[4, '30px']} alignItems={'center'} justifyContent={'space-between'}>
<Box letterSpacing={1} fontSize={['20px', '24px']} color={'myGray.900'}> <Box letterSpacing={1} fontSize={['20px', '24px']} color={'myGray.900'}>
{t('app.My Apps')} {appT('My Apps')}
</Box> </Box>
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}> <Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
{t('common.New Create')} {commonT('New Create')}
</Button> </Button>
</Flex> </Flex>
<Grid <Grid
@@ -76,7 +77,7 @@ const MyApps = () => {
{myApps.map((app) => ( {myApps.map((app) => (
<MyTooltip <MyTooltip
key={app._id} key={app._id}
label={userInfo?.team.canWrite ? t('app.To Settings') : t('app.To Chat')} label={userInfo?.team.canWrite ? appT('To Settings') : appT('To Chat')}
> >
<Box <Box
lineHeight={1.5} lineHeight={1.5}
@@ -168,9 +169,6 @@ const MyApps = () => {
</MyTooltip> </MyTooltip>
))} ))}
</Grid> </Grid>
{/* (
<ShareBox></ShareBox>
) */}
{myApps.length === 0 && ( {myApps.length === 0 && (
<Flex mt={'35vh'} flexDirection={'column'} alignItems={'center'}> <Flex mt={'35vh'} flexDirection={'column'} alignItems={'center'}>
@@ -191,7 +189,7 @@ const MyApps = () => {
export async function getServerSideProps(content: any) { export async function getServerSideProps(content: any) {
return { return {
props: { props: {
...(await serviceSideProps(content)) ...(await serviceSideProps(content, ['app']))
} }
}; };
} }

View File

@@ -23,6 +23,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import { AppListItemType } from '@fastgpt/global/core/app/type'; import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import { useI18n } from '@/web/context/I18n';
type HistoryItemType = { type HistoryItemType = {
id: string; id: string;
@@ -68,6 +69,8 @@ const ChatHistorySlider = ({
const theme = useTheme(); const theme = useTheme();
const router = useRouter(); const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
@@ -86,7 +89,8 @@ const ChatHistorySlider = ({
const concatHistory = useMemo<HistoryItemType[]>( const concatHistory = useMemo<HistoryItemType[]>(
() => () =>
!activeChatId !activeChatId
? [{ id: activeChatId, title: t('core.chat.New Chat') }].concat(history) ? //@ts-ignore
[{ id: activeChatId, title: t('core.chat.New Chat') }].concat(history)
: history, : history,
[activeChatId, history, t] [activeChatId, history, t]
); );
@@ -115,7 +119,7 @@ const ChatHistorySlider = ({
whiteSpace={'nowrap'} whiteSpace={'nowrap'}
> >
{isPc && ( {isPc && (
<MyTooltip label={canRouteToDetail ? t('app.App Detail') : ''} offset={[0, 0]}> <MyTooltip label={canRouteToDetail ? appT('App Detail') : ''} offset={[0, 0]}>
<Flex <Flex
pt={5} pt={5}
pb={2} pb={2}

View File

@@ -17,6 +17,7 @@ import {
} from '@/web/core/workflow/utils'; } from '@/web/core/workflow/utils';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context'; import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context';
import { useI18n } from '@/web/context/I18n';
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings')); const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
@@ -25,6 +26,8 @@ type Props = { plugin: PluginItemSchema; onClose: () => void };
const Header = ({ plugin, onClose }: Props) => { const Header = ({ plugin, onClose }: Props) => {
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const { appT } = useI18n();
const { toast } = useToast(); const { toast } = useToast();
const { copyData } = useCopyData(); const { copyData } = useCopyData();
const edges = useContextSelector(WorkflowContext, (v) => v.edges); const edges = useContextSelector(WorkflowContext, (v) => v.edges);
@@ -77,10 +80,10 @@ const Header = ({ plugin, onClose }: Props) => {
null, null,
2 2
), ),
t('app.Export Config Successful') appT('Export Config Successful')
); );
} }
}, [copyData, flowData2StoreDataAndCheck, t]); }, [appT, copyData, flowData2StoreDataAndCheck]);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (
@@ -118,9 +121,9 @@ const Header = ({ plugin, onClose }: Props) => {
/> />
} }
menuList={[ menuList={[
{ label: t('app.Import Configs'), icon: 'common/importLight', onClick: onOpenImport }, { label: appT('Import Configs'), icon: 'common/importLight', onClick: onOpenImport },
{ {
label: t('app.Export Configs'), label: appT('Export Configs'),
icon: 'export', icon: 'export',
onClick: onCopy onClick: onCopy
} }
@@ -139,6 +142,7 @@ const Header = ({ plugin, onClose }: Props) => {
</> </>
); );
}, [ }, [
appT,
isLoading, isLoading,
isOpenImport, isOpenImport,
onClose, onClose,

View File

@@ -1,14 +1,20 @@
import 'i18next'; import 'i18next';
//import common from '../../i18n/en/common.json'; import common from '../../i18n/en/common.json';
import dataset from '../../i18n/en/dataset.json';
import app from '../../i18n/en/app.json';
interface I18nNamespaces { export interface I18nNamespaces {
common: any; common: typeof common;
dataset: typeof dataset;
app: typeof app;
} }
export type I18nNsType = (keyof I18nNamespaces)[];
declare module 'i18next' { declare module 'i18next' {
interface CustomTypeOptions { interface CustomTypeOptions {
returnNull: false; returnNull: false;
defaultNs: 'common'; defaultNs: 'common';
// resources: I18nNamespaces; resources: I18nNamespaces;
} }
} }

View File

@@ -1,3 +1,4 @@
import { I18nNsType } from '@/types/i18n';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
export const LANG_KEY = 'NEXT_LOCALE_LANG'; export const LANG_KEY = 'NEXT_LOCALE_LANG';
@@ -16,8 +17,8 @@ export const langMap = {
} }
}; };
export const serviceSideProps = (content: any) => { export const serviceSideProps = (content: any, ns: I18nNsType = []) => {
return serverSideTranslations(content.locale, undefined, null, content.locales); return serverSideTranslations(content.locale, ['common', ...ns], null, content.locales);
}; };
export const getLng = (lng: string) => { export const getLng = (lng: string) => {

View File

@@ -0,0 +1,38 @@
import { createContext, useContextSelector } from 'use-context-selector';
import { useTranslation } from 'next-i18next';
import { TFunction } from 'i18next';
type I18nContextType = {
commonT: TFunction<['common'], undefined>;
appT: TFunction<['app'], undefined>;
datasetT: TFunction<['dataset'], undefined>;
};
export const I18nContext = createContext<I18nContextType>({
// @ts-ignore
commonT: undefined
});
const I18nContextProvider = ({ children }: { children: React.ReactNode }) => {
const { t: commonT } = useTranslation('common');
const { t: appT } = useTranslation('app');
const { t: datasetT } = useTranslation('dataset');
return (
<I18nContext.Provider
value={{
commonT,
appT,
datasetT
}}
>
{children}
</I18nContext.Provider>
);
};
export default I18nContextProvider;
export const useI18n = () => {
return useContextSelector(I18nContext, (ctx) => ctx);
};