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

220 lines
10 KiB
Markdown
Raw Normal View History

# 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:
```bash
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:
```bash
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:
```text
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:
```bash
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:
```text
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:
```text
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](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](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](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](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](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 |
---
## Related
- [i18n-setup.md](i18n-setup.md): the init file + manifest
- [label-xml.md](label-xml.md): Custom Labels + translations metadata
- [interpolation.md](interpolation.md): `{0}/{1}` placeholders
- [gotchas.md](gotchas.md): silent-fail traps (unregistered keys, stale cache, API-version mismatch)