@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,253 @@
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
+ ## Local-First Resolution
96
+
97
+ `resolve(...)` is intended for browser rendering paths where repeated translations should not keep hitting the network.
98
+
99
+ ```ts
100
+ const response = await translations.resolve('collection-123', {
101
+ targetLanguage: 'fr',
102
+ sourceLanguage: 'en',
103
+ texts: [
104
+ 'Welcome to the product page',
105
+ 'Welcome to the product page',
106
+ 'Scan to verify authenticity',
107
+ ],
108
+ })
109
+
110
+ for (const item of response.items) {
111
+ console.log(item.index, item.cacheSource, item.translatedText)
112
+ }
113
+ ```
114
+
115
+ ### Default Client Cache Behavior
116
+
117
+ - cache backend hits in IndexedDB when available
118
+ - fall back to in-memory cache when IndexedDB is unavailable
119
+ - expire entries lazily on read
120
+ - keep entries for 90 days by default
121
+
122
+ IndexedDB does not provide native time-based expiry, so the SDK stores `expiresAt` and evicts stale records when reading them.
123
+
124
+ ### Configure Local Cache TTL
125
+
126
+ ```ts
127
+ await translations.resolve('collection-123', {
128
+ targetLanguage: 'fr',
129
+ texts: ['Limited edition'],
130
+ }, {
131
+ localCacheTtlMs: 180 * 24 * 60 * 60_000,
132
+ })
133
+ ```
134
+
135
+ ### Force A Fresh Remote Lookup
136
+
137
+ ```ts
138
+ await translations.resolve('collection-123', {
139
+ targetLanguage: 'fr',
140
+ texts: ['Welcome back'],
141
+ }, {
142
+ refreshLocalCache: true,
143
+ })
144
+ ```
145
+
146
+ ### Disable The Local Cache
147
+
148
+ ```ts
149
+ await translations.resolve('collection-123', {
150
+ targetLanguage: 'fr',
151
+ texts: ['Welcome back'],
152
+ }, {
153
+ useLocalCache: false,
154
+ })
155
+ ```
156
+
157
+ ## Context Matters
158
+
159
+ The same source string can require different translations depending on where it appears. Pass stable context whenever the meaning can change.
160
+
161
+ ```ts
162
+ await translations.resolve('collection-123', {
163
+ targetLanguage: 'fr',
164
+ texts: ['Claim'],
165
+ context: {
166
+ surface: 'portal-product-page',
167
+ field: 'cta-button',
168
+ },
169
+ })
170
+ ```
171
+
172
+ The SDK derives a deterministic local cache key from `context` so browser-local entries do not collide across different UI surfaces.
173
+
174
+ ## Hashing Helpers
175
+
176
+ The SDK also exposes the normalization and hashing helpers used by the local cache flow.
177
+
178
+ ```ts
179
+ const hash = await translations.hashText('Welcome to the product page')
180
+
181
+ const hashes = await translations.hashTexts([
182
+ 'Welcome to the product page',
183
+ 'Scan to verify authenticity',
184
+ ])
185
+
186
+ const normalized = translations.normalizeText(' Hello\r\nWorld ')
187
+ ```
188
+
189
+ By default the hash path:
190
+
191
+ - normalizes CRLF to LF
192
+ - trims surrounding whitespace
193
+ - applies Unicode NFC normalization
194
+ - preserves interior whitespace unless `collapseWhitespace: true` is set
195
+
196
+ ## Clearing Local Cache
197
+
198
+ Clear all locally cached translations:
199
+
200
+ ```ts
201
+ await translations.clearLocalCache()
202
+ ```
203
+
204
+ Clear only one collection's local entries:
205
+
206
+ ```ts
207
+ await translations.clearLocalCache('collection-123')
208
+ ```
209
+
210
+ ## Admin APIs
211
+
212
+ The namespace also exposes basic admin translation management.
213
+
214
+ ### List
215
+
216
+ ```ts
217
+ const page = await translations.list('collection-123', {
218
+ targetLanguage: 'fr',
219
+ q: 'authenticity',
220
+ limit: 20,
221
+ offset: 0,
222
+ })
223
+ ```
224
+
225
+ ### Get One
226
+
227
+ ```ts
228
+ const record = await translations.get('collection-123', 'translation-id')
229
+ ```
230
+
231
+ ### Update One
232
+
233
+ ```ts
234
+ const updated = await translations.update('collection-123', 'translation-id', {
235
+ translatedText: 'Bienvenue sur la page produit',
236
+ isOverride: true,
237
+ quality: 'human',
238
+ })
239
+ ```
240
+
241
+ ## Recommended Usage Pattern
242
+
243
+ For front-end rendering:
244
+
245
+ 1. Use static app-local i18n for fixed UI keys.
246
+ 2. Use `translations.resolve(...)` for dynamic runtime content.
247
+ 3. Always pass `context` for ambiguous strings.
248
+ 4. Prefer batched requests for multiple strings on the same screen.
249
+
250
+ For back-office or tooling flows:
251
+
252
+ 1. Use `translations.lookup(...)` for direct backend access.
253
+ 2. Use `translations.list/get/update` for translation review and correction workflows.
package/openapi.yaml CHANGED
@@ -53,6 +53,7 @@ tags:
53
53
  - name: segments
54
54
  - name: tags
55
55
  - name: template
56
+ - name: translations
56
57
  - name: variant
57
58
  security:
58
59
  - bearerAuth: []
@@ -7280,6 +7281,107 @@ paths:
7280
7281
  application/json:
7281
7282
  schema:
7282
7283
  $ref: "#/components/schemas/TemplateRenderRequest"
7284
+ /admin/collection/{collectionId}/translations/{translationId}:
7285
+ get:
7286
+ tags:
7287
+ - translations
7288
+ summary: translations.get
7289
+ operationId: translations_get
7290
+ security:
7291
+ - bearerAuth: []
7292
+ parameters:
7293
+ - name: collectionId
7294
+ in: path
7295
+ required: true
7296
+ schema:
7297
+ type: string
7298
+ - name: translationId
7299
+ in: path
7300
+ required: true
7301
+ schema:
7302
+ type: string
7303
+ responses:
7304
+ 200:
7305
+ description: Success
7306
+ content:
7307
+ application/json:
7308
+ schema:
7309
+ $ref: "#/components/schemas/TranslationRecord"
7310
+ 400:
7311
+ description: Bad request
7312
+ 401:
7313
+ description: Unauthorized
7314
+ 404:
7315
+ description: Not found
7316
+ patch:
7317
+ tags:
7318
+ - translations
7319
+ summary: translations.update
7320
+ operationId: translations_update
7321
+ security:
7322
+ - bearerAuth: []
7323
+ parameters:
7324
+ - name: collectionId
7325
+ in: path
7326
+ required: true
7327
+ schema:
7328
+ type: string
7329
+ - name: translationId
7330
+ in: path
7331
+ required: true
7332
+ schema:
7333
+ type: string
7334
+ responses:
7335
+ 200:
7336
+ description: Success
7337
+ content:
7338
+ application/json:
7339
+ schema:
7340
+ $ref: "#/components/schemas/TranslationRecord"
7341
+ 400:
7342
+ description: Bad request
7343
+ 401:
7344
+ description: Unauthorized
7345
+ 404:
7346
+ description: Not found
7347
+ requestBody:
7348
+ required: true
7349
+ content:
7350
+ application/json:
7351
+ schema:
7352
+ $ref: "#/components/schemas/TranslationUpdateRequest"
7353
+ /admin/collection/{collectionId}/translations{query}:
7354
+ get:
7355
+ tags:
7356
+ - translations
7357
+ summary: translations.list
7358
+ operationId: translations_list
7359
+ security:
7360
+ - bearerAuth: []
7361
+ parameters:
7362
+ - name: collectionId
7363
+ in: path
7364
+ required: true
7365
+ schema:
7366
+ type: string
7367
+ - name: query
7368
+ in: path
7369
+ required: true
7370
+ schema:
7371
+ type: string
7372
+ responses:
7373
+ 200:
7374
+ description: Success
7375
+ content:
7376
+ application/json:
7377
+ schema:
7378
+ $ref: "#/components/schemas/TranslationListResponse"
7379
+ 400:
7380
+ description: Bad request
7381
+ 401:
7382
+ description: Unauthorized
7383
+ 404:
7384
+ description: Not found
7283
7385
  /api/admin/auth/push:
7284
7386
  get:
7285
7387
  tags:
@@ -10737,6 +10839,38 @@ paths:
10737
10839
  description: Unauthorized
10738
10840
  404:
10739
10841
  description: Not found
10842
+ /public/collection/{collectionId}/translations/lookup:
10843
+ post:
10844
+ tags:
10845
+ - translations
10846
+ summary: translations.lookup
10847
+ operationId: translations_lookup
10848
+ security: []
10849
+ parameters:
10850
+ - name: collectionId
10851
+ in: path
10852
+ required: true
10853
+ schema:
10854
+ type: string
10855
+ responses:
10856
+ 200:
10857
+ description: Success
10858
+ content:
10859
+ application/json:
10860
+ schema:
10861
+ $ref: "#/components/schemas/TranslationLookupResponse"
10862
+ 400:
10863
+ description: Bad request
10864
+ 401:
10865
+ description: Unauthorized
10866
+ 404:
10867
+ description: Not found
10868
+ requestBody:
10869
+ required: true
10870
+ content:
10871
+ application/json:
10872
+ schema:
10873
+ $ref: "#/components/schemas/TranslationLookupRequest"
10740
10874
  /public/location/{locationId}:
10741
10875
  get:
10742
10876
  tags:
@@ -20922,6 +21056,236 @@ components:
20922
21056
  additionalProperties: true
20923
21057
  required:
20924
21058
  - ok
21059
+ TranslationContext:
21060
+ type: object
21061
+ properties:
21062
+ surface:
21063
+ type: string
21064
+ field:
21065
+ type: string
21066
+ TranslationLookupRequestBase:
21067
+ type: object
21068
+ properties:
21069
+ targetLanguage:
21070
+ type: string
21071
+ sourceLanguage:
21072
+ type: string
21073
+ mode:
21074
+ $ref: "#/components/schemas/TranslationLookupMode"
21075
+ contentType:
21076
+ $ref: "#/components/schemas/TranslationContentType"
21077
+ context:
21078
+ $ref: "#/components/schemas/TranslationContext"
21079
+ returnMeta:
21080
+ type: boolean
21081
+ required:
21082
+ - targetLanguage
21083
+ TranslationLookupSingleRequest:
21084
+ type: object
21085
+ properties:
21086
+ text:
21087
+ type: string
21088
+ texts: {}
21089
+ required:
21090
+ - text
21091
+ TranslationLookupBatchRequest:
21092
+ type: object
21093
+ properties:
21094
+ text: {}
21095
+ texts:
21096
+ type: array
21097
+ items:
21098
+ type: string
21099
+ required:
21100
+ - texts
21101
+ TranslationLookupItem:
21102
+ type: object
21103
+ properties:
21104
+ index:
21105
+ type: number
21106
+ hash:
21107
+ type: string
21108
+ sourceText:
21109
+ type: string
21110
+ translatedText:
21111
+ type: string
21112
+ status:
21113
+ $ref: "#/components/schemas/TranslationItemStatus"
21114
+ provider:
21115
+ type: string
21116
+ model:
21117
+ type: string
21118
+ isOverride:
21119
+ type: boolean
21120
+ quality:
21121
+ $ref: "#/components/schemas/TranslationQuality"
21122
+ createdAt:
21123
+ type: string
21124
+ updatedAt:
21125
+ type: string
21126
+ required:
21127
+ - index
21128
+ - hash
21129
+ - sourceText
21130
+ TranslationLookupResponse:
21131
+ type: object
21132
+ properties:
21133
+ targetLanguage:
21134
+ type: string
21135
+ sourceLanguage:
21136
+ type: string
21137
+ mode:
21138
+ $ref: "#/components/schemas/TranslationLookupMode"
21139
+ items:
21140
+ type: array
21141
+ items:
21142
+ $ref: "#/components/schemas/TranslationLookupItem"
21143
+ required:
21144
+ - targetLanguage
21145
+ - items
21146
+ ResolvedTranslationItem:
21147
+ type: object
21148
+ properties:
21149
+ cacheSource:
21150
+ type: string
21151
+ enum:
21152
+ - local
21153
+ - remote
21154
+ expiresAt:
21155
+ type: number
21156
+ ResolvedTranslationResponse:
21157
+ type: object
21158
+ properties:
21159
+ targetLanguage:
21160
+ type: string
21161
+ sourceLanguage:
21162
+ type: string
21163
+ mode:
21164
+ $ref: "#/components/schemas/TranslationLookupMode"
21165
+ items:
21166
+ type: array
21167
+ items:
21168
+ $ref: "#/components/schemas/ResolvedTranslationItem"
21169
+ required:
21170
+ - targetLanguage
21171
+ - items
21172
+ TranslationHashOptions:
21173
+ type: object
21174
+ properties:
21175
+ trim:
21176
+ type: boolean
21177
+ collapseWhitespace:
21178
+ type: boolean
21179
+ unicodeNormalization:
21180
+ type: string
21181
+ enum:
21182
+ - NFC
21183
+ - NFKC
21184
+ TranslationResolveOptions:
21185
+ type: object
21186
+ properties:
21187
+ useLocalCache:
21188
+ type: boolean
21189
+ refreshLocalCache:
21190
+ type: boolean
21191
+ localCacheTtlMs:
21192
+ type: number
21193
+ hashOptions:
21194
+ $ref: "#/components/schemas/TranslationHashOptions"
21195
+ TranslationRecord:
21196
+ type: object
21197
+ properties:
21198
+ id:
21199
+ type: string
21200
+ collectionId:
21201
+ type: string
21202
+ sourceHash:
21203
+ type: string
21204
+ sourceText:
21205
+ type: string
21206
+ sourceLanguage:
21207
+ type: string
21208
+ targetLanguage:
21209
+ type: string
21210
+ contentType:
21211
+ type: string
21212
+ contextKey:
21213
+ type: string
21214
+ translatedText:
21215
+ type: string
21216
+ provider:
21217
+ type: string
21218
+ model:
21219
+ type: string
21220
+ quality:
21221
+ $ref: "#/components/schemas/TranslationQuality"
21222
+ isOverride:
21223
+ type: boolean
21224
+ metadata:
21225
+ type: object
21226
+ additionalProperties: true
21227
+ createdAt:
21228
+ type: string
21229
+ updatedAt:
21230
+ type: string
21231
+ required:
21232
+ - id
21233
+ - collectionId
21234
+ - sourceHash
21235
+ - sourceText
21236
+ - targetLanguage
21237
+ - contentType
21238
+ - translatedText
21239
+ - quality
21240
+ - isOverride
21241
+ - createdAt
21242
+ - updatedAt
21243
+ TranslationListParams:
21244
+ type: object
21245
+ properties:
21246
+ targetLanguage:
21247
+ type: string
21248
+ sourceLanguage:
21249
+ type: string
21250
+ contentType:
21251
+ type: string
21252
+ contextKey:
21253
+ type: string
21254
+ q:
21255
+ type: string
21256
+ isOverride:
21257
+ type: boolean
21258
+ limit:
21259
+ type: number
21260
+ offset:
21261
+ type: number
21262
+ TranslationListResponse:
21263
+ type: object
21264
+ properties:
21265
+ items:
21266
+ type: array
21267
+ items:
21268
+ $ref: "#/components/schemas/TranslationRecord"
21269
+ total:
21270
+ type: number
21271
+ limit:
21272
+ type: number
21273
+ offset:
21274
+ type: number
21275
+ required:
21276
+ - items
21277
+ TranslationUpdateRequest:
21278
+ type: object
21279
+ properties:
21280
+ translatedText:
21281
+ type: string
21282
+ isOverride:
21283
+ type: boolean
21284
+ quality:
21285
+ $ref: "#/components/schemas/TranslationQuality"
21286
+ metadata:
21287
+ type: object
21288
+ additionalProperties: true
20925
21289
  AppConfigOptions:
20926
21290
  type: object
20927
21291
  properties:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",