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.
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
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>
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;
}
}
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.