Repository: strapi/strapi
File: packages/plugins/i18n/server/src/services/ai-localizations.ts (TypeScript)

Code before:
        documentId,
        sourceLocale: document.locale,
        targetLocales,
        status: 'processing',
      });

      let token: string;
      try {
        const tokenData = await strapi.service('admin::user').getAiToken();
        token = tokenData.token;
      } catch (error) {
        await aiLocalizationJobsService.upsertJobForDocument({
          documentId,
          contentType: model,
          sourceLocale: document.locale,
          targetLocales,
          status: 'failed',
        });

        throw new Error('Failed to retrieve AI token', {
          cause: error instanceof Error ? error : undefined,
        });
      }

      strapi.log.http('Contacting AI Server for localizations generation');
      const response = await fetch(`${aiServerUrl}/i18n/generate-localizations`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${token}`,
        },
        body: JSON.stringify({
          content: translateableContent,
          sourceLocale: document.locale,
          targetLocales,
          schema: Object.fromEntries(
            Object.entries(schema.attributes)
              // eslint-disable-next-line @typescript-eslint/no-unused-vars
              .filter(([_, attr]) => (attr?.pluginOptions as any)?.i18n?.localized === true)
              .map(([key, attr]) => [key, { type: attr.type }])
          ),
        }),
      });

      if (!response.ok) {
        strapi.log.error(
          `AI Localizations request failed: ${response.status} ${response.statusText}`
        );

        await aiLocalizationJobsService.upsertJobForDocument({
          documentId,

Review comment (suggestion):
Adding the components requires recursive logic. So I'm planning to replace this by extending the traverseEntity that @araksyagevorgyan is working on her branch once that is ready

Code after:
        status: 'processing',
      });

      let token: string;
      try {
        const tokenData = await strapi.service('admin::user').getAiToken();
        token = tokenData.token;
      } catch (error) {
        await aiLocalizationJobsService.upsertJobForDocument({
          documentId,
          contentType: model,
          sourceLocale: document.locale,
          targetLocales,
          status: 'failed',
        });

        throw new Error('Failed to retrieve AI token', {
          cause: error instanceof Error ? error : undefined,
        });
      }

      /**
       * Provide a schema to the LLM so that we can give it instructions about how to handle each
       * type of attribute. Only keep essential schema data to avoid cluttering the context.
       * Ignore fields that don't need to be localized.
       * TODO: also provide a schema of all the referenced components
       */
      const minimalContentTypeSchema = Object.fromEntries(
        Object.entries(schema.attributes)
          // eslint-disable-next-line @typescript-eslint/no-unused-vars
          .filter(([_, attr]) => {
            const isLocalized = isLocalizedAttribute(attr);
            const isSupportedType = !UNSUPPORTED_ATTRIBUTE_TYPES.includes(attr.type);
            return isLocalized && isSupportedType;
          })
          .map(([key, attr]) => {
            const minimalAttribute = { type: attr.type };
            if (attr.type === 'component') {
              (
                minimalAttribute as Schema.Attribute.Component<`${string}.${string}`, boolean>
              ).repeatable = attr.repeatable ?? false;
            }
            return [key, minimalAttribute];
          })
      );

      strapi.log.http('Contacting AI Server for localizations generation');
      const response = await fetch(`${aiServerUrl}/i18n/generate-localizations`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',