Symantec Management Platform (SMP) Community

 View Only

Localization 

Nov 01, 2011 03:21 PM

This topic covers the basics of internationalizing and localizing the user interface of the Altiris Platform console and solution user interfaces (UIs), normally web pages. The UI is currently localized into eight languages (and their culture codes): German (de), Spanish (es), French (fr), Italian (it), Portuguese (pt), Japanese (ja), Russion (ru) and Simplified Chinese (zh-Hans). More languages may be added in the future.

Internationalization (commonly abbreviated to i18n because there are eighteen letters between the first 'i' and last 'n') refers to writing software that is not specific to a single language or country. Localization (or l10n) is providing the UI for a given language and primarily involves switching the text shown in the user interface to one appropriate for the viewing user but time, date, number, currency and formats and even images may all need to be localized, too. Culture is the .Net term for a language and sometimes country, also known as locale.

This topic does not cover agent/plug-in, migration (upgrade) or installation (AIM) localization. For related information, see the "Useful Links" section at the bottom of the topic.

Language Packs

Previously, localizations for the Altiris Platform were provided as satellite assemblies shipped with the initial release. Although efficient and following the .Net model, it was impossible to correct strings at a customer’s site without a new build of the satellite assembly from development. Without a separate delivery vehicle, localizations needed to be provided with the release often either delaying releases or putting undue pressure on localizers or both.

In 7.0, the SDK provided language pack project produces an NS solution that installs strings to the database instead, making them easy to query and modify. (To prevent unnecessary database hits, the localization libraries cache strings in memory. See Internationalization/Localization Libraries for details.) Also language packs, being independent of the localized product, can be released on their own schedule.

The language pack project infrastructure also automates the process of collecting localized strings and contructing the language pack MSI. It provides of these steps from the language pack project's context menu:

  1. "Extract invariant strings". This scans through the RESX and config files of the solution and extracts localizable strings.
  2. "Update localizations". This sends the strings via web service calls to a localization databse in the US called "Polyglot" then downloads localized strings for each of the configured cultures.
  3. Build. Build an MSI that installs a solution containing all the localized strings.

See http://ali-mydev.altiris.com/Wiki/default.aspx/Altiris.Localization/CreatingNS7LanguagePack.html for more details.

See LocalizationTroubleShooting for more details on troubleshooting problems when creating NS7 language pack projects.

Internationalization/Localization Libraries

The Altiris Platform offers two main mechanisms for localization. The first, Altiris.Common.ResourceManagerEx (Altiris.Common assembly), was used exclusively in NS 6.0 for all localization. This class looks in compiled RESX files in assemblies first then the String table in the NS database. In 7.0, this is primarily used to localize names and descriptions or items and security objects (such as permissions and privileges). A helper class is also provided, Altiris.NS.Utilities.StringManager2 (Altiris.NS assembly), that allows bulk loading and setting of strings.

The second class, Altiris.NS.Utilities.AltirisResourceManager (Altiris.NS assembly), looks in the StaticStrings table first then compiled RESX files in assemblies. In 7.0, this is used for all UI strings, error messages and other immutable strings.

Both of these classes mimic the System.Resources.ResourceManager class's fallback mechanism where, if no string is available for the user's culture, e.g. en-US (US English), it will fall back to the parent culture, e.g en (Country independent English) and so on to the invariant culture. This way, strings can be provided for a language which works with every country that uses that language and, if localizations are not provided, the invariant (in Altiris's case, English) localizations will be used.

Strings are grouped into 'resource sets' or 'string sets', each uniquely identified by a culture and a GUID, sometimes called BaseGuid. For example, a string set can be associated with a .Net class, such as a web page, where the GUID is the Guid attribute for that class. A string set can also be associated with an item, where the GUID is the item's GUID.

Each string within that string set is uniquely identified by a 'string reference', normally abbreviated to string ref. Strings with defined meanings usually share the same string ref across different sets. For example, the string ref of an item's name is "item.name" and the string ref of an item's description is "item.description" in each string set that contains an item's localizations.

To load a localized string, first construct the appropriate localization class, usually AltirisResourceManager, then call GetString() passing in the desired string ref, just like with ResourceManager. However, both AltirisResourceManager and ResourceManagerEx support escaping the string by passing additional arguments (see StringEscaping for details).

In most cases, solution developers need not instantiate and use the ResourceManagerEx class as it is usually used internally by other classes. For example, the Name and Description properties of items are automatically localized by the item framework. To get the invariant name and description, use the InvariantName and InvariantDescription properties instead. Note, however, that the Name and Description properites of delay load items will be the invariant.

Both of these classes cache loaded-strings to avoid unnecessary database hits but their exact caching behavior differs. For example, ResourceManagerEx caches strings across all .Net app domains, as items are often shared, so clearing it requires restarting all Altiris services as well as IIS.

AltirisResourceManager, by comparison, maintains a different cache for each app domain because of web pages, its primary use, are never shared between app domains. However, unlike ResourceManagerEx, loading a single string with AltirisResourceManager causes the entire string set to load because pages are localized as a whole rather than in parts. Clearing the cache of AltirisResourceManager strings just requires restarting that web’s app pool or IIS.

Internationalization Patterns

When loading a web page, the web browser automatically includes the caller's culture code as part of the request. The AltirisWebApplication class, which all platform and solution webs' global.asax inherits from, extracts this culture and sets the CurrentCulture and CurrentUICulture appropriately for the current thread.

The culture is carried through any code that handles the request so most code that is culture aware will work. However, code that does web service calls or remotes to other processes may need to include this information as part of the call or as an argument. Similarly, UIs other than web pages will need to detect and set the culture appropriately.

Web Page

To internationalize an ASP.NET web page:

  1. Add a System.Runtime.InteropServices.GuidAttribute the class with a unique GUID.
  2. If you prefer using a designer when editing a web page, add a Altiris.NS.UI.(Altiris.NS.UI assembly) StaticDatabaseStringLocalization component to the page and use its GUI to assign strings to each label, listbox value etc on the page. This component is easy to use but it may not escape localized strings correctly under all circumstances.
  3. If you prefer modifying page code directly, add code to the class that instantiates an AltirisResourceManager object, such as that given below, and replace all literal strings in the page with calls to '<%= ResourceManager.GetString("stringRef", EscapeFor.Html) %>', substituting stringRef for a descriptive string, and move the literal string into the page's RESX file. Do the same with any text assigned in the server side, such as to Text properties. The best place to assign localized text is in either OnInit or OnLoad.
 using System.Runtime.InteropServices;
 using Altiris.NS.Utilities;


 ...


 // If you copy this code, remember to change the GUID!
 [Guid("2bc4b7cc-2ee8-412e-b0cf-6e628f6dfb56")]
 ///<summary>
 ///Page comment.
 ///</summary>
 public class MyPage
 {
         ...


         ///<summary>
         ///Called after Init and before events.
         ///<summary>
         ///<param name="e">
         ///Event-specific args.
         ///</param>
         protected override void OnLoad(EventArgs e)
         {
                 MyLabel.Text = ResourceManager.GetString("MyLabel");
         }


         #region L10n


         private static AltirisResourceManager _rm = null;


         ///<summary>
         ///Construct a <see cref="AltirisResourceManager"/> for localizing strings.
         ///</summary>
         ///<remarks>
         ///This property is not thread-safe.
         ///</remarks>
         protected static AltirisResourceManager ResourceManager
         {
                 get
                 {
                         if(_rm == null)
                         {
                                 _rm = new AltirisResourceManager(typeof(MyPage));
                         }
                         return _rm;
                 }
         }


         #endregion


         ...
 }

Irrespective of which method is used, keep the following in mind.

  1. Not all text is statically included on the page. Remeber to internationalize the:
    1. Page title
    2. Any error messages the page displays (see note about messages from caught Exceptions).
    3. Any text modified or shown by script, including alert() messages.
    4. Tooltips and image "alt" tags
    5. "Loading" and other transitory text
  2. Escape all strings to prevent stability and security issues. The Altiris.Common.EscapeFor class has extensive support for the various types of escaping required. For example, it supports escaping for HTML, JavaScript strings delimited by single quotes and JavaScript strings delimited by double quotes. See StringEscaping for more details.
  3. Do not escape strings that are passed into the Altiris.WebControls controls. As the controls' properties' use is unknown, it is impossible for control users to know which escaping type should be used. Hence, escaping of strings that are rendered by the Altiris.WebControls controls is the responsibility of the WebControls team.
  4. Examine the page for any code that shows or manipulates dates, times, currencies, numbers or anything whose display may very between languages or countries. Use the appropriate .Net class to output or manipulate it. JavaScript has limited support for internationalization and will use the computer's operating system language rather than the browser language.
  5. Examine the page for hardcoded layout. For example, assuming text is a certain height or width, even for the same font and size, is often problematic when text changes for different languages. Remove these assumptions or make the areas large enough to handle significantly more text. Also:
    1. Localized text will usually wrap at different points or words.
    2. The width of a space or non-breaking space (&nbsp; ) is different in certain languages.
  6. Localization of exception messages varies so, rather than show exception messages to the user, it may be better to catch the exception and show a message that is specific to the operation and is plain language rather than technical. Test the error conditions and make a judgement call.
  7. Access keys, also known as accelerator keys or short cuts, where a character is underlined to indicate that field can be navigated to quickly with a keystroke, are usually not used in web UIs as they only work if that frame has focus and have to compete with the browser's own accelerators. If they are used, remember the key will most likely change between languages so the accelerator key itself will need to be internationalized, too. One solution is to use Altiris.Common.UIUtilities.GetAccessKey() (Altiris.Common assembly) and embed the access key in the string using the HTML <u></u> tags. However, this means you cannot escape the string and potentially open up security vulnerabilities. Use with caution.

Class

Generally speaking, stand-alone classes do not need to be internationalized. Exception messages are the exception (pardon the pun) and Microsoft recommends that all exceptions include localized error messages (see http://msdn.microsoft.com/en-us/library/seyhszts.aspx). Exception messages fall into two categories. The first is where the message is passed in to the exception constructor, as is the case with most exceptions in the NS and the AeXException class. These messages should be internationalized by the throwing class. The second is where messages are provided by the Exception class itself. These messages should be internationalized by the Exception class.

To localize strings at a class level:

  1. Add a RESX file with the same name as the class to the same folder.
  2. Remove Visual Studio created designer files by changing the “Custom Tool” in the file’s properties from “ResXFileCodeGenerator” to an empty string. (Unfortunately, this loses the type safety the Visual Studio provided "designer" class supplies. An SDK provided designer is available but has not been verified.)
  3. Add a Guid attribute with a unique GUID, just like with a Page above.
  4. Add code similar to for a Page to load an AltirisResourceManager.
  5. For exceptions that use fixed strings, override the message property to return a localized message, using ResourceManager.GetString(), substituting parameters as appropriate. For examples where the message is passed in to the constructor, perform this in the throwing class.

The Altiris.NS.Exceptions.AeXException class, used throughout the Altiris Platform, has several constructions, some of which take string Refs and base GUIDs. These attempt to load strings from the String table (using ResourceManagerEx) rather than the language pack supported StaticString table (using AltirisResourceManager). These are considered deprecated and should not be used.

Stand-alone RESX files

Stand-alone RESX files, sometimes called Assembly Resource Files, are useful if strings are shared between many classes.

To ensure stand-alone RESX are included in localization:

  1. Remove Visual Studio created designer files by changing the “Custom Tool” in the file’s properties from “ResXFileCodeGenerator” to an empty string.
  2. Create an empty internal class with the same name as the RESX file.
  3. Add a System.Runtime.InteropServices.Guid attribute to the class with a unique GUID.

Reports and Data Sources

Report localization is covered in http://ali-mydev.altiris.com/Wiki/default.aspx/Altiris.Reporting/HowToLocalizeReports.html .

Database

Most solution provided SQL queries should be moved to the new Resource Data Query. Otherwise, join to the StringCache view using the BaseGuid, Culture and StringRef fields to get item names and other localizations from the String table. Alternatively, use the fnLocalizeString, fnLocalizeStrings or fnLocalizeStringsByGuid functions. There are no equivalents for the StaticString table.

See also http://ali-mydev.altiris.com/Wiki/default.aspx/Altiris.Localization/SQLLocalizationNote.html .

Best Practices

  1. JavaScript scripts should not be included in RESX files. However, if code or something that should not be localized must be included in a RESX file, append "_nonlocalized" to the string ref. The localization infrastructure will always use the invariant strings where this is specified.
  2. When constructing strings, use string.Format() rather than concatenating words or phrases. Word ordering varies considerably between languages and often individual words or phrases are hard to translate without context. Indeed, for particularly complex strings, rather than using string.Format() and placeholders in the form of {0}, {1}, etc, consider using more descriptive placeholders like {DAY}, {MONTH} or similar and replace them using regular expressions to help the localization team.
  3. When outputing times or dates, use a culture sensitive format (see the help for the .Net class System.Globalization.DateTimeFormatInfo for details). Similarly, when parsing dates, always use DateTime.Parse() or similar methods.
  4. As mentioned above, all Exceptions should have localised messages (see http://msdn.microsoft.com/en-us/library/seyhszts.aspx).
  5. There is no version of string.Format() in JavaScript. However, the built-in regular expression support is more than capable of duplicating it.
  6. Never compare or sort strings manually or use a binary comparison, unless speed is critical. Use String.Compare() rather than the '==' operator because the latter does a straight ordinal comparison using the Unicode values of each character. Similarly, use String.CompareTo() when sorting.

TODODatabase sorting vs code sorting.

Common Internationalization/Localization Issues

Localization problems usually fall into these categories:

  1. Hard coded strings: Usually not internationalized, these are usually easy to fix by adding a call to AltirisResourceManager.GetString() and moving the invariant string to a RESX file.
  2. Script or SQL errors: Common in languages that use apostrophes, such as French, these are usually caused by not escaping strings rendered into the page as script variables or script itself. Use Altiris.Common.Escaping.EscapeFor() to escape them appropriately. See StringEscaping for more details.
  3. Layout issues: The length of the localized phrase can vary considerably between languages. German, in particular, tends to be very verbose, The best solution is to either use relative sizes or make the area large enough to store the longest localization. Also, Asian languages often wrap vertically rather than horizontally unless the containing element has text wrapping turned off.
  4. "Weird Characters": Sometimes localised text contains "&#nnn;" where nnn is a number. For example, "Cuadrícula" is Spanish for grid but sometimes appears as "Cuadr&#237;cula". This is an HTML escape sequence where the ampersand, '&', has been escaped and can be fixed by NOT escaping the string. When fixing this, make sure to include a comment saying why it was not escaped.
  5. Incorrect version of script: If a different version of included JavaScript is running on a non-English system with a language pack installed, append "_nonlocalized" to the string ref. This forces the NS localization framework to always use the invariant string rather than a localized one.
  6. Truncated localizations: The localization framework suppots strings up to 1024 characters long. To localize strings longer than this, split them into multiple smaller strings.

Regarding the web controls grid, the column headings in the DataTable returned from the data provider are used for both the programmatic and user visible columns. Since these will change between cultures, it creates issues if columns need to be hidden, reordered or otherwise modified programmatically. Either look up the names at runtime, refer ro them by their index or manipulate the grid directly on the page.

Regarding localization of security objects, such as roles and permissions, provide string table entries in addition to the security object definition, such as in the example below. Otherwise, invariant versions of the name and description strings will not appear in the String table, meaning the StringCache will not expand to contain the localized versions. This means only the text in the namref and descriptionref attributes will attributes will appear for the name and description respectively, irrespective of the culture.

 <security>
         <privileges>
                 <!-- Edit Console Menu -->
                 <privilege guid="7e0e6f38-b535-41ca-87d8-0bc9bfb2a2d4" nameRef="Edit Console Menu" 
                         descriptionRef="Customize console menu choices" ... >
                         ...
                 </privilege>
         <privileges>
 </security>


 <strings>
         <string baseGuid="7e0e6f38-b535-41ca-87d8-0bc9bfb2a2d4" stringRef="Edit Console Menu" 
                 culture="" string="Edit Console Menu" />
         <string baseGuid="7e0e6f38-b535-41ca-87d8-0bc9bfb2a2d4" stringRef="Customize console menu choices" 
                 culture="" string="Customize console menu choices" />
 <strings>

They key point here is any "nameref" and "descriptionref" attributes are not the names or descriptions themselves. Rather, they are string refs for that aspect of the object. They appear in the UI because the display logic uses the string ref if no localized string is present.

Note that the <string> config file syntax above will update the invariant culture if either the invariant culture is specified or an English culture is specified and no invariant string is present.

Testing Internationalization/Localization

To set IE 7.0 to use a language other than the default:

  1. Go to Tools --> Internet Options.
  2. Go to the General Tab.
  3. In the Appearence section at the bottom of the tab, click the "Languages" button.
  4. Click the "Add" button to add a new lanuage.
  5. Select the desired language from the list or enter its culture code directly and press OK. If you are doing general testing, French (fr-FR), German (de-DE) and Chinese (zh-CN) are good choices.
  6. Select the desired language from the list and move it to the top.
  7. Press the "OK" button to save the changes.
  8. Press the "OK" button to close the "Internet Options" dialog.

The NS localization infrastructure can append and prepend characters to all internationalized strings, showing up any hard coded strings. To do so, save the contents of the following to a .REG file and import this into the registry:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Altiris\eXpress\Notification Server]

"TransformResourceManagerStringsEnabled"=dword:00000001
"TransformedResourceManagerStringsPrefix"="PËðモミ"
"TransformedResourceManagerStringsSuffix"="SネキÇæ"

Also run the following SQL commands. Note that you need to replace '#' characters with '@' characters below due to Wiki formatting rules.

DELETE FROM String WHERE Culture = 'en'
go
INSERT INTO String SELECT NEWID(), BaseGuid, StringRef, 'en', String, getdate(), getdate() FROM String WHERE Culture = ''
go
UPDATE String SET String = '^' + String + '^' + Left(Culture+'##',2) WHERE String not Like '^%%^__' AND Culture = 'en'
go
DELETE FROM KnownCulture
go

To remove these changes when done, save the following as a .REG file and import this into the registry:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Altiris\eXpress\Notification Server]

"TransformResourceManagerStringsEnabled"=-
"TransformedResourceManagerStringsCulture"=-
"TransformedResourceManagerStringsPrefix"=-
"TransformedResourceManagerStringsSuffix"=-

Also run the following SQL commands:

DELETE FROM String WHERE Culture = 'en'
go
DELETE FROM KnownCulture
go

For those testing the Altiris Platform Core localization, the following UI elements are not provided by Sydney and so are not localized by the Altiris Platform Core language packs. This list may change as UI elements are added, modified or removed.

  • Manage --> Software
  • Manage --> Jobs and Tasks (although many tasks under this tree are provided by the Core)
  • Settings --> Task Server
  • Settings --> Server Settings --> Sofware Library
  • Settings --> Site Server --> Parameters --> Task Service
  • Resource Manager --> Home --> Job and Task Status
  • Certain web parts and portal pages

Useful Links

Statistics
0 Favorited
0 Views
0 Files
0 Shares
0 Downloads

Tags and Keywords

Related Entries and Links

No Related Resource entered.