home *** CD-ROM | disk | FTP | other *** search
/ Monster Media 1994 #1 / monster.zip / monster / CLIPPER / LNGMUL.ZIP / LNGMULTI.TXT < prev    next >
Text File  |  1994-02-09  |  18KB  |  308 lines

  1.         ┌───────────────────────────────────────────────────────────┐
  2.         │         A SIMPLE GUIDE TO MULTILINGUAL PROGRAMMING        │
  3.         │                                                           │
  4.         │                 Marc Gagnon (c) 1993-94                   │
  5.         └───────────────────────────────────────────────────────────┘
  6.  
  7. 1. INTRODUCTION
  8.  
  9. Programming the same software for users speaking a diffent language
  10. than our own home language is rarely an issue.  Until some shiny day
  11. the boss walks in and asks "pronto" a version of the software in
  12. some foreign language: a hefty distribution aggrement accompagnied by
  13. a nice check!
  14.  
  15.  
  16. 2. MULTILINGUAL SUPPORT
  17.  
  18. So a decision needs to be made quickly, we need a strategy for
  19. translating the software.  The translation itself is not the real
  20. problem.  The problem is how the source code will be maintained afterwards.
  21.  
  22.   o Will we simply copy all source files to a new directory, hire a
  23.     translator, show him/her how to use a text editor and recognize strings
  24.     and bingo! le tour est joué?
  25.   o Will we create defines for each string?
  26.   o Will we create memory arrays for each string?
  27.   o Will we create a table (DBF) or a text file with all strings in it?
  28.   o Will we use conditional compiling and simply duplicate the portions
  29.     of code containing string?
  30.   o Will we embed the translation directly into on single source?
  31.  
  32.  
  33. Consider the following four methods of implementing multilingual
  34. support in your systems.  I have tried them all and prefer the
  35. last one (D) by far.  There are also many other ways to support
  36. multiple languages, but these are the most common:
  37.  
  38.  
  39. A) Single language (hard coded)
  40.  
  41. Well this method is the most common.  When it was never forseen in
  42. advance that multilingual support would ever be an issue.  The marketing
  43. folks suddenly have the brillant idea of expanding the territory for
  44. sales and surprise! surprise! surprise! .... foreign languages in
  45. foreign countries (or provinces for Québec and some bilingual Atlantic
  46. provinces; taking Canada as an example).  The worst solution to support
  47. the need for multiple language is to copy all source code into another
  48. directory and simply translate all strings into the desired language.
  49. Imagine supporting 3 languages, means three version of the source to
  50. maintain at all times; the smallest new release can become a nightmare!
  51.  
  52. Single language (hard coded):
  53. ┌────────────────────────────────────────────────────────────────────────────┐
  54. │C:\PROJECT\ENGLISH\CLIENT.PRG                                               │
  55. ╞════════════════════════════════════════════════════════════════════════════╡
  56. │                                                                            │
  57. │ FUNCTION Client()                                                          │
  58. │                                                                            │
  59. │   @ 0, 0 SAY "Client #" GET lcNumber                                       │
  60. │   @ 1, 0 SAY "Name   :" GET lcName                                         │
  61. │   @ 2, 0 SAY "Address:" GET lcAddr                                         │
  62. │   @ 3, 0 SAY "City   :" GET lcCity                                         │
  63. │   @ 4, 0 SAY "Phone #:" GET lcPhone                                        │
  64. │                                                                            │
  65. │   READ                                                                     │
  66. │                                                                            │
  67. │ RETURN( NIL )                                                              │
  68. │                                                                            │
  69. ├────────────────────────────────────────────────────────────────────────────┤
  70. │C:\PROJECT\FRENCH\CLIENT.PRG                                                │
  71. ╞════════════════════════════════════════════════════════════════════════════╡
  72. │                                                                            │
  73. │ FUNCTION Client()                                                          │
  74. │                                                                            │
  75. │   @ 0, 0 SAY "Nº client:" GET lcNumber                                     │
  76. │   @ 1, 0 SAY "Nom      :" GET lcName                                       │
  77. │   @ 2, 0 SAY "Adresse  :" GET lcAddr                                       │
  78. │   @ 3, 0 SAY "Ville    :" GET lcCity                                       │
  79. │   @ 4, 0 SAY "Téléphone:" GET lcPhone                                      │
  80. │                                                                            │
  81. │   READ                                                                     │
  82. │                                                                            │
  83. │ RETURN( NIL )                                                              │
  84. └────────────────────────────────────────────────────────────────────────────┘
  85.  
  86. B) Data driven (array driven and #DEFINE driven are the same)
  87.  
  88. In this form of implementation, the programmer must setup a database, text
  89. file, #DEFINE, array, or other storage format to contain all tokens
  90. and messages enclosed in the software.  This method also requires a
  91. unique key identifier to be assigned to each token/message.  Uniqueness
  92. can be implemented with a combination of two (2) columns ( "cTxtNo + cLng" )
  93. or directly in the "cTxtNo" column by padding the language code a prefix
  94. or a suffix to the text number (CL01E, CL01F where "E" stands for "English"
  95. and "F" for french).
  96.  
  97. This method is very convenient if you want to allow users to switch from
  98. one language to another on the fly.  But, on the other hand, if your
  99. system is used only in one language at a time (users do not start going
  100. mental -toggling from one language to another- as if they were flipping
  101. (no zapping) channels on TV; you are actually taxing heavily your users.
  102. The system need to load the strings each time you start it up.  Also,
  103. using this approach, your source code becomes a little cryptic.  No more
  104. strings appear in your PRG's that usually reveal a lot about what is
  105. actually programmed in a given fragment of code.  All strings are refered
  106. as codes or number, in fact you normally end up keeping a copy of the
  107. string in your home language as a comment next to each line using a data-
  108. driven string, for readability.  Maintaining such a setup means that you
  109. must exit or shell to DOS, enter DBU and lookup the table periodically
  110. for getting an existing code, creating one and so on....  You can print
  111. a report of all your strings; which is pretty nice! but it can be easly
  112. outdated.  For the programmer, this techology means continuosly switching
  113. back and forth between the source (PRG) and DBU (file containg the strings).
  114. If you use a text file, no need for DBU anymore.  But you still need
  115. cyphering techniques in your PRG.
  116.  
  117. Data driven:
  118. ┌─────────────────────────────────────────┬──────────────────────────────────┐
  119. │CLIENT.PRG                               │LNGTXT.PRG                        │
  120. ╞═════════════════════════════════════════╪══════════════════════════════════╡
  121. │FUNCTION Client()                        │STATIC sc Lng := "E"              │
  122. │                                         │                                  │
  123. │  @ 0,0 SAY GetTxt( "CL01" ) GET lcNumber│FUNCTION SetLng( pcLng )          │
  124. │  @ 1,0 SAY GetTxt( "CL02" ) GET lcName  │  IF pcLng # NIL                  │
  125. │  @ 2,0 SAY GetTxt( "CL03" ) GET lcAddr  │    scLng := pcLng                │
  126. │  @ 3,0 SAY GetTxt( "CL04" ) GET lcCity  │  ENDIF                           │
  127. │  @ 4,0 SAY GetTxt( "CL05" ) GET lcPhone │RETURN( scLng )                   │
  128. │  READ                                   │                                  │
  129. │                                         │FUNCTION GetTxt( pcTxtNo )        │
  130. │RETURN( NIL )                            │  Txt->( dbSeek( pcTxtNo + scLng )│
  131. │                                         │RETURN( Txt->cTxt )               │
  132. └─────────────────────────────────────────┴──────────────────────────────────┘
  133.  Txt.dbf        ┌──────┬────┬──────────────────┐
  134.                 │cTxtNo│cLng│cTxt              │
  135.                 ├──────┼────┼──────────────────┤
  136.                 │CL01  │E   │Client  #:        │
  137.                 │CL01  │F   │# Client :        │
  138.                 │CL02  │E   │Name     :        │
  139.                 │CL02  │E   │Nom      :        │
  140.                 │CL03  │E   │Address  :        │
  141.                 │CL03  │F   │Addresse :        │
  142.                 └──────┴────┴──────────────────┘
  143.  
  144. C) Basic conditional compiling
  145.  
  146. With CA-Clipper 5.x, we now have a built-in preprocessor.  By using
  147. #IFDEF...#ELSE...#ENDIF schemes, multilinguistic requirements can be
  148. achieved.  This means all ressources for the program in the PRG
  149. (as opposed to the data-driven technique).  But this method requires
  150. a lot of cut & paste and is synonym of major headaches for code that
  151. needs revision.  If your code imbeds 4 languages, your will have to
  152. maintain 4 fragments of the same code (oh! sh**!).
  153.  
  154. Basic conditional compiling:
  155. ┌────────────────────────────────────────────────────────────────────────────┐
  156. │CLIENT.PRG                                                                  │
  157. ╞════════════════════════════════════════════════════════════════════════════╡
  158. │                                                                            │
  159. │ FUNCTION Client()                                                          │
  160. │   :                                                                        │
  161. │   :                                                                        │
  162. │   #IFDEF ENGLISH                                                           │
  163. │      @ 0, 0 SAY "Client #" GET lcNumber                                    │
  164. │      @ 1, 0 SAY "Name   :" GET lcName                                      │
  165. │      @ 2, 0 SAY "Address:" GET lcAddr                                      │
  166. │      @ 3, 0 SAY "City   :" GET lcCity                                      │
  167. │      @ 4, 0 SAY "Phone #:" GET lcPhone                                     │
  168. │   #ENDIF                                                                   │
  169. │                                                                            │
  170. │   #IFDEF FRENCH                                                            │
  171. │      @ 0, 0 SAY "Nº Client:" GET lcNumber                                  │
  172. │      @ 1, 0 SAY "Nom      :" GET lcName                                    │
  173. │      @ 2, 0 SAY "Adresse  :" GET lcAddr                                    │
  174. │      @ 3, 0 SAY "Vile     :" GET lcCity                                    │
  175. │      @ 4, 0 SAY "Téléphone:" GET lcPhone                                   │
  176. │   #ENDIF                                                                   │
  177. │                                                                            │
  178. │   READ                                                                     │
  179. │                                                                            │
  180. │ RETURN( NIL )                                                              │
  181. └────────────────────────────────────────────────────────────────────────────┘
  182.  
  183. D) Embedded linguistic clauses
  184.  
  185. This technique uses CA-Clipper's preprocessor in areas not explored
  186. by many people.  I use the #xTRANSLATE directive to enhance all
  187. commands, statements, functions and expressions in CA-Clipper.  It is
  188. as if all commands <inherit> from the embedded linguistic clauses.  By
  189. simply including "LNGMULTI.CH", all commands, statements and expressions
  190. in CA-Clipper are open to the power of embedded multilingual programming.
  191.  
  192. Syntax:
  193.  
  194.    IN <IdLanguage> <xExpression>
  195.  [ IN <IdLanguage2> <xExpression2>...] => <xExpression?>
  196.  
  197. Arguments:
  198.  
  199.   <IdLanguage> is the language identifier for the expression to follow.
  200.   This identifier is case sensitive because there is an underlying
  201.   #DEFINE (normally uppercase is used).  This identifier must be present
  202.   and handles in "LNGMULTI.CH".
  203.  
  204.   <Expression> is any CA-Clipper expression.  The type of the expression
  205.   depends of the value required for the clause in the command, the
  206.   parameter in the function call, etc...
  207.  
  208. Yields:
  209.  
  210.   <xExpression> in the language defined at compile time.  ALL OTHER
  211.   LANGUAGES and expression for those languages will be nullified and
  212.   GENERATE NO CODE.  When no language is defined at compile time, the
  213.   home language, a defined in "LNGDEF.CH", is used and only the
  214.   expressions pertaining to the home language will be compiled. Only
  215.   the expressions the the language defined at compile time will be
  216.   compiled.
  217.  
  218. Description:
  219.  
  220.   This clause can be used in any command, expression or function call.
  221.   Yes, not only commands can have clauses! so can function calls and
  222.   expressions.  This is a concept I like to call the UDEC (User Defined
  223.   Expression Clause).
  224.  
  225.   This release, includes support for three (3) languages :
  226.  
  227.          o FRENCH
  228.          o ENGLISH
  229.          o SPANISH
  230.  
  231.  -Defining language at compile time:
  232.  
  233.   o CLIPPER <PrgName> /d<IdLanguage>      ex.: CLIPPER Client /dFRENCH
  234.   o SET CLIPPERCMD=/m/n/w/d<IdLanguage>   ex.: SET CLIPPERCMD=/m/n/w/dFRENCH
  235.  
  236.  -Adding new languages:
  237.  
  238.   If you need to support more or different languages, you can simply edit
  239.   "LNGMULTI.CH" an setup your new languages.  For each new language there
  240.   are two steps to do in editing "LNGMULTI.CH":
  241.  
  242.   A) The portion that sets the default language needs to check if the
  243.      new language is not defined.  You need to simply embed an additional
  244.      #IFNDEF <IdNewLanguage> ... #ENDIF.
  245.  
  246.   B) Add a language support section for your new language.  You can
  247.      copy the code for another language and simply replace the language
  248.      identifier expression be the new language identifier.
  249.  
  250.  *Note: Always use upper case to define your languages; because #DEFINE
  251.         is case sensitive.
  252.  
  253. Example:
  254.  
  255. Embedded lingustic support:
  256. ┌────────────────────────────────────────────────────────────────────────────┐
  257. │CLIENT.PRG                                                                  │
  258. ╞════════════════════════════════════════════════════════════════════════════╡
  259. │ #INCLUDE "LngMulti.ch"                                                     │
  260. │                                                                            │
  261. │ FUNCTION Client()                                                          │
  262. │   :                                                                        │
  263. │   @ 0, 0 SAY IN ENGLISH "Client #" IN FRENCH "Nº client:" GET lcNumber     │
  264. │   @ 1, 0 SAY IN ENGLISH "Name   :" IN FRENCH "Nom      :" GET lcName       │
  265. │   @ 2, 0 SAY IN ENGLISH "Address:" IN FRENCH "Adresse  :" GET lcAddr       │
  266. │   @ 3, 0 SAY IN ENGLISH "City   :" IN FRENCH "Vile     :" GET lcCity       │
  267. │   @ 4, 0 SAY IN ENGLISH "Phone #:" IN FRENCH "Téléphone:" GET lcPhone      │
  268. │                                                                            │
  269. │   READ                                                                     │
  270. │   :                                                                        │
  271. │   @ ( IN ENGLISH 22 IN FRENCH 23), 0 SAY IN ENGLISH "Press any key..."    ;│
  272. │                                          IN FRENCH  "Appuyez une touche..."│
  273. │   :                                                                        │
  274. │   Alert( IN ENGLISH "Select report output?"                        ;       │
  275. │          IN FRENCH  "Veuillez choisir la destination du rapport?", ;       │
  276. │          IN ENGLISH { "LPT1", "Screen", "File"    }                        │
  277. │          IN FRENCH  { "LPT1", "Écran" , "Fichier" }                        │
  278. │        )                                                                   │
  279. │                                                                            │
  280. │   laColor := IN ENGLISH { "Blue", "Red"  , "White", "Yellow" }     ;       │
  281. │              IN FRENCH  { "Bleu", "Rouge", "Blanc", "Jaune"  }             │
  282. │                                                                            │
  283. │ RETURN( NIL )                                                              │
  284. └────────────────────────────────────────────────────────────────────────────┘
  285.  
  286. 3. CONCLUSION
  287.  
  288. I tried to cover the most important techniques for multilingual support
  289. in programming.  I hope that this paper will help some with decisions
  290. that need to be made now or in the futur and ultimately help elaborating
  291. a better stategy.
  292.  
  293.  
  294. ABOUT THE AUTHOR
  295.  
  296. Marc Gagnon works for a medical software company.  He has been
  297. programming with Clipper since Winter 85.  Object oriented
  298. programming, preprocessor techniques are among his fields of
  299. interest.  He likes music, jokes, sci-fi...  He is qualified as
  300. mental by his co-workers (singing and drumming out loud listening
  301. to his walk-man !@#$).  He also pratices annoying linguistic
  302. exercices such as the "detachable first letter" for every work
  303. he says (example: T-Oday, T-Here was a P-Retty B-Ig B-Lizzard
  304. on M-Ontréal....) it drives everyone in the O-Ffice N-Uts.  In
  305. fact some are C-Atching it like a V-Irus.  Also this disease has
  306. been recently named as "Hot-Keyitis".  He is also President and
  307. co-founder of the GUCM (Montréal Clipper User's Group) since 1990.
  308.