From 636366a446d49f51b67ee1e6f0fef5c6dda5d988 Mon Sep 17 00:00:00 2001 From: gchust Date: Fri, 27 Jun 2025 20:32:23 +0800 Subject: [PATCH 01/17] feat: support i18n for flow settings --- .../contextual/StepRequiredSettingsDialog.tsx | 53 ++++++------ .../contextual/StepSettingsDialog.tsx | 17 ++-- .../contextual/StepSettingsDrawer.tsx | 17 ++-- packages/core/flow-engine/src/flowEngine.ts | 1 + packages/core/flow-engine/src/utils.ts | 86 +++++++++++++++++++ 5 files changed, 138 insertions(+), 36 deletions(-) diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx index 50a7e8b9aa..3ad8ebd0a8 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx @@ -12,7 +12,7 @@ import { message, Button } from 'antd'; import React from 'react'; import { FlowModel } from '../../../../models'; import { StepDefinition } from '../../../../types'; -import { resolveDefaultParams, resolveUiSchema } from '../../../../utils'; +import { resolveDefaultParams, resolveUiSchema, compileUiSchema } from '../../../../utils'; import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext'; /** @@ -340,37 +340,42 @@ const openRequiredParamsStepFormDialog = async ({ resolve({}); }; + const scopes = { + formStep, + totalSteps: requiredSteps.length, + requiredSteps, + useStepSettingContext, + closeDialog: handleClose, + handleNext: () => { + // 验证当前步骤的表单 + form + .validate() + .then(() => { + if (formStep) { + formStep.next(); + } + }) + .catch((errors: any) => { + console.log('表单验证失败:', errors); + // 可以在这里添加更详细的错误处理 + }); + }, + ...flowEngine.flowSettings?.scopes, + }; + + // 编译 formSchema 中的表达式 + const compiledFormSchema = compileUiSchema(scopes, formSchema); + return ( <> { - // 验证当前步骤的表单 - form - .validate() - .then(() => { - if (formStep) { - formStep.next(); - } - }) - .catch((errors: any) => { - console.log('表单验证失败:', errors); - // 可以在这里添加更详细的错误处理 - }); - }, - ...flowEngine.flowSettings?.scopes, - }} + scope={scopes} /> diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx index 301fe9f317..b8baca77dd 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx @@ -11,7 +11,7 @@ import { createSchemaField, ISchema } from '@formily/react'; import { message } from 'antd'; import React from 'react'; import { StepSettingsDialogProps } from '../../../../types'; -import { resolveDefaultParams, resolveUiSchema } from '../../../../utils'; +import { resolveDefaultParams, resolveUiSchema, compileUiSchema } from '../../../../utils'; import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext'; const SchemaField = createSchemaField(); @@ -148,17 +148,22 @@ const openStepSettingsDialog = async ({ stepKey, }; + const scopes = { + useStepSettingContext, + ...flowEngine.flowSettings?.scopes, + }; + + // 编译 formSchema 中的表达式 + const compiledFormSchema = compileUiSchema(scopes, formSchema); + return ( ); diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx index 69f301bd32..b5f4f7d807 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx @@ -11,7 +11,7 @@ import { createSchemaField, ISchema } from '@formily/react'; import { message, Button, Space } from 'antd'; import React, { useState } from 'react'; import { StepDefinition, StepSettingsDrawerProps } from '../../../../types'; -import { resolveDefaultParams, resolveUiSchema } from '../../../../utils'; +import { resolveDefaultParams, resolveUiSchema, compileUiSchema } from '../../../../utils'; import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext'; const SchemaField = createSchemaField(); @@ -189,20 +189,25 @@ const openStepSettingsDrawer = async ({ stepKey, }; + const scopes = { + useStepSettingContext, + ...flowEngine.flowSettings?.scopes, + }; + + // 编译 formSchema 中的表达式 + const compiledFormSchema = compileUiSchema(scopes, formSchema); + return (
diff --git a/packages/core/flow-engine/src/flowEngine.ts b/packages/core/flow-engine/src/flowEngine.ts index 9b4aa0aa7a..58bd0b36d6 100644 --- a/packages/core/flow-engine/src/flowEngine.ts +++ b/packages/core/flow-engine/src/flowEngine.ts @@ -46,6 +46,7 @@ export class FlowEngine { constructor() { this.reactView = new ReactView(this); + this.flowSettings.registerScopes({ t: this.t.bind(this) }); } // 注册默认的 FlowModel diff --git a/packages/core/flow-engine/src/utils.ts b/packages/core/flow-engine/src/utils.ts index e63e564019..2242f62ddc 100644 --- a/packages/core/flow-engine/src/utils.ts +++ b/packages/core/flow-engine/src/utils.ts @@ -9,6 +9,7 @@ import _ from 'lodash'; import type { ISchema } from '@formily/json-schema'; +import { Schema } from '@formily/json-schema'; import type { FlowModel } from './models'; import { ActionDefinition, DeepPartial, FlowContext, FlowDefinition, ModelConstructor, ParamsContext } from './types'; @@ -247,3 +248,88 @@ export class TranslationUtil { return this._templateCache.size; } } + +// 模块级全局缓存,与 useCompile 保持一致 +const compileCache = {}; + +/** + * 编译 UI Schema 中的表达式 + * + * @param scope 编译作用域,包含可用的变量和函数(如 t, randomString 等) + * @param uiSchema 待编译的 UI Schema + * @param options 编译选项 + * @returns 编译后的 UI Schema + */ +export function compileUiSchema(scope: Record, uiSchema: any, options: { noCache?: boolean } = {}): any { + const { noCache = false } = options; + + const hasVariable = (source: string): boolean => { + const reg = /\{\{.*?\}\}/g; + return reg.test(source); + }; + + const compile = (source: any): any => { + let shouldCompile = false; + let cacheKey: string; + + // source is expression, for example: {{ t('Add new') }} + if (typeof source === 'string' && source.startsWith('{{')) { + shouldCompile = true; + cacheKey = source; + } + + // source is Component Object, for example: { 'x-component': "Cascader", type: "array", title: "所属地区(行政区划)" } + if (source && typeof source === 'object' && !Array.isArray(source)) { + try { + cacheKey = JSON.stringify(source); + } catch (e) { + console.warn('Failed to stringify:', e); + return source; + } + if (compileCache[cacheKey]) return compileCache[cacheKey]; + shouldCompile = hasVariable(cacheKey); + } + + // source is Array, for example: [{ 'title': "{{ t('Admin') }}", name: 'admin' }, { 'title': "{{ t('Root') }}", name: 'root' }] + if (Array.isArray(source)) { + try { + cacheKey = JSON.stringify(source); + } catch (e) { + console.warn('Failed to stringify:', e); + return source; + } + if (compileCache[cacheKey]) return compileCache[cacheKey]; + shouldCompile = hasVariable(cacheKey); + } + + if (shouldCompile) { + if (!cacheKey) { + try { + return Schema.compile(source, scope); + } catch (error) { + console.warn('Failed to compile with Formily Schema.compile:', error); + return source; + } + } + try { + if (noCache) { + return Schema.compile(source, scope); + } + compileCache[cacheKey] = compileCache[cacheKey] || Schema.compile(source, scope); + return compileCache[cacheKey]; + } catch (e) { + console.log('compileUiSchema error', source, e); + try { + return Schema.compile(source, scope); + } catch (error) { + return source; + } + } + } + + // source is: plain object、string、number、boolean、undefined、null + return source; + }; + + return compile(uiSchema); +} From f38437ff1e5db8f3871746e2510aee8aa5e64ed7 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 08:28:39 +0800 Subject: [PATCH 02/17] feat: i18n --- .../core/client/src/flow/actions/confirm.tsx | 12 ++-- .../client/src/flow/actions/dataScope.tsx | 2 +- .../src/flow/actions/openLinkAction.tsx | 8 +-- .../src/flow/actions/openModeAction.tsx | 27 ++++++--- packages/core/client/src/locale/en-US.json | 3 +- packages/core/client/src/locale/zh-CN.json | 3 +- .../contextual/DefaultSettingsIcon.tsx | 38 +++++++------ .../wrappers/contextual/FlowsContextMenu.tsx | 16 +++--- .../contextual/StepRequiredSettingsDialog.tsx | 27 +++++---- .../wrappers/contextual/StepSettings.tsx | 19 ++++--- .../contextual/StepSettingsDialog.tsx | 34 ++++++----- .../contextual/StepSettingsDrawer.tsx | 40 +++++++------ packages/core/flow-engine/src/flowEngine.ts | 4 ++ .../core/flow-engine/src/locale/en-US.json | 57 +++++++++++++++++++ packages/core/flow-engine/src/locale/index.ts | 38 +++++++++++++ .../core/flow-engine/src/locale/zh-CN.json | 57 +++++++++++++++++++ packages/core/flow-engine/src/utils.ts | 19 +++++++ 17 files changed, 307 insertions(+), 97 deletions(-) create mode 100644 packages/core/flow-engine/src/locale/en-US.json create mode 100644 packages/core/flow-engine/src/locale/index.ts create mode 100644 packages/core/flow-engine/src/locale/zh-CN.json diff --git a/packages/core/client/src/flow/actions/confirm.tsx b/packages/core/client/src/flow/actions/confirm.tsx index 3ff2ce4187..38cd1d51a2 100644 --- a/packages/core/client/src/flow/actions/confirm.tsx +++ b/packages/core/client/src/flow/actions/confirm.tsx @@ -11,25 +11,25 @@ import { defineAction } from '@nocobase/flow-engine'; export const confirm = defineAction({ name: 'confirm', - title: '二次确认', + title: '{{t("Secondary confirmation")}}', uiSchema: { enable: { type: 'boolean', - title: 'Enable secondary confirmation', + title: '{{t("Enable secondary confirmation")}}', 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, title: { type: 'string', - title: 'Title', - default: 'Delete record', + title: '{{t("Title")}}', + default: '{{t("Delete record")}}', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', }, content: { type: 'string', - title: 'Content', - default: 'Are you sure you want to delete it?', + title: '{{t("Content")}}', + default: '{{t("Are you sure you want to delete it?")}}', 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', }, diff --git a/packages/core/client/src/flow/actions/dataScope.tsx b/packages/core/client/src/flow/actions/dataScope.tsx index 46ceb60382..96b679fd40 100644 --- a/packages/core/client/src/flow/actions/dataScope.tsx +++ b/packages/core/client/src/flow/actions/dataScope.tsx @@ -13,7 +13,7 @@ import { FilterGroup } from '../components/FilterGroup'; export const dataScope = defineAction({ name: 'dataScope', - title: '数据范围', + title: '{{t("Data scope")}}', uiSchema: { filter: { type: 'object', diff --git a/packages/core/client/src/flow/actions/openLinkAction.tsx b/packages/core/client/src/flow/actions/openLinkAction.tsx index 140cc81e9d..08138d2ab5 100644 --- a/packages/core/client/src/flow/actions/openLinkAction.tsx +++ b/packages/core/client/src/flow/actions/openLinkAction.tsx @@ -11,7 +11,7 @@ import { css } from '@emotion/css'; import { Variable } from '../../schema-component/antd/variable/Variable'; export const openLinkAction = { - title: '编辑链接', + title: '{{t("Edit link")}}', uiSchema: { url: { title: 'URL', @@ -23,7 +23,7 @@ export const openLinkAction = { type: 'array', 'x-component': 'ArrayItems', 'x-decorator': 'FormItem', - title: `Search parameters`, + title: `{{t("Search parameters")}}`, items: { type: 'object', properties: { @@ -73,14 +73,14 @@ export const openLinkAction = { properties: { add: { type: 'void', - title: 'Add parameter', + title: '{{t("Add parameter")}}', 'x-component': 'ArrayItems.Addition', }, }, }, openInNewWindow: { type: 'boolean', - 'x-content': 'Open in new window', + 'x-content': '{{t("Open in new window")}}', 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, diff --git a/packages/core/client/src/flow/actions/openModeAction.tsx b/packages/core/client/src/flow/actions/openModeAction.tsx index 1b253d4065..0e3cfa934c 100644 --- a/packages/core/client/src/flow/actions/openModeAction.tsx +++ b/packages/core/client/src/flow/actions/openModeAction.tsx @@ -1,26 +1,35 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + import React from 'react'; import { FlowPage } from '../FlowPage'; export const openModeAction = { - title: '打开方式', + title: '{{t("Open mode")}}', uiSchema: { mode: { type: 'string', - title: '打开方式', + title: '{{t("Open mode")}}', enum: [ - { label: 'Drawer', value: 'drawer' }, - { label: 'Modal', value: 'modal' }, + { label: '{{t("Drawer")}}', value: 'drawer' }, + { label: '{{t("Modal")}}', value: 'modal' }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', }, size: { type: 'string', - title: '弹窗尺寸', + title: '{{t("Popup size")}}', enum: [ - { label: '小', value: 'small' }, - { label: '中', value: 'medium' }, - { label: '大', value: 'large' }, + { label: '{{t("Small")}}', value: 'small' }, + { label: '{{t("Medium")}}', value: 'medium' }, + { label: '{{t("Large")}}', value: 'large' }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', @@ -57,7 +66,7 @@ export const openModeAction = { }; currentDrawer = ctx.globals[params.mode].open({ - title: '命令式 Drawer', + title: '{{t("Imperative Drawer")}}', width: sizeToWidthMap[params.size], content: , }); diff --git a/packages/core/client/src/locale/en-US.json b/packages/core/client/src/locale/en-US.json index 4529a266f8..be36dfc761 100644 --- a/packages/core/client/src/locale/en-US.json +++ b/packages/core/client/src/locale/en-US.json @@ -895,5 +895,6 @@ "Refresh data blocks": "Refresh data blocks", "Select data blocks to refresh": "Select data blocks to refresh", "After successful submission, the selected data blocks will be automatically refreshed.": "After successful submission, the selected data blocks will be automatically refreshed.", - "Reset link expiration": "Reset link expiration" + "Reset link expiration": "Reset link expiration", + "Imperative Drawer": "Imperative Drawer" } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index 95c4b45390..dea60651f9 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1139,5 +1139,6 @@ "Calendar Month":"日历月", "Calendar Year":"日历年", "Scan to input":"扫码录入", - "Disable manual input":"禁止手动输入" + "Disable manual input":"禁止手动输入", + "Imperative Drawer": "命令式抽屉" } diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx index 8f12102bc0..a9b02d2167 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx @@ -20,6 +20,7 @@ import { import { FlowModel } from '../../../../models'; import { StepDefinition } from '../../../../types'; import { openStepSettings } from './StepSettings'; +import { getT } from '../../../../utils'; // Type definitions for better type safety interface StepInfo { @@ -110,34 +111,35 @@ export const DefaultSettingsIcon: React.FC = ({ flattenSubMenus = true, }) => { const { message } = App.useApp(); + const t = getT(model); // 分离处理函数以便更好的代码组织 const handleCopyUid = useCallback(async () => { try { await navigator.clipboard.writeText(model.uid); - message.success('UID 已复制到剪贴板'); + message.success(t('UID copied to clipboard')); } catch (error) { - console.error('复制失败:', error); - message.error('复制失败,请重试'); + console.error(t('Copy failed'), ':', error); + message.error(t('Copy failed, please try again')); } }, [model.uid, message]); const handleDelete = useCallback(() => { Modal.confirm({ - title: '确认删除', + title: t('Confirm Delete'), icon: , - content: '确定要删除此项吗?此操作不可撤销。', - okText: '确认删除', + content: t('Are you sure you want to delete this item? This action cannot be undone.'), + okText: t('Confirm Delete'), okType: 'primary', - cancelText: '取消', + cancelText: t('Cancel'), async onOk() { try { await model.destroy(); } catch (error) { - console.error('删除操作失败:', error); + console.error(t('Delete operation failed'), ':', error); Modal.error({ - title: '删除失败', - content: '删除操作失败,请检查控制台获取详细信息。', + title: t('Delete Failed'), + content: t('Delete operation failed, please check the console for details.'), }); } }, @@ -181,7 +183,7 @@ export const DefaultSettingsIcon: React.FC = ({ stepKey, }); } catch (error) { - console.log('配置弹窗已取消或出错:', error); + console.log(t('Configuration popup cancelled or error'), ':', error); } }, [model], @@ -236,7 +238,7 @@ export const DefaultSettingsIcon: React.FC = ({ const action = targetModel.flowEngine?.getAction?.(actionStep.use); hasActionUiSchema = action && action.uiSchema != null; } catch (error) { - console.warn(`获取action '${actionStep.use}' 失败:`, error); + console.warn(t('Failed to get action {{action}}', { action: actionStep.use }), ':', error); } } @@ -263,7 +265,11 @@ export const DefaultSettingsIcon: React.FC = ({ }) .filter(Boolean); } catch (error) { - console.error(`获取模型 '${targetModel?.uid || 'unknown'}' 的可配置flows失败:`, error); + console.error( + t('Failed to get configurable flows for model {{model}}', { model: targetModel?.uid || 'unknown' }), + ':', + error, + ); return []; } }, []); @@ -443,7 +449,7 @@ export const DefaultSettingsIcon: React.FC = ({ items.push({ key: 'copy-uid', icon: , - label: '复制 UID', + label: t('Copy UID'), }); } @@ -452,7 +458,7 @@ export const DefaultSettingsIcon: React.FC = ({ items.push({ key: 'delete', icon: , - label: '删除', + label: t('Delete'), }); } } @@ -467,7 +473,7 @@ export const DefaultSettingsIcon: React.FC = ({ // 渲染前验证模型 if (!model || !model.uid) { - console.warn('提供的模型无效'); + console.warn(t('Invalid model provided')); return null; } diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx index 9bab5e49bd..ee4657ddf9 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx @@ -15,6 +15,7 @@ import { observer } from '@formily/react'; import { FlowModel } from '../../../../models'; import { useFlowModelById } from '../../../../hooks'; import { openStepSettingsDialog } from './StepSettingsDialog'; +import { getT } from '../../../../utils'; // 右键菜单组件接口 interface ModelProvidedProps { @@ -70,25 +71,26 @@ const FlowsContextMenu: React.FC = (props) => { // 使用传入的model const FlowsContextMenuWithModel: React.FC = observer( ({ model, children, enabled = true, position = 'right', showDeleteButton = true }) => { + const t = getT(model); const handleMenuClick = useCallback( ({ key }: { key: string }) => { if (key === 'delete') { // 处理删除操作 Modal.confirm({ - title: '确认删除', + title: t('Confirm Delete'), icon: , - content: '确定要删除此项吗?此操作不可撤销。', - okText: '确认删除', + content: t('Are you sure you want to delete this item? This action cannot be undone.'), + okText: t('Confirm Delete'), okType: 'danger', - cancelText: '取消', + cancelText: t('Cancel'), onOk() { try { model.dispatchEvent('remove'); } catch (error) { - console.error('删除操作失败:', error); + console.error(t('Delete operation failed'), ':', error); Modal.error({ - title: '删除失败', - content: '删除操作失败,请检查控制台获取详细信息。', + title: t('Delete Failed'), + content: t('Delete operation failed, please check the console for details.'), }); } }, diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx index 3ad8ebd0a8..4a2d518e5b 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx @@ -12,7 +12,7 @@ import { message, Button } from 'antd'; import React from 'react'; import { FlowModel } from '../../../../models'; import { StepDefinition } from '../../../../types'; -import { resolveDefaultParams, resolveUiSchema, compileUiSchema } from '../../../../utils'; +import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils'; import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext'; /** @@ -116,11 +116,14 @@ export interface StepFormDialogProps { const openRequiredParamsStepFormDialog = async ({ model, dialogWidth = 800, - dialogTitle = '步骤参数配置', + dialogTitle, }: StepFormDialogProps): Promise => { + const t = getT(model); + const defaultTitle = dialogTitle || t('Step Parameter Configuration'); + if (!model) { - message.error('提供的模型无效'); - throw new Error('提供的模型无效'); + message.error(t('Invalid model provided')); + throw new Error(t('Invalid model provided')); } // 创建一个Promise, 并最终返回,当此弹窗关闭时此promise resolve或者reject @@ -329,7 +332,7 @@ const openRequiredParamsStepFormDialog = async ({ resolve(currentValues); formDialog.close(); } catch (error) { - console.error('提交表单时出错:', error); + console.error(t('Error submitting form'), ':', error); // reject(error); // 这里不需要reject,因为forConfirm会处理 } @@ -356,7 +359,7 @@ const openRequiredParamsStepFormDialog = async ({ } }) .catch((errors: any) => { - console.log('表单验证失败:', errors); + console.log(t('Form validation failed'), ':', errors); // 可以在这里添加更详细的错误处理 }); }, @@ -393,7 +396,7 @@ const openRequiredParamsStepFormDialog = async ({ {/* 只有一个步骤时,只显示完成按钮 */} {requiredSteps.length === 1 ? ( ) : ( <> @@ -405,7 +408,7 @@ const openRequiredParamsStepFormDialog = async ({ } }} > - 上一步 + {t('Previous Step')} )} @@ -455,7 +458,7 @@ const openRequiredParamsStepFormDialog = async ({ initialValues, }); } catch (error) { - reject(new Error(`导入 FormDialog 或 FormStep 失败: ${error.message}`)); + reject(new Error(`${t('Failed to import FormDialog or FormStep')}: ${error.message}`)); } })(); }).catch((e) => { diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettings.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettings.tsx index 8fd167a435..b4579d6077 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettings.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettings.tsx @@ -12,6 +12,7 @@ import { StepSettingsProps } from '../../../../types'; import { openStepSettingsDialog } from './StepSettingsDialog'; import { openStepSettingsDrawer } from './StepSettingsDrawer'; import { FlowModel } from '../../../../models'; +import { getT } from '../../../../utils'; /** * 统一的步骤设置入口函数 @@ -24,9 +25,11 @@ import { FlowModel } from '../../../../models'; * @returns Promise 返回表单提交的值 */ const openStepSettings = async ({ model, flowKey, stepKey, width = 600, title }: StepSettingsProps): Promise => { + const t = getT(model); + if (!model) { - message.error('提供的模型无效'); - throw new Error('提供的模型无效'); + message.error(t('Invalid model provided')); + throw new Error(t('Invalid model provided')); } // 获取流程和步骤信息 @@ -34,13 +37,13 @@ const openStepSettings = async ({ model, flowKey, stepKey, width = 600, title }: const step = flow?.steps?.[stepKey]; if (!flow) { - message.error(`未找到Key为 ${flowKey} 的流程`); - throw new Error(`未找到Key为 ${flowKey} 的流程`); + message.error(t('Flow with key {{flowKey}} not found', { flowKey })); + throw new Error(t('Flow with key {{flowKey}} not found', { flowKey })); } if (!step) { - message.error(`未找到Key为 ${stepKey} 的步骤`); - throw new Error(`未找到Key为 ${stepKey} 的步骤`); + message.error(t('Step with key {{stepKey}} not found', { stepKey })); + throw new Error(t('Step with key {{stepKey}} not found', { stepKey })); } // 检查步骤的 settingMode 配置,默认为 'dialog' @@ -84,7 +87,7 @@ const isStepUsingDrawerMode = (model: FlowModel, flowKey: string, stepKey: strin return step.settingMode === 'drawer'; } catch (error) { - console.warn('检查步骤设置模式时出错:', error); + console.warn('Error checking step setting mode:', error); return false; } }; @@ -107,7 +110,7 @@ const getStepSettingMode = (model: FlowModel, flowKey: string, stepKey: string): return step.settingMode || 'dialog'; } catch (error) { - console.warn('获取步骤设置模式时出错:', error); + console.warn('Error getting step setting mode:', error); return null; } }; diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx index b8baca77dd..4fef33263f 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx @@ -11,7 +11,7 @@ import { createSchemaField, ISchema } from '@formily/react'; import { message } from 'antd'; import React from 'react'; import { StepSettingsDialogProps } from '../../../../types'; -import { resolveDefaultParams, resolveUiSchema, compileUiSchema } from '../../../../utils'; +import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils'; import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext'; const SchemaField = createSchemaField(); @@ -32,9 +32,11 @@ const openStepSettingsDialog = async ({ dialogWidth = 600, dialogTitle, }: StepSettingsDialogProps): Promise => { + const t = getT(model); + if (!model) { - message.error('提供的模型无效'); - throw new Error('提供的模型无效'); + message.error(t('Invalid model provided')); + throw new Error(t('Invalid model provided')); } // 获取流程和步骤信息 @@ -42,16 +44,18 @@ const openStepSettingsDialog = async ({ const step = flow?.steps?.[stepKey]; if (!flow) { - message.error(`未找到Key为 ${flowKey} 的流程`); - throw new Error(`未找到Key为 ${flowKey} 的流程`); + message.error(t('Flow with key {{flowKey}} not found', { flowKey })); + throw new Error(t('Flow with key {{flowKey}} not found', { flowKey })); } if (!step) { - message.error(`未找到Key为 ${stepKey} 的步骤`); - throw new Error(`未找到Key为 ${stepKey} 的步骤`); + message.error(t('Step with key {{stepKey}} not found', { stepKey })); + throw new Error(t('Step with key {{stepKey}} not found', { stepKey })); } - const title = dialogTitle || (step ? `${step.title || stepKey} - 配置` : `步骤配置 - ${stepKey}`); + const title = + dialogTitle || + (step ? `${step.title || stepKey} - ${t('Configuration')}` : `${t('Step Configuration')} - ${stepKey}`); // 创建参数解析上下文 const paramsContext = { @@ -90,7 +94,7 @@ const openStepSettingsDialog = async ({ // 如果没有可配置的UI Schema,显示提示 if (Object.keys(mergedUiSchema).length === 0) { - message.info('此步骤没有可配置的参数'); + message.info(t('This step has no configurable parameters')); return {}; } @@ -122,7 +126,7 @@ const openStepSettingsDialog = async ({ try { ({ FormDialog } = await import('@formily/antd-v5')); } catch (error) { - throw new Error(`导入 FormDialog 失败: ${error.message}`); + throw new Error(`${t('Failed to import FormDialog')}: ${error.message}`); } // 创建FormDialog @@ -130,8 +134,8 @@ const openStepSettingsDialog = async ({ { title, width: dialogWidth, - okText: '确认', - cancelText: '取消', + okText: t('OK'), + cancelText: t('Cancel'), destroyOnClose: true, }, (form) => { @@ -177,11 +181,11 @@ const openStepSettingsDialog = async ({ const currentValues = payload.values; model.setStepParams(flowKey, stepKey, currentValues); await model.save(); - message.success('配置已保存'); + message.success(t('Configuration saved')); next(payload); } catch (error) { - console.error('保存配置时出错:', error); - message.error('保存配置时出错,请检查控制台'); + console.error(t('Error saving configuration'), ':', error); + message.error(t('Error saving configuration, please check console')); throw error; } }); diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx index b5f4f7d807..1cc47a48f9 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx @@ -10,8 +10,9 @@ import { createSchemaField, ISchema } from '@formily/react'; import { message, Button, Space } from 'antd'; import React, { useState } from 'react'; -import { StepDefinition, StepSettingsDrawerProps } from '../../../../types'; -import { resolveDefaultParams, resolveUiSchema, compileUiSchema } from '../../../../utils'; +import { useTranslation } from 'react-i18next'; +import { StepSettingsDrawerProps } from '../../../../types'; +import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils'; import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext'; const SchemaField = createSchemaField(); @@ -32,9 +33,11 @@ const openStepSettingsDrawer = async ({ drawerWidth = 600, drawerTitle, }: StepSettingsDrawerProps): Promise => { + const t = getT(model); + if (!model) { - message.error('提供的模型无效'); - throw new Error('提供的模型无效'); + message.error(t('Invalid model provided')); + throw new Error(t('Invalid model provided')); } // 获取流程和步骤信息 @@ -42,16 +45,18 @@ const openStepSettingsDrawer = async ({ const step = flow?.steps?.[stepKey]; if (!flow) { - message.error(`未找到Key为 ${flowKey} 的流程`); - throw new Error(`未找到Key为 ${flowKey} 的流程`); + message.error(t('Flow with key {{flowKey}} not found', { flowKey })); + throw new Error(t('Flow with key {{flowKey}} not found', { flowKey })); } if (!step) { - message.error(`未找到Key为 ${stepKey} 的步骤`); - throw new Error(`未找到Key为 ${stepKey} 的步骤`); + message.error(t('Step with key {{stepKey}} not found', { stepKey })); + throw new Error(t('Step with key {{stepKey}} not found', { stepKey })); } - const title = drawerTitle || (step ? `${step.title || stepKey} - 配置` : `步骤配置 - ${stepKey}`); + const title = + drawerTitle || + (step ? `${step.title || stepKey} - ${t('Configuration')}` : `${t('Step Configuration')} - ${stepKey}`); // 创建参数解析上下文 const paramsContext = { @@ -89,7 +94,7 @@ const openStepSettingsDrawer = async ({ // 如果没有可配置的UI Schema,显示提示 if (Object.keys(mergedUiSchema).length === 0) { - message.info('此步骤没有可配置的参数'); + message.info(t('This step has no configurable parameters')); return {}; } @@ -122,13 +127,13 @@ const openStepSettingsDrawer = async ({ ({ Form } = await import('@formily/antd-v5')); ({ createForm } = await import('@formily/core')); } catch (error) { - throw new Error(`导入 Formily 组件失败: ${error.message}`); + throw new Error(`${t('Failed to import Formily components')}: ${error.message}`); } // 获取drawer API const drawer = model.flowEngine?.context?.drawer; if (!drawer) { - throw new Error('Drawer API 不可用,请确保在 FlowEngineGlobalsContextProvider 内使用'); + throw new Error(t('Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider')); } return new Promise((resolve) => { @@ -142,6 +147,7 @@ const openStepSettingsDrawer = async ({ // 创建抽屉内容组件 const DrawerContent: React.FC = () => { + const { t } = useTranslation(); const [loading, setLoading] = useState(false); const handleSubmit = async () => { @@ -156,13 +162,13 @@ const openStepSettingsDrawer = async ({ model.setStepParams(flowKey, stepKey, currentValues); await model.save(); - message.success('配置已保存'); + message.success(t('Configuration saved')); isResolved = true; drawerRef.destroy(); resolve(currentValues); } catch (error) { - console.error('保存配置时出错:', error); - message.error('保存配置时出错,请检查控制台'); + console.error(t('Error saving configuration'), ':', error); + message.error(t('Error saving configuration, please check console')); } finally { setLoading(false); } @@ -222,9 +228,9 @@ const openStepSettingsDrawer = async ({ }} > - +
diff --git a/packages/core/flow-engine/src/flowEngine.ts b/packages/core/flow-engine/src/flowEngine.ts index 58bd0b36d6..cf3881871c 100644 --- a/packages/core/flow-engine/src/flowEngine.ts +++ b/packages/core/flow-engine/src/flowEngine.ts @@ -19,6 +19,7 @@ import { ModelConstructor, } from './types'; import { isInheritedFrom, TranslationUtil } from './utils'; +import { initFlowEngineLocale } from './locale'; interface ApplyFlowCacheEntry { status: 'pending' | 'resolved' | 'rejected'; @@ -59,6 +60,9 @@ export class FlowEngine { setContext(context: any) { this.context = { ...this.context, ...context }; + if (this.context.i18n) { + initFlowEngineLocale(this.context.i18n); + } } getContext() { diff --git a/packages/core/flow-engine/src/locale/en-US.json b/packages/core/flow-engine/src/locale/en-US.json new file mode 100644 index 0000000000..af27d423bd --- /dev/null +++ b/packages/core/flow-engine/src/locale/en-US.json @@ -0,0 +1,57 @@ +{ + "Invalid model provided": "Invalid model provided", + "Flow with key {{flowKey}} not found": "Flow with key {{flowKey}} not found", + "Step with key {{stepKey}} not found": "Step with key {{stepKey}} not found", + "Configuration": "Configuration", + "Step Configuration": "Step Configuration", + "This step has no configurable parameters": "This step has no configurable parameters", + "Failed to import Formily components": "Failed to import Formily components", + "Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider": "Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider", + "Configuration saved": "Configuration saved", + "Error saving configuration": "Error saving configuration", + "Error saving configuration, please check console": "Error saving configuration, please check console", + "Failed to import FormDialog": "Failed to import FormDialog", + "OK": "OK", + "Cancel": "Cancel", + "Step Parameter Configuration": "Step Parameter Configuration", + "Error submitting form": "Error submitting form", + "Complete Configuration": "Complete Configuration", + "Previous Step": "Previous Step", + "Form validation failed": "Form validation failed", + "Next Step": "Next Step", + "Failed to import FormDialog or FormStep": "Failed to import FormDialog or FormStep", + "UID copied to clipboard": "UID copied to clipboard", + "Copy failed": "Copy failed", + "Copy failed, please try again": "Copy failed, please try again", + "Confirm Delete": "Confirm Delete", + "Are you sure you want to delete this item? This action cannot be undone.": "Are you sure you want to delete this item? This action cannot be undone.", + "Delete operation failed": "Delete operation failed", + "Delete Failed": "Delete Failed", + "Delete operation failed, please check the console for details.": "Delete operation failed, please check the console for details.", + "Configuration popup cancelled or error": "Configuration popup cancelled or error", + "Failed to get action {{action}}": "Failed to get action {{action}}", + "Failed to get configurable flows for model {{model}}": "Failed to get configurable flows for model {{model}}", + "Copy UID": "Copy UID", + "Delete": "Delete", + "This is likely a NocoBase internals bug. Please open an issue at": "This is likely a NocoBase internals bug. Please open an issue at", + "here": "here", + "Render Failed": "Render Failed", + "Feedback": "Feedback", + "Download logs": "Download logs", + "Copy Error Info": "Copy Error Info", + "Try Again": "Try Again", + "Data blocks": "Data blocks", + "Filter blocks": "Filter blocks", + "Other blocks": "Other blocks", + "Invalid input parameters": "Invalid input parameters", + "Invalid subModelKey format": "Invalid subModelKey format: {{subModelKey}}", + "SubModel not found": "SubModel '{{subKey}}' not found", + "Expected array for subModel": "Expected array for '{{subKey}}', got {{type}}", + "Array index out of bounds": "Array index {{index}} out of bounds for '{{subKey}}'", + "Expected object for subModel": "Expected object for '{{subKey}}', got array", + "No createModelOptions found for item": "No createModelOptions found for item", + "createModelOptions must specify use property": "createModelOptions must specify \"use\" property", + "Failed to add sub model": "Failed to add sub model", + "Failed to destroy model after creation error": "Failed to destroy model after creation error", + "Add": "Add" +} \ No newline at end of file diff --git a/packages/core/flow-engine/src/locale/index.ts b/packages/core/flow-engine/src/locale/index.ts new file mode 100644 index 0000000000..0ea7dc2bfd --- /dev/null +++ b/packages/core/flow-engine/src/locale/index.ts @@ -0,0 +1,38 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import enUS from './en-US.json'; +import zhCN from './zh-CN.json'; +import { FLOW_ENGINE_NAMESPACE } from '../utils'; + +export const locales = { + 'en-US': enUS, + 'zh-CN': zhCN, +}; + +/** + * Get translation for a key with fallback + */ +export function getFlowEngineTranslation(key: string, locale = 'en-US'): string { + const translations = locales[locale] || locales['en-US']; + return translations[key] || key; +} + +/** + * Initialize flow-engine locale resources + * This should be called when the flow-engine is initialized + */ +export function initFlowEngineLocale(i18nInstance?: any) { + if (i18nInstance && typeof i18nInstance.addResourceBundle === 'function') { + // Register flow-engine translations to the flow-engine namespace + Object.entries(locales).forEach(([locale, resources]) => { + i18nInstance.addResourceBundle(locale, FLOW_ENGINE_NAMESPACE, resources, true, false); + }); + } +} diff --git a/packages/core/flow-engine/src/locale/zh-CN.json b/packages/core/flow-engine/src/locale/zh-CN.json new file mode 100644 index 0000000000..6a5753fcbb --- /dev/null +++ b/packages/core/flow-engine/src/locale/zh-CN.json @@ -0,0 +1,57 @@ +{ + "Invalid model provided": "提供的模型无效", + "Flow with key {{flowKey}} not found": "未找到Key为 {{flowKey}} 的流程", + "Step with key {{stepKey}} not found": "未找到Key为 {{stepKey}} 的步骤", + "Configuration": "配置", + "Step Configuration": "步骤配置", + "This step has no configurable parameters": "此步骤没有可配置的参数", + "Failed to import Formily components": "导入Formily组件失败", + "Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider": "抽屉API不可用,请确保在FlowEngineGlobalsContextProvider中使用", + "Configuration saved": "配置已保存", + "Error saving configuration": "保存配置时出错", + "Error saving configuration, please check console": "保存配置时出错,请检查控制台", + "Failed to import FormDialog": "导入FormDialog失败", + "OK": "确定", + "Cancel": "取消", + "Step Parameter Configuration": "步骤参数配置", + "Error submitting form": "提交表单时出错", + "Complete Configuration": "完成配置", + "Previous Step": "上一步", + "Form validation failed": "表单验证失败", + "Next Step": "下一步", + "Failed to import FormDialog or FormStep": "导入 FormDialog 或 FormStep 失败", + "UID copied to clipboard": "UID 已复制到剪贴板", + "Copy failed": "复制失败", + "Copy failed, please try again": "复制失败,请重试", + "Confirm Delete": "确认删除", + "Are you sure you want to delete this item? This action cannot be undone.": "确定要删除此项吗?此操作不可撤销。", + "Delete operation failed": "删除操作失败", + "Delete Failed": "删除失败", + "Delete operation failed, please check the console for details.": "删除操作失败,请检查控制台获取详细信息。", + "Configuration popup cancelled or error": "配置弹窗已取消或出错", + "Failed to get action {{action}}": "获取action '{{action}}' 失败", + "Failed to get configurable flows for model {{model}}": "获取模型 '{{model}}' 的可配置flows失败", + "Copy UID": "复制 UID", + "Delete": "删除", + "This is likely a NocoBase internals bug. Please open an issue at": "这可能是 NocoBase 内部错误。请在以下地址提交问题", + "here": "这里", + "Render Failed": "渲染失败", + "Feedback": "反馈", + "Download logs": "下载日志", + "Copy Error Info": "复制错误信息", + "Try Again": "重试", + "Data blocks": "数据区块", + "Filter blocks": "筛选区块", + "Other blocks": "其他区块", + "Invalid input parameters": "输入参数无效", + "Invalid subModelKey format": "无效的 subModelKey 格式: {{subModelKey}}", + "SubModel not found": "未找到SubModel '{{subKey}}'", + "Expected array for subModel": "期望 '{{subKey}}' 为数组,实际为 {{type}}", + "Array index out of bounds": "数组索引 {{index}} 超出 '{{subKey}}' 的边界", + "Expected object for subModel": "期望 '{{subKey}}' 为对象,实际为数组", + "No createModelOptions found for item": "未找到该项的 createModelOptions", + "createModelOptions must specify use property": "createModelOptions 必须指定 \"use\" 属性", + "Failed to add sub model": "添加子模型失败", + "Failed to destroy model after creation error": "创建错误后销毁模型失败", + "Add": "添加" +} \ No newline at end of file diff --git a/packages/core/flow-engine/src/utils.ts b/packages/core/flow-engine/src/utils.ts index 2242f62ddc..44051ccaf7 100644 --- a/packages/core/flow-engine/src/utils.ts +++ b/packages/core/flow-engine/src/utils.ts @@ -13,6 +13,25 @@ import { Schema } from '@formily/json-schema'; import type { FlowModel } from './models'; import { ActionDefinition, DeepPartial, FlowContext, FlowDefinition, ModelConstructor, ParamsContext } from './types'; +// Flow Engine 命名空间常量 +export const FLOW_ENGINE_NAMESPACE = 'flow-engine'; + +/** + * 获取带有 flow-engine 命名空间的翻译函数 + * @param model FlowModel 实例 + * @returns 翻译函数,自动使用 flow-engine 命名空间 + */ +export function getT(model: FlowModel): (key: string, options?: any) => string { + if (model.flowEngine?.t) { + return (key: string, options?: any) => { + // 自动添加 flow-engine 命名空间 + return model.flowEngine.t(key, { ...options, ns: FLOW_ENGINE_NAMESPACE }); + }; + } + // 回退到原始键值 + return (key: string) => key; +} + export function generateUid(): string { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); } From cb770ee47df1fc5d663a910985092bbf34c37a39 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 09:07:12 +0800 Subject: [PATCH 03/17] feat: i18n --- .../core/flow-models/demos/FlowsFloatContextMenu.tsx | 2 +- .../wrappers/contextual/DefaultSettingsIcon.tsx | 12 ++++++------ .../wrappers/contextual/FlowsFloatContextMenu.tsx | 9 +++++++-- packages/core/flow-engine/src/locale/en-US.json | 4 +++- packages/core/flow-engine/src/locale/zh-CN.json | 4 +++- packages/core/flow-engine/src/utils.ts | 8 ++++---- .../src/client/LowcodeBlockModel.tsx | 8 ++++---- 7 files changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/core/client/docs/zh-CN/core/flow-models/demos/FlowsFloatContextMenu.tsx b/packages/core/client/docs/zh-CN/core/flow-models/demos/FlowsFloatContextMenu.tsx index 9e5d32bc7a..f7c20be553 100644 --- a/packages/core/client/docs/zh-CN/core/flow-models/demos/FlowsFloatContextMenu.tsx +++ b/packages/core/client/docs/zh-CN/core/flow-models/demos/FlowsFloatContextMenu.tsx @@ -18,7 +18,7 @@ HelloFlowModel.registerFlow('defaultFlow', { uiSchema: { name: { type: 'string', - title: 'Name', + title: "{{t('Name')}}", 'x-component': Input, }, }, diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx index a9b02d2167..84a62a5420 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx @@ -255,7 +255,7 @@ export const DefaultSettingsIcon: React.FC = ({ stepKey, step: actionStep, uiSchema: mergedUiSchema, - title: actionStep.title || stepKey, + title: t(actionStep.title) || stepKey, modelKey, // 添加模型标识 }; }) @@ -344,7 +344,7 @@ export const DefaultSettingsIcon: React.FC = ({ // 在平铺模式下始终按流程分组 items.push({ key: groupKey, - label: flow.title || flow.key, + label: t(flow.title) || flow.key, type: 'group', }); @@ -359,7 +359,7 @@ export const DefaultSettingsIcon: React.FC = ({ items.push({ key: uniqueKey, icon: , - label: stepInfo.title, + label: t(stepInfo.title), }); }); }); @@ -388,7 +388,7 @@ export const DefaultSettingsIcon: React.FC = ({ items.push({ key: groupKey, - label: flow.title || flow.key, + label: t(flow.title) || flow.key, type: 'group', }); @@ -398,7 +398,7 @@ export const DefaultSettingsIcon: React.FC = ({ items.push({ key: uniqueKey, icon: , - label: stepInfo.title, + label: t(stepInfo.title), }); }); }); @@ -414,7 +414,7 @@ export const DefaultSettingsIcon: React.FC = ({ subMenuChildren.push({ key: uniqueKey, icon: , - label: stepInfo.title, + label: t(stepInfo.title), }); }); }); diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx index 75589b75f8..2f112d47ff 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx @@ -16,6 +16,7 @@ import { ToolbarItemConfig } from '../../../../types'; import { useFlowModelById } from '../../../../hooks'; import { useFlowEngine } from '../../../../provider'; import { FlowEngine } from '../../../../flowEngine'; +import { getT } from '../../../../utils'; // 检测DOM中直接子元素是否包含button元素的辅助函数 const detectButtonInDOM = (container: HTMLElement): boolean => { @@ -298,7 +299,8 @@ const FlowsFloatContextMenuWithModel: React.FC = observer( }, []); if (!model) { - return ; + const t = getT(model || ({} as FlowModel)); + return ; } // 如果未启用或没有children,直接返回children @@ -355,7 +357,10 @@ const FlowsFloatContextMenuWithModelById: React.FC = observer( const model = useFlowModelById(uid, modelClassName); if (!model) { - return ; + // 创建一个临时的 FlowModel 实例用于翻译 + const tempModel = { flowEngine: { t: (key: string) => key } } as FlowModel; + const t = getT(tempModel); + return ; } return ( diff --git a/packages/core/flow-engine/src/locale/en-US.json b/packages/core/flow-engine/src/locale/en-US.json index af27d423bd..d82887ca96 100644 --- a/packages/core/flow-engine/src/locale/en-US.json +++ b/packages/core/flow-engine/src/locale/en-US.json @@ -53,5 +53,7 @@ "createModelOptions must specify use property": "createModelOptions must specify \"use\" property", "Failed to add sub model": "Failed to add sub model", "Failed to destroy model after creation error": "Failed to destroy model after creation error", - "Add": "Add" + "Add": "Add", + "Name": "Name", + "Model with ID {{uid}} not found": "Model with ID {{uid}} not found" } \ No newline at end of file diff --git a/packages/core/flow-engine/src/locale/zh-CN.json b/packages/core/flow-engine/src/locale/zh-CN.json index 6a5753fcbb..f7cb829628 100644 --- a/packages/core/flow-engine/src/locale/zh-CN.json +++ b/packages/core/flow-engine/src/locale/zh-CN.json @@ -53,5 +53,7 @@ "createModelOptions must specify use property": "createModelOptions 必须指定 \"use\" 属性", "Failed to add sub model": "添加子模型失败", "Failed to destroy model after creation error": "创建错误后销毁模型失败", - "Add": "添加" + "Add": "添加", + "Name": "名称", + "Model with ID {{uid}} not found": "未找到ID为 {{uid}} 的模型" } \ No newline at end of file diff --git a/packages/core/flow-engine/src/utils.ts b/packages/core/flow-engine/src/utils.ts index 44051ccaf7..7ceb327f27 100644 --- a/packages/core/flow-engine/src/utils.ts +++ b/packages/core/flow-engine/src/utils.ts @@ -25,7 +25,7 @@ export function getT(model: FlowModel): (key: string, options?: any) => string { if (model.flowEngine?.t) { return (key: string, options?: any) => { // 自动添加 flow-engine 命名空间 - return model.flowEngine.t(key, { ...options, ns: FLOW_ENGINE_NAMESPACE }); + return model.flowEngine.t(key, { ns: FLOW_ENGINE_NAMESPACE, ...options }); }; } // 回退到原始键值 @@ -236,10 +236,10 @@ export class TranslationUtil { if (optionsStr) { optionsStr = optionsStr.trim(); if (optionsStr.startsWith('{') && optionsStr.endsWith('}')) { + // 使用受限的 Function 构造器解析 try { - templateOptions = JSON.parse(optionsStr); - } catch (jsonError) { - // JSON 解析失败,返回原始匹配字符串 + templateOptions = new Function('$root', `with($root) { return (${optionsStr}); }`)({}); + } catch (parseError) { return match; } } diff --git a/packages/plugins/@nocobase/plugin-block-lowcode/src/client/LowcodeBlockModel.tsx b/packages/plugins/@nocobase/plugin-block-lowcode/src/client/LowcodeBlockModel.tsx index ef28d88478..b60a5b5849 100644 --- a/packages/plugins/@nocobase/plugin-block-lowcode/src/client/LowcodeBlockModel.tsx +++ b/packages/plugins/@nocobase/plugin-block-lowcode/src/client/LowcodeBlockModel.tsx @@ -19,7 +19,7 @@ import * as antd from 'antd'; import { Card, Spin } from 'antd'; import React, { createRef } from 'react'; import ReactDOM from 'react-dom/client'; -import { NAMESPACE } from './locale'; +import { NAMESPACE, tStr } from './locale'; export class LowcodeBlockModel extends BlockModel { ref = createRef(); declare resource: APIResource; @@ -125,15 +125,15 @@ ctx.element.innerHTML = \` LowcodeBlockModel.registerFlow({ key: 'default', - title: 'Configuration', + title: tStr('Configuration'), auto: true, steps: { executionStep: { - title: 'Code', + title: tStr('Code'), uiSchema: { code: { type: 'string', - title: 'Execution Code', + title: tStr('Execution Code'), 'x-component': 'CodeEditor', 'x-component-props': { minHeight: '400px', From 056fdf8d362fcde0f8abd0ca769a39d458f6c798 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 13:46:29 +0800 Subject: [PATCH 04/17] feat: i18n --- .../flow/models/actions/AddNewActionModel.tsx | 7 +++-- .../models/actions/BulkDeleteActionModel.tsx | 16 +++++----- .../models/actions/BulkEditActionModel.tsx | 18 ++++++----- .../flow/models/actions/DeleteActionModel.tsx | 14 +++++---- .../models/actions/DuplicateActionModel.tsx | 15 +++++----- .../flow/models/actions/EditActionModel.tsx | 7 +++-- .../flow/models/actions/FilterActionModel.tsx | 19 ++++++------ .../models/actions/RefreshActionModel.tsx | 10 ++++--- .../actions/UpdateRecordActionModel.tsx | 9 +++--- .../flow/models/actions/ViewActionModel.tsx | 7 +++-- .../src/flow/models/base/ActionModel.tsx | 23 +++++++------- .../client/src/flow/models/base/PageModel.tsx | 11 +++---- .../data-blocks/form/FormActionModel.tsx | 5 ++-- .../other-blocks/html/HtmlBlockModel.tsx | 5 ++-- packages/core/client/src/locale/en-US.json | 25 +++++++++++++++- packages/core/client/src/locale/zh-CN.json | 30 ++++++++++++++++++- .../contextual/FlowsFloatContextMenu.tsx | 6 ++-- .../core/flow-engine/src/data-source/index.ts | 6 ++-- packages/core/flow-engine/src/flowEngine.ts | 4 +-- packages/core/flow-engine/src/types.ts | 7 ++++- packages/core/flow-engine/src/utils.ts | 4 +-- 21 files changed, 159 insertions(+), 89 deletions(-) diff --git a/packages/core/client/src/flow/models/actions/AddNewActionModel.tsx b/packages/core/client/src/flow/models/actions/AddNewActionModel.tsx index f3ef5b6319..d93440535f 100644 --- a/packages/core/client/src/flow/models/actions/AddNewActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/AddNewActionModel.tsx @@ -8,23 +8,24 @@ */ import { ButtonProps } from 'antd'; +import { tval } from '@nocobase/utils/client'; import { GlobalActionModel } from '../base/ActionModel'; export class AddNewActionModel extends GlobalActionModel { defaultProps: ButtonProps = { type: 'primary', - title: 'Add new', + title: tval('Add new'), icon: 'PlusOutlined', }; } AddNewActionModel.define({ - title: 'Add new', + title: tval('Add new'), }); AddNewActionModel.registerFlow({ sort: 200, - title: '点击事件', + title: tval('Click event'), key: 'handleClick', on: { eventName: 'click', diff --git a/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx b/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx index 83019ebcd8..ab128120e6 100644 --- a/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx @@ -9,24 +9,23 @@ import { MultiRecordResource } from '@nocobase/flow-engine'; import { ButtonProps } from 'antd'; -import { refreshOnCompleteAction } from '../../actions/refreshOnCompleteAction'; -import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction'; +import { tval } from '@nocobase/utils/client'; import { GlobalActionModel } from '../base/ActionModel'; export class BulkDeleteActionModel extends GlobalActionModel { defaultProps: ButtonProps = { - title: 'Delete', + title: tval('Delete'), icon: 'DeleteOutlined', }; } BulkDeleteActionModel.define({ - title: 'Delete', + title: tval('Delete'), }); BulkDeleteActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, @@ -36,17 +35,18 @@ BulkDeleteActionModel.registerFlow({ }, delete: { async handler(ctx, params) { + const t = ctx.globals.flowEngine.translate; if (!ctx.shared?.currentBlockModel?.resource) { - ctx.globals.message.error('No resource selected for deletion.'); + ctx.globals.message.error(t('No resource selected for deletion')); return; } const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource; if (resource.getSelectedRows().length === 0) { - ctx.globals.message.warning('No records selected for deletion.'); + ctx.globals.message.warning(t('No records selected for deletion')); return; } await resource.destroySelectedRows(); - ctx.globals.message.success('Selected records deleted successfully.'); + ctx.globals.message.success(t('Selected records deleted successfully')); }, }, }, diff --git a/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx b/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx index a0f4e9f1a2..e2cb9b4774 100644 --- a/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx @@ -9,6 +9,7 @@ import { MultiRecordResource } from '@nocobase/flow-engine'; import { ButtonProps } from 'antd'; +import { tval } from '@nocobase/utils/client'; import { openModeAction } from '../../actions/openModeAction'; import { GlobalActionModel } from '../base/ActionModel'; @@ -26,21 +27,21 @@ BulkEditActionModel.define({ BulkEditActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, steps: { openModeAction, bulkEdit: { - title: '更新的数据', + title: tval('Data will be updated'), uiSchema: { updateMode: { 'x-component': 'Radio.Group', 'x-component-props': { options: [ - { label: '更新选中行', value: 'selected' }, - { label: '更新所有行', value: 'all' }, + { label: tval('Update selected data?'), value: 'selected' }, + { label: tval('Update all data?'), value: 'all' }, ], }, }, @@ -51,17 +52,18 @@ BulkEditActionModel.registerFlow({ }; }, async handler(ctx, params) { + const t = ctx.globals.flowEngine.translate; if (!ctx.shared?.currentBlockModel?.resource) { - ctx.globals.message.error('No resource selected for bulk edit.'); + ctx.globals.message.error(t('No resource selected for bulk edit')); return; } const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource; if (resource.getSelectedRows().length === 0) { - ctx.globals.message.warning('No records selected for bulk edit.'); + ctx.globals.message.warning(t('No records selected for bulk edit')); return; } - await resource.destroySelectedRows(); - ctx.globals.message.success('Successfully.'); + //TODO: await resource.updateSelectedRows(params); + ctx.globals.message.success(t('updateSelectedRows not implemented!')); }, }, }, diff --git a/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx b/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx index fc1c61a0c8..52884fc745 100644 --- a/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx @@ -9,22 +9,23 @@ import { MultiRecordResource } from '@nocobase/flow-engine'; import type { ButtonProps } from 'antd/es/button'; +import { tval } from '@nocobase/utils/client'; import { RecordActionModel } from '../base/ActionModel'; export class DeleteActionModel extends RecordActionModel { defaultProps: ButtonProps = { type: 'link', - title: 'Delete', + title: tval('Delete'), }; } DeleteActionModel.define({ - title: 'Delete', + title: tval('Delete'), }); DeleteActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, @@ -34,17 +35,18 @@ DeleteActionModel.registerFlow({ }, delete: { async handler(ctx, params) { + const t = ctx.globals.flowEngine.translate; if (!ctx.shared?.currentBlockModel?.resource) { - ctx.globals.message.error('No resource selected for deletion.'); + ctx.globals.message.error(t('No resource selected for deletion')); return; } if (!ctx.shared.currentRecord) { - ctx.globals.message.error('No resource or record selected for deletion.'); + ctx.globals.message.error(t('No resource or record selected for deletion')); return; } const resource = ctx.shared.currentBlockModel.resource as MultiRecordResource; await resource.destroy(ctx.shared.currentRecord); - ctx.globals.message.success('Record deleted successfully.'); + ctx.globals.message.success(t('Record deleted successfully')); }, }, }, diff --git a/packages/core/client/src/flow/models/actions/DuplicateActionModel.tsx b/packages/core/client/src/flow/models/actions/DuplicateActionModel.tsx index e0283a7b9e..9041239582 100644 --- a/packages/core/client/src/flow/models/actions/DuplicateActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/DuplicateActionModel.tsx @@ -8,31 +8,32 @@ */ import type { ButtonProps } from 'antd/es/button'; +import { tval } from '@nocobase/utils/client'; import { openModeAction } from '../../actions/openModeAction'; import { RecordActionModel } from '../base/ActionModel'; export class DuplicateActionModel extends RecordActionModel { defaultProps: ButtonProps = { type: 'link', - title: 'Duplicate', + title: tval('Duplicate'), }; } DuplicateActionModel.define({ - title: 'Duplicate', + title: tval('Duplicate'), hide: true, }); DuplicateActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, steps: { open: openModeAction, duplicateMode: { - title: '复制方式', + title: tval('Duplicate mode'), uiSchema: { // TODO duplicateMode: { @@ -40,15 +41,15 @@ DuplicateActionModel.registerFlow({ 'x-component': 'Select', enum: [ { - label: '快速复制', + label: tval('Quick duplicate'), value: 'quickDuplicate', }, { - label: '表单复制', + label: tval('Form duplicate'), value: 'formDuplicate', }, ], - title: '复制方式', + title: tval('Duplicate mode'), }, }, defaultParams(ctx) { diff --git a/packages/core/client/src/flow/models/actions/EditActionModel.tsx b/packages/core/client/src/flow/models/actions/EditActionModel.tsx index 6078ab1df0..a5ade006b8 100644 --- a/packages/core/client/src/flow/models/actions/EditActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/EditActionModel.tsx @@ -8,23 +8,24 @@ */ import type { ButtonProps } from 'antd/es/button'; +import { tval } from '@nocobase/utils/client'; import { openModeAction } from '../../actions/openModeAction'; import { RecordActionModel } from '../base/ActionModel'; export class EditActionModel extends RecordActionModel { defaultProps: ButtonProps = { type: 'link', - title: 'Edit', + title: tval('Edit'), }; } EditActionModel.define({ - title: 'Edit', + title: tval('Edit'), }); EditActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, diff --git a/packages/core/client/src/flow/models/actions/FilterActionModel.tsx b/packages/core/client/src/flow/models/actions/FilterActionModel.tsx index e968e46bc0..b33572aa6a 100644 --- a/packages/core/client/src/flow/models/actions/FilterActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/FilterActionModel.tsx @@ -10,6 +10,7 @@ import { MultiRecordResource, useFlowModel, useStepSettingContext } from '@nocobase/flow-engine'; import { Button, ButtonProps, Popover, Select, Space } from 'antd'; import React, { FC } from 'react'; +import { tval } from '@nocobase/utils/client'; import { FilterGroup } from '../../components/FilterGroup'; import { GlobalActionModel } from '../base/ActionModel'; import { DataBlockModel } from '../base/BlockModel'; @@ -42,7 +43,7 @@ export class FilterActionModel extends GlobalActionModel { defaultProps: any = { type: 'default', - children: 'Filter', + children: tval('Filter'), icon: 'FilterOutlined', filterValue: { $and: [] }, ignoreFieldsNames: [], @@ -63,16 +64,16 @@ export class FilterActionModel extends GlobalActionModel { } FilterActionModel.define({ - title: 'Filter', + title: tval('Filter'), }); FilterActionModel.registerFlow({ key: 'filterSettings', - title: '筛选配置', + title: tval('Filter configuration'), auto: true, steps: { ignoreFieldsNames: { - title: '可筛选字段', + title: tval('Filterable fields'), uiSchema: { ignoreFieldsNames: { type: 'array', @@ -90,7 +91,7 @@ FilterActionModel.registerFlow({ }, 'x-component-props': { mode: 'multiple', - placeholder: '请选择不可筛选的字段', + placeholder: tval('Please select non-filterable fields'), }, }, }, @@ -104,7 +105,7 @@ FilterActionModel.registerFlow({ }, }, defaultValue: { - title: '默认筛选条件', + title: tval('Default filter conditions'), uiSchema: { filter: { type: 'object', @@ -141,7 +142,7 @@ FilterActionModel.registerFlow({ FilterActionModel.registerFlow({ key: 'handleSubmit', - title: '提交', + title: tval('Submit'), on: { eventName: 'submit', }, @@ -162,7 +163,7 @@ FilterActionModel.registerFlow({ FilterActionModel.registerFlow({ key: 'handleReset', - title: '重置', + title: tval('Reset'), on: { eventName: 'reset', }, @@ -183,7 +184,7 @@ FilterActionModel.registerFlow({ FilterActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, diff --git a/packages/core/client/src/flow/models/actions/RefreshActionModel.tsx b/packages/core/client/src/flow/models/actions/RefreshActionModel.tsx index ddc3992870..20d5c62866 100644 --- a/packages/core/client/src/flow/models/actions/RefreshActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/RefreshActionModel.tsx @@ -8,31 +8,33 @@ */ import { ButtonProps } from 'antd'; +import { tval } from '@nocobase/utils/client'; import { GlobalActionModel } from '../base/ActionModel'; export class RefreshActionModel extends GlobalActionModel { defaultProps: ButtonProps = { - title: 'Refresh', + title: tval('Refresh'), icon: 'ReloadOutlined', }; } RefreshActionModel.define({ - title: 'Refresh', + title: tval('Refresh'), }); RefreshActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, steps: { refresh: { async handler(ctx, params) { + const t = ctx.globals.flowEngine.translate; const currentResource = ctx.shared?.currentBlockModel?.resource; if (!currentResource) { - ctx.globals.message.error('No resource selected for refresh.'); + ctx.globals.message.error(t('No resource selected for refresh')); return; } currentResource.loading = true; diff --git a/packages/core/client/src/flow/models/actions/UpdateRecordActionModel.tsx b/packages/core/client/src/flow/models/actions/UpdateRecordActionModel.tsx index ba9540f7cb..a6c24990c2 100644 --- a/packages/core/client/src/flow/models/actions/UpdateRecordActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/UpdateRecordActionModel.tsx @@ -12,29 +12,30 @@ import { afterSuccessAction } from '../../actions/afterSuccessAction'; import { refreshOnCompleteAction } from '../../actions/refreshOnCompleteAction'; import { secondaryConfirmationAction } from '../../actions/secondaryConfirmationAction'; import { RecordActionModel } from '../base/ActionModel'; +import { tval } from '@nocobase/utils/client'; export class UpdateRecordActionModel extends RecordActionModel { defaultProps: ButtonProps = { type: 'link', - title: 'Update record', + title: tval('Update record'), }; } UpdateRecordActionModel.define({ - title: 'Update record', + title: tval('Update record action'), hide: true, }); UpdateRecordActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, steps: { secondaryConfirmation: secondaryConfirmationAction, update: { - title: '字段赋值', + title: tval('Assign field values'), handler: async (ctx, params) => {}, }, afterSuccess: afterSuccessAction, diff --git a/packages/core/client/src/flow/models/actions/ViewActionModel.tsx b/packages/core/client/src/flow/models/actions/ViewActionModel.tsx index abf2cda6f6..6b57187ea0 100644 --- a/packages/core/client/src/flow/models/actions/ViewActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/ViewActionModel.tsx @@ -8,22 +8,23 @@ */ import type { ButtonProps } from 'antd/es/button'; +import { tval } from '@nocobase/utils/client'; import { RecordActionModel } from '../base/ActionModel'; export class ViewActionModel extends RecordActionModel { defaultProps: ButtonProps = { type: 'link', - title: 'View', + title: tval('View'), }; } ViewActionModel.define({ - title: 'View', + title: tval('View'), }); ViewActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, diff --git a/packages/core/client/src/flow/models/base/ActionModel.tsx b/packages/core/client/src/flow/models/base/ActionModel.tsx index 4678d20d86..cc17978ba5 100644 --- a/packages/core/client/src/flow/models/base/ActionModel.tsx +++ b/packages/core/client/src/flow/models/base/ActionModel.tsx @@ -9,6 +9,7 @@ import { FlowModel } from '@nocobase/flow-engine'; import { Button } from 'antd'; +import { tval } from '@nocobase/utils/client'; import type { ButtonProps } from 'antd/es/button'; import React from 'react'; import { Icon } from '../../../icon/Icon'; @@ -19,7 +20,7 @@ export class ActionModel extends FlowModel { defaultProps: ButtonProps = { type: 'default', - title: 'Action', + title: tval('Action'), }; render() { @@ -37,21 +38,21 @@ export class ActionModel extends FlowModel { ActionModel.registerFlow({ key: 'default', - title: '通用配置', + title: tval('General configuration'), auto: true, steps: { buttonProps: { - title: '编辑按钮', + title: tval('Edit button'), uiSchema: { title: { 'x-decorator': 'FormItem', 'x-component': 'Input', - title: 'Button title', + title: tval('Button title'), }, icon: { 'x-decorator': 'FormItem', 'x-component': IconPicker, - title: 'Button icon', + title: tval('Button icon'), }, }, defaultParams(ctx) { @@ -61,7 +62,7 @@ ActionModel.registerFlow({ }; }, handler(ctx, params) { - ctx.model.setProps('title', params.title); + ctx.model.setProps('title', ctx.globals.flowEngine.translate(params.title)); ctx.model.setProps('icon', params.icon); ctx.model.setProps('onClick', (event) => { ctx.model.dispatchEvent('click', { @@ -78,7 +79,7 @@ export class GlobalActionModel extends ActionModel {} export class RecordActionModel extends ActionModel { defaultProps: ButtonProps = { type: 'link', - children: 'Action', + children: tval('Action'), }; render() { @@ -95,21 +96,21 @@ export class RecordActionModel extends ActionModel { RecordActionModel.registerFlow({ key: 'default', - title: '通用配置', + title: tval('General configuration'), auto: true, steps: { buttonProps: { - title: '编辑按钮', + title: tval('Edit button'), uiSchema: { title: { 'x-decorator': 'FormItem', 'x-component': 'Input', - title: 'Button title', + title: tval('Button title'), }, icon: { 'x-decorator': 'FormItem', 'x-component': IconPicker, - title: 'Button icon', + title: tval('Button icon'), }, }, defaultParams(ctx) { diff --git a/packages/core/client/src/flow/models/base/PageModel.tsx b/packages/core/client/src/flow/models/base/PageModel.tsx index 2076a315fe..50c3d6628c 100644 --- a/packages/core/client/src/flow/models/base/PageModel.tsx +++ b/packages/core/client/src/flow/models/base/PageModel.tsx @@ -14,6 +14,7 @@ import { FlowModel, FlowModelRenderer, FlowSettingsButton } from '@nocobase/flow import { Tabs } from 'antd'; import _ from 'lodash'; import React from 'react'; +import { tval } from '@nocobase/utils/client'; type PageModelStructure = { subModels: { @@ -82,24 +83,24 @@ export class PageModel extends FlowModel { PageModel.registerFlow({ key: 'default', - title: '基础配置', + title: tval('Basic configuration'), auto: true, steps: { settings: { - title: '配置页面', + title: tval('Configure page'), uiSchema: { title: { type: 'string', - title: 'Page Title', + title: tval('Page Title'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter page title', + placeholder: tval('Enter page title'), }, }, enableTabs: { type: 'boolean', - title: 'Enable tabs', + title: tval('Enable tabs'), 'x-decorator': 'FormItem', 'x-component': 'Switch', }, diff --git a/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx b/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx index eb09f84754..e55fb47525 100644 --- a/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx @@ -8,6 +8,7 @@ */ import { ButtonProps } from 'antd'; +import { tval } from '@nocobase/utils/client'; import { ActionModel } from '../../base/ActionModel'; import { DataBlockModel } from '../../base/BlockModel'; import { FormModel } from './FormModel'; @@ -16,14 +17,14 @@ export class FormActionModel extends ActionModel {} export class FormSubmitActionModel extends FormActionModel { defaultProps: ButtonProps = { - children: 'Submit', + children: tval('Submit'), type: 'primary', htmlType: 'submit', }; } FormSubmitActionModel.define({ - title: 'Submit', + title: tval('Submit'), }); FormSubmitActionModel.registerFlow({ diff --git a/packages/core/client/src/flow/models/other-blocks/html/HtmlBlockModel.tsx b/packages/core/client/src/flow/models/other-blocks/html/HtmlBlockModel.tsx index af52471ba7..c274a9561a 100644 --- a/packages/core/client/src/flow/models/other-blocks/html/HtmlBlockModel.tsx +++ b/packages/core/client/src/flow/models/other-blocks/html/HtmlBlockModel.tsx @@ -9,6 +9,7 @@ import { Card } from 'antd'; import React, { createRef } from 'react'; +import { tval } from '@nocobase/utils/client'; import { BlockModel } from '../../base/BlockModel'; function waitForRefCallback(ref: React.RefObject, cb: (el: T) => void, timeout = 3000) { @@ -34,7 +35,7 @@ export class HtmlBlockModel extends BlockModel { } HtmlBlockModel.define({ - title: 'HTML', + title: tval('HTML'), group: 'Content', hide: true, defaultOptions: { @@ -58,7 +59,7 @@ HtmlBlockModel.registerFlow({ uiSchema: { html: { type: 'string', - title: 'HTML 内容', + title: tval('HTML content'), 'x-component': 'Input.TextArea', 'x-component-props': { autoSize: true, diff --git a/packages/core/client/src/locale/en-US.json b/packages/core/client/src/locale/en-US.json index be36dfc761..439b15a50a 100644 --- a/packages/core/client/src/locale/en-US.json +++ b/packages/core/client/src/locale/en-US.json @@ -896,5 +896,28 @@ "Select data blocks to refresh": "Select data blocks to refresh", "After successful submission, the selected data blocks will be automatically refreshed.": "After successful submission, the selected data blocks will be automatically refreshed.", "Reset link expiration": "Reset link expiration", - "Imperative Drawer": "Imperative Drawer" + "Imperative Drawer": "Imperative Drawer", + "Click event": "Click event", + "Form duplicate": "Form duplicate", + "Filter configuration": "Filter configuration", + "Default filter conditions": "Default filter conditions", + "No resource selected for deletion": "No resource selected for deletion", + "No records selected for deletion": "No records selected for deletion", + "Selected records deleted successfully": "Selected records deleted successfully", + "No resource selected for bulk edit": "No resource selected for bulk edit", + "No records selected for bulk edit": "No records selected for bulk edit", + "Successfully": "Successfully", + "No resource selected for refresh": "No resource selected for refresh", + "No resource or record selected for deletion": "No resource or record selected for deletion", + "Record deleted successfully": "Record deleted successfully", + "Please select non-filterable fields": "Please select non-filterable fields", + "Update record action": "Update record", + "Basic configuration": "Basic configuration", + "Configure page": "Configure page", + "Page Title": "Page Title", + "Enter page title": "Enter page title", + "Enable tabs": "Enable tabs", + "HTML content": "HTML content", + "Action": "Action", + "General configuration": "General configuration" } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index dea60651f9..734be299e7 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1140,5 +1140,33 @@ "Calendar Year":"日历年", "Scan to input":"扫码录入", "Disable manual input":"禁止手动输入", - "Imperative Drawer": "命令式抽屉" + "Imperative Drawer": "命令式抽屉", + "Click event": "点击事件", + "Form duplicate": "表单复制", + "Filter configuration": "筛选配置", + "Default filter conditions": "默认筛选条件", + "No resource selected for deletion": "未选择要删除的资源", + "No records selected for deletion": "未选择要删除的记录", + "Selected records deleted successfully": "选中的记录删除成功", + "No resource selected for bulk edit": "未选择要批量编辑的资源", + "No records selected for bulk edit": "未选择要批量编辑的记录", + "Successfully": "成功", + "No resource selected for refresh": "未选择要刷新的资源", + "No resource or record selected for deletion": "未选择要删除的资源或记录", + "Record deleted successfully": "记录删除成功", + "Please select non-filterable fields": "请选择不可筛选的字段", + "Update record action": "更新记录", + "Basic configuration": "基础配置", + "Configure page": "配置页面", + "Page Title": "页面标题", + "Enter page title": "输入页面标题", + "Enable tabs": "启用标签页", + "HTML content": "HTML内容", + "Action": "操作", + "Edit button": "编辑按钮", + "Button title": "按钮标题", + "Button icon": "按钮图标", + "General configuration": "通用配置", + "Duplicate": "复制", + "Refresh": "刷新" } diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx index 2f112d47ff..9e11f28494 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx @@ -355,12 +355,10 @@ const FlowsFloatContextMenuWithModelById: React.FC = observer( extralToolbarItems, }) => { const model = useFlowModelById(uid, modelClassName); + const flowEngine = useFlowEngine(); if (!model) { - // 创建一个临时的 FlowModel 实例用于翻译 - const tempModel = { flowEngine: { t: (key: string) => key } } as FlowModel; - const t = getT(tempModel); - return ; + return ; } return ( diff --git a/packages/core/flow-engine/src/data-source/index.ts b/packages/core/flow-engine/src/data-source/index.ts index 88e1108cbb..b2a1fa9015 100644 --- a/packages/core/flow-engine/src/data-source/index.ts +++ b/packages/core/flow-engine/src/data-source/index.ts @@ -96,7 +96,7 @@ export class DataSource { } get displayName() { - return this.options.displayName ? this.flowEngine?.t(this.options.displayName) : this.key; + return this.options.displayName ? this.flowEngine?.translate(this.options.displayName) : this.key; } get key() { @@ -272,7 +272,7 @@ export class Collection { } get title() { - return this.options.title ? this.flowEngine?.t(this.options.title) : this.name; + return this.options.title ? this.flowEngine?.translate(this.options.title) : this.name; } initInherits() { @@ -413,7 +413,7 @@ export class CollectionField { get title() { const titleValue = this.options?.title || this.options?.uiSchema?.title || this.options.name; - return this.flowEngine?.t(titleValue); + return this.flowEngine?.translate(titleValue); } set title(value: string) { diff --git a/packages/core/flow-engine/src/flowEngine.ts b/packages/core/flow-engine/src/flowEngine.ts index cf3881871c..96e9ce01b3 100644 --- a/packages/core/flow-engine/src/flowEngine.ts +++ b/packages/core/flow-engine/src/flowEngine.ts @@ -47,7 +47,7 @@ export class FlowEngine { constructor() { this.reactView = new ReactView(this); - this.flowSettings.registerScopes({ t: this.t.bind(this) }); + this.flowSettings.registerScopes({ t: this.translate.bind(this) }); } // 注册默认的 FlowModel @@ -87,7 +87,7 @@ export class FlowEngine { * flowEngine.t("前缀 {{ t('User Name') }} 后缀") * flowEngine.t("{{t('Hello {name}', {name: 'John'})}}") */ - public t(keyOrTemplate: string, options?: any): string { + public translate(keyOrTemplate: string, options?: any): string { return this._translationUtil.translate( keyOrTemplate, (key: string, opts?: any) => this.translateKey(key, opts), diff --git a/packages/core/flow-engine/src/types.ts b/packages/core/flow-engine/src/types.ts index 90e72cbf5e..676f4c5d2c 100644 --- a/packages/core/flow-engine/src/types.ts +++ b/packages/core/flow-engine/src/types.ts @@ -11,6 +11,7 @@ import { ISchema } from '@formily/json-schema'; import type { FlowEngine } from './flowEngine'; import type { FlowModel } from './models'; import { ReactView } from './ReactView'; +import { APIClient } from '@nocobase/sdk'; /** * 工具类型:如果 T 是数组类型,则提取数组元素类型;否则返回 T 本身 @@ -99,7 +100,11 @@ export interface FlowContext { reactView: ReactView; stepResults: Record; // Results from previous steps shared: Record; // Shared data within the flow (read/write) - globals: Record; // Global context data (read-only) + globals: Record & { + flowEngine: FlowEngine; + app: any; + api: APIClient; + }; extra: Record; // Extra context passed to applyFlow (read-only) model: TModel; // Current model instance with specific type app: any; // Application instance (required) diff --git a/packages/core/flow-engine/src/utils.ts b/packages/core/flow-engine/src/utils.ts index 7ceb327f27..cc0f401713 100644 --- a/packages/core/flow-engine/src/utils.ts +++ b/packages/core/flow-engine/src/utils.ts @@ -22,10 +22,10 @@ export const FLOW_ENGINE_NAMESPACE = 'flow-engine'; * @returns 翻译函数,自动使用 flow-engine 命名空间 */ export function getT(model: FlowModel): (key: string, options?: any) => string { - if (model.flowEngine?.t) { + if (model.flowEngine?.translate) { return (key: string, options?: any) => { // 自动添加 flow-engine 命名空间 - return model.flowEngine.t(key, { ns: FLOW_ENGINE_NAMESPACE, ...options }); + return model.flowEngine.translate(key, { ns: FLOW_ENGINE_NAMESPACE, ...options }); }; } // 回退到原始键值 From f24be1e3b60878b83409e1273c9cafa8c02f4c45 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 15:17:04 +0800 Subject: [PATCH 05/17] feat: i18n for actions --- .../core/flow-engine/demos/flow-sub-model.tsx | 2 +- .../src/flow/actions/afterSuccessAction.tsx | 40 ++++++---- .../core/client/src/flow/actions/confirm.tsx | 19 ++--- .../client/src/flow/actions/dataScope.tsx | 3 +- .../src/flow/actions/openLinkAction.tsx | 17 +++-- .../src/flow/actions/openModeAction.tsx | 19 ++--- .../core/client/src/flow/actions/openView.tsx | 19 ++--- .../flow/actions/refreshOnCompleteAction.tsx | 18 ++++- .../actions/secondaryConfirmationAction.tsx | 29 +++++--- .../CustomRequestGlobalActionModel.tsx | 74 ++++++++++--------- .../CustomRequestRecordActionModel.tsx | 72 +++++++++--------- .../src/flow/models/base/ActionModel.tsx | 2 +- .../client/src/flow/models/base/PageModel.tsx | 2 +- .../data-blocks/table/TableColumnModel.tsx | 3 +- .../models/data-blocks/table/TableModel.tsx | 17 +++-- .../EditableField/EditableFieldModel.tsx | 35 ++++----- packages/core/client/src/locale/en-US.json | 37 +++++++++- packages/core/client/src/locale/zh-CN.json | 31 ++++++-- 18 files changed, 269 insertions(+), 170 deletions(-) diff --git a/packages/core/client/docs/zh-CN/core/flow-engine/demos/flow-sub-model.tsx b/packages/core/client/docs/zh-CN/core/flow-engine/demos/flow-sub-model.tsx index 88f61bd9d3..44b4137a46 100644 --- a/packages/core/client/docs/zh-CN/core/flow-engine/demos/flow-sub-model.tsx +++ b/packages/core/client/docs/zh-CN/core/flow-engine/demos/flow-sub-model.tsx @@ -28,7 +28,7 @@ SubModel1.registerFlow({ }, }, handler(ctx, params) { - ctx.model.setProps('children', params.title); + ctx.model.setProps('children', ctx.globals.flowEngine.translate(params.title)); }, }, }, diff --git a/packages/core/client/src/flow/actions/afterSuccessAction.tsx b/packages/core/client/src/flow/actions/afterSuccessAction.tsx index 7905d464f8..73e7fa66bc 100644 --- a/packages/core/client/src/flow/actions/afterSuccessAction.tsx +++ b/packages/core/client/src/flow/actions/afterSuccessAction.tsx @@ -1,3 +1,13 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { tval } from '@nocobase/utils/client'; import { useGlobalVariable } from '../../application/hooks/useGlobalVariable'; import { BlocksSelector } from '../../schema-component/antd/action/Action.Designer'; import { useAfterSuccessOptions } from '../../schema-component/antd/action/hooks/useGetAfterSuccessVariablesOptions'; @@ -16,30 +26,30 @@ const useVariableProps = () => { }; export const afterSuccessAction = { - title: '提交成功后', + title: tval('After successful submission'), uiSchema: { successMessage: { - title: 'Popup message', + title: tval('Popup message'), 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': {}, }, manualClose: { - title: 'Message popup close method', + title: tval('Message popup close method'), enum: [ - { label: 'Automatic close', value: false }, - { label: 'Manually close', value: true }, + { label: tval('Automatic close'), value: false }, + { label: tval('Manually close'), value: true }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', 'x-component-props': {}, }, redirecting: { - title: 'Then', + title: tval('Then'), 'x-hidden': true, enum: [ - { label: 'Stay on current page', value: false }, - { label: 'Redirect to', value: true }, + { label: tval('Stay on current page'), value: false }, + { label: tval('Redirect to'), value: true }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', @@ -54,11 +64,11 @@ export const afterSuccessAction = { }, }, actionAfterSuccess: { - title: 'Action after successful submission', + title: tval('Action after successful submission'), enum: [ - { label: 'Stay on the current popup or page', value: 'stay' }, - { label: 'Return to the previous popup or page', value: 'previous' }, - { label: 'Redirect to', value: 'redirect' }, + { label: tval('Stay on the current popup or page'), value: 'stay' }, + { label: tval('Return to the previous popup or page'), value: 'previous' }, + { label: tval('Redirect to'), value: 'redirect' }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', @@ -73,7 +83,7 @@ export const afterSuccessAction = { }, }, redirectTo: { - title: 'Link', + title: tval('Link'), 'x-decorator': 'FormItem', 'x-component': 'Variable.TextArea', // eslint-disable-next-line react-hooks/rules-of-hooks @@ -81,11 +91,11 @@ export const afterSuccessAction = { }, blocksToRefresh: { type: 'array', - title: 'Refresh data blocks', + title: tval('Refresh data blocks'), 'x-decorator': 'FormItem', 'x-use-decorator-props': () => { return { - tooltip: 'After successful submission, the selected data blocks will be automatically refreshed.', + tooltip: tval('After successful submission, the selected data blocks will be automatically refreshed.'), }; }, 'x-component': BlocksSelector, diff --git a/packages/core/client/src/flow/actions/confirm.tsx b/packages/core/client/src/flow/actions/confirm.tsx index 38cd1d51a2..ee4c15fad1 100644 --- a/packages/core/client/src/flow/actions/confirm.tsx +++ b/packages/core/client/src/flow/actions/confirm.tsx @@ -8,41 +8,42 @@ */ import { defineAction } from '@nocobase/flow-engine'; +import { tval } from '@nocobase/utils/client'; export const confirm = defineAction({ name: 'confirm', - title: '{{t("Secondary confirmation")}}', + title: tval('Secondary confirmation'), uiSchema: { enable: { type: 'boolean', - title: '{{t("Enable secondary confirmation")}}', + title: tval('Enable secondary confirmation'), 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, title: { type: 'string', - title: '{{t("Title")}}', - default: '{{t("Delete record")}}', + title: tval('Title'), + default: tval('Delete record'), 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', }, content: { type: 'string', - title: '{{t("Content")}}', - default: '{{t("Are you sure you want to delete it?")}}', + title: tval('Content'), + default: tval('Are you sure you want to delete it?'), 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', }, }, defaultParams: { enable: true, - title: 'Delete record', - content: 'Are you sure you want to delete it?', + title: tval('Delete record'), + content: tval('Are you sure you want to delete it?'), }, async handler(ctx, params) { if (params.enable) { const confirmed = await ctx.globals.modal.confirm({ - title: params.title, + title: ctx.globals.flowEngine.translate(params.title), content: params.content, }); diff --git a/packages/core/client/src/flow/actions/dataScope.tsx b/packages/core/client/src/flow/actions/dataScope.tsx index 96b679fd40..e1030fd9f7 100644 --- a/packages/core/client/src/flow/actions/dataScope.tsx +++ b/packages/core/client/src/flow/actions/dataScope.tsx @@ -9,11 +9,12 @@ import { defineAction, MultiRecordResource, useStepSettingContext } from '@nocobase/flow-engine'; import React from 'react'; +import { tval } from '@nocobase/utils/client'; import { FilterGroup } from '../components/FilterGroup'; export const dataScope = defineAction({ name: 'dataScope', - title: '{{t("Data scope")}}', + title: tval('Data scope'), uiSchema: { filter: { type: 'object', diff --git a/packages/core/client/src/flow/actions/openLinkAction.tsx b/packages/core/client/src/flow/actions/openLinkAction.tsx index 08138d2ab5..acc0268bd1 100644 --- a/packages/core/client/src/flow/actions/openLinkAction.tsx +++ b/packages/core/client/src/flow/actions/openLinkAction.tsx @@ -8,22 +8,23 @@ */ import { css } from '@emotion/css'; +import { tval } from '@nocobase/utils/client'; import { Variable } from '../../schema-component/antd/variable/Variable'; export const openLinkAction = { - title: '{{t("Edit link")}}', + title: tval('Edit link'), uiSchema: { url: { - title: 'URL', + title: tval('URL'), 'x-decorator': 'FormItem', 'x-component': Variable.TextArea, - description: 'Do not concatenate search params in the URL', + description: tval('Do not concatenate search params in the URL'), }, params: { type: 'array', 'x-component': 'ArrayItems', 'x-decorator': 'FormItem', - title: `{{t("Search parameters")}}`, + title: tval('Search parameters'), items: { type: 'object', properties: { @@ -48,7 +49,7 @@ export const openLinkAction = { 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: `{{t("Name")}}`, + placeholder: tval('Name'), }, }, value: { @@ -56,7 +57,7 @@ export const openLinkAction = { 'x-decorator': 'FormItem', 'x-component': Variable.TextArea, 'x-component-props': { - placeholder: `{{t("Value")}}`, + placeholder: tval('Value'), useTypedConstant: true, changeOnSelect: true, }, @@ -73,14 +74,14 @@ export const openLinkAction = { properties: { add: { type: 'void', - title: '{{t("Add parameter")}}', + title: tval('Add parameter'), 'x-component': 'ArrayItems.Addition', }, }, }, openInNewWindow: { type: 'boolean', - 'x-content': '{{t("Open in new window")}}', + 'x-content': tval('Open in new window'), 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, diff --git a/packages/core/client/src/flow/actions/openModeAction.tsx b/packages/core/client/src/flow/actions/openModeAction.tsx index 0e3cfa934c..9feda838f2 100644 --- a/packages/core/client/src/flow/actions/openModeAction.tsx +++ b/packages/core/client/src/flow/actions/openModeAction.tsx @@ -8,28 +8,29 @@ */ import React from 'react'; +import { tval } from '@nocobase/utils/client'; import { FlowPage } from '../FlowPage'; export const openModeAction = { - title: '{{t("Open mode")}}', + title: tval('Open mode'), uiSchema: { mode: { type: 'string', - title: '{{t("Open mode")}}', + title: tval('Open mode'), enum: [ - { label: '{{t("Drawer")}}', value: 'drawer' }, - { label: '{{t("Modal")}}', value: 'modal' }, + { label: tval('Drawer'), value: 'drawer' }, + { label: tval('Modal'), value: 'modal' }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', }, size: { type: 'string', - title: '{{t("Popup size")}}', + title: tval('Popup size'), enum: [ - { label: '{{t("Small")}}', value: 'small' }, - { label: '{{t("Medium")}}', value: 'medium' }, - { label: '{{t("Large")}}', value: 'large' }, + { label: tval('Small'), value: 'small' }, + { label: tval('Medium'), value: 'medium' }, + { label: tval('Large'), value: 'large' }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', @@ -66,7 +67,7 @@ export const openModeAction = { }; currentDrawer = ctx.globals[params.mode].open({ - title: '{{t("Imperative Drawer")}}', + title: tval('Imperative Drawer'), width: sizeToWidthMap[params.size], content: , }); diff --git a/packages/core/client/src/flow/actions/openView.tsx b/packages/core/client/src/flow/actions/openView.tsx index f325dfda41..d9881ad499 100644 --- a/packages/core/client/src/flow/actions/openView.tsx +++ b/packages/core/client/src/flow/actions/openView.tsx @@ -8,31 +8,32 @@ */ import { defineAction } from '@nocobase/flow-engine'; +import { tval } from '@nocobase/utils/client'; import React from 'react'; import { FlowPage } from '../FlowPage'; export const openView = defineAction({ name: 'openView', - title: '打开方式配置', + title: tval('Open mode configuration'), uiSchema: { mode: { type: 'string', - title: '打开方式', + title: tval('Open mode'), enum: [ - { label: 'Drawer', value: 'drawer' }, - { label: 'Dialog', value: 'dialog' }, - { label: 'Page', value: 'page' }, + { label: tval('Drawer'), value: 'drawer' }, + { label: tval('Dialog'), value: 'dialog' }, + { label: tval('Page'), value: 'page' }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', }, size: { type: 'string', - title: '弹窗尺寸', + title: tval('Popup size'), enum: [ - { label: '小', value: 'small' }, - { label: '中', value: 'medium' }, - { label: '大', value: 'large' }, + { label: tval('Small'), value: 'small' }, + { label: tval('Medium'), value: 'medium' }, + { label: tval('Large'), value: 'large' }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', diff --git a/packages/core/client/src/flow/actions/refreshOnCompleteAction.tsx b/packages/core/client/src/flow/actions/refreshOnCompleteAction.tsx index a6bffdfd66..2787345ba8 100644 --- a/packages/core/client/src/flow/actions/refreshOnCompleteAction.tsx +++ b/packages/core/client/src/flow/actions/refreshOnCompleteAction.tsx @@ -1,9 +1,20 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { tval } from '@nocobase/utils/client'; + export const refreshOnCompleteAction = { - title: '执行后刷新数据', + title: tval('Refresh data after execution'), uiSchema: { enable: { type: 'boolean', - title: 'Enable refresh', + title: tval('Enable refresh'), 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, @@ -16,7 +27,8 @@ export const refreshOnCompleteAction = { async handler(ctx, params) { if (params.enable) { await ctx.extra.currentResource.refresh(); - ctx.globals.message.success('Data refreshed successfully.'); + const t = ctx.globals.flowEngine.translate; + ctx.globals.message.success(t('Data refreshed successfully')); } }, }; diff --git a/packages/core/client/src/flow/actions/secondaryConfirmationAction.tsx b/packages/core/client/src/flow/actions/secondaryConfirmationAction.tsx index 0b5db046b5..30fe3c77cb 100644 --- a/packages/core/client/src/flow/actions/secondaryConfirmationAction.tsx +++ b/packages/core/client/src/flow/actions/secondaryConfirmationAction.tsx @@ -1,23 +1,34 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { tval } from '@nocobase/utils/client'; + export const secondaryConfirmationAction = { - title: '二次确认', + title: tval('Secondary confirmation'), uiSchema: { enable: { type: 'boolean', - title: 'Enable secondary confirmation', + title: tval('Enable secondary confirmation'), 'x-decorator': 'FormItem', 'x-component': 'Checkbox', }, title: { type: 'string', - title: 'Title', - default: 'Delete record', + title: tval('Title'), + default: tval('Delete record'), 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', }, content: { type: 'string', - title: 'Content', - default: 'Are you sure you want to delete it?', + title: tval('Content'), + default: tval('Are you sure you want to delete it?'), 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', }, @@ -25,14 +36,14 @@ export const secondaryConfirmationAction = { defaultParams(ctx) { return { enable: true, - title: 'Delete record', - content: 'Are you sure you want to delete it?', + title: tval('Delete record'), + content: tval('Are you sure you want to delete it?'), }; }, async handler(ctx, params) { if (params.enable) { const confirmed = await ctx.globals.modal.confirm({ - title: params.title, + title: ctx.globals.flowEngine.translate(params.title), content: params.content, }); diff --git a/packages/core/client/src/flow/models/actions/CustomRequestGlobalActionModel.tsx b/packages/core/client/src/flow/models/actions/CustomRequestGlobalActionModel.tsx index 33c6c203d8..0cd53ae4e7 100644 --- a/packages/core/client/src/flow/models/actions/CustomRequestGlobalActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/CustomRequestGlobalActionModel.tsx @@ -8,6 +8,7 @@ */ import type { ButtonProps } from 'antd/es/button'; +import { tval } from '@nocobase/utils/client'; import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable'; import { BlocksSelector } from '../../../schema-component/antd/action/Action.Designer'; import { useAfterSuccessOptions } from '../../../schema-component/antd/action/hooks/useGetAfterSuccessVariablesOptions'; @@ -17,12 +18,12 @@ import { GlobalActionModel } from '../base/ActionModel'; export class CustomRequestGlobalActionModel extends GlobalActionModel { defaultProps: ButtonProps = { - title: 'Custom request', + title: tval('Custom request'), }; } CustomRequestGlobalActionModel.define({ - title: 'Custom request', + title: tval('Custom request'), hide: true, }); @@ -41,22 +42,23 @@ const useVariableProps = () => { CustomRequestGlobalActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, steps: { secondaryConfirmation: secondaryConfirmationAction, request: { - title: '请求设置', + title: tval('Request settings'), uiSchema: { method: { type: 'string', required: true, - title: 'HTTP method', + title: tval('HTTP method'), 'x-decorator-props': { - tooltip: + tooltip: tval( 'When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data', + ), }, 'x-decorator': 'FormItem', 'x-component': 'Select', @@ -77,20 +79,20 @@ CustomRequestGlobalActionModel.registerFlow({ url: { type: 'string', required: true, - title: 'URL', + title: tval('URL'), 'x-decorator': 'FormItem', 'x-component': 'Variable.TextArea', 'x-use-component-props': useVariableProps, 'x-component-props': { - placeholder: 'https://www.nocobase.com', + placeholder: tval('https://www.nocobase.com'), }, }, headers: { type: 'array', 'x-component': 'ArrayItems', 'x-decorator': 'FormItem', - title: 'Headers', - description: '"Content-Type" only support "application/json", and no need to specify', + title: tval('Headers'), + description: tval('"Content-Type" only support "application/json", and no need to specify'), items: { type: 'object', properties: { @@ -103,7 +105,7 @@ CustomRequestGlobalActionModel.registerFlow({ 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Name', + placeholder: tval('Name'), }, }, value: { @@ -124,7 +126,7 @@ CustomRequestGlobalActionModel.registerFlow({ properties: { add: { type: 'void', - title: 'Add request header', + title: tval('Add request header'), 'x-component': 'ArrayItems.Addition', }, }, @@ -133,7 +135,7 @@ CustomRequestGlobalActionModel.registerFlow({ type: 'array', 'x-component': 'ArrayItems', 'x-decorator': 'FormItem', - title: 'Parameters', + title: tval('Parameters'), items: { type: 'object', properties: { @@ -146,7 +148,7 @@ CustomRequestGlobalActionModel.registerFlow({ 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Name', + placeholder: tval('Name'), }, }, value: { @@ -167,14 +169,14 @@ CustomRequestGlobalActionModel.registerFlow({ properties: { add: { type: 'void', - title: 'Add parameter', + title: tval('Add parameter'), 'x-component': 'ArrayItems.Addition', }, }, }, data: { type: 'string', - title: 'Body', + title: tval('Body'), 'x-decorator': 'FormItem', 'x-decorator-props': {}, 'x-component': 'Variable.JSON', @@ -188,13 +190,13 @@ CustomRequestGlobalActionModel.registerFlow({ autoSize: { minRows: 10, }, - placeholder: 'Input request data', + placeholder: tval('Input request data'), }, - description: 'Only support standard JSON data', + description: tval('Only support standard JSON data'), }, timeout: { type: 'number', - title: 'Timeout config', + title: tval('Timeout config'), 'x-decorator': 'FormItem', 'x-decorator-props': {}, 'x-component': 'InputNumber', @@ -207,7 +209,7 @@ CustomRequestGlobalActionModel.registerFlow({ }, responseType: { type: 'string', - title: 'Response type', + title: tval('Response type'), 'x-decorator': 'FormItem', 'x-decorator-props': {}, 'x-component': 'Select', @@ -220,35 +222,35 @@ CustomRequestGlobalActionModel.registerFlow({ }, async handler(ctx, params) { ctx.globals.modal({ - title: 'TODO: Custom request action handler', + title: tval('TODO: Custom request action handler'), }); }, }, afterSuccess: { - title: '提交成功后', + title: tval('After successful submission'), uiSchema: { successMessage: { - title: 'Popup message', + title: tval('Popup message'), 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': {}, }, manualClose: { - title: 'Message popup close method', + title: tval('Message popup close method'), enum: [ - { label: 'Automatic close', value: false }, - { label: 'Manually close', value: true }, + { label: tval('Automatic close'), value: false }, + { label: tval('Manually close'), value: true }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', 'x-component-props': {}, }, redirecting: { - title: 'Then', + title: tval('Then'), 'x-hidden': true, enum: [ - { label: 'Stay on current page', value: false }, - { label: 'Redirect to', value: true }, + { label: tval('Stay on current page'), value: false }, + { label: tval('Redirect to'), value: true }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', @@ -263,11 +265,11 @@ CustomRequestGlobalActionModel.registerFlow({ }, }, actionAfterSuccess: { - title: 'Action after successful submission', + title: tval('Action after successful submission'), enum: [ - { label: 'Stay on the current popup or page', value: 'stay' }, - { label: 'Return to the previous popup or page', value: 'previous' }, - { label: 'Redirect to', value: 'redirect' }, + { label: tval('Stay on the current popup or page'), value: 'stay' }, + { label: tval('Return to the previous popup or page'), value: 'previous' }, + { label: tval('Redirect to'), value: 'redirect' }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', @@ -282,7 +284,7 @@ CustomRequestGlobalActionModel.registerFlow({ }, }, redirectTo: { - title: 'Link', + title: tval('Link'), 'x-decorator': 'FormItem', 'x-component': 'Variable.TextArea', // eslint-disable-next-line react-hooks/rules-of-hooks @@ -290,11 +292,11 @@ CustomRequestGlobalActionModel.registerFlow({ }, blocksToRefresh: { type: 'array', - title: 'Refresh data blocks', + title: tval('Refresh data blocks'), 'x-decorator': 'FormItem', 'x-use-decorator-props': () => { return { - tooltip: 'After successful submission, the selected data blocks will be automatically refreshed.', + tooltip: tval('After successful submission, the selected data blocks will be automatically refreshed.'), }; }, 'x-component': BlocksSelector, diff --git a/packages/core/client/src/flow/models/actions/CustomRequestRecordActionModel.tsx b/packages/core/client/src/flow/models/actions/CustomRequestRecordActionModel.tsx index bed776ee03..042cf5714a 100644 --- a/packages/core/client/src/flow/models/actions/CustomRequestRecordActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/CustomRequestRecordActionModel.tsx @@ -8,6 +8,7 @@ */ import type { ButtonProps } from 'antd/es/button'; +import { tval } from '@nocobase/utils/client'; import { useGlobalVariable } from '../../../application/hooks/useGlobalVariable'; import { BlocksSelector } from '../../../schema-component/antd/action/Action.Designer'; import { useAfterSuccessOptions } from '../../../schema-component/antd/action/hooks/useGetAfterSuccessVariablesOptions'; @@ -18,7 +19,7 @@ import { RecordActionModel } from '../base/ActionModel'; export class CustomRequestRecordActionModel extends RecordActionModel { defaultProps: ButtonProps = { type: 'link', - title: 'Custom request', + title: tval('Custom request'), }; } @@ -42,22 +43,23 @@ const useVariableProps = () => { CustomRequestRecordActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, steps: { secondaryConfirmation: secondaryConfirmationAction, request: { - title: '请求设置', + title: tval('Request settings'), uiSchema: { method: { type: 'string', required: true, - title: 'HTTP method', + title: tval('HTTP method'), 'x-decorator-props': { - tooltip: + tooltip: tval( 'When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data', + ), }, 'x-decorator': 'FormItem', 'x-component': 'Select', @@ -78,20 +80,20 @@ CustomRequestRecordActionModel.registerFlow({ url: { type: 'string', required: true, - title: 'URL', + title: tval('URL'), 'x-decorator': 'FormItem', 'x-component': 'Variable.TextArea', 'x-use-component-props': useVariableProps, 'x-component-props': { - placeholder: 'https://www.nocobase.com', + placeholder: tval('https://www.nocobase.com'), }, }, headers: { type: 'array', 'x-component': 'ArrayItems', 'x-decorator': 'FormItem', - title: 'Headers', - description: '"Content-Type" only support "application/json", and no need to specify', + title: tval('Headers'), + description: tval('"Content-Type" only support "application/json", and no need to specify'), items: { type: 'object', properties: { @@ -104,7 +106,7 @@ CustomRequestRecordActionModel.registerFlow({ 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Name', + placeholder: tval('Name'), }, }, value: { @@ -125,7 +127,7 @@ CustomRequestRecordActionModel.registerFlow({ properties: { add: { type: 'void', - title: 'Add request header', + title: tval('Add request header'), 'x-component': 'ArrayItems.Addition', }, }, @@ -134,7 +136,7 @@ CustomRequestRecordActionModel.registerFlow({ type: 'array', 'x-component': 'ArrayItems', 'x-decorator': 'FormItem', - title: 'Parameters', + title: tval('Parameters'), items: { type: 'object', properties: { @@ -147,7 +149,7 @@ CustomRequestRecordActionModel.registerFlow({ 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Name', + placeholder: tval('Name'), }, }, value: { @@ -168,14 +170,14 @@ CustomRequestRecordActionModel.registerFlow({ properties: { add: { type: 'void', - title: 'Add parameter', + title: tval('Add parameter'), 'x-component': 'ArrayItems.Addition', }, }, }, data: { type: 'string', - title: 'Body', + title: tval('Body'), 'x-decorator': 'FormItem', 'x-decorator-props': {}, 'x-component': 'Variable.JSON', @@ -189,13 +191,13 @@ CustomRequestRecordActionModel.registerFlow({ autoSize: { minRows: 10, }, - placeholder: 'Input request data', + placeholder: tval('Input request data'), }, - description: 'Only support standard JSON data', + description: tval('Only support standard JSON data'), }, timeout: { type: 'number', - title: 'Timeout config', + title: tval('Timeout config'), 'x-decorator': 'FormItem', 'x-decorator-props': {}, 'x-component': 'InputNumber', @@ -208,7 +210,7 @@ CustomRequestRecordActionModel.registerFlow({ }, responseType: { type: 'string', - title: 'Response type', + title: tval('Response type'), 'x-decorator': 'FormItem', 'x-decorator-props': {}, 'x-component': 'Select', @@ -221,35 +223,35 @@ CustomRequestRecordActionModel.registerFlow({ }, async handler(ctx, params) { ctx.globals.modal({ - title: 'TODO: Custom request action handler', + title: tval('TODO: Custom request action handler'), }); }, }, afterSuccess: { - title: '提交成功后', + title: tval('After successful submission'), uiSchema: { successMessage: { - title: 'Popup message', + title: tval('Popup message'), 'x-decorator': 'FormItem', 'x-component': 'Input.TextArea', 'x-component-props': {}, }, manualClose: { - title: 'Message popup close method', + title: tval('Message popup close method'), enum: [ - { label: 'Automatic close', value: false }, - { label: 'Manually close', value: true }, + { label: tval('Automatic close'), value: false }, + { label: tval('Manually close'), value: true }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', 'x-component-props': {}, }, redirecting: { - title: 'Then', + title: tval('Then'), 'x-hidden': true, enum: [ - { label: 'Stay on current page', value: false }, - { label: 'Redirect to', value: true }, + { label: tval('Stay on current page'), value: false }, + { label: tval('Redirect to'), value: true }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', @@ -264,11 +266,11 @@ CustomRequestRecordActionModel.registerFlow({ }, }, actionAfterSuccess: { - title: 'Action after successful submission', + title: tval('Action after successful submission'), enum: [ - { label: 'Stay on the current popup or page', value: 'stay' }, - { label: 'Return to the previous popup or page', value: 'previous' }, - { label: 'Redirect to', value: 'redirect' }, + { label: tval('Stay on the current popup or page'), value: 'stay' }, + { label: tval('Return to the previous popup or page'), value: 'previous' }, + { label: tval('Redirect to'), value: 'redirect' }, ], 'x-decorator': 'FormItem', 'x-component': 'Radio.Group', @@ -283,7 +285,7 @@ CustomRequestRecordActionModel.registerFlow({ }, }, redirectTo: { - title: 'Link', + title: tval('Link'), 'x-decorator': 'FormItem', 'x-component': 'Variable.TextArea', // eslint-disable-next-line react-hooks/rules-of-hooks @@ -291,11 +293,11 @@ CustomRequestRecordActionModel.registerFlow({ }, blocksToRefresh: { type: 'array', - title: 'Refresh data blocks', + title: tval('Refresh data blocks'), 'x-decorator': 'FormItem', 'x-use-decorator-props': () => { return { - tooltip: 'After successful submission, the selected data blocks will be automatically refreshed.', + tooltip: tval('After successful submission, the selected data blocks will be automatically refreshed.'), }; }, 'x-component': BlocksSelector, diff --git a/packages/core/client/src/flow/models/base/ActionModel.tsx b/packages/core/client/src/flow/models/base/ActionModel.tsx index cc17978ba5..f9b9d85682 100644 --- a/packages/core/client/src/flow/models/base/ActionModel.tsx +++ b/packages/core/client/src/flow/models/base/ActionModel.tsx @@ -127,7 +127,7 @@ RecordActionModel.registerFlow({ if (!currentBlockModel) { throw new Error('Current block model is not set in shared context'); } - ctx.model.setProps('title', params.title); + ctx.model.setProps('title', ctx.globals.flowEngine.translate(params.title)); ctx.model.setProps('icon', params.icon); ctx.model.setProps('onClick', (event) => { ctx.model.dispatchEvent('click', { diff --git a/packages/core/client/src/flow/models/base/PageModel.tsx b/packages/core/client/src/flow/models/base/PageModel.tsx index 50c3d6628c..5dbf6a45c5 100644 --- a/packages/core/client/src/flow/models/base/PageModel.tsx +++ b/packages/core/client/src/flow/models/base/PageModel.tsx @@ -112,7 +112,7 @@ PageModel.registerFlow({ }; }, async handler(ctx, params) { - ctx.model.setProps('title', params.title); + ctx.model.setProps('title', ctx.globals.flowEngine.translate(params.title)); ctx.model.setProps('enableTabs', params.enableTabs); if (ctx.shared.currentDrawer) { diff --git a/packages/core/client/src/flow/models/data-blocks/table/TableColumnModel.tsx b/packages/core/client/src/flow/models/data-blocks/table/TableColumnModel.tsx index e922ac21e1..ee43e50965 100644 --- a/packages/core/client/src/flow/models/data-blocks/table/TableColumnModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/table/TableColumnModel.tsx @@ -119,7 +119,8 @@ TableColumnModel.registerFlow({ }; }, handler(ctx, params) { - ctx.model.setProps('title', params.title || ctx.model.collectionField?.title); + const title = ctx.globals.flowEngine.translate(params.title || ctx.model.collectionField?.title); + ctx.model.setProps('title', title); }, }, editTooltip: { diff --git a/packages/core/client/src/flow/models/data-blocks/table/TableModel.tsx b/packages/core/client/src/flow/models/data-blocks/table/TableModel.tsx index be8b887bf6..32f036480b 100644 --- a/packages/core/client/src/flow/models/data-blocks/table/TableModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/table/TableModel.tsx @@ -15,6 +15,7 @@ import { Card, Space, Spin, Table } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; import React, { useRef } from 'react'; +import { tval } from '@nocobase/utils/client'; import { ActionModel } from '../../base/ActionModel'; import { DataBlockModel } from '../../base/BlockModel'; import { QuickEditForm } from '../form/QuickEditForm'; @@ -77,7 +78,7 @@ export class TableModel extends DataBlockModel { appendItems={[ { key: 'actions', - label: 'Actions column', + label: tval('Actions column'), createModelOptions: { use: 'TableActionsColumnModel', }, @@ -236,20 +237,20 @@ TableModel.registerFlow({ uiSchema: { dataSourceKey: { type: 'string', - title: 'Data Source Key', + title: tval('Data Source Key'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter data source key', + placeholder: tval('Enter data source key'), }, }, collectionName: { type: 'string', - title: 'Collection Name', + title: tval('Collection Name'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter collection name', + placeholder: tval('Enter collection name'), }, }, }, @@ -272,7 +273,7 @@ TableModel.registerFlow({ }, }, editPageSize: { - title: 'Edit page size', + title: tval('Edit page size'), uiSchema: { pageSize: { 'x-component': 'Select', @@ -301,13 +302,13 @@ TableModel.registerFlow({ }, dataScope: { use: 'dataScope', - title: '设置数据范围', + title: tval('Set data scope'), }, }, }); TableModel.define({ - title: 'Table', + title: tval('Table'), group: 'Content', defaultOptions: { use: 'TableModel', diff --git a/packages/core/client/src/flow/models/fields/EditableField/EditableFieldModel.tsx b/packages/core/client/src/flow/models/fields/EditableField/EditableFieldModel.tsx index e1a047fc95..3f58e1c21c 100644 --- a/packages/core/client/src/flow/models/fields/EditableField/EditableFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/EditableField/EditableFieldModel.tsx @@ -12,6 +12,7 @@ import type { FieldPatternTypes, FieldValidator } from '@formily/core'; import { Field, Form } from '@formily/core'; import { FieldContext } from '@formily/react'; import React from 'react'; +import { tval } from '@nocobase/utils/client'; import { FieldModel } from '../../base/FieldModel'; import { ReactiveField } from '../../../formily/ReactiveField'; import { FormModel } from '../..'; @@ -107,7 +108,7 @@ export class EditableFieldModel extends FieldModel { EditableFieldModel.registerFlow({ key: 'init', auto: true, - title: 'Basic', + title: tval('Basic'), sort: 150, steps: { createField: { @@ -125,13 +126,13 @@ EditableFieldModel.registerFlow({ }, }, editTitle: { - title: 'Edit Title', + title: tval('Edit Title'), uiSchema: { title: { 'x-component': 'Input', 'x-decorator': 'FormItem', 'x-component-props': { - placeholder: 'Enter field title', + placeholder: tval('Enter field title'), }, }, }, @@ -145,7 +146,7 @@ EditableFieldModel.registerFlow({ }, }, initialValue: { - title: 'Set default value', + title: tval('Set default value'), uiSchema: { defaultValue: { 'x-component': 'Input', @@ -158,14 +159,14 @@ EditableFieldModel.registerFlow({ }, }, required: { - title: 'Required', + title: tval('Required'), uiSchema: { required: { 'x-component': 'Switch', 'x-decorator': 'FormItem', 'x-component-props': { - checkedChildren: 'Yes', - unCheckedChildren: 'No', + checkedChildren: tval('Yes'), + unCheckedChildren: tval('No'), }, }, }, @@ -174,14 +175,14 @@ EditableFieldModel.registerFlow({ }, }, displayLabel: { - title: 'Display label', + title: tval('Display label'), uiSchema: { displayLabel: { 'x-component': 'Switch', 'x-decorator': 'FormItem', 'x-component-props': { - checkedChildren: 'Yes', - unCheckedChildren: 'No', + checkedChildren: tval('Yes'), + unCheckedChildren: tval('No'), }, }, }, @@ -193,7 +194,7 @@ EditableFieldModel.registerFlow({ }, }, editDescription: { - title: 'Edit description', + title: tval('Edit description'), uiSchema: { description: { 'x-component': 'Input.TextArea', @@ -205,7 +206,7 @@ EditableFieldModel.registerFlow({ }, }, editTooltip: { - title: 'Edit tooltip', + title: tval('Edit tooltip'), uiSchema: { tooltip: { 'x-component': 'Input.TextArea', @@ -217,7 +218,7 @@ EditableFieldModel.registerFlow({ }, }, pattern: { - title: 'Pattern', + title: tval('Pattern'), uiSchema: { pattern: { 'x-component': 'Select', @@ -225,19 +226,19 @@ EditableFieldModel.registerFlow({ enum: [ { value: 'editable', - label: 'Editable', + label: tval('Editable'), }, { value: 'disabled', - label: 'Disabled', + label: tval('Disabled'), }, { value: 'readOnly', - label: 'ReadOnly', + label: tval('ReadOnly'), }, { value: 'readPretty', - label: 'ReadPretty', + label: tval('ReadPretty'), }, ], }, diff --git a/packages/core/client/src/locale/en-US.json b/packages/core/client/src/locale/en-US.json index 439b15a50a..dc4d2f7c2e 100644 --- a/packages/core/client/src/locale/en-US.json +++ b/packages/core/client/src/locale/en-US.json @@ -919,5 +919,40 @@ "Enable tabs": "Enable tabs", "HTML content": "HTML content", "Action": "Action", - "General configuration": "General configuration" + "General configuration": "General configuration", + "Open mode configuration": "Open mode configuration", + "Medium": "Medium", + "Refresh data after execution": "Refresh data after execution", + "Enable refresh": "Enable refresh", + "Data refreshed successfully": "Data refreshed successfully", + "Secondary confirmation": "Secondary confirmation", + "Content": "Content", + "Edit link": "Edit link", + "URL": "URL", + "Do not concatenate search params in the URL": "Do not concatenate search params in the URL", + "Search parameters": "Search parameters", + "Add parameter": "Add parameter", + "When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data": "When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data", + "\"Content-Type\" only support \"application/json\", and no need to specify": "\"Content-Type\" only support \"application/json\", and no need to specify", + "Add request header": "Add request header", + "Input request data": "Input request data", + "Only support standard JSON data": "Only support standard JSON data", + "Timeout config": "Timeout config", + "Response type": "Response type", + "Action after successful submission": "Action after successful submission", + "Stay on the current popup or page": "Stay on the current popup or page", + "Return to the previous popup or page": "Return to the previous popup or page", + "Actions column": "Actions column", + "Data Source Key": "Data Source Key", + "Enter data source key": "Enter data source key", + "Collection Name": "Collection Name", + "Enter collection name": "Enter collection name", + "Edit page size": "Edit page size", + "Set data scope": "Set data scope", + "Edit Title": "Edit Title", + "Enter field title": "Enter field title", + "Display label": "Display label", + "ReadOnly": "ReadOnly", + "ReadPretty": "ReadPretty", + "Modal": "Modal" } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index 734be299e7..37e97001ad 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1162,11 +1162,30 @@ "Enter page title": "输入页面标题", "Enable tabs": "启用标签页", "HTML content": "HTML内容", - "Action": "操作", - "Edit button": "编辑按钮", - "Button title": "按钮标题", - "Button icon": "按钮图标", "General configuration": "通用配置", - "Duplicate": "复制", - "Refresh": "刷新" + "Open mode configuration": "打开方式配置", + "Medium": "中", + "Refresh data after execution": "执行后刷新数据", + "Enable refresh": "启用刷新", + "Data refreshed successfully": "数据刷新成功", + "When the HTTP method is Post, Put or Patch, and this custom request inside the form, the request body will be automatically filled in with the form data": "当 HTTP 方法为 Post、Put 或 Patch,且此自定义请求在表单内时,请求体将自动填充表单数据", + "\"Content-Type\" only support \"application/json\", and no need to specify": "\"Content-Type\" 仅支持 \"application/json\",无需指定", + "Add request header": "添加请求头", + "Input request data": "输入请求数据", + "Only support standard JSON data": "仅支持标准 JSON 数据", + "Timeout config": "超时配置", + "Response type": "响应类型", + "Actions column": "操作列", + "Data Source Key": "数据源键", + "Enter data source key": "输入数据源键", + "Collection Name": "数据表名称", + "Enter collection name": "输入数据表名称", + "Edit page size": "编辑页面大小", + "Set data scope": "设置数据范围", + "Edit Title": "编辑标题", + "Enter field title": "输入字段标题", + "Display label": "显示标签", + "ReadOnly": "只读", + "ReadPretty": "美观只读", + "Modal": "对话框" } From f94ac8ef2c1968c15c586d646ff0193a5260d441 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 15:32:11 +0800 Subject: [PATCH 06/17] feat: i18n for flow action models --- .../client/src/flow/models/actions/BulkEditActionModel.tsx | 4 ++-- .../flow/models/actions/CustomRequestRecordActionModel.tsx | 4 ++-- .../client/src/flow/models/actions/FilterActionModel.tsx | 5 +++-- .../src/flow/models/actions/LinkGlobalActionModel.tsx | 7 ++++--- .../src/flow/models/actions/LinkRecordActionModel.tsx | 7 ++++--- .../src/flow/models/actions/PopupRecordActionModel.tsx | 7 ++++--- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx b/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx index e2cb9b4774..2310ec0a1f 100644 --- a/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx @@ -15,13 +15,13 @@ import { GlobalActionModel } from '../base/ActionModel'; export class BulkEditActionModel extends GlobalActionModel { defaultProps: ButtonProps = { - title: 'Bulk edit', + title: tval('Bulk edit'), icon: 'EditOutlined', }; } BulkEditActionModel.define({ - title: 'Bulk edit', + title: tval('Bulk edit'), hide: true, }); diff --git a/packages/core/client/src/flow/models/actions/CustomRequestRecordActionModel.tsx b/packages/core/client/src/flow/models/actions/CustomRequestRecordActionModel.tsx index 042cf5714a..1901d9fbfa 100644 --- a/packages/core/client/src/flow/models/actions/CustomRequestRecordActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/CustomRequestRecordActionModel.tsx @@ -24,7 +24,7 @@ export class CustomRequestRecordActionModel extends RecordActionModel { } CustomRequestRecordActionModel.define({ - title: 'Custom request', + title: tval('Custom request'), hide: true, }); @@ -185,7 +185,7 @@ CustomRequestRecordActionModel.registerFlow({ scope: '{{useCustomRequestVariableOptions}}', fieldNames: { value: 'name', - label: 'title', + label: tval('title'), }, changeOnSelect: true, autoSize: { diff --git a/packages/core/client/src/flow/models/actions/FilterActionModel.tsx b/packages/core/client/src/flow/models/actions/FilterActionModel.tsx index b33572aa6a..9f981d2af3 100644 --- a/packages/core/client/src/flow/models/actions/FilterActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/FilterActionModel.tsx @@ -20,14 +20,15 @@ const FilterContent: FC<{ value: any }> = (props) => { const currentBlockModel = modelInstance.ctx.shared.currentBlockModel as DataBlockModel; const fields = currentBlockModel.collection.getFields(); const ignoreFieldsNames = modelInstance.props.ignoreFieldsNames || []; + const t = modelInstance.flowEngine.translate; return ( <> - + diff --git a/packages/core/client/src/flow/models/actions/LinkGlobalActionModel.tsx b/packages/core/client/src/flow/models/actions/LinkGlobalActionModel.tsx index 54012b4118..af59f69efd 100644 --- a/packages/core/client/src/flow/models/actions/LinkGlobalActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/LinkGlobalActionModel.tsx @@ -8,22 +8,23 @@ */ import type { ButtonProps } from 'antd'; +import { tval } from '@nocobase/utils/client'; import { GlobalActionModel } from '../base/ActionModel'; import { openLinkAction } from '../../actions/openLinkAction'; export class LinkGlobalActionModel extends GlobalActionModel { defaultProps: ButtonProps = { - title: 'Link', + title: tval('Link'), }; } LinkGlobalActionModel.define({ - title: 'Link', + title: tval('Link'), }); LinkGlobalActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, diff --git a/packages/core/client/src/flow/models/actions/LinkRecordActionModel.tsx b/packages/core/client/src/flow/models/actions/LinkRecordActionModel.tsx index b426cdc029..a14624eae8 100644 --- a/packages/core/client/src/flow/models/actions/LinkRecordActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/LinkRecordActionModel.tsx @@ -8,24 +8,25 @@ */ import type { ButtonProps } from 'antd'; +import { tval } from '@nocobase/utils/client'; import { openLinkAction } from '../../actions/openLinkAction'; import { RecordActionModel } from '../base/ActionModel'; export class LinkRecordActionModel extends RecordActionModel { defaultProps: ButtonProps = { type: 'link', - children: 'Link', + children: tval('Link'), }; } LinkRecordActionModel.define({ - title: 'Link', + title: tval('Link'), hide: true, }); LinkRecordActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, diff --git a/packages/core/client/src/flow/models/actions/PopupRecordActionModel.tsx b/packages/core/client/src/flow/models/actions/PopupRecordActionModel.tsx index 95b335ee0e..a9cebf2577 100644 --- a/packages/core/client/src/flow/models/actions/PopupRecordActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/PopupRecordActionModel.tsx @@ -8,22 +8,23 @@ */ import type { ButtonProps } from 'antd/es/button'; +import { tval } from '@nocobase/utils/client'; import { openModeAction } from '../../actions/openModeAction'; import { RecordActionModel } from '../base/ActionModel'; export class PopupRecordActionModel extends RecordActionModel { defaultProps: ButtonProps = { - title: 'Popup', + title: tval('Popup'), }; } PopupRecordActionModel.define({ - title: 'Popup', + title: tval('Popup'), }); PopupRecordActionModel.registerFlow({ key: 'handleClick', - title: '点击事件', + title: tval('Click event'), on: { eventName: 'click', }, From 9851bf1cc1b12f8f5a073a42eb2d83f5e9501bee Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 15:42:22 +0800 Subject: [PATCH 07/17] feat: i18n for base models --- .../client/src/flow/models/base/GridModel.tsx | 15 +++++++++------ .../client/src/flow/models/base/PageModel.tsx | 2 +- packages/core/client/src/locale/en-US.json | 7 ++++++- packages/core/client/src/locale/zh-CN.json | 7 ++++++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/core/client/src/flow/models/base/GridModel.tsx b/packages/core/client/src/flow/models/base/GridModel.tsx index 2c4e79c093..90bac2fe27 100644 --- a/packages/core/client/src/flow/models/base/GridModel.tsx +++ b/packages/core/client/src/flow/models/base/GridModel.tsx @@ -21,6 +21,7 @@ import { import { Alert, Space } from 'antd'; import _ from 'lodash'; import React, { useState } from 'react'; +import { tval } from '@nocobase/utils/client'; import { Grid } from '../../components/Grid'; import { BlockModel } from './BlockModel'; @@ -68,6 +69,7 @@ export class GridModel extends FlowModel { } render() { + const t = this.flowEngine.translate; console.log('GridModel render', JSON.stringify(this.props.rows, null, 2), this.props.sizes); return (
@@ -95,14 +97,14 @@ export class GridModel extends FlowModel { this.props.rows = { ...this.props.rows, [uid()]: [[model.uid]] }; }} > - }>{'Add block'} + }>{t('Add block')} { this.openStepSettingsDialog('defaultFlow', 'grid'); }} > - Configure rows + {t('Configure rows')}
@@ -141,7 +143,7 @@ GridModel.registerFlow({ grid: { uiSchema: { rows: { - title: 'Rows', + title: tval('Rows'), 'x-decorator': 'FormItem', 'x-component': ({ value, onChange }) => { // eslint-disable-next-line react-hooks/rules-of-hooks @@ -152,18 +154,19 @@ GridModel.registerFlow({ }, 'x-component-props': { autoSize: { minRows: 10, maxRows: 20 }, - description: 'Configure the rows and columns of the grid.', + description: tval('Configure the rows and columns of the grid.'), }, }, sizes: { - title: 'Sizes', + title: tval('Sizes'), 'x-decorator': 'FormItem', 'x-component': JsonEditor, 'x-component-props': { rows: 5, }, - description: + description: tval( 'Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.', + ), }, }, async handler(ctx, params) { diff --git a/packages/core/client/src/flow/models/base/PageModel.tsx b/packages/core/client/src/flow/models/base/PageModel.tsx index 5dbf6a45c5..1f96e390d0 100644 --- a/packages/core/client/src/flow/models/base/PageModel.tsx +++ b/packages/core/client/src/flow/models/base/PageModel.tsx @@ -64,7 +64,7 @@ export class PageModel extends FlowModel { }); }} > - Add tab + {this.flowEngine.translate('Add tab')} } /> diff --git a/packages/core/client/src/locale/en-US.json b/packages/core/client/src/locale/en-US.json index dc4d2f7c2e..45c60366a6 100644 --- a/packages/core/client/src/locale/en-US.json +++ b/packages/core/client/src/locale/en-US.json @@ -954,5 +954,10 @@ "Display label": "Display label", "ReadOnly": "ReadOnly", "ReadPretty": "ReadPretty", - "Modal": "Modal" + "Modal": "Modal", + "Rows": "Rows", + "Configure rows": "Configure rows", + "Sizes": "Sizes", + "Configure the rows and columns of the grid.": "Configure the rows and columns of the grid.", + "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.": "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row." } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index 37e97001ad..e7820edfd4 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1187,5 +1187,10 @@ "Display label": "显示标签", "ReadOnly": "只读", "ReadPretty": "美观只读", - "Modal": "对话框" + "Modal": "对话框", + "Rows": "行", + "Configure rows": "配置行", + "Sizes": "尺寸", + "Configure the rows and columns of the grid.": "配置网格的行和列。", + "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.": "配置每行的尺寸。该值是一个数字数组,表示行中每列的宽度。" } From 64618e63787c8c8fae8ae8390d6b36daa36b7b7f Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 16:06:25 +0800 Subject: [PATCH 08/17] feat: i18n for data blocks --- .../client/src/flow/models/base/GridModel.tsx | 2 +- .../calendar/CalendarBlockModel.tsx | 53 ++++++++++++------- .../data-blocks/form/FormActionModel.tsx | 2 +- .../models/data-blocks/form/FormModel.tsx | 11 ++-- .../models/data-blocks/form/QuickEditForm.tsx | 2 +- .../table/TableActionsColumnModel.tsx | 3 +- .../data-blocks/table/TableColumnModel.tsx | 15 +++--- .../models/data-blocks/table/TableModel.tsx | 2 +- .../data-blocks/tabulator/TabulatorModel.tsx | 21 ++++---- packages/core/client/src/locale/en-US.json | 12 ++++- packages/core/client/src/locale/zh-CN.json | 12 ++++- 11 files changed, 87 insertions(+), 48 deletions(-) diff --git a/packages/core/client/src/flow/models/base/GridModel.tsx b/packages/core/client/src/flow/models/base/GridModel.tsx index 90bac2fe27..509e38e027 100644 --- a/packages/core/client/src/flow/models/base/GridModel.tsx +++ b/packages/core/client/src/flow/models/base/GridModel.tsx @@ -69,7 +69,7 @@ export class GridModel extends FlowModel { } render() { - const t = this.flowEngine.translate; + const t = this.flowEngine.translate.bind(this.flowEngine); console.log('GridModel render', JSON.stringify(this.props.rows, null, 2), this.props.sizes); return (
diff --git a/packages/core/client/src/flow/models/data-blocks/calendar/CalendarBlockModel.tsx b/packages/core/client/src/flow/models/data-blocks/calendar/CalendarBlockModel.tsx index 6a76a16868..3ed80e51d5 100644 --- a/packages/core/client/src/flow/models/data-blocks/calendar/CalendarBlockModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/calendar/CalendarBlockModel.tsx @@ -12,6 +12,7 @@ import { Card, Modal } from 'antd'; import moment from 'moment'; import React from 'react'; import { Calendar, momentLocalizer } from 'react-big-calendar'; +import { tval } from '@nocobase/utils/client'; import 'react-big-calendar/lib/css/react-big-calendar.css'; import { DataBlockModel } from '../../base/BlockModel'; @@ -47,7 +48,7 @@ export class CalendarBlockModel extends DataBlockModel { } CalendarBlockModel.define({ - title: 'Calendar', + title: tval('Calendar'), group: 'Content', hide: true, defaultOptions: { @@ -64,13 +65,20 @@ CalendarBlockModel.registerFlow({ step1: { handler(ctx, params) { console.log('ctx.extra.event', ctx.extra.event); + const t = ctx.model.flowEngine.translate; Modal.info({ - title: 'Event Selected', + title: t('Event selected'), content: (
-

Title: {ctx.extra.event.nickname}

-

Start: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}

-

End: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}

+

+ {t('Title')}: {ctx.extra.event.nickname} +

+

+ {t('Start')}: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')} +

+

+ {t('End')}: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')} +

), }); @@ -88,13 +96,20 @@ CalendarBlockModel.registerFlow({ step1: { handler(ctx, params) { console.log('ctx.extra.event', ctx.extra.event); + const t = ctx.model.flowEngine.translate; Modal.info({ - title: 'Double Click', + title: t('Double click'), content: (
-

Title: {ctx.extra.event.nickname}

-

Start: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}

-

End: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')}

+

+ {t('Title')}: {ctx.extra.event.nickname} +

+

+ {t('Start')}: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')} +

+

+ {t('End')}: {moment(ctx.extra.event.createdAt).format('YYYY-MM-DD HH:mm:ss')} +

), }); @@ -113,20 +128,20 @@ CalendarBlockModel.registerFlow({ uiSchema: { dataSourceKey: { type: 'string', - title: 'Data Source Key', + title: tval('Data Source Key'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter data source key', + placeholder: tval('Enter data source key'), }, }, collectionName: { type: 'string', - title: 'Collection Name', + title: tval('Collection Name'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter collection name', + placeholder: tval('Enter collection name'), }, }, }, @@ -149,29 +164,29 @@ CalendarBlockModel.registerFlow({ uiSchema: { titleAccessor: { type: 'string', - title: 'Title accessor', + title: tval('Title accessor'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter title accessor', + placeholder: tval('Enter title accessor'), }, }, startAccessor: { type: 'string', - title: 'Start accessor', + title: tval('Start accessor'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter start accessor', + placeholder: tval('Enter start accessor'), }, }, endAccessor: { type: 'string', - title: 'End accessor', + title: tval('End accessor'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter end accessor', + placeholder: tval('Enter end accessor'), }, }, }, diff --git a/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx b/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx index e55fb47525..a1d3f18301 100644 --- a/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx @@ -36,7 +36,7 @@ FormSubmitActionModel.registerFlow({ step1: { async handler(ctx, params) { if (!ctx.shared?.currentBlockModel?.resource) { - ctx.globals.message.error('No resource selected for submission.'); + ctx.globals.message.error(ctx.model.flowEngine.translate('No resource selected for submission.')); return; } const currentBlockModel = ctx.shared.currentBlockModel as FormModel; diff --git a/packages/core/client/src/flow/models/data-blocks/form/FormModel.tsx b/packages/core/client/src/flow/models/data-blocks/form/FormModel.tsx index 7fc2d2e62b..42363ecaa6 100644 --- a/packages/core/client/src/flow/models/data-blocks/form/FormModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/form/FormModel.tsx @@ -13,6 +13,7 @@ import { FormProvider } from '@formily/react'; import { AddActionButton, AddFieldButton, FlowModelRenderer, SingleRecordResource } from '@nocobase/flow-engine'; import { Card } from 'antd'; import React from 'react'; +import { tval } from '@nocobase/utils/client'; import { DataBlockModel } from '../../base/BlockModel'; import { EditableFieldModel } from '../../fields/EditableField/EditableFieldModel'; @@ -80,20 +81,20 @@ FormModel.registerFlow({ uiSchema: { dataSourceKey: { type: 'string', - title: 'Data Source Key', + title: tval('Data Source Key'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter data source key', + placeholder: tval('Enter data source key'), }, }, collectionName: { type: 'string', - title: 'Collection Name', + title: tval('Collection Name'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter collection name', + placeholder: tval('Enter collection name'), }, }, }, @@ -126,7 +127,7 @@ FormModel.registerFlow({ }); FormModel.define({ - title: 'Form', + title: tval('Form'), group: 'Content', defaultOptions: { use: 'FormModel', diff --git a/packages/core/client/src/flow/models/data-blocks/form/QuickEditForm.tsx b/packages/core/client/src/flow/models/data-blocks/form/QuickEditForm.tsx index 199022b1fa..c2ec2fc8dc 100644 --- a/packages/core/client/src/flow/models/data-blocks/form/QuickEditForm.tsx +++ b/packages/core/client/src/flow/models/data-blocks/form/QuickEditForm.tsx @@ -116,7 +116,7 @@ export class QuickEditForm extends FlowModel { resolve(this.form.values); // 在 close 之后 resolve }} > - Submit + {this.ctx.globals.flowEngine.translate('Submit')} diff --git a/packages/core/client/src/flow/models/data-blocks/table/TableActionsColumnModel.tsx b/packages/core/client/src/flow/models/data-blocks/table/TableActionsColumnModel.tsx index 74c54226a3..d74785752d 100644 --- a/packages/core/client/src/flow/models/data-blocks/table/TableActionsColumnModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/table/TableActionsColumnModel.tsx @@ -12,6 +12,7 @@ import { observer } from '@formily/reactive-react'; import { AddActionButton, FlowModel, FlowModelRenderer, FlowsFloatContextMenu } from '@nocobase/flow-engine'; import { Skeleton, Space } from 'antd'; import React from 'react'; +import { tval } from '@nocobase/utils/client'; import { ActionModel } from '../../base/ActionModel'; import { SupportedFieldInterfaces } from '../../base/FieldModel'; @@ -63,7 +64,7 @@ export class TableActionsColumnModel extends FlowModel { }, ]} > - {this.props.title || 'Actions'} + {this.props.title || tval('Actions')} ), render: this.render(), diff --git a/packages/core/client/src/flow/models/data-blocks/table/TableColumnModel.tsx b/packages/core/client/src/flow/models/data-blocks/table/TableColumnModel.tsx index ee43e50965..32c4decaac 100644 --- a/packages/core/client/src/flow/models/data-blocks/table/TableColumnModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/table/TableColumnModel.tsx @@ -11,6 +11,7 @@ import { QuestionCircleOutlined } from '@ant-design/icons'; import { FlowEngineProvider, FlowsFloatContextMenu } from '@nocobase/flow-engine'; import { TableColumnProps, Tooltip } from 'antd'; import React from 'react'; +import { tval } from '@nocobase/utils/client'; import { FieldModel } from '../../base/FieldModel'; import { ReadPrettyFieldModel } from '../../fields/ReadPrettyField/ReadPrettyFieldModel'; @@ -65,7 +66,7 @@ export class TableColumnModel extends FieldModel { } TableColumnModel.define({ - title: 'Table Column', + title: tval('Table column'), icon: 'TableColumn', defaultOptions: { use: 'TableColumnModel', @@ -103,13 +104,13 @@ TableColumnModel.registerFlow({ }, }, editColumTitle: { - title: 'Column title', + title: tval('Column title'), uiSchema: { title: { 'x-component': 'Input', 'x-decorator': 'FormItem', 'x-component-props': { - placeholder: 'Column title', + placeholder: tval('Column title'), }, }, }, @@ -124,13 +125,13 @@ TableColumnModel.registerFlow({ }, }, editTooltip: { - title: 'Edit tooltip', + title: tval('Edit tooltip'), uiSchema: { tooltip: { 'x-component': 'Input.TextArea', 'x-decorator': 'FormItem', 'x-component-props': { - placeholder: 'Edit tooltip', + placeholder: tval('Edit tooltip'), }, }, }, @@ -139,7 +140,7 @@ TableColumnModel.registerFlow({ }, }, editColumnWidth: { - title: 'Column width', + title: tval('Column width'), uiSchema: { width: { 'x-component': 'NumberPicker', @@ -154,7 +155,7 @@ TableColumnModel.registerFlow({ }, }, enableEditable: { - title: 'Editable', + title: tval('Editable'), uiSchema: { editable: { 'x-component': 'Switch', diff --git a/packages/core/client/src/flow/models/data-blocks/table/TableModel.tsx b/packages/core/client/src/flow/models/data-blocks/table/TableModel.tsx index 32f036480b..ab30b8ac9e 100644 --- a/packages/core/client/src/flow/models/data-blocks/table/TableModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/table/TableModel.tsx @@ -309,7 +309,7 @@ TableModel.registerFlow({ TableModel.define({ title: tval('Table'), - group: 'Content', + group: tval('Content'), defaultOptions: { use: 'TableModel', subModels: { diff --git a/packages/core/client/src/flow/models/data-blocks/tabulator/TabulatorModel.tsx b/packages/core/client/src/flow/models/data-blocks/tabulator/TabulatorModel.tsx index 8ab7b0c19a..bdcf93ee46 100644 --- a/packages/core/client/src/flow/models/data-blocks/tabulator/TabulatorModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/tabulator/TabulatorModel.tsx @@ -21,6 +21,7 @@ import { import { Button, Card, Pagination, Skeleton, Space } from 'antd'; import _ from 'lodash'; import React from 'react'; +import { tval } from '@nocobase/utils/client'; import { ColumnDefinition, TabulatorFull as Tabulator } from 'tabulator-tables'; import { ActionModel } from '../../base/ActionModel'; import { DataBlockModel } from '../../base/BlockModel'; @@ -41,7 +42,7 @@ export class TabulatorColumnModel extends FlowModel { getColumnProps(): ColumnDefinition { return { - title: 'abcd', + title: tval('abcd'), width: 100, headerSort: false, editable: true, @@ -126,7 +127,7 @@ export class TabulatorTableActionsColumnModel extends TabulatorColumnModel { containerStyle={{ display: 'block', padding: '11px 8px', margin: '-11px -8px' }} > - {this.props.title || 'Actions'} + {this.props.title || tval('Actions')} @@ -194,7 +195,7 @@ export class TabulatorModel extends DataBlockModel { appendItems={[ { key: 'actions', - label: 'Actions column', + label: tval('Actions column'), createModelOptions: { use: 'TabulatorTableActionsColumnModel', }, @@ -260,7 +261,7 @@ export class TabulatorModel extends DataBlockModel { ))} - +
@@ -292,20 +293,20 @@ TabulatorModel.registerFlow({ uiSchema: { dataSourceKey: { type: 'string', - title: 'Data Source Key', + title: tval('Data Source Key'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter data source key', + placeholder: tval('Enter data source key'), }, }, collectionName: { type: 'string', - title: 'Collection Name', + title: tval('Collection Name'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter collection name', + placeholder: tval('Enter collection name'), }, }, }, @@ -342,8 +343,8 @@ TabulatorModel.registerFlow({ }); TabulatorModel.define({ - title: 'Tabulator', - group: 'Content', + title: tval('Tabulator'), + group: tval('Content'), requiresDataSource: true, hide: true, defaultOptions: { diff --git a/packages/core/client/src/locale/en-US.json b/packages/core/client/src/locale/en-US.json index 45c60366a6..4bfd36f5a6 100644 --- a/packages/core/client/src/locale/en-US.json +++ b/packages/core/client/src/locale/en-US.json @@ -959,5 +959,15 @@ "Configure rows": "Configure rows", "Sizes": "Sizes", "Configure the rows and columns of the grid.": "Configure the rows and columns of the grid.", - "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.": "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row." + "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.": "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.", + "Event selected": "Event selected", + "Double click": "Double click", + "Start": "Start", + "End": "End", + "Title accessor": "Title accessor", + "Enter title accessor": "Enter title accessor", + "Start accessor": "Start accessor", + "Enter start accessor": "Enter start accessor", + "End accessor": "End accessor", + "Enter end accessor": "Enter end accessor" } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index e7820edfd4..e5eb1e6dd9 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1192,5 +1192,15 @@ "Configure rows": "配置行", "Sizes": "尺寸", "Configure the rows and columns of the grid.": "配置网格的行和列。", - "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.": "配置每行的尺寸。该值是一个数字数组,表示行中每列的宽度。" + "Configure the sizes of each row. The value is an array of numbers representing the width of each column in the row.": "配置每行的尺寸。该值是一个数字数组,表示行中每列的宽度。", + "Event selected": "已选择事件", + "Double click": "双击", + "Start": "开始", + "End": "结束", + "Title accessor": "标题访问器", + "Enter title accessor": "输入标题访问器", + "Start accessor": "开始访问器", + "Enter start accessor": "输入开始访问器", + "End accessor": "结束访问器", + "Enter end accessor": "输入结束访问器" } From 02081d189d40ca446fd751866da2621b86837771 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 16:38:28 +0800 Subject: [PATCH 09/17] feat: i18n for field models --- .../AssociationSelectEditableFieldModel.tsx | 11 ++++++----- .../EditableField/ColorEditableFieldModel.tsx | 3 ++- .../DateTimeFieldModel/DateTimeFieldModel.tsx | 5 +++-- .../EditableField/NanoIDEditableFieldModel.tsx | 4 ++-- .../EditableField/PasswordEditableFieldModel.tsx | 16 ++++++++++------ .../AssociationSelectReadPrettyFieldModel.tsx | 9 ++++----- .../DateTimeReadPrettyFieldModel.tsx | 5 +++-- .../ReadPrettyField/JsonReadPrettyFieldModel.tsx | 2 +- .../ReadPrettyField/ReadPrettyFieldModel.tsx | 3 ++- packages/core/client/src/locale/en-US.json | 8 +++++++- packages/core/client/src/locale/zh-CN.json | 8 +++++++- 11 files changed, 47 insertions(+), 27 deletions(-) diff --git a/packages/core/client/src/flow/models/fields/EditableField/AssociationFieldModel/AssociationSelectEditableFieldModel.tsx b/packages/core/client/src/flow/models/fields/EditableField/AssociationFieldModel/AssociationSelectEditableFieldModel.tsx index 931ccff02e..5dca643652 100644 --- a/packages/core/client/src/flow/models/fields/EditableField/AssociationFieldModel/AssociationSelectEditableFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/EditableField/AssociationFieldModel/AssociationSelectEditableFieldModel.tsx @@ -11,6 +11,7 @@ import { Select } from 'antd'; import React from 'react'; import { FlowModelRenderer, useFlowEngine, useFlowModel, reactive } from '@nocobase/flow-engine'; import { useCompile } from '../../../../../schema-component'; +import { tval } from '@nocobase/utils/client'; import { AssociationFieldEditableFieldModel } from './AssociationFieldEditableFieldModel'; function toValue(record: any | any[], fieldNames, multiple = false) { @@ -41,7 +42,7 @@ function LabelByField(props) { const currentModel: any = useFlowModel(); const flowEngine = useFlowEngine(); if (modelCache.has(cacheKey)) { - return option[fieldNames.label] ? : 'N/A'; + return option[fieldNames.label] ? : tval('N/A'); } const collectionManager = currentModel.collectionField.collection.collectionManager; const target = currentModel.collectionField?.options?.target; @@ -76,7 +77,7 @@ function LabelByField(props) { return ( - {option[fieldNames.label] ? : 'N/A'} + {option[fieldNames.label] ? : tval('N/A')} ); } @@ -258,7 +259,7 @@ AssociationSelectEditableFieldModel.registerFlow({ paginationState.page++; } } catch (error) { - console.error('滚动分页请求失败:', error); + console.error('Scroll pagination request failed:', error); } finally { paginationState.loading = false; } @@ -316,13 +317,13 @@ AssociationSelectEditableFieldModel.registerFlow({ AssociationSelectEditableFieldModel.registerFlow({ key: 'fieldNames', - title: 'Specific properties', + title: tval('Specific properties'), auto: true, sort: 200, steps: { fieldNames: { use: 'titleField', - title: 'Title field', + title: tval('Title field'), }, }, }); diff --git a/packages/core/client/src/flow/models/fields/EditableField/ColorEditableFieldModel.tsx b/packages/core/client/src/flow/models/fields/EditableField/ColorEditableFieldModel.tsx index e4ad77d27a..61d4d93fef 100644 --- a/packages/core/client/src/flow/models/fields/EditableField/ColorEditableFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/EditableField/ColorEditableFieldModel.tsx @@ -9,6 +9,7 @@ import { connect, mapProps } from '@formily/react'; import { ColorPicker as AntdColorPicker } from 'antd'; +import { tval } from '@nocobase/utils/client'; import { EditableFieldModel } from './EditableFieldModel'; const ColorPicker = connect( @@ -23,7 +24,7 @@ const ColorPicker = connect( }, presets: [ { - label: 'Recommended', + label: tval('Recommended'), colors: [ '#8BBB11', '#52C41A', diff --git a/packages/core/client/src/flow/models/fields/EditableField/DateTimeFieldModel/DateTimeFieldModel.tsx b/packages/core/client/src/flow/models/fields/EditableField/DateTimeFieldModel/DateTimeFieldModel.tsx index 52d5573639..f612dd583d 100644 --- a/packages/core/client/src/flow/models/fields/EditableField/DateTimeFieldModel/DateTimeFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/EditableField/DateTimeFieldModel/DateTimeFieldModel.tsx @@ -7,6 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ import { DatePicker } from '@formily/antd-v5'; +import { tval } from '@nocobase/utils/client'; import { EditableFieldModel } from '../EditableFieldModel'; export class DateTimeFieldModel extends EditableFieldModel { @@ -35,11 +36,11 @@ DateTimeFieldModel.registerFlow({ key: 'key3', auto: true, sort: 1000, - title: 'Specific properties', + title: tval('Specific properties'), steps: { dateFormat: { use: 'dateDisplayFormat', - title: 'Date display format', + title: tval('Date display format'), }, }, }); diff --git a/packages/core/client/src/flow/models/fields/EditableField/NanoIDEditableFieldModel.tsx b/packages/core/client/src/flow/models/fields/EditableField/NanoIDEditableFieldModel.tsx index 15961b7099..d2d726a737 100644 --- a/packages/core/client/src/flow/models/fields/EditableField/NanoIDEditableFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/EditableField/NanoIDEditableFieldModel.tsx @@ -29,11 +29,11 @@ NanoIDEditableFieldModel.registerFlow({ const { size, customAlphabet } = ctx.model.collectionField.options || { size: 21 }; function isValidNanoid(value) { if (value?.length !== size) { - return 'Field value size is' + ` ${size || 21}`; + return ctx.globals.flowEngine.translate('Field value size is') + ` ${size || 21}`; } for (let i = 0; i < value.length; i++) { if (customAlphabet?.indexOf(value[i]) === -1) { - return `Field value do not meet the requirements`; + return ctx.globals.flowEngine.translate('Field value do not meet the requirements'); } } } diff --git a/packages/core/client/src/flow/models/fields/EditableField/PasswordEditableFieldModel.tsx b/packages/core/client/src/flow/models/fields/EditableField/PasswordEditableFieldModel.tsx index c210e36644..079c2bfc7c 100644 --- a/packages/core/client/src/flow/models/fields/EditableField/PasswordEditableFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/EditableField/PasswordEditableFieldModel.tsx @@ -8,6 +8,7 @@ */ import { Password } from '@formily/antd-v5'; +import { tval } from '@nocobase/utils/client'; import { EditableFieldModel } from './EditableFieldModel'; export class PasswordEditableFieldModel extends EditableFieldModel { @@ -21,14 +22,17 @@ PasswordEditableFieldModel.registerFlow({ key: 'key3', auto: true, sort: 1000, - title: 'Group3', + title: tval('Password Options'), steps: { placeholder: { - title: 'Placeholder', + title: tval('Placeholder'), uiSchema: { - checkStrength: { + placeholder: { 'x-component': 'Input', 'x-decorator': 'FormItem', + 'x-component-props': { + placeholder: tval('Enter placeholder text'), + }, }, }, handler(ctx, params) { @@ -36,14 +40,14 @@ PasswordEditableFieldModel.registerFlow({ }, }, checkStrength: { - title: 'Check strength', + title: tval('Check strength'), uiSchema: { checkStrength: { 'x-component': 'Switch', 'x-decorator': 'FormItem', 'x-component-props': { - checkedChildren: 'Yes', - unCheckedChildren: 'No', + checkedChildren: tval('Yes'), + unCheckedChildren: tval('No'), }, }, }, diff --git a/packages/core/client/src/flow/models/fields/ReadPrettyField/AssociationFieldModel/AssociationSelectReadPrettyFieldModel.tsx b/packages/core/client/src/flow/models/fields/ReadPrettyField/AssociationFieldModel/AssociationSelectReadPrettyFieldModel.tsx index 7d4e30fa13..54480cea0c 100644 --- a/packages/core/client/src/flow/models/fields/ReadPrettyField/AssociationFieldModel/AssociationSelectReadPrettyFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/ReadPrettyField/AssociationFieldModel/AssociationSelectReadPrettyFieldModel.tsx @@ -11,8 +11,7 @@ import React from 'react'; import { AssociationReadPrettyFieldModel } from './AssociationReadPrettyFieldModel'; import { FlowEngineProvider, reactive } from '@nocobase/flow-engine'; import { getUniqueKeyFromCollection } from '../../../../../collection-manager/interfaces/utils'; -import { useCompile } from '../../../../../schema-component'; -import { isTitleField } from '../../../../../data-source'; +import { tval } from '@nocobase/utils/client'; export class AssociationSelectReadPrettyFieldModel extends AssociationReadPrettyFieldModel { public static readonly supportedFieldInterfaces = [ @@ -71,7 +70,7 @@ export class AssociationSelectReadPrettyFieldModel extends AssociationReadPretty {idx > 0 && ,} - {v?.[fieldNames.label] ? mol.render() : 'N/A'} + {v?.[fieldNames.label] ? mol.render() : this.flowEngine.translate('N/A')} ); @@ -85,13 +84,13 @@ export class AssociationSelectReadPrettyFieldModel extends AssociationReadPretty AssociationSelectReadPrettyFieldModel.registerFlow({ key: 'fieldNames', - title: 'Specific properties', + title: tval('Specific properties'), auto: true, sort: 200, steps: { fieldNames: { use: 'titleField', - title: 'Title field', + title: tval('Title field'), handler(ctx, params) { const { target } = ctx.model.collectionField.options; const collectionManager = ctx.model.collectionField.collection.collectionManager; diff --git a/packages/core/client/src/flow/models/fields/ReadPrettyField/DateTimeReadPrettyFieldModel.tsx b/packages/core/client/src/flow/models/fields/ReadPrettyField/DateTimeReadPrettyFieldModel.tsx index 56f2570d44..2ccb997a9d 100644 --- a/packages/core/client/src/flow/models/fields/ReadPrettyField/DateTimeReadPrettyFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/ReadPrettyField/DateTimeReadPrettyFieldModel.tsx @@ -10,6 +10,7 @@ import React from 'react'; import dayjs from 'dayjs'; import { reactive } from '@nocobase/flow-engine'; +import { tval } from '@nocobase/utils/client'; import { ReadPrettyFieldModel } from './ReadPrettyFieldModel'; export class DateTimeReadPrettyFieldModel extends ReadPrettyFieldModel { @@ -54,11 +55,11 @@ DateTimeReadPrettyFieldModel.registerFlow({ key: 'key3', auto: true, sort: 1000, - title: 'Specific properties', + title: tval('Specific properties'), steps: { dateFormat: { use: 'dateDisplayFormat', - title: 'Date display format', + title: tval('Date display format'), defaultParams: (ctx) => { const { showTime, dateFormat, timeFormat, picker } = ctx.model.props; return { diff --git a/packages/core/client/src/flow/models/fields/ReadPrettyField/JsonReadPrettyFieldModel.tsx b/packages/core/client/src/flow/models/fields/ReadPrettyField/JsonReadPrettyFieldModel.tsx index 4085a51b9e..aa0b34d9b7 100644 --- a/packages/core/client/src/flow/models/fields/ReadPrettyField/JsonReadPrettyFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/ReadPrettyField/JsonReadPrettyFieldModel.tsx @@ -28,7 +28,7 @@ export class JsonReadPrettyFieldModel extends ReadPrettyFieldModel { try { content = JSON.stringify(value, null, space ?? 2); } catch (error) { - content = '[Invalid JSON]'; + content = this.flowEngine.translate('Invalid JSON format'); } } diff --git a/packages/core/client/src/flow/models/fields/ReadPrettyField/ReadPrettyFieldModel.tsx b/packages/core/client/src/flow/models/fields/ReadPrettyField/ReadPrettyFieldModel.tsx index 5da7808088..354ef6d0bf 100644 --- a/packages/core/client/src/flow/models/fields/ReadPrettyField/ReadPrettyFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/ReadPrettyField/ReadPrettyFieldModel.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { reactive } from '@nocobase/flow-engine'; +import { tval } from '@nocobase/utils/client'; import { FieldModel } from '../../base/FieldModel'; export class ReadPrettyFieldModel extends FieldModel { @@ -29,7 +30,7 @@ export class ReadPrettyFieldModel extends FieldModel { ReadPrettyFieldModel.registerFlow({ key: 'ReadPrettyFieldDefault', auto: true, - title: 'Basic', + title: tval('Basic'), sort: 100, steps: { step1: { diff --git a/packages/core/client/src/locale/en-US.json b/packages/core/client/src/locale/en-US.json index 4bfd36f5a6..191c386cce 100644 --- a/packages/core/client/src/locale/en-US.json +++ b/packages/core/client/src/locale/en-US.json @@ -969,5 +969,11 @@ "Start accessor": "Start accessor", "Enter start accessor": "Enter start accessor", "End accessor": "End accessor", - "Enter end accessor": "Enter end accessor" + "Enter end accessor": "Enter end accessor", + "Recommended": "Recommended", + "Password Options": "Password Options", + "Placeholder": "Placeholder", + "Enter placeholder text": "Enter placeholder text", + "Check strength": "Check strength", + "N/A": "N/A" } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index e5eb1e6dd9..b2334fc38b 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1202,5 +1202,11 @@ "Start accessor": "开始访问器", "Enter start accessor": "输入开始访问器", "End accessor": "结束访问器", - "Enter end accessor": "输入结束访问器" + "Enter end accessor": "输入结束访问器", + "Recommended": "推荐", + "Password Options": "密码选项", + "Placeholder": "占位符", + "Enter placeholder text": "输入占位符文本", + "Check strength": "检查强度", + "N/A": "不适用" } From b2bc7f15737dfd492d9e37a9ae91cbd6621e846d Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 16:53:59 +0800 Subject: [PATCH 10/17] feat: i18n for other blocks and filter blocks --- .../models/filter-blocks/form/FilterFormModel.tsx | 11 ++++++----- .../filter-blocks/form/FilterFormResetActionModel.tsx | 5 +++-- .../form/FilterFormSubmitActionModel.tsx | 5 +++-- .../flow/models/other-blocks/html/HtmlBlockModel.tsx | 2 +- packages/core/client/src/locale/en-US.json | 4 +++- packages/core/client/src/locale/zh-CN.json | 4 +++- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/core/client/src/flow/models/filter-blocks/form/FilterFormModel.tsx b/packages/core/client/src/flow/models/filter-blocks/form/FilterFormModel.tsx index 5a6cd82a3c..984ad33f62 100644 --- a/packages/core/client/src/flow/models/filter-blocks/form/FilterFormModel.tsx +++ b/packages/core/client/src/flow/models/filter-blocks/form/FilterFormModel.tsx @@ -13,6 +13,7 @@ import { FormProvider } from '@formily/react'; import { AddActionButton, AddFieldButton, Collection, FlowModelRenderer } from '@nocobase/flow-engine'; import { Card } from 'antd'; import React from 'react'; +import { tval } from '@nocobase/utils/client'; import { FilterBlockModel } from '../../base/BlockModel'; import { FilterFormFieldModel } from './FilterFormFieldModel'; @@ -69,7 +70,7 @@ export class FilterFormModel extends FilterBlockModel { FilterFormModel.define({ hide: true, - title: 'Form', + title: tval('Form'), }); FilterFormModel.registerFlow({ @@ -82,20 +83,20 @@ FilterFormModel.registerFlow({ uiSchema: { dataSourceKey: { type: 'string', - title: 'Data Source Key', + title: tval('Data Source Key'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter data source key', + placeholder: tval('Enter data source key'), }, }, collectionName: { type: 'string', - title: 'Collection Name', + title: tval('Collection Name'), 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-component-props': { - placeholder: 'Enter collection name', + placeholder: tval('Enter collection name'), }, }, }, diff --git a/packages/core/client/src/flow/models/filter-blocks/form/FilterFormResetActionModel.tsx b/packages/core/client/src/flow/models/filter-blocks/form/FilterFormResetActionModel.tsx index d4993dd021..4a8f0f8201 100644 --- a/packages/core/client/src/flow/models/filter-blocks/form/FilterFormResetActionModel.tsx +++ b/packages/core/client/src/flow/models/filter-blocks/form/FilterFormResetActionModel.tsx @@ -9,12 +9,13 @@ import { FlowEngine, MultiRecordResource } from '@nocobase/flow-engine'; import { ButtonProps } from 'antd'; +import { tval } from '@nocobase/utils/client'; import { DataBlockModel } from '../../base/BlockModel'; import { FilterFormActionModel } from './FilterFormActionModel'; export class FilterFormResetActionModel extends FilterFormActionModel { defaultProps: ButtonProps = { - children: 'Reset', + children: tval('Reset'), }; } @@ -27,7 +28,7 @@ FilterFormResetActionModel.registerFlow({ step1: { async handler(ctx, params) { if (!ctx.shared?.currentBlockModel?.form) { - ctx.globals.message.error('No form available for reset.'); + ctx.globals.message.error(ctx.globals.flowEngine.translate('No form available for reset.')); return; } const currentBlockModel = ctx.shared.currentBlockModel; diff --git a/packages/core/client/src/flow/models/filter-blocks/form/FilterFormSubmitActionModel.tsx b/packages/core/client/src/flow/models/filter-blocks/form/FilterFormSubmitActionModel.tsx index 793ba0f4cc..38a5feaca4 100644 --- a/packages/core/client/src/flow/models/filter-blocks/form/FilterFormSubmitActionModel.tsx +++ b/packages/core/client/src/flow/models/filter-blocks/form/FilterFormSubmitActionModel.tsx @@ -9,13 +9,14 @@ import { FlowEngine, MultiRecordResource } from '@nocobase/flow-engine'; import type { ButtonProps, ButtonType } from 'antd/es/button'; +import { tval } from '@nocobase/utils/client'; import { ActionModel } from '../../base/ActionModel'; import { DataBlockModel } from '../../base/BlockModel'; import { FilterFormActionModel } from './FilterFormActionModel'; export class FilterFormSubmitActionModel extends FilterFormActionModel { defaultProps: ButtonProps = { - children: 'Filter', + children: tval('Filter'), type: 'primary', }; } @@ -29,7 +30,7 @@ FilterFormSubmitActionModel.registerFlow({ step1: { async handler(ctx, params) { if (!ctx.shared?.currentBlockModel?.form) { - ctx.globals.message.error('No form available for submission.'); + ctx.globals.message.error(ctx.globals.flowEngine.translate('No form available for submission.')); return; } const currentBlockModel = ctx.shared.currentBlockModel; diff --git a/packages/core/client/src/flow/models/other-blocks/html/HtmlBlockModel.tsx b/packages/core/client/src/flow/models/other-blocks/html/HtmlBlockModel.tsx index c274a9561a..0220ef3c8b 100644 --- a/packages/core/client/src/flow/models/other-blocks/html/HtmlBlockModel.tsx +++ b/packages/core/client/src/flow/models/other-blocks/html/HtmlBlockModel.tsx @@ -36,7 +36,7 @@ export class HtmlBlockModel extends BlockModel { HtmlBlockModel.define({ title: tval('HTML'), - group: 'Content', + group: tval('Content'), hide: true, defaultOptions: { use: 'HtmlBlockModel', diff --git a/packages/core/client/src/locale/en-US.json b/packages/core/client/src/locale/en-US.json index 191c386cce..1607714011 100644 --- a/packages/core/client/src/locale/en-US.json +++ b/packages/core/client/src/locale/en-US.json @@ -975,5 +975,7 @@ "Placeholder": "Placeholder", "Enter placeholder text": "Enter placeholder text", "Check strength": "Check strength", - "N/A": "N/A" + "N/A": "N/A", + "No form available for reset.": "No form available for reset.", + "No form available for submission.": "No form available for submission." } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index b2334fc38b..50da8c010e 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -1208,5 +1208,7 @@ "Placeholder": "占位符", "Enter placeholder text": "输入占位符文本", "Check strength": "检查强度", - "N/A": "不适用" + "N/A": "不适用", + "No form available for reset.": "没有可用的表单进行重置。", + "No form available for submission.": "没有可用的表单进行提交。" } From efce5688004f8e3c44ad285a078b3f574daf8ced Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 17:05:46 +0800 Subject: [PATCH 11/17] feat: i18n for flow settings --- .../core/client/src/flow/flowSetting/DateTimeFormat.tsx | 8 ++++---- packages/core/client/src/flow/flowSetting/TitleField.tsx | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/core/client/src/flow/flowSetting/DateTimeFormat.tsx b/packages/core/client/src/flow/flowSetting/DateTimeFormat.tsx index 00cd527a10..2c253e2dfb 100644 --- a/packages/core/client/src/flow/flowSetting/DateTimeFormat.tsx +++ b/packages/core/client/src/flow/flowSetting/DateTimeFormat.tsx @@ -9,11 +9,11 @@ import { css } from '@emotion/css'; import { defineAction } from '@nocobase/flow-engine'; -import { getPickerFormat } from '@nocobase/utils/client'; +import { getPickerFormat, tval } from '@nocobase/utils/client'; import { ExpiresRadio, DateFormatCom } from '../components'; export const dateTimeFormat = defineAction({ - title: 'Date display format', + title: tval('Date display format'), name: 'dateDisplayFormat', uiSchema: { picker: { @@ -80,7 +80,7 @@ export const dateTimeFormat = defineAction({ value: 'DD/MM/YYYY', }, { - label: 'custom', + label: tval('Custom'), value: 'custom', }, ], @@ -146,7 +146,7 @@ export const dateTimeFormat = defineAction({ value: 'HH:mm:ss', }, { - label: 'custom', + label: tval('Custom'), value: 'custom', }, ], diff --git a/packages/core/client/src/flow/flowSetting/TitleField.tsx b/packages/core/client/src/flow/flowSetting/TitleField.tsx index d3ec136357..fac6610d2f 100644 --- a/packages/core/client/src/flow/flowSetting/TitleField.tsx +++ b/packages/core/client/src/flow/flowSetting/TitleField.tsx @@ -10,6 +10,7 @@ import { defineAction, useStepSettingContext } from '@nocobase/flow-engine'; import { Select } from 'antd'; import React from 'react'; +import { tval } from '@nocobase/utils/client'; import { useCompile } from '../../schema-component'; import { getUniqueKeyFromCollection } from '../../collection-manager/interfaces/utils'; import { isTitleField } from '../../data-source'; @@ -37,7 +38,7 @@ const SelectOptions = (props) => { export const titleField = defineAction({ name: 'titleField', - title: 'Title field', + title: tval('Title field'), uiSchema: { label: { 'x-component': SelectOptions, From 5bf4233a118c6f08275e1bc1d1dda43e3e3c2d68 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 18:39:18 +0800 Subject: [PATCH 12/17] fix: flow engine translate function --- .../flow-engine/src/__tests__/utils.test.ts | 149 ------------------ packages/core/flow-engine/src/flowEngine.ts | 60 ++++++- packages/core/flow-engine/src/utils.ts | 93 ----------- 3 files changed, 52 insertions(+), 250 deletions(-) delete mode 100644 packages/core/flow-engine/src/__tests__/utils.test.ts diff --git a/packages/core/flow-engine/src/__tests__/utils.test.ts b/packages/core/flow-engine/src/__tests__/utils.test.ts deleted file mode 100644 index 3ae58da357..0000000000 --- a/packages/core/flow-engine/src/__tests__/utils.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { describe, it, expect, beforeEach } from 'vitest'; -import { TranslationUtil } from '../utils'; - -describe('TranslationUtil', () => { - let translationUtil: TranslationUtil; - let mockTranslator: (key: string, options?: any) => string; - - beforeEach(() => { - translationUtil = new TranslationUtil(); - mockTranslator = (key: string, options?: any) => { - // 简单的模拟翻译函数 - if (options?.name) { - return key.replace('{name}', options.name); - } - return `translated_${key}`; - }; - }); - - describe('translate', () => { - it('should handle simple translation', () => { - const result = translationUtil.translate('Hello World', mockTranslator); - expect(result).toBe('translated_Hello World'); - }); - - it('should handle translation with options', () => { - const result = translationUtil.translate('Hello {name}', mockTranslator, { name: 'John' }); - expect(result).toBe('Hello John'); - }); - - it('should handle template compilation with single quotes', () => { - const template = "{{t('Hello World')}}"; - const result = translationUtil.translate(template, mockTranslator); - expect(result).toBe('translated_Hello World'); - }); - - it('should handle template compilation with double quotes', () => { - const template = '{{t("User Name")}}'; - const result = translationUtil.translate(template, mockTranslator); - expect(result).toBe('translated_User Name'); - }); - - it('should handle template compilation with backticks', () => { - const template = '{{t(`Email`)}}'; - const result = translationUtil.translate(template, mockTranslator); - expect(result).toBe('translated_Email'); - }); - - it('should handle template with options', () => { - const template = '{{t(\'Hello {name}\', {"name": "John"})}}'; - const result = translationUtil.translate(template, mockTranslator); - expect(result).toBe('Hello John'); - }); - - it('should handle template with whitespace', () => { - const template = "{{ t ( 'User Name' ) }}"; - const result = translationUtil.translate(template, mockTranslator); - expect(result).toBe('translated_User Name'); - }); - - it('should handle mixed content', () => { - const template = "前缀 {{t('User Name')}} 后缀"; - const result = translationUtil.translate(template, mockTranslator); - expect(result).toBe('前缀 translated_User Name 后缀'); - }); - - it('should handle multiple templates', () => { - const template = "{{t('Hello')}} {{t('World')}}"; - const result = translationUtil.translate(template, mockTranslator); - expect(result).toBe('translated_Hello translated_World'); - }); - - it('should handle invalid template gracefully', () => { - const template = "{{t('unclosed quote)}}"; - const result = translationUtil.translate(template, mockTranslator); - // 由于模板格式不匹配,会被当作普通字符串翻译 - expect(result).toBe("translated_{{t('unclosed quote)}}"); - }); - - it('should return original value for non-string input', () => { - const result = translationUtil.translate(null as any, mockTranslator); - expect(result).toBe(null); - }); - - it('should return empty string for empty input', () => { - const result = translationUtil.translate('', mockTranslator); - expect(result).toBe(''); - }); - }); - - describe('caching', () => { - it('should cache template results', () => { - const template = "{{t('Hello World')}}"; - - // 第一次调用 - const result1 = translationUtil.translate(template, mockTranslator); - expect(result1).toBe('translated_Hello World'); - expect(translationUtil.getCacheSize()).toBe(1); - - // 第二次调用应该使用缓存 - const result2 = translationUtil.translate(template, mockTranslator); - expect(result2).toBe('translated_Hello World'); - expect(translationUtil.getCacheSize()).toBe(1); - }); - - it('should not cache simple translations', () => { - const result1 = translationUtil.translate('Hello World', mockTranslator); - expect(result1).toBe('translated_Hello World'); - expect(translationUtil.getCacheSize()).toBe(0); - }); - - it('should clear cache', () => { - const template = "{{t('Hello World')}}"; - translationUtil.translate(template, mockTranslator); - expect(translationUtil.getCacheSize()).toBe(1); - - translationUtil.clearCache(); - expect(translationUtil.getCacheSize()).toBe(0); - }); - }); - - describe('error handling', () => { - it('should handle translator function errors', () => { - const errorTranslator = () => { - throw new Error('Translation error'); - }; - - const template = "{{t('Hello World')}}"; - const result = translationUtil.translate(template, errorTranslator); - // 应该返回原始模板而不是抛出错误 - expect(result).toBe("{{t('Hello World')}}"); - }); - - it('should handle invalid JSON options', () => { - const template = "{{t('Hello', invalid json)}}"; - const result = translationUtil.translate(template, mockTranslator); - // JSON 解析失败,但仍会调用翻译器,只是没有 options - expect(result).toBe('translated_Hello'); - }); - }); -}); diff --git a/packages/core/flow-engine/src/flowEngine.ts b/packages/core/flow-engine/src/flowEngine.ts index 96e9ce01b3..303b6e0759 100644 --- a/packages/core/flow-engine/src/flowEngine.ts +++ b/packages/core/flow-engine/src/flowEngine.ts @@ -18,7 +18,7 @@ import { IFlowModelRepository, ModelConstructor, } from './types'; -import { isInheritedFrom, TranslationUtil } from './utils'; +import { isInheritedFrom } from './utils'; import { initFlowEngineLocale } from './locale'; interface ApplyFlowCacheEntry { @@ -40,8 +40,6 @@ export class FlowEngine { context: Record = {}; private modelRepository: IFlowModelRepository | null = null; private _applyFlowCache = new Map(); - /** @private Translation utility for template compilation and caching */ - private _translationUtil = new TranslationUtil(); reactView: ReactView; @@ -88,11 +86,19 @@ export class FlowEngine { * flowEngine.t("{{t('Hello {name}', {name: 'John'})}}") */ public translate(keyOrTemplate: string, options?: any): string { - return this._translationUtil.translate( - keyOrTemplate, - (key: string, opts?: any) => this.translateKey(key, opts), - options, - ); + if (!keyOrTemplate || typeof keyOrTemplate !== 'string') { + return keyOrTemplate; + } + + // 先尝试一次翻译 + let result = this.translateKey(keyOrTemplate, options); + + // 检查翻译结果是否包含模板语法,如果有则进行模板编译 + if (this.isTemplate(result)) { + result = this.compileTemplate(result); + } + + return result; } /** @@ -107,6 +113,44 @@ export class FlowEngine { return key; } + /** + * 检查字符串是否包含模板语法 + * @private + */ + private isTemplate(str: string): boolean { + return /\{\{\s*t\s*\(\s*["'`].*?["'`]\s*(?:,\s*.*?)?\s*\)\s*\}\}/g.test(str); + } + + /** + * 编译模板字符串 + * @private + */ + private compileTemplate(template: string): string { + return template.replace( + /\{\{\s*t\s*\(\s*["'`](.*?)["'`]\s*(?:,\s*((?:[^{}]|\{[^}]*\})*?))?\s*\)\s*\}\}/g, + (match, key, optionsStr) => { + try { + let templateOptions = {}; + if (optionsStr) { + optionsStr = optionsStr.trim(); + if (optionsStr.startsWith('{') && optionsStr.endsWith('}')) { + // 使用受限的 Function 构造器解析 + try { + templateOptions = new Function('$root', `with($root) { return (${optionsStr}); }`)({}); + } catch (parseError) { + return match; + } + } + } + return this.translateKey(key, templateOptions); + } catch (error) { + console.warn(`FlowEngine: Failed to compile template "${match}":`, error); + return match; + } + }, + ); + } + get applyFlowCache() { return this._applyFlowCache; } diff --git a/packages/core/flow-engine/src/utils.ts b/packages/core/flow-engine/src/utils.ts index cc0f401713..e7aa388467 100644 --- a/packages/core/flow-engine/src/utils.ts +++ b/packages/core/flow-engine/src/utils.ts @@ -175,99 +175,6 @@ export function defineAction(options: ActionDefinition) { return options; } -/** - * 翻译工具类,负责模板编译和缓存管理 - */ -export class TranslationUtil { - /** @private Simple template cache - template string -> compiled result */ - private _templateCache = new Map(); - - /** - * 翻译函数,支持简单翻译和模板编译 - * @param keyOrTemplate 翻译键或包含 {{t('key', options)}} 的模板字符串 - * @param translator 翻译函数,通常是 i18n.t - * @param options 翻译选项(如命名空间、参数等) - * @returns 翻译后的文本 - */ - public translate(keyOrTemplate: string, translator: (key: string, options?: any) => string, options?: any): string { - if (!keyOrTemplate || typeof keyOrTemplate !== 'string') { - return keyOrTemplate; - } - - // 检查是否包含模板语法 {{t('...')}} - const hasTemplate = this.isTemplate(keyOrTemplate); - - if (hasTemplate) { - // 模板编译模式 - 简单缓存 - if (this._templateCache.has(keyOrTemplate)) { - return this._templateCache.get(keyOrTemplate)!; - } - - // 解析模板 - const result = this.compileTemplate(keyOrTemplate, translator); - - // 简单缓存:直接存储 - this._templateCache.set(keyOrTemplate, result); - return result; - } else { - // 简单翻译模式 - 直接调用翻译函数 - return translator(keyOrTemplate, options); - } - } - - /** - * 检查字符串是否包含模板语法 - * @private - */ - private isTemplate(str: string): boolean { - return /\{\{\s*t\s*\(\s*["'`].*?["'`]\s*(?:,\s*.*?)?\s*\)\s*\}\}/g.test(str); - } - - /** - * 编译模板字符串 - * @private - */ - private compileTemplate(template: string, translator: (key: string, options?: any) => string): string { - return template.replace( - /\{\{\s*t\s*\(\s*["'`](.*?)["'`]\s*(?:,\s*((?:[^{}]|\{[^}]*\})*))?\s*\)\s*\}\}/g, - (match, key, optionsStr) => { - try { - let templateOptions = {}; - if (optionsStr) { - optionsStr = optionsStr.trim(); - if (optionsStr.startsWith('{') && optionsStr.endsWith('}')) { - // 使用受限的 Function 构造器解析 - try { - templateOptions = new Function('$root', `with($root) { return (${optionsStr}); }`)({}); - } catch (parseError) { - return match; - } - } - } - return translator(key, templateOptions); - } catch (error) { - console.warn(`TranslationUtil: Failed to compile template "${match}":`, error); - return match; - } - }, - ); - } - - /** - * 清空模板缓存 - */ - public clearCache(): void { - this._templateCache.clear(); - } - - /** - * 获取缓存大小 - */ - public getCacheSize(): number { - return this._templateCache.size; - } -} - // 模块级全局缓存,与 useCompile 保持一致 const compileCache = {}; From 1f95f6d5d0765262d6974c69dcbe44cc50c207b2 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 18:53:02 +0800 Subject: [PATCH 13/17] feat: i18n for add sub models --- .../src/components/subModel/LazyDropdown.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx b/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx index ec2a0b90d6..707d9bca24 100644 --- a/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx +++ b/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx @@ -9,6 +9,7 @@ import { Dropdown, DropdownProps, Input, Menu, Spin, Empty, InputProps } from 'antd'; import React, { useEffect, useState, useMemo, useRef, FC } from 'react'; +import { useFlowModel } from '../../hooks'; /** * 通过鼠标的位置计算出最佳的 dropdown 的高度,以尽量避免出现滚动条 @@ -72,6 +73,7 @@ interface LazyDropdownMenuProps extends Omit { } const LazyDropdown: React.FC & { menu: LazyDropdownMenuProps }> = ({ menu, ...props }) => { + const model = useFlowModel(); const [loadedChildren, setLoadedChildren] = useState>({}); const [loadingKeys, setLoadingKeys] = useState>(new Set()); const [menuVisible, setMenuVisible] = useState(false); @@ -82,6 +84,7 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM const dropdownMaxHeight = useNiceDropdownMaxHeight([menuVisible]); const [isSearching, setIsSearching] = useState(false); const searchTimeoutRef = useRef(null); + const t = model.flowEngine.translate.bind(model.flowEngine); // 清理定时器,避免内存泄露 useEffect(() => { @@ -223,7 +226,7 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM visible={menuVisible} variant="borderless" allowClear - placeholder={item.searchPlaceholder || 'search'} + placeholder={t(item.searchPlaceholder || 'Search')} value={currentSearchValue} onChange={(e) => { e.stopPropagation(); @@ -264,7 +267,7 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM key: `${item.key}-empty`, label: (
- +
), disabled: true, @@ -278,7 +281,7 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM return { type: 'group', key: item.key, - label: item.label, + label: typeof item.label === 'string' ? t(item.label) : item.label, children: groupChildren, }; } @@ -289,7 +292,7 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM return { key: item.key, - label: item.label, + label: typeof item.label === 'string' ? t(item.label) : item.label, onClick: (info) => { if (children) { return; @@ -316,7 +319,7 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM key: `${keyPath}-empty`, label: (
- +
), disabled: true, From 05f5530232a4330167a302a896b74dfca5a2bfab Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 20:46:50 +0800 Subject: [PATCH 14/17] fix: some i18n error --- .../client/src/flow/actions/refreshOnCompleteAction.tsx | 3 +-- .../src/flow/models/actions/BulkDeleteActionModel.tsx | 2 +- .../client/src/flow/models/actions/BulkEditActionModel.tsx | 2 +- .../client/src/flow/models/actions/DeleteActionModel.tsx | 2 +- .../client/src/flow/models/actions/FilterActionModel.tsx | 4 ++-- .../client/src/flow/models/actions/RefreshActionModel.tsx | 2 +- packages/core/client/src/flow/models/base/GridModel.tsx | 2 +- .../models/data-blocks/calendar/CalendarBlockModel.tsx | 4 ++-- .../src/flow/models/data-blocks/form/FormActionModel.tsx | 2 +- .../models/data-blocks/table/TableActionsColumnModel.tsx | 2 +- .../flow/models/data-blocks/tabulator/TabulatorModel.tsx | 2 +- .../filter-blocks/form/FilterFormSubmitActionModel.tsx | 2 +- .../settings/wrappers/contextual/DefaultSettingsIcon.tsx | 4 +++- .../wrappers/contextual/StepRequiredSettingsDialog.tsx | 2 +- .../settings/wrappers/contextual/StepSettingsDialog.tsx | 7 +++---- .../settings/wrappers/contextual/StepSettingsDrawer.tsx | 7 +++---- .../flow-engine/src/components/subModel/LazyDropdown.tsx | 2 +- packages/core/flow-engine/src/flowEngine.ts | 3 ++- packages/core/flow-engine/src/models/flowModel.tsx | 6 +++++- 19 files changed, 32 insertions(+), 28 deletions(-) diff --git a/packages/core/client/src/flow/actions/refreshOnCompleteAction.tsx b/packages/core/client/src/flow/actions/refreshOnCompleteAction.tsx index 2787345ba8..7f59df2744 100644 --- a/packages/core/client/src/flow/actions/refreshOnCompleteAction.tsx +++ b/packages/core/client/src/flow/actions/refreshOnCompleteAction.tsx @@ -27,8 +27,7 @@ export const refreshOnCompleteAction = { async handler(ctx, params) { if (params.enable) { await ctx.extra.currentResource.refresh(); - const t = ctx.globals.flowEngine.translate; - ctx.globals.message.success(t('Data refreshed successfully')); + ctx.globals.message.success(ctx.model.translate('Data refreshed successfully')); } }, }; diff --git a/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx b/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx index ab128120e6..30fbc491c1 100644 --- a/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/BulkDeleteActionModel.tsx @@ -35,7 +35,7 @@ BulkDeleteActionModel.registerFlow({ }, delete: { async handler(ctx, params) { - const t = ctx.globals.flowEngine.translate; + const t = ctx.model.translate; if (!ctx.shared?.currentBlockModel?.resource) { ctx.globals.message.error(t('No resource selected for deletion')); return; diff --git a/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx b/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx index 2310ec0a1f..924edf49f4 100644 --- a/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/BulkEditActionModel.tsx @@ -52,7 +52,7 @@ BulkEditActionModel.registerFlow({ }; }, async handler(ctx, params) { - const t = ctx.globals.flowEngine.translate; + const t = ctx.model.translate; if (!ctx.shared?.currentBlockModel?.resource) { ctx.globals.message.error(t('No resource selected for bulk edit')); return; diff --git a/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx b/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx index 52884fc745..79b3bb5315 100644 --- a/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/DeleteActionModel.tsx @@ -35,7 +35,7 @@ DeleteActionModel.registerFlow({ }, delete: { async handler(ctx, params) { - const t = ctx.globals.flowEngine.translate; + const t = ctx.model.translate; if (!ctx.shared?.currentBlockModel?.resource) { ctx.globals.message.error(t('No resource selected for deletion')); return; diff --git a/packages/core/client/src/flow/models/actions/FilterActionModel.tsx b/packages/core/client/src/flow/models/actions/FilterActionModel.tsx index 9f981d2af3..0e82db2ee9 100644 --- a/packages/core/client/src/flow/models/actions/FilterActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/FilterActionModel.tsx @@ -20,7 +20,7 @@ const FilterContent: FC<{ value: any }> = (props) => { const currentBlockModel = modelInstance.ctx.shared.currentBlockModel as DataBlockModel; const fields = currentBlockModel.collection.getFields(); const ignoreFieldsNames = modelInstance.props.ignoreFieldsNames || []; - const t = modelInstance.flowEngine.translate; + const t = modelInstance.translate; return ( <> @@ -44,7 +44,7 @@ export class FilterActionModel extends GlobalActionModel { defaultProps: any = { type: 'default', - children: tval('Filter'), + title: tval('Filter'), icon: 'FilterOutlined', filterValue: { $and: [] }, ignoreFieldsNames: [], diff --git a/packages/core/client/src/flow/models/actions/RefreshActionModel.tsx b/packages/core/client/src/flow/models/actions/RefreshActionModel.tsx index 20d5c62866..6ae3998a97 100644 --- a/packages/core/client/src/flow/models/actions/RefreshActionModel.tsx +++ b/packages/core/client/src/flow/models/actions/RefreshActionModel.tsx @@ -31,7 +31,7 @@ RefreshActionModel.registerFlow({ steps: { refresh: { async handler(ctx, params) { - const t = ctx.globals.flowEngine.translate; + const t = ctx.model.translate; const currentResource = ctx.shared?.currentBlockModel?.resource; if (!currentResource) { ctx.globals.message.error(t('No resource selected for refresh')); diff --git a/packages/core/client/src/flow/models/base/GridModel.tsx b/packages/core/client/src/flow/models/base/GridModel.tsx index 509e38e027..b49e6e81bf 100644 --- a/packages/core/client/src/flow/models/base/GridModel.tsx +++ b/packages/core/client/src/flow/models/base/GridModel.tsx @@ -69,7 +69,7 @@ export class GridModel extends FlowModel { } render() { - const t = this.flowEngine.translate.bind(this.flowEngine); + const t = this.translate; console.log('GridModel render', JSON.stringify(this.props.rows, null, 2), this.props.sizes); return (
diff --git a/packages/core/client/src/flow/models/data-blocks/calendar/CalendarBlockModel.tsx b/packages/core/client/src/flow/models/data-blocks/calendar/CalendarBlockModel.tsx index 3ed80e51d5..98f21ca3b7 100644 --- a/packages/core/client/src/flow/models/data-blocks/calendar/CalendarBlockModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/calendar/CalendarBlockModel.tsx @@ -65,7 +65,7 @@ CalendarBlockModel.registerFlow({ step1: { handler(ctx, params) { console.log('ctx.extra.event', ctx.extra.event); - const t = ctx.model.flowEngine.translate; + const t = ctx.model.translate; Modal.info({ title: t('Event selected'), content: ( @@ -96,7 +96,7 @@ CalendarBlockModel.registerFlow({ step1: { handler(ctx, params) { console.log('ctx.extra.event', ctx.extra.event); - const t = ctx.model.flowEngine.translate; + const t = ctx.model.translate; Modal.info({ title: t('Double click'), content: ( diff --git a/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx b/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx index a1d3f18301..0cb51df89a 100644 --- a/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/form/FormActionModel.tsx @@ -17,7 +17,7 @@ export class FormActionModel extends ActionModel {} export class FormSubmitActionModel extends FormActionModel { defaultProps: ButtonProps = { - children: tval('Submit'), + title: tval('Submit'), type: 'primary', htmlType: 'submit', }; diff --git a/packages/core/client/src/flow/models/data-blocks/table/TableActionsColumnModel.tsx b/packages/core/client/src/flow/models/data-blocks/table/TableActionsColumnModel.tsx index d74785752d..31a1553f3c 100644 --- a/packages/core/client/src/flow/models/data-blocks/table/TableActionsColumnModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/table/TableActionsColumnModel.tsx @@ -64,7 +64,7 @@ export class TableActionsColumnModel extends FlowModel { }, ]} > - {this.props.title || tval('Actions')} + {this.props.title || this.flowEngine.translate('Actions')} ), render: this.render(), diff --git a/packages/core/client/src/flow/models/data-blocks/tabulator/TabulatorModel.tsx b/packages/core/client/src/flow/models/data-blocks/tabulator/TabulatorModel.tsx index bdcf93ee46..224eee78a1 100644 --- a/packages/core/client/src/flow/models/data-blocks/tabulator/TabulatorModel.tsx +++ b/packages/core/client/src/flow/models/data-blocks/tabulator/TabulatorModel.tsx @@ -261,7 +261,7 @@ export class TabulatorModel extends DataBlockModel { ))} - +
diff --git a/packages/core/client/src/flow/models/filter-blocks/form/FilterFormSubmitActionModel.tsx b/packages/core/client/src/flow/models/filter-blocks/form/FilterFormSubmitActionModel.tsx index 38a5feaca4..9bb35c94d1 100644 --- a/packages/core/client/src/flow/models/filter-blocks/form/FilterFormSubmitActionModel.tsx +++ b/packages/core/client/src/flow/models/filter-blocks/form/FilterFormSubmitActionModel.tsx @@ -16,7 +16,7 @@ import { FilterFormActionModel } from './FilterFormActionModel'; export class FilterFormSubmitActionModel extends FilterFormActionModel { defaultProps: ButtonProps = { - children: tval('Filter'), + title: tval('Filter'), type: 'primary', }; } diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx index 84a62a5420..bb08660cb6 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx @@ -233,10 +233,12 @@ export const DefaultSettingsIcon: React.FC = ({ // 如果step使用了action,检查action是否有uiSchema let hasActionUiSchema = false; + let stepTitle = actionStep.title; if (actionStep.use) { try { const action = targetModel.flowEngine?.getAction?.(actionStep.use); hasActionUiSchema = action && action.uiSchema != null; + stepTitle = stepTitle || action.title; } catch (error) { console.warn(t('Failed to get action {{action}}', { action: actionStep.use }), ':', error); } @@ -255,7 +257,7 @@ export const DefaultSettingsIcon: React.FC = ({ stepKey, step: actionStep, uiSchema: mergedUiSchema, - title: t(actionStep.title) || stepKey, + title: t(stepTitle) || stepKey, modelKey, // 添加模型标识 }; }) diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx index 4a2d518e5b..c30e9d0da3 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx @@ -306,7 +306,7 @@ const openRequiredParamsStepFormDialog = async ({ // 创建FormDialog const formDialog = FormDialog( { - title: dialogTitle, + title: dialogTitle || t('Step Parameter Configuration'), width: dialogWidth, footer: null, // 移除默认的底部按钮,使用自定义的导航按钮 destroyOnClose: true, diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx index 4fef33263f..8509ecba9c 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx @@ -53,9 +53,7 @@ const openStepSettingsDialog = async ({ throw new Error(t('Step with key {{stepKey}} not found', { stepKey })); } - const title = - dialogTitle || - (step ? `${step.title || stepKey} - ${t('Configuration')}` : `${t('Step Configuration')} - ${stepKey}`); + let title = step.title; // 创建参数解析上下文 const paramsContext = { @@ -76,6 +74,7 @@ const openStepSettingsDialog = async ({ actionUiSchema = action.uiSchema; } actionDefaultParams = action.defaultParams || {}; + title = title || action.title; } // 解析动态 uiSchema @@ -132,7 +131,7 @@ const openStepSettingsDialog = async ({ // 创建FormDialog const formDialog = FormDialog( { - title, + title: dialogTitle || `${t(title)} - ${t('Configuration')}`, width: dialogWidth, okText: t('OK'), cancelText: t('Cancel'), diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx index 1cc47a48f9..f0c7be7631 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx @@ -54,9 +54,7 @@ const openStepSettingsDrawer = async ({ throw new Error(t('Step with key {{stepKey}} not found', { stepKey })); } - const title = - drawerTitle || - (step ? `${step.title || stepKey} - ${t('Configuration')}` : `${t('Step Configuration')} - ${stepKey}`); + let title = step.title; // 创建参数解析上下文 const paramsContext = { @@ -76,6 +74,7 @@ const openStepSettingsDrawer = async ({ actionUiSchema = action.uiSchema; } actionDefaultParams = action.defaultParams || {}; + title = title || action.title; } // 解析动态 uiSchema @@ -240,7 +239,7 @@ const openStepSettingsDrawer = async ({ // 打开抽屉 const drawerRef = drawer.open({ - title, + title: drawerTitle || `${t(title)} - ${t('Configuration')}`, width: drawerWidth, content: , onClose: () => { diff --git a/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx b/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx index 707d9bca24..49fb6e0510 100644 --- a/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx +++ b/packages/core/flow-engine/src/components/subModel/LazyDropdown.tsx @@ -84,7 +84,7 @@ const LazyDropdown: React.FC & { menu: LazyDropdownM const dropdownMaxHeight = useNiceDropdownMaxHeight([menuVisible]); const [isSearching, setIsSearching] = useState(false); const searchTimeoutRef = useRef(null); - const t = model.flowEngine.translate.bind(model.flowEngine); + const t = model.translate; // 清理定时器,避免内存泄露 useEffect(() => { diff --git a/packages/core/flow-engine/src/flowEngine.ts b/packages/core/flow-engine/src/flowEngine.ts index 303b6e0759..f8195bbd14 100644 --- a/packages/core/flow-engine/src/flowEngine.ts +++ b/packages/core/flow-engine/src/flowEngine.ts @@ -14,6 +14,7 @@ import { ActionDefinition, ActionOptions, CreateModelOptions, + FlowContext, FlowDefinition, IFlowModelRepository, ModelConstructor, @@ -37,7 +38,7 @@ export class FlowEngine { private modelInstances: Map = new Map(); /** @public Stores flow settings including components and scopes for formily settings. */ public flowSettings: FlowSettings = new FlowSettings(); - context: Record = {}; + context: FlowContext['globals'] = {} as FlowContext['globals']; private modelRepository: IFlowModelRepository | null = null; private _applyFlowCache = new Map(); diff --git a/packages/core/flow-engine/src/models/flowModel.tsx b/packages/core/flow-engine/src/models/flowModel.tsx index c508825aee..be3cef9db8 100644 --- a/packages/core/flow-engine/src/models/flowModel.tsx +++ b/packages/core/flow-engine/src/models/flowModel.tsx @@ -397,7 +397,7 @@ export class FlowModel = { exit: () => { throw new FlowExitException(flowKey, this.uid); @@ -902,6 +902,10 @@ export class FlowModel) { this._sharedContext = { ...this._sharedContext, ...ctx }; } From cbbd4a1db895f75b8420bd13278d941d6e96728c Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 21:23:53 +0800 Subject: [PATCH 15/17] fix: uischema i18n --- .../settings/wrappers/contextual/DefaultSettingsIcon.tsx | 2 +- .../wrappers/contextual/StepRequiredSettingsDialog.tsx | 5 +++-- .../settings/wrappers/contextual/StepSettingsDialog.tsx | 5 +++-- .../settings/wrappers/contextual/StepSettingsDrawer.tsx | 5 +++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx index bb08660cb6..455561b2d2 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx @@ -129,7 +129,7 @@ export const DefaultSettingsIcon: React.FC = ({ title: t('Confirm Delete'), icon: , content: t('Are you sure you want to delete this item? This action cannot be undone.'), - okText: t('Confirm Delete'), + okText: t('Confirm'), okType: 'primary', cancelText: t('Cancel'), async onOk() { diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx index c30e9d0da3..467a271a28 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx @@ -14,6 +14,7 @@ import { FlowModel } from '../../../../models'; import { StepDefinition } from '../../../../types'; import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils'; import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext'; +import { toJS } from '@formily/reactive'; /** * 检查步骤是否已经有了所需的配置值 @@ -174,8 +175,8 @@ const openRequiredParamsStepFormDialog = async ({ const resolvedStepUiSchema = await resolveUiSchema(stepUiSchema, paramsContext); // 合并uiSchema,确保step的uiSchema优先级更高 - const mergedUiSchema = { ...resolvedActionUiSchema }; - Object.entries(resolvedStepUiSchema).forEach(([fieldKey, schema]) => { + const mergedUiSchema = { ...toJS(resolvedActionUiSchema) }; + Object.entries(toJS(resolvedStepUiSchema)).forEach(([fieldKey, schema]) => { if (mergedUiSchema[fieldKey]) { mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema }; } else { diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx index 8509ecba9c..0bd86dea38 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx @@ -13,6 +13,7 @@ import React from 'react'; import { StepSettingsDialogProps } from '../../../../types'; import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils'; import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext'; +import { toJS } from '@formily/reactive'; const SchemaField = createSchemaField(); @@ -82,8 +83,8 @@ const openStepSettingsDialog = async ({ const resolvedStepUiSchema = await resolveUiSchema(stepUiSchema, paramsContext); // 合并uiSchema,确保step的uiSchema优先级更高 - const mergedUiSchema = { ...resolvedActionUiSchema }; - Object.entries(resolvedStepUiSchema).forEach(([fieldKey, schema]) => { + const mergedUiSchema = { ...toJS(resolvedActionUiSchema) }; + Object.entries(toJS(resolvedStepUiSchema)).forEach(([fieldKey, schema]) => { if (mergedUiSchema[fieldKey]) { mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema }; } else { diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx index f0c7be7631..f794d9e8d9 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx @@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next'; import { StepSettingsDrawerProps } from '../../../../types'; import { resolveDefaultParams, resolveUiSchema, compileUiSchema, getT } from '../../../../utils'; import { StepSettingContextProvider, StepSettingContextType, useStepSettingContext } from './StepSettingContext'; +import { toJS } from '@formily/reactive'; const SchemaField = createSchemaField(); @@ -82,8 +83,8 @@ const openStepSettingsDrawer = async ({ const resolvedStepUiSchema = await resolveUiSchema(stepUiSchema, paramsContext); // 合并uiSchema,确保step的uiSchema优先级更高 - const mergedUiSchema = { ...resolvedActionUiSchema }; - Object.entries(resolvedStepUiSchema).forEach(([fieldKey, schema]) => { + const mergedUiSchema = { ...toJS(resolvedActionUiSchema) }; + Object.entries(toJS(resolvedStepUiSchema)).forEach(([fieldKey, schema]) => { if (mergedUiSchema[fieldKey]) { mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema }; } else { From 6e177e0923fa29ffcb77ae488b6e7bd4e8bdf381 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 22:25:45 +0800 Subject: [PATCH 16/17] fix: i18n --- .../core/client/src/flow/actions/confirm.tsx | 4 +- .../src/components/FlowErrorFallback.tsx | 22 +++++----- .../contextual/DefaultSettingsIcon.tsx | 4 +- .../wrappers/contextual/FlowsContextMenu.tsx | 4 +- .../contextual/StepRequiredSettingsDialog.tsx | 43 +++++++++++-------- .../contextual/StepSettingsDialog.tsx | 17 ++++---- .../contextual/StepSettingsDrawer.tsx | 17 ++++---- .../components/subModel/AddActionButton.tsx | 8 +++- .../components/subModel/AddFieldButton.tsx | 8 +++- .../core/flow-engine/src/locale/en-US.json | 22 +++++----- .../core/flow-engine/src/locale/zh-CN.json | 38 ++++++++-------- 11 files changed, 103 insertions(+), 84 deletions(-) diff --git a/packages/core/client/src/flow/actions/confirm.tsx b/packages/core/client/src/flow/actions/confirm.tsx index ee4c15fad1..cc6f512b14 100644 --- a/packages/core/client/src/flow/actions/confirm.tsx +++ b/packages/core/client/src/flow/actions/confirm.tsx @@ -43,8 +43,8 @@ export const confirm = defineAction({ async handler(ctx, params) { if (params.enable) { const confirmed = await ctx.globals.modal.confirm({ - title: ctx.globals.flowEngine.translate(params.title), - content: params.content, + title: ctx.model.translate(params.title), + content: ctx.model.translate(params.content), }); if (!confirmed) { diff --git a/packages/core/flow-engine/src/components/FlowErrorFallback.tsx b/packages/core/flow-engine/src/components/FlowErrorFallback.tsx index c4f01a135c..4af56f880b 100644 --- a/packages/core/flow-engine/src/components/FlowErrorFallback.tsx +++ b/packages/core/flow-engine/src/components/FlowErrorFallback.tsx @@ -11,6 +11,7 @@ import { Button, Result, Typography } from 'antd'; import React, { FC, useState } from 'react'; import { FallbackProps } from 'react-error-boundary'; import { useFlowModel } from '../hooks/useFlowModel'; +import { getT } from '../utils'; const { Paragraph, Text } = Typography; @@ -20,6 +21,7 @@ const { Paragraph, Text } = Typography; const FlowErrorFallbackInner: FC = ({ error, resetErrorBoundary }) => { const [loading, setLoading] = useState(false); const model = useFlowModel(); // 在这里安全地使用 Hook + const t = getT(model); const handleCopyError = async () => { setLoading(true); @@ -104,9 +106,9 @@ const FlowErrorFallbackInner: FC = ({ error, resetErrorBoundary } const subTitle = ( - {'This is likely a NocoBase internals bug. Please open an issue at '} + {t('This is likely a NocoBase internals bug. Please open an issue at')}{' '} - here + {t('here')} {model && (
@@ -121,23 +123,23 @@ const FlowErrorFallbackInner: FC = ({ error, resetErrorBoundary } - Feedback + {t('Feedback')} , canDownloadLogs && ( ), , resetErrorBoundary && ( ), ].filter(Boolean)} @@ -197,18 +199,18 @@ export const FlowErrorFallback: FC & { Feedback , , resetErrorBoundary && ( ), ].filter(Boolean)} diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx index 455561b2d2..90f6ac2d36 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx @@ -126,7 +126,7 @@ export const DefaultSettingsIcon: React.FC = ({ const handleDelete = useCallback(() => { Modal.confirm({ - title: t('Confirm Delete'), + title: t('Confirm delete'), icon: , content: t('Are you sure you want to delete this item? This action cannot be undone.'), okText: t('Confirm'), @@ -138,7 +138,7 @@ export const DefaultSettingsIcon: React.FC = ({ } catch (error) { console.error(t('Delete operation failed'), ':', error); Modal.error({ - title: t('Delete Failed'), + title: t('Delete failed'), content: t('Delete operation failed, please check the console for details.'), }); } diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx index ee4657ddf9..31207526ca 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx @@ -77,7 +77,7 @@ const FlowsContextMenuWithModel: React.FC = observer( if (key === 'delete') { // 处理删除操作 Modal.confirm({ - title: t('Confirm Delete'), + title: t('Confirm delete'), icon: , content: t('Are you sure you want to delete this item? This action cannot be undone.'), okText: t('Confirm Delete'), @@ -89,7 +89,7 @@ const FlowsContextMenuWithModel: React.FC = observer( } catch (error) { console.error(t('Delete operation failed'), ':', error); Modal.error({ - title: t('Delete Failed'), + title: t('Delete failed'), content: t('Delete operation failed, please check the console for details.'), }); } diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx index 467a271a28..95f48b7a0b 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepRequiredSettingsDialog.tsx @@ -120,7 +120,7 @@ const openRequiredParamsStepFormDialog = async ({ dialogTitle, }: StepFormDialogProps): Promise => { const t = getT(model); - const defaultTitle = dialogTitle || t('Step Parameter Configuration'); + const defaultTitle = dialogTitle || t('Step parameter configuration'); if (!model) { message.error(t('Invalid model provided')); @@ -228,7 +228,11 @@ const openRequiredParamsStepFormDialog = async ({ // 解析 defaultParams const resolvedActionDefaultParams = await resolveDefaultParams(actionDefaultParams, paramsContext); const resolvedDefaultParams = await resolveDefaultParams(step.defaultParams, paramsContext); - const mergedParams = { ...resolvedActionDefaultParams, ...resolvedDefaultParams, ...stepParams }; + const mergedParams = { + ...toJS(resolvedActionDefaultParams), + ...toJS(resolvedDefaultParams), + ...toJS(stepParams), + }; if (Object.keys(mergedParams).length > 0) { if (!initialValues[flowKey]) { @@ -304,17 +308,24 @@ const openRequiredParamsStepFormDialog = async ({ // 创建分步表单实例(只有多个步骤时才需要) const formStep = requiredSteps.length > 1 ? FormStep.createFormStep(0) : null; + const flowEngine = model.flowEngine || {}; + const scopes = { + formStep, + totalSteps: requiredSteps.length, + requiredSteps, + useStepSettingContext, + ...flowEngine.flowSettings?.scopes, + }; + // 创建FormDialog const formDialog = FormDialog( { - title: dialogTitle || t('Step Parameter Configuration'), + title: dialogTitle || t('Step parameter configuration'), width: dialogWidth, footer: null, // 移除默认的底部按钮,使用自定义的导航按钮 destroyOnClose: true, }, (form) => { - const flowEngine = model.flowEngine || {}; - const handleSubmit = async () => { try { await form.submit(); @@ -344,11 +355,8 @@ const openRequiredParamsStepFormDialog = async ({ resolve({}); }; - const scopes = { - formStep, - totalSteps: requiredSteps.length, - requiredSteps, - useStepSettingContext, + const dialogScopes = { + ...scopes, closeDialog: handleClose, handleNext: () => { // 验证当前步骤的表单 @@ -364,11 +372,10 @@ const openRequiredParamsStepFormDialog = async ({ // 可以在这里添加更详细的错误处理 }); }, - ...flowEngine.flowSettings?.scopes, }; // 编译 formSchema 中的表达式 - const compiledFormSchema = compileUiSchema(scopes, formSchema); + const compiledFormSchema = compileUiSchema(dialogScopes, formSchema); return ( <> @@ -379,7 +386,7 @@ const openRequiredParamsStepFormDialog = async ({ FormStep, ...flowEngine.flowSettings?.components, }} - scope={scopes} + scope={dialogScopes} /> @@ -397,7 +404,7 @@ const openRequiredParamsStepFormDialog = async ({ {/* 只有一个步骤时,只显示完成按钮 */} {requiredSteps.length === 1 ? ( ) : ( <> @@ -409,7 +416,7 @@ const openRequiredParamsStepFormDialog = async ({ } }} > - {t('Previous Step')} + {t('Previous step')} )} @@ -456,7 +463,7 @@ const openRequiredParamsStepFormDialog = async ({ // 打开对话框 formDialog.open({ - initialValues, + initialValues: compileUiSchema(scopes, initialValues), }); } catch (error) { reject(new Error(`${t('Failed to import FormDialog or FormStep')}: ${error.message}`)); diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx index 0bd86dea38..b8c10acd05 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx @@ -98,13 +98,19 @@ const openStepSettingsDialog = async ({ return {}; } + const flowEngine = model.flowEngine; + const scopes = { + useStepSettingContext, + ...flowEngine.flowSettings?.scopes, + }; + // 获取初始值 const stepParams = model.getStepParams(flowKey, stepKey) || {}; // 解析 defaultParams const resolvedDefaultParams = await resolveDefaultParams(step.defaultParams, paramsContext); const resolveActionDefaultParams = await resolveDefaultParams(actionDefaultParams, paramsContext); - const initialValues = { ...resolveActionDefaultParams, ...resolvedDefaultParams, ...stepParams }; + const initialValues = { ...toJS(resolveActionDefaultParams), ...toJS(resolvedDefaultParams), ...toJS(stepParams) }; // 构建表单Schema const formSchema: ISchema = { @@ -139,8 +145,6 @@ const openStepSettingsDialog = async ({ destroyOnClose: true, }, (form) => { - const flowEngine = model.flowEngine; - // 创建上下文值 const contextValue: StepSettingContextType = { model, @@ -152,11 +156,6 @@ const openStepSettingsDialog = async ({ stepKey, }; - const scopes = { - useStepSettingContext, - ...flowEngine.flowSettings?.scopes, - }; - // 编译 formSchema 中的表达式 const compiledFormSchema = compileUiSchema(scopes, formSchema); @@ -194,7 +193,7 @@ const openStepSettingsDialog = async ({ // 打开对话框 return formDialog.open({ - initialValues, + initialValues: compileUiSchema(scopes, initialValues), }); }; diff --git a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx index f794d9e8d9..1d0811379e 100644 --- a/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx +++ b/packages/core/flow-engine/src/components/settings/wrappers/contextual/StepSettingsDrawer.tsx @@ -101,10 +101,16 @@ const openStepSettingsDrawer = async ({ // 获取初始值 const stepParams = model.getStepParams(flowKey, stepKey) || {}; + const flowEngine = model.flowEngine; + const scopes = { + useStepSettingContext, + ...flowEngine.flowSettings?.scopes, + }; + // 解析 defaultParams const resolvedDefaultParams = await resolveDefaultParams(step.defaultParams, paramsContext); const resolveActionDefaultParams = await resolveDefaultParams(actionDefaultParams, paramsContext); - const initialValues = { ...resolveActionDefaultParams, ...resolvedDefaultParams, ...stepParams }; + const initialValues = { ...toJS(resolveActionDefaultParams), ...toJS(resolvedDefaultParams), ...toJS(stepParams) }; // 构建表单Schema const formSchema: ISchema = { @@ -142,7 +148,7 @@ const openStepSettingsDrawer = async ({ // 创建表单实例 const form = createForm({ - initialValues, + initialValues: compileUiSchema(scopes, initialValues), }); // 创建抽屉内容组件 @@ -182,8 +188,6 @@ const openStepSettingsDrawer = async ({ drawerRef.destroy(); }; - const flowEngine = model.flowEngine; - // 创建上下文值 const contextValue: StepSettingContextType = { model, @@ -195,11 +199,6 @@ const openStepSettingsDrawer = async ({ stepKey, }; - const scopes = { - useStepSettingContext, - ...flowEngine.flowSettings?.scopes, - }; - // 编译 formSchema 中的表达式 const compiledFormSchema = compileUiSchema(scopes, formSchema); diff --git a/packages/core/flow-engine/src/components/subModel/AddActionButton.tsx b/packages/core/flow-engine/src/components/subModel/AddActionButton.tsx index f4b56d0b53..3b0073c25c 100644 --- a/packages/core/flow-engine/src/components/subModel/AddActionButton.tsx +++ b/packages/core/flow-engine/src/components/subModel/AddActionButton.tsx @@ -14,6 +14,7 @@ import { ModelConstructor } from '../../types'; import { FlowSettingsButton } from '../common/FlowSettingsButton'; import { withFlowDesignMode } from '../common/withFlowDesignMode'; import { AddSubModelButton, SubModelItemsType } from './AddSubModelButton'; +import { useTranslation } from 'react-i18next'; interface AddActionButtonProps { /** @@ -48,6 +49,11 @@ interface AddActionButtonProps { items?: SubModelItemsType; } +const DefaultBtn = () => { + const { t } = useTranslation(); + return }>{t('Configure actions')}; +}; + /** * 专门用于添加动作模型的按钮组件 * @@ -63,7 +69,7 @@ const AddActionButtonCore: React.FC = ({ model, subModelBaseClass = 'ActionFlowModel', subModelKey = 'actions', - children = }>{'Configure actions'}, + children = , subModelType = 'array', items, filter, diff --git a/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx b/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx index c20ece0eeb..16cfc5143c 100644 --- a/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx +++ b/packages/core/flow-engine/src/components/subModel/AddFieldButton.tsx @@ -15,6 +15,7 @@ import { FlowModelOptions, ModelConstructor } from '../../types'; import { FlowSettingsButton } from '../common/FlowSettingsButton'; import { withFlowDesignMode } from '../common/withFlowDesignMode'; import { AddSubModelButton, SubModelItemsType, mergeSubModelItems } from './AddSubModelButton'; +import { useTranslation } from 'react-i18next'; export type BuildCreateModelOptionsType = { defaultOptions: FlowModelOptions; @@ -65,6 +66,11 @@ function defaultBuildCreateModelOptions({ defaultOptions }: BuildCreateModelOpti return defaultOptions; } +const DefaultBtn = () => { + const { t } = useTranslation(); + return }>{t('Configure fields')}; +}; + /** * 专门用于添加字段模型的按钮组件 * @@ -80,7 +86,7 @@ const AddFieldButtonCore: React.FC = ({ model, subModelBaseClass = 'FieldFlowModel', subModelKey = 'fields', - children = }>{'Configure fields'}, + children = , subModelType = 'array', collection, buildCreateModelOptions = defaultBuildCreateModelOptions, diff --git a/packages/core/flow-engine/src/locale/en-US.json b/packages/core/flow-engine/src/locale/en-US.json index d82887ca96..3f470cd725 100644 --- a/packages/core/flow-engine/src/locale/en-US.json +++ b/packages/core/flow-engine/src/locale/en-US.json @@ -3,7 +3,7 @@ "Flow with key {{flowKey}} not found": "Flow with key {{flowKey}} not found", "Step with key {{stepKey}} not found": "Step with key {{stepKey}} not found", "Configuration": "Configuration", - "Step Configuration": "Step Configuration", + "Step configuration": "Step configuration", "This step has no configurable parameters": "This step has no configurable parameters", "Failed to import Formily components": "Failed to import Formily components", "Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider": "Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider", @@ -13,20 +13,20 @@ "Failed to import FormDialog": "Failed to import FormDialog", "OK": "OK", "Cancel": "Cancel", - "Step Parameter Configuration": "Step Parameter Configuration", + "Step parameter configuration": "Step parameter configuration", "Error submitting form": "Error submitting form", - "Complete Configuration": "Complete Configuration", - "Previous Step": "Previous Step", + "Complete configuration": "Complete configuration", + "Previous step": "Previous step", "Form validation failed": "Form validation failed", - "Next Step": "Next Step", + "Next step": "Next step", "Failed to import FormDialog or FormStep": "Failed to import FormDialog or FormStep", "UID copied to clipboard": "UID copied to clipboard", "Copy failed": "Copy failed", "Copy failed, please try again": "Copy failed, please try again", - "Confirm Delete": "Confirm Delete", + "Confirm delete": "Confirm delete", "Are you sure you want to delete this item? This action cannot be undone.": "Are you sure you want to delete this item? This action cannot be undone.", "Delete operation failed": "Delete operation failed", - "Delete Failed": "Delete Failed", + "Delete failed": "Delete failed", "Delete operation failed, please check the console for details.": "Delete operation failed, please check the console for details.", "Configuration popup cancelled or error": "Configuration popup cancelled or error", "Failed to get action {{action}}": "Failed to get action {{action}}", @@ -35,17 +35,17 @@ "Delete": "Delete", "This is likely a NocoBase internals bug. Please open an issue at": "This is likely a NocoBase internals bug. Please open an issue at", "here": "here", - "Render Failed": "Render Failed", + "Render failed": "Render failed", "Feedback": "Feedback", "Download logs": "Download logs", - "Copy Error Info": "Copy Error Info", - "Try Again": "Try Again", + "Copy error info": "Copy error info", + "Try again": "Try again", "Data blocks": "Data blocks", "Filter blocks": "Filter blocks", "Other blocks": "Other blocks", "Invalid input parameters": "Invalid input parameters", "Invalid subModelKey format": "Invalid subModelKey format: {{subModelKey}}", - "SubModel not found": "SubModel '{{subKey}}' not found", + "Submodel not found": "Submodel '{{subKey}}' not found", "Expected array for subModel": "Expected array for '{{subKey}}', got {{type}}", "Array index out of bounds": "Array index {{index}} out of bounds for '{{subKey}}'", "Expected object for subModel": "Expected object for '{{subKey}}', got array", diff --git a/packages/core/flow-engine/src/locale/zh-CN.json b/packages/core/flow-engine/src/locale/zh-CN.json index f7cb829628..d4044d60e3 100644 --- a/packages/core/flow-engine/src/locale/zh-CN.json +++ b/packages/core/flow-engine/src/locale/zh-CN.json @@ -1,51 +1,51 @@ { "Invalid model provided": "提供的模型无效", - "Flow with key {{flowKey}} not found": "未找到Key为 {{flowKey}} 的流程", - "Step with key {{stepKey}} not found": "未找到Key为 {{stepKey}} 的步骤", + "Flow with key {{flowKey}} not found": "未找到key为 {{flowKey}} 的流程", + "Step with key {{stepKey}} not found": "未找到key为 {{stepKey}} 的步骤", "Configuration": "配置", - "Step Configuration": "步骤配置", + "Step configuration": "步骤配置", "This step has no configurable parameters": "此步骤没有可配置的参数", - "Failed to import Formily components": "导入Formily组件失败", - "Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider": "抽屉API不可用,请确保在FlowEngineGlobalsContextProvider中使用", + "Failed to import Formily components": "导入 Formily 组件失败", + "Drawer API is not available, please ensure it is used within FlowEngineGlobalsContextProvider": "抽屉 API 不可用,请确保在 FlowEngineGlobalsContextProvider 中使用", "Configuration saved": "配置已保存", "Error saving configuration": "保存配置时出错", "Error saving configuration, please check console": "保存配置时出错,请检查控制台", - "Failed to import FormDialog": "导入FormDialog失败", + "Failed to import FormDialog": "导入 FormDialog 失败", "OK": "确定", "Cancel": "取消", - "Step Parameter Configuration": "步骤参数配置", + "Step parameter configuration": "步骤参数配置", "Error submitting form": "提交表单时出错", - "Complete Configuration": "完成配置", - "Previous Step": "上一步", + "Complete configuration": "完成配置", + "Previous step": "上一步", "Form validation failed": "表单验证失败", - "Next Step": "下一步", + "Next step": "下一步", "Failed to import FormDialog or FormStep": "导入 FormDialog 或 FormStep 失败", "UID copied to clipboard": "UID 已复制到剪贴板", "Copy failed": "复制失败", "Copy failed, please try again": "复制失败,请重试", - "Confirm Delete": "确认删除", + "Confirm delete": "确认删除", "Are you sure you want to delete this item? This action cannot be undone.": "确定要删除此项吗?此操作不可撤销。", "Delete operation failed": "删除操作失败", - "Delete Failed": "删除失败", + "Delete failed": "删除失败", "Delete operation failed, please check the console for details.": "删除操作失败,请检查控制台获取详细信息。", "Configuration popup cancelled or error": "配置弹窗已取消或出错", - "Failed to get action {{action}}": "获取action '{{action}}' 失败", - "Failed to get configurable flows for model {{model}}": "获取模型 '{{model}}' 的可配置flows失败", + "Failed to get action {{action}}": "获取 action '{{action}}' 失败", + "Failed to get configurable flows for model {{model}}": "获取模型 '{{model}}' 的可配置 flows 失败", "Copy UID": "复制 UID", "Delete": "删除", "This is likely a NocoBase internals bug. Please open an issue at": "这可能是 NocoBase 内部错误。请在以下地址提交问题", "here": "这里", - "Render Failed": "渲染失败", + "Render failed": "渲染失败", "Feedback": "反馈", "Download logs": "下载日志", - "Copy Error Info": "复制错误信息", - "Try Again": "重试", + "Copy error info": "复制错误信息", + "Try again": "重试", "Data blocks": "数据区块", "Filter blocks": "筛选区块", "Other blocks": "其他区块", "Invalid input parameters": "输入参数无效", "Invalid subModelKey format": "无效的 subModelKey 格式: {{subModelKey}}", - "SubModel not found": "未找到SubModel '{{subKey}}'", + "Submodel not found": "未找到 Submodel '{{subKey}}'", "Expected array for subModel": "期望 '{{subKey}}' 为数组,实际为 {{type}}", "Array index out of bounds": "数组索引 {{index}} 超出 '{{subKey}}' 的边界", "Expected object for subModel": "期望 '{{subKey}}' 为对象,实际为数组", @@ -55,5 +55,5 @@ "Failed to destroy model after creation error": "创建错误后销毁模型失败", "Add": "添加", "Name": "名称", - "Model with ID {{uid}} not found": "未找到ID为 {{uid}} 的模型" + "Model with ID {{uid}} not found": "未找到 ID 为 {{uid}} 的模型" } \ No newline at end of file From 0a1c9df1f21b7491ca19b737a53e7bf3ff8b35a7 Mon Sep 17 00:00:00 2001 From: gchust Date: Sat, 28 Jun 2025 22:29:34 +0800 Subject: [PATCH 17/17] fix: i18n --- packages/core/flow-engine/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/flow-engine/src/utils.ts b/packages/core/flow-engine/src/utils.ts index e7aa388467..7eb98fd82c 100644 --- a/packages/core/flow-engine/src/utils.ts +++ b/packages/core/flow-engine/src/utils.ts @@ -25,7 +25,7 @@ export function getT(model: FlowModel): (key: string, options?: any) => string { if (model.flowEngine?.translate) { return (key: string, options?: any) => { // 自动添加 flow-engine 命名空间 - return model.flowEngine.translate(key, { ns: FLOW_ENGINE_NAMESPACE, ...options }); + return model.flowEngine.translate(key, { ns: [FLOW_ENGINE_NAMESPACE, 'client'], nsMode: 'fallback', ...options }); }; } // 回退到原始键值