Programming Reference:Localization: Difference between revisions
Atennissen (talk | contribs) No edit summary |
Atennissen (talk | contribs) |
||
| Line 37: | Line 37: | ||
This function needs to be called explicitly from inside the <tt>Initialize()</tt> function of the <tt>GenericFilter</tt> descendant that determines the GUI object's behavior (in most cases, this is the application module's <tt>TTask</tt> class). | This function needs to be called explicitly from inside the <tt>Initialize()</tt> function of the <tt>GenericFilter</tt> descendant that determines the GUI object's behavior (in most cases, this is the application module's <tt>TTask</tt> class). | ||
==Implementation== | |||
== '''3 Implementation''' == | |||
The implementation is centered around a <tt>Localization</tt> class declared in <tt>Application/shared/Localization.h</tt>. | The implementation is centered around a <tt>Localization</tt> class declared in <tt>Application/shared/Localization.h</tt>. | ||
''' 3.1 Interface to the operator user''' | |||
The <tt>Localization</tt> class adds two parameters to the system that govern its localization | The <tt>Localization</tt> class adds two parameters to the system that govern its localization | ||
behavior: | behavior: | ||
'''*''' <tt>Language</tt> defines the language to translate strings into; if its value matches one of the <tt>LocalizedStrings</tt> row labels, translations will be taken from that row; otherwise, strings will not be translated. A value of <tt>Default</tt> results in all strings keeping their original values. | |||
'''*''' <tt>LocalizedStrings</tt> defines string translations. Strings that don't appear as a column label will not be translated. Also, strings with an empty translation entry in <tt>LocalizedStrings</tt> will not be translated. | |||
'''3.2 Interface to the programmer''' | |||
The <tt>LocalizedStrings</tt> parameter is empty by default. | The <tt>LocalizedStrings</tt> parameter is empty by default. | ||
Although a user may add translations | Although a user may add translations in desired languages to the empty matrix by hand -- using the operator module's matrix editor --, | ||
translations will preferably be provided in a filter constructor by listing them as in the following example: | translations will preferably be provided in a filter constructor by listing them as in the following example: | ||
<pre> | <pre> | ||
| Line 59: | Line 66: | ||
"Si", | "Si", | ||
"Oui", | "Oui", | ||
"No", | |||
"No", | "No", | ||
"Non", | "Non", | ||
| Line 77: | Line 84: | ||
\end{tabular} | \end{tabular} | ||
\end{center} | \end{center} | ||
There may be any number of translation tables inside filter constructors, with their | |||
There may be any number of translation tables inside filter constructors, with their | |||
entries being added to the existing ones, or overriding entries that already exist. | entries being added to the existing ones, or overriding entries that already exist. | ||
Once entered, | Once entered, the translations contained in <tt>LocalizedStrings</tt> are applied via two mechanisms: | ||
the translations contained in <tt>LocalizedStrings</tt> are applied via two mechanisms: | '''*''' The function <tt>LocalizableString()</tt> takes a string as an argument and returns the appropriate entry from <tt>LocalizedStrings</tt>, or the unmodified string if no entry can be found. E.g., instead of | ||
pre> TellUser( "Well done!" ); </pre> | |||
one would write | |||
<pre> #include "Localization.h" | |||
... | |||
TellUser( LocalizableString( "Well done!" ) ); | |||
</pre> to have a translation for "Well done!" looked up in <tt>LocalizedStrings</tt>. | |||
* The function <tt>ApplyLocalizations()</tt> takes a pointer to a GUI object (usually a VCL <tt>TForm*</tt>) and translates all localizable text contained within it. This function must be called during <tt>GenericFilter:: Initialize</tt> for each GUI object generated from a resource file. | |||
'''3.3 Further implementation details''' | |||
'''*''' You should not use <tt>LocalizableString()</tt> on string constants used before the first call to <tt>GenericFilter::Initialize()</tt> or for initializing static or global objects of any kind because localization information used will not be available at global initialization time, and local static variables, once initialized, will not be updated appropriately. | |||
'''*''' Language names are case-insensitive. You may use any string for a language name but as a convention we suggest its most common English name, as in <tt>Italian, Dutch, French, German</tt>, with international country abbreviations as optional regional qualifiers as in <tt>EnglishUS, EnglishGB, GermanA, GermanCH</tt> if necessary. | |||
\begin{table} | \begin{table} | ||
\centering | \centering | ||
| Line 164: | Line 189: | ||
(tab:HTMLchars) | (tab:HTMLchars) | ||
\end{table} | \end{table} | ||
'''*''' Encoding of non-ASCII characters follows the UTF8 convention. To ensure platform independent readability of source code files, there are macros that define HTML character names to their UTF8 encoded strings. This allows you to write | |||
<tt>"Sm" oslash "rrebr" oslash "d"</tt> | |||
for "Sm\o{}rrebr\o{}d" (cf. table~(tab:HTMLchars)). | |||
Revision as of 16:45, 1 March 2007
\maketitle \tableofcontents \pagebreak
- Introduction
As BCI2000 modules are being used in various countries, there emerges some need to adapt user interface elements to regional customs. Most notably, text that gives information to the subject requires translation into the subject's native language, as often a knowledge of English cannot be assumed, and if so, reading text in a foreign language is a distracting factor that makes the already demanding task of operating a BCI even more difficult.
This document explains the approach taken to manage localized (translated) user interface
strings in BCI2000 application modules, trying to meet the following goals:
- Flexibility: For the end user (operator), it should be possible to add translations into a language that is not provided inside an application module, and to modify translations he does not feel to be appropriate. This should be achievable without making changes to the source code and recompiling the application module.
- Separability: For the user, switching languages should be done at a central place, i.e. by changing a single parameter. For the programmer, adding localization capabilities to existing modules should be possible with minimal changes to existing code. When writing new application modules, there should be no need to consider localization issues in advance.
- Documentation: Documenting and providing a collection of existing translations into various languages should be possible inside a module's source code.
- 2 Strategies
For a number of text values that have always been set from parameters (such as the Speller module's Goal parameter that holds the text the user is told to spell in copy spelling mode), localization is not an issue because the user may just change the value as it seems appropriate.
The remaining text items, according to the way they are specified, fall into two categories: #1. \item C string literals in *.cpp source files, and #2. \item text fields in GUI resource files (such as Borland *.dfm files).
For both categories, translations are kept in a single matrix type parameter that uses string labels,
i.e. row and column titles. Each column title represents the native English version of a text; row titles represent languages for which translations exist. The user chooses amongst languages by specifying the desired language in a second parameter; the BCI2000 framework will then use this table to look up a text's translation, matching the text itself against column labels, and the target language against row labels. If no match is found, it will leave the text unchanged.
For string literals in *.cpp files, the strategy is to simply put a function layer around the string, i.e. to send it through a function that checks for a translation
and exchanges the text if there is a match. When introducing localizability into existing code, this implies only a very small amount of changes compared to, e.g., introducing a separate parameter for each string. In the former case, one needs only wrap the string literal into a function call in-place; in the latter case, one would have to add a parameter line to a possibly remote filter class constructor, read that parameter from inside that filter's Initialize() call, and put it into the object that actually holds the string which might require introduction of additional accessor functions.
For strings specified in GUI resource files, the strategy is to supply a function that, very generally, examines string properties of GUI objects, replacing them with their localized versions if applicable.
This function needs to be called explicitly from inside the Initialize() function of the GenericFilter descendant that determines the GUI object's behavior (in most cases, this is the application module's TTask class).
3 Implementation
The implementation is centered around a Localization class declared in Application/shared/Localization.h.
3.1 Interface to the operator user
The Localization class adds two parameters to the system that govern its localization behavior:
* Language defines the language to translate strings into; if its value matches one of the LocalizedStrings row labels, translations will be taken from that row; otherwise, strings will not be translated. A value of Default results in all strings keeping their original values. * LocalizedStrings defines string translations. Strings that don't appear as a column label will not be translated. Also, strings with an empty translation entry in LocalizedStrings will not be translated.
3.2 Interface to the programmer
The LocalizedStrings parameter is empty by default. Although a user may add translations in desired languages to the empty matrix by hand -- using the operator module's matrix editor --, translations will preferably be provided in a filter constructor by listing them as in the following example:
#include "Localization.h"
...
TTask::TTask()
...
LANGUAGES "Italian",
"French",
BEGIN_LOCALIZED_STRINGS
"Yes",
"Si",
"Oui",
"No",
"No",
"Non",
END_LOCALIZED_STRINGS
...
If the LocalizedStrings matrix was empty before the TTask constructor gets called, it will have these entries after execution of the constructor: \begin{center} \begin{tabular}[t]{l|ll} & Yes & No
\hline
Italian & Si & No
French & Oui & Non
\end{tabular} \end{center}
There may be any number of translation tables inside filter constructors, with their
entries being added to the existing ones, or overriding entries that already exist.
Once entered, the translations contained in LocalizedStrings are applied via two mechanisms: * The function LocalizableString() takes a string as an argument and returns the appropriate entry from LocalizedStrings, or the unmodified string if no entry can be found. E.g., instead of
pre> TellUser( "Well done!" );
one would write
#include "Localization.h"
...
TellUser( LocalizableString( "Well done!" ) );
to have a translation for "Well done!" looked up in LocalizedStrings.
* The function ApplyLocalizations() takes a pointer to a GUI object (usually a VCL TForm*) and translates all localizable text contained within it. This function must be called during GenericFilter:: Initialize for each GUI object generated from a resource file.
3.3 Further implementation details
* You should not use LocalizableString() on string constants used before the first call to GenericFilter::Initialize() or for initializing static or global objects of any kind because localization information used will not be available at global initialization time, and local static variables, once initialized, will not be updated appropriately. * Language names are case-insensitive. You may use any string for a language name but as a convention we suggest its most common English name, as in Italian, Dutch, French, German, with international country abbreviations as optional regional qualifiers as in EnglishUS, EnglishGB, GermanA, GermanCH if necessary.
\begin{table}
\centering
\begin{tabular}[t]{|l|c||l|c||l|c||l|c|} \hline
Agrave & \`A &
Aacute & \'A &
Acirc & \^A &
Atilde & \~A
Auml & \"{A} & Aring & \AA & AElig & \AE & Ccedil & \c{C}
Egrave & \`E & Eacute & \'E & Ecirc & \^E & Euml & \"{E}
Igrave & \`I & Iacute & \'I & Icirc & \^I & Iuml & \"{I}
Ntilde & \~N & Ograve & \`O & Oacute & \'O & Ocirc & \^O
Otilde & \~O & Ouml & \"{O} & Oslash & \O & Ugrave & \`U
Uacute & \'U & Ucirc & \^U & Uuml & \"{U} & Yacute & \'Y
szlig & \ss & agrave & \`a & aacute & á & acirc & \^a
atilde & \~a & auml & \"{a} & aring & \aa & aelig & \ae
ccedil & \c{c} & egrave & \`e & eacute & \'e & ecirc & \^e
euml & \"{e} & igrave & \`i & iacute & \'i & icirc & \^i
iuml & \"{i} & ntilde & \~n & ograve & \`o & oacute & \'o
ocirc & \^o & otilde & \~o & ouml & \"{o} & oslash & \o
ugrave & \`u & uacute & \'u & ucirc & \^u & uuml & \"{u}
yacute & \'y & yuml & \"{y} &&&&
\hline
\end{tabular} \caption{HTML names for international characters}
(tab:HTMLchars)
\end{table}
* Encoding of non-ASCII characters follows the UTF8 convention. To ensure platform independent readability of source code files, there are macros that define HTML character names to their UTF8 encoded strings. This allows you to write
"Sm" oslash "rrebr" oslash "d"
for "Sm\o{}rrebr\o{}d" (cf. table~(tab:HTMLchars)).