There is of course the well documented way of adding for example multiple languages for some text to be displayed:
However if someone wanted to add some new language they would have to obtain the source code and recompile the program after adding the string in their language....
LangString Message ${LANG_ENGLISH} "String in English"
LangString Message ${LANG_FRENCH} "Texte en français"
...
MessageBox MB_OK|MB_ICONINFORMATION "$(Message)"
...
So the question that I was faced with was how can I add support for another language if I do not want to have the default language(s) present, without changing the source code?
I don't like the idea of having external language files and since I was playing around with Message Table resources in DLLs recently I thought that I could give it a shot and try to add my own messages to my program in the form of Message Table resources. In this way the end user can choose to use something like Resource Hacker to change the language of the resource and modify all the strings on that table, thus achieving localization for their system. Furthermore I can use two tables inside the message table resource, one using {LANG_NEUTRAL,SUBLANG_NEUTRAL} and the other using some other language (for example english). The end user can change the table that contains the language (1033 for example) but leave the neutral language table unaltered, so that if the program is executed on a system that does not contain the end user's language, the neutral language would be picked instead.
To achieve the above I used a tool called XN Resource Editor to add a new message table resource to an already compiled version of my program. After saving the modified program I used Resource Hacker to edit the message table(s) and exported them as RES files. These files are binary and not easy to edit, but once they are imported to an EXE file they can be easily manipulated using Resource Hacker.
So now I had the message tables in an RES format and I wanted to add them to my program upon compiling it. I used the following batch script during compilation (header.cmd):
This batch file was called from my NSIS script during compilation time:echo Adding Message Table resources
"%ProgramFiles%\Resource Hacker\ResHacker.exe" -add "%TEMP%\exehead.dat", "%TEMP%\exehead.dat", "%~dps0Lang\Lang_Default.res" , MESSAGETABLE,,
"%ProgramFiles%\Resource Hacker\ResHacker.exe" -add "%TEMP%\exehead.dat", "%TEMP%\exehead.dat", "%~dps0Lang\Lang_EN.res" , MESSAGETABLE,,
echo.
echo Compressing the header with UPX
echo.
upx --best --compress-icons=0 "%TEMP%\exehead.dat"
echo.
The resulting program contained the message table(s) and it was easy to generate a macro that would pick the strings from those tables depending on the system language:!packhdr "$%TEMP%\exehead.dat" 'header.cmd'
Using the macro is also straight forward:!macro GetMessage MESSAGE_ID VAR
; Get the installer's name on $R0
System::Call 'kernel32::GetModuleFileNameA(i 0, t .R0, i 1024) i r1'
; Get the handle of the installer and place it on $R1
System::Call 'kernel32::GetModuleHandleA(t R0)i .R1'
; Get the system language
System::Call 'kernel32::GetSystemDefaultLangID(i v)i .R7'
; Get the message out
StrCpy $0 ${FORMAT_MESSAGE_FROM_HMODULE}
IntOp $0 $0 + ${FORMAT_MESSAGE_ALLOCATE_BUFFER}
IntOp $0 $0 + ${FORMAT_MESSAGE_MAX_WIDTH_MASK}
StrCpy $1 ${MESSAGE_ID}
StrCpy $2 $R7
StrCpy $3 0
System::Call 'kernel32::FormatMessageW(i r0, i R1, i r1, i r2, *w .R3, i r3, i n) i .R4'
StrCpy ${VAR} $R3
!macroend
Everything looked sweet, until I tried to emulate the end user's behavior ... I tried to edit the strings on the message tables using resource Hacker on the final program and failed miserably: the program fails to run after any string has been tampered with!Var "MessageText"
...
!insertmacro ReadStringFromInstaller 2 $MessageText
MessageBox MB_OK|MB_ICONINFORMATION "$MessageText"
...
I managed to get around this problem by adding
to my NSIS script. Not ideal but at least it works.CRCCheck off
Although I am quite happy with the result, I am not sure if this is the best approach to the problem described at the beginning of this post. I am certainly not happy having to disable the CRC check.
I would appreciate any comments/suggestions/thoughts.
😁
CF