Hello everyone,
In today’s post I would like to discuss about the feature introduced in Dynamics 365 v9.0 with which complete localization can be achieved. If we take a look back at prior versions of CRM, localization can be achieved for Entities and it’s sub components using “CrmTranslations.xml” file in the solution. However, there is no OOB support for achieving the same for custom web resources like JScript and HTML. With introduction of “String (RESX) Web Resources”, we have got complete package of localization solution.
Before jumping into more details, we need to understand another feature introduced in Dynamics 365 v9.0 which is required in order to make String Web Resources work. In my prior post, i touched up on using Attribute dependencies for JScript web resources. Similarly, we also got Web Resource Dependencies introduced. Let’s quickly understand this feature. For an instance, we have JScript web resource named “new_servicehelpers.js” which uses JQuery and JSON libraries. In prior versions of CRM, in order to use “new_servicehelper.js” on a form we require to add JQuery and JSON JScript web resources as well on the form libraries. This is one of the tough task developer has to undergo in order to keep track of dependent web resources and their order of loading. With v9.0 on wards, we can simply add JQuery and JSON web resources as dependencies for the “new_servicehelper.js” web resource by adding them to “Dependencies” tab’s first grid. By doing this, CRM will automatically download JQuery and JSON web resources wherever “new_servicehelper.js” is about to load on a form. In one of my previous implementations, we used require.js library to achieve modular and dependency file loading. Now, we got it this OOB. Later in this post, i will show how we can use the same concept for String Web Resources.
So, lets start looking into what is a String Web Resource, how to create and use it in the other web resources. For this post, I will be focusing on using String Web Resources in JScript Web Resources.
At a high level, following are the quick bits about working with String Web Resources:
- String Web Resource’s file extension is .resx and uses RESX XML which holds keys and localized strings. This is the standard format used across windows applications.
- String Web Resources has to follow a standard naming convention. It’s format is webresourcename.languagecode.resx. ex: new_text.1033.resx
- For same content, separate String Web Resources has to be created for each language.
- Add String Web Resources as Web Resource Dependencies to JScript Web Resources.
- Use Xrm.Utility.getResourceString(webResourceName, key) to get the localized string based on the current logged in user’s language code.
Creating .RESX file:
As i mentioned earlier, RESX XML is the standard format used across windows applications. So, lets go to Visual Studio and find out how to create it in a simple way.
- Add a Resource File as shown below to one of your existing or a new project. We don’t need to follow CRM standard naming convention for String Web Resources here for this .resx file in Visual Studio but I suggest we follow that here as well.
My bad: I could have named this file as “new_Messages.1081.resx”
2. Open Designer view of newly created Resource File and add keys and respective localized strings. Following is the Resource File i created for “Hindi” language. Comments are optional.
Following is the auto generated RESX XML code generated for this file. No need to check what has been written in this XML file. Save your time for other important things.!!
<?xml version="1.0" encoding="utf-8"?> <root> <!-- Microsoft ResX Schema Version 2.0 The primary goals of this format is to allow a simple XML format that is mostly human readable. The generation and parsing of the various data types are done through the TypeConverter classes associated with the data types. Example: ... ado.net/XML headers & schema ... <resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="version">2.0</resheader> <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> <value>[base64 mime encoded serialized .NET Framework object]</value> </data> <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> <comment>This is a comment</comment> </data> There are any number of "resheader" rows that contain simple name/value pairs. Each data row contains a name, and value. The row also contains a type or mimetype. Type corresponds to a .NET class that support text/value conversion through the TypeConverter architecture. Classes that don't support this are serialized and stored with the mimetype set. The mimetype is used for serialized objects, and tells the ResXResourceReader how to depersist the object. This is currently not extensible. For a given mimetype the value must be set accordingly: Note - application/x-microsoft.net.object.binary.base64 is the format that the ResXResourceWriter will generate, however the reader can read any of the formats listed below. mimetype: application/x-microsoft.net.object.binary.base64 value : The object must be serialized with : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : and then encoded with base64 encoding. mimetype: application/x-microsoft.net.object.soap.base64 value : The object must be serialized with : System.Runtime.Serialization.Formatters.Soap.SoapFormatter : and then encoded with base64 encoding. mimetype: application/x-microsoft.net.object.bytearray.base64 value : The object must be serialized into a byte array : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> <xsd:element name="root" msdata:IsDataSet="true"> <xsd:complexType> <xsd:choice maxOccurs="unbounded"> <xsd:element name="metadata"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:string" minOccurs="0" /> </xsd:sequence> <xsd:attribute name="name" use="required" type="xsd:string" /> <xsd:attribute name="type" type="xsd:string" /> <xsd:attribute name="mimetype" type="xsd:string" /> <xsd:attribute ref="xml:space" /> </xsd:complexType> </xsd:element> <xsd:element name="assembly"> <xsd:complexType> <xsd:attribute name="alias" type="xsd:string" /> <xsd:attribute name="name" type="xsd:string" /> </xsd:complexType> </xsd:element> <xsd:element name="data"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> </xsd:sequence> <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> <xsd:attribute ref="xml:space" /> </xsd:complexType> </xsd:element> <xsd:element name="resheader"> <xsd:complexType> <xsd:sequence> <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> </xsd:sequence> <xsd:attribute name="name" type="xsd:string" use="required" /> </xsd:complexType> </xsd:element> </xsd:choice> </xsd:complexType> </xsd:element> </xsd:schema> <resheader name="resmimetype"> <value>text/microsoft-resx</value> </resheader> <resheader name="version"> <value>2.0</value> </resheader> <resheader name="reader"> <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> <data name="ApprovalConfirmRequest" xml:space="preserve"> <value>Do you really want to approve this request?</value> <comment>Approval Confirmation Dialog </comment> </data> <data name="ApprovalReject" xml:space="preserve"> <value>Request is Rejected</value> <comment>Approval Rejection Message</comment> </data> <data name="ApprovalSuccess" xml:space="preserve"> <value>Request Approved Successfully</value> <comment>Approval Success Message</comment> </data> <data name="CancelButtonLabel" xml:space="preserve"> <value>Cancel</value> </data> <data name="ConfirmButtonLabel" xml:space="preserve"> <value>Approve</value> </data> <data name="GotIt" xml:space="preserve"> <value>Got it</value> </data> <data name="SubTitle" xml:space="preserve"> <value>This action will approve the request</value> <comment>This action will approve the request</comment> </data> <data name="Title" xml:space="preserve"> <value>Approval Request</value> <comment>Approval Request</comment> </data> </root>
3. Create a new String Web Resource in CRM. Here are the important things to consider.
- Use Standard naming convention. For this web resource, i named it as “new_Messages.1081.resx”. 1081 is the language code for Hindi. For a list of language codes, please check this link
- Select Type as “String (RESX)
- Select appropriate language in the Language dropdown. For this web resource, i have selected “Hindi (India).
- Choose .resx file created in Visual Studio. As of we don’t have an option to view/update RESX web resource content within CRM. So, any changes has to be done has to happen in Visual Studio for now. So, better if we follow the CRM naming convention in Visual Studio project as well.
4. Similarly, I am creating one more RESX web resource for English with same keys but with english strings.
Name of the web resource: new_Messages.1033.resx.
We require to keep the name similar except the language code which is 1033. The reason being, we would like CRM to auto load either of these files based on user’s language code.
5. Now, lets add these newly created two String Web Resources as dependencies to JScript Web Resource. In one of my previous post, i have used JScript to show Confirm and Alert dialogs using Xrm.Navigation methods. Check out the post here . I will use the same JScript web resource and update it to use String Web Resources to show localized content.
Here is the updated JScript code to use:
function GetResourceString(webResourceName, key) { return Xrm.Utility.getResourceString(webResourceName, key); } function ApproveRequest() { var reqTitle = GetResourceString("new_Messages", "Title"); var reqText = GetResourceString("new_Messages", "ApprovalConfirmRequest"); var reqSubtitle = GetResourceString("new_Messages", "SubTitle"); var reqConfirmButtonLabel = GetResourceString("new_Messages", "ConfirmButtonLabel"); var reqCancelButtonLabel = GetResourceString("new_Messages", "CancelButtonLabel"); var confirmStrings = { title: reqTitle, text: reqText, subtitle: reqSubtitle, confirmButtonLabel: reqConfirmButtonLabel, cancelButtonLabel: reqCancelButtonLabel }; var confirmOptions = { height: 200, width: 450 }; //New v9.0 Method Xrm.Navigation.openConfirmDialog(confirmStrings, confirmOptions).then(successCallback, errorCallback); } function successCallback(success) { var approvedText = GetResourceString("new_Messages", "ApprovalSuccess"); var rejectedText = GetResourceString("new_Messages", "ApprovalReject"); var okText = GetResourceString("new_Messages", "GotIt"); //On Click on Approve Button if (success.confirmed) { //Here Logic to Approve the Request // var alertStrings = { confirmButtonLabel: okText, text: approvedText }; var alertOptions = { height: 120, width: 260 }; //New v9.0 Method Xrm.Navigation.openAlertDialog(alertStrings, alertOptions); } else { var alertStrings = { confirmButtonLabel: okText, text: rejectedText }; var alertOptions = { height: 120, width: 260 }; //New v9.0 Method Xrm.Navigation.openAlertDialog(alertStrings, alertOptions); } } function errorCallback(fail) { //On Error Logic }
Important changes done in this JScript web resource:
- Used Xrm.Utility.getResourceString(webResourceName, key) method to get the localized string.
- Used first part of the web resource name new_Messages and excluded remaining .languaguecode.resx part of it as the first parameter to the method. Reason for this obvious. We expect CRM product to auto select appropriate RESX file based on the user’s language. So, as a developer we need to pass the first part of the web resource name which is common for all language files and let CRM fills the language code and choose the appropriate one.
6. As shown below, under Dependencies tab for the JScript web resource add the RESX web resources in the first grid. Care to know about what’s in the second grid? Check out my previous post here
That’s it. We are done. Following is the result:
In Hindi(Indian)
In English:
Few more things to know:
- Organization’s default language is the fallback language option for RESX web resources. Let’s say my organization’s default language is English and I enabled Hindi and French as other available languages. With our current customizations in this post when a user with French language logs in, English RESX file will be loaded for them.
- For a No Key Found Scenarios where a key is not available in the loaded RESX file, a null value will be returned.
Hope it helps..!!!