@asksable/site-connector 0.2.0 → 0.3.1

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 CHANGED
@@ -40,9 +40,97 @@ import { BookingWidgetPanel, useSableSiteProfile } from '@asksable/site-connecto
40
40
  - `useSableSiteProfile`
41
41
  - `useSableSiteClient`
42
42
  - `useSableSiteConfig`
43
+ - `useSableLocale`
44
+ - `useTranslation`
43
45
  - `BookingWidgetPanel`
44
46
  - `getResolvedSiteProfile`
45
- - types for public site and booking payloads
47
+ - `createTranslator`, `pickLocaleField`, `localeToIntl`, `TRANSLATIONS`, `DEFAULT_LOCALE`
48
+ - types: `SableSiteConfig`, `Locale`, `TranslationKey`, `TranslationOverrides`, plus public site / booking payloads
49
+
50
+ ## Multi-language support
51
+
52
+ The booking widget translates its own UI chrome (form labels, buttons, summaries, error messages, date/time formatting) to match the host site's language. **Every Sable customer website MUST declare its current language to the widget** so the customer never sees mismatched copy (e.g. an English "Confirm Booking" button on a Spanish-language site).
53
+
54
+ Supported locales: `'en'` (default), `'es'`. Adding a locale requires a package version bump.
55
+
56
+ ### Three usage modes
57
+
58
+ | Site type | Pattern |
59
+ | --- | --- |
60
+ | **Single-language site (English only)** | Omit `language` entirely or pass `'en'`. Widget defaults to English. |
61
+ | **Single-language site (Spanish only)** | Pass `language: 'es'` once at provider mount. |
62
+ | **Multi-language site with a toggle** | Pass a reactive value that updates when the toggle changes. The provider re-renders, the widget re-renders with the new locale. |
63
+
64
+ ### Multi-language example
65
+
66
+ ```tsx
67
+ import { SableSiteProvider } from '@asksable/site-connector'
68
+ import { useLanguage } from './your-i18n-context'
69
+
70
+ function App() {
71
+ const { lang } = useLanguage() // your toggle owns this state
72
+ return (
73
+ <SableSiteProvider
74
+ config={{
75
+ apiUrl: import.meta.env.VITE_SABLE_PUBLIC_API_URL,
76
+ siteSlug: import.meta.env.VITE_SABLE_SITE_SLUG,
77
+ language: lang,
78
+ }}
79
+ >
80
+ <Routes />
81
+ </SableSiteProvider>
82
+ )
83
+ }
84
+ ```
85
+
86
+ The widget responds instantly to language changes — your toggle component flips both the host site's text and the widget by sharing the same `language` state.
87
+
88
+ ### Override individual strings (rare)
89
+
90
+ When a specific client needs different brand voice (e.g. "Reserva mi entrega" instead of the canonical "Confirmar reserva"), pass `translationOverrides`:
91
+
92
+ ```tsx
93
+ <SableSiteProvider
94
+ config={{
95
+ apiUrl,
96
+ siteSlug,
97
+ language: lang,
98
+ translationOverrides: {
99
+ es: { btnConfirmBooking: 'Reserva mi entrega' },
100
+ },
101
+ }}
102
+ >
103
+ <App />
104
+ </SableSiteProvider>
105
+ ```
106
+
107
+ Use overrides sparingly. If a change would benefit all customers, extend the canonical dictionary in `translations.ts` and bump the package version instead.
108
+
109
+ ### What the widget translates
110
+
111
+ Form labels, button text, mobile step labels, helper text, success/cancelled state copy, error messages, ARIA labels, date/time formatting (via `Intl.DateTimeFormat(locale)`), and currency formatting.
112
+
113
+ ### What the widget does NOT translate
114
+
115
+ - **Service names, descriptions, category names** — these come from the Sable workspace. The widget reads `nameEn` / `nameEs` (or any `${field}En` / `${field}Es`) fields when available, falling back to the base field. If the workspace only entered one locale, that text renders regardless of UI language. (Future workstream: dashboard support for entering both locales.)
116
+ - **Customer-typed input** — names, notes, etc.
117
+
118
+ ### Detecting locale in custom components
119
+
120
+ If you build something inside `SableSiteProvider` that needs locale awareness, use the exposed hooks:
121
+
122
+ ```tsx
123
+ import { useTranslation, useSableLocale } from '@asksable/site-connector'
124
+
125
+ function MyComponent() {
126
+ const { t, locale } = useTranslation()
127
+ // t('contactFullName') → "Nombre completo" when locale is 'es'
128
+ }
129
+ ```
130
+
131
+ ### For template builders
132
+
133
+ Every Sable website template should include the `language` prop wiring as part of the boilerplate. If the template supports a toggle, the toggle component must flip both the host site's text and the widget by sharing the same language state. **Never let the widget and host site drift to different locales** — pass a single reactive `language` value into `SableSiteConfig` and the widget stays in sync automatically.
46
134
 
47
135
  ## Public API Contract
48
136
 
@@ -0,0 +1,495 @@
1
+ {
2
+ "breakpoints": {
3
+ "375": {
4
+ "name": "booking-widget",
5
+ "viewportWidth": 375,
6
+ "width": 375,
7
+ "height": 738,
8
+ "bones": [
9
+ [
10
+ 0,
11
+ 0,
12
+ 100,
13
+ 738,
14
+ 8,
15
+ true
16
+ ],
17
+ [
18
+ 5.3333,
19
+ 20,
20
+ 89.3333,
21
+ 24,
22
+ 8
23
+ ],
24
+ [
25
+ 5.3333,
26
+ 77,
27
+ 89.3333,
28
+ 130,
29
+ 8
30
+ ],
31
+ [
32
+ 5.3333,
33
+ 207,
34
+ 89.3333,
35
+ 1,
36
+ 8
37
+ ],
38
+ [
39
+ 5.3333,
40
+ 210,
41
+ 89.3333,
42
+ 152,
43
+ 8
44
+ ],
45
+ [
46
+ 5.3333,
47
+ 362,
48
+ 89.3333,
49
+ 1,
50
+ 8
51
+ ],
52
+ [
53
+ 5.3333,
54
+ 662,
55
+ 89.3333,
56
+ 48,
57
+ 16
58
+ ]
59
+ ]
60
+ },
61
+ "768": {
62
+ "name": "booking-widget",
63
+ "viewportWidth": 768,
64
+ "width": 768,
65
+ "height": 738,
66
+ "bones": [
67
+ [
68
+ 0,
69
+ 0,
70
+ 100,
71
+ 738,
72
+ 8,
73
+ true
74
+ ],
75
+ [
76
+ 3.125,
77
+ 20,
78
+ 93.75,
79
+ 31,
80
+ 8
81
+ ],
82
+ [
83
+ 3.125,
84
+ 88,
85
+ 93.75,
86
+ 107,
87
+ 8
88
+ ],
89
+ [
90
+ 3.125,
91
+ 195,
92
+ 93.75,
93
+ 1,
94
+ 8
95
+ ],
96
+ [
97
+ 3.125,
98
+ 198,
99
+ 93.75,
100
+ 107,
101
+ 8
102
+ ],
103
+ [
104
+ 3.125,
105
+ 305,
106
+ 93.75,
107
+ 1,
108
+ 8
109
+ ],
110
+ [
111
+ 3.125,
112
+ 666,
113
+ 93.75,
114
+ 48,
115
+ 16
116
+ ]
117
+ ]
118
+ },
119
+ "1280": {
120
+ "name": "booking-widget",
121
+ "viewportWidth": 1280,
122
+ "width": 1280,
123
+ "height": 738,
124
+ "bones": [
125
+ [
126
+ 0,
127
+ 0,
128
+ 100,
129
+ 738,
130
+ 8,
131
+ true
132
+ ],
133
+ [
134
+ 3.75,
135
+ 48,
136
+ 92.5,
137
+ 31,
138
+ 8
139
+ ],
140
+ [
141
+ 3.75,
142
+ 111,
143
+ 25.8997,
144
+ 21,
145
+ 8
146
+ ],
147
+ [
148
+ 3.75,
149
+ 158,
150
+ 25.8997,
151
+ 121,
152
+ 12
153
+ ],
154
+ [
155
+ 3.75,
156
+ 280,
157
+ 25.8997,
158
+ 121,
159
+ 12
160
+ ],
161
+ [
162
+ 32.1497,
163
+ 111,
164
+ 41.2512,
165
+ 579,
166
+ 16,
167
+ true
168
+ ],
169
+ [
170
+ 34.7278,
171
+ 136,
172
+ 9.2981,
173
+ 31,
174
+ 6
175
+ ],
176
+ [
177
+ 66.1353,
178
+ 137,
179
+ 2.1875,
180
+ 28,
181
+ 6
182
+ ],
183
+ [
184
+ 68.6353,
185
+ 137,
186
+ 2.1875,
187
+ 28,
188
+ 6
189
+ ],
190
+ [
191
+ 34.7278,
192
+ 190,
193
+ 4.7546,
194
+ 29,
195
+ 8
196
+ ],
197
+ [
198
+ 39.9512,
199
+ 190,
200
+ 4.7546,
201
+ 29,
202
+ 8
203
+ ],
204
+ [
205
+ 45.1746,
206
+ 190,
207
+ 4.7546,
208
+ 29,
209
+ 8
210
+ ],
211
+ [
212
+ 50.3979,
213
+ 190,
214
+ 4.7546,
215
+ 29,
216
+ 8
217
+ ],
218
+ [
219
+ 55.6213,
220
+ 190,
221
+ 4.7546,
222
+ 29,
223
+ 8
224
+ ],
225
+ [
226
+ 60.8447,
227
+ 190,
228
+ 4.7546,
229
+ 29,
230
+ 8
231
+ ],
232
+ [
233
+ 66.0681,
234
+ 190,
235
+ 4.7546,
236
+ 29,
237
+ 8
238
+ ],
239
+ [
240
+ 60.8447,
241
+ 247,
242
+ 4.7546,
243
+ 48,
244
+ 8
245
+ ],
246
+ [
247
+ 66.0681,
248
+ 247,
249
+ 4.7546,
250
+ 48,
251
+ 8
252
+ ],
253
+ [
254
+ 34.7278,
255
+ 301,
256
+ 4.7546,
257
+ 48,
258
+ 8
259
+ ],
260
+ [
261
+ 39.9512,
262
+ 301,
263
+ 4.7546,
264
+ 48,
265
+ 8
266
+ ],
267
+ [
268
+ 45.1746,
269
+ 301,
270
+ 4.7546,
271
+ 48,
272
+ 8
273
+ ],
274
+ [
275
+ 50.3979,
276
+ 301,
277
+ 4.7546,
278
+ 48,
279
+ 8
280
+ ],
281
+ [
282
+ 55.6213,
283
+ 301,
284
+ 4.7546,
285
+ 48,
286
+ 8
287
+ ],
288
+ [
289
+ 60.8447,
290
+ 301,
291
+ 4.7546,
292
+ 48,
293
+ 8
294
+ ],
295
+ [
296
+ 66.0681,
297
+ 301,
298
+ 4.7546,
299
+ 48,
300
+ 8
301
+ ],
302
+ [
303
+ 34.7278,
304
+ 355,
305
+ 4.7546,
306
+ 48,
307
+ 8
308
+ ],
309
+ [
310
+ 39.9512,
311
+ 355,
312
+ 4.7546,
313
+ 48,
314
+ 8
315
+ ],
316
+ [
317
+ 45.1746,
318
+ 355,
319
+ 4.7546,
320
+ 48,
321
+ 8
322
+ ],
323
+ [
324
+ 50.3979,
325
+ 355,
326
+ 4.7546,
327
+ 48,
328
+ 8
329
+ ],
330
+ [
331
+ 55.6213,
332
+ 355,
333
+ 4.7546,
334
+ 48,
335
+ 8
336
+ ],
337
+ [
338
+ 60.8447,
339
+ 355,
340
+ 4.7546,
341
+ 48,
342
+ 8
343
+ ],
344
+ [
345
+ 66.0681,
346
+ 355,
347
+ 4.7546,
348
+ 48,
349
+ 8
350
+ ],
351
+ [
352
+ 34.7278,
353
+ 409,
354
+ 4.7546,
355
+ 48,
356
+ 8
357
+ ],
358
+ [
359
+ 39.9512,
360
+ 409,
361
+ 4.7546,
362
+ 48,
363
+ 8
364
+ ],
365
+ [
366
+ 45.1746,
367
+ 409,
368
+ 4.7546,
369
+ 48,
370
+ 8
371
+ ],
372
+ [
373
+ 50.3979,
374
+ 409,
375
+ 4.7546,
376
+ 48,
377
+ 8
378
+ ],
379
+ [
380
+ 55.6213,
381
+ 409,
382
+ 4.7546,
383
+ 48,
384
+ 8
385
+ ],
386
+ [
387
+ 60.8447,
388
+ 409,
389
+ 4.7546,
390
+ 48,
391
+ 8
392
+ ],
393
+ [
394
+ 66.0681,
395
+ 409,
396
+ 4.7546,
397
+ 48,
398
+ 8
399
+ ],
400
+ [
401
+ 34.7278,
402
+ 463,
403
+ 4.7546,
404
+ 48,
405
+ 8
406
+ ],
407
+ [
408
+ 39.9512,
409
+ 463,
410
+ 4.7546,
411
+ 48,
412
+ 8
413
+ ],
414
+ [
415
+ 45.1746,
416
+ 463,
417
+ 4.7546,
418
+ 48,
419
+ 8
420
+ ],
421
+ [
422
+ 50.3979,
423
+ 463,
424
+ 4.7546,
425
+ 48,
426
+ 8
427
+ ],
428
+ [
429
+ 55.6213,
430
+ 463,
431
+ 4.7546,
432
+ 48,
433
+ 8
434
+ ],
435
+ [
436
+ 60.8447,
437
+ 463,
438
+ 4.7546,
439
+ 48,
440
+ 8
441
+ ],
442
+ [
443
+ 66.0681,
444
+ 463,
445
+ 4.7546,
446
+ 48,
447
+ 8
448
+ ],
449
+ [
450
+ 34.7278,
451
+ 517,
452
+ 4.7546,
453
+ 48,
454
+ 8
455
+ ],
456
+ [
457
+ 34.7278,
458
+ 589,
459
+ 36.095,
460
+ 26,
461
+ 8
462
+ ],
463
+ [
464
+ 48.0713,
465
+ 640,
466
+ 1.25,
467
+ 16,
468
+ "50%"
469
+ ],
470
+ [
471
+ 49.79,
472
+ 639,
473
+ 7.688,
474
+ 18,
475
+ 8
476
+ ],
477
+ [
478
+ 75.9009,
479
+ 111,
480
+ 10.7581,
481
+ 21,
482
+ 8
483
+ ],
484
+ [
485
+ 75.9009,
486
+ 156,
487
+ 19.8804,
488
+ 120,
489
+ 8
490
+ ]
491
+ ]
492
+ }
493
+ },
494
+ "_hash": "020501954315e528d46c293201a85b5d"
495
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/bones/registry.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ // Auto-generated by `npx boneyard-js build` — do not edit
3
+ import { registerBones } from 'boneyard-js';
4
+ import { configureBoneyard } from 'boneyard-js/react';
5
+ import _booking_widget from './booking-widget.bones.json';
6
+ configureBoneyard({ "color": "#efede7", "animate": "pulse" });
7
+ registerBones({
8
+ "booking-widget": _booking_widget,
9
+ });
10
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/bones/registry.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AACZ,0DAA0D;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,eAAe,MAAM,6BAA6B,CAAA;AAEzD,iBAAiB,CAAC,EAAC,OAAO,EAAC,SAAS,EAAC,SAAS,EAAC,OAAO,EAAC,CAAC,CAAA;AAExD,aAAa,CAAC;IACZ,gBAAgB,EAAE,eAAe;CAClC,CAAC,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import { type ReactNode } from 'react';
2
+ import './bones/registry.js';
2
3
  /**
3
4
  * Pre-existing context required by reschedule mode. The host (or the
4
5
  * customer arriving via magic link) is moving an existing booking, so
@@ -1 +1 @@
1
- {"version":3,"file":"booking-widget.d.ts","sourceRoot":"","sources":["../src/booking-widget.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqH,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AA+CzJ;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB;;qBAEiB;IACjB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB;qCACiC;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB;;sBAEkB;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;yCACqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;oEACgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAA;IAC9B,iBAAiB,CAAC,EAAE,wBAAwB,CAAA;IAC5C;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE;QAC1B,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EAAE,MAAM,CAAA;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,cAAc,GAAG,iBAAiB,CAAA;IAC9E,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AAID,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,WAAW,EACX,YAAY,EACZ,IAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,mBAAyB,EACzB,eAAe,EACf,iBAAiB,GAClB,EAAE,kBAAkB,2CA+jEpB"}
1
+ {"version":3,"file":"booking-widget.d.ts","sourceRoot":"","sources":["../src/booking-widget.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqH,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AASzJ,OAAO,qBAAqB,CAAA;AA4C5B;;;;;GAKG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB;;qBAEiB;IACjB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB;qCACiC;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB;;sBAEkB;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;yCACqC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;oEACgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED,KAAK,kBAAkB,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAA;IAC9B,iBAAiB,CAAC,EAAE,wBAAwB,CAAA;IAC5C;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE;QAC1B,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EAAE,MAAM,CAAA;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,cAAc,GAAG,iBAAiB,CAAA;IAC9E,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AAKD,wBAAgB,kBAAkB,CAAC,EACjC,KAAK,EACL,WAAW,EACX,YAAY,EACZ,IAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,mBAAyB,EACzB,eAAe,EACf,iBAAiB,GAClB,EAAE,kBAAkB,2CA6uEpB"}