mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-29 12:01:40 -04:00
Update browser extension and app translations
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user