Inno setup i18n for custom panel - internationalization

I made a custom panel for Inno-Setup and i want internationalization for this.
Can i use the *.isl files to add my translation keys or must i use the [custommessages]? And how can i access the keys in the [code] section.
Non of the inno-setup examples using the i18n.
thx
Tom

1. Can I modify the isl localization files ?
It's upon you, if you modify standard *.isl files, or create your own modified ones. For sure keep in mind, that if you modify the standard ones, they might get updated by a new version of Inno Setup you install. That might be the reason why many people suggests to only create entries in the [CustomMessages] section.
But you can of course create a separate language file which you'll merge with each Inno Setup update, or even better, like Miral suggests, specify your custom messages in your own *.isl file, and then in the MessagesFile parameter of the [Languages] section specify that file at the end of the comma separated list of files:
[Languages]
Name: "en"; MessagesFile: "compiler:Default.isl,compiler:YourEnMessages.isl"
Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl,compiler:YourNlMessages.isl"
As the reference states for the MessagesFile parameter:
When multiple files are specified, they are read in the order they are
specified, thus the last message file overrides any messages in
previous files.
So if you make only *.isl file(s) with only [CustomMessages] section and specify them in the script the above way, you won't break anything and you'll get the separate reusable language file(s). Structure of such custom *.isl file might look eaxctly like the [CustomMessages] section:
[CustomMessages]
SomeCustomKey=Some custom value
...
Making your own language files might be better for you if you're going to reuse those custom messages in many setups.
2. How can I access custom messages from the [Code] section ?
By using CustomMessage function. For instance this way:
...
[CustomMessages]
; the following key value pair can be moved to the *.isl file into the same
; named file section, if needed; as a downside of doing that you'll need to
; keep track of changes if you update Inno Setup itself
SomeCustomKey=Some custom value
[Code]
procedure InitializeWizard;
var
S: string;
begin
S := CustomMessage('SomeCustomKey');
MsgBox(S, mbInformation, MB_OK);
end;

Answer provided by #TLama is really usefull. I faced with an additional problem, which was related to use custom messages with params.
To define custom messages:
Messages may take arguments, from %1 up to %9. You can rearrange the order of the arguments (i.e. move the %2 before a %1) and also duplicate arguments if needed (i.e. "%1 ... %1 %2"). On messages with arguments, use two consecutive "%" characters to embed a single "%". "%n" creates a line break.
For example:
[CustomMessages]
...
NameAndVersion=%1 version %2
...
And then, to use it in code section, simply use FmtMessage function together with CustomMessage function:
Example:
S := FmtMessage(CustomMessage('NameAndVersion'), ['My Program', '1.0']);
// S = 'My Program version 1.0'

Related

Lazarus localization / multiple projects

I've got some Delphi experience and I'm trying to build a project with Lazarus, which is completely new for me.
I think, I've read all information available about Lazarus,Translating/Internationalization/Localization, but I was unable to find what I really want.
I've been working in a project with 3 or 4 EXEcutables, that share between them, the same database the same Interface (form inheritance), common utils, settings, and common and related strings.
I don't want to have 3 or 4 .po files with different names (one for each EXE) , with the same common strings shared between them.
Is there a solution for that?
probably force ONE "gobal.po" for all Apps? how?
do I have to create my own system to localize \ centralize all strings in ONE file?
any suggestions?
thanks for any help.
regards,
Use a shared language unit into which you put all the strings of all projects. Create a package into which you add this shared strings unit. Turn i18n on for this package. Compile the package and you'll get the shared po file in the languages folder.
For every project needing the resource strings, you must add this package as a requirement - the easiest way to do this is in the package editor and to select "Use" / "Add to project".
Don't turn on i18n for the projects because this would add the project po files to the languages folder.
In order to use translated strings in each project, add the unit "Translations" to uses and call
TranslateUnitResourceStrings(
StringsUnit,
Format('languages\%s.%s.po', [StringsUnit, ALang])
);
Here StringsUnit is the name of the shared unit with all the collected resource strings. ALang is the language symbol to be used, e.g. 'de' for German, 'fr' for French etc.
And now the hard part... Since i18n is off, the strings assigned to captions of components are not automatically put into the po file. So, you leave the component captions alone, create a resource string for each one (in the 'stringsunit', of course), and then, at runtime, assign the captions to the resourcestring. Maybe something like this:
procedure TForm1.FormCreate(Sender: TObject);
begin
SetCaptions;
end;
procedure TForm1.SetCaptions;
begin
Caption := RSTitleOfForm1; // resource string for form title
Label1.Caption := RSYDateLabel; // label caption showing "Date:"
// etc.
end;
If the user selects a new language, for example from a combobox with all the available translations, you must call TranslateUnitResourceStrings and then SetCaptions for each existing form (in case of dynamically created forms, SetCaptions will be called automatically since it is in the FormCreate event).

Lazarus messagebox: how to localize buttons

I want to localize buttons of MesssageBox, MessageDlg, InputQuery, etc.
I see resourcestring exist for this in lclstrconsts: "OK"/"Cancel".... but I want to set button strings using asssignments.
How to do it?
I need it on Win32. I see that German/Rus OS shows En buttons.
That's how it is done for MessageDlg: http://delphi.xcjc.net/viewthread.php?tid=47562.
Variables with rs prefix should be defined.
Other components could be localized the same way.
There are several articles in the wiki about localization:
Translating/Internationalization/Localization
In short:
Set "Enable i18n" at the project options -> i18n, set PO Output Directory (locale for example)
Recompile project
Copy created yourproject.po file to yourproject.de.po, yourproject.it.po and so on for desired languages into the same directory.
Copy to locale directory of your project *.po files from lazarus/lcl/languages to translate LCL.
Add unit LCLTranslator to the uses clause of your main form.
Call SetDefaultLang('it'); somewhere to set desired translation (Italian in this example). Check is all Ok by inspecting GetDefaultLang function result.
Note that (3) is required.
You may find ready to use POs in other thirdparty libraries if any.

loading macro files in a directory, transparently

I am looking for a way to separate the repetitive html codes from web pages, and for this I am planning to use the macro functionality. The problem here is for every macro I need to put this macro in a file, or put some of them in a file and include this in the template file.
What I need is to include once just the directory name something like
<#import "/tags/widgetDirectory" as widgets />
here the /tags/widgetDirectory is a directory , and every files here can be seen as a macro defined.
when I need to insert a code part from a file from this directory lets say slide.ftl I will just use
<#widgets.slider />
the system will check for slider.ftl in the /tags/widgetDirectory directory . here the slider.ftl can have <#macro> as first and as last line , or these can transparently added and system can load it as a macro
this will easy my designer work.
Maybe there is better way for doing this kind of widgets/components based web design ?
best regards,
This feature (importing directories) is something that's planned for FM actually... but it won't happen anytime soon. But I guess it can be solved fairly well with a hack right now. Instead of #import, use your own TemplateMethodModelEx, that you could use like <#assign widgets = importDirectory('/tags/widgetDirectory') >. This will return a TemplateHashModel that's also implemented by you and is bound to the directory path. When an item of that hash is get, it uses Environment.getCurrentEnvironment().include. The included file is expected to create a macro with name __main or something. So then you get that variable with Environment.getCurrentNamespace().get("__main") and return it as the result of the hash lookup. Of course this hash should also maintain a cache, so that if the same item is get twice, it wont include the template for the second time, just return the macro extracted earlier. This can be developer further, so that if the include file didn't define __main, then it's supposed that it prints directly to the output, and so it will be included again, when the "tag" is called again.

How can I resolve MSI paths in VBScript?

I am looking for resolving the paths of files in an MSI with or without installing (whichever is faster) from outside an MSI, using VBScript.
I found a similar query using C# instead and Christpher had provided a solution, below: How can I resolve MSI paths in C#?
I am going through the very same pain now but is there anyway to achieve this using WindowsInstaller object in VBScript, rather than go with endless queries through SQL Tables of MSI back and forth to achieve the same. Though any direction would be welcoming as I have tried tested whatever I can with very limited success.
yes there is a solution without installing the msi and using vbscript.
there is a very good example in the Windows Installer SDK called "WiFilVer.vbs"
using that example i've thrown together an quick example script that does exactly what you need.
set installer = CreateObject("WindowsInstaller.Installer")
const READONLY = 0
set db = installer.OpenDataBase("<FULL PATH TO YOUR MSI>", READONLY)
set session = installer.OpenPackage(db, READONLY)
session.DoAction("CostInitialize")
session.DoAction("CostFinalize")
set view = db.OpenView("SELECT File, Directory_, FileName, Component_, Component FROM File,Component WHERE Component=Component_ ORDER BY Directory_")
view.Execute
set record = view.Fetch
do until record is nothing
file = record.StringData(1)
directoryName = record.StringData(2)
fileName = record.StringData(3)
if instr(fileName, "|") then fileName = split(fileName, "|")(1)
wsh.echo(session.TargetPath(directoryName) & fileName)
set record = view.Fetch
loop
just add the path to your MSI file.
tell me if you need a more detailed answer. i will have some more time to answer this in detail this evening.
EDIT the promised background (and why i need to call ConstFinalize)
naveen actually MSDN was the only resource that can give an definitive answer on this, but you need to know where and how to look since windows installer ist IMHO a pretty complex topic.
I really recommend a mix of the msdn installer function reference, the database reference, and the examples from the windows installer SDK (sorry couldn't find a download link, i think its somewhere hidden in the like 3GB windows SDK)
first you need general knowledge of MSIs:
an MSI is actually a relational database.
Everything is stored in tables that relate to each other.
(actually not everything, but i will try to keep it simple ;))
This database is interpreted by the Windows Installer,
this creates a 'Session'
also some parts are dynamically resolved, depending on the system you install the msi on,
like 'special' folders similar to environment variables.
E.g. msi has a "ProgramFilesFolder", where windows generally has %ProgramFiles%.
All dynamic stuff only exists in the Installer session, not the database itself.
In your case there are 3 tables you need to look at, take care of the relations and resolve them.
the 'File' table contains all Files, the 'Component' table tells you which file goes into which directory and the 'Directory' table contains all information about the filesystem structure.
Using a SQL Query i could link the Component and File table to find the directory name (or primary key in database jargon).
But the directory table has relations in itself, its structured like a tree.
take a look at this example directory table (taken from the instEd MSI)
The columns are Directory, Directory_Parent and DefaultDir
InstEdAllUseAppDat InstEdAppData InstEd
INSTALLDIR InstEdPF InstEd
CUBDIR INSTALLDIR hkyb3vcm|Validation
InstEdAppData CommonAppDataFolder instedit.com
CommonAppDataFolder TARGETDIR .
TARGETDIR SourceDir
InstEdPF ProgramFilesFolder instedit.com
ProgramFilesFolder TARGETDIR .
ProgramMenuFolder TARGETDIR .
SendToFolder TARGETDIR .
WindowsFolder_x86_VC.1DEE2A86_2F57_3629_8107_A71DBB4DBED2 TARGETDIR Win
SystemFolder_x86_VC.1DEE2A86_2F57_3629_8107_A71DBB4DBED2 WindowsFolder_x86_VC.1DEE2A86_2F57_3629_8107_A71DBB4DBED2 System
The directory_parent links it to a directory. the DefaultDir contains the actual name.
You could now resolve the tree by yourself and replace all special folders(which in a vbscript would be very tedious)...
...or let the windows installer handle that (just like when installing a msi).
now i have to introduce a new thing: Actions (and Sequences):
when running (installing, removing, repairing) an msi a defined list of actions is performed.
some actions just collect information, some change the actual database.
there are list of actions (called sequences) for various things a msi can do,
like one sequence for installing (called InstallExecuteSequence), one for collecting information from the user (the UI of the MSI: InstallUISequence) or one for adminpoint installations(AdminExecuteSequence).
in our case we don't want to run a whole sequence (which could alter the system or just take to long),
luckily the windows installer lets us run single actions without running a whole sequence.
reading the reference of the directory table on MSDN (the remarks section) you can see which action you need:
Directory resolution is performed during the CostFinalize action
so putting all this together the script is easier to read
* open the msi file
* 'parse' it (providing the session)
* query component and file table
* run the CostFinalize action to resolve directory table (without running the whole MSI)
* get the resolved path with the targetPath function
btw i found the targetPath function by browsing the Installer Reference on MSDN
also i just noticed that CostInitialize is not required. its only required if you want to get the sourcePath of a file.
I hope this makes everything clearer, its very hard to explain since it took me like half a year to understand it myself ;)
And regarding PhilmEs answer:
Yes there are more influences to the resolution of the directory table, like custom actions.
keeping that in mind also the administrative installation might result in different directorys (eg. because different sequence might hold different custom actions).
Components have conditions so maybe a file is not installed at all.
Im pretty sure InstEd doesnt take custom actions into account either.
So yes, there is no 100% solution. Maybe a mix of everything is necessary.
The script given by weberik (deriven from MS SDK VB code) is very good, because it makes it easy to analyse the directory table without an own algorithm (which is a mid-size effort to do it in a loop or with a recursion algorithm).
But it gives not a 100% perfect view for all files, see below.
The method of the script is semi-dynamic (can be extended by other actions), but in effect it gives only the static directory structure, similar to a default administrative install or advanced MSI viewers.
Normally this is enough and what we want.
But be aware, that this is not the 100% solution (Knowing before exact the path of each file afterwards). That does mean, this will not give you always the correct paths in some cases:
You use command line parameters which substitute predefined directory table entries.
The MSI uses custom actions which change paths.
Especially it is not guaranteed, that every file is installed. There may be optional conditions and features and this may depend on the install environment.
In effect, a 100% solution is very very hard to achieve, without installing really. It comes near to reprogram nearly the whole windows installer engine. So simplifications are normally sufficient and accepted.
But you can extend the method to cover custom actions, e.g. with adding a line "session.DoAction(..)" for each additional action needed. Or to include command line parameters.
Sometimes it can be tricky. The easier the structure of the MSI is, the more likely it is that you succeed without more efforts.
Alternative to write an own program:
The question is, what you really want to find out, and if it is really necessary to program it:
If you don't want to write an automatical every-day MSI analyzer maybe the following is sufficient for you:
First tip: install the MSI with "msiexec /a mysetup.msi TARGETDIR="c:\mytestpath" . (similar restrictions as script above by weberik)
If the MSI has not used custom actions to change pathes including forgetting to add to the admin sequence ("forgetting" should be taken as the normal case for 99% or existing setups :-), you get the filestructure like if you install "really" with some special namings for the Windows predefined folders which you will find out easily.
If the administrative install lacks some folders, it is often a better idea of fixing the custom action (adding to the admin sequence) and using this scenario as your primary test case.
The advantage is, that only you limit the dynamics used by admin install. BTW, you can use the same command line params or path settings custom actions as in real install.
Second tip: Google for the InstEd tool , go to the file or component table and you will see the resulting MSI paths in the same static way as with the mentioned VB-script after calling CostInitialize/CostFinalize. For human view such an editor view maybe better.
For automatic testing and improvements or accuracy, you need an own program of course.
For those of you that mentioned snippet given is a good starting point. :-)
The rest of you should live easier with one of the two given methods without programming.

Using JScript or VBScript for CustomActions

In my installer I need to do the following: obtain location of the external application from registry and create folders for my application executive and various files in this “parent” directory. I know how to get this directory from registry, but unfortunately the string I get looks like this: C:/Programm Files/Manufacturer/ExtApplication/extapp.exe.
For sure, it does not suit me. So, as I understand, I need to remove the “extapp.exe” part from it. I tried to do it via CustomAction using VBScript or JScript, but I am not experienced enough nether in both these scripts, nor wix itself. I read about Type51, Type 35, etc. CustomAction types, but I cannot get how I should 1) remove the part of the string; 2) use it in Directory element; 3) do all this before the installation of the files.
Will be grateful for any advice (without using Scripts as well).
First, you shouldn't use script custom actions. They are very fragile:
Link
Here is how I would roll:
Use RegLocator/AppSearch to pull the registry value into a property at the beginning of the isntall. Then call out to a C++ custom action that parses off the file name. This custom action should be scheduled before CostInitialize.
You won't use a 51 or a 35 here, you'll use a Type 1 with a call to MsiSetProperty() which is equivilant to Type 51. The difference between 51 and 35 is you use 51 before costing and 35 after costing.
The name of the property you set should match the name of the Id for the Directory table row you are using. Any children of that row will be relative path'd to what you set it to.

Resources