@salesforce/afv-skills 1.50.0 → 1.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/experience-ui-bundle-deploy/SKILL.md +43 -2
- package/skills/experience-ui-bundle-deploy/references/config-scaffold.md +16 -3
- package/skills/experience-ui-bundle-deploy/references/logout-url.md +97 -0
- package/skills/experience-ui-bundle-deploy/scripts/set-logout-url.mjs +304 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +107 -90
- package/skills/experience-ui-bundle-localize/references/angular/check-i18n-wired.sh +159 -0
- package/skills/experience-ui-bundle-localize/references/angular/i18n-setup.md +250 -0
- package/skills/experience-ui-bundle-localize/references/angular/interpolation.md +156 -0
- package/skills/experience-ui-bundle-localize/references/angular/localize.md +111 -0
- package/skills/experience-ui-bundle-localize/references/{gotchas.md → common/gotchas.md} +27 -43
- package/skills/experience-ui-bundle-localize/references/{label-xml.md → common/label-xml.md} +27 -17
- package/skills/experience-ui-bundle-localize/references/common/platform-sdk-i18n.md +169 -0
- package/skills/experience-ui-bundle-localize/references/{verifying.md → common/verifying.md} +26 -16
- package/skills/experience-ui-bundle-localize/{scripts → references/react}/check-i18n-wired.sh +8 -3
- package/skills/experience-ui-bundle-localize/references/{i18n-setup.md → react/i18n-setup.md} +48 -10
- package/skills/experience-ui-bundle-localize/references/{interpolation.md → react/interpolation.md} +4 -4
- package/skills/experience-ui-bundle-localize/references/react/localize.md +76 -0
- package/skills/experience-ui-bundle-localize/scripts/check-manifest-registered.sh +92 -24
- package/skills/experience-ui-bundle-localize/scripts/detect-framework.sh +73 -0
- package/skills/experience-ui-bundle-site-generate/SKILL.md +1 -1
- package/skills/field-service-data-capture-form-deployer-configure/SKILL.md +1 -1
- package/skills/field-service-data-capture-form-deployer-configure/references/flow-metadata-json.md +1 -1
- package/skills/field-service-data-capture-form-editor-configure/SKILL.md +1 -1
- package/skills/field-service-data-capture-reference-configure/SKILL.md +28 -15
- package/skills/field-service-foundation-setup-designer-get/SKILL.md +1 -1
- package/skills/field-service-mobile-branding-configure/SKILL.md +1 -1
- package/skills/field-service-objective-designer-configure/SKILL.md +381 -64
- package/skills/field-service-prework-brief-deployer-configure/SKILL.md +566 -3
- package/skills/field-service-scheduling-policy-designer-query/SKILL.md +1097 -57
- package/skills/field-service-sobject-create-configure/SKILL.md +222 -7
- package/skills/field-service-voice-to-form-configure/SKILL.md +11 -11
- package/skills/field-service-work-rule-designer-configure/SKILL.md +92 -107
- package/skills/service-digital-engagement-channel-configure/SKILL.md +20 -37
- package/skills/service-digital-engagement-channel-configure/assets/messaging_channel_template.xml +3 -2
- package/skills/service-digital-engagement-channel-configure/examples/asa_agent_channel.xml +4 -4
- package/skills/service-helpagent-coordinate/SKILL.md +37 -29
- package/skills/service-helpagent-coordinate/assets/help-agent-spec.md +33 -28
- package/skills/service-helpagent-coordinate/references/agent-script.md +4 -1
- package/skills/service-helpagent-coordinate/references/channel-voice.md +1 -1
- package/skills/service-helpagent-coordinate/references/channel-web-chat.md +10 -12
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: experience-ui-bundle-localize
|
|
3
|
-
description: "MUST activate to localize / internationalize a uiBundles/*/src/ React
|
|
3
|
+
description: "MUST activate to localize / internationalize a uiBundles/*/src/ project (React or Angular): extract hardcoded user-facing strings into Custom Labels, wire a runtime i18n library over the Platform SDK backend, add labels for another language, or troubleshoot label rendering across locales. Triggers: user-facing string literals in component files, a CustomLabels.labels-meta.xml, a src/i18n/ directory or label-manifest.ts, translation call sites, or requests to 'translate / localize / internationalize / support another language.' Scope: authenticated B2E UI Bundles and B2C site bundles. Use experience-ui-bundle-site-generate instead for site language configuration or sfdc_cms__languageSettings. DO NOT TRIGGER for B2B site bundles, building app shell/UI or styling, reading/writing/refreshing records (use experience-ui-bundle-salesforce-data-access), generating a new bundle (use experience-ui-bundle-frontend-generate), deploying (use experience-ui-bundle-deploy), or authoring translations in Translation Workbench."
|
|
4
4
|
metadata:
|
|
5
|
-
version: "1.
|
|
5
|
+
version: "1.2"
|
|
6
6
|
domains: ["Experience"]
|
|
7
7
|
minApiVersion: "68.0"
|
|
8
8
|
relatedSkills:
|
|
@@ -19,30 +19,26 @@ metadata:
|
|
|
19
19
|
semver: ">=2.0.0"
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
# Localize a
|
|
22
|
+
# Localize a UI Bundle
|
|
23
23
|
|
|
24
|
-
Localize a
|
|
24
|
+
Localize a UI Bundle: extract user-facing strings into Salesforce Custom Labels, wire a
|
|
25
|
+
runtime i18n library over the Platform SDK backend, and verify labels across locales.
|
|
25
26
|
|
|
26
|
-
This file is the **workflow + guardrail spine**.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- **[references/label-xml.md](references/label-xml.md)**: Custom Labels and translation metadata XML shapes; the `namespace:Key` rules
|
|
30
|
-
- **[references/interpolation.md](references/interpolation.md)**: positional `{0}/{1}` placeholder interpolation in labels
|
|
31
|
-
- **[references/verifying.md](references/verifying.md)**: serve URL, locale flip, and verifying labels render
|
|
32
|
-
- **[references/gotchas.md](references/gotchas.md)**: the three silent-fail traps: unregistered manifest keys, API-version bake-in, stale label cache
|
|
27
|
+
This file is the **framework-neutral workflow + guardrail spine**. The framework-specific
|
|
28
|
+
detail — which i18n library, the translation call convention, the files scanned, the wiring
|
|
29
|
+
shape, and the depth docs — lives in a per-framework reference.
|
|
33
30
|
|
|
34
31
|
## The one-paragraph mental model
|
|
35
32
|
|
|
36
|
-
A
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```
|
|
33
|
+
A UI Bundle can't use compile-time label imports the way LWC does (`@salesforce/label/*`
|
|
34
|
+
resolves inside the platform's compiler, which your standalone bundle doesn't go through).
|
|
35
|
+
Instead, your app **fetches labels at runtime** through the Salesforce GraphQL UI API and
|
|
36
|
+
hands them to a standard i18n library to render. The Platform SDK provides the runtime
|
|
37
|
+
plumbing: a detector that reads the user's language, a backend that fetches labels over
|
|
38
|
+
GraphQL, and a context fetch. You write two thin files — a short init that wires the SDK
|
|
39
|
+
pieces into your i18n library, and a manifest listing which labels your app uses — then
|
|
40
|
+
author the labels themselves as Salesforce Custom Labels metadata. The exact library and
|
|
41
|
+
call convention are framework-specific; see your framework reference.
|
|
46
42
|
|
|
47
43
|
---
|
|
48
44
|
|
|
@@ -53,7 +49,46 @@ function WelcomeBanner() {
|
|
|
53
49
|
| Bundle doesn't exist yet | **experience-ui-bundle-frontend-generate** skill |
|
|
54
50
|
| Deploying the app with its labels | **experience-ui-bundle-deploy** skill |
|
|
55
51
|
| Configuring site languages or `sfdc_cms__languageSettings` | **experience-ui-bundle-site-generate** skill |
|
|
56
|
-
| Localizing an existing bundle | **
|
|
52
|
+
| Localizing an existing bundle | **Determine the framework (below), then the workflow** |
|
|
53
|
+
|
|
54
|
+
**Determine the framework.** It is normally already decided by the calling context — passed
|
|
55
|
+
down by the coordinator skill that invoked this one, or stated in the user's request. Use that.
|
|
56
|
+
|
|
57
|
+
The frameworks this skill supports are exactly the reference folders under
|
|
58
|
+
`<SKILL_DIR>/references/`, each containing a `localize.md` (so `react` →
|
|
59
|
+
`<SKILL_DIR>/references/react/localize.md`). This is the single source of truth — adding a
|
|
60
|
+
framework means adding a reference folder, nothing here changes.
|
|
61
|
+
|
|
62
|
+
- **If the framework is known** — open `<SKILL_DIR>/references/<framework>/localize.md` and
|
|
63
|
+
keep it alongside this spine. It supplies the library, the call convention, the files to
|
|
64
|
+
scan, and the wiring code.
|
|
65
|
+
- **If it is unknown** (a standalone run where nobody said which) — run the deterministic
|
|
66
|
+
detector on the app / uiBundle root before asking anyone:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
bash "<SKILL_DIR>/scripts/detect-framework.sh" "<app-or-uiBundle-root>"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
It combines an `angular.json` at/above the root, `@angular/core` / `react` in any
|
|
73
|
+
non-`node_modules` `package.json`, and source-file signatures, then prints one token and
|
|
74
|
+
sets a matching exit code:
|
|
75
|
+
- `react` or `angular` (exit 0) → use that framework. **Do not ask the user** — the
|
|
76
|
+
detection is deterministic. Open `<SKILL_DIR>/references/<framework>/localize.md`.
|
|
77
|
+
- `ambiguous` (exit 2) → both frameworks are present. List `<SKILL_DIR>/references/` and ask
|
|
78
|
+
the user which one to localize. If they name a framework with no matching reference folder,
|
|
79
|
+
it is not supported here — stop.
|
|
80
|
+
- `unknown` (exit 3) → **no supported framework detected. Terminate the workflow.** Do not
|
|
81
|
+
guess and do not proceed: report that neither React nor Angular signals were found in the
|
|
82
|
+
bundle, so localization cannot continue, and stop here.
|
|
83
|
+
|
|
84
|
+
Throughout the steps below, `<framework>` means the folder chosen here. The deterministic
|
|
85
|
+
check scripts split by coupling:
|
|
86
|
+
- **Framework-neutral, shared in `<SKILL_DIR>/scripts/`** — `detect-framework.sh` (the Step-0
|
|
87
|
+
detector above), `check-org-api-version.sh` and `detect-bundle-type.sh` (pure org/metadata
|
|
88
|
+
checks), and `check-manifest-registered.sh` (agnostic skeleton; it takes `--framework
|
|
89
|
+
<framework>` to select the call-site grammar).
|
|
90
|
+
- **Framework-specific, under `<SKILL_DIR>/references/<framework>/`** — `check-i18n-wired.sh`
|
|
91
|
+
(its manifest-into-backend detection is i18n-library-shaped, so each framework ships its own).
|
|
57
92
|
|
|
58
93
|
---
|
|
59
94
|
|
|
@@ -61,9 +96,9 @@ function WelcomeBanner() {
|
|
|
61
96
|
|
|
62
97
|
| # | Requirement | Verify | If missing |
|
|
63
98
|
|---|---|---|---|
|
|
64
|
-
| 1 | It's a `uiBundles/*/src/` React
|
|
65
|
-
| 2 | Platform SDK, UI Bundle, and
|
|
66
|
-
| 3 | You can identify where the app mounts | Read the entry file (
|
|
99
|
+
| 1 | It's a `uiBundles/*/src/` project (React or Angular) | Project structure matches | Not a UI Bundle → route to the correct skill |
|
|
100
|
+
| 2 | Platform SDK, UI Bundle, and build-plugin siblings installed and aligned (≥11.49.3) | `package.json` in the UI bundle dir | Tell user to align and upgrade them; cannot proceed |
|
|
101
|
+
| 3 | You can identify where the app mounts | Read the entry file (see the framework reference) | No clear mount point → ask user to point it out |
|
|
67
102
|
| 4 | Target org actually supports API v68.0+ (runtime label GraphQL for UI Bundles ships in Release 264) | Run the runtime org-release check below | Org's max API version is below v68.0 (Release 262 or older) → cannot proceed; retarget a Release 264+ org or upgrade the org |
|
|
68
103
|
| 5 | The bundle is authenticated B2E or the request/context explicitly identifies a B2C site, not B2B | Run the bundle-type detection below and use the request/context for site product identity | Explicit B2B → reject; site type not explicit → ask the user and stop until confirmed; B2C also requires precondition 6 |
|
|
69
104
|
| 6 | For B2C only, an admin has enabled `GraphQLApiOrgPrefForGuestUsers` | Ask the admin to confirm the org preference is already enabled | Do not enable it; explain that guest GraphQL returns HTTP 403 without it and stop (dependency: W-23854208) |
|
|
@@ -71,15 +106,15 @@ function WelcomeBanner() {
|
|
|
71
106
|
**Runtime org-release check (precondition 4).** The `platform.labels` GraphQL path that resolves labels at runtime for UI Bundles ships in Salesforce Release 264 (API v68.0 or higher). A `sourceApiVersion` in `sfdx-project.json` records what you declared, not what the org supports, so a newer CLI pointed at an older org can pass a static file check and then fail at runtime. Query the org's actual maximum API version before wiring anything:
|
|
72
107
|
|
|
73
108
|
```bash
|
|
74
|
-
bash <
|
|
109
|
+
bash <SKILL_DIR>/scripts/check-org-api-version.sh <org-alias-or-username>
|
|
75
110
|
```
|
|
76
111
|
|
|
77
112
|
Exit `0` → the org supports v68.0+, proceed. Exit `1` → the org is too old or unreachable; do not write i18n wiring or labels, report the version mismatch to the user and stop. (`sf api request rest` inside the script keeps authentication at the CLI transport layer, so no access token enters context.)
|
|
78
113
|
|
|
79
|
-
**Bundle-type detection (precondition 5).** The bundle's type decides which localization branch applies. Pass the full path to the bundle dir; the script derives the metadata root from it, so the current directory does not matter:
|
|
114
|
+
**Bundle-type detection (precondition 5).** The bundle's type decides which localization branch applies. It is framework-agnostic (pure Salesforce metadata). Pass the full path to the bundle dir; the script derives the metadata root from it, so the current directory does not matter:
|
|
80
115
|
|
|
81
116
|
```bash
|
|
82
|
-
bash <
|
|
117
|
+
bash <SKILL_DIR>/scripts/detect-bundle-type.sh <path-to-uiBundles/<name>/ dir>
|
|
83
118
|
```
|
|
84
119
|
|
|
85
120
|
Act on the exit-code contract: `0` → authenticated app (B2E or in-core internal), use the B2E branch; `10` → bound public site app-container candidate, meaning metadata proves site binding and guest access but **not** B2C versus B2B; `11` → bound non-public/unsupported site, stop; `12` → both CustomApplication and one site binding exist, ask which runtime context is the localization target; `13` → multiple matching Experience site bindings, show the reported site names and ask which site/runtime context is the target; `2` → unbound/unknown, report the script output and stop rather than guessing.
|
|
@@ -94,14 +129,17 @@ If a precondition isn't met, stop: report the specific block to the user and rec
|
|
|
94
129
|
|
|
95
130
|
## Workflow: the five steps
|
|
96
131
|
|
|
97
|
-
Each step has a **completion criterion** and a **confirm-before-continue** pause.
|
|
132
|
+
Each step has a **completion criterion** and a **confirm-before-continue** pause. Framework
|
|
133
|
+
specifics (file extensions, the translation call, the install, the init code) come from
|
|
134
|
+
`<SKILL_DIR>/references/<framework>/localize.md`.
|
|
98
135
|
|
|
99
136
|
### Step 1: Detect
|
|
100
137
|
|
|
101
|
-
**Goal:** Scan
|
|
138
|
+
**Goal:** Scan the framework's component files for user-facing hardcoded strings. (The
|
|
139
|
+
framework reference names the file extensions to scan.)
|
|
102
140
|
|
|
103
141
|
**What to scan:**
|
|
104
|
-
- String literals
|
|
142
|
+
- String literals shown to users in markup: `Welcome` in a heading → candidate
|
|
105
143
|
- String props shown to users: `placeholder="Enter name"` → candidate
|
|
106
144
|
- User-facing accessible text: `aria-label`, `aria-describedby`, `alt` → candidate (a screen-reader user hears these, so they must localize too)
|
|
107
145
|
|
|
@@ -110,12 +148,12 @@ Each step has a **completion criterion** and a **confirm-before-continue** pause
|
|
|
110
148
|
- Object keys / property names
|
|
111
149
|
- `data-*` attributes (machine-readable)
|
|
112
150
|
- Test IDs (`data-testid`, `id` attributes)
|
|
113
|
-
- Text already wrapped in
|
|
151
|
+
- Text already wrapped in a translation call
|
|
114
152
|
- Console logs, error messages thrown to developers (not user-facing)
|
|
115
153
|
- Class names, file paths, technical constants
|
|
116
154
|
|
|
117
155
|
**Action:**
|
|
118
|
-
1. Scan the `src/` directory for
|
|
156
|
+
1. Scan the `src/` directory for the framework's component files
|
|
119
157
|
2. Extract candidates, showing file path + line number for each
|
|
120
158
|
3. Show the list to the developer
|
|
121
159
|
|
|
@@ -128,7 +166,7 @@ Developer confirms the list (or edits it to remove false positives).
|
|
|
128
166
|
|
|
129
167
|
### Step 2: Extract
|
|
130
168
|
|
|
131
|
-
**Goal:** For each confirmed string, add a Custom Label and replace the
|
|
169
|
+
**Goal:** For each confirmed string, add a Custom Label and replace the literal with a translation call.
|
|
132
170
|
|
|
133
171
|
**Action for each string:**
|
|
134
172
|
1. **Propose a key name**, format: `<Context>_<Role>` (e.g., `"Welcome"` → `Welcome_Text`, `"Save"` → `Save_Button`, `"Failed to save"` → `Save_Failed_Message`). Follow naming: PascalCase words, underscores between parts, descriptive enough to be unique.
|
|
@@ -142,24 +180,20 @@ Developer confirms the list (or edits it to remove false positives).
|
|
|
142
180
|
<value>Welcome</value>
|
|
143
181
|
</labels>
|
|
144
182
|
```
|
|
145
|
-
(Full XML structure:
|
|
146
|
-
3. **Replace the string** in the component with
|
|
147
|
-
|
|
148
|
-
// Before: <h1>Welcome</h1>
|
|
149
|
-
// After: <h1>{t("Welcome_Text")}</h1>
|
|
150
|
-
```
|
|
151
|
-
4. **Add the import** if not present: `import { useTranslation } from "react-i18next";` and `const { t } = useTranslation("c");` at the top of the component function.
|
|
183
|
+
(Full XML structure: `references/common/label-xml.md`)
|
|
184
|
+
3. **Replace the string** in the component with your framework's translation call, and add
|
|
185
|
+
any required import/injection. The exact call convention is in the framework reference.
|
|
152
186
|
|
|
153
187
|
**Completion criterion:**
|
|
154
|
-
Every confirmed string has both a `CustomLabels` entry and a
|
|
188
|
+
Every confirmed string has both a `CustomLabels` entry and a translation call in its original location.
|
|
155
189
|
|
|
156
|
-
**Pause:** "For each string I'll add a Custom Label and replace the
|
|
190
|
+
**Pause:** "For each string I'll add a Custom Label and replace the literal with a translation call. Here are the proposed keys: [show string → namespace:Key mapping]. Apply these edits? [y / review each]"
|
|
157
191
|
|
|
158
192
|
---
|
|
159
193
|
|
|
160
194
|
### Step 3: Register
|
|
161
195
|
|
|
162
|
-
**Goal:** Add each key to the label manifest so
|
|
196
|
+
**Goal:** Add each key to the label manifest so the i18n runtime knows to fetch it.
|
|
163
197
|
|
|
164
198
|
**Action:**
|
|
165
199
|
1. Add each key to the manifest array in `src/i18n/label-manifest.ts`:
|
|
@@ -173,14 +207,14 @@ Every confirmed string has both a `CustomLabels` entry and a `t()` call in its o
|
|
|
173
207
|
If the file doesn't exist yet, Step 4 scaffolds it; the completion check below reports its absence, so don't test for the file by hand.
|
|
174
208
|
|
|
175
209
|
**Completion criterion:**
|
|
176
|
-
Run `check-manifest-registered.sh` from the UI bundle dir (it scans `src/` relative to the current directory) and report any errors it returns. It owns the deterministic inspection: it cross-checks every
|
|
210
|
+
Run `check-manifest-registered.sh` from the UI bundle dir (it scans `src/` relative to the current directory) and report any errors it returns. It owns the deterministic inspection: it cross-checks every translation call site against the manifest and treats a missing `label-manifest.ts` (when call sites exist) as a failure. A key that's called but not registered renders as its own literal name at runtime with no error, the silent-fail trap this guards.
|
|
177
211
|
|
|
178
212
|
```bash
|
|
179
213
|
cd <path-to-uiBundles/<name>/ dir> # scripts scan src/ relative to here
|
|
180
|
-
bash <
|
|
214
|
+
bash <SKILL_DIR>/scripts/check-manifest-registered.sh --framework <framework>
|
|
181
215
|
```
|
|
182
216
|
|
|
183
|
-
Branch on the exit code: `0`, every key is registered (or there are no
|
|
217
|
+
Branch on the exit code: `0`, every key is registered (or there are no call sites to gate), proceed. `1`, the manifest is missing or the listed keys aren't in it; scaffold or add them (Step 4 scaffolds the file) and re-run. `64`, usage error, the source dir doesn't exist (wrong cwd or bad argument); this is **not** a "keys missing" result, do not scaffold or register, fix the path and re-run.
|
|
184
218
|
|
|
185
219
|
**Pause:** "Added N entries to label-manifest.ts. check-manifest-registered.sh passed: [confirm]."
|
|
186
220
|
|
|
@@ -188,48 +222,32 @@ Branch on the exit code: `0`, every key is registered (or there are no `t()` cal
|
|
|
188
222
|
|
|
189
223
|
### Step 4: Wire
|
|
190
224
|
|
|
191
|
-
**Goal:** Ensure the
|
|
192
|
-
|
|
193
|
-
|
|
225
|
+
**Goal:** Ensure the i18n wiring exists; scaffold it if the app has no i18n yet. The install
|
|
226
|
+
command, the wiring code, and the B2E vs B2C fallback configuration are all in the framework
|
|
227
|
+
reference (`references/<framework>/localize.md` and its `i18n-setup.md`).
|
|
194
228
|
|
|
195
229
|
**Check:**
|
|
196
|
-
Run `check-i18n-wired.sh` from the UI bundle dir (it scans `src/` relative to the current directory) and report what it returns. The script owns the whole deterministic inspection: it looks for
|
|
230
|
+
Run `check-i18n-wired.sh` from the UI bundle dir (it scans `src/` relative to the current directory) and report what it returns. The script owns the whole deterministic inspection: it looks for the framework's i18n wiring (React: an `initI18n()` init file called at boot; Angular: `provideTranslateService`/`TranslateModule.forRoot` registering a custom `TranslateLoader`, loaded at boot via `TranslateService.use`), and when that exists it also reports whether the label manifest is imported and actually reaches the label backend/loader. Do not re-derive any of this by reading files yourself.
|
|
197
231
|
|
|
198
232
|
```bash
|
|
199
233
|
cd <path-to-uiBundles/<name>/ dir> # scripts scan src/ relative to here
|
|
200
|
-
bash <
|
|
234
|
+
bash <SKILL_DIR>/references/<framework>/check-i18n-wired.sh
|
|
201
235
|
```
|
|
202
236
|
|
|
203
237
|
Branch on the **exit code** (the printed message names the specific file/symbol for your report, but the decision is the code):
|
|
204
|
-
- Exit `0` → fully wired, the manifest
|
|
205
|
-
- Exit `1` → no
|
|
206
|
-
- Exit `2` → the
|
|
207
|
-
- Exit `3` → wired at boot but the script **could not confirm** the manifest
|
|
208
|
-
- Exit `64` → usage error: the source dir doesn't exist (wrong cwd or bad argument). This is **not** a "no
|
|
209
|
-
|
|
210
|
-
**If no i18n setup exists yet:**
|
|
211
|
-
1. Install dependencies (tell the user to run):
|
|
212
|
-
```bash
|
|
213
|
-
npm install i18next react-i18next i18next-chained-backend i18next-localstorage-backend
|
|
214
|
-
```
|
|
215
|
-
2. Create `src/i18n/index.ts` with the init wiring (full code: [references/i18n-setup.md](references/i18n-setup.md))
|
|
216
|
-
3. Create `src/i18n/label-manifest.ts` with an empty array (Step 3 will populate it)
|
|
217
|
-
4. Call `initI18n()` once at boot in the entry file (before mounting the app):
|
|
218
|
-
```typescript
|
|
219
|
-
import { initI18n } from "./i18n";
|
|
220
|
-
|
|
221
|
-
initI18n().then(() => {
|
|
222
|
-
// mount app
|
|
223
|
-
});
|
|
224
|
-
```
|
|
238
|
+
- Exit `0` → fully wired, the manifest reaches the label backend/loader; just add new keys.
|
|
239
|
+
- Exit `1` → no i18n wiring exists; scaffold the whole setup per the framework reference.
|
|
240
|
+
- Exit `2` → the wiring exists but is incomplete (React: init not called at boot; Angular: loader not registered, or registered but no boot-time `TranslateService.use`); do **not** re-scaffold or overwrite it. Add only the missing wiring the message names, then re-run.
|
|
241
|
+
- Exit `3` → wired at boot but the script **could not confirm** the manifest reaches the backend/loader. This last check is a textual heuristic: the manifest may be wired through a variable, spread, factory, or helper the script can't see, so treat exit 3 as "verify before editing," not "definitely broken." Open the file the message names and confirm before reconciling; never re-scaffold or duplicate wiring that already works.
|
|
242
|
+
- Exit `64` → usage error: the source dir doesn't exist (wrong cwd or bad argument). This is **not** a "no wiring" result; do not scaffold. Fix the path and re-run.
|
|
225
243
|
|
|
226
|
-
**If i18n
|
|
227
|
-
Act on the message `check-i18n-wired.sh` already printed (above): if it reports the manifest wired, just add new keys to it; if it reports a reconcile is needed, do exactly what its message names (import the manifest and/or pass it into the backend config) without clobbering existing wiring.
|
|
244
|
+
**B2C override — applies even at exit `0`:** the wiring check only proves i18n *exists*, not that it is correct for B2C. A seed with B2E wiring — `dir = ctx.dir` and a loader with **no** `labelFallback` — passes `check-i18n-wired.sh` at exit `0` but is **wrong for a B2C site**. If the site is B2C, don't stop at "add new keys": open `src/i18n/index.ts` (or the framework's init file) and, using `resolvedLang` (= `SFDC_ENV.language || ctx.lang`; the detector does not read `SFDC_ENV.language`), (a) add `labelFallback: "USER_DEFAULT"`, (b) set direction from it — `i18next.dir(resolvedLang)`, never `ctx.dir`, and (c) initialize in it — React `lng: resolvedLang` in `i18next.init`; Angular `translate.use(resolvedLang)`. See `references/<framework>/i18n-setup.md`. Never leave B2E wiring on a B2C site.
|
|
228
245
|
|
|
229
246
|
**Completion criterion:**
|
|
230
|
-
|
|
247
|
+
The i18n wiring exists and is called once at boot; the manifest reaches the label backend/loader. For B2C, all three `resolvedLang` overrides are applied — `USER_DEFAULT` fallback, display language (React `lng`, Angular `translate.use`), and `i18next.dir(resolvedLang)` direction — not the seed's B2E defaults.
|
|
248
|
+
Follow the framework reference for the exact scaffold and never clobber existing wiring.
|
|
231
249
|
|
|
232
|
-
**Pause:** "
|
|
250
|
+
**Pause:** "i18n setup [exists / created]. It's loaded at boot: [confirm]."
|
|
233
251
|
|
|
234
252
|
---
|
|
235
253
|
|
|
@@ -237,19 +255,18 @@ Act on the message `check-i18n-wired.sh` already printed (above): if it reports
|
|
|
237
255
|
|
|
238
256
|
**Goal:** Guide the developer to verify labels render in a second language.
|
|
239
257
|
|
|
240
|
-
**Action:** Follow the branch-specific procedure in
|
|
258
|
+
**Action:** Follow the branch-specific procedure in `references/common/verifying.md`.
|
|
241
259
|
|
|
242
|
-
For **B2E**, activate a second language, author or retrieve its translation metadata, build against the target org, deploy only the target bundle and label metadata, then change the authenticated user's Language and reload.
|
|
260
|
+
For **B2E**, activate a second language, author or retrieve its translation metadata, build against the target org, deploy only the target bundle and label metadata, then change the authenticated user's Language and reload.
|
|
243
261
|
|
|
244
|
-
For **B2C**, verify configured site languages, URL routing, `SFDC_ENV.language`, the full-reload language switcher, localized local preview, guest GraphQL access, and cache clearing as detailed in
|
|
262
|
+
For **B2C**, verify configured site languages, URL routing, `SFDC_ENV.language`, the full-reload language switcher, localized local preview, guest GraphQL access, and cache clearing as detailed in the framework reference.
|
|
245
263
|
|
|
246
264
|
Deploying the bundle, labels, and translations does not publish the Experience site. Treat `sf community publish` as a separate go-live mutation: show the exact site and target org, then wait for explicit user confirmation immediately before running it.
|
|
247
265
|
|
|
248
|
-
**If it doesn't render:**
|
|
249
|
-
Check the three gotchas in [references/gotchas.md](references/gotchas.md):
|
|
266
|
+
**If it doesn't render:** check the gotchas in `references/common/gotchas.md`:
|
|
250
267
|
- Unregistered manifest key (Step 3 missed a label)
|
|
251
268
|
- API-version mismatch (built against a different org)
|
|
252
|
-
- Stale
|
|
269
|
+
- Stale label cache (React: `i18next_res_*` in localStorage; Angular: in-memory, reload refetches)
|
|
253
270
|
- B2C guest GraphQL 403 (`GraphQLApiOrgPrefForGuestUsers` is not admin-enabled)
|
|
254
271
|
- B2C route, site language, and `SFDC_ENV.language` disagree
|
|
255
272
|
|
|
@@ -262,7 +279,7 @@ Labels render in ≥2 locales, or the blocking gotcha is identified.
|
|
|
262
279
|
|
|
263
280
|
## Edge cases: handle gracefully
|
|
264
281
|
|
|
265
|
-
- **Already-localized code**: detect existing
|
|
282
|
+
- **Already-localized code**: detect existing translation-call usage / a populated manifest; offer to *add to* the setup rather than re-scaffold everything.
|
|
266
283
|
- **No strings found**: report cleanly and stop; do not invent work.
|
|
267
284
|
- **App has no i18n setup yet**: Step 4 scaffolds the two files first before Step 3 can register anything.
|
|
268
285
|
- **Partial setup** (manifest exists but init missing, or vice-versa), reconcile what's present; never clobber existing wiring.
|
|
@@ -293,25 +310,25 @@ Labels render in ≥2 locales, or the blocking gotcha is identified.
|
|
|
293
310
|
├── i18n/
|
|
294
311
|
│ ├── index.ts ← init wiring (you write this once)
|
|
295
312
|
│ └── label-manifest.ts ← list of labels to fetch (you maintain this)
|
|
296
|
-
└── components/ ← components call
|
|
313
|
+
└── components/ ← components call the translation function
|
|
297
314
|
```
|
|
298
315
|
|
|
299
316
|
| Command | Run from | Purpose |
|
|
300
317
|
|---|---|---|
|
|
301
|
-
|
|
|
318
|
+
| Install i18n dependencies (see `references/<framework>/localize.md`) | UI bundle dir | Install the framework's i18n libraries (Step 4) |
|
|
302
319
|
| `npm run build` | UI bundle dir | Build the app (API version bakes in, set target-org first) |
|
|
303
|
-
| See `references
|
|
320
|
+
| See `references/<framework>/verifying.md` | Project root | Review and deploy the exact target bundle + changed label metadata to an explicit org |
|
|
304
321
|
| `sf project retrieve start --metadata Translations:<locale>` | Project root | Pull translations authored in Translation Workbench |
|
|
305
322
|
|
|
306
323
|
---
|
|
307
324
|
|
|
308
325
|
## Pre-flight checklist: completion criteria for the whole run
|
|
309
326
|
|
|
310
|
-
- [ ] Every confirmed string has both a `CustomLabels` entry and a
|
|
327
|
+
- [ ] Every confirmed string has both a `CustomLabels` entry and a translation call
|
|
311
328
|
- [ ] `label-manifest.ts` entry count == label count (no unregistered keys)
|
|
312
|
-
- [ ]
|
|
329
|
+
- [ ] i18n wiring present and called once at boot; manifest wired into the label backend/loader
|
|
313
330
|
- [ ] B2C only: guest GraphQL preference confirmed, `USER_DEFAULT` configured, and site language route matches `SFDC_ENV.language`
|
|
314
|
-
- [ ] B2E only: no
|
|
331
|
+
- [ ] B2E only: no fallback override; the framework reference's default is preserved
|
|
315
332
|
- [ ] Labels render in ≥2 locales (or the blocking gotcha is named)
|
|
316
333
|
- [ ] No hand-written machine translations landed in `*-meta.xml` (only scaffold-and-guide)
|
|
317
334
|
- [ ] Both metadata files parse as XML; the translation scaffold has one `<Translations>` root and one closed block per label
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail # exit on error (-e), undefined vars (-u), and propagate pipeline failures (-o pipefail)
|
|
3
|
+
#
|
|
4
|
+
# check-i18n-wired.sh — Confirm the Angular ngx-translate wiring exists, is
|
|
5
|
+
# registered with a custom loader, and is loaded at boot.
|
|
6
|
+
#
|
|
7
|
+
# Angular-specific: the detection targets are ngx-translate-shaped
|
|
8
|
+
# (provideTranslateService / TranslateModule.forRoot, a TranslateLoader subclass,
|
|
9
|
+
# and a boot-time TranslateService.use(...) call), which have no i18next analog,
|
|
10
|
+
# so this script lives under the Angular reference (references/angular/) rather
|
|
11
|
+
# than the shared scripts/ folder. The React equivalent (initI18n / SalesforceBackend
|
|
12
|
+
# detection) lives under references/react/.
|
|
13
|
+
#
|
|
14
|
+
# Whether the app already has i18n wiring is a file-existence plus call-site
|
|
15
|
+
# check, so run this rather than reading for it by hand. It looks for the
|
|
16
|
+
# ngx-translate provider registration, a custom TranslateLoader class, and a
|
|
17
|
+
# boot-time TranslateService.use(...) call.
|
|
18
|
+
#
|
|
19
|
+
# Usage (run from the UI bundle dir, or pass its src path):
|
|
20
|
+
# bash <skill-dir>/references/angular/check-i18n-wired.sh [src-dir]
|
|
21
|
+
#
|
|
22
|
+
# src-dir defaults to "src".
|
|
23
|
+
#
|
|
24
|
+
# When wiring exists, it also reports whether the label manifest is imported AND
|
|
25
|
+
# actually referenced inside the loader class (not merely imported and left
|
|
26
|
+
# unused), so the reconcile decision is a script result rather than a by-hand read.
|
|
27
|
+
#
|
|
28
|
+
# Exit codes (aligned with references/react/check-i18n-wired.sh and
|
|
29
|
+
# scripts/check-manifest-registered.sh so the workflow branches on the code, not
|
|
30
|
+
# the message text):
|
|
31
|
+
# 0 fully wired — provideTranslateService / TranslateModule.forRoot registers
|
|
32
|
+
# the custom loader, a boot-time TranslateService.use(...) runs, AND the
|
|
33
|
+
# manifest is imported and referenced inside the loader. Just add new keys.
|
|
34
|
+
# 1 no ngx-translate wiring found — neither a provider registration nor a
|
|
35
|
+
# TranslateLoader subclass. Scaffold the whole setup (see
|
|
36
|
+
# references/angular/i18n-setup.md).
|
|
37
|
+
# 2 partially wired — a loader class OR a registration exists, but the setup is
|
|
38
|
+
# incomplete: the loader is not registered via provideTranslateService /
|
|
39
|
+
# TranslateModule.forRoot, or it is registered but nothing calls
|
|
40
|
+
# TranslateService.use(...) at boot. Add ONLY the missing wiring; do NOT
|
|
41
|
+
# re-scaffold or overwrite the existing loader.
|
|
42
|
+
# 3 registered and booted, but the manifest could not be confirmed as reaching
|
|
43
|
+
# the loader (not imported into the loader file, no detectable loader class,
|
|
44
|
+
# or imported but never referenced in it). This is a textual heuristic: the
|
|
45
|
+
# manifest may be wired via a variable/factory/spread this check cannot see.
|
|
46
|
+
# VERIFY by hand; reconcile only if it genuinely dangles, and never clobber
|
|
47
|
+
# the existing loader.
|
|
48
|
+
# 64 usage error — the source dir does not exist (bad argument or wrong cwd).
|
|
49
|
+
# This is NOT a "no wiring found" result; do not scaffold. Fix the path and
|
|
50
|
+
# re-run. (64 = EX_USAGE, kept out of the 0-3 semantic range so exit 1
|
|
51
|
+
# uniquely means "no wiring found".)
|
|
52
|
+
# The message still names the specific file/symbol/gap for the report, but the
|
|
53
|
+
# decision is the exit code.
|
|
54
|
+
|
|
55
|
+
SRC_DIR="${1:-src}"
|
|
56
|
+
if [ ! -d "$SRC_DIR" ]; then
|
|
57
|
+
echo "ERROR: source dir not found: $SRC_DIR (run from the UI bundle dir, or pass its src path)" >&2
|
|
58
|
+
exit 64
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Use POSIX character classes, not \s / \b: BSD/macOS grep -E does not honor those.
|
|
62
|
+
|
|
63
|
+
# (A) The ngx-translate provider registration: standalone provideTranslateService()
|
|
64
|
+
# or the NgModule TranslateModule.forRoot(). Either one means "the setup exists".
|
|
65
|
+
REGISTERED=$(grep -rlE 'provideTranslateService[[:space:]]*\(|TranslateModule[[:space:]]*\.[[:space:]]*forRoot[[:space:]]*\(' \
|
|
66
|
+
"$SRC_DIR" --include='*.ts' 2>/dev/null | head -1 || true)
|
|
67
|
+
|
|
68
|
+
# (B) A custom loader class: `... implements TranslateLoader` or `... extends <X>TranslateLoader`.
|
|
69
|
+
# -l over the whole tree; the file list is reused for the manifest-reach check.
|
|
70
|
+
LOADER_FILES=$(grep -rlE '(implements|extends)[[:space:]].*TranslateLoader' \
|
|
71
|
+
"$SRC_DIR" --include='*.ts' 2>/dev/null || true)
|
|
72
|
+
LOADER=$(printf '%s' "$LOADER_FILES" | head -1 || true)
|
|
73
|
+
|
|
74
|
+
# (C) A boot-time TranslateService.use(...) call: <translate-ish>.use( — matches
|
|
75
|
+
# translate.use(, this.translate.use(, translateService.use(, etc. This is the
|
|
76
|
+
# Angular analog of React's boot-time initI18n() call.
|
|
77
|
+
BOOTED=$(grep -rlE '[Tt]ranslate[A-Za-z0-9_]*[[:space:]]*\.[[:space:]]*use[[:space:]]*\(' \
|
|
78
|
+
"$SRC_DIR" --include='*.ts' 2>/dev/null | head -1 || true)
|
|
79
|
+
|
|
80
|
+
# --- Decision tree (mirrors the React script's DEFINES/CALLS/reconcile shape) ---
|
|
81
|
+
|
|
82
|
+
# 1: nothing ngx-translate at all -> scaffold everything.
|
|
83
|
+
if [ -z "$REGISTERED" ] && [ -z "$LOADER" ]; then
|
|
84
|
+
echo "no ngx-translate wiring found (no provideTranslateService/TranslateModule.forRoot and no TranslateLoader subclass under $SRC_DIR) -> scaffold it (see references/angular/i18n-setup.md)"
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# 2a: a loader class exists but is never registered -> add only the provider wiring.
|
|
89
|
+
if [ -z "$REGISTERED" ]; then
|
|
90
|
+
echo "TranslateLoader subclass defined in $LOADER but not registered (no provideTranslateService/TranslateModule.forRoot) -> add the provider registration; do not re-scaffold the loader"
|
|
91
|
+
exit 2
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# 2b: registered but nothing loads a language at boot -> add only the boot call.
|
|
95
|
+
if [ -z "$BOOTED" ]; then
|
|
96
|
+
echo "ngx-translate registered in $REGISTERED but no boot-time TranslateService.use(...) call found -> add the boot wiring (fetch the i18n context and call translate.use(ctx.lang)); do not re-scaffold"
|
|
97
|
+
exit 2
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# Registered AND booted. Confirm the manifest actually reaches the loader: the
|
|
101
|
+
# loader file must import the manifest AND reference the bound symbol somewhere
|
|
102
|
+
# OTHER than its import line. Importing alone is not enough — a loader can
|
|
103
|
+
# `import { labelManifest }` and never iterate it, in which case it fetches
|
|
104
|
+
# nothing and every key renders as its own literal name. Resolve the symbol PER
|
|
105
|
+
# loader file from that file's own import (named / aliased / default), because an
|
|
106
|
+
# imported binding is only usable in the file that imports it.
|
|
107
|
+
RESOLVE_SYMBOL='s/.*[{,][[:space:]]*labelManifest[[:space:]]+as[[:space:]]+([A-Za-z0-9_]+).*/\1/; t
|
|
108
|
+
s/.*[{,][[:space:]]*labelManifest[[:space:]]*[,}].*/labelManifest/; t
|
|
109
|
+
s/.*import[[:space:]]+labelManifest[[:space:]]+from.*/labelManifest/; t
|
|
110
|
+
s/.*/labelManifest/'
|
|
111
|
+
|
|
112
|
+
MANIFEST_USED=""
|
|
113
|
+
MANIFEST_SYMBOL="labelManifest" # fallback for the message if no hit is found
|
|
114
|
+
if [ -n "$LOADER_FILES" ]; then
|
|
115
|
+
while IFS= read -r LOADER_FILE; do
|
|
116
|
+
[ -z "$LOADER_FILE" ] && continue
|
|
117
|
+
FILE_IMPORT=$(grep -nE '^[[:space:]]*import[^A-Za-z0-9_].*(label-manifest|labelManifest)' \
|
|
118
|
+
"$LOADER_FILE" 2>/dev/null | head -1 || true)
|
|
119
|
+
[ -z "$FILE_IMPORT" ] && continue # this file cannot use a binding it never imported
|
|
120
|
+
SYM=$(printf '%s\n' "$FILE_IMPORT" | sed -E "$RESOLVE_SYMBOL")
|
|
121
|
+
[ -z "$SYM" ] && continue
|
|
122
|
+
# Reference to SYM anywhere OTHER than an import line (strip // comments and
|
|
123
|
+
# import lines first, so `import { labelManifest }` and a commented mention do
|
|
124
|
+
# not count as use). A bare `for (const e of labelManifest)` / `labelManifest.`
|
|
125
|
+
# / `[...labelManifest]` all match.
|
|
126
|
+
if sed -E 's://.*$::' "$LOADER_FILE" \
|
|
127
|
+
| grep -vE '^[[:space:]]*import[^A-Za-z0-9_]' \
|
|
128
|
+
| grep -qE "(^|[^A-Za-z0-9_])${SYM}([^A-Za-z0-9_]|$)" 2>/dev/null; then
|
|
129
|
+
MANIFEST_USED="yes"; MANIFEST_SYMBOL="$SYM"; break
|
|
130
|
+
fi
|
|
131
|
+
done <<EOF
|
|
132
|
+
$LOADER_FILES
|
|
133
|
+
EOF
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
if [ -n "$MANIFEST_USED" ]; then
|
|
137
|
+
echo "i18n wired: ngx-translate registered in $REGISTERED, loaded at boot ($BOOTED), custom loader in $LOADER; the manifest ($MANIFEST_SYMBOL) is imported and referenced inside the loader -> add new keys to the manifest, do not clobber the loader"
|
|
138
|
+
exit 0
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# Registered + booted, but the manifest reach into the loader could not be
|
|
142
|
+
# confirmed. Heuristic textual check, so treat exit 3 as "verify, do not clobber":
|
|
143
|
+
# the manifest may be wired through a form this script cannot see (a factory, a
|
|
144
|
+
# variable, a spread, or a loader class this check did not detect). Report the
|
|
145
|
+
# specific gap for the human/agent to confirm before editing.
|
|
146
|
+
MISSING=""
|
|
147
|
+
if [ -z "$LOADER" ]; then
|
|
148
|
+
MISSING="no TranslateLoader subclass detected (the loader may use a factory/useFactory this check cannot see)"
|
|
149
|
+
else
|
|
150
|
+
MANIFEST_IMPORT_ANY=$(grep -rhnE '^[[:space:]]*import[^A-Za-z0-9_].*(label-manifest|labelManifest)' \
|
|
151
|
+
"$SRC_DIR" --include='*.ts' 2>/dev/null | head -1 || true)
|
|
152
|
+
if [ -z "$MANIFEST_IMPORT_ANY" ]; then
|
|
153
|
+
MISSING="the manifest is not imported into the loader file ($LOADER)"
|
|
154
|
+
else
|
|
155
|
+
MISSING="could not confirm the manifest is referenced inside the loader ($LOADER) (it may be imported but unused, or reached via a variable this check cannot see)"
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
158
|
+
echo "i18n wired: ngx-translate registered in $REGISTERED and loaded at boot ($BOOTED); but $MISSING -> verify by hand; if it genuinely dangles, reconcile (import the manifest AND iterate it in the loader's getTranslation) without clobbering"
|
|
159
|
+
exit 3
|