1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-28 22:23:35 +00:00

[EC-1002] [BEEEP] Add ability to change language in app (#2299)

* EC-1002 BEEEP Added ability to change language in app

* EC-1002 fix format

* EC-1002 Renamed IPreferencesStorageService to ISynchronousStorageService

* EC-1002 Moved get/set Locale to the StateService and added the StorageMediatorService to a new way to interact with the storage. Later the StateService will only interact with this mediator instead of directly with the storage services, with this we have more control inside the mediator and we can have both sync and async methods to interact with storages handled by the mediator
This commit is contained in:
Federico Maccaroni
2023-03-01 13:28:28 -03:00
committed by GitHub
parent 5164762f2e
commit 470e08f165
18 changed files with 298 additions and 29 deletions

View File

@@ -80,6 +80,22 @@
Text="{u:I18n ClearClipboardDescription}"
StyleClass="box-footer-label" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n Language}"
StyleClass="box-label" />
<Picker
x:Name="_languagePicker"
ItemsSource="{Binding LocalesOptions, Mode=OneTime}"
SelectedItem="{Binding SelectedLocale}"
ItemDisplayBinding="{Binding Value}"
StyleClass="box-value" />
</StackLayout>
<Label
Text="{u:I18n LanguageChangeRequiresAppRestart}"
StyleClass="box-footer-label" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-switch">
<Label

View File

@@ -34,6 +34,7 @@ namespace Bit.App.Pages
_autoDarkThemePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_languagePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities;
@@ -14,7 +15,8 @@ namespace Bit.App.Pages
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly II18nService _i18nService;
private readonly IPlatformUtilsService _platformUtilsService;
private bool _autofillSavePrompt;
private string _autofillBlockedUris;
@@ -24,6 +26,7 @@ namespace Bit.App.Pages
private int _themeSelectedIndex;
private int _autoDarkThemeSelectedIndex;
private int _uriMatchSelectedIndex;
private KeyValuePair<string, string> _selectedLocale;
private bool _inited;
private bool _updatingAutofill;
private bool _showAndroidAutofillSettings;
@@ -32,6 +35,8 @@ namespace Bit.App.Pages
{
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_i18nService = ServiceContainer.Resolve<II18nService>();
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
PageTitle = AppResources.Options;
var iosIos = Device.RuntimePlatform == Device.iOS;
@@ -74,12 +79,18 @@ namespace Bit.App.Pages
new KeyValuePair<UriMatchType?, string>(UriMatchType.Exact, AppResources.Exact),
new KeyValuePair<UriMatchType?, string>(UriMatchType.Never, AppResources.Never),
};
LocalesOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(null, AppResources.DefaultSystem)
};
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
}
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
public List<KeyValuePair<string, string>> ThemeOptions { get; set; }
public List<KeyValuePair<string, string>> AutoDarkThemeOptions { get; set; }
public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; }
public List<KeyValuePair<string, string>> LocalesOptions { get; }
public int ClearClipboardSelectedIndex
{
@@ -133,6 +144,18 @@ namespace Bit.App.Pages
}
}
public KeyValuePair<string, string> SelectedLocale
{
get => _selectedLocale;
set
{
if (SetProperty(ref _selectedLocale, value))
{
UpdateCurrentLocaleAsync().FireAndForget();
}
}
}
public bool Favicon
{
get => _favicon;
@@ -184,19 +207,30 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
AutofillBlockedUris = blockedUrisList != null ? string.Join(", ", blockedUrisList) : null;
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
var theme = await _stateService.GetThemeAsync();
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
var autoDarkTheme = await _stateService.GetAutoDarkThemeAsync() ?? "dark";
AutoDarkThemeSelectedIndex = AutoDarkThemeOptions.FindIndex(k => k.Key == autoDarkTheme);
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
var clearClipboard = await _stateService.GetClearClipboardAsync();
ClearClipboardSelectedIndex = ClearClipboardOptions.FindIndex(k => k.Key == clearClipboard);
var appLocale = _stateService.GetLocale();
SelectedLocale = appLocale == null ? LocalesOptions.First() : LocalesOptions.FirstOrDefault(kv => kv.Key == appLocale);
_inited = true;
}
@@ -288,5 +322,17 @@ namespace Bit.App.Pages
catch { }
}
}
private async Task UpdateCurrentLocaleAsync()
{
if (!_inited)
{
return;
}
_stateService.SetLocale(SelectedLocale.Key);
await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.LanguageChangeXDescription, SelectedLocale.Value), AppResources.Language, AppResources.Ok);
}
}
}

View File

@@ -1807,6 +1807,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Default (System).
/// </summary>
public static string DefaultSystem {
get {
return ResourceManager.GetString("DefaultSystem", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Default URI match detection.
/// </summary>
@@ -3373,6 +3382,33 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Language.
/// </summary>
public static string Language {
get {
return ResourceManager.GetString("Language", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Language change requires app restart.
/// </summary>
public static string LanguageChangeRequiresAppRestart {
get {
return ResourceManager.GetString("LanguageChangeRequiresAppRestart", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The language has been changed to {0}. Please restart the app to see the change.
/// </summary>
public static string LanguageChangeXDescription {
get {
return ResourceManager.GetString("LanguageChangeXDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Last name.
/// </summary>

View File

@@ -2541,6 +2541,18 @@ Do you want to switch to this account?</value>
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
<value>Enable camera permission to use the scanner</value>
</data>
<data name="Language" xml:space="preserve">
<value>Language</value>
</data>
<data name="LanguageChangeXDescription" xml:space="preserve">
<value>The language has been changed to {0}. Please restart the app to see the change</value>
</data>
<data name="LanguageChangeRequiresAppRestart" xml:space="preserve">
<value>Language change requires app restart</value>
</data>
<data name="DefaultSystem" xml:space="preserve">
<value>Default (System)</value>
</data>
<data name="Important" xml:space="preserve">
<value>Important</value>
</data>

View File

@@ -51,32 +51,32 @@ namespace Bit.App.Services
["bg"] = "български",
["ca"] = "català",
["cs"] = "čeština",
["da"] = "dansk",
["da"] = "Dansk",
["de"] = "Deutsch",
["el"] = "Ελληνικά",
["en"] = "English",
["en-GB"] = "English (British)",
["eo"] = "Esperanto",
["es"] = "español",
["es"] = "Español",
["et"] = "eesti",
["fa"] = "فارسی",
["fi"] = "suomi",
["fr"] = "français",
["fr"] = "Français",
["he"] = "עברית",
["hi"] = "हिन्दी",
["hr"] = "hrvatski",
["hu"] = "magyar",
["id"] = "Bahasa Indonesia",
["it"] = "italiano",
["it"] = "Italiano",
["ja"] = "日本語",
["ko"] = "한국어",
["lv"] = "Latvietis",
["ml"] = "മലയാളം",
["nb"] = "norsk (bokmål)",
["nl"] = "Nederlands",
["pl"] = "polski",
["pt-BR"] = "português do Brasil",
["pt-PT"] = "português",
["pl"] = "Polski",
["pt-BR"] = "Português do Brasil",
["pt-PT"] = "Português",
["ro"] = "română",
["ru"] = "русский",
["sk"] = "slovenčina",
@@ -100,10 +100,16 @@ namespace Bit.App.Services
throw new Exception("I18n already inited.");
}
_inited = true;
SetCurrentCulture(culture);
}
public void SetCurrentCulture(CultureInfo culture)
{
if (culture != null)
{
Culture = culture;
}
AppResources.Culture = Culture;
Thread.CurrentThread.CurrentCulture = Culture;
Thread.CurrentThread.CurrentUICulture = Culture;

View File

@@ -6,7 +6,7 @@ using Newtonsoft.Json.Serialization;
namespace Bit.App.Services
{
public class PreferencesStorageService : IStorageService
public class PreferencesStorageService : IStorageService, ISynchronousStorageService
{
public static string KeyFormat = "bwPreferencesStorage:{0}";
@@ -22,57 +22,72 @@ namespace Bit.App.Services
_sharedName = sharedName;
}
public Task<T> GetAsync<T>(string key)
public Task<T> GetAsync<T>(string key) => Task.FromResult(Get<T>(key));
public Task SaveAsync<T>(string key, T obj)
{
Save(key, obj);
return Task.CompletedTask;
}
public Task RemoveAsync(string key)
{
Remove(key);
return Task.CompletedTask;
}
public T Get<T>(string key)
{
var formattedKey = string.Format(KeyFormat, key);
if (!Xamarin.Essentials.Preferences.ContainsKey(formattedKey, _sharedName))
{
return Task.FromResult(default(T));
return default(T);
}
var objType = typeof(T);
if (objType == typeof(string))
{
var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(string), _sharedName);
return Task.FromResult((T)(object)val);
return (T)(object)val;
}
else if (objType == typeof(bool) || objType == typeof(bool?))
{
var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(bool), _sharedName);
return Task.FromResult(ChangeType<T>(val));
return ChangeType<T>(val);
}
else if (objType == typeof(int) || objType == typeof(int?))
{
var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(int), _sharedName);
return Task.FromResult(ChangeType<T>(val));
return ChangeType<T>(val);
}
else if (objType == typeof(long) || objType == typeof(long?))
{
var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(long), _sharedName);
return Task.FromResult(ChangeType<T>(val));
return ChangeType<T>(val);
}
else if (objType == typeof(double) || objType == typeof(double?))
{
var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(double), _sharedName);
return Task.FromResult(ChangeType<T>(val));
return ChangeType<T>(val);
}
else if (objType == typeof(DateTime) || objType == typeof(DateTime?))
{
var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(DateTime), _sharedName);
return Task.FromResult(ChangeType<T>(val));
return ChangeType<T>(val);
}
else
{
var val = Xamarin.Essentials.Preferences.Get(formattedKey, default(string), _sharedName);
return Task.FromResult(JsonConvert.DeserializeObject<T>(val, _jsonSettings));
return JsonConvert.DeserializeObject<T>(val, _jsonSettings);
}
}
public Task SaveAsync<T>(string key, T obj)
public void Save<T>(string key, T obj)
{
if (obj == null)
{
return RemoveAsync(key);
Remove(key);
return;
}
var formattedKey = string.Format(KeyFormat, key);
@@ -106,17 +121,15 @@ namespace Bit.App.Services
Xamarin.Essentials.Preferences.Set(formattedKey, JsonConvert.SerializeObject(obj, _jsonSettings),
_sharedName);
}
return Task.FromResult(0);
}
public Task RemoveAsync(string key)
public void Remove(string key)
{
var formattedKey = string.Format(KeyFormat, key);
if (Xamarin.Essentials.Preferences.ContainsKey(formattedKey, _sharedName))
{
Xamarin.Essentials.Preferences.Remove(formattedKey, _sharedName);
}
return Task.FromResult(0);
}
private static T ChangeType<T>(object value)