Solved

Loading the base language in a multilanguage application

Posted on 2001-07-03
12
2,525 Views
Last Modified: 2012-06-21
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;




   
0
Comment
Question by:Probie
  • 5
  • 3
  • 2
  • +1
12 Comments
 
LVL 3

Expert Comment

by:karouri
Comment Utility
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..
0
 
LVL 3

Accepted Solution

by:
alzv earned 300 total points
Comment Utility
Try to call function LoadNewResourceModule with zero parameter:

function LoadBaseResource: LongInt;
begin
  Result := LoadNewResourceModule(0);
end;

I didn't test it, but I think it will work.

Also if you want you can load any resource module yourself by calling LoadNewResourceModule function with an appropriate parameter (look at the example, SwitchLanguage function; event handler for menu items OnClick events).

Best regards,
Alexey Zverev.
0
 
LVL 3

Expert Comment

by:karouri
Comment Utility
I think you are right, Alexey. I tested LoadNewResourceModule(0) and it worked.
0
 
LVL 3

Expert Comment

by:karouri
Comment Utility
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.
0
 
LVL 1

Author Comment

by:Probie
Comment Utility
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???
0
 
LVL 3

Expert Comment

by:alzv
Comment Utility
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.
0
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 
LVL 3

Expert Comment

by:karouri
Comment Utility
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?



0
 
LVL 8

Expert Comment

by:TOndrej
Comment Utility
> 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?
0
 
LVL 1

Author Comment

by:Probie
Comment Utility
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
0
 
LVL 8

Expert Comment

by:TOndrej
Comment Utility
> 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.
0
 
LVL 3

Expert Comment

by:karouri
Comment Utility
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?
0
 
LVL 1

Author Comment

by:Probie
Comment Utility
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?
0

Featured Post

Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

Join & Write a Comment

Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…
In this tutorial you'll learn about bandwidth monitoring with flows and packet sniffing with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're interested in additional methods for monitoring bandwidt…

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now