@mrcharles/sdk 0.1.0 → 0.2.0
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 +20 -1
- package/dist/mrcharles.esm.js +685 -19
- package/dist/mrcharles.esm.js.map +12 -10
- package/dist/mrcharles.min.js +2 -2
- package/dist/mrcharles.min.js.map +12 -10
- package/dist/types/client.d.ts +16 -0
- package/dist/types/embed.d.ts +3 -1
- package/dist/types/env.d.ts +7 -1
- package/dist/types/format.d.ts +24 -0
- package/dist/types/http.d.ts +1 -1
- package/dist/types/i18n.d.ts +50 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/links.d.ts +4 -3
- package/dist/types/rooms.d.ts +15 -3
- package/dist/types/types.d.ts +8 -5
- package/package.json +1 -1
package/dist/mrcharles.esm.js
CHANGED
|
@@ -45,7 +45,7 @@ function isMrCharlesError(e) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// src/http.ts
|
|
48
|
-
function createHttp(apiBase, config) {
|
|
48
|
+
function createHttp(apiBase, config, getLocale = () => config.locale) {
|
|
49
49
|
const resolved = config.fetch ?? (typeof fetch === "function" ? fetch.bind(globalThis) : undefined);
|
|
50
50
|
if (!resolved)
|
|
51
51
|
throw new Error("@mrcharles/sdk: no fetch available — pass { fetch } in the client config");
|
|
@@ -62,8 +62,9 @@ function createHttp(apiBase, config) {
|
|
|
62
62
|
url += (url.includes("?") ? "&" : "?") + s;
|
|
63
63
|
}
|
|
64
64
|
const headers = { Accept: "application/json", ...config.headers ?? {} };
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
const locale = getLocale();
|
|
66
|
+
if (locale)
|
|
67
|
+
headers["Accept-Language"] = locale;
|
|
67
68
|
if (body !== undefined)
|
|
68
69
|
headers["Content-Type"] = "application/json";
|
|
69
70
|
let res;
|
|
@@ -94,8 +95,565 @@ function createHttp(apiBase, config) {
|
|
|
94
95
|
};
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
// src/i18n.ts
|
|
99
|
+
var LOCALES = ["es", "ca", "en", "fr", "pt", "it", "de"];
|
|
100
|
+
var HOSTED_LOCALES = ["es", "ca", "en"];
|
|
101
|
+
function normalizeLocale(input, fallback = "en") {
|
|
102
|
+
if (!input)
|
|
103
|
+
return fallback;
|
|
104
|
+
const lang = String(input).trim().toLowerCase().split(/[-_]/)[0];
|
|
105
|
+
return LOCALES.includes(lang) ? lang : fallback;
|
|
106
|
+
}
|
|
107
|
+
function toHostedLocale(input, fallback = "en") {
|
|
108
|
+
const l = normalizeLocale(input, fallback);
|
|
109
|
+
return HOSTED_LOCALES.includes(l) ? l : fallback;
|
|
110
|
+
}
|
|
111
|
+
function intlTag(input) {
|
|
112
|
+
const raw = input ? String(input).trim().replace("_", "-") : "";
|
|
113
|
+
if (/^[a-z]{2,3}-[A-Za-z]{2,4}/.test(raw))
|
|
114
|
+
return raw;
|
|
115
|
+
const l = normalizeLocale(raw);
|
|
116
|
+
return { es: "es-ES", ca: "ca-ES", en: "en-GB", fr: "fr-FR", pt: "pt-PT", it: "it-IT", de: "de-DE" }[l];
|
|
117
|
+
}
|
|
118
|
+
function pick(localized, locale, defaultLocale) {
|
|
119
|
+
if (!localized)
|
|
120
|
+
return "";
|
|
121
|
+
const want = toHostedLocale(locale);
|
|
122
|
+
const def = defaultLocale ? toHostedLocale(defaultLocale) : undefined;
|
|
123
|
+
for (const k of [want, def, "es", "ca", "en"]) {
|
|
124
|
+
if (k && localized[k])
|
|
125
|
+
return localized[k];
|
|
126
|
+
}
|
|
127
|
+
const first = Object.values(localized).find((v) => !!v);
|
|
128
|
+
return first ?? "";
|
|
129
|
+
}
|
|
130
|
+
var en = {
|
|
131
|
+
"step.slot": "Pick a time",
|
|
132
|
+
"step.details": "Your details",
|
|
133
|
+
"step.verify": "Enter the code",
|
|
134
|
+
"step.pay": "Confirm",
|
|
135
|
+
"step.done": "Booked",
|
|
136
|
+
"step.expired": "Time ran out",
|
|
137
|
+
"label.date": "Date",
|
|
138
|
+
"label.time": "Time",
|
|
139
|
+
"label.duration": "Duration",
|
|
140
|
+
"label.name": "Name",
|
|
141
|
+
"label.phone": "Mobile",
|
|
142
|
+
"label.email": "Email",
|
|
143
|
+
"label.contact": "Where to send the code",
|
|
144
|
+
"label.code": "Code",
|
|
145
|
+
"label.notes": "Notes for the studio",
|
|
146
|
+
"label.price": "Price",
|
|
147
|
+
"label.deposit": "Deposit",
|
|
148
|
+
"label.total": "Total",
|
|
149
|
+
"label.perHour": "per hour",
|
|
150
|
+
"action.continue": "Continue",
|
|
151
|
+
"action.back": "Back",
|
|
152
|
+
"action.sendCode": "Send me a code",
|
|
153
|
+
"action.verify": "Verify",
|
|
154
|
+
"action.resend": "Send it again",
|
|
155
|
+
"action.confirm": "Confirm booking",
|
|
156
|
+
"action.payCard": "Pay by card",
|
|
157
|
+
"action.payOnSite": "Pay at the studio",
|
|
158
|
+
"action.startOver": "Start again",
|
|
159
|
+
"action.manage": "See or cancel the booking",
|
|
160
|
+
"action.cancel": "Cancel",
|
|
161
|
+
"hint.codeSent": "We sent a code to {contact}.",
|
|
162
|
+
"hint.resendsLeft": "{n} resends left",
|
|
163
|
+
"hint.holdExpiresIn": "Your slot is held for {time}",
|
|
164
|
+
"hint.payOnSite": "Nothing to pay now; settle at the studio.",
|
|
165
|
+
"hint.payCard": "A deposit of {amount} is charged now.",
|
|
166
|
+
"hint.cancelNotice": "Free cancellation up to {hours} h before.",
|
|
167
|
+
"hint.sandbox": "Test mode: nothing is sent or charged, and this booking will be deleted.",
|
|
168
|
+
"hint.noSlots": "No free times that day.",
|
|
169
|
+
"hint.pickDate": "Choose a day to see free times.",
|
|
170
|
+
"done.title": "Booking confirmed",
|
|
171
|
+
"done.reference": "Reference {ref}",
|
|
172
|
+
"done.manage": "Keep this link to see or cancel the booking.",
|
|
173
|
+
"channel.phone": "SMS",
|
|
174
|
+
"channel.email": "Email",
|
|
175
|
+
"pay.card": "Card",
|
|
176
|
+
"pay.on_site": "At the studio",
|
|
177
|
+
"error.VALIDATION_ERROR": "Please check what you typed.",
|
|
178
|
+
"error.RESOURCE_NOT_FOUND": "That was not found.",
|
|
179
|
+
"error.SITE_NOT_LIVE": "This studio is not taking online bookings yet.",
|
|
180
|
+
"error.ROOM_NOT_BOOKABLE": "This room cannot be booked online right now.",
|
|
181
|
+
"error.STUDIO_NOT_VERIFIED": "This studio is not taking online bookings yet.",
|
|
182
|
+
"error.OUTSIDE_HOURS": "The studio is closed at that time.",
|
|
183
|
+
"error.OUTSIDE_BOOKING_WINDOW": "That date cannot be booked yet — try one closer.",
|
|
184
|
+
"error.SLOT_TAKEN": "That time was just taken. Pick another one.",
|
|
185
|
+
"error.HOLD_EXPIRED": "Time ran out. Pick a time again.",
|
|
186
|
+
"error.HOLD_NOT_VERIFIED": "Enter the code first.",
|
|
187
|
+
"error.HOLD_ALREADY_CONFIRMED": "This booking is already confirmed.",
|
|
188
|
+
"error.CODE_INVALID": "That code is not right.",
|
|
189
|
+
"error.TOO_MANY_ATTEMPTS": "Too many tries. Start again.",
|
|
190
|
+
"error.RATE_LIMIT_EXCEEDED": "Too many requests. Wait a minute and try again.",
|
|
191
|
+
"error.CAPTCHA_FAILED": "Please complete the verification challenge.",
|
|
192
|
+
"error.SMS_UNAVAILABLE": "We cannot send SMS right now — use email.",
|
|
193
|
+
"error.PAYMENTS_UNAVAILABLE": "Card payments are unavailable right now — pay at the studio.",
|
|
194
|
+
"error.PAYMENT_MODE_DENIED": "That payment method is not accepted for this room.",
|
|
195
|
+
"error.CANCEL_TOO_LATE": "It is too late to cancel online — call the studio.",
|
|
196
|
+
"error.INVALID_STATE": "That action is not possible right now.",
|
|
197
|
+
"error.INTERNAL_ERROR": "Something went wrong. Try again.",
|
|
198
|
+
"error.NETWORK_ERROR": "No connection. Check your network and try again.",
|
|
199
|
+
"error.default": "Something went wrong. Try again."
|
|
200
|
+
};
|
|
201
|
+
var es = {
|
|
202
|
+
"step.slot": "Elige una hora",
|
|
203
|
+
"step.details": "Tus datos",
|
|
204
|
+
"step.verify": "Introduce el código",
|
|
205
|
+
"step.pay": "Confirmar",
|
|
206
|
+
"step.done": "Reservado",
|
|
207
|
+
"step.expired": "Se agotó el tiempo",
|
|
208
|
+
"label.date": "Fecha",
|
|
209
|
+
"label.time": "Hora",
|
|
210
|
+
"label.duration": "Duración",
|
|
211
|
+
"label.name": "Nombre",
|
|
212
|
+
"label.phone": "Móvil",
|
|
213
|
+
"label.email": "Email",
|
|
214
|
+
"label.contact": "Dónde enviamos el código",
|
|
215
|
+
"label.code": "Código",
|
|
216
|
+
"label.notes": "Notas para el estudio",
|
|
217
|
+
"label.price": "Precio",
|
|
218
|
+
"label.deposit": "Señal",
|
|
219
|
+
"label.total": "Total",
|
|
220
|
+
"label.perHour": "por hora",
|
|
221
|
+
"action.continue": "Continuar",
|
|
222
|
+
"action.back": "Atrás",
|
|
223
|
+
"action.sendCode": "Enviarme un código",
|
|
224
|
+
"action.verify": "Verificar",
|
|
225
|
+
"action.resend": "Reenviar",
|
|
226
|
+
"action.confirm": "Confirmar reserva",
|
|
227
|
+
"action.payCard": "Pagar con tarjeta",
|
|
228
|
+
"action.payOnSite": "Pagar en el estudio",
|
|
229
|
+
"action.startOver": "Empezar de nuevo",
|
|
230
|
+
"action.manage": "Ver o cancelar la reserva",
|
|
231
|
+
"action.cancel": "Cancelar",
|
|
232
|
+
"hint.codeSent": "Hemos enviado un código a {contact}.",
|
|
233
|
+
"hint.resendsLeft": "Quedan {n} reenvíos",
|
|
234
|
+
"hint.holdExpiresIn": "Tu hora está reservada durante {time}",
|
|
235
|
+
"hint.payOnSite": "No pagas nada ahora; se paga en el estudio.",
|
|
236
|
+
"hint.payCard": "Se cobra ahora una señal de {amount}.",
|
|
237
|
+
"hint.cancelNotice": "Cancelación gratuita hasta {hours} h antes.",
|
|
238
|
+
"hint.sandbox": "Modo pruebas: no se envía ni se cobra nada, y esta reserva se borrará.",
|
|
239
|
+
"hint.noSlots": "No hay horas libres ese día.",
|
|
240
|
+
"hint.pickDate": "Elige un día para ver las horas libres.",
|
|
241
|
+
"done.title": "Reserva confirmada",
|
|
242
|
+
"done.reference": "Referencia {ref}",
|
|
243
|
+
"done.manage": "Guarda este enlace para ver o cancelar la reserva.",
|
|
244
|
+
"channel.phone": "SMS",
|
|
245
|
+
"channel.email": "Email",
|
|
246
|
+
"pay.card": "Tarjeta",
|
|
247
|
+
"pay.on_site": "En el estudio",
|
|
248
|
+
"error.VALIDATION_ERROR": "Revisa lo que has escrito.",
|
|
249
|
+
"error.RESOURCE_NOT_FOUND": "No lo encontramos.",
|
|
250
|
+
"error.SITE_NOT_LIVE": "Este estudio aún no acepta reservas online.",
|
|
251
|
+
"error.ROOM_NOT_BOOKABLE": "Esta sala no se puede reservar online ahora mismo.",
|
|
252
|
+
"error.STUDIO_NOT_VERIFIED": "Este estudio aún no acepta reservas online.",
|
|
253
|
+
"error.OUTSIDE_HOURS": "El estudio está cerrado a esa hora.",
|
|
254
|
+
"error.OUTSIDE_BOOKING_WINDOW": "Esa fecha aún no se puede reservar: prueba una más cercana.",
|
|
255
|
+
"error.SLOT_TAKEN": "Esa hora acaba de ocuparse. Elige otra.",
|
|
256
|
+
"error.HOLD_EXPIRED": "Se agotó el tiempo. Vuelve a elegir una hora.",
|
|
257
|
+
"error.HOLD_NOT_VERIFIED": "Introduce primero el código.",
|
|
258
|
+
"error.HOLD_ALREADY_CONFIRMED": "Esta reserva ya está confirmada.",
|
|
259
|
+
"error.CODE_INVALID": "El código no es correcto.",
|
|
260
|
+
"error.TOO_MANY_ATTEMPTS": "Demasiados intentos. Empieza de nuevo.",
|
|
261
|
+
"error.RATE_LIMIT_EXCEEDED": "Demasiadas peticiones. Espera un minuto y vuelve a intentarlo.",
|
|
262
|
+
"error.CAPTCHA_FAILED": "Completa la verificación de seguridad.",
|
|
263
|
+
"error.SMS_UNAVAILABLE": "Ahora no podemos enviar SMS: usa el email.",
|
|
264
|
+
"error.PAYMENTS_UNAVAILABLE": "El pago con tarjeta no está disponible: paga en el estudio.",
|
|
265
|
+
"error.PAYMENT_MODE_DENIED": "Esta sala no acepta ese método de pago.",
|
|
266
|
+
"error.CANCEL_TOO_LATE": "Ya es tarde para cancelar online: llama al estudio.",
|
|
267
|
+
"error.INVALID_STATE": "Esa acción no es posible ahora.",
|
|
268
|
+
"error.INTERNAL_ERROR": "Algo ha fallado. Inténtalo de nuevo.",
|
|
269
|
+
"error.NETWORK_ERROR": "Sin conexión. Revisa tu red e inténtalo de nuevo.",
|
|
270
|
+
"error.default": "Algo ha fallado. Inténtalo de nuevo."
|
|
271
|
+
};
|
|
272
|
+
var ca = {
|
|
273
|
+
"step.slot": "Tria una hora",
|
|
274
|
+
"step.details": "Les teves dades",
|
|
275
|
+
"step.verify": "Introdueix el codi",
|
|
276
|
+
"step.pay": "Confirmar",
|
|
277
|
+
"step.done": "Reservat",
|
|
278
|
+
"step.expired": "S'ha esgotat el temps",
|
|
279
|
+
"label.date": "Data",
|
|
280
|
+
"label.time": "Hora",
|
|
281
|
+
"label.duration": "Durada",
|
|
282
|
+
"label.name": "Nom",
|
|
283
|
+
"label.phone": "Mòbil",
|
|
284
|
+
"label.email": "Correu",
|
|
285
|
+
"label.contact": "On enviem el codi",
|
|
286
|
+
"label.code": "Codi",
|
|
287
|
+
"label.notes": "Notes per a l'estudi",
|
|
288
|
+
"label.price": "Preu",
|
|
289
|
+
"label.deposit": "Paga i senyal",
|
|
290
|
+
"label.total": "Total",
|
|
291
|
+
"label.perHour": "per hora",
|
|
292
|
+
"action.continue": "Continuar",
|
|
293
|
+
"action.back": "Enrere",
|
|
294
|
+
"action.sendCode": "Envia’m un codi",
|
|
295
|
+
"action.verify": "Verificar",
|
|
296
|
+
"action.resend": "Torna a enviar",
|
|
297
|
+
"action.confirm": "Confirmar la reserva",
|
|
298
|
+
"action.payCard": "Pagar amb targeta",
|
|
299
|
+
"action.payOnSite": "Pagar a l'estudi",
|
|
300
|
+
"action.startOver": "Tornar a començar",
|
|
301
|
+
"action.manage": "Veure o cancel·lar la reserva",
|
|
302
|
+
"action.cancel": "Cancel·lar",
|
|
303
|
+
"hint.codeSent": "Hem enviat un codi a {contact}.",
|
|
304
|
+
"hint.resendsLeft": "Queden {n} reenviaments",
|
|
305
|
+
"hint.holdExpiresIn": "La teva hora està reservada durant {time}",
|
|
306
|
+
"hint.payOnSite": "No pagues res ara; es paga a l'estudi.",
|
|
307
|
+
"hint.payCard": "Ara es cobra una paga i senyal de {amount}.",
|
|
308
|
+
"hint.cancelNotice": "Cancel·lació gratuïta fins a {hours} h abans.",
|
|
309
|
+
"hint.sandbox": "Mode de proves: no s'envia ni es cobra res, i aquesta reserva s'esborrarà.",
|
|
310
|
+
"hint.noSlots": "No hi ha hores lliures aquell dia.",
|
|
311
|
+
"hint.pickDate": "Tria un dia per veure les hores lliures.",
|
|
312
|
+
"done.title": "Reserva confirmada",
|
|
313
|
+
"done.reference": "Referència {ref}",
|
|
314
|
+
"done.manage": "Guarda aquest enllaç per veure o cancel·lar la reserva.",
|
|
315
|
+
"channel.phone": "SMS",
|
|
316
|
+
"channel.email": "Correu",
|
|
317
|
+
"pay.card": "Targeta",
|
|
318
|
+
"pay.on_site": "A l'estudi",
|
|
319
|
+
"error.VALIDATION_ERROR": "Revisa el que has escrit.",
|
|
320
|
+
"error.RESOURCE_NOT_FOUND": "No ho hem trobat.",
|
|
321
|
+
"error.SITE_NOT_LIVE": "Aquest estudi encara no accepta reserves en línia.",
|
|
322
|
+
"error.ROOM_NOT_BOOKABLE": "Aquesta sala no es pot reservar en línia ara mateix.",
|
|
323
|
+
"error.STUDIO_NOT_VERIFIED": "Aquest estudi encara no accepta reserves en línia.",
|
|
324
|
+
"error.OUTSIDE_HOURS": "L'estudi està tancat a aquella hora.",
|
|
325
|
+
"error.OUTSIDE_BOOKING_WINDOW": "Aquesta data encara no es pot reservar: prova’n una de més propera.",
|
|
326
|
+
"error.SLOT_TAKEN": "Aquesta hora s'acaba d'ocupar. Tria’n una altra.",
|
|
327
|
+
"error.HOLD_EXPIRED": "S'ha esgotat el temps. Torna a triar una hora.",
|
|
328
|
+
"error.HOLD_NOT_VERIFIED": "Introdueix primer el codi.",
|
|
329
|
+
"error.HOLD_ALREADY_CONFIRMED": "Aquesta reserva ja està confirmada.",
|
|
330
|
+
"error.CODE_INVALID": "El codi no és correcte.",
|
|
331
|
+
"error.TOO_MANY_ATTEMPTS": "Massa intents. Torna a començar.",
|
|
332
|
+
"error.RATE_LIMIT_EXCEEDED": "Massa peticions. Espera un minut i torna-ho a provar.",
|
|
333
|
+
"error.CAPTCHA_FAILED": "Completa la verificació de seguretat.",
|
|
334
|
+
"error.SMS_UNAVAILABLE": "Ara no podem enviar SMS: fes servir el correu.",
|
|
335
|
+
"error.PAYMENTS_UNAVAILABLE": "El pagament amb targeta no està disponible: paga a l'estudi.",
|
|
336
|
+
"error.PAYMENT_MODE_DENIED": "Aquesta sala no accepta aquest mètode de pagament.",
|
|
337
|
+
"error.CANCEL_TOO_LATE": "Ja és tard per cancel·lar en línia: truca a l'estudi.",
|
|
338
|
+
"error.INVALID_STATE": "Aquesta acció no és possible ara.",
|
|
339
|
+
"error.INTERNAL_ERROR": "Alguna cosa ha fallat. Torna-ho a provar.",
|
|
340
|
+
"error.NETWORK_ERROR": "Sense connexió. Revisa la xarxa i torna-ho a provar.",
|
|
341
|
+
"error.default": "Alguna cosa ha fallat. Torna-ho a provar."
|
|
342
|
+
};
|
|
343
|
+
var fr = {
|
|
344
|
+
"step.slot": "Choisissez un horaire",
|
|
345
|
+
"step.details": "Vos coordonnées",
|
|
346
|
+
"step.verify": "Saisissez le code",
|
|
347
|
+
"step.pay": "Confirmer",
|
|
348
|
+
"step.done": "Réservé",
|
|
349
|
+
"step.expired": "Temps écoulé",
|
|
350
|
+
"label.date": "Date",
|
|
351
|
+
"label.time": "Heure",
|
|
352
|
+
"label.duration": "Durée",
|
|
353
|
+
"label.name": "Nom",
|
|
354
|
+
"label.phone": "Mobile",
|
|
355
|
+
"label.email": "E-mail",
|
|
356
|
+
"label.contact": "Où envoyer le code",
|
|
357
|
+
"label.code": "Code",
|
|
358
|
+
"label.notes": "Notes pour le studio",
|
|
359
|
+
"label.price": "Prix",
|
|
360
|
+
"label.deposit": "Acompte",
|
|
361
|
+
"label.total": "Total",
|
|
362
|
+
"label.perHour": "par heure",
|
|
363
|
+
"action.continue": "Continuer",
|
|
364
|
+
"action.back": "Retour",
|
|
365
|
+
"action.sendCode": "Recevoir un code",
|
|
366
|
+
"action.verify": "Vérifier",
|
|
367
|
+
"action.resend": "Renvoyer",
|
|
368
|
+
"action.confirm": "Confirmer la réservation",
|
|
369
|
+
"action.payCard": "Payer par carte",
|
|
370
|
+
"action.payOnSite": "Payer au studio",
|
|
371
|
+
"action.startOver": "Recommencer",
|
|
372
|
+
"action.manage": "Voir ou annuler la réservation",
|
|
373
|
+
"action.cancel": "Annuler",
|
|
374
|
+
"hint.codeSent": "Nous avons envoyé un code à {contact}.",
|
|
375
|
+
"hint.resendsLeft": "{n} renvois restants",
|
|
376
|
+
"hint.holdExpiresIn": "Votre créneau est bloqué pendant {time}",
|
|
377
|
+
"hint.payOnSite": "Rien à payer maintenant ; réglez au studio.",
|
|
378
|
+
"hint.payCard": "Un acompte de {amount} est débité maintenant.",
|
|
379
|
+
"hint.cancelNotice": "Annulation gratuite jusqu'à {hours} h avant.",
|
|
380
|
+
"hint.sandbox": "Mode test : rien n'est envoyé ni débité, et cette réservation sera supprimée.",
|
|
381
|
+
"hint.noSlots": "Aucun horaire libre ce jour-là.",
|
|
382
|
+
"hint.pickDate": "Choisissez un jour pour voir les horaires libres.",
|
|
383
|
+
"done.title": "Réservation confirmée",
|
|
384
|
+
"done.reference": "Référence {ref}",
|
|
385
|
+
"done.manage": "Conservez ce lien pour voir ou annuler la réservation.",
|
|
386
|
+
"channel.phone": "SMS",
|
|
387
|
+
"channel.email": "E-mail",
|
|
388
|
+
"pay.card": "Carte",
|
|
389
|
+
"pay.on_site": "Au studio",
|
|
390
|
+
"error.VALIDATION_ERROR": "Vérifiez ce que vous avez saisi.",
|
|
391
|
+
"error.RESOURCE_NOT_FOUND": "Introuvable.",
|
|
392
|
+
"error.SITE_NOT_LIVE": "Ce studio n'accepte pas encore les réservations en ligne.",
|
|
393
|
+
"error.ROOM_NOT_BOOKABLE": "Cette salle ne peut pas être réservée en ligne pour le moment.",
|
|
394
|
+
"error.STUDIO_NOT_VERIFIED": "Ce studio n'accepte pas encore les réservations en ligne.",
|
|
395
|
+
"error.OUTSIDE_HOURS": "Le studio est fermé à cette heure.",
|
|
396
|
+
"error.OUTSIDE_BOOKING_WINDOW": "Cette date ne peut pas encore être réservée — essayez une date plus proche.",
|
|
397
|
+
"error.SLOT_TAKEN": "Ce créneau vient d'être pris. Choisissez-en un autre.",
|
|
398
|
+
"error.HOLD_EXPIRED": "Temps écoulé. Choisissez à nouveau un horaire.",
|
|
399
|
+
"error.HOLD_NOT_VERIFIED": "Saisissez d'abord le code.",
|
|
400
|
+
"error.HOLD_ALREADY_CONFIRMED": "Cette réservation est déjà confirmée.",
|
|
401
|
+
"error.CODE_INVALID": "Ce code est incorrect.",
|
|
402
|
+
"error.TOO_MANY_ATTEMPTS": "Trop de tentatives. Recommencez.",
|
|
403
|
+
"error.RATE_LIMIT_EXCEEDED": "Trop de requêtes. Patientez une minute et réessayez.",
|
|
404
|
+
"error.CAPTCHA_FAILED": "Veuillez compléter la vérification de sécurité.",
|
|
405
|
+
"error.SMS_UNAVAILABLE": "Impossible d'envoyer des SMS pour le moment — utilisez l'e-mail.",
|
|
406
|
+
"error.PAYMENTS_UNAVAILABLE": "Le paiement par carte est indisponible — payez au studio.",
|
|
407
|
+
"error.PAYMENT_MODE_DENIED": "Ce moyen de paiement n'est pas accepté pour cette salle.",
|
|
408
|
+
"error.CANCEL_TOO_LATE": "Trop tard pour annuler en ligne — appelez le studio.",
|
|
409
|
+
"error.INVALID_STATE": "Cette action n'est pas possible pour le moment.",
|
|
410
|
+
"error.INTERNAL_ERROR": "Une erreur s'est produite. Réessayez.",
|
|
411
|
+
"error.NETWORK_ERROR": "Pas de connexion. Vérifiez votre réseau et réessayez.",
|
|
412
|
+
"error.default": "Une erreur s'est produite. Réessayez."
|
|
413
|
+
};
|
|
414
|
+
var pt = {
|
|
415
|
+
"step.slot": "Escolha uma hora",
|
|
416
|
+
"step.details": "Os seus dados",
|
|
417
|
+
"step.verify": "Introduza o código",
|
|
418
|
+
"step.pay": "Confirmar",
|
|
419
|
+
"step.done": "Reservado",
|
|
420
|
+
"step.expired": "O tempo esgotou-se",
|
|
421
|
+
"label.date": "Data",
|
|
422
|
+
"label.time": "Hora",
|
|
423
|
+
"label.duration": "Duração",
|
|
424
|
+
"label.name": "Nome",
|
|
425
|
+
"label.phone": "Telemóvel",
|
|
426
|
+
"label.email": "E-mail",
|
|
427
|
+
"label.contact": "Para onde enviamos o código",
|
|
428
|
+
"label.code": "Código",
|
|
429
|
+
"label.notes": "Notas para o estúdio",
|
|
430
|
+
"label.price": "Preço",
|
|
431
|
+
"label.deposit": "Sinal",
|
|
432
|
+
"label.total": "Total",
|
|
433
|
+
"label.perHour": "por hora",
|
|
434
|
+
"action.continue": "Continuar",
|
|
435
|
+
"action.back": "Voltar",
|
|
436
|
+
"action.sendCode": "Enviar-me um código",
|
|
437
|
+
"action.verify": "Verificar",
|
|
438
|
+
"action.resend": "Reenviar",
|
|
439
|
+
"action.confirm": "Confirmar reserva",
|
|
440
|
+
"action.payCard": "Pagar com cartão",
|
|
441
|
+
"action.payOnSite": "Pagar no estúdio",
|
|
442
|
+
"action.startOver": "Recomeçar",
|
|
443
|
+
"action.manage": "Ver ou cancelar a reserva",
|
|
444
|
+
"action.cancel": "Cancelar",
|
|
445
|
+
"hint.codeSent": "Enviámos um código para {contact}.",
|
|
446
|
+
"hint.resendsLeft": "Restam {n} reenvios",
|
|
447
|
+
"hint.holdExpiresIn": "A sua hora está reservada durante {time}",
|
|
448
|
+
"hint.payOnSite": "Não paga nada agora; paga no estúdio.",
|
|
449
|
+
"hint.payCard": "É cobrado agora um sinal de {amount}.",
|
|
450
|
+
"hint.cancelNotice": "Cancelamento gratuito até {hours} h antes.",
|
|
451
|
+
"hint.sandbox": "Modo de testes: nada é enviado nem cobrado, e esta reserva será apagada.",
|
|
452
|
+
"hint.noSlots": "Não há horas livres nesse dia.",
|
|
453
|
+
"hint.pickDate": "Escolha um dia para ver as horas livres.",
|
|
454
|
+
"done.title": "Reserva confirmada",
|
|
455
|
+
"done.reference": "Referência {ref}",
|
|
456
|
+
"done.manage": "Guarde esta ligação para ver ou cancelar a reserva.",
|
|
457
|
+
"channel.phone": "SMS",
|
|
458
|
+
"channel.email": "E-mail",
|
|
459
|
+
"pay.card": "Cartão",
|
|
460
|
+
"pay.on_site": "No estúdio",
|
|
461
|
+
"error.VALIDATION_ERROR": "Verifique o que escreveu.",
|
|
462
|
+
"error.RESOURCE_NOT_FOUND": "Não encontrado.",
|
|
463
|
+
"error.SITE_NOT_LIVE": "Este estúdio ainda não aceita reservas online.",
|
|
464
|
+
"error.ROOM_NOT_BOOKABLE": "Esta sala não pode ser reservada online neste momento.",
|
|
465
|
+
"error.STUDIO_NOT_VERIFIED": "Este estúdio ainda não aceita reservas online.",
|
|
466
|
+
"error.OUTSIDE_HOURS": "O estúdio está fechado a essa hora.",
|
|
467
|
+
"error.OUTSIDE_BOOKING_WINDOW": "Essa data ainda não pode ser reservada — tente uma mais próxima.",
|
|
468
|
+
"error.SLOT_TAKEN": "Essa hora acabou de ser ocupada. Escolha outra.",
|
|
469
|
+
"error.HOLD_EXPIRED": "O tempo esgotou-se. Volte a escolher uma hora.",
|
|
470
|
+
"error.HOLD_NOT_VERIFIED": "Introduza primeiro o código.",
|
|
471
|
+
"error.HOLD_ALREADY_CONFIRMED": "Esta reserva já está confirmada.",
|
|
472
|
+
"error.CODE_INVALID": "Esse código não está correto.",
|
|
473
|
+
"error.TOO_MANY_ATTEMPTS": "Demasiadas tentativas. Recomece.",
|
|
474
|
+
"error.RATE_LIMIT_EXCEEDED": "Demasiados pedidos. Aguarde um minuto e tente novamente.",
|
|
475
|
+
"error.CAPTCHA_FAILED": "Complete a verificação de segurança.",
|
|
476
|
+
"error.SMS_UNAVAILABLE": "Não é possível enviar SMS agora — use o e-mail.",
|
|
477
|
+
"error.PAYMENTS_UNAVAILABLE": "O pagamento com cartão está indisponível — pague no estúdio.",
|
|
478
|
+
"error.PAYMENT_MODE_DENIED": "Esse método de pagamento não é aceite nesta sala.",
|
|
479
|
+
"error.CANCEL_TOO_LATE": "É tarde para cancelar online — ligue ao estúdio.",
|
|
480
|
+
"error.INVALID_STATE": "Essa ação não é possível agora.",
|
|
481
|
+
"error.INTERNAL_ERROR": "Algo correu mal. Tente novamente.",
|
|
482
|
+
"error.NETWORK_ERROR": "Sem ligação. Verifique a rede e tente novamente.",
|
|
483
|
+
"error.default": "Algo correu mal. Tente novamente."
|
|
484
|
+
};
|
|
485
|
+
var it = {
|
|
486
|
+
"step.slot": "Scegli un orario",
|
|
487
|
+
"step.details": "I tuoi dati",
|
|
488
|
+
"step.verify": "Inserisci il codice",
|
|
489
|
+
"step.pay": "Conferma",
|
|
490
|
+
"step.done": "Prenotato",
|
|
491
|
+
"step.expired": "Tempo scaduto",
|
|
492
|
+
"label.date": "Data",
|
|
493
|
+
"label.time": "Ora",
|
|
494
|
+
"label.duration": "Durata",
|
|
495
|
+
"label.name": "Nome",
|
|
496
|
+
"label.phone": "Cellulare",
|
|
497
|
+
"label.email": "E-mail",
|
|
498
|
+
"label.contact": "Dove inviare il codice",
|
|
499
|
+
"label.code": "Codice",
|
|
500
|
+
"label.notes": "Note per lo studio",
|
|
501
|
+
"label.price": "Prezzo",
|
|
502
|
+
"label.deposit": "Caparra",
|
|
503
|
+
"label.total": "Totale",
|
|
504
|
+
"label.perHour": "all'ora",
|
|
505
|
+
"action.continue": "Continua",
|
|
506
|
+
"action.back": "Indietro",
|
|
507
|
+
"action.sendCode": "Inviami un codice",
|
|
508
|
+
"action.verify": "Verifica",
|
|
509
|
+
"action.resend": "Invia di nuovo",
|
|
510
|
+
"action.confirm": "Conferma la prenotazione",
|
|
511
|
+
"action.payCard": "Paga con carta",
|
|
512
|
+
"action.payOnSite": "Paga in studio",
|
|
513
|
+
"action.startOver": "Ricomincia",
|
|
514
|
+
"action.manage": "Vedi o annulla la prenotazione",
|
|
515
|
+
"action.cancel": "Annulla",
|
|
516
|
+
"hint.codeSent": "Abbiamo inviato un codice a {contact}.",
|
|
517
|
+
"hint.resendsLeft": "{n} invii rimasti",
|
|
518
|
+
"hint.holdExpiresIn": "Il tuo orario è bloccato per {time}",
|
|
519
|
+
"hint.payOnSite": "Niente da pagare ora; si paga in studio.",
|
|
520
|
+
"hint.payCard": "Viene addebitata ora una caparra di {amount}.",
|
|
521
|
+
"hint.cancelNotice": "Cancellazione gratuita fino a {hours} h prima.",
|
|
522
|
+
"hint.sandbox": "Modalità test: non viene inviato né addebitato nulla, e questa prenotazione sarà eliminata.",
|
|
523
|
+
"hint.noSlots": "Nessun orario libero quel giorno.",
|
|
524
|
+
"hint.pickDate": "Scegli un giorno per vedere gli orari liberi.",
|
|
525
|
+
"done.title": "Prenotazione confermata",
|
|
526
|
+
"done.reference": "Riferimento {ref}",
|
|
527
|
+
"done.manage": "Conserva questo link per vedere o annullare la prenotazione.",
|
|
528
|
+
"channel.phone": "SMS",
|
|
529
|
+
"channel.email": "E-mail",
|
|
530
|
+
"pay.card": "Carta",
|
|
531
|
+
"pay.on_site": "In studio",
|
|
532
|
+
"error.VALIDATION_ERROR": "Controlla quello che hai scritto.",
|
|
533
|
+
"error.RESOURCE_NOT_FOUND": "Non trovato.",
|
|
534
|
+
"error.SITE_NOT_LIVE": "Questo studio non accetta ancora prenotazioni online.",
|
|
535
|
+
"error.ROOM_NOT_BOOKABLE": "Questa sala non è prenotabile online al momento.",
|
|
536
|
+
"error.STUDIO_NOT_VERIFIED": "Questo studio non accetta ancora prenotazioni online.",
|
|
537
|
+
"error.OUTSIDE_HOURS": "Lo studio è chiuso a quell’ora.",
|
|
538
|
+
"error.OUTSIDE_BOOKING_WINDOW": "Quella data non è ancora prenotabile — provane una più vicina.",
|
|
539
|
+
"error.SLOT_TAKEN": "Quell’orario è appena stato preso. Scegline un altro.",
|
|
540
|
+
"error.HOLD_EXPIRED": "Tempo scaduto. Scegli di nuovo un orario.",
|
|
541
|
+
"error.HOLD_NOT_VERIFIED": "Inserisci prima il codice.",
|
|
542
|
+
"error.HOLD_ALREADY_CONFIRMED": "Questa prenotazione è già confermata.",
|
|
543
|
+
"error.CODE_INVALID": "Il codice non è corretto.",
|
|
544
|
+
"error.TOO_MANY_ATTEMPTS": "Troppi tentativi. Ricomincia.",
|
|
545
|
+
"error.RATE_LIMIT_EXCEEDED": "Troppe richieste. Aspetta un minuto e riprova.",
|
|
546
|
+
"error.CAPTCHA_FAILED": "Completa la verifica di sicurezza.",
|
|
547
|
+
"error.SMS_UNAVAILABLE": "Non possiamo inviare SMS al momento — usa l’e-mail.",
|
|
548
|
+
"error.PAYMENTS_UNAVAILABLE": "Il pagamento con carta non è disponibile — paga in studio.",
|
|
549
|
+
"error.PAYMENT_MODE_DENIED": "Questo metodo di pagamento non è accettato per questa sala.",
|
|
550
|
+
"error.CANCEL_TOO_LATE": "È troppo tardi per annullare online — chiama lo studio.",
|
|
551
|
+
"error.INVALID_STATE": "Questa azione non è possibile adesso.",
|
|
552
|
+
"error.INTERNAL_ERROR": "Qualcosa è andato storto. Riprova.",
|
|
553
|
+
"error.NETWORK_ERROR": "Nessuna connessione. Controlla la rete e riprova.",
|
|
554
|
+
"error.default": "Qualcosa è andato storto. Riprova."
|
|
555
|
+
};
|
|
556
|
+
var de = {
|
|
557
|
+
"step.slot": "Zeit wählen",
|
|
558
|
+
"step.details": "Deine Angaben",
|
|
559
|
+
"step.verify": "Code eingeben",
|
|
560
|
+
"step.pay": "Bestätigen",
|
|
561
|
+
"step.done": "Gebucht",
|
|
562
|
+
"step.expired": "Zeit abgelaufen",
|
|
563
|
+
"label.date": "Datum",
|
|
564
|
+
"label.time": "Uhrzeit",
|
|
565
|
+
"label.duration": "Dauer",
|
|
566
|
+
"label.name": "Name",
|
|
567
|
+
"label.phone": "Handy",
|
|
568
|
+
"label.email": "E-Mail",
|
|
569
|
+
"label.contact": "Wohin wir den Code schicken",
|
|
570
|
+
"label.code": "Code",
|
|
571
|
+
"label.notes": "Hinweise für das Studio",
|
|
572
|
+
"label.price": "Preis",
|
|
573
|
+
"label.deposit": "Anzahlung",
|
|
574
|
+
"label.total": "Gesamt",
|
|
575
|
+
"label.perHour": "pro Stunde",
|
|
576
|
+
"action.continue": "Weiter",
|
|
577
|
+
"action.back": "Zurück",
|
|
578
|
+
"action.sendCode": "Code schicken",
|
|
579
|
+
"action.verify": "Prüfen",
|
|
580
|
+
"action.resend": "Erneut senden",
|
|
581
|
+
"action.confirm": "Buchung bestätigen",
|
|
582
|
+
"action.payCard": "Mit Karte zahlen",
|
|
583
|
+
"action.payOnSite": "Im Studio zahlen",
|
|
584
|
+
"action.startOver": "Von vorn beginnen",
|
|
585
|
+
"action.manage": "Buchung ansehen oder stornieren",
|
|
586
|
+
"action.cancel": "Abbrechen",
|
|
587
|
+
"hint.codeSent": "Wir haben einen Code an {contact} geschickt.",
|
|
588
|
+
"hint.resendsLeft": "Noch {n} Versuche",
|
|
589
|
+
"hint.holdExpiresIn": "Dein Termin ist {time} lang reserviert",
|
|
590
|
+
"hint.payOnSite": "Jetzt nichts zu zahlen; du zahlst im Studio.",
|
|
591
|
+
"hint.payCard": "Eine Anzahlung von {amount} wird jetzt abgebucht.",
|
|
592
|
+
"hint.cancelNotice": "Kostenlose Stornierung bis {hours} h vorher.",
|
|
593
|
+
"hint.sandbox": "Testmodus: nichts wird gesendet oder abgebucht, und diese Buchung wird gelöscht.",
|
|
594
|
+
"hint.noSlots": "An diesem Tag ist nichts frei.",
|
|
595
|
+
"hint.pickDate": "Wähle einen Tag, um freie Zeiten zu sehen.",
|
|
596
|
+
"done.title": "Buchung bestätigt",
|
|
597
|
+
"done.reference": "Referenz {ref}",
|
|
598
|
+
"done.manage": "Bewahre diesen Link auf, um die Buchung anzusehen oder zu stornieren.",
|
|
599
|
+
"channel.phone": "SMS",
|
|
600
|
+
"channel.email": "E-Mail",
|
|
601
|
+
"pay.card": "Karte",
|
|
602
|
+
"pay.on_site": "Im Studio",
|
|
603
|
+
"error.VALIDATION_ERROR": "Bitte prüfe deine Eingaben.",
|
|
604
|
+
"error.RESOURCE_NOT_FOUND": "Nicht gefunden.",
|
|
605
|
+
"error.SITE_NOT_LIVE": "Dieses Studio nimmt noch keine Online-Buchungen an.",
|
|
606
|
+
"error.ROOM_NOT_BOOKABLE": "Dieser Raum kann gerade nicht online gebucht werden.",
|
|
607
|
+
"error.STUDIO_NOT_VERIFIED": "Dieses Studio nimmt noch keine Online-Buchungen an.",
|
|
608
|
+
"error.OUTSIDE_HOURS": "Das Studio ist zu dieser Zeit geschlossen.",
|
|
609
|
+
"error.OUTSIDE_BOOKING_WINDOW": "Dieses Datum kann noch nicht gebucht werden — versuche ein näheres.",
|
|
610
|
+
"error.SLOT_TAKEN": "Diese Zeit wurde gerade vergeben. Wähle eine andere.",
|
|
611
|
+
"error.HOLD_EXPIRED": "Zeit abgelaufen. Wähle die Zeit erneut.",
|
|
612
|
+
"error.HOLD_NOT_VERIFIED": "Gib zuerst den Code ein.",
|
|
613
|
+
"error.HOLD_ALREADY_CONFIRMED": "Diese Buchung ist bereits bestätigt.",
|
|
614
|
+
"error.CODE_INVALID": "Der Code ist falsch.",
|
|
615
|
+
"error.TOO_MANY_ATTEMPTS": "Zu viele Versuche. Beginne von vorn.",
|
|
616
|
+
"error.RATE_LIMIT_EXCEEDED": "Zu viele Anfragen. Warte eine Minute und versuche es erneut.",
|
|
617
|
+
"error.CAPTCHA_FAILED": "Bitte schließe die Sicherheitsprüfung ab.",
|
|
618
|
+
"error.SMS_UNAVAILABLE": "SMS sind gerade nicht möglich — nutze E-Mail.",
|
|
619
|
+
"error.PAYMENTS_UNAVAILABLE": "Kartenzahlung ist gerade nicht verfügbar — zahle im Studio.",
|
|
620
|
+
"error.PAYMENT_MODE_DENIED": "Diese Zahlungsart wird für diesen Raum nicht akzeptiert.",
|
|
621
|
+
"error.CANCEL_TOO_LATE": "Für eine Online-Stornierung ist es zu spät — ruf das Studio an.",
|
|
622
|
+
"error.INVALID_STATE": "Diese Aktion ist gerade nicht möglich.",
|
|
623
|
+
"error.INTERNAL_ERROR": "Etwas ist schiefgelaufen. Versuche es erneut.",
|
|
624
|
+
"error.NETWORK_ERROR": "Keine Verbindung. Prüfe dein Netzwerk und versuche es erneut.",
|
|
625
|
+
"error.default": "Etwas ist schiefgelaufen. Versuche es erneut."
|
|
626
|
+
};
|
|
627
|
+
var MESSAGES = { en, es, ca, fr, pt, it, de };
|
|
628
|
+
function t(locale, key, params) {
|
|
629
|
+
const dict = MESSAGES[normalizeLocale(locale)];
|
|
630
|
+
let s = dict[key] ?? en[key] ?? key;
|
|
631
|
+
if (params)
|
|
632
|
+
for (const [k, v] of Object.entries(params))
|
|
633
|
+
s = s.split(`{${k}}`).join(String(v));
|
|
634
|
+
return s;
|
|
635
|
+
}
|
|
636
|
+
function messageFor(error, locale) {
|
|
637
|
+
const e = error ?? {};
|
|
638
|
+
const dict = MESSAGES[normalizeLocale(locale)];
|
|
639
|
+
if (e.code && dict[`error.${e.code}`])
|
|
640
|
+
return dict[`error.${e.code}`];
|
|
641
|
+
if (e.code && en[`error.${e.code}`])
|
|
642
|
+
return en[`error.${e.code}`];
|
|
643
|
+
return dict["error.default"];
|
|
644
|
+
}
|
|
645
|
+
function translator(locale) {
|
|
646
|
+
const l = normalizeLocale(locale);
|
|
647
|
+
const fn = (key, params) => t(l, key, params);
|
|
648
|
+
fn.locale = l;
|
|
649
|
+
fn.error = (error) => messageFor(error, l);
|
|
650
|
+
return fn;
|
|
651
|
+
}
|
|
652
|
+
|
|
97
653
|
// src/links.ts
|
|
98
|
-
function createLinks(endpoints) {
|
|
654
|
+
function createLinks(endpoints, getLocale = () => {
|
|
655
|
+
return;
|
|
656
|
+
}) {
|
|
99
657
|
const siteOrigin = (slug) => `https://${slug}.${endpoints.sitesBase}`;
|
|
100
658
|
return {
|
|
101
659
|
site(slug) {
|
|
@@ -109,8 +667,9 @@ function createLinks(endpoints) {
|
|
|
109
667
|
q.set("start", o.start);
|
|
110
668
|
if (o.durationMinutes)
|
|
111
669
|
q.set("duration", String(o.durationMinutes));
|
|
112
|
-
|
|
113
|
-
|
|
670
|
+
const lang = o.lang ?? getLocale();
|
|
671
|
+
if (lang)
|
|
672
|
+
q.set("lang", toHostedLocale(lang));
|
|
114
673
|
if (o.embed)
|
|
115
674
|
q.set("embed", "1");
|
|
116
675
|
const s = q.toString();
|
|
@@ -157,8 +716,10 @@ function parseEmbedEvent(data) {
|
|
|
157
716
|
}
|
|
158
717
|
return null;
|
|
159
718
|
}
|
|
160
|
-
function createEmbed(endpoints) {
|
|
161
|
-
|
|
719
|
+
function createEmbed(endpoints, getLocale = () => {
|
|
720
|
+
return;
|
|
721
|
+
}) {
|
|
722
|
+
const links = createLinks(endpoints, getLocale);
|
|
162
723
|
function mountRoomBooking(target, opts) {
|
|
163
724
|
const el = typeof target === "string" ? document.querySelector(target) : target;
|
|
164
725
|
if (!el)
|
|
@@ -196,6 +757,9 @@ function createEmbed(endpoints) {
|
|
|
196
757
|
el.appendChild(iframe);
|
|
197
758
|
return {
|
|
198
759
|
iframe,
|
|
760
|
+
setLang(lang) {
|
|
761
|
+
iframe.src = links.bookRoom({ ...opts, lang, embed: true });
|
|
762
|
+
},
|
|
199
763
|
destroy() {
|
|
200
764
|
window.removeEventListener("message", onMessage);
|
|
201
765
|
iframe.remove();
|
|
@@ -276,8 +840,20 @@ function addDays(key, days) {
|
|
|
276
840
|
}
|
|
277
841
|
|
|
278
842
|
// src/rooms.ts
|
|
279
|
-
function createRoomBooking(http, links, opts) {
|
|
280
|
-
|
|
843
|
+
function createRoomBooking(http, links, opts, clientLocale) {
|
|
844
|
+
const explicit = opts.locale ?? clientLocale?.();
|
|
845
|
+
let state = {
|
|
846
|
+
step: "slot",
|
|
847
|
+
site: null,
|
|
848
|
+
room: null,
|
|
849
|
+
availability: null,
|
|
850
|
+
hold: null,
|
|
851
|
+
confirmation: null,
|
|
852
|
+
error: null,
|
|
853
|
+
errorMessage: null,
|
|
854
|
+
busy: false,
|
|
855
|
+
locale: normalizeLocale(explicit, "es")
|
|
856
|
+
};
|
|
281
857
|
const listeners = new Set;
|
|
282
858
|
if (opts.onChange)
|
|
283
859
|
listeners.add(opts.onChange);
|
|
@@ -285,6 +861,8 @@ function createRoomBooking(http, links, opts) {
|
|
|
285
861
|
let expiryTimer;
|
|
286
862
|
const set = (patch) => {
|
|
287
863
|
state = { ...state, ...patch };
|
|
864
|
+
if ("error" in patch || "locale" in patch)
|
|
865
|
+
state.errorMessage = state.error ? messageFor(state.error, state.locale) : null;
|
|
288
866
|
for (const l of listeners)
|
|
289
867
|
l(state);
|
|
290
868
|
};
|
|
@@ -301,7 +879,7 @@ function createRoomBooking(http, links, opts) {
|
|
|
301
879
|
expiryTimer = setTimeout(() => {
|
|
302
880
|
if (state.step === "verify" || state.step === "pay") {
|
|
303
881
|
idempotencyKey = uuid();
|
|
304
|
-
set({ step: "expired", error: new MrCharlesError("HOLD_EXPIRED",
|
|
882
|
+
set({ step: "expired", error: new MrCharlesError("HOLD_EXPIRED", t(state.locale, "error.HOLD_EXPIRED"), 410) });
|
|
305
883
|
}
|
|
306
884
|
}, ms);
|
|
307
885
|
};
|
|
@@ -341,7 +919,8 @@ function createRoomBooking(http, links, opts) {
|
|
|
341
919
|
throw new MrCharlesError("RESOURCE_NOT_FOUND", `Room ${opts.roomId} is not on site ${opts.slug}`, 404);
|
|
342
920
|
if (!room.bookable)
|
|
343
921
|
throw new MrCharlesError("ROOM_NOT_BOOKABLE", `${room.name} does not take online bookings right now`, 409);
|
|
344
|
-
|
|
922
|
+
const locale = explicit ? state.locale : normalizeLocale(site.site?.defaultLocale, state.locale);
|
|
923
|
+
set({ site, room, locale });
|
|
345
924
|
return site;
|
|
346
925
|
}),
|
|
347
926
|
loadAvailability: (window2 = {}) => run(async () => {
|
|
@@ -377,7 +956,7 @@ function createRoomBooking(http, links, opts) {
|
|
|
377
956
|
guestName: details.guestName,
|
|
378
957
|
channel: details.channel,
|
|
379
958
|
contact: details.contact,
|
|
380
|
-
locale: details.locale ?? state.
|
|
959
|
+
locale: toHostedLocale(details.locale ?? state.locale),
|
|
381
960
|
idempotencyKey,
|
|
382
961
|
...details.captchaToken ? { captchaToken: details.captchaToken } : {}
|
|
383
962
|
};
|
|
@@ -425,6 +1004,10 @@ function createRoomBooking(http, links, opts) {
|
|
|
425
1004
|
idempotencyKey = uuid();
|
|
426
1005
|
set({ step: "slot", hold: null, confirmation: null, error: null, busy: false });
|
|
427
1006
|
},
|
|
1007
|
+
t: (key, params) => t(state.locale, key, params),
|
|
1008
|
+
setLocale(locale) {
|
|
1009
|
+
set({ locale: normalizeLocale(locale, state.locale) });
|
|
1010
|
+
},
|
|
428
1011
|
destroy() {
|
|
429
1012
|
clearTimer();
|
|
430
1013
|
listeners.clear();
|
|
@@ -438,12 +1021,68 @@ function requireHold(s) {
|
|
|
438
1021
|
return s.hold;
|
|
439
1022
|
}
|
|
440
1023
|
|
|
1024
|
+
// src/format.ts
|
|
1025
|
+
function createFormatter(locale) {
|
|
1026
|
+
const tag = intlTag(locale);
|
|
1027
|
+
const lang = normalizeLocale(locale);
|
|
1028
|
+
const hourAbbr = { es: ["h", "min"], ca: ["h", "min"], en: ["h", "min"], fr: ["h", "min"], pt: ["h", "min"], it: ["h", "min"], de: ["Std.", "Min."] };
|
|
1029
|
+
const [hU, mU] = hourAbbr[lang] ?? ["h", "min"];
|
|
1030
|
+
const safe = (fn, fallback) => {
|
|
1031
|
+
try {
|
|
1032
|
+
return fn();
|
|
1033
|
+
} catch {
|
|
1034
|
+
return fallback;
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
return {
|
|
1038
|
+
locale: tag,
|
|
1039
|
+
price: (cents, currency) => safe(() => new Intl.NumberFormat(tag, { style: "currency", currency }).format(cents / 100), `${(cents / 100).toFixed(2)} ${currency}`),
|
|
1040
|
+
date: (key, style = "long") => {
|
|
1041
|
+
const [y, m, d] = key.split("-").map(Number);
|
|
1042
|
+
const dt = new Date(Date.UTC(y, m - 1, d, 12));
|
|
1043
|
+
const opts = style === "long" ? { weekday: "long", day: "numeric", month: "long", timeZone: "UTC" } : style === "medium" ? { weekday: "short", day: "numeric", month: "short", timeZone: "UTC" } : { day: "2-digit", month: "2-digit", timeZone: "UTC" };
|
|
1044
|
+
return safe(() => new Intl.DateTimeFormat(tag, opts).format(dt), key);
|
|
1045
|
+
},
|
|
1046
|
+
time: (hhmm) => {
|
|
1047
|
+
const [h, mi] = hhmm.split(":").map(Number);
|
|
1048
|
+
const dt = new Date(Date.UTC(2000, 0, 1, h, mi));
|
|
1049
|
+
return safe(() => new Intl.DateTimeFormat(tag, { hour: "numeric", minute: "2-digit", timeZone: "UTC" }).format(dt), hhmm);
|
|
1050
|
+
},
|
|
1051
|
+
timeRange(start, minutes) {
|
|
1052
|
+
const [h, mi] = start.split(":").map(Number);
|
|
1053
|
+
const total = h * 60 + mi + minutes;
|
|
1054
|
+
const end = `${String(Math.floor(total / 60) % 24).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
|
|
1055
|
+
return `${this.time(start)} – ${this.time(end)}`;
|
|
1056
|
+
},
|
|
1057
|
+
duration: (minutes) => {
|
|
1058
|
+
const h = Math.floor(minutes / 60);
|
|
1059
|
+
const m = minutes % 60;
|
|
1060
|
+
if (h && m)
|
|
1061
|
+
return `${h} ${hU} ${m} ${mU}`;
|
|
1062
|
+
if (h)
|
|
1063
|
+
return `${h} ${hU}`;
|
|
1064
|
+
return `${m} ${mU}`;
|
|
1065
|
+
},
|
|
1066
|
+
weekday: (dow, style = "short") => {
|
|
1067
|
+
const dt = new Date(Date.UTC(2023, 0, 1 + (dow % 7 + 7) % 7, 12));
|
|
1068
|
+
return safe(() => new Intl.DateTimeFormat(tag, { weekday: style, timeZone: "UTC" }).format(dt), String(dow));
|
|
1069
|
+
},
|
|
1070
|
+
countdown: (ms) => {
|
|
1071
|
+
const s = Math.max(0, Math.floor(ms / 1000));
|
|
1072
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
|
|
441
1077
|
// src/client.ts
|
|
442
1078
|
function createClient(config = {}) {
|
|
443
1079
|
const endpoints = resolveEndpoints(config);
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const
|
|
1080
|
+
let locale = config.locale ? normalizeLocale(config.locale) : undefined;
|
|
1081
|
+
let formatter;
|
|
1082
|
+
const getLocale = () => locale;
|
|
1083
|
+
const http = createHttp(endpoints.apiBase, config, getLocale);
|
|
1084
|
+
const links = createLinks(endpoints, getLocale);
|
|
1085
|
+
const embed = createEmbed(endpoints, getLocale);
|
|
447
1086
|
const enc = encodeURIComponent;
|
|
448
1087
|
const sites = {
|
|
449
1088
|
get: (slug) => http.get(`/public/sites/${enc(slug)}`),
|
|
@@ -476,34 +1115,61 @@ function createClient(config = {}) {
|
|
|
476
1115
|
teachers,
|
|
477
1116
|
links,
|
|
478
1117
|
mountRoomBooking: embed.mountRoomBooking,
|
|
479
|
-
roomBooking: (opts) => createRoomBooking(http, links, opts)
|
|
1118
|
+
roomBooking: (opts) => createRoomBooking(http, links, opts, getLocale),
|
|
1119
|
+
get locale() {
|
|
1120
|
+
return locale;
|
|
1121
|
+
},
|
|
1122
|
+
get hostedLocale() {
|
|
1123
|
+
return locale ? toHostedLocale(locale) : "es";
|
|
1124
|
+
},
|
|
1125
|
+
setLocale(next) {
|
|
1126
|
+
locale = next ? normalizeLocale(next) : undefined;
|
|
1127
|
+
formatter = undefined;
|
|
1128
|
+
},
|
|
1129
|
+
t: (key, params) => t(locale ?? "en", key, params),
|
|
1130
|
+
errorMessage: (error) => messageFor(error, locale ?? "en"),
|
|
1131
|
+
get format() {
|
|
1132
|
+
return formatter ??= createFormatter(locale ?? "en");
|
|
1133
|
+
},
|
|
1134
|
+
pick: (localized, defaultLocale) => pick(localized, locale ?? defaultLocale ?? "es", defaultLocale)
|
|
480
1135
|
};
|
|
481
1136
|
}
|
|
482
1137
|
|
|
483
1138
|
// src/index.ts
|
|
484
|
-
var VERSION = "0.
|
|
1139
|
+
var VERSION = "0.2.0";
|
|
485
1140
|
export {
|
|
486
1141
|
ENVIRONMENTS,
|
|
1142
|
+
HOSTED_LOCALES,
|
|
1143
|
+
LOCALES,
|
|
1144
|
+
MESSAGES,
|
|
487
1145
|
MrCharlesError,
|
|
488
1146
|
VERSION,
|
|
489
1147
|
addDays,
|
|
490
1148
|
addMinutes,
|
|
491
1149
|
createClient,
|
|
492
1150
|
createEmbed,
|
|
1151
|
+
createFormatter,
|
|
493
1152
|
createLinks,
|
|
494
1153
|
createRoomBooking,
|
|
495
1154
|
dateKey,
|
|
496
1155
|
durationOptions,
|
|
497
1156
|
formatPrice,
|
|
1157
|
+
intlTag,
|
|
498
1158
|
isMrCharlesError,
|
|
499
1159
|
isTrustedOrigin,
|
|
1160
|
+
messageFor,
|
|
1161
|
+
normalizeLocale,
|
|
500
1162
|
parseEmbedEvent,
|
|
1163
|
+
pick,
|
|
501
1164
|
resolveEndpoints,
|
|
502
1165
|
startTimes,
|
|
1166
|
+
t,
|
|
503
1167
|
toHHMM,
|
|
1168
|
+
toHostedLocale,
|
|
504
1169
|
toMinutes,
|
|
1170
|
+
translator,
|
|
505
1171
|
uuid
|
|
506
1172
|
};
|
|
507
1173
|
|
|
508
|
-
//# debugId=
|
|
1174
|
+
//# debugId=1DCD9FB716D0EA3864756E2164756E21
|
|
509
1175
|
//# sourceMappingURL=mrcharles.esm.js.map
|