@nocobase/plugin-localization 2.1.0-beta.8 → 2.2.0-alpha.1

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 (37) 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.1f79b801d226f40d.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.0ec484505de4b04a.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 +18 -13
  18. package/dist/locale/en-US.json +38 -1
  19. package/dist/locale/zh-CN.json +38 -1
  20. package/dist/server/actions/aiTranslate.d.ts +14 -0
  21. package/dist/server/actions/aiTranslate.js +151 -0
  22. package/dist/server/actions/localization.js +30 -15
  23. package/dist/server/actions/localizationTexts.js +8 -9
  24. package/dist/server/collections/localization-texts.js +1 -0
  25. package/dist/server/collections/localization-translations.js +1 -0
  26. package/dist/server/migrations/20260511230000-delete-official-plugin-package-resource-modules.d.ts +14 -0
  27. package/dist/server/migrations/20260511230000-delete-official-plugin-package-resource-modules.js +64 -0
  28. package/dist/server/plugin.d.ts +5 -2
  29. package/dist/server/plugin.js +76 -14
  30. package/dist/server/tasks/localization-ai-translate.d.ts +48 -0
  31. package/dist/server/tasks/localization-ai-translate.js +606 -0
  32. package/dist/server/translation-scope.d.ts +31 -0
  33. package/dist/server/translation-scope.js +107 -0
  34. package/package.json +7 -2
  35. package/dist/client/177bd849c95cf6cc.js +0 -10
  36. package/dist/server/source-manager.d.ts +0 -35
  37. package/dist/server/source-manager.js +0 -77
@@ -0,0 +1,14 @@
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
+ import { Context, Next } from '@nocobase/actions';
10
+ declare const _default: {
11
+ aiTranslate: (ctx: Context, next: Next) => Promise<void>;
12
+ aiTranslatePreview: (ctx: Context, next: Next) => Promise<void>;
13
+ };
14
+ export default _default;
@@ -0,0 +1,151 @@
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 aiTranslate_exports = {};
28
+ __export(aiTranslate_exports, {
29
+ default: () => aiTranslate_default
30
+ });
31
+ module.exports = __toCommonJS(aiTranslate_exports);
32
+ var import_localization_ai_translate = require("../tasks/localization-ai-translate");
33
+ var import_translation_scope = require("../translation-scope");
34
+ const validateParams = (ctx) => {
35
+ const {
36
+ mode,
37
+ locale,
38
+ employeeUsername = "lina",
39
+ model,
40
+ textIds,
41
+ scope = "all",
42
+ referenceLocales
43
+ } = ctx.action.params.values || {};
44
+ if (!["full", "incremental", "selected"].includes(mode)) {
45
+ ctx.throw(400, "Invalid translation mode");
46
+ }
47
+ if (!["all", "builtIn", "custom"].includes(scope)) {
48
+ ctx.throw(400, "Invalid translation scope");
49
+ }
50
+ if (mode === "selected" && (!Array.isArray(textIds) || !textIds.length)) {
51
+ ctx.throw(400, "Please select the records you want to translate");
52
+ }
53
+ return {
54
+ mode,
55
+ locale: locale || ctx.get("X-Locale") || "en-US",
56
+ employeeUsername,
57
+ model,
58
+ textIds,
59
+ scope,
60
+ referenceLocales
61
+ };
62
+ };
63
+ const countTexts = async (ctx, mode, locale, scope, textIds) => {
64
+ const options = await (0, import_translation_scope.buildFindTextsOptions)({
65
+ app: ctx.app,
66
+ mode,
67
+ locale,
68
+ scope,
69
+ textIds
70
+ });
71
+ return await ctx.db.getRepository("localizationTexts").count(options);
72
+ };
73
+ const getScopeTitle = (scope) => {
74
+ return {
75
+ all: "All entries",
76
+ builtIn: "Built-in entries",
77
+ custom: "Custom entries"
78
+ }[scope];
79
+ };
80
+ const getTaskTitle = (mode, scope, locale) => {
81
+ if (mode === "selected") {
82
+ return `AI ${mode} localization translation - ${locale}`;
83
+ }
84
+ return `AI ${mode} localization translation - ${getScopeTitle(scope)} - ${locale}`;
85
+ };
86
+ const getAITranslatePreview = async (ctx) => {
87
+ const { mode, locale, employeeUsername, model, scope, referenceLocales, textIds } = validateParams(ctx);
88
+ const aiPlugin = ctx.app.pm.get("ai");
89
+ if (!(aiPlugin == null ? void 0 : aiPlugin.aiEmployeesManager)) {
90
+ ctx.throw(500, "AI plugin is not available");
91
+ }
92
+ const employee = await aiPlugin.aiEmployeesManager.getEmployee(employeeUsername);
93
+ if (!employee) {
94
+ ctx.throw(400, `AI employee "${employeeUsername}" not found`);
95
+ }
96
+ const resolvedModel = await aiPlugin.aiEmployeesManager.resolveModel(employee, model);
97
+ const { model: modelName, service } = await aiPlugin.aiManager.getLLMService(resolvedModel);
98
+ const providerMeta = aiPlugin.aiManager.llmProviders.get(service.provider);
99
+ const count = await countTexts(ctx, mode, locale, scope, textIds);
100
+ return {
101
+ mode,
102
+ locale,
103
+ scope,
104
+ referenceLocales,
105
+ count,
106
+ provider: service.provider,
107
+ providerTitle: providerMeta == null ? void 0 : providerMeta.title,
108
+ llmService: service.name,
109
+ llmServiceTitle: service.title,
110
+ model: modelName
111
+ };
112
+ };
113
+ const aiTranslatePreview = async (ctx, next) => {
114
+ ctx.body = await getAITranslatePreview(ctx);
115
+ await next();
116
+ };
117
+ const aiTranslate = async (ctx, next) => {
118
+ var _a, _b, _c, _d;
119
+ const { mode, locale, employeeUsername, model, textIds, scope, referenceLocales } = validateParams(ctx);
120
+ const currentUserId = ((_b = (_a = ctx.auth) == null ? void 0 : _a.user) == null ? void 0 : _b.id) || ((_d = (_c = ctx.state) == null ? void 0 : _c.currentUser) == null ? void 0 : _d.id);
121
+ const taskManager = ctx.app.container.get("AsyncTaskManager");
122
+ if (!taskManager) {
123
+ ctx.throw(500, "AsyncTaskManager is not available");
124
+ }
125
+ const task = await taskManager.createTask(
126
+ {
127
+ origin: "localization",
128
+ type: import_localization_ai_translate.LOCALIZATION_AI_TRANSLATE_TASK_TYPE,
129
+ title: getTaskTitle(mode, scope, locale),
130
+ params: {
131
+ mode,
132
+ locale,
133
+ employeeUsername,
134
+ model,
135
+ userId: currentUserId,
136
+ textIds,
137
+ scope,
138
+ referenceLocales
139
+ },
140
+ createdById: currentUserId,
141
+ cancelable: true
142
+ },
143
+ {
144
+ useQueue: true,
145
+ context: ctx
146
+ }
147
+ );
148
+ ctx.body = task.toJSON();
149
+ await next();
150
+ };
151
+ var aiTranslate_default = { aiTranslate, aiTranslatePreview };
@@ -35,15 +35,23 @@ const sync = async (ctx, next) => {
35
35
  const plugin = ctx.app.pm.get("localization");
36
36
  const resourcesInstance = plugin.resources;
37
37
  const locale = ctx.get("X-Locale") || "en-US";
38
- const { types = [] } = ctx.action.params.values || {};
38
+ const { types = [], resetTranslations = false } = ctx.action.params.values || {};
39
39
  if (!types.length) {
40
40
  ctx.throw(400, ctx.t("Please provide synchronization source."));
41
41
  }
42
- const resources = await plugin.sourceManager.sync(ctx, types);
43
- let textValues = [];
42
+ const resources = await ctx.app.localeManager.syncSources(ctx, types);
43
+ const normalizedResources = {};
44
44
  Object.entries(resources).forEach(([module2, resource]) => {
45
+ const normalizedModule = plugin.normalizeResourceModule(module2).replace("resources.", "");
46
+ normalizedResources[normalizedModule] = {
47
+ ...normalizedResources[normalizedModule] || {},
48
+ ...resource
49
+ };
50
+ });
51
+ let textValues = [];
52
+ Object.entries(normalizedResources).forEach(([module2, resource]) => {
45
53
  Object.keys(resource).forEach((text) => {
46
- textValues.push({ module: `resources.${module2}`, text });
54
+ textValues.push({ module: plugin.normalizeResourceModule(module2), text });
47
55
  });
48
56
  });
49
57
  textValues = await resourcesInstance.filterExists(textValues);
@@ -53,26 +61,34 @@ const sync = async (ctx, next) => {
53
61
  });
54
62
  const texts = await ctx.db.getModel("localizationTexts").findAll({
55
63
  include: [{ association: "translations", where: { locale }, required: false }],
56
- where: { "$translations.id$": null },
64
+ where: resetTranslations ? void 0 : { "$translations.id$": null },
57
65
  transaction: t
58
66
  });
59
67
  const translationValues = texts.filter((text) => {
60
68
  var _a;
61
- const module2 = text.module.replace("resources.", "");
62
- return (_a = resources[module2]) == null ? void 0 : _a[text.text];
69
+ const module2 = plugin.normalizeResourceModule(text.module).replace("resources.", "");
70
+ return (_a = normalizedResources[module2]) == null ? void 0 : _a[text.text];
63
71
  }).map((text) => {
64
72
  var _a;
65
- const module2 = text.module.replace("resources.", "");
73
+ const module2 = plugin.normalizeResourceModule(text.module).replace("resources.", "");
66
74
  return {
67
75
  locale,
68
76
  textId: text.id,
69
- translation: (_a = resources[module2]) == null ? void 0 : _a[text.text]
77
+ translation: (_a = normalizedResources[module2]) == null ? void 0 : _a[text.text]
70
78
  };
71
79
  });
72
- await ctx.db.getModel("localizationTranslations").bulkCreate(translationValues, {
73
- transaction: t
74
- });
80
+ if (resetTranslations) {
81
+ await ctx.db.getModel("localizationTranslations").bulkCreate(translationValues, {
82
+ updateOnDuplicate: ["translation"],
83
+ transaction: t
84
+ });
85
+ } else {
86
+ await ctx.db.getModel("localizationTranslations").bulkCreate(translationValues, {
87
+ transaction: t
88
+ });
89
+ }
75
90
  await resourcesInstance.updateCacheTexts(newTexts);
91
+ await resourcesInstance.reset();
76
92
  });
77
93
  ctx.logger.info(`Sync localization resources done, ${Date.now() - startTime}ms`);
78
94
  await next();
@@ -82,9 +98,8 @@ const publish = async (ctx, next) => {
82
98
  await next();
83
99
  };
84
100
  const getSources = async (ctx, next) => {
85
- const plugin = ctx.app.pm.get("localization");
86
- const sources = Array.from(plugin.sourceManager.sources.getEntities());
87
- ctx.body = sources.map(([name, source]) => ({
101
+ const sources = Array.from(ctx.app.localeManager.sources.getEntities());
102
+ ctx.body = sources.filter(([, source]) => source.sync).map(([name, source]) => ({
88
103
  name,
89
104
  title: source.title
90
105
  }));
@@ -104,18 +104,17 @@ const list = async (ctx, next) => {
104
104
  const [rows, count] = await listText(ctx.db, { module: module2, keyword, hasTranslation, locale, options });
105
105
  const cache = ctx.app.cache;
106
106
  const pm = ctx.app.pm;
107
- const plugin = pm.get("localization");
108
107
  const plugins = await cache.wrap(`lm-plugins:${locale}`, () => pm.list({ locale }));
109
- const sources = Array.from(plugin.sourceManager.sources.getValues());
108
+ const sources = Array.from(ctx.app.localeManager.sources.getValues());
110
109
  const extendModules = sources.filter((source) => source.namespace).map((source) => ({
111
110
  value: source.namespace,
112
111
  label: source.title
113
112
  }));
114
113
  const modules = [
115
114
  ...extendModules,
116
- ...plugins.map((plugin2) => ({
117
- value: plugin2.alias || plugin2.name,
118
- label: plugin2.displayName
115
+ ...plugins.map((plugin) => ({
116
+ value: plugin.alias || plugin.name,
117
+ label: plugin.displayName
119
118
  }))
120
119
  ];
121
120
  for (const row of rows) {
@@ -133,9 +132,9 @@ const list = async (ctx, next) => {
133
132
  totalPage: Math.ceil(count / pageSize),
134
133
  modules: [
135
134
  ...extendModules,
136
- ...plugins.map((plugin2) => ({
137
- value: plugin2.alias || plugin2.name,
138
- label: plugin2.displayName
135
+ ...plugins.map((plugin) => ({
136
+ value: plugin.alias || plugin.name,
137
+ label: plugin.displayName
139
138
  }))
140
139
  ]
141
140
  };
@@ -163,7 +162,7 @@ const missing = async (ctx, next) => {
163
162
  const plugin = (_a = ctx.app.pm) == null ? void 0 : _a.get("localization");
164
163
  const currentLocale = locale || ctx.get("X-Locale") || "en-US";
165
164
  await (plugin == null ? void 0 : plugin.addNewTexts(
166
- keys.map((key) => ({ text: key.text, module: `resources.${key.ns}` })),
165
+ keys.map((key) => ({ text: key.text, module: plugin.normalizeResourceModule(key.ns) })),
167
166
  {
168
167
  locale: currentLocale
169
168
  }
@@ -36,6 +36,7 @@ var localization_texts_default = (0, import_database.defineCollection)({
36
36
  },
37
37
  migrationRules: ["overwrite", "schema-only"],
38
38
  name: "localizationTexts",
39
+ dataCategory: "system",
39
40
  model: "LocalizationTextModel",
40
41
  createdBy: true,
41
42
  updatedBy: true,
@@ -37,6 +37,7 @@ var localization_translations_default = (0, import_database.defineCollection)({
37
37
  },
38
38
  migrationRules: ["overwrite", "schema-only"],
39
39
  name: "localizationTranslations",
40
+ dataCategory: "system",
40
41
  model: "LocalizationTranslationModel",
41
42
  createdBy: true,
42
43
  updatedBy: true,
@@ -0,0 +1,14 @@
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
+ import { Migration } from '@nocobase/server';
10
+ export default class extends Migration {
11
+ on: string;
12
+ appVersion: string;
13
+ up(): Promise<void>;
14
+ }
@@ -0,0 +1,64 @@
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 delete_official_plugin_package_resource_modules_exports = {};
28
+ __export(delete_official_plugin_package_resource_modules_exports, {
29
+ default: () => delete_official_plugin_package_resource_modules_default
30
+ });
31
+ module.exports = __toCommonJS(delete_official_plugin_package_resource_modules_exports);
32
+ var import_database = require("@nocobase/database");
33
+ var import_server = require("@nocobase/server");
34
+ class delete_official_plugin_package_resource_modules_default extends import_server.Migration {
35
+ on = "afterLoad";
36
+ // 'beforeLoad' or 'afterLoad'
37
+ appVersion = "<2.2.0";
38
+ async up() {
39
+ const textRepo = this.db.getRepository("localizationTexts");
40
+ const translationRepo = this.db.getRepository("localizationTranslations");
41
+ const texts = await textRepo.find({
42
+ filter: {
43
+ module: {
44
+ [import_database.Op.like]: `resources.${import_server.OFFICIAL_PLUGIN_PREFIX}%`
45
+ }
46
+ },
47
+ fields: ["id"]
48
+ });
49
+ const textIds = texts.map((text) => text.get("id"));
50
+ if (!textIds.length) {
51
+ return;
52
+ }
53
+ await translationRepo.destroy({
54
+ filter: {
55
+ textId: {
56
+ $in: textIds
57
+ }
58
+ }
59
+ });
60
+ await textRepo.destroy({
61
+ filterByTk: textIds
62
+ });
63
+ }
64
+ }
@@ -9,10 +9,11 @@
9
9
  /// <reference types="node" />
10
10
  import { InstallOptions, Plugin } from '@nocobase/server';
11
11
  import Resources from './resources';
12
- import { SourceManager } from './source-manager';
13
12
  export declare class PluginLocalizationServer extends Plugin {
14
13
  resources: Resources;
15
- sourceManager: SourceManager;
14
+ private aiTranslateTaskRegistered;
15
+ private localeSourceTextHookKeys;
16
+ normalizeResourceModule(module: string): string;
16
17
  addNewTexts: (texts: {
17
18
  text: string;
18
19
  module: string;
@@ -22,7 +23,9 @@ export declare class PluginLocalizationServer extends Plugin {
22
23
  }) => Promise<void>;
23
24
  afterAdd(): void;
24
25
  beforeLoad(): void;
26
+ private registerAITranslateTaskType;
25
27
  load(): Promise<void>;
28
+ private handleLocaleSourceTextsSaved;
26
29
  handleSyncMessage(message: any): Promise<void>;
27
30
  install(options?: InstallOptions): Promise<void>;
28
31
  afterEnable(): Promise<void>;
@@ -42,17 +42,29 @@ __export(plugin_exports, {
42
42
  module.exports = __toCommonJS(plugin_exports);
43
43
  var import_server = require("@nocobase/server");
44
44
  var import_localization = __toESM(require("./actions/localization"));
45
+ var import_aiTranslate = __toESM(require("./actions/aiTranslate"));
45
46
  var import_localizationTexts = __toESM(require("./actions/localizationTexts"));
46
47
  var import_resources = __toESM(require("./resources"));
47
48
  var import_utils = require("./utils");
48
49
  var import_constants = require("./constants");
49
- var import_source_manager = require("./source-manager");
50
50
  var import_utils2 = require("@nocobase/utils");
51
+ var import_localization_ai_translate = require("./tasks/localization-ai-translate");
51
52
  var import_package = __toESM(require("../../package.json"));
52
53
  class PluginLocalizationServer extends import_server.Plugin {
53
54
  resources;
54
- sourceManager = new import_source_manager.SourceManager();
55
+ aiTranslateTaskRegistered = false;
56
+ localeSourceTextHookKeys = /* @__PURE__ */ new Set();
57
+ normalizeResourceModule(module2) {
58
+ const prefix = "resources.";
59
+ const namespace = module2.startsWith(prefix) ? module2.slice(prefix.length) : module2;
60
+ const normalizedNamespace = namespace.startsWith(import_server.OFFICIAL_PLUGIN_PREFIX) ? namespace.replace(import_server.OFFICIAL_PLUGIN_PREFIX, "") : namespace;
61
+ return `${prefix}${normalizedNamespace}`;
62
+ }
55
63
  addNewTexts = async (texts, options) => {
64
+ texts = texts.map(({ text, module: module2 }) => ({
65
+ text,
66
+ module: this.normalizeResourceModule(module2)
67
+ }));
56
68
  texts = await this.resources.filterExists(texts, options == null ? void 0 : options.transaction);
57
69
  await this.db.getModel("localizationTexts").bulkCreate(
58
70
  texts.map(({ text, module: module2 }) => ({
@@ -88,23 +100,44 @@ class PluginLocalizationServer extends import_server.Plugin {
88
100
  });
89
101
  };
90
102
  afterAdd() {
91
- this.app.on("afterLoad", () => this.sourceManager.handleTextsSaved(this.db, this.addNewTexts));
103
+ this.app.on("afterLoad", () => this.handleLocaleSourceTextsSaved());
104
+ this.app.on("afterLoad", () => this.registerAITranslateTaskType());
92
105
  }
93
106
  beforeLoad() {
94
107
  }
108
+ registerAITranslateTaskType() {
109
+ if (this.aiTranslateTaskRegistered) {
110
+ return;
111
+ }
112
+ try {
113
+ const taskManager = this.app.container.get("AsyncTaskManager");
114
+ taskManager.registerTaskType(import_localization_ai_translate.LocalizationAITranslateTask);
115
+ this.aiTranslateTaskRegistered = true;
116
+ } catch (error) {
117
+ this.log.warn("AsyncTaskManager is not available, skip localization AI translate task registration.");
118
+ }
119
+ }
95
120
  async load() {
121
+ this.registerAITranslateTaskType();
96
122
  this.app.resourceManager.define({
97
123
  name: "localizationTexts",
98
124
  actions: import_localizationTexts.default
99
125
  });
100
126
  this.app.resourceManager.define({
101
127
  name: "localization",
102
- actions: import_localization.default
128
+ actions: {
129
+ ...import_localization.default,
130
+ ...import_aiTranslate.default
131
+ }
103
132
  });
104
133
  this.app.acl.registerSnippet({
105
134
  name: `pm.${this.name}.localization`,
106
135
  actions: ["localization:*", "localizationTexts:*", "localizationTranslations:*"]
107
136
  });
137
+ this.app.acl.registerSnippet({
138
+ name: "ui.localization",
139
+ actions: ["localizationTexts:missing"]
140
+ });
108
141
  this.app.localeManager.registerResourceStorer("plugin-localization", {
109
142
  getResources: (lang) => this.resources.getResources(lang),
110
143
  reset: () => this.resources.reset()
@@ -115,24 +148,22 @@ class PluginLocalizationServer extends import_server.Plugin {
115
148
  store: "memory"
116
149
  });
117
150
  this.resources = new import_resources.default(this.db, cache);
118
- this.sourceManager.registerSource("local", {
151
+ this.app.localeManager.registerSource("local", {
119
152
  title: (0, import_utils2.tval)("System & Plugins", { ns: import_package.default.name }),
120
153
  sync: async (ctx) => {
121
- const resources = await ctx.app.localeManager.getCacheResources(ctx.get("X-Locale") || "en-US");
154
+ const resources = await ctx.app.localeManager.getBuiltInResources(ctx.get("X-Locale") || "en-US");
122
155
  const result = {};
123
156
  Object.entries(resources).forEach(([module2, resource]) => {
124
- if (module2.startsWith(import_server.OFFICIAL_PLUGIN_PREFIX)) {
125
- const name = module2.replace(import_server.OFFICIAL_PLUGIN_PREFIX, "");
126
- if (resources[name]) {
127
- return;
128
- }
129
- }
130
- result[module2] = resource;
157
+ const name = module2.startsWith(import_server.OFFICIAL_PLUGIN_PREFIX) ? module2.replace(import_server.OFFICIAL_PLUGIN_PREFIX, "") : module2;
158
+ result[name] = {
159
+ ...result[name] || {},
160
+ ...resource
161
+ };
131
162
  });
132
163
  return result;
133
164
  }
134
165
  });
135
- this.sourceManager.registerSource("db", {
166
+ this.app.localeManager.registerSource("db", {
136
167
  title: (0, import_utils2.tval)("Collections & Fields", { ns: import_package.default.name }),
137
168
  namespace: import_constants.NAMESPACE_COLLECTIONS,
138
169
  sync: async (ctx) => {
@@ -181,6 +212,37 @@ class PluginLocalizationServer extends import_server.Plugin {
181
212
  await this.addNewTexts(texts, options);
182
213
  });
183
214
  }
215
+ handleLocaleSourceTextsSaved() {
216
+ for (const [sourceName, source] of this.app.localeManager.sources.getEntities()) {
217
+ if (!source.collections) {
218
+ continue;
219
+ }
220
+ for (const [index, collectionOptions] of source.collections.entries()) {
221
+ const hookKey = `${sourceName}:${index}:${collectionOptions.collection}`;
222
+ if (this.localeSourceTextHookKeys.has(hookKey)) {
223
+ continue;
224
+ }
225
+ this.localeSourceTextHookKeys.add(hookKey);
226
+ this.db.on(`${collectionOptions.collection}.afterSave`, async (instance, options) => {
227
+ let texts = [];
228
+ if (collectionOptions.getTexts) {
229
+ texts = await collectionOptions.getTexts(instance, options);
230
+ } else {
231
+ const fields = collectionOptions.fields || [];
232
+ const changedFields = fields.filter((field) => instance["_changed"].has(field));
233
+ if (!changedFields.length) {
234
+ return;
235
+ }
236
+ texts = changedFields.map((field) => instance.get(field)).filter(Boolean).map((text) => ({ text, module: `resources.${source.namespace}` }));
237
+ }
238
+ if (!(texts == null ? void 0 : texts.length)) {
239
+ return;
240
+ }
241
+ await this.addNewTexts(texts, options);
242
+ });
243
+ }
244
+ }
245
+ }
184
246
  async handleSyncMessage(message) {
185
247
  switch (message.type) {
186
248
  case "updateCacheTexts":
@@ -0,0 +1,48 @@
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
+ import { TaskType } from '@nocobase/plugin-async-task-manager';
10
+ import type { LocalizationTextRecord } from '../translation-scope';
11
+ export declare const LOCALIZATION_AI_TRANSLATE_TASK_TYPE = "localization:ai-translate";
12
+ export declare const pickBuiltInResourceReference: (row: LocalizationTextRecord, references: Map<string, Map<string, string>>, locales: ReferenceLocaleConfig) => {
13
+ locale: string;
14
+ translation: string;
15
+ } | {
16
+ locale?: undefined;
17
+ translation?: undefined;
18
+ };
19
+ type ReferenceLocaleConfig = {
20
+ primary?: string;
21
+ fallback?: string;
22
+ };
23
+ export type TranslationReferenceLocales = {
24
+ builtIn?: ReferenceLocaleConfig;
25
+ custom?: ReferenceLocaleConfig;
26
+ };
27
+ export declare class LocalizationAITranslateTask extends TaskType {
28
+ static type: string;
29
+ execute(): Promise<{
30
+ translated: number;
31
+ total: number;
32
+ }>;
33
+ private countTexts;
34
+ private getLocaleReferences;
35
+ private getReferenceMaps;
36
+ private getBuiltInReferenceMaps;
37
+ private pickDbReference;
38
+ private getSystemDefaultLocale;
39
+ private resolveReferenceLocales;
40
+ private buildTranslationItems;
41
+ private translateItem;
42
+ private translateText;
43
+ private getEmployeeSystemPrompt;
44
+ private buildProviderContext;
45
+ private getLanguageName;
46
+ private extractTextContent;
47
+ }
48
+ export {};