From fd64ea8647afecbd6f590c98afcd1aa2926f6ef6 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 13 Nov 2025 22:57:35 +0100 Subject: [PATCH] Cleanup unused translations in mobile app (#1355) --- apps/mobile-app/.vscode/settings.json | 11 +++++ .../app/(tabs)/credentials/add-edit.tsx | 5 --- apps/mobile-app/app/(tabs)/emails/[id].tsx | 8 ++-- apps/mobile-app/app/(tabs)/emails/index.tsx | 4 +- .../app/(tabs)/settings/clipboard-clear.tsx | 18 ++++---- .../app/(tabs)/settings/vault-unlock.tsx | 10 ++--- apps/mobile-app/app/login.tsx | 7 ++-- apps/mobile-app/app/unlock.tsx | 8 ++-- .../credentials/details/AttachmentSection.tsx | 4 +- .../credentials/details/FilePreviewModal.tsx | 6 +-- apps/mobile-app/context/AppContext.tsx | 6 +-- apps/mobile-app/context/AuthContext.tsx | 24 +++++------ apps/mobile-app/hooks/useVaultMutate.ts | 15 ++++--- apps/mobile-app/hooks/useVaultSync.ts | 4 +- apps/mobile-app/i18n/locales/en.json | 41 ++++--------------- 15 files changed, 72 insertions(+), 99 deletions(-) create mode 100644 apps/mobile-app/.vscode/settings.json diff --git a/apps/mobile-app/.vscode/settings.json b/apps/mobile-app/.vscode/settings.json new file mode 100644 index 000000000..0b85f2b76 --- /dev/null +++ b/apps/mobile-app/.vscode/settings.json @@ -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" +} \ No newline at end of file diff --git a/apps/mobile-app/app/(tabs)/credentials/add-edit.tsx b/apps/mobile-app/app/(tabs)/credentials/add-edit.tsx index 0d4227203..d948baecb 100644 --- a/apps/mobile-app/app/(tabs)/credentials/add-edit.tsx +++ b/apps/mobile-app/app/(tabs)/credentials/add-edit.tsx @@ -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') - }); } }; diff --git a/apps/mobile-app/app/(tabs)/emails/[id].tsx b/apps/mobile-app/app/(tabs)/emails/[id].tsx index b6afa6c65..0d7e69bbb 100644 --- a/apps/mobile-app/app/(tabs)/emails/[id].tsx +++ b/apps/mobile-app/app/(tabs)/emails/[id].tsx @@ -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')); } }; diff --git a/apps/mobile-app/app/(tabs)/emails/index.tsx b/apps/mobile-app/app/(tabs)/emails/index.tsx index 5cc218699..33758eed8 100644 --- a/apps/mobile-app/app/(tabs)/emails/index.tsx +++ b/apps/mobile-app/app/(tabs)/emails/index.tsx @@ -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); } diff --git a/apps/mobile-app/app/(tabs)/settings/clipboard-clear.tsx b/apps/mobile-app/app/(tabs)/settings/clipboard-clear.tsx index 6134d48f2..22e3d5d57 100644 --- a/apps/mobile-app/app/(tabs)/settings/clipboard-clear.tsx +++ b/apps/mobile-app/app/(tabs)/settings/clipboard-clear.tsx @@ -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(10); const [isIgnoringBatteryOptimizations, setIsIgnoringBatteryOptimizations] = useState(true); @@ -198,7 +198,7 @@ export default function ClipboardClearScreen(): React.ReactNode { style={[styles.option, isLast && styles.optionLast]} onPress={() => handleTimeoutChange(option.value)} > - {t(option.label)} + {option.label} {selectedTimeout === option.value && ( )} diff --git a/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx b/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx index 94dfad3c6..291471062 100644 --- a/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx +++ b/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx @@ -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) { diff --git a/apps/mobile-app/app/login.tsx b/apps/mobile-app/app/login.tsx index 13e56077d..3a261c735 100644 --- a/apps/mobile-app/app/login.tsx +++ b/apps/mobile-app/app/login.tsx @@ -88,9 +88,8 @@ export default function LoginScreen() : React.ReactNode { passwordHashBase64: string, initiateLoginResponse: LoginResponse ) : Promise => { - // 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); diff --git a/apps/mobile-app/app/unlock.tsx b/apps/mobile-app/app/unlock.tsx index 9906c0021..688b7fe64 100644 --- a/apps/mobile-app/app/unlock.tsx +++ b/apps/mobile-app/app/unlock.tsx @@ -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. diff --git a/apps/mobile-app/components/credentials/details/AttachmentSection.tsx b/apps/mobile-app/components/credentials/details/AttachmentSection.tsx index ea090c8a9..b5aac0bb2 100644 --- a/apps/mobile-app/components/credentials/details/AttachmentSection.tsx +++ b/apps/mobile-app/components/credentials/details/AttachmentSection.tsx @@ -106,13 +106,13 @@ export const AttachmentSection: React.FC = ({ 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')); } }; diff --git a/apps/mobile-app/components/credentials/details/FilePreviewModal.tsx b/apps/mobile-app/components/credentials/details/FilePreviewModal.tsx index 95fd08580..542f61051 100644 --- a/apps/mobile-app/components/credentials/details/FilePreviewModal.tsx +++ b/apps/mobile-app/components/credentials/details/FilePreviewModal.tsx @@ -115,7 +115,7 @@ export const FilePreviewModal: React.FC = ({ }); } else { Alert.alert( - t('credentials.fileReady'), + t('common.success'), `${t('credentials.fileSavedTo')}: ${filePath}` ); } @@ -234,10 +234,6 @@ export const FilePreviewModal: React.FC = ({ imageUrls={imageUrls} enableSwipeDown={false} backgroundColor={colors.background} - /** - * Handle image load failure. - */ - onLoadFailure={(): void => Alert.alert('Error', 'Could not load image')} /> ); } diff --git a/apps/mobile-app/context/AppContext.tsx b/apps/mobile-app/context/AppContext.tsx index dce21ed1f..b62db73fe 100644 --- a/apps/mobile-app/context/AppContext.tsx +++ b/apps/mobile-app/context/AppContext.tsx @@ -26,7 +26,7 @@ type AppContextType = { setAutoLockTimeout: (timeout: number) => Promise; getClipboardClearTimeout: () => Promise; setClipboardClearTimeout: (timeout: number) => Promise; - getBiometricDisplayNameKey: () => Promise; + getBiometricDisplayName: () => Promise; isBiometricsEnabledOnDevice: () => Promise; setOfflineMode: (isOffline: boolean) => void; verifyPassword: (password: string) => Promise; @@ -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, diff --git a/apps/mobile-app/context/AuthContext.tsx b/apps/mobile-app/context/AuthContext.tsx index c9bda626f..5f9d4119e 100644 --- a/apps/mobile-app/context/AuthContext.tsx +++ b/apps/mobile-app/context/AuthContext.tsx @@ -35,7 +35,7 @@ type AuthContextType = { setAutoLockTimeout: (timeout: number) => Promise; getClipboardClearTimeout: () => Promise; setClipboardClearTimeout: (timeout: number) => Promise; - getBiometricDisplayNameKey: () => Promise; + getBiometricDisplayName: () => Promise; isBiometricsEnabledOnDevice: () => Promise; setOfflineMode: (isOffline: boolean) => void; verifyPassword: (password: string) => Promise; @@ -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 => { + const getBiometricDisplayName = 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 '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, diff --git a/apps/mobile-app/hooks/useVaultMutate.ts b/apps/mobile-app/hooks/useVaultMutate.ts index a17ea20e8..91272537f 100644 --- a/apps/mobile-app/hooks/useVaultMutate.ts +++ b/apps/mobile-app/hooks/useVaultMutate.ts @@ -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'))); diff --git a/apps/mobile-app/hooks/useVaultSync.ts b/apps/mobile-app/hooks/useVaultSync.ts index aeffe2fc4..b30fed6e2 100644 --- a/apps/mobile-app/hooks/useVaultSync.ts +++ b/apps/mobile-app/hooks/useVaultSync.ts @@ -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); diff --git a/apps/mobile-app/i18n/locales/en.json b/apps/mobile-app/i18n/locales/en.json index e475c7e7e..83c5cda7b 100644 --- a/apps/mobile-app/i18n/locales/en.json +++ b/apps/mobile-app/i18n/locales/en.json @@ -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}})" } } -} \ No newline at end of file +}