Subsections


9. Resource strings

9.1 Introduction

Resource strings primarily exist to make internationalization of applications easier, by introducing a language construct that provides a uniform way of handling constant strings.

Most applications communicate with the user through some messages on the graphical screen or console. Storing these messages in special constants allows to store them in a uniform way in separate files, which can be used for translation. A programmers interface exists to manipulate the actual values of the constant strings at runtime, and a utility tool comes with the Free Pascal compiler to convert the resource string files to whatever format is wanted by the programmer. Both these things are discussed in the following sections.

9.2 The resource string file

When a unit is compiled that contains a resourcestring section, the compiler does 2 things:
  1. It generates a table that contains the value of the strings as it is declared in the sources.
  2. It generates a resource string file that contains the names of all strings, together with their declared values.
This approach has 2 advantages: first of all, the value of the string is always present in the program. If the programmer doesn't care to translate the strings, the default values are always present in the binary. This also avoids having to provide a file containing the strings. Secondly, having all strings together in a compiler generated file ensures that all strings are together (you can have multiple resourcestring sections in 1 unit or program) and having this file in a fixed format, allows the programmer to choose his way of internationalization.

For each unit that is compiled and that contains a resourcestring section, the compiler generates a file that has the name of the unit, and an extension .rst. The format of this file is as follows:

  1. An empty line.
  2. A line starting with a hash sign (#) and the hash value of the string, preceded by the text hash value =.
  3. A third line, containing the name of the resource string in the format unitname.constantname, all lowercase, followed by an equal sign, and the string value, in a format equal to the pascal representation of this string. The line may be continued on the next line, in that case it reads as a pascal string expression with a plus sign in it.
  4. Another empty line.
If the unit contains no resourcestring section, no file is generated.

For example, the following unit:

unit rsdemo;

{$mode delphi}
{$H+}

interface

resourcestring

  First = 'First';
  Second = 'A Second very long string that should cover more than 1 line';


implementation

end.
Will result in the following resource string file:
# hash value = 5048740
rsdemo.first='First'


# hash value = 171989989
rsdemo.second='A Second very long string that should cover more than 1 li'+
'ne'
The hash value is calculated with the function Hash. It is present in the objpas unit. The value is the same value that the GNU gettext mechanism uses. It is in no way unique, and can only be used to speed up searches.

The rstconv utility that comes with the Free Pascal compiler allows to manipulate these resource string files. At the moment, it can only be used to make a .po file that can be fed to the GNU msgfmt program. If someone wishes to have another format (Win32 resource files spring to mind) he/she can enhance the rstconv program so it can generate other types of files as well. GNU gettext was chosen because it is available on all platforms, and is already widely used in the Unix and free software community. Since the Free Pascal team doesn't want to restrict the use of resource strings, the .rst format was chosen to provide a neutral method, not restricted to any tool.

If you use resource strings in your units, and you want people to be able to translate the strings, you must provide the resource string file. Currently, there is no way to extract them from the unit file, though this is in principle possible. It is not required to do this, the program can be compiled without it, but then the translation of the strings isn't possible.

9.3 Updating the string tables

Having compiled a program with resourcestrings is not enough to internationalize your program. At run-time, the program must initialize the string tables with the correct values for the anguage that the user selected. By default no such initialization is performed. All strings are initialized with their declared values.

The objpas unit provides the mechanism to correctly initialize the string tables. There is no need to include this unit in a uses clause, since it is automatically loaded when a program or unit is compiled in Delphi or objfpc mode. Since this is required to use resource strings, the unit is always loaded when needed.

The resource strings are stored in tables, one per unit, and one for the program, if it contains a resourcestring section as well. Each resourcestring is stored with it's name, hash value, default value, and the current value, all as AnsiStrings.

The objpas unit offers methods to retrieve the number of resourcestring tables, the number of strings per table, and the above information for each string. It also offers a method to set the current value of the strings.

Here are the declarations of all the functions:

Function ResourceStringTableCount : Longint;
Function ResourceStringCount(TableIndex : longint) : longint;
Function GetResourceStringName(TableIndex,
                               StringIndex : Longint) : Ansistring;
Function GetResourceStringHash(TableIndex,
                               StringIndex : Longint) : Longint;
Function GetResourceStringDefaultValue(TableIndex,
                                       StringIndex : Longint) : AnsiString;
Function GetResourceStringCurrentValue(TableIndex,
                                       StringIndex : Longint) : AnsiString;
Function SetResourceStringValue(TableIndex,
                                StringIndex : longint;
                                Value : Ansistring) : Boolean;
Procedure SetResourceStrings (SetFunction :  TResourceIterator);
Two other function exist, for convenience only:
Function Hash(S : AnsiString) : longint;
Procedure ResetResourceTables;
Here is a short explanation of what each function does. A more detailed explanation of the functions can be found in the Reference guide.
ResourceStringTableCount
returns the number of resource string tables in the program.
ResourceStringCount
returns the number of resource string entries in a given table (tables are denoted by a zero-based index).
GetResourceStringName
returns the name of a resource string in a resource table. This is the name of the unit, a dot (.) and the name of the string constant, all in lowercase. The strings are denoted by index, also zero-based.
GetResourceStringHash
returns the hash value of a resource string, as calculated by the compiler with the Hash function.
GetResourceStringDefaultValue
returns the default value of a resource string, i.e. the value that appears in the resource string declaration, and that is stored in the binary.
GetResourceStringCurrentValue
returns the current value of a resource string, i.e. the value set by the initialization (the default value), or the value set by some previous internationalization routine.
SetResourceStringValue
sets the current value of a resource string. This function must be called to initialize all strings.
SetResourceStrings
giving this function a callback will cause the calback to be called for all resource strings, one by one, and set the value of the string to the return value of the callback.
Two other functions exist, for convenience only:
Hash
can be used to calculate the hash value of a string. The hash value stored in the tables is the result of this function, applied on the default value. That value is calculated at compile time by the compiler.
ResetResourceTables
will reset all the resource strings to their default values. It is called by the initialization code of the objpas unit.

Given some Translate function, the following code would initialize all resource strings:

Var I,J : Longint;
    S : AnsiString;

begin
  For I:=0 to ResourceStringTableCount-1 do
    For J:=0 to ResourceStringCount(i)-1 do
      begin
      S:=Translate(GetResourceStringDefaultValue(I,J));
      SetResourceStringValue(I,J,S);
      end;
end;
Other methods are of course possible, and the Translate function can be implemented in a variety of ways.

9.4 GNU gettext

The unit gettext provides a way to internationalize an application with the GNU gettext utilities. This unit is supplied with the Free Component Library (FCL). it can be used as follows:

for a given application, the following steps must be followed:

  1. Collect all resource string files and concatenate them together.
  2. Invoke the rstconv program with the file resulting out of step 1, resulting in a single .po file containing all resource strings of the program.
  3. Translate the .po file of step 2 in all required languages.
  4. Run the msgfmt formatting program on all the .po files, resulting in a set of .mo files, which can be distributed with your application.
  5. Call the gettext unit's TranslateReosurceStrings method, giving it a template for the location of the .mo files, e.g. as in
    TranslateResourcestrings('intl/restest.%s.mo');
    
    the %s specifier will be replaced by the contents of the LANG environment variable. This call should happen at program startup.
An example program exists in the FCL sources, in the fcl/tests directory.

9.5 Caveat

In principle it is possible to translate all resource strings at any time in a running program. However, this change is not communicated to other strings; its change is noticed only when a constant string is being used.

Consider the following example:

Const
  help = 'With a little help of a programmer.';

Var
  A : AnsiString;


begin

  { lots of code }

  A:=Help;

  { Again some code}

  TranslateStrings;

  { More code }
After the call to TranslateStrings, the value of A will remain unchanged. This means that the assignment A:=Help must be executed again in order for the change to become visible. This is important, especially for GUI programs which have e.g. a menu. In order for the change in resource strings to become visible, the new values must be reloaded by program code into the menus...



root
2000-12-20