Friday, April 6, 2012

PRI Files Deep Dive - Building Global Windows 8 Metro XAML Apps Part 3

In Part 1 we explored localizing strings, and in Part 2 explored localizing images, styles and custom layouts. In this post we will dig deeper into PRI files. All resource metadata is compiled into binary PRI files using a tool called makepri.exe. PRI files store all string resources and files metadata to a tree structure that can be navigated using Windows.ApplicationModel.Resources.Core.ResourceManager. This is unmanaged WinRT class different from the managed System.Resources.ResourceManager. It is not recommended to use the managed ResourceManager unless you are building a portable class library that is not targeting WinRT only.

Dumping PRI Files

You can use makepri.exe tool to dump the PRI binary into XML following these steps
- Open “Developer Command Prompt”
- Navigate to the bin directory.
- You should see a resources.pri file
- Type “makepri dump”
- The tool will generate a “resource.pri.xml” file.
The following snippet shows a very basic PRI dump.
   1: <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   2: <PriInfo>
   3:     <ResourceMap name="8f615d67-84d4-474a-8d44-04f3482dc045" version="1.0" primary="true">
   4:         <Qualifiers/>
   5:         <ResourceMapSubtree name="Files">
   6:             <NamedResource name="App.xaml" uri="ms-resource://8f615d67-84d4-474a-8d44-04f3482dc045/Files/App.xaml">
   7:                 <Candidate type="Path">
   8:                     <Value>App.xaml</Value>
   9:                 </Candidate>
  10:             </NamedResource>
  11:             <ResourceMapSubtree name="Assets">
  12:                 <NamedResource name="SplashScreen.png" uri="ms-resource://8f615d67-84d4-474a-8d44-04f3482dc045/Files/Assets/SplashScreen.png">
  13:                     <Candidate type="Path">
  14:                         <Value>Assets\SplashScreen.png</Value>
  15:                     </Candidate>
  16:                 </NamedResource>
  17:             </ResourceMapSubtree>
  18:             <ResourceMapSubtree name="Common">
  19:                 <NamedResource name="StandardStyles.xaml" uri="ms-resource://8f615d67-84d4-474a-8d44-04f3482dc045/Files/Common/StandardStyles.xaml">
  20:                     <Candidate type="Path">
  21:                         <Value>Common\StandardStyles.xaml</Value>
  22:                     </Candidate>
  23:                 </NamedResource>
  24:             </ResourceMapSubtree>
  25:         </ResourceMapSubtree>
  26:     </ResourceMap>
  27: </PriInfo>

The root of resource trees is the ResourceMap. The ResourceMap begins with a definition of Qulaifiers list. Qualifiers include Languages, Contrasts, and Scales as explained in Part 2 of this series. For example the following project structure generates the following qualifier list.


   1: <Qualifiers>
   2:  <Language>FR-FR, EN-US</Language>
   3:  <Contrast>WHITE</Contrast>
   4:  <Scale>100</Scale>
   5: </Qualifiers>

Resource Subtrees

Resource are grouped into ResourceMapSubTree elements. Knowing the resource map tree structure is essential for navigating the map using ResourceManager. As shown in Part 1 and Part 2 of this series, you can use ResourceLoader or ms-appx Uri to load a resource with simple API. But in case you need to load a resource based on custom ResourceContext, which is really useful in situations like Unit Testing or custom configurations independent of the system settings.
Typically there are two main ResourceMapSubtree in a ResourceMap, one is named “Files” for images, xaml, and media files. The other is named “Resources” for string resources. The following screenshot shows the ResourceMapSubtree hierarchy for the SplashScreen.png file in the previous example.


All string resources are merged into a single Subtree as long as they are defined with same file name. The following project structure will generate only two Subtrees, one named Resources, the other named Custom.


Unit Testing Resources

Testing resources requires simulations of different settings like Languages, Contrasts, and Scales. Using the ResourceManager with custom ResourceContext enables customizing qualifiers. The ResourceMap.GetValue method returns the best match ResourceCandidate. The ResourceCandidate has Score property that can be used if the resource is an exact match. The following snippet demonstrates this technique.

   1: var ctx = new ResourceContext();
   2: ctx.QualifierValues["Contrast"] = "black";
   3: ctx.QualifierValues["Scale"] = "100";
   4: var c1 = ResourceManager.Current.MainResourceMap.GetValue("ms-resource:///Files/Assets/SplashScreen.png", ctx);
   5: var candidate = ResourceManager.Current.MainResourceMap.GetValue("ms-appx:///Resources/HelloWorld", ctx);
   6: foreach (var c in candidate.Qualifiers)
   7: {
   8:   Assert.AreEqual(c.Score, 1.0);
   9: }

Note that the GetValue method can receive a Uri with the path of the resource instead of navigating to the exact subtree. The Uri can be either qualified with ms-appx or ms-resource scheme.

Resource in Referenced Class Libraries

Resources in referenced libraries will be merged in the same PRI file, and in the main ResourceMap, and to load the resource you must include the Library name in the resource Uri as in the following snippet

   1: var c = ResourceManager.Current.MainResourceMap
   2:                .GetValue("ms-resource:///ClassLibrary1/Resources/HelloWorld");


In this post we explored the internals of PRI files, and how to inspect their structure. Also explored how to unit test resource based on custom contexts. In the following post we will explore localization for Culture specific values like Dates, and Currencies.


Anonymous said...

This is kinda related to this post. How do you localize a splash screen?

I tried creating the language structure in the folders (ie 'assets\en\splashscreen.scale-100.png' etc.), but it does not use the correct locale.

I thought this might be because explicitly reference the english folder's splash screen. But referencing a splash screen in the assets folder makes it ignore it as well.

Yasser Makram said...

The runtime does not seem to follow the globalization rules while loading the initial splash screen.

Hazem Elshabini said...

Very informative :) Thnx Yasser :)