file-online-preview/server/libreoffice/share/basic/ScriptForge/SF_L10N.xba

697 lines
30 KiB
Java

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_L10N" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. ===
REM === Full documentation is available on https://help.libreoffice.org/ ===
REM =======================================================================================================================
Option Compatible
Option ClassModule
&apos;Option Private Module
Option Explicit
&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;
&apos;&apos;&apos; L10N (aka SF_L10N)
&apos;&apos;&apos; ====
&apos;&apos;&apos; Implementation of a Basic class for providing a number of services
&apos;&apos;&apos; related to the translation of user interfaces into a huge number of languages
&apos;&apos;&apos; with a minimal impact on the program code itself
&apos;&apos;&apos;
&apos;&apos;&apos; The design choices of this module are based on so-called PO-files
&apos;&apos;&apos; PO-files (portable object files) have long been promoted in the free software industry
&apos;&apos;&apos; as a mean of providing multilingual UIs. This is accomplished through the use of human-readable
&apos;&apos;&apos; text files with a well defined structure that specifies, for any given language,
&apos;&apos;&apos; the source language string and the localized string
&apos;&apos;&apos;
&apos;&apos;&apos; To read more about the PO format and its ecosystem of associated toolsets:
&apos;&apos;&apos; https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html#PO-Files
&apos;&apos;&apos; and, IMHO, a very good tutorial:
&apos;&apos;&apos; http://pology.nedohodnik.net/doc/user/en_US/ch-about.html
&apos;&apos;&apos;
&apos;&apos;&apos; The main advantage of the PO format is the complete dissociation between the two
&apos;&apos;&apos; very different profiles, i.e. the programmer and the translator(s).
&apos;&apos;&apos; Being independent text files, one per language to support, the programmer may give away
&apos;&apos;&apos; pristine PO template files (known as POT-files) for a translator to process.
&apos;&apos;&apos;
&apos;&apos;&apos; This class implements mainly 3 mechanisms:
&apos;&apos;&apos; - AddText: for the programmer to build a set of words or sentences
&apos;&apos;&apos; meant for being translated later
&apos;&apos;&apos; - ExportToPOTFile: All the above texts are exported into a pristine POT-file
&apos;&apos;&apos; - GetText: At runtime get the text in the user language
&apos;&apos;&apos; Note that the first two are optional: POT and PO-files may be built with a simple text editor
&apos;&apos;&apos;
&apos;&apos;&apos; Several instances of the L10N class may coexist
&apos; The constraint however is that each instance should find its PO-files
&apos;&apos;&apos; in a separate directory
&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;
&apos;&apos;&apos;
&apos;&apos;&apos; Service invocation syntax
&apos;&apos;&apos; CreateScriptService(&quot;L10N&quot;[, FolderName[, Locale]])
&apos;&apos;&apos; FolderName: the folder containing the PO-files (in SF_FileSystem.FileNaming notation)
&apos;&apos;&apos; Locale: in the form la-CO (language-COUNTRY)
&apos;&apos;&apos; Service invocation examples:
&apos;&apos;&apos; Dim myPO As Variant
&apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;) &apos; AddText and ExportToPOTFile are allowed
&apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;, &quot;C:\myPOFiles\&quot;, &quot;fr-BE&quot;)
&apos;&apos;&apos; &apos;All functionalities are available
&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;
REM =============================================================== PRIVATE TYPES
&apos;&apos;&apos; The recognized elements of an entry in a PO file are (other elements are ignored) :
&apos;&apos;&apos; #. Extracted comments (given by the programmer to the translator)
&apos;&apos;&apos; #, flag (the kde-format flag when the string contains tokens)
&apos;&apos;&apos; msgctxt Context (to store an acronym associated with the message, this is a distortion of the norm)
&apos;&apos;&apos; msgid untranslated-string
&apos;&apos;&apos; msgstr translated-string
&apos;&apos;&apos; NB: plural forms are not supported
Type POEntry
Comment As String
Flag As String
Context As String
MsgId As String
MsgStr As String
End Type
REM ================================================================== EXCEPTIONS
Const DUPLICATEKEYERROR = &quot;DUPLICATEKEYERROR&quot;
REM ============================================================= PRIVATE MEMBERS
Private [Me] As Object
Private [_Parent] As Object
Private ObjectType As String &apos; Must be &quot;L10N&quot;
Private ServiceName As String
Private _POFolder As String &apos; PO files container
Private _Locale As String &apos; la-CO
Private _POFile As String &apos; PO file in URL format
Private _Encoding As String &apos; Used to open the PO file, default = UTF-8
Private _Dictionary As Object &apos; SF_Dictionary
REM ===================================================== CONSTRUCTOR/DESTRUCTOR
REM -----------------------------------------------------------------------------
Private Sub Class_Initialize()
Set [Me] = Nothing
Set [_Parent] = Nothing
ObjectType = &quot;L10N&quot;
ServiceName = &quot;ScriptForge.L10N&quot;
_POFolder = &quot;&quot;
_Locale = &quot;&quot;
_POFile = &quot;&quot;
Set _Dictionary = Nothing
End Sub &apos; ScriptForge.SF_L10N Constructor
REM -----------------------------------------------------------------------------
Private Sub Class_Terminate()
If Not IsNull(_Dictionary) Then Set _Dictionary = _Dictionary.Dispose()
Call Class_Initialize()
End Sub &apos; ScriptForge.SF_L10N Destructor
REM -----------------------------------------------------------------------------
Public Function Dispose() As Variant
Call Class_Terminate()
Set Dispose = Nothing
End Function &apos; ScriptForge.SF_L10N Explicit Destructor
REM ================================================================== PROPERTIES
REM -----------------------------------------------------------------------------
Property Get Folder() As String
&apos;&apos;&apos; Returns the FolderName containing the PO-files expressed as given by the current FileNaming
&apos;&apos;&apos; property of the SF_FileSystem service. Default = URL format
&apos;&apos;&apos; May be empty
&apos;&apos;&apos; Example:
&apos;&apos;&apos; myPO.Folder
Folder = _PropertyGet(&quot;Folder&quot;)
End Property &apos; ScriptForge.SF_L10N.Folder
REM -----------------------------------------------------------------------------
Property Get Languages() As Variant
&apos;&apos;&apos; Returns a zero-based array listing all the BaseNames of the PO-files found in Folder,
&apos;&apos;&apos; Example:
&apos;&apos;&apos; myPO.Languages
Languages = _PropertyGet(&quot;Languages&quot;)
End Property &apos; ScriptForge.SF_L10N.Languages
REM -----------------------------------------------------------------------------
Property Get Locale() As String
&apos;&apos;&apos; Returns the currently active language-COUNTRY combination. May be empty
&apos;&apos;&apos; Example:
&apos;&apos;&apos; myPO.Locale
Locale = _PropertyGet(&quot;Locale&quot;)
End Property &apos; ScriptForge.SF_L10N.Locale
REM ===================================================================== METHODS
REM -----------------------------------------------------------------------------
Public Function AddText(Optional ByVal Context As Variant _
, Optional ByVal MsgId As Variant _
, Optional ByVal Comment As Variant _
, Optional ByVal MsgStr As Variant _
) As Boolean
&apos;&apos;&apos; Add a new entry in the list of localizable text strings
&apos;&apos;&apos; Args:
&apos;&apos;&apos; Context: when not empty, the key to retrieve the translated string via GetText. Default = &quot;&quot;
&apos;&apos;&apos; MsgId: the untranslated string, i.e. the text appearing in the program code. Must not be empty
&apos;&apos;&apos; The key to retrieve the translated string via GetText when Context is empty
&apos;&apos;&apos; May contain placeholders (%1 ... %9) for dynamic arguments to be inserted in the text at run-time
&apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
&apos;&apos;&apos; Comment: the so-called &quot;extracted-comments&quot; intended to inform/help translators
&apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
&apos;&apos;&apos; MsgStr: (internal use only) the translated string
&apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; True if successful
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; DUPLICATEKEYERROR: such a key exists already
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; myPO.AddText(, &quot;This is a text to be included in a POT file&quot;)
Dim bAdd As Boolean &apos; Output buffer
Dim sKey As String &apos; The key part of the new entry in the dictionary
Dim vItem As POEntry &apos; The item part of the new entry in the dictionary
Const cstPipe = &quot;|&quot; &apos; Pipe forbidden in MsgId&apos;s
Const cstThisSub = &quot;L10N.AddText&quot;
Const cstSubArgs = &quot;[Context=&quot;&quot;&quot;&quot;], MsgId, [Comment=&quot;&quot;&quot;&quot;]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
bAdd = False
Check:
If IsMissing(Context) Or IsMissing(Context) Then Context = &quot;&quot;
If IsMissing(Comment) Or IsMissing(Comment) Then Comment = &quot;&quot;
If IsMissing(MsgStr) Or IsMissing(MsgStr) Then MsgStr = &quot;&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._Validate(Context, &quot;Context&quot;, V_STRING) Then GoTo Finally
If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
If Not SF_Utils._Validate(Comment, &quot;Comment&quot;, V_STRING) Then GoTo Finally
If Not SF_Utils._Validate(MsgStr, &quot;MsgStr&quot;, V_STRING) Then GoTo Finally
End If
If Len(MsgId) = 0 Then GoTo Finally
Try:
If Len(Context) &gt; 0 Then sKey = Context Else sKey = MsgId
If _Dictionary.Exists(sKey) Then GoTo CatchDuplicate
With vItem
.Comment = Comment
If InStr(MsgId, &quot;%&quot;) &gt; 0 Then .Flag = &quot;kde-format&quot; Else .Flag = &quot;&quot;
.Context = Replace(Context, cstPipe, &quot; &quot;)
.MsgId = Replace(MsgId, cstPipe, &quot; &quot;)
.MsgStr = MsgStr
End With
_Dictionary.Add(sKey, vItem)
Finally:
AddText = bAdd
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
CatchDuplicate:
SF_Exception.RaiseFatal(DUPLICATEKEYERROR, Iif(Len(Context) &gt; 0, &quot;Context&quot;, &quot;MsgId&quot;), sKey)
GoTo Finally
End Function &apos; ScriptForge.SF_L10N.AddText
REM -----------------------------------------------------------------------------
Public Function ExportToPOTFile(Optional ByVal FileName As Variant _
, Optional ByVal Header As Variant _
, Optional ByVal Encoding As Variant _
) As Boolean
&apos;&apos;&apos; Export a set of untranslated strings as a POT file
&apos;&apos;&apos; The set of strings has been built either by a succession of AddText() methods
&apos;&apos;&apos; or by a successful invocation of the L10N service with the FolderName argument
&apos;&apos;&apos; The generated file should pass successfully the &quot;msgfmt --check &apos;the pofile&apos;&quot; GNU command
&apos;&apos;&apos; Args:
&apos;&apos;&apos; FileName: the complete file name to export to. If it exists, is overwritten without warning
&apos;&apos;&apos; Header: Comments that will appear on top of the generated file. Do not include any leading &quot;#&quot;
&apos;&apos;&apos; If the string spans multiple lines, insert escape sequences (\n) where relevant
&apos;&apos;&apos; A standard header will be added anyway
&apos;&apos;&apos; Encoding: The character set that should be used
&apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
&apos;&apos;&apos; Note that LibreOffice probably does not implement all existing sets
&apos;&apos;&apos; Default = UTF-8
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; True if successful
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; myPO.ExportToPOTFile(&quot;myFile.pot&quot;, Header := &quot;Top comment\nSecond line of top comment&quot;)
Dim bExport As Boolean &apos; Return value
Dim oFile As Object &apos; Generated file handler
Dim vLines As Variant &apos; Wrapped lines
Dim sLine As String &apos; A single line
Dim vItems As Variant &apos; Array of dictionary items
Dim vItem As Variant &apos; POEntry type
Const cstSharp = &quot;# &quot;, cstSharpDot = &quot;#. &quot;, cstFlag = &quot;#, kde-format&quot;
Const cstTabSize = 4
Const cstWrap = 70
Const cstThisSub = &quot;L10N.ExportToPOTFile&quot;
Const cstSubArgs = &quot;FileName, [Header=&quot;&quot;&quot;&quot;], [Encoding=&quot;&quot;UTF-8&quot;&quot;&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
bExport = False
Check:
If IsMissing(Header) Or IsMissing(Header) Then Header = &quot;&quot;
If IsMissing(Encoding) Or IsMissing(Encoding) Then Encoding = &quot;UTF-8&quot;
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._ValidateFile(FileName, &quot;FileName&quot;) Then GoTo Finally
If Not SF_Utils._Validate(Header, &quot;Header&quot;, V_STRING) Then GoTo Finally
If Not SF_Utils._Validate(Encoding, &quot;Encoding&quot;, V_STRING) Then GoTo Finally
End If
Try:
Set oFile = SF_FileSystem.CreateTextFile(FileName, Overwrite := True, Encoding := Encoding)
If Not IsNull(oFile) Then
With oFile
&apos; Standard header
.WriteLine(cstSharp)
.WriteLine(cstSharp &amp; &quot;This pristine POT file has been generated by LibreOffice/ScriptForge&quot;)
.WriteLine(cstSharp &amp; &quot;Full documentation is available on https://help.libreoffice.org/&quot;)
&apos; User header
If Len(Header) &gt; 0 Then
.WriteLine(cstSharp)
vLines = SF_String.Wrap(Header, cstWrap, cstTabSize)
For Each sLine In vLines
.WriteLine(cstSharp &amp; Replace(sLine, SF_String.sfLF, &quot;&quot;))
Next sLine
End If
&apos; Standard header
.WriteLine(cstSharp)
.WriteLine(&quot;msgid &quot;&quot;&quot;&quot;&quot;)
.WriteLine(&quot;msgstr &quot;&quot;&quot;&quot;&quot;)
.WriteLine(SF_String.Quote(&quot;Project-Id-Version: PACKAGE VERSION\n&quot;))
.WriteLine(SF_String.Quote(&quot;Report-Msgid-Bugs-To: &quot; _
&amp; &quot;https://bugs.libreoffice.org/enter_bug.cgi?product=LibreOffice&amp;bug_status=UNCONFIRMED&amp;component=UI\n&quot;))
.WriteLine(SF_String.Quote(&quot;POT-Creation-Date: &quot; &amp; SF_STring.Represent(Now()) &amp; &quot;\n&quot;))
.WriteLine(SF_String.Quote(&quot;PO-Revision-Date: YYYY-MM-DD HH:MM:SS\n&quot;))
.WriteLine(SF_String.Quote(&quot;Last-Translator: FULL NAME &lt;EMAIL@ADDRESS&gt;\n&quot;))
.WriteLine(SF_String.Quote(&quot;Language-Team: LANGUAGE &lt;EMAIL@ADDRESS&gt;\n&quot;))
.WriteLine(SF_String.Quote(&quot;Language: en_US\n&quot;))
.WriteLine(SF_String.Quote(&quot;MIME-Version: 1.0\n&quot;))
.WriteLine(SF_String.Quote(&quot;Content-Type: text/plain; charset=&quot; &amp; Encoding &amp; &quot;\n&quot;))
.WriteLine(SF_String.Quote(&quot;Content-Transfer-Encoding: 8bit\n&quot;))
.WriteLine(SF_String.Quote(&quot;Plural-Forms: nplurals=2; plural=n &gt; 1;\n&quot;))
.WriteLine(SF_String.Quote(&quot;X-Generator: LibreOffice - ScriptForge\n&quot;))
.WriteLine(SF_String.Quote(&quot;X-Accelerator-Marker: ~\n&quot;))
&apos; Individual translatable strings
vItems = _Dictionary.Items()
For Each vItem in vItems
.WriteBlankLines(1)
&apos; Comments
vLines = Split(vItem.Comment, &quot;\n&quot;)
For Each sLine In vLines
.WriteLine(cstSharpDot &amp; SF_String.ExpandTabs(SF_String.Unescape(sLine), cstTabSize))
Next sLine
&apos; Flag
If InStr(vItem.MsgId, &quot;%&quot;) &gt; 0 Then .WriteLine(cstFlag)
&apos; Context
If Len(vItem.Context) &gt; 0 Then
.WriteLine(&quot;msgctxt &quot; &amp; SF_String.Quote(vItem.Context))
End If
&apos; MsgId
vLines = SF_String.Wrap(vItem.MsgId, cstWrap, cstTabSize)
If UBound(vLines) = 0 Then
.WriteLine(&quot;msgid &quot; &amp; SF_String.Quote(SF_String.Escape(vLines(0))))
Else
.WriteLine(&quot;msgid &quot;&quot;&quot;&quot;&quot;)
For Each sLine in vLines
.WriteLine(SF_String.Quote(SF_String.Escape(sLine)))
Next sLine
End If
&apos; MsgStr
.WriteLine(&quot;msgstr &quot;&quot;&quot;&quot;&quot;)
Next vItem
.CloseFile()
End With
End If
bExport = True
Finally:
If Not IsNull(oFile) Then Set oFile = oFile.Dispose()
ExportToPOTFile = bExport
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_L10N.ExportToPOTFile
REM -----------------------------------------------------------------------------
Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
&apos;&apos;&apos; Return the actual value of the given property
&apos;&apos;&apos; Args:
&apos;&apos;&apos; PropertyName: the name of the property as a string
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; The actual value of the property
&apos;&apos;&apos; If the property does not exist, returns Null
&apos;&apos;&apos; Exceptions:
&apos;&apos;&apos; ARGUMENTERROR The property does not exist
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; myL10N.GetProperty(&quot;MyProperty&quot;)
Const cstThisSub = &quot;L10N.GetProperty&quot;
Const cstSubArgs = &quot;&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
GetProperty = Null
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
End If
Try:
GetProperty = _PropertyGet(PropertyName)
Finally:
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_L10N.GetProperty
REM -----------------------------------------------------------------------------
Public Function GetText(Optional ByVal MsgId As Variant _
, ParamArray pvArgs As Variant _
) As String
&apos;&apos;&apos; Get the translated string corresponding with the given argument
&apos;&apos;&apos; Args:
&apos;&apos;&apos; MsgId: the identifier of the string or the untranslated string
&apos;&apos;&apos; Either - the untranslated text (MsgId)
&apos;&apos;&apos; - the reference to the untranslated text (Context)
&apos;&apos;&apos; - both (Context|MsgId) : the pipe character is essential
&apos;&apos;&apos; pvArgs(): a list of arguments present as %1, %2, ... in the (un)translated string)
&apos;&apos;&apos; to be substituted in the returned string
&apos;&apos;&apos; Any type is admitted but only strings, numbers or dates are relevant
&apos;&apos;&apos; Returns:
&apos;&apos;&apos; The translated string
&apos;&apos;&apos; If not found the MsgId string or the Context string
&apos;&apos;&apos; Anyway the substitution is done
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; myPO.GetText(&quot;This is a text to be included in a POT file&quot;)
&apos;&apos;&apos; &apos; Ceci est un text à inclure dans un fichier POT
Dim sText As String &apos; Output buffer
Dim sContext As String &apos; Context part of argument
Dim sMsgId As String &apos; MsgId part of argument
Dim vItem As POEntry &apos; Entry in the dictionary
Dim vMsgId As Variant &apos; MsgId split on pipe
Dim sKey As String &apos; Key of dictionary
Dim sPercent As String &apos; %1, %2, ... placeholders
Dim i As Long
Const cstPipe = &quot;|&quot;
Const cstThisSub = &quot;L10N.GetText&quot;
Const cstSubArgs = &quot;MsgId, [Arg0, Arg1, ...]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
sText = &quot;&quot;
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
End If
If Len(Trim(MsgId)) = 0 Then GoTo Finally
sText = MsgId
Try:
&apos; Find and load entry from dictionary
If Left(MsgId, 1) = cstPipe then MsgId = Mid(MsgId, 2)
vMsgId = Split(MsgId, cstPipe)
sKey = vMsgId(0)
If Not _Dictionary.Exists(sKey) Then &apos; Not found
If UBound(vMsgId) = 0 Then sText = vMsgId(0) Else sText = Mid(MsgId, InStr(MsgId, cstPipe) + 1)
Else
vItem = _Dictionary.Item(sKey)
If Len(vItem.MsgStr) &gt; 0 Then sText = vItem.MsgStr Else sText = vItem.MsgId
End If
&apos; Substitute %i placeholders
For i = UBound(pvArgs) To 0 Step -1 &apos; Go downwards to not have a limit in number of args
sPercent = &quot;%&quot; &amp; (i + 1)
sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i)))
Next i
Finally:
GetText = sText
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_L10N.GetText
REM - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Public Function _(Optional ByVal MsgId As Variant _
, ParamArray pvArgs As Variant _
) As String
&apos;&apos;&apos; Get the translated string corresponding with the given argument
&apos;&apos;&apos; Alias of GetText() - See above
&apos;&apos;&apos; Examples:
&apos;&apos;&apos; myPO._(&quot;This is a text to be included in a POT file&quot;)
&apos;&apos;&apos; &apos; Ceci est un text à inclure dans un fichier POT
Dim sText As String &apos; Output buffer
Dim sPercent As String &apos; %1, %2, ... placeholders
Dim i As Long
Const cstPipe = &quot;|&quot;
Const cstThisSub = &quot;L10N._&quot;
Const cstSubArgs = &quot;MsgId, [Arg0, Arg1, ...]&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
sText = &quot;&quot;
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._Validate(MsgId, &quot;MsgId&quot;, V_STRING) Then GoTo Finally
End If
If Len(Trim(MsgId)) = 0 Then GoTo Finally
Try:
&apos; Find and load entry from dictionary
sText = GetText(MsgId)
&apos; Substitute %i placeholders - done here, not in GetText(), because # of arguments is undefined
For i = 0 To UBound(pvArgs)
sPercent = &quot;%&quot; &amp; (i + 1)
sText = Replace(sText, sPercent, SF_String.Represent(pvArgs(i)))
Next i
Finally:
_ = sText
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_L10N._
REM -----------------------------------------------------------------------------
Public Function Methods() As Variant
&apos;&apos;&apos; Return the list of public methods of the L10N service as an array
Methods = Array( _
&quot;AddText&quot; _
, &quot;ExportToPOTFile&quot; _
, &quot;GetText&quot; _
, &quot;_&quot; _
)
End Function &apos; ScriptForge.SF_L10N.Methods
REM -----------------------------------------------------------------------------
Public Function Properties() As Variant
&apos;&apos;&apos; Return the list or properties of the Timer class as an array
Properties = Array( _
&quot;Folder&quot; _
, &quot;Languages&quot; _
, &quot;Locale&quot; _
)
End Function &apos; ScriptForge.SF_L10N.Properties
REM -----------------------------------------------------------------------------
Public Function SetProperty(Optional ByVal PropertyName As Variant _
, Optional ByRef Value As Variant _
) As Boolean
&apos;&apos;&apos; Set a new value to the given property
&apos;&apos;&apos; Args:
&apos;&apos;&apos; PropertyName: the name of the property as a string
&apos;&apos;&apos; Value: its new value
&apos;&apos;&apos; Exceptions
&apos;&apos;&apos; ARGUMENTERROR The property does not exist
Const cstThisSub = &quot;L10N.SetProperty&quot;
Const cstSubArgs = &quot;PropertyName, Value&quot;
If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
SetProperty = False
Check:
If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
End If
Try:
Select Case UCase(PropertyName)
Case Else
End Select
Finally:
SF_Utils._ExitFunction(cstThisSub)
Exit Function
Catch:
GoTo Finally
End Function &apos; ScriptForge.SF_L10N.SetProperty
REM =========================================================== PRIVATE FUNCTIONS
REM -----------------------------------------------------------------------------
Public Sub _Initialize(ByVal psPOFile As String _
, ByVal Encoding As String _
)
&apos;&apos;&apos; Completes initialization of the current instance requested from CreateScriptService()
&apos;&apos;&apos; Load the POFile in the dictionary, otherwise leave the dictionary empty
&apos;&apos;&apos; Args:
&apos;&apos;&apos; psPOFile: the file to load the translated strings from
&apos;&apos;&apos; Encoding: The character set that should be used. Default = UTF-8
Dim oFile As Object &apos; PO file handler
Dim sContext As String &apos; Collected context string
Dim sMsgId As String &apos; Collected untranslated string
Dim sComment As String &apos; Collected comment string
Dim sMsgStr As String &apos; Collected translated string
Dim sLine As String &apos; Last line read
Dim iContinue As Integer &apos; 0 = None, 1 = MsgId, 2 = MsgStr
Const cstMsgId = 1, cstMsgStr = 2
Try:
&apos; Initialize dictionary anyway
Set _Dictionary = SF_Services.CreateScriptService(&quot;Dictionary&quot;)
Set _Dictionary.[_Parent] = [Me]
&apos; Load PO file
If Len(psPOFile) &gt; 0 Then
With SF_FileSystem
_POFolder = ._ConvertToUrl(.GetParentFolderName(psPOFile))
_Locale = .GetBaseName(psPOFile)
_POFile = ._ConvertToUrl(psPOFile)
End With
&apos; Load PO file
Set oFile = SF_FileSystem.OpenTextFile(psPOFile, IOMode := SF_FileSystem.ForReading, Encoding := Encoding)
If Not IsNull(oFile) Then
With oFile
&apos; The PO file is presumed valid =&gt; syntax check is not very strict
sContext = &quot;&quot; : sMsgId = &quot;&quot; : sComment = &quot;&quot; : sMsgStr = &quot;&quot;
Do While Not .AtEndOfStream
sLine = Trim(.ReadLine())
&apos; Trivial examination of line header
Select Case True
Case sLine = &quot;&quot;
If Len(sMsgId) &gt; 0 Then AddText(sContext, sMsgId, sComment, sMsgStr)
sContext = &quot;&quot; : sMsgId = &quot;&quot; : sComment = &quot;&quot; : sMsgStr = &quot;&quot;
iContinue = 0
Case Left(sLine, 3) = &quot;#. &quot;
sComment = sComment &amp; Iif(Len(sComment) &gt; 0, &quot;\n&quot;, &quot;&quot;) &amp; Trim(Mid(sLine, 4))
iContinue = 0
Case Left(sLine, 8) = &quot;msgctxt &quot;
sContext = SF_String.Unquote(Trim(Mid(sLine, 9)))
iContinue = 0
Case Left(sLine, 6) = &quot;msgid &quot;
sMsgId = SF_String.Unquote(Trim(Mid(sLine, 7)))
iContinue = cstMsgId
Case Left(sLine, 7) = &quot;msgstr &quot;
sMsgStr = sMsgStr &amp; SF_String.Unquote(Trim(Mid(sLine, 8)))
iContinue = cstMsgStr
Case Left(sLine, 1) = &quot;&quot;&quot;&quot;
If iContinue = cstMsgId Then
sMsgId = sMsgId &amp; SF_String.Unquote(sLine)
ElseIf iContinue = cstMsgStr Then
sMsgStr = sMsgStr &amp; SF_String.Unquote(sLine)
Else
iContinue = 0
End If
Case Else &apos; Skip line
iContinue = 0
End Select
Loop
&apos; Be sure to store the last entry
If Len(sMsgId) &gt; 0 Then AddText(sContext, sMsgId, sComment, sMsgStr)
.CloseFile()
Set oFile = .Dispose()
End With
End If
Else
_POFolder = &quot;&quot;
_Locale = &quot;&quot;
_POFile = &quot;&quot;
End If
Finally:
Exit Sub
End Sub &apos; ScriptForge.SF_L10N._Initialize
REM -----------------------------------------------------------------------------
Private Function _PropertyGet(Optional ByVal psProperty As String)
&apos;&apos;&apos; Return the value of the named property
&apos;&apos;&apos; Args:
&apos;&apos;&apos; psProperty: the name of the property
Dim vFiles As Variant &apos; Array of PO-files
Dim i As Long
Dim cstThisSub As String
Dim cstSubArgs As String
cstThisSub = &quot;SF_L10N.get&quot; &amp; psProperty
cstSubArgs = &quot;&quot;
SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
With SF_FileSystem
Select Case psProperty
Case &quot;Folder&quot;
If Len(_POFolder) &gt; 0 Then _PropertyGet = ._ConvertFromUrl(_POFolder) Else _PropertyGet = &quot;&quot;
Case &quot;Languages&quot;
If Len(_POFolder) &gt; 0 Then
vFiles = .Files(._ConvertFromUrl(_POFolder), &quot;??-??.po&quot;)
For i = 0 To UBound(vFiles)
vFiles(i) = SF_FileSystem.GetBaseName(vFiles(i))
Next i
Else
vFiles = Array()
End If
_PropertyGet = vFiles
Case &quot;Locale&quot;
_PropertyGet = _Locale
Case Else
_PropertyGet = Null
End Select
End With
Finally:
SF_Utils._ExitFunction(cstThisSub)
Exit Function
End Function &apos; ScriptForge.SF_L10N._PropertyGet
REM -----------------------------------------------------------------------------
Private Function _Repr() As String
&apos;&apos;&apos; Convert the L10N instance to a readable string, typically for debugging purposes (DebugPrint ...)
&apos;&apos;&apos; Args:
&apos;&apos;&apos; Return:
&apos;&apos;&apos; &quot;[L10N]: PO file&quot;
_Repr = &quot;[L10N]: &quot; &amp; _POFile
End Function &apos; ScriptForge.SF_L10N._Repr
REM ============================================ END OF SCRIPTFORGE.SF_L10N
</script:module>