@@ -2,19 +2,16 @@ import type { Core, Modules, Schema, UID } from '@strapi/types';
22import { traverseEntity } from '@strapi/utils' ;
33import { getService } from '../utils' ;
44
5+ const isLocalizedAttribute = ( attribute : Schema . Attribute . Attribute | undefined ) : boolean => {
6+ return ( attribute ?. pluginOptions as any ) ?. i18n ?. localized === true ;
7+ } ;
8+
59const createAILocalizationsService = ( { strapi } : { strapi : Core . Strapi } ) => {
610 // TODO: add a helper function to get the AI server URL
711 const aiServerUrl = process . env . STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io' ;
812 const aiLocalizationJobsService = getService ( 'ai-localization-jobs' ) ;
913
10- const UNSUPPORTED_ATTRIBUTE_TYPES : Schema . Attribute . Kind [ ] = [
11- 'media' ,
12- 'relation' ,
13- // TODO: remove these once the AI server can handle them reliably
14- 'component' ,
15- 'dynamiczone' ,
16- 'json' ,
17- ] ;
14+ const UNSUPPORTED_ATTRIBUTE_TYPES : Schema . Attribute . Kind [ ] = [ 'media' , 'relation' ] ;
1815
1916 return {
2017 // Async to avoid changing the signature later (there will be a db check in the future)
@@ -88,7 +85,7 @@ const createAILocalizationsService = ({ strapi }: { strapi: Core.Strapi }) => {
8885 // Extract only the localized content from the document
8986 const translateableContent = await traverseEntity (
9087 ( { key, attribute } , { remove } ) => {
91- const hasLocalizedOption = attribute ?. pluginOptions ?. i18n ?. localized === true ;
88+ const hasLocalizedOption = attribute && isLocalizedAttribute ( attribute ) ;
9289 // Only keep fields that actually need to be localized
9390 // TODO: remove blocks from this list once the AI server can handle it reliably
9491 if ( ! hasLocalizedOption || UNSUPPORTED_ATTRIBUTE_TYPES . includes ( attribute . type ) ) {
@@ -138,6 +135,31 @@ const createAILocalizationsService = ({ strapi }: { strapi: Core.Strapi }) => {
138135 } ) ;
139136 }
140137
138+ /**
139+ * Provide a schema to the LLM so that we can give it instructions about how to handle each
140+ * type of attribute. Only keep essential schema data to avoid cluttering the context.
141+ * Ignore fields that don't need to be localized.
142+ * TODO: also provide a schema of all the referenced components
143+ */
144+ const minimalContentTypeSchema = Object . fromEntries (
145+ Object . entries ( schema . attributes )
146+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
147+ . filter ( ( [ _ , attr ] ) => {
148+ const isLocalized = isLocalizedAttribute ( attr ) ;
149+ const isSupportedType = ! UNSUPPORTED_ATTRIBUTE_TYPES . includes ( attr . type ) ;
150+ return isLocalized && isSupportedType ;
151+ } )
152+ . map ( ( [ key , attr ] ) => {
153+ const minimalAttribute = { type : attr . type } ;
154+ if ( attr . type === 'component' ) {
155+ (
156+ minimalAttribute as Schema . Attribute . Component < `${string } .${string } `, boolean >
157+ ) . repeatable = attr . repeatable ?? false ;
158+ }
159+ return [ key , minimalAttribute ] ;
160+ } )
161+ ) ;
162+
141163 strapi . log . http ( 'Contacting AI Server for localizations generation' ) ;
142164 const response = await fetch ( `${ aiServerUrl } /i18n/generate-localizations` , {
143165 method : 'POST' ,
@@ -149,12 +171,7 @@ const createAILocalizationsService = ({ strapi }: { strapi: Core.Strapi }) => {
149171 content : translateableContent ,
150172 sourceLocale : document . locale ,
151173 targetLocales,
152- schema : Object . fromEntries (
153- Object . entries ( schema . attributes )
154- // eslint-disable-next-line @typescript-eslint/no-unused-vars
155- . filter ( ( [ _ , attr ] ) => ( attr ?. pluginOptions as any ) ?. i18n ?. localized === true )
156- . map ( ( [ key , attr ] ) => [ key , { type : attr . type } ] )
157- ) ,
174+ contentTypeSchema : minimalContentTypeSchema ,
158175 } ) ,
159176 } ) ;
160177
0 commit comments