Delphi 7 application not loading STRINGTABLE resources on Windows 10

144 Views Asked by At

Been away from Delphi development for some time. Recently an old client contacted me with issues using an older Delphi 7 application on Windows 10 (x86 and x64). The application generally works fine, but internationalisation is not loading the correct string.

Here's the basic scenario.

  1. An ASCII file named "String.rc" is generated from a translation spreadsheet:
Language LANG_ENGLISH, SUBLANG_ENGLISH_UK
STRINGTABLE
BEGIN
    cszTestString "English is good"
END

Language LANG_DUTCH, SUBLANG_DUTCH
STRINGTABLE
BEGIN
    cszTestString "En Nederlands is lekker"
END
  1. A separate file called "String_IDs.pas" is generated to give an integer value to cszTestString:
unit String_IDs;
interface
const
    cszTestString = 1234;
  1. "Strings.rc" is compiled using the Windows 10 SDK rc.exe to generate "Strings.res".

  2. The .pas and resource files are compiled into the Delphi application by including the following in the project .dpr file:

    uses
        String_IDs in 'source\String_IDs.pas';

    {$R 'source\Strings.res'}
  1. In code, set the locale to English or Dutch using:
    appLocale := (SUBLANG_ENGLISH_UK shl 10) or LANG_ENGLISH; // 2057
    if (isDutch) then
        appLocale := (SUBLANG_DUTCH shl 10) or LANG_DUTCH; // 1043
  1. Set the threadlocale using:
    SetThreadLocale(appLocale);
  1. Test loading the string resource in three different ways:
    // A: Using LoadStr
    stringA := LoadStr(cszTestString);

    // B: Using LoadString
    if (LoadString(0, cszTestString, szBuffer, Length(szBuffer)) > 0) then
        stringB := szBuffer;
    
    // C: Using LoadString in a different format
    try
    pstrBuffer := AllocMem(MAX_PATH);
    if (LoadString(0, cszTestString, pstrBuffer, MAX_PATH) > 0) then
        stringC := StrPas(pstrBuffer);
    finally
        FreeMem(pstrBuffer, MAX_PATH);
    end;
  1. This was tested in a Windows XP (x86) VM. Everything works perfectly. In English, all three are "English is good". And in Dutch, all are "En Nederlands is lekker".

  2. But I tested in Windows 10 (x86 and x64) VMs and the text is always "English is good". Have tried changing compatibility settings but without success.

Is there some other Windows API to use on Windows 10, or perhaps special compatibility settings for this older application?

2

There are 2 best solutions below

0
SilverWarior On BEST ANSWER

As mentioned in my comment on Windows Vista and newer you should be using SetThreadUILanguage instead of SetThreadLocale to handle localised resources when they are stored inside the same resource file, provided they are tagged with specific language identifier.

Also based on SetThreadLocale and SetThreadUILanguage for Localization on Windows XP and Vista article it is not advisable of using SetThreadUILanguage as it might not have the desired effect on Windows XP. You should keep using SetThreadLocale on Windows XP

In order to learn how to check for Windows version the application is executed I recommend you read Getting the Windows version?

3
AlainD On

Many thanks to @SilverWarior for providing the answer. Here are some technical details for anyone who wants to reproduce them.

Step 1

Determine the Windows version. If you know it will be Windows 10 (or later) you can ignore. Save this result in a flag and run once at startup.

function IsWindows10() : Boolean;
var
    osVersionInfo: TOSVERSIONINFO;
begin
    { Following taken from MSDN (and duplicates removed)
        Windows 11                      TODO    ***
        Windows 10                      10.0    ***
        Windows Server 2016             10.0    ***
        Windows 8.1                     6.3     ***
        Windows Server 2012 R2          6.3     ***
        Windows 8                       6.2
        Windows 7                       6.1
        Windows Server 2008 R2          6.1
        Windows Server 2008             6.0
        Windows Vista                   6.0
        Windows Server 2003             5.2
        Windows XP 64-Bit               5.2
        Windows Embedded Std 2009       5.1 [Not stated explicitly on MSDN, from testing]
        Windows XP                      5.1
        Windows 2000                    5.0
        Windows NT 4.0                  4.0
        Windows Me                      4.90
        Windows 98                      4.10
        Windows 95                      4.0

        *** The application manifest must specifically target Windows 8.1 or 10, otherwise the
        version info for Windows 8 (6.2) will be returned. Since we cannot generate the proper
        manifest in Delphi 7, only 6.2 is returned.
        See the comment from @SilverWarior about alternative methods to extracting the Windows version. }

    ZeroMemory(@osVersionInfo, SizeOf(osVersionInfo));
    osVersionInfo.dwOSVersionInfoSize := SizeOf(osVersionInfo);
    GetVersionEx(osVersionInfo);
    Result := (
        ((osVersionInfo.dwMajorVersion = 6) and             
            (osVersionInfo.dwMinorVersion >= 2)) or
        (osVersionInfo.dwMajorVersion = 10));
end;

Step 2

P/Invoke SetThreadUILanguage from kernel32.dll. This will not exist in Windows XP, but as we won't be calling the method (see next step) so that doesn't matter.

The Microsoft documentation for SetThreadUILanguage is here.

// Functions imported from external DLLs
function SetThreadUILanguage(wLanguageID: WORD) : WORD; stdcall;
    external 'Kernel32.dll' name 'SetThreadUILanguage';

Step 3

Write a method to set the GUI language.

procedure SetUILanguage(wLanguageID: WORD);
begin
    // Set the user interface language for the current thread
    // If this gets called a lot, save `IsWindows10` in a private member
    if (IsWindows10()) then
        SetThreadUILanguage(wLanguageID)
    else
        SetThreadLocale(wLanguageID);
end;

Step 4

Set the GUI language whenever required.

// Early in the application start, begin with English...
langID := ((SUBLANG_ENGLISH_UK shl 10) or LANG_ENGLISH);
SetUILanguage(langID);

// Later (in a galaxy far, far away) someone wants Czech...
langID := ((SUBLANG_DEFAULT shl 10) or LANG_CZECH);
SetUILanguage(langID);