From cb5cd1006c91d1ace2fe2792bcae0b2b3acf5867 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Fri, 11 Jul 2025 11:46:03 +0200 Subject: [PATCH] Update mobile app language setting configure for mobile app (#993) --- .../src/locales/nl/credentials.json | 4 +- .../src/locales/nl/settings.json | 4 +- .../src/utils/contentTranslations.ts | 2 +- apps/mobile-app/app/(tabs)/_layout.tsx | 2 +- .../app/(tabs)/credentials/add-edit.tsx | 10 +- .../autofill-credential-created.tsx | 6 +- .../app/(tabs)/credentials/index.tsx | 8 +- apps/mobile-app/app/(tabs)/emails/[id].tsx | 6 +- .../app/(tabs)/settings/auto-lock.tsx | 2 +- .../(tabs)/settings/identity-generator.tsx | 8 +- apps/mobile-app/app/(tabs)/settings/index.tsx | 38 ++- .../settings/security/active-sessions.tsx | 4 +- .../(tabs)/settings/security/auth-logs.tsx | 4 +- .../settings/security/change-password.tsx | 2 +- .../settings/security/delete-account.tsx | 2 +- .../app/(tabs)/settings/security/index.tsx | 2 +- .../app/(tabs)/settings/vault-unlock.tsx | 6 +- apps/mobile-app/app/_layout.tsx | 229 +++++++++--------- .../credentials/details/AliasDetails.tsx | 3 +- .../credentials/details/EmailPreview.tsx | 4 +- .../credentials/details/LoginCredentials.tsx | 3 +- .../credentials/details/NotesSection.tsx | 2 +- .../credentials/details/TotpSection.tsx | 2 +- apps/mobile-app/i18n/index.ts | 2 +- apps/mobile-app/i18n/locales/en.json | 3 +- apps/mobile-app/i18n/locales/nl.json | 7 +- .../AliasVault/nl.lproj/Localizable.strings | 6 +- .../ios/Autofill/nl.lproj/Localizable.strings | 2 +- 28 files changed, 202 insertions(+), 171 deletions(-) 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.";