@alicloud/appflow-chat 0.0.4-beta.4 → 0.0.4-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/appflow-chat.cjs.js +2028 -1
  2. package/dist/appflow-chat.esm.js +38352 -23
  3. package/dist/types/index.d.ts +169 -0
  4. package/package.json +3 -15
  5. package/src/components/HumanVerify/CustomParamsRenderer/ArrayField.tsx +6 -4
  6. package/src/components/HumanVerify/CustomParamsRenderer/EnumField.tsx +5 -3
  7. package/src/components/HumanVerify/CustomParamsRenderer/FieldRenderer.tsx +6 -4
  8. package/src/components/HumanVerify/CustomParamsRenderer/FileField.tsx +38 -30
  9. package/src/components/HumanVerify/CustomParamsRenderer/ObjectField.tsx +4 -2
  10. package/src/components/HumanVerify/CustomParamsRenderer/TimeField.tsx +38 -101
  11. package/src/components/HumanVerify/HistoryCard.tsx +6 -4
  12. package/src/components/HumanVerify/HumanVerify.tsx +5 -3
  13. package/src/components/MessageBubble.tsx +4 -2
  14. package/src/components/RichMessageBubble.tsx +4 -2
  15. package/src/core/RichBubbleContent.tsx +4 -2
  16. package/src/core/SourceContent.tsx +5 -3
  17. package/src/core/WebSearchContent.tsx +3 -1
  18. package/src/i18n/LocaleContext.tsx +70 -0
  19. package/src/i18n/index.ts +16 -0
  20. package/src/i18n/translate.ts +42 -0
  21. package/src/i18n/useTranslation.ts +39 -0
  22. package/src/i18n/utils.ts +67 -0
  23. package/src/index.ts +25 -0
  24. package/src/locales/en-US.ts +77 -0
  25. package/src/locales/index.ts +7 -0
  26. package/src/locales/types.ts +35 -0
  27. package/src/locales/zh-CN.ts +78 -0
  28. package/src/markdown/components/Chart.tsx +3 -1
  29. package/src/markdown/components/Mermaid.tsx +8 -6
  30. package/src/markdown/index.tsx +11 -8
  31. package/dist/dayjs.min-L7v6Rjvs.cjs +0 -1
  32. package/dist/dayjs.min-MIkW36mC.js +0 -301
  33. package/dist/index-CsdPG_CH.cjs +0 -2032
  34. package/dist/index-DuGIPL7L.js +0 -37902
  35. package/dist/moment-BOHN1eRP.js +0 -2578
  36. package/dist/moment-BdH06dpW.cjs +0 -10
@@ -320,6 +320,21 @@ export declare interface CustomParamsRendererProps extends UploadConfig {
320
320
  errors?: Record<string, string>;
321
321
  }
322
322
 
323
+ /** 递归生成所有叶子节点的扁平路径联合类型 */
324
+ declare type DeepKey<T, P extends string = ''> = {
325
+ [K in keyof T & string]: T[K] extends object ? DeepKey<T[K], `${P}${P extends '' ? '' : '.'}${K}`> : `${P}${P extends '' ? '' : '.'}${K}`;
326
+ }[keyof T & string];
327
+
328
+ /** 深度可选,用于 LocaleProvider 的 overrides 字段 */
329
+ export declare type DeepPartial<T> = {
330
+ [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
331
+ };
332
+
333
+ /** 把所有叶子节点的值类型统一为 string,并去掉 readonly */
334
+ declare type DeepStringify<T> = {
335
+ -readonly [K in keyof T]: T[K] extends object ? DeepStringify<T[K]> : string;
336
+ };
337
+
323
338
  export declare type DocReferenceItem = SourceItem;
324
339
 
325
340
  /**
@@ -357,6 +372,8 @@ export declare interface DocReferencesProps {
357
372
 
358
373
  declare type EnumDisplayStyle = 'select' | 'checkbox' | 'radio' | 'multi-select';
359
374
 
375
+ export declare const enUS: Locale;
376
+
360
377
  declare type ExtendedParamType = 'time' | 'file';
361
378
 
362
379
  declare type FileSubType = 'default' | 'jpg' | 'png' | 'svg' | 'doc' | 'ppt' | 'excel' | 'txt' | 'markdown' | 'zip';
@@ -367,6 +384,12 @@ declare type FileSubType = 'default' | 'jpg' | 'png' | 'svg' | 'doc' | 'ppt' | '
367
384
  */
368
385
  declare type FileUploader = (file: Blob, uploadUrl: string) => Promise<void>;
369
386
 
387
+ /** 获取当前全局 locale */
388
+ export declare function getGlobalLocale(): Locale;
389
+
390
+ /** 获取当前全局 locale 名称 */
391
+ export declare function getGlobalLocaleName(): string;
392
+
370
393
  /**
371
394
  * HistoryCard 历史卡片组件 (SDK 版本)
372
395
  * 用于展示历史对话中的 card 类型消息(只读模式)
@@ -448,6 +471,40 @@ export declare const loadEchartsScript: () => Promise<void>;
448
471
 
449
472
  export declare const loadMermaidScript: () => Promise<void>;
450
473
 
474
+ /** 词条对象类型:保留 zhCN 的结构,但叶子节点统一为 string */
475
+ export declare type Locale = DeepStringify<typeof zhCN>;
476
+
477
+ export declare const LocaleContext: default_2.Context<LocaleContextValue>;
478
+
479
+ export declare interface LocaleContextValue {
480
+ locale: Locale;
481
+ localeName: string;
482
+ }
483
+
484
+ /**
485
+ * LocaleProvider
486
+ *
487
+ * 用法示例:
488
+ * ```tsx
489
+ * import { LocaleProvider, enUS } from '@alicloud/appflow-chat';
490
+ *
491
+ * <LocaleProvider locale={enUS} localeName="en-US">
492
+ * <App />
493
+ * </LocaleProvider>
494
+ * ```
495
+ */
496
+ export declare const LocaleProvider: default_2.FC<LocaleProviderProps>;
497
+
498
+ export declare interface LocaleProviderProps {
499
+ /** 完整 locale 对象,默认使用内置 zhCN */
500
+ locale?: Locale;
501
+ /** 语言标识,如 'zh-CN' / 'en-US' */
502
+ localeName?: string;
503
+ /** 部分覆盖:在选定 locale 基础上 deep merge 自定义文案 */
504
+ overrides?: DeepPartial<Locale>;
505
+ children: default_2.ReactNode;
506
+ }
507
+
451
508
  /**
452
509
  * MarkdownRenderer - Markdown渲染组件
453
510
  *
@@ -672,6 +729,9 @@ export declare interface RichMessageBubbleProps {
672
729
  onWebSearchClick?: (items: DocReferenceItem[]) => void;
673
730
  }
674
731
 
732
+ /** 设置全局 locale,由 LocaleProvider 在挂载/更新时调用 */
733
+ export declare function setGlobalLocale(locale: Locale, localeName?: string): void;
734
+
675
735
  /**
676
736
  * ChatService - 独立的聊天服务类
677
737
  * 用于自定义UI场景,不依赖React
@@ -744,6 +804,21 @@ export declare interface SourceItem {
744
804
 
745
805
  declare type TimeSubType = 'year-month' | 'year-month-day' | 'datetime';
746
806
 
807
+ /**
808
+ * 全局翻译函数(非 Hook 版本)
809
+ *
810
+ * @example
811
+ * translate('common.loading') // '加载中...'
812
+ * translate('humanVerify.placeholder.input', { title: '名称' }) // '请输入名称'
813
+ */
814
+ export declare function translate(key: TranslationKey, params?: TranslationParams): string;
815
+
816
+ /** 所有合法的扁平 key 路径,如 'common.loading' | 'humanVerify.placeholder.input' */
817
+ export declare type TranslationKey = DeepKey<typeof zhCN>;
818
+
819
+ /** 插值参数 */
820
+ export declare type TranslationParams = Record<string, string | number>;
821
+
747
822
  /**
748
823
  * 上传配置
749
824
  */
@@ -787,6 +862,24 @@ export declare const useCustomParamsRenderer: (schema: CustomParamSchema) => {
787
862
 
788
863
  export declare const useRichBubbleContext: () => RichBubbleContextValue;
789
864
 
865
+ /**
866
+ * 在 React 组件内消费国际化文案
867
+ *
868
+ * @example
869
+ * const { t } = useTranslation();
870
+ * <Input placeholder={t('humanVerify.placeholder.input', { title: '名称' })} />
871
+ */
872
+ export declare function useTranslation(): UseTranslationResult;
873
+
874
+ export declare interface UseTranslationResult {
875
+ /** 翻译函数 */
876
+ t: (key: TranslationKey, params?: TranslationParams) => string;
877
+ /** 当前 locale 对象 */
878
+ locale: Locale;
879
+ /** 当前语言标识 */
880
+ localeName: string;
881
+ }
882
+
790
883
  /**
791
884
  * 校验 CustomParams 的值
792
885
  * @param schema Schema 定义
@@ -894,4 +987,80 @@ export declare interface WebSearchPanelProps {
894
987
  style?: default_2.CSSProperties;
895
988
  }
896
989
 
990
+ /**
991
+ * 中文词条(默认语言)
992
+ *
993
+ * 该文件作为 Locale 类型的基准结构,所有其他语言文件必须实现相同的 key 结构。
994
+ * 新增文案时优先在此处添加,再补齐其他语言文件。
995
+ */
996
+ export declare const zhCN: {
997
+ readonly common: {
998
+ readonly loading: "加载中...";
999
+ readonly confirm: "确定";
1000
+ readonly cancel: "取消";
1001
+ readonly submit: "提交";
1002
+ readonly retry: "重试";
1003
+ readonly copy: "复制";
1004
+ readonly copied: "已复制";
1005
+ readonly placeholderSelect: "请选择";
1006
+ };
1007
+ readonly humanVerify: {
1008
+ readonly requiredAll: "请填写所有必填项";
1009
+ readonly submitted: "已提交";
1010
+ readonly pending: "待提交";
1011
+ readonly placeholder: {
1012
+ readonly input: "请输入{title}";
1013
+ readonly select: "请选择";
1014
+ };
1015
+ readonly file: {
1016
+ readonly supportAll: "支持所有文件格式";
1017
+ readonly supportFormats: "支持 {formats} 格式";
1018
+ readonly uploadButton: "选择文件";
1019
+ readonly uploading: "上传中";
1020
+ readonly upload: "上传";
1021
+ readonly defaultFileName: "文件";
1022
+ readonly maxSizeError: "文件大小不能超过 {size}";
1023
+ readonly uploadFailed: "文件上传失败";
1024
+ readonly tokenFailed: "获取上传凭证失败";
1025
+ readonly uploaderNotConfigured: "上传功能未配置";
1026
+ readonly uploadMethodNotConfigured: "文件上传方法未配置";
1027
+ readonly getFileIdFailed: "获取文件ID失败";
1028
+ };
1029
+ };
1030
+ readonly webSearch: {
1031
+ readonly title: "搜索结果";
1032
+ readonly foundPages: "已搜索到{count}个网页";
1033
+ };
1034
+ readonly source: {
1035
+ readonly title: "参考资料";
1036
+ readonly answerFrom: "回答来源:";
1037
+ readonly imageFrom: "图片来源:";
1038
+ };
1039
+ readonly rich: {
1040
+ readonly stepLabel: "步骤{index}:";
1041
+ readonly emptyContent: "暂无内容";
1042
+ };
1043
+ readonly markdown: {
1044
+ readonly copyCode: "复制代码";
1045
+ readonly copied: "已复制!";
1046
+ readonly copy: "复制";
1047
+ readonly copiedShort: "已复制";
1048
+ readonly deepThinking: "深度思考";
1049
+ readonly chartLoading: "图表加载中...";
1050
+ readonly tableLoading: "表格加载中...";
1051
+ readonly tableLoadFailed: "表格数据加载失败,请检查数据格式";
1052
+ readonly chartLoadFailed: "图表数据加载失败,请检查数据格式";
1053
+ readonly mermaidLoadFailed: "Mermaid 库加载失败";
1054
+ readonly mermaidNotLoaded: "Mermaid 未正确加载";
1055
+ readonly mermaidRenderFailed: "Mermaid 图表渲染失败";
1056
+ };
1057
+ readonly message: {
1058
+ readonly like: "点赞";
1059
+ readonly dislike: "点踩";
1060
+ readonly regenerate: "重新生成";
1061
+ readonly copy: "复制";
1062
+ readonly copied: "已复制";
1063
+ };
1064
+ };
1065
+
897
1066
  export { }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alicloud/appflow-chat",
3
- "version": "0.0.4-beta.4",
3
+ "version": "0.0.4-beta.6",
4
4
  "description": "Appflow-Chat AI聊天机器人组件库,提供聊天服务和UI组件",
5
5
  "type": "module",
6
6
  "main": "./dist/appflow-chat.cjs.js",
@@ -48,31 +48,19 @@
48
48
  "@types/react-dom": "^18.3.1",
49
49
  "@vitejs/plugin-react": "^4.3.3",
50
50
  "antd": "^5.24.8",
51
- "dayjs": "^1.11.0",
52
51
  "eslint": "^9.13.0",
53
52
  "eslint-plugin-react-hooks": "^5.0.0",
54
53
  "eslint-plugin-react-refresh": "^0.4.14",
55
54
  "less": "^4.2.0",
56
- "moment": "^2.29.0",
57
55
  "typescript": "~5.6.2",
58
56
  "typescript-eslint": "^8.11.0",
59
57
  "vite": "^5.4.10",
60
58
  "vite-plugin-dts": "^4.3.0"
61
59
  },
62
60
  "peerDependencies": {
63
- "antd": "^4.0.0 || ^5.0.0",
61
+ "antd": "^5.0.0",
64
62
  "react": "^18.0.0",
65
- "react-dom": "^18.0.0",
66
- "dayjs": "^1.11.0",
67
- "moment": "^2.29.0"
68
- },
69
- "peerDependenciesMeta": {
70
- "dayjs": {
71
- "optional": true
72
- },
73
- "moment": {
74
- "optional": true
75
- }
63
+ "react-dom": "^18.0.0"
76
64
  },
77
65
  "files": [
78
66
  "dist",
@@ -3,6 +3,7 @@ import { Button, Input, InputNumber, Switch, Space } from 'antd';
3
3
  import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
4
4
  import { ArrayFieldProps, CustomParamSchema, FileSubType, sortPropertiesByOrder } from './types';
5
5
  import styled from 'styled-components';
6
+ import { useTranslation } from '../../../i18n';
6
7
 
7
8
  // 图片类型列表
8
9
  const IMAGE_TYPES: FileSubType[] = ['jpg', 'png', 'svg'];
@@ -191,7 +192,8 @@ export const ArrayField: React.FC<ArrayFieldProps> = ({
191
192
  }) => {
192
193
  const { Title, Description, Items } = schema;
193
194
  const displayTitle = Title || name;
194
-
195
+ const { t } = useTranslation();
196
+
195
197
  // 计算当前数组的完整路径
196
198
  const currentPath = fieldPath ? `${fieldPath}.${name}` : name;
197
199
 
@@ -370,7 +372,7 @@ export const ArrayField: React.FC<ArrayFieldProps> = ({
370
372
  return sortedProperties.map(([propertyName, propertySchema]) => {
371
373
  const isRequired = itemRequired.includes(propertyName);
372
374
  return (
373
- <React.Suspense key={propertyName} fallback={<div>加载中...</div>}>
375
+ <React.Suspense key={propertyName} fallback={<div>{t('common.loading')}</div>}>
374
376
  <FieldRenderer
375
377
  name={propertyName}
376
378
  schema={propertySchema}
@@ -392,7 +394,7 @@ export const ArrayField: React.FC<ArrayFieldProps> = ({
392
394
  // 渲染嵌套数组类型的数组项
393
395
  const renderArrayItemContent = (item: any, index: number) => {
394
396
  return (
395
- <React.Suspense fallback={<div>加载中...</div>}>
397
+ <React.Suspense fallback={<div>{t('common.loading')}</div>}>
396
398
  <FieldRenderer
397
399
  name={`${name}[${index}]`}
398
400
  schema={Items}
@@ -412,7 +414,7 @@ export const ArrayField: React.FC<ArrayFieldProps> = ({
412
414
  // 渲染复杂类型的数组项(file、time、带枚举的类型)
413
415
  const renderComplexItemContent = (item: any, index: number) => {
414
416
  return (
415
- <React.Suspense fallback={<div>加载中...</div>}>
417
+ <React.Suspense fallback={<div>{t('common.loading')}</div>}>
416
418
  <FieldRenderer
417
419
  name={`[${index}]`}
418
420
  schema={Items}
@@ -3,6 +3,7 @@ import { Select, Checkbox, Radio } from 'antd';
3
3
  import type { RadioChangeEvent } from 'antd';
4
4
  import { EnumFieldProps, EnumDisplayStyle } from './types';
5
5
  import styled from 'styled-components';
6
+ import { useTranslation } from '../../../i18n';
6
7
 
7
8
  const { Option } = Select;
8
9
 
@@ -70,7 +71,8 @@ export const EnumField: React.FC<EnumFieldProps> = ({
70
71
  disabled = false,
71
72
  }) => {
72
73
  const { Type } = schema;
73
-
74
+ const { t } = useTranslation();
75
+
74
76
  // 优先从 AssociationPropertyMetadata 中读取,兼容旧的字段
75
77
  const enumValues = useMemo(() => {
76
78
  return schema.AssociationPropertyMetadata?.EnumValues || schema.EnumValues || [];
@@ -152,7 +154,7 @@ export const EnumField: React.FC<EnumFieldProps> = ({
152
154
  onChange={handleSelectChange}
153
155
  disabled={disabled}
154
156
  mode="multiple"
155
- placeholder={`请选择`}
157
+ placeholder={t('common.placeholderSelect')}
156
158
  style={{ width: '100%' }}
157
159
  allowClear
158
160
  >
@@ -177,7 +179,7 @@ export const EnumField: React.FC<EnumFieldProps> = ({
177
179
  onChange={handleSelectChange}
178
180
  disabled={disabled}
179
181
  mode={isMultiple ? 'multiple' : undefined}
180
- placeholder={`请选择`}
182
+ placeholder={t('common.placeholderSelect')}
181
183
  style={{ width: '100%' }}
182
184
  allowClear
183
185
  >
@@ -7,6 +7,7 @@ import { EnumField } from './EnumField';
7
7
  import styled from 'styled-components';
8
8
  import TimeField from './TimeField';
9
9
  import FileField from './FileField';
10
+ import { useTranslation } from '../../../i18n';
10
11
 
11
12
 
12
13
  // ==================== Styled Components ====================
@@ -130,7 +131,8 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
130
131
  }) => {
131
132
  const { Type, Title, Description } = schema;
132
133
  const displayTitle = Title || name;
133
-
134
+ const { t } = useTranslation();
135
+
134
136
  // 获取 Ant Design 的 prefixCls 配置,自动继承用户项目的 ConfigProvider 设置
135
137
  // 使用 ConfigProvider.ConfigContext 获取完整配置
136
138
  const configContext = useContext(ConfigProvider.ConfigContext);
@@ -176,7 +178,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
176
178
  value={value}
177
179
  onChange={(e) => handleChange(e.target.value)}
178
180
  disabled={disabled}
179
- placeholder={`请输入${displayTitle}`}
181
+ placeholder={t('humanVerify.placeholder.input', { title: displayTitle })}
180
182
  />
181
183
  </InputWrapper>
182
184
  );
@@ -189,7 +191,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
189
191
  onChange={handleChange}
190
192
  disabled={disabled}
191
193
  style={{ width: '100%' }}
192
- placeholder={`请输入${displayTitle}`}
194
+ placeholder={t('humanVerify.placeholder.input', { title: displayTitle })}
193
195
  />
194
196
  </InputWrapper>
195
197
  );
@@ -307,7 +309,7 @@ export const FieldRenderer: React.FC<FieldRendererProps> = ({
307
309
  value={value}
308
310
  onChange={(e) => handleChange(e.target.value)}
309
311
  disabled={disabled}
310
- placeholder={`请输入${displayTitle}`}
312
+ placeholder={t('humanVerify.placeholder.input', { title: displayTitle })}
311
313
  />
312
314
  </InputWrapper>
313
315
  );
@@ -10,6 +10,8 @@ import {
10
10
  UploadFileResponse
11
11
  } from './types';
12
12
  import styled from 'styled-components';
13
+ import { useTranslation } from '../../../i18n';
14
+ import { translate } from '../../../i18n';
13
15
 
14
16
  // ==================== Styled Components ====================
15
17
 
@@ -114,16 +116,17 @@ const getAcceptBySubTypes = (subTypes?: FileSubType[]): string => {
114
116
 
115
117
  /**
116
118
  * 根据文件子类型数组获取上传提示文本
119
+ * 使用全局 translate(非 hook 版本)以便在组件外调用
117
120
  */
118
121
  const getUploadHintBySubTypes = (subTypes?: FileSubType[]): string => {
119
122
  if (!subTypes || subTypes.length === 0) {
120
- return '支持所有文件格式';
123
+ return translate('humanVerify.file.supportAll');
121
124
  }
122
-
125
+
123
126
  if (subTypes.includes('default')) {
124
- return '支持所有文件格式';
127
+ return translate('humanVerify.file.supportAll');
125
128
  }
126
-
129
+
127
130
  const hintMap: Record<FileSubType, string> = {
128
131
  'jpg': 'JPG',
129
132
  'png': 'PNG',
@@ -134,11 +137,13 @@ const getUploadHintBySubTypes = (subTypes?: FileSubType[]): string => {
134
137
  'txt': 'TXT',
135
138
  'markdown': 'Markdown',
136
139
  'zip': 'ZIP/RAR/7Z',
137
- 'default': '所有格式',
140
+ 'default': translate('humanVerify.file.supportAll'),
138
141
  };
139
-
142
+
140
143
  const hints = subTypes.map(subType => hintMap[subType] || subType).filter(Boolean);
141
- return hints.length > 0 ? `支持 ${hints.join('、')} 格式` : '支持所有文件格式';
144
+ return hints.length > 0
145
+ ? translate('humanVerify.file.supportFormats', { formats: hints.join('、') })
146
+ : translate('humanVerify.file.supportAll');
142
147
  };
143
148
 
144
149
  /**
@@ -164,6 +169,7 @@ export const FileField: React.FC<FileFieldProps> = ({
164
169
  uploadSender,
165
170
  fileUploader,
166
171
  }) => {
172
+ const { t } = useTranslation();
167
173
  // 优先从 AssociationPropertyMetadata.SubType 读取,兼容旧的 FileSubType 字段
168
174
  const subTypes = useMemo((): FileSubType[] => {
169
175
  const subTypeArray = schema.AssociationPropertyMetadata?.SubType;
@@ -190,7 +196,7 @@ export const FileField: React.FC<FileFieldProps> = ({
190
196
  if (Array.isArray(value)) {
191
197
  return value.map((file: any, index: number) => ({
192
198
  uid: file.uid || `${index}`,
193
- name: file.name || `文件${index + 1}`,
199
+ name: file.name || `${t('humanVerify.file.defaultFileName')}${index + 1}`,
194
200
  status: 'done' as const,
195
201
  url: file.url,
196
202
  ...file,
@@ -199,7 +205,7 @@ export const FileField: React.FC<FileFieldProps> = ({
199
205
  if (typeof value === 'object') {
200
206
  return [{
201
207
  uid: value.uid || '0',
202
- name: value.name || '文件',
208
+ name: value.name || t('humanVerify.file.defaultFileName'),
203
209
  status: 'done' as const,
204
210
  url: value.url,
205
211
  ...value,
@@ -227,7 +233,7 @@ export const FileField: React.FC<FileFieldProps> = ({
227
233
  */
228
234
  const getUploadToken = useCallback(async (fileName: string): Promise<UploadTokenResponse | null> => {
229
235
  if (!uploadSender) {
230
- message.error('上传功能未配置');
236
+ message.error(t('humanVerify.file.uploaderNotConfigured'));
231
237
  return null;
232
238
  }
233
239
 
@@ -238,7 +244,7 @@ export const FileField: React.FC<FileFieldProps> = ({
238
244
  });
239
245
 
240
246
  if (!response) {
241
- message.error('获取上传凭证失败');
247
+ message.error(t('humanVerify.file.tokenFailed'));
242
248
  return null;
243
249
  }
244
250
 
@@ -255,14 +261,14 @@ export const FileField: React.FC<FileFieldProps> = ({
255
261
  return parsedResponse as UploadTokenResponse;
256
262
  }
257
263
 
258
- message.error('获取上传凭证失败');
264
+ message.error(t('humanVerify.file.tokenFailed'));
259
265
  return null;
260
266
  } catch (error) {
261
- console.error('获取上传凭证失败:', error);
262
- message.error('获取上传凭证失败');
267
+ console.error(t('humanVerify.file.tokenFailed'), error);
268
+ message.error(t('humanVerify.file.tokenFailed'));
263
269
  return null;
264
270
  }
265
- }, [uploadSender]);
271
+ }, [uploadSender, t]);
266
272
 
267
273
  /**
268
274
  * 获取文件 ID(文件上传专用)
@@ -298,10 +304,10 @@ export const FileField: React.FC<FileFieldProps> = ({
298
304
 
299
305
  return null;
300
306
  } catch (error) {
301
- console.error('获取文件ID失败:', error);
307
+ console.error(t('humanVerify.file.getFileIdFailed'), error);
302
308
  return null;
303
309
  }
304
- }, [uploadSender]);
310
+ }, [uploadSender, t]);
305
311
 
306
312
  /**
307
313
  * 图片上传处理
@@ -311,12 +317,12 @@ export const FileField: React.FC<FileFieldProps> = ({
311
317
  // 1. 获取预签名 URL
312
318
  const tokenResponse = await getUploadToken(file.name);
313
319
  if (!tokenResponse) {
314
- throw new Error('获取上传凭证失败');
320
+ throw new Error(t('humanVerify.file.tokenFailed'));
315
321
  }
316
322
 
317
323
  // 2. 上传文件到 OSS
318
324
  if (!fileUploader) {
319
- throw new Error('文件上传方法未配置');
325
+ throw new Error(t('humanVerify.file.uploadMethodNotConfigured'));
320
326
  }
321
327
  const blob = new Blob([file]);
322
328
  await fileUploader(blob, tokenResponse.uploadUrl);
@@ -329,7 +335,7 @@ export const FileField: React.FC<FileFieldProps> = ({
329
335
  type: file.type,
330
336
  size: file.size,
331
337
  };
332
- }, [getUploadToken, fileUploader]);
338
+ }, [getUploadToken, fileUploader, t]);
333
339
 
334
340
  /**
335
341
  * 文件上传处理
@@ -339,12 +345,12 @@ export const FileField: React.FC<FileFieldProps> = ({
339
345
  // 1. 获取预签名 URL
340
346
  const tokenResponse = await getUploadToken(file.name);
341
347
  if (!tokenResponse) {
342
- throw new Error('获取上传凭证失败');
348
+ throw new Error(t('humanVerify.file.tokenFailed'));
343
349
  }
344
350
 
345
351
  // 2. 上传文件到 OSS
346
352
  if (!fileUploader) {
347
- throw new Error('文件上传方法未配置');
353
+ throw new Error(t('humanVerify.file.uploadMethodNotConfigured'));
348
354
  }
349
355
  const blob = new Blob([file]);
350
356
  await fileUploader(blob, tokenResponse.uploadUrl);
@@ -362,7 +368,7 @@ export const FileField: React.FC<FileFieldProps> = ({
362
368
  size: file.size,
363
369
  fileType: file.name.split('.').pop(),
364
370
  };
365
- }, [getUploadToken, fileUploader, getFileId]);
371
+ }, [getUploadToken, fileUploader, getFileId, t]);
366
372
 
367
373
  /**
368
374
  * 自定义上传逻辑
@@ -379,7 +385,7 @@ export const FileField: React.FC<FileFieldProps> = ({
379
385
  onSuccess?.({ url }, new XMLHttpRequest());
380
386
  } catch (error) {
381
387
  onError?.(error as Error);
382
- message.error('文件上传失败');
388
+ message.error(t('humanVerify.file.uploadFailed'));
383
389
  }
384
390
  return;
385
391
  }
@@ -399,12 +405,12 @@ export const FileField: React.FC<FileFieldProps> = ({
399
405
  onSuccess?.(result, new XMLHttpRequest());
400
406
  } catch (error) {
401
407
  onError?.(error as Error);
402
- message.error('文件上传失败');
408
+ message.error(t('humanVerify.file.uploadFailed'));
403
409
  } finally {
404
410
  setUploading(false);
405
411
  }
406
412
  },
407
- [uploadSender, fileUploader, isImage, handleImageUpload, handleFileUpload]
413
+ [uploadSender, fileUploader, isImage, handleImageUpload, handleFileUpload, t]
408
414
  );
409
415
 
410
416
  // 处理文件变化
@@ -440,19 +446,21 @@ export const FileField: React.FC<FileFieldProps> = ({
440
446
  // 文件大小限制(默认 10MB)
441
447
  const maxSize = 10 * 1024 * 1024;
442
448
  if (file.size > maxSize) {
443
- message.error('文件大小不能超过 10MB');
449
+ message.error(t('humanVerify.file.maxSizeError', { size: '10MB' }));
444
450
  return Upload.LIST_IGNORE;
445
451
  }
446
452
  return true;
447
453
  },
448
- []
454
+ [t]
449
455
  );
450
456
 
451
457
  // 上传按钮的加载指示器
452
458
  const uploadButton = (
453
459
  <div>
454
460
  {uploading ? <LoadingOutlined /> : <PlusOutlined />}
455
- <div style={{ marginTop: 8 }}>{uploading ? '上传中' : '上传'}</div>
461
+ <div style={{ marginTop: 8 }}>
462
+ {uploading ? t('humanVerify.file.uploading') : t('humanVerify.file.upload')}
463
+ </div>
456
464
  </div>
457
465
  );
458
466
 
@@ -498,7 +506,7 @@ export const FileField: React.FC<FileFieldProps> = ({
498
506
  disabled={disabled || uploading}
499
507
  loading={uploading}
500
508
  >
501
- {uploading ? '上传中' : '选择文件'}
509
+ {uploading ? t('humanVerify.file.uploading') : t('humanVerify.file.uploadButton')}
502
510
  </Button>
503
511
  </Upload>
504
512
  <FileHint>{hint}</FileHint>
@@ -1,6 +1,7 @@
1
1
  import React, { useCallback } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { ObjectFieldProps, sortPropertiesByOrder } from './types';
4
+ import { useTranslation } from '../../../i18n';
4
5
 
5
6
  // 前向声明,避免循环依赖
6
7
  const FieldRenderer = React.lazy(() => import('./FieldRenderer'));
@@ -71,7 +72,8 @@ export const ObjectField: React.FC<ObjectFieldProps> = ({
71
72
  }) => {
72
73
  const { Title, Description, Properties, Required: RequiredFields = [] } = schema;
73
74
  const displayTitle = Title || name;
74
-
75
+ const { t } = useTranslation();
76
+
75
77
  // 计算当前对象的完整路径
76
78
  const currentPath = fieldPath ? `${fieldPath}.${name}` : name;
77
79
 
@@ -108,7 +110,7 @@ export const ObjectField: React.FC<ObjectFieldProps> = ({
108
110
  {sortedProperties.map(([propertyName, propertySchema]) => {
109
111
  const isRequired = RequiredFields.includes(propertyName);
110
112
  return (
111
- <React.Suspense key={propertyName} fallback={<div>加载中...</div>}>
113
+ <React.Suspense key={propertyName} fallback={<div>{t('common.loading')}</div>}>
112
114
  <FieldRenderer
113
115
  name={propertyName}
114
116
  schema={propertySchema}