
     ~I                                                    ~N
     ~I ͸ ~N
     ~I                                                  ~N
     ~I   Programming for an International Environment   ~N
     ~I                                                  ~N
     ~I ; ~N
     ~I                                                    ~N

For most Clipper programmers, writing something like
~I
@ 5, 10 say "Press any key to continue..."
~N
is second nature.  Maybe if you are concerned about the size of
your executable file you have assigned frequently used strings to
memory variables at the top of your start-up routine.  In any
case, you have hard coded the text into your application.  What I
am going to propose in this article is that you ~Hstop doing that.~N

Most of us have little or no experience with languages other than
English, or people that cannot or do not speak it (unless you live
in or near Quebec, of course).  Therefore, we rarely consider that
large potential user pool of non-English speaking people.  This
does not imply that I am going to recommend that you start market-
ing your applications outside the U.S.  Instead, consider how much
more work could you get if your whiz-bang package could be oper-
ated in Spanish or English?  Requests for support English (both
British and American spellings), French, Spanish, German, Portu-
guese, and Dutch have come to me from within the United States and
Canada alone!  What I am going to outline in this article is how
to make it easy for you to support several different languages.

Unfortunately, I am going to restrict this discussion to those
languages that are supported by the extended ASCII character set. 
Trying to support graphics based texts is beyond the scope of my
knowledge.

Actually, there is more to consider than just text.  Clipper cur-
rently supports 5 different date formats.  This means we are actu-
ally talking about supporting other cultures, not just languages. 
The other things to consider are units of measure and currency. 
While there isn't any "perfect" way to try and do this, I will
present my solutions.  ~HNOTE: I do not write applications where ex-
treme accuracy of financial information is required.  I do not
guarantee my system will work for you.
~B
Dealing with text...
~N
When I started writing multilingual applications, I started by as-
signing each piece of text to a unique memory variable, and I just
restored the proper MEM file based on the language.  This very
quickly ate up all my symbol table space, not to mention scads of
RAM needed for other things (like code).

Thanks must be extended to Al Acker at this point for recommending
that I put all those variables into an array.  This significantly
reduced my symbol table usage, but meant that I couldn't use MEM
files any more (using them in the first place is a debate that I
don't want to get in to).  Switching to a text DBF file meant I
could read all the text into an array when the program started. 
Now my only problem was having hundreds of character strings sit-
ting in the free pool that were rarely (if ever) accessed.  There
are two solutions to that problem, and I will discuss them both.

The first solution was to split the file up into functional parts,
and only load those sections that are possibly used.  In my case,
that meant I had a main file loaded all the time, an input file, a
reports file, a utilities file, a communications file, and a few
very specialized files.  While loading  and unloading the various
arrays between program sections takes some time, I feel the memory
savings were worthwhile.  This is the solution I am currently us-
ing, and it seems to be quite satisfactory.

One problem with the array solution is it makes the code very dif-
ficult to read.  I mean, which would you rather see in your code:
~I
@ 5, 10 say "Press any key to continue..."
~N
or
~I
@ 5, 10 say smlang_[24]
~N

Someone on NanForum had solved a similar problem by leaving the
text data file open all the time, and just displaying directly out
of it.  This led to a discussion that clarified in my mind what I
am going to propose, which is :
~I
@ 5, 10 say SayText( "AnyKey" )                   
                                                  
function SayText                                  
parameters sSeekVal                               
private sReturn, nReadNum, nTextLen, i            
sReturn = space(0)                                
if ( MSG->( FarSeek( sSeekVal ) )                 
   nTextLen = MSG->MAX_SIZE                       
   nReadNum = 1 + int( ( nTextLen - 1 ) / 80 )    
   for i = 1 to nREadNum                          
      sReturn = sReturn + MSG->TEXT               
      skip 1 alias MSG                            
   next i                                         
   sReturn = Trim( Left( sReturn, nTextLen ) )    
endif                                             
return( sReturn )                                 
                                                  
function FarSeek                                  
parameters sSeekVal                               
seek sSeekVal                                     
return( found() )                                 
~N
First, let me explain some assumptions.  During our start-up rou-
tine we have opened our text data file, indexed on the NAME field,
aliased as MSG.  The data file consists of 3 fields: the name, the
actual text, and the allowable length of the text.  The last field
helps to keep the program from bombing if the translator has put
in a longer string than you can allow.  Actually, we have a trans-
lation routine that uses that number to limit the amount of text
that can be typed in.  Since the occasion arises when we want to
have multiple lines stored together (such as a complex help line),
the loading routine checks the size, and reads the proper number
of records into that array element.

~I
MESSAGE.DBF Structure             
Field          Type           Size
NAME           Character       20 
TEXT           Character       80 
MAX_SIZE       Numeric          3 
~N
The ~BSayText()~N function will return the text associated with any
name passed to it, or an empty string if the name doesn't exist in
the data file.

In situations where calling the function (and thus seeking in the
message data file) would be too repetitive, you can assign the
text to a memory variable.  If you will use the same several
strings of text several times in the same module, you may want to
assign each to an array element.  This leads to our legibility
problem again, but that can be handled in 5.0 with the preproces-
sor by defining some manifest constants that are more intuitive.

This system relegates the language decision to which text  data
file gets opened at start-up.  For that matter, the text could be
switched in the middle of the application if you wish.  Just close
the old file and open a new one.

~B
Dealing with dates...
~N
While most of the time dates cause no problem, the ones they do
cause are often unexpected.  For example, in a very nice Compass
article, Mike Schinkel discussed creating all of the data conver-
sion routines that Clipper had left out.  One example was STOD(),
a string-to-date conversion.  How would you do that if you didn't
know which of the Clipper supported date types your user had ac-
tive?  There are two solutions, both of which are in the accompa-
nying code file.  The C routine takes advantage of the fact that
the date that Clipper exports to C is the same string that DTOS()
returns.  The pure Clipper solution is much more involved.

Once again, the decision of which date type to use for display is
made at start-up, and can be changed on the fly by the user.

~B
Dealing with money...
~N
For monetary units there are basically two things to consider: the
number of significant digits; and the character used (e.g. $).  To
handle the first, I separate how I store a number from how I dis-
play it.  All money stared in data files goes in as a 6 digit in-
teger.  How that number gets displayed depends on a number stored
as text in the text data file.
~I
@ 4, 10 say MoneyOut( PAYMENTS->BOOZE )                     
replace PAYMENTS->BOOZE with MoneyIn( 20 )                  
                                                            
function MoneyOut                                           
parameters nAmount                                          
return( nAmount * Val( SayText( "CurrencyFactor" ) )        
                                                            
function MoneyIn                                            
parameters nAmount                                          
return( int( nAmount / Val( SayText( "CurrencyFactor" ) )   
~H
NOTE: This can cause major problems if you allow the user to
change the language on the fly!  You may want to store this factor
elsewhere.
~N
The problem with the text (i.e. '$') is that whether it gets dis-
played before or after the number depends on the currency.  I have
settled on using the accepted practice of international banks,
which is placing a 3 letter abbreviation after the amount.  This
means it could be handled like any other text.

~B
Dealing with units of measure...
~N
This one is often a non issue for most applications.  The biggest
trick is making sure that any calculations are unitless.  However,
this doesn't work when validating user input, and some sort of
factor needs to be stored.  Having a public variable that indi-
cates English, Imperial, or Metric units works extremely well, but
requires checking it in each validation of unit specific data. 
Another option is to select a system, and always store your data
in it, then present it as desired, similar to the system outlined
for money above.
~H
NOTE: This can also cause major problems if you allow the user to
change the language on the fly!  You may want to store this factor
elsewhere.

~B
Wrapping this up...
~N
The main thing that I want to get across is that it is not dif-
ficult to support other languages.  One final thing about creating
your text strings: don't try and save space by reusing portions. 
If you have "Please wait for index recreation." and "Creating re-
port.  Please wait...", don't try and store "Please wait" and "for
index recreation.", and then put them together.  This is very syn-
tax specific, and sure to cause problems.

While all of the code in this article is in Summer '87, it would
be very simple to change it for 5.0.  I didn't do it simply be-
cause I don't have it yet (and I'll make no other comment).

~B
About the author...
~N
Clayton Neff works for the worlds largest dairy supplier (who's
right next door) in Kansas City, and has been programming in Clip-
per since Autumn '86.  His favorite hobby is hitting his friends
with sticks ~Hso watch it!~N
