Cleanup unused translations in mobile app (#1355)

This commit is contained in:
Leendert de Borst
2025-11-13 22:57:35 +01:00
committed by Leendert de Borst
parent 4b9e2ba2e3
commit fd64ea8647
15 changed files with 72 additions and 99 deletions

11
apps/mobile-app/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"i18n-ally.localesPaths": [
"i18n",
"i18n/locales",
"ios/Pods/RCT-Folly/folly/lang",
"ios/Pods/boost/boost/predef/language",
"ios/Pods/Headers/Private/RCT-Folly/folly/lang",
"ios/Pods/Headers/Public/RCT-Folly/folly/lang"
],
"i18n-ally.keystyle": "nested"
}

View File

@@ -432,11 +432,6 @@ export default function AddEditCredentialScreen() : React.ReactNode {
}
} catch (error) {
console.error('Error generating random username:', error);
Toast.show({
type: 'error',
text1: t('credentials.errors.generateUsernameFailed'),
text2: t('auth.errors.enterPassword')
});
}
};

View File

@@ -114,7 +114,7 @@ export default function EmailDetailsScreen() : React.ReactNode {
// Go back to the emails list screen.
router.back();
} catch (err) {
setError(err instanceof Error ? err.message : t('emails.errors.deleteFailed'));
setError(err instanceof Error ? err.message : t('common.errors.unknownError'));
}
},
},
@@ -132,7 +132,7 @@ export default function EmailDetailsScreen() : React.ReactNode {
);
if (!dbContext?.sqliteClient || !email) {
setError(t('emails.errors.dbNotAvailable'));
setError(t('common.errors.unknownError'));
return;
}
@@ -144,7 +144,7 @@ export default function EmailDetailsScreen() : React.ReactNode {
);
if (!decryptedBytes) {
setError(t('emails.errors.decryptFailed'));
setError(t('common.errors.unknownError'));
return;
}
@@ -163,7 +163,7 @@ export default function EmailDetailsScreen() : React.ReactNode {
await FileSystem.deleteAsync(tempFile);
} catch (err) {
console.error('handleDownloadAttachment error', err);
setError(err instanceof Error ? err.message : t('emails.errors.downloadFailed'));
setError(err instanceof Error ? err.message : t('common.errors.unknownError'));
}
};

View File

@@ -83,10 +83,10 @@ export default function EmailsScreen() : React.ReactNode {
// Show toast and throw error
Toast.show({
type: 'error',
text1: t('emails.errors.loadFailed'),
text1: t('common.errors.unknownError'),
position: 'bottom',
});
throw new Error(t('emails.errors.loadFailed'));
throw new Error(t('common.errors.unknownError'));
} finally {
setIsLoading(false);
}

View File

@@ -11,20 +11,20 @@ import { ThemedText } from '@/components/themed/ThemedText';
import { useAuth } from '@/context/AuthContext';
import NativeVaultManager from '@/specs/NativeVaultManager';
const TIMEOUT_OPTIONS = [
{ value: 0, label: 'settings.clipboardClearOptions.never' },
{ value: 5, label: 'settings.clipboardClearOptions.5seconds' },
{ value: 10, label: 'settings.clipboardClearOptions.10seconds' },
{ value: 15, label: 'settings.clipboardClearOptions.15seconds' },
{ value: 30, label: 'settings.clipboardClearOptions.30seconds' },
];
/**
* Clipboard clear settings screen.
*/
export default function ClipboardClearScreen(): React.ReactNode {
const colors = useColors();
const { t } = useTranslation();
const TIMEOUT_OPTIONS = [
{ value: 0, label: t('settings.clipboardClearOptions.never') },
{ value: 5, label: t('settings.clipboardClearOptions.5seconds') },
{ value: 10, label: t('settings.clipboardClearOptions.10seconds') },
{ value: 15, label: t('settings.clipboardClearOptions.15seconds') },
{ value: 30, label: t('settings.clipboardClearOptions.30seconds') },
];
const { getClipboardClearTimeout, setClipboardClearTimeout } = useAuth();
const [selectedTimeout, setSelectedTimeout] = useState<number>(10);
const [isIgnoringBatteryOptimizations, setIsIgnoringBatteryOptimizations] = useState<boolean>(true);
@@ -198,7 +198,7 @@ export default function ClipboardClearScreen(): React.ReactNode {
style={[styles.option, isLast && styles.optionLast]}
onPress={() => handleTimeoutChange(option.value)}
>
<ThemedText style={styles.optionText}>{t(option.label)}</ThemedText>
<ThemedText style={styles.optionText}>{option.label}</ThemedText>
{selectedTimeout === option.value && (
<Ionicons name="checkmark" size={20} style={styles.selectedIcon} />
)}

View File

@@ -19,7 +19,7 @@ export default function VaultUnlockSettingsScreen() : React.ReactNode {
const colors = useColors();
const { t } = useTranslation();
const [initialized, setInitialized] = useState(false);
const { setAuthMethods, getEnabledAuthMethods, getBiometricDisplayNameKey } = useAuth();
const { setAuthMethods, getEnabledAuthMethods, getBiometricDisplayName } = useAuth();
const [hasBiometrics, setHasBiometrics] = useState(false);
const [isBiometricsEnabled, setIsBiometricsEnabled] = useState(false);
const [biometricDisplayName, setBiometricDisplayName] = useState('');
@@ -44,10 +44,8 @@ export default function VaultUnlockSettingsScreen() : React.ReactNode {
const isBiometricAvailable = compatible && enrolled;
setHasBiometrics(isBiometricAvailable);
// Get appropriate display name key from auth context
const displayNameKey = await getBiometricDisplayNameKey();
// Translate the key
const displayName = t(displayNameKey);
// Get appropriate display name from auth context
const displayName = await getBiometricDisplayName();
setBiometricDisplayName(displayName);
const methods = await getEnabledAuthMethods();
@@ -70,7 +68,7 @@ export default function VaultUnlockSettingsScreen() : React.ReactNode {
};
initializeAuth();
}, [getEnabledAuthMethods, getBiometricDisplayNameKey, t]);
}, [getEnabledAuthMethods, getBiometricDisplayName, t]);
useEffect(() => {
if (!initialized) {

View File

@@ -88,9 +88,8 @@ export default function LoginScreen() : React.ReactNode {
passwordHashBase64: string,
initiateLoginResponse: LoginResponse
) : Promise<void> => {
// Get biometric display name key and translate it
const biometricDisplayNameKey = await authContext.getBiometricDisplayNameKey();
const biometricDisplayName = t(biometricDisplayNameKey);
// Get biometric display name from auth context
const biometricDisplayName = await authContext.getBiometricDisplayName();
const isBiometricsEnabledOnDevice = await authContext.isBiometricsEnabledOnDevice();
if (isBiometricsEnabledOnDevice) {
@@ -318,7 +317,7 @@ export default function LoginScreen() : React.ReactNode {
console.error('Login error:', err);
// Check if self-hosted to show appropriate server error message
const isSelfHosted = await webApi.isSelfHosted();
setError(t(isSelfHosted ? 'auth.errors.serverErrorSelfHosted' : 'auth.errors.serverError'));
setError(isSelfHosted ? t('auth.errors.serverErrorSelfHosted') : t('auth.errors.serverError'));
}
setIsLoading(false);
setLoginStatus(null);

View File

@@ -26,7 +26,7 @@ import NativeVaultManager from '@/specs/NativeVaultManager';
* Unlock screen.
*/
export default function UnlockScreen() : React.ReactNode {
const { isLoggedIn, username, isBiometricsEnabled, getBiometricDisplayNameKey, getEncryptionKeyDerivationParams, logout } = useApp();
const { isLoggedIn, username, isBiometricsEnabled, getBiometricDisplayName, getEncryptionKeyDerivationParams, logout } = useApp();
const dbContext = useDb();
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(true);
@@ -118,8 +118,8 @@ export default function UnlockScreen() : React.ReactNode {
const enabled = await isBiometricsEnabled();
setIsBiometricsAvailable(enabled);
const displayNameKey = await getBiometricDisplayNameKey();
setBiometricDisplayName(t(displayNameKey));
const displayName = await getBiometricDisplayName();
setBiometricDisplayName(displayName);
// Check PIN availability
const pinEnabled = await NativeVaultManager.isPinEnabled();
@@ -139,7 +139,7 @@ export default function UnlockScreen() : React.ReactNode {
};
fetchConfigAndUnlock();
}, [isBiometricsEnabled, getKeyDerivationParams, getBiometricDisplayNameKey, t, handlePinUnlock]);
}, [isBiometricsEnabled, getKeyDerivationParams, getBiometricDisplayName, t, handlePinUnlock]);
/**
* Handle the unlock.

View File

@@ -106,13 +106,13 @@ export const AttachmentSection: React.FC<AttachmentSectionProps> = ({ credential
});
} else {
Alert.alert(
t('credentials.fileReady'),
t('common.success'),
`${t('credentials.fileSavedTo')}: ${filePath}`
);
}
} catch (error) {
console.error('Error downloading file:', error);
Alert.alert('Error', 'Failed to download file');
Alert.alert(t('common.error'), t('common.errors.unknownError'));
}
};

View File

@@ -115,7 +115,7 @@ export const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
});
} else {
Alert.alert(
t('credentials.fileReady'),
t('common.success'),
`${t('credentials.fileSavedTo')}: ${filePath}`
);
}
@@ -234,10 +234,6 @@ export const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
imageUrls={imageUrls}
enableSwipeDown={false}
backgroundColor={colors.background}
/**
* Handle image load failure.
*/
onLoadFailure={(): void => Alert.alert('Error', 'Could not load image')}
/>
);
}

View File

@@ -26,7 +26,7 @@ type AppContextType = {
setAutoLockTimeout: (timeout: number) => Promise<void>;
getClipboardClearTimeout: () => Promise<number>;
setClipboardClearTimeout: (timeout: number) => Promise<void>;
getBiometricDisplayNameKey: () => Promise<string>;
getBiometricDisplayName: () => Promise<string>;
isBiometricsEnabledOnDevice: () => Promise<boolean>;
setOfflineMode: (isOffline: boolean) => void;
verifyPassword: (password: string) => Promise<string | null>;
@@ -120,7 +120,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
setAutoLockTimeout: auth.setAutoLockTimeout,
getClipboardClearTimeout: auth.getClipboardClearTimeout,
setClipboardClearTimeout: auth.setClipboardClearTimeout,
getBiometricDisplayNameKey: auth.getBiometricDisplayNameKey,
getBiometricDisplayName: auth.getBiometricDisplayName,
isBiometricsEnabledOnDevice: auth.isBiometricsEnabledOnDevice,
setOfflineMode: auth.setOfflineMode,
verifyPassword: auth.verifyPassword,
@@ -145,7 +145,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
auth.setAutoLockTimeout,
auth.getClipboardClearTimeout,
auth.setClipboardClearTimeout,
auth.getBiometricDisplayNameKey,
auth.getBiometricDisplayName,
auth.isBiometricsEnabledOnDevice,
auth.setOfflineMode,
auth.verifyPassword,

View File

@@ -35,7 +35,7 @@ type AuthContextType = {
setAutoLockTimeout: (timeout: number) => Promise<void>;
getClipboardClearTimeout: () => Promise<number>;
setClipboardClearTimeout: (timeout: number) => Promise<void>;
getBiometricDisplayNameKey: () => Promise<string>;
getBiometricDisplayName: () => Promise<string>;
isBiometricsEnabledOnDevice: () => Promise<boolean>;
setOfflineMode: (isOffline: boolean) => void;
verifyPassword: (password: string) => Promise<string | null>;
@@ -257,19 +257,19 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
/**
* Get the appropriate biometric display name translation key based on device capabilities
*/
const getBiometricDisplayNameKey = useCallback(async (): Promise<string> => {
const getBiometricDisplayName = 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 'settings.vaultUnlockSettings.biometrics';
return i18n.t('settings.vaultUnlockSettings.biometrics');
}
// For iOS, we check if the device has explicit Face ID or Touch ID support.
if (!hasBiometrics || !enrolled) {
return 'settings.vaultUnlockSettings.faceIdTouchId';
return i18n.t('settings.vaultUnlockSettings.faceIdTouchId');
}
const types = await LocalAuthentication.supportedAuthenticationTypesAsync();
@@ -277,15 +277,15 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const hasTouchIDSupport = types.includes(LocalAuthentication.AuthenticationType.FINGERPRINT);
if (hasFaceIDSupport) {
return 'settings.vaultUnlockSettings.faceId';
return i18n.t('settings.vaultUnlockSettings.faceId');
} else if (hasTouchIDSupport) {
return 'settings.vaultUnlockSettings.touchId';
return i18n.t('settings.vaultUnlockSettings.touchId');
}
return 'settings.vaultUnlockSettings.faceIdTouchId';
return i18n.t('settings.vaultUnlockSettings.faceIdTouchId');
} catch (error) {
console.error('Failed to get biometric display name:', error);
return 'settings.vaultUnlockSettings.faceIdTouchId';
return i18n.t('settings.vaultUnlockSettings.faceIdTouchId');
}
}, []);
@@ -299,7 +299,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
const methods = await getEnabledAuthMethods();
if (methods.includes('faceid')) {
if (await isBiometricsEnabledOnDevice()) {
return await getBiometricDisplayNameKey();
return await getBiometricDisplayName();
}
}
@@ -315,7 +315,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
console.error('Failed to get auth method display key:', error);
return 'credentials.password';
}
}, [getEnabledAuthMethods, getBiometricDisplayNameKey, isBiometricsEnabledOnDevice]);
}, [getEnabledAuthMethods, getBiometricDisplayName, isBiometricsEnabledOnDevice]);
/**
* Get the auto-lock timeout from the iOS credentials manager
@@ -515,7 +515,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
setAutoLockTimeout,
getClipboardClearTimeout,
setClipboardClearTimeout,
getBiometricDisplayNameKey,
getBiometricDisplayName,
markAutofillConfigured,
setReturnUrl,
verifyPassword,
@@ -541,7 +541,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
setAutoLockTimeout,
getClipboardClearTimeout,
setClipboardClearTimeout,
getBiometricDisplayNameKey,
getBiometricDisplayName,
markAutofillConfigured,
setReturnUrl,
verifyPassword,

View File

@@ -169,7 +169,7 @@ export function useVaultMutate() : {
// Get username from the auth context, always lowercase and trimmed which is required for the argon2id key derivation
const username = authContext.username?.toLowerCase().trim();
if (!username) {
throw new Error(t('vault.errors.usernameNotFoundLoginAgain'));
throw new Error(t('common.errors.unknownError'));
}
const privateKey = srp.derivePrivateKey(currentSalt, username, currentPasswordHashString);
@@ -209,7 +209,7 @@ export function useVaultMutate() : {
await NativeVaultManager.unlockVault();
} catch {
// If any part of this fails, we need logout the user as the local vault and stored encryption key are now potentially corrupt.
await authContext.logout(t('vault.errors.errorDuringPasswordChange'));
await authContext.logout(t('common.errors.unknownErrorTryAgain'));
}
// Generate SRP password change data
@@ -325,8 +325,7 @@ export function useVaultMutate() : {
console.error('Error during vault mutation:', error);
Toast.show({
type: 'error',
text1: t('vault.errors.operationFailed'),
text2: error instanceof Error ? error.message : t('common.errors.unknownError'),
text1: t('common.errors.unknownError'),
position: 'bottom'
});
options.onError?.(error instanceof Error ? error : new Error(t('common.errors.unknownError')));
@@ -394,8 +393,8 @@ export function useVaultMutate() : {
onError: (error) => {
Toast.show({
type: 'error',
text1: t('vault.errors.failedToSyncVault'),
text2: error,
text1: t('common.error'),
text2: t('common.errors.unknownError'),
position: 'bottom'
});
options.onError?.(new Error(error));
@@ -406,8 +405,8 @@ export function useVaultMutate() : {
console.error('Error during vault mutation:', error);
Toast.show({
type: 'error',
text1: t('vault.errors.operationFailed'),
text2: error instanceof Error ? error.message : t('common.errors.unknownError'),
text1: t('common.error'),
text2: t('common.errors.unknownError'),
position: 'bottom'
});
options.onError?.(error instanceof Error ? error : new Error(t('common.errors.unknownError')));

View File

@@ -208,12 +208,12 @@ export const useVaultSync = () : {
// Log detailed error information for database setup failures
if (err?.code === 'DATABASE_SETUP_ERROR') {
console.error('Database setup error during unlock:', err.message);
throw new Error(`${t('vault.errors.vaultDecryptFailed')}: ${err.message}`);
throw new Error(t('common.errors.unknownErrorTryAgain'));
}
// Vault could not be unlocked
console.error('Failed to unlock vault:', err);
throw new Error(t('vault.errors.vaultDecryptFailed'));
throw new Error(t('common.errors.unknownErrorTryAgain'));
}
} catch (err) {
console.error('Vault sync error:', err);

View File

@@ -40,9 +40,6 @@
"verify": "Verify",
"unlockVault": "Unlock Vault",
"unlockWithPin": "Unlock with PIN",
"useMasterPassword": "Use Master Password",
"enterPin": "Enter PIN",
"enterPinToUnlock": "Enter your PIN to unlock your vault",
"enterPassword": "Enter your password to unlock your vault",
"enterPasswordPlaceholder": "Password",
"enterAuthCode": "Enter 6-digit code",
@@ -81,17 +78,11 @@
"errors": {
"failedToGetEncryptedDatabase": "Failed to get encrypted database",
"usernameNotFound": "Username not found",
"vaultMergeRequired": "Vault merge required. Please login via the web app to merge the multiple pending updates to your vault.",
"vaultOutdated": "Your vault is outdated. Please login on the AliasVault website and follow the steps.",
"failedToUploadVault": "Failed to upload vault to server. Please try again by re-opening the app.",
"usernameNotFoundLoginAgain": "Username not found. Please login again.",
"errorDuringPasswordChange": "Error during password change operation. Please log in again to retrieve your latest vault.",
"failedToSyncVault": "Failed to sync vault",
"operationFailed": "Operation failed",
"versionNotSupported": "This version of the AliasVault mobile app is not supported by the server anymore. Please update your app to the latest version.",
"serverVersionNotSupported": "The AliasVault server needs to be updated to a newer version in order to use this mobile app. Please contact support if you need help.",
"appOutdated": "This app is outdated and cannot be used to access this (newer) vault version. Please update the AliasVault app to continue.",
"vaultDecryptFailed": "Vault could not be decrypted, if the problem persists please logout and login again.",
"passwordChanged": "Your password has changed since the last time you logged in. Please login again for security reasons."
}
},
@@ -171,9 +162,10 @@
"twoFactorAuth": "Two-factor authentication",
"totpCode": "TOTP Code",
"attachments": "Attachments",
"loadingAttachments": "Loading attachments...",
"addAttachments": "Add Attachments",
"deleteAttachment": "Delete",
"fileSavedTo": "File saved to",
"previewNotSupported": "Preview not supported",
"downloadToView": "Download the file to view it",
"toasts": {
"credentialUpdated": "Credential updated successfully",
"credentialCreated": "Credential created successfully",
@@ -185,9 +177,7 @@
"createNewAliasFor": "Create new alias for",
"errors": {
"loadFailed": "Failed to load credential",
"saveFailed": "Failed to save credential",
"generateUsernameFailed": "Failed to generate username",
"generatePasswordFailed": "Failed to generate password"
"saveFailed": "Failed to save credential"
},
"contextMenu": {
"title": "Credential Options",
@@ -326,11 +316,6 @@
"random": "Random",
"male": "Male",
"female": "Female"
},
"errors": {
"loadFailed": "Failed to load identity generator settings.",
"languageUpdateFailed": "Failed to update language setting.",
"genderUpdateFailed": "Failed to update gender setting."
}
},
"passwordGeneratorSettings": {
@@ -378,7 +363,6 @@
"success": "Success",
"failed": "Failed",
"time": "Time",
"device": "Device",
"ipAddress": "IP Address",
"client": "Client",
"failedToLoad": "Failed to load auth logs"
@@ -435,14 +419,6 @@
"hoursAgo_single": "{{count}} hr ago",
"hoursAgo_plural": "{{count}} hrs ago",
"yesterday": "yesterday"
},
"errors": {
"generic": "An error occurred",
"loadFailed": "Failed to load emails",
"deleteFailed": "Failed to delete email",
"dbNotAvailable": "Database context or email not available",
"decryptFailed": "Failed to decrypt attachment",
"downloadFailed": "Failed to download attachment"
}
},
"validation": {
@@ -480,11 +456,11 @@
"VAULT_ERROR": "The local vault is not up-to-date. Please synchronize your vault by refreshing the page and try again."
},
"app": {
"openReadOnlyMode": "Open in read-only mode",
"status": {
"unlockingVault": "Unlocking vault",
"decryptingVault": "Decrypting vault",
"openingVaultReadOnly": "Opening vault in read-only mode"
"openingVaultReadOnly": "Opening vault in read-only mode",
"retryingConnection": "Retrying connection..."
},
"offline": {
"banner": "Offline mode (read-only)",
@@ -499,8 +475,7 @@
},
"navigation": {
"login": "Login",
"loginSettings": "Login Settings",
"notFound": "Not Found"
"loginSettings": "Login Settings"
},
"notFound": {
"title": "Page not found",
@@ -550,4 +525,4 @@
"failedToApplyMigration": "Failed to apply migration ({{current}} of {{total}})"
}
}
}
}