Commit c614f8b9 by Archer Committed by GitHub

Perf: i18n change and captcha code. (#2625)

* perf: send captcha check

* perf: back router

* perf: i18n init

* perf: ui

* i18n

* perf: ui duration
parent 478386c6
......@@ -13,7 +13,7 @@
"js",
"ts"
],
"i18n-ally.keystyle": "nested",
"i18n-ally.keystyle": "flat",
"i18n-ally.sortKeys": true,
"i18n-ally.keepFulfilled": false,
"i18n-ally.sourceLanguage": "zh", // 根据此语言文件翻译其他语言文件的变量和内容
......
......@@ -56,32 +56,35 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4810' \
7. 新增 - 应用调用迁移成单独节点,同时可以传递全局变量和用户的文件。
8. 新增 - 插件增加使用说明配置。
9. 新增 - 工作流导出导入,支持直接导出和导入 JSON 文件,便于交流。
10. 商业版新增 - 飞书机器人接入
11. 商业版新增 - 公众号接入接入
12. 商业版新增 - 自助开票申请
13. 商业版新增 - SSO 定制
14. 优化 - 工作流循环校验,避免 skip 循环空转。同时支持分支完全并发执行。
15. 优化 - SSE 响应优化。
16. 优化 - 无 SSL 证书情况下,优化复制。
17. 优化 - 单选框打开后自动滚动到选中的位置。
18. 优化 - 知识库集合禁用,目录禁用会递归修改其下所有 children 的禁用状态。
19. 优化 - 节点选择,避免切换 tab 时候,path 加载报错。
20. 优化 - 最新 React Markdown 组件,支持 Base64 图片。
21. 优化 - 知识库列表 UI。
22. 优化 - 知识库详情页 UI。
23. 优化 - 支持无网络配置情况下运行。
24. 优化 - 部分全局变量,增加数据类型约束。
25. 优化 - 查看工作流详情,切换 tab 时,自动滚动到顶部。
26. 修复 - 全局变量 key 可能重复。
27. 修复 - Prompt 模式调用工具,stream=false 模式下,会携带 0: 开头标记。
28. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。
29. 修复 - 选择 Milvus 部署时,无法导出知识库。
30. 修复 - 创建 APP 副本,无法复制系统配置。
31. 修复 - 图片识别模式下,自动解析图片链接正则不够严谨问题。
32. 修复 - 内容提取的数据类型与输出数据类型未一致。
33. 修复 - 工作流运行时间统计错误。
34. 修复 - stream 模式下,工具调用有可能出现 undefined
35. 修复 - 全局变量在 API 中无法持久化。
36. 修复 - OpenAPI,detail=false模式下,不应该返回 tool 调用结果,仅返回文字。(可解决 cow 不适配问题)
37. 修复 - 知识库标签重复加载。
38. 修复 - Debug 模式下,循环调用边问题。
10. 新增 - HTTP模块支持超时配置、支持更多的 Body 类型,params 和 headers 支持新的变量选择模式。
11. 新增 - 发送验证码安全校验。
12. 商业版新增 - 飞书机器人接入
13. 商业版新增 - 公众号接入接入
14. 商业版新增 - 自助开票申请
15. 商业版新增 - SSO 定制
16. 优化 - 工作流循环校验,避免 skip 循环空转。同时支持分支完全并发执行。
17. 优化 - SSE 响应优化。
18. 优化 - 无 SSL 证书情况下,优化复制。
19. 优化 - 单选框打开后自动滚动到选中的位置。
20. 优化 - 知识库集合禁用,目录禁用会递归修改其下所有 children 的禁用状态。
21. 优化 - 节点选择,避免切换 tab 时候,path 加载报错。
22. 优化 - 最新 React Markdown 组件,支持 Base64 图片。
23. 优化 - 知识库列表 UI。
24. 优化 - 知识库详情页 UI。
25. 优化 - 支持无网络配置情况下运行。
26. 优化 - 部分全局变量,增加数据类型约束。
27. 优化 - 查看工作流详情,切换 tab 时,自动滚动到顶部。
28. 优化 - 对话框性能问题。
29. 修复 - 全局变量 key 可能重复。
30. 修复 - Prompt 模式调用工具,stream=false 模式下,会携带 0: 开头标记。
31. 修复 - 对话日志鉴权问题:仅为 APP 管理员的用户,无法查看对话日志详情。
32. 修复 - 选择 Milvus 部署时,无法导出知识库。
33. 修复 - 创建 APP 副本,无法复制系统配置。
34. 修复 - 图片识别模式下,自动解析图片链接正则不够严谨问题。
35. 修复 - 内容提取的数据类型与输出数据类型未一致。
36. 修复 - 工作流运行时间统计错误。
37. 修复 - stream 模式下,工具调用有可能出现 undefined
38. 修复 - 全局变量在 API 中无法持久化。
39. 修复 - OpenAPI,detail=false模式下,不应该返回 tool 调用结果,仅返回文字。(可解决 cow 不适配问题)
40. 修复 - 知识库标签重复加载。
41. 修复 - Debug 模式下,循环调用边问题。
......@@ -48,7 +48,7 @@ export class PgVectorCtrl {
const { teamId, datasetId, collectionId, vector, retry = 3 } = props;
try {
const { rows } = await PgClient.insert(DatasetVectorTableName, {
const { rowCount, rows } = await PgClient.insert(DatasetVectorTableName, {
values: [
[
{ key: 'vector', value: `[${vector}]` },
......@@ -58,6 +58,11 @@ export class PgVectorCtrl {
]
]
});
if (rowCount === 0) {
return Promise.reject('insertDatasetData: no insert');
}
return {
insertId: rows[0].id
};
......
import Cookies, { CookieAttributes } from 'js-cookie';
import { useTranslation } from 'next-i18next';
const setCookie = (key: string, value: string, options?: CookieAttributes) => {
Cookies.set(key, value, options);
};
const getCookie = (key: string) => {
return Cookies.get(key);
};
const LANG_KEY = 'NEXT_LOCALE';
export const useI18nLng = () => {
const { i18n } = useTranslation();
const onChangeLng = (lng: string) => {
setCookie(LANG_KEY, lng, {
expires: 30,
sameSite: 'None',
secure: true
});
i18n?.changeLanguage(lng);
};
const setUserDefaultLng = () => {
if (!navigator || !localStorage) return;
if (getCookie(LANG_KEY)) return onChangeLng(getCookie(LANG_KEY) as string);
const languageMap = {
zh: 'zh',
'zh-CN': 'zh'
};
// @ts-ignore
const lang = languageMap[navigator.language] || 'en';
// currentLng not in userLang
return onChangeLng(lang);
};
return {
onChangeLng,
setUserDefaultLng
};
};
......@@ -15,8 +15,7 @@
"version_initial_copy": "Duplicate - Original State",
"version_name_tips": "Version name cannot be empty",
"version_past": "Previously Published",
"version_publish_tips": "This version will be saved to the team cloud, synchronized with the entire team, and update the app version on all release channels.",
"version_save_tips": "This version will be saved to the team cloud, synchronized with the entire team."
"version_publish_tips": "This version will be saved to the team cloud, synchronized with the entire team, and update the app version on all release channels."
},
"app_detail": "Application Details",
"chat_debug": "Chat Preview",
......@@ -138,13 +137,11 @@
"Create plugin bot": "Create Plugin",
"Create simple bot": "Create Simple App",
"Create simple bot tip": "Create a simple AI app by filling out a form, suitable for beginners.",
"Create template tip": "Explore more features in the template market to help you understand and get started with various applications.",
"Create workflow bot": "Create Workflow",
"Create workflow tip": "Build complex multi-turn dialogue AI applications through low-code methods, recommended for advanced users.",
"Http plugin": "HTTP Plugin",
"Plugin": "Plugin",
"Simple bot": "Simple App",
"Template": "Create via Template",
"Workflow bot": "Workflow"
},
"upload_file_max_amount": "Maximum File Quantity",
......@@ -169,8 +166,6 @@
"workflow": {
"Input guide": "Input Guide",
"file_url": "Document Link",
"option1": "Option 1",
"option2": "Option 2",
"read_files": "Document Parsing",
"read_files_result": "Document Parsing Result",
"read_files_result_desc": "Original document text, consisting of file names and document content, separated by hyphens between multiple files.",
......@@ -187,4 +182,4 @@
"user_select": "User Selection",
"user_select_tip": "This module can configure multiple options for selection during the dialogue. Different options can lead to different workflow branches."
}
}
}
\ No newline at end of file
......@@ -24,12 +24,10 @@
"items": "Items",
"module_runtime_and": "Total Module Runtime",
"multiple_AI_conversations": "Multiple AI Conversations",
"new_chat": "New Conversation",
"new_input_guide_lexicon": "New Lexicon",
"no_workflow_response": "No workflow data",
"plugins_output": "Plugin Output",
"question_tip": "From top to bottom, the response order of each module",
"rearrangement": "Rearrangement of Retrieval Results",
"response": {
"node_inputs": "Node Inputs"
},
......@@ -38,4 +36,4 @@
"stream_output": "Stream Output",
"view_citations": "View References",
"web_site_sync": "Web Site Sync"
}
}
\ No newline at end of file
{
"Disabled": "Disabled",
"Enable": "Enable",
"Enabled": "Enabled",
"collection": {
"Create update time": "Creation/Update Time",
"Training type": "Training Mode"
......@@ -47,4 +45,4 @@
"the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "The Dataset has indexes that are being trained or rebuilt",
"website_dataset": "Website Sync",
"website_dataset_desc": "Website sync allows you to build a Dataset directly using a web link."
}
}
\ No newline at end of file
......@@ -15,4 +15,4 @@
"upload_failed": "Upload Failed",
"reached_max_file_count": "Maximum file count reached",
"upload_error_description": "Only multiple files or a single folder can be uploaded at a time"
}
}
\ No newline at end of file
{
"Login": "Login",
"forget_password": "Find password",
"login_failed": "Login failed",
"login_success": "Login successful",
"password_condition": "Password maximum 60 characters",
"policy_tip": "By useing, you agree to our",
"privacy": "Privacy policy",
"register": "Register",
"root_password_placeholder": "The root user password is the value of the environment variable DEFAULT_ROOT_PSW",
"terms": "Terms",
"use_root_login": "Log in as root user"
}
\ No newline at end of file
......@@ -4,20 +4,17 @@
"copy_link_hint": "Copy the link below to the specified location",
"create_api_key": "Create New Key",
"create_link": "Create Link",
"default_response": "Default Response",
"edit_api_key": "Edit Key Details",
"edit_feishu_bot": "Edit Feishu Bot",
"edit_link": "Edit",
"feishu_api": "Feishu API",
"feishu_bot": "Feishu Bot",
"feishu_bot_desc": "Connect to Feishu Bot directly via API",
"feishu_name": "Feishu",
"key_alias": "Key alias, for display only",
"key_tips": "You can use the API key to access specific interfaces (cannot access the application, use the in-app API key for that)",
"link_name": "Share Link Name",
"new_feishu_bot": "Add New Feishu Bot",
"official_account": {
"api": "WeChat Official Account API",
"create_modal_title": "Create WeChat Official Account Integration",
"desc": "Connect to WeChat Official Account directly via API",
"edit_modal_title": "Edit WeChat Official Account Integration",
......@@ -40,4 +37,4 @@
"edit_modal_title": "Edit WeCom Bot",
"title": "Publish to WeCom Bot"
}
}
}
\ No newline at end of file
......@@ -2,7 +2,6 @@
"bill": {
"balance": "Balance",
"buy_plan": "Purchase Plan",
"buy_standard_plan_success": "Plan Purchase Successful",
"contact_customer_service": "Contact Support",
"conversion": "Conversion",
"convert_error": "Conversion Failed",
......@@ -20,6 +19,7 @@
"you_can_convert": "You can convert",
"yuan": "Yuan"
},
"bill_and_invoices": "Bill & Invoice",
"bind_inform_account_error": "Failed to Bind Notification Account",
"bind_inform_account_success": "Notification Account Bound Successfully",
"delete": {
......@@ -27,28 +27,24 @@
"admin_success": "Admin Deleted Successfully"
},
"has_chosen": "Selected",
"individuation": "Individuation",
"login": {
"error": "Login Error",
"failed": "Login Failed",
"login_account": "Login to {{account}} Account",
"login_error": "Incorrect Username or Password",
"password_condition": "Password can be up to 60 characters",
"success": "Login Successful",
"to_register": "No account? Register"
"success": "Login Successful"
},
"name": "Name",
"notice": "Notice",
"notification": {
"Bind Notification Pipe Hint": "Please bind a notification receiving account to ensure you receive notifications such as plan expiration reminders, ensuring your service runs smoothly.",
"remind_owner_bind": "Please remind the creator to bind a notification account"
},
"operations": "Actions",
"password": {
"change_error": "Password Change Error",
"code_required": "Verification Code Required",
"code_send_error": "Failed to Send Verification Code",
"code_sended": "Verification Code Sent",
"confirm": "Confirm Password",
"email_phone": "Email/Phone Number",
"email_phone_error": "Invalid Email/Phone Number Format",
"email_phone_void": "Email/Phone Number Cannot Be Empty",
"get_code": "Get Verification Code",
......@@ -80,19 +76,17 @@
"write": "In addition to readable resources, you can also create new resources"
},
"permissions": "Permissions",
"promotion": {
"pay": "Friend Payment",
"register": "Friend Registration"
},
"personal_information": "Me",
"personalization": "Personalization",
"promotion_records": "Promotion",
"register": {
"confirm": "Confirm Registration",
"error": "Registration Error",
"failed": "Registration Failed",
"register_account": "Register {{account}} Account",
"success": "Registration Successful",
"to_login": "Already have an account? Login"
},
"search_user": "Search Username",
"sign_out": "Sign out",
"synchronization": {
"button": "Sync Now",
"placeholder": "Enter Sync Tag",
......@@ -109,5 +103,6 @@
"official_account": "Official Account",
"share": "Share Link",
"wecom": "WeCom"
}
}
},
"usage_record": "Usages"
}
\ No newline at end of file
{
" i18nT('workflow": {
"use_user_id'),\n required": {
"workflow": {
"use_user_id": ""
}
}
},
"Code": "Code",
"about_xxx_question": "Question regarding xxx",
"add_new_input": "Add New Input",
......@@ -122,15 +115,12 @@
"Custom outputs": "Custom Outputs",
"Error": "Error",
"Read file result": "Read File Result",
"User_select_description": "Description",
"User_select_result": "Selected Result",
"read files": "Read Files"
},
"select_an_application": "Select an Application",
"select_another_application_to_call": "You can choose another application to call",
"special_array_format": "Special array format, returns an empty array when the search result is empty.",
"start_with": "Starts With",
"system_variables": "system variables",
"target_fields_description": "A target field consists of 'description' and 'key'. Multiple target fields can be extracted.",
"template": {
"ai_chat": "AI Chat",
......@@ -156,11 +146,9 @@
"variable_picker_tips": "Type node name or variable name to search",
"variable_update": "Variable Update",
"workflow": {
"Back_to_current_version": "Back to Current Version",
"My edit": "My Edit",
"Switch_failed": "Switch Failed",
"Switch_success": "Switch Successful",
"Team cloud": "Team Cloud",
"exit_tips": "Your changes have not been saved. 'Exit directly' will not save your edits."
}
}
}
\ No newline at end of file
......@@ -15,8 +15,7 @@
"version_initial_copy": "副本-初始状态",
"version_name_tips": "版本名称不能为空",
"version_past": "发布过",
"version_publish_tips": "该版本将被保存至团队云端,同步给整个团队,同时更新所有发布渠道的应用版本",
"version_save_tips": "该版本将被保存至团队云端,同步给整个团队"
"version_publish_tips": "该版本将被保存至团队云端,同步给整个团队,同时更新所有发布渠道的应用版本"
},
"app_detail": "应用详情",
"chat_debug": "调试预览",
......@@ -131,13 +130,11 @@
"Create plugin bot": "创建插件",
"Create simple bot": "创建简易应用",
"Create simple bot tip": "通过填表单形式,创建简单的 AI 应用,适合新手",
"Create template tip": "在模板市场探索更多玩法,带你理解并上手各种应用",
"Create workflow bot": "创建工作流",
"Create workflow tip": "通过低代码的方式,构建逻辑复杂的多轮对话 AI 应用,推荐高级玩家使用",
"Http plugin": "HTTP 插件",
"Plugin": "插件",
"Simple bot": "简易应用",
"Template": "通过模板创建",
"Workflow bot": "工作流"
},
"upload_file_max_amount": "最大文件数量",
......@@ -162,8 +159,6 @@
"workflow": {
"Input guide": "填写说明",
"file_url": "文档链接",
"option1": "选项 1",
"option2": "选项 2",
"read_files": "文档解析",
"read_files_result": "文档解析结果",
"read_files_result_desc": "文档原文,由文件名和文档内容组成,多个文件之间通过横线隔开。",
......@@ -187,4 +182,4 @@
"manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限"
}
}
}
}
\ No newline at end of file
......@@ -24,12 +24,10 @@
"items": "条",
"module_runtime_and": "模块运行时间和",
"multiple_AI_conversations": "多组 AI 对话",
"new_chat": "新对话",
"new_input_guide_lexicon": "新词库",
"no_workflow_response": "没有运行数据",
"plugins_output": "插件输出",
"question_tip": "从上到下,为各个模块的响应顺序",
"rearrangement": "检索结果重排",
"response": {
"node_inputs": "节点输入"
},
......@@ -38,4 +36,4 @@
"stream_output": "流输出",
"view_citations": "查看引用",
"web_site_sync": "Web站点同步"
}
}
\ No newline at end of file
{
"Disabled": "已禁用",
"Enable": "启用",
"Enabled": "已启用",
"collection": {
"Create update time": "创建/更新时间",
"Training type": "训练模式"
......@@ -47,4 +45,4 @@
"manage": "可管理整个知识库数据和信息"
}
}
}
}
\ No newline at end of file
......@@ -15,4 +15,4 @@
"upload_failed": "上传异常",
"reached_max_file_count": "已达到最大文件数量",
"upload_error_description": "单次只支持上传多个文件或者一个文件夹"
}
}
\ No newline at end of file
{
"Login": "登录",
"forget_password": "忘记密码?",
"login_failed": "登录异常",
"login_success": "登录成功",
"password_condition": "密码最多 60 位",
"policy_tip": "使用即代表你同意我们的",
"privacy": "隐私协议",
"register": "注册账号",
"root_password_placeholder": "root 用户密码为环境变量 DEFAULT_ROOT_PSW 的值",
"terms": "服务协议",
"use_root_login": "使用 root 用户登录"
}
\ No newline at end of file
......@@ -4,20 +4,17 @@
"copy_link_hint": "将下面链接复制到指定位置",
"create_api_key": "创建新 key",
"create_link": "创建链接",
"default_response": "默认回复",
"edit_api_key": "编辑 key 信息",
"edit_feishu_bot": "编辑飞书机器人",
"edit_link": "编辑",
"feishu_api": "飞书接口",
"feishu_bot": "飞书机器人",
"feishu_bot_desc": "通过 API 直接接入飞书机器人",
"feishu_name": "飞书",
"key_alias": "key 的别名,仅用于展示",
"key_tips": "你可以使用 API 密钥访问一些特定的接口(无法访问应用,访问应用需使用应用内的 API key)",
"link_name": "分享链接的名字",
"new_feishu_bot": "新增飞书机器人",
"official_account": {
"api": "微信公众号 API",
"create_modal_title": "创建微信公众号接入",
"desc": "通过 API 直接接入微信公众号",
"edit_modal_title": "编辑微信公众号接入",
......@@ -40,4 +37,4 @@
"edit_modal_title": "编辑企微机器人",
"title": "发布到企业微信机器人"
}
}
}
\ No newline at end of file
......@@ -2,13 +2,14 @@
"bill": {
"balance": "余额",
"buy_plan": "购买套餐",
"buy_standard_plan_success": "购买套餐成功",
"contact_customer_service": "联系客服",
"conversion": "兑换",
"convert_error": "兑换失败",
"convert_success": "兑换成功",
"current_token_price": "当前积分价格",
"not_need_invoice": "余额支付,无法开票",
"price": "价格",
"renew_plan": "续费套餐",
"standard_valid_tip": "套餐使用规则:系统优先使用更高级的套餐,原未用完的套餐将延后生效",
"token_expire_1year": "积分有效期一年",
"tokens": "积分",
......@@ -16,14 +17,9 @@
"use_balance_hint": "由于系统升级,原“自动续费从余额扣款”模式取消,余额充值入口关闭。您的余额可用于购买积分",
"valid_time": "生效时间",
"you_can_convert": "您可兑换",
"renew_plan": "续费套餐",
"current_token_price": "当前积分价格",
"yuan": "元"
},
"promotion": {
"register": "好友注册",
"pay": "好友充值"
},
"bill_and_invoices": "账单 & 发票",
"bind_inform_account_error": "绑定通知账号异常",
"bind_inform_account_success": "绑定通知账号成功",
"delete": {
......@@ -31,28 +27,24 @@
"admin_success": "删除管理员成功"
},
"has_chosen": "已选择",
"individuation": "个性化",
"login": {
"error": "登录异常",
"failed": "登录失败",
"login_account": "登录 {{account}} 账号",
"login_error": "用户名或密码错误",
"password_condition": "密码最多 60 位",
"success": "登录成功",
"to_register": "没有账号,去注册"
"success": "登录成功"
},
"name": "名称",
"notice": "通知",
"notification": {
"Bind Notification Pipe Hint": "请绑定通知接收账号,以确保您能正常接收套餐过期提醒等通知,保障您的服务正常运行。",
"remind_owner_bind": "请提醒创建者绑定通知账号"
},
"operations": "操作",
"password": {
"change_error": "修改密码异常",
"code_required": "验证码不能为空",
"code_send_error": "验证码发送异常",
"code_sended": "验证码已发送",
"confirm": "确认密码",
"email_phone": "邮箱/手机号",
"email_phone_error": "邮箱/手机号格式错误",
"email_phone_void": "邮箱/手机号不能为空",
"get_code": "获取验证码",
......@@ -78,16 +70,23 @@
"team_read": "团队可访问",
"team_write": "团队可编辑"
},
"permission_des": {
"manage": "可创建资源、邀请、删除成员",
"read": "成员仅可阅读相关资源,无法新建资源",
"write": "除了可读资源外,还可以新建新的资源"
},
"permissions": "权限",
"personal_information": "个人信息",
"personalization": "个性化",
"promotion_records": "推广记录",
"register": {
"confirm": "确认注册",
"error": "注册异常",
"failed": "注册失败",
"register_account": "注册 {{account}} 账号",
"success": "注册成功",
"to_login": "已有账号,去登录"
},
"search_user": "搜索用户名",
"sign_out": "登出",
"synchronization": {
"button": "立即同步",
"placeholder": "请输入同步标签",
......@@ -105,9 +104,5 @@
"share": "分享链接",
"wecom": "企业微信"
},
"permission_des": {
"read": "成员仅可阅读相关资源,无法新建资源",
"write": "除了可读资源外,还可以新建新的资源",
"manage": "可创建资源、邀请、删除成员"
}
}
"usage_record": "使用记录"
}
\ No newline at end of file
{
" i18nT('workflow": {
"use_user_id'),\n required": {
"workflow": {
"use_user_id": "使用者 ID"
}
}
},
"Code": "代码",
"about_xxx_question": "关于 xxx 的问题",
"add_new_input": "新增输入",
......@@ -122,15 +115,12 @@
"Custom outputs": "自定义输出",
"Error": "错误信息",
"Read file result": "文档解析结果预览",
"User_select_description": "说明文字",
"User_select_result": "选择的结果",
"read files": "解析的文档"
},
"select_an_application": "选择一个应用",
"select_another_application_to_call": "可以选择一个其他应用进行调用",
"special_array_format": "特殊数组格式,搜索结果为空时,返回空数组。",
"start_with": "开始为",
"system_variables": "系统变量",
"target_fields_description": "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段",
"template": {
"ai_chat": "AI 对话",
......@@ -156,11 +146,9 @@
"variable_picker_tips": "可输入节点名或变量名搜索",
"variable_update": "变量更新",
"workflow": {
"Back_to_current_version": "回到初始状态",
"My edit": "我的编辑",
"Switch_failed": "切换失败",
"Switch_success": "切换成功",
"Team cloud": "团队云端",
"exit_tips": "您的更改尚未保存,「直接退出」将不会保存您的编辑记录。"
}
}
}
\ No newline at end of file
......@@ -21,25 +21,27 @@
"ahooks": "^3.7.11",
"date-fns": "2.30.0",
"dayjs": "^1.11.7",
"i18next": "23.11.5",
"js-cookie": "^3.0.5",
"lexical": "0.12.6",
"lodash": "^4.17.21",
"i18next": "23.11.5",
"next-i18next": "15.3.0",
"react-i18next": "14.1.2",
"papaparse": "^5.4.1",
"react": "18.3.1",
"react-beautiful-dnd": "^13.1.1",
"react-day-picker": "^8.7.1",
"react-dom": "18.3.1",
"react-hook-form": "7.43.1",
"react-i18next": "14.1.2",
"react-photo-view": "^1.2.6",
"use-context-selector": "^1.4.4"
},
"devDependencies": {
"@types/js-cookie": "^3.0.5",
"@types/lodash": "^4.14.191",
"@types/papaparse": "^5.3.7",
"@types/react": "18.3.1",
"@types/react-beautiful-dnd": "^13.1.1",
"@types/react-dom": "18.3.0"
}
}
\ No newline at end of file
}
......@@ -7,6 +7,7 @@ import publish from '../i18n/zh/publish.json';
import workflow from '../i18n/zh/workflow.json';
import user from '../i18n/zh/user.json';
import chat from '../i18n/zh/chat.json';
import login from '../i18n/zh/login.json';
export interface I18nNamespaces {
common: typeof common;
......@@ -17,18 +18,11 @@ export interface I18nNamespaces {
workflow: typeof workflow;
user: typeof user;
chat: typeof chat;
login: typeof login;
}
export type I18nNsType = (keyof I18nNamespaces)[];
export type I18nCommonKey = NestedKeyOf<I18nNamespaces['common']>;
export type I18nDataSetKey = NestedKeyOf<I18nNamespaces['dataset']>;
export type I18nAppKey = NestedKeyOf<I18nNamespaces['app']>;
export type I18nPublishKey = NestedKeyOf<I18nNamespaces['publish']>;
export type I18nWorkflowKey = NestedKeyOf<I18nNamespaces['workflow']>;
export type I18nUserKey = NestedKeyOf<I18nNamespaces['user']>;
export type I18nChatKey = NestedKeyOf<I18nNamespaces['chat']>;
export type NestedKeyOf<ObjectType extends object> = {
[Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object
? `${Key}` | `${Key}.${NestedKeyOf<ObjectType[Key]>}`
......@@ -46,7 +40,7 @@ export type I18nKeyFunction = {
declare module 'i18next' {
interface CustomTypeOptions {
returnNull: false;
defaultNS: ['common', 'dataset', 'app', 'file', 'publish', 'workflow', 'user', 'chat'];
defaultNS: ['common', 'dataset', 'app', 'file', 'publish', 'workflow', 'user', 'chat', 'login'];
resources: I18nNamespaces;
}
}
......@@ -22,7 +22,7 @@ importers:
version: 13.3.0
next-i18next:
specifier: 15.3.0
version: 15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
prettier:
specifier: 3.2.4
version: 3.2.4
......@@ -250,7 +250,7 @@ importers:
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@chakra-ui/next-js':
specifier: 2.1.5
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
'@chakra-ui/react':
specifier: 2.8.1
version: 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
......@@ -302,6 +302,9 @@ importers:
i18next:
specifier: 23.11.5
version: 23.11.5
js-cookie:
specifier: ^3.0.5
version: 3.0.5
lexical:
specifier: 0.12.6
version: 0.12.6
......@@ -310,7 +313,7 @@ importers:
version: 4.17.21
next-i18next:
specifier: 15.3.0
version: 15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
papaparse:
specifier: ^5.4.1
version: 5.4.1
......@@ -339,6 +342,9 @@ importers:
specifier: ^1.4.4
version: 1.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.23.2)
devDependencies:
'@types/js-cookie':
specifier: ^3.0.5
version: 3.0.6
'@types/lodash':
specifier: ^4.14.191
version: 4.17.7
......@@ -368,7 +374,7 @@ importers:
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@chakra-ui/next-js':
specifier: 2.1.5
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
version: 2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)
'@chakra-ui/react':
specifier: 2.8.1
version: 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
......@@ -464,7 +470,7 @@ importers:
version: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
next-i18next:
specifier: 15.3.0
version: 15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
version: 15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
nextjs-node-loader:
specifier: ^1.1.5
version: 1.1.5(webpack@5.92.1)
......@@ -529,9 +535,6 @@ importers:
'@types/formidable':
specifier: ^2.0.5
version: 2.0.6
'@types/js-cookie':
specifier: ^3.0.3
version: 3.0.6
'@types/js-yaml':
specifier: ^4.0.9
version: 4.0.9
......@@ -6015,6 +6018,10 @@ packages:
js-cookie@2.2.1:
resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
......@@ -10024,7 +10031,7 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
'@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
'@chakra-ui/next-js@2.1.5(@chakra-ui/react@2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react@18.3.1)':
dependencies:
'@chakra-ui/react': 2.8.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@emotion/cache': 11.11.0
......@@ -15529,6 +15536,8 @@ snapshots:
js-cookie@2.2.1: {}
js-cookie@3.0.5: {}
js-tokens@4.0.0: {}
js-tokens@9.0.0: {}
......@@ -16625,7 +16634,7 @@ snapshots:
neo-async@2.6.2: {}
next-i18next@15.3.0(i18next@23.11.5)(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
next-i18next@15.3.0(i18next@23.11.5)(next@14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.24.8
'@types/hoist-non-react-statics': 3.3.5
......
......@@ -5,11 +5,11 @@
module.exports = {
i18n: {
defaultLocale: 'zh',
defaultLocale: 'en',
locales: ['en', 'zh'],
localeDetection: false
},
localePath:
typeof window === 'undefined' ? require('path').resolve('../../packages/web/i18n') : '/i18n',
reloadOnPrerender: process.env.NODE_ENV === 'development'
}
};
......@@ -68,7 +68,6 @@
"devDependencies": {
"@svgr/webpack": "^6.5.1",
"@types/formidable": "^2.0.5",
"@types/js-cookie": "^3.0.3",
"@types/js-yaml": "^4.0.9",
"@types/jsonwebtoken": "^9.0.3",
"@types/lodash": "^4.14.191",
......
......@@ -7,9 +7,11 @@ import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getUnreadCount } from '@/web/support/user/inform/api';
import dynamic from 'next/dynamic';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import Auth from './auth';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useMount } from 'ahooks';
const Navbar = dynamic(() => import('./navbar'));
const NavbarPhone = dynamic(() => import('./navbarPhone'));
const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal'));
......@@ -46,6 +48,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
const { loading, feConfigs, isNotSufficientModal } = useSystemStore();
const { isPc } = useSystem();
const { userInfo } = useUserStore();
const { setUserDefaultLng } = useI18nLng();
const isChatPage = useMemo(
() => router.pathname === '/chat' && Object.values(router.query).join('').length !== 0,
......@@ -61,6 +64,10 @@ const Layout = ({ children }: { children: JSX.Element }) => {
const isHideNavbar = !!pcUnShowLayoutRoute[router.pathname];
useMount(() => {
setUserDefaultLng();
});
return (
<>
<Box h={'100%'} bg={'myGray.100'}>
......
......@@ -37,7 +37,7 @@ const SendCodeAuthModal = ({
<Skeleton
minH="200px"
isLoaded={!loading}
fadeDuration={2}
fadeDuration={1}
display={'flex'}
justifyContent={'center'}
my={1}
......
......@@ -15,6 +15,7 @@ import NextHead from '@/components/common/NextHead';
function App({ Component, pageProps }: AppProps) {
const { feConfigs, scripts, title } = useInitApp();
const { t } = useTranslation();
return (
<>
<NextHead
......
......@@ -7,8 +7,10 @@ import { UserType } from '@fastgpt/global/support/user/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
import { langMap, setLngStore } from '@/web/common/utils/i18n';
import { langMap } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import MySelect from '@fastgpt/web/components/common/MySelect';
import TimezoneSelect from '@fastgpt/web/components/common/MySelect/TimezoneSelect';
......@@ -16,7 +18,7 @@ const Individuation = () => {
const { t, i18n } = useTranslation();
const { userInfo, updateUserInfo } = useUserStore();
const { toast } = useToast();
const router = useRouter();
const { onChangeLng } = useI18nLng();
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
......@@ -55,14 +57,7 @@ const Individuation = () => {
}))}
onchange={(val: any) => {
const lang = val;
setLngStore(lang);
router.replace(
{
query: router.query
},
router.asPath,
{ locale: lang }
);
onChangeLng(lang);
}}
/>
</Box>
......
......@@ -40,14 +40,14 @@ const Account = ({ currentTab }: { currentTab: TabEnum }) => {
const tabList = [
{
icon: 'support/user/userLight',
label: t('common:user.Personal Information'),
label: t('user:personal_information'),
value: TabEnum.info
},
...(feConfigs?.isPlus
? [
{
icon: 'support/usage/usageRecordLight',
label: t('common:user.Usage Record'),
label: t('user:usage_record'),
value: TabEnum.usage
}
]
......@@ -57,7 +57,7 @@ const Account = ({ currentTab }: { currentTab: TabEnum }) => {
? [
{
icon: 'support/bill/payRecordLight',
label: t('common:support.wallet.Bills'),
label: t('user:bill_and_invoices'),
value: TabEnum.bill
}
]
......@@ -67,7 +67,7 @@ const Account = ({ currentTab }: { currentTab: TabEnum }) => {
? [
{
icon: 'support/account/promotionLight',
label: t('common:user.Promotion Record'),
label: t('user:promotion_records'),
value: TabEnum.promotion
}
]
......@@ -83,14 +83,14 @@ const Account = ({ currentTab }: { currentTab: TabEnum }) => {
: []),
{
icon: 'support/user/individuation',
label: t('common:support.account.Individuation'),
label: t('user:personalization'),
value: TabEnum.individuation
},
...(feConfigs.isPlus
? [
{
icon: 'support/user/informLight',
label: t('common:user.Notice'),
label: t('user:notice'),
value: TabEnum.inform
}
]
......@@ -98,7 +98,7 @@ const Account = ({ currentTab }: { currentTab: TabEnum }) => {
{
icon: 'support/account/loginoutLight',
label: t('common:user.Sign Out'),
label: t('user:sign_out'),
value: TabEnum.loginout
}
];
......
......@@ -125,9 +125,14 @@ const Header = () => {
try {
localStorage.removeItem(`${appDetail._id}-past`);
localStorage.removeItem(`${appDetail._id}-future`);
router.back();
router.push({
pathname: '/app/list',
query: {
parentId: appDetail.parentId
}
});
} catch (error) {}
}, [appDetail._id, router]);
}, [appDetail._id, appDetail.parentId, router]);
const Render = useMemo(() => {
return (
......
......@@ -125,9 +125,14 @@ const Header = () => {
try {
localStorage.removeItem(`${appDetail._id}-past`);
localStorage.removeItem(`${appDetail._id}-future`);
router.back();
router.push({
pathname: '/app/list',
query: {
parentId: appDetail.parentId
}
});
} catch (error) {}
}, [appDetail._id, router]);
}, [appDetail._id, appDetail.parentId, router]);
const Render = useMemo(() => {
return (
......
......@@ -272,7 +272,7 @@ function List() {
{owner && (
<HStack spacing={1}>
<Avatar src={owner.avatar} w={'0.875rem'} borderRadius={'50%'} />
<Box maxW={'150px'} className="textEllipsis">
<Box maxW={'150px'} className="textEllipsis" fontSize={'mini'}>
{owner.memberName}
</Box>
</HStack>
......@@ -288,7 +288,9 @@ function List() {
{isPc && (
<HStack spacing={1} className="time">
<MyIcon name={'history'} w={'0.85rem'} color={'myGray.400'} />
<Box color={'myGray.500'}>{formatTimeToChatTime(dataset.updateTime)}</Box>
<Box color={'myGray.500'} fontSize={'mini'}>
{formatTimeToChatTime(dataset.updateTime)}
</Box>
</HStack>
)}
{dataset.permission.hasWritePer && (
......
......@@ -43,12 +43,12 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
})
);
toast({
title: t('user:login.success'),
title: t('login:login_success'),
status: 'success'
});
} catch (error: any) {
toast({
title: error.message || t('user:login.error'),
title: error.message || t('login:login_failed'),
status: 'error'
});
}
......@@ -61,7 +61,7 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
const placeholder = (() => {
if (isCommunityVersion) {
return t('common:support.user.login.Root login');
return t('login:use_root_login');
}
return [t('common:support.user.login.Username')]
.concat(
......@@ -102,36 +102,36 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
type={'password'}
placeholder={
isCommunityVersion
? t('common:support.user.login.Root password placeholder')
? t('login:root_password_placeholder')
: t('common:support.user.login.Password')
}
{...register('password', {
required: true,
maxLength: {
value: 60,
message: t('user:login.password_condition')
message: t('login:password_condition')
}
})}
></Input>
</FormControl>
{feConfigs?.docUrl && (
<Flex alignItems={'center'} mt={7} fontSize={'sm'}>
{t('common:support.user.login.Policy tip')}
<Flex alignItems={'center'} mt={7} fontSize={'mini'}>
{t('login:policy_tip')}
<Link
ml={1}
href={getDocPath('/docs/agreement/terms/')}
target={'_blank'}
color={'primary.500'}
>
{t('common:support.user.login.Terms')}
{t('login:terms')}
</Link>
<Box mx={1}>{t('common:support.user.login.And')}</Box>
<Box mx={1}>&</Box>
<Link
href={getDocPath('/docs/agreement/privacy/')}
target={'_blank'}
color={'primary.500'}
>
{t('common:support.user.login.Privacy')}
{t('login:privacy')}
</Link>
</Flex>
)}
......@@ -145,7 +145,7 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
isLoading={requesting}
onClick={handleSubmit(onclickLogin)}
>
{t('common:Login')}
{t('login:Login')}
</Button>
<Flex align={'center'} justifyContent={'flex-end'} color={'primary.700'}>
......@@ -156,7 +156,7 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
onClick={() => setPageType('forgetPassword')}
fontSize="sm"
>
{t('common:support.user.login.Forget Password')}
{t('login:forget_password')}
</Box>
)}
{feConfigs?.register_method && feConfigs.register_method.length > 0 && (
......@@ -168,7 +168,7 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
onClick={() => setPageType('register')}
fontSize="sm"
>
{t('common:support.user.login.Register')}
{t('login:register')}
</Box>
</>
)}
......
......@@ -129,7 +129,7 @@ const Login = () => {
export async function getServerSideProps(context: any) {
return {
props: { ...(await serviceSideProps(context, ['app', 'user'])) }
props: { ...(await serviceSideProps(context, ['app', 'user', 'login'])) }
};
}
......
import { I18nNsType } from '@fastgpt/web/types/i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
export const LANG_KEY = 'NEXT_LOCALE_LANG';
export enum LangEnum {
'zh' = 'zh',
'en' = 'en'
......@@ -20,24 +19,3 @@ export const langMap = {
export const serviceSideProps = (content: any, ns: I18nNsType = []) => {
return serverSideTranslations(content.locale, ['common', 'error', ...ns], null, content.locales);
};
export const getLng = (lng: string) => {
return lng.split('-')[0];
};
export const change2DefaultLng = (currentLng: string) => {
if (!navigator || !localStorage) return;
if (localStorage.getItem(LANG_KEY)) return;
const userLang = navigator.language;
if (userLang.includes(currentLng)) {
return;
}
// currentLng not in userLang
return getLng(userLang);
};
export const setLngStore = (lng: string) => {
if (!localStorage) return;
localStorage.setItem(LANG_KEY, lng);
};
import { useEffect, useState } from 'react';
import { clientInitData } from '@/web/common/system/staticData';
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types/index.d';
import { change2DefaultLng, LangEnum, setLngStore } from '@/web/common/utils/i18n';
import { useMemoizedFn, useMount } from 'ahooks';
import { TrackEventName } from '../common/system/constants';
export const useInitApp = () => {
const router = useRouter();
const { hiId } = router.query as { hiId?: string };
const { i18n } = useTranslation();
const { loadGitStar, setInitd, feConfigs } = useSystemStore();
const [scripts, setScripts] = useState<FastGPTFeConfigsType['scripts']>([]);
const [title, setTitle] = useState(process.env.SYSTEM_NAME || 'AI');
......@@ -38,20 +35,8 @@ export const useInitApp = () => {
setInitd();
});
const initUserLanguage = useMemoizedFn(() => {
// get default language
const targetLng =
change2DefaultLng(i18n.language) ||
(['zh', 'zh-CN'].includes(navigator.language) ? 'zh' : 'en');
setLngStore(targetLng);
router.replace(router.asPath, undefined, { locale: targetLng });
});
useMount(() => {
initFetch();
initUserLanguage();
const errorTrack = (event: ErrorEvent) => {
window.umami?.track(TrackEventName.windowError, {
......
const fs = require('fs').promises
const path = require('path')
// 配置项
const CONFIG = {
i18nDirectory: path.join(__dirname, '../../packages/web/i18n'),
sourceDirectories: ['../../packages', '../../projects/app'].map(dir => path.join(__dirname, dir)),
fileExtensions: ['.ts', '.tsx', '.js', '.jsx'],
ignoreDirectories: ['node_modules', '.next', 'public', 'i18n']
}
// 从文件中加载 JSON
const loadJSON = async (filePath) => {
try {
const data = await fs.readFile(filePath, 'utf8')
return JSON.parse(data)
} catch (error) {
console.error(`读取 JSON 文件 ${filePath} 时出错:`, error)
return null
}
}
// 递归提取 JSON 对象的所有键
const extractKeysFromJSON = (obj, parentKey = '') => {
let keys = []
for (const [key, value] of Object.entries(obj)) {
const fullKey = parentKey ? `${parentKey}.${key}` : key
if (typeof value === 'object' && value !== null) {
keys = keys.concat(extractKeysFromJSON(value, fullKey))
} else {
keys.push(fullKey)
}
}
return keys
}
// 递归获取文件夹中的所有 JSON 文件
const getAllJSONFiles = async (dir) => {
let results = []
const list = await fs.readdir(dir)
await Promise.all(list.map(async (file) => {
const filePath = path.join(dir, file)
const stat = await fs.stat(filePath)
if (stat.isDirectory()) {
if (!CONFIG.ignoreDirectories.includes(file)) {
results = results.concat(await getAllJSONFiles(filePath))
}
} else if (filePath.endsWith('.json')) {
results.push(filePath)
}
}))
return results
}
// 提取文件夹中所有 JSON 文件的键
const extractKeysFromDirectory = async (dir) => {
let allKeys = new Set()
const subDirs = await fs.readdir(dir)
await Promise.all(subDirs.map(async (subDir) => {
const subDirPath = path.join(dir, subDir)
const stat = await fs.stat(subDirPath)
if (stat.isDirectory() && !CONFIG.ignoreDirectories.includes(subDir)) {
const files = await getAllJSONFiles(subDirPath)
await Promise.all(files.map(async (file) => {
const jsonObject = await loadJSON(file)
if (jsonObject) {
const keys = extractKeysFromJSON(jsonObject)
keys.forEach(key => allKeys.add(key))
}
}))
}
}))
return Array.from(allKeys)
}
// 检查键是否在内容中使用
const isKeyUsedInContent = (content, key) => {
const regex = new RegExp(`\\b${key}\\b`, 'g')
return regex.test(content)
}
// 递归在文件夹中搜索键
const searchKeysInFiles = async (dir, keys, usedKeys) => {
const list = await fs.readdir(dir)
await Promise.all(list.map(async (file) => {
const filePath = path.join(dir, file)
const stat = await fs.stat(filePath)
if (stat.isDirectory()) {
if (!CONFIG.ignoreDirectories.includes(file)) {
await searchKeysInFiles(filePath, keys, usedKeys)
}
} else if (CONFIG.fileExtensions.includes(path.extname(file))) {
const data = await fs.readFile(filePath, 'utf8')
keys.forEach(key => {
if (isKeyUsedInContent(data, key)) {
usedKeys.add(key)
}
})
}
}))
}
// 从扁平键路径恢复嵌套对象
const restoreNestedStructure = (keys) => {
const result = {}
keys.forEach(key => {
const parts = key.split('.')
let current = result
parts.forEach((part, index) => {
if (!current[part]) {
current[part] = index === parts.length - 1 ? null : {}
}
current = current[part]
})
})
return result
}
// 递归删除未使用的键
const removeUnusedKeys = (target, unusedKeys) => {
for (const [key, value] of Object.entries(unusedKeys)) {
if (value === null) {
delete target[key]
} else if (typeof target[key] === 'object' && target[key] !== null) {
removeUnusedKeys(target[key], value)
if (Object.keys(target[key]).length === 0) {
delete target[key]
}
}
}
}
// 处理 JSON 文件
const processJSONFile = async (filePath, unusedKeys) => {
try {
const jsonObject = await loadJSON(filePath)
if (jsonObject) {
removeUnusedKeys(jsonObject, unusedKeys)
await fs.writeFile(filePath, JSON.stringify(jsonObject, null, 2), 'utf8')
console.log(`已处理文件 ${filePath}`)
}
} catch (error) {
console.error(`处理文件 ${filePath} 时出错:`, error)
}
}
// 递归处理目录中的所有 JSON 文件
const processJSONFilesInDirectory = async (dir, unusedKeys) => {
const list = await fs.readdir(dir)
await Promise.all(list.map(async (file) => {
const filePath = path.join(dir, file)
const stat = await fs.stat(filePath)
if (stat.isDirectory() && !CONFIG.ignoreDirectories.includes(file)) {
await processJSONFilesInDirectory(filePath, unusedKeys)
} else if (stat.isFile() && file.endsWith('.json')) {
await processJSONFile(filePath, unusedKeys)
}
}))
}
// 将 keys 写入 JSON 文件
const writeKeysToFile = async (filePath, keys) => {
try {
await fs.writeFile(filePath, JSON.stringify(keys, null, 2), 'utf8')
console.log(`已将 keys 写入文件 ${filePath}`)
} catch (error) {
console.error(`写入文件 ${filePath} 时出错:`, error)
}
}
// 主函数
const main = async () => {
const allKeys = await extractKeysFromDirectory(CONFIG.i18nDirectory)
// await writeKeysToFile(path.join(__dirname, 'allKeys.json'), allKeys)
const usedKeys = new Set()
await Promise.all(CONFIG.sourceDirectories.map(dir => searchKeysInFiles(dir, allKeys, usedKeys)))
const unusedKeys = allKeys.filter(key => !usedKeys.has(key))
// await writeKeysToFile(path.join(__dirname, 'unusedKeys.json'), unusedKeys)
console.log(unusedKeys)
const nestedUnusedKeys = restoreNestedStructure(unusedKeys)
await processJSONFilesInDirectory(CONFIG.i18nDirectory, nestedUnusedKeys)
}
main().catch(err => console.error('执行过程中出错:', err))
\ No newline at end of file
{
"name": "i18n",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@phenomnomnominal/tsquery": "^6.1.3"
}
}
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var tsquery_1 = require("@phenomnomnominal/tsquery");
var path = require("path");
var fs = require("fs");
//
var root = path.join(__dirname, '../../');
// get all files in the project recursively
function getAllFiles(dirPath, arrayOfFiles) {
if (arrayOfFiles === void 0) { arrayOfFiles = []; }
var files = fs.readdirSync(dirPath);
files.forEach(function (file) {
var filePath = path.join(dirPath, file);
if (fs.statSync(filePath).isDirectory()) {
arrayOfFiles = getAllFiles(filePath, arrayOfFiles);
}
else {
arrayOfFiles.push(filePath);
}
});
return arrayOfFiles;
}
var allFiles = getAllFiles(root).filter(function (file) { return file.endsWith('.ts') || file.endsWith('.tsx'); })
.filter(function (file) { return !file.includes('node_modules'); })
.filter(function (file) { return !file.includes('jieba'); });
function processFiles(allFiles) {
return __awaiter(this, void 0, void 0, function () {
var fileContents, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, Promise.all(allFiles.map(function (file) { return fs.readFileSync(file, 'utf-8'); }))];
case 1:
fileContents = _a.sent();
// 处理每个文件的内容
fileContents.forEach(function (content, index) {
var astTree = (0, tsquery_1.ast)(content);
var res = (0, tsquery_1.query)(astTree, 'JsxText,StringLiteral');
for (var _i = 0, res_1 = res; _i < res_1.length; _i++) {
var node = res_1[_i];
var text = node.getText().trim();
if (text.length > 0 && text.match(/[\u4e00-\u9fa5]/g)) {
console.log(allFiles[index], text);
}
}
});
return [3 /*break*/, 3];
case 2:
error_1 = _a.sent();
console.error('Error processing files:', error_1);
return [3 /*break*/, 3];
case 3: return [2 /*return*/];
}
});
});
}
processFiles(allFiles);
import { ast, query } from '@phenomnomnominal/tsquery';
import * as path from 'path';
import * as fs from 'fs';
//
const root = path.join(__dirname, '../../');
// get all files in the project recursively
function getAllFiles(dirPath: string, arrayOfFiles: string[] = []): string[] {
const files = fs.readdirSync(dirPath);
files.forEach((file) => {
const filePath = path.join(dirPath, file);
if (fs.statSync(filePath).isDirectory()) {
arrayOfFiles = getAllFiles(filePath, arrayOfFiles);
} else {
arrayOfFiles.push(filePath);
}
});
return arrayOfFiles;
}
const allFiles = getAllFiles(root)
.filter((file) => file.endsWith('.ts') || file.endsWith('.tsx'))
.filter((file) => !file.includes('node_modules'))
.filter((file) => !file.includes('jieba'));
async function processFiles(allFiles: string[]) {
try {
// 并行读取所有文件内容
const fileContents = await Promise.all(allFiles.map((file) => fs.readFileSync(file, 'utf-8')));
// 处理每个文件的内容
fileContents.forEach((content, index) => {
const astTree = ast(content);
const res = query(astTree, 'JsxText,StringLiteral');
for (const node of res) {
const text = node.getText().trim();
if (text.length > 0 && text.match(/[\u4e00-\u9fa5]/g)) {
console.log(allFiles[index], text);
}
}
});
} catch (error) {
console.error('Error processing files:', error);
}
}
processFiles(allFiles);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment