@object-ui/i18n 3.1.5 → 3.3.0

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 (42) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +4 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +2 -0
  5. package/dist/locales/ar.d.ts +202 -1
  6. package/dist/locales/ar.js +202 -1
  7. package/dist/locales/de.d.ts +202 -1
  8. package/dist/locales/de.js +211 -1
  9. package/dist/locales/en.d.ts +201 -0
  10. package/dist/locales/en.js +211 -0
  11. package/dist/locales/es.d.ts +202 -1
  12. package/dist/locales/es.js +202 -1
  13. package/dist/locales/fr.d.ts +202 -1
  14. package/dist/locales/fr.js +211 -1
  15. package/dist/locales/index.d.ts +2041 -9
  16. package/dist/locales/ja.d.ts +213 -1
  17. package/dist/locales/ja.js +213 -1
  18. package/dist/locales/ko.d.ts +202 -1
  19. package/dist/locales/ko.js +202 -1
  20. package/dist/locales/pt.d.ts +202 -1
  21. package/dist/locales/pt.js +202 -1
  22. package/dist/locales/ru.d.ts +213 -1
  23. package/dist/locales/ru.js +222 -1
  24. package/dist/locales/zh.d.ts +202 -1
  25. package/dist/locales/zh.js +212 -1
  26. package/dist/useSafeTranslation.d.ts +5 -0
  27. package/dist/useSafeTranslation.js +46 -0
  28. package/package.json +6 -6
  29. package/src/__tests__/i18n.test.ts +27 -0
  30. package/src/index.ts +3 -0
  31. package/src/locales/ar.ts +202 -1
  32. package/src/locales/de.ts +211 -1
  33. package/src/locales/en.ts +211 -0
  34. package/src/locales/es.ts +202 -1
  35. package/src/locales/fr.ts +211 -1
  36. package/src/locales/ja.ts +213 -1
  37. package/src/locales/ko.ts +202 -1
  38. package/src/locales/pt.ts +202 -1
  39. package/src/locales/ru.ts +222 -1
  40. package/src/locales/zh.ts +212 -1
  41. package/src/useSafeTranslation.ts +49 -0
  42. package/tsconfig.tsbuildinfo +1 -1
package/src/locales/ru.ts CHANGED
@@ -32,6 +32,8 @@ const ru = {
32
32
  noResults: 'Результаты не найдены',
33
33
  required: 'Обязательно',
34
34
  optional: 'Необязательно',
35
+ selectOption: 'Выберите вариант',
36
+ select: 'Выбрать...',
35
37
  },
36
38
  validation: {
37
39
  required: 'Поле {{field}} обязательно для заполнения',
@@ -54,6 +56,11 @@ const ru = {
54
56
  saveError: 'Ошибка сохранения',
55
57
  unsavedChanges: 'У вас есть несохранённые изменения. Вы уверены, что хотите покинуть страницу?',
56
58
  stepOf: 'Шаг {{current}} из {{total}}',
59
+ createTitle: 'Создать {{object}}',
60
+ editTitle: 'Редактировать {{object}}',
61
+ createDescription: 'Добавить новый {{object}} в базу данных.',
62
+ editDescription: 'Обновить данные {{object}}',
63
+ saveRecord: 'Сохранить запись',
57
64
  },
58
65
  table: {
59
66
  rowsPerPage: 'Строк на странице',
@@ -130,6 +137,22 @@ const ru = {
130
137
  deleteCard: 'Удалить карточку',
131
138
  deleteColumn: 'Удалить колонку',
132
139
  },
140
+ view: {
141
+ rename: 'Переименовать',
142
+ duplicateView: 'Дублировать представление',
143
+ shareView: 'Поделиться представлением',
144
+ setAsDefault: 'Установить по умолчанию',
145
+ pinView: 'Закрепить представление',
146
+ unpinView: 'Открепить представление',
147
+ changeViewType: 'Изменить тип представления',
148
+ deleteView: 'Удалить представление',
149
+ addView: 'Добавить представление',
150
+ unsavedChanges: 'Несохранённые изменения',
151
+ saveAsView: 'Сохранить как представление',
152
+ moreViews: 'ещё {{count}}',
153
+ activeFilters: 'Фильтры активны',
154
+ activeSort: 'Сортировка активна',
155
+ },
133
156
  detail: {
134
157
  back: 'Назад',
135
158
  edit: 'Редактировать',
@@ -164,11 +187,157 @@ const ru = {
164
187
  viewAll: 'Показать все',
165
188
  new: 'Создать',
166
189
  emptyValue: '—',
190
+ activity: 'Активность',
191
+ editRow: 'Редактировать',
192
+ deleteRow: 'Удалить',
193
+ deleteRowConfirmation: 'Вы уверены, что хотите удалить эту запись?',
194
+ actions: 'Действия',
195
+ previousPage: 'Назад',
196
+ nextPage: 'Далее',
197
+ pageOf: 'Страница {{current}} из {{total}}',
198
+ sortBy: 'Сортировать по',
199
+ filterPlaceholder: 'Фильтр...',
200
+ highlightFields: 'Ключевые поля',
201
+ // Comments
202
+ comments: 'Комментарии',
203
+ searchComments: 'Поиск комментариев…',
204
+ addCommentPlaceholder: 'Добавить комментарий… (Ctrl+Enter для отправки)',
205
+ noMatchingComments: 'Нет совпадающих комментариев',
206
+ noCommentsYet: 'Комментариев пока нет',
207
+ pinned: 'Закреплено',
208
+ pin: 'Закрепить',
209
+ unpin: 'Открепить',
210
+ justNow: 'только что',
211
+ minutesAgo: '{{count}} мин. назад',
212
+ hoursAgo: '{{count}} ч. назад',
213
+ daysAgo: '{{count}} дн. назад',
214
+ // Attachments
215
+ dropFilesToUpload: 'Перетащите файлы сюда или нажмите для загрузки',
216
+ attachmentCount: '{{count}} вложение',
217
+ attachmentCountPlural: '{{count}} вложений',
218
+ removeAttachment: 'Удалить вложение',
219
+ // Diff
220
+ unifiedDiff: 'Единое представление',
221
+ sideBySideDiff: 'Параллельное представление',
222
+ noChanges: 'Нет изменений',
223
+ previousVersion: 'Предыдущая',
224
+ currentVersion: 'Текущая',
225
+ // Discussion
226
+ discussion: 'Обсуждение',
227
+ showDiscussion: 'Показать обсуждение ({{count}})',
228
+ hideDiscussion: 'Скрыть обсуждение',
229
+ // Rich text editor
230
+ bold: 'Жирный (Ctrl+B)',
231
+ italic: 'Курсив (Ctrl+I)',
232
+ listFormat: 'Список',
233
+ inlineCode: 'Строчный код',
234
+ mentionSomeone: 'Упомянуть кого-то',
235
+ preview: 'Предпросмотр',
236
+ submitComment: 'Отправить (Ctrl+Enter)',
237
+ writeComment: 'Написать комментарий…',
238
+ // Subscription
239
+ subscribedTooltip: 'Подписаны — нажмите для отписки',
240
+ unsubscribedTooltip: 'Подписаться на уведомления',
241
+ // Navigation
242
+ firstRecord: 'Первая запись (Home)',
243
+ previousRecordKey: 'Предыдущая запись (←)',
244
+ nextRecordKey: 'Следующая запись (→)',
245
+ lastRecord: 'Последняя запись (End)',
246
+ noRecords: 'Нет записей',
247
+ searchWhileNavigating: 'Поиск при навигации',
248
+ searchRecords: 'Поиск записей…',
249
+ // Activity timeline
250
+ allActivity: 'Вся активность',
251
+ commentsOnly: 'Только комментарии',
252
+ fieldChangesFilter: 'Изменения полей',
253
+ tasksOnly: 'Только задачи',
254
+ leaveCommentPlaceholder: 'Оставить комментарий… (Ctrl+Enter для отправки)',
255
+ noActivity: 'Нет зарегистрированной активности',
256
+ loadMore: 'Загрузить ещё',
257
+ edited: '(изменено)',
258
+ via: 'через {{source}}',
259
+ // Replies
260
+ replyCount: '{{count}} ответ',
261
+ replyCountPlural: '{{count}} ответов',
262
+ replyPlaceholder: 'Ответить…',
263
+ filterActivity: 'Фильтр активности',
264
+ openDiscussion: 'Открыть панель обсуждения',
265
+ closeDiscussion: 'Закрыть панель обсуждения',
266
+ subscribeAriaLabel: 'Подписаться на уведомления',
267
+ unsubscribeAriaLabel: 'Отписаться от уведомлений',
268
+ clearSearch: 'Очистить поиск',
167
269
  },
168
270
  chart: {
169
271
  noData: 'Нет данных для графика',
170
272
  loading: 'Загрузка графика...',
171
273
  },
274
+ map: {
275
+ searchLocations: 'Поиск местоположений…',
276
+ locationDetails: 'Детали местоположения',
277
+ markersCount: '{{count}} маркеров',
278
+ invalidCoordinates: '{{count}} запись с отсутствующими или недействительными координатами исключена из карты.',
279
+ invalidCoordinatesPlural: '{{count}} записей с отсутствующими или недействительными координатами исключены из карты.',
280
+ },
281
+ workflow: {
282
+ draft: 'Черновик',
283
+ active: 'Активный',
284
+ paused: 'Приостановлен',
285
+ completed: 'Завершён',
286
+ cancelled: 'Отменён',
287
+ save: 'Сохранить',
288
+ publish: 'Опубликовать',
289
+ addNode: 'Добавить узел',
290
+ workflowFlow: 'Рабочий процесс',
291
+ properties: 'Свойства',
292
+ connections: 'Соединения',
293
+ quickConnect: 'Быстрое соединение',
294
+ fromPlaceholder: 'От...',
295
+ toPlaceholder: 'К...',
296
+ connect: 'Соединить',
297
+ label: 'Метка',
298
+ type: 'Тип',
299
+ description: 'Описание',
300
+ assignee: 'Ответственный',
301
+ assigneeType: 'Тип ответственного',
302
+ user: 'Пользователь',
303
+ role: 'Роль',
304
+ group: 'Группа',
305
+ expression: 'Выражение',
306
+ timeoutMinutes: 'Тайм-аут (минуты)',
307
+ selectNodeToEdit: 'Выберите узел для редактирования его свойств',
308
+ workflowTitle: 'Название рабочего процесса',
309
+ newWorkflow: 'Новый рабочий процесс',
310
+ outConnections: '{{count}} выходов',
311
+ start: 'Начало',
312
+ end: 'Конец',
313
+ task: 'Задача',
314
+ userTask: 'Задача пользователя',
315
+ serviceTask: 'Сервисная задача',
316
+ scriptTask: 'Скриптовая задача',
317
+ approval: 'Одобрение',
318
+ condition: 'Условие',
319
+ parallelGateway: 'Параллельный шлюз',
320
+ joinGateway: 'Шлюз объединения',
321
+ boundaryEvent: 'Граничное событие',
322
+ delay: 'Задержка',
323
+ notification: 'Уведомление',
324
+ webhook: 'Webhook',
325
+ allowConcurrency: 'Разрешить (параллельное выполнение)',
326
+ forbidConcurrency: 'Запретить (пропустить новые)',
327
+ replaceConcurrency: 'Заменить (отменить текущие)',
328
+ queueConcurrency: 'Очередь (выполнить после текущего)',
329
+ conditionEvent: 'Условие',
330
+ manualEvent: 'Ручной',
331
+ webhookEvent: 'Webhook',
332
+ timerEvent: 'Таймер',
333
+ signalEvent: 'Сигнал',
334
+ versionHistory: 'История версий',
335
+ importBpmn: 'Импорт BPMN',
336
+ exportBpmn: 'Экспорт BPMN',
337
+ undo: 'Отменить',
338
+ redo: 'Повторить',
339
+ resetZoom: 'Сбросить масштаб',
340
+ },
172
341
  dashboard: {
173
342
  addWidget: 'Добавить виджет',
174
343
  removeWidget: 'Удалить виджет',
@@ -322,6 +491,58 @@ const ru = {
322
491
  modeLight: 'Светлая',
323
492
  modeDark: 'Тёмная',
324
493
  mobilePreview: 'Мобильный предпросмотр',
494
+ objectManager: {
495
+ title: 'Менеджер объектов',
496
+ addObject: 'Новый объект',
497
+ searchPlaceholder: 'Поиск объектов…',
498
+ noObjects: 'Объекты не найдены.',
499
+ objectName: 'Имя API',
500
+ objectLabel: 'Метка',
501
+ pluralLabel: 'Метка (мн. число)',
502
+ icon: 'Иконка',
503
+ selectIcon: 'Выбрать иконку…',
504
+ group: 'Группа',
505
+ noGroup: 'Без группы',
506
+ sortOrder: 'Порядок сортировки',
507
+ enabled: 'Включён',
508
+ relationships: 'Связи',
509
+ systemBadge: 'Системный',
510
+ fieldCount: '{{count}} полей',
511
+ ungrouped: 'Без группы',
512
+ deleteConfirmTitle: 'Удалить объект?',
513
+ deleteConfirmMessage: 'Объект и все его поля будут удалены безвозвратно. Это действие невозможно отменить.',
514
+ },
515
+ fieldDesigner: {
516
+ title: 'Дизайнер полей',
517
+ addField: 'Новое поле',
518
+ searchPlaceholder: 'Поиск полей…',
519
+ allTypes: 'Все типы',
520
+ noFields: 'Поля не найдены.',
521
+ fieldName: 'Имя API',
522
+ fieldLabel: 'Метка',
523
+ fieldType: 'Тип',
524
+ fieldGroup: 'Группа',
525
+ description: 'Описание',
526
+ required: 'Обязательное',
527
+ unique: 'Уникальное',
528
+ readOnly: 'Только чтение',
529
+ hidden: 'Скрытое',
530
+ indexed: 'Индексированное',
531
+ externalId: 'Внешний ID',
532
+ trackHistory: 'Отслеживание истории',
533
+ defaultValue: 'Значение по умолчанию',
534
+ placeholder: 'Подсказка',
535
+ referenceTo: 'Ссылка на',
536
+ formula: 'Формула',
537
+ options: 'Варианты',
538
+ addOption: 'Добавить вариант',
539
+ validationRules: 'Правила валидации',
540
+ addRule: 'Добавить правило',
541
+ systemBadge: 'Системное',
542
+ ungrouped: 'Общее',
543
+ deleteConfirmTitle: 'Удалить поле?',
544
+ deleteConfirmMessage: 'Поле будет удалено безвозвратно. Существующие данные будут потеряны.',
545
+ },
325
546
  },
326
547
  console: {
327
548
  title: 'Консоль ObjectStack',
@@ -402,7 +623,7 @@ const ru = {
402
623
  generalHint: 'Название, описание и тип представления',
403
624
  toolbar: 'Панель инструментов',
404
625
  toolbarHint: 'Поиск, фильтр, сортировка, группировка и переключение плотности',
405
- toolbarEnabledCount: '{{count}} of {{total}} enabled',
626
+ toolbarEnabledCount: '{{count}} из {{total}} включено',
406
627
  navigationSection: 'Навигация',
407
628
  navigationHint: 'Поведение при нажатии на строку и настройки детального представления',
408
629
  records: 'Записи',
package/src/locales/zh.ts CHANGED
@@ -32,6 +32,8 @@ const zh = {
32
32
  noResults: '未找到结果',
33
33
  required: '必填',
34
34
  optional: '选填',
35
+ selectOption: '请选择',
36
+ select: '选择...',
35
37
  },
36
38
  validation: {
37
39
  required: '{{field}}不能为空',
@@ -54,6 +56,11 @@ const zh = {
54
56
  saveError: '保存失败',
55
57
  unsavedChanges: '您有未保存的更改,确定要离开吗?',
56
58
  stepOf: '第{{current}}步,共{{total}}步',
59
+ createTitle: '新建{{object}}',
60
+ editTitle: '编辑{{object}}',
61
+ createDescription: '向数据库添加新的{{object}}。',
62
+ editDescription: '更新{{object}}的详情',
63
+ saveRecord: '保存记录',
57
64
  },
58
65
  table: {
59
66
  rowsPerPage: '每页行数',
@@ -146,6 +153,22 @@ const zh = {
146
153
  deleteCard: '删除卡片',
147
154
  deleteColumn: '删除列',
148
155
  },
156
+ view: {
157
+ rename: '重命名',
158
+ duplicateView: '复制视图',
159
+ shareView: '共享视图',
160
+ setAsDefault: '设为默认',
161
+ pinView: '固定视图',
162
+ unpinView: '取消固定',
163
+ changeViewType: '更改视图类型',
164
+ deleteView: '删除视图',
165
+ addView: '添加视图',
166
+ unsavedChanges: '未保存的更改',
167
+ saveAsView: '另存为视图',
168
+ moreViews: '还有 {{count}} 个',
169
+ activeFilters: '筛选条件生效中',
170
+ activeSort: '排序生效中',
171
+ },
149
172
  detail: {
150
173
  back: '返回',
151
174
  edit: '编辑',
@@ -191,11 +214,147 @@ const zh = {
191
214
  sortBy: '排序',
192
215
  filterPlaceholder: '筛选...',
193
216
  highlightFields: '关键字段',
217
+ // Comments
218
+ comments: '评论',
219
+ searchComments: '搜索评论…',
220
+ addCommentPlaceholder: '添加评论… (Ctrl+Enter 提交)',
221
+ noMatchingComments: '没有匹配的评论',
222
+ noCommentsYet: '暂无评论',
223
+ pinned: '已置顶',
224
+ pin: '置顶',
225
+ unpin: '取消置顶',
226
+ justNow: '刚刚',
227
+ minutesAgo: '{{count}}分钟前',
228
+ hoursAgo: '{{count}}小时前',
229
+ daysAgo: '{{count}}天前',
230
+ // Attachments
231
+ dropFilesToUpload: '拖拽文件到此处或点击上传',
232
+ attachmentCount: '{{count}} 个附件',
233
+ attachmentCountPlural: '{{count}} 个附件',
234
+ removeAttachment: '移除附件',
235
+ // Diff
236
+ unifiedDiff: '统一视图',
237
+ sideBySideDiff: '并排视图',
238
+ noChanges: '无变更',
239
+ previousVersion: '旧版本',
240
+ currentVersion: '新版本',
241
+ // Discussion
242
+ discussion: '讨论',
243
+ showDiscussion: '显示讨论 ({{count}})',
244
+ hideDiscussion: '隐藏讨论',
245
+ // Rich text editor
246
+ bold: '粗体 (Ctrl+B)',
247
+ italic: '斜体 (Ctrl+I)',
248
+ listFormat: '列表',
249
+ inlineCode: '行内代码',
250
+ mentionSomeone: '提及某人',
251
+ preview: '预览',
252
+ submitComment: '提交 (Ctrl+Enter)',
253
+ writeComment: '写评论…',
254
+ // Subscription
255
+ subscribedTooltip: '已订阅 — 点击取消订阅',
256
+ unsubscribedTooltip: '订阅通知',
257
+ // Navigation
258
+ firstRecord: '第一条记录 (Home)',
259
+ previousRecordKey: '上一条记录 (←)',
260
+ nextRecordKey: '下一条记录 (→)',
261
+ lastRecord: '最后一条记录 (End)',
262
+ noRecords: '无记录',
263
+ searchWhileNavigating: '导航时搜索',
264
+ searchRecords: '搜索记录…',
265
+ // Activity timeline
266
+ allActivity: '全部动态',
267
+ commentsOnly: '仅评论',
268
+ fieldChangesFilter: '字段变更',
269
+ tasksOnly: '仅任务',
270
+ leaveCommentPlaceholder: '留下评论… (Ctrl+Enter 提交)',
271
+ noActivity: '暂无活动记录',
272
+ loadMore: '加载更多',
273
+ edited: '(已编辑)',
274
+ via: '通过 {{source}}',
275
+ // Replies
276
+ replyCount: '{{count}} 条回复',
277
+ replyCountPlural: '{{count}} 条回复',
278
+ replyPlaceholder: '回复…',
279
+ // 无障碍标签
280
+ filterActivity: '筛选活动',
281
+ openDiscussion: '打开讨论面板',
282
+ closeDiscussion: '关闭讨论面板',
283
+ subscribeAriaLabel: '订阅通知',
284
+ unsubscribeAriaLabel: '取消订阅通知',
285
+ clearSearch: '清除搜索',
194
286
  },
195
287
  chart: {
196
288
  noData: '暂无图表数据',
197
289
  loading: '图表加载中...',
198
290
  },
291
+ map: {
292
+ searchLocations: '搜索位置…',
293
+ locationDetails: '位置详情',
294
+ markersCount: '{{count}} 个标记',
295
+ invalidCoordinates: '{{count}} 条记录因坐标缺失或无效已从地图中排除。',
296
+ invalidCoordinatesPlural: '{{count}} 条记录因坐标缺失或无效已从地图中排除。',
297
+ },
298
+ workflow: {
299
+ draft: '草稿',
300
+ active: '活跃',
301
+ paused: '已暂停',
302
+ completed: '已完成',
303
+ cancelled: '已取消',
304
+ save: '保存',
305
+ publish: '发布',
306
+ addNode: '添加节点',
307
+ workflowFlow: '工作流程',
308
+ properties: '属性',
309
+ connections: '连接',
310
+ quickConnect: '快速连接',
311
+ fromPlaceholder: '从...',
312
+ toPlaceholder: '到...',
313
+ connect: '连接',
314
+ label: '标签',
315
+ type: '类型',
316
+ description: '描述',
317
+ assignee: '负责人',
318
+ assigneeType: '负责人类型',
319
+ user: '用户',
320
+ role: '角色',
321
+ group: '组',
322
+ expression: '表达式',
323
+ timeoutMinutes: '超时时间(分钟)',
324
+ selectNodeToEdit: '选择节点以编辑其属性',
325
+ workflowTitle: '工作流标题',
326
+ newWorkflow: '新工作流',
327
+ outConnections: '{{count}} 个输出',
328
+ start: '开始',
329
+ end: '结束',
330
+ task: '任务',
331
+ userTask: '用户任务',
332
+ serviceTask: '服务任务',
333
+ scriptTask: '脚本任务',
334
+ approval: '审批',
335
+ condition: '条件',
336
+ parallelGateway: '并行网关',
337
+ joinGateway: '合并网关',
338
+ boundaryEvent: '边界事件',
339
+ delay: '延迟',
340
+ notification: '通知',
341
+ webhook: 'Webhook',
342
+ allowConcurrency: '允许(并发执行)',
343
+ forbidConcurrency: '禁止(跳过新任务)',
344
+ replaceConcurrency: '替换(取消现有任务)',
345
+ queueConcurrency: '排队(等待当前完成)',
346
+ conditionEvent: '条件',
347
+ manualEvent: '手动',
348
+ webhookEvent: 'Webhook',
349
+ timerEvent: '定时器',
350
+ signalEvent: '信号',
351
+ versionHistory: '版本历史',
352
+ importBpmn: '导入 BPMN',
353
+ exportBpmn: '导出 BPMN',
354
+ undo: '撤销',
355
+ redo: '重做',
356
+ resetZoom: '重置缩放',
357
+ },
199
358
  dashboard: {
200
359
  addWidget: '添加组件',
201
360
  removeWidget: '移除组件',
@@ -349,6 +508,58 @@ const zh = {
349
508
  modeLight: '浅色',
350
509
  modeDark: '深色',
351
510
  mobilePreview: '移动端预览',
511
+ objectManager: {
512
+ title: '对象管理器',
513
+ addObject: '新建对象',
514
+ searchPlaceholder: '搜索对象…',
515
+ noObjects: '未找到对象。',
516
+ objectName: 'API 名称',
517
+ objectLabel: '标签',
518
+ pluralLabel: '复数标签',
519
+ icon: '图标',
520
+ selectIcon: '选择图标…',
521
+ group: '分组',
522
+ noGroup: '无分组',
523
+ sortOrder: '排序',
524
+ enabled: '启用',
525
+ relationships: '关系',
526
+ systemBadge: '系统',
527
+ fieldCount: '{{count}} 个字段',
528
+ ungrouped: '未分组',
529
+ deleteConfirmTitle: '删除对象?',
530
+ deleteConfirmMessage: '这将永久删除该对象及其所有字段。此操作无法撤销。',
531
+ },
532
+ fieldDesigner: {
533
+ title: '字段设计器',
534
+ addField: '新建字段',
535
+ searchPlaceholder: '搜索字段…',
536
+ allTypes: '所有类型',
537
+ noFields: '未找到字段。',
538
+ fieldName: 'API 名称',
539
+ fieldLabel: '标签',
540
+ fieldType: '类型',
541
+ fieldGroup: '分组',
542
+ description: '描述',
543
+ required: '必填',
544
+ unique: '唯一',
545
+ readOnly: '只读',
546
+ hidden: '隐藏',
547
+ indexed: '索引',
548
+ externalId: '外部 ID',
549
+ trackHistory: '追踪历史',
550
+ defaultValue: '默认值',
551
+ placeholder: '占位文本',
552
+ referenceTo: '引用对象',
553
+ formula: '公式',
554
+ options: '选项',
555
+ addOption: '添加选项',
556
+ validationRules: '验证规则',
557
+ addRule: '添加规则',
558
+ systemBadge: '系统',
559
+ ungrouped: '通用',
560
+ deleteConfirmTitle: '删除字段?',
561
+ deleteConfirmMessage: '这将永久删除该字段。此字段中的现有数据将丢失。',
562
+ },
352
563
  },
353
564
  console: {
354
565
  title: 'ObjectStack 控制台',
@@ -434,7 +645,7 @@ const zh = {
434
645
  generalHint: '视图标题、描述和类型',
435
646
  toolbar: '工具栏',
436
647
  toolbarHint: '搜索、筛选、排序、分组和密度切换',
437
- toolbarEnabledCount: '{{count}} of {{total}} enabled',
648
+ toolbarEnabledCount: '已启用 {{count}}/{{total}} 项',
438
649
  navigationSection: '导航',
439
650
  navigationHint: '行点击行为和详情视图设置',
440
651
  records: '记录',
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Safe translation hook with fallback to default strings.
3
+ *
4
+ * When no I18nProvider is available (e.g., in tests or standalone usage),
5
+ * this hook falls back to the provided default translations instead of
6
+ * returning raw i18n keys.
7
+ *
8
+ * @param defaults - Fallback English translations keyed by i18n key
9
+ * @param testKey - A key to test if i18n is properly configured (must be in defaults)
10
+ */
11
+ import { useObjectTranslation } from './provider';
12
+
13
+ export function createSafeTranslation(
14
+ defaults: Record<string, string>,
15
+ testKey: string,
16
+ ) {
17
+ return function useSafeTranslation() {
18
+ try {
19
+ const result = useObjectTranslation();
20
+ const testValue = result.t(testKey);
21
+ if (testValue === testKey) {
22
+ return {
23
+ t: (key: string, options?: Record<string, unknown>) => {
24
+ let value = defaults[key] || key;
25
+ if (options) {
26
+ for (const [k, v] of Object.entries(options)) {
27
+ value = value.replace(`{{${k}}}`, String(v));
28
+ }
29
+ }
30
+ return value;
31
+ },
32
+ };
33
+ }
34
+ return { t: result.t };
35
+ } catch {
36
+ return {
37
+ t: (key: string, options?: Record<string, unknown>) => {
38
+ let value = defaults[key] || key;
39
+ if (options) {
40
+ for (const [k, v] of Object.entries(options)) {
41
+ value = value.replace(`{{${k}}}`, String(v));
42
+ }
43
+ }
44
+ return value;
45
+ },
46
+ };
47
+ }
48
+ };
49
+ }