From 32a2d13fccec972944a850b0992cf1d28d06c188 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 14 Jul 2025 21:10:06 +0200 Subject: [PATCH] Cache localized strings for performance (#1006) --- .../AliasVault.Client/Auth/Pages/Login.razor | 122 +++++++++++---- .../Auth/Pages/Register.razor | 59 ++++++-- .../Pages/Setup/Components/PasswordStep.razor | 52 +++++-- .../Components/TermsAndConditionsStep.razor | 20 ++- .../Pages/Setup/Components/UsernameStep.razor | 42 ++++-- .../AliasVault.Client/Auth/Pages/Unlock.razor | 70 ++++++--- .../Main/Components/Email/EmailModal.razor | 70 ++++++--- .../Main/Components/Email/EmailPreview.razor | 77 +++++++--- .../Main/Components/Email/EmailRow.razor | 14 +- .../Main/Components/Email/RecentEmails.razor | 36 +++-- .../Components/Forms/CopyPasteFormRow.razor | 8 +- .../Components/Forms/EditEmailFormRow.razor | 32 +++- .../Settings/DefaultPasswordSettings.razor | 16 +- .../Settings/PasswordSettingsPopup.razor | 52 +++++-- .../Main/Components/TotpCodes/TotpCodes.razor | 54 +++++-- .../Components/TotpCodes/TotpViewer.razor | 16 +- .../Widgets/CreateNewIdentityWidget.razor | 49 ++++-- .../Components/Widgets/SearchWidget.razor | 141 ++++++++++-------- .../Main/Layout/Footer.razor | 26 ++-- .../Main/Layout/TopMenu.razor | 79 ++++++++-- .../Main/Pages/Credentials/Home.razor | 66 ++++++-- .../Main/Pages/Emails/Home.razor | 51 +++++-- .../Settings/ImportExport/ImportExport.razor | 49 ++++-- .../Settings/Security/ChangePassword.razor | 60 ++++++-- .../Settings/Security/DeleteAccount.razor | 86 ++++++++--- .../Pages/Settings/Security/Disable2Fa.razor | 50 +++++-- .../Pages/Settings/Security/Enable2Fa.razor | 45 ++++-- .../Pages/Settings/Security/Security.razor | 22 ++- .../Main/Pages/Welcome.razor | 108 ++++++++++---- 29 files changed, 1179 insertions(+), 393 deletions(-) diff --git a/apps/server/AliasVault.Client/Auth/Pages/Login.razor b/apps/server/AliasVault.Client/Auth/Pages/Login.razor index 4639d58cd..7324a0459 100644 --- a/apps/server/AliasVault.Client/Auth/Pages/Login.razor +++ b/apps/server/AliasVault.Client/Auth/Pages/Login.razor @@ -14,17 +14,17 @@ @if (_showTwoFactorAuthStep) {

- @Localizer["TwoFactorAuthenticationTitle"] + @_twoFactorAuthenticationTitle

-

@Localizer["TwoFactorAuthenticationDescription"]

+

@_twoFactorAuthenticationDescription

- +
- +
- +

- @Localizer["DontHaveAuthenticatorText"] - . + @_dontHaveAuthenticatorText + .

} else if (_showLoginWithRecoveryCodeStep) {

- @Localizer["RecoveryCodeVerificationTitle"] + @_recoveryCodeVerificationTitle

- @Localizer["RecoveryCodeDescription"] + @_recoveryCodeDescription

- +
- +

- @Localizer["RegainedAccessText"] - instead. + @_regainedAccessText + instead.

} else {

- @Localizer["PageTitle"] + @_pageTitle

@@ -87,13 +87,13 @@ else
- - + +
- - + +
@@ -102,16 +102,16 @@ else
- +
- @Localizer["LostPasswordLink"] + @_lostPasswordLink - + @if (Config.PublicRegistrationEnabled) {
- @Localizer["NoAccountYetText"] @Localizer["CreateNewVaultLink"] + @_noAccountYetText @_createNewVaultLink
} @@ -133,6 +133,35 @@ else private SrpSession _clientSession = new(); private byte[] _passwordHash = []; + // Cached localized strings + private string _twoFactorAuthenticationTitle = string.Empty; + private string _twoFactorAuthenticationDescription = string.Empty; + private string _authenticatorCodeLabel = string.Empty; + private string _rememberMachineLabel = string.Empty; + private string _loginButton = string.Empty; + private string _dontHaveAuthenticatorText = string.Empty; + private string _loginWithRecoveryCodeLink = string.Empty; + private string _recoveryCodeVerificationTitle = string.Empty; + private string _recoveryCodeDescription = string.Empty; + private string _recoveryCodeLabel = string.Empty; + private string _regainedAccessText = string.Empty; + private string _loginWithAuthenticatorLink = string.Empty; + private string _pageTitle = string.Empty; + private string _usernameOrEmailLabel = string.Empty; + private string _usernamePlaceholder = string.Empty; + private string _passwordLabel = string.Empty; + private string _passwordPlaceholder = string.Empty; + private string _rememberMeLabel = string.Empty; + private string _lostPasswordLink = string.Empty; + private string _loginToAccountButton = string.Empty; + private string _noAccountYetText = string.Empty; + private string _createNewVaultLink = string.Empty; + private string _loggingInMessage = string.Empty; + private string _loginErrorMessage = string.Empty; + private string _loginRequestErrorMessage = string.Empty; + private string _verifyingRecoveryCodeMessage = string.Empty; + private string _verifyingTwoFactorCodeMessage = string.Empty; + /// protected override async Task OnInitializedAsync() { @@ -144,6 +173,35 @@ else // Already authenticated, redirect to home page. NavigationManager.NavigateTo("/"); } + + // Cache localized strings to prevent infinite rendering loops + _twoFactorAuthenticationTitle = Localizer["TwoFactorAuthenticationTitle"]; + _twoFactorAuthenticationDescription = Localizer["TwoFactorAuthenticationDescription"]; + _authenticatorCodeLabel = Localizer["AuthenticatorCodeLabel"]; + _rememberMachineLabel = Localizer["RememberMachineLabel"]; + _loginButton = Localizer["LoginButton"]; + _dontHaveAuthenticatorText = Localizer["DontHaveAuthenticatorText"]; + _loginWithRecoveryCodeLink = Localizer["LoginWithRecoveryCodeLink"]; + _recoveryCodeVerificationTitle = Localizer["RecoveryCodeVerificationTitle"]; + _recoveryCodeDescription = Localizer["RecoveryCodeDescription"]; + _recoveryCodeLabel = Localizer["RecoveryCodeLabel"]; + _regainedAccessText = Localizer["RegainedAccessText"]; + _loginWithAuthenticatorLink = Localizer["LoginWithAuthenticatorLink"]; + _pageTitle = Localizer["PageTitle"]; + _usernameOrEmailLabel = Localizer["UsernameOrEmailLabel"]; + _usernamePlaceholder = Localizer["UsernamePlaceholder"]; + _passwordLabel = Localizer["PasswordLabel"]; + _passwordPlaceholder = Localizer["PasswordPlaceholder"]; + _rememberMeLabel = Localizer["RememberMeLabel"]; + _lostPasswordLink = Localizer["LostPasswordLink"]; + _loginToAccountButton = Localizer["LoginToAccountButton"]; + _noAccountYetText = Localizer["NoAccountYetText"]; + _createNewVaultLink = Localizer["CreateNewVaultLink"]; + _loggingInMessage = Localizer["LoggingInMessage"]; + _loginErrorMessage = Localizer["LoginErrorMessage"]; + _loginRequestErrorMessage = Localizer["LoginRequestErrorMessage"]; + _verifyingRecoveryCodeMessage = Localizer["VerifyingRecoveryCodeMessage"]; + _verifyingTwoFactorCodeMessage = Localizer["VerifyingTwoFactorCodeMessage"]; } /// @@ -178,7 +236,7 @@ else /// private async Task HandleLogin() { - _loadingIndicator.Show(Localizer["LoggingInMessage"]); + _loadingIndicator.Show(_loggingInMessage); _serverValidationErrors.Clear(); try @@ -199,7 +257,7 @@ else catch { // If in release mode show a generic error. - _serverValidationErrors.AddError(Localizer["LoginErrorMessage"]); + _serverValidationErrors.AddError(_loginErrorMessage); } #endif finally @@ -233,7 +291,7 @@ else { return [ - Localizer["LoginRequestErrorMessage"], + _loginRequestErrorMessage, ]; } @@ -264,7 +322,7 @@ else { return [ - Localizer["LoginRequestErrorMessage"], + _loginRequestErrorMessage, ]; } @@ -284,7 +342,7 @@ else /// private async Task HandleRecoveryCode() { - _loadingIndicator.Show(Localizer["VerifyingRecoveryCodeMessage"]); + _loadingIndicator.Show(_verifyingRecoveryCodeMessage); _serverValidationErrors.Clear(); try @@ -308,7 +366,7 @@ else var validateLoginResponse = JsonSerializer.Deserialize(responseContent); if (validateLoginResponse == null) { - _serverValidationErrors.AddError(Localizer["LoginRequestErrorMessage"]); + _serverValidationErrors.AddError(_loginRequestErrorMessage); return; } @@ -328,7 +386,7 @@ else catch { // If in release mode show a generic error. - _serverValidationErrors.AddError(Localizer["LoginErrorMessage"]); + _serverValidationErrors.AddError(_loginErrorMessage); } #endif finally @@ -342,7 +400,7 @@ else /// private async Task Handle2Fa() { - _loadingIndicator.Show(Localizer["VerifyingTwoFactorCodeMessage"]); + _loadingIndicator.Show(_verifyingTwoFactorCodeMessage); _serverValidationErrors.Clear(); try @@ -366,7 +424,7 @@ else var validateLoginResponse = JsonSerializer.Deserialize(responseContent); if (validateLoginResponse == null) { - _serverValidationErrors.AddError(Localizer["LoginRequestErrorMessage"]); + _serverValidationErrors.AddError(_loginRequestErrorMessage); return; } @@ -386,7 +444,7 @@ else catch { // If in release mode show a generic error. - _serverValidationErrors.AddError(Localizer["LoginErrorMessage"]); + _serverValidationErrors.AddError(_loginErrorMessage); } #endif finally diff --git a/apps/server/AliasVault.Client/Auth/Pages/Register.razor b/apps/server/AliasVault.Client/Auth/Pages/Register.razor index 00b0fcafa..17a56119b 100644 --- a/apps/server/AliasVault.Client/Auth/Pages/Register.razor +++ b/apps/server/AliasVault.Client/Auth/Pages/Register.razor @@ -7,7 +7,7 @@ @using Microsoft.Extensions.Localization

- @Localizer["PageTitle"] + @_pageTitle

@@ -16,18 +16,18 @@
- - + +
- - + +
- - + +
@@ -36,14 +36,14 @@
- +
- +
- @Localizer["AlreadyRegisteredText"] @Localizer["LoginHereLink"] + @_alreadyRegisteredText @_loginHereLink
@@ -54,9 +54,44 @@ private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Auth.Register", "AliasVault.Client"); + // Cached localized strings + private string _pageTitle = string.Empty; + private string _usernameOrEmailLabel = string.Empty; + private string _usernamePlaceholder = string.Empty; + private string _passwordLabel = string.Empty; + private string _passwordPlaceholder = string.Empty; + private string _confirmPasswordLabel = string.Empty; + private string _acceptTermsLabel = string.Empty; + private string _termsAndConditionsLink = string.Empty; + private string _createAccountButton = string.Empty; + private string _alreadyRegisteredText = string.Empty; + private string _loginHereLink = string.Empty; + private string _creatingAccountMessage = string.Empty; + private string _registrationErrorMessage = string.Empty; + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + // Cache localized strings to prevent infinite rendering loops + _pageTitle = Localizer["PageTitle"]; + _usernameOrEmailLabel = Localizer["UsernameOrEmailLabel"]; + _usernamePlaceholder = Localizer["UsernamePlaceholder"]; + _passwordLabel = Localizer["PasswordLabel"]; + _passwordPlaceholder = Localizer["PasswordPlaceholder"]; + _confirmPasswordLabel = Localizer["ConfirmPasswordLabel"]; + _acceptTermsLabel = Localizer["AcceptTermsLabel"]; + _termsAndConditionsLink = Localizer["TermsAndConditionsLink"]; + _createAccountButton = Localizer["CreateAccountButton"]; + _alreadyRegisteredText = Localizer["AlreadyRegisteredText"]; + _loginHereLink = Localizer["LoginHereLink"]; + _creatingAccountMessage = Localizer["CreatingAccountMessage"]; + _registrationErrorMessage = Localizer["RegistrationErrorMessage"]; + } + private async Task HandleRegister() { - _loadingIndicator.Show(Localizer["CreatingAccountMessage"]); + _loadingIndicator.Show(_creatingAccountMessage); _serverValidationErrors.Clear(); var (success, errorMessage) = await UserRegistrationService.RegisterUserAsync(_registerModel.Username, _registerModel.Password); @@ -67,7 +102,7 @@ } else { - _serverValidationErrors.AddError(errorMessage ?? Localizer["RegistrationErrorMessage"]); + _serverValidationErrors.AddError(errorMessage ?? _registrationErrorMessage); StateHasChanged(); } diff --git a/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/PasswordStep.razor b/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/PasswordStep.razor index 707f37609..8c700b677 100644 --- a/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/PasswordStep.razor +++ b/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/PasswordStep.razor @@ -17,7 +17,7 @@

- @Localizer["WelcomeMessage"] + @_welcomeMessage

@@ -25,26 +25,26 @@

- @Localizer["ImportantNote"] + @_importantNote

    -
  • @Localizer["SecurityPoint1"]
  • -
  • @Localizer["SecurityPoint2"]
  • -
  • @Localizer["SecurityPoint3"]
  • +
  • @_securityPoint1
  • +
  • @_securityPoint2
  • +
  • @_securityPoint3
- +
- +
@if (_isValidating) { -
@Localizer["ValidatingPasswordMessage"]
+
@_validatingPasswordMessage
} else if (_isValid) { @@ -54,7 +54,7 @@ } else { -
@Localizer["PasswordValidAndStrongMessage"]
+
@_passwordValidAndStrongMessage
} } else if (!string.IsNullOrEmpty(_errorMessage)) @@ -69,6 +69,21 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Auth.Setup.PasswordStep", "AliasVault.Client"); + // Cached localized strings for performance + private string _welcomeMessage = string.Empty; + private string _importantNote = string.Empty; + private string _securityPoint1 = string.Empty; + private string _securityPoint2 = string.Empty; + private string _securityPoint3 = string.Empty; + private string _masterPasswordLabel = string.Empty; + private string _masterPasswordPlaceholder = string.Empty; + private string _confirmMasterPasswordLabel = string.Empty; + private string _confirmMasterPasswordPlaceholder = string.Empty; + private string _validatingPasswordMessage = string.Empty; + private string _passwordValidAndStrongMessage = string.Empty; + private string _passwordTooShortError = string.Empty; + private string _passwordsMismatchError = string.Empty; + /// /// The event callback for when the password changes. /// @@ -122,6 +137,21 @@ /// protected override void OnInitialized() { + // Cache localized strings for performance + _welcomeMessage = Localizer["WelcomeMessage"]; + _importantNote = Localizer["ImportantNote"]; + _securityPoint1 = Localizer["SecurityPoint1"]; + _securityPoint2 = Localizer["SecurityPoint2"]; + _securityPoint3 = Localizer["SecurityPoint3"]; + _masterPasswordLabel = Localizer["MasterPasswordLabel"]; + _masterPasswordPlaceholder = Localizer["MasterPasswordPlaceholder"]; + _confirmMasterPasswordLabel = Localizer["ConfirmMasterPasswordLabel"]; + _confirmMasterPasswordPlaceholder = Localizer["ConfirmMasterPasswordPlaceholder"]; + _validatingPasswordMessage = Localizer["ValidatingPasswordMessage"]; + _passwordValidAndStrongMessage = Localizer["PasswordValidAndStrongMessage"]; + _passwordTooShortError = Localizer["PasswordTooShortError"]; + _passwordsMismatchError = Localizer["PasswordsMismatchError"]; + _loadingTimer = new Timer(300); _loadingTimer.Elapsed += (sender, e) => FinishLoading(); _loadingTimer.AutoReset = false; @@ -177,7 +207,7 @@ { _isValidating = false; _isValid = false; - _errorMessage = Localizer["PasswordTooShortError"]; + _errorMessage = _passwordTooShortError; await OnPasswordChange.InvokeAsync(string.Empty); StateHasChanged(); return; @@ -197,7 +227,7 @@ { _isValidating = false; _isValid = false; - _errorMessage = Localizer["PasswordsMismatchError"]; + _errorMessage = _passwordsMismatchError; await OnPasswordChange.InvokeAsync(string.Empty); StateHasChanged(); return; diff --git a/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/TermsAndConditionsStep.razor b/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/TermsAndConditionsStep.razor index ac3dec807..3f4e07f00 100644 --- a/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/TermsAndConditionsStep.razor +++ b/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/TermsAndConditionsStep.razor @@ -13,18 +13,18 @@

- @Localizer["PleaseReadAndAgree"] + @_pleaseReadAndAgree

-

@Localizer["TermsAndConditionsTitle"]

+

@_termsAndConditionsTitle

- @Localizer["TermsContent"] + @_termsContent

@@ -33,6 +33,12 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Auth.Setup.TermsAndConditionsStep", "AliasVault.Client"); + + // Cached localized strings for performance + private string _pleaseReadAndAgree = string.Empty; + private string _termsAndConditionsTitle = string.Empty; + private string _termsContent = string.Empty; + private string _agreementCheckboxLabel = string.Empty; /// /// Gets or sets a value indicating whether the user has agreed to the terms and conditions. /// @@ -57,6 +63,12 @@ /// protected override void OnInitialized() { + // Cache localized strings for performance + _pleaseReadAndAgree = Localizer["PleaseReadAndAgree"]; + _termsAndConditionsTitle = Localizer["TermsAndConditionsTitle"]; + _termsContent = Localizer["TermsContent"]; + _agreementCheckboxLabel = Localizer["AgreementCheckboxLabel"]; + _loadingTimer = new Timer(300); _loadingTimer.Elapsed += (sender, e) => FinishLoading(); _loadingTimer.AutoReset = false; diff --git a/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/UsernameStep.razor b/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/UsernameStep.razor index 6959ac23f..79b874447 100644 --- a/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/UsernameStep.razor +++ b/apps/server/AliasVault.Client/Auth/Pages/Setup/Components/UsernameStep.razor @@ -14,17 +14,17 @@
- @Localizer[ + @_assistantAvatarAlt

- @Localizer["GreatNowLetsSetupUsername"] + @_greatNowLetsSetupUsername

- @Localizer["EnterUsernameInstructions"] + @_enterUsernameInstructions

- @Localizer["RememberUsernameNote"] + @_rememberUsernameNote

@@ -32,14 +32,14 @@
- + @if (_isValidating) { -
@Localizer["ValidatingUsernameMessage"]
+
@_validatingUsernameMessage
} else if (_isValid) { -
@Localizer["UsernameAvailableMessage"]
+
@_usernameAvailableMessage
} else if (!string.IsNullOrEmpty(_errorMessage)) { @@ -54,6 +54,18 @@ private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Auth.Setup.UsernameStep", "AliasVault.Client"); private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client"); + // Cached localized strings for performance + private string _assistantAvatarAlt = string.Empty; + private string _greatNowLetsSetupUsername = string.Empty; + private string _enterUsernameInstructions = string.Empty; + private string _rememberUsernameNote = string.Empty; + private string _usernameLabel = string.Empty; + private string _usernamePlaceholder = string.Empty; + private string _validatingUsernameMessage = string.Empty; + private string _usernameAvailableMessage = string.Empty; + private string _usernameRequiredError = string.Empty; + private string _serverCommunicationError = string.Empty; + /// /// The username that is previously entered by the user. When a user navigates with back/continue /// and entered a username already, the existing username might be provided by the parent component. @@ -103,6 +115,18 @@ /// protected override async Task OnInitializedAsync() { + // Cache localized strings for performance + _assistantAvatarAlt = Localizer["AssistantAvatarAlt"]; + _greatNowLetsSetupUsername = Localizer["GreatNowLetsSetupUsername"]; + _enterUsernameInstructions = Localizer["EnterUsernameInstructions"]; + _rememberUsernameNote = Localizer["RememberUsernameNote"]; + _usernameLabel = Localizer["UsernameLabel"]; + _usernamePlaceholder = Localizer["UsernamePlaceholder"]; + _validatingUsernameMessage = Localizer["ValidatingUsernameMessage"]; + _usernameAvailableMessage = Localizer["UsernameAvailableMessage"]; + _usernameRequiredError = Localizer["UsernameRequiredError"]; + _serverCommunicationError = Localizer["ServerCommunicationError"]; + // Set the default username if provided. _username = DefaultUsername; @@ -166,7 +190,7 @@ { _isValidating = false; _isValid = false; - _errorMessage = Localizer["UsernameRequiredError"]; + _errorMessage = _usernameRequiredError; await OnUsernameChange.InvokeAsync(string.Empty); StateHasChanged(); return; @@ -192,7 +216,7 @@ } catch { - _errorMessage = Localizer["ServerCommunicationError"]; + _errorMessage = _serverCommunicationError; _isValid = false; await OnUsernameChange.InvokeAsync(string.Empty); } diff --git a/apps/server/AliasVault.Client/Auth/Pages/Unlock.razor b/apps/server/AliasVault.Client/Auth/Pages/Unlock.razor index 11fa80ddd..5c9765639 100644 --- a/apps/server/AliasVault.Client/Auth/Pages/Unlock.razor +++ b/apps/server/AliasVault.Client/Auth/Pages/Unlock.razor @@ -19,7 +19,7 @@ else if (IsWebAuthnLoading) {

- @Localizer["LoggingInWithWebAuthn"] + @_loggingInWithWebAuthn

} else @@ -33,15 +33,15 @@ else {

- @Localizer["QuickUnlockDescription"] + @_quickUnlockDescription

@@ -49,26 +49,26 @@ else else {

- @Localizer["EnterMasterPasswordDescription"] + @_enterMasterPasswordDescription

- +
}
- @Localizer["SwitchAccountsText"] @Localizer["LogOutLink"] + @_switchAccountsText @_logOutLink
} @@ -89,11 +89,47 @@ else private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Auth.Unlock", "AliasVault.Client"); private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client"); + // Cached localized strings + private string _loggingInWithWebAuthn = string.Empty; + private string _quickUnlockDescription = string.Empty; + private string _unlockWithWebAuthn = string.Empty; + private string _unlockWithPassword = string.Empty; + private string _enterMasterPasswordDescription = string.Empty; + private string _yourPasswordLabel = string.Empty; + private string _unlockButton = string.Empty; + private string _switchAccountsText = string.Empty; + private string _logOutLink = string.Empty; + private string _unlockingVaultMessage = string.Empty; + private string _unlockRequestError = string.Empty; + private string _incorrectPasswordError = string.Empty; + private string _genericUnlockError = string.Empty; + private string _sessionTimedOutError = string.Empty; + private string _connectionFailedError = string.Empty; + private string _webAuthnNotSupportedError = string.Empty; + /// protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { + // Cache localized strings to prevent infinite rendering loops + _loggingInWithWebAuthn = Localizer["LoggingInWithWebAuthn"]; + _quickUnlockDescription = Localizer["QuickUnlockDescription"]; + _unlockWithWebAuthn = Localizer["UnlockWithWebAuthn"]; + _unlockWithPassword = Localizer["UnlockWithPassword"]; + _enterMasterPasswordDescription = Localizer["EnterMasterPasswordDescription"]; + _yourPasswordLabel = Localizer["YourPasswordLabel"]; + _unlockButton = Localizer["UnlockButton"]; + _switchAccountsText = Localizer["SwitchAccountsText"]; + _logOutLink = Localizer["LogOutLink"]; + _unlockingVaultMessage = Localizer["UnlockingVaultMessage"]; + _unlockRequestError = Localizer["UnlockRequestError"]; + _incorrectPasswordError = Localizer["IncorrectPasswordError"]; + _genericUnlockError = Localizer["GenericUnlockError"]; + _sessionTimedOutError = Localizer["SessionTimedOutError"]; + _connectionFailedError = Localizer["ConnectionFailedError"]; + _webAuthnNotSupportedError = Localizer["WebAuthnNotSupportedError"]; + // Trigger status API call to check if the user is still authenticated. // If user is not authenticated a redirect to the login page will be triggered automatically. await StatusCheck(); @@ -117,7 +153,7 @@ else ///
private async Task UnlockSubmit() { - _loadingIndicator.Show(Localizer["UnlockingVaultMessage"]); + _loadingIndicator.Show(_unlockingVaultMessage); _serverValidationErrors.Clear(); try @@ -142,7 +178,7 @@ else var loginResponse = JsonSerializer.Deserialize(responseContent); if (loginResponse == null) { - _serverValidationErrors.AddError(Localizer["UnlockRequestError"]); + _serverValidationErrors.AddError(_unlockRequestError); return; } @@ -154,7 +190,7 @@ else if (!validPassword) { - _serverValidationErrors.AddError(Localizer["IncorrectPasswordError"]); + _serverValidationErrors.AddError(_incorrectPasswordError); return; } @@ -183,7 +219,7 @@ else catch { // If in release mode show a generic error. - _serverValidationErrors.AddError(Localizer["GenericUnlockError"]); + _serverValidationErrors.AddError(_genericUnlockError); } #endif finally @@ -202,7 +238,7 @@ else var authState = await AuthStateProvider.GetAuthenticationStateAsync(); if (authState.User.Identity?.IsAuthenticated == false) { // Not authenticated (anymore), redirect to login page. - GlobalNotificationService.AddErrorMessage(Localizer["SessionTimedOutError"]); + GlobalNotificationService.AddErrorMessage(_sessionTimedOutError); NavigationManager.NavigateTo("/user/login"); return; } @@ -214,7 +250,7 @@ else // Clear all tokens and redirect to login page. await AuthService.RemoveTokensAsync(); GlobalNotificationService.ClearMessages(); - GlobalNotificationService.AddErrorMessage(Localizer["SessionTimedOutError"]); + GlobalNotificationService.AddErrorMessage(_sessionTimedOutError); NavigationManager.NavigateTo("/user/login"); return; } @@ -228,7 +264,7 @@ else await AuthService.RemoveTokensAsync(); await AuthStateProvider.GetAuthenticationStateAsync(); GlobalNotificationService.ClearMessages(); - GlobalNotificationService.AddErrorMessage(Localizer["SessionTimedOutError"]); + GlobalNotificationService.AddErrorMessage(_sessionTimedOutError); NavigationManager.NavigateTo("/user/login"); return; } @@ -241,7 +277,7 @@ else } catch (Exception ex) { - _serverValidationErrors.AddError(Localizer["ConnectionFailedError"]); + _serverValidationErrors.AddError(_connectionFailedError); Logger.LogError(ex, "An error occurred while checking the user status."); StateHasChanged(); } @@ -284,7 +320,7 @@ else } catch (NotSupportedException) { - GlobalNotificationService.AddErrorMessage(Localizer["WebAuthnNotSupportedError"], true); + GlobalNotificationService.AddErrorMessage(_webAuthnNotSupportedError, true); } catch (Exception ex) { diff --git a/apps/server/AliasVault.Client/Main/Components/Email/EmailModal.razor b/apps/server/AliasVault.Client/Main/Components/Email/EmailModal.razor index 1b23a2f8c..f6ffdd8dd 100644 --- a/apps/server/AliasVault.Client/Main/Components/Email/EmailModal.razor +++ b/apps/server/AliasVault.Client/Main/Components/Email/EmailModal.razor @@ -37,12 +37,12 @@
-

@Localizer["FromLabel"] @(Email?.FromLocal)@@@(Email?.FromDomain)

-

@Localizer["ToLabel"] @(Email?.ToLocal)@@@(Email?.ToDomain)

+

@_fromLabel @(Email?.FromLocal)@@@(Email?.FromDomain)

+

@_toLabel @(Email?.ToLocal)@@@(Email?.ToDomain)

-

@Localizer["DateLabel"] @Email?.DateSystem

-

@Localizer["ActionsLabel"]

+

@_dateLabel @Email?.DateSystem

+

@_actionsLabel

@@ -59,7 +59,7 @@ @if (Email?.Attachments?.Any() == true) {
-

@Localizer["AttachmentsLabel"]

+

@_attachmentsLabel

@foreach (var attachment in Email.Attachments) { @@ -78,7 +78,7 @@ }
- +
@@ -87,6 +87,23 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Email.EmailModal", "AliasVault.Client"); + + // Cached localized strings + private string _fromLabel = ""; + private string _toLabel = ""; + private string _dateLabel = ""; + private string _actionsLabel = ""; + private string _deleteButton = ""; + private string _attachmentsLabel = ""; + private string _closeButton = ""; + private string _deleteEmailTitle = ""; + private string _deleteEmailConfirmation = ""; + private string _emailDeletedSuccess = ""; + private string _emailDeleteFailed = ""; + private string _genericError = ""; + private string _noEmailBody = ""; + private string _attachmentDownloadFailed = ""; + private string _attachmentDownloadError = ""; /// /// The email to show in the modal. @@ -128,8 +145,8 @@ } var result = await ConfirmModalService.ShowConfirmation( - Localizer["DeleteEmailTitle"], - Localizer["DeleteEmailConfirmation"] + _deleteEmailTitle, + _deleteEmailConfirmation ); if (result) @@ -189,18 +206,18 @@ if (response.IsSuccessStatusCode) { await OnEmailDeleted.InvokeAsync(Email.Id); - GlobalNotificationService.AddSuccessMessage(Localizer["EmailDeletedSuccess"], true); + GlobalNotificationService.AddSuccessMessage(_emailDeletedSuccess, true); await Close(); } else { var errorMessage = await response.Content.ReadAsStringAsync(); - GlobalNotificationService.AddErrorMessage($"{Localizer["EmailDeleteFailed"]}: {errorMessage}", true); + GlobalNotificationService.AddErrorMessage($"{_emailDeleteFailed}: {errorMessage}", true); } } catch (Exception ex) { - GlobalNotificationService.AddErrorMessage($"{Localizer["GenericError"]}: {ex.Message}", true); + GlobalNotificationService.AddErrorMessage($"{_genericError}: {ex.Message}", true); } } @@ -220,22 +237,39 @@ if (response.IsSuccessStatusCode) { await OnEmailDeleted.InvokeAsync(Email.Id); - GlobalNotificationService.AddSuccessMessage(Localizer["EmailDeletedSuccess"], true); + GlobalNotificationService.AddSuccessMessage(_emailDeletedSuccess, true); } else { - GlobalNotificationService.AddErrorMessage(Localizer["EmailDeleteFailed"], true); + GlobalNotificationService.AddErrorMessage(_emailDeleteFailed, true); } } catch (Exception ex) { - GlobalNotificationService.AddErrorMessage($"{Localizer["EmailDeleteFailed"]}: {ex.Message}", true); + GlobalNotificationService.AddErrorMessage($"{_emailDeleteFailed}: {ex.Message}", true); } } /// protected override async Task OnInitializedAsync() { + // Cache localized strings + _fromLabel = Localizer["FromLabel"]; + _toLabel = Localizer["ToLabel"]; + _dateLabel = Localizer["DateLabel"]; + _actionsLabel = Localizer["ActionsLabel"]; + _deleteButton = Localizer["DeleteButton"]; + _attachmentsLabel = Localizer["AttachmentsLabel"]; + _closeButton = Localizer["CloseButton"]; + _deleteEmailTitle = Localizer["DeleteEmailTitle"]; + _deleteEmailConfirmation = Localizer["DeleteEmailConfirmation"]; + _emailDeletedSuccess = Localizer["EmailDeletedSuccess"]; + _emailDeleteFailed = Localizer["EmailDeleteFailed"]; + _genericError = Localizer["GenericError"]; + _noEmailBody = Localizer["NoEmailBody"]; + _attachmentDownloadFailed = Localizer["AttachmentDownloadFailed"]; + _attachmentDownloadError = Localizer["AttachmentDownloadError"]; + await base.OnInitializedAsync(); // Determine email body @@ -255,7 +289,7 @@ else { // No HTML and no plain text available - EmailBody = Localizer["NoEmailBody"]; + EmailBody = _noEmailBody; } } } @@ -282,7 +316,7 @@ } else { - GlobalNotificationService.AddErrorMessage(Localizer["AttachmentDownloadFailed"], true); + GlobalNotificationService.AddErrorMessage(_attachmentDownloadFailed, true); } } else @@ -305,13 +339,13 @@ } else { - GlobalNotificationService.AddErrorMessage(Localizer["AttachmentDownloadFailed"], true); + GlobalNotificationService.AddErrorMessage(_attachmentDownloadFailed, true); } } } catch (Exception ex) { - GlobalNotificationService.AddErrorMessage($"{Localizer["AttachmentDownloadError"]}: {ex.Message}", true); + GlobalNotificationService.AddErrorMessage($"{_attachmentDownloadError}: {ex.Message}", true); } } } diff --git a/apps/server/AliasVault.Client/Main/Components/Email/EmailPreview.razor b/apps/server/AliasVault.Client/Main/Components/Email/EmailPreview.razor index 2f4b5b262..235fa5a6e 100644 --- a/apps/server/AliasVault.Client/Main/Components/Email/EmailPreview.razor +++ b/apps/server/AliasVault.Client/Main/Components/Email/EmailPreview.razor @@ -37,14 +37,14 @@
-

@Localizer["FromLabel"] @(Email.FromLocal)@@@(Email.FromDomain)

-

@Localizer["ToLabel"] @(Email.ToLocal)@@@(Email.ToDomain)

+

@_fromLabel @(Email.FromLocal)@@@(Email.FromDomain)

+

@_toLabel @(Email.ToLocal)@@@(Email.ToDomain)

-

@Localizer["DateLabel"] @Email.DateSystem

+

@_dateLabel @Email.DateSystem

@if (!string.IsNullOrEmpty(CredentialName) && CredentialId != Guid.Empty) { -

@Localizer["CredentialLabel"] +

@_credentialLabel

@@ -73,7 +73,7 @@ {
-

@Localizer["AttachmentsLabel"]

+

@_attachmentsLabel

@foreach (var attachment in Email.Attachments) { @@ -99,7 +99,7 @@ -

@Localizer["SelectEmailMessage"]

+

@_selectEmailMessage

} @@ -107,6 +107,23 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Email.EmailPreview", "AliasVault.Client"); + + // Cached localized strings + private string _fromLabel = ""; + private string _toLabel = ""; + private string _dateLabel = ""; + private string _credentialLabel = ""; + private string _noneValue = ""; + private string _attachmentsLabel = ""; + private string _selectEmailMessage = ""; + private string _deleteEmailTitle = ""; + private string _deleteEmailConfirmation = ""; + private string _emailDeletedSuccess = ""; + private string _emailDeleteFailed = ""; + private string _genericError = ""; + private string _noEmailBody = ""; + private string _attachmentDownloadFailed = ""; + private string _attachmentDownloadError = ""; /// /// The email to show in the preview. /// @@ -159,8 +176,8 @@ } var result = await ConfirmModalService.ShowConfirmation( - Localizer["DeleteEmailTitle"], - Localizer["DeleteEmailConfirmation"] + _deleteEmailTitle, + _deleteEmailConfirmation ); if (result) @@ -211,17 +228,17 @@ if (response.IsSuccessStatusCode) { await OnEmailDeleted.InvokeAsync(Email.Id); - GlobalNotificationService.AddSuccessMessage(Localizer["EmailDeletedSuccess"], true); + GlobalNotificationService.AddSuccessMessage(_emailDeletedSuccess, true); } else { var errorMessage = await response.Content.ReadAsStringAsync(); - GlobalNotificationService.AddErrorMessage($"{Localizer["EmailDeleteFailed"]}: {errorMessage}", true); + GlobalNotificationService.AddErrorMessage($"{_emailDeleteFailed}: {errorMessage}", true); } } catch (Exception ex) { - GlobalNotificationService.AddErrorMessage($"{Localizer["GenericError"]}: {ex.Message}", true); + GlobalNotificationService.AddErrorMessage($"{_genericError}: {ex.Message}", true); } } @@ -241,19 +258,41 @@ if (response.IsSuccessStatusCode) { await OnEmailDeleted.InvokeAsync(Email.Id); - GlobalNotificationService.AddSuccessMessage(Localizer["EmailDeletedSuccess"], true); + GlobalNotificationService.AddSuccessMessage(_emailDeletedSuccess, true); } else { - GlobalNotificationService.AddErrorMessage(Localizer["EmailDeleteFailed"], true); + GlobalNotificationService.AddErrorMessage(_emailDeleteFailed, true); } } catch (Exception ex) { - GlobalNotificationService.AddErrorMessage($"{Localizer["EmailDeleteFailed"]}: {ex.Message}", true); + GlobalNotificationService.AddErrorMessage($"{_emailDeleteFailed}: {ex.Message}", true); } } + protected override async Task OnInitializedAsync() + { + // Cache localized strings + _fromLabel = Localizer["FromLabel"]; + _toLabel = Localizer["ToLabel"]; + _dateLabel = Localizer["DateLabel"]; + _credentialLabel = Localizer["CredentialLabel"]; + _noneValue = Localizer["NoneValue"]; + _attachmentsLabel = Localizer["AttachmentsLabel"]; + _selectEmailMessage = Localizer["SelectEmailMessage"]; + _deleteEmailTitle = Localizer["DeleteEmailTitle"]; + _deleteEmailConfirmation = Localizer["DeleteEmailConfirmation"]; + _emailDeletedSuccess = Localizer["EmailDeletedSuccess"]; + _emailDeleteFailed = Localizer["EmailDeleteFailed"]; + _genericError = Localizer["GenericError"]; + _noEmailBody = Localizer["NoEmailBody"]; + _attachmentDownloadFailed = Localizer["AttachmentDownloadFailed"]; + _attachmentDownloadError = Localizer["AttachmentDownloadError"]; + + await base.OnInitializedAsync(); + } + /// protected override async Task OnParametersSetAsync() { @@ -275,7 +314,7 @@ else { // No HTML and no plain text available - EmailBody = Localizer["NoEmailBody"]; + EmailBody = _noEmailBody; } } } @@ -302,7 +341,7 @@ } else { - GlobalNotificationService.AddErrorMessage(Localizer["AttachmentDownloadFailed"], true); + GlobalNotificationService.AddErrorMessage(_attachmentDownloadFailed, true); } } else @@ -325,13 +364,13 @@ } else { - GlobalNotificationService.AddErrorMessage(Localizer["AttachmentDownloadFailed"], true); + GlobalNotificationService.AddErrorMessage(_attachmentDownloadFailed, true); } } } catch (Exception ex) { - GlobalNotificationService.AddErrorMessage($"{Localizer["AttachmentDownloadError"]}: {ex.Message}", true); + GlobalNotificationService.AddErrorMessage($"{_attachmentDownloadError}: {ex.Message}", true); } } } diff --git a/apps/server/AliasVault.Client/Main/Components/Email/EmailRow.razor b/apps/server/AliasVault.Client/Main/Components/Email/EmailRow.razor index 27dd3261e..3081fccf4 100644 --- a/apps/server/AliasVault.Client/Main/Components/Email/EmailRow.razor +++ b/apps/server/AliasVault.Client/Main/Components/Email/EmailRow.razor @@ -21,7 +21,7 @@ } @if (IsNewEmail) { -
+
}
@@ -44,6 +44,10 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Email.EmailRow", "AliasVault.Client"); + + // Cached localized strings + private string _newEmailTooltip = ""; + /// /// The email model. /// @@ -68,4 +72,12 @@ /// [Parameter] public bool IsNewEmail { get; set; } + + protected override async Task OnInitializedAsync() + { + // Cache localized strings + _newEmailTooltip = Localizer["NewEmailTooltip"]; + + await base.OnInitializedAsync(); + } } diff --git a/apps/server/AliasVault.Client/Main/Components/Email/RecentEmails.razor b/apps/server/AliasVault.Client/Main/Components/Email/RecentEmails.razor index 72919a401..044df74ab 100644 --- a/apps/server/AliasVault.Client/Main/Components/Email/RecentEmails.razor +++ b/apps/server/AliasVault.Client/Main/Components/Email/RecentEmails.razor @@ -26,12 +26,12 @@
-

@Localizer["EmailSectionTitle"]

+

@_emailSectionTitle

@if (DbService.Settings.AutoEmailRefresh) { -
+
}
@code { private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Forms.CopyPasteFormRow", "AliasVault.Client"); + + // Cached localized strings + private string _copiedMessage = ""; /// /// Id for the input field. Defaults to a random GUID if not provided. @@ -45,6 +48,9 @@ /// protected override void OnInitialized() { + // Cache localized strings + _copiedMessage = Localizer["CopiedMessage"]; + ClipboardCopyService.OnCopy += HandleCopy; } diff --git a/apps/server/AliasVault.Client/Main/Components/Forms/EditEmailFormRow.razor b/apps/server/AliasVault.Client/Main/Components/Forms/EditEmailFormRow.razor index 2cee6699b..bd036f402 100644 --- a/apps/server/AliasVault.Client/Main/Components/Forms/EditEmailFormRow.razor +++ b/apps/server/AliasVault.Client/Main/Components/Forms/EditEmailFormRow.razor @@ -20,13 +20,13 @@ @if (IsCustomDomain) { } else { }
@@ -36,13 +36,13 @@
-

@Localizer["SelectEmailDomainTitle"]

+

@_selectEmailDomainTitle

@if (ShowPrivateDomains) {
-

@Localizer["PrivateEmailTitle"]

-

@Localizer["PrivateEmailDescription"]

+

@_privateEmailTitle

+

@_privateEmailDescription

@foreach (var domain in PrivateDomains) {
}
-

@Localizer["PublicEmailTitle"]

-

@Localizer["PublicEmailDescription"]

+

@_publicEmailTitle

+

@_publicEmailDescription

@foreach (var domain in PublicDomains) { - @Localizer["PasswordGeneratorSettingsDescription"] + @_passwordGeneratorSettingsDescription
@@ -23,6 +23,11 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Settings.DefaultPasswordSettings", "AliasVault.Client"); + + // Cached localized strings + private string _passwordGeneratorSettingsLabel = ""; + private string _configureButton = ""; + private string _passwordGeneratorSettingsDescription = ""; private PasswordSettings PasswordSettings { get; set; } = new(); private bool IsSettingsVisible { get; set; } @@ -30,6 +35,11 @@ /// protected override async Task OnInitializedAsync() { + // Cache localized strings + _passwordGeneratorSettingsLabel = Localizer["PasswordGeneratorSettingsLabel"]; + _configureButton = Localizer["ConfigureButton"]; + _passwordGeneratorSettingsDescription = Localizer["PasswordGeneratorSettingsDescription"]; + await base.OnInitializedAsync(); PasswordSettings = DbService.Settings.PasswordSettings; } diff --git a/apps/server/AliasVault.Client/Main/Components/Settings/PasswordSettingsPopup.razor b/apps/server/AliasVault.Client/Main/Components/Settings/PasswordSettingsPopup.razor index 2de8d6601..730bfaa22 100644 --- a/apps/server/AliasVault.Client/Main/Components/Settings/PasswordSettingsPopup.razor +++ b/apps/server/AliasVault.Client/Main/Components/Settings/PasswordSettingsPopup.razor @@ -10,10 +10,10 @@
-

@Localizer["Title"]

+

@_title

- + @@ -22,35 +22,35 @@
- +
- +
- +
- +
- +
- +
@if (IsTemporary) { }
@@ -83,6 +83,20 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Settings.PasswordSettingsPopup", "AliasVault.Client"); + + // Cached localized strings + private string _title = ""; + private string _passwordLengthLabel = ""; + private string _includeLowercaseLabel = ""; + private string _includeUppercaseLabel = ""; + private string _includeNumbersLabel = ""; + private string _includeSpecialCharsLabel = ""; + private string _avoidAmbiguousCharsLabel = ""; + private string _previewLabel = ""; + private string _cancelButton = ""; + private string _useJustOnceButton = ""; + private string _saveGloballyButton = ""; + private string _settingsUpdatedMessage = ""; /// /// The PasswordSettings to mutate. @@ -122,6 +136,20 @@ /// protected override async Task OnInitializedAsync() { + // Cache localized strings + _title = Localizer["Title"]; + _passwordLengthLabel = Localizer["PasswordLengthLabel"]; + _includeLowercaseLabel = Localizer["IncludeLowercaseLabel"]; + _includeUppercaseLabel = Localizer["IncludeUppercaseLabel"]; + _includeNumbersLabel = Localizer["IncludeNumbersLabel"]; + _includeSpecialCharsLabel = Localizer["IncludeSpecialCharsLabel"]; + _avoidAmbiguousCharsLabel = Localizer["AvoidAmbiguousCharsLabel"]; + _previewLabel = Localizer["PreviewLabel"]; + _cancelButton = Localizer["CancelButton"]; + _useJustOnceButton = Localizer["UseJustOnceButton"]; + _saveGloballyButton = Localizer["SaveGloballyButton"]; + _settingsUpdatedMessage = Localizer["SettingsUpdatedMessage"]; + // Clone the settings to avoid reference issues _workingSettings = new PasswordSettings { @@ -182,7 +210,7 @@ var settingsJson = System.Text.Json.JsonSerializer.Serialize(_workingSettings); await DbService.Settings.SetSettingAsync("PasswordGenerationSettings", settingsJson); GlobalLoadingService.Hide(); - GlobalNotificationService.AddSuccessMessage(Localizer["SettingsUpdatedMessage"], true); + GlobalNotificationService.AddSuccessMessage(_settingsUpdatedMessage, true); // Notify parent with both settings and the generated password. await OnSaveSettings.InvokeAsync((_workingSettings, _previewPassword)); diff --git a/apps/server/AliasVault.Client/Main/Components/TotpCodes/TotpCodes.razor b/apps/server/AliasVault.Client/Main/Components/TotpCodes/TotpCodes.razor index 06e30385e..2575b82e1 100644 --- a/apps/server/AliasVault.Client/Main/Components/TotpCodes/TotpCodes.razor +++ b/apps/server/AliasVault.Client/Main/Components/TotpCodes/TotpCodes.razor @@ -9,13 +9,13 @@
-

@Localizer["TwoFactorAuthenticationTitle"]

+

@_twoFactorAuthenticationTitle

@if (TotpCodeList.Any(t => !t.IsDeleted) && !IsAddFormVisible) {
} @@ -24,7 +24,7 @@ @if ((TotpCodeList.Count == 0 || TotpCodeList.All(t => t.IsDeleted)) && !IsAddFormVisible) { } else @@ -35,28 +35,28 @@
-

@Localizer["AddTotpCodeModalTitle"]

+

@_addTotpCodeModalTitle

-

@Localizer["TotpInstructions"]

+

@_totpInstructions

- +
- - + +
@@ -73,7 +73,7 @@
-
@Localizer["SaveToViewCodeMessage"]
+
@_saveToViewCodeMessage
@if (IsPopupVisible) @@ -15,25 +15,25 @@
-

@Localizer["CreateNewAliasTitle"]

+

@_createNewAliasTitle

- +
- +
@@ -43,6 +43,19 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Widgets.CreateNewIdentityWidget", "AliasVault.Client"); + + // Cached localized strings + private string _newAliasButtonShort = ""; + private string _newAliasButtonText = ""; + private string _createNewAliasTitle = ""; + private string _serviceNameLabel = ""; + private string _serviceNamePlaceholder = ""; + private string _serviceUrlLabel = ""; + private string _createButton = ""; + private string _advancedModeLink = ""; + private string _creatingNewAliasMessage = ""; + private string _createCredentialErrorMessage = ""; + private string _credentialCreatedSuccessMessage = ""; private bool IsPopupVisible = false; private bool IsCreating = false; @@ -61,6 +74,24 @@ } } + protected override async Task OnInitializedAsync() + { + // Cache localized strings + _newAliasButtonShort = Localizer["NewAliasButtonShort"]; + _newAliasButtonText = Localizer["NewAliasButtonText"]; + _createNewAliasTitle = Localizer["CreateNewAliasTitle"]; + _serviceNameLabel = Localizer["ServiceNameLabel"]; + _serviceNamePlaceholder = Localizer["ServiceNamePlaceholder"]; + _serviceUrlLabel = Localizer["ServiceUrlLabel"]; + _createButton = Localizer["CreateButton"]; + _advancedModeLink = Localizer["AdvancedModeLink"]; + _creatingNewAliasMessage = Localizer["CreatingNewAliasMessage"]; + _createCredentialErrorMessage = Localizer["CreateCredentialErrorMessage"]; + _credentialCreatedSuccessMessage = Localizer["CredentialCreatedSuccessMessage"]; + + await base.OnInitializedAsync(); + } + /// protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -150,7 +181,7 @@ } IsCreating = true; - GlobalLoadingSpinner.Show(Localizer["CreatingNewAliasMessage"]); + GlobalLoadingSpinner.Show(_creatingNewAliasMessage); StateHasChanged(); var credential = new Credential(); @@ -173,12 +204,12 @@ // Error saving. IsCreating = false; GlobalLoadingSpinner.Hide(); - GlobalNotificationService.AddErrorMessage(Localizer["CreateCredentialErrorMessage"], true); + GlobalNotificationService.AddErrorMessage(_createCredentialErrorMessage, true); return; } // No error, add success message. - GlobalNotificationService.AddSuccessMessage(Localizer["CredentialCreatedSuccessMessage"]); + GlobalNotificationService.AddSuccessMessage(_credentialCreatedSuccessMessage); NavigationManager.NavigateTo("/credentials/" + id); diff --git a/apps/server/AliasVault.Client/Main/Components/Widgets/SearchWidget.razor b/apps/server/AliasVault.Client/Main/Components/Widgets/SearchWidget.razor index 8e6c4cd56..b7b2294c9 100644 --- a/apps/server/AliasVault.Client/Main/Components/Widgets/SearchWidget.razor +++ b/apps/server/AliasVault.Client/Main/Components/Widgets/SearchWidget.razor @@ -6,78 +6,90 @@ @implements IAsyncDisposable @using Microsoft.Extensions.Localization - -
- +
+ - @if (ShowHelpText) - { -
- @if (string.IsNullOrEmpty(SearchTerm)) - { -

@Localizer["SearchHelpText"]

- } - else if (SearchTerm.Length == 1) - { -

@Localizer["SearchTooShortMessage"]

- } - else - { -

@string.Format(Localizer["SearchingForMessage"], SearchTerm)

- } -
- } - - @if (ShowResults && SearchTerm.Length >= 2) - { - @if (SearchResults.Any()) + @if (ShowHelpText || ShowResults) + { + + @if (ShowHelpText) { -
- @for (int i = 0; i < SearchResults.Count; i++) +
+ @if (string.IsNullOrEmpty(SearchTerm)) { - var result = SearchResults[i]; -
- -
-
@result.Service.Name
- @if (!string.IsNullOrEmpty(result.Alias.Email)) - { - (@result.Alias.Email) - } - else if (!string.IsNullOrEmpty(result.Username)) - { - (@result.Username) - } -
-
+

@_searchHelpText

+ } + else if (SearchTerm.Length == 1) + { +

@_searchTooShortMessage

+ } + else + { +

@string.Format(_searchingForMessage, SearchTerm)

}
} - else + + @if (ShowResults && SearchTerm.Length >= 2) { -
-
- @Localizer["NoResultsFoundMessage"] + @if (SearchResults.Any()) + { +
+ @for (int i = 0; i < SearchResults.Count; i++) + { + var result = SearchResults[i]; +
+ +
+
@result.Service.Name
+ @if (!string.IsNullOrEmpty(result.Alias.Email)) + { + (@result.Alias.Email) + } + else if (!string.IsNullOrEmpty(result.Username)) + { + (@result.Username) + } +
+
+ }
-
+ } + else + { +
+
+ @_noResultsFoundMessage +
+
+ } } - } -
- + + } +
@code { - private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Widgets.SearchWidget", "AliasVault.Client"); + private IStringLocalizer? _localizer; + private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Widgets.SearchWidget", "AliasVault.Client"); + + // Cached localized strings + private string _searchPlaceholder = string.Empty; + private string _searchHelpText = string.Empty; + private string _searchTooShortMessage = string.Empty; + private string _searchingForMessage = string.Empty; + private string _noResultsFoundMessage = string.Empty; + private string SearchTerm { get; set; } = string.Empty; private List SearchResults { get; set; } = new(); private bool ShowResults { get; set; } @@ -99,6 +111,13 @@ if (firstRender) { + // Initialize localized strings once + _searchPlaceholder = Localizer["SearchVaultPlaceholder"]; + _searchHelpText = Localizer["SearchHelpText"]; + _searchTooShortMessage = Localizer["SearchTooShortMessage"]; + _searchingForMessage = Localizer["SearchingForMessage"]; + _noResultsFoundMessage = Localizer["NoResultsFoundMessage"]; + await KeyboardShortcutService.RegisterShortcutAsync("gs", FocusSearchField); await KeyboardShortcutService.RegisterShortcutAsync("gf", FocusSearchField); NavigationManager.LocationChanged += ResetSearchField; diff --git a/apps/server/AliasVault.Client/Main/Layout/Footer.razor b/apps/server/AliasVault.Client/Main/Layout/Footer.razor index 7554e1697..a65d018f9 100644 --- a/apps/server/AliasVault.Client/Main/Layout/Footer.razor +++ b/apps/server/AliasVault.Client/Main/Layout/Footer.razor @@ -8,7 +8,7 @@

- © 2024 @AppInfo.ApplicationName v@(AppInfo.GetFullVersion()). @Localizer["CopyrightText"] + © 2024 @AppInfo.ApplicationName v@(AppInfo.GetFullVersion()). @_copyrightText

    @@ -29,14 +29,9 @@ private IStringLocalizer Localizer => LocalizerFactory.Create("Layout.Footer", "AliasVault.Client"); - private string[] Quotes => - [ - Localizer["TipCreateShortcut"], - Localizer["TipFindShortcut"], - Localizer["TipHomeShortcut"], - Localizer["TipLockShortcut"], - ]; - + // Cached localized strings + private string _copyrightText = string.Empty; + private string[] _quotes = []; private string _randomQuote = string.Empty; /// @@ -50,7 +45,16 @@ { if (firstRender) { - _randomQuote = Quotes[Random.Shared.Next(Quotes.Length)]; + // Cache localized strings to prevent infinite rendering loops + _copyrightText = Localizer["CopyrightText"]; + _quotes = [ + Localizer["TipCreateShortcut"], + Localizer["TipFindShortcut"], + Localizer["TipHomeShortcut"], + Localizer["TipLockShortcut"], + ]; + + _randomQuote = _quotes[Random.Shared.Next(_quotes.Length)]; NavigationManager.LocationChanged += RefreshQuote; } } @@ -60,7 +64,7 @@ ///
private void RefreshQuote(object? sender, LocationChangedEventArgs e) { - _randomQuote = Quotes[Random.Shared.Next(Quotes.Length)]; + _randomQuote = _quotes[Random.Shared.Next(_quotes.Length)]; StateHasChanged(); } } diff --git a/apps/server/AliasVault.Client/Main/Layout/TopMenu.razor b/apps/server/AliasVault.Client/Main/Layout/TopMenu.razor index 83b9e5873..82f27f671 100644 --- a/apps/server/AliasVault.Client/Main/Layout/TopMenu.razor +++ b/apps/server/AliasVault.Client/Main/Layout/TopMenu.razor @@ -1,5 +1,7 @@ @inherits AliasVault.Client.Main.Pages.MainBase @using Microsoft.Extensions.Localization +@using AliasVault.Client.Services +@inject LanguageService LanguageService @implements IDisposable
@@ -10,17 +12,17 @@ AliasVault Logo @@ -36,7 +38,7 @@
@@ -45,12 +47,12 @@
  • - @Localizer["CredentialsNav"] + @_credentialsNav
  • - @Localizer["EmailsNav"] + @_emailsNav
@@ -60,37 +62,37 @@
  • - @Localizer["GeneralSettingsNav"] + @_generalSettingsNav
  • - @Localizer["SecuritySettingsNav"] + @_securitySettingsNav
  • - @Localizer["ImportExportNav"] + @_importExportNav
  • - @Localizer["ExtensionsAppsNav"] + @_extensionsAppsNav - @Localizer["NewLabel"] + @_newLabel
  • - @Localizer["LogOut"] + @_logOut
@@ -101,11 +103,25 @@ @code { - private IStringLocalizer Localizer => LocalizerFactory.Create("Layout.TopMenu", "AliasVault.Client"); + private IStringLocalizer? _localizer; + private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Layout.TopMenu", "AliasVault.Client"); private bool IsUserMenuOpen { get; set; } = false; private bool IsMobileMenuOpen { get; set; } = false; private string Username { get; set; } = string.Empty; + // Cached localized strings + private string _betaLabel = string.Empty; + private string _credentialsNav = string.Empty; + private string _emailsNav = string.Empty; + private string _openMenuLabel = string.Empty; + private string _generalSettingsNav = string.Empty; + private string _securitySettingsNav = string.Empty; + private string _importExportNav = string.Empty; + private string _extensionsAppsNav = string.Empty; + private string _newLabel = string.Empty; + private string _toggleDarkMode = string.Empty; + private string _logOut = string.Empty; + /// /// Close the menu. /// @@ -132,6 +148,7 @@ public void Dispose() { NavigationManager.LocationChanged -= LocationChanged; + LanguageService.LanguageChanged -= OnLanguageChanged; } /// @@ -140,6 +157,40 @@ await base.OnInitializedAsync(); Username = await GetUsernameAsync(); NavigationManager.LocationChanged += LocationChanged; + LanguageService.LanguageChanged += OnLanguageChanged; + + // Cache localized strings to prevent infinite rendering loops + RefreshLocalizedStrings(); + } + + /// + /// Refreshes all cached localized strings. + /// + private void RefreshLocalizedStrings() + { + _betaLabel = Localizer["BetaLabel"]; + _credentialsNav = Localizer["CredentialsNav"]; + _emailsNav = Localizer["EmailsNav"]; + _openMenuLabel = Localizer["OpenMenuLabel"]; + _generalSettingsNav = Localizer["GeneralSettingsNav"]; + _securitySettingsNav = Localizer["SecuritySettingsNav"]; + _importExportNav = Localizer["ImportExportNav"]; + _extensionsAppsNav = Localizer["ExtensionsAppsNav"]; + _newLabel = Localizer["NewLabel"]; + _toggleDarkMode = Localizer["ToggleDarkMode"]; + _logOut = Localizer["LogOut"]; + } + + /// + /// Handles language change events. + /// + /// The new language code. + private void OnLanguageChanged(string newLanguage) + { + // Re-create the localizer to pick up the new culture + _localizer = null; + RefreshLocalizedStrings(); + StateHasChanged(); } /// diff --git a/apps/server/AliasVault.Client/Main/Pages/Credentials/Home.razor b/apps/server/AliasVault.Client/Main/Pages/Credentials/Home.razor index 626224cb9..94e938b01 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Credentials/Home.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Credentials/Home.razor @@ -8,8 +8,8 @@ + Title="@_pageTitle" + Description="@_pageDescription">
@@ -182,6 +182,20 @@ else @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Emails.Home", "AliasVault.Client"); + + // Cached localized strings + private string _pageTitle = string.Empty; + private string _pageDescription = string.Empty; + private string _autoRefreshEnabledTooltip = string.Empty; + private string _refreshButton = string.Empty; + private string _noEmailClaimsMessage = string.Empty; + private string _noEmailsReceivedMessage = string.Empty; + private string _loadingText = string.Empty; + private string _loadMoreButtonText = string.Empty; + private string _claimDoesNotExistError = string.Empty; + private string _loadEmailsFailedMessage = string.Empty; + private string _loadMoreEmailsFailedMessage = string.Empty; + private List EmailList { get; set; } = []; private bool IsLoading => LoadingService.IsLoading("emails"); private int CurrentPage { get; set; } = 1; @@ -361,7 +375,7 @@ else switch (errorResponse.Code) { case "CLAIM_DOES_NOT_EXIST": - GlobalNotificationService.AddErrorMessage(Localizer["ClaimDoesNotExistError"], true); + GlobalNotificationService.AddErrorMessage(_claimDoesNotExistError, true); break; default: throw new ArgumentException(errorResponse.Message); @@ -447,6 +461,19 @@ else { await base.OnInitializedAsync(); + // Cache localized strings to prevent infinite rendering loops + _pageTitle = Localizer["PageTitle"]; + _pageDescription = Localizer["PageDescription"]; + _autoRefreshEnabledTooltip = Localizer["AutoRefreshEnabledTooltip"]; + _refreshButton = Localizer["RefreshButton"]; + _noEmailClaimsMessage = Localizer["NoEmailClaimsMessage"]; + _noEmailsReceivedMessage = Localizer["NoEmailsReceivedMessage"]; + _loadingText = Localizer["LoadingText"]; + _loadMoreButtonText = Localizer["LoadMoreButtonText"]; + _claimDoesNotExistError = Localizer["ClaimDoesNotExistError"]; + _loadEmailsFailedMessage = Localizer["LoadEmailsFailedMessage"]; + _loadMoreEmailsFailedMessage = Localizer["LoadMoreEmailsFailedMessage"]; + // Create a single object reference for JS interop _dotNetRef = DotNetObjectReference.Create(this); await JsInteropService.RegisterVisibilityCallback(_dotNetRef); @@ -634,7 +661,7 @@ else } catch (Exception ex) { - GlobalNotificationService.AddErrorMessage(string.Format(Localizer["LoadEmailsFailedMessage"], ex.Message), true); + GlobalNotificationService.AddErrorMessage(string.Format(_loadEmailsFailedMessage, ex.Message), true); Logger.LogError(ex, "An error occurred while loading email for preview"); } } @@ -730,7 +757,7 @@ else } catch (Exception ex) { - GlobalNotificationService.AddErrorMessage(string.Format(Localizer["LoadMoreEmailsFailedMessage"], ex.Message), true); + GlobalNotificationService.AddErrorMessage(string.Format(_loadMoreEmailsFailedMessage, ex.Message), true); Logger.LogError(ex, "An error occurred while loading more emails"); } finally diff --git a/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor b/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor index f411623f3..2fb2e830a 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Settings/ImportExport/ImportExport.razor @@ -8,18 +8,18 @@ @using AliasVault.Client.Main.Pages.Settings.ImportExport.Components @using AliasVault.ImportExport -@Localizer["PageTitle"] +@_pageTitle + Title="@_pageTitle" + Description="@_pageDescription">
-

@Localizer["ImportSectionTitle"]

+

@_importSectionTitle

- @((MarkupString)Localizer["ImportSectionDescription"].Value) + @((MarkupString)_importSectionDescription)
@@ -38,16 +38,16 @@
-

@Localizer["ExportSectionTitle"]

+

@_exportSectionTitle

- @Localizer["ExportSectionDescription"] + @_exportSectionDescription

- +
- +
@@ -56,6 +56,18 @@ private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Settings.ImportExport.ImportExport", "AliasVault.Client"); private ExportType _currentExportType; + // Cached localized strings for performance + private string _pageTitle = string.Empty; + private string _pageDescription = string.Empty; + private string _importSectionTitle = string.Empty; + private string _importSectionDescription = string.Empty; + private string _exportSectionTitle = string.Empty; + private string _exportSectionDescription = string.Empty; + private string _exportCsvButton = string.Empty; + private string _exportSqliteButton = string.Empty; + private string _exportWarningMessage = string.Empty; + private string _exportConfirmTitle = string.Empty; + private enum ExportType { Csv, @@ -66,15 +78,28 @@ protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["PageTitle"] }); + + // Cache localized strings for performance + _pageTitle = Localizer["PageTitle"]; + _pageDescription = Localizer["PageDescription"]; + _importSectionTitle = Localizer["ImportSectionTitle"]; + _importSectionDescription = Localizer["ImportSectionDescription"]; + _exportSectionTitle = Localizer["ExportSectionTitle"]; + _exportSectionDescription = Localizer["ExportSectionDescription"]; + _exportCsvButton = Localizer["ExportCsvButton"]; + _exportSqliteButton = Localizer["ExportSqliteButton"]; + _exportWarningMessage = Localizer["ExportWarningMessage"]; + _exportConfirmTitle = Localizer["ExportConfirmTitle"]; + + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _pageTitle }); } private async Task ShowExportConfirmation(ExportType exportType) { _currentExportType = exportType; - var confirmMessage = Localizer["ExportWarningMessage"]; + var confirmMessage = _exportWarningMessage; - var result = await ConfirmModalService.ShowConfirmation(Localizer["ExportConfirmTitle"], confirmMessage, SharedLocalizer["Confirm"], SharedLocalizer["Cancel"]); + var result = await ConfirmModalService.ShowConfirmation(_exportConfirmTitle, confirmMessage, SharedLocalizer["Confirm"], SharedLocalizer["Cancel"]); if (!result) { return; diff --git a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/ChangePassword.razor b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/ChangePassword.razor index 9e0a642cf..2547d3c3a 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/ChangePassword.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/ChangePassword.razor @@ -9,13 +9,13 @@ @inherits MainBase @inject HttpClient Http -@Localizer["PageTitle"] +@_pageTitle
-

@Localizer["PageTitle"]

-

@Localizer["PageDescription"]

+

@_pageTitle

+

@_pageDescription

@@ -31,21 +31,21 @@ else
- +
- +
- + @@ -53,7 +53,7 @@ else
@@ -70,8 +70,24 @@ else /// private PasswordChangeFormModel PasswordChangeFormModel { get; set; } = new(); - private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Pages.Settings.Security.ChangePassword", "AliasVault.Client"); - private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client"); + private IStringLocalizer? _localizer; + private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Pages.Settings.Security.ChangePassword", "AliasVault.Client"); + private IStringLocalizer? _apiErrorLocalizer; + private IStringLocalizer ApiErrorLocalizer => _apiErrorLocalizer ??= LocalizerFactory.Create("ApiErrors", "AliasVault.Client"); + + // Cached localized strings for performance + private string _pageTitle = string.Empty; + private string _pageDescription = string.Empty; + private string _breadcrumbSecuritySettings = string.Empty; + private string _breadcrumbChangePassword = string.Empty; + private string _currentPasswordLabel = string.Empty; + private string _newPasswordLabel = string.Empty; + private string _confirmNewPasswordLabel = string.Empty; + private string _changePasswordButton = string.Empty; + private string _failedToInitiatePasswordChange = string.Empty; + private string _changingPasswordMessage = string.Empty; + private string _failedToChangePassword = string.Empty; + private string _passwordChangedSuccessfully = string.Empty; /// /// Gets or sets the current user's password salt. @@ -101,8 +117,22 @@ else { await base.OnInitializedAsync(); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbSecuritySettings"], Url = "/settings/security" }); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbChangePassword"] }); + // Cache localized strings for performance + _pageTitle = Localizer["PageTitle"]; + _pageDescription = Localizer["PageDescription"]; + _breadcrumbSecuritySettings = Localizer["BreadcrumbSecuritySettings"]; + _breadcrumbChangePassword = Localizer["BreadcrumbChangePassword"]; + _currentPasswordLabel = Localizer["CurrentPasswordLabel"]; + _newPasswordLabel = Localizer["NewPasswordLabel"]; + _confirmNewPasswordLabel = Localizer["ConfirmNewPasswordLabel"]; + _changePasswordButton = Localizer["ChangePasswordButton"]; + _failedToInitiatePasswordChange = Localizer["FailedToInitiatePasswordChange"]; + _changingPasswordMessage = Localizer["ChangingPasswordMessage"]; + _failedToChangePassword = Localizer["FailedToChangePassword"]; + _passwordChangedSuccessfully = Localizer["PasswordChangedSuccessfully"]; + + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbSecuritySettings, Url = "/settings/security" }); + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbChangePassword }); } /// @@ -131,7 +161,7 @@ else if (response == null) { - GlobalNotificationService.AddErrorMessage(Localizer["FailedToInitiatePasswordChange"], true); + GlobalNotificationService.AddErrorMessage(_failedToInitiatePasswordChange, true); IsLoading = false; StateHasChanged(); return; @@ -148,7 +178,7 @@ else /// private async Task InitiatePasswordChange() { - GlobalLoadingSpinner.Show(Localizer["ChangingPasswordMessage"]); + GlobalLoadingSpinner.Show(_changingPasswordMessage); GlobalNotificationService.ClearMessages(); StateHasChanged(); @@ -238,7 +268,7 @@ else } catch { - GlobalNotificationService.AddErrorMessage(Localizer["FailedToChangePassword"], true); + GlobalNotificationService.AddErrorMessage(_failedToChangePassword, true); // Set currentPasswordHash back to original, so we're back to the original state. await AuthService.StoreEncryptionKeyAsync(backupPasswordHash); @@ -249,7 +279,7 @@ else } // Set success message. - GlobalNotificationService.AddSuccessMessage(Localizer["PasswordChangedSuccessfully"], true); + GlobalNotificationService.AddSuccessMessage(_passwordChangedSuccessfully, true); // Get the new password ephemeral and salt from the server, which is required if the usre // wants to change the password again. diff --git a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/DeleteAccount.razor b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/DeleteAccount.razor index c349141cf..17eb90100 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/DeleteAccount.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/DeleteAccount.razor @@ -10,12 +10,12 @@ @using AliasVault.Client.Resources @inject HttpClient Http -@Localizer["PageTitle"] +@_pageTitle
-

@Localizer["PageTitle"]

+

@_pageTitle

@@ -23,25 +23,25 @@ @if (!_showPasswordConfirm) {
- +
-

@Localizer["PleaseNote"]

+

@_pleaseNote

    -
  • @Localizer["VaultsDeletedNote"]
  • -
  • @Localizer["EmailAliasesOrphanedNote"]
  • -
  • @Localizer["AccountCannotBeRecoveredNote"]
  • +
  • @_vaultsDeletedNote
  • +
  • @_emailAliasesOrphanedNote
  • +
  • @_accountCannotBeRecoveredNote
- +
- +
@@ -50,12 +50,12 @@ else {
- +
-

@Localizer["PleaseNote"]

+

@_pleaseNote

    -
  • @Localizer["DeletionIrreversibleNote"]
  • +
  • @_deletionIrreversibleNote
@@ -64,12 +64,12 @@
- +
- +
@@ -88,8 +88,30 @@ /// private readonly DeleteAccountPasswordModel _passwordModel = new(); - private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Pages.Settings.Security.DeleteAccount", "AliasVault.Client"); - private IStringLocalizer ApiErrorLocalizer => LocalizerFactory.Create("ApiErrors", "AliasVault.Client"); + private IStringLocalizer? _localizer; + private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Pages.Settings.Security.DeleteAccount", "AliasVault.Client"); + private IStringLocalizer? _apiErrorLocalizer; + private IStringLocalizer ApiErrorLocalizer => _apiErrorLocalizer ??= LocalizerFactory.Create("ApiErrors", "AliasVault.Client"); + + // Cached localized strings for performance + private string _pageTitle = string.Empty; + private string _breadcrumbSecuritySettings = string.Empty; + private string _breadcrumbDeleteAccount = string.Empty; + private string _permanentActionWarning = string.Empty; + private string _pleaseNote = string.Empty; + private string _vaultsDeletedNote = string.Empty; + private string _emailAliasesOrphanedNote = string.Empty; + private string _accountCannotBeRecoveredNote = string.Empty; + private string _confirmUsernameLabel = string.Empty; + private string _continueWithAccountDeletion = string.Empty; + private string _finalWarning = string.Empty; + private string _deletionIrreversibleNote = string.Empty; + private string _enterPasswordLabel = string.Empty; + private string _deleteMyAccount = string.Empty; + private string _usernameRequired = string.Empty; + private string _usernameDoesNotMatch = string.Empty; + private string _deletingAccountMessage = string.Empty; + private string _errorProcessingRequest = string.Empty; /// /// Whether to show the password confirmation step. @@ -111,8 +133,28 @@ { await base.OnInitializedAsync(); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbSecuritySettings"], Url = "/settings/security" }); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbDeleteAccount"] }); + // Cache localized strings for performance + _pageTitle = Localizer["PageTitle"]; + _breadcrumbSecuritySettings = Localizer["BreadcrumbSecuritySettings"]; + _breadcrumbDeleteAccount = Localizer["BreadcrumbDeleteAccount"]; + _permanentActionWarning = Localizer["PermanentActionWarning"]; + _pleaseNote = Localizer["PleaseNote"]; + _vaultsDeletedNote = Localizer["VaultsDeletedNote"]; + _emailAliasesOrphanedNote = Localizer["EmailAliasesOrphanedNote"]; + _accountCannotBeRecoveredNote = Localizer["AccountCannotBeRecoveredNote"]; + _confirmUsernameLabel = Localizer["ConfirmUsernameLabel"]; + _continueWithAccountDeletion = Localizer["ContinueWithAccountDeletion"]; + _finalWarning = Localizer["FinalWarning"]; + _deletionIrreversibleNote = Localizer["DeletionIrreversibleNote"]; + _enterPasswordLabel = Localizer["EnterPasswordLabel"]; + _deleteMyAccount = Localizer["DeleteMyAccount"]; + _usernameRequired = Localizer["UsernameRequired"]; + _usernameDoesNotMatch = Localizer["UsernameDoesNotMatch"]; + _deletingAccountMessage = Localizer["DeletingAccountMessage"]; + _errorProcessingRequest = Localizer["ErrorProcessingRequest"]; + + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbSecuritySettings, Url = "/settings/security" }); + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbDeleteAccount }); } /// @@ -124,7 +166,7 @@ if (string.IsNullOrEmpty(_usernameModel.Username)) { - GlobalNotificationService.AddErrorMessage(Localizer["UsernameRequired"], true); + GlobalNotificationService.AddErrorMessage(_usernameRequired, true); return; } @@ -132,7 +174,7 @@ var usernameMatches = string.Equals(_usernameModel.Username.Trim(), username.Trim(), StringComparison.OrdinalIgnoreCase); if (!usernameMatches) { - GlobalNotificationService.AddErrorMessage(Localizer["UsernameDoesNotMatch"], true); + GlobalNotificationService.AddErrorMessage(_usernameDoesNotMatch, true); return; } @@ -145,7 +187,7 @@ /// private async Task DeleteAccountConfirmed() { - GlobalLoadingSpinner.Show(Localizer["DeletingAccountMessage"]); + GlobalLoadingSpinner.Show(_deletingAccountMessage); GlobalNotificationService.ClearMessages(); try @@ -167,7 +209,7 @@ var loginResponse = JsonSerializer.Deserialize(responseContent); if (loginResponse == null) { - GlobalNotificationService.AddErrorMessage(Localizer["ErrorProcessingRequest"], true); + GlobalNotificationService.AddErrorMessage(_errorProcessingRequest, true); return; } diff --git a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Disable2Fa.razor b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Disable2Fa.razor index 21991adff..9d00ce628 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Disable2Fa.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Disable2Fa.razor @@ -1,8 +1,9 @@ @page "/settings/security/disable-2fa" @inherits MainBase @inject HttpClient Http +@using Microsoft.Extensions.Localization -Disable two-factor authentication +@_pageTitle @if (IsLoading) { @@ -13,17 +14,17 @@ else
-

Disable two-factor authentication

-

Disabling two-factor authentication means you will be able to login with only your password.

+

@_pageTitle

+

@_pageDescription

- -
Two factor authentication is currently enabled. Disable it in order to be able to access your vault with your password only.
+ +
@_descriptionText
} @@ -31,13 +32,40 @@ else @code { private bool IsLoading { get; set; } = true; + private IStringLocalizer? _localizer; + private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Pages.Settings.Security.Disable2Fa", "AliasVault.Client"); + + // Cached localized strings for performance + private string _pageTitle = string.Empty; + private string _pageDescription = string.Empty; + private string _breadcrumbSecuritySettings = string.Empty; + private string _breadcrumbDisable2Fa = string.Empty; + private string _alertMessage = string.Empty; + private string _descriptionText = string.Empty; + private string _confirmDisableButton = string.Empty; + private string _twoFactorNotEnabled = string.Empty; + private string _twoFactorDisabledSuccess = string.Empty; + private string _failedToDisable2Fa = string.Empty; + /// protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Security settings", Url = "/settings/security" }); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Disable two-factor authentication" }); + // Cache localized strings for performance + _pageTitle = Localizer["PageTitle"]; + _pageDescription = Localizer["PageDescription"]; + _breadcrumbSecuritySettings = Localizer["BreadcrumbSecuritySettings"]; + _breadcrumbDisable2Fa = Localizer["BreadcrumbDisable2Fa"]; + _alertMessage = Localizer["AlertMessage"]; + _descriptionText = Localizer["DescriptionText"]; + _confirmDisableButton = Localizer["ConfirmDisableButton"]; + _twoFactorNotEnabled = Localizer["TwoFactorNotEnabled"]; + _twoFactorDisabledSuccess = Localizer["TwoFactorDisabledSuccess"]; + _failedToDisable2Fa = Localizer["FailedToDisable2Fa"]; + + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbSecuritySettings, Url = "/settings/security" }); + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbDisable2Fa }); } /// @@ -51,7 +79,7 @@ else var response = await Http.GetFromJsonAsync("v1/TwoFactorAuth/status"); if (response is not null && !response.TwoFactorEnabled) { - GlobalNotificationService.AddErrorMessage("Two-factor authentication is not enabled."); + GlobalNotificationService.AddErrorMessage(_twoFactorNotEnabled); NavigationManager.NavigateTo("/settings/security"); return; } @@ -66,13 +94,13 @@ else var response = await Http.PostAsync("v1/TwoFactorAuth/disable", null); if (response.IsSuccessStatusCode) { - GlobalNotificationService.AddSuccessMessage("Two-factor authentication is now successfully disabled."); + GlobalNotificationService.AddSuccessMessage(_twoFactorDisabledSuccess); NavigationManager.NavigateTo("/settings/security"); return; } // Handle error - GlobalNotificationService.AddErrorMessage("Failed to disable two-factor authentication.", true); + GlobalNotificationService.AddErrorMessage(_failedToDisable2Fa, true); StateHasChanged(); } diff --git a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Enable2Fa.razor b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Enable2Fa.razor index b15e5dc9f..c1aa44883 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Enable2Fa.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Enable2Fa.razor @@ -4,13 +4,13 @@ @inherits MainBase @inject HttpClient Http -@Localizer["PageTitle"] +@_pageTitle
-

@Localizer["PageTitle"]

-

@Localizer["PageDescription"]

+

@_pageTitle

+

@_pageDescription

@@ -31,7 +31,7 @@ else

- @Localizer["QrCodeInstructions"] + @_qrCodeInstructions

@Secret
@@ -39,11 +39,11 @@ else
+ placeholder="@_verificationCodePlaceholder"/>
@@ -57,15 +57,38 @@ else private List? RecoveryCodes { get; set; } private readonly VerificationModel VerifyModel = new(); - private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Pages.Settings.Security.Enable2Fa", "AliasVault.Client"); + private IStringLocalizer? _localizer; + private IStringLocalizer Localizer => _localizer ??= LocalizerFactory.Create("Components.Main.Pages.Settings.Security.Enable2Fa", "AliasVault.Client"); + + // Cached localized strings + private string _pageTitle = string.Empty; + private string _pageDescription = string.Empty; + private string _breadcrumbSecuritySettings = string.Empty; + private string _breadcrumbEnable2Fa = string.Empty; + private string _qrCodeInstructions = string.Empty; + private string _verificationCodePlaceholder = string.Empty; + private string _verifyAndEnableButton = string.Empty; + private string _twoFactorEnabledSuccess = string.Empty; + private string _failedToEnable2Fa = string.Empty; /// protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbSecuritySettings"], Url = "/settings/security" }); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbEnable2Fa"] }); + // Initialize localized strings once + _pageTitle = Localizer["PageTitle"]; + _pageDescription = Localizer["PageDescription"]; + _breadcrumbSecuritySettings = Localizer["BreadcrumbSecuritySettings"]; + _breadcrumbEnable2Fa = Localizer["BreadcrumbEnable2Fa"]; + _qrCodeInstructions = Localizer["QrCodeInstructions"]; + _verificationCodePlaceholder = Localizer["VerificationCodePlaceholder"]; + _verifyAndEnableButton = Localizer["VerifyAndEnableButton"]; + _twoFactorEnabledSuccess = Localizer["TwoFactorEnabledSuccess"]; + _failedToEnable2Fa = Localizer["FailedToEnable2Fa"]; + + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbSecuritySettings, Url = "/settings/security" }); + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbEnable2Fa }); } /// @@ -110,7 +133,7 @@ else if (result != null) { - GlobalNotificationService.AddSuccessMessage(Localizer["TwoFactorEnabledSuccess"], true); + GlobalNotificationService.AddSuccessMessage(_twoFactorEnabledSuccess, true); // Show recovery codes. RecoveryCodes = result.RecoveryCodes; @@ -120,7 +143,7 @@ else } } - GlobalNotificationService.AddErrorMessage(Localizer["FailedToEnable2Fa"], true); + GlobalNotificationService.AddErrorMessage(_failedToEnable2Fa, true); StateHasChanged(); } diff --git a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Security.razor b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Security.razor index cdd10e7e2..813755d69 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Security.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Settings/Security/Security.razor @@ -3,14 +3,14 @@ @inherits MainBase @using Microsoft.Extensions.Localization -@Localizer["PageTitle"] +@_pageTitle + Title="@_pageTitle" + Description="@_pageDescription"> - + @@ -23,6 +23,12 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Settings.Security.Security", "AliasVault.Client"); + + // Cached localized strings for performance + private string _pageTitle = string.Empty; + private string _pageDescription = string.Empty; + private string _refreshButton = string.Empty; + private string _breadcrumbTitle = string.Empty; private TwoFactorAuthenticationSection? TwoFactorSection; private QuickVaultUnlockSection? QuickVaultUnlockSection; private ActiveSessionsSection? SessionsSection; @@ -33,7 +39,13 @@ { await base.OnInitializedAsync(); - BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = Localizer["BreadcrumbTitle"] }); + // Cache localized strings for performance + _pageTitle = Localizer["PageTitle"]; + _pageDescription = Localizer["PageDescription"]; + _refreshButton = Localizer["RefreshButton"]; + _breadcrumbTitle = Localizer["BreadcrumbTitle"]; + + BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = _breadcrumbTitle }); } /// diff --git a/apps/server/AliasVault.Client/Main/Pages/Welcome.razor b/apps/server/AliasVault.Client/Main/Pages/Welcome.razor index 487d86a1c..f4996190a 100644 --- a/apps/server/AliasVault.Client/Main/Pages/Welcome.razor +++ b/apps/server/AliasVault.Client/Main/Pages/Welcome.razor @@ -26,7 +26,7 @@ case TutorialStep.Welcome:

- @Localizer["WelcomeMessage"] + @_welcomeMessage

break; @@ -34,13 +34,13 @@ case TutorialStep.HowAliasVaultWorks:

- @Localizer["HowItWorksIntro"] + @_howItWorksIntro

    -
  1. @Localizer["HowItWorksStep1"]
  2. -
  3. @Localizer["HowItWorksStep2"]
  4. -
  5. @Localizer["HowItWorksStep3"]
  6. -
  7. @Localizer["HowItWorksStep4"]
  8. +
  9. @_howItWorksStep1
  10. +
  11. @_howItWorksStep2
  12. +
  13. @_howItWorksStep3
  14. +
  15. @_howItWorksStep4
break; @@ -49,18 +49,18 @@
-

@Localizer["MasterPasswordTipTitle"]

+

@_masterPasswordTipTitle

- @Localizer["MasterPasswordTipContent"] + @_masterPasswordTipContent

-

@Localizer["TwoFactorTipTitle"]

-

@Localizer["TwoFactorTipContent"]

+

@_twoFactorTipTitle

+

@_twoFactorTipContent

-

@Localizer["ExtensionsAppsTipTitle"]

-

@Localizer["ExtensionsAppsTipContent"]

+

@_extensionsAppsTipTitle

+

@_extensionsAppsTipContent

@foreach (var extension in AliasVault.Shared.Core.BrowserExtensions.Constants.Extensions.Where(x => x.Key != BrowserType.Unknown).Select(x => x.Value)) { @@ -80,7 +80,7 @@
@extension.Name - @Localizer["ComingSoonLabel"] + @_comingSoonLabel
} @@ -94,7 +94,7 @@ class="flex flex-col items-center p-3 rounded-lg bg-gray-200 dark:bg-gray-600 hover:bg-gray-300 dark:hover:bg-gray-500 transition-colors"> @app.Name - @Localizer["DownloadForPrefix"] @app.Name + @_downloadForPrefix @app.Name } @@ -103,7 +103,7 @@
@app.Name - @app.Name @Localizer["SoonSuffix"] + @app.Name @_soonSuffix
} @@ -116,14 +116,14 @@ case TutorialStep.CreateFirstIdentity:
-

@Localizer["ReadyToStartTitle"]

+

@_readyToStartTitle

- @Localizer["ReadyToStartMessage"] + @_readyToStartMessage

@@ -136,14 +136,14 @@ { } else { }
@@ -154,6 +154,34 @@ @code { private IStringLocalizer Localizer => LocalizerFactory.Create("Pages.Main.Welcome", "AliasVault.Client"); + // Cached localized strings for performance + private string _welcomeStepTitle = string.Empty; + private string _howAliasVaultWorksStepTitle = string.Empty; + private string _tipsStepTitle = string.Empty; + private string _getStartedStepTitle = string.Empty; + private string _tutorialStepTitle = string.Empty; + private string _welcomeMessage = string.Empty; + private string _howItWorksIntro = string.Empty; + private string _howItWorksStep1 = string.Empty; + private string _howItWorksStep2 = string.Empty; + private string _howItWorksStep3 = string.Empty; + private string _howItWorksStep4 = string.Empty; + private string _masterPasswordTipTitle = string.Empty; + private string _masterPasswordTipContent = string.Empty; + private string _twoFactorTipTitle = string.Empty; + private string _twoFactorTipContent = string.Empty; + private string _extensionsAppsTipTitle = string.Empty; + private string _extensionsAppsTipContent = string.Empty; + private string _comingSoonLabel = string.Empty; + private string _downloadForPrefix = string.Empty; + private string _soonSuffix = string.Empty; + private string _readyToStartTitle = string.Empty; + private string _readyToStartMessage = string.Empty; + private string _createFirstIdentityButton = string.Empty; + private string _continueButton = string.Empty; + private string _getStartedButton = string.Empty; + private string _finishingTutorialMessage = string.Empty; + private TutorialStep _currentStep = TutorialStep.Welcome; private enum TutorialStep @@ -168,11 +196,11 @@ { return step switch { - TutorialStep.Welcome => Localizer["WelcomeStepTitle"], - TutorialStep.HowAliasVaultWorks => Localizer["HowAliasVaultWorksStepTitle"], - TutorialStep.Tips => Localizer["TipsStepTitle"], - TutorialStep.CreateFirstIdentity => Localizer["GetStartedStepTitle"], - _ => Localizer["TutorialStepTitle"] + TutorialStep.Welcome => _welcomeStepTitle, + TutorialStep.HowAliasVaultWorks => _howAliasVaultWorksStepTitle, + TutorialStep.Tips => _tipsStepTitle, + TutorialStep.CreateFirstIdentity => _getStartedStepTitle, + _ => _tutorialStepTitle }; } @@ -181,6 +209,34 @@ { await base.OnInitializedAsync(); + // Cache localized strings for performance + _welcomeStepTitle = Localizer["WelcomeStepTitle"]; + _howAliasVaultWorksStepTitle = Localizer["HowAliasVaultWorksStepTitle"]; + _tipsStepTitle = Localizer["TipsStepTitle"]; + _getStartedStepTitle = Localizer["GetStartedStepTitle"]; + _tutorialStepTitle = Localizer["TutorialStepTitle"]; + _welcomeMessage = Localizer["WelcomeMessage"]; + _howItWorksIntro = Localizer["HowItWorksIntro"]; + _howItWorksStep1 = Localizer["HowItWorksStep1"]; + _howItWorksStep2 = Localizer["HowItWorksStep2"]; + _howItWorksStep3 = Localizer["HowItWorksStep3"]; + _howItWorksStep4 = Localizer["HowItWorksStep4"]; + _masterPasswordTipTitle = Localizer["MasterPasswordTipTitle"]; + _masterPasswordTipContent = Localizer["MasterPasswordTipContent"]; + _twoFactorTipTitle = Localizer["TwoFactorTipTitle"]; + _twoFactorTipContent = Localizer["TwoFactorTipContent"]; + _extensionsAppsTipTitle = Localizer["ExtensionsAppsTipTitle"]; + _extensionsAppsTipContent = Localizer["ExtensionsAppsTipContent"]; + _comingSoonLabel = Localizer["ComingSoonLabel"]; + _downloadForPrefix = Localizer["DownloadForPrefix"]; + _soonSuffix = Localizer["SoonSuffix"]; + _readyToStartTitle = Localizer["ReadyToStartTitle"]; + _readyToStartMessage = Localizer["ReadyToStartMessage"]; + _createFirstIdentityButton = Localizer["CreateFirstIdentityButton"]; + _continueButton = Localizer["ContinueButton"]; + _getStartedButton = Localizer["GetStartedButton"]; + _finishingTutorialMessage = Localizer["FinishingTutorialMessage"]; + // If tutorial is already done, redirect to the home page. if (DbService.Settings.TutorialDone) { @@ -208,7 +264,7 @@ private async Task FinishTutorial() { - GlobalLoadingSpinner.Show(Localizer["FinishingTutorialMessage"]); + GlobalLoadingSpinner.Show(_finishingTutorialMessage); await DbService.Settings.SetTutorialDoneAsync(true); NavigationManager.NavigateTo("credentials"); GlobalLoadingSpinner.Hide();