diff --git a/Pixiview.iOS/GlobalSuppressions.cs b/Pixiview.iOS/GlobalSuppressions.cs new file mode 100644 index 0000000..4285a53 --- /dev/null +++ b/Pixiview.iOS/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "")] diff --git a/Pixiview.iOS/Pixiview.iOS.csproj b/Pixiview.iOS/Pixiview.iOS.csproj index e7a89a4..eb8b40c 100644 --- a/Pixiview.iOS/Pixiview.iOS.csproj +++ b/Pixiview.iOS/Pixiview.iOS.csproj @@ -74,6 +74,7 @@ + diff --git a/Pixiview.iOS/Renderers/AdaptedPageRenderer.cs b/Pixiview.iOS/Renderers/AdaptedPageRenderer.cs index c06fbd2..9a4d45c 100644 --- a/Pixiview.iOS/Renderers/AdaptedPageRenderer.cs +++ b/Pixiview.iOS/Renderers/AdaptedPageRenderer.cs @@ -66,7 +66,6 @@ namespace Pixiview.iOS.Renderers } [SuppressMessage("Code Notifications", "XI0002:Notifies you from using newer Apple APIs when targeting an older OS version", Justification = "")] - [SuppressMessage("Style", "IDE0066:Convert switch statement to expression", Justification = "")] private UIStatusBarStyle ConvertStyle(StatusBarStyles style) { switch (style) diff --git a/Pixiview.iOS/Renderers/CardViewRenderer.cs b/Pixiview.iOS/Renderers/CardViewRenderer.cs index 6f2e7d9..8971cf5 100644 --- a/Pixiview.iOS/Renderers/CardViewRenderer.cs +++ b/Pixiview.iOS/Renderers/CardViewRenderer.cs @@ -1,7 +1,5 @@ -using CoreGraphics; -using Pixiview.iOS.Renderers; +using Pixiview.iOS.Renderers; using Pixiview.UI; -using UIKit; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; diff --git a/Pixiview.iOS/Services/EnvironmentService.cs b/Pixiview.iOS/Services/EnvironmentService.cs index dca6429..c060999 100644 --- a/Pixiview.iOS/Services/EnvironmentService.cs +++ b/Pixiview.iOS/Services/EnvironmentService.cs @@ -1,5 +1,9 @@ using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Threading; +using Foundation; using Pixiview.iOS.Services; +using Pixiview.Resources; using Pixiview.Utils; using UIKit; using Xamarin.Essentials; @@ -10,6 +14,20 @@ namespace Pixiview.iOS.Services { public class EnvironmentService : IEnvironmentService { + public EnvironmentParameter GetEnvironment() + { + return new EnvironmentParameter + { + IconLightFontFamily = "FontAwesome5Pro-Light", + IconRegularFontFamily = "FontAwesome5Pro-Regular", + IconSolidFontFamily = "FontAwesome5Pro-Solid", + + IconLeft = "\uf104" // for android, it's "\uf060" + }; + } + + #region - Theme - + [SuppressMessage("Code Notifications", "XI0002:Notifies you from using newer Apple APIs when targeting an older OS version", Justification = "")] public Theme GetApplicationTheme() { @@ -36,21 +54,110 @@ namespace Pixiview.iOS.Services } } - public EnvironmentParameter GetEnvironment() - { - return new EnvironmentParameter - { - IconLightFontFamily = "FontAwesome5Pro-Light", - IconRegularFontFamily = "FontAwesome5Pro-Regular", - IconSolidFontFamily = "FontAwesome5Pro-Solid", - - IconLeft = "\uf104" // for android, it's "\uf060" - }; - } - public void SetStatusBarColor(Color color) { // nothing need to do } + + #endregion + + #region - Culture Info - + + public CultureInfo GetCurrentCultureInfo() + { + string lang; + if (NSLocale.PreferredLanguages.Length > 0) + { + var pref = NSLocale.PreferredLanguages[0]; + lang = iOSToDotnetLanguage(pref); + } + else + { + lang = "zh-CN"; + } + + CultureInfo ci; + var platform = new PlatformCulture(lang); + try + { + ci = new CultureInfo(platform.Language); + } + catch (CultureNotFoundException e) + { + try + { + var fallback = ToDotnetFallbackLanguage(platform); + App.DebugPrint($"{lang} failed, trying {fallback} ({e.Message})"); + ci = new CultureInfo(fallback); + } + catch (CultureNotFoundException e1) + { + App.DebugError("culture.get", $"{lang} couldn't be set, using 'zh-CN' ({e1.Message})"); + ci = new CultureInfo("zh-CN"); + } + } + + return ci; + } + + [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] + string iOSToDotnetLanguage(string iOSLanguage) + { + string netLanguage; + + //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 + default: + netLanguage = iOSLanguage; + break; + } + + App.DebugPrint($"iOS Language: {iOSLanguage}, .NET Language/Locale: {netLanguage}"); + return netLanguage; + } + + string ToDotnetFallbackLanguage(PlatformCulture platCulture) + { + string netLanguage; + + 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 + default: + netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually); + break; + } + + App.DebugPrint($".NET Fallback Language/Locale: {platCulture.LanguageCode} to {netLanguage} (application-specific)"); + return netLanguage; + } + + public void SetCultureInfo(CultureInfo ci) + { + Thread.CurrentThread.CurrentCulture = ci; + Thread.CurrentThread.CurrentUICulture = ci; + + App.DebugPrint($"CurrentCulture set: {ci.Name}"); + } + + #endregion } } diff --git a/Pixiview/App.cs b/Pixiview/App.cs index 77180ec..686436b 100644 --- a/Pixiview/App.cs +++ b/Pixiview/App.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Pixiview.Resources; using Pixiview.UI.Theme; using Pixiview.Utils; using Xamarin.Forms; @@ -11,16 +12,11 @@ namespace Pixiview { // public properties public static Theme CurrentTheme { get; private set; } + public static PlatformCulture CurrentCulture { get; private set; } public static Dictionary ExtraResources { get; private set; } - public App() + private void InitResources(IEnvironmentService service) { - InitResources(); - } - - private void InitResources() - { - var service = DependencyService.Get(); var p = service.GetEnvironment(); ExtraResources = new Dictionary @@ -36,6 +32,13 @@ namespace Pixiview SetTheme(theme, true); } + private void InitLanguage(IEnvironmentService service) + { + var ci = service.GetCurrentCultureInfo(); + service.SetCultureInfo(ci); + CurrentCulture = new PlatformCulture(ci.Name.ToLower()); + } + private void SetTheme(Theme theme, bool force = false) { if (force || theme != CurrentTheme) @@ -62,6 +65,10 @@ namespace Pixiview protected override void OnStart() { + var service = DependencyService.Get(); + InitResources(service); + InitLanguage(service); + MainPage = UIFactory.CreateNavigationPage(new MainPage()); } diff --git a/Pixiview/MainPage.xaml b/Pixiview/MainPage.xaml index 50cc0bf..caf028f 100644 --- a/Pixiview/MainPage.xaml +++ b/Pixiview/MainPage.xaml @@ -5,13 +5,14 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:u="clr-namespace:Pixiview.UI" xmlns:util="clr-namespace:Pixiview.Utils" + xmlns:r="clr-namespace:Pixiview.Resources" mc:Ignorable="d" x:Class="Pixiview.MainPage" BackgroundColor="{DynamicResource WindowColor}" OrientationChanged="Page_OrientationChanged" util:StatusBar.StatusBarStyle="{DynamicResource StatusBarStyle}"> - diff --git a/Pixiview/Pixiview.csproj b/Pixiview/Pixiview.csproj index 7ae65c0..87eb65b 100644 --- a/Pixiview/Pixiview.csproj +++ b/Pixiview/Pixiview.csproj @@ -19,5 +19,13 @@ + + + + + + + + \ No newline at end of file diff --git a/Pixiview/Resources/Languages/zh-CN.xml b/Pixiview/Resources/Languages/zh-CN.xml new file mode 100644 index 0000000..d12a3ae --- /dev/null +++ b/Pixiview/Resources/Languages/zh-CN.xml @@ -0,0 +1,4 @@ + + + 已关注 + \ No newline at end of file diff --git a/Pixiview/Resources/PlatformCulture.cs b/Pixiview/Resources/PlatformCulture.cs new file mode 100644 index 0000000..cf986bf --- /dev/null +++ b/Pixiview/Resources/PlatformCulture.cs @@ -0,0 +1,43 @@ +namespace Pixiview.Resources +{ + public class PlatformCulture + { + public string PlatformString { get; private set; } + public string LanguageCode { get; private set; } + public string LocaleCode { get; private set; } + + public string Language + { + get { return string.IsNullOrEmpty(LocaleCode) ? LanguageCode : LanguageCode + "-" + LocaleCode; } + } + + public PlatformCulture() : this(null) { } + public PlatformCulture(string cultureString) + { + if (string.IsNullOrEmpty(cultureString)) + { + //throw new ArgumentNullException(nameof(cultureString), "Expected culture identieifer"); + cultureString = "en"; + } + + PlatformString = cultureString.Replace('_', '-'); + var index = PlatformString.IndexOf('-'); + if (index > 0) + { + var parts = PlatformString.Split('-'); + LanguageCode = parts[0]; + LocaleCode = parts[parts.Length - 1]; + } + else + { + LanguageCode = PlatformString; + LocaleCode = ""; + } + } + + public override string ToString() + { + return PlatformString; + } + } +} diff --git a/Pixiview/Resources/ResourceHelper.cs b/Pixiview/Resources/ResourceHelper.cs new file mode 100644 index 0000000..46d31e3 --- /dev/null +++ b/Pixiview/Resources/ResourceHelper.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace Pixiview.Resources +{ + public class ResourceHelper + { + static readonly Dictionary dict = new Dictionary(); + + public static string GetResource(string name, params object[] args) + { + if (!dict.TryGetValue(App.CurrentCulture.PlatformString, out LanguageResource lang)) + { + lang = new LanguageResource(App.CurrentCulture); + dict.Add(App.CurrentCulture.PlatformString, lang); + } + + if (args == null || args.Length == 0) + { + return lang[name]; + } + return string.Format(lang[name], args); + } + + private class LanguageResource + { + private readonly Dictionary strings; + + public string this[string key] + { + get + { + if (strings != null && strings.TryGetValue(key, out string val)) + { + return val; + } + return key; + } + } + + public LanguageResource(PlatformCulture lang) + { + try + { + var assembly = IntrospectionExtensions.GetTypeInfo(typeof(LanguageResource)).Assembly; + var names = assembly.GetManifestResourceNames(); + var name = names.FirstOrDefault(n + => string.Equals(n, $"Pixiview.Resources.Languages.{lang.Language}.xml", StringComparison.OrdinalIgnoreCase) + || string.Equals(n, $"Pixiview.Resources.Languages.{lang.LanguageCode}.xml", StringComparison.OrdinalIgnoreCase)); + if (name == null) + { + name = "Pixiview.Resources.Languages.zh-CN.xml"; + } + XDocument xml; + using (var stream = assembly.GetManifestResourceStream(name)) + { + xml = XDocument.Load(stream); + } + strings = new Dictionary(); + foreach (var ele in xml.Root.Elements()) + { + strings[ele.Name.LocalName] = ele.Value; + } + } + catch + { + // load failed + } + } + } + } + + [ContentProperty(nameof(Text))] + public class TextExtension : IMarkupExtension + { + public string Text { get; set; } + + public object ProvideValue(IServiceProvider serviceProvider) + { + if (Text == null) + { + return string.Empty; + } + return ResourceHelper.GetResource(Text); + } + } +} diff --git a/Pixiview/Utils/IEnvironmentService.cs b/Pixiview/Utils/IEnvironmentService.cs index fc37ea5..53f5645 100644 --- a/Pixiview/Utils/IEnvironmentService.cs +++ b/Pixiview/Utils/IEnvironmentService.cs @@ -1,12 +1,17 @@ -using Xamarin.Forms; +using System.Globalization; +using Xamarin.Forms; namespace Pixiview.Utils { public interface IEnvironmentService { EnvironmentParameter GetEnvironment(); + Theme GetApplicationTheme(); void SetStatusBarColor(Color color); + + CultureInfo GetCurrentCultureInfo(); + void SetCultureInfo(CultureInfo ci); } public class EnvironmentParameter