@strapi/i18n 5.54.0 → 5.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/components/CMHeaderActions.js +5 -5
- package/dist/admin/components/CMHeaderActions.js.map +1 -1
- package/dist/admin/components/CMHeaderActions.mjs +5 -5
- package/dist/admin/components/CMHeaderActions.mjs.map +1 -1
- package/dist/admin/hooks/useAITranslations.js +11 -0
- package/dist/admin/hooks/useAITranslations.js.map +1 -0
- package/dist/admin/hooks/useAITranslations.mjs +9 -0
- package/dist/admin/hooks/useAITranslations.mjs.map +1 -0
- package/dist/admin/pages/SettingsPage.js +2 -2
- package/dist/admin/pages/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage.mjs +2 -2
- package/dist/admin/pages/SettingsPage.mjs.map +1 -1
- package/dist/admin/src/hooks/useAITranslations.d.ts +1 -0
- package/dist/admin/src/services/settings.d.ts +1 -1
- package/dist/server/bootstrap.js +5 -1
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/server/bootstrap.mjs +5 -1
- package/dist/server/bootstrap.mjs.map +1 -1
- package/dist/server/controllers/settings.js +5 -2
- package/dist/server/controllers/settings.js.map +1 -1
- package/dist/server/controllers/settings.mjs +5 -2
- package/dist/server/controllers/settings.mjs.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/services/ai-localizations.js +8 -34
- package/dist/server/services/ai-localizations.js.map +1 -1
- package/dist/server/services/ai-localizations.mjs +8 -34
- package/dist/server/services/ai-localizations.mjs.map +1 -1
- package/dist/server/services/ai-translations-strapi-managed.js +44 -0
- package/dist/server/services/ai-translations-strapi-managed.js.map +1 -0
- package/dist/server/services/ai-translations-strapi-managed.mjs +42 -0
- package/dist/server/services/ai-translations-strapi-managed.mjs.map +1 -0
- package/dist/server/services/ai-translations.js +50 -0
- package/dist/server/services/ai-translations.js.map +1 -0
- package/dist/server/services/ai-translations.mjs +48 -0
- package/dist/server/services/ai-translations.mjs.map +1 -0
- package/dist/server/services/index.js +2 -0
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/index.mjs +2 -0
- package/dist/server/services/index.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts.map +1 -1
- package/dist/server/src/controllers/settings.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +4 -0
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/services/ai-localizations.d.ts.map +1 -1
- package/dist/server/src/services/ai-translations-strapi-managed.d.ts +7 -0
- package/dist/server/src/services/ai-translations-strapi-managed.d.ts.map +1 -0
- package/dist/server/src/services/ai-translations.d.ts +29 -0
- package/dist/server/src/services/ai-translations.d.ts.map +1 -0
- package/dist/server/src/services/index.d.ts +3 -0
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/utils/index.d.ts +2 -0
- package/dist/server/src/utils/index.d.ts.map +1 -1
- package/dist/server/utils/index.js.map +1 -1
- package/dist/server/utils/index.mjs.map +1 -1
- package/dist/shared/contracts/settings.d.ts +6 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.js","sources":["../../server/src/bootstrap.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { isEqual } from 'lodash/fp';\nimport { registerAuditEvents } from './audit-logs';\nimport { getService } from './utils';\n\nconst registerModelsHooks = () => {\n strapi.db.lifecycles.subscribe({\n models: ['plugin::i18n.locale'],\n\n async afterCreate() {\n await getService('permissions').actions.syncSuperAdminPermissionsWithLocales();\n },\n\n async afterDelete() {\n await getService('permissions').actions.syncSuperAdminPermissionsWithLocales();\n },\n });\n\n strapi.documents.use(async (context, next) => {\n const schema: Schema.ContentType = context.contentType;\n\n if (!['create', 'update', 'discardDraft', 'publish'].includes(context.action)) {\n return next();\n }\n\n if (!getService('content-types').isLocalizedContentType(schema)) {\n return next();\n }\n\n // Build a populate array for all non localized fields within the schema\n const { getNestedPopulateOfNonLocalizedAttributes, copyNonLocalizedAttributes } =\n getService('content-types');\n const attributesToPopulate = getNestedPopulateOfNonLocalizedAttributes(schema.uid);\n\n // Get original data before the update to compare what actually changed\n const originalData =\n 'documentId' in context.params && context.params.documentId\n ? await strapi.db.query(schema.uid).findOne({\n where: { documentId: context.params.documentId },\n populate: attributesToPopulate,\n })\n : null;\n\n // Get the result of the document service action\n const result = (await next()) as any;\n\n // We may not have received a result with everything populated that we need\n // Use the id and populate built from non localized fields to get the full\n // result\n let resultID;\n // TODO: fix bug where an empty array can be returned\n if (Array.isArray(result?.entries) && result.entries[0]?.id) {\n resultID = result.entries[0].id;\n } else if (result?.id) {\n resultID = result.id;\n } else {\n return result;\n }\n\n const populatedResult = await strapi.db.query(schema.uid).findOne({\n where: { id: resultID },\n populate: attributesToPopulate,\n });\n\n const originalFields = copyNonLocalizedAttributes(schema, originalData);\n const currentFields = copyNonLocalizedAttributes(schema, populatedResult);\n\n // Only sync if there are actual changes to non-localized fields\n const shouldSync =\n !originalData ||\n Object.keys(currentFields).some((key) => {\n return !isEqual(currentFields[key], originalFields[key]);\n });\n\n if (shouldSync) {\n await getService('localizations').syncNonLocalizedAttributes(populatedResult, schema);\n }\n\n return result;\n });\n};\n\nexport default async () => {\n const { sendDidInitializeEvent } = getService('metrics');\n const { initDefaultLocale } = getService('locales');\n const { sectionsBuilder, actions, engine } = getService('permissions');\n\n // Data\n await initDefaultLocale();\n\n // Sections Builder\n sectionsBuilder.registerLocalesPropertyHandler();\n\n // Actions\n await actions.registerI18nActions();\n actions.registerI18nActionsHooks();\n actions.updateActionsProperties();\n\n // Engine/Permissions\n engine.registerI18nPermissionsHandlers();\n\n // Hooks & Models\n registerModelsHooks();\n\n // Absent in CE, without the audit-logs license, or when disabled by config;\n // get() throws for services that were never added, so probe first.\n if (strapi.has('audit-logs-lifecycle')) {\n registerAuditEvents(strapi.get('audit-logs-lifecycle'));\n }\n\n // AI Localizations\n if (strapi.ai.admin.
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sources":["../../server/src/bootstrap.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { isEqual } from 'lodash/fp';\nimport { registerAuditEvents } from './audit-logs';\nimport { getService } from './utils';\n\nconst registerModelsHooks = () => {\n strapi.db.lifecycles.subscribe({\n models: ['plugin::i18n.locale'],\n\n async afterCreate() {\n await getService('permissions').actions.syncSuperAdminPermissionsWithLocales();\n },\n\n async afterDelete() {\n await getService('permissions').actions.syncSuperAdminPermissionsWithLocales();\n },\n });\n\n strapi.documents.use(async (context, next) => {\n const schema: Schema.ContentType = context.contentType;\n\n if (!['create', 'update', 'discardDraft', 'publish'].includes(context.action)) {\n return next();\n }\n\n if (!getService('content-types').isLocalizedContentType(schema)) {\n return next();\n }\n\n // Build a populate array for all non localized fields within the schema\n const { getNestedPopulateOfNonLocalizedAttributes, copyNonLocalizedAttributes } =\n getService('content-types');\n const attributesToPopulate = getNestedPopulateOfNonLocalizedAttributes(schema.uid);\n\n // Get original data before the update to compare what actually changed\n const originalData =\n 'documentId' in context.params && context.params.documentId\n ? await strapi.db.query(schema.uid).findOne({\n where: { documentId: context.params.documentId },\n populate: attributesToPopulate,\n })\n : null;\n\n // Get the result of the document service action\n const result = (await next()) as any;\n\n // We may not have received a result with everything populated that we need\n // Use the id and populate built from non localized fields to get the full\n // result\n let resultID;\n // TODO: fix bug where an empty array can be returned\n if (Array.isArray(result?.entries) && result.entries[0]?.id) {\n resultID = result.entries[0].id;\n } else if (result?.id) {\n resultID = result.id;\n } else {\n return result;\n }\n\n const populatedResult = await strapi.db.query(schema.uid).findOne({\n where: { id: resultID },\n populate: attributesToPopulate,\n });\n\n const originalFields = copyNonLocalizedAttributes(schema, originalData);\n const currentFields = copyNonLocalizedAttributes(schema, populatedResult);\n\n // Only sync if there are actual changes to non-localized fields\n const shouldSync =\n !originalData ||\n Object.keys(currentFields).some((key) => {\n return !isEqual(currentFields[key], originalFields[key]);\n });\n\n if (shouldSync) {\n await getService('localizations').syncNonLocalizedAttributes(populatedResult, schema);\n }\n\n return result;\n });\n};\n\nexport default async () => {\n const { sendDidInitializeEvent } = getService('metrics');\n const { initDefaultLocale } = getService('locales');\n const { sectionsBuilder, actions, engine } = getService('permissions');\n\n // Data\n await initDefaultLocale();\n\n // Sections Builder\n sectionsBuilder.registerLocalesPropertyHandler();\n\n // Actions\n await actions.registerI18nActions();\n actions.registerI18nActionsHooks();\n actions.updateActionsProperties();\n\n // Engine/Permissions\n engine.registerI18nPermissionsHandlers();\n\n // Hooks & Models\n registerModelsHooks();\n\n // Absent in CE, without the audit-logs license, or when disabled by config;\n // get() throws for services that were never added, so probe first.\n if (strapi.has('audit-logs-lifecycle')) {\n registerAuditEvents(strapi.get('audit-logs-lifecycle'));\n }\n\n // AI Localizations\n if (strapi.ai.admin.isAvailable()) {\n const aiTranslations = getService('ai-translations');\n\n if (!aiTranslations.hasProvider() && strapi.ai.admin.isStrapiManagedAiEnabled()) {\n aiTranslations.registerStrapiManagedProvider();\n }\n\n getService('ai-localizations').setupMiddleware();\n }\n\n sendDidInitializeEvent();\n};\n"],"names":["registerModelsHooks","strapi","db","lifecycles","subscribe","models","afterCreate","getService","actions","syncSuperAdminPermissionsWithLocales","afterDelete","documents","use","context","next","schema","contentType","includes","action","isLocalizedContentType","getNestedPopulateOfNonLocalizedAttributes","copyNonLocalizedAttributes","attributesToPopulate","uid","originalData","params","documentId","query","findOne","where","populate","result","resultID","Array","isArray","entries","id","populatedResult","originalFields","currentFields","shouldSync","Object","keys","some","key","isEqual","syncNonLocalizedAttributes","sendDidInitializeEvent","initDefaultLocale","sectionsBuilder","engine","registerLocalesPropertyHandler","registerI18nActions","registerI18nActionsHooks","updateActionsProperties","registerI18nPermissionsHandlers","has","registerAuditEvents","get","ai","admin","isAvailable","aiTranslations","hasProvider","isStrapiManagedAiEnabled","registerStrapiManagedProvider","setupMiddleware"],"mappings":"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"}
|
|
@@ -91,7 +91,11 @@ var bootstrap = (async ()=>{
|
|
|
91
91
|
registerAuditEvents(strapi.get('audit-logs-lifecycle'));
|
|
92
92
|
}
|
|
93
93
|
// AI Localizations
|
|
94
|
-
if (strapi.ai.admin.
|
|
94
|
+
if (strapi.ai.admin.isAvailable()) {
|
|
95
|
+
const aiTranslations = getService('ai-translations');
|
|
96
|
+
if (!aiTranslations.hasProvider() && strapi.ai.admin.isStrapiManagedAiEnabled()) {
|
|
97
|
+
aiTranslations.registerStrapiManagedProvider();
|
|
98
|
+
}
|
|
95
99
|
getService('ai-localizations').setupMiddleware();
|
|
96
100
|
}
|
|
97
101
|
sendDidInitializeEvent();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.mjs","sources":["../../server/src/bootstrap.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { isEqual } from 'lodash/fp';\nimport { registerAuditEvents } from './audit-logs';\nimport { getService } from './utils';\n\nconst registerModelsHooks = () => {\n strapi.db.lifecycles.subscribe({\n models: ['plugin::i18n.locale'],\n\n async afterCreate() {\n await getService('permissions').actions.syncSuperAdminPermissionsWithLocales();\n },\n\n async afterDelete() {\n await getService('permissions').actions.syncSuperAdminPermissionsWithLocales();\n },\n });\n\n strapi.documents.use(async (context, next) => {\n const schema: Schema.ContentType = context.contentType;\n\n if (!['create', 'update', 'discardDraft', 'publish'].includes(context.action)) {\n return next();\n }\n\n if (!getService('content-types').isLocalizedContentType(schema)) {\n return next();\n }\n\n // Build a populate array for all non localized fields within the schema\n const { getNestedPopulateOfNonLocalizedAttributes, copyNonLocalizedAttributes } =\n getService('content-types');\n const attributesToPopulate = getNestedPopulateOfNonLocalizedAttributes(schema.uid);\n\n // Get original data before the update to compare what actually changed\n const originalData =\n 'documentId' in context.params && context.params.documentId\n ? await strapi.db.query(schema.uid).findOne({\n where: { documentId: context.params.documentId },\n populate: attributesToPopulate,\n })\n : null;\n\n // Get the result of the document service action\n const result = (await next()) as any;\n\n // We may not have received a result with everything populated that we need\n // Use the id and populate built from non localized fields to get the full\n // result\n let resultID;\n // TODO: fix bug where an empty array can be returned\n if (Array.isArray(result?.entries) && result.entries[0]?.id) {\n resultID = result.entries[0].id;\n } else if (result?.id) {\n resultID = result.id;\n } else {\n return result;\n }\n\n const populatedResult = await strapi.db.query(schema.uid).findOne({\n where: { id: resultID },\n populate: attributesToPopulate,\n });\n\n const originalFields = copyNonLocalizedAttributes(schema, originalData);\n const currentFields = copyNonLocalizedAttributes(schema, populatedResult);\n\n // Only sync if there are actual changes to non-localized fields\n const shouldSync =\n !originalData ||\n Object.keys(currentFields).some((key) => {\n return !isEqual(currentFields[key], originalFields[key]);\n });\n\n if (shouldSync) {\n await getService('localizations').syncNonLocalizedAttributes(populatedResult, schema);\n }\n\n return result;\n });\n};\n\nexport default async () => {\n const { sendDidInitializeEvent } = getService('metrics');\n const { initDefaultLocale } = getService('locales');\n const { sectionsBuilder, actions, engine } = getService('permissions');\n\n // Data\n await initDefaultLocale();\n\n // Sections Builder\n sectionsBuilder.registerLocalesPropertyHandler();\n\n // Actions\n await actions.registerI18nActions();\n actions.registerI18nActionsHooks();\n actions.updateActionsProperties();\n\n // Engine/Permissions\n engine.registerI18nPermissionsHandlers();\n\n // Hooks & Models\n registerModelsHooks();\n\n // Absent in CE, without the audit-logs license, or when disabled by config;\n // get() throws for services that were never added, so probe first.\n if (strapi.has('audit-logs-lifecycle')) {\n registerAuditEvents(strapi.get('audit-logs-lifecycle'));\n }\n\n // AI Localizations\n if (strapi.ai.admin.
|
|
1
|
+
{"version":3,"file":"bootstrap.mjs","sources":["../../server/src/bootstrap.ts"],"sourcesContent":["import type { Schema } from '@strapi/types';\nimport { isEqual } from 'lodash/fp';\nimport { registerAuditEvents } from './audit-logs';\nimport { getService } from './utils';\n\nconst registerModelsHooks = () => {\n strapi.db.lifecycles.subscribe({\n models: ['plugin::i18n.locale'],\n\n async afterCreate() {\n await getService('permissions').actions.syncSuperAdminPermissionsWithLocales();\n },\n\n async afterDelete() {\n await getService('permissions').actions.syncSuperAdminPermissionsWithLocales();\n },\n });\n\n strapi.documents.use(async (context, next) => {\n const schema: Schema.ContentType = context.contentType;\n\n if (!['create', 'update', 'discardDraft', 'publish'].includes(context.action)) {\n return next();\n }\n\n if (!getService('content-types').isLocalizedContentType(schema)) {\n return next();\n }\n\n // Build a populate array for all non localized fields within the schema\n const { getNestedPopulateOfNonLocalizedAttributes, copyNonLocalizedAttributes } =\n getService('content-types');\n const attributesToPopulate = getNestedPopulateOfNonLocalizedAttributes(schema.uid);\n\n // Get original data before the update to compare what actually changed\n const originalData =\n 'documentId' in context.params && context.params.documentId\n ? await strapi.db.query(schema.uid).findOne({\n where: { documentId: context.params.documentId },\n populate: attributesToPopulate,\n })\n : null;\n\n // Get the result of the document service action\n const result = (await next()) as any;\n\n // We may not have received a result with everything populated that we need\n // Use the id and populate built from non localized fields to get the full\n // result\n let resultID;\n // TODO: fix bug where an empty array can be returned\n if (Array.isArray(result?.entries) && result.entries[0]?.id) {\n resultID = result.entries[0].id;\n } else if (result?.id) {\n resultID = result.id;\n } else {\n return result;\n }\n\n const populatedResult = await strapi.db.query(schema.uid).findOne({\n where: { id: resultID },\n populate: attributesToPopulate,\n });\n\n const originalFields = copyNonLocalizedAttributes(schema, originalData);\n const currentFields = copyNonLocalizedAttributes(schema, populatedResult);\n\n // Only sync if there are actual changes to non-localized fields\n const shouldSync =\n !originalData ||\n Object.keys(currentFields).some((key) => {\n return !isEqual(currentFields[key], originalFields[key]);\n });\n\n if (shouldSync) {\n await getService('localizations').syncNonLocalizedAttributes(populatedResult, schema);\n }\n\n return result;\n });\n};\n\nexport default async () => {\n const { sendDidInitializeEvent } = getService('metrics');\n const { initDefaultLocale } = getService('locales');\n const { sectionsBuilder, actions, engine } = getService('permissions');\n\n // Data\n await initDefaultLocale();\n\n // Sections Builder\n sectionsBuilder.registerLocalesPropertyHandler();\n\n // Actions\n await actions.registerI18nActions();\n actions.registerI18nActionsHooks();\n actions.updateActionsProperties();\n\n // Engine/Permissions\n engine.registerI18nPermissionsHandlers();\n\n // Hooks & Models\n registerModelsHooks();\n\n // Absent in CE, without the audit-logs license, or when disabled by config;\n // get() throws for services that were never added, so probe first.\n if (strapi.has('audit-logs-lifecycle')) {\n registerAuditEvents(strapi.get('audit-logs-lifecycle'));\n }\n\n // AI Localizations\n if (strapi.ai.admin.isAvailable()) {\n const aiTranslations = getService('ai-translations');\n\n if (!aiTranslations.hasProvider() && strapi.ai.admin.isStrapiManagedAiEnabled()) {\n aiTranslations.registerStrapiManagedProvider();\n }\n\n getService('ai-localizations').setupMiddleware();\n }\n\n sendDidInitializeEvent();\n};\n"],"names":["registerModelsHooks","strapi","db","lifecycles","subscribe","models","afterCreate","getService","actions","syncSuperAdminPermissionsWithLocales","afterDelete","documents","use","context","next","schema","contentType","includes","action","isLocalizedContentType","getNestedPopulateOfNonLocalizedAttributes","copyNonLocalizedAttributes","attributesToPopulate","uid","originalData","params","documentId","query","findOne","where","populate","result","resultID","Array","isArray","entries","id","populatedResult","originalFields","currentFields","shouldSync","Object","keys","some","key","isEqual","syncNonLocalizedAttributes","sendDidInitializeEvent","initDefaultLocale","sectionsBuilder","engine","registerLocalesPropertyHandler","registerI18nActions","registerI18nActionsHooks","updateActionsProperties","registerI18nPermissionsHandlers","has","registerAuditEvents","get","ai","admin","isAvailable","aiTranslations","hasProvider","isStrapiManagedAiEnabled","registerStrapiManagedProvider","setupMiddleware"],"mappings":"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"}
|
|
@@ -13,9 +13,12 @@ var settings = {
|
|
|
13
13
|
};
|
|
14
14
|
},
|
|
15
15
|
async getSettings (ctx) {
|
|
16
|
-
const
|
|
16
|
+
const settings = await index.getService('settings').getSettings();
|
|
17
17
|
ctx.body = {
|
|
18
|
-
data
|
|
18
|
+
data: {
|
|
19
|
+
...settings,
|
|
20
|
+
aiLocalizationsAvailable: index.getService('ai-translations').hasProvider()
|
|
21
|
+
}
|
|
19
22
|
};
|
|
20
23
|
}
|
|
21
24
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sources":["../../../server/src/controllers/settings.ts"],"sourcesContent":["import type { Context } from 'koa';\n\nimport { getService } from '../utils';\nimport validateSettings from '../validation/settings';\n\nexport default {\n async updateSettings(ctx: Context) {\n const {\n request: { body },\n } = ctx;\n\n const data = await validateSettings(body);\n\n await getService('settings').setSettings(data);\n\n ctx.body = { data };\n },\n\n async getSettings(ctx: Context) {\n const
|
|
1
|
+
{"version":3,"file":"settings.js","sources":["../../../server/src/controllers/settings.ts"],"sourcesContent":["import type { Context } from 'koa';\n\nimport { getService } from '../utils';\nimport validateSettings from '../validation/settings';\n\nexport default {\n async updateSettings(ctx: Context) {\n const {\n request: { body },\n } = ctx;\n\n const data = await validateSettings(body);\n\n await getService('settings').setSettings(data);\n\n ctx.body = { data };\n },\n\n async getSettings(ctx: Context) {\n const settings = await getService('settings').getSettings();\n\n ctx.body = {\n data: {\n ...settings,\n aiLocalizationsAvailable: getService('ai-translations').hasProvider(),\n },\n };\n },\n};\n"],"names":["updateSettings","ctx","request","body","data","validateSettings","getService","setSettings","getSettings","settings","aiLocalizationsAvailable","hasProvider"],"mappings":";;;;;AAKA,eAAe;AACb,IAAA,MAAMA,gBAAeC,GAAY,EAAA;AAC/B,QAAA,MAAM,EACJC,OAAAA,EAAS,EAAEC,IAAI,EAAE,EAClB,GAAGF,GAAAA;QAEJ,MAAMG,IAAAA,GAAO,MAAMC,UAAAA,CAAiBF,IAAAA,CAAAA;QAEpC,MAAMG,gBAAAA,CAAW,UAAA,CAAA,CAAYC,WAAW,CAACH,IAAAA,CAAAA;AAEzCH,QAAAA,GAAAA,CAAIE,IAAI,GAAG;AAAEC,YAAAA;AAAK,SAAA;AACpB,IAAA,CAAA;AAEA,IAAA,MAAMI,aAAYP,GAAY,EAAA;AAC5B,QAAA,MAAMQ,QAAAA,GAAW,MAAMH,gBAAAA,CAAW,UAAA,CAAA,CAAYE,WAAW,EAAA;AAEzDP,QAAAA,GAAAA,CAAIE,IAAI,GAAG;YACTC,IAAAA,EAAM;AACJ,gBAAA,GAAGK,QAAQ;gBACXC,wBAAAA,EAA0BJ,gBAAAA,CAAW,mBAAmBK,WAAW;AACrE;AACF,SAAA;AACF,IAAA;AACF,CAAA;;;;"}
|
|
@@ -11,9 +11,12 @@ var settings = {
|
|
|
11
11
|
};
|
|
12
12
|
},
|
|
13
13
|
async getSettings (ctx) {
|
|
14
|
-
const
|
|
14
|
+
const settings = await getService('settings').getSettings();
|
|
15
15
|
ctx.body = {
|
|
16
|
-
data
|
|
16
|
+
data: {
|
|
17
|
+
...settings,
|
|
18
|
+
aiLocalizationsAvailable: getService('ai-translations').hasProvider()
|
|
19
|
+
}
|
|
17
20
|
};
|
|
18
21
|
}
|
|
19
22
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.mjs","sources":["../../../server/src/controllers/settings.ts"],"sourcesContent":["import type { Context } from 'koa';\n\nimport { getService } from '../utils';\nimport validateSettings from '../validation/settings';\n\nexport default {\n async updateSettings(ctx: Context) {\n const {\n request: { body },\n } = ctx;\n\n const data = await validateSettings(body);\n\n await getService('settings').setSettings(data);\n\n ctx.body = { data };\n },\n\n async getSettings(ctx: Context) {\n const
|
|
1
|
+
{"version":3,"file":"settings.mjs","sources":["../../../server/src/controllers/settings.ts"],"sourcesContent":["import type { Context } from 'koa';\n\nimport { getService } from '../utils';\nimport validateSettings from '../validation/settings';\n\nexport default {\n async updateSettings(ctx: Context) {\n const {\n request: { body },\n } = ctx;\n\n const data = await validateSettings(body);\n\n await getService('settings').setSettings(data);\n\n ctx.body = { data };\n },\n\n async getSettings(ctx: Context) {\n const settings = await getService('settings').getSettings();\n\n ctx.body = {\n data: {\n ...settings,\n aiLocalizationsAvailable: getService('ai-translations').hasProvider(),\n },\n };\n },\n};\n"],"names":["updateSettings","ctx","request","body","data","validateSettings","getService","setSettings","getSettings","settings","aiLocalizationsAvailable","hasProvider"],"mappings":";;;AAKA,eAAe;AACb,IAAA,MAAMA,gBAAeC,GAAY,EAAA;AAC/B,QAAA,MAAM,EACJC,OAAAA,EAAS,EAAEC,IAAI,EAAE,EAClB,GAAGF,GAAAA;QAEJ,MAAMG,IAAAA,GAAO,MAAMC,gBAAAA,CAAiBF,IAAAA,CAAAA;QAEpC,MAAMG,UAAAA,CAAW,UAAA,CAAA,CAAYC,WAAW,CAACH,IAAAA,CAAAA;AAEzCH,QAAAA,GAAAA,CAAIE,IAAI,GAAG;AAAEC,YAAAA;AAAK,SAAA;AACpB,IAAA,CAAA;AAEA,IAAA,MAAMI,aAAYP,GAAY,EAAA;AAC5B,QAAA,MAAMQ,QAAAA,GAAW,MAAMH,UAAAA,CAAW,UAAA,CAAA,CAAYE,WAAW,EAAA;AAEzDP,QAAAA,GAAAA,CAAIE,IAAI,GAAG;YACTC,IAAAA,EAAM;AACJ,gBAAA,GAAGK,QAAQ;gBACXC,wBAAAA,EAA0BJ,UAAAA,CAAW,mBAAmBK,WAAW;AACrE;AACF,SAAA;AACF,IAAA;AACF,CAAA;;;;"}
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../server/src/index.ts"],"sourcesContent":["import bootstrap from './bootstrap';\nimport register from './register';\nimport contentTypes from './content-types';\nimport services from './services';\nimport routes from './routes';\nimport controllers from './controllers';\n\nexport default () => ({\n register,\n bootstrap,\n routes,\n controllers,\n contentTypes,\n services,\n});\n"],"names":["register","bootstrap","routes","controllers","contentTypes","services"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../server/src/index.ts"],"sourcesContent":["import bootstrap from './bootstrap';\nimport register from './register';\nimport contentTypes from './content-types';\nimport services from './services';\nimport routes from './routes';\nimport controllers from './controllers';\n\nexport type {\n AiTranslationsProvider,\n AiTranslationsService,\n GenerateTranslationsParams,\n GenerateTranslationsResult,\n} from './services/ai-translations';\n\nexport default () => ({\n register,\n bootstrap,\n routes,\n controllers,\n contentTypes,\n services,\n});\n"],"names":["register","bootstrap","routes","controllers","contentTypes","services"],"mappings":";;;;;;;;;AAcA,YAAe,CAAA,KAAO;AACpBA,QAAAA,QAAAA;AACAC,QAAAA,SAAAA;AACAC,gBAAAA,OAAAA;AACAC,qBAAAA,OAAAA;AACAC,sBAAAA,OAAAA;AACAC,kBAAAA;AACF,KAAA,CAAC;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../server/src/index.ts"],"sourcesContent":["import bootstrap from './bootstrap';\nimport register from './register';\nimport contentTypes from './content-types';\nimport services from './services';\nimport routes from './routes';\nimport controllers from './controllers';\n\nexport default () => ({\n register,\n bootstrap,\n routes,\n controllers,\n contentTypes,\n services,\n});\n"],"names":["register","bootstrap","routes","controllers","contentTypes","services"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../server/src/index.ts"],"sourcesContent":["import bootstrap from './bootstrap';\nimport register from './register';\nimport contentTypes from './content-types';\nimport services from './services';\nimport routes from './routes';\nimport controllers from './controllers';\n\nexport type {\n AiTranslationsProvider,\n AiTranslationsService,\n GenerateTranslationsParams,\n GenerateTranslationsResult,\n} from './services/ai-translations';\n\nexport default () => ({\n register,\n bootstrap,\n routes,\n controllers,\n contentTypes,\n services,\n});\n"],"names":["register","bootstrap","routes","controllers","contentTypes","services"],"mappings":";;;;;;;AAcA,YAAe,CAAA,KAAO;AACpBA,QAAAA,QAAAA;AACAC,QAAAA,SAAAA;AACAC,QAAAA,MAAAA;AACAC,QAAAA,WAAAA;AACAC,QAAAA,YAAAA;AACAC,QAAAA;AACF,KAAA,CAAC;;;;"}
|
|
@@ -105,13 +105,11 @@ const IGNORED_FIELDS = [
|
|
|
105
105
|
return deepMerge(unsupportedFieldsOnly, targetData);
|
|
106
106
|
};
|
|
107
107
|
const createAILocalizationsService = ({ strapi })=>{
|
|
108
|
-
// TODO: add a helper function to get the AI server URL
|
|
109
|
-
const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';
|
|
110
108
|
const aiLocalizationJobsService = index.getService('ai-localization-jobs');
|
|
111
109
|
return {
|
|
112
110
|
// Async to avoid changing the signature later (there will be a db check in the future)
|
|
113
111
|
async isEnabled () {
|
|
114
|
-
if (
|
|
112
|
+
if (index.getService('ai-translations').hasProvider() === false) {
|
|
115
113
|
return false;
|
|
116
114
|
}
|
|
117
115
|
const settings = index.getService('settings');
|
|
@@ -200,22 +198,6 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
200
198
|
targetLocales,
|
|
201
199
|
status: 'processing'
|
|
202
200
|
});
|
|
203
|
-
let token;
|
|
204
|
-
try {
|
|
205
|
-
const tokenData = await strapi.ai.admin.getAiToken();
|
|
206
|
-
token = tokenData.token;
|
|
207
|
-
} catch (error) {
|
|
208
|
-
await aiLocalizationJobsService.upsertJobForDocument({
|
|
209
|
-
documentId,
|
|
210
|
-
contentType: model,
|
|
211
|
-
sourceLocale: document.locale,
|
|
212
|
-
targetLocales,
|
|
213
|
-
status: 'failed'
|
|
214
|
-
});
|
|
215
|
-
throw new Error('Failed to retrieve AI token', {
|
|
216
|
-
cause: error instanceof Error ? error : undefined
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
201
|
/**
|
|
220
202
|
* Provide a schema to the LLM so that we can give it instructions about how to handle each
|
|
221
203
|
* type of attribute. Only keep essential schema data to avoid cluttering the context.
|
|
@@ -245,22 +227,15 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
245
227
|
minimalAttribute
|
|
246
228
|
];
|
|
247
229
|
}));
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
headers: {
|
|
252
|
-
'Content-Type': 'application/json',
|
|
253
|
-
Authorization: `Bearer ${token}`
|
|
254
|
-
},
|
|
255
|
-
body: JSON.stringify({
|
|
256
|
-
content: translateableContent,
|
|
230
|
+
let aiResult;
|
|
231
|
+
try {
|
|
232
|
+
aiResult = await index.getService('ai-translations').generateTranslations({
|
|
257
233
|
sourceLocale: document.locale,
|
|
258
234
|
targetLocales,
|
|
235
|
+
content: translateableContent,
|
|
259
236
|
contentTypeSchema: minimalContentTypeSchema
|
|
260
|
-
})
|
|
261
|
-
})
|
|
262
|
-
if (!response.ok) {
|
|
263
|
-
strapi.log.error(`AI Localizations request failed: ${response.status} ${response.statusText}`);
|
|
237
|
+
});
|
|
238
|
+
} catch (error) {
|
|
264
239
|
await aiLocalizationJobsService.upsertJobForDocument({
|
|
265
240
|
documentId,
|
|
266
241
|
contentType: model,
|
|
@@ -268,9 +243,8 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
268
243
|
targetLocales,
|
|
269
244
|
status: 'failed'
|
|
270
245
|
});
|
|
271
|
-
throw
|
|
246
|
+
throw error;
|
|
272
247
|
}
|
|
273
|
-
const aiResult = await response.json();
|
|
274
248
|
// Use populate-builder service for deep populate to fetch all nested fields
|
|
275
249
|
const populateBuilderService = strapi.plugin('content-manager').service('populate-builder');
|
|
276
250
|
// @ts-expect-error - populate-builder service returns a callable function
|
|
@@ -1 +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 UNSUPPORTED_ATTRIBUTE_TYPES: Schema.Attribute.Kind[] = [\n 'media',\n 'relation',\n 'boolean',\n 'enumeration',\n];\n\nconst IGNORED_FIELDS = [\n 'id',\n 'documentId',\n 'createdAt',\n 'updatedAt',\n 'publishedAt',\n 'locale',\n 'updatedBy',\n 'createdBy',\n 'localizations',\n];\n\n/**\n * Deep merge where target values take priority over source values.\n * Arrays are merged by index to align repeatable component / dynamic zone items.\n */\nconst deepMerge = (\n source: Record<string, any>,\n target: Record<string, any>\n): Record<string, any> => {\n const result = { ...source };\n\n for (const key of Object.keys(target)) {\n const sourceVal = source[key];\n const targetVal = target[key];\n\n if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {\n result[key] = targetVal.map((item, i) => {\n if (item && typeof item === 'object' && sourceVal[i] && typeof sourceVal[i] === 'object') {\n return deepMerge(sourceVal[i], item);\n }\n return item;\n });\n } else if (\n targetVal &&\n typeof targetVal === 'object' &&\n !Array.isArray(targetVal) &&\n sourceVal &&\n typeof sourceVal === 'object' &&\n !Array.isArray(sourceVal)\n ) {\n result[key] = deepMerge(sourceVal, targetVal);\n } else {\n result[key] = targetVal;\n }\n }\n\n return result;\n};\n\n/**\n * Merges unsupported field types (media, boolean, enumeration, relation)\n * from a source document into the target data object.\n *\n * Uses traverseEntity to walk the source document and extract only unsupported fields,\n * then deep-merges the AI-translated target data on top so translated values take priority.\n */\nconst mergeUnsupportedFields = async (\n targetData: Record<string, any>,\n sourceDoc: Record<string, any> | null,\n schema: Schema.Schema,\n getModel: (uid: UID.Schema) => Schema.Schema | undefined\n): Promise<Record<string, any>> => {\n if (!sourceDoc) {\n return targetData;\n }\n\n // Track paths of relation/media fields so traverseEntity's recursion\n // into those fields doesn't strip internal fields like `id` or `url`.\n const preservedPaths = new Set<string>();\n\n // Use traverseEntity to extract only unsupported fields from the source document.\n // traverseEntity handles component and dynamic zone recursion automatically.\n const unsupportedFieldsOnly = await traverseEntity(\n ({ key, attribute, path }, { remove }) => {\n // If we're inside a relation or media subtree, preserve everything.\n // Use path-based prefix check instead of parent-based check because\n // traverseEntity mutates `parent` across siblings at the same level,\n // which would incorrectly mark sibling fields as inside a preserved subtree.\n const isInsidePreservedSubtree =\n path.raw && Array.from(preservedPaths).some((pp) => path.raw!.startsWith(`${pp}.`));\n if (isInsidePreservedSubtree) {\n preservedPaths.add(path.raw!);\n return;\n }\n\n if (IGNORED_FIELDS.includes(key)) {\n remove(key);\n return;\n }\n\n // Keep fields with no schema attribute (e.g. __component in dynamic zones)\n if (!attribute) {\n return;\n }\n\n // Mark relation and media subtrees as preserved so their internal\n // fields (id, url, etc.) are not removed during recursion\n if (attribute.type === 'media' || attribute.type === 'relation') {\n preservedPaths.add(path.raw!);\n return;\n }\n\n // Keep other unsupported attribute types (boolean, enumeration)\n if (UNSUPPORTED_ATTRIBUTE_TYPES.includes(attribute.type)) {\n return;\n }\n\n // Keep components and dynamic zones — traverseEntity recurses into them\n if (attribute.type === 'component' || attribute.type === 'dynamiczone') {\n return;\n }\n\n // Remove supported (translatable) fields\n remove(key);\n },\n { schema, getModel: getModel as (uid: string) => Schema.Schema },\n sourceDoc\n );\n\n // Deep merge: AI-translated target takes priority over source unsupported fields\n return deepMerge(unsupportedFieldsOnly, targetData);\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 return {\n // Async to avoid changing the signature later (there will be a db check in the future)\n async isEnabled() {\n if (strapi.ai.admin.isEnabled() === false) {\n return false;\n }\n const settings = getService('settings');\n const aiSettings = await settings.getSettings();\n return aiSettings?.aiLocalizations === 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 if (Object.keys(translateableContent).length === 0) {\n strapi.log.info(\n `AI Localizations: no translatable content for ${schema.uid} document ${documentId}`\n );\n return;\n }\n\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.ai.admin.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 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: Record<string, unknown> = { 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\n const { maxLength, minLength } = attr as {\n maxLength?: number;\n minLength?: number;\n };\n if (typeof maxLength === 'number') {\n minimalAttribute.maxLength = maxLength;\n }\n if (typeof minLength === 'number') {\n minimalAttribute.minLength = minLength;\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 }\n\n const aiResult = (await response.json()) as {\n localizations: Array<{ content: Record<string, unknown>; locale: string }>;\n };\n\n // Use populate-builder service for deep populate to fetch all nested fields\n const populateBuilderService = strapi.plugin('content-manager').service('populate-builder');\n // @ts-expect-error - populate-builder service returns a callable function\n const deepPopulate = await populateBuilderService(model).populateDeep(Infinity).build();\n const getModelBound = strapi.getModel.bind(strapi);\n\n // Fetch the source document with all fields populated (for new locales that don't exist yet)\n const sourceDocWithAllFields = await strapi.documents(model).findOne({\n documentId,\n locale: document.locale,\n populate: deepPopulate,\n });\n\n const results = await Promise.allSettled(\n aiResult.localizations.map(async (localization) => {\n const { content, locale } = localization;\n\n const derivedDoc = await strapi.documents(model).findOne({\n documentId,\n locale,\n populate: deepPopulate,\n });\n\n let mergedData = structuredClone(content);\n\n const sourceForUnsupportedFields = derivedDoc || sourceDocWithAllFields;\n mergedData = await mergeUnsupportedFields(\n mergedData,\n sourceForUnsupportedFields,\n schema,\n getModelBound\n );\n\n await strapi.documents(model).update({\n documentId,\n locale,\n fields: [],\n data: mergedData,\n });\n })\n );\n\n const failedLocales: string[] = [];\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n const locale = aiResult.localizations[index]?.locale;\n failedLocales.push(locale);\n const reason =\n result.reason instanceof Error ? result.reason.message : String(result.reason);\n strapi.log.error(\n `AI Localizations: failed to save locale \"${locale}\" for ${model} document ${documentId}: ${reason}`\n );\n }\n });\n\n await aiLocalizationJobsService.upsertJobForDocument({\n documentId,\n contentType: model,\n sourceLocale: document.locale,\n targetLocales,\n status: failedLocales.length > 0 ? 'failed' : 'completed',\n });\n },\n setupMiddleware() {\n strapi.documents.use(async (context, next) => {\n const result = await next();\n\n // Only trigger for the allowed 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, mergeUnsupportedFields };\n"],"names":["isLocalizedAttribute","attribute","pluginOptions","i18n","localized","UNSUPPORTED_ATTRIBUTE_TYPES","IGNORED_FIELDS","deepMerge","source","target","result","key","Object","keys","sourceVal","targetVal","Array","isArray","map","item","i","mergeUnsupportedFields","targetData","sourceDoc","schema","getModel","preservedPaths","Set","unsupportedFieldsOnly","traverseEntity","path","remove","isInsidePreservedSubtree","raw","from","some","pp","startsWith","add","includes","type","createAILocalizationsService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","aiLocalizationJobsService","getService","isEnabled","ai","admin","settings","aiSettings","getSettings","aiLocalizations","generateDocumentLocalizations","model","document","isFeatureEnabled","localeService","isLocalizedContentType","defaultLocale","getDefaultLocale","locale","documentId","log","warn","uid","localizedRoots","translateableContent","parent","hasLocalizedOption","has","bind","length","info","localesList","find","targetLocales","filter","l","code","upsertJobForDocument","contentType","sourceLocale","status","token","tokenData","getAiToken","error","Error","cause","undefined","minimalContentTypeSchema","fromEntries","entries","attributes","_","attr","isLocalized","isSupportedType","minimalAttribute","repeatable","maxLength","minLength","http","response","fetch","method","headers","Authorization","body","JSON","stringify","content","contentTypeSchema","ok","statusText","aiResult","json","populateBuilderService","plugin","service","deepPopulate","populateDeep","Infinity","build","getModelBound","sourceDocWithAllFields","documents","findOne","populate","results","Promise","allSettled","localizations","localization","derivedDoc","mergedData","structuredClone","sourceForUnsupportedFields","update","fields","data","failedLocales","forEach","index","push","reason","message","String","setupMiddleware","use","context","next","action","catch"],"mappings":"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"}
|
|
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';\nimport type { GenerateTranslationsResult } from './ai-translations';\n\nconst isLocalizedAttribute = (attribute: Schema.Attribute.Attribute | undefined): boolean => {\n return (attribute?.pluginOptions as any)?.i18n?.localized === true;\n};\n\nconst UNSUPPORTED_ATTRIBUTE_TYPES: Schema.Attribute.Kind[] = [\n 'media',\n 'relation',\n 'boolean',\n 'enumeration',\n];\n\nconst IGNORED_FIELDS = [\n 'id',\n 'documentId',\n 'createdAt',\n 'updatedAt',\n 'publishedAt',\n 'locale',\n 'updatedBy',\n 'createdBy',\n 'localizations',\n];\n\n/**\n * Deep merge where target values take priority over source values.\n * Arrays are merged by index to align repeatable component / dynamic zone items.\n */\nconst deepMerge = (\n source: Record<string, any>,\n target: Record<string, any>\n): Record<string, any> => {\n const result = { ...source };\n\n for (const key of Object.keys(target)) {\n const sourceVal = source[key];\n const targetVal = target[key];\n\n if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {\n result[key] = targetVal.map((item, i) => {\n if (item && typeof item === 'object' && sourceVal[i] && typeof sourceVal[i] === 'object') {\n return deepMerge(sourceVal[i], item);\n }\n return item;\n });\n } else if (\n targetVal &&\n typeof targetVal === 'object' &&\n !Array.isArray(targetVal) &&\n sourceVal &&\n typeof sourceVal === 'object' &&\n !Array.isArray(sourceVal)\n ) {\n result[key] = deepMerge(sourceVal, targetVal);\n } else {\n result[key] = targetVal;\n }\n }\n\n return result;\n};\n\n/**\n * Merges unsupported field types (media, boolean, enumeration, relation)\n * from a source document into the target data object.\n *\n * Uses traverseEntity to walk the source document and extract only unsupported fields,\n * then deep-merges the AI-translated target data on top so translated values take priority.\n */\nconst mergeUnsupportedFields = async (\n targetData: Record<string, any>,\n sourceDoc: Record<string, any> | null,\n schema: Schema.Schema,\n getModel: (uid: UID.Schema) => Schema.Schema | undefined\n): Promise<Record<string, any>> => {\n if (!sourceDoc) {\n return targetData;\n }\n\n // Track paths of relation/media fields so traverseEntity's recursion\n // into those fields doesn't strip internal fields like `id` or `url`.\n const preservedPaths = new Set<string>();\n\n // Use traverseEntity to extract only unsupported fields from the source document.\n // traverseEntity handles component and dynamic zone recursion automatically.\n const unsupportedFieldsOnly = await traverseEntity(\n ({ key, attribute, path }, { remove }) => {\n // If we're inside a relation or media subtree, preserve everything.\n // Use path-based prefix check instead of parent-based check because\n // traverseEntity mutates `parent` across siblings at the same level,\n // which would incorrectly mark sibling fields as inside a preserved subtree.\n const isInsidePreservedSubtree =\n path.raw && Array.from(preservedPaths).some((pp) => path.raw!.startsWith(`${pp}.`));\n if (isInsidePreservedSubtree) {\n preservedPaths.add(path.raw!);\n return;\n }\n\n if (IGNORED_FIELDS.includes(key)) {\n remove(key);\n return;\n }\n\n // Keep fields with no schema attribute (e.g. __component in dynamic zones)\n if (!attribute) {\n return;\n }\n\n // Mark relation and media subtrees as preserved so their internal\n // fields (id, url, etc.) are not removed during recursion\n if (attribute.type === 'media' || attribute.type === 'relation') {\n preservedPaths.add(path.raw!);\n return;\n }\n\n // Keep other unsupported attribute types (boolean, enumeration)\n if (UNSUPPORTED_ATTRIBUTE_TYPES.includes(attribute.type)) {\n return;\n }\n\n // Keep components and dynamic zones — traverseEntity recurses into them\n if (attribute.type === 'component' || attribute.type === 'dynamiczone') {\n return;\n }\n\n // Remove supported (translatable) fields\n remove(key);\n },\n { schema, getModel: getModel as (uid: string) => Schema.Schema },\n sourceDoc\n );\n\n // Deep merge: AI-translated target takes priority over source unsupported fields\n return deepMerge(unsupportedFieldsOnly, targetData);\n};\n\nconst createAILocalizationsService = ({ strapi }: { strapi: Core.Strapi }) => {\n const aiLocalizationJobsService = getService('ai-localization-jobs');\n\n return {\n // Async to avoid changing the signature later (there will be a db check in the future)\n async isEnabled() {\n if (getService('ai-translations').hasProvider() === false) {\n return false;\n }\n const settings = getService('settings');\n const aiSettings = await settings.getSettings();\n return aiSettings?.aiLocalizations === 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 if (Object.keys(translateableContent).length === 0) {\n strapi.log.info(\n `AI Localizations: no translatable content for ${schema.uid} document ${documentId}`\n );\n return;\n }\n\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 /**\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: Record<string, unknown> = { 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\n const { maxLength, minLength } = attr as {\n maxLength?: number;\n minLength?: number;\n };\n if (typeof maxLength === 'number') {\n minimalAttribute.maxLength = maxLength;\n }\n if (typeof minLength === 'number') {\n minimalAttribute.minLength = minLength;\n }\n return [key, minimalAttribute];\n })\n );\n\n let aiResult: GenerateTranslationsResult;\n try {\n aiResult = await getService('ai-translations').generateTranslations({\n sourceLocale: document.locale,\n targetLocales,\n content: translateableContent,\n contentTypeSchema: minimalContentTypeSchema,\n });\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 error;\n }\n\n // Use populate-builder service for deep populate to fetch all nested fields\n const populateBuilderService = strapi.plugin('content-manager').service('populate-builder');\n // @ts-expect-error - populate-builder service returns a callable function\n const deepPopulate = await populateBuilderService(model).populateDeep(Infinity).build();\n const getModelBound = strapi.getModel.bind(strapi);\n\n // Fetch the source document with all fields populated (for new locales that don't exist yet)\n const sourceDocWithAllFields = await strapi.documents(model).findOne({\n documentId,\n locale: document.locale,\n populate: deepPopulate,\n });\n\n const results = await Promise.allSettled(\n aiResult.localizations.map(async (localization) => {\n const { content, locale } = localization;\n\n const derivedDoc = await strapi.documents(model).findOne({\n documentId,\n locale,\n populate: deepPopulate,\n });\n\n let mergedData = structuredClone(content);\n\n const sourceForUnsupportedFields = derivedDoc || sourceDocWithAllFields;\n mergedData = await mergeUnsupportedFields(\n mergedData,\n sourceForUnsupportedFields,\n schema,\n getModelBound\n );\n\n await strapi.documents(model).update({\n documentId,\n locale,\n fields: [],\n data: mergedData,\n });\n })\n );\n\n const failedLocales: string[] = [];\n results.forEach((result, index) => {\n if (result.status === 'rejected') {\n const locale = aiResult.localizations[index]?.locale;\n failedLocales.push(locale);\n const reason =\n result.reason instanceof Error ? result.reason.message : String(result.reason);\n strapi.log.error(\n `AI Localizations: failed to save locale \"${locale}\" for ${model} document ${documentId}: ${reason}`\n );\n }\n });\n\n await aiLocalizationJobsService.upsertJobForDocument({\n documentId,\n contentType: model,\n sourceLocale: document.locale,\n targetLocales,\n status: failedLocales.length > 0 ? 'failed' : 'completed',\n });\n },\n setupMiddleware() {\n strapi.documents.use(async (context, next) => {\n const result = await next();\n\n // Only trigger for the allowed 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, mergeUnsupportedFields };\n"],"names":["isLocalizedAttribute","attribute","pluginOptions","i18n","localized","UNSUPPORTED_ATTRIBUTE_TYPES","IGNORED_FIELDS","deepMerge","source","target","result","key","Object","keys","sourceVal","targetVal","Array","isArray","map","item","i","mergeUnsupportedFields","targetData","sourceDoc","schema","getModel","preservedPaths","Set","unsupportedFieldsOnly","traverseEntity","path","remove","isInsidePreservedSubtree","raw","from","some","pp","startsWith","add","includes","type","createAILocalizationsService","strapi","aiLocalizationJobsService","getService","isEnabled","hasProvider","settings","aiSettings","getSettings","aiLocalizations","generateDocumentLocalizations","model","document","isFeatureEnabled","localeService","isLocalizedContentType","defaultLocale","getDefaultLocale","locale","documentId","log","warn","uid","localizedRoots","translateableContent","parent","hasLocalizedOption","has","bind","length","info","localesList","find","targetLocales","filter","l","code","upsertJobForDocument","contentType","sourceLocale","status","minimalContentTypeSchema","fromEntries","entries","attributes","_","attr","isLocalized","isSupportedType","minimalAttribute","repeatable","maxLength","minLength","aiResult","generateTranslations","content","contentTypeSchema","error","populateBuilderService","plugin","service","deepPopulate","populateDeep","Infinity","build","getModelBound","sourceDocWithAllFields","documents","findOne","populate","results","Promise","allSettled","localizations","localization","derivedDoc","mergedData","structuredClone","sourceForUnsupportedFields","update","fields","data","failedLocales","forEach","index","push","reason","Error","message","String","setupMiddleware","use","context","next","action","catch"],"mappings":"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"}
|
|
@@ -103,13 +103,11 @@ const IGNORED_FIELDS = [
|
|
|
103
103
|
return deepMerge(unsupportedFieldsOnly, targetData);
|
|
104
104
|
};
|
|
105
105
|
const createAILocalizationsService = ({ strapi })=>{
|
|
106
|
-
// TODO: add a helper function to get the AI server URL
|
|
107
|
-
const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';
|
|
108
106
|
const aiLocalizationJobsService = getService('ai-localization-jobs');
|
|
109
107
|
return {
|
|
110
108
|
// Async to avoid changing the signature later (there will be a db check in the future)
|
|
111
109
|
async isEnabled () {
|
|
112
|
-
if (
|
|
110
|
+
if (getService('ai-translations').hasProvider() === false) {
|
|
113
111
|
return false;
|
|
114
112
|
}
|
|
115
113
|
const settings = getService('settings');
|
|
@@ -198,22 +196,6 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
198
196
|
targetLocales,
|
|
199
197
|
status: 'processing'
|
|
200
198
|
});
|
|
201
|
-
let token;
|
|
202
|
-
try {
|
|
203
|
-
const tokenData = await strapi.ai.admin.getAiToken();
|
|
204
|
-
token = tokenData.token;
|
|
205
|
-
} catch (error) {
|
|
206
|
-
await aiLocalizationJobsService.upsertJobForDocument({
|
|
207
|
-
documentId,
|
|
208
|
-
contentType: model,
|
|
209
|
-
sourceLocale: document.locale,
|
|
210
|
-
targetLocales,
|
|
211
|
-
status: 'failed'
|
|
212
|
-
});
|
|
213
|
-
throw new Error('Failed to retrieve AI token', {
|
|
214
|
-
cause: error instanceof Error ? error : undefined
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
199
|
/**
|
|
218
200
|
* Provide a schema to the LLM so that we can give it instructions about how to handle each
|
|
219
201
|
* type of attribute. Only keep essential schema data to avoid cluttering the context.
|
|
@@ -243,22 +225,15 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
243
225
|
minimalAttribute
|
|
244
226
|
];
|
|
245
227
|
}));
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
headers: {
|
|
250
|
-
'Content-Type': 'application/json',
|
|
251
|
-
Authorization: `Bearer ${token}`
|
|
252
|
-
},
|
|
253
|
-
body: JSON.stringify({
|
|
254
|
-
content: translateableContent,
|
|
228
|
+
let aiResult;
|
|
229
|
+
try {
|
|
230
|
+
aiResult = await getService('ai-translations').generateTranslations({
|
|
255
231
|
sourceLocale: document.locale,
|
|
256
232
|
targetLocales,
|
|
233
|
+
content: translateableContent,
|
|
257
234
|
contentTypeSchema: minimalContentTypeSchema
|
|
258
|
-
})
|
|
259
|
-
})
|
|
260
|
-
if (!response.ok) {
|
|
261
|
-
strapi.log.error(`AI Localizations request failed: ${response.status} ${response.statusText}`);
|
|
235
|
+
});
|
|
236
|
+
} catch (error) {
|
|
262
237
|
await aiLocalizationJobsService.upsertJobForDocument({
|
|
263
238
|
documentId,
|
|
264
239
|
contentType: model,
|
|
@@ -266,9 +241,8 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
266
241
|
targetLocales,
|
|
267
242
|
status: 'failed'
|
|
268
243
|
});
|
|
269
|
-
throw
|
|
244
|
+
throw error;
|
|
270
245
|
}
|
|
271
|
-
const aiResult = await response.json();
|
|
272
246
|
// Use populate-builder service for deep populate to fetch all nested fields
|
|
273
247
|
const populateBuilderService = strapi.plugin('content-manager').service('populate-builder');
|
|
274
248
|
// @ts-expect-error - populate-builder service returns a callable function
|