mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-04 08:50:18 +01:00
i18n service
This commit is contained in:
parent
6a65b6d735
commit
6ee109dc80
@ -80,6 +80,7 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\CryptoPrimitiveService.cs" />
|
||||
<Compile Include="Services\DeviceActionService.cs" />
|
||||
<Compile Include="Services\LocalizeService.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidAsset Include="Assets\FontAwesome.ttf" />
|
||||
|
@ -41,7 +41,11 @@ namespace Bit.Droid
|
||||
var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
|
||||
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
||||
var deviceActionService = new DeviceActionService();
|
||||
var localizeService = new LocalizeService();
|
||||
|
||||
ServiceContainer.Register<ILocalizeService>("localizeService", localizeService);
|
||||
ServiceContainer.Register<II18nService>("i18nService",
|
||||
new MobileI18nService(localizeService.GetCurrentCultureInfo()));
|
||||
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", new CryptoPrimitiveService());
|
||||
ServiceContainer.Register<IStorageService>("storageService",
|
||||
new MobileStorageService(preferencesStorage, liteDbStorage));
|
||||
|
97
src/Android/Services/LocalizeService.cs
Normal file
97
src/Android/Services/LocalizeService.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
public class LocalizeService : ILocalizeService
|
||||
{
|
||||
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;
|
||||
if(androidLanguage.StartsWith("zh"))
|
||||
{
|
||||
if(androidLanguage.Contains("Hant") || androidLanguage.Contains("TW") ||
|
||||
androidLanguage.Contains("HK") || androidLanguage.Contains("MO"))
|
||||
{
|
||||
netLanguage = "zh-Hant";
|
||||
}
|
||||
else
|
||||
{
|
||||
netLanguage = "zh-Hans";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
9
src/App/Abstractions/ILocalizeService.cs
Normal file
9
src/App/Abstractions/ILocalizeService.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Bit.App.Abstractions
|
||||
{
|
||||
public interface ILocalizeService
|
||||
{
|
||||
CultureInfo GetCurrentCultureInfo();
|
||||
}
|
||||
}
|
@ -1,8 +1,11 @@
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Services;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.StyleSheets;
|
||||
using Xamarin.Forms.Xaml;
|
||||
@ -12,18 +15,23 @@ namespace Bit.App
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
private readonly MobileI18nService _i18nService;
|
||||
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService;
|
||||
|
||||
InitializeComponent();
|
||||
SetCulture();
|
||||
ThemeManager.SetTheme("light");
|
||||
MainPage = new TabsPage();
|
||||
|
||||
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
|
||||
MessagingCenter.Subscribe<Application, DialogDetails>(Current, "ShowDialog", async (sender, details) =>
|
||||
{
|
||||
var confirmed = true;
|
||||
// TODO: ok text
|
||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ? "Ok" : details.ConfirmText;
|
||||
var confirmText = string.IsNullOrWhiteSpace(details.ConfirmText) ?
|
||||
AppResources.Ok : details.ConfirmText;
|
||||
if(!string.IsNullOrWhiteSpace(details.CancelText))
|
||||
{
|
||||
confirmed = await MainPage.DisplayAlert(details.Title, details.Text, confirmText,
|
||||
@ -51,5 +59,14 @@ namespace Bit.App
|
||||
{
|
||||
// Handle when your app resumes
|
||||
}
|
||||
|
||||
private void SetCulture()
|
||||
{
|
||||
_i18nService.Init();
|
||||
// Calendars are removed by linker. ref https://bugzilla.xamarin.com/show_bug.cgi?id=59077
|
||||
new System.Globalization.ThaiBuddhistCalendar();
|
||||
new System.Globalization.HijriCalendar();
|
||||
new System.Globalization.UmAlQuraCalendar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
src/App/Models/PlatformCulture.cs
Normal file
39
src/App/Models/PlatformCulture.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.App.Models
|
||||
{
|
||||
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 = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public string PlatformString { get; private set; }
|
||||
public string LanguageCode { get; private set; }
|
||||
public string LocaleCode { get; private set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return PlatformString;
|
||||
}
|
||||
}
|
||||
}
|
67
src/App/Services/MobileI18nService.cs
Normal file
67
src/App/Services/MobileI18nService.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Threading;
|
||||
|
||||
namespace Bit.App.Services
|
||||
{
|
||||
public class MobileI18nService : II18nService
|
||||
{
|
||||
private const string ResourceId = "UsingResxLocalization.Resx.AppResources";
|
||||
|
||||
private static readonly Lazy<ResourceManager> _resourceManager = new Lazy<ResourceManager>(() =>
|
||||
new ResourceManager(ResourceId, IntrospectionExtensions.GetTypeInfo(typeof(MobileI18nService)).Assembly));
|
||||
|
||||
private readonly CultureInfo _defaultCulture = new CultureInfo("en-US");
|
||||
private bool _inited;
|
||||
|
||||
public MobileI18nService(CultureInfo systemCulture)
|
||||
{
|
||||
Culture = systemCulture;
|
||||
}
|
||||
|
||||
public CultureInfo Culture { get; set; }
|
||||
|
||||
public void Init(CultureInfo culture = null)
|
||||
{
|
||||
if(_inited)
|
||||
{
|
||||
throw new Exception("I18n already inited.");
|
||||
}
|
||||
_inited = true;
|
||||
if(culture != null)
|
||||
{
|
||||
Culture = culture;
|
||||
}
|
||||
AppResources.Culture = Culture;
|
||||
Thread.CurrentThread.CurrentCulture = Culture;
|
||||
Thread.CurrentThread.CurrentUICulture = Culture;
|
||||
}
|
||||
|
||||
public string T(string id, params string[] p)
|
||||
{
|
||||
return Translate(id, p);
|
||||
}
|
||||
|
||||
public string Translate(string id, params string[] p)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
var result = _resourceManager.Value.GetString(id, Culture);
|
||||
if(result == null)
|
||||
{
|
||||
result = _resourceManager.Value.GetString(id, _defaultCulture);
|
||||
if(result == null)
|
||||
{
|
||||
result = $"{{{id}}}";
|
||||
}
|
||||
}
|
||||
return string.Format(result, p);
|
||||
}
|
||||
}
|
||||
}
|
29
src/App/Utilities/TranslateExtension.cs
Normal file
29
src/App/Utilities/TranslateExtension.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using System;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Bit.App.Utilities
|
||||
{
|
||||
[ContentProperty("Text")]
|
||||
public class TranslateExtension : IMarkupExtension
|
||||
{
|
||||
private II18nService _i18nService;
|
||||
|
||||
public TranslateExtension()
|
||||
{
|
||||
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string P1 { get; set; }
|
||||
public string P2 { get; set; }
|
||||
public string P3 { get; set; }
|
||||
|
||||
public object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return _i18nService.T(Id, P1, P2, P3);
|
||||
}
|
||||
}
|
||||
}
|
11
src/Core/Abstractions/II18nService.cs
Normal file
11
src/Core/Abstractions/II18nService.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Bit.Core.Abstractions
|
||||
{
|
||||
public interface II18nService
|
||||
{
|
||||
CultureInfo Culture { get; set; }
|
||||
string T(string id, params string[] p);
|
||||
string Translate(string id, params string[] p);
|
||||
}
|
||||
}
|
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 Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Foundation;
|
||||
|
||||
namespace Bit.iOS.Core.Services
|
||||
{
|
||||
public class LocalizeService : ILocalizeService
|
||||
{
|
||||
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;
|
||||
if(iOSLanguage.StartsWith("zh-Hant") || iOSLanguage.StartsWith("zh-HK"))
|
||||
{
|
||||
netLanguage = "zh-Hant";
|
||||
}
|
||||
else if(iOSLanguage.StartsWith("zh"))
|
||||
{
|
||||
netLanguage = "zh-Hans";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
// Use the first part of the identifier (two chars, usually);
|
||||
var netLanguage = platCulture.LanguageCode;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -49,9 +49,14 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\CryptoPrimitiveService.cs" />
|
||||
<Compile Include="Services\KeyChainStorageService.cs" />
|
||||
<Compile Include="Services\LocalizeService.cs" />
|
||||
<Compile Include="Views\Toast.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\App\App.csproj">
|
||||
<Project>{ee44c6a1-2a85-45fe-8d9b-bf1d5f88809c}</Project>
|
||||
<Name>App</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Core\Core.csproj">
|
||||
<Project>{4b8a8c41-9820-4341-974c-41e65b7f4366}</Project>
|
||||
<Name>Core</Name>
|
||||
|
Loading…
Reference in New Issue
Block a user