TheBuilder.AppTranslations 0.4.2

dotnet add package TheBuilder.AppTranslations --version 0.4.2
                    
NuGet\Install-Package TheBuilder.AppTranslations -Version 0.4.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TheBuilder.AppTranslations" Version="0.4.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TheBuilder.AppTranslations" Version="0.4.2" />
                    
Directory.Packages.props
<PackageReference Include="TheBuilder.AppTranslations" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TheBuilder.AppTranslations --version 0.4.2
                    
#r "nuget: TheBuilder.AppTranslations, 0.4.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TheBuilder.AppTranslations@0.4.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TheBuilder.AppTranslations&version=0.4.2
                    
Install as a Cake Addin
#tool nuget:?package=TheBuilder.AppTranslations&version=0.4.2
                    
Install as a Cake Tool

<p align="center"> <img src="https://raw.githubusercontent.com/thebuilder/umbraco-app-translations/refs/heads/main/apps/docs/public/logo-mark.svg" width="112" height="112" alt="App Translations logo" /> </p>

<h1 align="center">App Translations for Umbraco</h1>

<p align="center"> Let editors change the text in your application from the Umbraco backoffice,<br /> without a code change or a deploy. </p>

NuGet version NuGet downloads Umbraco Marketplace License

Your application keeps its own translation files. Umbraco reads them, editors change the messages that need changing, and your application fetches those changes and lays them over its own text.

  • The application owns the messages. Keys, the default text, the message syntax and the placeholders all come from the application's files, and stay there.
  • Umbraco owns the edits. Editors write custom text for any message in any language, and Umbraco records who last changed each one, and when.
  • The application decides what to show. It merges the custom text over its own, or asks Umbraco for the complete dictionary.

A site can also translate into languages the application does not ship. Umbraco serves text written for such a language as the whole message, because the application has nothing to merge it over.

The package supports Umbraco 17.1 and later, Umbraco 18 included, on .NET 10.

Getting started

  1. Install the package in your Umbraco site:

    dotnet add package TheBuilder.AppTranslations
    
  2. Make your application's translation files reachable over HTTP, one file per language. The package reads nested JSON, as next-intl and i18next write it, and next-intl's gettext PO files.

  3. In Umbraco, go to Settings > App translations and add a source. A source holds the URL of the files, the languages to read, and the message syntax your application uses. Test source fetches it without saving anything; Sync now reads it in.

  4. Open the App Translations section and start editing.

  5. Point your application at the delivery endpoint, as described in Using the translations in your application.

Adding a source

A source is one set of translation files. A placeholder in its URL names the language to fetch:

https://app.example.com/messages/{locale}.json     ->  .../messages/en-US.json
https://app.example.com/messages/{language}.json   ->  .../messages/en.json
https://app.example.com/{language}/{locale}.json   ->  .../en/en-US.json

Umbraco's language codes carry a region, such as en-US, but many applications name their files by language alone, such as en. Use {language} when yours do. Umbraco still stores each message under the full code, so en-US and en-GB stay separate.

A source has four more settings.

  • Sync automatically. Sync the source on a schedule, every hour up to every week. Off by default.
  • Message syntax. ICU messages, as next-intl and FormatJS use them, i18next JSON v4, or plain text. The editor checks every change against the syntax, so an edit cannot break a placeholder or a plural.
  • Namespace handling. Use the first key of the JSON as the namespace, or put every message in one namespace you name. Your application fetches one namespace at a time, or all of them at once.
  • Advanced. The source identifier for deployment-triggered sync, a request timeout, and the largest response the source accepts.

Sources that need authentication

You can add request headers to a source. Each header either holds its value, or names a configuration setting that supplies the value when Umbraco makes the request. A typed value is stored in the database in plain text, so put secrets in a setting. This example gives a header X-Api-Key the setting name Translations:SourceApiKey:

{
  "Translations": {
    "SourceApiKey": "development-value-only"
  }
}

In a deployed environment, supply the value as the environment variable Translations__SourceApiKey or through your secret store. For a bearer token, add an Authorization header and make the setting's value the whole header, for example Bearer your-token.

Sources on a private network

By default, sources connect only to public addresses, do not follow redirects and do not use the host's proxy. That stops anyone from using a source URL to reach internal services. To read from a private network or localhost, turn on AppTranslations:SourceSecurity:AllowPrivateNetworkEndpoints. Only do that in development or a network you trust.

Editing translations

The App Translations section lists every message, one row per key. Choose the language you are editing and, if you like, a language to compare against. The ⇄ button between them swaps the two. The editor remembers the pair for the rest of the browser session.

  • Search finds text in any language, and keys.
  • Filters narrow the list by namespace and by status. The statuses are not written, custom text, default text, default changed, and no longer used.
  • Click a row to edit it in place. The field highlights placeholders, and a message with plurals or choices shows an example of each case under the field.
  • Save saves the row. Save & next saves it and opens the next one, for working down a list. The bottom of the open row lists the keyboard shortcuts.
  • Opening another row or closing this one keeps your unsaved text. The row shows it, marked as unsaved, with its own Save and Discard. Save all at the top saves every unsaved row at once. Unsaved text survives a visit to another section or a reload.
  • Revert to default removes the custom text and returns to what the application ships.
  • Default changed marks custom text whose application default has changed since someone wrote it, so you can check it still fits.

Anyone with access to the App Translations section can edit. Administrators manage sources and the AI settings.

Who can open the section

The package installs its own App Translations section and opens it to the user groups that can open Umbraco's Translation section, which on a new site are Administrators and Translators. To let more people edit, add the section to their user group in Users > User groups.

Umbraco's Translation section

Umbraco's own Translation section holds only the Dictionary. On a site without Dictionary items it is a second, empty translation section next to App Translations, so the package hides it while the Dictionary is empty. As soon as the site has a Dictionary item, the section shows again.

To show it on a site without Dictionary items, for example to start adding some, turn off Hide while the Dictionary is empty in Settings > App translations. The section comes back at once.

Hiding it changes nothing in the user groups: the package leaves it out of the backoffice, so uninstalling the package brings it back.

AI suggestions

With Umbraco.AI set up, the editor can translate a message from the language you are comparing against. It can also improve, simplify or shorten the text in the field, or fix its spelling.

Suggestions need Umbraco.AI 17.5 or later, which needs Umbraco 17.5 or later. This package does not install Umbraco.AI for you. It comes with the provider package you install in step 1. Until the site has it, the AI assistant card says what to install.

  1. Install an Umbraco.AI provider package for your Umbraco version, for example Umbraco.AI.OpenAI.
  2. In the AI section, add a connection and a chat profile. Put brand voice and tone on the profile as contexts. They apply to every suggestion.
  3. In Settings > App translations, turn on Offer in the editor in the AI assistant card and choose the profile. You can also change the instructions sent with every request there.

The assistant is off until an administrator turns it on, because it sends the site's text to an AI provider.

A suggestion only fills the field, and the site does not change until you save. The editor checks every suggestion the same way it checks a save. If a reply drops or renames a placeholder, the assistant sends it back once with the reason, and refuses it if the second reply is no better.

Using the translations in your application

To use the custom text, fetch it for a language and namespace, then merge it over the messages your application already bundles.

GET /umbraco/delivery/api/v1/app-translations/overrides?locale=da-DK&namespace=website

If your application does not bundle its own text, fetch every message instead: the editor's text where an editor changed it, and your source's text everywhere else.

GET /umbraco/delivery/api/v1/app-translations/all?locale=da-DK&namespace=website

Repeat namespace to fetch several namespaces, or leave it out to fetch every namespace. Each is then a top-level key holding its own messages.

Umbraco keeps each response in memory until the translations change, on every server. Clients and CDNs may keep it for 60 seconds; AppTranslations:Delivery:MaxAgeSeconds changes that, and 0 makes them check on every request. Every response carries an ETag, so a client that sends If-None-Match gets 304 Not Modified until something changes.

The endpoints are public until you set AppTranslations:Delivery:ApiKey. With a key set, a request needs it in an Api-Key header or as a bearer token, and gets 401 without it. Settings > App translations lists the exact URL for each language and namespace.

Messages are served in the syntax their source declares. Add format=icu (next-intl, react-intl, Lingui) or format=i18next (i18next, react-i18next) to have Umbraco refuse anything your application would misread. For i18next, set the source's message syntax to i18next JSON v4. The output keeps nested keys, {{variable}} interpolation, context keys and plural suffixes such as _one, _other and _ordinal_one. Umbraco never converts text between ICU and i18next syntax, so keep each namespace to one syntax. Umbraco refuses to serve a namespace that mixes them until you split it.

Keeping things in sync

Syncing a source

Umbraco reads a source when someone presses Sync now in Settings > App translations, which also keeps the history of every sync. To sync as part of a deployment, use the endpoint below.

Syncing on a schedule

Choose Sync automatically in a source's settings and Umbraco syncs it every hour, every 6 or 12 hours, every day or every week, counted from its last sync from anywhere. A failed sync counts too, so a source that is down is tried again at the next interval. In a load-balanced setup, only the Single or SchedulingPublisher server runs the schedule.

Syncing from a deployment

A deployment can sync a source by its identifier, without a backoffice login. The endpoint is off until you give your pipeline a way in: a secret, or the OIDC token its platform already issues. You can set up both and move pipelines over one at a time.

With a secret, use a long random value; anyone who has it can start a sync.

AppTranslations__DeploymentSync__ApiKey=replace-with-a-long-random-value

Give your deployment pipeline the same secret, and have it call the endpoint once the new translation files are live.

curl --fail-with-body \
  --request POST \
  --header "Authorization: Bearer $UMBRACO_TRANSLATIONS_SYNC_KEY" \
  "$UMBRACO_URL/umbraco/app-translations/api/v1/sources/website/sync"

Replace website with the source identifier from the source's Advanced settings. The request waits for the sync to finish and returns how many messages it added, changed and removed.

Instead of a secret, a pipeline on GitHub Actions, GitLab CI or a cloud platform can send the short-lived OIDC token its platform gives it. App Translations doesn't check the token itself: you register an ASP.NET Core authorization policy under the name TranslationPolicies.DeploymentSync, and any request that passes it can start a sync. API OIDC, a separate package, checks the tokens. Install version 0.2.3 or later, trust your pipeline's issuer, and register the policy in Program.cs:

using TheBuilder.ApiOidc.Authentication;
using TheBuilder.AppTranslations.Authorization;

builder.Services.AddAuthorization(options => options.AddPolicy(
    TranslationPolicies.DeploymentSync, policy => policy.RequireOidcToken("GitHubActions")));

The pipeline sends the token in the X-Oidc-Token header. See With an OIDC token for the API OIDC settings and a GitHub Actions workflow.

Response Meaning
200 Synced.
401 The secret or token is missing or wrong.
403 The token is valid, but the policy doesn't let it in.
404 No source has that identifier.
409 The source is turned off, or already syncing.
502 Umbraco could not fetch or read the translation files.
503 No secret is configured and no deployment sync policy is registered.

Use HTTPS, and keep the secret in the secret stores on both sides.

Telling your application something changed

To drop a cached dictionary the moment it changes, add a webhook in Settings > Webhooks for the App translations updated event (under Other). Umbraco handles the URL, headers, retries and the request log as it does for any webhook.

The event fires once for every change that reaches the delivery endpoints.

change When
Synchronized A sync added, changed or removed at least one message.
OverrideSaved An editor saved custom text.
OverrideRemoved An editor reverted custom text to the default.
SourceDeleted Someone deleted a source, and everything it provided.
{
  "sourceId": "6f2f5d3e-1f4a-4a1e-9a5b-2c7d8e9f0a1b",
  "sourceAlias": "website",
  "change": "OverrideSaved",
  "occurredAt": "2026-08-26T09:30:00+00:00",
  "message": { "namespace": "website", "key": "cart.title", "locale": "da-DK" },
  "synchronization": null
}

For a saved or reverted message, message names it. Its locale and namespace tell you exactly which dictionary to purge. For a sync, synchronization holds the revision and the added, changed and removed counts. Umbraco does not announce a sync that found nothing new.

Umbraco sends webhooks only when Umbraco:CMS:Webhook:Enabled is on, which it is by default. In a load-balanced setup, Umbraco sends webhooks only from the Single or SchedulingPublisher server.

Configuration

Setting Default Purpose
AppTranslations:SourceSecurity:AllowPrivateNetworkEndpoints false Let sources read from private networks and localhost.
AppTranslations:DeploymentSync:ApiKey none Turns on deployment-triggered sync with a secret. A long random value, without spaces.
AppTranslations:Delivery:ApiKey none Requires a key for the delivery endpoints, which are public without one.
AppTranslations:Delivery:MaxAgeSeconds 60 How long clients and CDNs may keep a delivery response. 0 makes them check every time.

Source headers read their values from the setting names you give them.

Documentation

The full documentation is at app-translations.thebuilder.dk.

  • Quickstart: install the package and serve the first edit to your application.
  • Sources: connect Umbraco to your translation files.
  • Editing translations: find, change and review messages.
  • Delivery API: fetch the custom text and merge it into next-intl or i18next.
  • Syncing: sync from a deployment and react to changes with a webhook.
  • Troubleshooting: the problems people run into first.

Development

The repository includes a sample site with English and Danish messages to sync against.

pnpm install
pnpm build
dotnet run --project samples/TheBuilder.AppTranslations.Example

The sample runs at https://localhost:44389 and installs itself. Its backoffice login is in samples/TheBuilder.AppTranslations.Example/appsettings.json. Add a source with https://localhost:44389/sample/messages/{locale}.json for ICU messages, or https://localhost:44389/sample/i18next/{locale}.json for i18next JSON v4. The sample already allows private-network sources.

Run these before opening a pull request.

dotnet test TheBuilder.AppTranslations.slnx
pnpm check   # type check, lint and dead-code check
pnpm test
pnpm build

pnpm fix applies formatting and the safe lint fixes. src/TheBuilder.AppTranslations/Client/CLAUDE.md describes the client's conventions.

Generate the backoffice API client from a running sample.

pnpm --dir src/TheBuilder.AppTranslations/Client generate-client -- https://localhost:44389/umbraco/swagger/thebuilder-app-translations/swagger.json

The sample documents the deployment sync API at /umbraco/swagger/thebuilder-app-translations-automation/swagger.json.

The documentation site lives in apps/docs and is built with Blume. pnpm docs:dev serves it with hot reload, and pnpm docs:build builds it as CI does. With the sample running, pnpm docs:screenshots retakes the backoffice screenshots the docs use; set UMBRACO_URL if the sample is not at https://localhost:44389.

THIRD-PARTY-NOTICES.md lists the npm packages the backoffice bundle includes, with their licenses. After changing the client's dependencies, run pnpm build and then pnpm notices to regenerate it; CI fails while it is out of date.

RELEASE.md describes how to publish a release.

License

MIT. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.2 88 10/19/2026
0.4.1 75 10/18/2026
0.4.0 84 10/16/2026
0.3.0 82 10/15/2026
0.2.0 70 10/15/2026
0.1.1 73 10/14/2026
0.1.0 73 10/14/2026