@strapi/i18n 5.58.0 → 5.59.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/BulkLocaleActionModal.js +4 -3
- package/dist/admin/components/BulkLocaleActionModal.js.map +1 -1
- package/dist/admin/components/BulkLocaleActionModal.mjs +4 -3
- package/dist/admin/components/BulkLocaleActionModal.mjs.map +1 -1
- package/dist/admin/components/CMHeaderActions.js +1 -1
- package/dist/admin/components/CMHeaderActions.js.map +1 -1
- package/dist/admin/components/CMHeaderActions.mjs +1 -1
- package/dist/admin/components/CMHeaderActions.mjs.map +1 -1
- package/dist/admin/components/LocaleTable.js +2 -1
- package/dist/admin/components/LocaleTable.js.map +1 -1
- package/dist/admin/components/LocaleTable.mjs +2 -1
- package/dist/admin/components/LocaleTable.mjs.map +1 -1
- package/dist/admin/middlewares/rbac-middleware.js.map +1 -1
- package/dist/admin/middlewares/rbac-middleware.mjs.map +1 -1
- package/dist/admin/translations/da.json.js +2 -2
- package/dist/admin/translations/da.json.mjs +2 -2
- package/dist/admin/translations/de.json.js +2 -2
- package/dist/admin/translations/de.json.mjs +2 -2
- package/dist/admin/translations/es.json.js +2 -2
- package/dist/admin/translations/es.json.mjs +2 -2
- package/dist/admin/translations/fr.json.js +2 -2
- package/dist/admin/translations/fr.json.mjs +2 -2
- package/dist/admin/translations/ko.json.js +2 -2
- package/dist/admin/translations/ko.json.mjs +2 -2
- package/dist/admin/translations/pl.json.js +2 -2
- package/dist/admin/translations/pl.json.mjs +2 -2
- package/dist/admin/translations/ru.json.js +1 -1
- package/dist/admin/translations/ru.json.mjs +1 -1
- package/dist/admin/translations/tr.json.js +2 -2
- package/dist/admin/translations/tr.json.mjs +2 -2
- package/dist/admin/translations/zh-Hans.json.js +2 -2
- package/dist/admin/translations/zh-Hans.json.mjs +2 -2
- package/dist/admin/translations/zh.json.js +2 -2
- package/dist/admin/translations/zh.json.mjs +2 -2
- package/dist/server/services/ai-localizations.js +9 -9
- package/dist/server/services/ai-localizations.js.map +1 -1
- package/dist/server/services/ai-localizations.mjs +9 -9
- package/dist/server/services/ai-localizations.mjs.map +1 -1
- package/dist/server/services/sanitize/index.js +3 -3
- package/dist/server/services/sanitize/index.js.map +1 -1
- package/dist/server/services/sanitize/index.mjs +3 -3
- package/dist/server/services/sanitize/index.mjs.map +1 -1
- package/package.json +7 -7
|
@@ -10,9 +10,9 @@ var fr = {
|
|
|
10
10
|
"Field.localized": "Cette valeur est unique pour la locale sélectionnée",
|
|
11
11
|
"Field.not-localized": "Cette valeur est commune à toutes les locales",
|
|
12
12
|
"Settings.list.actions.add": "Ajouter une locale",
|
|
13
|
-
"Settings.list.actions.delete": "Supprimer
|
|
13
|
+
"Settings.list.actions.delete": "Supprimer la locale {name}",
|
|
14
14
|
"Settings.list.actions.deleteAdditionalInfos": "Cela supprimera les versions locales actives <em>(d'Internationalisation)</em>.",
|
|
15
|
-
"Settings.list.actions.edit": "Modifier
|
|
15
|
+
"Settings.list.actions.edit": "Modifier la locale {name}",
|
|
16
16
|
"Settings.list.description": "Configurer les paramètres du plugin d'internationalisation",
|
|
17
17
|
"Settings.list.empty.description": "Ce n'est pas un comportement habituel, ce qui signifie que vous avez peut-être modifié la base de données manuellement. Assurez-vous d'avoir au moins une locale enregistrée dans votre base de données afin de pouvoir utiliser Strapi correctement.",
|
|
18
18
|
"Settings.list.empty.title": "Il n'y a pas de locale",
|
|
@@ -12,9 +12,9 @@ var ko = {
|
|
|
12
12
|
"Field.localized": "이 값은 선택한 로케일에서 고유합니다.",
|
|
13
13
|
"Field.not-localized": "이 값은 모든 로케일에서 공통입니다.",
|
|
14
14
|
"Settings.list.actions.add": "로케일 추가",
|
|
15
|
-
"Settings.list.actions.delete": "로케일 삭제",
|
|
15
|
+
"Settings.list.actions.delete": "{name} 로케일 삭제",
|
|
16
16
|
"Settings.list.actions.deleteAdditionalInfos": "This will delete the active locale versions <em>(from Internationalization)</em>",
|
|
17
|
-
"Settings.list.actions.edit": "로케일 수정",
|
|
17
|
+
"Settings.list.actions.edit": "{name} 로케일 수정",
|
|
18
18
|
"Settings.list.description": "국제화 플러그인에 대한 설정을 구성합니다.",
|
|
19
19
|
"Settings.list.empty.description": "이건 일반적인 동작이 아니며, 데이터베이스를 수동으로 수정한 걸로 파악됩니다. Strapi를 올바르게 사용하려면 데이터베이스에 로케일이 하나 이상 저장되어 있어야 합니다.",
|
|
20
20
|
"Settings.list.empty.title": "로케일이 없습니다.",
|
|
@@ -10,9 +10,9 @@ var ko = {
|
|
|
10
10
|
"Field.localized": "이 값은 선택한 로케일에서 고유합니다.",
|
|
11
11
|
"Field.not-localized": "이 값은 모든 로케일에서 공통입니다.",
|
|
12
12
|
"Settings.list.actions.add": "로케일 추가",
|
|
13
|
-
"Settings.list.actions.delete": "로케일 삭제",
|
|
13
|
+
"Settings.list.actions.delete": "{name} 로케일 삭제",
|
|
14
14
|
"Settings.list.actions.deleteAdditionalInfos": "This will delete the active locale versions <em>(from Internationalization)</em>",
|
|
15
|
-
"Settings.list.actions.edit": "로케일 수정",
|
|
15
|
+
"Settings.list.actions.edit": "{name} 로케일 수정",
|
|
16
16
|
"Settings.list.description": "국제화 플러그인에 대한 설정을 구성합니다.",
|
|
17
17
|
"Settings.list.empty.description": "이건 일반적인 동작이 아니며, 데이터베이스를 수동으로 수정한 걸로 파악됩니다. Strapi를 올바르게 사용하려면 데이터베이스에 로케일이 하나 이상 저장되어 있어야 합니다.",
|
|
18
18
|
"Settings.list.empty.title": "로케일이 없습니다.",
|
|
@@ -36,11 +36,11 @@ var pl = {
|
|
|
36
36
|
"Field.localized": "Ta wartość jest unikalna dla wybranych języków",
|
|
37
37
|
"Field.not-localized": "Ta wartość jest wspólna dla wszystkich języków",
|
|
38
38
|
"Settings.list.actions.add": "Dodaj język",
|
|
39
|
-
"Settings.list.actions.delete": "Usuń",
|
|
39
|
+
"Settings.list.actions.delete": "Usuń język {name}",
|
|
40
40
|
"Settings.list.actions.deleteAdditionalInfos": "Aktywne wersje językowe zostaną usunięte <em>(z pluginu Internacjonalizacja)</em>",
|
|
41
41
|
"Settings.list.actions.publishAdditionalInfos": "To opublikuje aktywne wersje językowe <em>(z pluginu Internacjonalizacja)</em>",
|
|
42
42
|
"Settings.list.actions.unpublishAdditionalInfos": "To cofnie publikację aktywnych wersji językowych <em>(z pluginu Internacjonalizacja)</em>",
|
|
43
|
-
"Settings.list.actions.edit": "Edytuj",
|
|
43
|
+
"Settings.list.actions.edit": "Edytuj język {name}",
|
|
44
44
|
"Settings.list.description": "Skonfiguruj ustawienia dla pluginu Internacjonalizacja",
|
|
45
45
|
"Settings.list.empty.description": "To nie jest standardowe zachowanie, co oznacza że ostatecznie zmieniłeś bazę danych manualnie. Aby poprawnie używać strapi upewnij się, że w bazie masz zapisane przynajmniej jedno ustawienie regionalne.",
|
|
46
46
|
"Settings.list.empty.title": "Brak języków.",
|
|
@@ -34,11 +34,11 @@ var pl = {
|
|
|
34
34
|
"Field.localized": "Ta wartość jest unikalna dla wybranych języków",
|
|
35
35
|
"Field.not-localized": "Ta wartość jest wspólna dla wszystkich języków",
|
|
36
36
|
"Settings.list.actions.add": "Dodaj język",
|
|
37
|
-
"Settings.list.actions.delete": "Usuń",
|
|
37
|
+
"Settings.list.actions.delete": "Usuń język {name}",
|
|
38
38
|
"Settings.list.actions.deleteAdditionalInfos": "Aktywne wersje językowe zostaną usunięte <em>(z pluginu Internacjonalizacja)</em>",
|
|
39
39
|
"Settings.list.actions.publishAdditionalInfos": "To opublikuje aktywne wersje językowe <em>(z pluginu Internacjonalizacja)</em>",
|
|
40
40
|
"Settings.list.actions.unpublishAdditionalInfos": "To cofnie publikację aktywnych wersji językowych <em>(z pluginu Internacjonalizacja)</em>",
|
|
41
|
-
"Settings.list.actions.edit": "Edytuj",
|
|
41
|
+
"Settings.list.actions.edit": "Edytuj język {name}",
|
|
42
42
|
"Settings.list.description": "Skonfiguruj ustawienia dla pluginu Internacjonalizacja",
|
|
43
43
|
"Settings.list.empty.description": "To nie jest standardowe zachowanie, co oznacza że ostatecznie zmieniłeś bazę danych manualnie. Aby poprawnie używać strapi upewnij się, że w bazie masz zapisane przynajmniej jedno ustawienie regionalne.",
|
|
44
44
|
"Settings.list.empty.title": "Brak języków.",
|
|
@@ -12,7 +12,7 @@ var ru = {
|
|
|
12
12
|
"Field.localized": "Это значение уникально для выбранного перевода",
|
|
13
13
|
"Field.not-localized": "Это значение является общим для всех переводов",
|
|
14
14
|
"Settings.list.actions.add": "Добавить новый перевод",
|
|
15
|
-
"Settings.list.actions.delete": "Удалить перевод",
|
|
15
|
+
"Settings.list.actions.delete": "Удалить перевод {name}",
|
|
16
16
|
"Settings.list.actions.deleteAdditionalInfos": "Это приведет к удалению активных версий перевода <em>(из плагина интернационализации)</em>",
|
|
17
17
|
"Settings.list.actions.edit": "Редактировать перевод",
|
|
18
18
|
"Settings.list.actions.publishAdditionalInfos": "При этом будут опубликованы активные языковые версии <em>(из плагина Интернационализация)</em>",
|
|
@@ -10,7 +10,7 @@ var ru = {
|
|
|
10
10
|
"Field.localized": "Это значение уникально для выбранного перевода",
|
|
11
11
|
"Field.not-localized": "Это значение является общим для всех переводов",
|
|
12
12
|
"Settings.list.actions.add": "Добавить новый перевод",
|
|
13
|
-
"Settings.list.actions.delete": "Удалить перевод",
|
|
13
|
+
"Settings.list.actions.delete": "Удалить перевод {name}",
|
|
14
14
|
"Settings.list.actions.deleteAdditionalInfos": "Это приведет к удалению активных версий перевода <em>(из плагина интернационализации)</em>",
|
|
15
15
|
"Settings.list.actions.edit": "Редактировать перевод",
|
|
16
16
|
"Settings.list.actions.publishAdditionalInfos": "При этом будут опубликованы активные языковые версии <em>(из плагина Интернационализация)</em>",
|
|
@@ -28,11 +28,11 @@ var tr = {
|
|
|
28
28
|
"Field.localized": "Bu değer seçilmiş olan yerel ayara özgüdür",
|
|
29
29
|
"Field.not-localized": "Bu değer tüm yerel ayarlarda ortaktır",
|
|
30
30
|
"Settings.list.actions.add": "Yeni bir yerel ayar ekle",
|
|
31
|
-
"Settings.list.actions.delete": "
|
|
31
|
+
"Settings.list.actions.delete": "{name} yerel ayarını sil",
|
|
32
32
|
"Settings.list.actions.deleteAdditionalInfos": "Bu aktif yerel ayar versiyonlarını silecektir <em>(Uluslararasılaştırma'dan)</em>",
|
|
33
33
|
"Settings.list.actions.publishAdditionalInfos": "Bu, aktif yerel ayar versiyonlarını yayınlayacaktır <em>(Uluslararasılaştırma'dan)</em>",
|
|
34
34
|
"Settings.list.actions.unpublishAdditionalInfos": "Bu, aktif yerel ayar versiyonlarının yayını kaldıracaktır <em>(Uluslararasılaştırma'dan)</em>",
|
|
35
|
-
"Settings.list.actions.edit": "
|
|
35
|
+
"Settings.list.actions.edit": "{name} yerel ayarını düzenle",
|
|
36
36
|
"Settings.list.description": "Uluslararasılaştırma eklentisinin ayarlarını düzenle",
|
|
37
37
|
"Settings.list.empty.description": "Bu beklendik bir davranış değil. Veritabanına elle müdahale olduğu anlamına geliyor. Strapinin düzgün çalışabilmesi için veritabanınızda en az bir adet yerel ayarın olduğundan emin olun.",
|
|
38
38
|
"Settings.list.empty.title": "Hiçbir yerel ayar yok.",
|
|
@@ -26,11 +26,11 @@ var tr = {
|
|
|
26
26
|
"Field.localized": "Bu değer seçilmiş olan yerel ayara özgüdür",
|
|
27
27
|
"Field.not-localized": "Bu değer tüm yerel ayarlarda ortaktır",
|
|
28
28
|
"Settings.list.actions.add": "Yeni bir yerel ayar ekle",
|
|
29
|
-
"Settings.list.actions.delete": "
|
|
29
|
+
"Settings.list.actions.delete": "{name} yerel ayarını sil",
|
|
30
30
|
"Settings.list.actions.deleteAdditionalInfos": "Bu aktif yerel ayar versiyonlarını silecektir <em>(Uluslararasılaştırma'dan)</em>",
|
|
31
31
|
"Settings.list.actions.publishAdditionalInfos": "Bu, aktif yerel ayar versiyonlarını yayınlayacaktır <em>(Uluslararasılaştırma'dan)</em>",
|
|
32
32
|
"Settings.list.actions.unpublishAdditionalInfos": "Bu, aktif yerel ayar versiyonlarının yayını kaldıracaktır <em>(Uluslararasılaştırma'dan)</em>",
|
|
33
|
-
"Settings.list.actions.edit": "
|
|
33
|
+
"Settings.list.actions.edit": "{name} yerel ayarını düzenle",
|
|
34
34
|
"Settings.list.description": "Uluslararasılaştırma eklentisinin ayarlarını düzenle",
|
|
35
35
|
"Settings.list.empty.description": "Bu beklendik bir davranış değil. Veritabanına elle müdahale olduğu anlamına geliyor. Strapinin düzgün çalışabilmesi için veritabanınızda en az bir adet yerel ayarın olduğundan emin olun.",
|
|
36
36
|
"Settings.list.empty.title": "Hiçbir yerel ayar yok.",
|
|
@@ -11,9 +11,9 @@ var zhHans = {
|
|
|
11
11
|
"Field.localized": "该值对于选中语言是唯一的",
|
|
12
12
|
"Field.not-localized": "该值对所有语言通用",
|
|
13
13
|
"Settings.list.actions.add": "添加语言",
|
|
14
|
-
"Settings.list.actions.delete": "
|
|
14
|
+
"Settings.list.actions.delete": "删除 {name} 语言",
|
|
15
15
|
"Settings.list.actions.deleteAdditionalInfos": "这将会删除可用的语言版本<em>(来自国际化)</em>",
|
|
16
|
-
"Settings.list.actions.edit": "
|
|
16
|
+
"Settings.list.actions.edit": "编辑 {name} 语言",
|
|
17
17
|
"Settings.list.description": "国际化插件设置",
|
|
18
18
|
"Settings.list.empty.description": "这不是一个常规的动作,您动手修改了数据库。为了能正确使用Strapi,请确保至少保存一种语言在您的数据库中",
|
|
19
19
|
"Settings.list.empty.title": "暂无语言",
|
|
@@ -9,9 +9,9 @@ var zhHans = {
|
|
|
9
9
|
"Field.localized": "该值对于选中语言是唯一的",
|
|
10
10
|
"Field.not-localized": "该值对所有语言通用",
|
|
11
11
|
"Settings.list.actions.add": "添加语言",
|
|
12
|
-
"Settings.list.actions.delete": "
|
|
12
|
+
"Settings.list.actions.delete": "删除 {name} 语言",
|
|
13
13
|
"Settings.list.actions.deleteAdditionalInfos": "这将会删除可用的语言版本<em>(来自国际化)</em>",
|
|
14
|
-
"Settings.list.actions.edit": "
|
|
14
|
+
"Settings.list.actions.edit": "编辑 {name} 语言",
|
|
15
15
|
"Settings.list.description": "国际化插件设置",
|
|
16
16
|
"Settings.list.empty.description": "这不是一个常规的动作,您动手修改了数据库。为了能正确使用Strapi,请确保至少保存一种语言在您的数据库中",
|
|
17
17
|
"Settings.list.empty.title": "暂无语言",
|
|
@@ -12,9 +12,9 @@ var zh = {
|
|
|
12
12
|
"Field.localized": "此欄位對所選的地區設定來說是唯一的",
|
|
13
13
|
"Field.not-localized": "此欄位在所有地區設定間皆通用",
|
|
14
14
|
"Settings.list.actions.add": "新增地區設定",
|
|
15
|
-
"Settings.list.actions.delete": "
|
|
15
|
+
"Settings.list.actions.delete": "刪除 {name} 地區設定",
|
|
16
16
|
"Settings.list.actions.deleteAdditionalInfos": "這將刪除已啟用的地區設定版本 <em>(from Internationalization)</em>",
|
|
17
|
-
"Settings.list.actions.edit": "
|
|
17
|
+
"Settings.list.actions.edit": "編輯 {name} 地區設定",
|
|
18
18
|
"Settings.list.description": "設定國際化外掛程式",
|
|
19
19
|
"Settings.list.empty.description": "此行為不尋常,這表示您最終仍手動修改了資料庫。請至少在您的資料庫中儲存一種地區設定以正常使用 Strapi。",
|
|
20
20
|
"Settings.list.empty.title": "無地區設定。",
|
|
@@ -10,9 +10,9 @@ var zh = {
|
|
|
10
10
|
"Field.localized": "此欄位對所選的地區設定來說是唯一的",
|
|
11
11
|
"Field.not-localized": "此欄位在所有地區設定間皆通用",
|
|
12
12
|
"Settings.list.actions.add": "新增地區設定",
|
|
13
|
-
"Settings.list.actions.delete": "
|
|
13
|
+
"Settings.list.actions.delete": "刪除 {name} 地區設定",
|
|
14
14
|
"Settings.list.actions.deleteAdditionalInfos": "這將刪除已啟用的地區設定版本 <em>(from Internationalization)</em>",
|
|
15
|
-
"Settings.list.actions.edit": "
|
|
15
|
+
"Settings.list.actions.edit": "編輯 {name} 地區設定",
|
|
16
16
|
"Settings.list.description": "設定國際化外掛程式",
|
|
17
17
|
"Settings.list.empty.description": "此行為不尋常,這表示您最終仍手動修改了資料庫。請至少在您的資料庫中儲存一種地區設定以正常使用 Strapi。",
|
|
18
18
|
"Settings.list.empty.title": "無地區設定。",
|
|
@@ -6,13 +6,13 @@ var index = require('../utils/index.js');
|
|
|
6
6
|
const isLocalizedAttribute = (attribute)=>{
|
|
7
7
|
return attribute?.pluginOptions?.i18n?.localized === true;
|
|
8
8
|
};
|
|
9
|
-
const UNSUPPORTED_ATTRIBUTE_TYPES = [
|
|
9
|
+
const UNSUPPORTED_ATTRIBUTE_TYPES = new Set([
|
|
10
10
|
'media',
|
|
11
11
|
'relation',
|
|
12
12
|
'boolean',
|
|
13
13
|
'enumeration'
|
|
14
|
-
];
|
|
15
|
-
const IGNORED_FIELDS = [
|
|
14
|
+
]);
|
|
15
|
+
const IGNORED_FIELDS = new Set([
|
|
16
16
|
'id',
|
|
17
17
|
'documentId',
|
|
18
18
|
'createdAt',
|
|
@@ -22,7 +22,7 @@ const IGNORED_FIELDS = [
|
|
|
22
22
|
'updatedBy',
|
|
23
23
|
'createdBy',
|
|
24
24
|
'localizations'
|
|
25
|
-
];
|
|
25
|
+
]);
|
|
26
26
|
/**
|
|
27
27
|
* Deep merge where target values take priority over source values.
|
|
28
28
|
* Arrays are merged by index to align repeatable component / dynamic zone items.
|
|
@@ -73,7 +73,7 @@ const IGNORED_FIELDS = [
|
|
|
73
73
|
preservedPaths.add(path.raw);
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
|
-
if (IGNORED_FIELDS.
|
|
76
|
+
if (IGNORED_FIELDS.has(key)) {
|
|
77
77
|
remove(key);
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
@@ -88,7 +88,7 @@ const IGNORED_FIELDS = [
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
// Keep other unsupported attribute types (boolean, enumeration)
|
|
91
|
-
if (UNSUPPORTED_ATTRIBUTE_TYPES.
|
|
91
|
+
if (UNSUPPORTED_ATTRIBUTE_TYPES.has(attribute.type)) {
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
// Keep components and dynamic zones — traverseEntity recurses into them
|
|
@@ -144,12 +144,12 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
144
144
|
}
|
|
145
145
|
const localizedRoots = new Set();
|
|
146
146
|
const translateableContent = await utils.traverseEntity(({ key, attribute, parent, path }, { remove })=>{
|
|
147
|
-
if (IGNORED_FIELDS.
|
|
147
|
+
if (IGNORED_FIELDS.has(key)) {
|
|
148
148
|
remove(key);
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
151
|
const hasLocalizedOption = attribute && isLocalizedAttribute(attribute);
|
|
152
|
-
if (attribute && UNSUPPORTED_ATTRIBUTE_TYPES.
|
|
152
|
+
if (attribute && UNSUPPORTED_ATTRIBUTE_TYPES.has(attribute.type)) {
|
|
153
153
|
remove(key);
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
@@ -206,7 +206,7 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
206
206
|
*/ const minimalContentTypeSchema = Object.fromEntries(Object.entries(schema.attributes)// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
207
207
|
.filter(([_, attr])=>{
|
|
208
208
|
const isLocalized = isLocalizedAttribute(attr);
|
|
209
|
-
const isSupportedType = !UNSUPPORTED_ATTRIBUTE_TYPES.
|
|
209
|
+
const isSupportedType = !UNSUPPORTED_ATTRIBUTE_TYPES.has(attr.type);
|
|
210
210
|
return isLocalized && isSupportedType;
|
|
211
211
|
}).map(([key, attr])=>{
|
|
212
212
|
const minimalAttribute = {
|
|
@@ -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';\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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"}
|
|
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 = new Set<Schema.Attribute.Kind>([\n 'media',\n 'relation',\n 'boolean',\n 'enumeration',\n]);\n\nconst IGNORED_FIELDS = new Set([\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.has(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.has(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.has(key)) {\n remove(key);\n return;\n }\n const hasLocalizedOption = attribute && isLocalizedAttribute(attribute);\n if (attribute && UNSUPPORTED_ATTRIBUTE_TYPES.has(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.has(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","Set","IGNORED_FIELDS","deepMerge","source","target","result","key","Object","keys","sourceVal","targetVal","Array","isArray","map","item","i","mergeUnsupportedFields","targetData","sourceDoc","schema","getModel","preservedPaths","unsupportedFieldsOnly","traverseEntity","path","remove","isInsidePreservedSubtree","raw","from","some","pp","startsWith","add","has","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","includes","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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"}
|
|
@@ -4,13 +4,13 @@ import { getService } from '../utils/index.mjs';
|
|
|
4
4
|
const isLocalizedAttribute = (attribute)=>{
|
|
5
5
|
return attribute?.pluginOptions?.i18n?.localized === true;
|
|
6
6
|
};
|
|
7
|
-
const UNSUPPORTED_ATTRIBUTE_TYPES = [
|
|
7
|
+
const UNSUPPORTED_ATTRIBUTE_TYPES = new Set([
|
|
8
8
|
'media',
|
|
9
9
|
'relation',
|
|
10
10
|
'boolean',
|
|
11
11
|
'enumeration'
|
|
12
|
-
];
|
|
13
|
-
const IGNORED_FIELDS = [
|
|
12
|
+
]);
|
|
13
|
+
const IGNORED_FIELDS = new Set([
|
|
14
14
|
'id',
|
|
15
15
|
'documentId',
|
|
16
16
|
'createdAt',
|
|
@@ -20,7 +20,7 @@ const IGNORED_FIELDS = [
|
|
|
20
20
|
'updatedBy',
|
|
21
21
|
'createdBy',
|
|
22
22
|
'localizations'
|
|
23
|
-
];
|
|
23
|
+
]);
|
|
24
24
|
/**
|
|
25
25
|
* Deep merge where target values take priority over source values.
|
|
26
26
|
* Arrays are merged by index to align repeatable component / dynamic zone items.
|
|
@@ -71,7 +71,7 @@ const IGNORED_FIELDS = [
|
|
|
71
71
|
preservedPaths.add(path.raw);
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
|
-
if (IGNORED_FIELDS.
|
|
74
|
+
if (IGNORED_FIELDS.has(key)) {
|
|
75
75
|
remove(key);
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
@@ -86,7 +86,7 @@ const IGNORED_FIELDS = [
|
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
// Keep other unsupported attribute types (boolean, enumeration)
|
|
89
|
-
if (UNSUPPORTED_ATTRIBUTE_TYPES.
|
|
89
|
+
if (UNSUPPORTED_ATTRIBUTE_TYPES.has(attribute.type)) {
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
// Keep components and dynamic zones — traverseEntity recurses into them
|
|
@@ -142,12 +142,12 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
142
142
|
}
|
|
143
143
|
const localizedRoots = new Set();
|
|
144
144
|
const translateableContent = await traverseEntity(({ key, attribute, parent, path }, { remove })=>{
|
|
145
|
-
if (IGNORED_FIELDS.
|
|
145
|
+
if (IGNORED_FIELDS.has(key)) {
|
|
146
146
|
remove(key);
|
|
147
147
|
return;
|
|
148
148
|
}
|
|
149
149
|
const hasLocalizedOption = attribute && isLocalizedAttribute(attribute);
|
|
150
|
-
if (attribute && UNSUPPORTED_ATTRIBUTE_TYPES.
|
|
150
|
+
if (attribute && UNSUPPORTED_ATTRIBUTE_TYPES.has(attribute.type)) {
|
|
151
151
|
remove(key);
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
@@ -204,7 +204,7 @@ const createAILocalizationsService = ({ strapi })=>{
|
|
|
204
204
|
*/ const minimalContentTypeSchema = Object.fromEntries(Object.entries(schema.attributes)// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
205
205
|
.filter(([_, attr])=>{
|
|
206
206
|
const isLocalized = isLocalizedAttribute(attr);
|
|
207
|
-
const isSupportedType = !UNSUPPORTED_ATTRIBUTE_TYPES.
|
|
207
|
+
const isSupportedType = !UNSUPPORTED_ATTRIBUTE_TYPES.has(attr.type);
|
|
208
208
|
return isLocalized && isSupportedType;
|
|
209
209
|
}).map(([key, attr])=>{
|
|
210
210
|
const minimalAttribute = {
|
|
@@ -1 +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';\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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"}
|
|
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';\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 = new Set<Schema.Attribute.Kind>([\n 'media',\n 'relation',\n 'boolean',\n 'enumeration',\n]);\n\nconst IGNORED_FIELDS = new Set([\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.has(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.has(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.has(key)) {\n remove(key);\n return;\n }\n const hasLocalizedOption = attribute && isLocalizedAttribute(attribute);\n if (attribute && UNSUPPORTED_ATTRIBUTE_TYPES.has(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.has(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","Set","IGNORED_FIELDS","deepMerge","source","target","result","key","Object","keys","sourceVal","targetVal","Array","isArray","map","item","i","mergeUnsupportedFields","targetData","sourceDoc","schema","getModel","preservedPaths","unsupportedFieldsOnly","traverseEntity","path","remove","isInsidePreservedSubtree","raw","from","some","pp","startsWith","add","has","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","includes","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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"}
|