Index: openacs-4/packages/acs-core-docs/www/i18n-introduction.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-core-docs/www/i18n-introduction.html,v diff -u -r1.11 -r1.12 --- openacs-4/packages/acs-core-docs/www/i18n-introduction.html 17 Jul 2006 05:38:31 -0000 1.11 +++ openacs-4/packages/acs-core-docs/www/i18n-introduction.html 7 Jun 2008 20:28:49 -0000 1.12 @@ -1,19 +1,20 @@ -
+ +
This document describes how to develop internationalized OpenACS packages, including writing new packages with internationalization and converting old packages. Text that - users might see is "localizable text"; replacing monolingual text + users might see is "localizable text"; replacing monolingual text and single-locale date/time/money functions with generic - functions is "internationalization"; translating first - generation text into a specific language is "localization." At + functions is "internationalization"; translating first + generation text into a specific language is "localization." At a minimum, all packages should be internationalized. If you do not also localize your package for different locales, volunteers - may use a public "localization server" to submit suggested text. + may use a public "localization server" to submit suggested text. Otherwise, your package will not be usable for all locales.
The main difference between monolingual and internationalized packages is that all user-visible text in the code of an internationalized - package are coded as "message keys." The message keys + package are coded as "message keys." The message keys correspond to a message catalog, which contains versions of the text for each available language. Script files (.adp and .tcl and .vuh), database files (.sql), and APM parameters are affected. @@ -32,33 +33,33 @@ which are static and mostly text, it may be easier to create a new ADP page for each language. In this case, the pages are distinguished by a file naming convention. -
OpenACS does not have a general system for supporting multiple, localized versions of user-input content. This document currently refers only to internationalizing the text in the package user interface.
If the request processor finds a file named filename.locale.adp
, where locale matches the user's locale, it will process that file instead of filename.adp
. For example, for a user with locale tl_PH
, the file index.tl_PH.adp
, if found, will be used instead of index.adp
. The locale-specific file should thus contain text in the language appropriate for that locale. The code in the page, however, should still be in English. Message keys are processed normally.
+
OpenACS does not have a general system for supporting multiple, localized versions of user-input content. This document currently refers only to internationalizing the text in the package user interface.
If the request processor finds a file named filename.locale.adp, where locale matches the user's locale, it will process that file instead of filename.adp. For example, for a user with locale tl_PH, the file index.tl_PH.adp, if found, will be used instead of index.adp. The locale-specific file should thus contain text in the language appropriate for that locale. The code in the page, however, should still be in English. Message keys are processed normally.
Internationalizing templates is about replacing human readable text in a certain language with internal message keys, which can then be dynamically replaced with real human language in the desired locale. Message keys themselves should be in ASCII English, as should all code. Three different syntaxes are possible for message keys.
- "Short" syntax is the recommended syntax and should be used + "Short" syntax is the recommended syntax and should be used for new development. When internationalizing an existing - package, you can use the "temporary" syntax, which the APM can + package, you can use the "temporary" syntax, which the APM can use to auto-generate missing keys and automatically translate - to the short syntax. The "verbose" syntax is useful while + to the short syntax. The "verbose" syntax is useful while developing, because it allows default text so that the page is usable before you have done localization.
- The short:
- #package_key.message_key#
+ The short:
+ #package_key.message_key#
The advantage of the short syntax is that it's short. It's as simple as inserting the value of a variable. Example: #forum.title#
- The verbose: <trn
- key="package_key.message_key"
- locale="locale">default
- text</trn>
+ The verbose: <trn
+ key="package_key.message_key"
+ locale="locale">default
+ text</trn>
The verbose syntax allows you to specify a default text in a certain language. This syntax is not recommended @@ -67,11 +68,11 @@ in the message catalog yet, because what it'll do is create the message key with the default text from the tag as the localized message. Example: <trn - key="forum.title" locale="en_US">Title</trn> + key="forum.title" locale="en_US">Title</trn>
- The temporary:
- <#message_key
- original text#>
+ The temporary:
+ <#message_key
+ original text#>
This syntax has been designed to make it easy to internationalize existing pages. This is not a syntax that @@ -82,21 +83,21 @@ auto-generated by the APM. Example: <_ Title>
We recommend the short notation for new package development. -
In adp files message lookups are typically done with the syntax
- \#package_key.message_key\#
. In Tcl
+ \#package_key.message_key\#. In Tcl
files all message lookups *must* be on either of the following formats:
-
Typical static key lookup: [_ package_key.message_key]
- The message key and package key used here must be string literals, they can't result from variable evaluation.
- Static key lookup with non-default locale: [lang::message::lookup $locale package_key.message_key]
- The message key and package key used here must be string literals, they can't result from variable evaluation.
- Dynamic key lookup: [lang::util::localize $var_with_embedded_message_keys]
- In this case the message keys in the variable var_with_embedded_message_keys
must appear as string literals \#package_key.message_key\#
somewhere in the code. Here is an example of a dynamic lookup:
- set message_key_array {
+
Typical static key lookup: [_ package_key.message_key] - The message key and package key used here must be string literals, they can't result from variable evaluation.
+ Static key lookup with non-default locale: [lang::message::lookup $locale package_key.message_key] - The message key and package key used here must be string literals, they can't result from variable evaluation.
+ Dynamic key lookup: [lang::util::localize $var_with_embedded_message_keys] - In this case the message keys in the variable var_with_embedded_message_keys must appear as string literals \#package_key.message_key\# somewhere in the code. Here is an example of a dynamic lookup: + set message_key_array { dynamic_key_1 \#package_key.message_key1\# dynamic_key_2 \#package_key.message_key2\# } set my_text [lang::util::localize $message_key_array([get_dynamic_key])] - +
Translatable texts in page TCL scripts are often found in page titles, @@ -105,16 +106,16 @@ that can be used on Linux to highlight translatable text in TCL files:
# Find text in double quotes -find -iname '*.tcl'|xargs egrep -i '"[a-z]'
+find -iname '*.tcl'|xargs egrep -i '"[a-z]' # Find untranslated text in form labels, options and values -find -iname '*.tcl'|xargs egrep -i '\-(options|label|value)'|egrep -v '<#'|egrep -v '\-(value|label|options)[[:space:]]+\$[a-zA-Z_]+[[:space:]]*\\?[[:space:]]*$'
+find -iname '*.tcl'|xargs egrep -i '\-(options|label|value)'|egrep -v '<#'|egrep -v '\-(value|label|options)[[:space:]]+\$[a-zA-Z_]+[[:space:]]*\\?[[:space:]]*$' # Find text in page titles and context bars -find -iname '*.tcl'|xargs egrep -i 'set (title|page_title|context_bar) '|egrep -v '<#'
+find -iname '*.tcl'|xargs egrep -i 'set (title|page_title|context_bar) '|egrep -v '<#' # Find text in error messages -find -iname '*.tcl'|xargs egrep -i '(ad_complain|ad_return_error)'|egrep -v '<#'
+find -iname '*.tcl'|xargs egrep -i '(ad_complain|ad_return_error)'|egrep -v '<#'
You may mark up translatable text in TCL library files and TCL pages @@ -131,74 +132,74 @@ tempoarary message tag (<#_ text_with_percentage_vars#>) and run the action replace tags with keys in the APM.
The variable values in the message are usually fetched with upvar, here is an example from dotlrn:
-
- ad_return_complaint 1 "Error: A [parameter::get -parameter classes_pretty_name]
- must have <em>no</em>[parameter::get -parameter class_instances_pretty_plural] to be deleted"
-
+
+ ad_return_complaint 1 "Error: A [parameter::get -parameter classes_pretty_name]
+ must have <em>no</em>[parameter::get -parameter class_instances_pretty_plural] to be deleted"
+
was replaced by:
-
+
set subject [parameter::get -localize -parameter classes_pretty_name]
set class_instances [parameter::get -localize -parameter class_instances_pretty_plural]
ad_return_complaint 1 [_ dotlrn.class_may_not_be_deleted]
-
+
This kind of interpolation also works in adp files where adp variable values will be inserted into the message.
Alternatively, you may pass in an array list of the variable values to be interpolated into the message so that our example becomes:
-set msg_subst_list [list subject [parameter::get -localize -parameter classes_pretty_name] class_instances [parameter::get -localize -parameter class_instances_pretty_plural]]
+set msg_subst_list [list subject [parameter::get -localize -parameter classes_pretty_name] class_instances [parameter::get -localize -parameter class_instances_pretty_plural]]
ad_return_complaint 1 [_ dotlrn.class_may_not_be_deleted $msg_subst_list]
-
+
When we were done going through the tcl files we ran the following commands to check for mistakes:
# Message tags should usually not be in curly braces since then the message lookup may not be # executed then (you can usually replace curly braces with the list command). Find message tags # in curly braces (should return nothing, or possibly a few lines for inspection) -find -iname '*.tcl'|xargs egrep -i '\{.*<#'
+find -iname '*.tcl'|xargs egrep -i '\{.*<#' # Check if you've forgotten space between default key and text in message tags (should return nothing) -find -iname '*.tcl'|xargs egrep -i '<#_[^ ]'
+find -iname '*.tcl'|xargs egrep -i '<#_[^ ]' # Review the list of tcl files with no message lookups -for tcl_file in $(find -iname '*.tcl'); do egrep -L '(<#|\[_)' $tcl_file; done
+for tcl_file in $(find -iname '*.tcl'); do egrep -L '(<#|\[_)' $tcl_file; done
When you feel ready you may vist your package in the package manager - and run the action "Replace tags with keys - and insert into catalog" on the TCL files that you've edited to + and run the action "Replace tags with keys + and insert into catalog" on the TCL files that you've edited to replace the temporary tags with calls to the message lookup procedure. -
+
Most date, time, and number variables are calculated in TCL files. Dates and times must be converted when stored in the database,
when retrieved from the database, and when displayed. All dates
are stored in the database in the server's timezone, which is an
APM Parameter set at
- /acs-lang/admin/set-system-timezone
+ /acs-lang/admin/set-system-timezone
and readable at
- lang::system::timezone.
. When
+ lang::system::timezone.. When
retrieved from the database and displayed, dates and times must
be localized to the user's locale.
-
Some parameters contain text that need to be localized. In this case, instead of storing the real text in the parameter, you should use message keys using the short notation above, - i.e. #package_key.message_key#. + i.e. #package_key.message_key#.
In order to avoid clashes with other uses of the hash character, you need to tell the APM that the parameter value needs to be localized when retrieving it. You do that by saying: - parameter::get -localize. + parameter::get -localize.
Here are a couple of examples. Say we have the following two parameters, taken directly from the dotlrn package.
Parameter Name | Parameter Value |
---|---|
class_instance_pages_csv | #dotlrn.class_page_home_title#,Simple 2-Column;#dotlrn.class_page_calendar_title#,Simple 1-Column;#dotlrn.class_page_file_storage_title#,Simple 1-Column |
departments_pretty_name | #departments_pretty_name# |
Then, depending on how we retrieve the value, here's what we get: -
Command used to retrieve Value | Retrieved Value |
---|---|
parameter::get -localize -parameter class_instances_pages_csv | Kurs Startseite,Simple 2-Column;Kalender,Simple 1-Column;Dateien,Simple 1-Column |
parameter::get -localize -parameter departments_pretty_name | Abteilung |
parameter::get -parameter departments_pretty_name | #departments_pretty_name# |
+
Command used to retrieve Value | Retrieved Value |
---|---|
parameter::get -localize -parameter class_instances_pages_csv | Kurs Startseite,Simple 2-Column;Kalender,Simple 1-Column;Dateien,Simple 1-Column |
parameter::get -localize -parameter departments_pretty_name | Abteilung |
parameter::get -parameter departments_pretty_name | #departments_pretty_name# |
The value in the rightmost column in the table above is the value returned by an invocation of parameter::get. Note that for localization to happen you must use the -localize flag. @@ -208,5 +209,5 @@ locale.
Developers are responsible for creating the keys in the message
- catalog, which is available at /acs-lang/admin/
+ catalog, which is available at /acs-lang/admin/