feat(i18n-mobile): add bilingual support (Català / Castellano) for React Native app
Run Tests on Branches / Detect Changes (push) Successful in 13s
Run Tests on Branches / Backend Tests (push) Has been skipped
Run Tests on Branches / Frontend Tests (push) Successful in 1m40s
Run Tests on Branches / Frontend Mobile Tests (push) Successful in 1m37s
Run Tests on Branches / Parapharmacy API Tests (push) Has been skipped
Run Tests on Branches / PIP Platform Tests (push) Has been skipped

Add lightweight i18n system matching the web app architecture.
Language persisted in AsyncStorage('ff-lang').

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

Modified 16 files:
- app/_layout.tsx: wrap with LanguageProvider, translate stack titles
- app/(tabs)/_layout.tsx: translate tab bar labels
- app/(tabs)/index.tsx: translate home screen
- app/(tabs)/search.tsx: translate search screen
- app/(tabs)/alerts.tsx: translate alerts screen
- app/(tabs)/profile.tsx: translate profile + add language selector (CA/ES)
- app/(tabs)/scan.tsx: translate scanner screen
- app/auth/login.tsx: translate login/register
- app/medicine/[id].tsx: translate medicine detail
- app/pharmacy/[id].tsx: translate pharmacy detail
- app/product/[source]/[id].tsx: translate product detail
- 5 components: MedicineCard, StockBadge, SearchBar, LoadingSpinner, BarcodeScanner

Language selector in Profile screen (globe icon + CA/ES toggle).
No routes changed. No API calls affected.
This commit is contained in:
Antoni Nuñez Romeu
2026-07-17 10:22:14 +02:00
parent d12c575fcf
commit ee958d4525
21 changed files with 771 additions and 198 deletions
+228
View File
@@ -0,0 +1,228 @@
const ca = {
// Tab bar
'nav.home': 'Inici',
'nav.search': 'Cercar',
'nav.scan': 'Escanejar',
'nav.alerts': 'Alertes',
'nav.profile': 'Perfil',
// Stack screens
'stack.medicine': 'Medicament',
'stack.pharmacy': 'Farmàcia',
'stack.scanner': 'Escanejar',
'stack.login': 'Iniciar Sessió',
'stack.register': 'Registrar-se',
// Home
'home.description': 'Trobeu els vostres medicaments a farmàcies properes',
'home.searchMedicine': 'Cercar Medicament',
'home.scanTSI': 'Escanejar TSI',
// Search
'search.placeholder': 'Cercar medicaments...',
'search.suggestions': 'Suggeriments',
'search.recentSearches': 'Cerques recents',
'search.loading': 'Cercant...',
'search.noResults': 'No s\'han trobat resultats',
'search.resultsFound': 'resultats trobats',
'search.parapharmacy': 'Parafarmàcia',
'search.error': 'Error en cercar medicaments',
// Login
'login.tab.login': 'Iniciar Sessió',
'login.tab.register': 'Crear Compte',
'login.title.login': 'Benvingut de nou',
'login.title.register': 'Crea el teu compte',
'login.subtitle.login': 'Inicia sessió per gestionar el teu perfil i notificacions.',
'login.subtitle.register': 'Desa la teva adreça i rep notificacions quan arribin medicaments.',
'login.username': 'Usuari',
'login.password': 'Contrasenya',
'login.confirmPassword': 'Confirmar contrasenya',
'login.hint.username': '3-32 caràcters per a l\'usuari',
'login.hint.password': 'Mínim 8 caràcters per a la contrasenya',
'login.biometric': 'Iniciar amb biometria',
'login.link.toLogin': 'Ja tens compte? Inicia sessió',
'login.link.toRegister': 'No tens compte? Registra\'t',
'login.alert.error': 'Error',
'login.alert.fillFields': 'Si us plau, completeu tots els camps',
'login.alert.passwordLength': 'La contrasenya ha de tenir almenys 8 caràcters',
'login.alert.passwordMismatch': 'Les contrasenyes no coincideixen',
'login.alert.success': 'Èxit',
'login.alert.accountCreated': 'Compte creat correctament',
'login.alert.ok': 'D\'acord',
'login.alert.createError': 'No s\'ha pogut crear el compte',
'login.alert.wrongCredentials': 'Credencials incorrectes',
'login.alert.noBiometrics': 'No hi ha credencials biomètriques guardades',
'login.alert.biometricFailed': 'Autenticació biomètrica fallida',
'login.alert.biometricError': 'Error en l\'autenticació biomètrica',
// Scanner
'scanner.title': 'Escanejar TSI',
'scanner.description': 'Escanegeu el codi de barres de la vostra targeta sanitària per trobar els vostres medicaments',
'scanner.startScan': 'Iniciar escaneig',
'scanner.or': 'o',
'scanner.uploadPhoto': 'Pujar foto del dispositiu',
'scanner.uploadDescription': 'Tireu o seleccioneu una foto del dispositiu sanitari',
'scanner.takePhoto': 'Fer foto',
'scanner.gallery': 'Galeria',
'scanner.photoSelected': 'Foto seleccionada correctament',
'scanner.manualEntry': 'Introduir número manualment',
'scanner.manualDescription': 'Escriviu el número de la vostra targeta sanitària',
'scanner.manualPlaceholder': 'Introduïu el número de targeta',
'scanner.cameraPermission': 'Permís de càmera requerit',
'scanner.cameraPermissionDesc': 'Necessitem accés a la càmera per fer fotos del dispositiu.',
'scanner.galleryPermission': 'Permís requerit',
'scanner.galleryPermissionDesc': 'Necessitem accés a la galeria per seleccionar fotos.',
'scanner.fieldRequired': 'Camp requerit',
'scanner.fieldRequiredDesc': 'Si us plau, introduïu el número de la targeta.',
// Alerts
'alerts.title': 'Notificacions Desades',
'alerts.description': 'Rebeu avisos quan medicaments sense estoc es repoixin',
'alerts.loading': 'Carregant notificacions...',
'alerts.loginRequired': 'Inicieu sessió per continuar',
'alerts.loginDescription': 'Necessiteu estar autenticat per veure les vostres notificacions',
'alerts.loginBtn': 'Iniciar Sessió',
'alerts.empty': 'Sense notificacions',
'alerts.emptyDescription': 'Toqueu la campana a una farmàcia sense estoc per rebre notificacions quan es repoixi.',
'alerts.retry': 'Tornar a provar',
'alerts.delete': 'Eliminar notificació',
'alerts.deleteConfirm': 'Eliminar la notificació de',
'alerts.cancel': 'Cancel·lar',
'alerts.confirmDelete': 'Eliminar',
'alerts.deleteError': 'No s\'ha pogut eliminar',
'alerts.loadError': 'No s\'han pogut carregar les notificacions',
'alerts.pharmacy': 'Farmàcia',
'alerts.anyPharmacy': 'Qualsevol farmàcia',
// Profile
'profile.title': 'Perfil',
'profile.loginTitle': 'Iniciar Sessió',
'profile.loginDescription': 'Inicieu sessió per accedir al vostre perfil, notificacions i més',
'profile.loginBtn': 'Iniciar Sessió',
'profile.personalData': 'Dades personals',
'profile.firstName': 'Nom',
'profile.lastName': 'Cognoms',
'profile.appearance': 'Aparença',
'profile.config': 'Configuració',
'profile.myAddresses': 'Les Meves Adreces',
'profile.admin': 'Panell Admin',
'profile.recentSearches': 'Cerques recents',
'profile.logout': 'Tancar Sessió',
'profile.logoutConfirm': 'Esteu segur que voleu tancar la sessió?',
'profile.logoutTitle': 'Tancar Sessió',
'profile.logoutCancel': 'Cancel·lar',
'profile.logoutConfirmBtn': 'Tancar Sessió',
'profile.changeAvatar': 'Canviar Avatar',
'profile.presetAvatar': 'Predissenyat',
'profile.colors': 'Colors',
'profile.upload': 'Pujar',
'profile.takePhoto': 'Fer foto',
'profile.takePhotoDesc': 'Useu la càmera del vostre dispositiu',
'profile.chooseGallery': 'Trieu de galeria',
'profile.chooseGalleryDesc': 'Seleccioneu una imatge existent',
'profile.configTitle': 'Configuració',
'profile.firstNameLabel': 'Nom',
'profile.firstNamePlaceholder': 'El vostre nom',
'profile.lastNameLabel': 'Cognoms',
'profile.lastNamePlaceholder': 'Els vostres cognoms',
'profile.email': 'Correu electrònic',
'profile.city': 'Ciutat',
'profile.cityPlaceholder': 'La vostra ciutat',
'profile.address': 'Adreça',
'profile.addressPlaceholder': 'Carrer Major 1, Barcelona',
'profile.cancel': 'Cancel·lar',
'profile.save': 'Desar',
'profile.profileSaved': 'Perfil desat.',
'profile.saveError': 'Error en desar',
'profile.addressRequired': 'L\'adreça és obligatòria',
'profile.addressesTitle': 'Les Meves Adreces',
'profile.addressLabel': 'Adreça',
'profile.addressOptional': 'Etiqueta (opcional)',
'profile.addressLabelPlaceholder': 'Casa, Treball...',
'profile.defaultAddress': 'Adreça predeterminada',
'profile.update': 'Actualitzar',
'profile.add': 'Afegir',
'profile.mainAddress': 'Principal',
'profile.setDefault': 'Establir com a predeterminada',
'profile.addAddress': 'Afegir adreça',
'profile.cameraPermission': 'Permís requerit',
'profile.cameraPermissionDesc': 'Necessitem permís per accedir a la càmera.',
'profile.themeSystem': 'Sistema',
'profile.themeLight': 'Clar',
'profile.themeDark': 'Fosc',
'profile.language': 'Idioma',
'profile.languageTitle': 'Idioma de l\'aplicació',
'profile.languageCatalan': 'Català',
'profile.languageSpanish': 'Castellà',
// Medicine detail
'medicine.loading': 'Carregant medicament...',
'medicine.notFound': 'Medicament no trobat',
'medicine.activeIngredient': 'Principi actiu',
'medicine.laboratory': 'Laboratori',
'medicine.form': 'Forma farmacèutica',
'medicine.dosage': 'Dosificació',
'medicine.price': 'Preu',
'medicine.notAvailable': 'No disponible',
'medicine.registration': 'Registre',
'medicine.mapSoon': 'Mapa properament…',
'medicine.pharmacies': 'Farmàcies',
'medicine.locating': 'Localitzant…',
'medicine.distanceReset': 'Distància · Restablir',
'medicine.sortByDistance': 'Ordenar per distància',
'medicine.retry': 'Tornar a provar',
'medicine.noPharmacies': 'No hi ha farmàcies disponibles',
'medicine.checkPrice': 'Consultar preu',
'medicine.howToGet': 'Com arribar',
'medicine.locationDenied': 'Permís d\'ubicació denegat',
'medicine.locationError': 'No s\'ha pogut obtenir la vostra ubicació',
// Pharmacy detail
'pharmacy.loading': 'Carregant farmàcia...',
'pharmacy.notFound': 'Farmàcia no trobada',
'pharmacy.call': 'Trucar',
'pharmacy.howToGet': 'Com arribar',
'pharmacy.medications': 'Medicaments',
'pharmacy.noMedications': 'No hi ha medicaments disponibles',
'pharmacy.reg': 'Reg: ',
'pharmacy.stock': 'Estoc: ',
'pharmacy.notificationsActive': 'Notificacions actives',
'pharmacy.notifyWhenAvailable': 'Notificar-me quan hi hagi estoc',
// Product detail
'product.loading': 'Carregant producte...',
'product.notFound': 'Producte no trobat',
'product.noImage': 'Sense imatge',
'product.parapharmacy': 'Parafarmàcia',
'product.cimaDetails': 'Detalls CIMA',
'product.activeIngredient': 'Principi actiu',
'product.dosage': 'Dosificació',
'product.form': 'Forma farmacèutica',
'product.dispensation': 'Tipus de dispensació',
'product.commercialized': 'Comercialitzat',
'product.yes': 'Sí',
'product.no': 'No',
'product.productInfo': 'Informació del producte',
'product.price': 'Preu',
'product.previousPrice': 'Preu anterior',
'product.category': 'Categoria',
'product.brand': 'Marca',
'product.source': 'Font',
'product.images': 'Imatges',
// Components
'medicineCard.noPrice': 'Sense preu',
'stockBadge.outOfStock': 'Sense estoc',
'stockBadge.lowStock': 'Baix',
'stockBadge.inStock': 'Disponible',
'loadingSpinner.loading': 'Carregant...',
'barcodeScanner.permissionRequired': 'Permís de càmera requerit',
'barcodeScanner.permissionDesc': 'Necessitem accés a la càmera per escanejar codis de barres',
'barcodeScanner.grantPermission': 'Concedir permís',
'barcodeScanner.cancel': 'Cancel·lar',
'barcodeScanner.scanningHint': 'Apunteu la càmera al codi de barres del medicament',
'barcodeScanner.scanAgain': 'Escanejar de nou',
};
export default ca;