FoxProFoxPro Developer's Conference '94 |
Session 221
Steven
Blacks INTL Toolkit:
Making FoxPro an
International Tool
written by:
Andrew Ross MacNeill
PC Edge Inc.
Introduction
Around the world, languages separate people. Each language may have different dialects and slang terms, all of which can easily alienate people not familiar with the terms. In the same way that they separate, languages can also bring people together. In a company, a specific term may be used that transcends languages, making it easy only for company employees to understand the terminology.
As a developer, languages should not be considered as a problem specific to multilingual nations. If languages are considered as differences in terminology within an application, languages could also describe the differences between applications made for companies and applications made for government. These two clients often use completely different terminology. From a business point of view, languages can offer a virtual unlimited variety for a single application.
Steven Black is a Canadian developer who has done a substantial amount of work across the world in the field of international applications. One of the first things he points out in his documentation is that languages are only one of the barriers to building international applications. A truly international application needs to consider items such as currency, taxes, date formats and more. His INTL Toolkit provides several pieces needed to make an application multilingual and the documentation for the product provides many more hints on how to deal with the other issues involved.
The main force behind the INTL Toolkit is a function (called I() ) that provides the translations needed. This function returns a passed string in the language specified through the use of translation tables. The INTL Toolkit does not provide automatic translation; it provides "localization" routines that replace a string with the contents of a field in the translation table. The language that the application is originally designed in is named "CORIGINAL". Any additional languages are fields with identical types and lengths as the CORIGINAL field but with another name, always starting with "C". For example, to have a language field for GERMAN, the field would be "CGERMAN". French would be "CFRENCH", etc. The I() function uses a table named STRINGS to provide string translation.
Whats in the Toolkit
The INTL Toolkit provides three different kinds of tools that use the I() function: drivers for GENSCRNX and GENMENUX, Message Services (MSGSVC) , and INTLTOOL. The GENX drivers provide either runtime or compile-time translation. This allows you to build screens for a specific language or for "on-the-fly" translations. To identify the type of translation, a variable, _INTLTIMING, must be set to either "RUNTIME" or "GENERATE". The GENX drivers provide translations for the FoxPro Screen and Menu Power Tools. The Screen driver is called by placing *:SCXDRV5 INTL in the Screen Setup Snippet or _SCXDRV5 INTL in the FoxPro Configuration file (CONFIG.FP/ CONFIG.FPW). The Menu driver is called by placing INTLMENU as the MNXDRV2. In order to use any of these tools, you need to be using GENSCRNX and GENMENUX.
Creating screens suitable for international use is a tricky process. When creating new screens, there are some important guidelines to follow:
Different
languages can take up larger or smaller spaces. When
designing screens, it is useful to ensure extra space
around each screen object. If you leave a lot of
room, the entire process becomes much easier.
Unfortunately, this isnt always the case.
When converting existing screens, the process of translation becomes a bit trickier. When a screen is generated using the INTL driver, all of the text in the screen is replaced with a call to the I() function. When it does this, the text returned by the function is aligned to the right of the existing function. You can control this alignment by using some of the controls the INTL Driver provides.
For example, if you want INTL to align your text to the left instead of the right, place the directive *:INTL ALIGN LEFT in the Comment snippet of the object. If you want to align all of the objects in a specific screen, you can place the ALIGN directive in the screens setup snippet. You can also use the CENTER directive. With particular languages that align from right to left, you may prefer to align all of the screens using the ALIGN directive. You can place the statement _INTLALIGN=LEFT in the FoxPro Configuration file.
The INTLs GENX drivers are useful for text objects that appear directly to the user. In the case of object snippets (such as the Valid and When clauses) it is the responsibility of the developer to update any text items with the I() function or by using Message Services, which we will discuss next.
MSGSVC - A Power Tool unto itself
Screens and menus are only part of an application. The INTL Toolkit provides a single tool to handle dialogs, WAIT WINDOWS and thermometers. This tool is provided in a single function called MSGSVC. This function uses the MSGSVC table to identify not only the translations but also the function (dialogs, thermometers, etc), fonts, and more.
The principal behind MSGSVC is that
developers shouldnt have to code around languages.
Translations can be an afterthought using MSGSVC. Even
forgetting about the multilingual facility of the tool,
MSGSVC provides a single tool to provide dialogs and
thermometers to an application, a single tool that is easily
maintainable. MSGSVC dialogs always return the values that
were originally written by the developer, regardless of the
language. This makes it easier to code logical branching such
as Yes/No dialogs. For example, consider the following code:
lcAsk=Dialog("What
would you like to do?")
DO CASE
CASE lcAsk=[Cancel]
CASE lcAsk=[Retry]
CASE lcAsk=[Close]
ENDCASE
To use MSGSVC in this case, all that is required is to replace DIALOG with MSGSVC and create an entry in the MSGSVC table that provides the needed functionality.
The MSGSVC table has the following structure:
Field | Field Name | Type | Width | Dec | Index | Collate |
1 | CKEY | Character | 30 | Asc | Machine | |
2 | CFUNCTION | Character | 60 | |||
3 | CSCHEME | Character | 10 | |||
4 | CORIGINAL | Memo | 10 | |||
5 | CFRENCH | Memo | 10 | |||
6 | CGERMAN | Memo | 10 | |||
7 | CERRNO | Character | 5 | |||
8 | CALIGNMENT | Character | 1 | |||
9 | CBELL | Character | 5 | |||
10 | CROW | Character | 3 | |||
11 | CCOL | Character | 3 | |||
12 | CVISUAL | Character | 30 | |||
13 | CGUIVISUAL | Character | 60 | |||
14 | COBJECT | Character | 2 | |||
15 | CTIMEOUT | Character | 5 | |||
16 | CTITLE | Character | 50 | |||
17 | CRETTYPE | Character | 1 | |||
18 | CWHERE | Memo | 10 | |||
19 | CFONT | Character | 20 | |||
20 | NFONTSIZE | Numeric | 2 | |||
21 | CFONTSTYLE | Character | 1 |
Each of the fields perform a distinct function with each message service provided. For example, CSCHEME is the DOS Color Scheme used for the dialog, CROW and CCOL are the Row and Column positions of the dialog. Since all of this information is table-based, it is possible not only to change the text of the message but the actual colour, positioning and type of message. The CFUNCTION field contains the actual type of dialog shown to the user. If you wish to have a simple Yes/No dialog, you can enter "YN" into the CFUNCTION field. If at a later date, you want to change it to a WAIT WINDOW, simply change the CFUNCTION field values and MSGSVC will dynamically change the dialog displayed to the user. This dynamic control of dialogs is wonderful during prototyping because it allows you to code a simple message and worry about the final text at a later date.
Calling MSGSVC is very easy. The first parameter is the key that will be used to search for the text. Often times, this may be the actual text itself. If MSGSVC cannot find the text, it will add a new record into the MSGSVC table using the text as both the CKEY and the CORIGINAL fields. This allows you to dynamically add messages to the table. The second parameter can be one of two things. If you are passing a value to the message that doesnt need to be translated, it can be passed as the second parameter. In order for this to work, the message must already exist in the MSGSVC table and contain a %C% placeholder in the CORIGINAL field where the value will be placed. For example, to have a message that says :
You are now working on your tenth order.
You might add a record to the MSGSVC table
with a CKEY called "ORDER".
The CORIGINAL field would read:
You are now working on your %C% order.
When you want to call this function, you would use the following syntax:
=MSGSVC("ORDER", "tenth")
Message Services also provides the ability to create thermometers by using the word THERM in the CFUNCTION field. Thermometers are called a bit differently from MSGSVC than others. The first and second parameters are identical to standard MSGSVC. The third parameter to MSGSVC is how far you want the thermometer to be updated. If the third parameter is 100, MSGSVC will display the thermometer for a second and then it will disappear.
As an example, if you wanted to create a thermometer and there were no such entries in the MSGSVC table, you could do so with the following code:
DO MSGSVC WITH "Updating...", "THERM",10
This would place a thermometer in the middle of the screen. The first line of the thermometer would say "Updating..." and the progress bar would be at approximately 10 percent.
Now that the thermometer is visible, any subsequent calls to MSGSVC for thermometers will place the message directly below the first line.
DO MSGSVC WITH "Indexes...", "THERM",20
This call would update the thermometer originally created to 20 percent completed. The thermometer would continue to say "Updating..." but the second line would say "Indexes...".
The above examples work regardless of the entries in the MSGSVC table. Now lets look at an example of thermometer that displays names as it gets updated.
In the MSGSVC table, create an entry similar to one described below:
CKEY: Update Person
CORIGINAL:Updating %C%
CFUNCTION: THERM
Your call to create the thermometer can now be simply:
=MSGSVC("Update Person")
This will create the thermometer without creating a progress bar. Your next call could be:
=MSGSVC("Update Person", "Andrew MacNeill",20)
This will update the thermometer and place "Updating Andrew MacNeill" as the second line in the thermometer. This also ensures that the parameter will not be translated which may be important if you are displaying fields from a table. You could also replace the second parameter with a variable making it easier to display fields.
Localizing Strings
To properly localize an application, every string of text must be localized using the I() function. If you use arrays for data entry, the individual content of the arrays must also be localized. For example, if you have a piece of code for an array like the following:
DIMENSION a_fruit(5)
a_fruit(1)=Apples
a_fruit(2)=Oranges
a_fruit(3)=Bananas
a_fruit(4)=Pineapple
a_fruit(5)=Grapes
You will need to localize each string using the I() function so that Apples becomes I(Apples). As a developer, you may want to try to speed this process up by using a Project Search tool and search for all occurences of the string delimiters, like ,", and []. The pitfall here is you dont want to localize any FoxPro keywords. This makes the entire process a manual one.
The practice described above also is required for working with BROWSE headings. If you call a BROWSE statement using the :H clause so you can identify a column header, you may want to define these values outside of the original browse statement to increase performance. Take the following example:
This is the original browse statement:
BROWSE FIELDS cust_id :H="Customer Number", cust_name :H="Name of Customer", cust_sex :H="Gender", cust_phone :H="Phone Number" NORMAL
When converting it to using the I() function, it makes more sense to use macro substitution for the fields statement as shown below.
flds= [cust_id :H=I("Customer Number"), cust_name :H=I("Name of Customer"), cust_sex :H=I("Gender"), cust_phone :H=I("Phone Number")]
BROWSE FIELDS &flds NORMAL
Note that the use of delimiters is very important. You must use a delimiter not already being used to hold the entire browse statement.
INTLTOOL - Helping to ease the transition
The last tool of the INTL Toolkit, INTLTOOL, does a number of things. It can automatically update the STRINGS and MSGSVC table so that you dont have to worry about if you have entries for every call you make to I() or MSGSVC. This is useful near the final phases of your development. It can also create language specific source code programs when needed. Obviously, INTL goes a long way to providing on-the-fly translation. However, there may be parts of an application that need to be hard-coded for a particular language. In this case, you can call INTLTOOL to change all calls to I() with the appropriate language. For example, the file JUNK.PRG needs to be translated to French only. The following call to INTLTOOL will do this:
=INTLTOOL(4, "JUNK.PRG", "C:\FOXPRO25\INTL\", "FRENCH", "FRENCH\JUNK.PRG")
The third parameter is the location of the STRINGS table. The fourth is the language to be used. The fifth contains the path and name of the file that will be language specific.
As stated in the beginning of this article, there are many other things to consider when building an international application. The INTL Toolkit provides tools that can put you on the right path. The documentation provides one of the best sources of information on how to do it successfully that Ive ever seen. In previous years, custom applications had to be written specifically for a particular language or company. With the INTL Toolkit, its no longer the case.
Steven Blacks INTL Toolkit is available from SMB Consulting in Canada at (613) 542-3293 or from MicroMega in the US at (415) 346-4445.
The INTL Toolkit
(c)1994 Andrew Ross MacNeill