@proveanything/smartlinks 1.9.2 → 1.9.4

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.
@@ -0,0 +1,298 @@
1
+ const DB_NAME = 'smartlinks-sdk-translations';
2
+ const STORE_NAME = 'translations';
3
+ const DB_VERSION = 1;
4
+ const DEFAULT_LOCAL_TRANSLATION_TTL_MS = 90 * 24 * 60 * 60000;
5
+ let dbPromise = null;
6
+ const memoryTranslationCache = new Map();
7
+ export function getDefaultTranslationCacheTtlMs() {
8
+ return DEFAULT_LOCAL_TRANSLATION_TTL_MS;
9
+ }
10
+ function isIndexedDbAvailable() {
11
+ try {
12
+ return typeof indexedDB !== 'undefined' && indexedDB !== null;
13
+ }
14
+ catch (_a) {
15
+ return false;
16
+ }
17
+ }
18
+ function openDb() {
19
+ if (dbPromise)
20
+ return dbPromise;
21
+ dbPromise = new Promise((resolve, reject) => {
22
+ try {
23
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
24
+ request.onupgradeneeded = (event) => {
25
+ const db = event.target.result;
26
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
27
+ db.createObjectStore(STORE_NAME);
28
+ }
29
+ };
30
+ request.onsuccess = (event) => {
31
+ resolve(event.target.result);
32
+ };
33
+ request.onerror = () => {
34
+ dbPromise = null;
35
+ reject(request.error);
36
+ };
37
+ request.onblocked = () => {
38
+ dbPromise = null;
39
+ reject(new Error('[smartlinks] Translation IndexedDB open blocked'));
40
+ };
41
+ }
42
+ catch (error) {
43
+ dbPromise = null;
44
+ reject(error);
45
+ }
46
+ });
47
+ return dbPromise;
48
+ }
49
+ function stableContextKey(context) {
50
+ if (!context)
51
+ return null;
52
+ const entries = Object.entries(context)
53
+ .filter(([, value]) => value !== undefined)
54
+ .sort(([left], [right]) => left.localeCompare(right));
55
+ if (entries.length === 0)
56
+ return null;
57
+ if (typeof context.surface === 'string' && typeof context.field === 'string') {
58
+ return `${context.surface}:${context.field}`;
59
+ }
60
+ return entries
61
+ .map(([key, value]) => `${key}=${String(value)}`)
62
+ .join('|');
63
+ }
64
+ export function deriveTranslationContextKey(context) {
65
+ return stableContextKey(context);
66
+ }
67
+ export function normalizeTranslationText(text, options = {}) {
68
+ const { trim = true, collapseWhitespace = false, unicodeNormalization = 'NFC', } = options;
69
+ let normalized = text.replace(/\r\n?/g, '\n');
70
+ if (trim) {
71
+ normalized = normalized.trim();
72
+ }
73
+ if (collapseWhitespace) {
74
+ normalized = normalized.replace(/[\t\f\v ]+/g, ' ');
75
+ }
76
+ if (unicodeNormalization) {
77
+ normalized = normalized.normalize(unicodeNormalization);
78
+ }
79
+ return normalized;
80
+ }
81
+ function toHex(buffer) {
82
+ const bytes = new Uint8Array(buffer);
83
+ let output = '';
84
+ for (const byte of bytes) {
85
+ output += byte.toString(16).padStart(2, '0');
86
+ }
87
+ return output;
88
+ }
89
+ export async function hashTranslationText(text, options = {}) {
90
+ var _a;
91
+ const normalized = normalizeTranslationText(text, options);
92
+ const subtle = (_a = globalThis.crypto) === null || _a === void 0 ? void 0 : _a.subtle;
93
+ if (!subtle) {
94
+ throw new Error('[smartlinks] Web Crypto is required for translation hashing');
95
+ }
96
+ const encoded = new TextEncoder().encode(normalized);
97
+ const digest = await subtle.digest('SHA-256', encoded);
98
+ return `sha256:${toHex(digest)}`;
99
+ }
100
+ function buildLocalCacheKey(params) {
101
+ var _a;
102
+ return [
103
+ params.collectionId,
104
+ params.sourceLanguage,
105
+ params.targetLanguage,
106
+ params.contentType,
107
+ (_a = params.contextKey) !== null && _a !== void 0 ? _a : '',
108
+ params.hash,
109
+ ].join('::');
110
+ }
111
+ function isExpired(entry) {
112
+ return entry.expiresAt <= Date.now();
113
+ }
114
+ async function getEntry(key) {
115
+ const memoryEntry = memoryTranslationCache.get(key);
116
+ if (memoryEntry) {
117
+ if (isExpired(memoryEntry)) {
118
+ memoryTranslationCache.delete(key);
119
+ return null;
120
+ }
121
+ return memoryEntry;
122
+ }
123
+ if (!isIndexedDbAvailable())
124
+ return null;
125
+ try {
126
+ const db = await openDb();
127
+ return await new Promise((resolve) => {
128
+ try {
129
+ const tx = db.transaction(STORE_NAME, 'readonly');
130
+ const request = tx.objectStore(STORE_NAME).get(key);
131
+ request.onsuccess = () => {
132
+ var _a;
133
+ const result = (_a = request.result) !== null && _a !== void 0 ? _a : null;
134
+ if (!result) {
135
+ resolve(null);
136
+ return;
137
+ }
138
+ if (isExpired(result)) {
139
+ memoryTranslationCache.delete(key);
140
+ void deleteEntries([key]);
141
+ resolve(null);
142
+ return;
143
+ }
144
+ memoryTranslationCache.set(key, result);
145
+ resolve(result);
146
+ };
147
+ request.onerror = () => resolve(null);
148
+ }
149
+ catch (_a) {
150
+ resolve(null);
151
+ }
152
+ });
153
+ }
154
+ catch (_a) {
155
+ return null;
156
+ }
157
+ }
158
+ export async function getCachedTranslations(params) {
159
+ const entries = await Promise.all(params.hashes.map(async (hash) => {
160
+ const key = buildLocalCacheKey(Object.assign(Object.assign({}, params), { hash }));
161
+ const entry = await getEntry(key);
162
+ return entry ? [hash, entry] : null;
163
+ }));
164
+ return new Map(entries.filter((entry) => entry !== null));
165
+ }
166
+ async function setEntries(entries) {
167
+ for (const [key, entry] of entries) {
168
+ memoryTranslationCache.set(key, entry);
169
+ }
170
+ if (!isIndexedDbAvailable())
171
+ return;
172
+ try {
173
+ const db = await openDb();
174
+ await new Promise((resolve) => {
175
+ try {
176
+ const tx = db.transaction(STORE_NAME, 'readwrite');
177
+ const store = tx.objectStore(STORE_NAME);
178
+ for (const [key, entry] of entries) {
179
+ store.put(entry, key);
180
+ }
181
+ tx.oncomplete = () => resolve();
182
+ tx.onerror = () => resolve();
183
+ tx.onabort = () => resolve();
184
+ }
185
+ catch (_a) {
186
+ resolve();
187
+ }
188
+ });
189
+ }
190
+ catch (_a) {
191
+ // Best-effort persistence only.
192
+ }
193
+ }
194
+ export async function setCachedTranslations(params) {
195
+ var _a;
196
+ const ttlMs = (_a = params.ttlMs) !== null && _a !== void 0 ? _a : DEFAULT_LOCAL_TRANSLATION_TTL_MS;
197
+ const now = Date.now();
198
+ const entries = params.items.map(({ hash, item }) => {
199
+ const key = buildLocalCacheKey({
200
+ collectionId: params.collectionId,
201
+ sourceLanguage: params.sourceLanguage,
202
+ targetLanguage: params.targetLanguage,
203
+ contentType: params.contentType,
204
+ contextKey: params.contextKey,
205
+ hash,
206
+ });
207
+ const entry = {
208
+ collectionId: params.collectionId,
209
+ sourceLanguage: params.sourceLanguage,
210
+ targetLanguage: params.targetLanguage,
211
+ contentType: params.contentType,
212
+ contextKey: params.contextKey,
213
+ hash,
214
+ item: Object.assign(Object.assign({}, item), { expiresAt: now + ttlMs }),
215
+ cachedAt: now,
216
+ expiresAt: now + ttlMs,
217
+ };
218
+ return [key, entry];
219
+ });
220
+ await setEntries(entries);
221
+ }
222
+ async function deleteEntries(keys) {
223
+ for (const key of keys) {
224
+ memoryTranslationCache.delete(key);
225
+ }
226
+ if (!isIndexedDbAvailable() || keys.length === 0)
227
+ return;
228
+ try {
229
+ const db = await openDb();
230
+ await new Promise((resolve) => {
231
+ try {
232
+ const tx = db.transaction(STORE_NAME, 'readwrite');
233
+ const store = tx.objectStore(STORE_NAME);
234
+ for (const key of keys) {
235
+ store.delete(key);
236
+ }
237
+ tx.oncomplete = () => resolve();
238
+ tx.onerror = () => resolve();
239
+ tx.onabort = () => resolve();
240
+ }
241
+ catch (_a) {
242
+ resolve();
243
+ }
244
+ });
245
+ }
246
+ catch (_a) {
247
+ // Ignore deletion failures.
248
+ }
249
+ }
250
+ export async function clearCachedTranslations(collectionId) {
251
+ const memoryKeys = Array.from(memoryTranslationCache.keys());
252
+ const keysToDelete = collectionId
253
+ ? memoryKeys.filter((key) => key.startsWith(`${collectionId}::`))
254
+ : memoryKeys;
255
+ await deleteEntries(keysToDelete);
256
+ if (!isIndexedDbAvailable())
257
+ return;
258
+ try {
259
+ const db = await openDb();
260
+ if (!collectionId) {
261
+ await new Promise((resolve) => {
262
+ try {
263
+ const tx = db.transaction(STORE_NAME, 'readwrite');
264
+ tx.objectStore(STORE_NAME).clear();
265
+ tx.oncomplete = () => resolve();
266
+ tx.onerror = () => resolve();
267
+ tx.onabort = () => resolve();
268
+ }
269
+ catch (_a) {
270
+ resolve();
271
+ }
272
+ });
273
+ return;
274
+ }
275
+ await new Promise((resolve) => {
276
+ try {
277
+ const tx = db.transaction(STORE_NAME, 'readwrite');
278
+ const store = tx.objectStore(STORE_NAME);
279
+ const request = store.getAllKeys();
280
+ request.onsuccess = () => {
281
+ for (const key of request.result) {
282
+ if (key.startsWith(`${collectionId}::`)) {
283
+ store.delete(key);
284
+ }
285
+ }
286
+ resolve();
287
+ };
288
+ request.onerror = () => resolve();
289
+ }
290
+ catch (_a) {
291
+ resolve();
292
+ }
293
+ });
294
+ }
295
+ catch (_a) {
296
+ // Ignore deletion failures.
297
+ }
298
+ }
@@ -32,3 +32,4 @@ export * from "./ai";
32
32
  export * from "./appManifest";
33
33
  export * from "./appObjects";
34
34
  export * from "./loyalty";
35
+ export * from "./translations";
@@ -34,3 +34,4 @@ export * from "./ai";
34
34
  export * from "./appManifest";
35
35
  export * from "./appObjects";
36
36
  export * from "./loyalty";
37
+ export * from "./translations";
@@ -0,0 +1,107 @@
1
+ export type TranslationLookupMode = 'cache-fill' | 'cache-only';
2
+ export type TranslationContentType = 'text/plain' | 'text/html' | 'text/x-liquid' | (string & {});
3
+ export type TranslationQuality = 'machine' | 'human' | 'passthrough' | (string & {});
4
+ export type TranslationItemStatus = 'cached' | 'generated' | 'miss' | 'passthrough' | 'local-cache' | (string & {});
5
+ export type TranslationContextValue = string | number | boolean | null;
6
+ export interface TranslationContext {
7
+ surface?: string;
8
+ field?: string;
9
+ [key: string]: TranslationContextValue | undefined;
10
+ }
11
+ export interface TranslationLookupRequestBase {
12
+ targetLanguage: string;
13
+ sourceLanguage?: string;
14
+ mode?: TranslationLookupMode;
15
+ contentType?: TranslationContentType;
16
+ context?: TranslationContext;
17
+ returnMeta?: boolean;
18
+ }
19
+ export interface TranslationLookupSingleRequest extends TranslationLookupRequestBase {
20
+ text: string;
21
+ texts?: never;
22
+ }
23
+ export interface TranslationLookupBatchRequest extends TranslationLookupRequestBase {
24
+ text?: never;
25
+ texts: string[];
26
+ }
27
+ export type TranslationLookupRequest = TranslationLookupSingleRequest | TranslationLookupBatchRequest;
28
+ export interface TranslationLookupItem {
29
+ index: number;
30
+ hash: string;
31
+ sourceText: string;
32
+ translatedText?: string;
33
+ status?: TranslationItemStatus;
34
+ provider?: string;
35
+ model?: string;
36
+ isOverride?: boolean;
37
+ quality?: TranslationQuality;
38
+ createdAt?: string;
39
+ updatedAt?: string;
40
+ }
41
+ export interface TranslationLookupResponse {
42
+ targetLanguage: string;
43
+ sourceLanguage?: string;
44
+ mode?: TranslationLookupMode;
45
+ items: TranslationLookupItem[];
46
+ }
47
+ export interface ResolvedTranslationItem extends TranslationLookupItem {
48
+ cacheSource?: 'local' | 'remote';
49
+ expiresAt?: number;
50
+ }
51
+ export interface ResolvedTranslationResponse {
52
+ targetLanguage: string;
53
+ sourceLanguage?: string;
54
+ mode?: TranslationLookupMode;
55
+ items: ResolvedTranslationItem[];
56
+ }
57
+ export interface TranslationHashOptions {
58
+ trim?: boolean;
59
+ collapseWhitespace?: boolean;
60
+ unicodeNormalization?: 'NFC' | 'NFKC' | false;
61
+ }
62
+ export interface TranslationResolveOptions {
63
+ useLocalCache?: boolean;
64
+ refreshLocalCache?: boolean;
65
+ localCacheTtlMs?: number;
66
+ hashOptions?: TranslationHashOptions;
67
+ }
68
+ export interface TranslationRecord {
69
+ id: string;
70
+ collectionId: string;
71
+ sourceHash: string;
72
+ sourceText: string;
73
+ sourceLanguage?: string;
74
+ targetLanguage: string;
75
+ contentType: string;
76
+ contextKey?: string | null;
77
+ translatedText: string;
78
+ provider?: string | null;
79
+ model?: string | null;
80
+ quality: TranslationQuality;
81
+ isOverride: boolean;
82
+ metadata?: Record<string, any>;
83
+ createdAt: string;
84
+ updatedAt: string;
85
+ }
86
+ export interface TranslationListParams {
87
+ targetLanguage?: string;
88
+ sourceLanguage?: string;
89
+ contentType?: string;
90
+ contextKey?: string;
91
+ q?: string;
92
+ isOverride?: boolean;
93
+ limit?: number;
94
+ offset?: number;
95
+ }
96
+ export interface TranslationListResponse {
97
+ items: TranslationRecord[];
98
+ total?: number;
99
+ limit?: number;
100
+ offset?: number;
101
+ }
102
+ export interface TranslationUpdateRequest {
103
+ translatedText?: string;
104
+ isOverride?: boolean;
105
+ quality?: TranslationQuality;
106
+ metadata?: Record<string, any>;
107
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.9.2 | Generated: 2026-03-23T18:11:56.796Z
3
+ Version: 1.9.4 | Generated: 2026-03-23T19:52:46.756Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -110,6 +110,7 @@ The Smartlinks SDK is organized into the following namespaces:
110
110
  - **realtime** - Functions for realtime operations
111
111
  - **tags** - Functions for tags operations
112
112
  - **template** - Functions for template operations
113
+ - **translations** - Functions for translations operations
113
114
 
114
115
  ## HTTP Utilities
115
116
 
@@ -6343,6 +6344,153 @@ interface TemplateRenderSourceResponse {
6343
6344
 
6344
6345
  **TemplatePublic** = `TemplateBase`
6345
6346
 
6347
+ ### translations
6348
+
6349
+ **TranslationContext** (interface)
6350
+ ```typescript
6351
+ interface TranslationContext {
6352
+ surface?: string
6353
+ field?: string
6354
+ [key: string]: TranslationContextValue | undefined
6355
+ }
6356
+ ```
6357
+
6358
+ **TranslationLookupRequestBase** (interface)
6359
+ ```typescript
6360
+ interface TranslationLookupRequestBase {
6361
+ targetLanguage: string
6362
+ sourceLanguage?: string
6363
+ mode?: TranslationLookupMode
6364
+ contentType?: TranslationContentType
6365
+ context?: TranslationContext
6366
+ returnMeta?: boolean
6367
+ }
6368
+ ```
6369
+
6370
+ **TranslationLookupItem** (interface)
6371
+ ```typescript
6372
+ interface TranslationLookupItem {
6373
+ index: number
6374
+ hash: string
6375
+ sourceText: string
6376
+ translatedText?: string
6377
+ status?: TranslationItemStatus
6378
+ provider?: string
6379
+ model?: string
6380
+ isOverride?: boolean
6381
+ quality?: TranslationQuality
6382
+ createdAt?: string
6383
+ updatedAt?: string
6384
+ }
6385
+ ```
6386
+
6387
+ **TranslationLookupResponse** (interface)
6388
+ ```typescript
6389
+ interface TranslationLookupResponse {
6390
+ targetLanguage: string
6391
+ sourceLanguage?: string
6392
+ mode?: TranslationLookupMode
6393
+ items: TranslationLookupItem[]
6394
+ }
6395
+ ```
6396
+
6397
+ **ResolvedTranslationResponse** (interface)
6398
+ ```typescript
6399
+ interface ResolvedTranslationResponse {
6400
+ targetLanguage: string
6401
+ sourceLanguage?: string
6402
+ mode?: TranslationLookupMode
6403
+ items: ResolvedTranslationItem[]
6404
+ }
6405
+ ```
6406
+
6407
+ **TranslationHashOptions** (interface)
6408
+ ```typescript
6409
+ interface TranslationHashOptions {
6410
+ trim?: boolean
6411
+ collapseWhitespace?: boolean
6412
+ unicodeNormalization?: 'NFC' | 'NFKC' | false
6413
+ }
6414
+ ```
6415
+
6416
+ **TranslationResolveOptions** (interface)
6417
+ ```typescript
6418
+ interface TranslationResolveOptions {
6419
+ useLocalCache?: boolean
6420
+ refreshLocalCache?: boolean
6421
+ localCacheTtlMs?: number
6422
+ hashOptions?: TranslationHashOptions
6423
+ }
6424
+ ```
6425
+
6426
+ **TranslationRecord** (interface)
6427
+ ```typescript
6428
+ interface TranslationRecord {
6429
+ id: string
6430
+ collectionId: string
6431
+ sourceHash: string
6432
+ sourceText: string
6433
+ sourceLanguage?: string
6434
+ targetLanguage: string
6435
+ contentType: string
6436
+ contextKey?: string | null
6437
+ translatedText: string
6438
+ provider?: string | null
6439
+ model?: string | null
6440
+ quality: TranslationQuality
6441
+ isOverride: boolean
6442
+ metadata?: Record<string, any>
6443
+ createdAt: string
6444
+ updatedAt: string
6445
+ }
6446
+ ```
6447
+
6448
+ **TranslationListParams** (interface)
6449
+ ```typescript
6450
+ interface TranslationListParams {
6451
+ targetLanguage?: string
6452
+ sourceLanguage?: string
6453
+ contentType?: string
6454
+ contextKey?: string
6455
+ q?: string
6456
+ isOverride?: boolean
6457
+ limit?: number
6458
+ offset?: number
6459
+ }
6460
+ ```
6461
+
6462
+ **TranslationListResponse** (interface)
6463
+ ```typescript
6464
+ interface TranslationListResponse {
6465
+ items: TranslationRecord[]
6466
+ total?: number
6467
+ limit?: number
6468
+ offset?: number
6469
+ }
6470
+ ```
6471
+
6472
+ **TranslationUpdateRequest** (interface)
6473
+ ```typescript
6474
+ interface TranslationUpdateRequest {
6475
+ translatedText?: string
6476
+ isOverride?: boolean
6477
+ quality?: TranslationQuality
6478
+ metadata?: Record<string, any>
6479
+ }
6480
+ ```
6481
+
6482
+ **TranslationLookupMode** = `'cache-fill' | 'cache-only'`
6483
+
6484
+ **TranslationContentType** = `'text/plain' | 'text/html' | 'text/x-liquid' | (string & {})`
6485
+
6486
+ **TranslationQuality** = `'machine' | 'human' | 'passthrough' | (string & {})`
6487
+
6488
+ **TranslationItemStatus** = `'cached' | 'generated' | 'miss' | 'passthrough' | 'local-cache' | (string & {})`
6489
+
6490
+ **TranslationContextValue** = `string | number | boolean | null`
6491
+
6492
+ **TranslationLookupRequest** = `TranslationLookupSingleRequest | TranslationLookupBatchRequest`
6493
+
6346
6494
  ### variant
6347
6495
 
6348
6496
  **VariantResponse** = `any`
@@ -8169,6 +8317,33 @@ Reverse lookup by ref via POST (public). `POST /public/collection/:collectionId/
8169
8317
  **renderSource**(collectionId: string,
8170
8318
  body: TemplateRenderSourceRequest) → `Promise<TemplateRenderSourceResponse>`
8171
8319
 
8320
+ ### translations
8321
+
8322
+ **hashText**(text: string, options?: TranslationHashOptions) → `Promise<string>`
8323
+
8324
+ **hashTexts**(texts: string[], options?: TranslationHashOptions) → `Promise<string[]>`
8325
+
8326
+ **normalizeText**(text: string, options?: TranslationHashOptions) → `string`
8327
+
8328
+ **lookup**(collectionId: string,
8329
+ body: TranslationLookupRequest) → `Promise<TranslationLookupResponse>`
8330
+
8331
+ **resolve**(collectionId: string,
8332
+ body: TranslationLookupRequest,
8333
+ options: TranslationResolveOptions = {}) → `Promise<ResolvedTranslationResponse>`
8334
+
8335
+ **list**(collectionId: string,
8336
+ params?: TranslationListParams) → `Promise<TranslationListResponse>`
8337
+
8338
+ **get**(collectionId: string,
8339
+ translationId: string) → `Promise<TranslationRecord>`
8340
+
8341
+ **update**(collectionId: string,
8342
+ translationId: string,
8343
+ body: TranslationUpdateRequest) → `Promise<TranslationRecord>`
8344
+
8345
+ **clearLocalCache**(collectionId?: string) → `Promise<void>`
8346
+
8172
8347
  ### tts
8173
8348
 
8174
8349
  **generate**(collectionId: string,
package/docs/overview.md CHANGED
@@ -49,6 +49,7 @@ The SmartLinks SDK (`@proveanything/smartlinks`) includes comprehensive document
49
49
  | **Multi-Page Architecture** | `docs/mpa.md` | Build pipeline, entry points, multi-page setup, content hashing |
50
50
  | **AI & Chat** | `docs/ai.md` | Chat completions, RAG, streaming, tool calling, voice, podcasts, TTS |
51
51
  | **Analytics** | `docs/analytics.md` | Fire-and-forget page/click/tag analytics plus admin dashboard queries |
52
+ | **Translations** | `docs/translations.md` | Runtime translation lookup, local-first IndexedDB caching, and translation admin APIs |
52
53
  | **Theming** | `docs/theme.system.md` | Implementing dynamic themes via URL params or postMessage |
53
54
  | **Theme Defaults** | `docs/theme-defaults.md` | Default colour values for light/dark modes |
54
55
  | **Internationalization** | `docs/i18n.md` | Adding multi-language support, translation patterns |