{"id":47570,"date":"2020-07-21T10:32:51","date_gmt":"2020-07-21T17:32:51","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/xamarin\/?p=47570"},"modified":"2020-09-15T11:16:18","modified_gmt":"2020-09-15T18:16:18","slug":"mastering-multilingual-in-xamarin-forms","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/xamarin\/mastering-multilingual-in-xamarin-forms\/","title":{"rendered":"Mastering Multilingual in Xamarin.Forms"},"content":{"rendered":"<p><strong>This is a guest blog post by Charlin Agramonte. Charlin is a Microsoft MVP. She writes Xamarin articles in her blog <a href=\"http:\/\/xamgirl.com\">xamgirl.com<\/a> and you can find her on Twitter at <a href=\"https:\/\/twitter.com\/chard003\">@Chard003<\/a>.<\/strong><\/p>\n<p>Multilingual support is one of the most common requirements for mobile apps. One of the great parts of building mobile apps with Xamarin is that handling multiple languages is super simple. It may seem like a difficult task, but it is relatively easy if you leverage built in .NET capabilities to add multilingual support to your apps. A while back I wrote a<a href=\"https:\/\/xamgirl.com\/handle-multilingual-in-xamarin-forms-without-any-plugin\/\" rel=\"noopener noreferrer\" target=\"_blank\">n article<\/a> on how to do add multilingual support with my <a href=\"https:\/\/www.nuget.org\/packages\/Plugin.Multilingual\" rel=\"noopener noreferrer\" target=\"_blank\">plugin<\/a>. In this article, I will talk about some typical problems that you might face when adding multilingual support and how to solve them. Let\u2019s start!<\/p>\n<h2>Setting App Culture<\/h2>\n<p>When multiple languages are handled in our application, a typical use case is to support that the user can change the language within the application. The problem can come up because we often bind our XAML strings to the <code>AppResources<\/code> class directly. This means we don\u2019t have a mechanism to observe when the language changes and we would need to restart the application to get those strings updated.<\/p>\n<p>The code to set the <code>Culture<\/code> is usually set in the App startup code:<\/p>\n<pre><code class=\"csharp\">Thread.CurrentThread.CurrentUICulture = language;\nAppResources.Culture = language;\nApp.Current.MainPage = new NavigationPage(new MainPage());\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2020\/07\/Video-showing-selecting-a-new-langauge-and-seeing-text-update-on-the-mobile-app.gif\" alt=\"Mastering multilingual in Xamarin.Forms\" width=\"236\" height=\"480\" class=\"aligncenter size-full wp-image-47571\" \/><\/p>\n<p>While this works, restarting our entire application could be painful and isn\u2019t a good user experience.<\/p>\n<h2>Improving Runtime Changes<\/h2>\n<p>As I mentioned, the main problem with this is approach is that we bind to the AppResources class;s strings directly in the XAML. Or the app is using code behind and a translate class that returns a string with the text changed.<\/p>\n<p>When I was looking for a solution I found this great idea on <a href=\"https:\/\/stackoverflow.com\/questions\/40554561\/re-evaluate-all-values-in-xaml-page-calculated-by-a-markup-extension\/40567656\">StackOverflow<\/a> with a localization manager.<\/p>\n<p>I created a LocalizationResourceManager class that handles language changes and also will have a property with the translation. When the language changes it will invalidate that string so it will force the property to change.<\/p>\n<pre><code class=\"csharp\">\n public class LocalizationResourceManager : INotifyPropertyChanged\n    {\n        public static LocalizationResourceManager Instance { get; } = new LocalizationResourceManager();\n\n        public string this[string text]\n        {\n            get\n            {\n                return AppResources.ResourceManager.GetString(text, AppResources.Culture);\n            }\n        }\n\n        public void SetCulture(CultureInfo language)\n        {\n            Thread.CurrentThread.CurrentUICulture = language;\n            AppResources.Culture = language;\n\n            Invalidate();\n        }\n\n        public event PropertyChangedEventHandler PropertyChanged;\n\n        public void Invalidate()\n        {\n            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));\n        }\n    }\n<\/code><\/pre>\n<p>Instead of binding our strings directly to the AppResources class, a Translate Extension that returns a BindingProperty is used and binds to our new LocalizationResourceManager.<\/p>\n<pre><code class=\"csharp\">\n[ContentProperty(\"Text\")]\npublic class TranslateExtension : IMarkupExtension&lt;bindingbase>\n{\n    public string Text { get; set; }\n    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)\n    {\n        return ProvideValue(serviceProvider);\n    }\n\n    public BindingBase ProvideValue(IServiceProvider serviceProvider)\n    {\n        var binding = new Binding\n        {\n            Mode = BindingMode.OneWay,\n            Path = $\"[{Text}]\",\n            Source = ResourceManagerHelper.Instance,\n        };\n        return binding;\n    }\n}\n&lt;\/bindingbase><\/code><\/pre>\n<p>Finally, in our XAML we just have to use that translate extension.<\/p>\n<pre><code class=\"xaml\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n&lt;ContentPage xmlns=\"http:\/\/xamarin.com\/schemas\/2014\/forms\"\n             xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2009\/xaml\"\n             x:Class=\"MultilingualXFSample.Views.ChangeLanguagePage\"\n             xmlns:helpers=\"clr-namespace:MultilingualXFSample.Helpers\"&gt;\n\n         &lt;Label  Text=\"{helpers:Translate SelectLanguage}\" \/&gt;\n&lt;\/ContentPage&gt;\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2020\/07\/Video-showing-selecting-a-new-langauge-and-seeing-text-update-on-the-mobile-app-without-an-app-restart.gif\" alt=\"Video showing selecting a new langauge and seeing text update on the mobile app without an app restart\" width=\"234\" height=\"480\" class=\"aligncenter size-full wp-image-47572\" \/><\/p>\n<h2>Platform Control Localization<\/h2>\n<p>Since native platforms don\u2019t give us enough flexibility to change the control\u2019s locale in runtime we have to do a bit of custom work. After checking all the Xamarin.Forms controls, I only found 3 that we need to localize (<code>Picker<\/code>, <code>DatePicker<\/code>, and <code>TimePicker<\/code>). Let&#8217;s first start with the \u201cDone\u201d button text that is common on Pickers.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2020\/07\/Screen-Shot-2020-07-13-at-1.22.33-PM-1024x251.png\" alt=\"iOS pickers, date pickers, and time pickers\" width=\"640\" height=\"157\" class=\"aligncenter size-large wp-image-47573\" srcset=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2020\/07\/Screen-Shot-2020-07-13-at-1.22.33-PM-1024x251.png 1024w, https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2020\/07\/Screen-Shot-2020-07-13-at-1.22.33-PM-300x74.png 300w, https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2020\/07\/Screen-Shot-2020-07-13-at-1.22.33-PM-768x188.png 768w, https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2020\/07\/Screen-Shot-2020-07-13-at-1.22.33-PM-1536x376.png 1536w, https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2020\/07\/Screen-Shot-2020-07-13-at-1.22.33-PM.png 2048w\" sizes=\"(max-width: 640px) 100vw, 640px\" \/><\/p>\n<h3>iOS<\/h3>\n<p>For iOS, I created an effect that can be applied to all of these controls. I added it as a general style so I don\u2019t have to add it each control when I use them in my UI.<\/p>\n<h4>Effect<\/h4>\n<pre><code class=\"csharp\">[assembly:ResolutionGroupName(\"MyCompany\")]\n[assembly:ExportEffect(typeof(iOSPickerDoneButtonEffect), nameof(PickerDoneButton))]\nnamespace MultilingualXFSample.iOS\n{\n   public class iOSPickerDoneButtonEffect : PlatformEffect\n   {\n    UIButton _doneBtn;\n    UIBarButtonItem _originalDoneBarButtonItem;\n\n\n    protected override void OnAttached()\n    {\n        var effect = (PickerDoneButton)Element.Effects.FirstOrDefault(e =&gt; e is PickerDoneButton);\n\n        if (effect != null && Control?.InputAccessoryView is UIToolbar toolbar && toolbar.Items?.Count() &gt; 0)\n        {\n            _originalDoneBarButtonItem = toolbar.Items[1];\n\n            _doneBtn = new UIButton(UIButtonType.System);\n            _doneBtn.SetTitle(effect.ButtonTitle, UIControlState.Normal);\n            _doneBtn.Font = UIFont.BoldSystemFontOfSize(UIFont.SystemFontSize);\n            _doneBtn.TouchUpInside += HandleButtonClicked;\n\n            _originalDoneBarButtonItem.CustomView = _doneBtn;\n\n            if (Control.InputView is UIDatePicker picker)\n            {\n                picker.Locale = new Foundation.NSLocale(LocalizationResourceManager.Instance.CurrentCulture.TwoLetterISOLanguageName);\n            }\n        }\n    }\n\n\n    protected override void OnDetached()\n    {\n        if(_doneBtn !=null)\n        {\n            _doneBtn.TouchUpInside -= HandleButtonClicked;\n            _doneBtn = null;\n            _originalDoneBarButtonItem.CustomView = null;\n        }\n    }\n\n    void HandleButtonClicked(object sender, EventArgs e)\n    {\n        UIApplication.SharedApplication.SendAction(_originalDoneBarButtonItem.Action, _originalDoneBarButtonItem.Target, sender: null, forEvent: null);\n    }\n  }\n}\n<\/code><\/pre>\n<h4>Style<\/h4>\n<pre><code class=\"xml\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\n&lt;Application xmlns=\"http:\/\/xamarin.com\/schemas\/2014\/forms\"\n             xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2009\/xaml\"\n             xmlns:d=\"http:\/\/xamarin.com\/schemas\/2014\/forms\/design\"\n             xmlns:mc=\"http:\/\/schemas.openxmlformats.org\/markup-compatibility\/2006\"\n             xmlns:helpers=\"clr-namespace:MultilingualXFSample.Helpers\"\n             xmlns:effects=\"clr-namespace:MultilingualXFSample.Effects\"\n             mc:Ignorable=\"d\"\n             x:Class=\"MultilingualXFSample.App\"&gt;\n    &lt;Application.Resources&gt;\n        &lt;ResourceDictionary&gt;\n            &lt;Style TargetType=\"Picker\" ApplyToDerivedTypes=\"True\"&gt;\n                  &lt;Style.Setters&gt;\n                    &lt;Setter Property=\"effects:PickerDoneButtonEffect.DoneButtonText\" Value=\"{helpers:Translate Done}\" \/&gt;\n                  &lt;\/Style.Setters&gt;\n            &lt;\/Style&gt;\n\n             &lt;Style TargetType=\"DatePicker\" ApplyToDerivedTypes=\"True\"&gt;\n                  &lt;Style.Setters&gt;\n                    &lt;Setter Property=\"effects:PickerDoneButtonEffect.DoneButtonText\" Value=\"{helpers:Translate Done}\" \/&gt;\n                  &lt;\/Style.Setters&gt;\n            &lt;\/Style&gt;\n\n             &lt;Style TargetType=\"TimePicker\" ApplyToDerivedTypes=\"True\"&gt;\n                  &lt;Style.Setters&gt;\n                    &lt;Setter Property=\"effects:PickerDoneButtonEffect.DoneButtonText\" Value=\"{helpers:Translate Done}\" \/&gt;\n                  &lt;\/Style.Setters&gt;\n            &lt;\/Style&gt;\n        &lt;\/ResourceDictionary&gt;\n    &lt;\/Application.Resources&gt;\n&lt;\/Application&gt;\n<\/code><\/pre>\n<h3>Android<\/h3>\n<p>On Android, I created an effect for the Picker and for DatePicker\/TimePicker I used a platform renderer. Since they have a Cancel\/Done button, there\u2019s not much flexibility to customize those buttons.<\/p>\n<p>You can check the full implementation for both platforms <a href=\"https:\/\/github.com\/CrossGeeks\/MasteringMultilingualSample\">here<\/a>.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/xamarin\/wp-content\/uploads\/sites\/44\/2020\/07\/Video-showing-pickers-that-are-being-translated.gif\" alt=\"Image Video showing pickers that are being translated\" width=\"234\" height=\"480\" class=\"aligncenter size-full wp-image-47574\" \/><\/p>\n<h2>Localized C# Text<\/h2>\n<p>Since the AppResources class is a static class, we can access it directly in C# code with just a few steps:<\/p>\n<h3>Add the string to the Resource file.<\/h3>\n<pre><code class=\"xml\">&lt;data name=\"WelcomeText\" xml:space=\"preserve\"&gt;\n    &lt;value&gt;Welcome to my App&lt;\/value&gt;\n&lt;\/data&gt;\n<\/code><\/pre>\n<h3>Use it!<\/h3>\n<pre><code class=\"csharp\">var text = AppResources.WelcomeText; \n<\/code><\/pre>\n<h2>Remembering the Language<\/h2>\n<p>Thanks to Xamarin.Essentials this is pretty easy to achieve. We only have to persist the latest language code in the Preferences and load that persisted language code when opening the application.<\/p>\n<p>I added this logic to the actual LocalizationResourceManager.<\/p>\n<pre><code class=\"csharp\">namespace MultilingualXFSample.Helpers\n{\n    public class LocalizationResourceManager : INotifyPropertyChanged\n    {\n        private const string LanguageKey = nameof(LanguageKey);\n\n        private LocalizationResourceManager()\n        {\n            SetCulture(new CultureInfo(Preferences.Get(LanguageKey, CurrentCulture.TwoLetterISOLanguageName)));\n        }\n        \/\/...\n        public void SetCulture(CultureInfo language)\n        {\n            \/\/...\n            Preferences.Set(LanguageKey, language.TwoLetterISOLanguageName);\n        }\n    }\n}\n<\/code><\/pre>\n<h2>Handle small text modifications<\/h2>\n<p>There are some cases where we want to use translated text with a minor string modification. For example, we have the text \u201cSelect Language\u201d and \u201cSelect Language:\u201d. For these use cases we have a few options, we can add both texts in our AppResources file or add the text with the : in our viewmodel. The disadvantage of using the first one is that you will have repeatable texts in your AppResource file and using the second one you will be handling presentation logic in the ViewModel.<\/p>\n<p>A better approach is to use a FormattedText:<\/p>\n<pre><code class=\"xml\">  &lt;Label&gt;\n    &lt;Label.FormattedText&gt;\n        &lt;FormattedString&gt;\n            &lt;Span Text=\"{helpers:Translate SelectLanguage}\" \/&gt;\n            &lt;Span Text=\":\" \/&gt;\n        &lt;\/FormattedString&gt;\n    &lt;\/Label.FormattedText&gt;\n&lt;\/Label&gt;\n<\/code><\/pre>\n<p>This approach works very well when you are using a Label, since it supports FormattedText, but what happens when you have simple string? Since we bind to the AppResources class or use a TranslateExtension returning a simple text you can\u2019t use StringFormat.<\/p>\n<p>Workaround Modify our actual TranslateExtension file to support StringFormat.<\/p>\n<pre><code class=\"csharp\">[ContentProperty(\"Text\")]\n    public class TranslateExtension : IMarkupExtension&lt;BindingBase&gt;\n    {\n        public string Text { get; set; }\n        public string StringFormat { get; set; }\n        object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)\n        {\n            return ProvideValue(serviceProvider);\n        }\n\n        public BindingBase ProvideValue(IServiceProvider serviceProvider)\n        {\n            var binding = new Binding\n            {\n                Mode = BindingMode.OneWay,\n                Path = $\"[{Text}]\",\n                Source = ResourceManagerHelper.Instance,\n                StringFormat= StringFormat\n\n            };\n            return binding;\n        }\n    }\n<\/code><\/pre>\n<p>And the XAML:<\/p>\n<pre><code class=\"xml\"> &lt;Label  Text=\"{helpers:Translate SelectLanguage, StringFormat='{0}:'}\" \/&gt;\n<\/code><\/pre>\n<h2>Final thoughts<\/h2>\n<p>As you see, handling Multilingual in Xamarin.Forms is quite easy even in the most complex scenarios there are easy ways to get it working for your needs. As an extra recommendation, I encourage you to check these great tips in the <a href=\"https:\/\/docs.microsoft.com\/xamarin\/xamarin-forms\/app-fundamentals\/localization\/\" rel=\"noopener noreferrer\" target=\"_blank\">Xamarin documentation<\/a> about Localization best practices that will help you to have more consistent code. Additionally, for simplicity is to use the <a href=\"https:\/\/devblogs.microsoft.com\/xamarin\/add-languages-to-your-apps-with-xamarin-and-multilingual-app-toolkit\/\" rel=\"noopener noreferrer\" target=\"_blank\">Multilingual Toolkit<\/a> if you are using Visual Studio for Windows and <a href=\"https:\/\/www.mfractor.com\/blogs\/news\/localising-your-xamarin-forms-apps\" rel=\"noopener noreferrer\" target=\"_blank\">MFractor<\/a> if you are using Visual Studio for Mac. Both are really useful tools when it comes to localization related tasks. That\u2019s all for now. You can check the full source code used <a href=\"https:\/\/github.com\/CrossGeeks\/MasteringMultilingualSample\" rel=\"noopener noreferrer\" target=\"_blank\">here<\/a>.<\/p>\n<p>Happy coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This guest post by Charlin Agramonte elaborates on how multilingual support is one of the most common requirements for mobile apps and the simplicity of building mobile apps with Xamarin that handle multiple languages.<\/p>\n","protected":false},"author":579,"featured_media":47572,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[2,367],"tags":[7282,587,9003,9010,9019,9002,9018,27,16],"class_list":["post-47570","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-developers","category-xamarin-forms","tag-net","tag-accessibility","tag-localization","tag-mobile-applications","tag-mobile-apps","tag-multilingual","tag-multiple-languages","tag-xamarin","tag-xamarin-forms"],"acf":[],"blog_post_summary":"<p>This guest post by Charlin Agramonte elaborates on how multilingual support is one of the most common requirements for mobile apps and the simplicity of building mobile apps with Xamarin that handle multiple languages.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/47570","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/users\/579"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/comments?post=47570"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/posts\/47570\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media\/47572"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/media?parent=47570"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/categories?post=47570"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/xamarin\/wp-json\/wp\/v2\/tags?post=47570"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}