afv-library/skills/experience-ui-bundle-localize/references/verifying.md

10 KiB

Verifying: serve, deploy, and test labels across locales

How to build, deploy, open, and verify that labels render correctly in multiple languages.


Overview

Verification has five steps:

  1. Build the app (API version bakes in, point at the deploy target org first)
  2. Deploy the bundle + labels + translations in one shot
  3. Open the app at the correct URL (on the lightning.force.com domain)
  4. Flip the user's Language setting to the translated language
  5. Reload and confirm the labels render in the new language

Step 1: Build the app

Before building, set the target org so the API version matches:

sf config set target-org=<your-org-alias>

Why this matters: The build plugin reads your default org's API version and stamps it into the bundle's JavaScript (services/data/v{N}/graphql). If you build while pointed at a v65.0 org and deploy to a v63.0 org, the bundle calls a GraphQL endpoint that org doesn't have, the call 404s, the i18n context fetch throws, and the app boots to a blank page with no obvious error.

Then build:

npm run build

(Run from the UI bundle directory, force-app/main/default/uiBundles/<your-bundle>/.)

The built output lands in dist/ under the bundle directory. The deploy pushes the built JS, not your TypeScript source.


Step 2: Activate languages in the org (before deploy)

For every non-English language you're deploying, activate it first in the org:

Setup → Translation Workbench → Translation Settings → Add

Pick the languages (e.g., Spanish, French, German) and Save.

Why: Deploying a <locale>.translation-meta.xml for an inactive language fails with:

Not available for deploy for this organization

English (en_US) needs no activation; it's always available.


Step 3: Deploy the bundle + labels + translations

Deploy the entire force-app tree (bundle, labels, translations) in one command:

sf project deploy start --source-dir force-app --target-org <your-org-alias>

Run from the SFDX project root (not from inside the bundle directory). --source-dir force-app deploys everything under that folder:

  • labels/CustomLabels.labels-meta.xml (English base labels)
  • translations/<locale>.translation-meta.xml (translated labels)
  • uiBundles/<your-bundle>/dist/ (the built app)

You do not need a package.xml or any manifest entry; the CLI discovers the metadata automatically.

Point at force-app, not uiBundles/, or your labels and translations won't go up.


Step 4: Open the app

UI Bundles serve at a fixed LWR route:

https://<your-org>.lightning.force.com/lwr/application/ai/<namespace>-<bundleName>

Parts:

  • <your-org>: your org's My Domain (e.g., mycompany-dev-ed)
  • <namespace>: the bundle's namespace (usually c for custom)
  • <bundleName>: the bundle's name (lowercased; the framework lowercases appName at lookup, so camelCase silently 404s)

Example: https://mycompany-dev-ed.lightning.force.com/lwr/application/ai/c-freshi18n

The /lwr/application/ai/ segment is a fixed LWR route prefix; it's the same for every UI Bundle and isn't something you configure.

Enhanced-domain orgs (scratch orgs are enhanced by default) redirect: Type the lightning.force.com URL above, and the browser redirects to the standalone-app host:

https://<your-org>--<namespace>.<instance>.my.salesforce.app/lwr/application/ai/<namespace>-<bundleName>

That's expected: a UI Bundle is a standalone LWR app, not part of Lightning Experience, so it serves from the .my.salesforce.app app host. The lightning.force.com URL is fine to type (it forwards); just don't be surprised when the address bar ends on .my.salesforce.app.

If you see a blank page:

  • You're probably on the raw my.salesforce.com (API/session) host; switch to lightning.force.com and let it redirect.
  • Or the API version is baked in wrong (built against a different org), see Step 1.
  • Or the bundle name is camelCased in the URL (the framework lowercases it; use all lowercase).

See gotchas.md for the full list of blank-page causes.


Step 5: Change the user's Language

To test translations, you need to change the Language setting (not Locale, see the note below).

Setup → My Settings → Language & Time Zone → Language → pick the language you translated (e.g., Spanish) → Save.

Reload the app. Labels should now render in the selected language.


Language vs Locale (common confusion)

Salesforce has two separate settings:

  • Language drives translations: the text the user sees (en_US, es, de, fr)
  • Locale drives formatting only: dates, numbers, currency (en_US, de_DE, fr_CA)

A user can have Language = English, Locale = French: English text, French number formatting.

When you test localization, change the Language, not the Locale. Changing Locale won't flip label text.


Troubleshooting: labels don't flip

1. Label shows in English when it should be translated

Possible causes:

  • The language isn't activated (Step 2), though you'd have hit the deploy rejection if that were it.
  • The translation file is missing that label, or its <name> doesn't match the <fullName> in CustomLabels.
  • The user's Language isn't what you think; confirm it's set to the language you translated (not Locale).

Check: Go to Setup → Translation Workbench → Translate → pick the language and the label. Is the translation there? If not, author it and re-deploy.

Fallback behavior: If a key is in the manifest but untranslated for the active language, i18next renders the English base value (via fallbackLng: "en" in the init). That's correct behavior; it just means that one key wasn't translated yet.


2. Label shows as its own key name

You see the literal string Welcome_Text on screen instead of "Welcome."

Cause: The key isn't in your label-manifest.ts. The app only fetches labels listed in the manifest, so an unregistered key is never requested, and i18next, finding nothing, renders the key string. No console warning, no error; it fails silently.

Fix: Add the "c:Key" entry to label-manifest.ts (Step 3 of the workflow). This is the most common localization bug; if a label looks wrong, check the manifest first.

See gotchas.md for the full explanation.


3. You changed a translation but the app still shows the old text

You edited a label in the Translation Workbench (or redeployed a translation-meta.xml), confirmed the new value is in the org, but the app keeps rendering the previous value on reload.

Cause: The label cache. Your src/i18n/index.ts chains two backends, [LocalStorageBackend, SalesforceBackend], so on load i18next reads labels from localStorage first and only falls through to GraphQL on a cache miss. Labels are cached per language+namespace under keys like i18next_res_de-c (DevTools → Application → Local Storage), with a 24-hour expirationTime. Until that entry expires, your app serves the cached copy and never refetches.

Fix: Clear the cached labels, then reload:

  • DevTools → Application → Local Storage → delete the i18next_res_* keys
  • Or Clear site data to wipe everything

The next load misses the cache, refetches over GraphQL, and shows the current value.

This is expected behavior, not a bug. The cache is what makes labels fast after first load. In production, a translation change takes up to expirationTime (24 hours) to roll out; during development, clear the cache to see edits immediately.

See gotchas.md for the full explanation.


4. A user on a regional Language (e.g., en_GB) sees English base text, not their locale's translation

This is expected, not a bug, as long as you only authored the base (en_US) translation.

Asked with fallback: BASE_VALUE, the GraphQL server returns the label's base value for a regional Language it has no explicit translation for: resolvedLocale comes back as the base (en_US) with wasFallback: true. The server does not map en_GB → en_US region-aware; it just honors the base-value fallback. On the documented SDK floor (11.42.1) the backend does not yet request BASE_VALUE, so it resolves with the server default (USER_DEFAULT): a guest or org-default user still lands on the base value, but see the related note below for the logged-in case. The base-value request ships in a later SDK release (see gotchas.md).

Fix (only if you want region-specific text): Author a translation for that exact regional Language (en_GB.translation-meta.xml). Otherwise the base value is the intended, correct result.

Related: if instead a logged-in user sees their own language (not the org default) for an untranslated label, that's the fallback-strategy case, see gotchas.md ("Logged-in user sees their own language instead of the org default"). On 11.42.1 that is the expected behavior, since the backend still uses USER_DEFAULT; author an explicit org-default translation to avoid it. The BASE_VALUE fallback that prevents it arrives in a later SDK release.


Verification checklist

Use this to confirm everything works:

  • Built against the correct org (API version matches deploy target)
  • Every translated language is activated in the org
  • Deployed force-app (bundle + labels + translations in one shot)
  • Opened at lightning.force.com/lwr/application/ai/<namespace>-<bundleName> (redirected to .my.salesforce.app is fine)
  • Changed user's Language (not Locale) to the translated language
  • Reloaded, labels render in the new language
  • If labels are stale, cleared i18next_res_* from localStorage

Quick reference: commands

Command Run from Purpose
sf config set target-org=<alias> Anywhere Set default org (API version bakes in on next build)
npm run build UI bundle dir Build the app
sf project deploy start --source-dir force-app --target-org <alias> Project root Deploy bundle + labels + translations
sf project retrieve start --metadata Translations:<locale> Project root Pull translations authored in Translation Workbench