mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-01-31 01:12:31 -05:00
* New translations start.en.resx (French) Update translations from Crowdin [ci skip] * New translations start.en.resx (Italian) Update translations from Crowdin [ci skip] * New translations en.json (Hebrew) Update translations from Crowdin [ci skip] * New translations start.en.resx (Hebrew) Update translations from Crowdin [ci skip] * New translations en.json (Hebrew) Update translations from Crowdin [ci skip] * New translations home.en.resx (Hebrew) Update translations from Crowdin [ci skip] * New translations start.en.resx (Finnish) Update translations from Crowdin [ci skip] * Add Ukrainian language (#1183) * Add Hebrew language to all apps (#1182) * New translations emailmodal.en.resx (French) Update translations from Crowdin [ci skip] * New translations emailpreview.en.resx (French) Update translations from Crowdin [ci skip] * New translations emailrow.en.resx (French) Update translations from Crowdin [ci skip] * New translations recentemails.en.resx (French) Update translations from Crowdin [ci skip] * New translations editemailformrow.en.resx (French) Update translations from Crowdin [ci skip] * New translations resetvault.en.resx (French) Update translations from Crowdin [ci skip] * New translations en.json (French) Update translations from Crowdin [ci skip] * New translations en.json (Spanish) Update translations from Crowdin [ci skip] * New translations en.json (Catalan) Update translations from Crowdin [ci skip] * New translations en.json (German) Update translations from Crowdin [ci skip] * New translations en.json (Finnish) Update translations from Crowdin [ci skip] * New translations en.json (Hebrew) Update translations from Crowdin [ci skip] * New translations en.json (Italian) Update translations from Crowdin [ci skip] * New translations en.json (Dutch) Update translations from Crowdin [ci skip] * New translations en.json (Russian) Update translations from Crowdin [ci skip] * New translations en.json (Swedish) Update translations from Crowdin [ci skip] * New translations en.json (Turkish) Update translations from Crowdin [ci skip] * New translations en.json (Ukrainian) Update translations from Crowdin [ci skip] * New translations en.json (Chinese Simplified) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (French) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Spanish) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Catalan) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (German) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Finnish) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Hebrew) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Italian) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Dutch) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Swedish) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Turkish) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Ukrainian) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Chinese Simplified) Update translations from Crowdin [ci skip] * New translations en.json (French) Update translations from Crowdin [ci skip] * New translations en.json (Spanish) Update translations from Crowdin [ci skip] * New translations en.json (Catalan) Update translations from Crowdin [ci skip] * New translations en.json (German) Update translations from Crowdin [ci skip] * New translations en.json (Finnish) Update translations from Crowdin [ci skip] * New translations en.json (Hebrew) Update translations from Crowdin [ci skip] * New translations en.json (Italian) Update translations from Crowdin [ci skip] * New translations en.json (Dutch) Update translations from Crowdin [ci skip] * New translations en.json (Russian) Update translations from Crowdin [ci skip] * New translations en.json (Swedish) Update translations from Crowdin [ci skip] * New translations en.json (Turkish) Update translations from Crowdin [ci skip] * New translations en.json (Ukrainian) Update translations from Crowdin [ci skip] * New translations en.json (Chinese Simplified) Update translations from Crowdin [ci skip] * New translations en.json (German) Update translations from Crowdin [ci skip] * New translations start.en.resx (German) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (German) Update translations from Crowdin [ci skip] * New translations en.json (German) Update translations from Crowdin [ci skip] * New translations en.json (Finnish) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Finnish) Update translations from Crowdin [ci skip] * New translations en.json (Finnish) Update translations from Crowdin [ci skip] * New translations en.json (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations sharedresources.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations en.json (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations login.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations register.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations passwordstep.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations termsandconditionsstep.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations usernamestep.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations totpcodes.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations totpviewer.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations emailmodal.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations emailpreview.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations emailrow.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations recentemails.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations editemailformrow.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations defaultpasswordsettings.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations importservicecard.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations importservices.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations passwordsettingspopup.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations activesessionssection.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations deleteaccountsection.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations passwordchangesection.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations quickvaultunlocksection.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations recentauthlogssection.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations showrecoverycodes.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations twofactorauthenticationsection.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations createnewidentitywidget.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations searchwidget.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations footer.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations topmenu.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations forgotpassword.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations logout.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations setup.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations start.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations unlock.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations delete.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations home.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations view.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations home.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations apps.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations general.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations importexport.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations security.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations creating.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations errorvaultdecrypt.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations vaultdecryptionprogress.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations sync.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations welcome.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations apierrors.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations changepassword.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations deleteaccount.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations enable2fa.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations validationmessages.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations en.json (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations resetvault.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations resetvaultsection.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations disable2fa.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations infoplist.strings (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations strings.xml (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations localizable.strings (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations en.json (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations sharedresources.en.resx (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations en.json (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations en.json (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations en.json (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations en.json (Portuguese, Brazilian) Update translations from Crowdin [ci skip] * New translations en.json (Russian) Update translations from Crowdin [ci skip] * New translations topmenu.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations en.json (Russian) Update translations from Crowdin [ci skip] * New translations en.json (Russian) Update translations from Crowdin [ci skip] * New translations forgotpassword.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations setup.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations start.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations importexport.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations welcome.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations setup.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations welcome.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations en.json (Italian) Update translations from Crowdin [ci skip] * New translations en.json (Dutch) Update translations from Crowdin [ci skip] * New translations en.json (Chinese Simplified) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Italian) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Dutch) Update translations from Crowdin [ci skip] * New translations addedit.en.resx (Chinese Simplified) Update translations from Crowdin [ci skip] * New translations apps.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations welcome.en.resx (Russian) Update translations from Crowdin [ci skip] * New translations en.json (Italian) Update translations from Crowdin [ci skip] * New translations en.json (Dutch) Update translations from Crowdin [ci skip] * New translations en.json (Chinese Simplified) Update translations from Crowdin [ci skip]
337 lines
11 KiB
C#
337 lines
11 KiB
C#
//-----------------------------------------------------------------------
|
|
// <copyright file="LanguageService.cs" company="aliasvault">
|
|
// Copyright (c) aliasvault. All rights reserved.
|
|
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
|
|
// </copyright>
|
|
//-----------------------------------------------------------------------
|
|
|
|
namespace AliasVault.Client.Services;
|
|
|
|
using System.Globalization;
|
|
using AliasVault.Client.Services.Database;
|
|
using Blazored.LocalStorage;
|
|
using Microsoft.AspNetCore.Components.Authorization;
|
|
using Microsoft.JSInterop;
|
|
|
|
/// <summary>
|
|
/// Service for managing application language settings and culture switching.
|
|
/// </summary>
|
|
public class LanguageService(
|
|
ILocalStorageService localStorage,
|
|
IJSRuntime jsRuntime,
|
|
AuthenticationStateProvider authenticationStateProvider,
|
|
DbService dbService)
|
|
{
|
|
private const string AppLanguageKey = "AppLanguage";
|
|
|
|
/// <summary>
|
|
/// Language configuration containing all supported languages.
|
|
/// To add a new language, simply add a new entry to this list.
|
|
/// </summary>
|
|
private static readonly List<LanguageConfig> SupportedLanguages = new()
|
|
{
|
|
new LanguageConfig("de", "Deutsch", "🇩🇪"),
|
|
new LanguageConfig("en", "English", "🇺🇸"),
|
|
new LanguageConfig("fi", "Suomi", "🇫🇮"),
|
|
new LanguageConfig("he", "עברית", "🇮🇱"),
|
|
new LanguageConfig("it", "Italiano", "🇮🇹"),
|
|
new LanguageConfig("nl", "Nederlands", "🇳🇱"),
|
|
new LanguageConfig("uk", "Українська", "🇺🇦"),
|
|
new LanguageConfig("zh", "简体中文", "🇨🇳"),
|
|
|
|
// Add new languages here:
|
|
// new LanguageConfig("ru", "Русский", "🇷🇺"),
|
|
// new LanguageConfig("fr", "Français", "🇫🇷"),
|
|
// new LanguageConfig("es", "Español", "🇪🇸"),
|
|
};
|
|
|
|
private readonly ILocalStorageService _localStorage = localStorage;
|
|
private readonly IJSRuntime _jsRuntime = jsRuntime;
|
|
private readonly AuthenticationStateProvider _authenticationStateProvider = authenticationStateProvider;
|
|
private readonly DbService _dbService = dbService;
|
|
|
|
/// <summary>
|
|
/// Event that is triggered when the language is changed.
|
|
/// </summary>
|
|
public event Action<string>? LanguageChanged;
|
|
|
|
/// <summary>
|
|
/// Gets the list of supported languages.
|
|
/// </summary>
|
|
/// <returns>Dictionary of language codes and display names.</returns>
|
|
public static Dictionary<string, string> GetSupportedLanguages()
|
|
{
|
|
return SupportedLanguages.ToDictionary(lang => lang.Code, lang => lang.DisplayName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the list of supported languages with flag emojis.
|
|
/// </summary>
|
|
/// <returns>Dictionary of language codes and display names with flag emojis.</returns>
|
|
public static Dictionary<string, string> GetSupportedLanguagesWithFlags()
|
|
{
|
|
return SupportedLanguages.ToDictionary(lang => lang.Code, lang => $"{lang.FlagEmoji} {lang.DisplayName}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the flag emoji for a specific language code.
|
|
/// </summary>
|
|
/// <param name="languageCode">The language code.</param>
|
|
/// <returns>Flag emoji string.</returns>
|
|
public static string GetLanguageFlag(string languageCode)
|
|
{
|
|
var language = SupportedLanguages.FirstOrDefault(lang => lang.Code == languageCode);
|
|
return language?.FlagEmoji ?? "🌐";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the display name for a specific language code.
|
|
/// </summary>
|
|
/// <param name="languageCode">The language code.</param>
|
|
/// <returns>Display name string.</returns>
|
|
public static string GetLanguageDisplayName(string languageCode)
|
|
{
|
|
var language = SupportedLanguages.FirstOrDefault(lang => lang.Code == languageCode);
|
|
return language?.DisplayName ?? languageCode;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a language code is supported.
|
|
/// </summary>
|
|
/// <param name="languageCode">The language code to check.</param>
|
|
/// <returns>True if the language is supported, false otherwise.</returns>
|
|
public static bool IsLanguageSupported(string languageCode)
|
|
{
|
|
return SupportedLanguages.Any(lang => lang.Code == languageCode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the default language code.
|
|
/// </summary>
|
|
/// <returns>Default language code.</returns>
|
|
public static string GetDefaultLanguage()
|
|
{
|
|
return SupportedLanguages.FirstOrDefault()?.Code ?? "en";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current language from the browser.
|
|
/// </summary>
|
|
/// <returns>Browser language code.</returns>
|
|
public async Task<string> GetBrowserLanguageAsync()
|
|
{
|
|
try
|
|
{
|
|
var browserLanguage = await _jsRuntime.InvokeAsync<string>("navigator.language");
|
|
var cultureName = browserLanguage.Split('-')[0];
|
|
|
|
return GetSupportedLanguages().ContainsKey(cultureName) ? cultureName : "en";
|
|
}
|
|
catch
|
|
{
|
|
return "en";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current language setting.
|
|
/// </summary>
|
|
/// <returns>Current language code.</returns>
|
|
public async Task<string> GetCurrentLanguageAsync()
|
|
{
|
|
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
|
|
|
if (authState.User.Identity?.IsAuthenticated == true)
|
|
{
|
|
// User is authenticated, get language from vault settings
|
|
try
|
|
{
|
|
var language = await _dbService.Settings.GetSettingAsync<string>(AppLanguageKey);
|
|
if (!string.IsNullOrEmpty(language))
|
|
{
|
|
return language;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors and fall back to local storage first, then browser language
|
|
}
|
|
|
|
// If no vault setting found, check localStorage to migrate user's pre-auth preference
|
|
try
|
|
{
|
|
var storedLanguage = await _localStorage.GetItemAsync<string>(AppLanguageKey);
|
|
if (!string.IsNullOrEmpty(storedLanguage))
|
|
{
|
|
// Migrate the localStorage setting to vault and then return it
|
|
await MigrateLanguageSettingToVault(storedLanguage);
|
|
return storedLanguage;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors and fall back to browser language
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// User is not authenticated, check local storage
|
|
try
|
|
{
|
|
var storedLanguage = await _localStorage.GetItemAsync<string>(AppLanguageKey);
|
|
if (!string.IsNullOrEmpty(storedLanguage))
|
|
{
|
|
return storedLanguage;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors and fall back to browser language
|
|
}
|
|
}
|
|
|
|
// Fall back to browser language
|
|
return await GetBrowserLanguageAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the language and updates the culture.
|
|
/// </summary>
|
|
/// <param name="languageCode">Language code to set.</param>
|
|
/// <returns>Task.</returns>
|
|
public async Task SetLanguageAsync(string languageCode)
|
|
{
|
|
if (string.IsNullOrEmpty(languageCode))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!GetSupportedLanguages().ContainsKey(languageCode))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
|
|
|
|
if (authState.User.Identity?.IsAuthenticated == true)
|
|
{
|
|
// User is authenticated, save to vault settings
|
|
try
|
|
{
|
|
await _dbService.Settings.SetSettingAsync(AppLanguageKey, languageCode);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors, still set the culture
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// User is not authenticated, save to local storage
|
|
try
|
|
{
|
|
await _localStorage.SetItemAsync(AppLanguageKey, languageCode);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors, still set the culture
|
|
}
|
|
}
|
|
|
|
// Set the culture dynamically without page reload
|
|
var culture = new CultureInfo(languageCode);
|
|
CultureInfo.CurrentCulture = culture;
|
|
CultureInfo.CurrentUICulture = culture;
|
|
CultureInfo.DefaultThreadCurrentCulture = culture;
|
|
CultureInfo.DefaultThreadCurrentUICulture = culture;
|
|
|
|
// Store in blazorCulture for consistency
|
|
await _jsRuntime.InvokeVoidAsync("blazorCulture.set", languageCode);
|
|
|
|
// Notify listeners that language has changed
|
|
LanguageChanged?.Invoke(languageCode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the language service and sets the initial culture.
|
|
/// </summary>
|
|
/// <returns>Task.</returns>
|
|
public async Task InitializeAsync()
|
|
{
|
|
var initialLanguage = "en"; // Default fallback
|
|
|
|
try
|
|
{
|
|
// Get the initial language preference from JavaScript
|
|
initialLanguage = await _jsRuntime.InvokeAsync<string>("blazorCulture.get");
|
|
}
|
|
catch
|
|
{
|
|
// Fallback if JavaScript is not available yet
|
|
try
|
|
{
|
|
var browserLang = await _jsRuntime.InvokeAsync<string>("eval", "navigator.language");
|
|
var cultureName = browserLang.Split('-')[0];
|
|
if (GetSupportedLanguages().ContainsKey(cultureName))
|
|
{
|
|
initialLanguage = cultureName;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Use default "en"
|
|
}
|
|
}
|
|
|
|
// Validate the language
|
|
if (!GetSupportedLanguages().ContainsKey(initialLanguage))
|
|
{
|
|
initialLanguage = "en";
|
|
}
|
|
|
|
// Set the culture
|
|
var culture = new CultureInfo(initialLanguage);
|
|
CultureInfo.CurrentCulture = culture;
|
|
CultureInfo.CurrentUICulture = culture;
|
|
CultureInfo.DefaultThreadCurrentCulture = culture;
|
|
CultureInfo.DefaultThreadCurrentUICulture = culture;
|
|
|
|
// Store in blazorCulture for consistency (if available)
|
|
try
|
|
{
|
|
await _jsRuntime.InvokeVoidAsync("blazorCulture.set", initialLanguage);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore if blazorCulture is not available yet
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Migrates a language setting from localStorage to vault settings.
|
|
/// This is called when a user was anonymous, set a language preference, then authenticated.
|
|
/// </summary>
|
|
/// <param name="languageCode">The language code to migrate.</param>
|
|
/// <returns>Task.</returns>
|
|
private async Task MigrateLanguageSettingToVault(string languageCode)
|
|
{
|
|
try
|
|
{
|
|
// Save to vault settings
|
|
await _dbService.Settings.SetSettingAsync(AppLanguageKey, languageCode);
|
|
|
|
// Clear from localStorage since it's now in vault
|
|
await _localStorage.RemoveItemAsync(AppLanguageKey);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore migration errors - user can still change language manually
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configuration for a supported language.
|
|
/// </summary>
|
|
private sealed record LanguageConfig(string Code, string DisplayName, string FlagEmoji);
|
|
}
|