localization.bmx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. Strict
  2. Rem
  3. bbdoc: MaxGUI/Localization
  4. End Rem
  5. Module MaxGUI.Localization
  6. ModuleInfo "Version: 1.02"
  7. ModuleInfo "Author: Seb Hollington"
  8. ModuleInfo "License: zlib/libpng"
  9. Import BRL.System
  10. Import "language.bmx"
  11. Const LOCALIZATION_OFF:Int = 0
  12. Const LOCALIZATION_ON:Int = 1
  13. Rem
  14. bbdoc: Returns the localized version of a string.
  15. about: This function takes one parameter: @{localizationstring$}.
  16. A localization string is just like any other string except that any phrase enclosed in a double pair of
  17. curly-braces is identified as a localization token. For example, the following examples all use valid
  18. localization strings.
  19. {{
  20. LocalizeString("{{welcomemessage}}") 'Localization token(s): welcomemessage
  21. LocalizeString("{{apptitlelabel}}: {{AppTitle}}") 'Localization token(s): apptitlelabel, AppTitle
  22. LocalizeString("Current Time: {{CurrentTime}}") 'Localization token(s): CurrentTime
  23. }}
  24. Localization tokens are case insensitive, and may be made up of any combination of alphanumeric
  25. characters. Firstly, the token is tested to see if it is a reserved word. The following tokens
  26. are currently reserved (although more maybe added in the future):
  27. [ @{Localization Token} | @{Token Will Be Replaced With...}
  28. * AppDir | The value of the #AppDir global constant.
  29. * AppFile | The value of the #AppFile global constant.
  30. * AppTitle | The value of the #AppTitle global constant.
  31. * LaunchDir | The value of the #LaunchDir global constant.
  32. * GCMemAlloced | The value returned by the #GCMemAlloced function (at the moment the token is parsed).
  33. ]
  34. There are also some reserved date and time tokens which will display the current date and time (at the
  35. moment of parsing) using any formats defined in the current language. If there are no matching formats
  36. explicitly defined, the formats default to:
  37. [ @{Localization Token} | @{Default Format} | @{Sample Output}
  38. * ShortTime | "hh:mm pp" | 02:36 {{pm}}
  39. * LongTime | "hh:mm:ss" | 14:36:51
  40. * ShortDate | "dd/mm/yy" | 04/08/09
  41. * LongDate | "dddd oo mmmm yyyy" | {{Monday}} {{4th}} {{August}} 2009
  42. ]
  43. Notice how any text-based time and date information is wrapped in curly braces. These tokens will be
  44. localized, just like any other token, and so can be modified by adding a corresponding entry to the
  45. localization language.
  46. This also demonstrates the ability of the localization parser to handle nested tokens. To prevent lock-
  47. ups, the localization parser checks for cyclic token definitions, and if one is encountered the token will
  48. be simply replaced with '!ERROR!' and the the offending localization string will be identified in the warning
  49. message written to standard error.
  50. If and only if the localization token isn't reserved will the current localization language be queried. If
  51. no localization language is selected, or if there is no matching token defined in the current language, the
  52. token will simply be stripped of its curly braces in the returned string. Each language is required to have
  53. at least one token defined: {{LanguageID}}. This should represent the language name e.g. 'Fran�ais (French)'.
  54. %{NOTE: This function requires the LOCALIZATION_ON flag to be set (see #SetLocalizationMode) otherwise
  55. the function will simply return @{localizationstring$} exactly as it was passed (including any curly braces).}
  56. See Also: #SetLocalizationMode, #LocalizationMode, #SetLocalizationLanguage and #LocalizationLanguage.
  57. EndRem
  58. Function LocalizeString$( localizationstring$ )
  59. Return TMaxGUILocalizationEngine.LocalizeString( localizationstring )
  60. EndFunction
  61. Rem
  62. bbdoc: Enable or disable the localization engine, and set other localization modes.
  63. about: The mode can be set to:
  64. [ @{Constant} | @{Meaning}
  65. * LOCALIZATION_OFF | Any localized gadgets will display their localizedtext$ as their actual text.
  66. * LOCALIZATION_ON | Localized gadgets will use the current language to display their text.
  67. ]
  68. Either mode can be combined (btiwse OR'd) with LOCALIZATION_OVERRIDE, which will cause gadgets
  69. to become automatically 'localized' when they are created, with any @{text$} parameters supplied
  70. to the @{CreateGadget()} functions being interpreted as localization strings.
  71. If any window menus are localized, #UpdateWindowMenu may have to be called on all relevant windows for the text changes
  72. to be visible.
  73. See Also: #LocalizationMode, #SetLocalizationLanguage, #LocalizationLanguage and #LocalizeGadget.
  74. EndRem
  75. Function SetLocalizationMode( mode:Int = LOCALIZATION_ON )
  76. _SetLocalizationMode(mode:Int)
  77. EndFunction
  78. Global _SetLocalizationMode( mode:Int ) = TMaxGUILocalizationEngine.SetMode
  79. Rem
  80. bbdoc: Returns the value previously set using #SetLocalizationMode.
  81. about: The default value for a MaxGUI program is LOCALIZATION_OFF.
  82. See #SetLocalizationMode for valid modes, and their corresponding constants.
  83. EndRem
  84. Function LocalizationMode:Int()
  85. Return TMaxGUILocalizationEngine.GetMode()
  86. EndFunction
  87. Rem
  88. bbdoc: Set the language to be used by MaxGUI's localization system.
  89. about: Languages can be loaded from files/streams using #LoadLanguage and created from scratch using
  90. #CreateLanguage.
  91. This function will automatically update the text of any gadget marked as 'localized' using #LocalizeGadget.
  92. If any window menus are localized, #UpdateWindowMenu may have to be called on all relevant windows for the text changes
  93. to be visible.
  94. See Also: #LocalizationLanguage, #SetLocalizationMode, #LocalizationMode and #LocalizeString.
  95. EndRem
  96. Function SetLocalizationLanguage( language:TMaxGUILanguage )
  97. _SetLocalizationLanguage( language )
  98. EndFunction
  99. Global _SetLocalizationLanguage( language:TMaxGUILanguage ) = TMaxGUILocalizationEngine.SetLanguage
  100. Rem
  101. bbdoc: Returns the current language used by MaxGUI's localization system.
  102. about: Use the #DefineLanguageToken, #RemoveLanguageToken and #ClearLanguageTokens commands to add to and
  103. modify the returned language. #SetLanguageName and #LanguageName may also be useful.
  104. about: See Also: #SetLocalizationLanguage, #SetLocalizationMode, #LocalizationMode and #LocalizeGadget.
  105. EndRem
  106. Function LocalizationLanguage:TMaxGUILanguage()
  107. Return TMaxGUILocalizationEngine.GetLanguage()
  108. EndFunction
  109. Private
  110. Type TMaxGUILocalizationEngine
  111. Global intLocalizationMode:Int = LOCALIZATION_OFF
  112. Global _currentLanguage:TMaxGUILanguage
  113. Global _localizeStack:String[]
  114. Method New()
  115. Return Null
  116. EndMethod
  117. ' Indirection allows MaxGUI.MaxGUI's TMaxGUIDriver to intercept function calls
  118. ' and update gadgets if necessary (see bottom of maxgui.mod/driver.bmx).
  119. Global SetMode( mode:Int ) = TMaxGUILocalizationEngine._SetMode
  120. Global SetLanguage( language:TMaxGUILanguage ) = TMaxGUILocalizationEngine._SetLanguage
  121. Function _SetMode( mode:Int )
  122. intLocalizationMode = mode
  123. EndFunction
  124. Function GetMode:Int()
  125. Return intLocalizationMode
  126. EndFunction
  127. Function GetLanguage:TMaxGUILanguage()
  128. Return _currentLanguage
  129. EndFunction
  130. Function _SetLanguage( language:TMaxGUILanguage )
  131. _currentLanguage = language
  132. EndFunction
  133. Function LocalizeString:String( Text$ )
  134. ' Only localize the string if the localization engine is turned on.
  135. If (intLocalizationMode&LOCALIZATION_ON) Then
  136. ' Check for cyclic definitions by comparing localization string with those on the stack.
  137. For Local i:Int = _localizeStack.length-1 To 0 Step -1
  138. If _localizeStack[i] = Text Then
  139. WriteStderr "WARNING: Encountered cyclic localization string: ~q"+_localizeStack[i]+"~q.~n"
  140. Return "!ERROR!"
  141. EndIf
  142. Next
  143. ' Add localization string to the stack.
  144. _localizeStack:+[Text]
  145. Local tmpText:String
  146. Local i:Int, tmpPrevChar:Int
  147. Local tmpCount:Int, tmpOpenings:Int[Text.length/2] 'manages opening character index for nested tokens
  148. ' Start parsing for curly braces
  149. While i < Text.length
  150. Select Text[i]
  151. Case Asc("{")
  152. 'If previous char was also an opening "{" then we're entering into a token
  153. If tmpPrevChar = Text[i] Then
  154. tmpOpenings[tmpCount] = i+1
  155. tmpCount:+1
  156. tmpPrevChar = 0
  157. 'Otherwise update the value of the last char and move onto the next character.
  158. Else
  159. tmpPrevChar = Text[i]
  160. EndIf
  161. Case Asc("}")
  162. 'If previous char was also a closing "}" then we're leaving a token, so interpret it.
  163. If tmpPrevChar = Text[i] Then
  164. ' Retrieve the token text
  165. tmpCount:-1
  166. tmpText = Text[tmpOpenings[tmpCount]..i-1]
  167. ' Check for reserved words or run it through the dictionary.
  168. Select tmpText.ToLower()
  169. ' Keywords
  170. Case "appfile";tmpText = AppFile
  171. Case "appdir";tmpText = AppDir
  172. Case "apptitle";tmpText = AppTitle
  173. Case "launchdir";tmpText = LaunchDir
  174. Case "gcmemalloced";tmpText = GCMemAlloced()
  175. ' Time parsing
  176. Case "shorttime", "longtime"
  177. ' Check if the current language defines its own time format for the token
  178. If _currentLanguage Then tmpText = LocalizeString(_currentLanguage.LookupToken(tmpText))
  179. ' Either way, call LocalizeTime() and then localize the returned string (in case it contains tokens too).
  180. tmpText = LocalizeString(LocalizedTime(tmpText))
  181. ' Date parsing
  182. Case "shortdate", "longdate"
  183. ' Check if the current language defines its own date format for the token
  184. If _currentLanguage Then tmpText = LocalizeString(_currentLanguage.LookupToken(tmpText))
  185. ' Either way, call LocalizeDate() and then localize the returned string (in case it contains tokens too).
  186. tmpText = LocalizeString(LocalizedDate(tmpText))
  187. ' Language definition
  188. Default
  189. If _currentLanguage Then
  190. ' Lookup token using the current language, and localize the returned string (in case that contains tokens).
  191. tmpText = LocalizeString(_currentLanguage.LookupToken(tmpText))
  192. EndIf
  193. EndSelect
  194. ' Substitute the localized text into the string in-place, to enable nested parsing to work.
  195. Text = Text[..tmpOpenings[tmpCount]-2] + tmpText + Text[i+1..]
  196. i:+(tmpText.length-(i+3-tmpOpenings[tmpCount]))
  197. tmpPrevChar = 0
  198. 'Otherwise update the value of the last char and move onto the next character.
  199. Else
  200. tmpPrevChar = Text[i]
  201. EndIf
  202. Default
  203. tmpPrevChar = 0
  204. EndSelect
  205. i:+1
  206. Wend
  207. ' Remove localization string from stack as we're about to return
  208. _localizeStack = _localizeStack[.._localizeStack.length-1]
  209. EndIf
  210. ' Return the localized text.
  211. Return Text
  212. EndFunction
  213. Function LocalizedTime:String(pTimeFormat$)
  214. Local i:Int = 0, tmpTokenCount:Int = 0, tmpToken$, tmpTime:String[] = CurrentTime().Split(":")
  215. Select pTimeFormat.ToLower()
  216. Case "shorttime";pTimeFormat = "hh:mm "
  217. Case "longtime";pTimeFormat = "hh:mm:ss "
  218. Default;pTimeFormat:+" "
  219. EndSelect
  220. While i < pTimeFormat.length
  221. If tmpTokenCount And (pTimeFormat[i-1] <> pTimeFormat[i]) Then
  222. tmpToken = Null
  223. Select pTimeFormat[i-1]
  224. Case Asc("h")
  225. Select tmpTokenCount
  226. Case 1;tmpToken = Int(tmpTime[0]) Mod 12
  227. Case 2;tmpToken = Int(tmpTime[0])
  228. EndSelect
  229. Case Asc("m")
  230. Select tmpTokenCount
  231. Case 1;tmpToken = Int(tmpTime[1])
  232. Case 2;tmpToken = tmpTime[1]
  233. EndSelect
  234. Case Asc("s")
  235. Select tmpTokenCount
  236. Case 1;tmpToken = Int(tmpTime[2])
  237. Case 2;tmpToken = tmpTime[2]
  238. EndSelect
  239. Case Asc("p")
  240. If tmpTokenCount = 2 Then
  241. If tmpTime[0] < 12 Then tmpToken = "{{am}}" Else tmpToken = "{{pm}}"
  242. EndIf
  243. EndSelect
  244. If tmpToken
  245. pTimeFormat = pTimeFormat[..(i-tmpTokenCount)] + tmpToken + pTimeFormat[i..]
  246. i:+tmpToken.length-tmpTokenCount
  247. EndIf
  248. tmpTokenCount = 0
  249. EndIf
  250. Select pTimeFormat[i]
  251. Case "h"[0],"m"[0],"s"[0],"p"[0]
  252. tmpTokenCount:+1
  253. Default
  254. tmpTokenCount = 0
  255. EndSelect
  256. i:+1
  257. Wend
  258. Return pTimeFormat[..pTimeFormat.length-1]
  259. EndFunction
  260. Function LocalizedDate:String(pDateFormat$)
  261. Local i:Int = 0, tmpTokenCount:Int = 0, tmpToken$, tmpDate:String[] = CurrentDate().Split(" ")
  262. Select pDateFormat.ToLower()
  263. Case "shortdate";pDateFormat= "dd/mm/yy "
  264. Case "longdate";pDateFormat = "dddd oo mmmm yyyy "
  265. Default;pDateFormat:+" "
  266. EndSelect
  267. While i < pDateFormat.length
  268. If tmpTokenCount And (pDateFormat[i-1] <> pDateFormat[i]) Then
  269. tmpToken = Null
  270. Select pDateFormat[i-1]
  271. Case Asc("d")
  272. Select tmpTokenCount
  273. Case 1;tmpToken = Int(tmpDate[0])
  274. Case 2;tmpToken = tmpDate[0]
  275. Case 3,4
  276. Local tmpDayAsInt:Int = DayOfTheWeek(Int(tmpDate[0]),_MonthAsNumber(tmpDate[1]),Int(tmpDate[2]))
  277. If tmpTokenCount = 3 Then tmpToken = "{{"+_shortDays[tmpDayAsInt]+"}}" Else tmpToken = "{{"+_fullDays[tmpDayAsInt]+"}}"
  278. EndSelect
  279. Case Asc("m")
  280. Select tmpTokenCount
  281. Case 1,2
  282. Local tmpMonth:Int = _MonthAsNumber(tmpDate[1])
  283. If tmpTokenCount = 1 And tmpMonth < 10 Then tmpToken = " " + tmpMonth Else tmpToken = tmpMonth
  284. Case 3;tmpToken = "{{"+tmpDate[1]+"}}"
  285. Case 4;tmpToken = "{{" + _fullMonths[_MonthAsNumber(tmpDate[1])] + "}}"
  286. EndSelect
  287. Case Asc("y")
  288. Select tmpTokenCount
  289. Case 2;tmpToken = tmpDate[2][tmpDate.length-2..]
  290. Case 4;tmpToken = tmpDate[2]
  291. EndSelect
  292. Case Asc("o")
  293. If tmpTokenCount = 2 Then
  294. Select Int(tmpDate[0])
  295. Case 1,21,31;tmpToken = "{{" + Int(tmpDate[0]) + "st}}"
  296. Case 2,22;tmpToken = "{{" + Int(tmpDate[0]) + "nd}}"
  297. Case 3,23;tmpToken = "{{" + Int(tmpDate[0]) + "rd}}"
  298. Default;tmpToken = "{{" + Int(tmpDate[0]) + "th}}"
  299. EndSelect
  300. EndIf
  301. EndSelect
  302. If tmpToken
  303. pDateFormat = pDateFormat[..(i-tmpTokenCount)] + tmpToken + pDateFormat[i..]
  304. i:+tmpToken.length-tmpTokenCount
  305. EndIf
  306. tmpTokenCount = 0
  307. EndIf
  308. Select pDateFormat[i]
  309. Case Asc("d"),Asc("m"),Asc("y"),Asc("o")
  310. tmpTokenCount:+1
  311. Default
  312. tmpTokenCount = 0
  313. EndSelect
  314. i:+1
  315. Wend
  316. Return pDateFormat[..pDateFormat.length-1]
  317. EndFunction
  318. Global _fullMonths:String[] = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
  319. Global _shortDays:String[] = ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]
  320. Global _fullDays:String[] = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]
  321. Function _MonthAsNumber:Int(month$)
  322. Select month.ToLower()
  323. Case "jan";Return 1
  324. Case "feb";Return 2
  325. Case "mar";Return 3
  326. Case "apr";Return 4
  327. Case "may";Return 5
  328. Case "jun";Return 6
  329. Case "jul";Return 7
  330. Case "aug";Return 8
  331. Case "sep";Return 9
  332. Case "oct";Return 10
  333. Case "nov";Return 11
  334. Case "dec";Return 12
  335. EndSelect
  336. RuntimeError "Unrecognised month: ~q" + month + "~q"
  337. EndFunction
  338. Function DayOfTheWeek:Int(Day:Int, Month:Int, Year:Int)
  339. Local Jt:Float = Float(367 * Year - ((7 * (Year + 5001 + ((Month - 9) / 7))) / 4) + ((275 * Month) / 9) + Day + 1729777)+1.5
  340. Return (jt Mod 7)
  341. End Function
  342. EndType