@proveanything/smartlinks 1.2.4 → 1.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.
- package/README.md +85 -1
- package/{API_SUMMARY.md → dist/API_SUMMARY.md} +225 -1
- package/dist/README.md +569 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +2 -0
- package/dist/api/realtime.d.ts +103 -0
- package/dist/api/realtime.js +113 -0
- package/dist/docs/API_SUMMARY.md +3230 -0
- package/dist/docs/i18n.md +287 -0
- package/dist/docs/liquid-templates.md +484 -0
- package/dist/docs/realtime.md +764 -0
- package/dist/docs/theme-defaults.md +100 -0
- package/dist/docs/theme.system.md +338 -0
- package/dist/docs/widgets.md +510 -0
- package/dist/http.js +132 -19
- package/dist/i18n.md +287 -0
- package/dist/liquid-templates.md +484 -0
- package/dist/realtime.md +764 -0
- package/dist/theme-defaults.md +100 -0
- package/dist/theme.system.md +338 -0
- package/dist/types/error.d.ts +69 -3
- package/dist/types/error.js +98 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/realtime.d.ts +44 -0
- package/dist/types/realtime.js +2 -0
- package/dist/widgets.md +510 -0
- package/docs/API_SUMMARY.md +3230 -0
- package/docs/i18n.md +287 -0
- package/docs/liquid-templates.md +484 -0
- package/docs/realtime.md +764 -0
- package/docs/theme-defaults.md +100 -0
- package/docs/theme.system.md +338 -0
- package/docs/widgets.md +510 -0
- package/package.json +4 -4
- package/dist/api/actions.d.ts +0 -32
- package/dist/api/actions.js +0 -99
- package/dist/build-docs.js +0 -61
- package/dist/types/actions.d.ts +0 -123
- package/dist/types/actions.js +0 -2
package/dist/http.js
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
// This module replaces the ApiClient constructor. It keeps baseURL, apiKey, bearerToken
|
|
3
3
|
// in module-scope variables, and provides a shared `request<T>(path)` helper that will
|
|
4
4
|
// be used by all namespaced files (collection.ts, product.ts, etc.).
|
|
5
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
6
|
+
var t = {};
|
|
7
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
8
|
+
t[p] = s[p];
|
|
9
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
10
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
11
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
12
|
+
t[p[i]] = s[p[i]];
|
|
13
|
+
}
|
|
14
|
+
return t;
|
|
15
|
+
};
|
|
16
|
+
import { SmartlinksApiError } from "./types/error";
|
|
5
17
|
let baseURL = null;
|
|
6
18
|
let apiKey = undefined;
|
|
7
19
|
let bearerToken = undefined;
|
|
@@ -63,6 +75,88 @@ function safeBodyPreview(body) {
|
|
|
63
75
|
}
|
|
64
76
|
return body;
|
|
65
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Normalizes various server error response formats into a consistent ErrorResponse shape.
|
|
80
|
+
* Handles multiple formats:
|
|
81
|
+
* - { code: number, message: string } (standard)
|
|
82
|
+
* - { errorCode: string, errorText: string }
|
|
83
|
+
* - { error: string, message: string }
|
|
84
|
+
* - { error: string } (just error field)
|
|
85
|
+
* - { ok: false, error: string }
|
|
86
|
+
* - Plain string
|
|
87
|
+
*
|
|
88
|
+
* @param responseBody - The parsed JSON response body from the server
|
|
89
|
+
* @param statusCode - HTTP status code
|
|
90
|
+
* @returns Normalized ErrorResponse object
|
|
91
|
+
*/
|
|
92
|
+
function normalizeErrorResponse(responseBody, statusCode) {
|
|
93
|
+
if (!responseBody || typeof responseBody !== 'object') {
|
|
94
|
+
// Plain string or non-object response
|
|
95
|
+
const message = typeof responseBody === 'string' ? responseBody : 'Request failed';
|
|
96
|
+
return {
|
|
97
|
+
code: statusCode,
|
|
98
|
+
message,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Extract all possible fields from the response
|
|
102
|
+
const { code, errorCode, error, message, errorText, ok } = responseBody, rest = __rest(responseBody
|
|
103
|
+
// Determine the error code (prefer numeric code, fall back to errorCode or error string)
|
|
104
|
+
, ["code", "errorCode", "error", "message", "errorText", "ok"]);
|
|
105
|
+
// Determine the error code (prefer numeric code, fall back to errorCode or error string)
|
|
106
|
+
let normalizedCode = statusCode;
|
|
107
|
+
if (typeof code === 'number') {
|
|
108
|
+
normalizedCode = code;
|
|
109
|
+
}
|
|
110
|
+
else if (typeof errorCode === 'string' || typeof error === 'string') {
|
|
111
|
+
// Keep statusCode as numeric code, but preserve the string code in details
|
|
112
|
+
}
|
|
113
|
+
// Determine the error message
|
|
114
|
+
let normalizedMessage;
|
|
115
|
+
if (message) {
|
|
116
|
+
normalizedMessage = String(message);
|
|
117
|
+
}
|
|
118
|
+
else if (errorText) {
|
|
119
|
+
normalizedMessage = String(errorText);
|
|
120
|
+
}
|
|
121
|
+
else if (error) {
|
|
122
|
+
normalizedMessage = String(error);
|
|
123
|
+
}
|
|
124
|
+
else if (ok === false) {
|
|
125
|
+
normalizedMessage = 'Request failed';
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
normalizedMessage = `Request failed with status ${statusCode}`;
|
|
129
|
+
}
|
|
130
|
+
// Extract the server-specific error code string (distinct from HTTP status code)
|
|
131
|
+
let normalizedErrorCode;
|
|
132
|
+
if (errorCode && typeof errorCode === 'string') {
|
|
133
|
+
normalizedErrorCode = errorCode;
|
|
134
|
+
}
|
|
135
|
+
else if (error && typeof error === 'string') {
|
|
136
|
+
normalizedErrorCode = error;
|
|
137
|
+
}
|
|
138
|
+
// Collect any additional details
|
|
139
|
+
const details = Object.assign({}, rest);
|
|
140
|
+
// Preserve error fields in details for backward compatibility
|
|
141
|
+
if (errorCode && typeof errorCode === 'string') {
|
|
142
|
+
details.errorCode = errorCode;
|
|
143
|
+
}
|
|
144
|
+
if (error && typeof error === 'string') {
|
|
145
|
+
details.error = error;
|
|
146
|
+
}
|
|
147
|
+
if (errorText && errorText !== normalizedMessage) {
|
|
148
|
+
details.errorText = errorText;
|
|
149
|
+
}
|
|
150
|
+
if (ok === false) {
|
|
151
|
+
details.ok = ok;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
code: normalizedCode,
|
|
155
|
+
errorCode: normalizedErrorCode,
|
|
156
|
+
message: normalizedMessage,
|
|
157
|
+
details: Object.keys(details).length > 0 ? details : undefined,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
66
160
|
/**
|
|
67
161
|
* Call this once (e.g. at app startup) to configure baseURL/auth.
|
|
68
162
|
*
|
|
@@ -336,14 +430,18 @@ export async function request(path) {
|
|
|
336
430
|
});
|
|
337
431
|
logDebug('[smartlinks] GET response', { url, status: response.status, ok: response.ok });
|
|
338
432
|
if (!response.ok) {
|
|
339
|
-
// Try to parse
|
|
433
|
+
// Try to parse error response body and normalize it
|
|
434
|
+
let responseBody;
|
|
340
435
|
try {
|
|
341
|
-
|
|
342
|
-
throw new Error(`Error ${errBody.code}: ${errBody.message}`);
|
|
436
|
+
responseBody = await response.json();
|
|
343
437
|
}
|
|
344
438
|
catch (_a) {
|
|
345
|
-
|
|
439
|
+
// Failed to parse JSON, use status code only
|
|
440
|
+
responseBody = null;
|
|
346
441
|
}
|
|
442
|
+
const errBody = normalizeErrorResponse(responseBody, response.status);
|
|
443
|
+
const message = `Error ${errBody.code}: ${errBody.message}`;
|
|
444
|
+
throw new SmartlinksApiError(message, response.status, errBody, url);
|
|
347
445
|
}
|
|
348
446
|
return (await response.json());
|
|
349
447
|
}
|
|
@@ -384,13 +482,16 @@ export async function post(path, body, extraHeaders) {
|
|
|
384
482
|
});
|
|
385
483
|
logDebug('[smartlinks] POST response', { url, status: response.status, ok: response.ok });
|
|
386
484
|
if (!response.ok) {
|
|
485
|
+
let responseBody;
|
|
387
486
|
try {
|
|
388
|
-
|
|
389
|
-
throw new Error(`Error ${errBody.code}: ${errBody.message}`);
|
|
487
|
+
responseBody = await response.json();
|
|
390
488
|
}
|
|
391
489
|
catch (_a) {
|
|
392
|
-
|
|
490
|
+
responseBody = null;
|
|
393
491
|
}
|
|
492
|
+
const errBody = normalizeErrorResponse(responseBody, response.status);
|
|
493
|
+
const message = `Error ${errBody.code}: ${errBody.message}`;
|
|
494
|
+
throw new SmartlinksApiError(message, response.status, errBody, url);
|
|
394
495
|
}
|
|
395
496
|
return (await response.json());
|
|
396
497
|
}
|
|
@@ -431,13 +532,16 @@ export async function put(path, body, extraHeaders) {
|
|
|
431
532
|
});
|
|
432
533
|
logDebug('[smartlinks] PUT response', { url, status: response.status, ok: response.ok });
|
|
433
534
|
if (!response.ok) {
|
|
535
|
+
let responseBody;
|
|
434
536
|
try {
|
|
435
|
-
|
|
436
|
-
throw new Error(`Error ${errBody.code}: ${errBody.message}`);
|
|
537
|
+
responseBody = await response.json();
|
|
437
538
|
}
|
|
438
539
|
catch (_a) {
|
|
439
|
-
|
|
540
|
+
responseBody = null;
|
|
440
541
|
}
|
|
542
|
+
const errBody = normalizeErrorResponse(responseBody, response.status);
|
|
543
|
+
const message = `Error ${errBody.code}: ${errBody.message}`;
|
|
544
|
+
throw new SmartlinksApiError(message, response.status, errBody, url);
|
|
441
545
|
}
|
|
442
546
|
return (await response.json());
|
|
443
547
|
}
|
|
@@ -478,13 +582,16 @@ export async function patch(path, body, extraHeaders) {
|
|
|
478
582
|
});
|
|
479
583
|
logDebug('[smartlinks] PATCH response', { url, status: response.status, ok: response.ok });
|
|
480
584
|
if (!response.ok) {
|
|
585
|
+
let responseBody;
|
|
481
586
|
try {
|
|
482
|
-
|
|
483
|
-
throw new Error(`Error ${errBody.code}: ${errBody.message}`);
|
|
587
|
+
responseBody = await response.json();
|
|
484
588
|
}
|
|
485
589
|
catch (_a) {
|
|
486
|
-
|
|
590
|
+
responseBody = null;
|
|
487
591
|
}
|
|
592
|
+
const errBody = normalizeErrorResponse(responseBody, response.status);
|
|
593
|
+
const message = `Error ${errBody.code}: ${errBody.message}`;
|
|
594
|
+
throw new SmartlinksApiError(message, response.status, errBody, url);
|
|
488
595
|
}
|
|
489
596
|
return (await response.json());
|
|
490
597
|
}
|
|
@@ -528,13 +635,16 @@ export async function requestWithOptions(path, options) {
|
|
|
528
635
|
const response = await fetch(url, Object.assign(Object.assign({}, options), { headers }));
|
|
529
636
|
logDebug('[smartlinks] requestWithOptions response', { url, status: response.status, ok: response.ok });
|
|
530
637
|
if (!response.ok) {
|
|
638
|
+
let responseBody;
|
|
531
639
|
try {
|
|
532
|
-
|
|
533
|
-
throw new Error(`Error ${errBody.code}: ${errBody.message}`);
|
|
640
|
+
responseBody = await response.json();
|
|
534
641
|
}
|
|
535
642
|
catch (_a) {
|
|
536
|
-
|
|
643
|
+
responseBody = null;
|
|
537
644
|
}
|
|
645
|
+
const errBody = normalizeErrorResponse(responseBody, response.status);
|
|
646
|
+
const message = `Error ${errBody.code}: ${errBody.message}`;
|
|
647
|
+
throw new SmartlinksApiError(message, response.status, errBody, url);
|
|
538
648
|
}
|
|
539
649
|
return (await response.json());
|
|
540
650
|
}
|
|
@@ -569,13 +679,16 @@ export async function del(path, extraHeaders) {
|
|
|
569
679
|
});
|
|
570
680
|
logDebug('[smartlinks] DELETE response', { url, status: response.status, ok: response.ok });
|
|
571
681
|
if (!response.ok) {
|
|
682
|
+
let responseBody;
|
|
572
683
|
try {
|
|
573
|
-
|
|
574
|
-
throw new Error(`Error ${errBody.code}: ${errBody.message}`);
|
|
684
|
+
responseBody = await response.json();
|
|
575
685
|
}
|
|
576
686
|
catch (_a) {
|
|
577
|
-
|
|
687
|
+
responseBody = null;
|
|
578
688
|
}
|
|
689
|
+
const errBody = normalizeErrorResponse(responseBody, response.status);
|
|
690
|
+
const message = `Error ${errBody.code}: ${errBody.message}`;
|
|
691
|
+
throw new SmartlinksApiError(message, response.status, errBody, url);
|
|
579
692
|
}
|
|
580
693
|
// If the response is empty, just return undefined
|
|
581
694
|
if (response.status === 204)
|
package/dist/i18n.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Internationalization (i18n) System
|
|
2
|
+
|
|
3
|
+
This document explains the i18n system for SmartLinks apps, covering URL-based language selection, static translations, and dynamic overrides.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The i18n system supports:
|
|
10
|
+
|
|
11
|
+
- **URL parameter language selection** (`?lang=de`) - Ideal for iframe embedding
|
|
12
|
+
- **Static translations** in JSON files - Fast, no API calls
|
|
13
|
+
- **Dynamic overrides** via SmartLinks appConfig - Customer-configurable
|
|
14
|
+
- **Type-safe translation keys** - Compile-time safety
|
|
15
|
+
- **Widget support** - Translations can be passed to embedded widgets
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Using Translations in Components
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { useLanguage } from '@/i18n';
|
|
25
|
+
|
|
26
|
+
export const MyComponent = () => {
|
|
27
|
+
const { t, lang } = useLanguage();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<h1>{t('public.title')}</h1>
|
|
32
|
+
<p>{t('public.description')}</p>
|
|
33
|
+
<small>Language: {lang}</small>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### With Parameter Interpolation
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const { t } = useLanguage();
|
|
43
|
+
|
|
44
|
+
// Translation: "Hello, {{name}}!"
|
|
45
|
+
t('greeting', { name: 'John' }); // "Hello, John!"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Language Detection Priority
|
|
51
|
+
|
|
52
|
+
The system detects language from multiple sources (in order):
|
|
53
|
+
|
|
54
|
+
| Priority | Source | Example |
|
|
55
|
+
|----------|--------|---------|
|
|
56
|
+
| 1 | URL parameter | `?lang=de` |
|
|
57
|
+
| 2 | localStorage | `smartlinks-lang` key |
|
|
58
|
+
| 3 | Browser language | `navigator.language` |
|
|
59
|
+
| 4 | Default | `en` |
|
|
60
|
+
|
|
61
|
+
For iframe apps, the SmartLinks platform passes the language via URL parameter.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## File Structure
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
src/i18n/
|
|
69
|
+
├── index.ts # Main exports
|
|
70
|
+
├── LanguageContext.tsx # React context + provider
|
|
71
|
+
├── types.ts # TypeScript types
|
|
72
|
+
└── locales/
|
|
73
|
+
├── en.json # English translations
|
|
74
|
+
├── de.json # German translations
|
|
75
|
+
└── fr.json # French translations
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Adding New Translations
|
|
81
|
+
|
|
82
|
+
### 1. Add the Key to Types
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// src/i18n/types.ts
|
|
86
|
+
export type TranslationKey =
|
|
87
|
+
| 'common.loading'
|
|
88
|
+
| 'common.error'
|
|
89
|
+
| 'myFeature.title' // Add new key
|
|
90
|
+
| 'myFeature.description';
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 2. Add Translations to All Locales
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
// src/i18n/locales/en.json
|
|
97
|
+
{
|
|
98
|
+
"myFeature.title": "My Feature",
|
|
99
|
+
"myFeature.description": "Description of my feature"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/i18n/locales/de.json
|
|
103
|
+
{
|
|
104
|
+
"myFeature.title": "Meine Funktion",
|
|
105
|
+
"myFeature.description": "Beschreibung meiner Funktion"
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3. Use in Components
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const { t } = useLanguage();
|
|
113
|
+
return <h1>{t('myFeature.title')}</h1>;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Adding New Languages
|
|
119
|
+
|
|
120
|
+
### 1. Create the Locale File
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
// src/i18n/locales/es.json
|
|
124
|
+
{
|
|
125
|
+
"common.loading": "Cargando...",
|
|
126
|
+
"common.error": "Algo salió mal",
|
|
127
|
+
// ... all other keys
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 2. Import in LanguageContext
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// src/i18n/LanguageContext.tsx
|
|
135
|
+
import es from './locales/es.json';
|
|
136
|
+
|
|
137
|
+
const staticTranslations: Record<string, PartialTranslations> = {
|
|
138
|
+
en: en as PartialTranslations,
|
|
139
|
+
de: de as PartialTranslations,
|
|
140
|
+
fr: fr as PartialTranslations,
|
|
141
|
+
es: es as PartialTranslations, // Add new language
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const SUPPORTED_LANGUAGES = ['en', 'de', 'fr', 'es']; // Add to list
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Dynamic Translations (SmartLinks appConfig)
|
|
150
|
+
|
|
151
|
+
For customer-configurable translations, store them in SmartLinks appConfig:
|
|
152
|
+
|
|
153
|
+
### Config Structure
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"i18n": {
|
|
158
|
+
"supportedLanguages": ["en", "de"],
|
|
159
|
+
"defaultLanguage": "en",
|
|
160
|
+
"translations": {
|
|
161
|
+
"en": {
|
|
162
|
+
"public.title": "Custom Brand Title",
|
|
163
|
+
"public.description": "Custom brand description"
|
|
164
|
+
},
|
|
165
|
+
"de": {
|
|
166
|
+
"public.title": "Benutzerdefinierter Markentitel",
|
|
167
|
+
"public.description": "Benutzerdefinierte Markenbeschreibung"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Loading Dynamic Translations
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { useCollectionAppConfig } from '@/hooks/useSmartLinksData';
|
|
178
|
+
import { LanguageProvider } from '@/i18n';
|
|
179
|
+
|
|
180
|
+
const App = () => {
|
|
181
|
+
const { data: config } = useCollectionAppConfig(collectionId, appId);
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<LanguageProvider dynamicTranslations={config?.i18n?.translations}>
|
|
185
|
+
<MyApp />
|
|
186
|
+
</LanguageProvider>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Dynamic translations override static ones, so customers can customize specific strings while inheriting defaults.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Widgets
|
|
196
|
+
|
|
197
|
+
Widgets receive language context via props rather than React context (since they may run outside the provider).
|
|
198
|
+
|
|
199
|
+
### Widget Props
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
interface SmartLinksWidgetProps {
|
|
203
|
+
// ... other props
|
|
204
|
+
lang?: string;
|
|
205
|
+
translations?: Record<string, string>;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Using in Widgets
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { createTranslator } from '@/i18n';
|
|
213
|
+
|
|
214
|
+
export const MyWidget: React.FC<SmartLinksWidgetProps> = ({
|
|
215
|
+
lang = 'en',
|
|
216
|
+
translations = {},
|
|
217
|
+
...props
|
|
218
|
+
}) => {
|
|
219
|
+
const t = createTranslator(lang, translations);
|
|
220
|
+
|
|
221
|
+
return <div>{t('widget.title')}</div>;
|
|
222
|
+
};
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The `createTranslator` function creates a standalone translation function that doesn't require React context.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## URL Examples
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
# English (default)
|
|
233
|
+
/#/?collectionId=abc&appId=pamphlet
|
|
234
|
+
|
|
235
|
+
# German
|
|
236
|
+
/#/?collectionId=abc&appId=pamphlet&lang=de
|
|
237
|
+
|
|
238
|
+
# French
|
|
239
|
+
/#/?collectionId=abc&appId=pamphlet&lang=fr
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Best Practices
|
|
245
|
+
|
|
246
|
+
1. **Use dot notation** for key namespacing: `category.subcategory.key`
|
|
247
|
+
2. **Keep translations flat** - avoid nested objects in locale files
|
|
248
|
+
3. **Add keys to types.ts first** - ensures type safety
|
|
249
|
+
4. **Provide fallbacks** - English is always the fallback language
|
|
250
|
+
5. **Test all languages** - Use `?lang=xx` to switch during development
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## API Reference
|
|
255
|
+
|
|
256
|
+
### `useLanguage()`
|
|
257
|
+
|
|
258
|
+
Hook to access language context.
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const {
|
|
262
|
+
lang, // Current language code
|
|
263
|
+
setLang, // Function to change language
|
|
264
|
+
t, // Translation function
|
|
265
|
+
supportedLanguages // Array of supported language codes
|
|
266
|
+
} = useLanguage();
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### `createTranslator(lang, overrides)`
|
|
270
|
+
|
|
271
|
+
Creates a standalone translation function for widgets.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const t = createTranslator('de', { 'custom.key': 'Custom value' });
|
|
275
|
+
t('common.loading'); // "Laden..."
|
|
276
|
+
t('custom.key'); // "Custom value"
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### `LanguageProvider`
|
|
280
|
+
|
|
281
|
+
React provider component.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
<LanguageProvider dynamicTranslations={optionalOverrides}>
|
|
285
|
+
<App />
|
|
286
|
+
</LanguageProvider>
|
|
287
|
+
```
|