VCL has a built-in feature to use resource DLLs. This makes it possible to deploy the same application file (.exe or .dll) with different resource DLLs and still be able to run the application in the language needed. When a VCL application starts VCL looks for available resource DLLs from the same directory where the application file is located. If VCL can find a matching file it uses resources of the resource DLL instead of the original application file. For example if your application is Project1.exe it can have German and French resource DLLs: Project1.DE and Project1.FR. If you start the application on German Windows VCL will use resource data of Project1.DE instead of resource data of Project1.exe.
Sisulizer extends VCL's resource DLLs by providing classes to perform runtime language change. This makes it possible to start the application in one language and later change the language on run time as many times as possible. Sisulizer's runtime language change is very fast and flicker free. It does not require you to add any components on forms. You only have to use Sisulizer to create resource DLLs and include one or two Sisulizer units to your project.
To make Sisulizer to create localized resource DLLs for your VCL file right click the source name on the project tree and choose Properties. Check Resource DLL files check box.
By default Sisulizer adds all resources to the resource DLLs; even those that have not been localized. If you want to make you resource DLLs smaller uncheck Copy all resources. This will make Sisulizer add only those resource items that have been localized.
The method how VCL chooses the initial resource DLL is:
There is not way to disable VCL's build resource loading. If there is a matching resource DLL, VCL will load it. Fortunately there is a way to unload the loaded resource DLL. This makes is possible to disable using of resource DLL (if you want to do that). To disable use of resource DLL call DisableResourceDllUse function. The function must be called before applications creates any forms. A good place for that is the initialization part of main unit.
initialization DisableResourceDllUse; end.
Sisulizer contains functions that let you change the language of VCL application on run time. <sldir>\VCL contains the Delphi units. LaResource.pas, LaTranslator.pas and LaDialog.pas are the main units. LaResource.pas contains the functions for accessing resource data LaTranslator.pas contains class that translates forms, and LaDialog.pas contains a dialog that shows possible resource DLL language. User can select the one he or she likes to turn on. Use LaDialog.SelectResourceLocale to show a language dialog and turn on the new language. The functions show a dialog where the user can select the language and after selection it turns on the new language by loading the new resource DLL and reloading existing forms. The functions work with any Delphi 5 and later. In addition there are several La*Module.pas units that complement TLaTranslator providing translation of complex and/or binary properties of certain components.
Adding runtime language change is very easy. At minimum it only requires that you make Sisulizer to create resource DLLs instead of localized files and you add two lines of code into your application (shown in italic typeface).
uses LaDialog; procedure TForm1.Button1Click(Sender: TObject); begin SelectResourceLocale('EN'); end;
The 'EN' parameter in function specifies the original language of the application. The original language is the language that you used in the forms and menu when you wrote the application. When you run the application and click Button1 button the following dialog will be shown.
You select a new language and press OK. It is simple like that! If you want to use your own user interface to show available languages and way to choose a new one you can use the functions in LaResource.pas to get list of available languages and finally set a new language active.
Form translation requires little bit more explanation. LaDialog.SelectResourceLocale calls LaResource.SetNewResourceFile function that performs the resource loading. It load the resource DLL of the selected language (e.g. Project1.DE). After loading new resources SelectResourceLocale calls TLaTranslator.TranslateForms function that translates all existing forms. It is very important to understand what happens here. Sisulizer reads the form data from localized resource DLL and assigns all properties of every component on every form. This means that after the process all components and properties of all forms have the values that the localized resource DLL contains. If you application contains any dynamic property (e.g. value set on run time) you have to update them after language change. If you used OnCreate to initialize the dynamic properties call the event again. Note that now the resourcestring values contain value in different language so you can use them in the same way as in your original code. Let's have an example.
<sldir>\VCL\DelphiWin\RuntimeChange contains a simple application using resource DLLs. FormCreate events initializes dynamic property values.
procedure TForm1.FormCreate(Sender: TObject); resourcestring SMessage = 'Hello World!'; begin Label1.Caption := SMessage; end;
When application has been started the form no longer is in the design time state. The design time state is the state that exist in the DFM file (e.g. form data). The caption of the label have been changed on run time. When a language change occurs Sisulizer automatically updates the static properties (e.g. those that exist on the form data) but does not change dynamic properties. This is why you have to set them again.
The following code changes the language and translates dynamic properties.
procedure TForm1.Button1Click(Sender: TObject); begin if SelectResourceLocale('EN') then FormCreate(Self); end;
SelectResourceLocale changes the language and FormCreate resets the dynamic properties using the new language. You just have to remember to resets any dynamic properties after language change. This might seem a bit complicated but in most cases your language change routine is on the main menu or on the main form. In a situation like that you only have one form existing at that moment so you only have to take care if resetting dynamic properties of that form. All forms that you create after language change will automatically use the new language.
A final note. Delphi IDE will add form creation code in the initialization section of the application for each form that you add to the project. You better keep only main form there and create all other forms yourself at the moment when they are first needed.
begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end.
<sldir>\VCL contains the full source code of all Sisulizer functions and several sample applications showing how to make both localized and multilingual VCL applications.
TLaTranslator can translate all basic properties such as string, integer, color, etc. However it can not translate binary properties and some complex properties of certain components. In order to translate also those properties TLaTranslator uses modules. They are add-ons to TLaTranslator and provide translation of binary and complex properties. There are several module classes, one for each type of component. The following table contains the module components.
|TLaStringsModule||LaTranslator||Translates properties whose type is TStrings or derived.|
|TLaImageModule||LaImageModule||Translates images such as Picture property of TImage control.|
|TLaListViewModule||LaListViewModule||Translates list items of TListView control and derived.|
|TLaTreeViewModule||LaTreeViewModule||Translates tree nodes of TTreeView control and derived.|
|TLaTntModule||LaTntModule||Translates properties whose type is TTntStrings or derived.|
To take module in use just add its unit to the uses clause somewhere in the application. The following piece of code implements runtime language switch and adds module for TTreeView component.
implementation uses LaDialog, LaTreeViewModule; procedure TForm1.Button1Click(Sender: TObject); begin SelectResourceLocale('EN'); end; end.
You can easily make your own module if needed for your own components or 3rd party components that you use. See the source code of existing modules to learn how to write them. If you create your own module you have to register it. Call LaModules.Register function in LaModule.pas. A good place for that is the initialization part of the module unit.
type TMyModule = class(TLaModule) ... initialization LaModules.Register(TMyModule); end.
There are several ways you can control what forms, components and properties are translated.
This is the easiest way to control what to translate because it is done on Sisulizer application and not Sisulizer VCL units. You can exclude any resource type or any data type from your project. In that case Sisulizer does not modify those resources or data types when creating localized resource DLLs.
LaTranslator.pas unit contains LaEnabledProperties variable.
var LaEnabledProperties: TTypeKinds;
It is a set that stores the types that TLaTranslator should translate. By default it is an empty set that means that every property is translated no matter of its type. Because this includes Left, Top, Width, Height and Picture properties it might be that during the runtime language change you will see some flickering. In such case you can enable only string types. A good place for that is the initialization part of main unit.
initialization LaEnabledProperties := [tkString, tkLString, tkWString]; end.
In addition of giving you a flicker free language change it speeds up translation time by. However if you disable translation of integer properties TLaTranslator does not update Left, Top, Width and Height properties. This will disable any layout change done by your translator (e.g. in the case where translation did not fit to original space).
LaTranslator units contains following events.
var LaBeforeTranslate: TLaBeforeTranslateEvent; LaAfterTranslate: TLaAfterTranslateEvent;
TLaTranslator call LaBeforeTranslate event every time before assigning a value to a property. The event contains cancel parameter. It the event set this to True TLaTranslator does not set the property value but remains it intact. The following lines contains a sample event that disables translations of Label2 component.
procedure BeforeTranslate( host: TComponent; obj: TObject; propertyInfo: PPropInfo; const currentValue: Variant; var newValue: Variant; var cancel: Boolean); begin cancel := obj = Form1.Label2;
If you want to modify the value to be set change the value in the newValue parameter. If you want to disable translation of the property set cancel to True. You register the event my assigning it to global LaBeforeTranslate variable. A good place for that is the initialization part of main unit.
initialization LaBeforeTranslate := BeforeTranslate; end.
TLaTranslator calls LaAfterTranslate event every time after it has assigned a value to a property. Because these events are called before and after every property the events are called several even thousands of items. This is why it is important to keep the event functions as fast as possible to prevent runtime language change to slow down too much. Of course if you do not use events at all (e.g. do not assign the event variables) there is no speed penalty.