Adapting app content to the user’s location

This blog was written by Gilles Peigné, Program Manager, Developer Ecosystem & Platform

When we think about localization, we typically think in terms of translating UI strings and making sure we use globalization APIs for needs like number formatting, handling dates and times, and other region-specific settings. However, a user’s preferred language and settings do not necessarily reflect where they are located on the earth. This is commonly encountered with users who travel, where they want the UI of their apps to appear in their preferred language but want the content presented by those apps to be sensitive to their travel destinations. This is also a common need for users who live near international borders and regularly interact with people in neighboring countries.

In a previous article we looked at how an app could adapt itself for different locales, even when those locales share a common language. In this post we’ll explore how an app can adapt to user’s location independently from language settings and UI considerations.

Where is my user?

Windows provides powerful location information through the APIs in Windows.Devices.Geolocation, which can return quite accurate latitude and longitude data (see Location awareness in the documentation for more details). For our purposes here, however, we need to know only what country or region the user is presently in. The question is then how to translate a geocoordinate to a country or region, for which we employ what’s called a Civic Address provider.

Using the Bing Maps API

Unfortunately, Windows and Windows Phone don’t have a default provider. Although a provider can be installed by third party software vendors, the most reliable solution is to employ the Bing Maps API as follows:

1. Create a Bing Maps Key from your Bing Maps Account Center: see Getting a Bing Maps Key.

2. Make an HTTP request to the following URI to call the Bing Maps REST API:

    http://dev.virtualearth.net/REST/v1/Locations/[Lat],[Long]/?o=xml&key=[Key]

where [Lat] and [Long] are the coordinates provided by the geolocation service, and [Key] is Bing Maps Key you’ve created.

3. The service will return XML that contains a physical address corresponding to the geographic location.

4. Parse this file and search for the string framed by the <CountryRegion> tag.

For example, for latitude 46.191863 and longitude 6.207946, with a key we created, the URI looks like this:

http://dev.virtualearth.net/REST/v1/Locations/46.191863,6.207946?o=xml&key=Aj1zgNOquzTyunycQHwbCsJqRWfB5Nu3DqKDlNuIN7o-CPLAHyHTI1oPb12345678

Using your own key, you can paste this URI into a browser and see the results like those below, where the highlighted element is the one we’re interested in. (Some elements omitted for brevity.)

XML:

<?xml version="1.0" encoding="UTF-8"?>
<Response xmlns="http://schemas.microsoft.com/search/local/ws/rest/v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <BrandLogoUri>http://dev.virtualearth.net/Branding/logo_powered_by.png</BrandLogoUri>
  <StatusCode>200</StatusCode>
  <StatusDescription>OK</StatusDescription>
  <AuthenticationResultCode>ValidCredentials</AuthenticationResultCode>
  <ResourceSets>
    <ResourceSet>
      <EstimatedTotal>1</EstimatedTotal>
      <Resources>
      <Location>
        <Name>Rue de Vallard, 74240 Gaillard</Name>
        <Point>
          <Latitude>46.191863</Latitude>
          <Longitude>6.207946</Longitude>
        </Point>
        <BoundingBox>
          <SouthLatitude>46.188000282429321</SouthLatitude>
          <WestLongitude>6.2005065483762793</WestLongitude>
          <NorthLatitude>46.195725717570674</NorthLatitude>
          <EastLongitude>6.21538545162372</EastLongitude>
        </BoundingBox>
        <EntityType>Address</EntityType>
        <Address>
          <AddressLine>Rue de Vallard</AddressLine>
          <AdminDistrict>Rhone-Alpes</AdminDistrict>
          <AdminDistrict2>Haute-Savoie</AdminDistrict2>
<CountryRegion>France</CountryRegion>
          <FormattedAddress>Rue de Vallard, 74240 Gaillard</FormattedAddress>
          <Locality>Gaillard</Locality>
          <PostalCode>74240</PostalCode>
        </Address>
        <Confidence>Medium</Confidence>
        <MatchCode>Good</MatchCode>
        <GeocodePoint>
          <Latitude>46.191863</Latitude>
          <Longitude>6.207946</Longitude>
          <CalculationMethod>Interpolation</CalculationMethod>
          <UsageType>Display</UsageType>
          <UsageType>Route</UsageType>
        </GeocodePoint>
      </Location>
    </Resources>
  </ResourceSet>
</ResourceSets>
</Response>

As you can see, this geocoordinate is physically located in France.

Note: by default, Bing provides these XML results in English, so you don’t need to check against localized names for the <CountryRegion>. If desired, you can get results in a particular language by adding a culture parameter to the request URI. For example, if you add &c=de (German) to the URI you’ll see “Frankreich” instead; &c=es (Spanish) and &c=it (Italian) will give “Francia”, &c=ja (Japanese) will give フランス. So you can make the request in the user’s language and compare against localized names that might exist in your UI.

Parsing the name from the XML response is a simple matter using the Windows Runtime Windows.Data.Xml.Dom.XmlDocument API, whose LoadFromUriAsync method will make the call and create an XML object structure in one fell swoop. From there it’s easy to extract the CountryRegion value as shown in the code below.

JavaScript:

var uri = new Windows.Foundation.Uri("http://dev.virtualearth.net/REST/v1/Locations/46.191863,6.207946?o=xml&key=AhTTNOioICXvPRPUdr0_NAYWj64MuGK2msfRendz_fL9B1U6LGDymy2OhbGj7vhA"); 
Windows.Data.Xml.Dom.XmlDocument.loadFromUriAsync(uri).done(function (doc) { 
    var cr = doc.getElementsByTagName("CountryRegion"); 

    if (r != null && cr[0] != null) { 
        console.log("Geocoordinate is located in " + cr[0].innerText); 
    } 
}, function (e) { 
    console.log("Could not determine region." ); 
});

C#:

var uri = new Uri("http://dev.virtualearth.net/REST/v1/Locations/46.191863,6.207946?o=xml&key=AhTTNOioICXvPRPUdr0_NAYWj64MuGK2msfRendz_fL9B1U6LGDymy2OhbGj7vhA"); 

try 
{ 
    var doc = await XmlDocument.LoadFromUriAsync(uri); 
    var cr = doc.GetElementsByTagName("CountryRegion"); 

    if (cr != null && cr[0] != null) 
    { 
        Debug.WriteLine("Geocoordinate is located in " + cr[0].InnerText); 
    } 
} 
catch 
{ 
    Debug.WriteLine("Could not determine region."); 
}
The WinRT Civic Address API

The Windows.Devices.Geolocation namespace has a class to detect a civic address. At present, however, real address data is not available unless a Civic Address provider is installed on the user’s machine. If no Civic Address provider is installed, the API returns only the user’s home geographic region.

By default, this location is defined in PC settings > Time and language, and is typically set to the user’s primary locale when the user first boots their device. As such, this value is only accurate so long as the user has not moved to another country and has not manually changed his or her location after a move. However, most users will not change this regional setting when travelling or making temporary moves for business trips, holidays, and so on.

clip_image002

Be aware, then, that an app may retrieve a location that is based on a best guess. The least reliable location is when the user has no GPS, nor a Civic Address provider, and the user has moved to another country without manually changing his region in the Control Panel. In this case, the location will be mapped to Windows’ defaults or IP address-based location.

Nevertheless, most phones along with the new generation of PCs and tablets have location sensors that should generate accurate data. Here is code that retrieves location data and extracts the associated country without even checking of the geographic coordinates.

JavaScript:

var geolocator = new Windows.Devices.Geolocation.Geolocator(); 
geolocator.getGeopositionAsync().done(function (pos) { 
    var region = pos.civicAddress.country; 
    console.log("Device is located in " + region); 
}, function (e) { 
    console.log("Could not determine region."); 
});

C#:

var geolocator = new Windows.Devices.Geolocation.Geolocator(); 
var pos = await geolocator.GetGeopositionAsync(); 

if (pos != null) 
{ 
    var region = pos.CivicAddress.Country; 
    Debug.WriteLine("Device is located in " + region); 
} 
else 
{ 
    Debug.WriteLine("Could not determine region."); 
};

The region variable will then return a two-letter ISO-3166 country code.

Notes:

  • For any implementation, make sure the app declares the Location capability in its manifest.
  • Location detection is part of the Privacy settings of the PC, and the user can disable these features. Therefore, the app must implement fallbacks in the case it cannot obtain a Geoposition.
  • The user’s home geographic region (as set in PC settings) can also be retrieved using the GloblizationPreferences class as follows:
var homeGeographicRegion = Windows.System.UserProfile.GlobalizationPreferences.homeGeographicRegion;
  • If the device isn’t equipped with its own geolocation sensor, then earlier code that uses GetGeopositionAsync will return the same country code as GlobalizatonPreferences.

Change layout based on the location

Now that we know where the user is, it will be easier to adapt a layout to the location she actually is in, regardless of her locale and chosen UI language.

Consider the following scenario. Contoso is a regional messenger company that does most of its business in the Geneva (Switzerland) area. The company also works with customers located elsewhere in Switzerland as well as France, Germany, and Italy. To reach customers and serve all international workers in this expanded region, Contoso’s apps should be localized into French, German, Italian, and English. But their apps also need to take into account where the customers and workers are so that each one can deliver the best locale-sensitive experience. For example, a France-based customer can get an app that displays a local regional office picture, whereas a Switzerland-based customer will have a plain red background. Or customers in France will preferably pay in Euros (€), whereas customers in Switzerland will preferably pay in Swiss Francs (CHF or Fr.). These are all separate concerns from the user’s home locale and chosen UI language.

Let’s see some examples of implementations.

Dynamic Styling

The most visible assets of an app should depend on styling. This can be adapted dynamically.

JavaScript

For apps written in JavaScript, ask your designer to create a CSS file for each market into which you’ll release your app, plus one fallback design for when the user is out of the regions your app specifically supports. Then, place the files in the usual folder for your page’s CSS.

clip_image003

By default, the CSS is referenced from your HTML markup, but we’re going to comment those references out:

<!--<link href="/pages/hub/hub.css" rel="stylesheet" />-->

Instead, we’ll load the stylesheet dynamically by setting attributes on a new link element which we add to the DOM. An interesting practice would consist of creating a variable that points to the CSS that is used for a particular region.

//Format CSS
var fileref = document.createElement("link"); 
fileref.setAttribute("rel", "stylesheet"); 
fileref.setAttribute("type", "text/css"); 

switch (mylocation) { 
    case 'FR': //user located in France 
        fileref.setAttribute("href", "/pages/hub/hub_FR.css"); 
        break; 

    case 'CH': //user located in Switzerland 
        fileref.setAttribute("href", "/pages/hub/hub_CH.css"); 
break; 

    default: //user located somewhere else 
        fileref.setAttribute("href", "/pages/hub/hub.css"); 
        break; 
} 

//Load CSS 
document.getElementsByTagName("head")[0].appendChild(fileref);

All the graphic assets should now display according to the region the user is in, independently of Windows language and the user’s locale.

Here is the result when the user is in France, with the following CSS in hub_FR.css:

.hubpage .hub .hero { 
    -ms-high-contrast-adjust: none; 
    background-image: url(/images/FR_offices.jpg); 
    background-size: cover; 
    margin-left: -80px; 
    margin-right: 80px; 
    padding: 0; 
    width: 780px; 
}

clip_image005

Here is the result when the user is in Switzerland, with the following CSS part in hub_CH.css

.hubpage .hub .hero { 
    -ms-high-contrast-adjust: none; 
    background-color:red; 
    background-size: cover; 
    margin-left: -80px; 
    margin-right: 80px; 
    padding: 0; 
    width: 780px; 
}

clip_image007

Here is the result when the user is somewhere else, using the default hub.css file

clip_image009

C#

For apps written in C#, ask your designer to split the XAML resources in creating ResourceDictionary type files that correspond to the regions you support (plus one fallback) and that contain the design resources to be customized.

clip_image010

In each file, create localized entries that correspond to your local design. For example, here is the Hub_FR.xaml content that brings the same hero picture as the previous Javascript/CSS example:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="using:intlContosoXAML.Strings"> 
    <ImageSource x:Key="HubPicture">Assets/FR_offices.jpg</ImageSource> 
</ResourceDictionary>

Then, we need to reference this new key HubPicturein the corresponding XAML markup so it can be used. Let’s change the main hub’s picture in HubPage.xaml.

<HubSection Width="780" Margin="0,0,80,0" > 
    <HubSection.Background> 
        <ImageBrush ImageSource="{StaticResource HubPicture}" /> 
    </HubSection.Background> 
</HubSection>

Now, depending on the location, we need to load and assign the correct resource dictionary to the app, at runtime. Let’s add this to the HubPage.xaml.css and then let’s reload the page to make the change visible.

var Dictionary = new ResourceDictionary(); 

switch (mylocation) 
{ 
    case "FR": //user located in France 
        Dictionary.Source = new Uri("ms-appx:///Hub_FR.xaml", UriKind.RelativeOrAbsolute); 
        break; 

     case "CH": //user located in Switzerland 
        Dictionary.Source = new Uri("ms-appx:///Hub_CH.xaml", UriKind.RelativeOrAbsolute); 
        break; 

     default: //user located somewhere else 
        Dictionary.Source = new Uri("ms-appx:///Hub.xaml", UriKind.RelativeOrAbsolute); 
        break; 
}; 

// Clear existing dictionary and load needed one 
Application.Current.Resources.MergedDictionaries.Clear(); 
Application.Current.Resources.MergedDictionaries.Add(Dictionary); 

// Reload the page 
var _Frame = Window.Current.Content as Frame; 
_Frame.Navigate(_Frame.Content.GetType()); 
_Frame.GoBack();

Finally, to make sure there is at least one dictionary loaded when running the app (or else the keys we put in XAML won’t exist in the resource tree at compilation), we need to load a default resource dictionary. The simplest way is to merge the Hub.xaml fallback dictionary with the main resources from the App.xaml file:

<Application.Resources> 
    <!-- Application-specific resources --> 
    <ResourceDictionary> 
        <x:String x:Key="AppName">Contoso</x:String> 
        <!-- Add fallback ResourceDictionary --> 
        <ResourceDictionary.MergedDictionaries> 
            <ResourceDictionary Source="Hub.xaml"/> 
        </ResourceDictionary.MergedDictionaries> 
    </ResourceDictionary> 
</Application.Resources>

All the graphic assets should now display according to the region the user is in.

Adapt local currency units and format

Beyond styling, we may also need to tweak additional layout that takes into account local parameters or experiences. Currency is one such consideration that is tied to a location rather than the user’s home region. In the Contoso app, we’d like it to display pricing in the currency of the country the user is in. We can imagine the app calls a web service that queries a pricing list per country. The web service then returns arrays of prices for a specific country, but doesn’t necessarily include the currency unit. If the user is currently located in France or Germany, Contoso should display fees in Euros. Or if he is located in Switzerland, fees should be displayed in Swiss Francs. This is obviously done independently of a UI language, butit must definitely be done according to the user’s locale.

Fortunately, we can instruct the Globalization APIs to use a specific locale rather than the default when formatting numbers. This can be done with the CurrencyFormatter class, within the Windows.Globalization.NumberFormatting namespace, that will add the appropriate currency symbol to a number when we specify a location. (For more example of this API, refer to the Number formatting and parsing sample.)

JavaScript

var myLocation = "FR"; 
var myPrice = "123.45"; 

//Assume myLocation is the country code of the user's physical location, 
//and myPrice is the value to display 
//Get user's language 
var myLocale = Windows.System.UserProfile.GlobalizationPreferences.languages[0]; 

//Get currency symbol for the current region 
var currencySymbol = Windows.Globalization.GeographicRegion(myLocation).currenciesInUse[0]; 
var cf = Windows.Globalization.NumberFormatting.CurrencyFormatter; 

//CurrencyFormatter extended syntax: 
//var currencyFormat = new cf("EUR",    ["fr-FR"], "FR"); 
//                              ^currency ^locale  ^actual location 

var currencyFormat = new cf(currencySymbol, [myLocale], myLocation); 

//Result to display 
var result = currencyFormat.format(myPrice);

C#

//Assume myLocation is the country code of the user's physical location, 
//and myPrice is the value to display 
//Get user's language 
String myLocale = Windows.System.UserProfile.GlobalizationPreferences.Languages[0]; 

//Get currency symbol for the current region 
String currencySymbol = new GeographicRegion(myLocation).CurrenciesInUse[0]; 

//CurrencyFormatter extended syntax: 
//var currencyFormat = new cf("EUR",    ["fr-FR"], "FR"); 
//                              ^currency  ^locale  ^actual location 

var currencyFormat = new CurrencyFormatter(currencySymbol, new [] { myLocale }, myLocation); 

//Result to display 
var result = currencyFormat.Format(myPrice);

With the code above, here’s how a price is formatted through different combinations of user locations and locales:

User location User locale What is actually displayed
FR en-US €123.45
FR fr-FR 123,45 €
FR fr-CH 123.45 €
CH en-US fr.123.45
CH fr-FR 123,45 fr.
CH fr-CH 123.45 fr.

As you can see, the formatted currency value adapts automatically to the region’s currency with the appropriate symbol, and to the number format appropriate for the user’s language.

Note that such code depends on a closed list of regions. This is intentional: your app might represent a business that is present in several predefined regions, but might not be available in some others. It should then be made clear to define fallback that processes the inappropriate regions.

Wrapping up

Creating an international app requires designers to answer this question “Who are the users I want to address?” And most of the times, applying traditional globalization and localization methods are enough to provide a usability that fits to customer primary needs, mainly running an app from anywhere in the world and understanding its contents. In the past two decades, software has increasingly progressed in supporting international resources, and IT standardizations have helped interoperability across the world. But, paradoxically, globalized services have also increased the need for more granularity in the customer experience to tailor content or segment the users. To do that, apps and services–or devices–need to know users’ context, in which detailed location can now be an important part of the value proposition.

So today, app designers should also answer this question: “Where can my users be?” Based on this information, we’ve seen how to tailor a Windows Store App experience. This can obviously be combined with all other UI localization methods, or a locale-based adaptive design. Let’s be pragmatic though. Windows provides features for creating an app that works in all regions and is localized for many languages and whose UI can adapt to a real-time user context; but it will increase a lot of the planning, development and testing costs. Therefore, we recommend you adjust such features to your needs: restrict the number of customizable international assets, adjust the number of locales your core business needs to support,always think of fallback, and integrate this international design to your agile software development processes. And you will see Windows Store apps provide one of the best environments to address your customers, whoever they are, wherever they are.