首页  编辑  

多语言界面的实现

Tags: /超级猛料/VCL/用户接口(界面)/   Date Created:

Delphi 实现多语言界面

  关键字:多语言界面, Delphi ,国际化,本地化。

  随着 Internet 在全球的普及,一个软件开发者,开发出来的产品可以随意发布到全球各个角落,然而与此同时,开发出来的产品也面临着一个新的问题:如何实现各种不同的语言界面,甚至根据最终用户的操作系统的语言版本,自动更改语言界面?难道为每一个不同的语言编写一个不同的版本?不,完全没有必要。 Delphi 5.0 作为一个优秀的快速 RAD 开发工具,可以很容易地实现国际化支持,因为 Delphi 5.0 内置了对多语言界面的支持。

  一个程序,如果需要不同的语言版本,那么应该有一下几点需要注意的地方 []

1.         必须允许你的程序代码能够处理好各种语言字符串,例如如果要中文化,必须能够处理双字节。

2.         你必须设计好你的程序界面,以便能够使你的程序界面元素有足够的空间显示语言文字信息。一般说来,在 50 个字节以内的英文单词所表达的意思,用其他的语言来描述的话,长度要超过 50 字节,但中文是一个例外。特别对于几个字节的英文单词,其他的语言的长度几乎百分之百要超过英文的长度!因此,必须在控件中留出足够的长度以便在更改语言之后,还能显示全部的语言文字信息。

3.         你必须翻译所有的资源。

本文将着重讨论如何用 Delphi 5.0 实现多语言的支持和切换,界面设计和上述要求不在本文讨论范围之内。

要为程序添加语言支持,只要在 Delphi 主菜单项 Project 下面选择 Languages Add… 即可。点击之后出现语言向导,读者按照向导进行操作即可。向导结束之后,会生成一个工程组文件 (BPG) ,最后出现 Translation Manager ,软件开发者可以在这里翻译所有语言的所有资源,包括字体、位置、文字等等。说明一下:你可以随时随地用 Project 下面的 Languages 子菜单的功能来添加、删除、修改各种界面元素。

做完上述工作之后,我们现在就差切换语言的代码了。为了切换语言,大家可以使用下面的一个单元 [] ,单元中提供了两个函数,用来更换语言界面元素,其中 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.myetang.com/soft/MultiLan.rar> 下载。

后记:其实用 INI 文件也可以实现多语言界面的切换,好处是可以方便大家随时添加不同的语言文件,但是,对于一个大型的程序来说,用 INI 是不现实的。用 INI 实现语言界面的程序,大家可以到 <http://kingron.myetang.com/soft/winupx.rar> 下载源代码和测试程序。

MultiLan.rar (55.5KB)