tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / wizards / source / scriptforge / SF_L10N.xba
blobf621e4567119309c070d899128e03d93b7eb70c3
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 =======================================================================================================================
8 Option Compatible
9 Option ClassModule
10 &apos;Option Private Module
12 Option Explicit
14 &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;
15 &apos;&apos;&apos; L10N (aka SF_L10N)
16 &apos;&apos;&apos; ====
17 &apos;&apos;&apos; Implementation of a Basic class for providing a number of services
18 &apos;&apos;&apos; related to the translation of user interfaces into a huge number of languages
19 &apos;&apos;&apos; with a minimal impact on the program code itself
20 &apos;&apos;&apos;
21 &apos;&apos;&apos; The design choices of this module are based on so-called PO-files
22 &apos;&apos;&apos; PO-files (portable object files) have long been promoted in the free software industry
23 &apos;&apos;&apos; as a mean of providing multilingual UIs. This is accomplished through the use of human-readable
24 &apos;&apos;&apos; text files with a well defined structure that specifies, for any given language,
25 &apos;&apos;&apos; the source language string and the localized string
26 &apos;&apos;&apos;
27 &apos;&apos;&apos; To read more about the PO format and its ecosystem of associated toolsets:
28 &apos;&apos;&apos; https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files
29 &apos;&apos;&apos; and, IMHO, a very good tutorial:
30 &apos;&apos;&apos; http://pology.nedohodnik.net/doc/user/en_US/ch-about.html
31 &apos;&apos;&apos;
32 &apos;&apos;&apos; The main advantage of the PO format is the complete dissociation between the two
33 &apos;&apos;&apos; very different profiles, i.e. the programmer and the translator(s).
34 &apos;&apos;&apos; Being independent text files, one per language to support, the programmer may give away
35 &apos;&apos;&apos; pristine PO template files (known as POT-files) for a translator to process.
36 &apos;&apos;&apos;
37 &apos;&apos;&apos; This class implements mainly 4 mechanisms:
38 &apos;&apos;&apos; 1. AddText: for the programmer to build a set of words or sentences
39 &apos;&apos;&apos; meant for being translated later
40 &apos;&apos;&apos; 2. AddTextsFromDialog: to automatically execute AddText() on each fixed text of a dialog
41 &apos;&apos;&apos; 3. ExportToPOTFile: All the above texts are exported into a pristine POT-file
42 &apos;&apos;&apos; 4. GetText: At runtime get the text in the user language
43 &apos;&apos;&apos; Note that the first two are optional: POT and PO-files may be built with a simple text editor
44 &apos;&apos;&apos;
45 &apos;&apos;&apos; Several instances of the L10N class may coexist
46 &apos; The constraint however is that each instance should find its PO-files
47 &apos;&apos;&apos; in a separate directory
48 &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;
49 &apos;&apos;&apos;
50 &apos;&apos;&apos; Service invocation syntax
51 &apos;&apos;&apos; CreateScriptService(&quot;L10N&quot;[, FolderName[, Locale]])
52 &apos;&apos;&apos; FolderName: the folder containing the PO-files (in SF_FileSystem.FileNaming notation)
53 &apos;&apos;&apos; Locale: in the form la-CO (language-COUNTRY)
54 &apos;&apos;&apos; Encoding: The character set that should be used (default = UTF-8)
55 &apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
56 &apos;&apos;&apos; Locale2: fallback Locale to select if Locale po file does not exist (typically &quot;en-US&quot;)
57 &apos;&apos;&apos; Encoding2: Encoding of the 2nd Locale file
58 &apos;&apos;&apos; Service invocation examples:
59 &apos;&apos;&apos; Dim myPO As Variant
60 &apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;) &apos; AddText, AddTextsFromDialog and ExportToPOTFile are allowed
61 &apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;, &quot;C:\myPOFiles\&quot;, &quot;fr-BE&quot;)
62 &apos;&apos;&apos; &apos;All functionalities are available
63 &apos;&apos;&apos;
64 &apos;&apos;&apos; Detailed user documentation:
65 &apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_l10n.html?DbPAR=BASIC
66 &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;
68 REM =============================================================== PRIVATE TYPES
70 &apos;&apos;&apos; The recognized elements of an entry in a PO file are (other elements are ignored) :
71 &apos;&apos;&apos; #. Extracted comments (given by the programmer to the translator)
72 &apos;&apos;&apos; #, flag (the kde-format flag when the string contains tokens)
73 &apos;&apos;&apos; msgctxt Context (to store an acronym associated with the message, this is a distortion of the norm)
74 &apos;&apos;&apos; msgid untranslated-string
75 &apos;&apos;&apos; msgstr translated-string
76 &apos;&apos;&apos; NB: plural forms are not supported
78 Type POEntry
79 Comment As String
80 Flag As String
81 Context As String
82 MsgId As String
83 MsgStr As String
84 End Type
86 REM ================================================================== EXCEPTIONS
88 Const DUPLICATEKEYERROR = &quot;DUPLICATEKEYERROR&quot;
90 REM ============================================================= PRIVATE MEMBERS
92 Private [Me] As Object
93 Private [_Parent] As Object
94 Private ObjectType As String &apos; Must be &quot;L10N&quot;
95 Private ServiceName As String
96 Private _POFolder As String &apos; PO files container
97 Private _Locale As String &apos; la-CO
98 Private _POFile As String &apos; PO file in URL format
99 Private _Encoding As String &apos; Used to open the PO file, default = UTF-8
100 Private _Dictionary As Object &apos; SF_Dictionary
102 REM ===================================================== CONSTRUCTOR/DESTRUCTOR
104 REM -----------------------------------------------------------------------------
105 Private Sub Class_Initialize()
106 Set [Me] = Nothing
107 Set [_Parent] = Nothing
108 ObjectType = &quot;L10N&quot;
109 ServiceName = &quot;ScriptForge.L10N&quot;
110 _POFolder = &quot;&quot;
111 _Locale = &quot;&quot;
112 _POFile = &quot;&quot;
113 Set _Dictionary = Nothing
114 End Sub &apos; ScriptForge.SF_L10N Constructor
116 REM -----------------------------------------------------------------------------
117 Private Sub Class_Terminate()
119 If Not IsNull(_Dictionary) Then Set _Dictionary = _Dictionary.Dispose()
120 Call Class_Initialize()
121 End Sub &apos; ScriptForge.SF_L10N Destructor
123 REM -----------------------------------------------------------------------------
124 Public Function Dispose() As Variant
125 Call Class_Terminate()
126 Set Dispose = Nothing
127 End Function &apos; ScriptForge.SF_L10N Explicit Destructor
129 REM ================================================================== PROPERTIES
131 REM -----------------------------------------------------------------------------
132 Property Get Folder() As String
133 &apos;&apos;&apos; Returns the FolderName containing the PO-files expressed as given by the current FileNaming
134 &apos;&apos;&apos; property of the SF_FileSystem service. Default = URL format
135 &apos;&apos;&apos; May be empty
136 &apos;&apos;&apos; Example:
137 &apos;&apos;&apos; myPO.Folder
139 Folder = _PropertyGet(&quot;Folder&quot;)
141 End Property &apos; ScriptForge.SF_L10N.Folder
143 REM -----------------------------------------------------------------------------
144 Property Get Languages() As Variant
145 &apos;&apos;&apos; Returns a zero-based array listing all the BaseNames of the PO-files found in Folder,
146 &apos;&apos;&apos; Example:
147 &apos;&apos;&apos; myPO.Languages
149 Languages = _PropertyGet(&quot;Languages&quot;)
151 End Property &apos; ScriptForge.SF_L10N.Languages
153 REM -----------------------------------------------------------------------------
154 Property Get Locale() As String
155 &apos;&apos;&apos; Returns the currently active language-COUNTRY combination. May be empty
156 &apos;&apos;&apos; Example:
157 &apos;&apos;&apos; myPO.Locale
159 Locale = _PropertyGet(&quot;Locale&quot;)
161 End Property &apos; ScriptForge.SF_L10N.Locale
163 REM ===================================================================== METHODS
165 REM -----------------------------------------------------------------------------
166 Public Function AddText(Optional ByVal Context As Variant _
167 , Optional ByVal MsgId As Variant _
168 , Optional ByVal Comment As Variant _
169 , Optional ByVal MsgStr As Variant _
170 ) As Boolean
171 &apos;&apos;&apos; Add a new entry in the list of localizable text strings
172 &apos;&apos;&apos; Args:
173 &apos;&apos;&apos; Context: when not empty, the key to retrieve the translated string via GetText. Default = &quot;&quot;
174 &apos;&apos;&apos; MsgId: the untranslated string, i.e. the text appearing in the program code. Must not be empty
175 &apos;&apos;&apos; The key to retrieve the translated string via GetText when Context is empty
176 &apos;&apos;&apos; May contain placeholders (%1 ... %9) for dynamic arguments to be inserted in the text at run-time
177 &apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
178 &apos;&apos;&apos; Comment: the so-called &quot;extracted-comments&quot; intended to inform/help translators
179 &apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
180 &apos;&apos;&apos; MsgStr: (internal use only) the translated string
181 &apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
182 &apos;&apos;&apos; Returns:
183 &apos;&apos;&apos; True if successful
184 &apos;&apos;&apos; Exceptions:
185 &apos;&apos;&apos; DUPLICATEKEYERROR: such a key exists already
186 &apos;&apos;&apos; Examples:
187 &apos;&apos;&apos; myPO.AddText(, &quot;This is a text to be included in a POT file&quot;)
189 Dim bAdd As Boolean &apos; Output buffer
190 Dim sKey As String &apos; The key part of the new entry in the dictionary
191 Dim vItem As POEntry &apos; The item part of the new entry in the dictionary
192 Const cstPipe = &quot;|&quot; &apos; Pipe forbidden in MsgId&apos;s
193 Const cstThisSub = &quot;L10N.AddText&quot;
194 Const cstSubArgs = &quot;[Context=&quot;&quot;&quot;&quot;], MsgId, [Comment=&quot;&quot;&quot;&quot;]&quot;
196 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
197 bAdd = False
199 Check:
200 If IsMissing(Context) Or IsMissing(Context) Then Context = &quot;&quot;
201 If IsMissing(Comment) Or IsMissing(Comment) Then Comment = &quot;&quot;
202 If IsMissing(MsgStr) Or IsMissing(MsgStr) Then MsgStr = &quot;&quot;
203 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
204 If Not SF_Utils._Validate(Context, &quot;Context&quot;, V_STRING) Then GoTo Finally
205 If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
206 If Not SF_Utils._Validate(Comment, &quot;Comment&quot;, V_STRING) Then GoTo Finally
207 If Not SF_Utils._Validate(MsgStr, &quot;MsgStr&quot;, V_STRING) Then GoTo Finally
208 End If
209 If Len(MsgId) = 0 Then GoTo Finally
211 Try:
212 If Len(Context) &gt; 0 Then sKey = Context Else sKey = MsgId
213 If _Dictionary.Exists(sKey) Then GoTo CatchDuplicate
215 With vItem
216 .Comment = Comment
217 If InStr(MsgId, &quot;%&quot;) &gt; 0 Then .Flag = &quot;kde-format&quot; Else .Flag = &quot;&quot;
218 .Context = Replace(Context, cstPipe, &quot; &quot;)
219 .MsgId = Replace(MsgId, cstPipe, &quot; &quot;)
220 .MsgStr = MsgStr
221 End With
222 _Dictionary.Add(sKey, vItem)
223 bAdd = True
225 Finally:
226 AddText = bAdd
227 SF_Utils._ExitFunction(cstThisSub)
228 Exit Function
229 Catch:
230 GoTo Finally
231 CatchDuplicate:
232 SF_Exception.RaiseFatal(DUPLICATEKEYERROR, Iif(Len(Context) &gt; 0, &quot;Context&quot;, &quot;MsgId&quot;), sKey)
233 GoTo Finally
234 End Function &apos; ScriptForge.SF_L10N.AddText
236 REM -----------------------------------------------------------------------------
237 Public Function AddTextsFromDialog(Optional ByRef Dialog As Variant) As Boolean
238 &apos;&apos;&apos; Add all fixed text strings of a dialog to the list of localizable text strings
239 &apos;&apos;&apos; Added texts are:
240 &apos;&apos;&apos; - the title of the dialog
241 &apos;&apos;&apos; - the caption associated with next control types: Button, CheckBox, FixedLine, FixedText, GroupBox and RadioButton
242 &apos;&apos;&apos; - the content of list- and comboboxes
243 &apos;&apos;&apos; - the tip- or helptext displayed when the mouse is hovering the control
244 &apos;&apos;&apos; The current method has method SFDialogs.SF_Dialog.GetTextsFromL10N as counterpart
245 &apos;&apos;&apos; The targeted dialog must not be open when the current method is run
246 &apos;&apos;&apos; Args:
247 &apos;&apos;&apos; Dialog: a SFDialogs.Dialog service instance
248 &apos;&apos;&apos; Returns:
249 &apos;&apos;&apos; True when successful
250 &apos;&apos;&apos; Examples:
251 &apos;&apos;&apos; Dim myDialog As Object
252 &apos;&apos;&apos; Set myDialog = CreateScriptService(&quot;SFDialogs.Dialog&quot;, &quot;GlobalScope&quot;, &quot;XrayTool&quot;, &quot;DlgXray&quot;)
253 &apos;&apos;&apos; myPO.AddTextsFromDialog(myDialog)
255 Dim bAdd As Boolean &apos; Return value
256 Dim vControls As Variant &apos; Array of control names
257 Dim sControl As String &apos; A single control name
258 Dim oControl As Object &apos; SFDialogs.DialogControl
259 Dim sText As String &apos; The text to insert in the dictionary
260 Dim sDialogComment As String &apos; The prefix in the comment to insert in the dictionary for the dialog
261 Dim sControlComment As String &apos; The prefix in the comment to insert in the dictionary for a control
262 Dim vSource As Variant &apos; RowSource property of dialog control as an array
263 Dim i As Long
265 Const cstThisSub = &quot;L10N.AddTextsFromDialog&quot;
266 Const cstSubArgs = &quot;Dialog&quot;
268 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
269 bAdd = False
271 Check:
272 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
273 If Not SF_Utils._Validate(Dialog, &quot;Dialog&quot;, V_OBJECT, , , &quot;DIALOG&quot;) Then GoTo Finally
274 End If
276 Try:
277 With Dialog
278 &apos; Store the title of the dialog
279 sDialogComment = &quot;Dialog =&gt; &quot; &amp; ._Container &amp; &quot; : &quot; &amp; ._Library &amp; &quot; : &quot; &amp; ._Name &amp; &quot; : &quot;
280 stext = .Caption
281 If Len(sText) &gt; 0 Then
282 If Not _ReplaceText(&quot;&quot;, sText, sDialogComment &amp; &quot;Caption&quot;) Then GoTo Catch
283 End If
284 &apos; Scan all controls
285 vControls = .Controls()
286 For Each sControl In vControls
287 Set oControl = .Controls(sControl)
288 sControlComment = sDialogComment &amp; sControl &amp; &quot;.&quot;
289 With oControl
290 &apos; Extract fixed texts
291 sText = .Caption
292 If Len(sText) &gt; 0 Then
293 If Not _ReplaceText(&quot;&quot;, sText, sControlComment &amp; &quot;Caption&quot;) Then GoTo Catch
294 End If
295 vSource = .RowSource &apos; List and comboboxes only
296 If IsArray(vSource) Then
297 For i = 0 To UBound(vSource)
298 If Len(vSource(i)) &gt; 0 Then
299 If Not _ReplaceText(&quot;&quot;, vSource(i), sControlComment &amp; &quot;RowSource[&quot; &amp; i &amp; &quot;]&quot;) Then GoTo Catch
300 End If
301 Next i
302 End If
303 sText = .TipText
304 If Len(sText) &gt; 0 Then
305 If Not _ReplaceText(&quot;&quot;, sText, sControlComment &amp; &quot;TipText&quot;) Then GoTo Catch
306 End If
307 End With
308 Next sControl
309 End With
311 bAdd = True
313 Finally:
314 AddTextsFromDialog = bAdd
315 SF_Utils._ExitFunction(cstThisSub)
316 Exit Function
317 Catch:
318 GoTo Finally
319 End Function &apos; ScriptForge.SF_L10N.AddTextsFromDialog
321 REM -----------------------------------------------------------------------------
322 Public Function ExportToPOTFile(Optional ByVal FileName As Variant _
323 , Optional ByVal Header As Variant _
324 , Optional ByVal Encoding As Variant _
325 ) As Boolean
326 &apos;&apos;&apos; Export a set of untranslated strings as a POT file
327 &apos;&apos;&apos; The set of strings has been built either by a succession of AddText() methods
328 &apos;&apos;&apos; or by a successful invocation of the L10N service with the FolderName argument
329 &apos;&apos;&apos; The generated file should pass successfully the &quot;msgfmt --check &apos;the pofile&apos;&quot; GNU command
330 &apos;&apos;&apos; Args:
331 &apos;&apos;&apos; FileName: the complete file name to export to. If it exists, is overwritten without warning
332 &apos;&apos;&apos; Header: Comments that will appear on top of the generated file. Do not include any leading &quot;#&quot;
333 &apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
334 &apos;&apos;&apos; A standard header will be added anyway
335 &apos;&apos;&apos; Encoding: The character set that should be used
336 &apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
337 &apos;&apos;&apos; Note that LibreOffice probably does not implement all existing sets
338 &apos;&apos;&apos; Default = UTF-8
339 &apos;&apos;&apos; Returns:
340 &apos;&apos;&apos; True if successful
341 &apos;&apos;&apos; Examples:
342 &apos;&apos;&apos; myPO.ExportToPOTFile(&quot;myFile.pot&quot;, Header := &quot;Top comment\nSecond line of top comment&quot;)
344 Dim bExport As Boolean &apos; Return value
345 Dim oFile As Object &apos; Generated file handler
346 Dim vLines As Variant &apos; Wrapped lines
347 Dim sLine As String &apos; A single line
348 Dim vItems As Variant &apos; Array of dictionary items
349 Dim vItem As Variant &apos; POEntry type
350 Const cstSharp = &quot;# &quot;, cstSharpDot = &quot;#. &quot;, cstFlag = &quot;#, kde-format&quot;
351 Const cstTabSize = 4
352 Const cstWrap = 70
353 Const cstThisSub = &quot;L10N.ExportToPOTFile&quot;
354 Const cstSubArgs = &quot;FileName, [Header=&quot;&quot;&quot;&quot;], [Encoding=&quot;&quot;UTF-8&quot;&quot;&quot;
356 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
357 bExport = False
359 Check:
360 If IsMissing(Header) Or IsEmpty(Header) Then Header = &quot;&quot;
361 If IsMissing(Encoding) Or IsEmpty(Encoding) Then Encoding = &quot;UTF-8&quot;
362 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
363 If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
364 If Not SF_Utils._Validate(Header, &quot;Header&quot;, V_STRING) Then GoTo Finally
365 If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
366 End If
368 Try:
369 Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding)
370 If Not IsNull(oFile) Then
371 With oFile
372 &apos; Standard header
373 .WriteLine(cstSharp)
374 .WriteLine(cstSharp &amp; &quot;This pristine POT file has been generated by LibreOffice/ScriptForge&quot;)
375 .WriteLine(cstSharp &amp; &quot;Full documentation is available on https://help.libreoffice.org/&quot;)
376 &apos; User header
377 If Len(Header) &gt; 0 Then
378 .WriteLine(cstSharp)
379 vLines = SF_String.Wrap(Header, cstWrap, cstTabSize)
380 For Each sLine In vLines
381 .WriteLine(cstSharp &amp; Replace(sLine, SF_String.sfLF, &quot;&quot;))
382 Next sLine
383 End If
384 &apos; Standard header
385 .WriteLine(cstSharp)
386 .WriteLine(&quot;msgid &quot;&quot;&quot;&quot;&quot;)
387 .WriteLine(&quot;msgstr &quot;&quot;&quot;&quot;&quot;)
388 .WriteLine(SF_String.Quote(&quot;Project-Id-Version: PACKAGE VERSION\n&quot;))
389 .WriteLine(SF_String.Quote(&quot;Report-Msgid-Bugs-To: &quot; _
390 &amp; &quot;https://bugs.libreoffice.org/enter_bug.cgi?product=LibreOffice&amp;bug_status=UNCONFIRMED&amp;component=UI\n&quot;))
391 .WriteLine(SF_String.Quote(&quot;POT-Creation-Date: &quot; &amp; SF_STring.Represent(Now()) &amp; &quot;\n&quot;))
392 .WriteLine(SF_String.Quote(&quot;PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n&quot;))
393 .WriteLine(SF_String.Quote(&quot;Last-Translator: FULL NAME &lt;EMAIL@ADDRESS&gt;\n&quot;))
394 .WriteLine(SF_String.Quote(&quot;Language-Team: LANGUAGE &lt;EMAIL@ADDRESS&gt;\n&quot;))
395 .WriteLine(SF_String.Quote(&quot;Language: en_US\n&quot;))
396 .WriteLine(SF_String.Quote(&quot;MIME-Version: 1.0\n&quot;))
397 .WriteLine(SF_String.Quote(&quot;Content-Type: text/plain; charset=&quot; &amp; Encoding &amp; &quot;\n&quot;))
398 .WriteLine(SF_String.Quote(&quot;Content-Transfer-Encoding: 8bit\n&quot;))
399 .WriteLine(SF_String.Quote(&quot;Plural-Forms: nplurals=2; plural=n &gt; 1;\n&quot;))
400 .WriteLine(SF_String.Quote(&quot;X-Generator: LibreOffice - ScriptForge\n&quot;))
401 .WriteLine(SF_String.Quote(&quot;X-Accelerator-Marker: ~\n&quot;))
402 &apos; Individual translatable strings
403 vItems = _Dictionary.Items()
404 For Each vItem in vItems
405 .WriteBlankLines(1)
406 &apos; Comments
407 vLines = Split(vItem.Comment, &quot;\n&quot;)
408 For Each sLine In vLines
409 .WriteLine(cstSharpDot &amp; SF_String.ExpandTabs(SF_String.Unescape(sLine), cstTabSize))
410 Next sLine
411 &apos; Flag
412 If InStr(vItem.MsgId, &quot;%&quot;) &gt; 0 Then .WriteLine(cstFlag)
413 &apos; Context
414 If Len(vItem.Context) &gt; 0 Then
415 .WriteLine(&quot;msgctxt &quot; &amp; SF_String.Quote(vItem.Context))
416 End If
417 &apos; MsgId
418 vLines = SF_String.Wrap(vItem.MsgId, cstWrap, cstTabSize)
419 If UBound(vLines) = 0 Then
420 .WriteLine(&quot;msgid &quot; &amp; SF_String.Quote(SF_String.Escape(vLines(0))))
421 Else
422 .WriteLine(&quot;msgid &quot;&quot;&quot;&quot;&quot;)
423 For Each sLine in vLines
424 .WriteLine(SF_String.Quote(SF_String.Escape(sLine)))
425 Next sLine
426 End If
427 &apos; MsgStr
428 .WriteLine(&quot;msgstr &quot;&quot;&quot;&quot;&quot;)
429 Next vItem
430 .CloseFile()
431 End With
432 End If
433 bExport = True
435 Finally:
436 If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
437 ExportToPOTFile = bExport
438 SF_Utils._ExitFunction(cstThisSub)
439 Exit Function
440 Catch:
441 GoTo Finally
442 End Function &apos; ScriptForge.SF_L10N.ExportToPOTFile
444 REM -----------------------------------------------------------------------------
445 Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
446 &apos;&apos;&apos; Return the actual value of the given property
447 &apos;&apos;&apos; Args:
448 &apos;&apos;&apos; PropertyName: the name of the property as a string
449 &apos;&apos;&apos; Returns:
450 &apos;&apos;&apos; The actual value of the property
451 &apos;&apos;&apos; If the property does not exist, returns Null
452 &apos;&apos;&apos; Exceptions:
453 &apos;&apos;&apos; ARGUMENTERROR The property does not exist
454 &apos;&apos;&apos; Examples:
455 &apos;&apos;&apos; myL10N.GetProperty(&quot;MyProperty&quot;)
457 Const cstThisSub = &quot;L10N.GetProperty&quot;
458 Const cstSubArgs = &quot;&quot;
460 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
461 GetProperty = Null
463 Check:
464 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
465 If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
466 End If
468 Try:
469 GetProperty = _PropertyGet(PropertyName)
471 Finally:
472 SF_Utils._ExitFunction(cstThisSub)
473 Exit Function
474 Catch:
475 GoTo Finally
476 End Function &apos; ScriptForge.SF_L10N.GetProperty
478 REM -----------------------------------------------------------------------------
479 Public Function GetText(Optional ByVal MsgId As Variant _
480 , ParamArray pvArgs As Variant _
481 ) As String
482 &apos;&apos;&apos; Get the translated string corresponding with the given argument
483 &apos;&apos;&apos; Args:
484 &apos;&apos;&apos; MsgId: the identifier of the string or the untranslated string
485 &apos;&apos;&apos; Either - the untranslated text (MsgId)
486 &apos;&apos;&apos; - the reference to the untranslated text (Context)
487 &apos;&apos;&apos; - both (Context|MsgId) : the pipe character is essential
488 &apos;&apos;&apos; pvArgs(): a list of arguments present as %1, %2, ... in the (un)translated string)
489 &apos;&apos;&apos; to be substituted in the returned string
490 &apos;&apos;&apos; Any type is admitted but only strings, numbers or dates are relevant
491 &apos;&apos;&apos; Returns:
492 &apos;&apos;&apos; The translated string
493 &apos;&apos;&apos; If not found the MsgId string or the Context string
494 &apos;&apos;&apos; Anyway the substitution is done
495 &apos;&apos;&apos; Examples:
496 &apos;&apos;&apos; myPO.GetText(&quot;This is a text to be included in a POT file&quot;)
497 &apos;&apos;&apos; &apos; Ceci est un text à inclure dans un fichier POT
499 Dim sText As String &apos; Output buffer
500 Dim sContext As String &apos; Context part of argument
501 Dim sMsgId As String &apos; MsgId part of argument
502 Dim vItem As POEntry &apos; Entry in the dictionary
503 Dim vMsgId As Variant &apos; MsgId split on pipe
504 Dim sKey As String &apos; Key of dictionary
505 Dim sPercent As String &apos; %1, %2, ... placeholders
506 Dim i As Long
507 Const cstPipe = &quot;|&quot;
508 Const cstThisSub = &quot;L10N.GetText&quot;
509 Const cstSubArgs = &quot;MsgId, [Arg0, Arg1, ...]&quot;
511 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
512 sText = &quot;&quot;
514 Check:
515 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
516 If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
517 End If
518 If Len(Trim(MsgId)) = 0 Then GoTo Finally
519 sText = MsgId
521 Try:
522 &apos; Find and load entry from dictionary
523 If Left(MsgId, 1) = cstPipe then MsgId = Mid(MsgId, 2)
524 vMsgId = Split(MsgId, cstPipe)
525 sKey = vMsgId(0)
526 If Not _Dictionary.Exists(sKey) Then &apos; Not found
527 If UBound(vMsgId) = 0 Then sText = vMsgId(0) Else sText = Mid(MsgId, InStr(MsgId, cstPipe) + 1)
528 Else
529 vItem = _Dictionary.Item(sKey)
530 If Len(vItem.MsgStr) &gt; 0 Then sText = vItem.MsgStr Else sText = vItem.MsgId
531 End If
533 &apos; Substitute %i placeholders
534 For i = UBound(pvArgs) To 0 Step -1 &apos; Go downwards to not have a limit in number of args
535 sPercent = &quot;%&quot; &amp; (i + 1)
536 sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i)))
537 Next i
539 Finally:
540 GetText = sText
541 SF_Utils._ExitFunction(cstThisSub)
542 Exit Function
543 Catch:
544 GoTo Finally
545 End Function &apos; ScriptForge.SF_L10N.GetText
547 REM - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
548 Public Function _(Optional ByVal MsgId As Variant _
549 , ParamArray pvArgs As Variant _
550 ) As String
551 &apos;&apos;&apos; Get the translated string corresponding with the given argument
552 &apos;&apos;&apos; Alias of GetText() - See above
553 &apos;&apos;&apos; Examples:
554 &apos;&apos;&apos; myPO._(&quot;This is a text to be included in a POT file&quot;)
555 &apos;&apos;&apos; &apos; Ceci est un text à inclure dans un fichier POT
557 Dim sText As String &apos; Output buffer
558 Dim sPercent As String &apos; %1, %2, ... placeholders
559 Dim i As Long
560 Const cstPipe = &quot;|&quot;
561 Const cstThisSub = &quot;L10N._&quot;
562 Const cstSubArgs = &quot;MsgId, [Arg0, Arg1, ...]&quot;
564 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
565 sText = &quot;&quot;
567 Check:
568 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
569 If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
570 End If
571 If Len(Trim(MsgId)) = 0 Then GoTo Finally
573 Try:
574 &apos; Find and load entry from dictionary
575 sText = GetText(MsgId)
577 &apos; Substitute %i placeholders - done here, not in GetText(), because # of arguments is undefined
578 For i = 0 To UBound(pvArgs)
579 sPercent = &quot;%&quot; &amp; (i + 1)
580 sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i)))
581 Next i
583 Finally:
584 _ = sText
585 SF_Utils._ExitFunction(cstThisSub)
586 Exit Function
587 Catch:
588 GoTo Finally
589 End Function &apos; ScriptForge.SF_L10N._
591 REM -----------------------------------------------------------------------------
592 Public Function Methods() As Variant
593 &apos;&apos;&apos; Return the list of public methods of the L10N service as an array
595 Methods = Array( _
596 &quot;AddText&quot; _
597 , &quot;ExportToPOTFile&quot; _
598 , &quot;GetText&quot; _
599 , &quot;AddTextsFromDialog&quot; _
600 , &quot;_&quot; _
603 End Function &apos; ScriptForge.SF_L10N.Methods
605 REM -----------------------------------------------------------------------------
606 Public Function Properties() As Variant
607 &apos;&apos;&apos; Return the list or properties of the Timer class as an array
609 Properties = Array( _
610 &quot;Folder&quot; _
611 , &quot;Languages&quot; _
612 , &quot;Locale&quot; _
615 End Function &apos; ScriptForge.SF_L10N.Properties
617 REM -----------------------------------------------------------------------------
618 Public Function SetProperty(Optional ByVal PropertyName As Variant _
619 , Optional ByRef Value As Variant _
620 ) As Boolean
621 &apos;&apos;&apos; Set a new value to the given property
622 &apos;&apos;&apos; Args:
623 &apos;&apos;&apos; PropertyName: the name of the property as a string
624 &apos;&apos;&apos; Value: its new value
625 &apos;&apos;&apos; Exceptions
626 &apos;&apos;&apos; ARGUMENTERROR The property does not exist
628 Const cstThisSub = &quot;L10N.SetProperty&quot;
629 Const cstSubArgs = &quot;PropertyName, Value&quot;
631 If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
632 SetProperty = False
634 Check:
635 If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
636 If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
637 End If
639 Try:
640 Select Case UCase(PropertyName)
641 Case Else
642 End Select
644 Finally:
645 SF_Utils._ExitFunction(cstThisSub)
646 Exit Function
647 Catch:
648 GoTo Finally
649 End Function &apos; ScriptForge.SF_L10N.SetProperty
651 REM =========================================================== PRIVATE FUNCTIONS
653 REM -----------------------------------------------------------------------------
654 Public Sub _Initialize(ByVal psPOFile As String _
655 , ByVal Encoding As String _
657 &apos;&apos;&apos; Completes initialization of the current instance requested from CreateScriptService()
658 &apos;&apos;&apos; Load the POFile in the dictionary, otherwise leave the dictionary empty
659 &apos;&apos;&apos; Args:
660 &apos;&apos;&apos; psPOFile: the file to load the translated strings from
661 &apos;&apos;&apos; Encoding: The character set that should be used. Default = UTF-8
663 Dim oFile As Object &apos; PO file handler
664 Dim sContext As String &apos; Collected context string
665 Dim sMsgId As String &apos; Collected untranslated string
666 Dim sComment As String &apos; Collected comment string
667 Dim sMsgStr As String &apos; Collected translated string
668 Dim sLine As String &apos; Last line read
669 Dim iContinue As Integer &apos; 0 = None, 1 = MsgId, 2 = MsgStr
670 Const cstMsgId = 1, cstMsgStr = 2
672 Try:
673 &apos; Initialize dictionary anyway with case-sensitive comparison of keys
674 Set _Dictionary = SF_Services.CreateScriptService(&quot;Dictionary&quot;, True)
675 Set _Dictionary.[_Parent] = [Me]
677 &apos; Load PO file
678 If Len(psPOFile) &gt; 0 Then
679 With SF_FileSystem
680 _POFolder = ._ConvertToUrl(.GetParentFolderName(psPOFile))
681 _Locale = .GetBaseName(psPOFile)
682 _POFile = ._ConvertToUrl(psPOFile)
683 End With
684 &apos; Load PO file
685 Set oFile = SF_FileSystem.OpenTextFile(psPOFile, IOMode := SF_FileSystem.ForReading, Encoding := Encoding)
686 If Not IsNull(oFile) Then
687 With oFile
688 &apos; The PO file is presumed valid =&gt; syntax check is not very strict
689 sContext = &quot;&quot; : sMsgId = &quot;&quot; : sComment = &quot;&quot; : sMsgStr = &quot;&quot;
690 Do While Not .AtEndOfStream
691 sLine = Trim(.ReadLine())
692 &apos; Trivial examination of line header
693 Select Case True
694 Case sLine = &quot;&quot;
695 If Len(sMsgId) &gt; 0 Then AddText(sContext, sMsgId, sComment, sMsgStr)
696 sContext = &quot;&quot; : sMsgId = &quot;&quot; : sComment = &quot;&quot; : sMsgStr = &quot;&quot;
697 iContinue = 0
698 Case Left(sLine, 3) = &quot;#. &quot;
699 sComment = sComment &amp; Iif(Len(sComment) &gt; 0, &quot;\n&quot;, &quot;&quot;) &amp; Trim(Mid(sLine, 4))
700 iContinue = 0
701 Case Left(sLine, 8) = &quot;msgctxt &quot;
702 sContext = SF_String.Unquote(Trim(Mid(sLine, 9)))
703 iContinue = 0
704 Case Left(sLine, 6) = &quot;msgid &quot;
705 sMsgId = SF_String.Unquote(Trim(Mid(sLine, 7)))
706 iContinue = cstMsgId
707 Case Left(sLine, 7) = &quot;msgstr &quot;
708 sMsgStr = sMsgStr &amp; SF_String.Unquote(Trim(Mid(sLine, 8)))
709 iContinue = cstMsgStr
710 Case Left(sLine, 1) = &quot;&quot;&quot;&quot;
711 If iContinue = cstMsgId Then
712 sMsgId = sMsgId &amp; SF_String.Unquote(sLine)
713 ElseIf iContinue = cstMsgStr Then
714 sMsgStr = sMsgStr &amp; SF_String.Unquote(sLine)
715 Else
716 iContinue = 0
717 End If
718 Case Else &apos; Skip line
719 iContinue = 0
720 End Select
721 Loop
722 &apos; Be sure to store the last entry
723 If Len(sMsgId) &gt; 0 Then AddText(sContext, sMsgId, sComment, sMsgStr)
724 .CloseFile()
725 Set oFile = .Dispose()
726 End With
727 End If
728 Else
729 _POFolder = &quot;&quot;
730 _Locale = &quot;&quot;
731 _POFile = &quot;&quot;
732 End If
734 Finally:
735 Exit Sub
736 End Sub &apos; ScriptForge.SF_L10N._Initialize
738 REM -----------------------------------------------------------------------------
739 Private Function _PropertyGet(Optional ByVal psProperty As String)
740 &apos;&apos;&apos; Return the value of the named property
741 &apos;&apos;&apos; Args:
742 &apos;&apos;&apos; psProperty: the name of the property
744 Dim vFiles As Variant &apos; Array of PO-files
745 Dim i As Long
746 Dim cstThisSub As String
747 Dim cstSubArgs As String
749 cstThisSub = &quot;SF_L10N.get&quot; &amp; psProperty
750 cstSubArgs = &quot;&quot;
751 SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
753 With SF_FileSystem
754 Select Case psProperty
755 Case &quot;Folder&quot;
756 If Len(_POFolder) &gt; 0 Then _PropertyGet = ._ConvertFromUrl(_POFolder) Else _PropertyGet = &quot;&quot;
757 Case &quot;Languages&quot;
758 If Len(_POFolder) &gt; 0 Then
759 vFiles = .Files(._ConvertFromUrl(_POFolder), &quot;*.po&quot;)
760 For i = 0 To UBound(vFiles)
761 vFiles(i) = SF_FileSystem.GetBaseName(vFiles(i))
762 Next i
763 Else
764 vFiles = Array()
765 End If
766 _PropertyGet = vFiles
767 Case &quot;Locale&quot;
768 _PropertyGet = _Locale
769 Case Else
770 _PropertyGet = Null
771 End Select
772 End With
774 Finally:
775 SF_Utils._ExitFunction(cstThisSub)
776 Exit Function
777 End Function &apos; ScriptForge.SF_L10N._PropertyGet
779 REM -----------------------------------------------------------------------------
780 Private Function _ReplaceText(ByVal psContext As String _
781 , ByVal psMsgId As String _
782 , ByVal psComment As String _
783 ) As Boolean
784 &apos;&apos;&apos; When the entry in the dictionary does not yet exist, equivalent to AddText
785 &apos;&apos;&apos; When it exists already, extend the existing comment with the psComment argument
786 &apos;&apos;&apos; Used from AddTextsFromDialog to manage identical strings without raising errors,
787 &apos;&apos;&apos; e.g. when multiple dialogs have the same &quot;Close&quot; button
789 Dim bAdd As Boolean &apos; Return value
790 Dim sKey As String &apos; The key part of an entry in the dictionary
791 Dim vItem As POEntry &apos; The item part of the new entry in the dictionary
793 Try:
794 bAdd = False
795 If Len(psContext) &gt; 0 Then sKey = psContext Else sKey = psMsgId
796 If _Dictionary.Exists(sKey) Then
797 &apos; Load the entry, adapt comment and rewrite
798 vItem = _Dictionary.Item(sKey)
799 If Len(vItem.Comment) = 0 Then vItem.Comment = psComment Else vItem.Comment = vItem.Comment &amp; &quot;\n&quot; &amp; psComment
800 bAdd = _Dictionary.ReplaceItem(sKey, vItem)
801 Else
802 &apos; Add a new entry as usual
803 bAdd = AddText(psContext, psMsgId, psComment)
804 End If
806 Finally:
807 _ReplaceText = bAdd
808 Exit Function
809 Catch:
810 GoTo Finally
811 End Function &apos; ScriptForge.SF_L10N._ReplaceText
813 REM -----------------------------------------------------------------------------
814 Private Function _Repr() As String
815 &apos;&apos;&apos; Convert the L10N instance to a readable string, typically for debugging purposes (DebugPrint ...)
816 &apos;&apos;&apos; Args:
817 &apos;&apos;&apos; Return:
818 &apos;&apos;&apos; &quot;[L10N]: PO file&quot;
820 _Repr = &quot;[L10N]: &quot; &amp; _POFile
822 End Function &apos; ScriptForge.SF_L10N._Repr
824 REM ============================================ END OF SCRIPTFORGE.SF_L10N
825 </script:module>