用Delphi實現(xiàn)多語言界面
關鍵字:多語言界面,Delphi,國際化,本地化。
隨著Internet在全球的普及,一個軟件開發(fā)者,開發(fā)出來的產(chǎn)品可以隨意發(fā)布到全球各個角落,然而與此同時,開發(fā)出來的產(chǎn)品也面臨著一個新的問題:如何實現(xiàn)各種不同的語言界面,甚至根據(jù)最終用戶的操作系統(tǒng)的語言版本,自動更改語言界面?難道為每一個不同的語言編寫一個不同的版本?不,完全沒有必要。Delphi 5.0作為一個優(yōu)秀的快速RAD開發(fā)工具,可以很容易地實現(xiàn)國際化支持,因為Delphi 5.0內(nèi)置了對多語言界面的支持。
一個程序,如果需要不同的語言版本,那么應該有一下幾點需要注意的地方[]:
1. 必須允許你的程序代碼能夠處理好各種語言字符串,例如如果要中文化,必須能夠處理雙字節(jié)。
2. 你必須設計好你的程序界面,以便能夠使你的程序界面元素有足夠的空間顯示語言文字信息。一般說來,在50個字節(jié)以內(nèi)的英文單詞所表達的意思,用其他的語言來描述的話,長度要超過50字節(jié),但中文是一個例外。特別對于幾個字節(jié)的英文單詞,其他的語言的長度幾乎百分之百要超過英文的長度!因此,必須在控件中留出足夠的長度以便在更改語言之后,還能顯示全部的語言文字信息。
3. 你必須翻譯所有的資源。
本文將著重討論如何用Delphi 5.0實現(xiàn)多語言的支持和切換,界面設計和上述要求不在本文討論范圍之內(nèi)。
要為程序添加語言支持,只要在Delphi主菜單項Project下面選擇LanguagesàAdd…即可。點擊之后出現(xiàn)語言向?qū)?,讀者按照向?qū)нM行操作即可。向?qū)ЫY束之后,會生成一個工程組文件(BPG),最后出現(xiàn)Translation Manager,軟件開發(fā)者可以在這里翻譯所有語言的所有資源,包括字體、位置、文字等等。說明一下:你可以隨時隨地用Project下面的Languages子菜單的功能來添加、刪除、修改各種界面元素。
做完上述工作之后,我們現(xiàn)在就差切換語言的代碼了。為了切換語言,大家可以使用下面的一個單元[],單元中提供了兩個函數(shù),用來更換語言界面元素,其中LoadNewResourceModule是用來修改文字信息等等,ReinitializeForms用來重新刷新窗體和控件以保證同步。
///文件名:MaltiLan.pas
unit MaltiLan;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms;
procedure ReinitializeForms;
function LoadNewResourceModule(Locale: LCID): Longint;
implementation
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;
end.
測試程序窗體單元文件如下:
///單元文件名:unit1.pas
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus, MLanTool, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
Exit1: TMenuItem;
Language1: TMenuItem;
Chese1: TMenuItem;
English1: TMenuItem;
Button1: TButton;
Memo1: TMemo;
ListBox1: TListBox;
GroupBox1: TGroupBox;
Panel1: TPanel;
procedure Exit1Click(Sender: TObject);
procedure Chese1Click(Sender: TObject);
procedure English1Click(Sender: TObject);
Private
{ Private declarations }
Public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
const
ENGLISH = (SUBLANG_ENGLISH_US shl 10) or LANG_ENGLISH;
CHINESE = (SUBLANG_CHINESE_SIMPLIFIED shl 10) or LANG_CHINESE;
procedure TForm1.Exit1Click(Sender: TObject);
begin
Close;
end;
procedure TForm1.Chese1Click(Sender: TObject);
begin
if LoadNewResourceModule(CHINESE) <> 0 then
ReinitializeForms;
end;
procedure TForm1.English1Click(Sender: TObject);
begin
if LoadNewResourceModule(ENGLISH) <> 0 then
ReinitializeForms;
end;
end.
如果要自動切換語言,只要在FormCreate事件中添加如下代碼即可:
if LoadNewResourceModule(SysLocale.DefaultLCID) <> 0 then
ReinitializeForms;
說明一點:在程序完成的時候,你應該用Luanguages子菜單的Update Resources DLL功能更新所有的窗體和代碼,然后用Build All Project編譯所有的文件,這樣才能保證你的程序正常運行。
所有的源代碼可以到<http://kingron./soft/MultiLan.rar>下載。
后記:其實用INI文件也可以實現(xiàn)多語言界面的切換,好處是可以方便大家隨時添加不同的語言文件,但是,對于一個大型的程序來說,用INI是不現(xiàn)實的。用INI實現(xiàn)語言界面的程序,大家可以到<http://kingron./soft/winupx.rar>下載源代碼和測試程序。