Chapter 14—Localization

Note that the following section describes a localization method that can only be used for desktop applications, that is, apps containing window classes, so the following information is not relevant for some editions of Omnis Studio, including the Community Edition. For all new libraries that use the JavaScript Client, we urge you to use the localization method described in the Localization chapter in the Creating Web & Mobile Apps manual.*

If you are developing desktop applications for an international market, you may want to translate the text and labels in your libraries into another language or support multiple languages. Omnis provides tools for localization of your desktop applications using the String Table tab in the Catalog window and via the String Table editor in the Add-ins submenu of the Tools menu.

As well as translating your Omnis libraries, you can translate most of the text and strings that appear in the Omnis Runtime environment (the Omnis.exe) and the Omnis Web Client. You can localize various language dependent strings in the Omnis executable itself, such as the days of the week.

Right to Left Data Entry

In addition to the localization or translation functionality in Omnis Studio, you can force data entry fields to display their data from right to left, for example, to support text entry on Arabic machines. The $righttoleft property allows data in single- and multi-line edit fields to scroll from the right to the left. When this property is enabled for a multi-line field, the vertical scrollbar is displayed on the left of the field.

Localizing Your Libraries

Omnis contains an external package called ‘StringTable’ that contains a special String label object and a set of functions that allow you to dynamically change the language of text labels in your Omnis application.

You can use the String Table Editor in Omnis to create tables containing a matrix of strings or words for any number of languages. String tables and the functions in the StringTable package allow you to load the text for fields, buttons, and text labels when the window is opened, and then change the language of the text while the window is open.

The String Table Editor allows you to translate a whole list of strings (stored in the first column of your string table) into one or more languages automatically. For example, you could add English labels to a window or remote form, add the text for these labels to a string table, and translate all the labels to French, German, and Italian, with a single mouse click. The String Table Editor uses the translation tools provided by Google Translate™ to translate the text in your string tables automatically.

Using String Labels

First you need to create your window or form in your application that you wish to display in multiple languages. If you are enabling an existing window for dynamic label and text translation, you will need to replace existing text labels with the special ‘String Label’ external component from the ‘StringTable’ package. You can use standard fields and buttons.

Locating the StringLabel component

To create String labels

The string label object can display single or multiple lines of text.

It is also possible to translate the text for pushbuttons, check boxes and the contents of lists that may appear in your window or form. To identify these objects in the string table and your Omnis code you should make sure the objects have a suitable text string specified in the $name property. The text in the $name property of an object should be used as the row ID in the string table and also used in your code to reference the text.

Editing String Tables

Having created the string labels and buttons in your window or remote form, you need to create a String Table that contains all the alternative text for each object in the different languages you wish to support.

Note for existing users

In previous versions of Omnis Studio, the first column of a string table was labeled “ID” but in Studio 5 onwards it is called “STRINGID”. This is because “ID” is the ISO 639 code for Indonesia, and the Omnis localization features use ISO 639 codes to identify the language columns, so “ID” could no longer be used. Old string tables, where column one is called “ID” will usually still work, unless of course you are using ID to represent Indonesia in one of the language columns.

To create a string table

Note: Your Omnis library cannot be called “STRINGTABLE”.

The first column in the new string table is called STRINGID: do not rename this column since this will contain the object rowIDs used to identify the strings in the table.

You can find specific strings in Omnis using the Find strings… option (right-click on the string table name in the Catalog). You can drag a string from the Find window into the STRINGID column to enter the exact string to replace, and avoiding any mis-typing.

You can tab to the end of the row to create a new row or click the Add Row button and enter the Row ID for the next table entry; continue adding rows for each label or object you wish to translate.

The following example shows a String Table for a remote form that has a number of string labels and pushbuttons. The English strings are in the second column, while the French and German strings were added to subsequent columns.

Under certain circumstances you can use the STRINGID column in your string table as your “default” language, for example, if you have used the exact label names in the STRINGID column rather than some other rowID. In this case, you need to specify the Locale of your STRINGID column in the Translate dialog, using the two-letter ISO 639 language code, to identify its language.

Accessing String Tables via the Catalog

String Tables can be accessed and edited via the String Table tab in the Catalog (F9) window. Your own string tables for windows and remote forms will appear in the Catalog window when the window or form is the top design class.

String Table Functions

The StringTable package contains a number of functions that allow you to load string tables, load text items from a table, and replace the text in the windows in your application. The following methods are available:

The following functions apply to string tables for remote forms and are available for execution in server and client methods: when used on the server they will use the correct language for the task instance.

Programming String Tables

The following method loads a string table with the name ‘Lang.stb’ and sets the column containing the English text as the current column.

# custom method $loadStringTable
Calculate lvpath as sys(10)
Do FileOps.$splitpathname(lvpath,lvdrive,lvdirname,lvfilename,lvfileext)
Calculate lvpath as con(lvdrive,lvdirname,"Lang.stb")
Do StringTable.$loadStringTable(iTableName,lvpathReturns ln
If ln
  OK message Error (Icon) {The Language String Table "Lang.stb" could not be loaded.}
  Quit method ln
End If

# Select the English Language
Do StringTable.$setColumn("English"Returns ln

Quit method ln

Having loaded the string table and specified the current column, the following method can be used to load the text or string values into the appropriate field labels, buttons, and lists in a data entry window. Note that the IDs for each object are stored in custom constants that are defined in the the Startup_task in the library.

# custom method $loadFields
# Define the button and group box descriptions using $getText
Do StringTable.$getText(kPrintButton) Returns lval
Do $cwind.$objs.PrintButton.$text.$assign(lval)
Do StringTable.$getText(kLanguageButton) Returns lval
Do $cwind.$objs.LanguageButton.$text.$assign(lval)
Do StringTable.$getText(kEmpTitle) Returns lval
Do $cwind.$objs.stEntry_1022.$text.$assign(lval)

The next section of the method defines the dropdown lists for Sex (male/female) and Marital Status by loading the relevant entries from the current string table.

Set current list iSex
Define list {iField}
Do StringTable.$getText(kMale) Returns iField
Add line to list
Do StringTable.$getText(kFemale) Returns iField
Add line to list
Calculate iSex.$line as 1
Set current list iStatus
Define list {iField}
Do StringTable.$getText(kMarried) Returns iField
Add line to list
Do StringTable.$getText(kSingle) Returns iField
Add line to list
Calculate iStatus.$line as 1

Within your application you need to provide some way for the user to select and change the language setting. This could be done using a separate window containing a list of available languages and a button to set the selected language. The following method gets all language column names from the Lang String Table and puts them into an Omnis list.

# custom method $loadList
Set current list iLang
Define list {iLangField}

# Save the current column number
Do StringTable.$getColumnNumber() Returns ln

# Build a list of all column names in the String Table.
Do StringTable.$colCnt() Returns maxc

# Start from column 2 as column 1 is reserved for String Table IDs
For lcount from 2 to maxc step 1
  Do StringTable.$setcolumn(lcount)
  Do StringTable.$getcolumnname() Returns iLangField
  Add line to list
End For
Do StringTable.$setColumn(ln)
Calculate iLang.$line as pLine

When the user selects a language from the list, they should click a button with the following method which changes the language of the text on itself and the data entry window.

On evClick ## Event Parameters - pRow( Itemreference )
  Set current list iLang
  Calculate lval as ((iLang.$line)+1)
  # need to offset by 1 since col1 in string table has the rowID
  Do StringTable.$setColumn(lval)
  Do StringTable.$getText(kLangTitle) Returns lval
  Do $cwind.$objs.stLang_1016.$text.$assign(lval)
  Do StringTable.$getText(kSetLanguage) Returns lval
  Do $cwind.$objs.SetLang.$text.$assign(lval)
  Do $root.$iwindows.stEntry.$loadFields
  # this line runs the $loadFields method again (see above) to change the objects in the data entry window
  Do method $loadList (iLang.$line)
  # this line reloads the language list in the new language
  Do method $translateList
  Do method $redrawAll

The following method translates the language list depending on the Language selected. The code uses the String Table column headings to look up corresponding IDs from within the table itself.

# custom method $translateList
Set current list iLang
Calculate ln as iLang.$line
Calculate lrowcnt as iLang.$linecount
For lcount from 1 to lrowcnt step 1
  Do StringTable.$getText(iLang.[lcount].iLangFieldReturns iLang.[lcount].iLangField
  Calculate iLang.$line as lcount
End For
Calculate iLang.$line as ln

Finally you need a method to redraw all the fields and text labels on any open windows or forms.

# custom method $redrawAll
Do $iwindows.$first() Returns ref
While ref
  Do StringTable.$redraw(ref.$hwnd)
  Do $iwindows.$next(ref) Returns ref
End While

Localizing Remote Forms

The String Label object is available for remote forms, and together with string tables, allows you to localize your applications running in the Omnis Web Client. See the previous section for details about how to create string labels and string tables; the technique is the same for remote forms.

In Omnis Studio 5, remote form classes have a new property called $stringtabledata. The contents of this property is the data, in list format, from a standard Omnis string table. To populate $stringtabledata in the IDE, you can click on the droplist button on the property in the Property Manager, and select a string table file. When you press OK, Omnis takes a copy of the string table data and stores it in the class. If you cancel the dialog, Omnis asks if you wish to clear the data from the class, which is a convenient way to clear the contents of $stringtabledata.

When the web client creates an instance of the remote form class, on the client, it automatically loads the string table data as a client-side string table, and sets the string table name to the remote form class name.

The column names in the string table must be:

When the client loads a new string table (because a remote form is being instantiated in some way), it uses the operating system locale of the client to locate the string table column to use for lookups. You can override this behavior by setting the remote task instance property $stringtablelocale to a two character ISO 639 language code to use instead of the client operating system locale, but you can only do this in $construct of the remote task. If there is no column in the string table for the desired locale (specified in $stringtablelocale), lookups default to using column 2.

The $rowid property of a remote form string label object can refer to one of the Web Client string tables. If $rowid is not prefixed with a table name (remote form name), then the string must be in the string table for the remote form containing the object.

Any character string property of a remote form control can be specified as $st.id (note that $st is not notation; it is just a special prefix recognized by the client). This tells the client to lookup id in the string tables for the client, and set the property value to the result of the lookup. The $rowid rules regarding the presence of a table name prefix also apply in this case. Note that when you get a property set in this way, the result is the result of the lookup, not $st.id.

There is a new remote form property called $stringtabledesignform which allows you to access the string table in the Catalog (F9) windows while designing the remote form.

There are three new “Client String Table” functions, available for execution in client methods only:

Remote tasks: $construct()

There is also a new column, “ClientLocale”, in the row variable parameter passed to $construct() of the remote task. This contains the locale for the client, as returned by the locale() function; the first two characters are the ISO 639 language code of the client.

Remote Menu Lines

You can set the text for a remote menu line to $st.id. The lookup occurs when the menu is built on the client, before any event processing for the menu.

Tooltips

You can use the $st. lookup notation to lookup text from a string table to fill out the $tooltip property for a field.

Decimal point character

The decimal point character for display and entry on the JavaScript Client is the character for the current locale (either set by the current locale of the browser, or overridden using $ctask.$stringtablelocale).

Multi-threaded Language Separators

The main Omnis App Server thread and any other server thread(s) can now have their own values for decimal point, thousand separator, and import dp, which are stored in the $separators Omnis root preference. This allows you to support multiple languages in a single app running on the Omnis App Server.

If you call $separators from an Omnis App Server thread, the new values for the function parameter and import separators are ignored – you can only set these two separators when running in the main thread.

In addition, once you have started the server with the Start server command, subsequently changing the language only affects the decimal point, thousands separator and import decimal place for the main thread.

Localizing Omnis

In order to provide a completely foreign version of your Omnis application, you may need to translate various resources that appear in Omnis itself (rather than strings that appear in your libraries, as described in the previous section). This applies equally to applications that run on the desktop using the Omnis executable (Runtime), and applications that run on the Internet using the Omnis Web Client: you can translate string resources in the Omnis runtime and in the web client. This method may also be useful if you wish to replace any references to “Omnis” in the Runtime with the name of your own product, e.g. the ‘Hide/Quit Omnis’ option in the macOS version of Omnis.

Omnis Studio 5 introduces two new String Tables that allow you to translate:

The new String Tables can be accessed via the String Table tab in the Catalog (F9) window and are edited using the String Table editor.

In addition, the Localization Library (omnisloc.lbs) is still available in the Local folder for you to translate further string resources in the Omnis executable.

Localizing Built-in Client Resources

The built-in resources comprise the string resources, and the strings in dialogs, in the Omnis core, externals and external components. Omnis has two new string tables used for the translation of built-in and web client resources. The new tables are located in the Local folder of the main Omnis tree, and are:

These string tables can be edited using the String Table Editor. However, there are some special rules regarding their structure: the STRINGID column is the exact value of the built-in resource text and cannot be changed; the other columns contain the translations of the built-in resource text, and they are named using a 2 character ISO 639 language code, in lower case.

Usually, there is no column for “en” (English), because the built-in resources are English. If you are using a mixture of languages in the built-in resources, then you can add a column for “en”. As a result, you will have cases where the STRINGID and language column for the built-in resource would have the same value. To avoid duplicating the value in the language column, you can enter “*” for that column, meaning the string does not need translating from its STRINGID.

When reading a string resource, Omnis looks up the string value in the string table, and if present, it replaces the string value with the translation. If no translation is available, the string remains unchanged. The string value lookup is case-sensitive, allowing the translation to contain the correct case for the translated text.

Omnis loads the studio.stb string table when it starts up, and when $root.$prefs.$language changes, and the appropriate language version of the resources is loaded. The column used for translations is the ISO 639 language component of the locale stored in the current language record. This is also stored in a small text file (locale.txt) in the Local folder of the Omnis tree, to cater for the fact that the string table is loaded sooner in the life of Studio than the localization data.

When a web-based client connects, Omnis sends client.stb to the client; this is handled via the cache, so it will not always be sent. The client loads client.stb, and uses the column corresponding to the client locale to obtain translations (the client locale can be specifically set using $cinst.$stringtablelocale for the remote task instance, or it can be allowed to default to the locale of the client machine).

Editing the Built-in Client Resources

You can open and edit the studio.stb and client.stb string tables via the Omnis Catalog (F9) window. You can click on the String Table tab in the Catalog, and right-click on the string table you wish to edit.

Representing carriage return, linefeed, and tab

You can represent cr (carriage return), lf (linefeed), and tab in strings in the studio.stb file using the escape sequences: <cr>, <lf> and <tab>. The Find Strings dialog automatically generates these escape sequences.

Local Language

The locale() function for the fat client now has an optional Boolean parameter, which when passed as kTrue, causes it to return the locale field value for the current $root.$prefs.$language.

Changing the System Menu Options in macOS

You can change the Hide Omnis and Quit Omnis options in the Omnis Studio runtime on macOS by adding strings to the Studio String Table (studio.stb). In addition, you can localize items in the Preferences and Services menus.

If you have renamed the Omnis app package on macOS, to match your product name, you may also like to change the Hide Omnis and Quit Omnis options in the main application menu to reflect your product name. To do this:

You can find specific strings in Omnis Studio using the Find strings… option (right-click on the string table name in the Catalog). You can drag a string from the Find window into the STRINGID column to enter the exact string to replace, and avoiding any mis-typing.

Existing users should note that the Translate button has been removed from the String Table editor since automatic translation is no longer supported.

Localizing the Omnis Runtime

Developers and distributors in non-English speaking countries may need to localize the Omnis program (runtime) itself. You can localize the following Omnis internal items:

Storage of Localization Data

All libraries share the same set of data, stored in an Omnis data file or localization database called OmnisLOC.DF1, located in the Omnis local folder.

OmnisLOC contains a data slot for configuration data; each record in that slot contains a complete set of data corresponding to a particular language. It also contains a data slot with a single record, which identifies the current language, that is, the current set of configuration data.

Overriding the Language

You can override the current language set in the localisation data file by setting an item in the Omnis config.json file, which can be used with the Linux headless server. The entry is called "language" and is located in the "defaults" section. It defaults to empty, which means the setting in omnisloc.df1 will be used. To override the language setting in omnisloc.df1, you can add the name of a language in omnisloc.df1 to the language item.

The Localization Data

The following items are stored for each language.

Days of Week

This comprises 2 strings for each of the 7 days of the week, allowing for a full name such as Wednesday, and an abbreviated name such as Wed.

If a day of the week item is empty, Omnis will read its value from the operating system.

Months of Year

This comprises 2 strings for each of the 12 months of the year, allowing for a full name such as August, and an abbreviated name such as Aug.

If a month item is empty, Omnis will read its value from the operating system.

Separators

These comprise the following:

Standard Text Strings

These comprise the strings for Yes and No, OK and Cancel, True and False, AM and PM, and On and Off. If the value of the AM or PM text is the single character ‘0’ (zero), Omnis will read the text from the operating system.

Locale: National Sort Ordering

The ISO 639 language code in the Locale field is used to define the sort ordering for National fields. The “Use Locale For Defaulted Items” check box below the Locale field determines the locale used for language items that have been left empty, so that they get a default value from the system:

Date Ordinal Suffixes

You can localize the date ordinal suffixes which are the strings that can be appended to a day number to result in language specific days such as 1st, 2nd, 3rd, 4th, etc. You can edit the date ordinal suffixes field on the Text Strings tab of the localization library language settings window.

For English, the value of the date ordinal suffixes field is:

th$1st$2nd$3rd$21st$22nd$23rd$31st$

If you leave the field empty, then no suffix is applied, otherwise, the string before the first $ is the default suffix, and the remaining $-separated strings are a day number followed by its suffix. There must be a trailing $.

To see the suffix used for a particular date you can use the function call dat(date,’d’).

The natcmp() function

The natcmp() function lets you compare two values using the national sort ordering.

natcmp (value1, value2)

Omnis converts both values to strings before doing the comparison.

Omnis uses the same rules for comparing the strings as it does for normal strings, except that it performs the comparison using the national sort ordering.

natcmp() returns 0 if the strings are equal, 1 if value1 > value2, and -1 if value 1 < value2.

User Interface

The Omnis preferences accessed from the IDE Tools>>Options menu line let you assign a new language from the dropdown list in the $language property. The current language is shown in the $language property. The language must already be defined in the localization data file.

The new language assigned in the $language property applies straight away; Omnis does not need to be restarted. However if the localization database is shared by several users, then the new language setting only affects them when they have restarted.

An Omnis library, OmnisLOC.LBS is provided that lets you create and edit language information. To use it:

You use the Next and Previous buttons to move through the records in the data file, the Find button to locate a particular language record, and Edit to modify data already present in the data file.

Two Insert buttons are available. Insert lets you create a brand new record, while Insert CV lets you make a copy of an existing language record and edit that. This is particularly useful for cases where there are only minimal differences between two language records. To use Insert CV:

A new record is created. Remember to edit the language name as well as the specific internal data.

Any fields that are left blank will default to a single space. Some of the fields on the General tab are limited in terms of which characters can be used; for example trying to define a letter as a decimal separator is not allowed, and will generate an error message.

Setting the Locale

In the localization library (omnisloc.lbs), the sort order field is now labeled Locale. There is a new check box below the Locale field, “Use Locale For Defaulted Items”. This determines the locale used for language items that have been left empty, so that they get a default value from the system:

Notation

There is no requirement to manipulate localization data at runtime, so the localization notation is minimal.

Every data file stores its national sort order. When you create a new data file, Omnis stores the national sort order for the current language in the data file.

$hascurrlangnationalsortorder is assignable, but you cannot set it to kFalse only kTrue. When set to kTrue Omnis drops all of the indexes from the data file, changes the sort order to that for the current language, and rebuilds all of the indexes.