How to integrate ai models to extended translation

Magnolia already provides a solution to add OpenAI to your extended translation workflow. You can also use the instant translation feature with it. However, it is not currently connected to the AI Accelerator tasks. This short tutorial should help you connect the Magnolia Extended Translation module with Gemini or Azure OpenAI, depending on your needs.

As a prerequisite, you need to install the latest Magnolia AI Accelerator as well as the latest Extended Translation module. Please install them according to the provided documentation

1

Create an ai model definition

According to the documentation you need to setup the ai model definition. In our example i have used the Azure OpenAI model definition.

aiModels/GPT4o.yaml

modelName: OpenAI GPT-4o
modelVersion: gpt-4o
$type: azureAiModel
2

Add API Configuration

According to this documentation page set up the api configuration.

/decorations/ai-accelerator-azure/config.yaml

apiKey: <your-azure-key>
azureDeployment: <your-azure-deployment>
azureResourceName: <your-azure-resource-name>
3

Add an AiAcceleratorTranslator class

Create a new translator implementation class. The most of the magic starts within the translateWithGPT method.

package com.magnoliacentral.i18n;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.validation.constraints.NotBlank;

import org.apache.logging.log4j.util.Strings;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;

import info.magnolia.ai.AiAcceleratorCoreModule;
import info.magnolia.ai.config.model.ConfiguredModelDefinition;
import info.magnolia.ai.config.task.ConfiguredAiTaskDefinition;
import info.magnolia.ai.model.AiModelExecutor;
import info.magnolia.ai.model.Prompt;
import info.magnolia.ai.registry.AiModelRegistry;
import info.magnolia.ai.registry.AiTaskRegistry;
import info.magnolia.ai.task.AiExecutionContext;
import info.magnolia.ai.task.ExecutePromptTask;
import info.magnolia.commands.CommandsManager;
import info.magnolia.rest.definition.dto.Property;
import info.magnolia.rest.definition.dto.PropertyType;
import info.magnolia.rest.definition.dto.Schema;
import info.magnolia.task.definition.ConfiguredTaskDefinition;
import info.magnolia.translation.ext.core.exception.TranslationException;
import info.magnolia.translation.ext.core.helper.AbstractAutoTranslator;
import info.magnolia.translation.ext.core.model.TranslationSubmission;
import info.magnolia.translation.ext.core.translator.Translator;
import info.magnolia.translation.ext.provider.chatgpt.definition.ChatGPTProviderDefinition;
import info.magnolia.translation.ext.provider.chatgpt.translator.ChatGPTTranslator;

public class AiAcceleratorTranslator extends AbstractAutoTranslator implements Translator {

    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(ChatGPTTranslator.class);

    /**
     * Definition.
     */
    private final ChatGPTProviderDefinition definition;
    private final AiModelRegistry modelRegistry;
    private final AiTaskRegistry taskRegistry;
    private final AiModelExecutor modelExecutor;
    public static final Schema DEFAULT_TEXT_SCHEMA = Schema.builder()
            .property("text", Property.builder().type(PropertyType.STRING).build())
            .build();


    @Inject
    public AiAcceleratorTranslator(ChatGPTProviderDefinition definition, AiModelExecutor modelExecutor, AiModelRegistry modelRegistry, AiTaskRegistry taskRegistry) {
        this.definition = definition;
        this.modelRegistry = modelRegistry;
        this.modelExecutor = modelExecutor;
        this.taskRegistry = taskRegistry;
    }

    @Override
    public <T> T translate(TranslationSubmission submission) throws TranslationException {
        try {

            submission.getI18nFields()
                    .forEach(i18nItem -> {
                        String textToTranslate = i18nItem.getMasterValue();
                        Document doc = Jsoup.parseBodyFragment(textToTranslate);
                        Element body = doc.body();
                        Node node = translateAndSplitTextNodesRecursive(
                                body,
                                submission.getSourceLanguage(),
                                submission.getTargetLanguage());

                        Document document = Jsoup.parseBodyFragment(String.valueOf(node));
                        i18nItem.setTranslatedValue(document.body().html());

                    });

            submission.setTranslatedDocument(this.getTranslatedDocument(submission));

        } catch (Exception e) {
            LOG.error("Error occurred while translating", e);
            throw new TranslationException("Failed to translate via ChatGPT service", e);
        }

        return (T) submission;
    }


    private Node translateAndSplitTextNodesRecursive(Node node, String sourceLang, String targetLang) {
        List<String> resultsOfSplitContent = new ArrayList<>();
        if (node instanceof TextNode) {
            TextNode textNode = (TextNode) node;
            this.splitContent(textNode.text())
                    .forEach(splitContent -> {
                        resultsOfSplitContent.add(translateWithGPT(
                                sourceLang,
                                targetLang,
                                splitContent));
                    });
            String result = String.join("", resultsOfSplitContent);
            textNode.text(result);
        }
        for (Node child : node.childNodes()) {
            translateAndSplitTextNodesRecursive(child, sourceLang, targetLang);
        }
        return node;
    }

    private List<String> splitContent(@NotBlank String textToSplit) {
        List<String> result = new ArrayList<>();
        String unporcessedText = textToSplit;

        while (!unporcessedText.isEmpty()) {

            if (unporcessedText.length() > this.definition.getCharacterLimit()) {
                int whiteSpaceIndex = unporcessedText.indexOf(" ", this.definition.getWhiteSpaceCharacterIndex());
                if (whiteSpaceIndex == -1) {
                    whiteSpaceIndex = unporcessedText.length();
                }
                String newContent = unporcessedText.substring(0, whiteSpaceIndex);
                result.add(newContent);
                unporcessedText = unporcessedText.substring(whiteSpaceIndex);
            } else {
                result.add(unporcessedText);
                unporcessedText = Strings.EMPTY;
            }
        }

        return result;
    }

    private String translateWithGPT(String sourceLang, String targetLang, String content) {

        if (content.trim().isEmpty())
            return "";
        
        AiExecutionContext aiExecutionContext = new AiExecutionContext();
        Prompt prompt = generateMessagesForRequest(sourceLang, targetLang, content);
        
        aiExecutionContext.setSchema(DEFAULT_TEXT_SCHEMA).setPrompt(prompt);
        
        aiExecutionContext.setModelDefinition(modelRegistry.getByModelId(this.definition.getModel()));
        aiExecutionContext.setTaskDefinition(taskRegistry.getByTaskType(ExecutePromptTask.class).iterator().next());

        Map<String, Object> execute = modelExecutor.execute(aiExecutionContext);

        Map<String, Object> response = ImmutableMap.<String, Object>builder().putAll(execute).put("prompt", prompt).build();
        LinkedHashMap responseData = ((LinkedHashMap) response.get("data"));
        
        return responseData.get("text").toString();
    }

    private Prompt generateMessagesForRequest(String sourceLang, String targetLang, String content) {
        String stringMessage = String.format(this.definition.getPrompt(), sourceLang, targetLang);
        
        return Prompt.builder()
        .message(Prompt.Message.builder().role(Prompt.Role.system).content(stringMessage).build())
        .message(Prompt.Message.builder().role(Prompt.Role.user).content(content).build())
        .build();
    }
    
    @Override
    public String output(TranslationSubmission submission) throws TranslationException {
        return null;
    }

    @Override
    public boolean checkConnection() {
        return false;
    }
}
4

Add decoration for your translationProvider

Similar to the following description your should set up your translation Provider

decorations/magnolia-content-translation-support-ext-chatgpt/translationProviders/ChatGPT.yaml

config:
  translationProviders:
    ChatGPT:
      class: 'info.magnolia.translation.ext.provider.chatgpt.definition.ChatGPTProviderDefinition'
      configName: 'ChatGPT.com'
      enabledFlag: 'true'
      implementationClass: 'com.magnoliacentral.i18n.AiAcceleratorTranslator'
      model: 'GPT4o'
      prompt: 'Please translate the following text from %s language to %s language. Only return the translated text—no questions or explanations. The input may be a single word or a full sentence. Context: If the text is short (e.g., one or two words), assume it is part of a user interface element such as a button, label, tooltip, or menu item. Translate based on intended user action, not literal meaning. If the text is long-form, translate naturally for general reading.'

Please notice that you use the name of your ai model inside the model declaration here. You should also add the corresponding implementationClass.

Conclusion

After that you should be able to use the Azure GPT Connection or Gemini Integration. You could specify your own prompt through the prompt property in the yaml file.

Portrait: Marvin Kerkhoff</h2
About the author

Marvin Kerkhoff

Marvin Kerkhoff is a web developer since 2004. He started to work for Arvato Systems Schweiz 13 years ago. He did his Magnolia certification in 2010 and achieved the highest Magnolia certification in 2020. He has a strong background in broad E-Commerce and Digital Business projects with various customers.

Business contact