@nocobase/plugin-localization 2.1.0-beta.29 → 2.1.0-beta.32

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 (33) hide show
  1. package/client-v2.d.ts +2 -0
  2. package/client-v2.js +1 -0
  3. package/dist/ai/ai-employees/lina.d.ts +10 -0
  4. package/dist/ai/ai-employees/lina.js +60 -0
  5. package/dist/client/300.3a4b9b688d36da96.js +10 -0
  6. package/dist/client/i18n-missing-handler.d.ts +1 -17
  7. package/dist/client/index.js +1 -1
  8. package/dist/client-v2/796.b9d793cda3c8b932.js +10 -0
  9. package/dist/client-v2/common/constants.d.ts +13 -0
  10. package/dist/client-v2/common/i18n-missing-handler.d.ts +23 -0
  11. package/dist/{client/Localization.d.ts → client-v2/i18n-missing-handler.d.ts} +1 -2
  12. package/dist/client-v2/index.d.ts +9 -0
  13. package/dist/client-v2/index.js +10 -0
  14. package/dist/client-v2/locale.d.ts +11 -0
  15. package/dist/client-v2/pages/LocalizationPage.d.ts +11 -0
  16. package/dist/client-v2/plugin.d.ts +13 -0
  17. package/dist/externalVersion.js +16 -12
  18. package/dist/locale/en-US.json +15 -1
  19. package/dist/locale/zh-CN.json +15 -1
  20. package/dist/server/actions/aiTranslate.d.ts +14 -0
  21. package/dist/server/actions/aiTranslate.js +127 -0
  22. package/dist/server/actions/localization.js +30 -15
  23. package/dist/server/actions/localizationTexts.js +8 -9
  24. package/dist/server/migrations/20260511230000-delete-official-plugin-package-resource-modules.d.ts +14 -0
  25. package/dist/server/migrations/20260511230000-delete-official-plugin-package-resource-modules.js +64 -0
  26. package/dist/server/plugin.d.ts +5 -2
  27. package/dist/server/plugin.js +72 -14
  28. package/dist/server/tasks/localization-ai-translate.d.ts +31 -0
  29. package/dist/server/tasks/localization-ai-translate.js +596 -0
  30. package/package.json +7 -2
  31. package/dist/client/304.6a0dd0c975aa0b7c.js +0 -10
  32. package/dist/server/source-manager.d.ts +0 -35
  33. package/dist/server/source-manager.js +0 -77
@@ -0,0 +1,596 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var localization_ai_translate_exports = {};
28
+ __export(localization_ai_translate_exports, {
29
+ LOCALIZATION_AI_TRANSLATE_TASK_TYPE: () => LOCALIZATION_AI_TRANSLATE_TASK_TYPE,
30
+ LocalizationAITranslateTask: () => LocalizationAITranslateTask
31
+ });
32
+ module.exports = __toCommonJS(localization_ai_translate_exports);
33
+ var import_plugin_async_task_manager = require("@nocobase/plugin-async-task-manager");
34
+ var import_messages = require("@langchain/core/messages");
35
+ const LOCALIZATION_AI_TRANSLATE_TASK_TYPE = "localization:ai-translate";
36
+ const TRANSLATION_BATCH_SIZE = 10;
37
+ const DEFAULT_TRANSLATION_WORKER_COUNT = 10;
38
+ const MIN_TRANSLATION_WORKER_COUNT = 1;
39
+ const MAX_TRANSLATION_WORKER_COUNT = 20;
40
+ const MAX_TRANSLATION_QUEUE_SIZE = TRANSLATION_BATCH_SIZE * 2;
41
+ const elapsed = (start) => Date.now() - start;
42
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
43
+ const truncateForLog = (value, maxLength = 500) => value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
44
+ const LEGACY_SYMBOL_TRANSLATIONS = /* @__PURE__ */ new Set(["<", "=", ">"]);
45
+ const getTranslationWorkerCount = () => {
46
+ const value = Number.parseInt(process.env.AI_LOCALIZATION_CONCURRENCY || "", 10);
47
+ if (Number.isInteger(value) && value >= MIN_TRANSLATION_WORKER_COUNT && value <= MAX_TRANSLATION_WORKER_COUNT) {
48
+ return value;
49
+ }
50
+ return DEFAULT_TRANSLATION_WORKER_COUNT;
51
+ };
52
+ class LocalizationAITranslationError extends Error {
53
+ constructor(message, details) {
54
+ super(message);
55
+ this.details = details;
56
+ this.name = "LocalizationAITranslationError";
57
+ }
58
+ }
59
+ class LocalizationAITranslateTask extends import_plugin_async_task_manager.TaskType {
60
+ static type = LOCALIZATION_AI_TRANSLATE_TASK_TYPE;
61
+ async execute() {
62
+ var _a, _b;
63
+ const params = this.record.params;
64
+ const locale = params.locale || "en-US";
65
+ const employeeUsername = params.employeeUsername || "lina";
66
+ const aiPlugin = this.app.pm.get("ai");
67
+ if (!(aiPlugin == null ? void 0 : aiPlugin.aiConversationsManager) || !(aiPlugin == null ? void 0 : aiPlugin.aiEmployeesManager)) {
68
+ throw new Error("AI plugin is not available");
69
+ }
70
+ const employee = await aiPlugin.aiEmployeesManager.getEmployee(employeeUsername);
71
+ if (!employee) {
72
+ throw new Error(`AI employee "${employeeUsername}" not found`);
73
+ }
74
+ const resolvedModel = await aiPlugin.aiEmployeesManager.resolveModel(employee, params.model);
75
+ const { provider, model, service } = await aiPlugin.aiManager.getLLMService(resolvedModel);
76
+ const defaultReferenceLocale = await this.getSystemDefaultLocale();
77
+ const builtInReferenceResources = await this.app.localeManager.getBuiltInResources("zh-CN");
78
+ const builtInMatchResources = await this.app.localeManager.getBuiltInResources("en-US");
79
+ const workerCount = getTranslationWorkerCount();
80
+ const countStart = Date.now();
81
+ const total = await this.countTexts(params.mode, locale, params.textIds);
82
+ (_a = this.logger) == null ? void 0 : _a.debug("Localization AI translation task started", {
83
+ taskId: this.record.id,
84
+ mode: params.mode,
85
+ locale,
86
+ total,
87
+ countElapsedMs: elapsed(countStart),
88
+ workerCount,
89
+ queueLimit: MAX_TRANSLATION_QUEUE_SIZE,
90
+ defaultReferenceLocale,
91
+ provider: service == null ? void 0 : service.provider,
92
+ llmService: service == null ? void 0 : service.name,
93
+ model
94
+ });
95
+ let translated = 0;
96
+ let chunkIndex = 0;
97
+ let producerDone = false;
98
+ let firstError;
99
+ const queue = [];
100
+ this.reportProgress({ total, current: 0 });
101
+ const workers = Array.from(
102
+ { length: workerCount },
103
+ (_, workerIndex) => this.runTranslationWorker({
104
+ workerIndex: workerIndex + 1,
105
+ queue,
106
+ isDone: () => producerDone,
107
+ getError: () => firstError,
108
+ setError: (error) => {
109
+ firstError = firstError ?? error;
110
+ },
111
+ total,
112
+ getTranslated: () => translated,
113
+ incrementTranslated: () => {
114
+ translated += 1;
115
+ return translated;
116
+ },
117
+ locale,
118
+ employeeUsername,
119
+ employee,
120
+ provider,
121
+ service,
122
+ model
123
+ })
124
+ );
125
+ try {
126
+ await this.app.db.getRepository("localizationTexts").chunkWithCursor({
127
+ ...this.buildFindTextsOptions(params.mode, locale, params.textIds),
128
+ chunkSize: TRANSLATION_BATCH_SIZE,
129
+ beforeFind: async () => {
130
+ while (!firstError && queue.length >= MAX_TRANSLATION_QUEUE_SIZE) {
131
+ await sleep(100);
132
+ }
133
+ if (firstError) {
134
+ throw firstError;
135
+ }
136
+ },
137
+ callback: async (rows) => {
138
+ var _a2;
139
+ chunkIndex += 1;
140
+ const chunkStart = Date.now();
141
+ const textRows = rows.map((row) => this.normalizeTextRecord(row)).filter(Boolean);
142
+ const textIds = textRows.map((row) => row.id);
143
+ const englishReferences = await this.getLocaleReferences(textIds, "en-US");
144
+ const defaultLocaleReferences = defaultReferenceLocale === "en-US" ? englishReferences : await this.getLocaleReferences(textIds, defaultReferenceLocale);
145
+ const queueItems = textRows.map((row) => {
146
+ const isBuiltIn = this.isBuiltInText(row, builtInMatchResources);
147
+ return {
148
+ row,
149
+ chunkIndex,
150
+ englishReference: englishReferences.get(String(row.id)),
151
+ referenceTranslation: isBuiltIn ? this.getBuiltInReference(row, builtInReferenceResources) : defaultLocaleReferences.get(String(row.id)),
152
+ referenceLocale: isBuiltIn ? "zh-CN" : defaultReferenceLocale,
153
+ isBuiltIn
154
+ };
155
+ });
156
+ queue.push(...queueItems);
157
+ (_a2 = this.logger) == null ? void 0 : _a2.debug("Localization AI translation chunk enqueued", {
158
+ taskId: this.record.id,
159
+ chunkIndex,
160
+ rows: textRows.length,
161
+ englishReferences: englishReferences.size,
162
+ referenceLocale: defaultReferenceLocale,
163
+ referenceTranslations: queueItems.filter((item) => item.referenceTranslation).length,
164
+ builtInReferences: queueItems.filter((item) => item.isBuiltIn && item.referenceTranslation).length,
165
+ queueSize: queue.length,
166
+ elapsedMs: elapsed(chunkStart),
167
+ translated,
168
+ total
169
+ });
170
+ }
171
+ });
172
+ } finally {
173
+ producerDone = true;
174
+ }
175
+ await Promise.all(workers);
176
+ if (firstError) {
177
+ throw firstError;
178
+ }
179
+ (_b = this.logger) == null ? void 0 : _b.debug("Localization AI translation task completed", {
180
+ taskId: this.record.id,
181
+ translated,
182
+ total
183
+ });
184
+ return {
185
+ translated,
186
+ total
187
+ };
188
+ }
189
+ async countTexts(mode, locale, textIds) {
190
+ return await this.app.db.getRepository("localizationTexts").count(this.buildFindTextsOptions(mode, locale, textIds));
191
+ }
192
+ buildFindTextsOptions(mode, locale, textIds) {
193
+ const options = {
194
+ fields: ["id", "text", "module"],
195
+ sort: ["id"]
196
+ };
197
+ if (mode === "selected") {
198
+ options.filter = {
199
+ id: {
200
+ $in: textIds || []
201
+ }
202
+ };
203
+ }
204
+ if (mode === "incremental") {
205
+ options.include = [{ association: "translations", where: { locale }, required: false }];
206
+ options.where = {
207
+ "$translations.id$": null
208
+ };
209
+ }
210
+ return options;
211
+ }
212
+ normalizeTextRecord(row) {
213
+ if (!row) {
214
+ return void 0;
215
+ }
216
+ return typeof row.toJSON === "function" ? row.toJSON() : row;
217
+ }
218
+ async getLocaleReferences(textIds, locale) {
219
+ const references = /* @__PURE__ */ new Map();
220
+ if (!textIds.length) {
221
+ return references;
222
+ }
223
+ const rows = await this.app.db.getRepository("localizationTranslations").find({
224
+ fields: ["textId", "translation"],
225
+ filter: {
226
+ locale,
227
+ textId: {
228
+ $in: textIds
229
+ }
230
+ }
231
+ });
232
+ for (const row of rows) {
233
+ const record = typeof row.toJSON === "function" ? row.toJSON() : row;
234
+ if ((record == null ? void 0 : record.textId) != null && record.translation) {
235
+ references.set(String(record.textId), record.translation);
236
+ }
237
+ }
238
+ return references;
239
+ }
240
+ async getSystemDefaultLocale() {
241
+ var _a;
242
+ const systemSetting = await ((_a = this.app.db.getRepository("systemSettings")) == null ? void 0 : _a.findOne());
243
+ const enabledLanguages = (systemSetting == null ? void 0 : systemSetting.get("enabledLanguages")) || [];
244
+ return (enabledLanguages == null ? void 0 : enabledLanguages[0]) || process.env.APP_LANG || "en-US";
245
+ }
246
+ getModuleName(row) {
247
+ var _a;
248
+ return (_a = row.module) == null ? void 0 : _a.replace("resources.", "");
249
+ }
250
+ isBuiltInText(row, resources) {
251
+ var _a;
252
+ const moduleName = this.getModuleName(row);
253
+ return Boolean(moduleName && ((_a = resources[moduleName]) == null ? void 0 : _a[row.text]) !== void 0);
254
+ }
255
+ getBuiltInReference(row, resources) {
256
+ var _a;
257
+ const moduleName = this.getModuleName(row);
258
+ return moduleName ? (_a = resources[moduleName]) == null ? void 0 : _a[row.text] : void 0;
259
+ }
260
+ async runTranslationWorker(options) {
261
+ var _a, _b, _c, _d, _e, _f, _g, _h;
262
+ const {
263
+ workerIndex,
264
+ queue,
265
+ isDone,
266
+ getError,
267
+ setError,
268
+ total,
269
+ getTranslated,
270
+ incrementTranslated,
271
+ locale,
272
+ employeeUsername,
273
+ employee,
274
+ provider,
275
+ service,
276
+ model
277
+ } = options;
278
+ while (!isDone() || queue.length > 0) {
279
+ if (getError()) {
280
+ return;
281
+ }
282
+ if (this.isCanceled) {
283
+ throw new import_plugin_async_task_manager.CancelError();
284
+ }
285
+ const item = queue.shift();
286
+ if (!item) {
287
+ await sleep(50);
288
+ continue;
289
+ }
290
+ const { row, chunkIndex, englishReference, referenceTranslation, referenceLocale, isBuiltIn } = item;
291
+ try {
292
+ const textStart = Date.now();
293
+ (_c = (_a = this.logger) == null ? void 0 : _a.trace) == null ? void 0 : _c.call(_a, "Localization AI translation text started", {
294
+ taskId: this.record.id,
295
+ workerIndex,
296
+ chunkIndex,
297
+ textId: row.id,
298
+ textLength: ((_b = row.text) == null ? void 0 : _b.length) ?? 0,
299
+ hasEnglishReference: Boolean(englishReference),
300
+ hasReferenceTranslation: Boolean(referenceTranslation),
301
+ referenceLocale,
302
+ isBuiltIn,
303
+ queueSize: queue.length,
304
+ translated: getTranslated(),
305
+ total
306
+ });
307
+ const isLegacySymbolTranslation = LEGACY_SYMBOL_TRANSLATIONS.has(row.text);
308
+ if (isLegacySymbolTranslation) {
309
+ (_e = (_d = this.logger) == null ? void 0 : _d.trace) == null ? void 0 : _e.call(_d, "Localization AI translation legacy symbol skipped", {
310
+ taskId: this.record.id,
311
+ workerIndex,
312
+ chunkIndex,
313
+ textId: row.id,
314
+ text: row.text
315
+ });
316
+ }
317
+ const translation = isLegacySymbolTranslation ? row.text : await this.translateText({
318
+ text: row.text,
319
+ module: row.module,
320
+ englishReference,
321
+ referenceTranslation,
322
+ referenceLocale,
323
+ isBuiltIn,
324
+ locale,
325
+ employeeUsername,
326
+ employee,
327
+ provider,
328
+ service,
329
+ model
330
+ });
331
+ const aiElapsedMs = elapsed(textStart);
332
+ const writeStart = Date.now();
333
+ await this.app.db.getRepository("localizationTranslations").updateOrCreate({
334
+ filterKeys: ["textId", "locale"],
335
+ values: {
336
+ textId: row.id,
337
+ locale,
338
+ translation
339
+ }
340
+ });
341
+ const writeElapsedMs = elapsed(writeStart);
342
+ const translated = incrementTranslated();
343
+ this.reportProgress({ total, current: translated });
344
+ (_g = this.logger) == null ? void 0 : _g.debug("Localization AI translation text completed", {
345
+ taskId: this.record.id,
346
+ workerIndex,
347
+ chunkIndex,
348
+ textId: row.id,
349
+ textLength: ((_f = row.text) == null ? void 0 : _f.length) ?? 0,
350
+ translationLength: translation.length,
351
+ aiElapsedMs,
352
+ writeElapsedMs,
353
+ totalElapsedMs: elapsed(textStart),
354
+ translated,
355
+ total,
356
+ queueSize: queue.length
357
+ });
358
+ } catch (error) {
359
+ const message = error instanceof Error ? error.message : String(error);
360
+ const details = {
361
+ id: row.id,
362
+ text: row.text,
363
+ error: message
364
+ };
365
+ (_h = this.logger) == null ? void 0 : _h.error(`Failed to translate localization text ${row.id}: ${message}`, { error });
366
+ if (error instanceof import_plugin_async_task_manager.CancelError) {
367
+ throw error;
368
+ }
369
+ setError(
370
+ new LocalizationAITranslationError(`Failed to translate localization text ${row.id}: ${message}`, details)
371
+ );
372
+ return;
373
+ }
374
+ }
375
+ }
376
+ async translateText(options) {
377
+ var _a, _b, _c, _d, _e, _f;
378
+ const {
379
+ text,
380
+ module: module2,
381
+ englishReference,
382
+ referenceTranslation,
383
+ referenceLocale,
384
+ isBuiltIn,
385
+ locale,
386
+ employeeUsername,
387
+ provider,
388
+ service,
389
+ model,
390
+ employee
391
+ } = options;
392
+ const setupStart = Date.now();
393
+ const sourceText = englishReference || text;
394
+ const sourceLang = englishReference ? "English" : "auto";
395
+ const targetLang = this.getLanguageName(locale);
396
+ const context = this.buildProviderContext({
397
+ sourceText,
398
+ targetLang,
399
+ referenceSourceTerm: sourceText,
400
+ referenceTargetTerm: referenceTranslation
401
+ });
402
+ const invokeStart = Date.now();
403
+ (_b = (_a = this.logger) == null ? void 0 : _a.trace) == null ? void 0 : _b.call(_a, "Localization AI translation invoke started", {
404
+ taskId: this.record.id,
405
+ textLength: (text == null ? void 0 : text.length) ?? 0,
406
+ sourceTextLength: (sourceText == null ? void 0 : sourceText.length) ?? 0,
407
+ locale,
408
+ sourceLang,
409
+ targetLang,
410
+ module: module2,
411
+ employeeUsername,
412
+ provider: service == null ? void 0 : service.provider,
413
+ llmService: service == null ? void 0 : service.name,
414
+ model,
415
+ hasEnglishReference: Boolean(englishReference),
416
+ hasReferenceTranslation: Boolean(referenceTranslation),
417
+ referenceLocale,
418
+ isBuiltIn
419
+ });
420
+ const result = await provider.invoke(context, {
421
+ modelRequestParams: {
422
+ sourceText,
423
+ sourceLang,
424
+ targetLang,
425
+ terms: this.buildTranslationTerms({
426
+ sourceTerm: sourceText,
427
+ targetTerm: referenceTranslation,
428
+ targetLang
429
+ })
430
+ }
431
+ });
432
+ const invokeElapsedMs = elapsed(invokeStart);
433
+ (_d = (_c = this.logger) == null ? void 0 : _c.trace) == null ? void 0 : _d.call(_c, "Localization AI translation invoke completed", {
434
+ taskId: this.record.id,
435
+ textLength: (text == null ? void 0 : text.length) ?? 0,
436
+ sourceTextLength: (sourceText == null ? void 0 : sourceText.length) ?? 0,
437
+ locale,
438
+ sourceLang,
439
+ targetLang,
440
+ module: module2,
441
+ employeeUsername,
442
+ provider: service == null ? void 0 : service.provider,
443
+ llmService: service == null ? void 0 : service.name,
444
+ model,
445
+ hasEnglishReference: Boolean(englishReference),
446
+ hasReferenceTranslation: Boolean(referenceTranslation),
447
+ referenceLocale,
448
+ isBuiltIn,
449
+ setupElapsedMs: elapsed(setupStart) - invokeElapsedMs,
450
+ invokeElapsedMs,
451
+ totalElapsedMs: elapsed(setupStart)
452
+ });
453
+ const translation = this.extractTextContent(result == null ? void 0 : result.content).trim();
454
+ if (!translation) {
455
+ throw new Error("LLM service returned empty translation");
456
+ }
457
+ (_f = (_e = this.logger) == null ? void 0 : _e.trace) == null ? void 0 : _f.call(_e, "Localization AI translation result extracted", {
458
+ taskId: this.record.id,
459
+ textLength: (text == null ? void 0 : text.length) ?? 0,
460
+ sourceTextLength: (sourceText == null ? void 0 : sourceText.length) ?? 0,
461
+ translationLength: translation.length,
462
+ locale,
463
+ sourceLang,
464
+ targetLang,
465
+ module: module2,
466
+ employeeUsername,
467
+ provider: service == null ? void 0 : service.provider,
468
+ llmService: service == null ? void 0 : service.name,
469
+ model,
470
+ hasEnglishReference: Boolean(englishReference),
471
+ hasReferenceTranslation: Boolean(referenceTranslation),
472
+ referenceLocale,
473
+ isBuiltIn,
474
+ sourceText: truncateForLog(sourceText),
475
+ referenceTranslation: referenceTranslation ? truncateForLog(referenceTranslation) : void 0,
476
+ translation: truncateForLog(translation)
477
+ });
478
+ return translation;
479
+ }
480
+ buildProviderContext(options) {
481
+ const { sourceText, targetLang, referenceSourceTerm, referenceTargetTerm } = options;
482
+ const reference = referenceSourceTerm && referenceTargetTerm ? `Refer to the following translation:
483
+ ${referenceSourceTerm} is translated as ${referenceTargetTerm}
484
+
485
+ ` : "";
486
+ const content = `${reference}Translate the following text into ${targetLang}. Output only the translated result without any additional explanation:
487
+
488
+ ${sourceText}
489
+ `;
490
+ return {
491
+ messages: [new import_messages.HumanMessage(content)]
492
+ };
493
+ }
494
+ buildTranslationTerms(options) {
495
+ const { sourceTerm, targetTerm, targetLang } = options;
496
+ if (!sourceTerm || !targetTerm || !["Chinese", "Traditional Chinese"].includes(targetLang)) {
497
+ return void 0;
498
+ }
499
+ return [
500
+ {
501
+ source: sourceTerm,
502
+ target: targetTerm
503
+ }
504
+ ];
505
+ }
506
+ getLanguageName(locale) {
507
+ const normalized = locale.replace("_", "-");
508
+ const map = {
509
+ "en-US": "English",
510
+ "zh-CN": "Chinese",
511
+ "zh-TW": "Traditional Chinese",
512
+ "ja-JP": "Japanese",
513
+ "ko-KR": "Korean",
514
+ "fr-FR": "French",
515
+ "de-DE": "German",
516
+ "es-ES": "Spanish",
517
+ "it-IT": "Italian",
518
+ "pt-PT": "Portuguese",
519
+ "pt-BR": "Portuguese",
520
+ "ru-RU": "Russian",
521
+ "th-TH": "Thai",
522
+ "vi-VN": "Vietnamese",
523
+ "id-ID": "Indonesian",
524
+ "ms-MY": "Malay",
525
+ "ar-SA": "Arabic",
526
+ "hi-IN": "Hindi",
527
+ "tr-TR": "Turkish",
528
+ "nl-NL": "Dutch",
529
+ "pl-PL": "Polish",
530
+ "sv-SE": "Swedish",
531
+ "da-DK": "Danish",
532
+ "fi-FI": "Finnish",
533
+ "uk-UA": "Ukrainian"
534
+ };
535
+ if (map[normalized]) {
536
+ return map[normalized];
537
+ }
538
+ const language = normalized.split("-")[0];
539
+ const languageMap = {
540
+ en: "English",
541
+ zh: "Chinese",
542
+ ja: "Japanese",
543
+ ko: "Korean",
544
+ fr: "French",
545
+ de: "German",
546
+ es: "Spanish",
547
+ it: "Italian",
548
+ pt: "Portuguese",
549
+ ru: "Russian",
550
+ th: "Thai",
551
+ vi: "Vietnamese",
552
+ id: "Indonesian",
553
+ ms: "Malay",
554
+ ar: "Arabic",
555
+ hi: "Hindi",
556
+ tr: "Turkish",
557
+ nl: "Dutch",
558
+ pl: "Polish",
559
+ sv: "Swedish",
560
+ da: "Danish",
561
+ fi: "Finnish",
562
+ uk: "Ukrainian"
563
+ };
564
+ return languageMap[language] || language;
565
+ }
566
+ extractTextContent(content) {
567
+ if (typeof content === "string") {
568
+ return content;
569
+ }
570
+ if (Array.isArray(content)) {
571
+ return content.map((item) => {
572
+ if (typeof item === "string") {
573
+ return item;
574
+ }
575
+ if (item && typeof item === "object") {
576
+ if ("type" in item && item.type === "text") {
577
+ return typeof item.text === "string" ? item.text : "";
578
+ }
579
+ if ("content" in item) {
580
+ return this.extractTextContent(item.content);
581
+ }
582
+ }
583
+ return "";
584
+ }).join("").trim();
585
+ }
586
+ if (content && typeof content === "object" && "content" in content) {
587
+ return this.extractTextContent(content.content);
588
+ }
589
+ return "";
590
+ }
591
+ }
592
+ // Annotate the CommonJS export names for ESM import in node:
593
+ 0 && (module.exports = {
594
+ LOCALIZATION_AI_TRANSLATE_TASK_TYPE,
595
+ LocalizationAITranslateTask
596
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/plugin-localization",
3
- "version": "2.1.0-beta.29",
3
+ "version": "2.1.0-beta.32",
4
4
  "main": "dist/server/index.js",
5
5
  "homepage": "https://docs.nocobase.com/handbook/localization-management",
6
6
  "homepage.ru-RU": "https://docs-ru.nocobase.com/handbook/localization-management",
@@ -10,9 +10,14 @@
10
10
  "deepmerge": "^4.3.1"
11
11
  },
12
12
  "peerDependencies": {
13
+ "@nocobase/ai": "2.x",
13
14
  "@nocobase/cache": "2.x",
14
15
  "@nocobase/client": "2.x",
16
+ "@nocobase/client-v2": "2.x",
15
17
  "@nocobase/database": "2.x",
18
+ "@nocobase/flow-engine": "2.x",
19
+ "@nocobase/plugin-ai": "2.x",
20
+ "@nocobase/plugin-async-task-manager": "2.x",
16
21
  "@nocobase/server": "2.x",
17
22
  "@nocobase/test": "2.x"
18
23
  },
@@ -25,5 +30,5 @@
25
30
  "description": "Allows to manage localization resources of the application.",
26
31
  "description.ru-RU": "Позволяет управлять ресурсами локализации приложения.",
27
32
  "description.zh-CN": "支持管理应用程序的本地化资源。",
28
- "gitHead": "86c41be29dcbcac6fd6aa46b4a137ef07a27c1d0"
33
+ "gitHead": "659c5efe992da7118d33c768bbd9e837a2c4716f"
29
34
  }
@@ -1,10 +0,0 @@
1
- /**
2
- * This file is part of the NocoBase (R) project.
3
- * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
- * Authors: NocoBase Team.
5
- *
6
- * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
- * For more information, please refer to: https://www.nocobase.com/agreement.
8
- */
9
-
10
- "use strict";(self.webpackChunk_nocobase_plugin_localization=self.webpackChunk_nocobase_plugin_localization||[]).push([["304"],{339:function(e,t,n){n.r(t),n.d(t,{Localization:function(){return F}});var o=n(375),r=n(452),l=n(230),a=n(342),i=n(625),c=n(59),u=n(155),s=n.n(u),p=n(872),m={name:"localization",disableTranslation:!0,fields:[{interface:"input",type:"string",name:"text",uiSchema:{type:"string",title:'{{t("Text")}}',"x-component":"Input.TextArea",required:!0}},{interface:"input",type:"string",name:"translation",uiSchema:{type:"string",title:'{{t("Translation")}}',"x-component":"Input.TextArea"}},{interface:"select",type:"string",name:"moduleTitle",uiSchema:{type:"string",title:'{{t("Module")}}',"x-component":"Select",enum:[{value:"Menu",label:'{{t("Menu")}}'},{value:"Collections & Fields",label:'{{t("Collections & Fields", {ns:"localization"})}}'}]}}]},d={type:"void",name:"localization","x-decorator":"ResourceActionProvider","x-decorator-props":{collection:m,resourceName:"localizationTexts",request:{resource:"localizationTexts",action:"list",params:{pageSize:50}}},"x-component":"CollectionProvider_deprecated","x-component-props":{collection:m},properties:{actions:{type:"void","x-component":"ActionBar","x-component-props":{style:{marginBottom:16}},properties:{currentLang:{type:"void","x-align":"left","x-component":"CurrentLang"},filter:{type:"void",title:'{{t("Filter")}}',"x-align":"left","x-component":"Filter"},deleteTranslation:{type:"void",title:'{{t("Delete translation")}}',"x-component":"Action","x-component-props":{icon:"DeleteOutlined",useAction:"{{ useBulkDestroyTranslationAction }}",confirm:{title:"{{t('Delete translation')}}",content:"{{t('Are you sure you want to delete it?')}}"}}},sync:{type:"void",title:'{{t("Sync")}}',"x-component":"Sync"},publish:{type:"void",title:'{{t("Publish")}}',"x-component":"Action","x-component-props":{icon:"UploadOutlined",type:"primary",useAction:"{{ usePublishAction }}"}}}},table:{type:"void","x-uid":"input","x-component":"Table.Void","x-component-props":{rowKey:"id",rowSelection:{type:"checkbox",getCheckboxProps:function(e){return{disabled:!(null==e?void 0:e.translationId)}}},useDataSource:"{{ cm.useDataSourceFromRAC }}"},properties:{text:{type:"void","x-decorator":"Table.Column.Decorator","x-component":"Table.Column",properties:{text:{type:"string","x-component":"CollectionField","x-read-pretty":!0}}},translation:{type:"void","x-decorator":"Table.Column.Decorator","x-component":"Table.Column",properties:{translation:{type:"string","x-component":"CollectionField","x-component-props":{component:"TranslationField"},"x-read-pretty":!0}}},moduleTitle:{type:"void","x-decorator":"Table.Column.Decorator","x-component":"Table.Column",properties:{moduleTitle:{type:"string","x-component":"ModuleTitle"}}},actions:{type:"void",title:'{{t("Actions")}}',"x-component":"Table.Column",properties:{actions:{type:"void","x-component":"Space","x-component-props":{split:"|"},properties:{update:{type:"void",title:'{{t("Edit")}}',"x-component":"Action.Link","x-component-props":{type:"primary"},properties:{drawer:{type:"void","x-component":"Action.Drawer","x-decorator":"Form","x-decorator-props":{useValues:"{{ cm.useValuesFromRecord }}"},title:'{{t("Edit")}}',properties:{moduleTitle:{title:'{{t("Module")}}',"x-component":"ModuleTitle","x-decorator":"FormItem"},text:{"x-component":"CollectionField","x-decorator":"FormItem","x-read-pretty":!0},translation:{"x-component":"CollectionField","x-decorator":"FormItem",required:!0},footer:{type:"void","x-component":"Action.Drawer.Footer",properties:{cancel:{title:'{{t("Cancel")}}',"x-component":"Action","x-component-props":{useAction:"{{ cm.useCancelAction }}"}},submit:{title:'{{t("Submit")}}',"x-component":"Action","x-component-props":{type:"primary",useAction:"{{ useUpdateTranslationAction }}"}}}}}}}},deleteTranslation:{type:"void",title:'{{ t("Delete translation") }}',"x-component":"Action.Link","x-component-props":{confirm:{title:"{{t('Delete translation')}}",content:"{{t('Are you sure you want to delete it?')}}"},useAction:"{{useDestroyTranslationAction}}"},"x-visible":"{{useHasTranslation()}}"}}}}}}}}};function f(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=Array(t);n<t;n++)o[n]=e[n];return o}function y(e,t,n,o,r,l,a){try{var i=e[l](a),c=i.value}catch(e){n(e);return}i.done?t(c):Promise.resolve(c).then(o,r)}function h(e){return function(){var t=this,n=arguments;return new Promise(function(o,r){var l=e.apply(t,n);function a(e){y(l,o,r,a,i,"next",e)}function i(e){y(l,o,r,a,i,"throw",e)}a(void 0)})}}function b(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{},o=Object.keys(n);"function"==typeof Object.getOwnPropertySymbols&&(o=o.concat(Object.getOwnPropertySymbols(n).filter(function(e){return Object.getOwnPropertyDescriptor(n,e).enumerable}))),o.forEach(function(t){var o;o=n[t],t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o})}return e}function v(e,t){return t=null!=t?t:{},Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):(function(e){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t.push.apply(t,n)}return t})(Object(t)).forEach(function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}),e}function x(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n,o,r=null==e?null:"u">typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var l=[],a=!0,i=!1;try{for(r=r.call(e);!(a=(n=r.next()).done)&&(l.push(n.value),!t||l.length!==t);a=!0);}catch(e){i=!0,o=e}finally{try{a||null==r.return||r.return()}finally{if(i)throw o}}return l}}(e,t)||function(e,t){if(e){if("string"==typeof e)return f(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if("Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n)return Array.from(n);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return f(e,t)}}(e,t)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function g(e,t){var n,o,r,l={label:0,sent:function(){if(1&r[0])throw r[1];return r[1]},trys:[],ops:[]},a=Object.create(("function"==typeof Iterator?Iterator:Object).prototype),i=Object.defineProperty;return i(a,"next",{value:c(0)}),i(a,"throw",{value:c(1)}),i(a,"return",{value:c(2)}),"function"==typeof Symbol&&i(a,Symbol.iterator,{value:function(){return this}}),a;function c(i){return function(c){var u=[i,c];if(n)throw TypeError("Generator is already executing.");for(;a&&(a=0,u[0]&&(l=0)),l;)try{if(n=1,o&&(r=2&u[0]?o.return:u[0]?o.throw||((r=o.return)&&r.call(o),0):o.next)&&!(r=r.call(o,u[1])).done)return r;switch(o=0,r&&(u=[2&u[0],r.value]),u[0]){case 0:case 1:r=u;break;case 4:return l.label++,{value:u[1],done:!1};case 5:l.label++,o=u[1],u=[0];continue;case 7:u=l.ops.pop(),l.trys.pop();continue;default:if(!(r=(r=l.trys).length>0&&r[r.length-1])&&(6===u[0]||2===u[0])){l=0;continue}if(3===u[0]&&(!r||u[1]>r[0]&&u[1]<r[3])){l.label=u[1];break}if(6===u[0]&&l.label<r[1]){l.label=r[1],r=u;break}if(r&&l.label<r[2]){l.label=r[2],l.ops.push(u);break}r[2]&&l.ops.pop(),l.trys.pop();continue}u=t.call(e,l)}catch(e){u=[6,e],o=0}finally{n=r=0}if(5&u[0])throw u[1];return{value:u[0]?u[1]:void 0,done:!0}}}}var A=c.Typography.Text,C=function(){var e=(0,l.useField)(),t=(0,l.useForm)(),n=(0,a.useActionContext)(),o=(0,a.useResourceActionContext)().refresh,r=(0,a.useResourceContext)().targetKey,i=(0,a.useRecord)()[r],c=(0,a.useAPIClient)(),u=c.auth.getLocale();return{run:function(){return h(function(){return g(this,function(r){switch(r.label){case 0:return[4,t.submit()];case 1:r.sent(),e.data=e.data||{},e.data.loading=!0,r.label=2;case 2:return r.trys.push([2,5,6,7]),[4,c.resource("localizationTranslations").updateOrCreate({filterKeys:["textId","locale"],values:{textId:i,locale:u,translation:t.values.translation}})];case 3:return r.sent(),n.setVisible(!1),[4,t.reset()];case 4:return r.sent(),o(),[3,7];case 5:return console.log(r.sent()),[3,7];case 6:return e.data.loading=!1,[7];case 7:return[2]}})})()}}},T=function(){var e=(0,a.useResourceActionContext)().refresh,t=(0,a.useAPIClient)(),n=(0,a.useRecord)().translationId;return{run:function(){return h(function(){return g(this,function(o){switch(o.label){case 0:if(!n)return[2];return[4,t.resource("localizationTranslations").destroy({filterByTk:n})];case 1:return o.sent(),e(),[2]}})})()}}},w=function(){var e=(0,a.useResourceActionContext)(),t=e.state,n=e.setState,o=e.refresh,r=e.data,l=(0,a.useAPIClient)(),i=(0,p.Q)().t;return{run:function(){return h(function(){var e,a,u,s,p;return g(this,function(m){switch(m.label){case 0:if(!(a=(null==t?void 0:t.selectedRowKeys)||[]).length||(u=(null==r||null==(e=r.data)?void 0:e.rows)||(null==r?void 0:r.data)||(null==r?void 0:r.rows)||[],s=new Set(a.map(function(e){return String(e)})),!(p=u.filter(function(e){return s.has(String(e.id))}).map(function(e){return e.translationId}).filter(Boolean)).length))return[2,c.message.error(i("Please select the records you want to delete"))];return[4,l.resource("localizationTranslations").destroy({filterByTk:p})];case 1:return m.sent(),null==n||n({selectedRowKeys:[]}),o(),[2]}})})()}}},S=function(){var e=(0,a.useAPIClient)();return{run:function(){return h(function(){return g(this,function(t){switch(t.label){case 0:return[4,e.resource("localization").publish()];case 1:return t.sent(),window.location.reload(),[2]}})})()}}},E=function(){var e=(0,p.Q)().t,t=(0,a.useResourceActionContext)().refresh,n=(0,a.useAPIClient)(),r=x((0,u.useState)(!1),2),i=r[0],m=r[1],d=x((0,u.useState)([]),2),f=d[0],y=d[1],b=x((0,u.useState)([]),2),v=b[0],A=b[1],C=x((0,u.useState)(!1),2),T=C[0],w=C[1],S=x((0,u.useState)(!0),2),E=S[0],O=S[1],P=(0,a.useRequest)(function(){return n.resource("localization").getSources().then(function(e){var t;return null==e||null==(t=e.data)?void 0:t.data})},{onSuccess:function(e){var t=e.map(function(e){return e.name});y(t),A(t)}}),k=P.data;return P.loading?null:s().createElement(a.StablePopover,{placement:"bottomRight",content:s().createElement(s().Fragment,null,s().createElement(c.Checkbox,{indeterminate:T,onChange:function(e){A(e.target.checked?f:[]),w(!1),O(e.target.checked)},checked:E},e("All")),s().createElement(c.Divider,{style:{margin:"5px 0"}}),s().createElement(c.Checkbox.Group,{onChange:function(e){A(e),w(!!e.length&&e.length<f.length),O(e.length===f.length)},value:v},s().createElement(c.Col,null,(k||[]).map(function(t){return s().createElement(c.Row,{key:t.name},s().createElement(c.Checkbox,{value:t.name},l.Schema.compile(t.title,{t:e})))}))))},s().createElement(c.Button,{icon:s().createElement(o.SyncOutlined,null),loading:i,onClick:function(){return h(function(){return g(this,function(o){switch(o.label){case 0:if(!v.length)return[2,c.message.error(e("Please select the resources you want to synchronize"))];return m(!0),[4,n.resource("localization").sync({values:{types:v}})];case 1:return o.sent(),m(!1),t(),[2]}})})()}},e("Sync")))},O=function(){var e,t=(0,p.Q)().t,n=(0,i.useMemoizedFn)(t),o=(0,a.useResourceActionContext)().data;return(0,u.useMemo)(function(){var e,t;return(null==o||null==(t=o.meta)||null==(e=t.modules)?void 0:e.map(function(e){return{value:e.value,label:l.Schema.compile(e.label,{t:n})}}))||[]},[null==o||null==(e=o.meta)?void 0:e.modules,n])},P=function(){var e=(0,p.Q)().t,t=(0,a.useResourceActionContext)().run,n=O(),o=(0,u.useMemo)(function(){return(0,r.createForm)({initialValues:{hasTranslation:!0}})},[]),i=function(e){t(b({},e||o.values))};return(0,u.useEffect)(function(){o.query("module").take().dataSource=n},[o,n]),s().createElement(a.FormProvider,{form:o},s().createElement("div",{style:{display:"flex"}},s().createElement(l.Field,{name:"module",dataSource:n,component:[a.Select,{allowClear:!0,placeholder:e("Module"),onChange:function(e){return i(v(b({},o.values),{module:e}))}}]}),s().createElement(l.Field,{name:"keyword",component:[c.Input.Search,{placeholder:e("Keyword"),allowClear:!0,style:{marginLeft:"8px",width:"fit-content"},onSearch:function(e){return i(v(b({},o.values),{keyword:e}))}}]}),s().createElement(l.Field,{name:"hasTranslation",dataSource:[{label:e("All"),value:!0},{label:e("No translation"),value:!1}],component:[a.Radio.Group,{defaultValue:!0,style:{marginLeft:"8px",width:"fit-content"},optionType:"button",onChange:function(){return i()}}]})))},k=function(){var e=(0,p.Q)().t,t=(0,a.useRecord)(),n=t.moduleTitle,o=t.module;return n?s().createElement(c.Tag,null,l.Schema.compile(n,{t:e})):s().createElement(c.Tag,null,o)},F=function(){var e,t=(0,p.Q)().t,n=(0,a.useAPIClient)().auth.getLocale(),o=(null==(e=a.locale[n])?void 0:e.label)||n;return s().createElement(c.Card,{bordered:!1},s().createElement(a.SchemaComponent,{schema:d,components:{TranslationField:function(e){return void 0!==e.value?s().createElement(a.Input.TextArea,e):s().createElement("div",null)},CurrentLang:function(){return s().createElement(c.Typography,null,s().createElement(A,{strong:!0},t("Current language")),s().createElement(c.Tag,{style:{marginLeft:"10px"}},o))},Sync:E,Filter:P,ModuleTitle:k},scope:{t:t,useDestroyTranslationAction:T,useBulkDestroyTranslationAction:w,useUpdateTranslationAction:C,usePublishAction:S,useModules:O}}))}}}]);