@salesforce/afv-skills 1.34.0 → 1.35.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.
Files changed (67) hide show
  1. package/package.json +1 -1
  2. package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +239 -0
  3. package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +21 -0
  4. package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +90 -0
  5. package/skills/automation-sandbox-post-copy-config-generate/examples/sample_sop_excerpt.md +31 -0
  6. package/skills/automation-sandbox-post-copy-config-generate/examples/sample_sop_to_config.json +50 -0
  7. package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +76 -0
  8. package/skills/automation-sandbox-post-copy-config-generate/references/sop_parsing_patterns.md +157 -0
  9. package/skills/automation-sandbox-post-copy-config-generate/references/source_format_handling.md +230 -0
  10. package/skills/dx-apexguru-scan/SKILL.md +403 -0
  11. package/skills/dx-apexguru-scan/examples/README.md +54 -0
  12. package/skills/dx-apexguru-scan/examples/sample-decoded-summary.json +176 -0
  13. package/skills/dx-apexguru-scan/examples/sample-full-no-runtime-response.json +26 -0
  14. package/skills/dx-apexguru-scan/examples/sample-succeeded-response.json +15 -0
  15. package/skills/dx-apexguru-scan/references/api-reference.md +81 -0
  16. package/skills/dx-apexguru-scan/references/authentication.md +134 -0
  17. package/skills/dx-apexguru-scan/references/error-handling.md +56 -0
  18. package/skills/dx-apexguru-scan/references/violation-catalog.md +28 -0
  19. package/skills/dx-apexguru-scan/scripts/build-zip.sh +87 -0
  20. package/skills/dx-apexguru-scan/scripts/decode-report.js +389 -0
  21. package/skills/dx-apexguru-scan/scripts/resolve-token.sh +151 -0
  22. package/skills/dx-apexguru-scan/scripts/run-scan.sh +153 -0
  23. package/skills/dx-apexguru-scan/scripts/scan.sh +96 -0
  24. package/skills/dx-apexguru-scan/scripts/validate-token.js +121 -0
  25. package/skills/dx-devops-pipeline-manage/SKILL.md +263 -0
  26. package/skills/dx-devops-pipeline-manage/examples/common-workflows.md +177 -0
  27. package/skills/dx-devops-pipeline-manage/references/cli-commands.md +298 -0
  28. package/skills/dx-devops-pipeline-manage/references/parsing-patterns.md +134 -0
  29. package/skills/dx-devops-pipeline-manage/scripts/check-activation-ready.sh +34 -0
  30. package/skills/dx-devops-pipeline-manage/scripts/validate-org-type.sh +17 -0
  31. package/skills/dx-devops-pipeline-manage/scripts/verify-operation.sh +82 -0
  32. package/skills/dx-devops-promote/SKILL.md +214 -0
  33. package/skills/dx-devops-promote/examples/promotion-workflows.md +212 -0
  34. package/skills/dx-devops-promote/references/cli-commands.md +303 -0
  35. package/skills/experience-lwc-base-components-integrate/SKILL.md +176 -0
  36. package/skills/experience-lwc-base-components-integrate/references/lbc-expert-guidance.md +127 -0
  37. package/skills/experience-lwc-base-components-integrate/references/lightning-component-index.md +179 -0
  38. package/skills/experience-lwc-base-components-integrate/references/lightning-components.md +5429 -0
  39. package/skills/experience-lwc-base-components-integrate/scripts/extract-component-docs.sh +61 -0
  40. package/skills/experience-lwc-rtl-validate/SKILL.md +149 -0
  41. package/skills/experience-lwc-rtl-validate/references/rtl-expert.md +892 -0
  42. package/skills/experience-lwc-rtl-validate/scripts/scan-rtl-css.sh +206 -0
  43. package/skills/experience-lwc-typescript-migrate/SKILL.md +207 -0
  44. package/skills/experience-lwc-typescript-migrate/assets/dts-template.ts +15 -0
  45. package/skills/experience-lwc-typescript-migrate/assets/type-patterns.ts +44 -0
  46. package/skills/experience-lwc-typescript-migrate/scripts/find-consumers.sh +128 -0
  47. package/skills/experience-ui-bundle-localize/SKILL.md +323 -0
  48. package/skills/experience-ui-bundle-localize/references/gotchas.md +249 -0
  49. package/skills/experience-ui-bundle-localize/references/i18n-setup.md +169 -0
  50. package/skills/experience-ui-bundle-localize/references/interpolation.md +311 -0
  51. package/skills/experience-ui-bundle-localize/references/label-xml.md +282 -0
  52. package/skills/experience-ui-bundle-localize/references/verifying.md +219 -0
  53. package/skills/experience-ui-bundle-localize/scripts/check-i18n-wired.sh +195 -0
  54. package/skills/experience-ui-bundle-localize/scripts/check-manifest-registered.sh +100 -0
  55. package/skills/experience-ui-bundle-localize/scripts/check-org-api-version.sh +40 -0
  56. package/skills/experience-ui-bundle-localize/scripts/detect-bundle-type.sh +57 -0
  57. package/skills/platform-custom-lightning-type-generate/SKILL.md +3 -0
  58. package/skills/platform-custom-lightning-type-generate/assets/primitive-types-and-constraints.md +1 -1
  59. package/skills/platform-mcp-tool-widget-coordinate/SKILL.md +250 -0
  60. package/skills/platform-mcp-tool-widget-coordinate/examples/action-name-source-prompt.md +74 -0
  61. package/skills/platform-mcp-tool-widget-coordinate/examples/apex-invocable-source-prompt.md +90 -0
  62. package/skills/platform-mcp-tool-widget-coordinate/examples/nested-object-source-prompt.md +191 -0
  63. package/skills/platform-mcp-tool-widget-coordinate/examples/pasted-tool-output-prompt.md +85 -0
  64. package/skills/platform-mcp-tool-widget-coordinate/references/build-plan-format.md +74 -0
  65. package/skills/platform-mcp-tool-widget-coordinate/references/mcp-tool-output-discovery.md +184 -0
  66. package/skills/platform-mcp-tool-widget-coordinate/references/two-clt-modeling.md +128 -0
  67. package/skills/platform-mcp-tool-widget-coordinate/references/validation-gates.md +181 -0
@@ -0,0 +1,169 @@
1
+ # i18n Setup: the two files you write
2
+
3
+ You write two files to set up i18n in a React UI Bundle. The Platform SDK provides the runtime plumbing (detector, backend, context fetch); you just wire it into i18next.
4
+
5
+ ---
6
+
7
+ ## File 1: `src/i18n/index.ts` (the init wiring)
8
+
9
+ This is the only "glue" you write. It connects the SDK's i18n pieces to i18next.
10
+
11
+ ```typescript
12
+ import { createDataSDK } from "@salesforce/platform-sdk";
13
+ import {
14
+ createSalesforceDetector,
15
+ fetchI18nContext,
16
+ SalesforceBackend,
17
+ } from "@salesforce/platform-sdk/i18n";
18
+ import i18next from "i18next";
19
+ import ChainedBackend from "i18next-chained-backend";
20
+ import LocalStorageBackend from "i18next-localstorage-backend";
21
+ import { initReactI18next } from "react-i18next";
22
+ import { labelManifest } from "./label-manifest";
23
+
24
+ export async function initI18n() {
25
+ const dataSDK = await createDataSDK();
26
+ const ctx = await fetchI18nContext(dataSDK);
27
+
28
+ // Tell the browser the user's language + text direction (RTL support).
29
+ document.documentElement.dir = ctx.dir;
30
+ document.documentElement.lang = ctx.lang;
31
+
32
+ await i18next
33
+ .use(ChainedBackend)
34
+ .use(createSalesforceDetector(dataSDK))
35
+ .use(initReactI18next)
36
+ .init({
37
+ fallbackLng: "en", // untranslated keys fall back to the English base value
38
+ defaultNS: "c", // "c" = your org's custom-label namespace
39
+ backend: {
40
+ backends: [LocalStorageBackend, SalesforceBackend],
41
+ backendOptions: [
42
+ { expirationTime: 86400000 }, // cache labels in localStorage for a day
43
+ { dataSDK, labelManifest }, // fetch the rest over GraphQL
44
+ ],
45
+ },
46
+ interpolation: {
47
+ // escapeValue: false is correct for React: React already escapes JSX
48
+ // output. Do NOT feed interpolated label output into
49
+ // dangerouslySetInnerHTML; that bypasses React's escaping and, with this
50
+ // setting, is an XSS vector when a label interpolates user-controlled
51
+ // text. Render labels as normal JSX (`{t(...)}`).
52
+ escapeValue: false,
53
+ prefix: "{", // Salesforce labels interpolate with {0}, {1}, …
54
+ suffix: "}",
55
+ },
56
+ });
57
+ }
58
+ ```
59
+
60
+ **Call it once at boot**, before mounting your app:
61
+
62
+ ```typescript
63
+ // src/index.tsx
64
+ import { initI18n } from "./i18n";
65
+
66
+ initI18n().then(() => {
67
+ // mount app here
68
+ root.render(<App />);
69
+ });
70
+ ```
71
+
72
+ ---
73
+
74
+ ## File 2: `src/i18n/label-manifest.ts` (the list of labels your app uses)
75
+
76
+ This tells i18next which labels to fetch at boot.
77
+
78
+ ```typescript
79
+ export const labelManifest = [
80
+ "c:Welcome_Text",
81
+ "c:Save_Button",
82
+ "c:Save_Failed_Message",
83
+ // one entry per label, "namespace:Key"
84
+ ];
85
+ ```
86
+
87
+ **Format:** `"<namespace>:<Key>"`
88
+ - `c` = custom labels in your org
89
+ - `Key` = the `<fullName>` from your `CustomLabels.labels-meta.xml`
90
+
91
+ The manifest is how i18next knows what to fetch. An **unregistered key fails silently**: it renders as its own literal name (e.g., `"Welcome_Text"` instead of "Welcome") with no console warning. Always keep the manifest in sync with your `t()` calls.
92
+
93
+ ---
94
+
95
+ ## Dependencies
96
+
97
+ Install these first (Step 4 of the main workflow):
98
+
99
+ ```bash
100
+ npm install i18next react-i18next i18next-chained-backend i18next-localstorage-backend
101
+ ```
102
+
103
+ Use `@salesforce/platform-sdk` **≥11.42.1**. The `@salesforce/platform-sdk/i18n` subpath (`SalesforceBackend`, `createSalesforceDetector`, `fetchI18nContext`) has existed since 11.4.1, and 11.7.0 added `reloadI18nContext` for refreshing the cached label context. 11.42.1 is the validated floor because it carries the fix that batches a namespace into 100-name-or-fewer queries. Without it, a manifest of more than 100 labels in one namespace hits the `uiapi.platform.labels` limit and the whole namespace read fails. Keep the SDK's siblings (`@salesforce/vite-plugin-ui-bundle`, `@salesforce/ui-bundle`) on the same version.
104
+
105
+ Known-good companion versions:
106
+ - `i18next` **^24.2.2**
107
+ - `react-i18next` **^15.5.1**
108
+ - `i18next-chained-backend` **^4.6.2**
109
+ - `i18next-localstorage-backend` **^4.2.0**
110
+
111
+ ---
112
+
113
+ ## What you DON'T write
114
+
115
+ The Platform SDK ships with:
116
+ - `createSalesforceDetector`: reads the user's language from the org
117
+ - `SalesforceBackend`: fetches labels over GraphQL
118
+ - `fetchI18nContext`: gets language, locale, text direction, currency
119
+
120
+ If you see an older example that vendors `salesforce-detector.ts` or `salesforce-backend.ts` into `src/`, it predates the SDK's i18n export. You no longer copy those files in; just import from `@salesforce/platform-sdk/i18n`.
121
+
122
+ ---
123
+
124
+ ## How it works at boot
125
+
126
+ 1. Bundle loads, `initI18n()` runs
127
+ 2. `createDataSDK()` initializes the SDK
128
+ 3. `fetchI18nContext()` queries the org for the user's language/locale/direction
129
+ 4. `SalesforceBackend` reads the manifest and issues a GraphQL query per namespace:
130
+ ```graphql
131
+ query LoadLabels {
132
+ uiapi {
133
+ platform {
134
+ labels(namespace: "c", names: ["Welcome_Text", "Save_Button", ...]) {
135
+ name
136
+ value
137
+ resolvedLocale
138
+ }
139
+ }
140
+ }
141
+ }
142
+ ```
143
+ 5. Platform returns labels at the user's resolved locale
144
+ 6. i18next caches them (in memory + localStorage)
145
+ 7. React mounts; components call `t()`; lookups hit the cache
146
+
147
+ The two backends chain: `LocalStorageBackend` serves cached labels (24-hour expiry), and `SalesforceBackend` fetches misses over GraphQL. This makes subsequent loads fast.
148
+
149
+ ---
150
+
151
+ ## Namespace note
152
+
153
+ `defaultNS: "c"` means components can call `t("Welcome_Text")` instead of `t("c:Welcome_Text")`; the namespace is implicit. If you're loading labels from multiple namespaces (e.g., framework-shipped labels like `LightningDatatable`), you'd specify the namespace in the `useTranslation` hook:
154
+
155
+ ```typescript
156
+ const { t } = useTranslation("c"); // custom labels
157
+ const { t: tFw } = useTranslation("LightningDatatable"); // framework labels
158
+ ```
159
+
160
+ For most bundles, a single `"c"` namespace is all you need.
161
+
162
+ ---
163
+
164
+ ## Related
165
+
166
+ - [label-xml.md](label-xml.md): the Custom Labels metadata XML shape
167
+ - [interpolation.md](interpolation.md): how `{0}/{1}` placeholders work
168
+ - [verifying.md](verifying.md): the serve/verify flow
169
+ - [gotchas.md](gotchas.md): silent-fail traps to avoid
@@ -0,0 +1,311 @@
1
+ # Interpolation: how `{0}`, `{1}` placeholders work
2
+
3
+ Salesforce Custom Labels use **positional interpolation**: `{0}`, `{1}`, `{N}` placeholders that get replaced with runtime values at render time. This is the same substitution syntax used by Apex `String.format` and LWC `@salesforce/label`, so one label works across all three frameworks.
4
+
5
+ ---
6
+
7
+ ## TLDR
8
+
9
+ It's a string-replace. i18next finds every `{0}`, `{1}`, `{N}` in the cached label content and substitutes the value at that key from the second argument of `t()`.
10
+
11
+ ---
12
+
13
+ ## Basic example
14
+
15
+ **Label:**
16
+ ```xml
17
+ <labels>
18
+ <fullName>Greeting</fullName>
19
+ <value>Hello, {0}</value>
20
+ </labels>
21
+ ```
22
+
23
+ **Component:**
24
+ ```typescript
25
+ const { t } = useTranslation("c");
26
+ const userName = "Tosin";
27
+ return <h1>{t("Greeting", { 0: userName })}</h1>;
28
+ // → "Hello, Tosin"
29
+ ```
30
+
31
+ ---
32
+
33
+ ## How it works
34
+
35
+ 1. At boot, the Platform SDK's `SalesforceBackend` fetches the label content from the org via GraphQL
36
+ 2. i18next caches it: `{ "c:Greeting": "Hello, {0}" }`
37
+ 3. At render time, the component calls `t("Greeting", { 0: "Tosin" })`
38
+ 4. i18next's interpolator does:
39
+ ```javascript
40
+ template = "Hello, {0}";
41
+ template.replace("{0}", "Tosin");
42
+ // → "Hello, Tosin"
43
+ ```
44
+ 5. The resulting string renders to the DOM
45
+
46
+ The configuration that makes this work is in `src/i18n/index.ts`:
47
+
48
+ ```typescript
49
+ interpolation: {
50
+ escapeValue: false,
51
+ prefix: "{", // scan for {X} placeholders
52
+ suffix: "}",
53
+ },
54
+ ```
55
+
56
+ i18next ships with `{{name}}`-style named interpolation by default. We override it to use Salesforce's `{0}` positional style so one label works across Apex, LWC, and React.
57
+
58
+ ---
59
+
60
+ ## Multiple placeholders
61
+
62
+ **Label:**
63
+ ```xml
64
+ <value>Failed to save {0}: {1}</value>
65
+ ```
66
+
67
+ **Component:**
68
+ ```typescript
69
+ t("Save_Failed", { 0: "Account", 1: "Permission denied" });
70
+ // → "Failed to save Account: Permission denied"
71
+ ```
72
+
73
+ Order in the object doesn't matter; the placeholder **number** is what binds:
74
+
75
+ ```typescript
76
+ t("Save_Failed", { 1: "Permission denied", 0: "Account" }); // same result
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Same placeholder used twice
82
+
83
+ Salesforce labels sometimes reuse the same positional placeholder:
84
+
85
+ **Label:**
86
+ ```xml
87
+ <value>User {0} cannot edit {0}'s own profile</value>
88
+ ```
89
+
90
+ **Component:**
91
+ ```typescript
92
+ t("Profile_Edit_Error", { 0: "tosin" });
93
+ // → "User tosin cannot edit tosin's own profile"
94
+ ```
95
+
96
+ This is common when sentence structure differs across languages and the same value appears in different grammatical roles.
97
+
98
+ ---
99
+
100
+ ## Numeric and date values
101
+
102
+ Placeholders accept any JavaScript value. The value's `toString()` is what gets interpolated:
103
+
104
+ ```typescript
105
+ // Numbers
106
+ t("Record_Count", { 0: 42, 1: 100 });
107
+ // label: "Showing {0} of {1} records" → "Showing 42 of 100 records"
108
+
109
+ // Dates (default JS toString, not localized)
110
+ t("Last_Modified", { 0: new Date() });
111
+ // label: "Last modified {0}" → "Last modified Mon Jul 07 2026 14:23:00 GMT-0700"
112
+ ```
113
+
114
+ **Note:** This does **not** apply locale-aware formatting (e.g., `1.000,00` for German number formatting). For that, you'd use `Intl.NumberFormat` / `Intl.DateTimeFormat` separately and pass the **formatted string** as the placeholder value:
115
+
116
+ ```typescript
117
+ const formattedCount = new Intl.NumberFormat(locale).format(count);
118
+ t("Record_Count", { 0: formattedCount, 1: total });
119
+ ```
120
+
121
+ The Platform SDK's `fetchI18nContext()` gives you `ctx.locale` and `ctx.currency` for feeding `Intl` formatters.
122
+
123
+ ---
124
+
125
+ ## Translations must preserve placeholders
126
+
127
+ **English:**
128
+ ```xml
129
+ <value>Failed to save {0}: {1}</value>
130
+ ```
131
+
132
+ **Spanish translation:**
133
+ ```xml
134
+ <label>Error al guardar {0}: {1}</label>
135
+ ```
136
+
137
+ The placeholders stay as `{0}`, `{1}`; they can move (grammar might flip the order), but the **numbers must match**. If the Spanish translation says `{A}` or drops `{1}`, the substitution breaks.
138
+
139
+ This is why Translation Workbench is valuable: it shows translators the placeholders and warns if they're missing.
140
+
141
+ ---
142
+
143
+ ## Failure modes
144
+
145
+ ### Missing placeholder value
146
+
147
+ **Label:** `"Hello, {0} from {1}"`
148
+
149
+ **Call:**
150
+ ```typescript
151
+ t("Greeting", { 0: "Tosin" }); // forgot {1}
152
+ // → "Hello, Tosin from {1}" (literal placeholder leaks)
153
+ ```
154
+
155
+ There's no runtime error or console warning; the unsubstituted placeholder just renders as-is. This is a visual bug, not a crash.
156
+
157
+ **Fix:** Always pass every placeholder the label expects. PR review catches this today (no build-time validation yet).
158
+
159
+ ### Missing label entirely
160
+
161
+ ```typescript
162
+ t("Nonexistent_Key");
163
+ // → "Nonexistent_Key" (literal key string)
164
+ ```
165
+
166
+ This is the **unregistered manifest key** trap (see [gotchas.md](gotchas.md)): the label wasn't fetched, so i18next has nothing to interpolate.
167
+
168
+ ---
169
+
170
+ ## Worked examples by complexity
171
+
172
+ ### 1. No interpolation (most labels)
173
+
174
+ **Label:** `"Welcome"`
175
+
176
+ **Call:** `t("Welcome_Text")`
177
+
178
+ **Result:** `"Welcome"`
179
+
180
+ Most labels are just lookups, no placeholders.
181
+
182
+ ---
183
+
184
+ ### 2. Single value
185
+
186
+ **Label:** `"You have {0} items"`
187
+
188
+ **Call:** `t("Item_Count", { 0: 5 })`
189
+
190
+ **Result:** `"You have 5 items"`
191
+
192
+ ---
193
+
194
+ ### 3. Error with context
195
+
196
+ **Label:** `"{0} failed at step {1} for record {2}: {3}"`
197
+
198
+ **Call:**
199
+ ```typescript
200
+ t("Pipeline_Error", {
201
+ 0: "OnboardingPipeline",
202
+ 1: err.step,
203
+ 2: record.name,
204
+ 3: err.message,
205
+ });
206
+ ```
207
+
208
+ **Result:** `"OnboardingPipeline failed at step ValidateAccount for record Acme Corp: Missing tax ID"`
209
+
210
+ One label, reusable across pipelines. Translated once. Engineers in any framework call it the same way.
211
+
212
+ ---
213
+
214
+ ### 4. Pluralization (leveraging i18next on top of positional)
215
+
216
+ i18next has native pluralization support. For Salesforce labels, you author separate labels for each plural form:
217
+
218
+ **Labels:**
219
+ ```xml
220
+ <labels>
221
+ <fullName>Item_Count_one</fullName>
222
+ <value>You have {0} item</value>
223
+ </labels>
224
+ <labels>
225
+ <fullName>Item_Count_other</fullName>
226
+ <value>You have {0} items</value>
227
+ </labels>
228
+ ```
229
+
230
+ **Manifest:**
231
+ ```typescript
232
+ export const labelManifest = [
233
+ "c:Item_Count_one",
234
+ "c:Item_Count_other",
235
+ ];
236
+ ```
237
+
238
+ **Component:**
239
+ ```typescript
240
+ t("Item_Count", { 0: count, count: count });
241
+ // count = 1 → "You have 1 item"
242
+ // count = 5 → "You have 5 items"
243
+ ```
244
+
245
+ i18next reads the `count` arg and picks the `_one` or `_other` suffix per the English plural rules. The `0` arg drives substitution. The suffixes must be **lowercase** (`_one`, `_other`): i18next derives them from the browser's `Intl.PluralRules` categories, which are lowercase, and appends them verbatim to the key. A PascalCase `_One` label would never be found, and the label would render as its literal key name.
246
+
247
+ Languages with more plural forms (Russian, Polish, Arabic) need more label variants, `_zero`, `_few`, `_many`, etc. Author them as separate Custom Labels with the appropriate lowercase suffixes.
248
+
249
+ ---
250
+
251
+ ## Why positional (`{0}`) instead of named (`{name}`)?
252
+
253
+ **Portability.** Salesforce has three UI frameworks:
254
+ - Apex (backend): `String.format(label, [arg0, arg1])`
255
+ - LWC: `@salesforce/label/c.Save_Failed` with `{0}/{1}`
256
+ - React UI Bundles: `t("Save_Failed", { 0: ..., 1: ... })`
257
+
258
+ All three use the same Custom Labels metadata. If React used `{name}` syntax, the same logical string would need to be authored **twice**, once for Apex/LWC with `{0}`, once for React with `{name}`. Translators would maintain two formats. Engineers couldn't reuse strings across frameworks.
259
+
260
+ With the bridge (`prefix: "{", suffix: "}"`), **one Custom Label serves all three**. Portability is the win.
261
+
262
+ ---
263
+
264
+ ## What the bridge doesn't do (not in MVP)
265
+
266
+ - **Date/number formatting**: `{ 0: new Date() }` produces JS's default `toString`, not localized formatting. Wiring up `Intl.DateTimeFormat` / `Intl.NumberFormat` via i18next's `interpolation.format` callback is a separate design decision (not in the MVP scope).
267
+ - **Type safety**: nothing checks that `Save_Failed` actually has 2 placeholders at build time. A build-time extractor could close this gap (future work).
268
+ - **Escape for literal `{0}` in content**: i18next has escape options if labels genuinely need to contain literal curly braces. Edge case.
269
+
270
+ ---
271
+
272
+ ## Visual flow
273
+
274
+ ```text
275
+ ┌─────────────────────────────────────────────────┐
276
+ │ 1. Custom Label (authored in Salesforce): │
277
+ │ "Failed to save {0}: {1}" │
278
+ └─────────────────────────────────────────────────┘
279
+ ↓
280
+ GraphQL fetch at boot
281
+ ↓
282
+ ┌─────────────────────────────────────────────────┐
283
+ │ 2. Cached in i18next memory: │
284
+ │ { 'c:Save_Failed': "Failed to save {0}: {1}" }│
285
+ └─────────────────────────────────────────────────┘
286
+ ↓
287
+ Component calls t('Save_Failed',
288
+ { 0: 'Account',
289
+ 1: 'Permission denied' })
290
+ ↓
291
+ ┌─────────────────────────────────────────────────┐
292
+ │ 3. i18next's interpolator does: │
293
+ │ template = "Failed to save {0}: {1}" │
294
+ │ template.replace('{0}', 'Account') │
295
+ │ .replace('{1}', 'Permission denied') │
296
+ └─────────────────────────────────────────────────┘
297
+ ↓
298
+ ┌─────────────────────────────────────────────────┐
299
+ │ 4. Output: │
300
+ │ "Failed to save Account: Permission denied" │
301
+ └─────────────────────────────────────────────────┘
302
+ ```
303
+
304
+ ---
305
+
306
+ ## Related
307
+
308
+ - [i18n-setup.md](i18n-setup.md): the init file where the `prefix`/`suffix` are configured
309
+ - [label-xml.md](label-xml.md): how to author labels with placeholders
310
+ - [verifying.md](verifying.md): testing interpolated labels
311
+ - [gotchas.md](gotchas.md): silent-fail traps