language.bmx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. Strict
  2. Import Brl.Map
  3. Import Brl.TextStream
  4. Rem
  5. bbdoc: Create a new empty language to be used with MaxGUI's localization system.
  6. about: This function is provided in case it is necessary to create a new language from scratch.
  7. In the majority of cases, languages should instead be loaded from INI files using #LoadLanguage.
  8. Use the #DefineLanguageToken, #RemoveLanguageToken and #ClearLanguageTokens commands to add to and
  9. modify the returned language. #SetLanguageName and #LanguageName may also be useful.
  10. See Also: #LoadLanguage, #SetLocalizationLanguage, #LocalizeString and #LocalizeGadget.
  11. EndRem
  12. Function CreateLanguage:TMaxGuiLanguage( name$ )
  13. Return New TMaxGuiLanguage.Create(name)
  14. EndFunction
  15. Rem
  16. bbdoc: Load a language from an INI text stream.
  17. about: @{url} can be a filepath or any other readable #TStream object.
  18. The INI text stream must contain, at minimum, an INI section labelled '[LanguageDefinition]' and
  19. a 'LanguageID' token which should be assigned an appropriate language name.
  20. A typical language INI file may look something like:
  21. {{
  22. [LanguageDefinition]
  23. LanguageID = Français (French)
  24. LanguageVersion = v0.1
  25. LanguageAuthor = Various Sources
  26. ; Toolbar Tips
  27. tb_new = "Nouveau"
  28. tb_open = "Ouvrir"
  29. tb_close = "Fermer"
  30. tb_save = "Enregistrer"
  31. tb_cut = "Couper"
  32. tb_copy = "Copier"
  33. tb_paste = "Coller"
  34. ...
  35. tb_home = "Page d'Accueil"
  36. tb_back = "Précédente"
  37. tb_forward = "Suivante"
  38. ; Tabs
  39. tab_help = "Aide"
  40. tab_output = "Sortie"
  41. tab_locked:%1 = "construction:%1"
  42. ; Time Format, by example: 13:09:02
  43. ; h = 1 (12 hour clock) hh = 13 (24 hour clock)
  44. ; m = 9 (without leading 0) mm = 09 (including leading 0)
  45. ; s = 2 (without leading 0) ss = 02 (including leading 0)
  46. ; pp = {{pm}} (or '{{am}}' from 00:00 -> 11:59)
  47. longtime = hh:mm:ss pp
  48. shorttime = {{longtime}} ; We want short time to be formatted exactly like {{longtime}}
  49. ; Date Format, by example: 9th June 2009
  50. ; d = 9 dd = 09 ddd = {{Wed}} dddd = {{Wednesday}}
  51. ; m = 6 mm = 06 mmm = {{Jun}} mmmm = {{June}}
  52. ; yy = 09 yyyy = 2009 oo = {{9th}}
  53. longdate = dddd oo mmmm dddd ; e.g. Wednesday 9th June 2009
  54. shortdate = dd/mm/yy ; e.g. 09/06/09
  55. ; AM / PM
  56. am = AM
  57. pm = PM
  58. ; Ordinals
  59. 1st = 1e
  60. 2nd = 2er
  61. 3rd = 3e
  62. 4th = 4e
  63. ; etc.
  64. ; Days of the week
  65. Monday = "Lundi"
  66. Mon = "Lun"
  67. Tueday = "Mardi"
  68. Tue = "Mar"
  69. Wednesday = "Mercredi"
  70. Wed = "Mer"
  71. Thursday = "Jeudi"
  72. Thu = "Jeu"
  73. Friday = "Vendredi"
  74. Fri = "Ven"
  75. Saturday = "Samedi"
  76. Sat = "Sam"
  77. Sunday = "Dimanche"
  78. Sun = "Dim"
  79. }}
  80. Note how a semicolon ';' is used to mark the start of a line comment.
  81. INI files support the following escape sequence characters:
  82. [ @{INI Escape Sequence} | @{BlitzMax Equivalent}
  83. * \\ | ~\ 
  84. * \r | ~~r
  85. * \n | ~~n
  86. * \t | ~~t
  87. * \# | ~#
  88. * \; | ;
  89. * \: | :
  90. ]
  91. The bottom three escape sequences are only strictly required if the value of the INI key is not enclosed
  92. in quotes. For example, the following definitions are expected to evaluate to the same string ( ~#;: ).
  93. {{
  94. MyToken = \#\;\:
  95. MyToken = "#;:"
  96. MyToken = "\#\;\:"
  97. }}
  98. Use the #DefineLanguageToken, #RemoveLanguageToken and #ClearLanguageTokens commands to add to and
  99. modify the returned language. #SetLanguageName and #LanguageName may also be useful.
  100. To construct a new language from scratch, use the #CreateLanguage command instead.
  101. See Also: #SetLocalizationLanguage, #SaveLanguage, #LocalizeString and #LocalizeGadget.
  102. EndRem
  103. Function LoadLanguage:TMaxGuiLanguage( url:Object )
  104. Return TMaxGuiLanguage.LoadLanguage(url)
  105. EndFunction
  106. Rem
  107. bbdoc: Saves a language as an INI section to the supplied stream.
  108. about: @{url} can be a filepath or any other writable #TStream object.
  109. If @{url} is a string ending in "/" or "\", it is assumed that @{url} is a directory path and a default filename
  110. will be appended like so:
  111. {{
  112. url = String(url) + LanguageName(language).Split(" ")[0] + ".language.ini"
  113. }}
  114. WARNING: This command will automatically overwrite any existing file at the supplied/resulting file path.
  115. See Also: #LoadLanguage, #SetLocalizationLanguage, #LocalizeString and #LocalizeGadget.
  116. EndRem
  117. Function SaveLanguage( language:TMaxGuiLanguage, url:Object )
  118. If Not TStream(url) And (String(url).EndsWith("/") Or String(url).EndsWith("\")) Then
  119. url = String(url) + language.GetName().Split(" ")[0] + ".language.ini"
  120. EndIf
  121. SaveText( language.Serialize(), url )
  122. EndFunction
  123. Rem
  124. bbdoc: Redefine a language's name.
  125. about: See Also: #LanguageName, #LoadLanguage, #CreateLanguage and #SetLocalizationLanguage.
  126. EndRem
  127. Function SetLanguageName( language:TMaxGuiLanguage, name$ )
  128. language.SetName(name)
  129. EndFunction
  130. Rem
  131. bbdoc: Returns a language's name.
  132. about: See Also: #SetLanguageName, #LoadLanguage, #CreateLanguage and #SetLocalizationLanguage.
  133. EndRem
  134. Function LanguageName$( language:TMaxGuiLanguage )
  135. Return language.GetName()
  136. EndFunction
  137. Rem
  138. bbdoc: Define a language-specific value for a localization token.
  139. about: Localization tokens are case insensitive, and if a token definition already exists in the language
  140. the token value will be overwritten with this most recent @{value$}.
  141. See Also: #LoadLanguage, #CreateLanguage, #SaveLanguage and #SetLocalizationLanguage.
  142. EndRem
  143. Function DefineLanguageToken( language:TMaxGuiLanguage, token$, value$ )
  144. language.DefineToken(token,value)
  145. EndFunction
  146. Rem
  147. bbdoc: Look-up the value of a token for a specific language.
  148. about: Localization tokens are case insensitive, and are either loaded in with the language or defined
  149. using the #DefineLanguageToken command.
  150. If an explicit token definition is not found in the language, the @{token$} string is returned as it was passed.
  151. See Also: #LoadLanguage, #CreateLanguage, #SaveLanguage and #SetLocalizationLanguage.
  152. EndRem
  153. Function LanguageTokenDefinition$( language:TMaxGuiLanguage, token$ )
  154. Return language.LookupToken(token)
  155. EndFunction
  156. Rem
  157. bbdoc: Remove a token definition from a language.
  158. about: The only token which cannot be removed is 'LanguageID' as every language requires this one token to be defined -
  159. it defines the language name. If a matching token isn't already defined, then the command returns silently.
  160. Note: Localization tokens are case insensitive so the following commands are all requesting the same token to be removed:
  161. {{
  162. RemoveLanguageToken( language, "WelcomeMessage" )
  163. RemoveLanguageToken( language, "WELCOMEMESSAGE" )
  164. RemoveLanguageToken( language, "welcomemessage" )
  165. RemoveLanguageToken( language, "WeLcOmEmEsSaGe" )
  166. }}
  167. See Also: #ClearLanguageTokens, #DefineLanguageToken, #LoadLanguage, #CreateLanguage, #SaveLanguage and #SetLocalizationLanguage.
  168. EndRem
  169. Function RemoveLanguageToken( language:TMaxGuiLanguage, token$ )
  170. language.RemoveToken(token)
  171. EndFunction
  172. Rem
  173. bbdoc: Removes all the tokens defined in a language.
  174. about: The only token which will not be removed is 'LanguageID' as every language requires this one token to be defined -
  175. it defines the language name.
  176. See Also: #RemoveLanguageToken, #DefineLanguageToken, #LoadLanguage, #CreateLanguage, #SaveLanguage and #SetLocalizationLanguage.
  177. EndRem
  178. Function ClearLanguageTokens( language:TMaxGuiLanguage )
  179. language.ClearTokens()
  180. EndFunction
  181. Type TMaxGuiLanguage
  182. ?Win32
  183. Const CARRIAGE_RETURN:String = "~r~n"
  184. ?Not Win32
  185. Const CARRIAGE_RETURN:String = "~n"
  186. ?
  187. Const SECTION_HEADER:String = "LanguageDefinition"
  188. Const LANGUAGENAME_TOKEN:String = "LanguageId"
  189. Field strName:String = "Unknown Language"
  190. Field mapDictionary:TMap = CreateMap()
  191. Method Create:TMaxGuiLanguage(name$)
  192. SetName(name)
  193. Return Self
  194. EndMethod
  195. Method SetName(pName$)
  196. DefineToken( LANGUAGENAME_TOKEN, pName )
  197. EndMethod
  198. Method GetName$()
  199. Return strName
  200. EndMethod
  201. Method DefineToken( pToken:String, pText:String )
  202. If pToken Then
  203. If pToken = LANGUAGENAME_TOKEN Then strName = pText
  204. MapInsert( mapDictionary, pToken.ToLower(), Prepare(pText) )
  205. EndIf
  206. EndMethod
  207. Method RemoveToken( pToken:String )
  208. If pToken And pToken.ToLower() <> LANGUAGENAME_TOKEN.ToLower() Then MapRemove( mapDictionary, pToken.ToLower() )
  209. EndMethod
  210. Method LookupToken$(pToken$)
  211. Local tmpValue$ = String(MapValueForKey( mapDictionary, pToken.ToLower() ))
  212. If tmpValue Then Return Prepare(tmpValue) Else Return pToken
  213. EndMethod
  214. Method ClearTokens()
  215. ClearMap mapDictionary
  216. SetName(GetName())
  217. EndMethod
  218. Method LoadEntriesFromText( text:String )
  219. 'Very rough INI section parser
  220. Local tmpIndex:Int, tmpStage:Int = 0
  221. For Local tmpLine:String = EachIn text.Split("~n")
  222. tmpStage = 0
  223. 'Search for valid ';' comment
  224. For tmpIndex = 0 Until tmpLine.length
  225. Select tmpLine[tmpIndex]
  226. Case Asc("~q");If tmpStage <> 0 Then tmpStage = 2
  227. Case Asc("=");If tmpStage = 0 Then tmpStage = 1
  228. Case Asc(";")
  229. If tmpStage = 0 Or tmpStage = 1 Then
  230. Exit
  231. Else
  232. If tmpLine[tmpIndex-1] <> Asc("\") Then tmpStage = 0-(tmpIndex)
  233. EndIf
  234. EndSelect
  235. Next
  236. 'Strip the comment if ';' was the last character
  237. If tmpStage < 0 Then tmpLine = tmpLine[..Abs(tmpStage)]
  238. 'Strip any whitespace
  239. tmpLine = StripWhitespace(tmpLine[..tmpIndex])
  240. If Not tmpLine Then Continue
  241. 'Test for a new section header
  242. If tmpLine[0] = "["[0] Then Exit
  243. 'Slow but easy code to handle any escape characters
  244. tmpLine = tmpLine.Replace("\\","~0").Replace("\r","~r").Replace("\n","~n").Replace("\;",";").Replace("\#","#").Replace("\:",":").Replace("\t","~t").Replace("~0","\")
  245. 'Find the separator
  246. tmpIndex = tmpLine.Find("=")
  247. 'If we have a key/value pair, define a new key in our dictionary.
  248. If tmpIndex > 0 Then DefineToken( StripWhitespace(tmpLine[..tmpIndex],True), StripWhitespace(tmpLine[tmpIndex+1..],True) )
  249. Next
  250. EndMethod
  251. Method Serialize:String()
  252. Local tmpString:String = "["+SECTION_HEADER+"]" + CARRIAGE_RETURN + LANGUAGENAME_TOKEN + " = ~q" + GetName() + "~q" + CARRIAGE_RETURN
  253. For Local tmpKey:String = EachIn MapKeys(mapDictionary)
  254. If tmpKey <> LANGUAGENAME_TOKEN Then
  255. tmpString:+tmpKey+" = ~q"+String(MapValueForKey(mapDictionary,tmpKey))+"~q"+CARRIAGE_RETURN
  256. EndIf
  257. Next
  258. Return tmpString
  259. EndMethod
  260. Method Deserialize:TMaxGuiLanguage(pString$)
  261. Local tmpIndex:Int = pString.Find("["+SECTION_HEADER+"]")
  262. If tmpIndex < 0 Then Return Null
  263. tmpIndex = pString.Find("~n",tmpIndex+SECTION_HEADER.length+2)
  264. If tmpIndex < 0 Then Return Null
  265. If pString.ToLower().Find(LANGUAGENAME_TOKEN.ToLower(),tmpIndex+1) < 0 Then Return Null
  266. ClearMap mapDictionary
  267. LoadEntriesFromText( pString[tmpIndex..] )
  268. Return Self
  269. EndMethod
  270. Function LoadLanguage:TMaxGUILanguage( stream:Object )
  271. Return New TMaxGuiLanguage.Deserialize( LoadText(stream) )
  272. EndFunction
  273. Function StripWhitespace:String( text$, pStripQuotes:Int = False )
  274. Local i:Int, j:Int
  275. For i = 0 Until text.length
  276. If text[i] = " "[0] Or text[i] = "~t"[0] Then Continue
  277. Exit
  278. Next
  279. For j = text.length-1 To i Step -1
  280. If text[j] = " "[0] Or text[j] = "~t"[0] Or text[j] = "~r"[0] Then Continue
  281. Exit
  282. Next
  283. If pStripQuotes And (j-i)>0 And text[i] = "~q"[0] And text[j] = "~q"[0] Then
  284. i:+1;j:-1
  285. EndIf
  286. Return text[i..j+1]
  287. EndFunction
  288. Function Prepare$(text$)
  289. If text="~0" Then Return "" ElseIf text="" Then Return "~0"
  290. Return text
  291. EndFunction
  292. EndType