← All articlesUmbraco

Translating Umbraco media captions with AI and Multilanguage Textbox

A practical design for generating and translating media descriptions in Umbraco 17, preserving authored values and storing each language with Multilanguage Textbox.

Translating pages is only part of a multilingual website. Media descriptions need attention too, especially when the same image appears across several language versions.

On a commercial careers-site project, I worked on a workflow that generates an initial image description with AI and fills missing translations. Multilanguage Textbox provides the editing and storage part: one textbox per configured language, on a shared media item.

Start with the right property

Install a package version compatible with your CMS, create a Multilanguage Textbox data type, and add it to the relevant media type. The 17.0.2 release on NuGet targets Umbraco 17 and .NET 10. The repository’s older README examples should not be your only guide to version compatibility.

Think about the field’s purpose before naming it. A visible caption and an image’s alternative text are different editorial decisions. A caption can add background information; alt text should communicate the image’s purpose in its context. A decorative image may correctly use an empty alt attribute.

For that reason, an empty string alone is a poor signal that AI must generate something. Give editors a way to distinguish “not yet written” from “intentionally decorative”, and review how the front end chooses the appropriate value.

Split generation from translation

The implementation I worked on has two distinct operations:

  1. An image-capable model, accessed through Umbraco.AI, produces a source-language description.
  2. An Azure AI Translator integration translates that text into missing language values.

That second step is a dedicated translation service, rather than an LLM call. You could implement the translation adapter using a Chat profile in Umbraco.AI instead, supplying the source language, target language and editorial guidance. That would be a different provider implementation to test, not a feature you get simply by installing the textbox package.

Keeping these operations separate means a manually written source caption can be translated without sending the image to a vision model. It also lets you compare translation providers without changing the editor’s stored data.

Understand the stored value

The workflow reads the raw media property as JSON containing culture/text pairs. A simplified value looks like this:

[
  { "culture": "en-GB", "text": "Two colleagues discussing a design" },
  { "culture": "fr-FR", "text": "" }
]

That storage format is different from the package’s published-value conversion, which can return the string for the current culture. Use the right API for the job: published rendering and updating an IMedia property’s raw value are different operations.

Read the site’s configured languages and ensure each has an entry. Preserve existing entries and non-empty translations. Select the source by an explicit culture; silently choosing the first populated slot can label a French sentence as English.

Here is a small, provider-independent transformation. It takes a validated list with unique cultures and an explicit source. It returns a new list; it does not save media or call any particular AI SDK.

using System.Text.Json.Serialization;

public sealed record CaptionValue(
    [property: JsonPropertyName("culture")] string Culture,
    [property: JsonPropertyName("text")] string Text);

public static class CaptionTranslation
{
    public static async Task<List<CaptionValue>> FillMissingAsync(
        IReadOnlyList<CaptionValue> values,
        string sourceCulture,
        Func<string, string, string, CancellationToken, Task<string>> translate,
        CancellationToken cancellationToken)
    {
        var source = values.Single(value => string.Equals(
            value.Culture, sourceCulture, StringComparison.OrdinalIgnoreCase));
        if (string.IsNullOrWhiteSpace(source.Text))
            throw new InvalidOperationException("Write a source caption first.");

        var result = new List<CaptionValue>();
        foreach (var value in values)
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (!string.IsNullOrWhiteSpace(value.Text))
            {
                result.Add(value);
                continue;
            }

            var text = await translate(source.Text, source.Culture,
                value.Culture, cancellationToken);
            if (string.IsNullOrWhiteSpace(text))
                throw new InvalidOperationException("Translation returned no text.");
            result.Add(value with { Text = text });
        }
        return result;
    }
}

Do not strip every culture to the part before the hyphen. Provider language codes need deliberate mapping: regional and script differences can matter. If translation fails, leave the target pending and report the problem. Saving the source text as a supposedly successful translation makes retries and editorial review harder.

Make saving deliberate

A media-saving notification is a tempting trigger, but an external AI request can take time. Avoid a long remote call inside a database transaction. For work that must survive restarts, use a persisted job and store the media key, source culture and a source-value fingerprint.

When the job finishes, reload the media in a fresh scope. Recheck that the source has not changed and the target is still empty before saving through Umbraco’s media service. Otherwise, a slow translation can overwrite an editor’s newer work.

Saving can trigger your handler again. Guard your own processing against recursion; broad notification suppression also hides events other code may need. Include cancellation, bounded retries, provider failure reporting and an explicit review step before relying on the workflow at scale.

Give the model a bounded task

For image generation, request a short factual description and avoid guessing identities, sensitive characteristics or events the image cannot establish. Check how the provider receives the image: an inaccessible local URL will not work for a service that fetches it remotely.

For LLM translation, supply the caption’s intended use, language pair and terminology. Ask for the translation alone, then validate the response. A smaller model may handle short descriptions well, but compare real examples in your target languages and include human review.

This complements page and Dictionary translation. My AI translation guide covers the wider approach, while free Dictionary Translator and Translator Omni address their own supported content workflows. The custom media pipeline described here is separate from those packages.