Two separate systems translate what editors see. They use different files, different key syntax and different fallback rules, so identify which text you are translating before writing anything.
| Reference | Where the text appears | Files | |
|---|---|---|---|
| A | ui-localization.md | Everything drawn in the backoffice UI: extension labels, Document/Media/Member Type names, tabs, groups, properties, core wording | App_Plugins/<Package>/umbraco-package.json + a JS module per culture (assets) |
| B | net-localization.md | Text built on the server with no UI: emails, login errors, health checks | config/lang/<culture>.user.xml, read through ILocalizedTextService (assets) |
How to decide. Default to A: nearly all backoffice text is UI localization, and .NET
localization is only for strings the server composes itself. Use B when the string is produced in
C# and sent out (an email subject, an error from a login flow, a health check message). A request can
need both: say which part each file serves. Open the chosen reference and start from its assets before
writing any file.
Website content in several languages (variants, domains, dictionary items) is a different feature,
handled by the umbraco-multilingual skill. If the same strings must exist for visitors, do not put
them in these files. If you have to answer a visitor-text question here, view text comes from
dictionary items, read with Umbraco.GetDictionaryValue(key) or
Umbraco.GetDictionaryValueOrDefault(key, defaultText); GetDictionaryValue(key, "text") does not
exist, and Razor views are not compiled by dotnet build, so that mistake only shows at runtime.
Version compatibility
Umbraco 17+. The UI system is the extension-registry localization type (Umbraco 14 and later);
.user.xml overrides for server-side text are far older and unchanged.
Best practices
- Keep UI keys grouped as
areathenkey. Every key is requested asarea_key, and only one grouping level exists. Prefix areas with your package name so they cannot collide with core areas, in camelCase with letters, digits and_only (acmeWelcome, notAcme.Welcome): a#area_keyreference stops matching at a.or-. - Ship an
enfile as the complete set. English is the fallback for every missing UI key, so other cultures can be partial. - Server-side text falls back to the English (
en-US) file only, soen.user.xmlmust hold every key you use. A key missing from English too, or a culture with no language file at all, returns[key]. Give keys distinctive aliases: a key missing from your area is first looked up by alias in every other area, so a generic one (publish) can pick up unrelated core text. - Register a manifest only at
App_Plugins/orApp_Plugins/<Package>/. A deeper folder is ignored without any error. - Do not edit the core language files. Override with your own extension (UI) or
.user.xml(server). - Tell the user a restart is needed after registering or renaming a manifest, a browser reload after
editing a JS text, and a restart after editing a
.user.xml.
Reply checklist
End your reply to the user with these, each as its own sentence (a code comment does not count):
- Fallback. UI text: a key missing in the user's language shows the
entext, and ifenlacks it too the element's inner text. Server text: a key missing in one language shows the English text, so the English file must hold every key, and a key missing from English, or a language with no file at all, shows[key], for example[subject]. - Coverage. Which languages you set up and what happens for any other.
- Restart or reload. What the user must do for the change to show.
- What you did not run. Only claim a build or a rendered backoffice if you ran one.
Validation
evals/evals.json holds the scenarios and checkable expectations;
evals/README.md explains how they are run.