@proveanything/smartlinks 1.9.3 → 1.9.5

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,335 @@
1
+ # SmartLinks Translations
2
+
3
+ Runtime translation API for collection-scoped UI strings and content blocks, with optional browser-side IndexedDB caching.
4
+
5
+ This guide covers the new `translations` namespace in the SDK.
6
+
7
+ ## When To Use This
8
+
9
+ Use `translations` when you need to translate dynamic runtime content such as:
10
+
11
+ - product descriptions fetched from APIs
12
+ - customer-configurable portal copy
13
+ - CMS-driven long-form content blocks
14
+ - repeated UI strings that should be cached per collection and language
15
+
16
+ This is different from the static app-local i18n flow in [i18n.md](i18n.md), which is still the right choice for build-time translation keys such as button labels and route titles.
17
+
18
+ ## Two Levels Of API
19
+
20
+ The SDK exposes two ways to work with translations:
21
+
22
+ ### 1. `translations.lookup(...)`
23
+
24
+ This is the direct backend API wrapper.
25
+
26
+ - calls `POST /public/collection/:collectionId/translations/lookup`
27
+ - sends one string or many strings
28
+ - returns the server response as-is
29
+ - does not check local IndexedDB first
30
+
31
+ ### 2. `translations.resolve(...)`
32
+
33
+ This is the enriched client helper for front-end use.
34
+
35
+ - hashes each source string locally
36
+ - checks the browser-local translation cache first
37
+ - sends only cache misses to the backend
38
+ - stores successful results back into IndexedDB
39
+ - preserves the original input order, including duplicates
40
+
41
+ For browser apps, `resolve(...)` is the recommended default.
42
+
43
+ ## Quick Start
44
+
45
+ ```ts
46
+ import { initializeApi, translations } from '@proveanything/smartlinks'
47
+
48
+ initializeApi({ baseURL: 'https://smartlinks.app/api/v1' })
49
+
50
+ const response = await translations.resolve('collection-123', {
51
+ targetLanguage: 'fr',
52
+ sourceLanguage: 'en',
53
+ context: {
54
+ surface: 'portal-product-page',
55
+ field: 'description',
56
+ },
57
+ texts: [
58
+ 'Welcome to the product page',
59
+ 'Scan to verify authenticity',
60
+ ],
61
+ })
62
+
63
+ console.log(response.items.map((item) => item.translatedText))
64
+ ```
65
+
66
+ ## Raw Backend Lookup
67
+
68
+ Use `lookup(...)` if you want the SDK to stay close to the backend contract.
69
+
70
+ ```ts
71
+ const response = await translations.lookup('collection-123', {
72
+ targetLanguage: 'de',
73
+ text: 'Claim your item',
74
+ })
75
+
76
+ console.log(response.items[0].translatedText)
77
+ ```
78
+
79
+ ### Batch Lookup
80
+
81
+ ```ts
82
+ const response = await translations.lookup('collection-123', {
83
+ targetLanguage: 'es',
84
+ sourceLanguage: 'en',
85
+ mode: 'cache-fill',
86
+ contentType: 'text/plain',
87
+ texts: [
88
+ 'Welcome to the product page',
89
+ 'Scan to verify authenticity',
90
+ ],
91
+ returnMeta: true,
92
+ })
93
+ ```
94
+
95
+ ## Content Types
96
+
97
+ `contentType` is part of the translation cache identity. Use it to tell the backend what kind of content is being translated so it can preserve structure correctly.
98
+
99
+ ### `text/plain`
100
+
101
+ Use this for normal strings, paragraphs, labels, and other plain text content with no markup.
102
+
103
+ ```ts
104
+ await translations.resolve('collection-123', {
105
+ targetLanguage: 'fr',
106
+ sourceLanguage: 'en',
107
+ contentType: 'text/plain',
108
+ texts: [
109
+ 'Welcome to the product page',
110
+ 'Scan to verify authenticity',
111
+ ],
112
+ })
113
+ ```
114
+
115
+ ### `text/html`
116
+
117
+ Use this when the source contains HTML tags that must survive translation unchanged. This is important for rich text descriptions, CMS content, and formatted snippets.
118
+
119
+ ```ts
120
+ await translations.resolve('collection-123', {
121
+ targetLanguage: 'de',
122
+ sourceLanguage: 'en',
123
+ contentType: 'text/html',
124
+ context: {
125
+ surface: 'portal-product-page',
126
+ field: 'rich-description',
127
+ },
128
+ texts: [
129
+ '<p>Welcome to the <strong>product page</strong>.</p>',
130
+ '<p>Scan the tag to <a href="/verify">verify authenticity</a>.</p>',
131
+ ],
132
+ })
133
+ ```
134
+
135
+ Use `text/html` when you want the translation system to preserve tags such as:
136
+
137
+ - `<p>`, `<strong>`, `<em>`
138
+ - `<ul>`, `<li>`
139
+ - `<a>`
140
+ - inline markup embedded in CMS-rendered HTML
141
+
142
+ ### `text/x-liquid`
143
+
144
+ Use this when the content includes Liquid syntax that must be preserved exactly, including output tags, control-flow blocks, and filters.
145
+
146
+ ```ts
147
+ await translations.resolve('collection-123', {
148
+ targetLanguage: 'es',
149
+ sourceLanguage: 'en',
150
+ contentType: 'text/x-liquid',
151
+ context: {
152
+ surface: 'portal-email-template',
153
+ field: 'body',
154
+ },
155
+ texts: [
156
+ 'Hello {{ customer.first_name }}, your item {{ product.title }} is ready.',
157
+ '{% if claimable %}Claim your item{% else %}View item details{% endif %}',
158
+ ],
159
+ })
160
+ ```
161
+
162
+ Use `text/x-liquid` when the source includes constructs such as:
163
+
164
+ - `{{ customer.first_name }}`
165
+ - `{{ product.title | upcase }}`
166
+ - `{% if claimable %}...{% endif %}`
167
+ - loop or conditional blocks used in templates
168
+
169
+ ### Choosing The Right Value
170
+
171
+ - use `text/plain` for normal strings with no markup
172
+ - use `text/html` when HTML tags are part of the source and must be preserved
173
+ - use `text/x-liquid` when Liquid template syntax is part of the source and must be preserved
174
+
175
+ Do not mix plain text, HTML, and Liquid under the same `contentType` if you want stable cache keys and predictable translation behavior.
176
+
177
+ ## Local-First Resolution
178
+
179
+ `resolve(...)` is intended for browser rendering paths where repeated translations should not keep hitting the network.
180
+
181
+ ```ts
182
+ const response = await translations.resolve('collection-123', {
183
+ targetLanguage: 'fr',
184
+ sourceLanguage: 'en',
185
+ texts: [
186
+ 'Welcome to the product page',
187
+ 'Welcome to the product page',
188
+ 'Scan to verify authenticity',
189
+ ],
190
+ })
191
+
192
+ for (const item of response.items) {
193
+ console.log(item.index, item.cacheSource, item.translatedText)
194
+ }
195
+ ```
196
+
197
+ ### Default Client Cache Behavior
198
+
199
+ - cache backend hits in IndexedDB when available
200
+ - fall back to in-memory cache when IndexedDB is unavailable
201
+ - expire entries lazily on read
202
+ - keep entries for 90 days by default
203
+
204
+ IndexedDB does not provide native time-based expiry, so the SDK stores `expiresAt` and evicts stale records when reading them.
205
+
206
+ ### Configure Local Cache TTL
207
+
208
+ ```ts
209
+ await translations.resolve('collection-123', {
210
+ targetLanguage: 'fr',
211
+ texts: ['Limited edition'],
212
+ }, {
213
+ localCacheTtlMs: 180 * 24 * 60 * 60_000,
214
+ })
215
+ ```
216
+
217
+ ### Force A Fresh Remote Lookup
218
+
219
+ ```ts
220
+ await translations.resolve('collection-123', {
221
+ targetLanguage: 'fr',
222
+ texts: ['Welcome back'],
223
+ }, {
224
+ refreshLocalCache: true,
225
+ })
226
+ ```
227
+
228
+ ### Disable The Local Cache
229
+
230
+ ```ts
231
+ await translations.resolve('collection-123', {
232
+ targetLanguage: 'fr',
233
+ texts: ['Welcome back'],
234
+ }, {
235
+ useLocalCache: false,
236
+ })
237
+ ```
238
+
239
+ ## Context Matters
240
+
241
+ The same source string can require different translations depending on where it appears. Pass stable context whenever the meaning can change.
242
+
243
+ ```ts
244
+ await translations.resolve('collection-123', {
245
+ targetLanguage: 'fr',
246
+ texts: ['Claim'],
247
+ context: {
248
+ surface: 'portal-product-page',
249
+ field: 'cta-button',
250
+ },
251
+ })
252
+ ```
253
+
254
+ The SDK derives a deterministic local cache key from `context` so browser-local entries do not collide across different UI surfaces.
255
+
256
+ ## Hashing Helpers
257
+
258
+ The SDK also exposes the normalization and hashing helpers used by the local cache flow.
259
+
260
+ ```ts
261
+ const hash = await translations.hashText('Welcome to the product page')
262
+
263
+ const hashes = await translations.hashTexts([
264
+ 'Welcome to the product page',
265
+ 'Scan to verify authenticity',
266
+ ])
267
+
268
+ const normalized = translations.normalizeText(' Hello\r\nWorld ')
269
+ ```
270
+
271
+ By default the hash path:
272
+
273
+ - normalizes CRLF to LF
274
+ - trims surrounding whitespace
275
+ - applies Unicode NFC normalization
276
+ - preserves interior whitespace unless `collapseWhitespace: true` is set
277
+
278
+ ## Clearing Local Cache
279
+
280
+ Clear all locally cached translations:
281
+
282
+ ```ts
283
+ await translations.clearLocalCache()
284
+ ```
285
+
286
+ Clear only one collection's local entries:
287
+
288
+ ```ts
289
+ await translations.clearLocalCache('collection-123')
290
+ ```
291
+
292
+ ## Admin APIs
293
+
294
+ The namespace also exposes basic admin translation management.
295
+
296
+ ### List
297
+
298
+ ```ts
299
+ const page = await translations.list('collection-123', {
300
+ targetLanguage: 'fr',
301
+ q: 'authenticity',
302
+ limit: 20,
303
+ offset: 0,
304
+ })
305
+ ```
306
+
307
+ ### Get One
308
+
309
+ ```ts
310
+ const record = await translations.get('collection-123', 'translation-id')
311
+ ```
312
+
313
+ ### Update One
314
+
315
+ ```ts
316
+ const updated = await translations.update('collection-123', 'translation-id', {
317
+ translatedText: 'Bienvenue sur la page produit',
318
+ isOverride: true,
319
+ quality: 'human',
320
+ })
321
+ ```
322
+
323
+ ## Recommended Usage Pattern
324
+
325
+ For front-end rendering:
326
+
327
+ 1. Use static app-local i18n for fixed UI keys.
328
+ 2. Use `translations.resolve(...)` for dynamic runtime content.
329
+ 3. Always pass `context` for ambiguous strings.
330
+ 4. Prefer batched requests for multiple strings on the same screen.
331
+
332
+ For back-office or tooling flows:
333
+
334
+ 1. Use `translations.lookup(...)` for direct backend access.
335
+ 2. Use `translations.list/get/update` for translation review and correction workflows.
package/dist/index.d.ts CHANGED
@@ -17,6 +17,7 @@ export type { VariantResponse, VariantCreateRequest, VariantUpdateRequest, } fro
17
17
  export type { BroadcastSendRequest } from "./types/broadcasts";
18
18
  export type { AppConfigOptions } from "./api/appConfiguration";
19
19
  export type { AdditionalGtin, ISODateString, JsonPrimitive, JsonValue, ProductCreateRequest, ProductClaimCreateInput, ProductClaimCreateRequestBody, ProductClaimLookupInput, ProductFacetMap, ProductFacetValue, ProductImage, ProductImageThumbnails, ProductImageUrlInput, ProductKey, ProductQueryRequest, ProductQueryResponse, ProductUpdateRequest, Product, ProductWriteInput, PublicProduct, } from "./types/product";
20
+ export type { TranslationLookupMode, TranslationContentType, TranslationQuality, TranslationItemStatus, TranslationContextValue, TranslationContext, TranslationLookupRequestBase, TranslationLookupSingleRequest, TranslationLookupBatchRequest, TranslationLookupRequest, TranslationLookupItem, TranslationLookupResponse, ResolvedTranslationItem, ResolvedTranslationResponse, TranslationHashOptions, TranslationResolveOptions, TranslationRecord, TranslationListParams, TranslationListResponse, TranslationUpdateRequest, } from "./types/translations";
20
21
  export type { FacetBucket, FacetDefinition, FacetDefinitionWriteInput, FacetGetParams, FacetListParams, FacetListResponse, FacetQueryRequest, FacetQueryResponse, FacetValue, FacetValueDefinition, FacetValueGetParams, FacetValueListParams, FacetValueListResponse, FacetValueResponse, FacetValueWriteInput, PublicFacetListParams, } from "./types/facets";
21
22
  export type { Collection, CollectionResponse, CollectionCreateRequest, CollectionUpdateRequest, } from "./types/collection";
22
23
  export type { Proof, ProofResponse, ProofCreateRequest, ProofUpdateRequest, ProofClaimRequest, } from "./types/proof";