From 54853c7a4d3bed0ebdaccccc680119410a9eb504 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 6 Aug 2025 08:45:12 +0200 Subject: [PATCH] Refactor AuthContext to return translation keys instead of direct translations (#1085) --- apps/mobile-app/app/(tabs)/settings/index.tsx | 8 ++-- .../app/(tabs)/settings/vault-unlock.tsx | 12 +++--- apps/mobile-app/app/login.tsx | 5 ++- apps/mobile-app/app/unlock.tsx | 8 ++-- apps/mobile-app/context/AuthContext.tsx | 43 +++++++++---------- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/apps/mobile-app/app/(tabs)/settings/index.tsx b/apps/mobile-app/app/(tabs)/settings/index.tsx index 22838088e..e8b44f7a4 100644 --- a/apps/mobile-app/app/(tabs)/settings/index.tsx +++ b/apps/mobile-app/app/(tabs)/settings/index.tsx @@ -26,7 +26,7 @@ export default function SettingsScreen() : React.ReactNode { const webApi = useWebApi(); const colors = useColors(); const { t } = useTranslation(); - const { getAuthMethodDisplay, shouldShowAutofillReminder } = useAuth(); + const { getAuthMethodDisplayKey, shouldShowAutofillReminder } = useAuth(); const { getAutoLockTimeout } = useAuth(); const { loadApiUrl, getDisplayUrl } = useApiUrl(); const scrollY = useRef(new Animated.Value(0)).current; @@ -69,8 +69,8 @@ export default function SettingsScreen() : React.ReactNode { * Load the auth method display. */ const loadAuthMethodDisplay = async () : Promise => { - const authMethod = await getAuthMethodDisplay(); - setAuthMethodDisplay(authMethod); + const authMethodKey = await getAuthMethodDisplayKey(); + setAuthMethodDisplay(t(authMethodKey)); }; /** @@ -82,7 +82,7 @@ export default function SettingsScreen() : React.ReactNode { }; loadData(); - }, [getAutoLockTimeout, getAuthMethodDisplay, setIsFirstLoad, loadApiUrl, t]) + }, [getAutoLockTimeout, getAuthMethodDisplayKey, setIsFirstLoad, loadApiUrl, t]) ); /** diff --git a/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx b/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx index fe65f2c3c..3bc37bdce 100644 --- a/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx +++ b/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx @@ -18,10 +18,10 @@ export default function VaultUnlockSettingsScreen() : React.ReactNode { const colors = useColors(); const { t } = useTranslation(); const [initialized, setInitialized] = useState(false); - const { setAuthMethods, getEnabledAuthMethods, getBiometricDisplayName } = useAuth(); + const { setAuthMethods, getEnabledAuthMethods, getBiometricDisplayNameKey } = useAuth(); const [hasBiometrics, setHasBiometrics] = useState(false); const [isBiometricsEnabled, setIsBiometricsEnabled] = useState(false); - const [biometricDisplayName, setBiometricDisplayName] = useState(Platform.OS === 'ios' ? t('settings.vaultUnlockSettings.faceIdTouchId') : t('settings.vaultUnlockSettings.biometrics')); + const [biometricDisplayName, setBiometricDisplayName] = useState(''); const [_, setEnabledAuthMethods] = useState([]); useEffect(() => { @@ -40,8 +40,10 @@ export default function VaultUnlockSettingsScreen() : React.ReactNode { const isBiometricAvailable = compatible && enrolled; setHasBiometrics(isBiometricAvailable); - // Get appropriate display name - const displayName = Platform.OS === 'ios' ? await getBiometricDisplayName() : t('settings.vaultUnlockSettings.biometrics'); + // Get appropriate display name key from auth context + const displayNameKey = await getBiometricDisplayNameKey(); + // Translate the key + const displayName = t(displayNameKey); setBiometricDisplayName(displayName); const methods = await getEnabledAuthMethods(); @@ -60,7 +62,7 @@ export default function VaultUnlockSettingsScreen() : React.ReactNode { }; initializeAuth(); - }, [getEnabledAuthMethods, getBiometricDisplayName, t]); + }, [getEnabledAuthMethods, getBiometricDisplayNameKey, t]); useEffect(() => { if (!initialized) { diff --git a/apps/mobile-app/app/login.tsx b/apps/mobile-app/app/login.tsx index a1e161a67..7df0e0909 100644 --- a/apps/mobile-app/app/login.tsx +++ b/apps/mobile-app/app/login.tsx @@ -85,8 +85,9 @@ export default function LoginScreen() : React.ReactNode { passwordHashBase64: string, initiateLoginResponse: LoginResponse ) : Promise => { - // Get biometric display name - const biometricDisplayName = await authContext.getBiometricDisplayName(); + // Get biometric display name key and translate it + const biometricDisplayNameKey = await authContext.getBiometricDisplayNameKey(); + const biometricDisplayName = t(biometricDisplayNameKey); const isBiometricsEnabledOnDevice = await authContext.isBiometricsEnabledOnDevice(); if (isBiometricsEnabledOnDevice) { diff --git a/apps/mobile-app/app/unlock.tsx b/apps/mobile-app/app/unlock.tsx index 385db76ba..734304d1b 100644 --- a/apps/mobile-app/app/unlock.tsx +++ b/apps/mobile-app/app/unlock.tsx @@ -31,7 +31,7 @@ export default function UnlockScreen() : React.ReactNode { const colors = useColors(); const { t } = useTranslation(); const webApi = useWebApi(); - const { getBiometricDisplayName } = useAuth(); + const { getBiometricDisplayNameKey } = useAuth(); const [biometricDisplayName, setBiometricDisplayName] = useState(''); /** @@ -58,12 +58,12 @@ export default function UnlockScreen() : React.ReactNode { const enabled = await isBiometricsEnabled(); setIsBiometricsAvailable(enabled); - const displayName = await getBiometricDisplayName(); - setBiometricDisplayName(displayName); + const displayNameKey = await getBiometricDisplayNameKey(); + setBiometricDisplayName(t(displayNameKey)); }; fetchBiometricConfig(); - }, [isBiometricsEnabled, getKeyDerivationParams, getBiometricDisplayName]); + }, [isBiometricsEnabled, getKeyDerivationParams, getBiometricDisplayNameKey, t]); /** * Handle the unlock. diff --git a/apps/mobile-app/context/AuthContext.tsx b/apps/mobile-app/context/AuthContext.tsx index 77637d5dc..c1e47a941 100644 --- a/apps/mobile-app/context/AuthContext.tsx +++ b/apps/mobile-app/context/AuthContext.tsx @@ -3,7 +3,6 @@ import { NavigationContainerRef, ParamListBase } from '@react-navigation/native' import * as LocalAuthentication from 'expo-local-authentication'; import { router, useGlobalSearchParams, usePathname } from 'expo-router'; import React, { createContext, useContext, useState, useEffect, useMemo, useCallback, useRef } from 'react'; -import { useTranslation } from 'react-i18next'; import { AppState, Platform } from 'react-native'; import EncryptionUtility from '@/utils/EncryptionUtility'; @@ -30,10 +29,10 @@ type AuthContextType = { globalMessage: string | null; clearGlobalMessage: () => void; setAuthMethods: (methods: AuthMethod[]) => Promise; - getAuthMethodDisplay: () => Promise; + getAuthMethodDisplayKey: () => Promise; getAutoLockTimeout: () => Promise; setAutoLockTimeout: (timeout: number) => Promise; - getBiometricDisplayName: () => Promise; + getBiometricDisplayNameKey: () => Promise; isBiometricsEnabledOnDevice: () => Promise; setOfflineMode: (isOffline: boolean) => void; verifyPassword: (password: string) => Promise; @@ -56,7 +55,6 @@ const AuthContext = createContext(undefined); * AuthProvider to provide the authentication state to the app that components can use. */ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const { t } = useTranslation(); const [isLoggedIn, setIsLoggedIn] = useState(false); const [isInitialized, setIsInitialized] = useState(false); const [username, setUsername] = useState(null); @@ -70,6 +68,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const params = useGlobalSearchParams(); const lastRouteRef = useRef<{ path: string, params?: object }>({ path: pathname, params }); + useEffect(() => { lastRouteRef.current = { path: pathname, params }; }, [pathname, params]); @@ -201,21 +200,21 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }, []); /** - * Get the appropriate biometric display name based on device capabilities + * Get the appropriate biometric display name translation key based on device capabilities */ - const getBiometricDisplayName = useCallback(async (): Promise => { + const getBiometricDisplayNameKey = useCallback(async (): Promise => { try { const hasBiometrics = await LocalAuthentication.hasHardwareAsync(); const enrolled = await LocalAuthentication.isEnrolledAsync(); // For Android, we use the term "Biometrics" for facial recognition and fingerprint. if (Platform.OS === 'android') { - return t('settings.vaultUnlockSettings.biometrics'); + return 'settings.vaultUnlockSettings.biometrics'; } // For iOS, we check if the device has explicit Face ID or Touch ID support. if (!hasBiometrics || !enrolled) { - return t('settings.vaultUnlockSettings.faceIdTouchId'); + return 'settings.vaultUnlockSettings.faceIdTouchId'; } const types = await LocalAuthentication.supportedAuthenticationTypesAsync(); @@ -223,35 +222,35 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const hasTouchIDSupport = types.includes(LocalAuthentication.AuthenticationType.FINGERPRINT); if (hasFaceIDSupport) { - return t('settings.vaultUnlockSettings.faceId'); + return 'settings.vaultUnlockSettings.faceId'; } else if (hasTouchIDSupport) { - return t('settings.vaultUnlockSettings.touchId'); + return 'settings.vaultUnlockSettings.touchId'; } - return t('settings.vaultUnlockSettings.faceIdTouchId'); + return 'settings.vaultUnlockSettings.faceIdTouchId'; } catch (error) { console.error('Failed to get biometric display name:', error); - return t('settings.vaultUnlockSettings.faceIdTouchId'); + return 'settings.vaultUnlockSettings.faceIdTouchId'; } - }, [t]); + }, []); /** - * Get the display label for the current auth method + * Get the display label translation key for the current auth method * Prefers Face ID if enabled, otherwise falls back to Password */ - const getAuthMethodDisplay = useCallback(async (): Promise => { + const getAuthMethodDisplayKey = useCallback(async (): Promise => { const methods = await getEnabledAuthMethods(); if (methods.includes('faceid')) { try { if (await isBiometricsEnabledOnDevice()) { - return await getBiometricDisplayName(); + return await getBiometricDisplayNameKey(); } } catch (error) { console.error('Failed to check Face ID enrollment:', error); } } - return t('credentials.password'); - }, [getEnabledAuthMethods, getBiometricDisplayName, isBiometricsEnabledOnDevice, t]); + return 'credentials.password'; + }, [getEnabledAuthMethods, getBiometricDisplayNameKey, isBiometricsEnabledOnDevice]); /** * Get the auto-lock timeout from the iOS credentials manager @@ -397,11 +396,11 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children logout, clearGlobalMessage, setAuthMethods, - getAuthMethodDisplay, + getAuthMethodDisplayKey, isBiometricsEnabledOnDevice, getAutoLockTimeout, setAutoLockTimeout, - getBiometricDisplayName, + getBiometricDisplayNameKey, markAutofillConfigured, setReturnUrl, verifyPassword, @@ -422,11 +421,11 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children logout, clearGlobalMessage, setAuthMethods, - getAuthMethodDisplay, + getAuthMethodDisplayKey, isBiometricsEnabledOnDevice, getAutoLockTimeout, setAutoLockTimeout, - getBiometricDisplayName, + getBiometricDisplayNameKey, markAutofillConfigured, setReturnUrl, verifyPassword,