SF_L10N.xba 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
  3. <script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_L10N" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
  4. REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. ===
  5. REM === Full documentation is available on https://help.libreoffice.org/ ===
  6. REM =======================================================================================================================
  7. Option Compatible
  8. Option ClassModule
  9. &apos;Option Private Module
  10. Option Explicit
  11. &apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
  12. &apos;&apos;&apos; L10N (aka SF_L10N)
  13. &apos;&apos;&apos; ====
  14. &apos;&apos;&apos; Implementation of a Basic class for providing a number of services
  15. &apos;&apos;&apos; related to the translation of user interfaces into a huge number of languages
  16. &apos;&apos;&apos; with a minimal impact on the program code itself
  17. &apos;&apos;&apos;
  18. &apos;&apos;&apos; The design choices of this module are based on so-called PO-files
  19. &apos;&apos;&apos; PO-files (portable object files) have long been promoted in the free software industry
  20. &apos;&apos;&apos; as a mean of providing multilingual UIs. This is accomplished through the use of human-readable
  21. &apos;&apos;&apos; text files with a well defined structure that specifies, for any given language,
  22. &apos;&apos;&apos; the source language string and the localized string
  23. &apos;&apos;&apos;
  24. &apos;&apos;&apos; To read more about the PO format and its ecosystem of associated toolsets:
  25. &apos;&apos;&apos; https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files
  26. &apos;&apos;&apos; and, IMHO, a very good tutorial:
  27. &apos;&apos;&apos; http://pology.nedohodnik.net/doc/user/en_US/ch-about.html
  28. &apos;&apos;&apos;
  29. &apos;&apos;&apos; The main advantage of the PO format is the complete dissociation between the two
  30. &apos;&apos;&apos; very different profiles, i.e. the programmer and the translator(s).
  31. &apos;&apos;&apos; Being independent text files, one per language to support, the programmer may give away
  32. &apos;&apos;&apos; pristine PO template files (known as POT-files) for a translator to process.
  33. &apos;&apos;&apos;
  34. &apos;&apos;&apos; This class implements mainly 3 mechanisms:
  35. &apos;&apos;&apos; - AddText: for the programmer to build a set of words or sentences
  36. &apos;&apos;&apos; meant for being translated later
  37. &apos;&apos;&apos; - ExportToPOTFile: All the above texts are exported into a pristine POT-file
  38. &apos;&apos;&apos; - GetText: At runtime get the text in the user language
  39. &apos;&apos;&apos; Note that the first two are optional: POT and PO-files may be built with a simple text editor
  40. &apos;&apos;&apos;
  41. &apos;&apos;&apos; Several instances of the L10N class may coexist
  42. &apos; The constraint however is that each instance should find its PO-files
  43. &apos;&apos;&apos; in a separate directory
  44. &apos;&apos;&apos; PO-files must be named with the targeted locale: f.i. &quot;en-US.po&quot; or &quot;fr-BE.po&quot;
  45. &apos;&apos;&apos;
  46. &apos;&apos;&apos; Service invocation syntax
  47. &apos;&apos;&apos; CreateScriptService(&quot;L10N&quot;[, FolderName[, Locale]])
  48. &apos;&apos;&apos; FolderName: the folder containing the PO-files (in SF_FileSystem.FileNaming notation)
  49. &apos;&apos;&apos; Locale: in the form la-CO (language-COUNTRY)
  50. &apos;&apos;&apos; Service invocation examples:
  51. &apos;&apos;&apos; Dim myPO As Variant
  52. &apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;) &apos; AddText and ExportToPOTFile are allowed
  53. &apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;, &quot;C:\myPOFiles\&quot;, &quot;fr-BE&quot;)
  54. &apos;&apos;&apos; &apos;All functionalities are available
  55. &apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
  56. REM =============================================================== PRIVATE TYPES
  57. &apos;&apos;&apos; The recognized elements of an entry in a PO file are (other elements are ignored) :
  58. &apos;&apos;&apos; #. Extracted comments (given by the programmer to the translator)
  59. &apos;&apos;&apos; #, flag (the kde-format flag when the string contains tokens)
  60. &apos;&apos;&apos; msgctxt Context (to store an acronym associated with the message, this is a distortion of the norm)
  61. &apos;&apos;&apos; msgid untranslated-string
  62. &apos;&apos;&apos; msgstr translated-string
  63. &apos;&apos;&apos; NB: plural forms are not supported
  64. Type POEntry
  65. Comment As String
  66. Flag As String
  67. Context As String
  68. MsgId As String
  69. MsgStr As String
  70. End Type
  71. REM ================================================================== EXCEPTIONS
  72. Const DUPLICATEKEYERROR = &quot;DUPLICATEKEYERROR&quot;
  73. REM ============================================================= PRIVATE MEMBERS
  74. Private [Me] As Object
  75. Private [_Parent] As Object
  76. Private ObjectType As String &apos; Must be &quot;L10N&quot;
  77. Private ServiceName As String
  78. Private _POFolder As String &apos; PO files container
  79. Private _Locale As String &apos; la-CO
  80. Private _POFile As String &apos; PO file in URL format
  81. Private _Encoding As String &apos; Used to open the PO file, default = UTF-8
  82. Private _Dictionary As Object &apos; SF_Dictionary
  83. REM ===================================================== CONSTRUCTOR/DESTRUCTOR
  84. REM -----------------------------------------------------------------------------
  85. Private Sub Class_Initialize()
  86. Set [Me] = Nothing
  87. Set [_Parent] = Nothing
  88. ObjectType = &quot;L10N&quot;
  89. ServiceName = &quot;ScriptForge.L10N&quot;
  90. _POFolder = &quot;&quot;
  91. _Locale = &quot;&quot;
  92. _POFile = &quot;&quot;
  93. Set _Dictionary = Nothing
  94. End Sub &apos; ScriptForge.SF_L10N Constructor
  95. REM -----------------------------------------------------------------------------
  96. Private Sub Class_Terminate()
  97. If Not IsNull(_Dictionary) Then Set _Dictionary = _Dictionary.Dispose()
  98. Call Class_Initialize()
  99. End Sub &apos; ScriptForge.SF_L10N Destructor
  100. REM -----------------------------------------------------------------------------
  101. Public Function Dispose() As Variant
  102. Call Class_Terminate()
  103. Set Dispose = Nothing
  104. End Function &apos; ScriptForge.SF_L10N Explicit Destructor
  105. REM ================================================================== PROPERTIES
  106. REM -----------------------------------------------------------------------------
  107. Property Get Folder() As String
  108. &apos;&apos;&apos; Returns the FolderName containing the PO-files expressed as given by the current FileNaming
  109. &apos;&apos;&apos; property of the SF_FileSystem service. Default = URL format
  110. &apos;&apos;&apos; May be empty
  111. &apos;&apos;&apos; Example:
  112. &apos;&apos;&apos; myPO.Folder
  113. Folder = _PropertyGet(&quot;Folder&quot;)
  114. End Property &apos; ScriptForge.SF_L10N.Folder
  115. REM -----------------------------------------------------------------------------
  116. Property Get Languages() As Variant
  117. &apos;&apos;&apos; Returns a zero-based array listing all the BaseNames of the PO-files found in Folder,
  118. &apos;&apos;&apos; Example:
  119. &apos;&apos;&apos; myPO.Languages
  120. Languages = _PropertyGet(&quot;Languages&quot;)
  121. End Property &apos; ScriptForge.SF_L10N.Languages
  122. REM -----------------------------------------------------------------------------
  123. Property Get Locale() As String
  124. &apos;&apos;&apos; Returns the currently active language-COUNTRY combination. May be empty
  125. &apos;&apos;&apos; Example:
  126. &apos;&apos;&apos; myPO.Locale
  127. Locale = _PropertyGet(&quot;Locale&quot;)
  128. End Property &apos; ScriptForge.SF_L10N.Locale
  129. REM ===================================================================== METHODS
  130. REM -----------------------------------------------------------------------------
  131. Public Function AddText(Optional ByVal Context As Variant _
  132. , Optional ByVal MsgId As Variant _
  133. , Optional ByVal Comment As Variant _
  134. , Optional ByVal MsgStr As Variant _
  135. ) As Boolean
  136. &apos;&apos;&apos; Add a new entry in the list of localizable text strings
  137. &apos;&apos;&apos; Args:
  138. &apos;&apos;&apos; Context: when not empty, the key to retrieve the translated string via GetText. Default = &quot;&quot;
  139. &apos;&apos;&apos; MsgId: the untranslated string, i.e. the text appearing in the program code. Must not be empty
  140. &apos;&apos;&apos; The key to retrieve the translated string via GetText when Context is empty
  141. &apos;&apos;&apos; May contain placeholders (%1 ... %9) for dynamic arguments to be inserted in the text at run-time
  142. &apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
  143. &apos;&apos;&apos; Comment: the so-called &quot;extracted-comments&quot; intended to inform/help translators
  144. &apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
  145. &apos;&apos;&apos; MsgStr: (internal use only) the translated string
  146. &apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
  147. &apos;&apos;&apos; Returns:
  148. &apos;&apos;&apos; True if successful
  149. &apos;&apos;&apos; Exceptions:
  150. &apos;&apos;&apos; DUPLICATEKEYERROR: such a key exists already
  151. &apos;&apos;&apos; Examples:
  152. &apos;&apos;&apos; myPO.AddText(, &quot;This is a text to be included in a POT file&quot;)
  153. Dim bAdd As Boolean &apos; Output buffer
  154. Dim sKey As String &apos; The key part of the new entry in the dictionary
  155. Dim vItem As POEntry &apos; The item part of the new entry in the dictionary
  156. Const cstPipe = &quot;|&quot; &apos; Pipe forbidden in MsgId&apos;s
  157. Const cstThisSub = &quot;L10N.AddText&quot;
  158. Const cstSubArgs = &quot;[Context=&quot;&quot;&quot;&quot;], MsgId, [Comment=&quot;&quot;&quot;&quot;]&quot;
  159. If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
  160. bAdd = False
  161. Check:
  162. If IsMissing(Context) Or IsMissing(Context) Then Context = &quot;&quot;
  163. If IsMissing(Comment) Or IsMissing(Comment) Then Comment = &quot;&quot;
  164. If IsMissing(MsgStr) Or IsMissing(MsgStr) Then MsgStr = &quot;&quot;
  165. If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
  166. If Not SF_Utils._Validate(Context, &quot;Context&quot;, V_STRING) Then GoTo Finally
  167. If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
  168. If Not SF_Utils._Validate(Comment, &quot;Comment&quot;, V_STRING) Then GoTo Finally
  169. If Not SF_Utils._Validate(MsgStr, &quot;MsgStr&quot;, V_STRING) Then GoTo Finally
  170. End If
  171. If Len(MsgId) = 0 Then GoTo Finally
  172. Try:
  173. If Len(Context) &gt; 0 Then sKey = Context Else sKey = MsgId
  174. If _Dictionary.Exists(sKey) Then GoTo CatchDuplicate
  175. With vItem
  176. .Comment = Comment
  177. If InStr(MsgId, &quot;%&quot;) &gt; 0 Then .Flag = &quot;kde-format&quot; Else .Flag = &quot;&quot;
  178. .Context = Replace(Context, cstPipe, &quot; &quot;)
  179. .MsgId = Replace(MsgId, cstPipe, &quot; &quot;)
  180. .MsgStr = MsgStr
  181. End With
  182. _Dictionary.Add(sKey, vItem)
  183. Finally:
  184. AddText = bAdd
  185. SF_Utils._ExitFunction(cstThisSub)
  186. Exit Function
  187. Catch:
  188. GoTo Finally
  189. CatchDuplicate:
  190. SF_Exception.RaiseFatal(DUPLICATEKEYERROR, Iif(Len(Context) &gt; 0, &quot;Context&quot;, &quot;MsgId&quot;), sKey)
  191. GoTo Finally
  192. End Function &apos; ScriptForge.SF_L10N.AddText
  193. REM -----------------------------------------------------------------------------
  194. Public Function ExportToPOTFile(Optional ByVal FileName As Variant _
  195. , Optional ByVal Header As Variant _
  196. , Optional ByVal Encoding As Variant _
  197. ) As Boolean
  198. &apos;&apos;&apos; Export a set of untranslated strings as a POT file
  199. &apos;&apos;&apos; The set of strings has been built either by a succession of AddText() methods
  200. &apos;&apos;&apos; or by a successful invocation of the L10N service with the FolderName argument
  201. &apos;&apos;&apos; The generated file should pass successfully the &quot;msgfmt --check &apos;the pofile&apos;&quot; GNU command
  202. &apos;&apos;&apos; Args:
  203. &apos;&apos;&apos; FileName: the complete file name to export to. If it exists, is overwritten without warning
  204. &apos;&apos;&apos; Header: Comments that will appear on top of the generated file. Do not include any leading &quot;#&quot;
  205. &apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
  206. &apos;&apos;&apos; A standard header will be added anyway
  207. &apos;&apos;&apos; Encoding: The character set that should be used
  208. &apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
  209. &apos;&apos;&apos; Note that LibreOffice probably does not implement all existing sets
  210. &apos;&apos;&apos; Default = UTF-8
  211. &apos;&apos;&apos; Returns:
  212. &apos;&apos;&apos; True if successful
  213. &apos;&apos;&apos; Examples:
  214. &apos;&apos;&apos; myPO.ExportToPOTFile(&quot;myFile.pot&quot;, Header := &quot;Top comment\nSecond line of top comment&quot;)
  215. Dim bExport As Boolean &apos; Return value
  216. Dim oFile As Object &apos; Generated file handler
  217. Dim vLines As Variant &apos; Wrapped lines
  218. Dim sLine As String &apos; A single line
  219. Dim vItems As Variant &apos; Array of dictionary items
  220. Dim vItem As Variant &apos; POEntry type
  221. Const cstSharp = &quot;# &quot;, cstSharpDot = &quot;#. &quot;, cstFlag = &quot;#, kde-format&quot;
  222. Const cstTabSize = 4
  223. Const cstWrap = 70
  224. Const cstThisSub = &quot;L10N.ExportToPOTFile&quot;
  225. Const cstSubArgs = &quot;FileName, [Header=&quot;&quot;&quot;&quot;], [Encoding=&quot;&quot;UTF-8&quot;&quot;&quot;
  226. If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
  227. bExport = False
  228. Check:
  229. If IsMissing(Header) Or IsMissing(Header) Then Header = &quot;&quot;
  230. If IsMissing(Encoding) Or IsMissing(Encoding) Then Encoding = &quot;UTF-8&quot;
  231. If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
  232. If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
  233. If Not SF_Utils._Validate(Header, &quot;Header&quot;, V_STRING) Then GoTo Finally
  234. If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
  235. End If
  236. Try:
  237. Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding)
  238. If Not IsNull(oFile) Then
  239. With oFile
  240. &apos; Standard header
  241. .WriteLine(cstSharp)
  242. .WriteLine(cstSharp &amp; &quot;This pristine POT file has been generated by LibreOffice/ScriptForge&quot;)
  243. .WriteLine(cstSharp &amp; &quot;Full documentation is available on https://help.libreoffice.org/&quot;)
  244. &apos; User header
  245. If Len(Header) &gt; 0 Then
  246. .WriteLine(cstSharp)
  247. vLines = SF_String.Wrap(Header, cstWrap, cstTabSize)
  248. For Each sLine In vLines
  249. .WriteLine(cstSharp &amp; Replace(sLine, SF_String.sfLF, &quot;&quot;))
  250. Next sLine
  251. End If
  252. &apos; Standard header
  253. .WriteLine(cstSharp)
  254. .WriteLine(&quot;msgid &quot;&quot;&quot;&quot;&quot;)
  255. .WriteLine(&quot;msgstr &quot;&quot;&quot;&quot;&quot;)
  256. .WriteLine(SF_String.Quote(&quot;Project-Id-Version: PACKAGE VERSION\n&quot;))
  257. .WriteLine(SF_String.Quote(&quot;Report-Msgid-Bugs-To: &quot; _
  258. &amp; &quot;https://bugs.libreoffice.org/enter_bug.cgi?product=LibreOffice&amp;bug_status=UNCONFIRMED&amp;component=UI\n&quot;))
  259. .WriteLine(SF_String.Quote(&quot;POT-Creation-Date: &quot; &amp; SF_STring.Represent(Now()) &amp; &quot;\n&quot;))
  260. .WriteLine(SF_String.Quote(&quot;PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n&quot;))
  261. .WriteLine(SF_String.Quote(&quot;Last-Translator: FULL NAME &lt;EMAIL@ADDRESS&gt;\n&quot;))
  262. .WriteLine(SF_String.Quote(&quot;Language-Team: LANGUAGE &lt;EMAIL@ADDRESS&gt;\n&quot;))
  263. .WriteLine(SF_String.Quote(&quot;Language: en_US\n&quot;))
  264. .WriteLine(SF_String.Quote(&quot;MIME-Version: 1.0\n&quot;))
  265. .WriteLine(SF_String.Quote(&quot;Content-Type: text/plain; charset=&quot; &amp; Encoding &amp; &quot;\n&quot;))
  266. .WriteLine(SF_String.Quote(&quot;Content-Transfer-Encoding: 8bit\n&quot;))
  267. .WriteLine(SF_String.Quote(&quot;Plural-Forms: nplurals=2; plural=n &gt; 1;\n&quot;))
  268. .WriteLine(SF_String.Quote(&quot;X-Generator: LibreOffice - ScriptForge\n&quot;))
  269. .WriteLine(SF_String.Quote(&quot;X-Accelerator-Marker: ~\n&quot;))
  270. &apos; Individual translatable strings
  271. vItems = _Dictionary.Items()
  272. For Each vItem in vItems
  273. .WriteBlankLines(1)
  274. &apos; Comments
  275. vLines = Split(vItem.Comment, &quot;\n&quot;)
  276. For Each sLine In vLines
  277. .WriteLine(cstSharpDot &amp; SF_String.ExpandTabs(SF_String.Unescape(sLine), cstTabSize))
  278. Next sLine
  279. &apos; Flag
  280. If InStr(vItem.MsgId, &quot;%&quot;) &gt; 0 Then .WriteLine(cstFlag)
  281. &apos; Context
  282. If Len(vItem.Context) &gt; 0 Then
  283. .WriteLine(&quot;msgctxt &quot; &amp; SF_String.Quote(vItem.Context))
  284. End If
  285. &apos; MsgId
  286. vLines = SF_String.Wrap(vItem.MsgId, cstWrap, cstTabSize)
  287. If UBound(vLines) = 0 Then
  288. .WriteLine(&quot;msgid &quot; &amp; SF_String.Quote(SF_String.Escape(vLines(0))))
  289. Else
  290. .WriteLine(&quot;msgid &quot;&quot;&quot;&quot;&quot;)
  291. For Each sLine in vLines
  292. .WriteLine(SF_String.Quote(SF_String.Escape(sLine)))
  293. Next sLine
  294. End If
  295. &apos; MsgStr
  296. .WriteLine(&quot;msgstr &quot;&quot;&quot;&quot;&quot;)
  297. Next vItem
  298. .CloseFile()
  299. End With
  300. End If
  301. bExport = True
  302. Finally:
  303. If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
  304. ExportToPOTFile = bExport
  305. SF_Utils._ExitFunction(cstThisSub)
  306. Exit Function
  307. Catch:
  308. GoTo Finally
  309. End Function &apos; ScriptForge.SF_L10N.ExportToPOTFile
  310. REM -----------------------------------------------------------------------------
  311. Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
  312. &apos;&apos;&apos; Return the actual value of the given property
  313. &apos;&apos;&apos; Args:
  314. &apos;&apos;&apos; PropertyName: the name of the property as a string
  315. &apos;&apos;&apos; Returns:
  316. &apos;&apos;&apos; The actual value of the property
  317. &apos;&apos;&apos; If the property does not exist, returns Null
  318. &apos;&apos;&apos; Exceptions:
  319. &apos;&apos;&apos; ARGUMENTERROR The property does not exist
  320. &apos;&apos;&apos; Examples:
  321. &apos;&apos;&apos; myL10N.GetProperty(&quot;MyProperty&quot;)
  322. Const cstThisSub = &quot;L10N.GetProperty&quot;
  323. Const cstSubArgs = &quot;&quot;
  324. If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
  325. GetProperty = Null
  326. Check:
  327. If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
  328. If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
  329. End If
  330. Try:
  331. GetProperty = _PropertyGet(PropertyName)
  332. Finally:
  333. SF_Utils._ExitFunction(cstThisSub)
  334. Exit Function
  335. Catch:
  336. GoTo Finally
  337. End Function &apos; ScriptForge.SF_L10N.GetProperty
  338. REM -----------------------------------------------------------------------------
  339. Public Function GetText(Optional ByVal MsgId As Variant _
  340. , ParamArray pvArgs As Variant _
  341. ) As String
  342. &apos;&apos;&apos; Get the translated string corresponding with the given argument
  343. &apos;&apos;&apos; Args:
  344. &apos;&apos;&apos; MsgId: the identifier of the string or the untranslated string
  345. &apos;&apos;&apos; Either - the untranslated text (MsgId)
  346. &apos;&apos;&apos; - the reference to the untranslated text (Context)
  347. &apos;&apos;&apos; - both (Context|MsgId) : the pipe character is essential
  348. &apos;&apos;&apos; pvArgs(): a list of arguments present as %1, %2, ... in the (un)translated string)
  349. &apos;&apos;&apos; to be substituted in the returned string
  350. &apos;&apos;&apos; Any type is admitted but only strings, numbers or dates are relevant
  351. &apos;&apos;&apos; Returns:
  352. &apos;&apos;&apos; The translated string
  353. &apos;&apos;&apos; If not found the MsgId string or the Context string
  354. &apos;&apos;&apos; Anyway the substitution is done
  355. &apos;&apos;&apos; Examples:
  356. &apos;&apos;&apos; myPO.GetText(&quot;This is a text to be included in a POT file&quot;)
  357. &apos;&apos;&apos; &apos; Ceci est un text à inclure dans un fichier POT
  358. Dim sText As String &apos; Output buffer
  359. Dim sContext As String &apos; Context part of argument
  360. Dim sMsgId As String &apos; MsgId part of argument
  361. Dim vItem As POEntry &apos; Entry in the dictionary
  362. Dim vMsgId As Variant &apos; MsgId split on pipe
  363. Dim sKey As String &apos; Key of dictionary
  364. Dim sPercent As String &apos; %1, %2, ... placeholders
  365. Dim i As Long
  366. Const cstPipe = &quot;|&quot;
  367. Const cstThisSub = &quot;L10N.GetText&quot;
  368. Const cstSubArgs = &quot;MsgId, [Arg0, Arg1, ...]&quot;
  369. If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
  370. sText = &quot;&quot;
  371. Check:
  372. If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
  373. If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
  374. End If
  375. If Len(Trim(MsgId)) = 0 Then GoTo Finally
  376. sText = MsgId
  377. Try:
  378. &apos; Find and load entry from dictionary
  379. If Left(MsgId, 1) = cstPipe then MsgId = Mid(MsgId, 2)
  380. vMsgId = Split(MsgId, cstPipe)
  381. sKey = vMsgId(0)
  382. If Not _Dictionary.Exists(sKey) Then &apos; Not found
  383. If UBound(vMsgId) = 0 Then sText = vMsgId(0) Else sText = Mid(MsgId, InStr(MsgId, cstPipe) + 1)
  384. Else
  385. vItem = _Dictionary.Item(sKey)
  386. If Len(vItem.MsgStr) &gt; 0 Then sText = vItem.MsgStr Else sText = vItem.MsgId
  387. End If
  388. &apos; Substitute %i placeholders
  389. For i = UBound(pvArgs) To 0 Step -1 &apos; Go downwards to not have a limit in number of args
  390. sPercent = &quot;%&quot; &amp; (i + 1)
  391. sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i)))
  392. Next i
  393. Finally:
  394. GetText = sText
  395. SF_Utils._ExitFunction(cstThisSub)
  396. Exit Function
  397. Catch:
  398. GoTo Finally
  399. End Function &apos; ScriptForge.SF_L10N.GetText
  400. REM - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  401. Public Function _(Optional ByVal MsgId As Variant _
  402. , ParamArray pvArgs As Variant _
  403. ) As String
  404. &apos;&apos;&apos; Get the translated string corresponding with the given argument
  405. &apos;&apos;&apos; Alias of GetText() - See above
  406. &apos;&apos;&apos; Examples:
  407. &apos;&apos;&apos; myPO._(&quot;This is a text to be included in a POT file&quot;)
  408. &apos;&apos;&apos; &apos; Ceci est un text à inclure dans un fichier POT
  409. Dim sText As String &apos; Output buffer
  410. Dim sPercent As String &apos; %1, %2, ... placeholders
  411. Dim i As Long
  412. Const cstPipe = &quot;|&quot;
  413. Const cstThisSub = &quot;L10N._&quot;
  414. Const cstSubArgs = &quot;MsgId, [Arg0, Arg1, ...]&quot;
  415. If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
  416. sText = &quot;&quot;
  417. Check:
  418. If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
  419. If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
  420. End If
  421. If Len(Trim(MsgId)) = 0 Then GoTo Finally
  422. Try:
  423. &apos; Find and load entry from dictionary
  424. sText = GetText(MsgId)
  425. &apos; Substitute %i placeholders - done here, not in GetText(), because # of arguments is undefined
  426. For i = 0 To UBound(pvArgs)
  427. sPercent = &quot;%&quot; &amp; (i + 1)
  428. sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i)))
  429. Next i
  430. Finally:
  431. _ = sText
  432. SF_Utils._ExitFunction(cstThisSub)
  433. Exit Function
  434. Catch:
  435. GoTo Finally
  436. End Function &apos; ScriptForge.SF_L10N._
  437. REM -----------------------------------------------------------------------------
  438. Public Function Methods() As Variant
  439. &apos;&apos;&apos; Return the list of public methods of the L10N service as an array
  440. Methods = Array( _
  441. &quot;AddText&quot; _
  442. , &quot;ExportToPOTFile&quot; _
  443. , &quot;GetText&quot; _
  444. , &quot;_&quot; _
  445. )
  446. End Function &apos; ScriptForge.SF_L10N.Methods
  447. REM -----------------------------------------------------------------------------
  448. Public Function Properties() As Variant
  449. &apos;&apos;&apos; Return the list or properties of the Timer class as an array
  450. Properties = Array( _
  451. &quot;Folder&quot; _
  452. , &quot;Languages&quot; _
  453. , &quot;Locale&quot; _
  454. )
  455. End Function &apos; ScriptForge.SF_L10N.Properties
  456. REM -----------------------------------------------------------------------------
  457. Public Function SetProperty(Optional ByVal PropertyName As Variant _
  458. , Optional ByRef Value As Variant _
  459. ) As Boolean
  460. &apos;&apos;&apos; Set a new value to the given property
  461. &apos;&apos;&apos; Args:
  462. &apos;&apos;&apos; PropertyName: the name of the property as a string
  463. &apos;&apos;&apos; Value: its new value
  464. &apos;&apos;&apos; Exceptions
  465. &apos;&apos;&apos; ARGUMENTERROR The property does not exist
  466. Const cstThisSub = &quot;L10N.SetProperty&quot;
  467. Const cstSubArgs = &quot;PropertyName, Value&quot;
  468. If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
  469. SetProperty = False
  470. Check:
  471. If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
  472. If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
  473. End If
  474. Try:
  475. Select Case UCase(PropertyName)
  476. Case Else
  477. End Select
  478. Finally:
  479. SF_Utils._ExitFunction(cstThisSub)
  480. Exit Function
  481. Catch:
  482. GoTo Finally
  483. End Function &apos; ScriptForge.SF_L10N.SetProperty
  484. REM =========================================================== PRIVATE FUNCTIONS
  485. REM -----------------------------------------------------------------------------
  486. Public Sub _Initialize(ByVal psPOFile As String _
  487. , ByVal Encoding As String _
  488. )
  489. &apos;&apos;&apos; Completes initialization of the current instance requested from CreateScriptService()
  490. &apos;&apos;&apos; Load the POFile in the dictionary, otherwise leave the dictionary empty
  491. &apos;&apos;&apos; Args:
  492. &apos;&apos;&apos; psPOFile: the file to load the translated strings from
  493. &apos;&apos;&apos; Encoding: The character set that should be used. Default = UTF-8
  494. Dim oFile As Object &apos; PO file handler
  495. Dim sContext As String &apos; Collected context string
  496. Dim sMsgId As String &apos; Collected untranslated string
  497. Dim sComment As String &apos; Collected comment string
  498. Dim sMsgStr As String &apos; Collected translated string
  499. Dim sLine As String &apos; Last line read
  500. Dim iContinue As Integer &apos; 0 = None, 1 = MsgId, 2 = MsgStr
  501. Const cstMsgId = 1, cstMsgStr = 2
  502. Try:
  503. &apos; Initialize dictionary anyway
  504. Set _Dictionary = SF_Services.CreateScriptService(&quot;Dictionary&quot;)
  505. Set _Dictionary.[_Parent] = [Me]
  506. &apos; Load PO file
  507. If Len(psPOFile) &gt; 0 Then
  508. With SF_FileSystem
  509. _POFolder = ._ConvertToUrl(.GetParentFolderName(psPOFile))
  510. _Locale = .GetBaseName(psPOFile)
  511. _POFile = ._ConvertToUrl(psPOFile)
  512. End With
  513. &apos; Load PO file
  514. Set oFile = SF_FileSystem.OpenTextFile(psPOFile, IOMode := SF_FileSystem.ForReading, Encoding := Encoding)
  515. If Not IsNull(oFile) Then
  516. With oFile
  517. &apos; The PO file is presumed valid =&gt; syntax check is not very strict
  518. sContext = &quot;&quot; : sMsgId = &quot;&quot; : sComment = &quot;&quot; : sMsgStr = &quot;&quot;
  519. Do While Not .AtEndOfStream
  520. sLine = Trim(.ReadLine())
  521. &apos; Trivial examination of line header
  522. Select Case True
  523. Case sLine = &quot;&quot;
  524. If Len(sMsgId) &gt; 0 Then AddText(sContext, sMsgId, sComment, sMsgStr)
  525. sContext = &quot;&quot; : sMsgId = &quot;&quot; : sComment = &quot;&quot; : sMsgStr = &quot;&quot;
  526. iContinue = 0
  527. Case Left(sLine, 3) = &quot;#. &quot;
  528. sComment = sComment &amp; Iif(Len(sComment) &gt; 0, &quot;\n&quot;, &quot;&quot;) &amp; Trim(Mid(sLine, 4))
  529. iContinue = 0
  530. Case Left(sLine, 8) = &quot;msgctxt &quot;
  531. sContext = SF_String.Unquote(Trim(Mid(sLine, 9)))
  532. iContinue = 0
  533. Case Left(sLine, 6) = &quot;msgid &quot;
  534. sMsgId = SF_String.Unquote(Trim(Mid(sLine, 7)))
  535. iContinue = cstMsgId
  536. Case Left(sLine, 7) = &quot;msgstr &quot;
  537. sMsgStr = sMsgStr &amp; SF_String.Unquote(Trim(Mid(sLine, 8)))
  538. iContinue = cstMsgStr
  539. Case Left(sLine, 1) = &quot;&quot;&quot;&quot;
  540. If iContinue = cstMsgId Then
  541. sMsgId = sMsgId &amp; SF_String.Unquote(sLine)
  542. ElseIf iContinue = cstMsgStr Then
  543. sMsgStr = sMsgStr &amp; SF_String.Unquote(sLine)
  544. Else
  545. iContinue = 0
  546. End If
  547. Case Else &apos; Skip line
  548. iContinue = 0
  549. End Select
  550. Loop
  551. &apos; Be sure to store the last entry
  552. If Len(sMsgId) &gt; 0 Then AddText(sContext, sMsgId, sComment, sMsgStr)
  553. .CloseFile()
  554. Set oFile = .Dispose()
  555. End With
  556. End If
  557. Else
  558. _POFolder = &quot;&quot;
  559. _Locale = &quot;&quot;
  560. _POFile = &quot;&quot;
  561. End If
  562. Finally:
  563. Exit Sub
  564. End Sub &apos; ScriptForge.SF_L10N._Initialize
  565. REM -----------------------------------------------------------------------------
  566. Private Function _PropertyGet(Optional ByVal psProperty As String)
  567. &apos;&apos;&apos; Return the value of the named property
  568. &apos;&apos;&apos; Args:
  569. &apos;&apos;&apos; psProperty: the name of the property
  570. Dim vFiles As Variant &apos; Array of PO-files
  571. Dim i As Long
  572. Dim cstThisSub As String
  573. Dim cstSubArgs As String
  574. cstThisSub = &quot;SF_L10N.get&quot; &amp; psProperty
  575. cstSubArgs = &quot;&quot;
  576. SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
  577. With SF_FileSystem
  578. Select Case psProperty
  579. Case &quot;Folder&quot;
  580. If Len(_POFolder) &gt; 0 Then _PropertyGet = ._ConvertFromUrl(_POFolder) Else _PropertyGet = &quot;&quot;
  581. Case &quot;Languages&quot;
  582. If Len(_POFolder) &gt; 0 Then
  583. vFiles = .Files(._ConvertFromUrl(_POFolder), &quot;??-??.po&quot;)
  584. For i = 0 To UBound(vFiles)
  585. vFiles(i) = SF_FileSystem.GetBaseName(vFiles(i))
  586. Next i
  587. Else
  588. vFiles = Array()
  589. End If
  590. _PropertyGet = vFiles
  591. Case &quot;Locale&quot;
  592. _PropertyGet = _Locale
  593. Case Else
  594. _PropertyGet = Null
  595. End Select
  596. End With
  597. Finally:
  598. SF_Utils._ExitFunction(cstThisSub)
  599. Exit Function
  600. End Function &apos; ScriptForge.SF_L10N._PropertyGet
  601. REM -----------------------------------------------------------------------------
  602. Private Function _Repr() As String
  603. &apos;&apos;&apos; Convert the L10N instance to a readable string, typically for debugging purposes (DebugPrint ...)
  604. &apos;&apos;&apos; Args:
  605. &apos;&apos;&apos; Return:
  606. &apos;&apos;&apos; &quot;[L10N]: PO file&quot;
  607. _Repr = &quot;[L10N]: &quot; &amp; _POFile
  608. End Function &apos; ScriptForge.SF_L10N._Repr
  609. REM ============================================ END OF SCRIPTFORGE.SF_L10N
  610. </script:module>