Internacionalización
El portal está traducido a 7 idiomas con next-intl, sin prefijo de idioma en la URL (no usa el middleware de routing de next-intl, solo getRequestConfig).
1. Idiomas soportados
Definidos en app/constants/languages.ts:
| Código | Idioma | Bandera |
|---|---|---|
en | English | 🇬🇧 |
es | Español | 🇪🇸 |
fr | Français | 🇫🇷 |
de | Deutsch | 🇩🇪 |
it | Italiano | 🇮🇹 |
pt | Português | 🇵🇹 |
el | Ελληνικά | 🇬🇷 |
en es el idioma por defecto (defaultLanguage) y el fallback cuando una traducción remota no existe.
2. Resolución del idioma (i18n/request.tsx)
En cada petición, getRequestConfig resuelve el locale por orden de prioridad:
- Cookie
app_custom_language— selección explícita del usuario en el portal. - Cookie
HW_preferred_locale— preferencia establecida por la web de Hawkers (SFCC); permite que un cliente que llega desde la tienda vea el portal en el mismo idioma. - Cabecera
Accept-Language(primer valor). enpor defecto.
El valor resultante se recorta a dos letras en minúscula (es-ES → es) y se cargan los mensajes con import('../messages/<language>.json').
En este punto no se valida que el idioma esté entre los soportados. Un navegador que envíe, por ejemplo, Accept-Language: nl provocará el import de un fichero inexistente. Ver Notas técnicas.
3. Selector de idioma
SelectLanguage (en el Footer) escribe la cookie app_custom_language con cookies-next (path: '/') y hace window.location.reload(): al no haber routing por locale, el cambio de idioma requiere una recarga completa para que el servidor vuelva a resolver el locale y recargar los mensajes.
4. Ficheros de mensajes
messages/{de,el,en,es,fr,it,pt}.json — 102 claves en cada fichero (los siete están sincronizados). Namespaces de primer nivel:
| Namespace | Uso |
|---|---|
brand, button, order, Error | Comunes: marca, botones, etiquetas y errores genéricos |
Form | label.*, placeholder.*, info.* y error.* de formularios |
FormSearchOrder, FormRetrieveCase | Textos y errores de los dos formularios de entrada |
Step2, Step3, Step4 | Títulos, introducciones, diálogos y errores por paso |
ThankYouPage | Pantalla de confirmación |
ReturnConditions | Condiciones de devolución (condition1, condition3–condition7) |
productPrices | Panel explicativo del cálculo de precios |
pdf | Títulos dentro de los PDFs |
SelectLanguage, Footer | Selector de idioma y pie |
next, total, subtotal, subtotalItems, free, shippingCost | Claves sueltas de importes y navegación |
Texto con formato
Las claves que contienen etiquetas (<p>, <strong>, <i>, <a>) se renderizan con el componente RichText, que las mapea a elementos React. Los enlaces se resuelven por índice mediante la prop links — así ReturnConditions puede inyectar la URL de contacto correcta según el idioma.
ReturnConditions además consulta tReturnConditions.raw('conditionN') para omitir condiciones vacías: una condición que no aplica a un mercado se deja como cadena vacía en su fichero de mensajes y desaparece de la lista.
5. Traducciones que no están en el repositorio
Parte del contenido visible no vive en messages/, sino que llega traducido desde robo-api en un campo translations indexado por código de idioma:
| Dato | Origen |
|---|---|
| Título, descripción e instrucciones del método de devolución | /return-method/search → mapReturnMethod() |
| Motivos de devolución | /return-reason/info → getTranslation() |
| Estados de devolución y de línea | /return-status/info, /return-line-status/info |
Ese campo puede llegar como objeto o como string JSON, por lo que el código hace typeof translations === 'string' ? JSON.parse(...) : translations antes de usarlo. Si falta la traducción del idioma activo, se cae al valor de en (estados) o a cadena vacía (métodos y motivos).
Para añadir un idioma nuevo hay que hacer las dos cosas: crear messages/<código>.json y añadir la entrada en app/constants/languages.ts, y dar de alta las traducciones correspondientes en robo-api (métodos, motivos y estados). Con solo lo primero, el portal se mostraría traducido pero con métodos y motivos vacíos.