Link to home
Start Free TrialLog in
Avatar of Probie
ProbieFlag for Sweden

asked on

Loading the base language in a multilanguage application

I am writing a class that makes it possible to select language dependent resource files (language strings) in runtime looking on the example shipped with Delphi in the REInit.pas file under "DELPHI5\Demos\RichEdit".

I have no problem with selecting/using a different language then the one used with Windows (Delphi always selects the resource file that is compatible with your current windows language) but what if the user wants to turn back to the base language during runtime?

The base language is stored in the resource data in the EXE file and can not be loaded the same way that REInit.pas does with external resource files.

This becomes a problem if the application is made for English and runs on a (for example) German windows version. Then Delphi loads the german resource file upon start, and if the user still wants English on the German system there is no easy way to go back.

I know that there is a registry key that can be set to force a multilanguage application to use a certain language, but I think that is a poor solution and there must be a better way???

So my questions are:

1. Can the automatically language selection that delphi does on startup be disabled in some way?

2. How can I load back the base language resource data?
Can anyone supply a code example on how that could fit in into the REInit.pas routines like a function:
function LoadBaseLanguageResource(EXEName : string) : boolean;

---[REInit.pas]-------------------------------------------

type TAsInheritedReader = class(TReader)
     public
      procedure ReadPrefix(var Flags: TFilerFlags; var AChildPos: Integer); override;
     end;

procedure TAsInheritedReader.ReadPrefix(var Flags: TFilerFlags; var AChildPos: Integer);
begin
  inherited ReadPrefix(Flags, AChildPos);
  Include(Flags, ffInherited);
end;

function SetResourceHInstance(NewInstance: Longint): Longint;
var CurModule: PLibModule;
begin
 CurModule := LibModuleList;
 Result := 0;
 while CurModule <> nil do begin
  if CurModule.Instance = HInstance then begin
   if CurModule.ResInstance <> CurModule.Instance then FreeLibrary(CurModule.ResInstance);
   CurModule.ResInstance := NewInstance;
   Result := NewInstance;
   Exit;
  end;
  CurModule := CurModule.Next;
 end;
end;

function LoadNewResourceModule(Locale: LCID): Longint;
var
  FileName: array [0..260] of char;
  P: PChar;
  LocaleName: array[0..4] of Char;
  NewInst: Longint;
begin
 GetModuleFileName(HInstance, FileName, SizeOf(FileName));
 GetLocaleInfo(Locale, LOCALE_SABBREVLANGNAME, LocaleName, SizeOf(LocaleName));
 P := PChar(@FileName) + lstrlen(FileName);
 while (P^ <> '.') and (P <> @FileName) do Dec(P);
 NewInst := 0;
 Result := 0;
 if P <> @FileName then begin
  Inc(P);
  if LocaleName[0] <> #0 then begin
   // Then look for a potential language/country translation
   lstrcpy(P, LocaleName);
   NewInst := LoadLibraryEx(FileName, 0, LOAD_LIBRARY_AS_DATAFILE);
   if NewInst = 0 then  begin
    // Finally look for a language only translation
    LocaleName[2] := #0;
    lstrcpy(P, LocaleName);
    NewInst := LoadLibraryEx(FileName, 0, LOAD_LIBRARY_AS_DATAFILE);
   end;
  end;
 end;
 if NewInst <> 0 then Result := SetResourceHInstance(NewInst)
end;

function InternalReloadComponentRes(const ResName: string; HInst: THandle; var Instance: TComponent): Boolean;
var HRsrc: THandle;
    ResStream: TResourceStream;
    AsInheritedReader: TAsInheritedReader;
begin                   { avoid possible EResNotFound exception }
 if HInst = 0 then HInst := HInstance;
 HRsrc := FindResource(HInst, PChar(ResName), RT_RCDATA);
 Result := HRsrc <> 0;
 if not Result then Exit;
 ResStream := TResourceStream.Create(HInst, ResName, RT_RCDATA);
 try
  AsInheritedReader := TAsInheritedReader.Create(ResStream, 4096);
  try
   Instance := AsInheritedReader.ReadRootComponent(Instance);
  finally
   AsInheritedReader.Free;
  end;
 finally
  ResStream.Free;
 end;
 Result := True;
end;

function ReloadInheritedComponent(Instance: TComponent; RootAncestor: TClass): Boolean;
 function InitComponent(ClassType: TClass): Boolean;
 begin
   Result := False;
   if (ClassType = TComponent) or (ClassType = RootAncestor) then Exit;
   Result := InitComponent(ClassType.ClassParent);
   Result := InternalReloadComponentRes(ClassType.ClassName, FindResourceHInstance(
     FindClassHInstance(ClassType)), Instance) or Result;
 end;
begin
  Result := InitComponent(Instance.ClassType);
end;

procedure ReinitializeForms;
var
  Count: Integer;
  I: Integer;
  Form: TForm;
begin
 Count := Screen.FormCount;
 for I := 0 to Count - 1 do begin
  Form := Screen.Forms[I];
  ReloadInheritedComponent(Form, TForm);
 end;
end;




   
Avatar of karouri
karouri

The easiest way I found is to implement a resource dll for the default language. It is very simple, you usually don't have to edit it as there is no transliteration from the actual form's data. And the Delphi help states that the resource dll for the language is used even if it's the default, so the following will work:

const

  ENGLISH = (SUBLANG_ENGLISH_US shl 10) or LANG_ENGLISH;
  ARABIC = (SUBLANG_ARABIC_SAUDI_ARABIA shl 10) or LANG_ARABIC;

procedure TForm1.English1Click(Sender: TObject);
begin
  (Sender as TMenuItem).Checked:=true;
  if Sender = Arabic1 then
  begin
    if LoadNewResourceModule(ARABIC) <> 0 then
      ReinitializeForms;
  end
  else if Sender = English1 then
  begin
    if LoadNewResourceModule(ENGLISH) <> 0 then
      ReinitializeForms;
  end;

end;

// The default language is Arabic in my machine..
ASKER CERTIFIED SOLUTION
Avatar of alzv
alzv

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I think you are right, Alexey. I tested LoadNewResourceModule(0) and it worked.
To be more specific, the default language can be specified by a constant as defined in the API help..
----------
The following three combinations of usPrimaryLanguage and usSubLanguage have special meaning:

Primary language ID     Sublanguage ID     Meaning
LANG_NEUTRAL     SUBLANG_NEUTRAL     Language neutral
LANG_NEUTRAL     SUBLANG_DEFAULT     User default language
LANG_NEUTRAL     SUBLANG_SYS_DEFAULT     System default language
----------
So,
const
  DEFAULT = (SUBLANG_DEFAULT shl 10) or LANG_NEUTRAL;
will work. I tested it.
Avatar of Probie

ASKER

But I guess that it needs more code then LoadNewResourceModule(0) to load the base langauge???

Does anyone know how to disable the autoselection of resource files in a delphi application???
Hello, Probie.

> But I guess that it needs more code then LoadNewResourceModule(0) to load the base langauge???

No, not more code is needed to load base language.

>Does anyone know how to disable the autoselection of resource files in a delphi application???

Here is a form for selecting interface language from one of my projects. Here there is three buttons for choosing between English, Spanish and Catalan languages.

unit ufrmLogo;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Buttons, ExtCtrls, uReinit;

type
  TfrmLogo = class(TForm)
    Image1: TImage;
    btnEnglish: TButton;
    btnCastellano: TButton;
    btnCatalan: TButton;
    procedure FormShow(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure btnLangClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure HideButtons(Value: Boolean = False);
  end;

implementation

{$R *.DFM}

const
  ENGLISH   = (SUBLANG_ENGLISH_US shl 10) or LANG_ENGLISH;
  FRENCH    = (SUBLANG_FRENCH shl 10) or LANG_FRENCH;
  GERMAN    = (SUBLANG_GERMAN shl 10) or LANG_GERMAN;
  CASTILIAN = (SUBLANG_SPANISH shl 10) or LANG_SPANISH;
  CATALAN   = LANG_CATALAN;

procedure TfrmLogo.FormShow(Sender: TObject);
begin
  btnCastellano.Visible := fsModal in FormState;
  btnCatalan.Visible := fsModal in FormState;
  btnEnglish.Visible := fsModal in FormState;
end;

procedure TfrmLogo.FormCreate(Sender: TObject);
begin
  btnEnglish.Tag := ENGLISH;
  btnCastellano.Tag := CASTILIAN;
  btnCatalan.Tag := CATALAN;
end;

(* OnClick event handler for language buttons *)
procedure TfrmLogo.btnLangClick(Sender: TObject);
begin
  LoadNewResourceModule(TComponent(Sender).Tag);
  ModalResult := 1;
end;

procedure TfrmLogo.HideButtons(Value: Boolean);
begin
  btnCastellano.Visible := Value;
  btnCatalan.Visible := Value;
  btnEnglish.Visible := Value;
end;

end.
Hi
>But I guess that it needs more code then LoadNewResourceModule(0) to load the base langauge???
Well ,you don't. I tested it. (From Alexey, adapted)

>Does anyone know how to disable the autoselection of resource files in a delphi application???
I didn't get the question..

Hi Alexey,

>procedure TfrmLogo.btnLangClick(Sender: TObject);
>begin
> LoadNewResourceModule(TComponent(Sender).Tag);
> ModalResult := 1;
>end;
I thought you need ReInitializeForms;
By the way have you customized ReInit or something to use uReInit?



> The base language is stored in the resource data in the EXE file and can not be loaded the same way
that REInit.pas does with external resource files.

But it can, you simply use SetResourceHInstance(HInstance), what's the problem?
Avatar of Probie

ASKER

Ok, LoadNewResourceModule(0) or LoadNewResourceModule(HInstance); does the trick. So Alzy gets the points since he was first with that.

The question about how to disable Delphis automaticaly selection/load of resource files still remains.

Thanks to you all!

/Probie
> how to disable Delphis automaticaly selection/load of resource files
The resource DLLs are loaded based on the DLLs' file name and extension.
Perhaps you could use different DLL file names or extensions for the resource DLLs so they are not found at app's startup. Then you can load them dynamically at runtime as needed.
What do you mean by autoselection? Choice of the language at startup of the program? Or is it a question of memory, loading all DLLs at once?
Avatar of Probie

ASKER

Nah, if you create an application with multiple languages (one external resourcefile for each language), Delphi puts code so that the application automaticaly selectes a external resource file depending on your locale settings in windows upon application startup. I want to avoid this.

One way to avoid this is to set a registry value in /Software/Borland/Locales, forcing the language I want, but that is a poor solution...

What part in the code reads this registry value and how can I avoid that this happpens?