Skip to main content

Zen Localization

When you localize the text for an application, you create an inventory of text strings in one language, then establish a convention for substituting translated versions of these messages when the application locale is different. Most of the information you need to localize Zen pages can be found in the “Localizing Text in a CSP Application” chapter in the book Using Caché Server Pages (CSP). Everything described in that chapter for development of CSP pages also applies to Zen pages.

There are a few additional details that apply only to Zen. This chapter:

Important:

In order for Zen localization techniques to work in any Zen class, the class must provide a value for the DOMAIN class parameter.

CSP Localization

Important:

This topic briefly outlines material from the “Localizing Text in a CSP Application” chapter in the book Using Caché Server Pages (CSP). If you are new to CSP, please read the other chapter before continuing in this book.

For a simple demonstration of a localized web application, enter the following URI while Caché is running. Substitute your Caché web server port number for 57772:

http://localhost:57772/csp/samples/language.csp

Localization Practices

Caché supports the following process for localizing web application text:

  1. Developers determine where text strings are displayed in the application user interface.

  2. Developers create an XML message file that contains text strings in the original language.

  3. Developers import the XML into a Caché namespace.

    This adds new entries to the message dictionary in that namespace.

  4. Developers give the XML to a team of translators.

  5. Translators produce a new XML message file by substituting translated text for the original.

  6. Developers import the new XML into a Caché namespace.

    Translated and original texts coexist in the message dictionary.

  7. At runtime, the application chooses which text to display based on the browser default language.

message dictionary

A message dictionary is a simple database of text strings organized by domain name, language name, and message ID:

  • The text of each message is a string of up to 32K characters. A message may consist solely of text, or it may also contain one or more parameters specified by %1, %2, etc.

  • A domain name is any arbitrary string. It identifies a group of related text items, such as all messages for a specific application or page.

  • A language name is an all-lowercase language tag that conforms to RFC1766Opens in a new tab. It consists of one or more parts: a primary language tag (such as en or ja) optionally followed by a hyphen (-) and a secondary language tag (en-gb or ja-jp).

  • A message ID is any arbitrary string; it uniquely identifies a message. The message ID only needs to be unique within a domain. You may assign a message ID or allow Caché to assign one, depending on the conventions you use to create the message.

Each user-defined namespace in Caché stores its message dictionary in a subscripted global called ^CacheMsg. The order of subscripts in ^CacheMsg is domain, language, and message ID.

$$$Text Macros

Important:

For $$$Text macros to work in any Zen class, the class must provide a value for the DOMAIN class parameter.

The easiest way to create a message dictionary is to automatically generate message dictionary entries at compile time, by seeding the CSP class code with $$$Text macro calls. $$$Text automatically creates the message at compile time and generates the code that retrieves the message at runtime, as this topic explains.

When it comes time to translate the application messages into another language, you can export the full list of messages for the original language out of the message dictionary by running an Export command. This generates a complete XML message file in the original language. See the article String Localization and Message Dictionaries.

The return value of a $$$Text macro is a %String. The correct $$$Text macro to use depends on the format you need for this output string:

  • $$$Text

  • $$$TextJS (applies JavaScript escaping to the $$$Text result)

  • $$$TextHTML (applies HTML escaping to the $$$Text result)

The %String returned by $$$Text may be assigned to a variable, which you can use to represent the message in subsequent calls. For example:

 Set tmsg = $$$TextJS("Error saving production")
 &js<alert('#(tmsg)#: #($ZCVT($ZE,"O","JS"))#');>

Or, you can simply insert a $$$Text macro anywhere you need a string:

 &js<alert('#($$$TextJS("Error saving production"))#: #($ZCVT($ZE,"O","JS"))#');>

$$$Text Arguments

$$$Text has the arguments text, domain, and language as described in the following table. Only the first argument, text, is required.

Argument Description
text

Non-empty string. text must be a literal string. It cannot use any type of expression syntax. The format used for text may be:

"actualText"

Or:

"@textId@actualText"

Where textId is a message ID and actualText is the text of the message.

The string actualText may consist of any of the following items, separately or in combination:

  • Simple text, as permitted by the file format

  • Substitution arguments %1, %2, %3, or %4

  • HTML formatting

  • A string expression in ObjectScript format

If provided, the textId is used as the message ID. If @textId@ is not specified, the system generates a new textId by calculating the 32–bit CRC (Cyclic Redundancy Check) of this text. If the textId is specified and a message already exists with this ID, the existing message is checked to see if it has the same text as actualText. If not, an error is reported.

domain (Optional) A string specifying the domain for the new message. If not specified, domain defaults to the value of the DOMAIN class parameter at compile time and %response.Domain at runtime.
language

(Optional) An RFC1766Opens in a new tab code specifying the language. Caché converts this string to all-lowercase. If not specified, language defaults as follows:

  • At compile time: If there are no previous compilations in this domain, the default is the general default language for the system. This value can be found in the variable $$$SessionLanguage. If there was a previous compilation in this domain, the language specified during that compilation is the default.

  • At runtime: The default is %response.Language, or if no value is defined for %response.Language, the default is %session.Language which contains the same value as $$$SessionLanguage.

    Tag-based CSP pages automatically acquire a value for %response.Language from browser settings, so it is available as a default language and also as a value for %session.Language. This is not true for class-based CSP pages, which must explicitly set a value for %response.Language to use it as a default. Setting a value %response.Language also has the side effect of setting %session.Language to the same value.

    You can assign a value to %response.Language by giving it the return value of the %Library.MessageDictionaryOpens in a new tab class method MatchLanguage. Given a list of languages and a domain name, this method uses HTTP 1.1 matching rules (RFC2616Opens in a new tab) to find the best-match language within the domain.

$$$Text at Compile Time

When you compile a class that contains calls to $$$Text, $$$TextJS, or $$$TextHTML macros, each call generates a message in the message dictionary, with text, message ID, domain, and language as provided by the macro arguments.

$$$Text at Runtime

If the message text contains arguments (%1, %2, %3, %4) you must specify the corresponding substitution text before displaying the text. Since $$$Text returns a string, you can use any string operation native to your coding language. For example, in JavaScript:

 var prompt = '#($$$TextJS("Remove user %1 from this Role?"))#';
 prompt = prompt.replace(/%1/g,member.userName);

You can also use the $$$Text string as the first argument of the %response.FormatText method or a $$$FormatText macro. For details, “Other Options for Displaying Localized Strings” in the chapter “Localizing Text in a CSP Application” in Using Caché Server Pages (CSP).

Zen Localization

Important:

For the following conventions to work in any Zen class, the class must provide a value for the DOMAIN class parameter.

Zen pages are CSP pages. All CSP localization practices also apply to Zen pages. However, Zen adds the following conventions to make localization even easier:

  • Any Zen property that has its ZENLOCALIZE parameter set to 1 (true) automatically generates a message dictionary entry. Caché generates the message dictionary using similar conventions as when you use textid="" in <csp:text> and other localizable CSP tags. That is:

    • The message text is the string value of the property

    • The message ID is the 32–bit CRC of the text

    • The domain takes the value of the DOMAIN class parameter

    • The language takes the value of the browser default language (at runtime)

  • The Zen datatype class, %ZEN.Datatype.captionOpens in a new tab, has a default value of 1 (true) for its ZENLOCALIZE parameter, so any property defined as having a type of %ZEN.Datatype.captionOpens in a new tab is automatically localized as described above.

Localization for Zen Components

If a Zen component has any string-valued properties with its datatype parameter ZENLOCALIZE set to 1, then when Zen generates the %CreatePage method from the <page> description, Zen automatically localizes the text supplied for those properties.

It is convenient to give all string-valued properties the Zen datatype %ZEN.Datatype.captionOpens in a new tab, which has ZENLOCALIZE automatically set to 1, but you can also set ZENLOCALIZE directly by including the datatype parameter ZENLOCALIZE=1 in the property definition.

If all of the following conditions are true:

  • You are using components that have properties with ZENLOCALIZE set to 1

  • You place these components within the <page> element in XData Contents

  • Your Zen page class has a DOMAIN parameter value defined

Then the code that Zen generates for the %CreatePage method automatically calls $$$Text to localize each of these properties. Of course, you must provide the translations yourself, as described in “Localization Practices.” However, if you do this and also use ZENLOCALIZE and $$$Text as described in this section, all of the mechanisms for delivering these translated strings work automatically. The result is that Zen automatically serves the correct translation to the user by detecting the language locale for the browser or the Caché server at runtime.

The following is a simple example:

<button caption="OK" />

Generates the following code in %CreatePage:

Set bttn.caption = $$$Text("OK")

Important:

If you programmatically build pages (without using <page>) there is no automatic localization of component properties, even if ZENLOCALIZE=1. In this case your code must issue a call to $$$Text for each one of these strings, as shown in the previous example of generated code for a button caption.

Localization for Custom Components

If you are creating a new component, you can define properties that should be localized as datatype %ZEN.Datatype.captionOpens in a new tab:

Property title As %ZEN.Datatype.caption;

Within your component, to refer to the property simply use the property name with double dot syntax (..title in this example). You do not need $$$Text within your custom component code unless you have some static text that you wish to localize. Whenever a <page> references your component, your property is automatically localized in the same way as built-in Zen components. For example:

<MyComponent title="AAA" />

Generates code like this in %CreatePage:

Set MyCmp = $$$Text("AAA")

Localization for Client-side Text

Zen supports a variant of $$$Text that you can use within JavaScript methods. This feature greatly simplifies the use of localized strings in client-side code.

The syntax for JavaScript localization is similar to the $$$Text macro in ObjectScript:

var str = $$$Text('This is a localized string.');

When a method containing this code runs in a Zen page, $$$Text returns a localized string if localization has been enabled by setting the DOMAIN parameter, and a localized version of the string is available. $$$Text takes an optional second argument, which is a DOMAIN name. This argument lets you override the default localization domain:

var str = $$$Text('This is a localized string.', 'MyDomain');

JavaScript does not support macros, so $$$Text is implemented as a function that returns a string. Strings in JavaScript can be quoted with either single quotes (') or double quotes ("), and both conventions are supported. Both arguments to $$$Text must be literal (quoted) strings or the function does not return localized values. The complete $$$Text() expression must be written on a single line of code. All occurrences of $$$Text('string') are processed, wherever they appear in the JavaScript, including in comments and strings. This does not effect the behavior of the code, but may add extra entries to the string table.

You can use explicit id values for strings, such as "@33@MyString", in the same manner as the server-side $$$Text.

In Caché version 2010.2, the $$$Text function exists but simply returns the string it is passed.

The JavaScript function, $$$FormatText is also supported. This substitutes all occurrences of %1,%2,%3,%4 with the values of up to four parameters:

alert($$$FormatText('Ok to delete file %1?', filename));

$$$Text automatically captures string content and adds it to the localization global. The macro preprocessor does this in the case of the $$$Text macro in ObjectScript. As there are no macros in JavaScript, the compiler that converts JavaScript methods into client-side functions scans JavaScript for occurrences of $$$Text('string','domain') and pulls the first argument (and the second if it is present) and constructs a JavaScript array of localized values, which is served as part of the page. The $$$Text function uses this array to convert strings to the localized version. The generation of the array uses the server-side $$$Text macro, which ensures that the JavaScript localized strings are created using the same mechanism as server-side strings.

When a Zen page is rendered, the array of localized strings is created for every type of component and superclasses of that component found on the page. If a component is added to a page dynamically and Zen determines that the class definition for this component must be dynamically loaded, then localized strings are generated for this component as well.

Localization with zenText

An additional client side equivalent of $$$Text is the zenText JavaScript function:

zenText(id,p1,p2,p3,p4)

This function finds a localized text message from a set of client-side text resources, where:

  • id is the id of the text message.

  • p* variables are optional. Zen substitutes them for %1, %2, and so on if these variables appear in the message text.

If no localized strings are defined for the supplied id, zenText returns a default string.

You can define a set of localized text messages by overriding the %OnGetJSResources callback method in your Zen page class, and filling it with Set statements, such as those that define the resources MyMessage and FieldValidation in the following example:

Method %OnGetJSResources(ByRef pResources As %String) As %Status [ Private ]
{
  Set pResources("MyMessage") = $$$Text("This is my message")
  Set pResources("FieldValidation") = $$$Text("Field %1 has an error")
  Quit $$$OK
}

The %OnGetJSResources method fills in an array containing pairs of resource identifiers and text strings. %OnGetJSResources is invoked automatically, allowing you to access these resources from JavaScript using the zenText function whenever you need it; for example:

alert(zenText('MyMessage'));
alert(zenText('FieldValidation','SSN')); 
Note:

It is possible to define client side resources that apply to every page in the application. To do this, override the %OnGetJSResources method within the application class rather than in the page class.

As with $$$Text, you must provide the translations for strings such as "This is my message" yourself, as described in “Localization Practices.” However, if you do this and also use zenText and %OnGetJSResources as described in this section, all of the mechanisms for delivering these translated strings work automatically. The result is that Zen automatically serves the correct translation to the user by detecting the language locale for the browser or the Caché server at runtime.

There are some localized text strings already defined as resources for client side localization in all Zen page classes. The following display lists them. The syntax in this example is not complete, because the right-hand side of the display is truncated to keep the lines short. However, each line shows enough to indicate what to expect from this resource if you invoke it using zenText:

Set pResources("zenMonthNames") = $$$Text("January,February,March,April,May,June
Set pResources("zenMonthShortNames") = $$$Text("Jan,Feb,Mar,Apr,May,Jun,Jul,Augu
Set pResources("zenDayNames") = $$$Text("Sunday,Monday,Tuesday,Wednesday,Thursda
Set pResources("zenDayShortNames") = $$$Text("S,M,T,W,T,F,S","%ZEN")
Note:

For information about JavaScript functions other than zenText, see “Client-Side Functions and Variables” in the “Zen Page Classes” section of the chapter “Zen Pages.”

FeedbackOpens in a new tab