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