Introduction
The major issue in developing multilingual
application is displaying text, messages and user
interaction in the local language. We can write
the program in any language like English, French
etc.; The user doesn't care about the language of
PowerScript we use to develop the application
since it is not what he sees.
In any PowerBuilder application we display the
text to the user in two places. The first one
being on all visual objects such as Windows,
Menus, Visual User Objects and DataWindows. The
second one being messages using MessageBox()
function.
One solution to this problem is making copies
of all the visual objects and change the text
into the target language and change all the
messages in the MessageBox()
into the target language. In this method we are
not using any of PowerBuilder OO functionality
and maintenance of these applications will be
very difficult. For example if a company has ties
in 10 different countries, we have 10 copies of
the same application. Later if we need to do some
changes in the system we need to change at 10
different places. So this method is not feasible.
Another solution is to create base language
objects first, say in English and create objects
for each target language by inheriting from these
base objects. In the descendent objects replace
the visual text with the target language
alphabets. Later if you want to do any changes
just do it in the base language object; Since all
other objects are descendents of base language
objects, the changes will be reflected in all the
descendents.
However, the above solution is not a complete
solution.
DataWindow object can't be inherited. We need
to create copies of the base language DataWindow
objects and do changes in all the copies. Any
change needs to be applied in all the copies.
There is no PowerBuilder solution for this
problem.
This solution do not solve the MessageBox()
problem. For example, if you are calling a MessageBox()
some where in the middle of the ancestor event
script with English language message, all the
descent objects will display English version of
the message. To make it to display in the local
language, you need to override the ancestor event
and write all the code in the descent. It is
similar to the problem we faced in the first
solution.
The solution to the MessageBox()
problem is not to hard code the messages in the
application. Instead, write down all the messages
that you need to use in the application and
assign a message number to each message. Now
translate all those message texts into different
languages. Store these messages either in a text
file or in a database table and retrieve
appropriate message depending on the local
language and display it to the user. If you
decide to store in a text file you may want to
maintain different section for each language.
[English]
80001=Could not find any file
...
[French]
80001=Impossible de trouver des fichiers
If you decide to store in a database table you
may want to make message number and the language
id as the primary key.
| ColumnName |
Data type |
| MessageID |
Number |
| LanguageID |
String |
| MessageText |
String |
Once you do this, in the code you may want to
create a function as shown below and call the
function instead of MessageBox().
f_MessageBox( 10030, 80001, "French" )
// f_MessageBox() using INI file.
String ls_TitleText, ls_MessageText
ls_TitleText = ProfileString( "Messages.INI", &
as_LanguageID, String( ai_TitleID ), "" )
ls_MessageText = ProfileString( "Messages.INI", &
as_LanguageID, String( ai_MessageID ), "" )
MessageBox( ls_TitleText, ls_MessageText )
return 0
The above is a simple example. We are taking
two message ids, one for the title and the other
is for the message text. You may want to take
parameters for buttons, default button and the
icon to display on the left also. One more
functionality in a typical application is the use
of parameters in the messages. For example, you
may want to create a message "File %1 not
found" and want to replace '%1' with the
parameter name at runtime. If you don't define
parameterized messages, you will end up creating
thousands of messages.
Instead of using text files you can as well
store messages in the registry. However, it would
kill the registry performance for all
applications. The best solution would be to store
it in a database and maintain a separate database
connection for this purpose.
There is one more problem we need to solve,
that is opening appropriate version of menu,
window and user objects. For example, in the
French version you need to open 'w_login_f',
instead of 'w_login_e' window. There are two
solutions to this problem as explained below.
The first solution is use CASE statement to
check the language and open appropriate window.
For example:
// gs_Language is a global variable.
CHOOSE CASE gs_Language
CASE 'English'
Open( w_login_English )
CASE 'French'
Open( w_login_French )
CASE ELSE
MessageBox( "Error", "Language Error" )
HALT
END CHOOSE
Even though the code looks simple it is not
advisable. Instead you can use a different format
of Open()
function. Open(),
OpenSheet(),
OpenWithParm()
and OpenSheetWithParm()
comes in different flavors. You can use a string
that contains the window name as a parameter to
these functions. This method would be more simple
and efficient than the above one.
String ls_WindowName
Window lw_Window
ls_WindowName = "w_login_' + gs_Language
Open(
lw_Window, ls_WindowName )
Refer to the above function syntax in the
online help. You don't have to open the menus,
because most of the time you assign the menu to
the window. This can be done in the descendent
windows at painting time. You can open the French
version of the window and assign the French
version of the menu in the window properties
dialog box. However, you may be using menu
variables to invoke as popup menus (right mouse
button menus). A typical example would be:
// Declare an instance menu variable in the Window.
m_dw_popup_menu im_popup menu
// In the rButtonDown event of the DataWindow.
im_popup_menu.PopMenu( Parent.PointerX(), &
Parent.PointerY() )
This code is fine if you are developing a
single language version application. In
international applications:
// Declare an instance menu variable in the Window.
Menu im_popup menu
// Create a menu instance
String ls_PopMenuName
ls_PopMenuName = 'm_dw_popup_menu' + gs_Language
im_popup_menu = CREATE USING ls_PopMenuName
// In the rButtonDown event of the DataWindow.
im_popup_menu.PopMenu( Parent.PointerX(), Parent.PointerY() )
Here we are making use of USING
clause in the CREATE
statement. This clause is available from
PowerBuilder version 5.0 onwards.
Having discussed the problems involved in
creating international PowerBuilder applications
and the solutions wondering is there any tool
that can automate any of the above processes? Of
course. PowerBuilder has an 'Translation
Assistant' utility. Till version 5.0 you need to
purchase this product separately. With version
6.0 it is being bundled with Enterprise
PowerBuilder edition. Now, let's see what exactly
Translation Assistant utility is and how it
works.
|