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

Code before:
        });

        throw new Error(`AI Localizations request failed: ${response.statusText}`);
      } else {
        await aiLocalizationJobsService.upsertJobForDocument({
          documentId,
          contentType: model,
          sourceLocale: document.locale,
          targetLocales,
          status: 'completed',
        });
      }

      const aiResult = await response.json();

      // Get all media field names dynamically from the schema
      const mediaFields = Object.entries(schema.attributes)
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        .filter(([_, attr]) => attr.type === 'media')
        .map(([key]) => key);

      try {
        await Promise.allSettled(
          aiResult.localizations.map(async (localization: any) => {
            const { content, locale } = localization;

            // Fetch the derived locale document
            const derivedDoc = await strapi.documents(model).findOne({
              documentId,
              locale,
              populate: mediaFields,
            });

            // Merge AI content and media fields, works only on first level media fields (root level)
            const mergedData = { ...content };
            for (const field of mediaFields) {
              // Only copy media if not already set in derived locale
              if (!derivedDoc || !derivedDoc[field]) {
                mergedData[field] = document[field];
              } else {
                mergedData[field] = derivedDoc[field];
              }
            }

            return strapi.documents(model).update({
              documentId,
              locale,
              fields: [],
              data: mergedData,
            });
          })

Review comment (suggestion):
I don't think this makes a difference. Can revert if y'all prefer. I had tried this because I had some issues translating blocks on existing entries. 

The blocks returned from AI were transformed into an array 🤷‍♂️ . I was confused how changing to the query engine could have caused that issue. But I'm no longer able to reproduce, it appears to be working. 

Here's what it looked:

```
BEFORE ANY CHANGe

 {
  mergedData: {
    title: 'bonjour',
    blocksContent: [
      '{"type": "paragraph", "children": [{"type": "text", "text": "testeur"}]}'
    ],
    markdownContent: null,
    slug: null,
    cover: null
  }
}

AFTER CHANGE

{
    title: 'bonjour',
    blocksContent: [
      'type',
      'paragraph',
      'children',
      'type',
      'text',
      'text',
      'testeurs'
    ],
    markdownContent: null,
    slug: null,
    cover: null
  }
}
```

Code after:
        });

        throw new Error(`AI Localizations request failed: ${response.statusText}`);
      }

      const aiResult = await response.json();
      // Get all media field names dynamically from the schema
      const mediaFields = Object.entries(schema.attributes)
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        .filter(([_, attr]) => attr.type === 'media')
        .map(([key]) => key);

      try {
        await Promise.all(
          aiResult.localizations.map(async (localization: any) => {
            const { content, locale } = localization;

            // Fetch the derived locale document
            const derivedDoc = await strapi.documents(model).findOne({
              documentId,
              locale,
              populate: mediaFields,
            });

            // Merge AI content and media fields, works only on first level media fields (root level)
            const mergedData = structuredClone(content);
            for (const field of mediaFields) {
              // Only copy media if not already set in derived locale
              if (!derivedDoc || !derivedDoc[field]) {
                mergedData[field] = document[field];
              } else {
                mergedData[field] = derivedDoc[field];
              }
            }

            /**
             * TODO:
             * We should use the document service here, however currently,
             * updating a localized entry marks the source document as modified.
             * @see https://github.com/strapi/strapi/issues/22924
             *
             * Falling back to the query engine to preserve the correct status
             */
            await strapi.db.query(model).update({
              where: { documentId, locale },
              data: mergedData,
            });

            await aiLocalizationJobsService.upsertJobForDocument({
              documentId,
              contentType: model,