orionplace-react 0.7.6 → 0.7.8

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.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/templates/components/orionplace/legal/StorefrontPrivacy.README.md +2 -2
  4. package/templates/components/orionplace/orders/OrderDetails.tsx +2 -2
  5. package/templates/components/orionplace/search/MainSearch.tsx +2 -2
  6. package/templates/components/orionplace/shared/OrionplaceBreadcrumbs.tsx +1 -1
  7. package/templates/components/orionplace/shared/StorefrontPagination.tsx +1 -1
  8. package/templates/components/orionplace/storefront/StorefrontFooter.tsx +1 -1
  9. package/templates/hooks/useOrionplaceStorefront.tsx +26 -8
  10. package/templates/lib/orionplace/accountLabels.ts +7 -0
  11. package/templates/lib/orionplace/activityLabels.ts +32 -1
  12. package/templates/lib/orionplace/blogLabels.ts +47 -1
  13. package/templates/lib/orionplace/browseLabels.ts +48 -1
  14. package/templates/lib/orionplace/cartLabels.ts +128 -1
  15. package/templates/lib/orionplace/cartPreview.ts +15 -0
  16. package/templates/lib/orionplace/categoryLabels.ts +9 -1
  17. package/templates/lib/orionplace/contactLabels.ts +51 -1
  18. package/templates/lib/orionplace/directoryLabels.ts +66 -1
  19. package/templates/lib/orionplace/errorPageLabels.ts +15 -0
  20. package/templates/lib/orionplace/featureLabels.ts +136 -1
  21. package/templates/lib/orionplace/featuredLabels.ts +10 -0
  22. package/templates/lib/orionplace/footerLabels.ts +17 -1
  23. package/templates/lib/orionplace/headerLabels.ts +30 -0
  24. package/templates/lib/orionplace/homeLabels.ts +22 -0
  25. package/templates/lib/orionplace/manufacturerIndex.ts +15 -0
  26. package/templates/lib/orionplace/orderLabels.ts +148 -1
  27. package/templates/lib/orionplace/orderPresentation.ts +45 -2
  28. package/templates/lib/orionplace/privacyLabels.ts +77 -2
  29. package/templates/lib/orionplace/productActionLabels.ts +48 -0
  30. package/templates/lib/orionplace/productLabels.ts +37 -0
  31. package/templates/lib/orionplace/profileLabels.ts +75 -1
  32. package/templates/lib/orionplace/promoLabels.ts +37 -1
  33. package/templates/lib/orionplace/reviewLabels.ts +81 -1
  34. package/templates/lib/orionplace/sessionLabels.ts +12 -1
  35. package/templates/lib/orionplace/subscriptionLabels.ts +34 -1
  36. package/templates/lib/orionplace/subscriptionManageLabels.ts +118 -2
  37. package/templates/lib/orionplace/tariffLabels.ts +54 -3
  38. package/templates/lib/orionplace/tariffVariables.ts +38 -0
  39. package/templates/lib/orionplace/ticketLabels.ts +94 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  `orionplace-react` creates a server-rendered shop connected to your personal
6
6
  marketplace API: catalog and search, product pages, cart and checkout, customer
7
- account, journal and support, in English and Russian. The package includes a
7
+ account, journal and support, in English, Russian and Spanish. The package includes a
8
8
  setup CLI and the full storefront source, so every page and component in your
9
9
  project is yours to change.
10
10
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "orionplace-react",
3
- "version": "0.7.6",
4
- "description": "Next.js 16 storefront for your Orionplace marketplace: setup CLI, server API gateway, typed store and ready-made catalog, cart, checkout and account components in English and Russian, plus an Android app build.",
3
+ "version": "0.7.8",
4
+ "description": "Next.js 16 storefront for your Orionplace marketplace: setup CLI, server API gateway, typed store and ready-made catalog, cart, checkout and account components in English, Russian and Spanish, plus an Android app build.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Orionplace",
@@ -10,7 +10,7 @@ the cookies section has the anchor `#cookies`. The operator block shows the
10
10
  company name, tax ID, registration number, address and phone from the
11
11
  Application Load contact details; empty values are left out.
12
12
 
13
- The texts are in `lib/orionplace/privacyLabels.ts`, in Russian and English. A
13
+ The texts are in `lib/orionplace/privacyLabels.ts`, in Russian, English and Spanish. A
14
14
  store that adds analytics, chat widgets or other cookies lists them in the
15
15
  cookies section.
16
16
 
@@ -34,7 +34,7 @@ Source is maintained in
34
34
  показывает название компании, ИНН, регистрационный номер, адрес и телефон из
35
35
  контактов загрузки приложения; пустые значения не выводятся.
36
36
 
37
- Тексты — в `lib/orionplace/privacyLabels.ts`, на русском и английском.
37
+ Тексты — в `lib/orionplace/privacyLabels.ts`, на русском, английском и испанском.
38
38
  Магазин, который добавит аналитику, чат или другие cookie, перечисляет их в
39
39
  разделе о cookie.
40
40
 
@@ -404,8 +404,8 @@ export function OrderDetails({
404
404
  {step.current ? (
405
405
  <span className={styles.stepState}>{labels.stepStates.current}</span>
406
406
  ) : null}
407
- {step.key === 'placed' && order.time_submited?.date ? (
408
- <span className={styles.stepNote}>{order.time_submited.date}</span>
407
+ {step.date ? (
408
+ <span className={styles.stepNote}>{step.date}</span>
409
409
  ) : step.total > 1 ? (
410
410
  <span className={styles.stepNote}>
411
411
  {step.count} {labels.of} {step.total}
@@ -68,7 +68,7 @@ export function MainSearch({
68
68
  Boolean(historyPending || history.length || historyError);
69
69
  const removeLabel = locale.toLowerCase().startsWith('ru')
70
70
  ? 'Удалить из истории'
71
- : 'Remove from history';
71
+ : locale.toLowerCase().startsWith('es') ? 'Quitar del historial' : 'Remove from history';
72
72
 
73
73
  useEffect(() => {
74
74
  if (!query && routeQuery) {
@@ -164,7 +164,7 @@ export function MainSearch({
164
164
  className={styles.historyStatus}
165
165
  role="status"
166
166
  >
167
- {locale.toLowerCase().startsWith('ru') ? 'Загрузка…' : 'Loading…'}
167
+ {locale.toLowerCase().startsWith('ru') ? 'Загрузка…' : locale.toLowerCase().startsWith('es') ? 'Cargando…' : 'Loading…'}
168
168
  </span>
169
169
  ) : historyError ? (
170
170
  <p
@@ -15,7 +15,7 @@ export interface OrionplaceBreadcrumbsProps {
15
15
  function labels(locale: string) {
16
16
  return locale.toLowerCase().startsWith('ru')
17
17
  ? { path: 'Путь к странице', home: 'Главная' }
18
- : { path: 'Page path', home: 'Home' };
18
+ : locale.toLowerCase().startsWith('es') ? { path: 'Ruta de la página', home: 'Inicio' } : { path: 'Page path', home: 'Home' };
19
19
  }
20
20
 
21
21
  export function OrionplaceBreadcrumbs({
@@ -36,7 +36,7 @@ export function StorefrontPagination({
36
36
  const localeCode = useOrionplaceSelections((state) => state.localeCode);
37
37
  const labels = localeCode.toLowerCase().startsWith('ru')
38
38
  ? { page: 'Страница', previous: 'Назад', next: 'Далее' }
39
- : { page: 'Page', previous: 'Previous', next: 'Next' };
39
+ : localeCode.toLowerCase().startsWith('es') ? { page: 'Página', previous: 'Anterior', next: 'Siguiente' } : { page: 'Page', previous: 'Previous', next: 'Next' };
40
40
  const page = pagination.page ?? 1;
41
41
  const nav = useRef<HTMLElement>(null);
42
42
 
@@ -53,7 +53,7 @@ export function StorefrontFooter({
53
53
  catalog: labels.catalog,
54
54
  tariffs: locale.toLowerCase().startsWith('ru')
55
55
  ? 'Тарифы подписок'
56
- : 'Subscription plans',
56
+ : locale.toLowerCase().startsWith('es') ? 'Planes de suscripción' : 'Subscription plans',
57
57
  manufacturers: labels.manufacturers,
58
58
  ...(init?.visual_settings?.single_seller_mode ? {} : { sellers: labels.sellers }),
59
59
  stores: labels.stores,
@@ -29,6 +29,17 @@ import { apiErrorMessage, OrionplaceApiError } from '@/lib/orionplace/api';
29
29
  import { useOrionplaceApi } from '@/hooks/useOrionplaceApi';
30
30
  import { productAvailability } from '@/lib/orionplace/productAvailability';
31
31
  import { productActionLabels } from '@/lib/orionplace/productActionLabels';
32
+
33
+ // What a finished product action did: a key of productActionLabels.
34
+ type ProductActionDone =
35
+ | 'cartAdded'
36
+ | 'cartUpdated'
37
+ | 'cartRemoved'
38
+ | 'favoriteAdded'
39
+ | 'favoriteRemoved'
40
+ | 'subscribed'
41
+ | 'unsubscribed'
42
+ | 'preorderSaved';
32
43
  import styles from '@/app/storefront.module.css';
33
44
  import { signInPageLocation } from '@/lib/orionplace/signInReturn';
34
45
 
@@ -236,7 +247,11 @@ export function OrionplaceStorefrontProvider({
236
247
  }
237
248
  }
238
249
 
239
- async function action(work: () => Promise<void>) {
250
+ // done names what the action did, for the notice («Added to the cart.»).
251
+ async function action(
252
+ work: () => Promise<void>,
253
+ done: ProductActionDone,
254
+ ) {
240
255
  if (actionPending.current) {
241
256
  return;
242
257
  }
@@ -248,7 +263,7 @@ export function OrionplaceStorefrontProvider({
248
263
  setMessageSignIn(false);
249
264
  try {
250
265
  await work();
251
- setMessage(copy.saved);
266
+ setMessage(copy[done]);
252
267
  } catch (reason) {
253
268
  if (
254
269
  reason instanceof OrionplaceApiError &&
@@ -399,9 +414,12 @@ export function OrionplaceStorefrontProvider({
399
414
  } finally {
400
415
  products.getState().setPendingFavorite(selection.product);
401
416
  }
402
- });
417
+ }, selection.selected ? 'favoriteAdded' : 'favoriteRemoved');
403
418
  },
404
419
  onAdd(selection) {
420
+ // Adding a product already in the cart sets its new quantity.
421
+ const inCart = Boolean(product(selection.product)?.cart_counts?.cart_id);
422
+
405
423
  void action(async () => {
406
424
  const data = await api.request<{
407
425
  cart_item_id?: number;
@@ -431,7 +449,7 @@ export function OrionplaceStorefrontProvider({
431
449
  c_cart_positions: data.c_cart_positions,
432
450
  });
433
451
  }
434
- });
452
+ }, inCart ? 'cartUpdated' : 'cartAdded');
435
453
  },
436
454
  onRemove(selection) {
437
455
  void action(async () => {
@@ -446,7 +464,7 @@ export function OrionplaceStorefrontProvider({
446
464
  c_cart_positions: data.c_cart_positions,
447
465
  });
448
466
  }
449
- });
467
+ }, 'cartRemoved');
450
468
  },
451
469
  onSubscribe(selection) {
452
470
  if (!init?.is_authenticated) {
@@ -465,7 +483,7 @@ export function OrionplaceStorefrontProvider({
465
483
  });
466
484
  await refreshNotificationState();
467
485
  await refreshProduct(selection.product);
468
- });
486
+ }, 'subscribed');
469
487
  },
470
488
  onUnsubscribe(selection) {
471
489
  if (!init?.is_authenticated) {
@@ -477,7 +495,7 @@ export function OrionplaceStorefrontProvider({
477
495
  product_id: selection.product,
478
496
  });
479
497
  await refreshProduct(selection.product);
480
- });
498
+ }, 'unsubscribed');
481
499
  },
482
500
  onPreorder(selection) {
483
501
  const current = product(selection.product);
@@ -511,7 +529,7 @@ export function OrionplaceStorefrontProvider({
511
529
  }
512
530
  await api.request('preorders_create', body);
513
531
  setPreorder(undefined);
514
- });
532
+ }, 'preorderSaved');
515
533
  },
516
534
  closePreorder() {
517
535
  setPreorder(undefined);
@@ -15,6 +15,13 @@ const labels = {
15
15
  wallet: 'Кошелёк',
16
16
  account: 'Личный кабинет',
17
17
  },
18
+ es: {
19
+ signIn: 'Iniciar sesión',
20
+ signOut: 'Cerrar sesión',
21
+ profile: 'Perfil',
22
+ wallet: 'Monedero',
23
+ account: 'Tu cuenta',
24
+ },
18
25
  };
19
26
 
20
27
  export function accountLabels(locale: string) {
@@ -60,6 +60,37 @@ const ru: typeof en = {
60
60
  viewProduct: 'Открыть товар',
61
61
  };
62
62
 
63
+ const es: typeof en = {
64
+ title: 'Mi actividad',
65
+ logs: 'Historial de accesos',
66
+ logsTab: 'Accesos',
67
+ logsEmpty: 'No hay accesos que mostrar.',
68
+ from: 'Desde la fecha',
69
+ to: 'Hasta la fecha',
70
+ device: 'Dispositivo',
71
+ browser: 'Navegador',
72
+ location: 'Ubicación',
73
+ unknown: 'Desconocido',
74
+ promocodes: 'Mis códigos promocionales',
75
+ promocodesTab: 'Promociones',
76
+ promosEmpty: 'No hay códigos promocionales disponibles.',
77
+ apply: 'Aplicar',
78
+ applied: 'Aplicado al carrito',
79
+ notStarted: 'Empieza el',
80
+ expired: 'Caducado',
81
+ unavailable: 'No disponible',
82
+ cart: 'Abrir el carrito',
83
+ reviews: 'Mis reseñas',
84
+ reviewsTab: 'Reseñas',
85
+ wishlist: 'Favoritos',
86
+ wishlistEmpty: 'Guarda productos con el botón del corazón para encontrarlos aquí.',
87
+ notifications: 'Avisos',
88
+ notificationsEmpty: 'No hay productos con avisos de disponibilidad.',
89
+ removeWish: 'Quitar',
90
+ stopNotification: 'Cancelar aviso',
91
+ viewProduct: 'Ver el producto',
92
+ };
93
+
63
94
  export function activityLabels(locale: string) {
64
- return locale.toLowerCase().startsWith('ru') ? ru : en;
95
+ return locale.toLowerCase().startsWith('ru') ? ru : locale.toLowerCase().startsWith('es') ? es : en;
65
96
  }
@@ -107,6 +107,52 @@ const ru: typeof en = {
107
107
  imageViewer: 'Просмотр изображения',
108
108
  };
109
109
 
110
+ const es: typeof en = {
111
+ journal: 'Blog',
112
+ emptyJournal: 'Aún no hay publicaciones',
113
+ minutes: 'min de lectura',
114
+ subscription: 'Suscripción',
115
+ message: 'Comentario',
116
+ send: 'Publicar',
117
+ replyTo: 'Responder a',
118
+ own: 'Tu comentario',
119
+ editing: 'Editar el comentario',
120
+ comments: 'Comentarios',
121
+ mine: 'Mías',
122
+ everyone: 'Debate',
123
+ reply: 'Responder',
124
+ more: 'Más',
125
+ moreReplies: 'Ver más respuestas',
126
+ writeComment: 'Escribir un comentario',
127
+ emptyComments: 'Sé el primero en comentar.',
128
+ noMine: 'Todavía no tienes comentarios en este artículo.',
129
+ pending: 'Pendiente de publicación',
130
+ denied: 'No publicada',
131
+ deleteQuestion: '¿Eliminar este comentario?',
132
+ fullAccess: 'Sigue leyendo con una suscripción',
133
+ fullAccessText:
134
+ 'Este es el comienzo del artículo. El texto completo se abre con un plan de suscripción activo que incluya este artículo.',
135
+ tariffs: 'Planes',
136
+ tariffsHint: 'Planes de suscripción',
137
+ related: 'Productos de este artículo',
138
+ unavailable: 'Este artículo no está disponible.',
139
+ accessChanged: 'El acceso al artículo ha cambiado. Actualiza para comprobarlo.',
140
+ back: 'Artículos',
141
+ backHint: 'Todos los artículos',
142
+ video: 'Vídeo del artículo',
143
+ filters: 'Filtros',
144
+ filtersHint: 'Mostrar u ocultar los filtros de artículos',
145
+ categories: 'Categorías',
146
+ writers: 'Autores',
147
+ reset: 'Restablecer',
148
+ resetHint: 'Restablecer los filtros de artículos',
149
+ articleCount: (count: number) =>
150
+ `${count} ${count === 1 ? 'artículo' : 'artículos'}`,
151
+ noFiltered: 'Ningún artículo coincide con los filtros seleccionados.',
152
+ imageOpen: 'Abrir la imagen',
153
+ imageViewer: 'Visor de imágenes',
154
+ };
155
+
110
156
  export function blogLabels(locale: string) {
111
- return locale.toLowerCase().startsWith('ru') ? ru : en;
157
+ return locale.toLowerCase().startsWith('ru') ? ru : locale.toLowerCase().startsWith('es') ? es : en;
112
158
  }
@@ -47,7 +47,54 @@ export function browseLabels(locale: string) {
47
47
  query: 'Название товара (от 3 символов)',
48
48
  home: 'Главная',
49
49
  }
50
- : {
50
+ : locale.toLowerCase().startsWith('es') ? {
51
+ price: 'Precio',
52
+ from: 'Desde',
53
+ to: 'Hasta',
54
+ priceFrom: 'Precio mínimo',
55
+ priceTo: 'Precio máximo',
56
+ applyPrice: 'Aplicar',
57
+ moreFilters: 'Más filtros',
58
+ discountFrom: 'Descuento mínimo, %',
59
+ ratedProducts: 'Productos con valoración de 4,5 o más',
60
+ ratedSellers: 'Vendedores con valoración de 4,5 o más',
61
+ withReviews: 'Solo con reseñas',
62
+ results: 'Productos encontrados',
63
+ products: 'Productos',
64
+ searchResults: 'Resultados de búsqueda',
65
+ clear: 'Borrar',
66
+ emptyTitle: 'No se han encontrado productos',
67
+ emptyHint: 'Prueba otra búsqueda o borra los filtros.',
68
+ emptyCategoryHint:
69
+ 'Todavía no hay productos que coincidan en esta categoría. Prueba a quitar filtros o elige otra categoría.',
70
+ startTitle: '¿Qué estás buscando?',
71
+ startHint:
72
+ 'Escribe el nombre de un producto en la búsqueda: al menos tres caracteres.',
73
+ page: 'Página',
74
+ of: 'de',
75
+ categories: 'Categorías',
76
+ sellers: 'Vendedores',
77
+ manufacturers: 'Fabricantes',
78
+ stores: 'Tiendas',
79
+ searchCrumb: 'Buscar',
80
+ breadcrumb: 'Ruta de la página',
81
+ manufacturerProducts: 'Productos del fabricante',
82
+ sellerProducts: 'Productos del vendedor',
83
+ storeProducts: 'Productos disponibles en esta tienda',
84
+ filters: 'Filtros',
85
+ emptyFilters: 'No hay filtros disponibles',
86
+ blog: 'Blog',
87
+ emptyBlog: 'Aún no hay publicaciones',
88
+ minutes: 'min de lectura',
89
+ loading: 'Cargando…',
90
+ failed: 'No se han podido cargar los datos',
91
+ retry: 'Reintentar',
92
+ previous: 'Anterior',
93
+ next: 'Siguiente',
94
+ search: 'Buscar',
95
+ query: 'Nombre del producto (al menos 3 caracteres)',
96
+ home: 'Inicio',
97
+ } : {
51
98
  price: 'Price',
52
99
  from: 'From',
53
100
  to: 'To',
@@ -250,6 +250,133 @@ const ru: typeof en = {
250
250
  'Не удалось подтвердить создание заказа. Перед повторной попыткой проверьте историю заказов.',
251
251
  };
252
252
 
253
+ const es: typeof en = {
254
+ title: 'Carrito',
255
+ checkout: 'Finalizar compra',
256
+ checkoutAction: 'Finalizar compra',
257
+ items: 'Artículos',
258
+ empty: 'Tu carrito está vacío.',
259
+ browse: 'Seguir comprando',
260
+ address: 'Dirección de entrega',
261
+ addAddress: 'Añadir',
262
+ chooseAddress: 'Elige una dirección guardada',
263
+ selectedAddress: 'Entrega en',
264
+ unsavedAddress:
265
+ 'Esta dirección no está entre tus direcciones guardadas. Elige una dirección guardada para sustituirla.',
266
+ abroadHidden:
267
+ 'No se muestran las direcciones guardadas en países sin envío.',
268
+ savedAbroad:
269
+ 'La dirección está guardada, pero no hay envío a este país. Elige otra dirección.',
270
+ payment: 'Método de pago',
271
+ choosePayment: 'Elige el método de pago',
272
+ noPayment: 'No hay ningún método de pago disponible para este pedido. Los métodos disponibles dependen del pedido y del país de tu número de teléfono.',
273
+ recipient: 'Destinatario',
274
+ myself: 'Yo',
275
+ myselfHint: 'Yo: recibiré el pedido',
276
+ otherRecipient: 'Otra persona',
277
+ otherRecipientHint: 'Otro destinatario',
278
+ name: 'Nombre completo',
279
+ phone: 'Número de teléfono',
280
+ comment: 'Comentario del pedido',
281
+ quantity: 'Cantidad',
282
+ delivery: 'Envío',
283
+ chooseDelivery: 'Elige el envío',
284
+ selectDelivery: 'Selecciona un método de envío',
285
+ shipment: 'Envío parcial',
286
+ of: 'de',
287
+ shipsFrom: 'Se envía desde',
288
+ digitalDelivery: 'Entrega digital',
289
+ digitalItems: 'Artículos digitales, sin envío',
290
+ splitShipments:
291
+ 'Tu pedido se envía en {count} partes desde distintas tiendas. Elige el envío de cada parte.',
292
+ choose: 'Elegir',
293
+ refreshDelivery: 'Actualizar',
294
+ refreshDeliveryHint: 'Actualizar los métodos de envío',
295
+ noDelivery: 'No hay métodos de envío disponibles. Prueba con otra dirección.',
296
+ deliveryChanged:
297
+ 'Las condiciones de envío han cambiado. Revisa el método seleccionado y el precio.',
298
+ pickup: 'Elige un punto de recogida',
299
+ pickupAction: 'Elegir',
300
+ noPoints: 'No se ha recibido ningún punto de recogida.',
301
+ pointSearch: 'Busca un punto de recogida por dirección',
302
+ courier: 'Confirmar el envío a la dirección seleccionada',
303
+ courierAction: 'Confirmar',
304
+ pickupType: 'Punto de recogida',
305
+ courierType: 'Envío por mensajero',
306
+ selectedPoint: 'Seleccionado',
307
+ postalCode: 'Código postal',
308
+ pickupMap: 'Puntos de recogida en el mapa',
309
+ mapUnavailable: 'No se ha podido cargar el mapa.',
310
+ remove: 'Quitar',
311
+ clear: 'Borrar',
312
+ clearQuestion: '¿Quitar todos los artículos del carrito?',
313
+ unavailable: 'No disponible para este pedido',
314
+ estimate: 'Coste estimado del envío',
315
+ goods: 'Artículos',
316
+ discount: 'Descuento',
317
+ promoDiscount: 'Descuento promocional',
318
+ deliveryDiscount: 'Descuento en el envío',
319
+ total: 'Total',
320
+ promo: 'Código promocional',
321
+ apply: 'Aplicar',
322
+ removePromo: 'Quitar',
323
+ removePromoHint: 'Quitar el código promocional',
324
+ promoSignIn: 'Inicia sesión para aplicar un código promocional',
325
+ promoRemoved: 'El código promocional {code} se ha quitado del carrito.',
326
+ promoNotApplied: 'El código promocional {code} no se ha aplicado.',
327
+ promoMinimumOrder: 'Pedido mínimo para este código: {amount}.',
328
+ promoMissingAmount: 'Añade {amount} más de los productos incluidos.',
329
+ // Why a promo code gives no discount, by `promo_notice.reason`. OrionMarketplace's
330
+ // own `message` is shown for a reason this list does not name.
331
+ promoReasons: {
332
+ no_lines: 'Añade artículos al carrito para usar este código promocional.',
333
+ not_matched: 'El código promocional no incluye los artículos de tu carrito.',
334
+ product_discounted:
335
+ 'El código promocional no se aplica a artículos que ya tienen descuento.',
336
+ below_minimum: 'El total del pedido no llega al mínimo del código promocional.',
337
+ subscriptions_only: 'Este código promocional es solo para suscripciones.',
338
+ too_small: 'El total del carrito es demasiado bajo para este código promocional.',
339
+ nothing_to_ship:
340
+ 'El envío ya es gratuito, así que el código promocional no aporta nada.',
341
+ expired: 'El código promocional ha caducado.',
342
+ not_active: 'El código promocional ya no está activo.',
343
+ not_started: 'La promoción todavía no ha empezado.',
344
+ usage_limit: 'El código promocional se ha usado el número máximo de veces.',
345
+ promotion_limit: 'La promoción se ha agotado.',
346
+ not_yours: 'Este código promocional pertenece a otro cliente.',
347
+ personal_limit:
348
+ 'Ya has usado este código promocional el número máximo de veces.',
349
+ first_order_only: 'Este código promocional es solo para la primera compra.',
350
+ wrong_domain: 'Este código promocional no es válido en esta tienda.',
351
+ } as Record<string, string>,
352
+ options: 'Opciones del artículo',
353
+ optionLimit: 'Cantidad máxima: {count}',
354
+ optionUnavailable: 'Esta opción no se puede añadir ahora.',
355
+ optionSelected: 'También seleccionado',
356
+ optionSingle: 'Una opción',
357
+ optionRequired: 'Obligatorio',
358
+ optionMissing: 'Necesario para hacer el pedido',
359
+ optionMultiple: 'Varias opciones',
360
+ optionFile: 'Adjuntar un archivo',
361
+ optionText: 'Tu texto',
362
+ selectedFile: 'Archivo adjunto',
363
+ optionFileLimit: 'Hasta {size} MB',
364
+ optionFileTooLarge: 'El archivo supera los {size} MB.',
365
+ clearOption: 'Borrar',
366
+ clearOptionHint: 'Borrar el texto y el archivo guardados',
367
+ stockChanged: 'Han cambiado las cantidades o los precios de los artículos. Revisa tu carrito.',
368
+ placeOrder: 'Pedido',
369
+ created: 'Pedido creado',
370
+ viewOrder: 'Ver el pedido',
371
+ paymentLinkFailed:
372
+ 'El pedido está hecho, pero no se ha podido crear el enlace de pago: {error}. Paga el pedido más tarde desde su página.',
373
+ payLater: 'Abrir el pedido para pagar',
374
+ review: 'Revisa los artículos, el envío y el total antes de hacer el pedido.',
375
+ refreshRequired: 'Actualiza tu carrito antes de continuar.',
376
+ orderUncertain:
377
+ 'No se ha podido confirmar el pedido. Revisa tu historial de pedidos antes de volver a intentarlo.',
378
+ };
379
+
253
380
  /**
254
381
  * The notice of an order placed without a payment link: the backend's reason
255
382
  * is shown as it came, without its closing full stop.
@@ -259,5 +386,5 @@ export function cartPaymentLinkError(template: string, error: string) {
259
386
  }
260
387
 
261
388
  export function cartLabels(locale: string) {
262
- return locale.toLowerCase().startsWith('ru') ? ru : en;
389
+ return locale.toLowerCase().startsWith('ru') ? ru : locale.toLowerCase().startsWith('es') ? es : en;
263
390
  }
@@ -19,6 +19,21 @@ export function cartPreviewLabels(locale: string) {
19
19
  checkout: 'Перейти в корзину',
20
20
  };
21
21
  }
22
+ if (locale.toLowerCase().startsWith('es')) {
23
+ return {
24
+ title: 'Carrito',
25
+ quantity: 'Artículos',
26
+ pieces: (count: number) => `Cant. ${count}`,
27
+ empty: 'Tu carrito está vacío',
28
+ loading: 'Cargando el carrito…',
29
+ failed: 'No se ha podido cargar el carrito. Inténtalo de nuevo.',
30
+ unavailable: 'La vista previa del carrito no está disponible.',
31
+ retry: 'Reintentar',
32
+ remove: 'Quitar del carrito',
33
+ removeFailed: 'No se ha podido quitar el artículo. Inténtalo de nuevo.',
34
+ checkout: 'Ir al carrito',
35
+ };
36
+ }
22
37
  return {
23
38
  title: 'Cart',
24
39
  quantity: 'Items',
@@ -9,7 +9,15 @@ export function categoryLabels(locale: string) {
9
9
  missingHint: 'Выберите доступную категорию в каталоге.',
10
10
  home: 'Главная',
11
11
  }
12
- : {
12
+ : locale.toLowerCase().startsWith('es') ? {
13
+ breadcrumb: 'Ruta de la categoría',
14
+ catalog: 'Catálogo',
15
+ category: 'Categoría',
16
+ subcategories: 'Explorar esta categoría',
17
+ missing: 'Categoría no encontrada',
18
+ missingHint: 'Elige una categoría disponible del catálogo.',
19
+ home: 'Inicio',
20
+ } : {
13
21
  breadcrumb: 'Category path',
14
22
  catalog: 'Catalog',
15
23
  category: 'Category',
@@ -97,6 +97,56 @@ const ru: typeof en = {
97
97
  'Подтвердите электронную почту, чтобы получить промокод. При записи на акцию письма включаются.',
98
98
  };
99
99
 
100
+ const es: typeof en = {
101
+ pageTitle: 'Información de contacto',
102
+ intro: 'Consulta los datos de la empresa o envía una pregunta a la tienda.',
103
+ company: 'Datos de la empresa',
104
+ companyName: 'Empresa',
105
+ legalName: 'Razón social',
106
+ address: 'Dirección',
107
+ phone: 'Teléfono',
108
+ social: 'Redes sociales',
109
+ registration: 'Datos de registro',
110
+ taxId: 'NIF',
111
+ registrationNumber: 'Número de registro',
112
+ copy: 'Copiar',
113
+ copied: 'Copiado',
114
+ copyAddress: 'Copiar la dirección',
115
+ copyPhone: 'Copiar el número de teléfono',
116
+ copyTaxId: 'Copiar el NIF',
117
+ copyRegistrationNumber: 'Copiar el número de registro',
118
+ copiedStatus: 'Copiado al portapapeles.',
119
+ copyFailed: 'No se ha podido copiar. Selecciona el texto.',
120
+ title: 'Contáctanos',
121
+ description: 'Rellena el formulario y la tienda recibirá tu pregunta.',
122
+ name: 'Nombre',
123
+ email: 'Correo electrónico',
124
+ message: 'Mensaje',
125
+ messageHint: 'De 15 a 2048 caracteres.',
126
+ captcha: 'Código de la imagen',
127
+ refreshCaptcha: 'Actualizar',
128
+ refreshCaptchaHint: 'Cambiar la imagen',
129
+ send: 'Enviar',
130
+ sent: 'Tu mensaje se ha enviado a la tienda.',
131
+ another: 'Otro',
132
+ anotherHint: 'Escribir otro mensaje',
133
+ unsubscribe: 'Preferencias de correo',
134
+ unsubscribeAction: 'Cancelar aviso',
135
+ newsletter: 'Dejar de recibir boletines',
136
+ products: 'Dejar de recibir correos de disponibilidad',
137
+ unsubscribeDone: 'Te has dado de baja de estos correos.',
138
+ invalidLink:
139
+ 'Este enlace está incompleto. Abre el enlace de baja desde tu correo.',
140
+ event: 'Participa en esta oferta',
141
+ eventDescription: 'Confirma tu participación para recibir la oferta por correo.',
142
+ eventJoin: 'Participar',
143
+ eventDoneTitle: 'Ya participas',
144
+ eventDone: 'El código promocional se enviará a tu correo confirmado.',
145
+ eventDoneAddress: 'Correo de entrega',
146
+ eventEmail:
147
+ 'Confirma una dirección de correo para recibir el código promocional. Al participar se activan los correos.',
148
+ };
149
+
100
150
  export function contactLabels(locale: string) {
101
- return locale.toLowerCase().startsWith('ru') ? ru : en;
151
+ return locale.toLowerCase().startsWith('ru') ? ru : locale.toLowerCase().startsWith('es') ? es : en;
102
152
  }