mirror of
https://github.com/bitwarden/android.git
synced 2024-12-18 23:31:52 +03:00
Localization services for setting culture
This commit is contained in:
parent
320d2c5c96
commit
9938fdd4a2
12 changed files with 319 additions and 3 deletions
|
@ -293,6 +293,7 @@
|
|||
<Compile Include="Controls\ExtendedTextCellRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedPickerRenderer.cs" />
|
||||
<Compile Include="Controls\ExtendedEntryRenderer.cs" />
|
||||
<Compile Include="Services\LocalizeService.cs" />
|
||||
<Compile Include="MainApplication.cs" />
|
||||
<Compile Include="Resources\Resource.Designer.cs" />
|
||||
<Compile Include="Services\DeviceInfoService.cs" />
|
||||
|
|
|
@ -61,7 +61,8 @@ namespace Bit.Android
|
|||
Resolver.Resolve<IFingerprint>(),
|
||||
Resolver.Resolve<ISettings>(),
|
||||
Resolver.Resolve<ILockService>(),
|
||||
Resolver.Resolve<IGoogleAnalyticsService>()));
|
||||
Resolver.Resolve<IGoogleAnalyticsService>(),
|
||||
Resolver.Resolve<ILocalizeService>()));
|
||||
|
||||
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, "RateApp", (sender) =>
|
||||
{
|
||||
|
|
|
@ -203,6 +203,7 @@ namespace Bit.Android
|
|||
.RegisterType<IAppInfoService, AppInfoService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IGoogleAnalyticsService, GoogleAnalyticsService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IDeviceInfoService, DeviceInfoService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ILocalizeService, LocalizeService>(new ContainerControlledLifetimeManager())
|
||||
// Repositories
|
||||
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
|
||||
|
|
97
src/Android/Services/LocalizeService.cs
Normal file
97
src/Android/Services/LocalizeService.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Globalization;
|
||||
using Bit.App.Models;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
{
|
||||
public class LocalizeService : App.Abstractions.ILocalizeService
|
||||
{
|
||||
public void SetLocale(CultureInfo ci)
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = ci;
|
||||
Thread.CurrentThread.CurrentUICulture = ci;
|
||||
Console.WriteLine("CurrentCulture set: " + ci.Name);
|
||||
}
|
||||
|
||||
public CultureInfo GetCurrentCultureInfo()
|
||||
{
|
||||
var netLanguage = "en";
|
||||
var androidLocale = Java.Util.Locale.Default;
|
||||
netLanguage = AndroidToDotnetLanguage(androidLocale.ToString().Replace("_", "-"));
|
||||
|
||||
// this gets called a lot - try/catch can be expensive so consider caching or something
|
||||
CultureInfo ci = null;
|
||||
try
|
||||
{
|
||||
ci = new CultureInfo(netLanguage);
|
||||
}
|
||||
catch(CultureNotFoundException e1)
|
||||
{
|
||||
// iOS locale not valid .NET culture (eg. "en-ES" : English in Spain)
|
||||
// fallback to first characters, in this case "en"
|
||||
try
|
||||
{
|
||||
var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
|
||||
Console.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")");
|
||||
ci = new CultureInfo(fallback);
|
||||
}
|
||||
catch(CultureNotFoundException e2)
|
||||
{
|
||||
// iOS language not valid .NET culture, falling back to English
|
||||
Console.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")");
|
||||
ci = new CultureInfo("en");
|
||||
}
|
||||
}
|
||||
|
||||
return ci;
|
||||
}
|
||||
|
||||
private string AndroidToDotnetLanguage(string androidLanguage)
|
||||
{
|
||||
Console.WriteLine("Android Language:" + androidLanguage);
|
||||
var netLanguage = androidLanguage;
|
||||
|
||||
// certain languages need to be converted to CultureInfo equivalent
|
||||
switch(androidLanguage)
|
||||
{
|
||||
case "ms-BN": // "Malaysian (Brunei)" not supported .NET culture
|
||||
case "ms-MY": // "Malaysian (Malaysia)" not supported .NET culture
|
||||
case "ms-SG": // "Malaysian (Singapore)" not supported .NET culture
|
||||
netLanguage = "ms"; // closest supported
|
||||
break;
|
||||
case "in-ID": // "Indonesian (Indonesia)" has different code in .NET
|
||||
netLanguage = "id-ID"; // correct code for .NET
|
||||
break;
|
||||
case "gsw-CH": // "Schwiizertüütsch (Swiss German)" not supported .NET culture
|
||||
netLanguage = "de-CH"; // closest supported
|
||||
break;
|
||||
// add more application-specific cases here (if required)
|
||||
// ONLY use cultures that have been tested and known to work
|
||||
}
|
||||
|
||||
Console.WriteLine(".NET Language/Locale:" + netLanguage);
|
||||
return netLanguage;
|
||||
}
|
||||
|
||||
private string ToDotnetFallbackLanguage(PlatformCulture platCulture)
|
||||
{
|
||||
Console.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode);
|
||||
var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
|
||||
|
||||
switch(platCulture.LanguageCode)
|
||||
{
|
||||
case "gsw":
|
||||
netLanguage = "de-CH"; // equivalent to German (Switzerland) for this app
|
||||
break;
|
||||
// add more application-specific cases here (if required)
|
||||
// ONLY use cultures that have been tested and known to work
|
||||
}
|
||||
|
||||
Console.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)");
|
||||
return netLanguage;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
31
src/App/Abstractions/Services/ILocalizeService.cs
Normal file
31
src/App/Abstractions/Services/ILocalizeService.cs
Normal file
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementations of this interface MUST convert iOS and Android
|
||||
/// platform-specific locales to a value supported in .NET because
|
||||
/// ONLY valid .NET cultures can have their RESX resources loaded and used.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Lists of valid .NET cultures can be found here:
|
||||
/// http://www.localeplanet.com/dotnet/
|
||||
/// http://www.csharp-examples.net/culture-names/
|
||||
/// You should always test all the locales implemented in your application.
|
||||
/// </remarks>
|
||||
public interface ILocalizeService
|
||||
{
|
||||
/// <summary>
|
||||
/// This method must evaluate platform-specific locale settings
|
||||
/// and convert them (when necessary) to a valid .NET locale.
|
||||
/// </summary>
|
||||
CultureInfo GetCurrentCultureInfo();
|
||||
|
||||
/// <summary>
|
||||
/// CurrentCulture and CurrentUICulture must be set in the platform project,
|
||||
/// because the Thread object can't be accessed in a PCL.
|
||||
/// </summary>
|
||||
void SetLocale(CultureInfo ci);
|
||||
}
|
||||
}
|
|
@ -12,6 +12,8 @@ using Plugin.Connectivity.Abstractions;
|
|||
using System.Net;
|
||||
using Acr.UserDialogs;
|
||||
using XLabs.Ioc;
|
||||
using System.Reflection;
|
||||
using Bit.App.Resources;
|
||||
|
||||
namespace Bit.App
|
||||
{
|
||||
|
@ -26,6 +28,7 @@ namespace Bit.App
|
|||
private readonly ISettings _settings;
|
||||
private readonly ILockService _lockService;
|
||||
private readonly IGoogleAnalyticsService _googleAnalyticsService;
|
||||
private readonly ILocalizeService _localizeService;
|
||||
|
||||
public App(
|
||||
IAuthService authService,
|
||||
|
@ -36,7 +39,8 @@ namespace Bit.App
|
|||
IFingerprint fingerprint,
|
||||
ISettings settings,
|
||||
ILockService lockService,
|
||||
IGoogleAnalyticsService googleAnalyticsService)
|
||||
IGoogleAnalyticsService googleAnalyticsService,
|
||||
ILocalizeService localizeService)
|
||||
{
|
||||
_databaseService = databaseService;
|
||||
_connectivity = connectivity;
|
||||
|
@ -47,7 +51,9 @@ namespace Bit.App
|
|||
_settings = settings;
|
||||
_lockService = lockService;
|
||||
_googleAnalyticsService = googleAnalyticsService;
|
||||
_localizeService = localizeService;
|
||||
|
||||
SetCulture();
|
||||
SetStyles();
|
||||
|
||||
if(authService.IsAuthenticated)
|
||||
|
@ -347,5 +353,24 @@ namespace Bit.App
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void SetCulture()
|
||||
{
|
||||
Debug.WriteLine("====== resource debug info =========");
|
||||
var assembly = typeof(App).GetTypeInfo().Assembly;
|
||||
foreach(var res in assembly.GetManifestResourceNames())
|
||||
{
|
||||
Debug.WriteLine("found resource: " + res);
|
||||
}
|
||||
Debug.WriteLine("====================================");
|
||||
|
||||
// This lookup NOT required for Windows platforms - the Culture will be automatically set
|
||||
if(Device.OS == TargetPlatform.iOS || Device.OS == TargetPlatform.Android)
|
||||
{
|
||||
var ci = _localizeService.GetCurrentCultureInfo();
|
||||
AppResources.Culture = ci;
|
||||
_localizeService.SetLocale(ci);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
<Compile Include="Enums\CipherType.cs" />
|
||||
<Compile Include="Enums\PushType.cs" />
|
||||
<Compile Include="Enums\ReturnType.cs" />
|
||||
<Compile Include="Abstractions\Services\ILocalizeService.cs" />
|
||||
<Compile Include="Models\Api\ApiError.cs" />
|
||||
<Compile Include="Models\Api\ApiResult.cs" />
|
||||
<Compile Include="Models\Api\FolderDataModel.cs" />
|
||||
|
@ -109,6 +110,7 @@
|
|||
<Compile Include="Models\Page\SettingsFolderPageModel.cs" />
|
||||
<Compile Include="Models\Page\PinPageModel.cs" />
|
||||
<Compile Include="Models\Page\PasswordGeneratorPageModel.cs" />
|
||||
<Compile Include="Models\PlatformCulture.cs" />
|
||||
<Compile Include="Models\PushNotification.cs" />
|
||||
<Compile Include="Models\Site.cs" />
|
||||
<Compile Include="Models\Page\VaultViewSitePageModel.cs" />
|
||||
|
|
43
src/App/Models/PlatformCulture.cs
Normal file
43
src/App/Models/PlatformCulture.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for splitting locales like
|
||||
/// iOS: ms_MY, gsw_CH
|
||||
/// Android: in-ID
|
||||
/// into parts so we can create a .NET culture (or fallback culture)
|
||||
/// </summary>
|
||||
public class PlatformCulture
|
||||
{
|
||||
public PlatformCulture(string platformCultureString)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(platformCultureString))
|
||||
{
|
||||
throw new ArgumentException("Expected culture identifier", nameof(platformCultureString));
|
||||
}
|
||||
|
||||
// .NET expects dash, not underscore
|
||||
PlatformString = platformCultureString.Replace("_", "-");
|
||||
var dashIndex = PlatformString.IndexOf("-", StringComparison.Ordinal);
|
||||
if(dashIndex > 0)
|
||||
{
|
||||
var parts = PlatformString.Split('-');
|
||||
LanguageCode = parts[0];
|
||||
LocaleCode = parts[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
LanguageCode = PlatformString;
|
||||
LocaleCode = "";
|
||||
}
|
||||
}
|
||||
public string PlatformString { get; private set; }
|
||||
public string LanguageCode { get; private set; }
|
||||
public string LocaleCode { get; private set; }
|
||||
public override string ToString()
|
||||
{
|
||||
return PlatformString;
|
||||
}
|
||||
}
|
||||
}
|
101
src/iOS.Core/Services/LocalizeService.cs
Normal file
101
src/iOS.Core/Services/LocalizeService.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using Foundation;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class LocalizeService : ILocalizeService
|
||||
{
|
||||
public void SetLocale(CultureInfo ci)
|
||||
{
|
||||
Thread.CurrentThread.CurrentCulture = ci;
|
||||
Thread.CurrentThread.CurrentUICulture = ci;
|
||||
Console.WriteLine("CurrentCulture set: " + ci.Name);
|
||||
}
|
||||
|
||||
public CultureInfo GetCurrentCultureInfo()
|
||||
{
|
||||
var netLanguage = "en";
|
||||
if(NSLocale.PreferredLanguages.Length > 0)
|
||||
{
|
||||
var pref = NSLocale.PreferredLanguages[0];
|
||||
|
||||
netLanguage = iOSToDotnetLanguage(pref);
|
||||
}
|
||||
|
||||
// this gets called a lot - try/catch can be expensive so consider caching or something
|
||||
CultureInfo ci = null;
|
||||
try
|
||||
{
|
||||
ci = new CultureInfo(netLanguage);
|
||||
}
|
||||
catch(CultureNotFoundException e1)
|
||||
{
|
||||
// iOS locale not valid .NET culture (eg. "en-ES" : English in Spain)
|
||||
// fallback to first characters, in this case "en"
|
||||
try
|
||||
{
|
||||
var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
|
||||
Console.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")");
|
||||
ci = new CultureInfo(fallback);
|
||||
}
|
||||
catch(CultureNotFoundException e2)
|
||||
{
|
||||
// iOS language not valid .NET culture, falling back to English
|
||||
Console.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")");
|
||||
ci = new CultureInfo("en");
|
||||
}
|
||||
}
|
||||
|
||||
return ci;
|
||||
}
|
||||
|
||||
private string iOSToDotnetLanguage(string iOSLanguage)
|
||||
{
|
||||
Console.WriteLine("iOS Language:" + iOSLanguage);
|
||||
var netLanguage = iOSLanguage;
|
||||
|
||||
//certain languages need to be converted to CultureInfo equivalent
|
||||
switch(iOSLanguage)
|
||||
{
|
||||
case "ms-MY": // "Malaysian (Malaysia)" not supported .NET culture
|
||||
case "ms-SG": // "Malaysian (Singapore)" not supported .NET culture
|
||||
netLanguage = "ms"; // closest supported
|
||||
break;
|
||||
case "gsw-CH": // "Schwiizertüütsch (Swiss German)" not supported .NET culture
|
||||
netLanguage = "de-CH"; // closest supported
|
||||
break;
|
||||
// add more application-specific cases here (if required)
|
||||
// ONLY use cultures that have been tested and known to work
|
||||
}
|
||||
|
||||
Console.WriteLine(".NET Language/Locale:" + netLanguage);
|
||||
return netLanguage;
|
||||
}
|
||||
|
||||
private string ToDotnetFallbackLanguage(PlatformCulture platCulture)
|
||||
{
|
||||
Console.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode);
|
||||
var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
|
||||
|
||||
switch(platCulture.LanguageCode)
|
||||
{
|
||||
//
|
||||
case "pt":
|
||||
netLanguage = "pt-PT"; // fallback to Portuguese (Portugal)
|
||||
break;
|
||||
case "gsw":
|
||||
netLanguage = "de-CH"; // equivalent to German (Switzerland) for this app
|
||||
break;
|
||||
// add more application-specific cases here (if required)
|
||||
// ONLY use cultures that have been tested and known to work
|
||||
}
|
||||
|
||||
Console.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)");
|
||||
return netLanguage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,6 +92,7 @@
|
|||
<Compile Include="Services\CommonCryptoKeyDerivationService.cs" />
|
||||
<Compile Include="Services\Settings.cs" />
|
||||
<Compile Include="Services\GoogleAnalyticsService.cs" />
|
||||
<Compile Include="Services\LocalizeService.cs" />
|
||||
<Compile Include="Services\SqlService.cs" />
|
||||
<Compile Include="Utilities\Dialogs.cs" />
|
||||
<Compile Include="Views\ISelectable.cs" />
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace Bit.iOS.Extension
|
|||
public override void ViewDidLoad()
|
||||
{
|
||||
SetIoc();
|
||||
SetCulture();
|
||||
|
||||
base.ViewDidLoad();
|
||||
View.BackgroundColor = new UIColor(red: 0.94f, green: 0.94f, blue: 0.96f, alpha: 1.0f);
|
||||
|
@ -276,6 +277,7 @@ namespace Bit.iOS.Extension
|
|||
.RegisterType<IAppIdService, AppIdService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ILockService, LockService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IGoogleAnalyticsService, GoogleAnalyticsService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ILocalizeService, LocalizeService>(new ContainerControlledLifetimeManager())
|
||||
// Repositories
|
||||
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
|
||||
|
@ -292,6 +294,14 @@ namespace Bit.iOS.Extension
|
|||
Resolver.ResetResolver(new UnityResolver(container));
|
||||
}
|
||||
|
||||
private void SetCulture()
|
||||
{
|
||||
var localizeService = Resolver.Resolve<ILocalizeService>();
|
||||
var ci = localizeService.GetCurrentCultureInfo();
|
||||
AppResources.Culture = ci;
|
||||
localizeService.SetLocale(ci);
|
||||
}
|
||||
|
||||
private bool ProcessItemProvider(NSItemProvider itemProvider, string type, Action<NSDictionary> action)
|
||||
{
|
||||
if(!itemProvider.HasItemConformingTo(type))
|
||||
|
|
|
@ -64,7 +64,8 @@ namespace Bit.iOS
|
|||
Resolver.Resolve<IFingerprint>(),
|
||||
Resolver.Resolve<ISettings>(),
|
||||
Resolver.Resolve<ILockService>(),
|
||||
Resolver.Resolve<IGoogleAnalyticsService>()));
|
||||
Resolver.Resolve<IGoogleAnalyticsService>(),
|
||||
Resolver.Resolve<ILocalizeService>()));
|
||||
|
||||
// Appearance stuff
|
||||
|
||||
|
@ -259,6 +260,8 @@ namespace Bit.iOS
|
|||
.RegisterType<IAppInfoService, AppInfoService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IGoogleAnalyticsService, GoogleAnalyticsService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IDeviceInfoService, DeviceInfoService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ILocalizeService, LocalizeService>(new ContainerControlledLifetimeManager())
|
||||
// Repositories
|
||||
// Repositories
|
||||
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
|
||||
|
|
Loading…
Reference in a new issue