@strapi/i18n 0.0.0-next.990615b2ed4dd2f0589b4fda91418246d7b9450e → 0.0.0-next.9c630ba4333dffc2c1c8150d9226dfbb67b02e09

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 (193) hide show
  1. package/dist/admin/components/CMHeaderActions.js +240 -4
  2. package/dist/admin/components/CMHeaderActions.js.map +1 -1
  3. package/dist/admin/components/CMHeaderActions.mjs +244 -9
  4. package/dist/admin/components/CMHeaderActions.mjs.map +1 -1
  5. package/dist/admin/components/CreateLocale.js +1 -1
  6. package/dist/admin/components/CreateLocale.js.map +1 -1
  7. package/dist/admin/components/CreateLocale.mjs +1 -1
  8. package/dist/admin/components/CreateLocale.mjs.map +1 -1
  9. package/dist/admin/components/EditLocale.js +1 -1
  10. package/dist/admin/components/EditLocale.js.map +1 -1
  11. package/dist/admin/components/EditLocale.mjs +1 -1
  12. package/dist/admin/components/EditLocale.mjs.map +1 -1
  13. package/dist/admin/components/LocaleListCell.js +65 -45
  14. package/dist/admin/components/LocaleListCell.js.map +1 -1
  15. package/dist/admin/components/LocaleListCell.mjs +66 -46
  16. package/dist/admin/components/LocaleListCell.mjs.map +1 -1
  17. package/dist/admin/components/LocalePicker.js +18 -11
  18. package/dist/admin/components/LocalePicker.js.map +1 -1
  19. package/dist/admin/components/LocalePicker.mjs +19 -12
  20. package/dist/admin/components/LocalePicker.mjs.map +1 -1
  21. package/dist/admin/contentManagerHooks/editView.js +6 -3
  22. package/dist/admin/contentManagerHooks/editView.js.map +1 -1
  23. package/dist/admin/contentManagerHooks/editView.mjs +7 -4
  24. package/dist/admin/contentManagerHooks/editView.mjs.map +1 -1
  25. package/dist/admin/contentManagerHooks/listView.js +2 -1
  26. package/dist/admin/contentManagerHooks/listView.js.map +1 -1
  27. package/dist/admin/contentManagerHooks/listView.mjs +2 -1
  28. package/dist/admin/contentManagerHooks/listView.mjs.map +1 -1
  29. package/dist/admin/hooks/useAILocalizationJobsPolling.js +110 -0
  30. package/dist/admin/hooks/useAILocalizationJobsPolling.js.map +1 -0
  31. package/dist/admin/hooks/useAILocalizationJobsPolling.mjs +89 -0
  32. package/dist/admin/hooks/useAILocalizationJobsPolling.mjs.map +1 -0
  33. package/dist/admin/hooks/useI18n.js +4 -4
  34. package/dist/admin/hooks/useI18n.js.map +1 -1
  35. package/dist/admin/hooks/useI18n.mjs +4 -4
  36. package/dist/admin/hooks/useI18n.mjs.map +1 -1
  37. package/dist/admin/index.js +1 -1
  38. package/dist/admin/index.js.map +1 -1
  39. package/dist/admin/index.mjs +2 -2
  40. package/dist/admin/index.mjs.map +1 -1
  41. package/dist/admin/middlewares/extendCTBAttributeInitialData.js +34 -24
  42. package/dist/admin/middlewares/extendCTBAttributeInitialData.js.map +1 -1
  43. package/dist/admin/middlewares/extendCTBAttributeInitialData.mjs +34 -24
  44. package/dist/admin/middlewares/extendCTBAttributeInitialData.mjs.map +1 -1
  45. package/dist/admin/pages/SettingsPage.js +121 -46
  46. package/dist/admin/pages/SettingsPage.js.map +1 -1
  47. package/dist/admin/pages/SettingsPage.mjs +124 -30
  48. package/dist/admin/pages/SettingsPage.mjs.map +1 -1
  49. package/dist/admin/services/aiLocalizationJobs.js +26 -0
  50. package/dist/admin/services/aiLocalizationJobs.js.map +1 -0
  51. package/dist/admin/services/aiLocalizationJobs.mjs +24 -0
  52. package/dist/admin/services/aiLocalizationJobs.mjs.map +1 -0
  53. package/dist/admin/services/api.js +4 -1
  54. package/dist/admin/services/api.js.map +1 -1
  55. package/dist/admin/services/api.mjs +4 -1
  56. package/dist/admin/services/api.mjs.map +1 -1
  57. package/dist/admin/services/locales.js +4 -2
  58. package/dist/admin/services/locales.js.map +1 -1
  59. package/dist/admin/services/locales.mjs +4 -2
  60. package/dist/admin/services/locales.mjs.map +1 -1
  61. package/dist/admin/services/settings.js +29 -0
  62. package/dist/admin/services/settings.js.map +1 -0
  63. package/dist/admin/services/settings.mjs +26 -0
  64. package/dist/admin/services/settings.mjs.map +1 -0
  65. package/dist/admin/src/components/CMHeaderActions.d.ts +17 -4
  66. package/dist/admin/src/components/LocaleListCell.d.ts +2 -1
  67. package/dist/admin/src/hooks/useAILocalizationJobsPolling.d.ts +9 -0
  68. package/dist/admin/src/services/aiLocalizationJobs.d.ts +6 -0
  69. package/dist/admin/src/services/api.d.ts +1 -1
  70. package/dist/admin/src/services/locales.d.ts +1 -1
  71. package/dist/admin/src/services/relations.d.ts +1 -1
  72. package/dist/admin/src/services/settings.d.ts +5 -0
  73. package/dist/admin/src/utils/schemas.d.ts +642 -16
  74. package/dist/admin/translations/en.json.js +10 -0
  75. package/dist/admin/translations/en.json.js.map +1 -1
  76. package/dist/admin/translations/en.json.mjs +10 -0
  77. package/dist/admin/translations/en.json.mjs.map +1 -1
  78. package/dist/admin/utils/clean.js +2 -2
  79. package/dist/admin/utils/clean.js.map +1 -1
  80. package/dist/admin/utils/clean.mjs +2 -2
  81. package/dist/admin/utils/clean.mjs.map +1 -1
  82. package/dist/admin/utils/fields.js +6 -1
  83. package/dist/admin/utils/fields.js.map +1 -1
  84. package/dist/admin/utils/fields.mjs +6 -1
  85. package/dist/admin/utils/fields.mjs.map +1 -1
  86. package/dist/admin/utils/schemas.js +19 -13
  87. package/dist/admin/utils/schemas.js.map +1 -1
  88. package/dist/admin/utils/schemas.mjs +19 -13
  89. package/dist/admin/utils/schemas.mjs.map +1 -1
  90. package/dist/server/bootstrap.js +2 -0
  91. package/dist/server/bootstrap.js.map +1 -1
  92. package/dist/server/bootstrap.mjs +2 -0
  93. package/dist/server/bootstrap.mjs.map +1 -1
  94. package/dist/server/constants/iso-locales.json.js +4 -0
  95. package/dist/server/constants/iso-locales.json.js.map +1 -1
  96. package/dist/server/constants/iso-locales.json.mjs +4 -0
  97. package/dist/server/constants/iso-locales.json.mjs.map +1 -1
  98. package/dist/server/controllers/ai-localization-jobs.js +47 -0
  99. package/dist/server/controllers/ai-localization-jobs.js.map +1 -0
  100. package/dist/server/controllers/ai-localization-jobs.mjs +45 -0
  101. package/dist/server/controllers/ai-localization-jobs.mjs.map +1 -0
  102. package/dist/server/controllers/index.js +5 -1
  103. package/dist/server/controllers/index.js.map +1 -1
  104. package/dist/server/controllers/index.mjs +5 -1
  105. package/dist/server/controllers/index.mjs.map +1 -1
  106. package/dist/server/controllers/settings.js +24 -0
  107. package/dist/server/controllers/settings.js.map +1 -0
  108. package/dist/server/controllers/settings.mjs +22 -0
  109. package/dist/server/controllers/settings.mjs.map +1 -0
  110. package/dist/server/models/ai-localization-job.js +60 -0
  111. package/dist/server/models/ai-localization-job.js.map +1 -0
  112. package/dist/server/models/ai-localization-job.mjs +57 -0
  113. package/dist/server/models/ai-localization-job.mjs.map +1 -0
  114. package/dist/server/register.js +3 -1
  115. package/dist/server/register.js.map +1 -1
  116. package/dist/server/register.mjs +3 -1
  117. package/dist/server/register.mjs.map +1 -1
  118. package/dist/server/routes/admin.js +40 -0
  119. package/dist/server/routes/admin.js.map +1 -1
  120. package/dist/server/routes/admin.mjs +40 -0
  121. package/dist/server/routes/admin.mjs.map +1 -1
  122. package/dist/server/routes/content-api.js +11 -7
  123. package/dist/server/routes/content-api.js.map +1 -1
  124. package/dist/server/routes/content-api.mjs +11 -7
  125. package/dist/server/routes/content-api.mjs.map +1 -1
  126. package/dist/server/routes/index.mjs +2 -2
  127. package/dist/server/routes/validation/locale.js +57 -0
  128. package/dist/server/routes/validation/locale.js.map +1 -0
  129. package/dist/server/routes/validation/locale.mjs +36 -0
  130. package/dist/server/routes/validation/locale.mjs.map +1 -0
  131. package/dist/server/services/ai-localization-jobs.js +64 -0
  132. package/dist/server/services/ai-localization-jobs.js.map +1 -0
  133. package/dist/server/services/ai-localization-jobs.mjs +62 -0
  134. package/dist/server/services/ai-localization-jobs.mjs.map +1 -0
  135. package/dist/server/services/ai-localizations.js +272 -0
  136. package/dist/server/services/ai-localizations.js.map +1 -0
  137. package/dist/server/services/ai-localizations.mjs +270 -0
  138. package/dist/server/services/ai-localizations.mjs.map +1 -0
  139. package/dist/server/services/index.js +7 -1
  140. package/dist/server/services/index.js.map +1 -1
  141. package/dist/server/services/index.mjs +7 -1
  142. package/dist/server/services/index.mjs.map +1 -1
  143. package/dist/server/services/settings.js +25 -0
  144. package/dist/server/services/settings.js.map +1 -0
  145. package/dist/server/services/settings.mjs +23 -0
  146. package/dist/server/services/settings.mjs.map +1 -0
  147. package/dist/server/src/bootstrap.d.ts.map +1 -1
  148. package/dist/server/src/controllers/ai-localization-jobs.d.ts +17 -0
  149. package/dist/server/src/controllers/ai-localization-jobs.d.ts.map +1 -0
  150. package/dist/server/src/controllers/index.d.ts +10 -0
  151. package/dist/server/src/controllers/index.d.ts.map +1 -1
  152. package/dist/server/src/controllers/settings.d.ts +7 -0
  153. package/dist/server/src/controllers/settings.d.ts.map +1 -0
  154. package/dist/server/src/index.d.ts +47 -8
  155. package/dist/server/src/index.d.ts.map +1 -1
  156. package/dist/server/src/models/ai-localization-job.d.ts +5 -0
  157. package/dist/server/src/models/ai-localization-job.d.ts.map +1 -0
  158. package/dist/server/src/models/index.d.ts +5 -0
  159. package/dist/server/src/models/index.d.ts.map +1 -0
  160. package/dist/server/src/register.d.ts +1 -1
  161. package/dist/server/src/register.d.ts.map +1 -1
  162. package/dist/server/src/routes/admin.d.ts.map +1 -1
  163. package/dist/server/src/routes/content-api.d.ts +5 -8
  164. package/dist/server/src/routes/content-api.d.ts.map +1 -1
  165. package/dist/server/src/routes/index.d.ts +3 -7
  166. package/dist/server/src/routes/index.d.ts.map +1 -1
  167. package/dist/server/src/routes/validation/index.d.ts +2 -0
  168. package/dist/server/src/routes/validation/index.d.ts.map +1 -0
  169. package/dist/server/src/routes/validation/locale.d.ts +41 -0
  170. package/dist/server/src/routes/validation/locale.d.ts.map +1 -0
  171. package/dist/server/src/services/ai-localization-jobs.d.ts +26 -0
  172. package/dist/server/src/services/ai-localization-jobs.d.ts.map +1 -0
  173. package/dist/server/src/services/ai-localizations.d.ts +18 -0
  174. package/dist/server/src/services/ai-localizations.d.ts.map +1 -0
  175. package/dist/server/src/services/index.d.ts +33 -0
  176. package/dist/server/src/services/index.d.ts.map +1 -1
  177. package/dist/server/src/services/settings.d.ts +13 -0
  178. package/dist/server/src/services/settings.d.ts.map +1 -0
  179. package/dist/server/src/utils/index.d.ts +7 -1
  180. package/dist/server/src/utils/index.d.ts.map +1 -1
  181. package/dist/server/src/validation/settings.d.ts +12 -0
  182. package/dist/server/src/validation/settings.d.ts.map +1 -0
  183. package/dist/server/utils/index.js.map +1 -1
  184. package/dist/server/utils/index.mjs.map +1 -1
  185. package/dist/server/validation/settings.js +11 -0
  186. package/dist/server/validation/settings.js.map +1 -0
  187. package/dist/server/validation/settings.mjs +9 -0
  188. package/dist/server/validation/settings.mjs.map +1 -0
  189. package/dist/shared/contracts/ai-localization-jobs.d.ts +27 -0
  190. package/dist/shared/contracts/ai-localization-jobs.d.ts.map +1 -0
  191. package/dist/shared/contracts/settings.d.ts +40 -0
  192. package/dist/shared/contracts/shared.d.ts.map +1 -0
  193. package/package.json +12 -9
@@ -1,9 +1,9 @@
1
1
  import admin from './admin.mjs';
2
- import contentApi from './content-api.mjs';
2
+ import createContentApiRoutes from './content-api.mjs';
3
3
 
4
4
  var routes = {
5
5
  admin,
6
- 'content-api': contentApi
6
+ 'content-api': createContentApiRoutes
7
7
  };
8
8
 
9
9
  export { routes as default };
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ var z = require('zod/v4');
4
+
5
+ function _interopNamespaceDefault(e) {
6
+ var n = Object.create(null);
7
+ if (e) {
8
+ Object.keys(e).forEach(function (k) {
9
+ if (k !== 'default') {
10
+ var d = Object.getOwnPropertyDescriptor(e, k);
11
+ Object.defineProperty(n, k, d.get ? d : {
12
+ enumerable: true,
13
+ get: function () { return e[k]; }
14
+ });
15
+ }
16
+ });
17
+ }
18
+ n.default = e;
19
+ return Object.freeze(n);
20
+ }
21
+
22
+ var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
23
+
24
+ /**
25
+ * A validator for i18n locale routes.
26
+ *
27
+ */ class I18nLocaleRouteValidator {
28
+ /**
29
+ * Generates a validation schema for a single locale.
30
+ *
31
+ * @returns A schema for validating locale objects
32
+ */ get locale() {
33
+ return z__namespace.object({
34
+ id: z__namespace.number().int().positive(),
35
+ documentId: z__namespace.string().uuid(),
36
+ name: z__namespace.string(),
37
+ code: z__namespace.string().length(2, 'Locale code must be exactly 2 characters'),
38
+ createdAt: z__namespace.string(),
39
+ updatedAt: z__namespace.string(),
40
+ publishedAt: z__namespace.string().nullable(),
41
+ isDefault: z__namespace.boolean()
42
+ });
43
+ }
44
+ /**
45
+ * Generates a validation schema for an array of locales
46
+ *
47
+ * @returns A schema for validating arrays of locales
48
+ */ get locales() {
49
+ return z__namespace.array(this.locale);
50
+ }
51
+ constructor(strapi){
52
+ this._strapi = strapi;
53
+ }
54
+ }
55
+
56
+ exports.I18nLocaleRouteValidator = I18nLocaleRouteValidator;
57
+ //# sourceMappingURL=locale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.js","sources":["../../../../server/src/routes/validation/locale.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport * as z from 'zod/v4';\n\n/**\n * A validator for i18n locale routes.\n *\n */\nexport class I18nLocaleRouteValidator {\n protected readonly _strapi: Core.Strapi;\n\n public constructor(strapi: Core.Strapi) {\n this._strapi = strapi;\n }\n\n /**\n * Generates a validation schema for a single locale.\n *\n * @returns A schema for validating locale objects\n */\n get locale() {\n return z.object({\n id: z.number().int().positive(),\n documentId: z.string().uuid(),\n name: z.string(),\n code: z.string().length(2, 'Locale code must be exactly 2 characters'),\n createdAt: z.string(),\n updatedAt: z.string(),\n publishedAt: z.string().nullable(),\n isDefault: z.boolean(),\n });\n }\n\n /**\n * Generates a validation schema for an array of locales\n *\n * @returns A schema for validating arrays of locales\n */\n get locales() {\n return z.array(this.locale);\n }\n}\n"],"names":["I18nLocaleRouteValidator","locale","z","object","id","number","int","positive","documentId","string","uuid","name","code","length","createdAt","updatedAt","publishedAt","nullable","isDefault","boolean","locales","array","strapi","_strapi"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAGA;;;AAGC,IACM,MAAMA,wBAAAA,CAAAA;AAOX;;;;AAIC,MACD,IAAIC,MAAS,GAAA;QACX,OAAOC,YAAAA,CAAEC,MAAM,CAAC;AACdC,YAAAA,EAAAA,EAAIF,YAAEG,CAAAA,MAAM,EAAGC,CAAAA,GAAG,GAAGC,QAAQ,EAAA;YAC7BC,UAAYN,EAAAA,YAAAA,CAAEO,MAAM,EAAA,CAAGC,IAAI,EAAA;AAC3BC,YAAAA,IAAAA,EAAMT,aAAEO,MAAM,EAAA;AACdG,YAAAA,IAAAA,EAAMV,YAAEO,CAAAA,MAAM,EAAGI,CAAAA,MAAM,CAAC,CAAG,EAAA,0CAAA,CAAA;AAC3BC,YAAAA,SAAAA,EAAWZ,aAAEO,MAAM,EAAA;AACnBM,YAAAA,SAAAA,EAAWb,aAAEO,MAAM,EAAA;YACnBO,WAAad,EAAAA,YAAAA,CAAEO,MAAM,EAAA,CAAGQ,QAAQ,EAAA;AAChCC,YAAAA,SAAAA,EAAWhB,aAAEiB,OAAO;AACtB,SAAA,CAAA;AACF;AAEA;;;;AAIC,MACD,IAAIC,OAAU,GAAA;AACZ,QAAA,OAAOlB,YAAEmB,CAAAA,KAAK,CAAC,IAAI,CAACpB,MAAM,CAAA;AAC5B;AA7BA,IAAA,WAAA,CAAmBqB,MAAmB,CAAE;QACtC,IAAI,CAACC,OAAO,GAAGD,MAAAA;AACjB;AA4BF;;;;"}
@@ -0,0 +1,36 @@
1
+ import * as z from 'zod/v4';
2
+
3
+ /**
4
+ * A validator for i18n locale routes.
5
+ *
6
+ */ class I18nLocaleRouteValidator {
7
+ /**
8
+ * Generates a validation schema for a single locale.
9
+ *
10
+ * @returns A schema for validating locale objects
11
+ */ get locale() {
12
+ return z.object({
13
+ id: z.number().int().positive(),
14
+ documentId: z.string().uuid(),
15
+ name: z.string(),
16
+ code: z.string().length(2, 'Locale code must be exactly 2 characters'),
17
+ createdAt: z.string(),
18
+ updatedAt: z.string(),
19
+ publishedAt: z.string().nullable(),
20
+ isDefault: z.boolean()
21
+ });
22
+ }
23
+ /**
24
+ * Generates a validation schema for an array of locales
25
+ *
26
+ * @returns A schema for validating arrays of locales
27
+ */ get locales() {
28
+ return z.array(this.locale);
29
+ }
30
+ constructor(strapi){
31
+ this._strapi = strapi;
32
+ }
33
+ }
34
+
35
+ export { I18nLocaleRouteValidator };
36
+ //# sourceMappingURL=locale.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale.mjs","sources":["../../../../server/src/routes/validation/locale.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport * as z from 'zod/v4';\n\n/**\n * A validator for i18n locale routes.\n *\n */\nexport class I18nLocaleRouteValidator {\n protected readonly _strapi: Core.Strapi;\n\n public constructor(strapi: Core.Strapi) {\n this._strapi = strapi;\n }\n\n /**\n * Generates a validation schema for a single locale.\n *\n * @returns A schema for validating locale objects\n */\n get locale() {\n return z.object({\n id: z.number().int().positive(),\n documentId: z.string().uuid(),\n name: z.string(),\n code: z.string().length(2, 'Locale code must be exactly 2 characters'),\n createdAt: z.string(),\n updatedAt: z.string(),\n publishedAt: z.string().nullable(),\n isDefault: z.boolean(),\n });\n }\n\n /**\n * Generates a validation schema for an array of locales\n *\n * @returns A schema for validating arrays of locales\n */\n get locales() {\n return z.array(this.locale);\n }\n}\n"],"names":["I18nLocaleRouteValidator","locale","z","object","id","number","int","positive","documentId","string","uuid","name","code","length","createdAt","updatedAt","publishedAt","nullable","isDefault","boolean","locales","array","strapi","_strapi"],"mappings":";;AAGA;;;AAGC,IACM,MAAMA,wBAAAA,CAAAA;AAOX;;;;AAIC,MACD,IAAIC,MAAS,GAAA;QACX,OAAOC,CAAAA,CAAEC,MAAM,CAAC;AACdC,YAAAA,EAAAA,EAAIF,CAAEG,CAAAA,MAAM,EAAGC,CAAAA,GAAG,GAAGC,QAAQ,EAAA;YAC7BC,UAAYN,EAAAA,CAAAA,CAAEO,MAAM,EAAA,CAAGC,IAAI,EAAA;AAC3BC,YAAAA,IAAAA,EAAMT,EAAEO,MAAM,EAAA;AACdG,YAAAA,IAAAA,EAAMV,CAAEO,CAAAA,MAAM,EAAGI,CAAAA,MAAM,CAAC,CAAG,EAAA,0CAAA,CAAA;AAC3BC,YAAAA,SAAAA,EAAWZ,EAAEO,MAAM,EAAA;AACnBM,YAAAA,SAAAA,EAAWb,EAAEO,MAAM,EAAA;YACnBO,WAAad,EAAAA,CAAAA,CAAEO,MAAM,EAAA,CAAGQ,QAAQ,EAAA;AAChCC,YAAAA,SAAAA,EAAWhB,EAAEiB,OAAO;AACtB,SAAA,CAAA;AACF;AAEA;;;;AAIC,MACD,IAAIC,OAAU,GAAA;AACZ,QAAA,OAAOlB,CAAEmB,CAAAA,KAAK,CAAC,IAAI,CAACpB,MAAM,CAAA;AAC5B;AA7BA,IAAA,WAAA,CAAmBqB,MAAmB,CAAE;QACtC,IAAI,CAACC,OAAO,GAAGD,MAAAA;AACjB;AA4BF;;;;"}
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ var aiLocalizationJob = require('../models/ai-localization-job.js');
4
+
5
+ const createAILocalizationJobsService = ({ strapi })=>({
6
+ /**
7
+ * Create a new AI localizations job or update an existing one for a document
8
+ * Ensures only one job exists per document
9
+ */ async upsertJobForDocument ({ documentId, contentType, sourceLocale, targetLocales, status = 'processing' }) {
10
+ // Check if job already exists for this document
11
+ const existingJob = await this.getJobByDocument(contentType, documentId);
12
+ if (existingJob) {
13
+ strapi.log.info(`[AI Localizations Job] Updated existing job for document ${documentId} with status: ${status}`);
14
+ // Update existing job with new data and status
15
+ return strapi.db.query(aiLocalizationJob.AI_LOCALIZATION_JOB_UID).update({
16
+ where: {
17
+ id: existingJob.id
18
+ },
19
+ data: {
20
+ contentType,
21
+ sourceLocale,
22
+ targetLocales,
23
+ status,
24
+ updatedAt: new Date()
25
+ }
26
+ });
27
+ }
28
+ strapi.log.info(`[AI Localizations Job] Created new job for document ${documentId} with status: ${status}`);
29
+ // Create new AI localizations job
30
+ return strapi.db.query(aiLocalizationJob.AI_LOCALIZATION_JOB_UID).create({
31
+ data: {
32
+ contentType,
33
+ relatedDocumentId: documentId,
34
+ sourceLocale,
35
+ targetLocales,
36
+ status,
37
+ createdAt: new Date(),
38
+ updatedAt: new Date()
39
+ }
40
+ });
41
+ },
42
+ /**
43
+ * Get job by document ID
44
+ */ async getJobByDocument (contentType, documentId) {
45
+ return strapi.db.query(aiLocalizationJob.AI_LOCALIZATION_JOB_UID).findOne({
46
+ where: {
47
+ relatedDocumentId: documentId,
48
+ contentType
49
+ }
50
+ });
51
+ },
52
+ /**
53
+ * Get job by content type
54
+ */ async getJobByContentType (contentType) {
55
+ return strapi.db.query(aiLocalizationJob.AI_LOCALIZATION_JOB_UID).findOne({
56
+ where: {
57
+ contentType
58
+ }
59
+ });
60
+ }
61
+ });
62
+
63
+ exports.createAILocalizationJobsService = createAILocalizationJobsService;
64
+ //# sourceMappingURL=ai-localization-jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-localization-jobs.js","sources":["../../../server/src/services/ai-localization-jobs.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport { AI_LOCALIZATION_JOB_UID } from '../models/ai-localization-job';\nimport type { AILocalizationJobs } from '../../../shared/contracts/ai-localization-jobs';\n\nexport const createAILocalizationJobsService = ({ strapi }: { strapi: Core.Strapi }) => ({\n /**\n * Create a new AI localizations job or update an existing one for a document\n * Ensures only one job exists per document\n */\n async upsertJobForDocument({\n documentId,\n contentType,\n sourceLocale,\n targetLocales,\n status = 'processing',\n }: {\n documentId: string;\n contentType: string;\n sourceLocale: string;\n targetLocales: string[];\n status?: AILocalizationJobs['status'];\n }) {\n // Check if job already exists for this document\n const existingJob = await this.getJobByDocument(contentType, documentId);\n\n if (existingJob) {\n strapi.log.info(\n `[AI Localizations Job] Updated existing job for document ${documentId} with status: ${status}`\n );\n // Update existing job with new data and status\n return strapi.db.query(AI_LOCALIZATION_JOB_UID).update({\n where: { id: existingJob.id },\n data: {\n contentType,\n sourceLocale,\n targetLocales,\n status,\n updatedAt: new Date(),\n },\n });\n }\n\n strapi.log.info(\n `[AI Localizations Job] Created new job for document ${documentId} with status: ${status}`\n );\n // Create new AI localizations job\n return strapi.db.query(AI_LOCALIZATION_JOB_UID).create({\n data: {\n contentType,\n relatedDocumentId: documentId,\n sourceLocale,\n targetLocales,\n status,\n createdAt: new Date(),\n updatedAt: new Date(),\n },\n });\n },\n\n /**\n * Get job by document ID\n */\n async getJobByDocument(contentType: string, documentId: string) {\n return strapi.db.query(AI_LOCALIZATION_JOB_UID).findOne({\n where: {\n relatedDocumentId: documentId,\n contentType,\n },\n });\n },\n\n /**\n * Get job by content type\n */\n async getJobByContentType(contentType: string) {\n return strapi.db.query(AI_LOCALIZATION_JOB_UID).findOne({\n where: {\n contentType,\n },\n });\n },\n});\n"],"names":["createAILocalizationJobsService","strapi","upsertJobForDocument","documentId","contentType","sourceLocale","targetLocales","status","existingJob","getJobByDocument","log","info","db","query","AI_LOCALIZATION_JOB_UID","update","where","id","data","updatedAt","Date","create","relatedDocumentId","createdAt","findOne","getJobByContentType"],"mappings":";;;;MAIaA,+BAAkC,GAAA,CAAC,EAAEC,MAAM,EAA2B,IAAM;AACvF;;;AAGC,MACD,MAAMC,oBAAAA,CAAAA,CAAqB,EACzBC,UAAU,EACVC,WAAW,EACXC,YAAY,EACZC,aAAa,EACbC,MAAAA,GAAS,YAAY,EAOtB,EAAA;;AAEC,YAAA,MAAMC,cAAc,MAAM,IAAI,CAACC,gBAAgB,CAACL,WAAaD,EAAAA,UAAAA,CAAAA;AAE7D,YAAA,IAAIK,WAAa,EAAA;gBACfP,MAAOS,CAAAA,GAAG,CAACC,IAAI,CACb,CAAC,yDAAyD,EAAER,UAAW,CAAA,cAAc,EAAEI,MAAAA,CAAO,CAAC,CAAA;;AAGjG,gBAAA,OAAON,OAAOW,EAAE,CAACC,KAAK,CAACC,yCAAAA,CAAAA,CAAyBC,MAAM,CAAC;oBACrDC,KAAO,EAAA;AAAEC,wBAAAA,EAAAA,EAAIT,YAAYS;AAAG,qBAAA;oBAC5BC,IAAM,EAAA;AACJd,wBAAAA,WAAAA;AACAC,wBAAAA,YAAAA;AACAC,wBAAAA,aAAAA;AACAC,wBAAAA,MAAAA;AACAY,wBAAAA,SAAAA,EAAW,IAAIC,IAAAA;AACjB;AACF,iBAAA,CAAA;AACF;YAEAnB,MAAOS,CAAAA,GAAG,CAACC,IAAI,CACb,CAAC,oDAAoD,EAAER,UAAW,CAAA,cAAc,EAAEI,MAAAA,CAAO,CAAC,CAAA;;AAG5F,YAAA,OAAON,OAAOW,EAAE,CAACC,KAAK,CAACC,yCAAAA,CAAAA,CAAyBO,MAAM,CAAC;gBACrDH,IAAM,EAAA;AACJd,oBAAAA,WAAAA;oBACAkB,iBAAmBnB,EAAAA,UAAAA;AACnBE,oBAAAA,YAAAA;AACAC,oBAAAA,aAAAA;AACAC,oBAAAA,MAAAA;AACAgB,oBAAAA,SAAAA,EAAW,IAAIH,IAAAA,EAAAA;AACfD,oBAAAA,SAAAA,EAAW,IAAIC,IAAAA;AACjB;AACF,aAAA,CAAA;AACF,SAAA;AAEA;;AAEC,MACD,MAAMX,gBAAAA,CAAAA,CAAiBL,WAAmB,EAAED,UAAkB,EAAA;AAC5D,YAAA,OAAOF,OAAOW,EAAE,CAACC,KAAK,CAACC,yCAAAA,CAAAA,CAAyBU,OAAO,CAAC;gBACtDR,KAAO,EAAA;oBACLM,iBAAmBnB,EAAAA,UAAAA;AACnBC,oBAAAA;AACF;AACF,aAAA,CAAA;AACF,SAAA;AAEA;;MAGA,MAAMqB,qBAAoBrB,WAAmB,EAAA;AAC3C,YAAA,OAAOH,OAAOW,EAAE,CAACC,KAAK,CAACC,yCAAAA,CAAAA,CAAyBU,OAAO,CAAC;gBACtDR,KAAO,EAAA;AACLZ,oBAAAA;AACF;AACF,aAAA,CAAA;AACF;AACF,KAAA;;;;"}
@@ -0,0 +1,62 @@
1
+ import { AI_LOCALIZATION_JOB_UID } from '../models/ai-localization-job.mjs';
2
+
3
+ const createAILocalizationJobsService = ({ strapi })=>({
4
+ /**
5
+ * Create a new AI localizations job or update an existing one for a document
6
+ * Ensures only one job exists per document
7
+ */ async upsertJobForDocument ({ documentId, contentType, sourceLocale, targetLocales, status = 'processing' }) {
8
+ // Check if job already exists for this document
9
+ const existingJob = await this.getJobByDocument(contentType, documentId);
10
+ if (existingJob) {
11
+ strapi.log.info(`[AI Localizations Job] Updated existing job for document ${documentId} with status: ${status}`);
12
+ // Update existing job with new data and status
13
+ return strapi.db.query(AI_LOCALIZATION_JOB_UID).update({
14
+ where: {
15
+ id: existingJob.id
16
+ },
17
+ data: {
18
+ contentType,
19
+ sourceLocale,
20
+ targetLocales,
21
+ status,
22
+ updatedAt: new Date()
23
+ }
24
+ });
25
+ }
26
+ strapi.log.info(`[AI Localizations Job] Created new job for document ${documentId} with status: ${status}`);
27
+ // Create new AI localizations job
28
+ return strapi.db.query(AI_LOCALIZATION_JOB_UID).create({
29
+ data: {
30
+ contentType,
31
+ relatedDocumentId: documentId,
32
+ sourceLocale,
33
+ targetLocales,
34
+ status,
35
+ createdAt: new Date(),
36
+ updatedAt: new Date()
37
+ }
38
+ });
39
+ },
40
+ /**
41
+ * Get job by document ID
42
+ */ async getJobByDocument (contentType, documentId) {
43
+ return strapi.db.query(AI_LOCALIZATION_JOB_UID).findOne({
44
+ where: {
45
+ relatedDocumentId: documentId,
46
+ contentType
47
+ }
48
+ });
49
+ },
50
+ /**
51
+ * Get job by content type
52
+ */ async getJobByContentType (contentType) {
53
+ return strapi.db.query(AI_LOCALIZATION_JOB_UID).findOne({
54
+ where: {
55
+ contentType
56
+ }
57
+ });
58
+ }
59
+ });
60
+
61
+ export { createAILocalizationJobsService };
62
+ //# sourceMappingURL=ai-localization-jobs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-localization-jobs.mjs","sources":["../../../server/src/services/ai-localization-jobs.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport { AI_LOCALIZATION_JOB_UID } from '../models/ai-localization-job';\nimport type { AILocalizationJobs } from '../../../shared/contracts/ai-localization-jobs';\n\nexport const createAILocalizationJobsService = ({ strapi }: { strapi: Core.Strapi }) => ({\n /**\n * Create a new AI localizations job or update an existing one for a document\n * Ensures only one job exists per document\n */\n async upsertJobForDocument({\n documentId,\n contentType,\n sourceLocale,\n targetLocales,\n status = 'processing',\n }: {\n documentId: string;\n contentType: string;\n sourceLocale: string;\n targetLocales: string[];\n status?: AILocalizationJobs['status'];\n }) {\n // Check if job already exists for this document\n const existingJob = await this.getJobByDocument(contentType, documentId);\n\n if (existingJob) {\n strapi.log.info(\n `[AI Localizations Job] Updated existing job for document ${documentId} with status: ${status}`\n );\n // Update existing job with new data and status\n return strapi.db.query(AI_LOCALIZATION_JOB_UID).update({\n where: { id: existingJob.id },\n data: {\n contentType,\n sourceLocale,\n targetLocales,\n status,\n updatedAt: new Date(),\n },\n });\n }\n\n strapi.log.info(\n `[AI Localizations Job] Created new job for document ${documentId} with status: ${status}`\n );\n // Create new AI localizations job\n return strapi.db.query(AI_LOCALIZATION_JOB_UID).create({\n data: {\n contentType,\n relatedDocumentId: documentId,\n sourceLocale,\n targetLocales,\n status,\n createdAt: new Date(),\n updatedAt: new Date(),\n },\n });\n },\n\n /**\n * Get job by document ID\n */\n async getJobByDocument(contentType: string, documentId: string) {\n return strapi.db.query(AI_LOCALIZATION_JOB_UID).findOne({\n where: {\n relatedDocumentId: documentId,\n contentType,\n },\n });\n },\n\n /**\n * Get job by content type\n */\n async getJobByContentType(contentType: string) {\n return strapi.db.query(AI_LOCALIZATION_JOB_UID).findOne({\n where: {\n contentType,\n },\n });\n },\n});\n"],"names":["createAILocalizationJobsService","strapi","upsertJobForDocument","documentId","contentType","sourceLocale","targetLocales","status","existingJob","getJobByDocument","log","info","db","query","AI_LOCALIZATION_JOB_UID","update","where","id","data","updatedAt","Date","create","relatedDocumentId","createdAt","findOne","getJobByContentType"],"mappings":";;MAIaA,+BAAkC,GAAA,CAAC,EAAEC,MAAM,EAA2B,IAAM;AACvF;;;AAGC,MACD,MAAMC,oBAAAA,CAAAA,CAAqB,EACzBC,UAAU,EACVC,WAAW,EACXC,YAAY,EACZC,aAAa,EACbC,MAAAA,GAAS,YAAY,EAOtB,EAAA;;AAEC,YAAA,MAAMC,cAAc,MAAM,IAAI,CAACC,gBAAgB,CAACL,WAAaD,EAAAA,UAAAA,CAAAA;AAE7D,YAAA,IAAIK,WAAa,EAAA;gBACfP,MAAOS,CAAAA,GAAG,CAACC,IAAI,CACb,CAAC,yDAAyD,EAAER,UAAW,CAAA,cAAc,EAAEI,MAAAA,CAAO,CAAC,CAAA;;AAGjG,gBAAA,OAAON,OAAOW,EAAE,CAACC,KAAK,CAACC,uBAAAA,CAAAA,CAAyBC,MAAM,CAAC;oBACrDC,KAAO,EAAA;AAAEC,wBAAAA,EAAAA,EAAIT,YAAYS;AAAG,qBAAA;oBAC5BC,IAAM,EAAA;AACJd,wBAAAA,WAAAA;AACAC,wBAAAA,YAAAA;AACAC,wBAAAA,aAAAA;AACAC,wBAAAA,MAAAA;AACAY,wBAAAA,SAAAA,EAAW,IAAIC,IAAAA;AACjB;AACF,iBAAA,CAAA;AACF;YAEAnB,MAAOS,CAAAA,GAAG,CAACC,IAAI,CACb,CAAC,oDAAoD,EAAER,UAAW,CAAA,cAAc,EAAEI,MAAAA,CAAO,CAAC,CAAA;;AAG5F,YAAA,OAAON,OAAOW,EAAE,CAACC,KAAK,CAACC,uBAAAA,CAAAA,CAAyBO,MAAM,CAAC;gBACrDH,IAAM,EAAA;AACJd,oBAAAA,WAAAA;oBACAkB,iBAAmBnB,EAAAA,UAAAA;AACnBE,oBAAAA,YAAAA;AACAC,oBAAAA,aAAAA;AACAC,oBAAAA,MAAAA;AACAgB,oBAAAA,SAAAA,EAAW,IAAIH,IAAAA,EAAAA;AACfD,oBAAAA,SAAAA,EAAW,IAAIC,IAAAA;AACjB;AACF,aAAA,CAAA;AACF,SAAA;AAEA;;AAEC,MACD,MAAMX,gBAAAA,CAAAA,CAAiBL,WAAmB,EAAED,UAAkB,EAAA;AAC5D,YAAA,OAAOF,OAAOW,EAAE,CAACC,KAAK,CAACC,uBAAAA,CAAAA,CAAyBU,OAAO,CAAC;gBACtDR,KAAO,EAAA;oBACLM,iBAAmBnB,EAAAA,UAAAA;AACnBC,oBAAAA;AACF;AACF,aAAA,CAAA;AACF,SAAA;AAEA;;MAGA,MAAMqB,qBAAoBrB,WAAmB,EAAA;AAC3C,YAAA,OAAOH,OAAOW,EAAE,CAACC,KAAK,CAACC,uBAAAA,CAAAA,CAAyBU,OAAO,CAAC;gBACtDR,KAAO,EAAA;AACLZ,oBAAAA;AACF;AACF,aAAA,CAAA;AACF;AACF,KAAA;;;;"}
@@ -0,0 +1,272 @@
1
+ 'use strict';
2
+
3
+ var utils = require('@strapi/utils');
4
+ var index = require('../utils/index.js');
5
+
6
+ const isLocalizedAttribute = (attribute)=>{
7
+ return attribute?.pluginOptions?.i18n?.localized === true;
8
+ };
9
+ const createAILocalizationsService = ({ strapi })=>{
10
+ // TODO: add a helper function to get the AI server URL
11
+ const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';
12
+ const aiLocalizationJobsService = index.getService('ai-localization-jobs');
13
+ const UNSUPPORTED_ATTRIBUTE_TYPES = [
14
+ 'media',
15
+ 'relation'
16
+ ];
17
+ const IGNORED_FIELDS = [
18
+ 'id',
19
+ 'documentId',
20
+ 'createdAt',
21
+ 'updatedAt',
22
+ 'updatedBy',
23
+ 'localizations'
24
+ ];
25
+ return {
26
+ // Async to avoid changing the signature later (there will be a db check in the future)
27
+ async isEnabled () {
28
+ // Check if future flag is enabled
29
+ const isFutureFlagEnabled = strapi.features.future.isEnabled('unstableAILocalizations');
30
+ if (!isFutureFlagEnabled) {
31
+ return false;
32
+ }
33
+ // Check if user disabled AI features globally
34
+ const isAIEnabled = strapi.config.get('admin.ai.enabled', true);
35
+ if (!isAIEnabled) {
36
+ return false;
37
+ }
38
+ // Check if the user's license grants access to AI features
39
+ const hasAccess = strapi.ee.features.isEnabled('cms-ai');
40
+ if (!hasAccess) {
41
+ return false;
42
+ }
43
+ const settings = index.getService('settings');
44
+ const aiSettings = await settings.getSettings();
45
+ if (!aiSettings?.aiLocalizations) {
46
+ return false;
47
+ }
48
+ return true;
49
+ },
50
+ /**
51
+ * Checks if there are localizations that need to be generated for the given document,
52
+ * and if so, calls the AI service and saves the results to the database.
53
+ * Works for both single and collection types, on create and update.
54
+ */ async generateDocumentLocalizations ({ model, document }) {
55
+ const isFeatureEnabled = await this.isEnabled();
56
+ if (!isFeatureEnabled) {
57
+ return;
58
+ }
59
+ const schema = strapi.getModel(model);
60
+ const localeService = index.getService('locales');
61
+ // No localizations needed for content types with i18n disabled
62
+ const isLocalizedContentType = index.getService('content-types').isLocalizedContentType(schema);
63
+ if (!isLocalizedContentType) {
64
+ return;
65
+ }
66
+ // Don't trigger localizations if the update is on a derived locale, only do it on the default
67
+ const defaultLocale = await localeService.getDefaultLocale();
68
+ if (document?.locale !== defaultLocale) {
69
+ return;
70
+ }
71
+ const documentId = document.documentId;
72
+ if (!documentId) {
73
+ strapi.log.warn(`AI Localizations: missing documentId for ${schema.uid}`);
74
+ return;
75
+ }
76
+ const localizedRoots = new Set();
77
+ const translateableContent = await utils.traverseEntity(({ key, attribute, parent, path }, { remove })=>{
78
+ if (IGNORED_FIELDS.includes(key)) {
79
+ remove(key);
80
+ return;
81
+ }
82
+ const hasLocalizedOption = attribute && isLocalizedAttribute(attribute);
83
+ if (attribute && UNSUPPORTED_ATTRIBUTE_TYPES.includes(attribute.type)) {
84
+ remove(key);
85
+ return;
86
+ }
87
+ // If this field is localized, keep it (and mark as localized root if component/dz)
88
+ if (hasLocalizedOption) {
89
+ // If it's a component/dynamiczone, add to the set
90
+ if ([
91
+ 'component',
92
+ 'dynamiczone'
93
+ ].includes(attribute.type)) {
94
+ localizedRoots.add(path.raw);
95
+ }
96
+ return; // keep
97
+ }
98
+ if (parent && localizedRoots.has(parent.path.raw)) {
99
+ // If parent exists in the localized roots set, keep it
100
+ // If this is also a component/dz, propagate the localized root flag
101
+ if ([
102
+ 'component',
103
+ 'dynamiczone'
104
+ ].includes(attribute?.type ?? '')) {
105
+ localizedRoots.add(path.raw);
106
+ }
107
+ return; // keep
108
+ }
109
+ // Otherwise, remove the field
110
+ remove(key);
111
+ }, {
112
+ schema,
113
+ getModel: strapi.getModel.bind(strapi)
114
+ }, document);
115
+ // Call the AI server to get the localized content
116
+ const localesList = await localeService.find();
117
+ const targetLocales = localesList.filter((l)=>l.code !== document.locale).map((l)=>l.code);
118
+ if (targetLocales.length === 0) {
119
+ strapi.log.info(`AI Localizations: no target locales for ${schema.uid} document ${documentId}`);
120
+ return;
121
+ }
122
+ await aiLocalizationJobsService.upsertJobForDocument({
123
+ contentType: model,
124
+ documentId,
125
+ sourceLocale: document.locale,
126
+ targetLocales,
127
+ status: 'processing'
128
+ });
129
+ let token;
130
+ try {
131
+ const tokenData = await strapi.service('admin::user').getAiToken();
132
+ token = tokenData.token;
133
+ } catch (error) {
134
+ await aiLocalizationJobsService.upsertJobForDocument({
135
+ documentId,
136
+ contentType: model,
137
+ sourceLocale: document.locale,
138
+ targetLocales,
139
+ status: 'failed'
140
+ });
141
+ throw new Error('Failed to retrieve AI token', {
142
+ cause: error instanceof Error ? error : undefined
143
+ });
144
+ }
145
+ /**
146
+ * Provide a schema to the LLM so that we can give it instructions about how to handle each
147
+ * type of attribute. Only keep essential schema data to avoid cluttering the context.
148
+ * Ignore fields that don't need to be localized.
149
+ * TODO: also provide a schema of all the referenced components
150
+ */ const minimalContentTypeSchema = Object.fromEntries(Object.entries(schema.attributes)// eslint-disable-next-line @typescript-eslint/no-unused-vars
151
+ .filter(([_, attr])=>{
152
+ const isLocalized = isLocalizedAttribute(attr);
153
+ const isSupportedType = !UNSUPPORTED_ATTRIBUTE_TYPES.includes(attr.type);
154
+ return isLocalized && isSupportedType;
155
+ }).map(([key, attr])=>{
156
+ const minimalAttribute = {
157
+ type: attr.type
158
+ };
159
+ if (attr.type === 'component') {
160
+ minimalAttribute.repeatable = attr.repeatable ?? false;
161
+ }
162
+ return [
163
+ key,
164
+ minimalAttribute
165
+ ];
166
+ }));
167
+ strapi.log.http('Contacting AI Server for localizations generation');
168
+ const response = await fetch(`${aiServerUrl}/i18n/generate-localizations`, {
169
+ method: 'POST',
170
+ headers: {
171
+ 'Content-Type': 'application/json',
172
+ Authorization: `Bearer ${token}`
173
+ },
174
+ body: JSON.stringify({
175
+ content: translateableContent,
176
+ sourceLocale: document.locale,
177
+ targetLocales,
178
+ contentTypeSchema: minimalContentTypeSchema
179
+ })
180
+ });
181
+ if (!response.ok) {
182
+ strapi.log.error(`AI Localizations request failed: ${response.status} ${response.statusText}`);
183
+ await aiLocalizationJobsService.upsertJobForDocument({
184
+ documentId,
185
+ contentType: model,
186
+ sourceLocale: document.locale,
187
+ targetLocales,
188
+ status: 'failed'
189
+ });
190
+ throw new Error(`AI Localizations request failed: ${response.statusText}`);
191
+ } else {
192
+ await aiLocalizationJobsService.upsertJobForDocument({
193
+ documentId,
194
+ contentType: model,
195
+ sourceLocale: document.locale,
196
+ targetLocales,
197
+ status: 'completed'
198
+ });
199
+ }
200
+ const aiResult = await response.json();
201
+ // Get all media field names dynamically from the schema
202
+ const mediaFields = Object.entries(schema.attributes)// eslint-disable-next-line @typescript-eslint/no-unused-vars
203
+ .filter(([_, attr])=>attr.type === 'media').map(([key])=>key);
204
+ try {
205
+ await Promise.allSettled(aiResult.localizations.map(async (localization)=>{
206
+ const { content, locale } = localization;
207
+ // Fetch the derived locale document
208
+ const derivedDoc = await strapi.documents(model).findOne({
209
+ documentId,
210
+ locale,
211
+ populate: '*'
212
+ });
213
+ // Merge AI content and media fields, works only on first level media fields (root level)
214
+ const mergedData = {
215
+ ...content
216
+ };
217
+ for (const field of mediaFields){
218
+ // Only copy media if not already set in derived locale
219
+ if (!derivedDoc || !derivedDoc[field]) {
220
+ mergedData[field] = document[field];
221
+ } else {
222
+ mergedData[field] = derivedDoc[field];
223
+ }
224
+ }
225
+ return strapi.documents(model).update({
226
+ documentId,
227
+ locale,
228
+ fields: [],
229
+ data: mergedData
230
+ });
231
+ }));
232
+ } catch (error) {
233
+ await aiLocalizationJobsService.upsertJobForDocument({
234
+ documentId,
235
+ contentType: model,
236
+ sourceLocale: document.locale,
237
+ targetLocales,
238
+ status: 'failed'
239
+ });
240
+ strapi.log.error('AI Localizations generation failed', error);
241
+ }
242
+ },
243
+ setupMiddleware () {
244
+ strapi.documents.use(async (context, next)=>{
245
+ const result = await next();
246
+ // Only trigger on create/update actions
247
+ if (![
248
+ 'create',
249
+ 'update'
250
+ ].includes(context.action)) {
251
+ return result;
252
+ }
253
+ // Check if AI localizations are enabled before triggering
254
+ const isEnabled = await this.isEnabled();
255
+ if (!isEnabled) {
256
+ return result;
257
+ }
258
+ // Don't await since localizations should be done in the background without blocking the request
259
+ strapi.plugin('i18n').service('ai-localizations').generateDocumentLocalizations({
260
+ model: context.contentType.uid,
261
+ document: result
262
+ }).catch((error)=>{
263
+ strapi.log.error('AI Localizations generation failed', error);
264
+ });
265
+ return result;
266
+ });
267
+ }
268
+ };
269
+ };
270
+
271
+ exports.createAILocalizationsService = createAILocalizationsService;
272
+ //# sourceMappingURL=ai-localizations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-localizations.js","sources":["../../../server/src/services/ai-localizations.ts"],"sourcesContent":["import type { Core, Modules, Schema, UID } from '@strapi/types';\nimport { traverseEntity } from '@strapi/utils';\nimport { getService } from '../utils';\n\nconst isLocalizedAttribute = (attribute: Schema.Attribute.Attribute | undefined): boolean => {\n return (attribute?.pluginOptions as any)?.i18n?.localized === true;\n};\n\nconst createAILocalizationsService = ({ strapi }: { strapi: Core.Strapi }) => {\n // TODO: add a helper function to get the AI server URL\n const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';\n const aiLocalizationJobsService = getService('ai-localization-jobs');\n\n const UNSUPPORTED_ATTRIBUTE_TYPES: Schema.Attribute.Kind[] = ['media', 'relation'];\n const IGNORED_FIELDS = [\n 'id',\n 'documentId',\n 'createdAt',\n 'updatedAt',\n 'updatedBy',\n 'localizations',\n ];\n\n return {\n // Async to avoid changing the signature later (there will be a db check in the future)\n async isEnabled() {\n // Check if future flag is enabled\n const isFutureFlagEnabled = strapi.features.future.isEnabled('unstableAILocalizations');\n if (!isFutureFlagEnabled) {\n return false;\n }\n\n // Check if user disabled AI features globally\n const isAIEnabled = strapi.config.get('admin.ai.enabled', true);\n if (!isAIEnabled) {\n return false;\n }\n\n // Check if the user's license grants access to AI features\n const hasAccess = strapi.ee.features.isEnabled('cms-ai');\n if (!hasAccess) {\n return false;\n }\n\n const settings = getService('settings');\n const aiSettings = await settings.getSettings();\n if (!aiSettings?.aiLocalizations) {\n return false;\n }\n\n return true;\n },\n\n /**\n * Checks if there are localizations that need to be generated for the given document,\n * and if so, calls the AI service and saves the results to the database.\n * Works for both single and collection types, on create and update.\n */\n async generateDocumentLocalizations({\n model,\n document,\n }: {\n model: UID.ContentType;\n document: Modules.Documents.AnyDocument;\n }) {\n const isFeatureEnabled = await this.isEnabled();\n if (!isFeatureEnabled) {\n return;\n }\n\n const schema = strapi.getModel(model);\n const localeService = getService('locales');\n\n // No localizations needed for content types with i18n disabled\n const isLocalizedContentType = getService('content-types').isLocalizedContentType(schema);\n if (!isLocalizedContentType) {\n return;\n }\n\n // Don't trigger localizations if the update is on a derived locale, only do it on the default\n const defaultLocale = await localeService.getDefaultLocale();\n if (document?.locale !== defaultLocale) {\n return;\n }\n\n const documentId = document.documentId;\n\n if (!documentId) {\n strapi.log.warn(`AI Localizations: missing documentId for ${schema.uid}`);\n return;\n }\n\n const localizedRoots = new Set();\n\n const translateableContent = await traverseEntity(\n ({ key, attribute, parent, path }, { remove }) => {\n if (IGNORED_FIELDS.includes(key)) {\n remove(key);\n return;\n }\n const hasLocalizedOption = attribute && isLocalizedAttribute(attribute);\n if (attribute && UNSUPPORTED_ATTRIBUTE_TYPES.includes(attribute.type)) {\n remove(key);\n return;\n }\n\n // If this field is localized, keep it (and mark as localized root if component/dz)\n if (hasLocalizedOption) {\n // If it's a component/dynamiczone, add to the set\n if (['component', 'dynamiczone'].includes(attribute.type)) {\n localizedRoots.add(path.raw);\n }\n return; // keep\n }\n\n if (parent && localizedRoots.has(parent.path.raw)) {\n // If parent exists in the localized roots set, keep it\n // If this is also a component/dz, propagate the localized root flag\n if (['component', 'dynamiczone'].includes(attribute?.type ?? '')) {\n localizedRoots.add(path.raw);\n }\n return; // keep\n }\n\n // Otherwise, remove the field\n remove(key);\n },\n { schema, getModel: strapi.getModel.bind(strapi) },\n document\n );\n\n // Call the AI server to get the localized content\n const localesList = await localeService.find();\n const targetLocales = localesList\n .filter((l) => l.code !== document.locale)\n .map((l) => l.code);\n\n if (targetLocales.length === 0) {\n strapi.log.info(\n `AI Localizations: no target locales for ${schema.uid} document ${documentId}`\n );\n return;\n }\n\n await aiLocalizationJobsService.upsertJobForDocument({\n contentType: model,\n documentId,\n sourceLocale: document.locale,\n targetLocales,\n status: 'processing',\n });\n\n let token: string;\n try {\n const tokenData = await strapi.service('admin::user').getAiToken();\n token = tokenData.token;\n } catch (error) {\n await aiLocalizationJobsService.upsertJobForDocument({\n documentId,\n contentType: model,\n sourceLocale: document.locale,\n targetLocales,\n status: 'failed',\n });\n\n throw new Error('Failed to retrieve AI token', {\n cause: error instanceof Error ? error : undefined,\n });\n }\n\n /**\n * Provide a schema to the LLM so that we can give it instructions about how to handle each\n * type of attribute. Only keep essential schema data to avoid cluttering the context.\n * Ignore fields that don't need to be localized.\n * TODO: also provide a schema of all the referenced components\n */\n const minimalContentTypeSchema = Object.fromEntries(\n Object.entries(schema.attributes)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n .filter(([_, attr]) => {\n const isLocalized = isLocalizedAttribute(attr);\n const isSupportedType = !UNSUPPORTED_ATTRIBUTE_TYPES.includes(attr.type);\n return isLocalized && isSupportedType;\n })\n .map(([key, attr]) => {\n const minimalAttribute = { type: attr.type };\n if (attr.type === 'component') {\n (\n minimalAttribute as Schema.Attribute.Component<`${string}.${string}`, boolean>\n ).repeatable = attr.repeatable ?? false;\n }\n return [key, minimalAttribute];\n })\n );\n\n strapi.log.http('Contacting AI Server for localizations generation');\n const response = await fetch(`${aiServerUrl}/i18n/generate-localizations`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n content: translateableContent,\n sourceLocale: document.locale,\n targetLocales,\n contentTypeSchema: minimalContentTypeSchema,\n }),\n });\n\n if (!response.ok) {\n strapi.log.error(\n `AI Localizations request failed: ${response.status} ${response.statusText}`\n );\n\n await aiLocalizationJobsService.upsertJobForDocument({\n documentId,\n contentType: model,\n sourceLocale: document.locale,\n targetLocales,\n status: 'failed',\n });\n\n throw new Error(`AI Localizations request failed: ${response.statusText}`);\n } else {\n await aiLocalizationJobsService.upsertJobForDocument({\n documentId,\n contentType: model,\n sourceLocale: document.locale,\n targetLocales,\n status: 'completed',\n });\n }\n\n const aiResult = await response.json();\n\n // Get all media field names dynamically from the schema\n const mediaFields = Object.entries(schema.attributes)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n .filter(([_, attr]) => attr.type === 'media')\n .map(([key]) => key);\n\n try {\n await Promise.allSettled(\n aiResult.localizations.map(async (localization: any) => {\n const { content, locale } = localization;\n\n // Fetch the derived locale document\n const derivedDoc = await strapi.documents(model).findOne({\n documentId,\n locale,\n populate: '*',\n });\n\n // Merge AI content and media fields, works only on first level media fields (root level)\n const mergedData = { ...content };\n for (const field of mediaFields) {\n // Only copy media if not already set in derived locale\n if (!derivedDoc || !derivedDoc[field]) {\n mergedData[field] = document[field];\n } else {\n mergedData[field] = derivedDoc[field];\n }\n }\n\n return strapi.documents(model).update({\n documentId,\n locale,\n fields: [],\n data: mergedData,\n });\n })\n );\n } catch (error) {\n await aiLocalizationJobsService.upsertJobForDocument({\n documentId,\n contentType: model,\n sourceLocale: document.locale,\n targetLocales,\n status: 'failed',\n });\n strapi.log.error('AI Localizations generation failed', error);\n }\n },\n setupMiddleware() {\n strapi.documents.use(async (context, next) => {\n const result = await next();\n\n // Only trigger on create/update actions\n if (!['create', 'update'].includes(context.action)) {\n return result;\n }\n\n // Check if AI localizations are enabled before triggering\n const isEnabled = await this.isEnabled();\n if (!isEnabled) {\n return result;\n }\n\n // Don't await since localizations should be done in the background without blocking the request\n strapi\n .plugin('i18n')\n .service('ai-localizations')\n .generateDocumentLocalizations({\n model: context.contentType.uid,\n document: result,\n })\n .catch((error: any) => {\n strapi.log.error('AI Localizations generation failed', error);\n });\n\n return result;\n });\n },\n };\n};\n\nexport { createAILocalizationsService };\n"],"names":["isLocalizedAttribute","attribute","pluginOptions","i18n","localized","createAILocalizationsService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","aiLocalizationJobsService","getService","UNSUPPORTED_ATTRIBUTE_TYPES","IGNORED_FIELDS","isEnabled","isFutureFlagEnabled","features","future","isAIEnabled","config","get","hasAccess","ee","settings","aiSettings","getSettings","aiLocalizations","generateDocumentLocalizations","model","document","isFeatureEnabled","schema","getModel","localeService","isLocalizedContentType","defaultLocale","getDefaultLocale","locale","documentId","log","warn","uid","localizedRoots","Set","translateableContent","traverseEntity","key","parent","path","remove","includes","hasLocalizedOption","type","add","raw","has","bind","localesList","find","targetLocales","filter","l","code","map","length","info","upsertJobForDocument","contentType","sourceLocale","status","token","tokenData","service","getAiToken","error","Error","cause","undefined","minimalContentTypeSchema","Object","fromEntries","entries","attributes","_","attr","isLocalized","isSupportedType","minimalAttribute","repeatable","http","response","fetch","method","headers","Authorization","body","JSON","stringify","content","contentTypeSchema","ok","statusText","aiResult","json","mediaFields","Promise","allSettled","localizations","localization","derivedDoc","documents","findOne","populate","mergedData","field","update","fields","data","setupMiddleware","use","context","next","result","action","plugin","catch"],"mappings":"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"}