summaryrefslogtreecommitdiffstats
path: root/wizards/source/scriptforge/SF_L10N.xba
diff options
context:
space:
mode:
Diffstat (limited to 'wizards/source/scriptforge/SF_L10N.xba')
-rw-r--r--wizards/source/scriptforge/SF_L10N.xba825
1 files changed, 825 insertions, 0 deletions
diff --git a/wizards/source/scriptforge/SF_L10N.xba b/wizards/source/scriptforge/SF_L10N.xba
new file mode 100644
index 000000000..6bc6b236f
--- /dev/null
+++ b/wizards/source/scriptforge/SF_L10N.xba
@@ -0,0 +1,825 @@
+<?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 4 mechanisms:
+&apos;&apos;&apos; 1. AddText: for the programmer to build a set of words or sentences
+&apos;&apos;&apos; meant for being translated later
+&apos;&apos;&apos; 2. AddTextsFromDialog: to automatically execute AddText() on each fixed text of a dialog
+&apos;&apos;&apos; 3. ExportToPOTFile: All the above texts are exported into a pristine POT-file
+&apos;&apos;&apos; 4. 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; Encoding: The character set that should be used (default = UTF-8)
+&apos;&apos;&apos; Use one of the Names listed in https://www.iana.org/assignments/character-sets/character-sets.xhtml
+&apos;&apos;&apos; Locale2: fallback Locale to select if Locale po file does not exist (typically &quot;en-US&quot;)
+&apos;&apos;&apos; Encoding2: Encoding of the 2nd Locale file
+&apos;&apos;&apos; Service invocation examples:
+&apos;&apos;&apos; Dim myPO As Variant
+&apos;&apos;&apos; myPO = CreateScriptService(&quot;L10N&quot;) &apos; AddText, AddTextsFromDialog 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; Detailed user documentation:
+&apos;&apos;&apos; https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_l10n.html?DbPAR=BASIC
+&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&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)
+ bAdd = True
+
+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 AddTextsFromDialog(Optional ByRef Dialog As Variant) As Boolean
+&apos;&apos;&apos; Add all fixed text strings of a dialog to the list of localizable text strings
+&apos;&apos;&apos; Added texts are:
+&apos;&apos;&apos; - the title of the dialog
+&apos;&apos;&apos; - the caption associated with next control types: Button, CheckBox, FixedLine, FixedText, GroupBox and RadioButton
+&apos;&apos;&apos; - the content of list- and comboboxes
+&apos;&apos;&apos; - the tip- or helptext displayed when the mouse is hovering the control
+&apos;&apos;&apos; The current method has method SFDialogs.SF_Dialog.GetTextsFromL10N as counterpart
+&apos;&apos;&apos; The targeted dialog must not be open when the current method is run
+&apos;&apos;&apos; Args:
+&apos;&apos;&apos; Dialog: a SFDialogs.Dialog service instance
+&apos;&apos;&apos; Returns:
+&apos;&apos;&apos; True when successful
+&apos;&apos;&apos; Examples:
+&apos;&apos;&apos; Dim myDialog As Object
+&apos;&apos;&apos; Set myDialog = CreateScriptService(&quot;SFDialogs.Dialog&quot;, &quot;GlobalScope&quot;, &quot;XrayTool&quot;, &quot;DlgXray&quot;)
+&apos;&apos;&apos; myPO.AddTextsFromDialog(myDialog)
+
+Dim bAdd As Boolean &apos; Return value
+Dim vControls As Variant &apos; Array of control names
+Dim sControl As String &apos; A single control name
+Dim oControl As Object &apos; SFDialogs.DialogControl
+Dim sText As String &apos; The text to insert in the dictionary
+Dim sDialogComment As String &apos; The prefix in the comment to insert in the dictionary for the dialog
+Dim sControlComment As String &apos; The prefix in the comment to insert in the dictionary for a control
+Dim vSource As Variant &apos; RowSource property of dialog control as an array
+Dim i As Long
+
+Const cstThisSub = &quot;L10N.AddTextsFromDialog&quot;
+Const cstSubArgs = &quot;Dialog&quot;
+
+ If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
+ bAdd = False
+
+Check:
+ If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
+ If Not SF_Utils._Validate(Dialog, &quot;Dialog&quot;, V_OBJECT, , , &quot;DIALOG&quot;) Then GoTo Finally
+ End If
+
+Try:
+ With Dialog
+ &apos; Store the title of the dialog
+ sDialogComment = &quot;Dialog =&gt; &quot; &amp; ._Container &amp; &quot; : &quot; &amp; ._Library &amp; &quot; : &quot; &amp; ._Name &amp; &quot; : &quot;
+ stext = .Caption
+ If Len(sText) &gt; 0 Then
+ If Not _ReplaceText(&quot;&quot;, sText, sDialogComment &amp; &quot;Caption&quot;) Then GoTo Catch
+ End If
+ &apos; Scan all controls
+ vControls = .Controls()
+ For Each sControl In vControls
+ Set oControl = .Controls(sControl)
+ sControlComment = sDialogComment &amp; sControl &amp; &quot;.&quot;
+ With oControl
+ &apos; Extract fixed texts
+ sText = .Caption
+ If Len(sText) &gt; 0 Then
+ If Not _ReplaceText(&quot;&quot;, sText, sControlComment &amp; &quot;Caption&quot;) Then GoTo Catch
+ End If
+ vSource = .RowSource &apos; List and comboboxes only
+ If IsArray(vSource) Then
+ For i = 0 To UBound(vSource)
+ If Len(vSource(i)) &gt; 0 Then
+ If Not _ReplaceText(&quot;&quot;, vSource(i), sControlComment &amp; &quot;RowSource[&quot; &amp; i &amp; &quot;]&quot;) Then GoTo Catch
+ End If
+ Next i
+ End If
+ sText = .TipText
+ If Len(sText) &gt; 0 Then
+ If Not _ReplaceText(&quot;&quot;, sText, sControlComment &amp; &quot;TipText&quot;) Then GoTo Catch
+ End If
+ End With
+ Next sControl
+ End With
+
+ bAdd = True
+
+Finally:
+ AddTextsFromDialog = bAdd
+ SF_Utils._ExitFunction(cstThisSub)
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N.AddTextsFromDialog
+
+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 IsEmpty(Header) Then Header = &quot;&quot;
+ If IsMissing(Encoding) Or IsEmpty(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;AddTextsFromDialog&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 _ReplaceText(ByVal psContext As String _
+ , ByVal psMsgId As String _
+ , ByVal psComment As String _
+ ) As Boolean
+&apos;&apos;&apos; When the entry in the dictionary does not yet exist, equivalent to AddText
+&apos;&apos;&apos; When it exists already, extend the existing comment with the psComment argument
+&apos;&apos;&apos; Used from AddTextsFromDialog to manage identical strings without raising errors,
+&apos;&apos;&apos; e.g. when multiple dialogs have the same &quot;Close&quot; button
+
+Dim bAdd As Boolean &apos; Return value
+Dim sKey As String &apos; The key part of an entry in the dictionary
+Dim vItem As POEntry &apos; The item part of the new entry in the dictionary
+
+Try:
+ bAdd = False
+ If Len(psContext) &gt; 0 Then sKey = psContext Else sKey = psMsgId
+ If _Dictionary.Exists(sKey) Then
+ &apos; Load the entry, adapt comment and rewrite
+ vItem = _Dictionary.Item(sKey)
+ If Len(vItem.Comment) = 0 Then vItem.Comment = psComment Else vItem.Comment = vItem.Comment &amp; &quot;\n&quot; &amp; psComment
+ bAdd = _Dictionary.ReplaceItem(sKey, vItem)
+ Else
+ &apos; Add a new entry as usual
+ bAdd = AddText(psContext, psMsgId, psComment)
+ End If
+
+Finally:
+ _ReplaceText = bAdd
+ Exit Function
+Catch:
+ GoTo Finally
+End Function &apos; ScriptForge.SF_L10N._ReplaceText
+
+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> \ No newline at end of file