unit Keyman.System.DevTools.BuildSetupStringTranslations; interface uses System.Classes, System.SysUtils, Xml.XMLIntf, Xml.XMLDoc; type TBuildSetupStringTranslations = class private class function ProcessFile(const SourceFile, DestinationFile, BCP47, BCP47ID: string): Boolean; static; class function PascalifyString(s: string): string; static; public class function Run(SourcePath, DestinationPath: string): Boolean; static; end; implementation uses Xml.Win.msxmldom, Winapi.ActiveX, System.Generics.Collections, System.StrUtils, System.TypInfo, System.Variants, Keyman.System.AndroidStringToKeymanLocaleString, SetupStrings; // From Desktop/Setup { TBuildSetupStringTranslations } class function TBuildSetupStringTranslations.PascalifyString(s: string): string; var i: Integer; ch: Integer; InQuotes: Boolean; begin InQuotes := False; Result := ''; for i := 1 to Length(s) do begin ch := Ord(s[i]); if (ch < 32) or (ch > 126) or (ch = Ord('''')) then begin if InQuotes then Result := Result + ''''; InQuotes := False; // Coming from UTF-8 XML, we may have only #10 so convert to #13#10 if so if (ch = 10) and (i > 1) and (Ord(s[i-1]) <> 13) then Result := Result + '#13#10' else Result := Result + '#'+IntToStr(ch); end else begin if not InQuotes then Result := Result + ''''; InQuotes := True; Result := Result + Char(ch); end; // If string is too long, Delphi doesn't like it if (i mod 64) = 0 then if InQuotes then Result := Result + '''+''' else Result := Result + '+'; end; if InQuotes then Result := Result + ''''; end; class function TBuildSetupStringTranslations.ProcessFile(const SourceFile, DestinationFile, BCP47, BCP47ID: string): Boolean; var n: Integer; iit: TInstallInfoText; doc: IXmlDocument; f: TextFile; sconst: string; node: IXMLNode; HasPreviousNode: Boolean; strings: TDictionary; begin doc := TXMLDocument.Create(nil); doc.ParseOptions := [poResolveExternals]; // I902 - resolve externals when loading XML files doc.LoadFromFile(SourceFile); strings := TDictionary.Create; try node := doc.DocumentElement.ChildNodes[0]; while Assigned(node) do begin if node.NodeName = 'string' then begin n := GetEnumValue(TypeInfo(TInstallInfoText), VarToStr(node.Attributes['name'])); if (n >= Ord(Low(TInstallInfoText))) and (n <= Ord(High(TInstallInfoText))) then strings.Add(TInstallInfoText(n), PascalifyString(TAndroidStringToKeymanLocaleString.Transform(VarToStr(node.NodeValue)))); end; node := node.NextSibling; end; AssignFile(f, DestinationFile); Rewrite(f); writeln(f, '// Generated file - generated by devtools -buildsetupstrings at '+FormatDateTime('dd/mm/yyyy hh:nn:ss', Now)); writeln(f, 'unit '+ChangeFileExt(ExtractFileName(DestinationFile), '')+';'#13#10'interface'#13#10); writeln(f, 'uses Keyman.Setup.System.SetupUILanguageManager, SetupStrings;'#13#10); sconst := 'const FLocaleStrings_'+BCP47ID+': TSetupUILanguageManager.TLocaleArray = ('#13#10; HasPreviousNode := False; for iit := Low(TInstallInfoText) to High(TInstallInfoText) do begin if HasPreviousNode then sconst := sconst + ','#13#10; if strings.ContainsKey(iit) then sconst := sconst + ' '+strings[iit] else sconst := sconst + ' '''''; HasPreviousNode := True; end; finally strings.Free; end; sconst := sconst + #13#10');'; writeln(f, sconst); writeln(f); writeln(f, 'implementation'); writeln(f); writeln(f, 'initialization'); writeln(f,' TSetupUILanguageManager.RegisterSetupStrings('''+BCP47+''', FLocaleStrings_'+BCP47ID+');'); writeln(f, 'end.'); CloseFile(f); Result := True; end; class function TBuildSetupStringTranslations.Run(SourcePath, DestinationPath: string): Boolean; var f: TSearchRec; outfile: TextFile; SourceFile, DestFile, BCP47, BCP47ID: string; HasPreviousNode: Boolean; begin SourcePath := IncludeTrailingPathDelimiter(SourcePath); DestinationPath := IncludeTrailingPathDelimiter(DestinationPath); // Iterate through SourcePath/*/strings.xml and generate DestinationPath/Keyman.Setup.System.Locale.*.pas file for each CoInitializeEx(nil, COINIT_APARTMENTTHREADED); try Xml.Win.msxmldom.MSXMLDOMDocumentFactory.AddDOMProperty('ProhibitDTD', False); AssignFile(outfile, DestinationPath+'Keyman.Setup.System.Locales.pas'); Rewrite(outfile); writeln(outfile, 'unit Keyman.Setup.System.Locales;'); writeln(outfile, 'interface'); writeln(outfile, 'implementation'); writeln(outfile, 'uses'); HasPreviousNode := False; if FindFirst(SourcePath+'*', faDirectory, f) = 0 then begin repeat BCP47 := f.Name; BCP47ID := ReplaceText(BCP47, '-', '_'); SourceFile := SourcePath+BCP47+'\strings.xml'; DestFile := DestinationPath+'Keyman.Setup.System.Locale.'+BCP47ID+'.pas'; if (BCP47 <> '..') and (BCP47 <> '.') and FileExists(SourcePath+BCP47+'\strings.xml') then begin if not ProcessFile(SourceFile, DestFile, BCP47, BCP47ID) then Exit(False); if HasPreviousNode then writeln(outfile, ','); write(outfile, ' Keyman.Setup.System.Locale.'+BCP47ID); HasPreviousNode := True; end; until FindNext(f) <> 0; FindClose(f); end; writeln(outfile, ';'); writeln(outfile, 'end.'); CloseFile(outfile); finally CoUninitialize; end; Result := True; end; end.