Multilingual App Toolkit (MAT)

I’ve previously looked at the MAT in the post Adventures in UWP – Globalization & Localization UWP – Globalization & Localization. I’ve also used this within mobile applications using Xamarin Forms which I was about to update, but then realised I’d forgotten how this stuff worked.

Requirements

Before we start, we need to install a couple of things.

  • From Visual Studio 2019 (if not already installed) select Extensions | Manage Extensions and download the Multilingual App Toolkit. You’ll need to restart Visual Studio for the installation to take place.
  • Install (if not already installed) the Multilingual app toolkit 4.0 Editor as this is extremely useful for managing our translated files, but is NOT required.

Note: This does not currently seem to be supported in Visual Studio for Mac.

Mobile Application

Let’s start by creating a Xamarin Forms application to see how things work.

  • Create a Mobile App (Xamarin.Forms) in Visual Studio
  • Add a Resources folder to the shared project and then add a Resource File item to this folder, named AppResources.resx
  • Right mouse click on the shared project and select Properties, now select Package and towards the bottom of this screen, change the Assembly neutral language to your default language – for example mine’s English (United Kingdom). In other words our project culture will be set to en-GB
  • Enable MAT by selecting Tools | Multilingual App Toolkit | Enable on your solutions

At this point we now have the basics in place to begin localizing our application. We can now start adding strings to our AppResources.resx or alternatively we could start adding new languages, so let’s do that.

  • Right mouse click on your shared project and select Multilingual App Toolkit and from this select, Add translation languages…

Note: You may get and error regarding the need to install translation providers, this requires us to add an Azure subscription, but we don’t actually need this to use the tools, so just ignore this error for now.

From the Translation Languages check on any languages you want to support (you can also add languages at any point so don’t worry if you need to add more at a later date).

In my case I’m going to add German [de] and French (France) [fr-FR]. Once you press OK you’ll see two additional AppResources files added to the Resources folder, AppResources.de.resx for German and AppResources.fr-FR.resx for French.

You’ll also see the MultiligualResources folder with two .xlf files, one for German and one for French, these are used by MAT to generate our new AppResources files.

Note: Do not edit the localized AppResources.*.resx files by hand. Only AppResources.resx should be edited.

Editing our resources

Now that we have the xlf files and the default culture AppResources.resx file, we’ll want to start adding some strings.

Open the AppResources.resx file in Visual Studio and add some named strings, the Name being the identifier or key, the Value being the string to display and we can also add comments which might be useful for somebody who handles the subsequent translations.

So I’m going to create a basic login page, hence we’ll add the following

  • Name: Welcome, Value: Welcome to the application
  • Name: Login, Value: Login
  • Name: Username, Value: Username or email address
  • Name: Password, Value: Enter your password

Now build your application before we look to create some translations.

There’s a couple of ways to proceed with this, the simplest is as follows

  • From Visual Studio. Select you MultilingualResources folder or each .xlf in turn.
  • Right mouse click on the folder or file then select Multilingual App Toolkit | Generate Machine Translations.

You may find that some values are not translated, for example in my case Welcome to the application was not translated, we can edit the de.xlf file directly. In this case I change the target element to Willkommen bei der Anwendung and changed the target state to needs-review-translation

Once you’ve made your changes then build the project and MAT will generate the AppResources.*.resx files.

The second way to handle the translations and probably one that would be useful for people who are working on translations of your application. In this case we use the Multilingual Editor that was installed earlier. Open each of your xlf files in turn. For each one we’ll see a list of the strings as entered into the AppResources.rex file. So let’s concentrate on editing the .de.xlf German translation file.

  • Having opened the .de.xlf file, select the Strings tab at the bottom of the screen to ensure all the strings were added, now here we could manually translate each row by selecting the each row in turn, then changing the Translation in the editor in the middle of the editor UI.
  • If you edit the translation yourself then the State on the right of the screen will switch to Translated. This state will tell the MAT editor whether it should try to translate the value. If the user has marked the state as Translated we can assume this is correct and the editor will not attempt to translate the string using it’s generate translation option. You can change this State back to New if you want the auto-translation to take place.
  • If you prefer or like me you’re simply going to use something like Bing or Google to do the translations, i.e. https://www.bing.com/translator/. Then the Editor offers the Translate button. Clicking this will generate translations for all the text and mark them with the State Needs Review which is basically saying that the translation was made but needs somebody to review whether this makes sense in each language. You can leave this State as is or change to Translated if you’re happy with these translations.
  • Once completed, save the file(s) and build your solution to get the AppResources.*.rexr updated.

Displaying our translations in an application

Note: This post has been updated as the previously used Multilingual plugin is no longer required.

We can display translations is XAML using the AppResources generated code, for example

Title="{x:Static resources:AppResources.Settings}

and in code just as easily we use

var title = AppResources.Settings;

To test your translations you can set things up in code, i.e.

In your App.xaml.cx, after the InitializeComponent you can place the following code to set the culture to that of the device

var culture = CrossMultilingual.Current.DeviceCultureInfo;
AppResources.Culture = culture;

If you want to test your German translation, for example, replace these two lines with

var culture = new CultureInfo("de");
AppResources.Culture = culture;
CrossMultilingual.Current.CurrentCultureInfo = culture;

See https://github.com/putridparrot/blog-projects/tree/master/MatTestMobile for a sample application.

If you prefer to use the Android Emulator to set your language – which ofcourse makes a lot of sense, then go to Android Settings | System | Languages & input | Languages then Add a language if the one you want is not listed, then drag and drop that language to the top of the Languages list and Android will switch to that language.

Application lifecycle within Xamarin Forms

You’ve got your Xamarin Forms (or native Android/iOS) application up and running and now you need to start thinking about what happens when your application hibernates.

An application can go into hibernation (or sleep mode) and then resume at some point. The OS hibernates an application in response to events, such as power off or lock screen but also if it’s running low on memory. Therefore, so that our application “places nicely” on the OS, we need to think about how we will persist state and restore state during this hibernation phases.

Let’s look at the various methods in Xamarin Forms for handling different aspects of the application’s life cycle…

Xamarin Forms

Initialization/Starting

By default a Xamarin Forms application will create an OnInitialized method which is basically where the InitializeComponent method is called. It’s a good place to set up any initialization for your UI elements etc.

Assuming we’re starting our application from cold (i.e. first start) Then the following lifecycle methods are called in order

  1. OnInitialized
  2. OnStart

According to the App Lifecycle documentation “On Android, the OnStart method will be called on rotation as well as when the application first starts, if the main activity lacks ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation in the [Activity()] attribute”.

So OnInitialized remains the best place to set defaults up, but OnStart may be the best place to make any UI changes based upon orientation etc.

Entering sleep mode

If the device goes into sleep mode (we can simulate easily by hitting the power button on our emulator or device). The following is called

  1. OnSleep

This is our opportunity to persist state from the device so that when we resume the application it’ll appear in the same state as when it went into sleep mode.

There’s various ways to achieve this, if we’re using a pure MVVM approach with Prism (for example) we might use the EventAggregator to send a sleep message to all listening view models etc., which would then persist their data, or maybe we hold a cache (or similar) as a single data store and we persist this. Whichever way we go, the intention is that everything that is required to get the application back to the state pre-sleep, needs persisting. This also includes things such as which view (in a multiple view application) is displayed along with things like the view stack – well you get the idea, it depends how far you want to go to recreate the state of your application.

Note: another useful way to deal with view model data etc. is to actually persist it automatically when it changes, I don’t mean for every change of an entry control, but for example if you get data from a service, then you might store this in a cache and persist at the same time – obviously it depends on your specific scenarios and design.

Resuming from sleep mode

When an application is resumed, the following is called

  1. OnResume

At this point we need to read any persisted data, hydrate our data/view models with this previously stored data and then get the application back into the state it was when it went into sleep mode. As mentioned for OnSleep, this includes any changes to the UI to put the correct view in place. Basically the user should not even realise that the application was, essentially, closed and restarted.

To test OnResume we simply click the power button on the emulator or unlock the device at which point OnResume will be called.

References

Xamarin.Forms App Lifecycle
Android Activity Lifecycle
UIApplicationDelegate Class

Creating a Swift Package

In my previous post I looked at creating a CircleImage, basically creating a reusable SwiftUI control. Now let’s look at how we might start creating packages to allow us to share such library code.

Creating a Swift Package

From Xcode, select File | New | Swift Package. A very basic package is created (mine’s name MyLibrary) with a Sources/YouPackageName/YourPackageName.swift file containing the following code

struct MyLibrary {
    var text = "Hello, World!"
}

We’ll stick with this simple bit of code but make the struct and the text var public. Also we’ll need to add a public init method or you’ll see an error such as this

initializer is inaccessible due to ‘internal’ protection level

So the new code looks like this

public struct MyLibrary {
    public var text = "Hello, World!"
    
    public init() {
    }
}

Now use Command + B to build the package.

Local Package

To use the package locally, i.e. not having it deployed to a remote repos or the likes.

Within the application that you wish to use the package, simply (using Finder) select the package folder (i..e mine’s MyLibrary) and drag and drop this into your application under the top level project node in the Project Navigator.

Now we can use the code by simply importing it, for example

import MyLibrary

and now within your code

let text = MyLibrary().text

Making our package more useful

Let’s now move the code for our CircleImage into the package, we’ll leave the package name and therefore the component will be renamed, as MyLibrary

Here’s the code

import SwiftUI

@available(iOS 13, *)
public struct MyLibrary: View {
    var image: Image
    var borderColor: Color
    var shadowRadius: CGFloat

    public var body: some View {
        image
            .clipShape(Circle())
            .overlay(Circle().stroke(borderColor, lineWidth: 4))
            .shadow(radius: shadowRadius)
    }
    
    public init(image: Image,
                borderColor: Color = .white,
                shadowRadius: CGFloat = 10) {
        self.image = image
        self.borderColor = borderColor
        self.shadowRadius = shadowRadius
    }
}

In the above we’ve removed the public keyword on the fields and instead relied on the init method to set up any defaults. We also need to mark the code with the @available attribute to state what versions of OSX or iOS is supported, for example Image is available in 13.0 of iOS.

Within the package file Package.swift we also add the platforms value, here’s a snippet of my code, showing where to expect the platforms value

...
    name: "MyLibrary",
    platforms: [
        .macOS(.v10_15),
        .iOS(.v13),
    ],
    products: [
...

and in our application we now simply use something like the following

MyLibrary(image: landmark.image)
   .offset(x: 0, y: -130)
   .padding(.bottom, -130)

We’ll look at using GitHub or the likes to host our package in a subsequent post, but this gives an idea on how to get something up and running.

Swift UI Circle Image

Here’s a simple circle image using SwiftUI. This is pretty much taken from the SwiftUI Tutorial with a few additions.

struct CircleImage: View {
    var image: Image
    var borderColor: Color = .white
    var shadowRadius: CGFloat = 10

    var body: some View {
        image
            .clipShape(Circle())
            .overlay(Circle().stroke(borderColor, lineWidth: 4))
            .shadow(radius: shadowRadius)
    }
}

In this case we pass and image to the CircleImage, clipping it into a circle shape. Then we display a line (in white by default) around it with a shadow (with radius 10 by default).

We simply clip the supplied image, i.e.

CircleImage(image: Image("imageFrom.Assets.xcassets"), 
   borderColor: .red, 
   shadowRadius: 5)

The image should be stored within the Assets.xcassets editor.

Splitter bars in React

I went through a load of splitter bar implementations for use within a React site, i.e. I simply wanted to partition my application into two halves (top and bottom), with a horizontal splitter bar.

The top half should be filled with a grid of data (I was using ag-grid), the bottom half filled with the query builder control. Also the button half should be capable of being collapse/hidden.

I was surprised at how hard it was to find anything that “just worked”. But this one did…

Run

yarn add m-react-splitters

Mine is “m-react-splitters”: “^1.2.0”. Now import the following

import Splitter from "m-react-splitters";
import "m-react-splitters/lib/splitters.css";

and here’s the basics for the code (obviously replacing the comment blocks with your components).

<Splitter
   position="horizontal"
   maximizedPrimaryPane={!showQueryBuilder}>
<div style={{width: "100%, height: "100%" }}>
<!-- grid -->
</div>
<div style={{width: "100%, height: "100%", overflow: "auto" }}>
<!-- query builder -->
</div>

Material UI in Xamarin Forms

To get nice, material ui, visuals on your Android and/or iOS application you can use the Xamarin.Forms.Visual.Material nuget package, see Xamarin.Forms Material Visual for a full explanation of using this package. I’m just going to summarise this information in this post for my own information.

Add the nuget package Xamarin.Forms.Visual.Material you may need to upgrade your Xamarin Forms if it’s an older version.

Init the FormsMaterial in the AppDelegate of the iOS project

global::Xamarin.Forms.Forms.Init();
global::Xamarin.Forms.FormsMaterial.Init();

and the same in the MainActivity

global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
global::Xamarin.Forms.FormsMaterial.Init(this, savedInstanceState);

Finally set the Visual element of the ContentPage, i.e.

<ContentPage Visual="Material"
/>

That’s all there is to it.

Sideloading an Android application from a website

You might want to supply your Android application outside of the Play store. This might be for testing or simply because you don’t want or need your application to go through the Play store (i.e. a bespoke application for a company).

Assuming you’ve got your APK file you can simply drop in onto a web site and link to it (or supply the location). For example

<html>
  <head>
    <title>My Android Application</title>
  </head>
  <body>
      <p>
      <h2>Install My Android application by clicking on 
this <a href="com.puridparrot.myapp.apk">link</a>
      </h2>
    </p>
  </body>
</html>

Simple enough – we might need to set the mime type, for example here’s the web.config for IIS

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <staticContent>
      <mimeMap 
         fileExtension=".apk" 
         mimeType="application/vnd.android.package-archive" />
    </staticContent>
  </system.webServer>
</configuration>

Now when you click this link from your device it’ll download the file and install it, you will need to set “Unknown sources” on to allow this to happen.

The downsides of this approach are that you obviously don’t get any notifications for updates to an application, also you’ll get a couple of security prompts to remind the user that this app. may not be from a trusted source.

Emailing (using default Email client) from mobile using Xamarin

A simply way to send email from Android and iOS using Xamarin is by using the Xamarin.Essentials.Email class. This allows us to use the default email client on the device to send our email, so we simply compose the email then pass the user to the default client to press the send button. What’s useful about this is our application can then leave the user to add any further information to the email without having to create the entry fields in our app.

You’ll need to add the NuGet package Xamarin.Essentials, then we can use the following code (don’t forget to add using Xamarin.Essentials)

try
{
   var message = new EmailMessage
   {
      Subject = subject,
      Body = body,
      To = recipients,
      Cc = ccRecipients,
      Bcc = bccRecipients,
      BodyFormat = EmailBodyFormat.PlainText
   };

   if (attachments != null && attachments.Count > 0)
   {
      attachments.ForEach(attachment =>
      {
         message.Attachments.Add(new EmailAttachment(attachment));
      });
   }

   await Email.ComposeAsync(message);
}
catch (Exception ex)
{
   // Log exception
}

On Android this will display the default email client, for example gmail, with all the fields filled in along with any attachments.

Express server and compression

You may want to turn on compression within your express service(s) in which case simply run

yarn add compression

and we just need to add to our server code like this

import express from "express";
import compression from "compression";
 
const app = express();
const server = http.createServer(app);
 
app.use(compression());
 
const port = 4000;
server.listen(port, 
  () => console.log(`Server on port ${port}`)
);

Now, in many case you’ll possibly already have the client header including the following

Accept-Encoding: gzip, compress, br

or you can set the headers yourself and express will serve up gzip compressed responses.

Remember though, that compression happens based upon reading an amount of data before starting to stream/send the response, so small(ish) amounts of data will not benefit from any performance increases, whereas large amounts of data will by the very nature of compression, reduce the amount of data being transferred and therefore you should see a performance increase.

See also Compression in HTTP.