Multi language - best strategy / solution

Hi
I'm looking for your experience regarding the best approach to produce TMS Web app in 2 (or more) languages mainly based upon Delphi level than javascript
In VCL world I like solutions similar to TsiLang, but how do you process in TMS Web devs?
thanks

You might want to have a look at the TMSWeb_MultiLanguage demo. Basically, each Form has multiple associated HTML files. One for each language. You switch languages at runtime like this:

Application.Language := lGerman;

Hth, Walter

Thanks for
But in this case, it will be a mess for designer manage N templates per form for complex applications without taking profit of IDE level and components.
But it's a first approach... thanks

In another project, we read the language strings from a DB based on the Delphi controls name sitting on the form. It goes like this:

Type
 TSupportedLanguage = (slNone,slGerman,slEnglish,slFrench);
 TLanguageStringObject = Class
  LanguageString : String;
 End;

Const
 LanguageID : Array [TSupportedLanguage] of String = ('','DE','EN','FR');
 ControlTagNoLanguageCheck = 999;
 ControlTagShowByLanguage  = 998;

{---------------------------------------}

Function LanguageIDtoTSupportedLanguage(Const ID : String) : TSupportedLanguage;
Begin
 For Result := TSupportedLanguage(1) to High(TSupportedLanguage) do
  If ID=LanguageID[Result] then exit;
 Result := slNone;
End;

{---------------------------------------}

Procedure TDM.SetLanguagePack(LanguagePack: TJSArray);
// LanguagePack is a JS Key-Value array received from a REST call
Var
 LangItem : TJSObject;
 LangKey,
 LangStr  : String;
 LSO      : TLanguageStringObject;
 I        : Integer;
Begin
 Console.log(TrIn('TDM.SetLanguagePack'));
 Try

  If not assigned(CurrentLanguagePack) then
   Begin
    CurrentLanguagePack := TStringList.Create;
    CurrentLanguagePack.Duplicates := dupError;
    CurrentLanguagePack.Sorted := True;
    CurrentLanguagePack.CaseSensitive := False;
   End
  Else
   CurrentLanguagePack.Clear;

  For I := 0 to LanguagePack.Length-1 do
   Begin
    LangItem := TJSObject(LanguagePack[I]);
    LSO := TLanguageStringObject.Create;
    LangKey := String(LangItem['Key']);
    LangStr := String(LangItem['String']);
    LSO.LanguageString := LangStr;
    CurrentLanguagePack.AddObject(LangKey,LSO);
   End;

  MainForm.OnLanguageChange;

 Finally
  Console.log(TrOut('TDM.SetLanguagePack'));
 End;
End;

{---------------------------------------}

Procedure TBaseForm.OnLanguageChange;
Var
 I,Ind,
 ItemInd : Integer;
 Ctrl    : TWebControl;
 LangStr,
 ItemName,
 LangID  : String;
 e       : Exception;
Begin
 If CurrentLanguage=FFormLanguage then exit;

 Console.log(TrIn('TBaseForm.OnLanguageChange',[Self.ClassName]));
 Try
  Try
   // For every control on the form...
   For I := 0 to ControlCount-1 do
    Begin
     Ctrl := Controls[I];

     // Skip language checking if the control is not of an allowed class
     // or has the Tag set to ControlTagNoLanguageCheck (i.e. 999)
     If not (
       ((Ctrl is TWebLabel) or (Ctrl is TWebButton) or (Ctrl is TWebHTMLDiv) or (Ctrl is TWebComboBox))
       and (Ctrl.Tag<>ControlTagNoLanguageCheck)
      ) then continue;

     // If the controls Tag is set to ControlTagShowByLanguage (i.e. 998), then
     // the forms HTML file holds an element (tpically a DIV) for each language.
     // The IDs of those elements have to be named like this: <normalidname_DE>
     // or <normalidname_FR>, i.e. the language-id (e.g. DE, EN, FR) has to be
     // attached to the normal ID name, separated by an underscore. If an IDs
     // language-id matches the current language, than this element will be set
     // visible. All other elements will be set invisible.
     If Ctrl.Tag=ControlTagShowByLanguage then
      Begin
       LangID := RightStr(Ctrl.Name,2);
       Ctrl.Visible := (LanguageIDtoTSupportedLanguage(LangID)=CurrentLanguage);
       Continue;
      End;

     // Multiple items for a TWebComboBox have keys in DB like: <control.name>_nnn
     If Ctrl is TWebComboBox then
      Begin
       ItemInd := 1;
       ItemName := Ctrl.Name + Format('_%.3d',[ItemInd]);
       While CurrentLanguagePack.Find(ItemName,Ind) do
        Begin
         If ItemInd=1 then TWebComboBox(Ctrl).Clear;
         LangStr := TLanguageStringObject(CurrentLanguagePack.Objects[Ind]).LanguageString;
         TWebComboBox(Ctrl).AddItem(LangStr,Nil);
         ItemInd := ItemInd + 1;
         ItemName := Ctrl.Name + Format('_%.3d',[ItemInd]);
        End;
       If TWebComboBox(Ctrl).Items.Count>0 then
        TWebComboBox(Ctrl).ItemIndex := 0;
       Continue;
      End;

     // Search the controls name in the language pack string list
     If CurrentLanguagePack.Find(Ctrl.Name,Ind) then
      Begin
       // If found, set the string in the control
       LangStr := TLanguageStringObject(CurrentLanguagePack.Objects[Ind]).LanguageString;
       If Ctrl is TWebLabel then with Ctrl as TWebLabel do Caption := LangStr
       Else if Ctrl is TWebButton then with Ctrl as TWebButton do Caption := LangStr
       Else if Ctrl is TWebHTMLDiv then with Ctrl as TWebHTMLDiv do HTML.Text := LangStr
      End
     Else
      // If not found, leave a warning about a missing language string in the console
      Console.warn(Lg('TBaseForm.OnLanguageChange',['Missing: ',Self.ClassName,'.',Ctrl.Name]));
    End;
   FFormLanguage := CurrentLanguage;
  Except
   {$IFNDEF WIN32} ASM e = $e; END; {$ENDIF}
   Console.error(Err(e,'TBaseForm.OnLanguageChange'));
  End;
 Finally
  Console.log(TrOut('TBaseForm.OnLanguageChange',[Self.ClassName]));
 End;
End;

Hope it's useful, Walter

1 Like

Many thanks Walter
Excellent approach of problem, looks like first VCL solutions, decades ago.

Yes, that's me, a decades ago Turbo Pascal 3.0 guy...