diff --git a/apps/browser-extension/src/locales/nl/credentials.json b/apps/browser-extension/src/locales/nl/credentials.json
index d1a5ec26e..3eab72ed2 100644
--- a/apps/browser-extension/src/locales/nl/credentials.json
+++ b/apps/browser-extension/src/locales/nl/credentials.json
@@ -28,12 +28,12 @@
"searchCredentials": "Zoek inloggegevens...",
"searchPlaceholder": "Inloggegevens zoeken...",
"welcomeTitle": "Welkom bij AliasVault!",
- "welcomeDescription": "Om de AliasVault browser extensie te gebruiken: navigeer naar een website en gebruik de AliasVault automatisch invullen popup om nieuwe inloggegevens aan te maken.",
+ "welcomeDescription": "Om de AliasVault browser extensie te gebruiken: navigeer naar een website en gebruik de AliasVault autofill popup om nieuwe inloggegevens aan te maken.",
"manualCreationHint": "Als je handmatig identiteiten wilt aanmaken, open dan de volledige AliasVault app via het uitklapicoon in de rechterbovenhoek.",
"lastUsed": "Laatst gebruikt",
"createdAt": "Aangemaakt",
"updatedAt": "Laatst bijgewerkt",
- "autofill": "Automatisch invullen",
+ "autofill": "Autofill",
"fillForm": "Formulier Invullen",
"copyUsername": "Gebruikersnaam Kopiƫren",
"openWebsite": "Website Openen",
diff --git a/apps/browser-extension/src/locales/nl/settings.json b/apps/browser-extension/src/locales/nl/settings.json
index a7a61655a..07caf3cb1 100644
--- a/apps/browser-extension/src/locales/nl/settings.json
+++ b/apps/browser-extension/src/locales/nl/settings.json
@@ -61,14 +61,14 @@
"loggedIn": "Ingelogd",
"logout": "Uitloggen",
"globalSettings": "Globale Instellingen",
- "autofillPopup": "Automatisch invullen popup",
+ "autofillPopup": "Autofill popup",
"activeOnAllSites": "Actief op alle sites (tenzij hieronder uitgeschakeld)",
"disabledOnAllSites": "Uitgeschakeld op alle sites",
"enabled": "Ingeschakeld",
"disabled": "Uitgeschakeld",
"rightClickContextMenu": "Rechts-klik contextmenu",
"siteSpecificSettings": "Site-specifieke Instellingen",
- "autofillPopupOn": "Automatisch invullen popup op: ",
+ "autofillPopupOn": "Autofill popup op: ",
"enabledForThisSite": "Ingeschakeld voor deze site",
"disabledForThisSite": "Uitgeschakeld voor deze site",
"temporarilyDisabledUntil": "Tijdelijk uitgeschakeld tot ",
diff --git a/apps/browser-extension/src/utils/contentTranslations.ts b/apps/browser-extension/src/utils/contentTranslations.ts
index df4a57f96..7169e785b 100644
--- a/apps/browser-extension/src/utils/contentTranslations.ts
+++ b/apps/browser-extension/src/utils/contentTranslations.ts
@@ -113,7 +113,7 @@ const nlTranslations: IContentTranslations = {
manualCredentialDescription: 'Specificeer je eigen e-mailadres en gebruikersnaam.',
failedToCreateIdentity: 'Identiteit aanmaken mislukt. Probeer opnieuw.',
enterEmailAndOrUsername: 'Voer e-mail en/of gebruikersnaam in',
- autofillWithAliasVault: 'Automatisch invullen met AliasVault',
+ autofillWithAliasVault: 'Autofill met AliasVault',
generateRandomPassword: 'Willekeurig wachtwoord genereren (kopiƫren naar klembord)',
passwordCopiedToClipboard: 'Wachtwoord gekopieerd naar klembord'
};
diff --git a/apps/mobile-app/app/(tabs)/_layout.tsx b/apps/mobile-app/app/(tabs)/_layout.tsx
index af5464193..9330bda61 100644
--- a/apps/mobile-app/app/(tabs)/_layout.tsx
+++ b/apps/mobile-app/app/(tabs)/_layout.tsx
@@ -1,7 +1,7 @@
import { Tabs, router } from 'expo-router';
import React, { useEffect } from 'react';
-import { Platform, StyleSheet, View } from 'react-native';
import { useTranslation } from 'react-i18next';
+import { Platform, StyleSheet, View } from 'react-native';
import emitter from '@/utils/EventEmitter';
diff --git a/apps/mobile-app/app/(tabs)/credentials/add-edit.tsx b/apps/mobile-app/app/(tabs)/credentials/add-edit.tsx
index b0ab34e70..2638c66b5 100644
--- a/apps/mobile-app/app/(tabs)/credentials/add-edit.tsx
+++ b/apps/mobile-app/app/(tabs)/credentials/add-edit.tsx
@@ -4,10 +4,10 @@ import * as Haptics from 'expo-haptics';
import { Stack, useLocalSearchParams, useNavigation, useRouter } from 'expo-router';
import { useState, useEffect, useRef, useCallback } from 'react';
import { Resolver, useForm } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
import { StyleSheet, View, TouchableOpacity, Alert, Keyboard, KeyboardAvoidingView, Platform, Pressable } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import Toast from 'react-native-toast-message';
-import { useTranslation } from 'react-i18next';
import { CreateIdentityGenerator, IdentityHelperUtils, IdentityGenerator } from '@/utils/dist/shared/identity-generator';
import type { Credential } from '@/utils/dist/shared/models/vault';
@@ -98,7 +98,7 @@ export default function AddEditCredentialScreen() : React.ReactNode {
text2: t('auth.errors.enterPassword')
});
}
- }, [id, dbContext.sqliteClient, setValue]);
+ }, [id, dbContext.sqliteClient, setValue, t]);
/**
* On mount, load an existing credential if we're in edit mode, or extract the service name from the service URL
@@ -133,7 +133,7 @@ export default function AddEditCredentialScreen() : React.ReactNode {
serviceNameRef.current?.focus();
}, 100);
}
- }, [id, isEditMode, serviceUrl, loadExistingCredential, setValue, authContext.isOffline, router]);
+ }, [id, isEditMode, serviceUrl, loadExistingCredential, setValue, authContext.isOffline, router, t]);
/**
* Initialize the identity and password generators with settings from user's vault.
@@ -315,7 +315,7 @@ export default function AddEditCredentialScreen() : React.ReactNode {
setIsSyncing(false);
}
- }, [isEditMode, id, serviceUrl, router, executeVaultMutation, dbContext.sqliteClient, mode, generateRandomAlias, webApi, watch, setIsSaveDisabled, setIsSyncing, isSaveDisabled]);
+ }, [isEditMode, id, serviceUrl, router, executeVaultMutation, dbContext.sqliteClient, mode, generateRandomAlias, webApi, watch, setIsSaveDisabled, setIsSyncing, isSaveDisabled, t]);
/**
* Generate a random username.
@@ -555,7 +555,7 @@ export default function AddEditCredentialScreen() : React.ReactNode {
),
});
}
- }, [navigation, mode, handleSubmit, onSubmit, colors.primary, isEditMode, router, styles.headerLeftButton, styles.headerLeftButtonText, styles.headerRightButton, styles.headerRightButtonDisabled, isSaveDisabled]);
+ }, [navigation, mode, handleSubmit, onSubmit, colors.primary, isEditMode, router, styles.headerLeftButton, styles.headerLeftButtonText, styles.headerRightButton, styles.headerRightButtonDisabled, isSaveDisabled, t]);
return (
<>
diff --git a/apps/mobile-app/app/(tabs)/credentials/autofill-credential-created.tsx b/apps/mobile-app/app/(tabs)/credentials/autofill-credential-created.tsx
index 59976a2ff..dacd0cfe2 100644
--- a/apps/mobile-app/app/(tabs)/credentials/autofill-credential-created.tsx
+++ b/apps/mobile-app/app/(tabs)/credentials/autofill-credential-created.tsx
@@ -1,8 +1,8 @@
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { useNavigation, useRouter } from 'expo-router';
import { useCallback, useEffect } from 'react';
-import { StyleSheet, View, TouchableOpacity, AppState } from 'react-native';
import { useTranslation } from 'react-i18next';
+import { StyleSheet, View, TouchableOpacity, AppState } from 'react-native';
import { useColors } from '@/hooks/useColorScheme';
@@ -89,7 +89,7 @@ export default function AutofillCredentialCreatedScreen() : React.ReactNode {
),
});
- }, [navigation, colors.primary, styles.headerRightButton, handleStayInApp]);
+ }, [navigation, colors.primary, styles.headerRightButton, handleStayInApp, t]);
return (
@@ -108,7 +108,7 @@ export default function AutofillCredentialCreatedScreen() : React.ReactNode {
{t('credentials.credentialCreatedMessage')}
- {t('credentials.switchBackToBrowser')}
+ {t('credentials.switchBackToBrowser')}
diff --git a/apps/mobile-app/app/(tabs)/credentials/index.tsx b/apps/mobile-app/app/(tabs)/credentials/index.tsx
index a0e0b0514..0f7e35b3e 100644
--- a/apps/mobile-app/app/(tabs)/credentials/index.tsx
+++ b/apps/mobile-app/app/(tabs)/credentials/index.tsx
@@ -3,10 +3,10 @@ import { useNavigation, useFocusEffect } from '@react-navigation/native';
import * as Haptics from 'expo-haptics';
import { useRouter, useLocalSearchParams } from 'expo-router';
import { useState, useEffect, useCallback, useRef } from 'react';
+import { useTranslation } from 'react-i18next';
import { StyleSheet, Text, FlatList, TouchableOpacity, TextInput, RefreshControl, Platform, Animated, Alert } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Toast from 'react-native-toast-message';
-import { useTranslation } from 'react-i18next';
import type { Credential } from '@/utils/dist/shared/models/vault';
import emitter from '@/utils/EventEmitter';
@@ -76,7 +76,7 @@ export default function CredentialsScreen() : React.ReactNode {
});
setIsLoadingCredentials(false);
}
- }, [dbContext.sqliteClient, setIsLoadingCredentials]);
+ }, [dbContext.sqliteClient, setIsLoadingCredentials, t]);
useEffect(() => {
const unsubscribeFocus = navigation.addListener('focus', () => {
@@ -194,7 +194,7 @@ export default function CredentialsScreen() : React.ReactNode {
text2: err instanceof Error ? err.message : 'Unknown error',
});
}
- }, [syncVault, loadCredentials, setIsLoadingCredentials, setRefreshing, webApi, authContext, router]);
+ }, [syncVault, loadCredentials, setIsLoadingCredentials, setRefreshing, webApi, authContext, router, t]);
useEffect(() => {
if (!isAuthenticated || !isDatabaseAvailable) {
@@ -301,7 +301,7 @@ export default function CredentialsScreen() : React.ReactNode {
*/
headerTitle: (): React.ReactNode => Platform.OS === 'android' ? : {t('credentials.title')},
});
- }, [navigation]);
+ }, [navigation, t]);
/**
* Delete a credential.
diff --git a/apps/mobile-app/app/(tabs)/emails/[id].tsx b/apps/mobile-app/app/(tabs)/emails/[id].tsx
index 907b937d1..e07a75920 100644
--- a/apps/mobile-app/app/(tabs)/emails/[id].tsx
+++ b/apps/mobile-app/app/(tabs)/emails/[id].tsx
@@ -2,9 +2,9 @@ import { Ionicons } from '@expo/vector-icons';
import * as FileSystem from 'expo-file-system';
import { useLocalSearchParams, useRouter, useNavigation, Stack } from 'expo-router';
import React, { useEffect, useState, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
import { StyleSheet, View, TouchableOpacity, ActivityIndicator, Alert, Share, useColorScheme, TextInput, Linking } from 'react-native';
import { WebView } from 'react-native-webview';
-import { useTranslation } from 'react-i18next';
import type { Credential } from '@/utils/dist/shared/models/vault';
import type { Email } from '@/utils/dist/shared/models/webapi';
@@ -76,7 +76,7 @@ export default function EmailDetailsScreen() : React.ReactNode {
} finally {
setIsLoading(false);
}
- }, [dbContext.sqliteClient, id, webApi]);
+ }, [dbContext.sqliteClient, id, webApi, t]);
useEffect(() => {
loadEmail();
@@ -117,7 +117,7 @@ export default function EmailDetailsScreen() : React.ReactNode {
},
]
);
- }, [id, router, webApi]);
+ }, [id, router, webApi, t]);
/**
* Handle the download attachment button press.
diff --git a/apps/mobile-app/app/(tabs)/settings/auto-lock.tsx b/apps/mobile-app/app/(tabs)/settings/auto-lock.tsx
index 5b0bb08f5..b3b2e20dc 100644
--- a/apps/mobile-app/app/(tabs)/settings/auto-lock.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/auto-lock.tsx
@@ -1,7 +1,7 @@
import { Ionicons } from '@expo/vector-icons';
import { useEffect, useState } from 'react';
-import { StyleSheet, View, TouchableOpacity } from 'react-native';
import { useTranslation } from 'react-i18next';
+import { StyleSheet, View, TouchableOpacity } from 'react-native';
import { useColors } from '@/hooks/useColorScheme';
diff --git a/apps/mobile-app/app/(tabs)/settings/identity-generator.tsx b/apps/mobile-app/app/(tabs)/settings/identity-generator.tsx
index 0777a520d..a17c2f089 100644
--- a/apps/mobile-app/app/(tabs)/settings/identity-generator.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/identity-generator.tsx
@@ -1,8 +1,8 @@
import { Ionicons } from '@expo/vector-icons';
import { useFocusEffect } from 'expo-router';
import { useState, useCallback } from 'react';
-import { StyleSheet, View, Alert, TouchableOpacity } from 'react-native';
import { useTranslation } from 'react-i18next';
+import { StyleSheet, View, Alert, TouchableOpacity } from 'react-native';
import { useColors } from '@/hooks/useColorScheme';
import { useVaultMutate } from '@/hooks/useVaultMutate';
@@ -58,7 +58,7 @@ export default function IdentityGeneratorSettingsScreen(): React.ReactNode {
};
loadSettings();
- }, [dbContext.sqliteClient])
+ }, [dbContext.sqliteClient, t])
);
/**
@@ -75,7 +75,7 @@ export default function IdentityGeneratorSettingsScreen(): React.ReactNode {
console.error('Error updating language setting:', error);
Alert.alert(t('common.error'), t('settings.identityGeneratorSettings.errors.languageUpdateFailed'));
}
- }, [executeVaultMutation, dbContext.sqliteClient]);
+ }, [executeVaultMutation, dbContext.sqliteClient, t]);
/**
* Handle gender change.
@@ -90,7 +90,7 @@ export default function IdentityGeneratorSettingsScreen(): React.ReactNode {
console.error('Error updating gender setting:', error);
Alert.alert(t('common.error'), t('settings.identityGeneratorSettings.errors.genderUpdateFailed'));
}
- }, [executeVaultMutation, dbContext.sqliteClient]);
+ }, [executeVaultMutation, dbContext.sqliteClient, t]);
const styles = StyleSheet.create({
descriptionText: {
diff --git a/apps/mobile-app/app/(tabs)/settings/index.tsx b/apps/mobile-app/app/(tabs)/settings/index.tsx
index 4dc328105..22838088e 100644
--- a/apps/mobile-app/app/(tabs)/settings/index.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/index.tsx
@@ -147,21 +147,45 @@ export default function SettingsScreen() : React.ReactNode {
* Handle the language settings press.
*/
const handleLanguagePress = (): void => {
+ const isIOS = Platform.OS === 'ios';
+
Alert.alert(
t('settings.language'),
t('settings.languageSystemMessage'),
[
{ text: t('common.cancel'), style: 'cancel' },
- {
- text: t('settings.openSettings'),
+ {
+ text: t('settings.openSettings'),
style: 'default',
/**
- * Open iOS settings
+ * Open platform-specific settings
*/
- onPress: (): void => {
- // Open iOS Settings app
- if (Platform.OS === 'ios') {
- Linking.openURL('app-settings:');
+ onPress: async (): Promise => {
+ if (isIOS) {
+ // Open iOS Settings app
+ await Linking.openURL('app-settings:');
+ } else {
+ // Fallback to general locale settings
+ try {
+ await Linking.openSettings();
+ return;
+ } catch (error) {
+ console.warn('Failed to open general locale settings:', error);
+ }
+
+ // Fallback to general settings
+ try {
+ await Linking.openSettings();
+ return;
+ } catch (error) {
+ console.warn('Failed to open general settings:', error);
+ }
+
+ // Final fallback - show manual instructions
+ Alert.alert(
+ t('common.error') ?? 'Error',
+ 'Unable to open device settings. Please manually navigate to the app settings and change the language.'
+ );
}
}
}
diff --git a/apps/mobile-app/app/(tabs)/settings/security/active-sessions.tsx b/apps/mobile-app/app/(tabs)/settings/security/active-sessions.tsx
index d7f13b4ce..10f93d320 100644
--- a/apps/mobile-app/app/(tabs)/settings/security/active-sessions.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/security/active-sessions.tsx
@@ -1,8 +1,8 @@
import * as Haptics from 'expo-haptics';
import { useState, useEffect, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
import { StyleSheet, View, TouchableOpacity, Alert, RefreshControl, Platform } from 'react-native';
import Toast from 'react-native-toast-message';
-import { useTranslation } from 'react-i18next';
import type { RefreshToken } from '@/utils/dist/shared/models/webapi';
@@ -94,7 +94,7 @@ export default function ActiveSessionsScreen() : React.ReactNode {
} finally {
setIsLoading(false);
}
- }, [webApi, setIsLoading, setRefreshTokens]);
+ }, [webApi, setIsLoading, setRefreshTokens, t]);
/**
* Handle the revoke session action.
diff --git a/apps/mobile-app/app/(tabs)/settings/security/auth-logs.tsx b/apps/mobile-app/app/(tabs)/settings/security/auth-logs.tsx
index dfd5eabb9..e2f0ca0b0 100644
--- a/apps/mobile-app/app/(tabs)/settings/security/auth-logs.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/security/auth-logs.tsx
@@ -1,8 +1,8 @@
import * as Haptics from 'expo-haptics';
import { useState, useEffect, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
import { StyleSheet, View, RefreshControl, Platform } from 'react-native';
import Toast from 'react-native-toast-message';
-import { useTranslation } from 'react-i18next';
import type { AuthLogModel } from '@/utils/dist/shared/models/webapi';
import { AuthEventType } from '@/utils/dist/shared/models/webapi';
@@ -102,7 +102,7 @@ export default function AuthLogsScreen() : React.ReactNode {
} finally {
setIsLoading(false);
}
- }, [webApi, setIsLoading, setLogs]);
+ }, [webApi, setIsLoading, setLogs, t]);
/**
* Refresh the logs on pull to refresh.
diff --git a/apps/mobile-app/app/(tabs)/settings/security/change-password.tsx b/apps/mobile-app/app/(tabs)/settings/security/change-password.tsx
index d789456c7..a09b2b9f4 100644
--- a/apps/mobile-app/app/(tabs)/settings/security/change-password.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/security/change-password.tsx
@@ -1,7 +1,7 @@
import { router } from 'expo-router';
import { useState } from 'react';
-import { StyleSheet, View, Alert, KeyboardAvoidingView, Platform } from 'react-native';
import { useTranslation } from 'react-i18next';
+import { StyleSheet, View, Alert, KeyboardAvoidingView, Platform } from 'react-native';
import { useColors } from '@/hooks/useColorScheme';
import { useVaultMutate } from '@/hooks/useVaultMutate';
diff --git a/apps/mobile-app/app/(tabs)/settings/security/delete-account.tsx b/apps/mobile-app/app/(tabs)/settings/security/delete-account.tsx
index 5e05deddb..0e4a24dc6 100644
--- a/apps/mobile-app/app/(tabs)/settings/security/delete-account.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/security/delete-account.tsx
@@ -1,8 +1,8 @@
import { router } from 'expo-router';
import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { StyleSheet, View, Alert, KeyboardAvoidingView, Platform } from 'react-native';
import srp from 'secure-remote-password/client';
-import { useTranslation } from 'react-i18next';
import type { DeleteAccountInitiateRequest, DeleteAccountInitiateResponse, DeleteAccountRequest } from '@/utils/dist/shared/models/webapi';
diff --git a/apps/mobile-app/app/(tabs)/settings/security/index.tsx b/apps/mobile-app/app/(tabs)/settings/security/index.tsx
index 35e74726e..2f68d5132 100644
--- a/apps/mobile-app/app/(tabs)/settings/security/index.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/security/index.tsx
@@ -1,7 +1,7 @@
import { Ionicons } from '@expo/vector-icons';
import { router } from 'expo-router';
-import { StyleSheet, View, TouchableOpacity } from 'react-native';
import { useTranslation } from 'react-i18next';
+import { StyleSheet, View, TouchableOpacity } from 'react-native';
import { useColors } from '@/hooks/useColorScheme';
diff --git a/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx b/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx
index 04f8a771e..b287c9d5e 100644
--- a/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx
+++ b/apps/mobile-app/app/(tabs)/settings/vault-unlock.tsx
@@ -1,8 +1,8 @@
import * as LocalAuthentication from 'expo-local-authentication';
import { useState, useEffect, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
import { StyleSheet, View, Alert, Platform, Linking, Switch, TouchableOpacity } from 'react-native';
import Toast from 'react-native-toast-message';
-import { useTranslation } from 'react-i18next';
import { useColors } from '@/hooks/useColorScheme';
@@ -134,7 +134,7 @@ export default function VaultUnlockSettingsScreen() : React.ReactNode {
visibilityTime: 1200,
});
}
- }, [hasBiometrics, setAuthMethods, biometricDisplayName]);
+ }, [hasBiometrics, setAuthMethods, biometricDisplayName, t]);
const styles = StyleSheet.create({
disabledText: {
@@ -200,7 +200,7 @@ export default function VaultUnlockSettingsScreen() : React.ReactNode {
- {t('settings.vaultUnlockSettings.biometricHelp', {
+ {t('settings.vaultUnlockSettings.biometricHelp', {
keystore: Platform.OS === 'ios' ? t('settings.vaultUnlockSettings.keystoreIOS') : t('settings.vaultUnlockSettings.keystoreAndroid'),
biometric: biometricDisplayName
})}
diff --git a/apps/mobile-app/app/_layout.tsx b/apps/mobile-app/app/_layout.tsx
index 2a6adf20e..0f95d8811 100644
--- a/apps/mobile-app/app/_layout.tsx
+++ b/apps/mobile-app/app/_layout.tsx
@@ -41,7 +41,10 @@ function RootLayoutNav() : React.ReactNode {
const hasBooted = useRef(false);
useEffect(() => {
- const initializeApp = async () => {
+ /**
+ * Initialize the app.
+ */
+ const initializeApp = async () : Promise => {
if (hasBooted.current) {
return;
}
@@ -54,135 +57,135 @@ function RootLayoutNav() : React.ReactNode {
hasBooted.current = true;
- /**
- * Handle vault unlocking process.
- */
- async function handleVaultUnlock() : Promise {
- const { enabledAuthMethods } = await initializeAuth();
+ /**
+ * Handle vault unlocking process.
+ */
+ async function handleVaultUnlock() : Promise {
+ const { enabledAuthMethods } = await initializeAuth();
- try {
- const hasEncryptedDatabase = await NativeVaultManager.hasEncryptedDatabase();
- if (hasEncryptedDatabase) {
- const isFaceIDEnabled = enabledAuthMethods.includes('faceid');
- if (!isFaceIDEnabled) {
- setRedirectTarget('/unlock');
- setBootComplete(true);
- return;
- }
-
- setStatus('Unlocking vault');
- const isUnlocked = await dbContext.unlockVault();
- if (isUnlocked) {
- await new Promise(resolve => setTimeout(resolve, 750));
- setStatus('Decrypting vault');
- await new Promise(resolve => setTimeout(resolve, 750));
-
- // Check if the vault is up to date, if not, redirect to the upgrade page.
- if (await dbContext.hasPendingMigrations()) {
- setRedirectTarget('/upgrade');
+ try {
+ const hasEncryptedDatabase = await NativeVaultManager.hasEncryptedDatabase();
+ if (hasEncryptedDatabase) {
+ const isFaceIDEnabled = enabledAuthMethods.includes('faceid');
+ if (!isFaceIDEnabled) {
+ setRedirectTarget('/unlock');
setBootComplete(true);
return;
}
+ setStatus('Unlocking vault');
+ const isUnlocked = await dbContext.unlockVault();
+ if (isUnlocked) {
+ await new Promise(resolve => setTimeout(resolve, 750));
+ setStatus('Decrypting vault');
+ await new Promise(resolve => setTimeout(resolve, 750));
+
+ // Check if the vault is up to date, if not, redirect to the upgrade page.
+ if (await dbContext.hasPendingMigrations()) {
+ setRedirectTarget('/upgrade');
+ setBootComplete(true);
+ return;
+ }
+
+ setBootComplete(true);
+ return;
+ }
+
+ setRedirectTarget('/unlock');
+ setBootComplete(true);
+ return;
+ } else {
+ setRedirectTarget('/unlock');
setBootComplete(true);
return;
}
-
- setRedirectTarget('/unlock');
- setBootComplete(true);
- return;
- } else {
+ } catch {
setRedirectTarget('/unlock');
setBootComplete(true);
return;
}
- } catch {
- setRedirectTarget('/unlock');
- setBootComplete(true);
- return;
- }
- }
-
- /**
- * Initialize the app.
- */
- const initialize = async () : Promise => {
- const { isLoggedIn } = await initializeAuth();
-
- if (!isLoggedIn) {
- setRedirectTarget('/login');
- setBootComplete(true);
- return;
}
- // First perform vault sync
- await syncVault({
- initialSync: true,
- /**
- * Handle the status update.
- */
- onStatus: (message) => {
- setStatus(message);
- },
- /**
- * Handle successful vault sync and continue with vault unlock flow.
- */
- onSuccess: async () => {
- // Continue with the rest of the flow after successful sync
- handleVaultUnlock();
- },
- /**
- * Handle offline state and prompt user for action.
- */
- onOffline: async () => {
- Alert.alert(
- 'Sync Issue',
- 'The AliasVault server could not be reached and your vault could not be synced. Would you like to open your local vault in read-only mode or retry the connection?',
- [
- {
- text: 'Open Local Vault',
- /**
- * Handle opening vault in read-only mode.
- */
- onPress: async () : Promise => {
- setStatus('Opening vault in read-only mode');
- await handleVaultUnlock();
- }
- },
- {
- text: 'Retry Sync',
- /**
- * Handle retrying the connection.
- */
- onPress: () : void => {
- setStatus('Retrying connection...');
- initialize();
- }
- }
- ]
- );
- },
- /**
- * Handle error during vault sync.
- */
- onError: async (error: string) => {
- // Show modal with error message
- Alert.alert('Error', error);
+ /**
+ * Initialize the app.
+ */
+ const initialize = async () : Promise => {
+ const { isLoggedIn } = await initializeAuth();
- // The logout user and navigate to the login screen.
- await webApi.logout(error);
+ if (!isLoggedIn) {
setRedirectTarget('/login');
setBootComplete(true);
- },
- /**
- * On upgrade required.
- */
- onUpgradeRequired: () : void => {
- setRedirectTarget('/upgrade');
- setBootComplete(true);
- },
- });
- };
+ return;
+ }
+
+ // First perform vault sync
+ await syncVault({
+ initialSync: true,
+ /**
+ * Handle the status update.
+ */
+ onStatus: (message) => {
+ setStatus(message);
+ },
+ /**
+ * Handle successful vault sync and continue with vault unlock flow.
+ */
+ onSuccess: async () => {
+ // Continue with the rest of the flow after successful sync
+ handleVaultUnlock();
+ },
+ /**
+ * Handle offline state and prompt user for action.
+ */
+ onOffline: async () => {
+ Alert.alert(
+ 'Sync Issue',
+ 'The AliasVault server could not be reached and your vault could not be synced. Would you like to open your local vault in read-only mode or retry the connection?',
+ [
+ {
+ text: 'Open Local Vault',
+ /**
+ * Handle opening vault in read-only mode.
+ */
+ onPress: async () : Promise => {
+ setStatus('Opening vault in read-only mode');
+ await handleVaultUnlock();
+ }
+ },
+ {
+ text: 'Retry Sync',
+ /**
+ * Handle retrying the connection.
+ */
+ onPress: () : void => {
+ setStatus('Retrying connection...');
+ initialize();
+ }
+ }
+ ]
+ );
+ },
+ /**
+ * Handle error during vault sync.
+ */
+ onError: async (error: string) => {
+ // Show modal with error message
+ Alert.alert('Error', error);
+
+ // The logout user and navigate to the login screen.
+ await webApi.logout(error);
+ setRedirectTarget('/login');
+ setBootComplete(true);
+ },
+ /**
+ * On upgrade required.
+ */
+ onUpgradeRequired: () : void => {
+ setRedirectTarget('/upgrade');
+ setBootComplete(true);
+ },
+ });
+ };
initialize();
};
diff --git a/apps/mobile-app/components/credentials/details/AliasDetails.tsx b/apps/mobile-app/components/credentials/details/AliasDetails.tsx
index 94dd9e09e..6768cd932 100644
--- a/apps/mobile-app/components/credentials/details/AliasDetails.tsx
+++ b/apps/mobile-app/components/credentials/details/AliasDetails.tsx
@@ -1,6 +1,7 @@
+import { useTranslation } from 'react-i18next';
+
import { IdentityHelperUtils } from '@/utils/dist/shared/identity-generator';
import type { Credential } from '@/utils/dist/shared/models/vault';
-import { useTranslation } from 'react-i18next';
import FormInputCopyToClipboard from '@/components/form/FormInputCopyToClipboard';
import { ThemedText } from '@/components/themed/ThemedText';
diff --git a/apps/mobile-app/components/credentials/details/EmailPreview.tsx b/apps/mobile-app/components/credentials/details/EmailPreview.tsx
index 8d88aa7c8..72ccdcf12 100644
--- a/apps/mobile-app/components/credentials/details/EmailPreview.tsx
+++ b/apps/mobile-app/components/credentials/details/EmailPreview.tsx
@@ -1,8 +1,8 @@
import { useFocusEffect } from '@react-navigation/native';
import { router } from 'expo-router';
import React, { useState, useEffect, useCallback } from 'react';
-import { View, StyleSheet, TouchableOpacity, Linking, AppState } from 'react-native';
import { useTranslation } from 'react-i18next';
+import { View, StyleSheet, TouchableOpacity, Linking, AppState } from 'react-native';
import { AppInfo } from '@/utils/AppInfo';
import type { ApiErrorResponse, MailboxEmail } from '@/utils/dist/shared/models/webapi';
@@ -210,7 +210,7 @@ export const EmailPreview: React.FC = ({ email }) : React.Rea
clearInterval(interval);
}
};
- }, [email, loading, webApi, dbContext, isPublicDomain, isPrivateDomain, authContext.isOffline, isComponentVisible]);
+ }, [email, loading, webApi, dbContext, isPublicDomain, isPrivateDomain, authContext.isOffline, isComponentVisible, t]);
const styles = StyleSheet.create({
date: {
diff --git a/apps/mobile-app/components/credentials/details/LoginCredentials.tsx b/apps/mobile-app/components/credentials/details/LoginCredentials.tsx
index 029005eee..99a6f544a 100644
--- a/apps/mobile-app/components/credentials/details/LoginCredentials.tsx
+++ b/apps/mobile-app/components/credentials/details/LoginCredentials.tsx
@@ -1,6 +1,7 @@
-import type { Credential } from '@/utils/dist/shared/models/vault';
import { useTranslation } from 'react-i18next';
+import type { Credential } from '@/utils/dist/shared/models/vault';
+
import FormInputCopyToClipboard from '@/components/form/FormInputCopyToClipboard';
import { ThemedText } from '@/components/themed/ThemedText';
import { ThemedView } from '@/components/themed/ThemedView';
diff --git a/apps/mobile-app/components/credentials/details/NotesSection.tsx b/apps/mobile-app/components/credentials/details/NotesSection.tsx
index ff1873984..ec1fe8c7c 100644
--- a/apps/mobile-app/components/credentials/details/NotesSection.tsx
+++ b/apps/mobile-app/components/credentials/details/NotesSection.tsx
@@ -1,5 +1,5 @@
-import { View, Text, StyleSheet, Linking, Pressable } from 'react-native';
import { useTranslation } from 'react-i18next';
+import { View, Text, StyleSheet, Linking, Pressable } from 'react-native';
import type { Credential } from '@/utils/dist/shared/models/vault';
diff --git a/apps/mobile-app/components/credentials/details/TotpSection.tsx b/apps/mobile-app/components/credentials/details/TotpSection.tsx
index 5f2cb14e7..1f1f36ae0 100644
--- a/apps/mobile-app/components/credentials/details/TotpSection.tsx
+++ b/apps/mobile-app/components/credentials/details/TotpSection.tsx
@@ -1,9 +1,9 @@
import * as Clipboard from 'expo-clipboard';
import * as OTPAuth from 'otpauth';
import React, { useState, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
import { View, StyleSheet, TouchableOpacity, Platform } from 'react-native';
import Toast from 'react-native-toast-message';
-import { useTranslation } from 'react-i18next';
import type { Credential, TotpCode } from '@/utils/dist/shared/models/vault';
diff --git a/apps/mobile-app/i18n/index.ts b/apps/mobile-app/i18n/index.ts
index db841cb52..95e98e9dc 100644
--- a/apps/mobile-app/i18n/index.ts
+++ b/apps/mobile-app/i18n/index.ts
@@ -19,10 +19,10 @@ const initI18n = async (): Promise => {
const deviceLanguage = locales[0]?.languageCode ?? 'en';
const selectedLanguage = resources[deviceLanguage as keyof typeof resources] ? deviceLanguage : 'en';
+ // eslint-disable-next-line import/no-named-as-default-member
await i18n
.use(initReactI18next)
.init({
- compatibilityJSON: 'v3',
resources,
lng: selectedLanguage,
fallbackLng: 'en',
diff --git a/apps/mobile-app/i18n/locales/en.json b/apps/mobile-app/i18n/locales/en.json
index 8e039488e..1274c2a46 100644
--- a/apps/mobile-app/i18n/locales/en.json
+++ b/apps/mobile-app/i18n/locales/en.json
@@ -142,8 +142,9 @@
},
"language": "Language",
"systemLanguage": "System Language",
- "languageSystemMessage": "To change the app language, we'll open iOS Settings where you can configure the preferred language for AliasVault.",
+ "languageSystemMessage": "To change the app language, configure the preferred language for AliasVault in your device settings.",
"openSettings": "Open Settings",
+ "unableToOpenSettings": "Unable to open device settings. Please manually navigate to the app settings and change the language.",
"vaultUnlockSettings": {
"title": "Vault Unlock Method",
"description": "Choose how you want to unlock your vault.",
diff --git a/apps/mobile-app/i18n/locales/nl.json b/apps/mobile-app/i18n/locales/nl.json
index 88f449451..f090eabdb 100644
--- a/apps/mobile-app/i18n/locales/nl.json
+++ b/apps/mobile-app/i18n/locales/nl.json
@@ -122,8 +122,8 @@
},
"settings": {
"title": "Instellingen",
- "iosAutofill": "iOS Automatisch Invullen",
- "androidAutofill": "Android Automatisch Invullen",
+ "iosAutofill": "iOS autofill",
+ "androidAutofill": "Android autofill",
"vaultUnlock": "Kluis Ontgrendel Methode",
"autoLock": "Automatisch Vergrendelen Timeout",
"identityGenerator": "Identiteit Generator",
@@ -142,8 +142,9 @@
},
"language": "Taal",
"systemLanguage": "Systeemtaal",
- "languageSystemMessage": "Om de app-taal te wijzigen, openen we iOS Instellingen waar u de gewenste taal voor AliasVault kunt configureren.",
+ "languageSystemMessage": "Wijzig de app-taal via het app instellingen scherm op je apparaat.",
"openSettings": "Open Instellingen",
+ "unableToOpenSettings": "Het openen van de instellingen is niet gelukt. Ga naar de app-instellingen en wijzig de taal.",
"vaultUnlockSettings": {
"title": "Kluis Ontgrendel Methode",
"description": "Kies hoe u uw kluis wilt ontgrendelen.",
diff --git a/apps/mobile-app/ios/AliasVault/nl.lproj/Localizable.strings b/apps/mobile-app/ios/AliasVault/nl.lproj/Localizable.strings
index 528e43570..17588a8e3 100644
--- a/apps/mobile-app/ios/AliasVault/nl.lproj/Localizable.strings
+++ b/apps/mobile-app/ios/AliasVault/nl.lproj/Localizable.strings
@@ -3,10 +3,10 @@
"loading_error_message" = "Het laden van referenties ging mis. Open de AliasVault app om te controleren op updates.";
"ok" = "OK";
"login_required" = "Inloggen Vereist";
-"login_required_message" = "Om Automatisch Invullen te gebruiken, log eerst in op uw AliasVault account in de AliasVault app.";
+"login_required_message" = "Om autofill te gebruiken, log eerst in op uw AliasVault account in de AliasVault app.";
"biometric_required" = "%@ Vereist";
-"biometric_required_message" = "Om AliasVault Automatisch Invullen te gebruiken, schakel %@ in bij uw apparaat instellingen en/of ga naar de AliasVault app instellingen om het te configureren.";
-"biometric_app_required_message" = "Om Automatisch Invullen te gebruiken, schakel %@ in als uw kluis ontgrendel methode in de AliasVault app instellingen.";
+"biometric_required_message" = "Om autofill te gebruiken, schakel %@ in bij uw apparaat instellingen en/of ga naar de AliasVault app instellingen om het te configureren.";
+"biometric_app_required_message" = "Om autofill te gebruiken, schakel %@ in als uw kluis ontgrendel methode in de AliasVault app instellingen.";
"loading_credentials" = "Referenties laden...";
"no_credentials_found" = "Geen referenties gevonden";
"no_credentials_match" = "Geen bestaande referenties komen overeen met uw zoekopdracht";
diff --git a/apps/mobile-app/ios/Autofill/nl.lproj/Localizable.strings b/apps/mobile-app/ios/Autofill/nl.lproj/Localizable.strings
index 110b14aac..493432444 100644
--- a/apps/mobile-app/ios/Autofill/nl.lproj/Localizable.strings
+++ b/apps/mobile-app/ios/Autofill/nl.lproj/Localizable.strings
@@ -3,7 +3,7 @@
"loading_error_message" = "Het laden van referenties ging mis. Open de AliasVault app om te controleren op updates.";
"ok" = "OK";
"login_required" = "Inloggen Vereist";
-"login_required_message" = "Om Automatisch Invullen te gebruiken, log eerst in op uw AliasVault account in de AliasVault app.";
+"login_required_message" = "Om autofill te gebruiken, log eerst in op uw AliasVault account in de AliasVault app.";
"biometric_required" = "%@ Vereist";
"biometric_required_message" = "Om AliasVault Automatisch Invullen te gebruiken, schakel %@ in bij uw apparaat instellingen en/of ga naar de AliasVault app instellingen om het te configureren.";
"biometric_app_required_message" = "Om Automatisch Invullen te gebruiken, schakel %@ in als uw kluis ontgrendel methode in de AliasVault app instellingen.";