@salesforce/b2c-dx-mcp 0.0.1 → 0.3.2

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 (48) hide show
  1. package/README.md +422 -29
  2. package/bin/run.cmd +3 -0
  3. package/bin/run.js +27 -0
  4. package/content/auth.md +62 -0
  5. package/content/components.md +123 -0
  6. package/content/config.md +180 -0
  7. package/content/data-fetching.md +323 -0
  8. package/content/extensions.md +80 -0
  9. package/content/i18n.md +121 -0
  10. package/content/page-designer.md +86 -0
  11. package/content/performance.md +80 -0
  12. package/content/pitfalls.md +141 -0
  13. package/content/quick-reference.md +226 -0
  14. package/content/state-management.md +75 -0
  15. package/content/styling.md +51 -0
  16. package/content/testing.md +232 -0
  17. package/dist/commands/mcp.d.ts +110 -0
  18. package/dist/commands/mcp.js +333 -0
  19. package/dist/registry.d.ts +37 -0
  20. package/dist/registry.js +212 -0
  21. package/dist/server.d.ts +46 -0
  22. package/dist/server.js +98 -0
  23. package/dist/services.d.ts +168 -0
  24. package/dist/services.js +191 -0
  25. package/dist/tools/adapter.d.ts +201 -0
  26. package/dist/tools/adapter.js +220 -0
  27. package/dist/tools/cartridges/index.d.ts +20 -0
  28. package/dist/tools/cartridges/index.js +101 -0
  29. package/dist/tools/index.d.ts +17 -0
  30. package/dist/tools/index.js +25 -0
  31. package/dist/tools/mrt/index.d.ts +20 -0
  32. package/dist/tools/mrt/index.js +101 -0
  33. package/dist/tools/pwav3/index.d.ts +13 -0
  34. package/dist/tools/pwav3/index.js +78 -0
  35. package/dist/tools/scapi/index.d.ts +9 -0
  36. package/dist/tools/scapi/index.js +68 -0
  37. package/dist/tools/storefrontnext/developer-guidelines.d.ts +9 -0
  38. package/dist/tools/storefrontnext/developer-guidelines.js +140 -0
  39. package/dist/tools/storefrontnext/index.d.ts +13 -0
  40. package/dist/tools/storefrontnext/index.js +83 -0
  41. package/dist/utils/constants.d.ts +16 -0
  42. package/dist/utils/constants.js +18 -0
  43. package/dist/utils/index.d.ts +7 -0
  44. package/dist/utils/index.js +16 -0
  45. package/dist/utils/types.d.ts +45 -0
  46. package/dist/utils/types.js +7 -0
  47. package/oclif.manifest.json +377 -0
  48. package/package.json +123 -7
@@ -0,0 +1,180 @@
1
+ # Configuration Management
2
+
3
+ ## Overview
4
+
5
+ All configuration is centralized in `config.server.ts` with environment variable overrides via `.env` files. The configuration system provides type-safe access to app settings with automatic parsing and validation.
6
+
7
+ ## Required Variables
8
+
9
+ Copy `.env.default` to `.env` and set these required Commerce Cloud credentials:
10
+
11
+ ```bash
12
+ PUBLIC__app__commerce__api__clientId=your-client-id
13
+ PUBLIC__app__commerce__api__organizationId=your-org-id
14
+ PUBLIC__app__commerce__api__siteId=your-site-id
15
+ PUBLIC__app__commerce__api__shortCode=your-short-code
16
+ PUBLIC__app__defaultSiteId=your-site-id
17
+ PUBLIC__app__commerce__sites='[{"id":"your-site-id","defaultLocale":"en-US","defaultCurrency":"USD","supportedLocales":[{"id":"en-US","preferredCurrency":"USD"}],"supportedCurrencies":["USD"]}]'
18
+ ```
19
+
20
+ **Note:** The `commerce.sites` array defines your site configuration including locales, currencies, and supported options. See `.env.default` for a complete example with multiple locales and currencies.
21
+
22
+ ## Adding Configuration
23
+
24
+ 1. **Define type in `src/config/schema.ts`**:
25
+
26
+ ```typescript
27
+ export type Config = {
28
+ app: {
29
+ myFeature: {
30
+ enabled: boolean;
31
+ maxItems: number;
32
+ };
33
+ };
34
+ };
35
+ ```
36
+
37
+ 2. **Add defaults in `config.server.ts`**:
38
+
39
+ ```typescript
40
+ export default defineConfig({
41
+ app: {
42
+ myFeature: {
43
+ enabled: false,
44
+ maxItems: 10,
45
+ },
46
+ },
47
+ });
48
+ ```
49
+
50
+ 3. **Override via environment variables**:
51
+
52
+ ```bash
53
+ PUBLIC__app__myFeature__enabled=true
54
+ PUBLIC__app__myFeature__maxItems=20
55
+ ```
56
+
57
+ ## Usage Patterns
58
+
59
+ **In React Components**:
60
+
61
+ ```typescript
62
+ import { useConfig } from '@/config';
63
+
64
+ export function MyComponent() {
65
+ const config = useConfig();
66
+
67
+ if (config.myFeature.enabled) {
68
+ const maxItems = config.myFeature.maxItems;
69
+ // Your feature code
70
+ }
71
+ }
72
+ ```
73
+
74
+ **In Server Loaders/Actions**:
75
+
76
+ ```typescript
77
+ import { getConfig } from '@/config';
78
+
79
+ export function loader({ context }: LoaderFunctionArgs) {
80
+ const config = getConfig(context);
81
+
82
+ if (config.myFeature.enabled) {
83
+ // Your loader code
84
+ }
85
+ }
86
+ ```
87
+
88
+ **In Client Loaders**:
89
+
90
+ ```typescript
91
+ import { getConfig } from '@/config';
92
+
93
+ export function clientLoader() {
94
+ const config = getConfig(); // No context needed - uses window.__APP_CONFIG__
95
+
96
+ if (config.myFeature.enabled) {
97
+ // Your loader code
98
+ }
99
+ }
100
+ ```
101
+
102
+ **Note:** `getConfig()` and `useConfig()` return `AppConfig` which is the `app` section of the full `Config` type. So you access properties directly (e.g., `config.myFeature.enabled`) without the `app` prefix.
103
+
104
+ ## Environment Variable Rules
105
+
106
+ Use the `PUBLIC__` prefix with double underscores (`__`) to set any config path:
107
+
108
+ ```bash
109
+ # Environment variable → Config path (in Config type) → Access via getConfig()/useConfig()
110
+ PUBLIC__app__commerce__sites='[...]' → config.app.commerce.sites → config.commerce.sites
111
+ PUBLIC__app__defaultSiteId=RefArchGlobal → config.app.defaultSiteId → config.defaultSiteId
112
+ PUBLIC__app__myFeature__enabled=true → config.app.myFeature.enabled → config.myFeature.enabled
113
+ ```
114
+
115
+ **Multi-site Configuration Example:**
116
+
117
+ ```bash
118
+ PUBLIC__app__commerce__sites='[
119
+ {
120
+ "id": "RefArchGlobal",
121
+ "defaultLocale": "en-US",
122
+ "defaultCurrency": "USD",
123
+ "supportedLocales": [
124
+ {"id": "en-US", "preferredCurrency": "USD"},
125
+ {"id": "de-DE", "preferredCurrency": "EUR"}
126
+ ],
127
+ "supportedCurrencies": ["USD", "EUR"]
128
+ }
129
+ ]'
130
+ ```
131
+
132
+ **Accessing Site Configuration:**
133
+
134
+ ```typescript
135
+ const config = getConfig(context);
136
+ const currentSite = config.commerce.sites[0]; // Get first site
137
+ const locale = currentSite.defaultLocale; // "en-US"
138
+ const currency = currentSite.defaultCurrency; // "USD"
139
+ ```
140
+
141
+ Values are automatically parsed (numbers, booleans, JSON arrays/objects).
142
+
143
+ Rules:
144
+ 1. **`PUBLIC__` prefix**: Exposed to browser (client-safe values)
145
+ 2. **No prefix**: Server-only (secrets, never exposed)
146
+ 3. **`__` separator**: Navigate nested paths (`PUBLIC__app__commerce__sites`)
147
+ 4. **Case-insensitive**: All casings work (normalized to match `config.server.ts`)
148
+ 5. **Auto-parsing**: Strings, numbers, booleans, JSON arrays/objects
149
+ 6. **Validation**: Paths must exist in `config.server.ts` (prevents typos)
150
+ 7. **Depth limit**: Maximum 10 levels deep (use JSON values for deeper nesting)
151
+ 8. **Path precedence**: More specific paths override less specific ones
152
+ 9. **Protected paths**: `app__engagement` cannot be overridden via environment variables
153
+ 10. **MRT limits**: Variable names max 512 characters, total PUBLIC__ values max 32KB
154
+
155
+ **Note:** Site configuration (locales, currencies) is now managed via `PUBLIC__app__commerce__sites` array instead of individual `PUBLIC__app__site__locale` variables. This enables multi-site support.
156
+
157
+ **Setting nested objects with JSON:**
158
+
159
+ ```bash
160
+ # Instead of multiple variables:
161
+ PUBLIC__app__myFeature__option1=value1
162
+ PUBLIC__app__myFeature__option2=value2
163
+
164
+ # Use a single JSON value:
165
+ PUBLIC__app__myFeature='{"option1":"value1","option2":"value2","nested":{"enabled":true}}'
166
+ ```
167
+
168
+ ## Security
169
+
170
+ ```bash
171
+ # ✅ Safe for client (PUBLIC__ prefix)
172
+ PUBLIC__app__commerce__api__clientId=abc123
173
+
174
+ # ✅ Server-only (no prefix)
175
+ COMMERCE_API_SLAS_SECRET=your-secret
176
+ ```
177
+
178
+ Read server-only secrets directly from `process.env` - never add to config.
179
+
180
+ **Reference:** See src/config/README.md for complete configuration documentation.
@@ -0,0 +1,323 @@
1
+ # Data Fetching Patterns
2
+
3
+ ## Loader Functions
4
+
5
+ **IMPORTANT**: This project **mandates server-only data loading**. Every UI route must only export a `loader` function.
6
+
7
+ ### Critical Rule: Synchronous Loaders for Streaming
8
+
9
+ **IMPORTANT**: Loaders should be **synchronous functions that return objects containing promises**, NOT async functions. This enables non-blocking page transitions and streaming SSR.
10
+
11
+ ```typescript
12
+ // ✅ CORRECT - Synchronous loader returning promises
13
+ export function loader({ context }: LoaderFunctionArgs): ProductPageData {
14
+ const clients = createApiClients(context);
15
+ return {
16
+ product: clients.shopperProducts.getProduct({...}), // Promise - streams
17
+ reviews: clients.shopperProducts.getReviews({...}), // Promise - streams
18
+ };
19
+ }
20
+
21
+ // ❌ AVOID - Async loader blocks page transitions
22
+ export async function loader({ context }: LoaderFunctionArgs): Promise<ProductPageData> {
23
+ const product = await clients.shopperProducts.getProduct({...}); // Blocks!
24
+ return { product };
25
+ }
26
+ ```
27
+
28
+ **Why this matters:**
29
+ - Async loaders with `await` **block the entire page transition** until all data resolves
30
+ - Synchronous loaders returning promises allow React to **stream data progressively**
31
+ - Each promise resolves independently, enabling granular Suspense boundaries
32
+ - Users see content as it becomes available, not all at once
33
+
34
+ **Behavior**:
35
+ - Initial load: Runs on server (SSR)
36
+ - Navigation: Runs on server (XHR/fetch to server)
37
+ - SCAPI requests always on MRT
38
+
39
+ ## Data Loading Strategies
40
+
41
+ ### Pattern 1: Awaited Data (Blocking)
42
+
43
+ ```typescript
44
+ // ⚠️ BLOCKS rendering until all data is ready
45
+ export async function loader({ params, context }: LoaderFunctionArgs) {
46
+ const clients = createApiClients(context);
47
+ return {
48
+ product: await clients.shopperProducts.getProduct({
49
+ params: { path: { id: params.productId } }
50
+ }).then(({ data }) => data)
51
+ };
52
+ }
53
+ ```
54
+
55
+ **Use when:** Critical data must be available before rendering (SEO, above-the-fold content)
56
+
57
+ ### Pattern 2: Deferred Data (Streaming)
58
+
59
+ ```typescript
60
+ // ✅ RECOMMENDED - Streams data progressively
61
+ export function loader({ params, context }: LoaderFunctionArgs) {
62
+ const clients = createApiClients(context);
63
+ return {
64
+ // Return promises directly - they'll stream to client
65
+ product: clients.shopperProducts.getProduct({
66
+ params: { path: { id: params.productId } }
67
+ }).then(({ data }) => data),
68
+
69
+ reviews: clients.shopperProducts.getReviews({
70
+ params: { path: { id: params.productId } }
71
+ }).then(({ data }) => data)
72
+ };
73
+ }
74
+ ```
75
+
76
+ **Use when:** Non-critical data can load after initial render
77
+
78
+ ### Pattern 3: Mixed Strategy
79
+
80
+ ```typescript
81
+ // ✅ BEST OF BOTH - Critical data awaited, rest streamed
82
+ export async function loader({ params, context }: LoaderFunctionArgs) {
83
+ const clients = createApiClients(context);
84
+
85
+ // Await critical data
86
+ const product = await clients.shopperProducts.getProduct({
87
+ params: { path: { id: params.productId } }
88
+ }).then(({ data }) => data);
89
+
90
+ return {
91
+ product, // Resolved
92
+ reviews: clients.shopperProducts.getReviews({
93
+ params: { path: { id: params.productId } }
94
+ }).then(({ data }) => data), // Streamed
95
+ recommendations: clients.shopperProducts.getRecommendations({
96
+ params: { path: { id: params.productId } }
97
+ }).then(({ data }) => data) // Streamed
98
+ };
99
+ }
100
+ ```
101
+
102
+ ## Action Functions
103
+
104
+ Handle mutations (form submissions, cart updates):
105
+
106
+ ```typescript
107
+ import {data, redirect} from 'react-router';
108
+
109
+ export async function action({request, context}: ActionFunctionArgs) {
110
+ const formData = await request.formData();
111
+ const productId = formData.get('productId') as string;
112
+
113
+ const clients = createApiClients(context);
114
+
115
+ try {
116
+ await clients.shopperBasketsV2.addItemToBasket({
117
+ params: {
118
+ path: {basketId},
119
+ body: {productId, quantity: 1},
120
+ },
121
+ });
122
+
123
+ return data({success: true});
124
+ } catch (error) {
125
+ return data({success: false, error: error.message}, {status: 400});
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## Interactive Data Fetching: useScapiFetcher
131
+
132
+ For on-demand, user-triggered data fetching (after page load), use the `useScapiFetcher` hook instead of loaders.
133
+
134
+ ### `loader` vs `useScapiFetcher`
135
+
136
+ | Aspect | `loader` | `useScapiFetcher` |
137
+ |--------|----------|-------------------|
138
+ | **When it runs** | Route navigation (page load) | On-demand (user interaction) |
139
+ | **Triggered by** | URL change | Component code (useEffect, button click) |
140
+ | **Data availability** | Before/during component render (streamed) | After component mounts |
141
+ | **Execution context** | Server (MRT) | Triggers server route |
142
+ | **Use case** | Initial page data | Dynamic, interactive fetching |
143
+
144
+ ### How `useScapiFetcher` Works
145
+
146
+ ```text
147
+ Component calls useScapiFetcher()
148
+
149
+ Hook builds URL: /resource/api/client/{encoded-params}
150
+
151
+ fetcher.load() or fetcher.submit()
152
+
153
+ resource.api.client.$resource.ts loader/action runs ON SERVER
154
+
155
+ createApiClients(context) makes SCAPI call (server-side)
156
+
157
+ JSON response returned to component
158
+ ```
159
+
160
+ **Important:** Even though you call `useScapiFetcher` from the browser, the actual SCAPI requests still happen **on the server** through the resource route, keeping credentials secure.
161
+
162
+ ### Example: Search Suggestions
163
+
164
+ ```typescript
165
+ import { useScapiFetcher } from '@/hooks/use-scapi-fetcher';
166
+ import { useMemo, useCallback } from 'react';
167
+
168
+ export function useSearchSuggestions({ q, limit, currency }) {
169
+ // Prepare SCAPI parameters
170
+ const parameters = useMemo(
171
+ () => ({
172
+ params: {
173
+ query: { q, limit, currency }
174
+ }
175
+ }),
176
+ [q, limit, currency]
177
+ );
178
+
179
+ // Hook automatically routes to server
180
+ const fetcher = useScapiFetcher(
181
+ 'shopperSearch', // SCAPI client
182
+ 'getSearchSuggestions', // Method name
183
+ parameters // Parameters
184
+ );
185
+
186
+ const refetch = useCallback(async () => {
187
+ await fetcher.load(); // Triggers server request
188
+ }, [fetcher]);
189
+
190
+ return {
191
+ data: fetcher.data,
192
+ isLoading: fetcher.state === 'loading',
193
+ refetch
194
+ };
195
+ }
196
+ ```
197
+
198
+ ### When to Use Each Approach
199
+
200
+ | Scenario | Use |
201
+ |----------|-----|
202
+ | Load product data when visiting `/product/123` | `loader` |
203
+ | Load checkout data | `loader` |
204
+ | Search suggestions as user types | `useScapiFetcher` |
205
+ | Update customer profile in modal | `useScapiFetcher` |
206
+ | Load recommendations after page loads | `useScapiFetcher` |
207
+ | Fetch bonus products when modal opens | `useScapiFetcher` |
208
+ | Infinite scroll / Load more | `useScapiFetcher` |
209
+
210
+ ### Timeline Comparison
211
+
212
+ ```text
213
+ ┌─────────────────────────────────────────────────────────────────┐
214
+ │ loader (Server) │
215
+ ├─────────────────────────────────────────────────────────────────┤
216
+ │ User clicks link → Server loader() → Stream data → Page render │
217
+ │ │
218
+ │ Timeline: [navigate] → [server fetch] → [stream to client] │
219
+ │ │
220
+ │ Data available: Streamed during render via Suspense │
221
+ └─────────────────────────────────────────────────────────────────┘
222
+
223
+ ┌─────────────────────────────────────────────────────────────────┐
224
+ │ useScapiFetcher │
225
+ ├─────────────────────────────────────────────────────────────────┤
226
+ │ Page loads → Component mounts → User types → fetcher.load() │
227
+ │ │
228
+ │ Timeline: [render] → [user action] → [fetch] → [re-render] │
229
+ │ │
230
+ │ Data available: AFTER user action, component re-renders │
231
+ └─────────────────────────────────────────────────────────────────┘
232
+ ```
233
+
234
+ ## API Client Usage
235
+
236
+ Always use `createApiClients(context)`:
237
+
238
+ ```typescript
239
+ import { createApiClients } from '@/lib/api-clients';
240
+
241
+ export function loader({ context }: LoaderFunctionArgs) {
242
+ const clients = createApiClients(context);
243
+
244
+ // All SCAPI clients with full type safety:
245
+ clients.shopperProducts.getProduct({...});
246
+ clients.shopperCustomers.getCustomer({...});
247
+ clients.shopperBasketsV2.getBasket({...});
248
+ clients.shopperSearch.productSearch({...});
249
+ clients.shopperOrders.getOrder({...});
250
+ }
251
+ ```
252
+
253
+ ## Parallel vs Sequential
254
+
255
+ ```typescript
256
+ // ✅ GOOD - Parallel requests
257
+ export function loader({ context }: LoaderFunctionArgs) {
258
+ const clients = createApiClients(context);
259
+
260
+ return {
261
+ product: clients.shopperProducts.getProduct({...}),
262
+ reviews: clients.shopperProducts.getReviews({...}),
263
+ recommendations: clients.shopperProducts.getRecommendations({...})
264
+ };
265
+ // All three requests start simultaneously
266
+ }
267
+
268
+ // ❌ BAD - Sequential requests
269
+ export async function loader({ context }: LoaderFunctionArgs) {
270
+ const clients = createApiClients(context);
271
+
272
+ const product = await clients.shopperProducts.getProduct({...});
273
+ const reviews = await clients.shopperProducts.getReviews({...});
274
+ const recommendations = await clients.shopperProducts.getRecommendations({...});
275
+
276
+ return { product, reviews, recommendations };
277
+ // Each request waits for the previous to complete
278
+ }
279
+ ```
280
+
281
+ ## Understanding Data Flow
282
+
283
+ ### Initial Page Load (SSR)
284
+
285
+ ```text
286
+ Browser → MRT Server
287
+
288
+ loader() runs on server
289
+
290
+ SCAPI requests on MRT
291
+
292
+ HTML response → Browser
293
+ ```
294
+
295
+ **Key characteristics:**
296
+ - The `loader()` runs on the server
297
+ - SCAPI requests happen server-side (direct in production, proxied in dev)
298
+ - Full HTML is returned to browser
299
+ - Client hydrates the HTML
300
+
301
+ ### Subsequent Navigation (SPA)
302
+
303
+ All routes use server `loader` for both SSR and SPA navigation:
304
+
305
+ ```text
306
+ User clicks link → React Router intercepts
307
+
308
+ Browser makes fetch() to server
309
+
310
+ MRT Server receives request
311
+
312
+ Same loader() runs on server
313
+
314
+ SCAPI requests on MRT
315
+
316
+ JSON response → Browser
317
+
318
+ React updates DOM
319
+ ```
320
+
321
+ **Key Point:** The loader function code is identical for both SSR and SPA navigation. The only difference is the response format (HTML vs JSON). This is why SCAPI credentials stay secure and MRT orchestration works consistently.
322
+
323
+ **Reference:** See README-DATA.md for complete data fetching documentation.
@@ -0,0 +1,80 @@
1
+ # Extension Development
2
+
3
+ ## Structure
4
+
5
+ ```
6
+ src/extensions/my-extension/
7
+ ├── plugin-config.json # Plugin configuration
8
+ ├── components/ # Extension components
9
+ ├── routes/ # Extension routes
10
+ ├── locales/ # Extension translations
11
+ └── providers/ # Extension providers
12
+ ```
13
+
14
+ ## Plugin Configuration
15
+
16
+ **Insert component into plugin point**:
17
+
18
+ ```json
19
+ {
20
+ "components": [
21
+ {
22
+ "pluginId": "header.before.cart",
23
+ "path": "extensions/my-extension/components/badge.tsx",
24
+ "order": 0
25
+ }
26
+ ],
27
+ "contextProviders": [
28
+ {
29
+ "path": "extensions/my-extension/providers/my-provider.tsx",
30
+ "order": 0
31
+ }
32
+ ]
33
+ }
34
+ ```
35
+
36
+ ## Extension Routes
37
+
38
+ Files in `routes/` auto-register:
39
+
40
+ ```typescript
41
+ // src/extensions/my-extension/routes/my-route.tsx
42
+ export function loader() {
43
+ return { message: 'Hello' };
44
+ }
45
+
46
+ export default function MyRoute() {
47
+ const { message } = useLoaderData();
48
+ return <div>{message}</div>;
49
+ }
50
+ ```
51
+
52
+ ## Extension Translations
53
+
54
+ Auto-namespaced as `extPascalCase`:
55
+
56
+ ```
57
+ src/extensions/my-extension/locales/
58
+ ├── en-US/translations.json
59
+ └── it-IT/translations.json
60
+ ```
61
+
62
+ ```typescript
63
+ const {t} = useTranslation('extMyExtension');
64
+ t('welcome');
65
+ ```
66
+
67
+ ## Integration Markers
68
+
69
+ ```typescript
70
+ // Single line
71
+ /** @sfdc-extension-line SFDC_EXT_MY_FEATURE */
72
+ import myFeature from '@extensions/my-feature';
73
+
74
+ // Block
75
+ {/* @sfdc-extension-block-start SFDC_EXT_MY_FEATURE */}
76
+ <Link to="/my-feature">My Feature</Link>
77
+ {/* @sfdc-extension-block-end SFDC_EXT_MY_FEATURE */}
78
+ ```
79
+
80
+ For full documentation, read: src/extensions/README.md
@@ -0,0 +1,121 @@
1
+ # Internationalization (i18n)
2
+
3
+ ## Overview
4
+
5
+ - **Server instance**: Has access to all translations for all languages
6
+ - **Client instance**: Dynamically imports translations as JavaScript chunks
7
+ - **Dual API**: `useTranslation()` for components, `getTranslation()` for everything else
8
+
9
+ ## Adding Translations
10
+
11
+ **In `src/locales/{language}/translations.json`**:
12
+
13
+ ```json
14
+ {
15
+ "product": {
16
+ "title": "Product Details",
17
+ "addToCart": "Add to Cart",
18
+ "greeting": "Hello, {{name}}!",
19
+ "itemCount": {
20
+ "zero": "No items",
21
+ "one": "{{count}} item",
22
+ "other": "{{count}} items"
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ **2. Use in React components:**
31
+
32
+ ```typescript
33
+ import { useTranslation } from 'react-i18next';
34
+
35
+ export function ProductCard() {
36
+ const { t } = useTranslation('product');
37
+
38
+ return (
39
+ <div>
40
+ <h1>{t('title')}</h1>
41
+ <button>{t('addToCart')}</button>
42
+ <p>{t('greeting', { name: 'John' })}</p>
43
+ <p>{t('itemCount', { count: 5 })}</p>
44
+ </div>
45
+ );
46
+ }
47
+ ```
48
+
49
+ **3. Use in non-component code:**
50
+
51
+ ```typescript
52
+ import { getTranslation } from '@/lib/i18next';
53
+
54
+ // Client-side or utilities
55
+ const { t } = getTranslation();
56
+ const message = t('product:addToCart');
57
+
58
+ // Server-side (loaders/actions)
59
+ export function loader(args: LoaderFunctionArgs) {
60
+ const { t } = getTranslation(args.context);
61
+ return { title: t('product:title') };
62
+ }
63
+ ```
64
+
65
+ ## Validation Schemas with Translations
66
+
67
+ **CRITICAL**: Use factory pattern for Zod schemas to avoid race conditions:
68
+
69
+ ```typescript
70
+ // ❌ WRONG - Module-level schema (race condition)
71
+ export const schema = z.object({
72
+ email: z.string().email(t('validation:emailInvalid'))
73
+ });
74
+
75
+ // ✅ CORRECT - Factory function
76
+ import type { TFunction } from 'i18next';
77
+
78
+ export const createSchema = (t: TFunction) => {
79
+ return z.object({
80
+ email: z.string().email(t('validation:emailInvalid'))
81
+ });
82
+ };
83
+
84
+ // Usage in component
85
+ import { useMemo } from 'react';
86
+ import { useTranslation } from 'react-i18next';
87
+
88
+ function MyForm() {
89
+ const { t } = useTranslation();
90
+ const schema = useMemo(() => createSchema(t), [t]);
91
+
92
+ const form = useForm({ resolver: zodResolver(schema) });
93
+ }
94
+ ```
95
+
96
+ ## Language Switching
97
+
98
+ ```typescript
99
+ import LocaleSwitcher from '@/components/locale-switcher';
100
+
101
+ export function Footer() {
102
+ return <footer><LocaleSwitcher /></footer>;
103
+ }
104
+ ```
105
+
106
+ ## Extension Translations
107
+
108
+ Extensions use `extPascalCase` namespace:
109
+
110
+ ```
111
+ src/extensions/my-extension/locales/
112
+ ├── en-US/translations.json
113
+ └── it-IT/translations.json
114
+ ```
115
+
116
+ ```typescript
117
+ const {t} = useTranslation('extMyExtension');
118
+ t('welcome');
119
+ ```
120
+
121
+ **Reference:** See README-I18N.md for complete internationalization documentation.