Refactor AuthContext to return translation keys instead of direct translations (#1085)

This commit is contained in:
Leendert de Borst
2025-08-06 08:45:12 +02:00
parent 1dde9ab4b4
commit 54853c7a4d
5 changed files with 39 additions and 37 deletions

View File

@@ -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<void> => {
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])
);
/**

View File

@@ -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<AuthMethod[]>([]);
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) {

View File

@@ -85,8 +85,9 @@ export default function LoginScreen() : React.ReactNode {
passwordHashBase64: string,
initiateLoginResponse: LoginResponse
) : Promise<void> => {
// 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) {

View File

@@ -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.

View File

@@ -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<void>;
getAuthMethodDisplay: () => Promise<string>;
getAuthMethodDisplayKey: () => Promise<string>;
getAutoLockTimeout: () => Promise<number>;
setAutoLockTimeout: (timeout: number) => Promise<void>;
getBiometricDisplayName: () => Promise<string>;
getBiometricDisplayNameKey: () => Promise<string>;
isBiometricsEnabledOnDevice: () => Promise<boolean>;
setOfflineMode: (isOffline: boolean) => void;
verifyPassword: (password: string) => Promise<string | null>;
@@ -56,7 +55,6 @@ const AuthContext = createContext<AuthContextType | undefined>(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<string | null>(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<string> => {
const getBiometricDisplayNameKey = useCallback(async (): Promise<string> => {
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<string> => {
const getAuthMethodDisplayKey = useCallback(async (): Promise<string> => {
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,