feat(i18n): add bilingual support (Català / Castellano)
Run Tests on Branches / Detect Changes (push) Successful in 11s
Run Tests on Branches / Backend Tests (push) Has been skipped
Run Tests on Branches / Frontend Tests (push) Successful in 1m44s
Run Tests on Branches / Frontend Mobile Tests (push) Has been skipped
Run Tests on Branches / Parapharmacy API Tests (push) Has been skipped

Add lightweight i18n system using React Context + useTranslation hook.
No external dependencies. Language persisted in localStorage('ff-lang').

New files:
- src/i18n/locales/ca.js (~410 translation keys)
- src/i18n/locales/es.js (~410 translation keys)
- src/i18n/LanguageContext.jsx (Provider + t() function)
- src/i18n/useTranslation.js (hook)
- src/i18n/index.js (barrel export)

Modified 22 files:
- main.jsx: wrap App with LanguageProvider
- App.test.jsx: add LanguageProvider wrapper for tests
- 13 user components: BottomNav, HomeView, LoginModal, SearchView,
  ProfileView, AlertsView, ScannerView, PharmacyList, MedicineResults,
  ProductResults, SavedNotifications, ErrorBoundary, SearchBar
- 6 admin components: AdminView, LoginForm, PharmacyManagement,
  MedicineManagement, PharmacyMedicineLink, PharmacyProductLink

ProfileView includes language selector (globe icon + modal CA/ES)
placed next to the Theme selector.

No routes changed. No API calls affected. No navigation changes.
This commit is contained in:
Antoni Nuñez Romeu
2026-07-17 09:43:45 +02:00
parent 41d3e5cd9a
commit 38374341ed
26 changed files with 1371 additions and 384 deletions
+416
View File
@@ -0,0 +1,416 @@
const es = {
// BottomNav
'nav.home': 'Inicio',
'nav.search': 'Buscar',
'nav.scan': 'Escanear',
'nav.alerts': 'Avisos',
'nav.profile': 'Usuario',
// HomeView
'home.description': 'Encuentra tus medicamentos en farmacias cercanas',
'home.searchMedicine': 'Buscar Medicamento',
'home.scanTSI': 'Escanear TSI',
// SearchBar
'search.placeholder': 'Escriba el nombre del medicamento',
'search.clear': 'Limpiar búsqueda',
// SearchView
'search.suggestions': 'Sugerencias',
'search.recentResults': 'Resultados Recientes',
'search.findNearby': 'Encontrar cerca',
'search.resultsFound': 'resultados encontrados',
'search.parapharmacy': 'Parafarmacia',
'search.activeIngredient': 'Ingrediente Activo',
'search.dosage': 'Dosis',
'search.form': 'Forma',
'search.backToSearch': '← Volver a búsqueda',
'search.sortByDistance': '📍 Ordenar por distancia',
'search.sortBySavedLocation': '📍 Ordenar por ubicación guardada',
'search.sortedByDistance': '📍 Ordenado por distancia · Reset',
'search.locating': '📍 Localizando…',
'search.usingSavedAddress': 'Usando tu dirección guardada',
'search.usingRecentLocation': 'Usando ubicación reciente',
'search.retry': 'Reintentar',
'search.locationDenied': 'Permiso de ubicación denegado. Permite el acceso a la ubicación en tu navegador.',
'search.locationUnavailable': 'Ubicación no disponible. Verifica que el GPS esté activado.',
'search.locationTimeout': 'La ubicación tardó demasiado. Intenta de nuevo o verifica tu conexión.',
'search.locationError': 'No se pudo obtener tu ubicación',
'search.searching': 'Buscando...',
// MedicineResults
'medicine.noResults': 'No se encontraron medicamentos para',
'medicine.principioActivo': 'Principio Activo:',
'medicine.dosis': 'Dosis:',
'medicine.forma': 'Forma:',
'medicine.viewPharmacies': 'Ver farmacias →',
'medicine.loginForNotifications': 'Inicia sesión para activar notificaciones',
'medicine.disableNotifications': 'Desactivar notificaciones para este medicamento',
'medicine.enableNotifications': 'Notificarme cuando esté disponible',
'medicine.notificationsActivated': 'Notificaciones activadas — clic para desactivar',
'medicine.notifyWhenAvailable': 'Notificarme cuando este medicamento esté en una farmacia',
'medicine.subscriptionError': 'No se pudo actualizar la suscripción',
// PharmacyList
'pharmacy.loading': 'Cargando farmacias...',
'pharmacy.notFound': 'No se encontraron farmacias con este medicamento',
'pharmacy.availableAt': 'Disponible en',
'pharmacy.pharmacy': 'farmacia',
'pharmacy.pharmacies': 'farmacias',
'pharmacy.howToGet': 'Cómo llegar',
'pharmacy.inStock': '✓ En Stock',
'pharmacy.lowStock': '⚠ Stock Bajo',
'pharmacy.outOfStock': '✗ Sin Stock',
'pharmacy.loginForNotifications': 'Inicia sesión para activar notificaciones',
'pharmacy.disableNotificationsPharmacy': 'Desactivar notificaciones para esta farmacia',
'pharmacy.notifyWhenArrives': 'Notificarme cuando llegue a esta farmacia',
'pharmacy.notificationsActivatedPharmacy': 'Notificaciones activadas para esta farmacia — clic para desactivar',
'pharmacy.notificationsRequired': 'Las notificaciones requieren iOS 16.4+ y este sitio instalado como app (Compartir → Añadir a Pantalla de Inicio).',
// ProductResults
'product.sinReceta': 'Sin Receta',
'product.parapharmacy': 'Parafarmacia',
'product.dermocosmetica': 'Dermocosmética',
'product.formulasLacteas': 'Fórmulas lácteas',
'product.vitamins': 'Vitaminas',
'product.analgesics': 'Analgésicos',
'product.noResults': 'No se encontraron productos',
'product.brand': 'Marca:',
'product.price': 'Precio:',
'product.activeIngredient': 'Principio Activo:',
'product.dosage': 'Dosis:',
'product.viewDetails': 'Ver detalles →',
// LoginModal
'login.login': 'Iniciar sesión',
'login.register': 'Crear cuenta',
'login.welcomeBack': 'Bienvenido de nuevo',
'login.createAccount': 'Crea tu cuenta',
'login.registerDescription': 'Guarda tu dirección y recibe notificaciones cuando lleguen medicamentos.',
'login.loginDescription': 'Inicia sesión para gestionar tu perfil y notificaciones.',
'login.username': 'Usuario',
'login.usernameHint': '3–32 caracteres: letras, dígitos o guión bajo.',
'login.password': 'Contraseña',
'login.passwordHint': 'Al menos 8 caracteres.',
'login.cancel': 'Cancelar',
'login.creating': 'Creando…',
'login.loggingIn': 'Iniciando sesión…',
'login.createAccountBtn': 'Crear cuenta',
'login.loginBtn': 'Iniciar sesión',
'login.passwordError': 'La contraseña debe tener al menos 8 caracteres',
'login.registerError': 'No se pudo crear la cuenta',
'login.loginError': 'Inicio de sesión fallido',
'login.networkError': 'Error de red — inténtalo de nuevo',
// AlertsView
'alerts.title': 'Mis Avisos',
'alerts.subtitle': 'Mantente al día con tus medicamentos.',
'alerts.loading': 'Cargando avisos...',
'alerts.loginRequired': 'Por favor, inicia sesión para ver tus avisos.',
'alerts.loadError': 'No se pudieron cargar los avisos.',
'alerts.availability': 'Disponibilidad',
'alerts.noSubscriptions': 'No tienes suscripciones de disponibilidad.',
'alerts.notifyWhenAvailable': 'Notificarme cuando esté disponible',
'alerts.deleteError': 'No se pudo eliminar la alerta de disponibilidad.',
'alerts.pharmacy': 'Farmacia',
// ScannerView
'scanner.title': 'Escanear TSI',
'scanner.description': 'Escanea el código de barras de tu tarjeta sanitaria para ver tus recetas activas',
'scanner.openCamera': 'Abrir cámara',
'scanner.orUploadPhoto': 'O sube una foto de tu TSI',
'scanner.takePhoto': 'Hacer foto',
'scanner.uploadFromGallery': 'Subir de galería',
'scanner.scanImage': 'Escanear imagen',
'scanner.discard': 'Descartar',
'scanner.enterCIP': 'O introduce el código CIP manualmente',
'scanner.cipPlaceholder': 'Código CIP de 16 dígitos',
'scanner.search': 'Buscar',
'scanner.scanning': 'Apunta al código de barras',
'scanner.openingCamera': 'Abriendo cámara…',
'scanner.processingImage': 'Procesando imagen…',
'scanner.tsiScanned': 'TSI Escaneada',
'scanner.activePrescriptions': 'Recetas Activas',
'scanner.tapToFind': 'Toca un medicamento para ver disponibilidad en farmacias cercanas.',
'scanner.loadingPrescriptions': 'Cargando recetas…',
'scanner.noPrescriptions': 'No se encontraron recetas activas para esta tarjeta.',
'scanner.scanAnother': 'Escanear otra tarjeta',
'scanner.tryAgain': 'Intentar de nuevo',
'scanner.scannerUnavailable': 'El escáner no está disponible en este dispositivo.',
'scanner.cameraPermissionDenied': 'Permiso de cámara denegado. Actívalo en ajustes o introduce el código CIP manualmente.',
'scanner.invalidBarcode': 'Código de barras inválido. Intenta de nuevo o introduce el CIP manualmente.',
'scanner.cameraNotAvailable': 'Cámara no disponible en este navegador.',
'scanner.invalidCIP': 'Formato CIP inválido. Debe tener 16 caracteres alfanuméricos.',
'scanner.enterCIPError': 'Introduce un código CIP.',
'scanner.cameraPermissionWeb': 'Permiso de cámara denegado. Permite el acceso e intenta de nuevo.',
'scanner.noCamera': 'No se detectó ninguna cámara. Introduce el código CIP manualmente.',
'scanner.imageReadError': 'No se pudo leer la imagen. Intenta con otra foto.',
'scanner.invalidCIPDetected': 'CIP detectado no tiene formato válido. Introduce el código manualmente.',
'scanner.scanError': 'Error al escanear:',
'scanner.unknownError': 'Error desconocido',
'scanner.cameraError': 'Error de cámara:',
'scanner.imageProcessError': 'Error al procesar la imagen:',
// ProfileView
'profile.name': 'Nombre',
'profile.lastName': 'Apellidos',
'profile.config': 'Configuración',
'profile.myAddresses': 'Mis Direcciones',
'profile.theme': 'Tema',
'profile.themeAuto': 'Automático',
'profile.themeLight': 'Claro',
'profile.themeDark': 'Oscuro',
'profile.admin': 'Panel de Administración',
'profile.logout': 'Cerrar Sesión',
'profile.uploadingPhoto': 'Subiendo foto...',
'profile.recentSearches': 'Tus búsquedas recientes:',
'profile.delete': 'Eliminar',
'profile.changeAvatar': 'Cambiar Avatar',
'profile.presetAvatar': 'Prediseñado',
'profile.colors': 'Colores',
'profile.upload': 'Subir',
'profile.chooseFromGallery': 'Elegir de galería',
'profile.selectExistingImage': 'Selecciona una imagen existente',
'profile.imageTooBig': 'La imagen no puede superar los 5 MB',
'profile.imageSaveError': 'Error al guardar la imagen. Intenta con otra foto.',
'profile.imageConnectionError': 'Error de conexión al guardar la imagen.',
'profile.imageProcessError': 'Error al procesar la imagen.',
'profile.configTitle': 'Configuración',
'profile.firstName': 'Nombre',
'profile.firstNamePlaceholder': 'Tu nombre',
'profile.lastNamePlaceholder': 'Tus apellidos',
'profile.email': 'Correo electrónico',
'profile.emailPlaceholder': 'tu@email.com',
'profile.city': 'Ciudad',
'profile.cityPlaceholder': 'Tu ciudad',
'profile.address': 'Dirección',
'profile.addressPlaceholder': 'Calle Mayor 1, Madrid',
'profile.cancel': 'Cancelar',
'profile.saving': 'Guardando...',
'profile.save': 'Guardar',
'profile.profileSaved': 'Perfil guardado.',
'profile.saveError': 'Error al guardar',
'profile.addressesTitle': 'Mis Direcciones',
'profile.addressLabel': 'Dirección',
'profile.addressOptional': 'Etiqueta (opcional)',
'profile.addressLabelPlaceholder': 'Ej: Casa, Trabajo, Segunda residencia',
'profile.defaultAddress': 'Dirección predeterminada',
'profile.add': 'Añadir',
'profile.update': 'Actualizar',
'profile.loadingAddresses': 'Cargando direcciones...',
'profile.mainAddress': 'Dirección principal',
'profile.default': 'Predeterminada',
'profile.setDefault': 'Establecer como predeterminada',
'profile.addMore': 'Añadir más',
'profile.addressRequired': 'La dirección es obligatoria',
'profile.addressSaveError': 'Error al guardar la dirección',
'profile.themeTitle': 'Tema de visualización',
'profile.themeDescription': 'Elige cómo se ve la aplicación. En modo automático, se adapta al tema de tu dispositivo.',
'profile.themeAutoDesc': 'Seguir sistema',
'profile.themeLightDesc': 'Siempre claro',
'profile.themeDarkDesc': 'Siempre oscuro',
'profile.language': 'Idioma',
'profile.languageTitle': 'Idioma de la aplicación',
'profile.languageCatalan': 'Català',
'profile.languageSpanish': 'Castellano',
// SavedNotifications
'savedNotifications.title': '🔔 Notificaciones Guardadas',
'savedNotifications.close': 'Cerrar',
'savedNotifications.loading': 'Cargando…',
'savedNotifications.empty': 'Aún no hay notificaciones guardadas. Toca la campana 🔕 en una farmacia sin stock para recibir notificaciones cuando se reponga.',
'savedNotifications.anyPharmacy': 'Cualquier farmacia',
'savedNotifications.delete': 'Eliminar notificación',
'savedNotifications.loadError': 'No se pudieron cargar las notificaciones guardadas',
'savedNotifications.deleteError': 'No se pudo eliminar la notificación',
// ErrorBoundary
'error.title': 'Algo salió mal',
'error.description': 'Ha ocurrido un error inesperado. Por favor, recarga la página.',
'error.reload': 'Recargar',
// AdminView
'admin.title': '⚙️ Panel de Administración',
'admin.authRequired': 'Autenticación requerida',
'admin.managePharmacies': 'Gestiona farmacias y medicamentos',
'admin.logout': 'Cerrar sesión',
'admin.checkingAuth': 'Comprobando autenticación...',
'admin.pharmacies': '🏥 Farmacias',
'admin.medicines': '💊 Medicamentos',
'admin.linkMedicine': '🔗 Vincular Medicamento a Farmacia',
'admin.linkProduct': '🍎 Vincular Producto a Farmacia',
// LoginForm
'admin.login.title': '🔐 Acceso Administración',
'admin.login.description': 'Introduce tus credenciales para acceder al panel de administración',
'admin.login.username': 'Usuario',
'admin.login.usernamePlaceholder': 'Introduce usuario',
'admin.login.password': 'Contraseña',
'admin.login.passwordPlaceholder': 'Introduce contraseña',
'admin.login.loggingIn': 'Iniciando sesión...',
'admin.login.loginBtn': 'Iniciar sesión',
'admin.login.defaultCredentials': 'Credenciales por defecto:',
'admin.login.changePasswordWarning': '⚠️ ¡Cambia la contraseña por defecto tras el primer inicio de sesión!',
'admin.login.failed': 'Inicio de sesión fallido',
'admin.login.invalidCredentials': 'Usuario o contraseña inválidos',
// PharmacyManagement
'admin.pharmacy.title': 'Gestionar Farmacias',
'admin.pharmacy.addNew': '+ Añadir Nueva Farmacia',
'admin.pharmacy.edit': 'Editar Farmacia',
'admin.pharmacy.add': 'Añadir Nueva Farmacia',
'admin.pharmacy.name': 'Nombre *',
'admin.pharmacy.address': 'Dirección *',
'admin.pharmacy.phone': 'Teléfono',
'admin.pharmacy.latitude': 'Latitud',
'admin.pharmacy.longitude': 'Longitud',
'admin.pharmacy.radius': 'Radio',
'admin.pharmacy.openingHours': 'Horario de apertura',
'admin.pharmacy.closed': 'Cerrado',
'admin.pharmacy.opensAt': 'abre a las',
'admin.pharmacy.closesAt': 'cierra a las',
'admin.pharmacy.saving': 'Guardando...',
'admin.pharmacy.update': 'Actualizar',
'admin.pharmacy.create': 'Añadir',
'admin.pharmacy.cancel': 'Cancelar',
'admin.pharmacy.loading': 'Cargando farmacias...',
'admin.pharmacy.showing': 'Mostrando',
'admin.pharmacy.of': 'de',
'admin.pharmacy.withinRadio': '(dentro del radio)',
'admin.pharmacy.empty': 'Aún no hay farmacias. Importa desde webhook o añade una manualmente.',
'admin.pharmacy.noResults': 'No hay farmacias en este radio con coordenadas. Amplía el radio, busca otra ciudad o desactiva el filtro de región.',
'admin.pharmacy.editBtn': 'Editar',
'admin.pharmacy.deleteBtn': 'Eliminar',
'admin.pharmacy.deleteConfirm': '¿Estás seguro de que quieres eliminar esta farmacia?',
'admin.pharmacy.updated': '¡Farmacia actualizada!',
'admin.pharmacy.created': '¡Farmacia añadida!',
'admin.pharmacy.deleted': '¡Farmacia eliminada!',
'admin.pharmacy.loadError': 'Error al cargar farmacias',
'admin.pharmacy.saveError': 'Error al guardar farmacia',
'admin.pharmacy.updateError': 'Error al actualizar farmacia',
'admin.pharmacy.createError': 'Error al crear farmacia',
'admin.pharmacy.deleteError': 'Error al eliminar farmacia',
'admin.pharmacy.citySearch': 'Ciudad, región e importación',
'admin.pharmacy.searchCity': 'Buscar ciudad',
'admin.pharmacy.chooseOne': 'Elije una',
'admin.pharmacy.openDataUrl': 'URL de datos abiertos',
'admin.pharmacy.cityPlaceholder': 'Ej: Rubí, Madrid, Valencia…',
'admin.pharmacy.searching': 'Buscando…',
'admin.pharmacy.areaPreset': 'Preset de área',
'admin.pharmacy.customCoordinates': 'Coordenadas personalizadas',
'admin.pharmacy.areaExample': 'Ejemplo: Área de Rubí (1.5 km)',
'admin.pharmacy.enterCity': 'Introduce una ciudad o lugar.',
'admin.pharmacy.setCoordinates': 'Establece latitud, longitud y radio (usa Buscar ciudad o un preset).',
'admin.pharmacy.dataSource': 'Fuente de datos',
'admin.pharmacy.webhookLegacy': 'n8n webhook (heredado)',
'admin.pharmacy.openDataJson': 'URL de datos abiertos JSON',
'admin.pharmacy.jsonUrl': 'URL JSON',
'admin.pharmacy.importing': 'Importando…',
'admin.pharmacy.importWebhook': 'Importar desde webhook',
'admin.pharmacy.importUrl': 'Importar desde URL',
'admin.pharmacy.importFrom': 'Importar desde',
'admin.pharmacy.showWithinRadio': 'Mostrar solo farmacias dentro del radio',
'admin.pharmacy.sessionExpired': 'Sesión expirada o no has iniciado sesión. Inicia sesión de nuevo en el panel Admin y reintenta.',
'admin.pharmacy.apiNotFound': 'La app no pudo conectar con la API (404). Usa http://localhost:3000 con frontend y backend activos.',
'admin.pharmacy.geocodingNotFound': 'Servicio de geocodificación no encontrado. Actualiza el backend y reinícialo.',
'admin.pharmacy.searchFailed': 'Búsqueda fallida (HTTP',
'admin.pharmacy.dayClosed': 'Marca un día como Cerrado si la farmacia no abre ese día.',
'admin.pharmacy.saveError': 'Error al guardar farmacia',
'admin.pharmacy.radius': 'Radio (m)',
// MedicineManagement
'admin.medicine.title': 'Buscar Medicamentos (API CIMA)',
'admin.medicine.description': 'Los medicamentos ahora se obtienen directamente de la',
'admin.medicine.cimaApi': 'API de CIMA',
'admin.medicine.cimaDescription': '(Agencia Española de Medicamentos y Productos Sanitarios).',
'admin.medicine.linkDescription': 'Busca medicamentos para vincularlos a farmacias en la pestaña "Link Medicine".',
'admin.medicine.search': 'Buscar medicamentos',
'admin.medicine.searchPlaceholder': 'Escribe el nombre de un medicamento...',
'admin.medicine.searching': 'Buscando en API CIMA...',
'admin.medicine.found': 'Se encontraron',
'admin.medicine.medications': 'medicamentos',
'admin.medicine.activeIngredient': 'Principio Activo:',
'admin.medicine.dosage': 'Dosis:',
'admin.medicine.form': 'Forma:',
'admin.medicine.laboratory': 'Laboratorio:',
'admin.medicine.registrationNumber': 'Nº Registro:',
'admin.medicine.generic': 'Genérico',
'admin.medicine.brand': 'Marca',
'admin.medicine.noResults': 'No se encontraron medicamentos con ese nombre.',
'admin.medicine.loadError': 'Error al buscar medicamentos en la API CIMA',
// PharmacyMedicineLink
'admin.linkMedicine.title': 'Vincular Medicamento a Farmacia',
'admin.linkMedicine.pharmacy': 'Farmacia *',
'admin.linkMedicine.searchPharmacy': 'Buscar entre',
'admin.linkMedicine.pharmacies': 'farmacias por nombre o dirección…',
'admin.linkMedicine.loadingPharmacies': 'Cargando farmacias…',
'admin.linkMedicine.noPharmacies': 'No hay farmacias que coincidan con',
'admin.linkMedicine.changePharmacy': 'Cambiar farmacia',
'admin.linkMedicine.medicine': 'Buscar Medicamento (API CIMA) *',
'admin.linkMedicine.searchMedicine': 'Escribe para buscar medicamentos en CIMA...',
'admin.linkMedicine.searching': 'Buscando...',
'admin.linkMedicine.activeIngredient': 'Principio activo:',
'admin.linkMedicine.dosage': 'Dosis:',
'admin.linkMedicine.registrationNumber': 'Nº Registro:',
'admin.linkMedicine.changeMedicine': 'Cambiar medicamento',
'admin.linkMedicine.price': 'Precio (€)',
'admin.linkMedicine.stock': 'Stock',
'admin.linkMedicine.linkBtn': 'Vincular Medicamento',
'admin.linkMedicine.reset': 'Reiniciar',
'admin.linkMedicine.medicationsIn': 'Medicamentos en',
'admin.linkMedicine.loading': 'Cargando...',
'admin.linkMedicine.empty': 'Aún no hay medicamentos vinculados a esta farmacia.',
'admin.linkMedicine.priceLabel': 'Precio:',
'admin.linkMedicine.undefined': 'No definido',
'admin.linkMedicine.newPrice': 'Introduce nuevo precio:',
'admin.linkMedicine.newStock': 'Introduce nuevo stock:',
'admin.linkMedicine.update': 'Actualizar',
'admin.linkMedicine.delete': 'Eliminar',
'admin.linkMedicine.selectMedicineFirst': 'Por favor, selecciona un medicamento primero',
'admin.linkMedicine.linkSuccess': '¡Medicamento vinculado a la farmacia correctamente!',
'admin.linkMedicine.linkError': 'Error al vincular medicamento a farmacia',
'admin.linkMedicine.updateSuccess': '¡Actualizado correctamente!',
'admin.linkMedicine.updateError': 'Error al actualizar',
'admin.linkMedicine.deleteConfirm': '¿Eliminar este medicamento de la farmacia?',
'admin.linkMedicine.deleteSuccess': '¡Medicamento eliminado de la farmacia!',
'admin.linkMedicine.deleteError': 'Error al eliminar medicamento',
// PharmacyProductLink
'admin.linkProduct.title': 'Vincular Producto a Farmacia',
'admin.linkProduct.pharmacy': 'Farmacia *',
'admin.linkProduct.searchPharmacy': 'Buscar entre',
'admin.linkProduct.pharmacies': 'farmacias por nombre o dirección…',
'admin.linkProduct.loadingPharmacies': 'Cargando farmacias…',
'admin.linkProduct.noPharmacies': 'No hay farmacias que coincidan con',
'admin.linkProduct.changePharmacy': 'Cambiar farmacia',
'admin.linkProduct.product': 'Buscar Producto (CIMA / Parafarmacia) *',
'admin.linkProduct.searchProduct': 'Escribe para buscar medicamentos o productos de parafarmacia...',
'admin.linkProduct.searching': 'Buscando...',
'admin.linkProduct.brand': 'Marca:',
'admin.linkProduct.price': 'Precio:',
'admin.linkProduct.changeProduct': 'Cambiar producto',
'admin.linkProduct.priceInput': 'Precio (€)',
'admin.linkProduct.stock': 'Stock',
'admin.linkProduct.linkBtn': 'Vincular Producto',
'admin.linkProduct.reset': 'Reiniciar',
'admin.linkProduct.productsIn': 'Productos en',
'admin.linkProduct.loading': 'Cargando...',
'admin.linkProduct.empty': 'Aún no hay productos vinculados a esta farmacia.',
'admin.linkProduct.priceLabel': 'Precio:',
'admin.linkProduct.undefined': 'No definido',
'admin.linkProduct.newPrice': 'Introduce nuevo precio:',
'admin.linkProduct.newStock': 'Introduce nuevo stock:',
'admin.linkProduct.update': 'Actualizar',
'admin.linkProduct.delete': 'Eliminar',
'admin.linkProduct.selectProductFirst': 'Por favor, selecciona un producto primero',
'admin.linkProduct.linkSuccess': '¡Producto vinculado a la farmacia correctamente!',
'admin.linkProduct.linkError': 'Error al vincular producto a farmacia',
'admin.linkProduct.updateSuccess': '¡Actualizado correctamente!',
'admin.linkProduct.updateError': 'Error al actualizar',
'admin.linkProduct.deleteConfirm': '¿Eliminar este producto de la farmacia?',
'admin.linkProduct.deleteSuccess': '¡Producto eliminado de la farmacia!',
'admin.linkProduct.deleteError': 'Error al eliminar producto',
};
export default es;