Update browser extension and app translations

This commit is contained in:
Leendert de Borst
2025-07-29 15:48:13 +02:00
parent 3fbfca6163
commit df6bcff8b3
10 changed files with 188 additions and 94 deletions

View File

@@ -27,7 +27,7 @@ const DEFAULT_OPTIONS: ApiOption[] = [
const createUrlSchema = (t: (key: string) => string): Yup.ObjectSchema<{apiUrl: string; clientUrl: string}> => Yup.object().shape({
apiUrl: Yup.string()
.required(t('validation.apiUrlRequired'))
.test('is-valid-api-url', t('validation.apiUrlInvalid'), (value: string | undefined) => {
.test('is-valid-api-url', t('settings.validation.apiUrlInvalid'), (value: string | undefined) => {
if (!value) {
return true; // Allow empty for non-custom option
}
@@ -40,7 +40,7 @@ const createUrlSchema = (t: (key: string) => string): Yup.ObjectSchema<{apiUrl:
}),
clientUrl: Yup.string()
.required(t('validation.clientUrlRequired'))
.test('is-valid-client-url', t('validation.clientUrlInvalid'), (value: string | undefined) => {
.test('is-valid-client-url', t('settings.validation.clientUrlInvalid'), (value: string | undefined) => {
if (!value) {
return true; // Allow empty for non-custom option
}

View File

@@ -111,17 +111,17 @@ const EmailsList: React.FC = () => {
// Less than 1 hour ago
const minutes = Math.floor(secondsAgo / 60);
if (minutes === 1) {
return t('emails.time.minutesAgo_single', { count: minutes });
return t('emails.dateFormat.minutesAgo_single', { count: minutes });
} else {
return t('emails.time.minutesAgo_plural', { count: minutes });
return t('emails.dateFormat.minutesAgo_plural', { count: minutes });
}
} else if (secondsAgo < 86400) {
// Less than 24 hours ago
const hours = Math.floor(secondsAgo / 3600);
if (hours === 1) {
return t('emails.time.hoursAgo_single', { count: hours });
return t('emails.dateFormat.hoursAgo_single', { count: hours });
} else {
return t('emails.time.hoursAgo_plural', { count: hours });
return t('emails.dateFormat.hoursAgo_plural', { count: hours });
}
} else if (secondsAgo < 172800) {
// Less than 48 hours ago

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import Button from '@/entrypoints/popup/components/Button';
@@ -22,6 +23,7 @@ import { VaultSqlGenerator } from '@/utils/dist/shared/vault-sql';
* Upgrade page for handling vault version upgrades.
*/
const Upgrade: React.FC = () => {
const { t } = useTranslation();
const { username } = useAuth();
const dbContext = useDb();
const { sqliteClient } = dbContext;
@@ -44,7 +46,7 @@ const Upgrade: React.FC = () => {
<>
<HeaderButton
onClick={() => PopoutUtility.openInNewPopup()}
title="Open in new window"
title={t('common.openInNewWindow')}
iconType={HeaderIconType.EXPAND}
/>
</>
@@ -55,7 +57,7 @@ const Upgrade: React.FC = () => {
return () => {
setHeaderButtons(null);
};
}, [setHeaderButtons]);
}, [setHeaderButtons, t]);
/**
* Load version information from the database.
@@ -71,9 +73,9 @@ const Upgrade: React.FC = () => {
setIsInitialLoading(false);
} catch (error) {
console.error('Failed to load version information:', error);
setError('Failed to load version information. Please try again.');
setError(t('upgrade.alerts.unableToGetVersionInfo'));
}
}, [sqliteClient, setIsInitialLoading]);
}, [sqliteClient, setIsInitialLoading, t]);
useEffect(() => {
loadVersionInfo();
@@ -84,7 +86,7 @@ const Upgrade: React.FC = () => {
*/
const handleUpgrade = async (): Promise<void> => {
if (!sqliteClient || !currentVersion || !latestVersion) {
setError('Unable to get version information. Please try again.');
setError(t('upgrade.alerts.unableToGetVersionInfo'));
return;
}
@@ -102,7 +104,7 @@ const Upgrade: React.FC = () => {
*/
const performUpgrade = async (): Promise<void> => {
if (!sqliteClient || !currentVersion || !latestVersion) {
setError('Unable to get version information. Please try again.');
setError(t('upgrade.alerts.unableToGetVersionInfo'));
return;
}
@@ -115,7 +117,7 @@ const Upgrade: React.FC = () => {
const upgradeResult = vaultSqlGenerator.getUpgradeVaultSql(currentVersion.revision, latestVersion.revision);
if (!upgradeResult.success) {
throw new Error(upgradeResult.error ?? 'Failed to generate upgrade SQL');
throw new Error(upgradeResult.error ?? t('upgrade.alerts.upgradeFailed'));
}
if (upgradeResult.sqlCommands.length === 0) {
@@ -125,30 +127,24 @@ const Upgrade: React.FC = () => {
}
// Use the useVaultMutate hook to handle the upgrade and vault upload
console.debug('executeVaultMutation');
await executeVaultMutation(async () => {
// Begin transaction
console.debug('beginTransaction');
sqliteClient.beginTransaction();
// Execute each SQL command
console.debug('executeRaw', upgradeResult.sqlCommands.length);
for (let i = 0; i < upgradeResult.sqlCommands.length; i++) {
const sqlCommand = upgradeResult.sqlCommands[i];
try {
console.debug('executeRaw', sqlCommand);
sqliteClient.executeRaw(sqlCommand);
} catch (error) {
console.debug('error', error);
console.error(`Error executing SQL command ${i + 1}:`, sqlCommand, error);
sqliteClient.rollbackTransaction();
throw new Error(`Failed to apply migration ${i + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`);
throw new Error(t('upgrade.alerts.failedToApplyMigration', { current: i + 1, total: upgradeResult.sqlCommands.length }));
}
}
// Commit transaction
console.debug('commitTransaction');
sqliteClient.commitTransaction();
}, {
skipSyncCheck: true, // Skip sync check during upgrade to prevent loop
@@ -156,14 +152,12 @@ const Upgrade: React.FC = () => {
* Handle successful upgrade completion.
*/
onSuccess: () => {
console.debug('onSuccess');
void handleUpgradeSuccess();
},
/**
* Handle upgrade error.
*/
onError: (error: Error) => {
console.debug('onError');
console.error('Upgrade failed:', error);
setError(error.message);
}
@@ -171,7 +165,7 @@ const Upgrade: React.FC = () => {
console.debug('executeVaultMutation done?');
} catch (error) {
console.error('Upgrade failed:', error);
setError(error instanceof Error ? error.message : 'An unknown error occurred during the upgrade. Please try again.');
setError(error instanceof Error ? error.message : t('upgrade.alerts.unknownErrorDuringUpgrade'));
} finally {
setIsLoading(false);
}
@@ -229,7 +223,7 @@ const Upgrade: React.FC = () => {
<div className="fixed inset-0 flex flex-col justify-center items-center bg-white dark:bg-gray-900 bg-opacity-90 dark:bg-opacity-90 z-50">
<LoadingSpinner />
<div className="text-sm text-gray-500 mt-2">
{syncStatus || 'Upgrading vault...'}
{syncStatus || t('upgrade.upgrading')}
</div>
</div>
)}
@@ -242,10 +236,10 @@ const Upgrade: React.FC = () => {
setShowSelfHostedWarning(false);
void performUpgrade();
}}
title="Self-Hosted Server"
message="If you're using a self-hosted server, make sure to also update your self-hosted instance as otherwise logging in to the web client will stop working. Do you want to continue with the upgrade?"
confirmText="Continue"
cancelText="Cancel"
title={t('upgrade.alerts.selfHostedServer')}
message={t('upgrade.alerts.selfHostedWarning')}
confirmText={t('upgrade.alerts.continueUpgrade')}
cancelText={t('upgrade.alerts.cancel')}
/>
{/* Version info modal */}
@@ -253,8 +247,8 @@ const Upgrade: React.FC = () => {
isOpen={showVersionInfo}
onClose={() => setShowVersionInfo(false)}
onConfirm={() => setShowVersionInfo(false)}
title="What's New"
message={`An upgrade is required to support the following changes:\n\n${latestVersion?.description ?? 'No description available for this version.'}`}
title={t('upgrade.whatsNew')}
message={`${t('upgrade.whatsNewDescription')}\n\n${latestVersion?.description ?? t('upgrade.noDescriptionAvailable')}`}
/>
<form className="w-full px-2 pt-2 pb-2 mb-4">
@@ -280,33 +274,33 @@ const Upgrade: React.FC = () => {
</div>
</div>
<h2 className="text-xl font-bold dark:text-gray-200 mb-4">Upgrade Vault</h2>
<h2 className="text-xl font-bold dark:text-gray-200 mb-4">{t('upgrade.title')}</h2>
<div className="mb-6">
<p className="text-gray-700 dark:text-gray-200 text-sm mb-4">
AliasVault has updated and your vault needs to be upgraded. This should only take a few seconds.
{t('upgrade.subtitle')}
</p>
<div className="bg-gray-50 dark:bg-gray-800 rounded p-4 mb-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">Version Information</span>
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">{t('upgrade.versionInformation')}</span>
<button
type="button"
onClick={showVersionDialog}
className="bg-gray-200 dark:bg-gray-600 text-gray-800 dark:text-gray-200 rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold hover:bg-gray-300 dark:hover:bg-gray-500"
title="Show version details"
title={t('upgrade.whatsNew')}
>
?
</button>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">Your vault:</span>
<span className="text-sm text-gray-600 dark:text-gray-400">{t('upgrade.yourVault')}</span>
<span className="text-sm font-bold text-orange-600 dark:text-orange-400">
{currentVersion?.releaseVersion ?? '...'}
</span>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-600 dark:text-gray-400">New version:</span>
<span className="text-sm text-gray-600 dark:text-gray-400">{t('upgrade.newVersion')}</span>
<span className="text-sm font-bold text-green-600 dark:text-green-400">
{latestVersion?.releaseVersion ?? '...'}
</span>
@@ -320,7 +314,7 @@ const Upgrade: React.FC = () => {
type="button"
onClick={handleUpgrade}
>
{isLoading || isVaultMutationLoading ? (syncStatus || 'Upgrading...') : 'Upgrade Vault'}
{isLoading || isVaultMutationLoading ? (syncStatus || t('upgrade.upgrading')) : t('upgrade.upgrade')}
</Button>
<button
type="button"
@@ -328,7 +322,7 @@ const Upgrade: React.FC = () => {
className="text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 text-sm font-medium py-2"
disabled={isLoading || isVaultMutationLoading}
>
Logout
{t('upgrade.logout')}
</button>
</div>
</form>

View File

@@ -338,5 +338,38 @@
"clientUrlRequired": "Client URL is required",
"clientUrlInvalid": "Please enter a valid client URL"
}
},
"upgrade": {
"title": "Upgrade Vault",
"subtitle": "AliasVault has updated and your vault needs to be upgraded. This should only take a few seconds.",
"versionInformation": "Version Information",
"yourVault": "Your vault:",
"newVersion": "New version:",
"upgrade": "Upgrade Vault",
"upgrading": "Upgrading...",
"logout": "Logout",
"whatsNew": "What's New",
"whatsNewDescription": "An upgrade is required to support the following changes:",
"noDescriptionAvailable": "No description available for this version.",
"okay": "Ok",
"status": {
"preparingUpgrade": "Preparing upgrade...",
"vaultAlreadyUpToDate": "Vault is already up to date",
"startingDatabaseTransaction": "Starting database transaction...",
"applyingDatabaseMigrations": "Applying database migrations...",
"applyingMigration": "Applying migration {{current}} of {{total}}...",
"committingChanges": "Committing changes..."
},
"alerts": {
"error": "Error",
"unableToGetVersionInfo": "Unable to get version information. Please try again.",
"selfHostedServer": "Self-Hosted Server",
"selfHostedWarning": "If you're using a self-hosted server, make sure to also update your self-hosted instance as otherwise logging in to the web client will stop working.",
"cancel": "Cancel",
"continueUpgrade": "Continue Upgrade",
"upgradeFailed": "Upgrade Failed",
"failedToApplyMigration": "Failed to apply migration ({{current}} of {{total}})",
"unknownErrorDuringUpgrade": "An unknown error occurred during the upgrade. Please try again."
}
}
}

View File

@@ -1,4 +1,5 @@
import { Stack } from 'expo-router';
import { useTranslation } from 'react-i18next';
import { Platform, Text } from 'react-native';
import { defaultHeaderOptions } from '@/components/themed/ThemedHeader';
@@ -8,12 +9,13 @@ import { AndroidHeader } from '@/components/ui/AndroidHeader';
* Emails layout.
*/
export default function EmailsLayout(): React.ReactNode {
const { t } = useTranslation();
return (
<Stack>
<Stack.Screen
name="index"
options={{
title: 'Emails',
title: t('emails.title'),
headerShown: Platform.OS === 'android',
/**
* On Android, we use a custom header component that includes the AliasVault logo.
@@ -27,7 +29,7 @@ export default function EmailsLayout(): React.ReactNode {
<Stack.Screen
name="[id]"
options={{
title: 'Email',
title: t('emails.title'),
...defaultHeaderOptions,
headerTransparent: false,
}}

View File

@@ -3,6 +3,7 @@ import { useFonts } from 'expo-font';
import { Href, Stack, useRouter } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Linking, StyleSheet, Alert, Platform } from 'react-native';
import 'react-native-reanimated';
import 'react-native-get-random-values';
@@ -35,6 +36,7 @@ function RootLayoutNav() : React.ReactNode {
const { syncVault } = useVaultSync();
const dbContext = useDb();
const webApi = useWebApi();
const { t } = useTranslation();
const [bootComplete, setBootComplete] = useState(false);
const [redirectTarget, setRedirectTarget] = useState<string | null>(null);
@@ -73,11 +75,11 @@ function RootLayoutNav() : React.ReactNode {
return;
}
setStatus('Unlocking vault');
setStatus(t('app.status.unlockingVault'));
const isUnlocked = await dbContext.unlockVault();
if (isUnlocked) {
await new Promise(resolve => setTimeout(resolve, 750));
setStatus('Decrypting vault');
setStatus(t('app.status.decryptingVault'));
await new Promise(resolve => setTimeout(resolve, 750));
// Check if the vault is up to date, if not, redirect to the upgrade page.
@@ -139,26 +141,26 @@ function RootLayoutNav() : React.ReactNode {
*/
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?',
t('app.alerts.syncIssue'),
t('app.alerts.syncIssueMessage'),
[
{
text: 'Open Local Vault',
text: t('app.alerts.openLocalVault'),
/**
* Handle opening vault in read-only mode.
*/
onPress: async () : Promise<void> => {
setStatus('Opening vault in read-only mode');
setStatus(t('app.status.openingVaultReadOnly'));
await handleVaultUnlock();
}
},
{
text: 'Retry Sync',
text: t('app.alerts.retrySync'),
/**
* Handle retrying the connection.
*/
onPress: () : void => {
setStatus('Retrying connection...');
setStatus(t('app.status.retryingConnection'));
initialize();
}
}
@@ -170,7 +172,7 @@ function RootLayoutNav() : React.ReactNode {
*/
onError: async (error: string) => {
// Show modal with error message
Alert.alert('Error', error);
Alert.alert(t('app.alerts.error'), error);
// The logout user and navigate to the login screen.
await webApi.logout(error);
@@ -191,7 +193,7 @@ function RootLayoutNav() : React.ReactNode {
};
initializeApp();
}, [dbContext, syncVault, initializeAuth, webApi]);
}, [dbContext, syncVault, initializeAuth, webApi, t]);
useEffect(() => {
/**
@@ -284,13 +286,13 @@ function RootLayoutNav() : React.ReactNode {
}}
>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="login" options={{ title: 'Login', headerShown: false }} />
<Stack.Screen name="login" options={{ title: t('app.navigation.login'), headerShown: false }} />
<Stack.Screen name="login-settings" options={{ title: 'Login Settings' }} />
<Stack.Screen name="reinitialize" options={{ headerShown: false }} />
<Stack.Screen name="unlock" options={{ headerShown: false }} />
<Stack.Screen name="upgrade" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" options={{ title: 'Not Found' }} />
<Stack.Screen name="+not-found" options={{ title: t('app.navigation.notFound') }} />
</Stack>
<AliasVaultToast />
</ThemeProvider>

View File

@@ -1,5 +1,6 @@
import { Href, router } from 'expo-router';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet, View, Alert } from 'react-native';
import { useColors } from '@/hooks/useColorScheme';
@@ -23,6 +24,7 @@ export default function ReinitializeScreen() : React.ReactNode {
const [status, setStatus] = useState('');
const hasInitialized = useRef(false);
const colors = useColors();
const { t } = useTranslation();
useEffect(() => {
if (hasInitialized.current) {
@@ -89,11 +91,11 @@ export default function ReinitializeScreen() : React.ReactNode {
return;
}
setStatus('Unlocking vault');
setStatus(t('app.status.unlockingVault'));
const isUnlocked = await dbContext.unlockVault();
if (isUnlocked) {
await new Promise(resolve => setTimeout(resolve, 1000));
setStatus('Decrypting vault');
setStatus(t('app.status.decryptingVault'));
await new Promise(resolve => setTimeout(resolve, 1000));
// Check if the vault is up to date, if not, redirect to the upgrade page.
@@ -151,26 +153,26 @@ export default function ReinitializeScreen() : React.ReactNode {
*/
onOffline: () => {
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?',
t('app.alerts.syncIssue'),
t('app.alerts.syncIssueMessage'),
[
{
text: 'Open Local Vault',
text: t('app.alerts.openLocalVault'),
/**
* Handle opening vault in read-only mode.
*/
onPress: async () : Promise<void> => {
setStatus('Opening vault in read-only mode');
setStatus(t('app.status.openingVaultReadOnly'));
await handleVaultUnlock();
}
},
{
text: 'Retry Sync',
text: t('app.alerts.retrySync'),
/**
* Handle retrying the connection.
*/
onPress: () : void => {
setStatus('Retrying connection...');
setStatus(t('app.status.retryingConnection'));
initialize();
}
}
@@ -187,7 +189,7 @@ export default function ReinitializeScreen() : React.ReactNode {
};
initialize();
}, [syncVault, authContext, dbContext]);
}, [syncVault, authContext, dbContext, t]);
const styles = StyleSheet.create({
container: {
@@ -212,8 +214,8 @@ export default function ReinitializeScreen() : React.ReactNode {
return (
<ThemedView style={styles.container}>
<View style={styles.messageContainer}>
<ThemedText style={styles.message1}>Vault auto-locked after timeout.</ThemedText>
<ThemedText style={styles.message2}>Attempting to unlock.</ThemedText>
<ThemedText style={styles.message1}>{t('app.reinitialize.vaultAutoLockedMessage')}</ThemedText>
<ThemedText style={styles.message2}>{t('app.reinitialize.attemptingToUnlockMessage')}</ThemedText>
{status ? <LoadingIndicator status={status} /> : null}
</View>
</ThemedView>

View File

@@ -269,7 +269,7 @@ export default function UnlockScreen() : React.ReactNode {
<ThemedView style={styles.container}>
{isLoading ? (
<View style={styles.loadingContainer}>
<LoadingIndicator status={t('auth.unlockingVault')} />
<LoadingIndicator status={t('app.status.unlockingVault')} />
</View>
) : (
<KeyboardAvoidingView

View File

@@ -1,6 +1,7 @@
import { LinearGradient } from 'expo-linear-gradient';
import { router } from 'expo-router';
import { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { StyleSheet, View, TouchableOpacity, Alert, KeyboardAvoidingView, Platform, ScrollView, Dimensions, TouchableWithoutFeedback, Keyboard, Text } from 'react-native';
import type { VaultVersion } from '@/utils/dist/shared/vault-sql';
@@ -29,12 +30,18 @@ export default function UpgradeScreen() : React.ReactNode {
const [isLoading, setIsLoading] = useState(false);
const [currentVersion, setCurrentVersion] = useState<VaultVersion | null>(null);
const [latestVersion, setLatestVersion] = useState<VaultVersion | null>(null);
const [upgradeStatus, setUpgradeStatus] = useState('Preparing upgrade...');
const [upgradeStatus, setUpgradeStatus] = useState('');
const colors = useColors();
const { t } = useTranslation();
const webApi = useWebApi();
const { executeVaultMutation, isLoading: isVaultMutationLoading, syncStatus } = useVaultMutate();
const { syncVault } = useVaultSync();
// Initialize upgrade status with translation
useEffect(() => {
setUpgradeStatus(t('upgrade.status.preparingUpgrade'));
}, [t]);
/**
* Load version information from the database.
*/
@@ -60,19 +67,19 @@ export default function UpgradeScreen() : React.ReactNode {
*/
const handleUpgrade = async (): Promise<void> => {
if (!sqliteClient || !currentVersion || !latestVersion) {
Alert.alert('Error', 'Unable to get version information. Please try again.');
Alert.alert(t('upgrade.alerts.error'), t('upgrade.alerts.unableToGetVersionInfo'));
return;
}
// Check if this is a self-hosted instance and show warning if needed
if (await webApi.isSelfHosted()) {
Alert.alert(
'Self-Hosted Server',
"If you're using a self-hosted server, make sure to also update your self-hosted instance as otherwise logging in to the web client will stop working.",
t('upgrade.alerts.selfHostedServer'),
t('upgrade.alerts.selfHostedWarning'),
[
{ text: 'Cancel', style: 'cancel' },
{ text: t('upgrade.alerts.cancel'), style: 'cancel' },
{
text: 'Continue Upgrade',
text: t('upgrade.alerts.continueUpgrade'),
style: 'default',
/**
* Continue upgrade.
@@ -93,26 +100,25 @@ export default function UpgradeScreen() : React.ReactNode {
*/
const performUpgrade = async (): Promise<void> => {
if (!sqliteClient || !currentVersion || !latestVersion) {
Alert.alert('Error', 'Unable to get version information. Please try again.');
Alert.alert(t('upgrade.alerts.error'), t('upgrade.alerts.unableToGetVersionInfo'));
return;
}
setIsLoading(true);
setUpgradeStatus('Preparing upgrade...');
setUpgradeStatus(t('upgrade.status.preparingUpgrade'));
try {
// Get upgrade SQL commands from vault-sql shared library
setUpgradeStatus('Generating upgrade SQL...');
const vaultSqlGenerator = new VaultSqlGenerator();
const upgradeResult = vaultSqlGenerator.getUpgradeVaultSql(currentVersion.revision, latestVersion.revision);
if (!upgradeResult.success) {
throw new Error(upgradeResult.error ?? 'Failed to generate upgrade SQL');
throw new Error(upgradeResult.error ?? t('upgrade.alerts.upgradeFailed'));
}
if (upgradeResult.sqlCommands.length === 0) {
// No upgrade needed, vault is already up to date
setUpgradeStatus('Vault is already up to date');
setUpgradeStatus(t('upgrade.status.vaultAlreadyUpToDate'));
await new Promise(resolve => setTimeout(resolve, 1000));
await handleUpgradeSuccess();
return;
@@ -121,26 +127,26 @@ export default function UpgradeScreen() : React.ReactNode {
// Use the useVaultMutate hook to handle the upgrade and vault upload
await executeVaultMutation(async () => {
// Begin transaction
setUpgradeStatus('Starting database transaction...');
setUpgradeStatus(t('upgrade.status.startingDatabaseTransaction'));
await NativeVaultManager.beginTransaction();
// Execute each SQL command
setUpgradeStatus('Applying database migrations...');
setUpgradeStatus(t('upgrade.status.applyingDatabaseMigrations'));
for (let i = 0; i < upgradeResult.sqlCommands.length; i++) {
const sqlCommand = upgradeResult.sqlCommands[i];
setUpgradeStatus(`Applying migration ${i + 1} of ${upgradeResult.sqlCommands.length}...`);
setUpgradeStatus(t('upgrade.status.applyingMigration', { current: i + 1, total: upgradeResult.sqlCommands.length }));
try {
await NativeVaultManager.executeRaw(sqlCommand);
} catch (error) {
console.error(`Error executing SQL command ${i + 1}:`, sqlCommand, error);
await NativeVaultManager.rollbackTransaction();
throw new Error(`Failed to apply migration (${i + 1} of ${upgradeResult.sqlCommands.length})`);
throw new Error(t('upgrade.alerts.failedToApplyMigration', { current: i + 1, total: upgradeResult.sqlCommands.length }));
}
}
// Commit transaction
setUpgradeStatus('Committing changes...');
setUpgradeStatus(t('upgrade.status.committingChanges'));
await NativeVaultManager.commitTransaction();
}, {
skipSyncCheck: true, // Skip sync check during upgrade to prevent loop
@@ -155,16 +161,16 @@ export default function UpgradeScreen() : React.ReactNode {
*/
onError: (error: Error) => {
console.error('Upgrade failed:', error);
Alert.alert('Upgrade Failed', error.message);
Alert.alert(t('upgrade.alerts.upgradeFailed'), error.message);
}
});
} catch (error) {
console.error('Upgrade failed:', error);
Alert.alert('Upgrade Failed', error instanceof Error ? error.message : 'An unknown error occurred during the upgrade. Please try again.');
Alert.alert(t('upgrade.alerts.upgradeFailed'), error instanceof Error ? error.message : t('upgrade.alerts.unknownErrorDuringUpgrade'));
} finally {
setIsLoading(false);
setUpgradeStatus('Preparing upgrade...');
setUpgradeStatus(t('upgrade.status.preparingUpgrade'));
}
};
@@ -219,10 +225,10 @@ export default function UpgradeScreen() : React.ReactNode {
*/
const showVersionDialog = (): void => {
Alert.alert(
"What's New",
`An upgrade is required to support the following changes:\n\n${latestVersion?.description ?? 'No description available for this version.'}`,
t('upgrade.whatsNew'),
`${t('upgrade.whatsNewDescription')}\n\n${latestVersion?.description ?? t('upgrade.noDescriptionAvailable')}`,
[
{ text: 'Okay', style: 'default' }
{ text: t('upgrade.okay'), style: 'default' }
]
);
};
@@ -399,7 +405,7 @@ export default function UpgradeScreen() : React.ReactNode {
<View style={styles.headerSection}>
<View style={styles.logoContainer}>
<Logo width={80} height={80} />
<Text style={styles.appName}>Upgrade Vault</Text>
<Text style={styles.appName}>{t('upgrade.title')}</Text>
</View>
</View>
<View style={styles.content}>
@@ -407,10 +413,10 @@ export default function UpgradeScreen() : React.ReactNode {
<Avatar />
<ThemedText style={styles.username}>{username}</ThemedText>
</View>
<ThemedText style={styles.subtitle}>AliasVault has updated and your vault needs to be upgraded. This should only take a few seconds.</ThemedText>
<ThemedText style={styles.subtitle}>{t('upgrade.subtitle')}</ThemedText>
<View style={styles.versionContainer}>
<View style={styles.versionHeader}>
<ThemedText style={styles.versionTitle}>Version Information</ThemedText>
<ThemedText style={styles.versionTitle}>{t('upgrade.versionInformation')}</ThemedText>
<TouchableOpacity
style={styles.helpButton}
onPress={showVersionDialog}
@@ -419,13 +425,13 @@ export default function UpgradeScreen() : React.ReactNode {
</TouchableOpacity>
</View>
<View style={styles.versionRow}>
<ThemedText style={styles.versionLabel}>Your vault:</ThemedText>
<ThemedText style={styles.versionLabel}>{t('upgrade.yourVault')}</ThemedText>
<ThemedText style={[styles.versionValue, styles.currentVersionValue]}>
{currentVersion?.releaseVersion ?? '...'}
</ThemedText>
</View>
<View style={styles.versionRow}>
<ThemedText style={styles.versionLabel}>New version:</ThemedText>
<ThemedText style={styles.versionLabel}>{t('upgrade.newVersion')}</ThemedText>
<ThemedText style={[styles.versionValue, styles.latestVersionValue]}>
{latestVersion?.releaseVersion ?? '...'}
</ThemedText>
@@ -438,7 +444,7 @@ export default function UpgradeScreen() : React.ReactNode {
disabled={isLoading || isVaultMutationLoading}
>
<ThemedText style={styles.buttonText}>
{isLoading || isVaultMutationLoading ? (syncStatus || 'Upgrading...') : 'Upgrade'}
{isLoading || isVaultMutationLoading ? (syncStatus || t('upgrade.upgrading')) : t('upgrade.upgrade')}
</ThemedText>
</TouchableOpacity>
@@ -446,7 +452,7 @@ export default function UpgradeScreen() : React.ReactNode {
style={styles.logoutButton}
onPress={handleLogout}
>
<ThemedText style={styles.logoutButtonText}>Logout</ThemedText>
<ThemedText style={styles.logoutButtonText}>{t('upgrade.logout')}</ThemedText>
</TouchableOpacity>
</View>
</View>

View File

@@ -23,7 +23,6 @@
"authCode": "Authentication Code",
"unlock": "Unlock",
"unlocking": "Unlocking...",
"unlockingVault": "Unlocking vault",
"loggingIn": "Logging in",
"validatingCredentials": "Validating credentials",
"syncingVault": "Syncing vault",
@@ -354,5 +353,61 @@
"VAULT_NOT_UP_TO_DATE": "Your vault is not up-to-date. Please synchronize your vault and try again.",
"INTERNAL_SERVER_ERROR": "Internal server error.",
"VAULT_ERROR": "The local vault is not up-to-date. Please synchronize your vault by refreshing the page and try again."
},
"app": {
"status": {
"unlockingVault": "Unlocking vault",
"decryptingVault": "Decrypting vault",
"openingVaultReadOnly": "Opening vault in read-only mode",
"retryingConnection": "Retrying connection..."
},
"alerts": {
"syncIssue": "Sync Issue",
"syncIssueMessage": "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?",
"openLocalVault": "Open Local Vault",
"retrySync": "Retry Sync",
"error": "Error"
},
"navigation": {
"login": "Login",
"notFound": "Not Found"
},
"reinitialize": {
"vaultAutoLockedMessage": "Vault auto-locked after timeout.",
"attemptingToUnlockMessage": "Attempting to unlock."
}
},
"upgrade": {
"title": "Upgrade Vault",
"subtitle": "AliasVault has updated and your vault needs to be upgraded. This should only take a few seconds.",
"versionInformation": "Version Information",
"yourVault": "Your vault:",
"newVersion": "New version:",
"upgrade": "Upgrade",
"upgrading": "Upgrading...",
"logout": "Logout",
"whatsNew": "What's New",
"whatsNewDescription": "An upgrade is required to support the following changes:",
"noDescriptionAvailable": "No description available for this version.",
"okay": "Ok",
"status": {
"preparingUpgrade": "Preparing upgrade...",
"vaultAlreadyUpToDate": "Vault is already up to date",
"startingDatabaseTransaction": "Starting database transaction...",
"applyingDatabaseMigrations": "Applying database migrations...",
"applyingMigration": "Applying migration {{current}} of {{total}}...",
"committingChanges": "Committing changes..."
},
"alerts": {
"error": "Error",
"unableToGetVersionInfo": "Unable to get version information. Please try again.",
"selfHostedServer": "Self-Hosted Server",
"selfHostedWarning": "If you're using a self-hosted server, make sure to also update your self-hosted instance as otherwise logging in to the web client will stop working.",
"cancel": "Cancel",
"continueUpgrade": "Continue Upgrade",
"upgradeFailed": "Upgrade Failed",
"failedToApplyMigration": "Failed to apply migration ({{current}} of {{total}})",
"unknownErrorDuringUpgrade": "An unknown error occurred during the upgrade. Please try again."
}
}
}