mirror of
https://gitlab.com/freepascal.org/fpc/pas2js.git
synced 2025-04-27 02:03:37 +02:00
465 lines
13 KiB
ObjectPascal
465 lines
13 KiB
ObjectPascal
{
|
|
This file is part of the Pas2JS run time library.
|
|
Copyright (c) 2019 by Michael Van Canneyt
|
|
|
|
RTL HTML tag translator class
|
|
|
|
See the file COPYING.FPC, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
**********************************************************************}
|
|
unit Rtl.HTMLTranslate;
|
|
|
|
interface
|
|
|
|
Uses SysUtils, JS, Web, Classes, Rtl.HTMLUtils;
|
|
|
|
(* Language data is a JSON object structure.
|
|
|
|
2 file-based formats are supported (see LanguageSource):
|
|
|
|
lsSingle: Single Language file in the following form
|
|
{
|
|
"name" : "value"
|
|
}
|
|
or
|
|
{
|
|
scope1 : {
|
|
"name' : "value"
|
|
},
|
|
scope1 : {
|
|
"name' : "value"
|
|
}
|
|
}
|
|
lsMulti: Multiple languages are in the file in the following form.
|
|
Languages are lowercase !
|
|
(
|
|
"lang1" : {
|
|
"name" : "value"
|
|
},
|
|
"lang2" : {
|
|
scope1 : {
|
|
"name' : "value"
|
|
},
|
|
scope1 : {
|
|
"name' : "value"
|
|
}
|
|
}
|
|
}
|
|
|
|
there is an Javascript-only mode as well:
|
|
|
|
for language mode lsScoped: a JS global variable is assumed to exist
|
|
with a JS object using the same format as the lsMultiple format.
|
|
|
|
*)
|
|
|
|
Const
|
|
DefaultDataTagName = 'translate';
|
|
|
|
Type
|
|
TLanguageLoadedEvent = reference to Procedure (Sender : TObject; aLanguage : String);
|
|
TLanguageLoadErrorEvent = reference to Procedure (Sender : TObject; aLanguague,aErrorMessage : String);
|
|
|
|
// Are all languages in a single file or in multiple files or in global JS scope.
|
|
|
|
TLanguageSource = (lsSingle, lsMulti, lsScoped);
|
|
|
|
EHTMLTagTranslator = Class(Exception);
|
|
|
|
{ THTMLTagTranslator }
|
|
|
|
THTMLTagTranslator = class(TComponent)
|
|
private
|
|
FDataTagName: String;
|
|
FLanguage: String;
|
|
FOnLanguageLoaded: TLanguageLoadedEvent;
|
|
FCurrLanguageStrings: TJSObject;
|
|
FDefaultScopeObj : TJSObject;
|
|
FLanguages: TJSObject;
|
|
FLanguageFileURL: String;
|
|
FLanguageVarName: String;
|
|
FFileMode: TLanguageSource;
|
|
FOnLanguageLoadError: TLanguageLoadErrorEvent;
|
|
FDefaultScopeName: String;
|
|
FLoadingLanguage: String;
|
|
FContinueKey: String;
|
|
FTextMode: TTextMode;
|
|
procedure LoadLanguageFile(aLanguage: String);
|
|
Protected
|
|
procedure DoSetLanguageData(aData : TJSObject; aLanguage : String);
|
|
function GetDataTagName : String;
|
|
function GetSeeAlsoScope(aScope: TJSObject): TJSObject;
|
|
procedure SetLanguage(const Value: String); virtual;
|
|
Procedure DoLanguageLoaded; virtual;
|
|
Property LoadingLanguage : String Read FloadingLanguage;
|
|
Procedure TranslateHTMLTag(aEl : TJSHTMLElement; aScope,aSeealsoScope : TJSObject); overload;
|
|
Procedure TranslateBelowHTMLTag(aEl : TJSHTMLElement; aScope,aSeealsoScope : TJSObject); overload;
|
|
Public
|
|
// Directly set the language data instead of using an URL. This can be used with lsSingle or lsMulti
|
|
procedure SetLanguageData(aData : TJSObject);
|
|
// Translate a single HTML tag, using indicated scope.
|
|
Procedure TranslateHTMLTag(aEl : TJSHTMLElement; aScope : String = ''); overload;
|
|
// Translate everything below the indicated HTML tag, using indicated scope.
|
|
Procedure TranslateBelowHTMLTag(aEl : TJSHTMLElement; aScope : String = ''); overload;
|
|
// Search message name in current root, scope
|
|
function GetMessageByName(const aScope, aName: string): String; overload;
|
|
// Search scope in provided root, use to search message in found scope
|
|
Function GetMessageByName(aRoot : TJSObject; const aScope,aName: string): String; overload;
|
|
// Search in aScope for message aName
|
|
Function GetMessageByName(aScope : TJSObject; aName : string) : String; overload;
|
|
// Find in aScope and aSeeAlsoScope message named aName
|
|
Function GetMessageByName(aScope,aSeealsoScope : TJSObject; aName : string) : String; overload;
|
|
// Check if language is available
|
|
Function HasLanguage(aLanguage : String) : Boolean;
|
|
// Find a scope in the current languages
|
|
Function GetScope(const aScope : String) : TJSObject; overload;
|
|
Function GetScope(aRoot : TJSObject; const aScope : String) : TJSObject; overload;
|
|
Property CurrLanguageStrings : TJSObject Read FCurrLanguageStrings;
|
|
Published
|
|
// Data Tag name. Default is 'translate'
|
|
Property DataTagName : String Read GetDataTagName Write FDataTagName;
|
|
// Default scope to use when looking for translations. First the local scope is checked, then default scope, then ContinueKey
|
|
Property DefaultScope : String Read FDefaultScopeName Write FDefaultScopeName;
|
|
// Continue key name;
|
|
// When set, indicates the name of a scope in which to continue searching for a translation term.
|
|
// This can be used for form inheritance;
|
|
// ContinueKey can be set to continue searching in the inherited scope.
|
|
Property ContinueKey : String Read FContinueKey Write FContinueKey;
|
|
// Language name (will be lowercased)
|
|
Property Language : String Read FLanguage Write SetLanguage;
|
|
// Language source: single file, multiple files or in Javascript Scope
|
|
Property LanguageSource : TLanguageSource Read FFileMode Write FFileMode;
|
|
// File to load language strings from.
|
|
// If LanguageSource=lsMulti, a format with the language code is done (use %s)
|
|
// Example: /js/languages/lang-%s.json
|
|
Property LanguageFileURL : String Read FLanguageFileURL Write FLanguageFileURL;
|
|
// if LanguageSource=lsScoped then LanguageVarName is the name of the global Window property that contains the translations.
|
|
property LanguageVarName: String read FLanguageVarName write FLanguageVarName;
|
|
// Textmode determines whether we use InnerText or InnerHTML to set the text.
|
|
Property TextMode : TTextMode Read FTextMode Write FTextMode;
|
|
// Called when the language file is succesfully loaded.
|
|
Property OnLanguageLoaded : TLanguageLoadedEvent Read FOnLanguageLoaded Write FOnLanguageLoaded;
|
|
// Called when an error occurs during loading of a language file.
|
|
Property OnLanguageLoadError : TLanguageLoadErrorEvent Read FOnLanguageLoadError Write FOnLanguageLoadError;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses Types;
|
|
|
|
Resourcestring
|
|
SErrNoSuchLanguage = 'No such language available: %s.';
|
|
SErrFailedToLoadURL = 'Failed to load language file from URL: "%s".';
|
|
SErrUnknownError = 'Unknown error loading language from URL "%s": %s.';
|
|
SErrNoLanguageSelected = 'Cannot TranslateHTML, no language selected.';
|
|
|
|
|
|
procedure THTMLTagTranslator.DoLanguageLoaded;
|
|
begin
|
|
FloadingLanguage:='';
|
|
if Assigned(FOnLanguageLoaded) then
|
|
FOnLanguageLoaded(Self,FLanguage);
|
|
end;
|
|
|
|
function THTMLTagTranslator.GetDataTagName : String;
|
|
|
|
begin
|
|
Result:=FDataTagName;
|
|
if Result='' then
|
|
Result:=DefaultDataTagName;
|
|
end;
|
|
|
|
|
|
function THTMLTagTranslator.GetScope(const aScope: String): TJSObject;
|
|
|
|
begin
|
|
Result:=GetScope(FCurrLanguageStrings,aScope);
|
|
end;
|
|
|
|
function THTMLTagTranslator.GetScope(aRoot: TJSObject; const aScope: String
|
|
): TJSObject;
|
|
|
|
begin
|
|
if (aScope='') then
|
|
Result:=aRoot
|
|
else if Assigned(aRoot) and isObject(aRoot[aScope]) and Assigned(TJSObject(aRoot[aScope])) then
|
|
Result:=TJSObject(aRoot[aScope])
|
|
else
|
|
Result:=Nil;
|
|
end;
|
|
|
|
function THTMLTagTranslator.GetMessageByName(const aScope,aName: string): String;
|
|
|
|
begin
|
|
Result:=GetMessageByName(FCurrLanguageStrings,aScope,aName);
|
|
end;
|
|
|
|
function THTMLTagTranslator.GetMessageByName(aRoot : TJSObject; const aScope,aName: string): String;
|
|
|
|
begin
|
|
Result:=GetMessageByname(GetScope(aRoot,aScope),aName)
|
|
end;
|
|
|
|
function THTMLTagTranslator.GetSeeAlsoScope(aScope : TJSObject) : TJSObject;
|
|
|
|
begin
|
|
if Assigned(aScope) and (ContinueKey<>'') and IsString(aScope[ContinueKey]) then
|
|
Result:=GetScope(FCurrLanguageStrings,String(aScope[ContinueKey]))
|
|
else
|
|
Result:=Nil;
|
|
end;
|
|
|
|
function THTMLTagTranslator.GetMessageByName(aScope : TJSObject; aName: string): String;
|
|
|
|
Var
|
|
aScope2 : TJSObject;
|
|
|
|
begin
|
|
aScope2:=GetSeeAlsoScope(aScope);
|
|
Result:=GetMessageByName(aScope,aScope2,aName);
|
|
end;
|
|
|
|
function THTMLTagTranslator.GetMessageByName(aScope, aSeealsoScope: TJSObject; aName: string): String;
|
|
|
|
Var
|
|
DoDefault : Boolean;
|
|
|
|
begin
|
|
Result:='';
|
|
DoDefault:=Not Assigned(aScope);
|
|
if Assigned(aScope) then
|
|
begin
|
|
if IsString(aScope[aName]) then
|
|
Result:=String(aScope[aName])
|
|
else if Assigned(aSeeAlsoScope) then
|
|
begin
|
|
if IsString(aSeeAlsoScope[aName]) then
|
|
Result:=String(aSeeAlsoScope[aName])
|
|
else
|
|
DoDefault:=True;
|
|
end
|
|
else
|
|
DoDefault:=True;
|
|
end;
|
|
if DoDefault and Assigned(FDefaultScopeObj) and IsString(FDefaultScopeObj[aName]) then
|
|
Result:=String(FDefaultScopeObj[aName]);
|
|
end;
|
|
|
|
function THTMLTagTranslator.HasLanguage(aLanguage: String): Boolean;
|
|
begin
|
|
aLanguage:=LowerCase(aLanguage);
|
|
Result:=False;
|
|
if FLanguages=Nil then exit;
|
|
if (LanguageSource=lsMulti) or (LanguageSource=lsScoped) then
|
|
Result:=isObject(FLanguages[aLanguage])
|
|
else
|
|
Result:=(sameText(aLanguage,FLanguage))
|
|
end;
|
|
|
|
procedure THTMLTagTranslator.LoadLanguageFile(aLanguage: String);
|
|
|
|
var
|
|
URL : String;
|
|
|
|
Procedure DoLoadError(aMsg : String);
|
|
|
|
begin
|
|
If Assigned(FOnLanguageLoadError) then
|
|
FOnLanguageLoadError(Self,aLanguage,aMsg);
|
|
end;
|
|
|
|
function LoadLanguageJson(value : JSValue) : JSValue;
|
|
begin
|
|
Result:=True;
|
|
try
|
|
DoSetLanguageData(TJSJSON.parseObject(String(Value)),aLanguage);
|
|
except
|
|
on er : TJSError do
|
|
DoLoadError(Format(SErrUnknownError,[URL,er.message]));
|
|
on e : TJSObject do
|
|
DoLoadError(Format(SErrUnknownError,[URL,TJSJSON.stringify(E)]));
|
|
end;
|
|
DoLanguageLoaded;
|
|
end;
|
|
|
|
function doOK(response : JSValue) : JSValue;
|
|
|
|
var
|
|
Res : TJSResponse absolute response;
|
|
|
|
begin
|
|
Result:=Null;
|
|
If (Res.status<>200) then
|
|
begin
|
|
DoLoadError(Format(SErrUnknownError,[URL,res.StatusText]));
|
|
end
|
|
else
|
|
Res.text._then( @LoadLanguageJson );
|
|
end;
|
|
|
|
function doFail(response{%H-} : JSValue) : JSValue;
|
|
|
|
begin
|
|
Result:=Null;
|
|
DoLoadError(Format(SErrFailedToLoadURL,[URL]));
|
|
end;
|
|
|
|
begin
|
|
if LanguageSource = lsScoped then
|
|
DoSetLanguageData(TJSObject(window[LanguageVarName]),aLanguage)
|
|
else
|
|
begin
|
|
FloadingLanguage:=aLanguage;
|
|
if LanguageSource=lsMulti then
|
|
URL:=LanguageFileURL
|
|
else
|
|
URL:=Format(LanguageFileURL,[aLanguage]);
|
|
Window.Fetch(URl)._then(@DoOK).catch(@DoFail);
|
|
end;
|
|
end;
|
|
|
|
procedure THTMLTagTranslator.DoSetLanguageData(aData: TJSObject;
|
|
aLanguage: String);
|
|
|
|
Procedure DoLoadError(aMsg : String);
|
|
|
|
begin
|
|
If Assigned(FOnLanguageLoadError) then
|
|
FOnLanguageLoadError(Self,aLanguage,aMsg);
|
|
end;
|
|
|
|
var
|
|
aVal: JSValue;
|
|
|
|
begin
|
|
FLanguages:=aData;
|
|
if FLanguages=Nil then
|
|
exit;
|
|
if (LanguageSource=lsMulti) or (LanguageSource=lsScoped) then
|
|
FCurrLanguageStrings:=TJSObject(FLanguages[aLanguage])
|
|
else
|
|
FCurrLanguageStrings:=TJSObject(FLanguages);
|
|
if Not Assigned(FCurrLanguageStrings) then
|
|
DoLoadError(Format(SErrNoSuchLanguage,[aLanguage]));
|
|
FDefaultScopeObj:=Nil;
|
|
if DefaultScope<>'' then
|
|
begin
|
|
aVal:=FCurrLanguageStrings[DefaultScope];
|
|
if IsObject(aVal) then
|
|
FDefaultScopeObj:=TJSObject(aVal)
|
|
end;
|
|
FLanguage:=aLanguage;
|
|
end;
|
|
|
|
procedure THTMLTagTranslator.SetLanguage(const Value: String);
|
|
|
|
Var
|
|
NewLanguage : String;
|
|
|
|
begin
|
|
NewLanguage:=LowerCase(Value);
|
|
if NewLanguage=FLanguage then Exit;
|
|
if ((LanguageSource=lsSingle) or (LanguageSource=lsScoped)) and Assigned(FLanguages) then
|
|
begin
|
|
if Not Assigned(TJSObject(FLanguages[NewLanguage])) then
|
|
Raise EHTMLTagTranslator.CreateFmt(SErrNoSuchLanguage,[Value]);
|
|
FCurrLanguageStrings:=TJSObject(FLanguages[NewLanguage]);
|
|
FLanguage := NewLanguage;
|
|
end
|
|
else
|
|
LoadLanguageFile(NewLanguage);
|
|
end;
|
|
|
|
procedure THTMLTagTranslator.TranslateHTMLTag(aEl: TJSHTMLElement;
|
|
aScope: String);
|
|
|
|
Var
|
|
aScope1,aScope2 : TJSObject;
|
|
|
|
begin
|
|
aScope1:=GetScope(aScope);
|
|
aScope2:=GetSeeAlsoScope(aScope1);
|
|
TranslateHTMLTag(aEl,aScope1,aScope2);
|
|
end;
|
|
|
|
procedure THTMLTagTranslator.TranslateHTMLTag(aEl: TJSHTMLElement; aScope,
|
|
aSeealsoScope: TJSObject);
|
|
|
|
Var
|
|
Terms : TStringDynArray;
|
|
T,S,aAttr,aValue : String;
|
|
P : Integer;
|
|
|
|
begin
|
|
T:=String(aEl.Dataset.Properties[DataTagName]);
|
|
If T='' then
|
|
exit;
|
|
Terms:=T.Split(';');
|
|
For S in Terms do
|
|
begin
|
|
P:=Pos('-',S);
|
|
if P=0 then
|
|
P:=Length(S)+1;
|
|
aAttr:=Copy(S,P+1,Length(S)-P);
|
|
aValue:=GetMessageByName(aScope,aSeeAlsoScope,S);
|
|
if (aValue<>'') then
|
|
if aAttr='' then
|
|
begin
|
|
if TextMode=tmText then
|
|
aEl.InnerText:=aValue
|
|
else
|
|
aEl.InnerHTML:=aValue
|
|
end
|
|
else
|
|
aEl[aAttr]:=aValue;
|
|
end;
|
|
end;
|
|
|
|
procedure THTMLTagTranslator.TranslateBelowHTMLTag(aEl: TJSHTMLElement;
|
|
aScope: String);
|
|
|
|
Var
|
|
aScope1,aScope2 : TJSObject;
|
|
|
|
begin
|
|
aScope1:=GetScope(aScope);
|
|
aScope2:=GetSeeAlsoScope(aScope1);
|
|
TranslateBelowHTMLTag(aEl,aScope1,aScope2);
|
|
end;
|
|
|
|
procedure THTMLTagTranslator.TranslateBelowHTMLTag(aEl: TJSHTMLElement; aScope,
|
|
aSeealsoScope: TJSObject);
|
|
|
|
Var
|
|
NL : TJSNodeList;
|
|
TN : String;
|
|
E: TJSHTMLElement;
|
|
I : Integer;
|
|
|
|
begin
|
|
TN:=DataTagName;
|
|
if Not assigned(FCurrLanguageStrings) then
|
|
Raise EHTMLTagTranslator.Create(SErrNoLanguageSelected);
|
|
nl:=aEl.querySelectorAll('[data-'+TN+']');
|
|
for I:=0 to nl.length-1 do
|
|
begin
|
|
E:=TJSHTMLElement(nl.Nodes[i]);
|
|
TranslateHTMLTag(E,aScope,aSeealsoScope);
|
|
end;
|
|
end;
|
|
|
|
procedure THTMLTagTranslator.SetLanguageData(aData: TJSObject);
|
|
|
|
begin
|
|
DoSetLanguageData(aData,Language);
|
|
DoLanguageLoaded;
|
|
end;
|
|
|
|
|
|
end.
|