Update mobile app language setting configure for mobile app (#993)

This commit is contained in:
Leendert de Borst
2025-07-11 11:46:03 +02:00
committed by Leendert de Borst
parent ca9b9e465c
commit cb5cd1006c
28 changed files with 202 additions and 171 deletions

View File

@@ -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",

View File

@@ -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 ",

View File

@@ -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'
};

View File

@@ -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';

View File

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

View File

@@ -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 {
</TouchableOpacity>
),
});
}, [navigation, colors.primary, styles.headerRightButton, handleStayInApp]);
}, [navigation, colors.primary, styles.headerRightButton, handleStayInApp, t]);
return (
<ThemedSafeAreaView style={styles.container}>
@@ -108,7 +108,7 @@ export default function AutofillCredentialCreatedScreen() : React.ReactNode {
{t('credentials.credentialCreatedMessage')}
</ThemedText>
<ThemedText style={[styles.message, styles.boldMessage]}>
{t('credentials.switchBackToBrowser')}
{t('credentials.switchBackToBrowser')}
</ThemedText>
</ThemedView>
</ThemedSafeAreaView>

View File

@@ -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' ? <AndroidHeader title={t('credentials.title')} /> : <Text>{t('credentials.title')}</Text>,
});
}, [navigation]);
}, [navigation, t]);
/**
* Delete a credential.

View File

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

View File

@@ -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';

View File

@@ -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: {

View File

@@ -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<void> => {
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.'
);
}
}
}

View File

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

View File

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

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {
</View>
</View>
<ThemedText style={styles.helpText}>
{t('settings.vaultUnlockSettings.biometricHelp', {
{t('settings.vaultUnlockSettings.biometricHelp', {
keystore: Platform.OS === 'ios' ? t('settings.vaultUnlockSettings.keystoreIOS') : t('settings.vaultUnlockSettings.keystoreAndroid'),
biometric: biometricDisplayName
})}

View File

@@ -41,7 +41,10 @@ function RootLayoutNav() : React.ReactNode {
const hasBooted = useRef(false);
useEffect(() => {
const initializeApp = async () => {
/**
* Initialize the app.
*/
const initializeApp = async () : Promise<void> => {
if (hasBooted.current) {
return;
}
@@ -54,135 +57,135 @@ function RootLayoutNav() : React.ReactNode {
hasBooted.current = true;
/**
* Handle vault unlocking process.
*/
async function handleVaultUnlock() : Promise<void> {
const { enabledAuthMethods } = await initializeAuth();
/**
* Handle vault unlocking process.
*/
async function handleVaultUnlock() : Promise<void> {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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();
};

View File

@@ -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';

View File

@@ -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<EmailPreviewProps> = ({ 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: {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -19,10 +19,10 @@ const initI18n = async (): Promise<void> => {
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',

View File

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

View File

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

View File

@@ -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";

View File

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