@strapi/i18n 0.0.0-experimental.e347d09bda40b82b260af06636f17c00f9286ac2 → 0.0.0-experimental.e4103be75de4e986d3c7eacedc971623d1dd2d63

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 (176) hide show
  1. package/dist/admin/components/CMHeaderActions.js +132 -1
  2. package/dist/admin/components/CMHeaderActions.js.map +1 -1
  3. package/dist/admin/components/CMHeaderActions.mjs +135 -5
  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/index.js +1 -0
  34. package/dist/admin/index.js.map +1 -1
  35. package/dist/admin/index.mjs +2 -1
  36. package/dist/admin/index.mjs.map +1 -1
  37. package/dist/admin/pages/SettingsPage.js +121 -46
  38. package/dist/admin/pages/SettingsPage.js.map +1 -1
  39. package/dist/admin/pages/SettingsPage.mjs +124 -30
  40. package/dist/admin/pages/SettingsPage.mjs.map +1 -1
  41. package/dist/admin/services/aiLocalizationJobs.js +26 -0
  42. package/dist/admin/services/aiLocalizationJobs.js.map +1 -0
  43. package/dist/admin/services/aiLocalizationJobs.mjs +24 -0
  44. package/dist/admin/services/aiLocalizationJobs.mjs.map +1 -0
  45. package/dist/admin/services/api.js +4 -1
  46. package/dist/admin/services/api.js.map +1 -1
  47. package/dist/admin/services/api.mjs +4 -1
  48. package/dist/admin/services/api.mjs.map +1 -1
  49. package/dist/admin/services/locales.js +4 -2
  50. package/dist/admin/services/locales.js.map +1 -1
  51. package/dist/admin/services/locales.mjs +4 -2
  52. package/dist/admin/services/locales.mjs.map +1 -1
  53. package/dist/admin/services/settings.js +29 -0
  54. package/dist/admin/services/settings.js.map +1 -0
  55. package/dist/admin/services/settings.mjs +26 -0
  56. package/dist/admin/services/settings.mjs.map +1 -0
  57. package/dist/admin/src/components/CMHeaderActions.d.ts +7 -1
  58. package/dist/admin/src/components/LocaleListCell.d.ts +2 -1
  59. package/dist/admin/src/hooks/useAILocalizationJobsPolling.d.ts +9 -0
  60. package/dist/admin/src/services/aiLocalizationJobs.d.ts +6 -0
  61. package/dist/admin/src/services/api.d.ts +1 -1
  62. package/dist/admin/src/services/locales.d.ts +1 -1
  63. package/dist/admin/src/services/relations.d.ts +1 -1
  64. package/dist/admin/src/services/settings.d.ts +5 -0
  65. package/dist/admin/translations/en.json.js +9 -0
  66. package/dist/admin/translations/en.json.js.map +1 -1
  67. package/dist/admin/translations/en.json.mjs +9 -0
  68. package/dist/admin/translations/en.json.mjs.map +1 -1
  69. package/dist/admin/utils/clean.js +2 -2
  70. package/dist/admin/utils/clean.js.map +1 -1
  71. package/dist/admin/utils/clean.mjs +2 -2
  72. package/dist/admin/utils/clean.mjs.map +1 -1
  73. package/dist/server/bootstrap.js +2 -0
  74. package/dist/server/bootstrap.js.map +1 -1
  75. package/dist/server/bootstrap.mjs +2 -0
  76. package/dist/server/bootstrap.mjs.map +1 -1
  77. package/dist/server/constants/iso-locales.json.js +4 -0
  78. package/dist/server/constants/iso-locales.json.js.map +1 -1
  79. package/dist/server/constants/iso-locales.json.mjs +4 -0
  80. package/dist/server/constants/iso-locales.json.mjs.map +1 -1
  81. package/dist/server/controllers/ai-localization-jobs.js +47 -0
  82. package/dist/server/controllers/ai-localization-jobs.js.map +1 -0
  83. package/dist/server/controllers/ai-localization-jobs.mjs +45 -0
  84. package/dist/server/controllers/ai-localization-jobs.mjs.map +1 -0
  85. package/dist/server/controllers/index.js +5 -1
  86. package/dist/server/controllers/index.js.map +1 -1
  87. package/dist/server/controllers/index.mjs +5 -1
  88. package/dist/server/controllers/index.mjs.map +1 -1
  89. package/dist/server/controllers/settings.js +24 -0
  90. package/dist/server/controllers/settings.js.map +1 -0
  91. package/dist/server/controllers/settings.mjs +22 -0
  92. package/dist/server/controllers/settings.mjs.map +1 -0
  93. package/dist/server/models/ai-localization-job.js +60 -0
  94. package/dist/server/models/ai-localization-job.js.map +1 -0
  95. package/dist/server/models/ai-localization-job.mjs +57 -0
  96. package/dist/server/models/ai-localization-job.mjs.map +1 -0
  97. package/dist/server/register.js +3 -1
  98. package/dist/server/register.js.map +1 -1
  99. package/dist/server/register.mjs +3 -1
  100. package/dist/server/register.mjs.map +1 -1
  101. package/dist/server/routes/admin.js +40 -0
  102. package/dist/server/routes/admin.js.map +1 -1
  103. package/dist/server/routes/admin.mjs +40 -0
  104. package/dist/server/routes/admin.mjs.map +1 -1
  105. package/dist/server/routes/content-api.js +11 -7
  106. package/dist/server/routes/content-api.js.map +1 -1
  107. package/dist/server/routes/content-api.mjs +11 -7
  108. package/dist/server/routes/content-api.mjs.map +1 -1
  109. package/dist/server/routes/index.mjs +2 -2
  110. package/dist/server/routes/validation/locale.js +57 -0
  111. package/dist/server/routes/validation/locale.js.map +1 -0
  112. package/dist/server/routes/validation/locale.mjs +36 -0
  113. package/dist/server/routes/validation/locale.mjs.map +1 -0
  114. package/dist/server/services/ai-localization-jobs.js +64 -0
  115. package/dist/server/services/ai-localization-jobs.js.map +1 -0
  116. package/dist/server/services/ai-localization-jobs.mjs +62 -0
  117. package/dist/server/services/ai-localization-jobs.mjs.map +1 -0
  118. package/dist/server/services/ai-localizations.js +222 -0
  119. package/dist/server/services/ai-localizations.js.map +1 -0
  120. package/dist/server/services/ai-localizations.mjs +220 -0
  121. package/dist/server/services/ai-localizations.mjs.map +1 -0
  122. package/dist/server/services/index.js +7 -1
  123. package/dist/server/services/index.js.map +1 -1
  124. package/dist/server/services/index.mjs +7 -1
  125. package/dist/server/services/index.mjs.map +1 -1
  126. package/dist/server/services/settings.js +25 -0
  127. package/dist/server/services/settings.js.map +1 -0
  128. package/dist/server/services/settings.mjs +23 -0
  129. package/dist/server/services/settings.mjs.map +1 -0
  130. package/dist/server/src/bootstrap.d.ts.map +1 -1
  131. package/dist/server/src/controllers/ai-localization-jobs.d.ts +17 -0
  132. package/dist/server/src/controllers/ai-localization-jobs.d.ts.map +1 -0
  133. package/dist/server/src/controllers/index.d.ts +10 -0
  134. package/dist/server/src/controllers/index.d.ts.map +1 -1
  135. package/dist/server/src/controllers/settings.d.ts +7 -0
  136. package/dist/server/src/controllers/settings.d.ts.map +1 -0
  137. package/dist/server/src/index.d.ts +47 -8
  138. package/dist/server/src/index.d.ts.map +1 -1
  139. package/dist/server/src/models/ai-localization-job.d.ts +5 -0
  140. package/dist/server/src/models/ai-localization-job.d.ts.map +1 -0
  141. package/dist/server/src/models/index.d.ts +5 -0
  142. package/dist/server/src/models/index.d.ts.map +1 -0
  143. package/dist/server/src/register.d.ts +1 -1
  144. package/dist/server/src/register.d.ts.map +1 -1
  145. package/dist/server/src/routes/admin.d.ts.map +1 -1
  146. package/dist/server/src/routes/content-api.d.ts +5 -8
  147. package/dist/server/src/routes/content-api.d.ts.map +1 -1
  148. package/dist/server/src/routes/index.d.ts +3 -7
  149. package/dist/server/src/routes/index.d.ts.map +1 -1
  150. package/dist/server/src/routes/validation/index.d.ts +2 -0
  151. package/dist/server/src/routes/validation/index.d.ts.map +1 -0
  152. package/dist/server/src/routes/validation/locale.d.ts +41 -0
  153. package/dist/server/src/routes/validation/locale.d.ts.map +1 -0
  154. package/dist/server/src/services/ai-localization-jobs.d.ts +26 -0
  155. package/dist/server/src/services/ai-localization-jobs.d.ts.map +1 -0
  156. package/dist/server/src/services/ai-localizations.d.ts +18 -0
  157. package/dist/server/src/services/ai-localizations.d.ts.map +1 -0
  158. package/dist/server/src/services/index.d.ts +33 -0
  159. package/dist/server/src/services/index.d.ts.map +1 -1
  160. package/dist/server/src/services/settings.d.ts +13 -0
  161. package/dist/server/src/services/settings.d.ts.map +1 -0
  162. package/dist/server/src/utils/index.d.ts +7 -1
  163. package/dist/server/src/utils/index.d.ts.map +1 -1
  164. package/dist/server/src/validation/settings.d.ts +12 -0
  165. package/dist/server/src/validation/settings.d.ts.map +1 -0
  166. package/dist/server/utils/index.js.map +1 -1
  167. package/dist/server/utils/index.mjs.map +1 -1
  168. package/dist/server/validation/settings.js +11 -0
  169. package/dist/server/validation/settings.js.map +1 -0
  170. package/dist/server/validation/settings.mjs +9 -0
  171. package/dist/server/validation/settings.mjs.map +1 -0
  172. package/dist/shared/contracts/ai-localization-jobs.d.ts +27 -0
  173. package/dist/shared/contracts/ai-localization-jobs.d.ts.map +1 -0
  174. package/dist/shared/contracts/settings.d.ts +40 -0
  175. package/dist/shared/contracts/shared.d.ts.map +1 -0
  176. package/package.json +12 -9
@@ -0,0 +1,222 @@
1
+ 'use strict';
2
+
3
+ var utils = require('@strapi/utils');
4
+ var index = require('../utils/index.js');
5
+
6
+ const createAILocalizationsService = ({ strapi })=>{
7
+ // TODO: add a helper function to get the AI server URL
8
+ const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';
9
+ const aiLocalizationJobsService = index.getService('ai-localization-jobs');
10
+ const UNSUPPORTED_ATTRIBUTE_TYPES = [
11
+ 'media',
12
+ 'relation',
13
+ // TODO: remove these once the AI server can handle them reliably
14
+ 'component',
15
+ 'dynamiczone',
16
+ 'json'
17
+ ];
18
+ return {
19
+ // Async to avoid changing the signature later (there will be a db check in the future)
20
+ async isEnabled () {
21
+ // Check if future flag is enabled
22
+ const isFutureFlagEnabled = strapi.features.future.isEnabled('unstableAILocalizations');
23
+ if (!isFutureFlagEnabled) {
24
+ return false;
25
+ }
26
+ // Check if user disabled AI features globally
27
+ const isAIEnabled = strapi.config.get('admin.ai.enabled', true);
28
+ if (!isAIEnabled) {
29
+ return false;
30
+ }
31
+ // Check if the user's license grants access to AI features
32
+ const hasAccess = strapi.ee.features.isEnabled('cms-ai');
33
+ if (!hasAccess) {
34
+ return false;
35
+ }
36
+ const settings = index.getService('settings');
37
+ const aiSettings = await settings.getSettings();
38
+ if (!aiSettings?.aiLocalizations) {
39
+ return false;
40
+ }
41
+ return true;
42
+ },
43
+ /**
44
+ * Checks if there are localizations that need to be generated for the given document,
45
+ * and if so, calls the AI service and saves the results to the database.
46
+ * Works for both single and collection types, on create and update.
47
+ */ async generateDocumentLocalizations ({ model, document }) {
48
+ const isFeatureEnabled = await this.isEnabled();
49
+ if (!isFeatureEnabled) {
50
+ return;
51
+ }
52
+ const schema = strapi.getModel(model);
53
+ const localeService = index.getService('locales');
54
+ // No localizations needed for content types with i18n disabled
55
+ const isLocalizedContentType = index.getService('content-types').isLocalizedContentType(schema);
56
+ if (!isLocalizedContentType) {
57
+ return;
58
+ }
59
+ // Don't trigger localizations if the update is on a derived locale, only do it on the default
60
+ const defaultLocale = await localeService.getDefaultLocale();
61
+ if (document?.locale !== defaultLocale) {
62
+ return;
63
+ }
64
+ const documentId = document.documentId;
65
+ if (!documentId) {
66
+ strapi.log.warn(`AI Localizations: missing documentId for ${schema.uid}`);
67
+ return;
68
+ }
69
+ // Extract only the localized content from the document
70
+ const translateableContent = await utils.traverseEntity(({ key, attribute }, { remove })=>{
71
+ const hasLocalizedOption = attribute?.pluginOptions?.i18n?.localized === true;
72
+ // Only keep fields that actually need to be localized
73
+ // TODO: remove blocks from this list once the AI server can handle it reliably
74
+ if (!hasLocalizedOption || UNSUPPORTED_ATTRIBUTE_TYPES.includes(attribute.type)) {
75
+ remove(key);
76
+ }
77
+ }, {
78
+ schema,
79
+ getModel: strapi.getModel.bind(strapi)
80
+ }, document);
81
+ // Call the AI server to get the localized content
82
+ const localesList = await localeService.find();
83
+ const targetLocales = localesList.filter((l)=>l.code !== document.locale).map((l)=>l.code);
84
+ if (targetLocales.length === 0) {
85
+ strapi.log.info(`AI Localizations: no target locales for ${schema.uid} document ${documentId}`);
86
+ return;
87
+ }
88
+ await aiLocalizationJobsService.upsertJobForDocument({
89
+ contentType: model,
90
+ documentId,
91
+ sourceLocale: document.locale,
92
+ targetLocales,
93
+ status: 'processing'
94
+ });
95
+ let token;
96
+ try {
97
+ const tokenData = await strapi.service('admin::user').getAiToken();
98
+ token = tokenData.token;
99
+ } catch (error) {
100
+ await aiLocalizationJobsService.upsertJobForDocument({
101
+ documentId,
102
+ contentType: model,
103
+ sourceLocale: document.locale,
104
+ targetLocales,
105
+ status: 'failed'
106
+ });
107
+ throw new Error('Failed to retrieve AI token', {
108
+ cause: error instanceof Error ? error : undefined
109
+ });
110
+ }
111
+ strapi.log.http('Contacting AI Server for localizations generation');
112
+ const response = await fetch(`${aiServerUrl}/i18n/generate-localizations`, {
113
+ method: 'POST',
114
+ headers: {
115
+ 'Content-Type': 'application/json',
116
+ Authorization: `Bearer ${token}`
117
+ },
118
+ body: JSON.stringify({
119
+ content: translateableContent,
120
+ sourceLocale: document.locale,
121
+ targetLocales,
122
+ schema: Object.fromEntries(Object.entries(schema.attributes)// eslint-disable-next-line @typescript-eslint/no-unused-vars
123
+ .filter(([_, attr])=>attr?.pluginOptions?.i18n?.localized === true).map(([key, attr])=>[
124
+ key,
125
+ {
126
+ type: attr.type
127
+ }
128
+ ]))
129
+ })
130
+ });
131
+ if (!response.ok) {
132
+ strapi.log.error(`AI Localizations request failed: ${response.status} ${response.statusText}`);
133
+ await aiLocalizationJobsService.upsertJobForDocument({
134
+ documentId,
135
+ contentType: model,
136
+ sourceLocale: document.locale,
137
+ targetLocales,
138
+ status: 'failed'
139
+ });
140
+ throw new Error(`AI Localizations request failed: ${response.statusText}`);
141
+ } else {
142
+ await aiLocalizationJobsService.upsertJobForDocument({
143
+ documentId,
144
+ contentType: model,
145
+ sourceLocale: document.locale,
146
+ targetLocales,
147
+ status: 'completed'
148
+ });
149
+ }
150
+ const aiResult = await response.json();
151
+ // Get all media field names dynamically from the schema
152
+ const mediaFields = Object.entries(schema.attributes)// eslint-disable-next-line @typescript-eslint/no-unused-vars
153
+ .filter(([_, attr])=>attr.type === 'media').map(([key])=>key);
154
+ try {
155
+ await Promise.allSettled(aiResult.localizations.map(async (localization)=>{
156
+ const { content, locale } = localization;
157
+ // Fetch the derived locale document
158
+ const derivedDoc = await strapi.documents(model).findOne({
159
+ documentId,
160
+ locale,
161
+ populate: mediaFields
162
+ });
163
+ // Merge AI content and media fields
164
+ const mergedData = {
165
+ ...content
166
+ };
167
+ for (const field of mediaFields){
168
+ // Only copy media if not already set in derived locale
169
+ if (!derivedDoc || !derivedDoc[field]) {
170
+ mergedData[field] = document[field];
171
+ } else {
172
+ mergedData[field] = derivedDoc[field];
173
+ }
174
+ }
175
+ return strapi.documents(model).update({
176
+ documentId,
177
+ locale,
178
+ fields: [],
179
+ data: mergedData
180
+ });
181
+ }));
182
+ } catch (error) {
183
+ await aiLocalizationJobsService.upsertJobForDocument({
184
+ documentId,
185
+ contentType: model,
186
+ sourceLocale: document.locale,
187
+ targetLocales,
188
+ status: 'failed'
189
+ });
190
+ strapi.log.error('AI Localizations generation failed', error);
191
+ }
192
+ },
193
+ setupMiddleware () {
194
+ strapi.documents.use(async (context, next)=>{
195
+ const result = await next();
196
+ // Only trigger on create/update actions
197
+ if (![
198
+ 'create',
199
+ 'update'
200
+ ].includes(context.action)) {
201
+ return result;
202
+ }
203
+ // Check if AI localizations are enabled before triggering
204
+ const isEnabled = await this.isEnabled();
205
+ if (!isEnabled) {
206
+ return result;
207
+ }
208
+ // Don't await since localizations should be done in the background without blocking the request
209
+ strapi.plugin('i18n').service('ai-localizations').generateDocumentLocalizations({
210
+ model: context.contentType.uid,
211
+ document: result
212
+ }).catch((error)=>{
213
+ strapi.log.error('AI Localizations generation failed', error);
214
+ });
215
+ return result;
216
+ });
217
+ }
218
+ };
219
+ };
220
+
221
+ exports.createAILocalizationsService = createAILocalizationsService;
222
+ //# 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 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[] = [\n 'media',\n 'relation',\n // TODO: remove these once the AI server can handle them reliably\n 'component',\n 'dynamiczone',\n 'json',\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 // Extract only the localized content from the document\n const translateableContent = await traverseEntity(\n ({ key, attribute }, { remove }) => {\n const hasLocalizedOption = attribute?.pluginOptions?.i18n?.localized === true;\n // Only keep fields that actually need to be localized\n // TODO: remove blocks from this list once the AI server can handle it reliably\n if (!hasLocalizedOption || UNSUPPORTED_ATTRIBUTE_TYPES.includes(attribute.type)) {\n remove(key);\n }\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 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 schema: Object.fromEntries(\n Object.entries(schema.attributes)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n .filter(([_, attr]) => (attr?.pluginOptions as any)?.i18n?.localized === true)\n .map(([key, attr]) => [key, { type: attr.type }])\n ),\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: mediaFields,\n });\n\n // Merge AI content and media fields\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":["createAILocalizationsService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","aiLocalizationJobsService","getService","UNSUPPORTED_ATTRIBUTE_TYPES","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","translateableContent","traverseEntity","key","attribute","remove","hasLocalizedOption","pluginOptions","i18n","localized","includes","type","bind","localesList","find","targetLocales","filter","l","code","map","length","info","upsertJobForDocument","contentType","sourceLocale","status","token","tokenData","service","getAiToken","error","Error","cause","undefined","http","response","fetch","method","headers","Authorization","body","JSON","stringify","content","Object","fromEntries","entries","attributes","_","attr","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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"}
@@ -0,0 +1,220 @@
1
+ import { traverseEntity } from '@strapi/utils';
2
+ import { getService } from '../utils/index.mjs';
3
+
4
+ const createAILocalizationsService = ({ strapi })=>{
5
+ // TODO: add a helper function to get the AI server URL
6
+ const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';
7
+ const aiLocalizationJobsService = getService('ai-localization-jobs');
8
+ const UNSUPPORTED_ATTRIBUTE_TYPES = [
9
+ 'media',
10
+ 'relation',
11
+ // TODO: remove these once the AI server can handle them reliably
12
+ 'component',
13
+ 'dynamiczone',
14
+ 'json'
15
+ ];
16
+ return {
17
+ // Async to avoid changing the signature later (there will be a db check in the future)
18
+ async isEnabled () {
19
+ // Check if future flag is enabled
20
+ const isFutureFlagEnabled = strapi.features.future.isEnabled('unstableAILocalizations');
21
+ if (!isFutureFlagEnabled) {
22
+ return false;
23
+ }
24
+ // Check if user disabled AI features globally
25
+ const isAIEnabled = strapi.config.get('admin.ai.enabled', true);
26
+ if (!isAIEnabled) {
27
+ return false;
28
+ }
29
+ // Check if the user's license grants access to AI features
30
+ const hasAccess = strapi.ee.features.isEnabled('cms-ai');
31
+ if (!hasAccess) {
32
+ return false;
33
+ }
34
+ const settings = getService('settings');
35
+ const aiSettings = await settings.getSettings();
36
+ if (!aiSettings?.aiLocalizations) {
37
+ return false;
38
+ }
39
+ return true;
40
+ },
41
+ /**
42
+ * Checks if there are localizations that need to be generated for the given document,
43
+ * and if so, calls the AI service and saves the results to the database.
44
+ * Works for both single and collection types, on create and update.
45
+ */ async generateDocumentLocalizations ({ model, document }) {
46
+ const isFeatureEnabled = await this.isEnabled();
47
+ if (!isFeatureEnabled) {
48
+ return;
49
+ }
50
+ const schema = strapi.getModel(model);
51
+ const localeService = getService('locales');
52
+ // No localizations needed for content types with i18n disabled
53
+ const isLocalizedContentType = getService('content-types').isLocalizedContentType(schema);
54
+ if (!isLocalizedContentType) {
55
+ return;
56
+ }
57
+ // Don't trigger localizations if the update is on a derived locale, only do it on the default
58
+ const defaultLocale = await localeService.getDefaultLocale();
59
+ if (document?.locale !== defaultLocale) {
60
+ return;
61
+ }
62
+ const documentId = document.documentId;
63
+ if (!documentId) {
64
+ strapi.log.warn(`AI Localizations: missing documentId for ${schema.uid}`);
65
+ return;
66
+ }
67
+ // Extract only the localized content from the document
68
+ const translateableContent = await traverseEntity(({ key, attribute }, { remove })=>{
69
+ const hasLocalizedOption = attribute?.pluginOptions?.i18n?.localized === true;
70
+ // Only keep fields that actually need to be localized
71
+ // TODO: remove blocks from this list once the AI server can handle it reliably
72
+ if (!hasLocalizedOption || UNSUPPORTED_ATTRIBUTE_TYPES.includes(attribute.type)) {
73
+ remove(key);
74
+ }
75
+ }, {
76
+ schema,
77
+ getModel: strapi.getModel.bind(strapi)
78
+ }, document);
79
+ // Call the AI server to get the localized content
80
+ const localesList = await localeService.find();
81
+ const targetLocales = localesList.filter((l)=>l.code !== document.locale).map((l)=>l.code);
82
+ if (targetLocales.length === 0) {
83
+ strapi.log.info(`AI Localizations: no target locales for ${schema.uid} document ${documentId}`);
84
+ return;
85
+ }
86
+ await aiLocalizationJobsService.upsertJobForDocument({
87
+ contentType: model,
88
+ documentId,
89
+ sourceLocale: document.locale,
90
+ targetLocales,
91
+ status: 'processing'
92
+ });
93
+ let token;
94
+ try {
95
+ const tokenData = await strapi.service('admin::user').getAiToken();
96
+ token = tokenData.token;
97
+ } catch (error) {
98
+ await aiLocalizationJobsService.upsertJobForDocument({
99
+ documentId,
100
+ contentType: model,
101
+ sourceLocale: document.locale,
102
+ targetLocales,
103
+ status: 'failed'
104
+ });
105
+ throw new Error('Failed to retrieve AI token', {
106
+ cause: error instanceof Error ? error : undefined
107
+ });
108
+ }
109
+ strapi.log.http('Contacting AI Server for localizations generation');
110
+ const response = await fetch(`${aiServerUrl}/i18n/generate-localizations`, {
111
+ method: 'POST',
112
+ headers: {
113
+ 'Content-Type': 'application/json',
114
+ Authorization: `Bearer ${token}`
115
+ },
116
+ body: JSON.stringify({
117
+ content: translateableContent,
118
+ sourceLocale: document.locale,
119
+ targetLocales,
120
+ schema: Object.fromEntries(Object.entries(schema.attributes)// eslint-disable-next-line @typescript-eslint/no-unused-vars
121
+ .filter(([_, attr])=>attr?.pluginOptions?.i18n?.localized === true).map(([key, attr])=>[
122
+ key,
123
+ {
124
+ type: attr.type
125
+ }
126
+ ]))
127
+ })
128
+ });
129
+ if (!response.ok) {
130
+ strapi.log.error(`AI Localizations request failed: ${response.status} ${response.statusText}`);
131
+ await aiLocalizationJobsService.upsertJobForDocument({
132
+ documentId,
133
+ contentType: model,
134
+ sourceLocale: document.locale,
135
+ targetLocales,
136
+ status: 'failed'
137
+ });
138
+ throw new Error(`AI Localizations request failed: ${response.statusText}`);
139
+ } else {
140
+ await aiLocalizationJobsService.upsertJobForDocument({
141
+ documentId,
142
+ contentType: model,
143
+ sourceLocale: document.locale,
144
+ targetLocales,
145
+ status: 'completed'
146
+ });
147
+ }
148
+ const aiResult = await response.json();
149
+ // Get all media field names dynamically from the schema
150
+ const mediaFields = Object.entries(schema.attributes)// eslint-disable-next-line @typescript-eslint/no-unused-vars
151
+ .filter(([_, attr])=>attr.type === 'media').map(([key])=>key);
152
+ try {
153
+ await Promise.allSettled(aiResult.localizations.map(async (localization)=>{
154
+ const { content, locale } = localization;
155
+ // Fetch the derived locale document
156
+ const derivedDoc = await strapi.documents(model).findOne({
157
+ documentId,
158
+ locale,
159
+ populate: mediaFields
160
+ });
161
+ // Merge AI content and media fields
162
+ const mergedData = {
163
+ ...content
164
+ };
165
+ for (const field of mediaFields){
166
+ // Only copy media if not already set in derived locale
167
+ if (!derivedDoc || !derivedDoc[field]) {
168
+ mergedData[field] = document[field];
169
+ } else {
170
+ mergedData[field] = derivedDoc[field];
171
+ }
172
+ }
173
+ return strapi.documents(model).update({
174
+ documentId,
175
+ locale,
176
+ fields: [],
177
+ data: mergedData
178
+ });
179
+ }));
180
+ } catch (error) {
181
+ await aiLocalizationJobsService.upsertJobForDocument({
182
+ documentId,
183
+ contentType: model,
184
+ sourceLocale: document.locale,
185
+ targetLocales,
186
+ status: 'failed'
187
+ });
188
+ strapi.log.error('AI Localizations generation failed', error);
189
+ }
190
+ },
191
+ setupMiddleware () {
192
+ strapi.documents.use(async (context, next)=>{
193
+ const result = await next();
194
+ // Only trigger on create/update actions
195
+ if (![
196
+ 'create',
197
+ 'update'
198
+ ].includes(context.action)) {
199
+ return result;
200
+ }
201
+ // Check if AI localizations are enabled before triggering
202
+ const isEnabled = await this.isEnabled();
203
+ if (!isEnabled) {
204
+ return result;
205
+ }
206
+ // Don't await since localizations should be done in the background without blocking the request
207
+ strapi.plugin('i18n').service('ai-localizations').generateDocumentLocalizations({
208
+ model: context.contentType.uid,
209
+ document: result
210
+ }).catch((error)=>{
211
+ strapi.log.error('AI Localizations generation failed', error);
212
+ });
213
+ return result;
214
+ });
215
+ }
216
+ };
217
+ };
218
+
219
+ export { createAILocalizationsService };
220
+ //# sourceMappingURL=ai-localizations.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-localizations.mjs","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 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[] = [\n 'media',\n 'relation',\n // TODO: remove these once the AI server can handle them reliably\n 'component',\n 'dynamiczone',\n 'json',\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 // Extract only the localized content from the document\n const translateableContent = await traverseEntity(\n ({ key, attribute }, { remove }) => {\n const hasLocalizedOption = attribute?.pluginOptions?.i18n?.localized === true;\n // Only keep fields that actually need to be localized\n // TODO: remove blocks from this list once the AI server can handle it reliably\n if (!hasLocalizedOption || UNSUPPORTED_ATTRIBUTE_TYPES.includes(attribute.type)) {\n remove(key);\n }\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 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 schema: Object.fromEntries(\n Object.entries(schema.attributes)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n .filter(([_, attr]) => (attr?.pluginOptions as any)?.i18n?.localized === true)\n .map(([key, attr]) => [key, { type: attr.type }])\n ),\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: mediaFields,\n });\n\n // Merge AI content and media fields\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":["createAILocalizationsService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","aiLocalizationJobsService","getService","UNSUPPORTED_ATTRIBUTE_TYPES","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","translateableContent","traverseEntity","key","attribute","remove","hasLocalizedOption","pluginOptions","i18n","localized","includes","type","bind","localesList","find","targetLocales","filter","l","code","map","length","info","upsertJobForDocument","contentType","sourceLocale","status","token","tokenData","service","getAiToken","error","Error","cause","undefined","http","response","fetch","method","headers","Authorization","body","JSON","stringify","content","Object","fromEntries","entries","attributes","_","attr","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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"}
@@ -7,6 +7,9 @@ var locales = require('./locales.js');
7
7
  var isoLocales = require('./iso-locales.js');
8
8
  var contentTypes = require('./content-types.js');
9
9
  var index = require('./sanitize/index.js');
10
+ var settings = require('./settings.js');
11
+ var aiLocalizations = require('./ai-localizations.js');
12
+ var aiLocalizationJobs = require('./ai-localization-jobs.js');
10
13
 
11
14
  var services = {
12
15
  permissions,
@@ -15,7 +18,10 @@ var services = {
15
18
  locales,
16
19
  sanitize: index,
17
20
  'iso-locales': isoLocales,
18
- 'content-types': contentTypes
21
+ 'content-types': contentTypes,
22
+ 'ai-localizations': aiLocalizations.createAILocalizationsService,
23
+ 'ai-localization-jobs': aiLocalizationJobs.createAILocalizationJobsService,
24
+ settings: settings.createSettingsService
19
25
  };
20
26
 
21
27
  module.exports = services;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../server/src/services/index.ts"],"sourcesContent":["import permissions from './permissions';\nimport metrics from './metrics';\nimport localizations from './localizations';\nimport locales from './locales';\nimport isoLocales from './iso-locales';\nimport contentTypes from './content-types';\nimport sanitize from './sanitize';\n\nexport default {\n permissions,\n metrics,\n localizations,\n locales,\n sanitize,\n 'iso-locales': isoLocales,\n 'content-types': contentTypes,\n};\n"],"names":["permissions","metrics","localizations","locales","sanitize","isoLocales","contentTypes"],"mappings":";;;;;;;;;;AAQA,eAAe;AACbA,IAAAA,WAAAA;AACAC,IAAAA,OAAAA;AACAC,IAAAA,aAAAA;AACAC,IAAAA,OAAAA;AACAC,cAAAA,KAAAA;IACA,aAAeC,EAAAA,UAAAA;IACf,eAAiBC,EAAAA;AACnB,CAAE;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../server/src/services/index.ts"],"sourcesContent":["import permissions from './permissions';\nimport metrics from './metrics';\nimport localizations from './localizations';\nimport locales from './locales';\nimport isoLocales from './iso-locales';\nimport contentTypes from './content-types';\nimport sanitize from './sanitize';\nimport { createSettingsService } from './settings';\nimport { createAILocalizationsService } from './ai-localizations';\nimport { createAILocalizationJobsService } from './ai-localization-jobs';\n\nexport default {\n permissions,\n metrics,\n localizations,\n locales,\n sanitize,\n 'iso-locales': isoLocales,\n 'content-types': contentTypes,\n 'ai-localizations': createAILocalizationsService,\n 'ai-localization-jobs': createAILocalizationJobsService,\n settings: createSettingsService,\n};\n"],"names":["permissions","metrics","localizations","locales","sanitize","isoLocales","contentTypes","createAILocalizationsService","createAILocalizationJobsService","settings","createSettingsService"],"mappings":";;;;;;;;;;;;;AAWA,eAAe;AACbA,IAAAA,WAAAA;AACAC,IAAAA,OAAAA;AACAC,IAAAA,aAAAA;AACAC,IAAAA,OAAAA;AACAC,cAAAA,KAAAA;IACA,aAAeC,EAAAA,UAAAA;IACf,eAAiBC,EAAAA,YAAAA;IACjB,kBAAoBC,EAAAA,4CAAAA;IACpB,sBAAwBC,EAAAA,kDAAAA;IACxBC,QAAUC,EAAAA;AACZ,CAAE;;;;"}
@@ -5,6 +5,9 @@ import locales from './locales.mjs';
5
5
  import isoLocalesService from './iso-locales.mjs';
6
6
  import contentTypes from './content-types.mjs';
7
7
  import sanitize from './sanitize/index.mjs';
8
+ import { createSettingsService } from './settings.mjs';
9
+ import { createAILocalizationsService } from './ai-localizations.mjs';
10
+ import { createAILocalizationJobsService } from './ai-localization-jobs.mjs';
8
11
 
9
12
  var services = {
10
13
  permissions,
@@ -13,7 +16,10 @@ var services = {
13
16
  locales,
14
17
  sanitize,
15
18
  'iso-locales': isoLocalesService,
16
- 'content-types': contentTypes
19
+ 'content-types': contentTypes,
20
+ 'ai-localizations': createAILocalizationsService,
21
+ 'ai-localization-jobs': createAILocalizationJobsService,
22
+ settings: createSettingsService
17
23
  };
18
24
 
19
25
  export { services as default };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../../server/src/services/index.ts"],"sourcesContent":["import permissions from './permissions';\nimport metrics from './metrics';\nimport localizations from './localizations';\nimport locales from './locales';\nimport isoLocales from './iso-locales';\nimport contentTypes from './content-types';\nimport sanitize from './sanitize';\n\nexport default {\n permissions,\n metrics,\n localizations,\n locales,\n sanitize,\n 'iso-locales': isoLocales,\n 'content-types': contentTypes,\n};\n"],"names":["permissions","metrics","localizations","locales","sanitize","isoLocales","contentTypes"],"mappings":";;;;;;;;AAQA,eAAe;AACbA,IAAAA,WAAAA;AACAC,IAAAA,OAAAA;AACAC,IAAAA,aAAAA;AACAC,IAAAA,OAAAA;AACAC,IAAAA,QAAAA;IACA,aAAeC,EAAAA,iBAAAA;IACf,eAAiBC,EAAAA;AACnB,CAAE;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":["../../../server/src/services/index.ts"],"sourcesContent":["import permissions from './permissions';\nimport metrics from './metrics';\nimport localizations from './localizations';\nimport locales from './locales';\nimport isoLocales from './iso-locales';\nimport contentTypes from './content-types';\nimport sanitize from './sanitize';\nimport { createSettingsService } from './settings';\nimport { createAILocalizationsService } from './ai-localizations';\nimport { createAILocalizationJobsService } from './ai-localization-jobs';\n\nexport default {\n permissions,\n metrics,\n localizations,\n locales,\n sanitize,\n 'iso-locales': isoLocales,\n 'content-types': contentTypes,\n 'ai-localizations': createAILocalizationsService,\n 'ai-localization-jobs': createAILocalizationJobsService,\n settings: createSettingsService,\n};\n"],"names":["permissions","metrics","localizations","locales","sanitize","isoLocales","contentTypes","createAILocalizationsService","createAILocalizationJobsService","settings","createSettingsService"],"mappings":";;;;;;;;;;;AAWA,eAAe;AACbA,IAAAA,WAAAA;AACAC,IAAAA,OAAAA;AACAC,IAAAA,aAAAA;AACAC,IAAAA,OAAAA;AACAC,IAAAA,QAAAA;IACA,aAAeC,EAAAA,iBAAAA;IACf,eAAiBC,EAAAA,YAAAA;IACjB,kBAAoBC,EAAAA,4BAAAA;IACpB,sBAAwBC,EAAAA,+BAAAA;IACxBC,QAAUC,EAAAA;AACZ,CAAE;;;;"}
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const createSettingsService = ({ strapi })=>{
4
+ const settings = strapi.store({
5
+ type: 'plugin',
6
+ name: 'i18n',
7
+ key: 'settings'
8
+ });
9
+ async function getSettings() {
10
+ const res = await settings.get({});
11
+ return res;
12
+ }
13
+ function setSettings(value) {
14
+ return settings.set({
15
+ value
16
+ });
17
+ }
18
+ return {
19
+ getSettings,
20
+ setSettings
21
+ };
22
+ };
23
+
24
+ exports.createSettingsService = createSettingsService;
25
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sources":["../../../server/src/services/settings.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport type { Settings } from '../validation/settings';\n\nconst createSettingsService = ({ strapi }: { strapi: Core.Strapi }) => {\n const settings = strapi.store!({ type: 'plugin', name: 'i18n', key: 'settings' });\n\n async function getSettings() {\n const res = (await settings.get({})) as Settings | null;\n\n return res;\n }\n\n function setSettings(value: Settings) {\n return settings.set({ value });\n }\n\n return {\n getSettings,\n setSettings,\n };\n};\n\nexport { createSettingsService };\nexport type SettingsService = ReturnType<typeof createSettingsService>;\n"],"names":["createSettingsService","strapi","settings","store","type","name","key","getSettings","res","get","setSettings","value","set"],"mappings":";;AAGA,MAAMA,qBAAwB,GAAA,CAAC,EAAEC,MAAM,EAA2B,GAAA;IAChE,MAAMC,QAAAA,GAAWD,MAAOE,CAAAA,KAAK,CAAE;QAAEC,IAAM,EAAA,QAAA;QAAUC,IAAM,EAAA,MAAA;QAAQC,GAAK,EAAA;AAAW,KAAA,CAAA;IAE/E,eAAeC,WAAAA,GAAAA;AACb,QAAA,MAAMC,GAAO,GAAA,MAAMN,QAASO,CAAAA,GAAG,CAAC,EAAC,CAAA;QAEjC,OAAOD,GAAAA;AACT;AAEA,IAAA,SAASE,YAAYC,KAAe,EAAA;QAClC,OAAOT,QAAAA,CAASU,GAAG,CAAC;AAAED,YAAAA;AAAM,SAAA,CAAA;AAC9B;IAEA,OAAO;AACLJ,QAAAA,WAAAA;AACAG,QAAAA;AACF,KAAA;AACF;;;;"}
@@ -0,0 +1,23 @@
1
+ const createSettingsService = ({ strapi })=>{
2
+ const settings = strapi.store({
3
+ type: 'plugin',
4
+ name: 'i18n',
5
+ key: 'settings'
6
+ });
7
+ async function getSettings() {
8
+ const res = await settings.get({});
9
+ return res;
10
+ }
11
+ function setSettings(value) {
12
+ return settings.set({
13
+ value
14
+ });
15
+ }
16
+ return {
17
+ getSettings,
18
+ setSettings
19
+ };
20
+ };
21
+
22
+ export { createSettingsService };
23
+ //# sourceMappingURL=settings.mjs.map