Index: openacs-4/packages/acs-core-docs/www/i18n-convert.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-core-docs/www/i18n-convert.html,v diff -u -r1.8 -r1.9 --- openacs-4/packages/acs-core-docs/www/i18n-convert.html 21 Apr 2004 09:49:42 -0000 1.8 +++ openacs-4/packages/acs-core-docs/www/i18n-convert.html 21 Apr 2004 13:18:34 -0000 1.9 @@ -12,42 +12,7 @@ package and then click on Internationalization, then Convert ADP, Tcl, and SQL files to using the - message catalog.. This pass only changes the adp files; it does not affect catalog files or the catalog in the database.

You will now be walked through all of the selected adp pages. The UI shows you the intended changes and lets you edit or cancel them key by key.

  • Replace the temporary message tags in ADP files.�From the same Convert ADP ... page in /acs-admin/apm as in the last step, repeat the process but deselect Find human language text ... and select Replace <# ... #> tags ... and click OK. This step replaces all of the temporary tags with "short" message lookups, inserts the message keys into the database message catalog, and then writes that catalog out to an xml file.

  • Replace human-readable text in TCL files with temporary tags.�Examine all of the tcl files in the packages for human-readable text and replace it with temporary tags. The temporary tags in TCL are slightly different from those in ADP. If the first character in the temporary tag is an underscore (_), then the message keys will be auto-generated from the original message text. Here is an unmodified tcl file:

    +        message catalog..  This pass only changes the adp files; it does not affect catalog files or the catalog in the database.

    You will now be walked through all of the selected adp pages. The UI shows you the intended changes and lets you edit or cancel them key by key.

  • Replace the temporary message tags in ADP files.�From the same Convert ADP ... page in /acs-admin/apm as in the last step, repeat the process but deselect Find human language text ... and select Replace <# ... #> tags ... and click OK. This step replaces all of the temporary tags with "short" message lookups, inserts the message keys into the database message catalog, and then writes that catalog out to an xml file.

  • Replace human-readable text in TCL files with temporary tags.�Examine all of the tcl files in the packages for human-readable text and replace it with temporary tags. The temporary tags in TCL are slightly different from those in ADP. If the first character in the temporary tag is an underscore (_), then the message keys will be auto-generated from the original message text. Here is an unmodified tcl file:

     set title "Messages for $a(name) in $b(label)"
     set context [list [list . "SimPlay"] \
                       [list [export_vars -base case-admin { case_id }] \ 
    @@ -105,288 +70,64 @@
             test. If you don't provide the package_key argument then all
             packages with catalog files will be checked. 
             The script will run its checks primarily on en_US xml catalog files.
    -      

  • Common mistakes while internationalizing

    View comments on this page at openacs.org
    +

    Avoiding common i18n mistakes

    View comments on this page at openacs.org
    Index: openacs-4/packages/acs-core-docs/www/xml/developers-guide/i18n.xml =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-core-docs/www/xml/developers-guide/i18n.xml,v diff -u -r1.17 -r1.18 --- openacs-4/packages/acs-core-docs/www/xml/developers-guide/i18n.xml 21 Apr 2004 09:49:46 -0000 1.17 +++ openacs-4/packages/acs-core-docs/www/xml/developers-guide/i18n.xml 21 Apr 2004 13:18:35 -0000 1.18 @@ -504,70 +504,6 @@ - - - The automatic process can easily get confused by tags within message texts, so that it tries to create two or three message keys for one long string with a tag in the middle. In these cases, uncheck those keys during the conversion and then edit the files directly. For example, this code: - <p class="form-help-text"><b>Invitations</b> are sent, - when this wizard is completed and casting begins.</p> - has a bold tag which confuses the converter into thinking there are two message keys for the text beginning "Invitations ..." where there should be one: - - - - - - Instead, we cancel those keys, edit the file manually, and put in a single temporary message tag: - <p class="form-help-text"> <#Invitations_are_sent <b>Invitations</b> are sent, - when this wizard is completed and casting begins.#> - </p> - - - Complex if statements may produce convoluted message keys that are very hard to localize. Rewrite these if statements. For example: - -Select which case <if @simulation.casting_type@ eq "open">and -role</if> to join, or create a new case for yourself. If you do not -select a case <if @simulation.casting_type@ eq "open">and role</if> -to join, you will be automatically assigned to a case <if -@simulation.casting_type@ eq "open">and role</if> when the -simulation begins. - ... can be rewritten: -<if @simulation.casting_type@ eq "open"> - -Select which case and role to join, or create a new case for -yourself. If you do not select a case and role to join, you will -be automatically assigned to a case and role when the simulation -begins. - -</if> -<else> - -Select which case to join, or create a new case for -yourself. If you do not select a case to join, you will -be automatically assigned to a case when the simulation -begins. - -</else> - - - Check for duplicate keys. For common words such as - Yes and No, you can use a library of keys at acs-kernel. - For example, instead of using - myfirstpackage.Yes, you - can use acs-kernel.Yes. - You can also use the Message Key Search facility to find duplicates. - Be careful, however, building up sentences from keys - because grammar and other elements may not be consistent - across different locales. - Additional discussion: Re: - Bug 961 ("Control Panel" displayed instead of - "Administer"), Translation - server upgraded, and Localization questions. - - - @@ -748,309 +684,190 @@ - Common mistakes while internationalizing + Avoiding common i18n mistakes - Don't internationalize internal code words - Many packages use code words or key words, such as "open" and "closed", which will never be shown to the user. They may match key values in the database, or be used in a switch or if statement. Don't change these. + Replace complicated keys with longer, simpler keys + When writing in one language, it is possible to create clever code to make correct text. In English, for example, you can put an if command at the end of a word which adds "s" if a count is anything but 1. This pluralizes nouns correctly based on the data. However, it is confusing to read and, when internationalized, may result in message keys that are both confusing and impossible to set correctly in some languages. While internationalizing, watch out that the automate converter does not create such keys. Also, refactor compound text as you encounter it. - + + The automated system can easily get confused by tags within message texts, so that it tries to create two or three message keys for one long string with a tag in the middle. In these cases, uncheck those keys during the conversion and then edit the files directly. For example, this code: + <p class="form-help-text"><b>Invitations</b> are sent, + when this wizard is completed and casting begins.</p> + has a bold tag which confuses the converter into thinking there are two message keys for the text beginning "Invitations ..." where there should be one: + + + + + + Instead, we cancel those keys, edit the file manually, and put in a single temporary message tag: - - These notes haven't been formatted yet: - + <p class="form-help-text"> <#Invitations_are_sent <b>Invitations</b> are sent, +when this wizard is completed and casting begins.#> + </p> + + Complex if statements may produce convoluted message keys that are very hard to localize. Rewrite these if statements. For example: + Select which case <if @simulation.casting_type@ eq "open">and +role</if> to join, or create a new case for yourself. If you do not +select a case <if @simulation.casting_type@ eq "open">and role</if> +to join, you will be automatically assigned to a case <if +@simulation.casting_type@ eq "open">and role</if> when the +simulation begins. + ... can be rewritten: + <if @simulation.casting_type@ eq "open"> +Select which case and role to join, or create a new case for +yourself. If you do not select a case and role to join, you will +be automatically assigned to a case and role when the simulation +begins. -There are a nubmer of instances of constructing sentences via concatination (you -can just look at keys like: - <msg key="Page_with_pagination">Page with [ %pagination_filter% ] &nbsp; &nbsp; Show</msg> -to spot them. +</if> +<else> -They key choice is not great. It's as if in coding you decided to use -v1, v2, v3... for your variable names. +Select which case to join, or create a new case for +yourself. If you do not select a case to join, you will +be automatically assigned to a case when the simulation +begins. -Here is You_#: +</else> + Another example, where bugs are concatenated with a number: +<if @components.view_bugs_url@ not nil><a href="@components.view_bugs_url@" title="View the @pretty_names.bugs@ for this component"></if>@components.num_bugs@ <if @components.num_bugs@ eq 1>@pretty_names.bug@</if><else>@pretty_names.bugs@</else><if @components.view_bugs_url@ not nil></a></if> -<msg key="You">You can filter by this %component_name% by viisting %filter_url_string%</msg> -<msg key="You_1">You do not have permission to map this patch to a bug. Only the submitter of the patch and users with write permission on this Bug Tracker project (package instance) may do so.</msg> -<msg key="You_2">You do not have permission to edit this patch. Only the submitter of the patch and users with write permission on the Bug Tracker project (package instance) may do so.</msg> -<msg key="You_3">You do not have permission to reopen this refused patch, only users with write permission on the Bug Tracker package instance (project) may do so.</msg> -<msg key="You_4">You do not have permission to reopen this deleted patch, only users with write permission on the Bug Tracker package instance (project) and the submitter of the patch may do so.</msg> -<msg key="You_5">do not have permission to cancel this patch - only the submitter of the patch may do so. If you are an administrator you can however refuse the patch.</msg> -<msg key="You_6">You don't have permission to edit this patch.</msg> -<msg key="You_7">You do not have permission to unmap a bug from this patch. Only the submitter of the patch and users with write permission on the Bug Tracker package instance (project) may do so.</msg> - -I would much prefer keys like: filter_by_link, perm_nomap, perm_noedit, perm_noropen_refused, etc. - -Also, the semantic content is at odds with the key in a lot of them -eg: - - <msg key="Fix">for version</msg> - <msg key="Fix_1">for</msg> - <msg key="Fix_2">for Bugs</msg> - - -The reason I think message key choice is important is that programmers and -designers, when editing an tcl or adp page, can easily get things -confused and put in the wrong key. It can also act as a simple -guide for translators for intended semantic content of a key. - - -============================================================- - -SOME SPECIFIC COMMENTS PER FILE: - -http://cvs.openacs.org/cvs/openacs-4/packages/bug-tracker/lib/nav-bar.tcl?r1=1.12&r2=1.11 - -You assume: - - multirow append links "New [bug_tracker::conn Bug]" "${url_prefix}bug-add" - -can be translated as - - multirow append links "[_ bug-tracker.New] [bug_tracker::conn Bug]" "${url_prefix}bug-add" - -You should translate this as something like - - set bug_label [bug_tracker::conn Bug] - multirow append links "[_ bug-tracker.New_Bug]" "${url_prefix}bug-add" - -where New_Bug is "New %bug_label%" - -(same problem with New Patch). - -============================================================ - -http://cvs.openacs.org/cvs/openacs-4/packages/bug-tracker/lib/pagination.adp?r1=1.4&r2=1.3 - - -Again, bad to translate fragments: - - Page with [ @pagination_filter@ ]     Show <input type="text" name="interval_size" value="@interval_size@" /> @pretty_plural@ per page <input type="submit" value="Go" /> - - translated to - - #bug-tracker.Page_with_pagination# <input type="text" name="interval_size" value="@interval_size@" /> #bug-tracker.pretty_plural_per_page# <input type="submit" value="Go" /> - - with key - - <msg key="Page_with_pagination">Page with [ %pagination_filter% ] &nbsp; &nbsp; Show</msg> - - - -============================================================ - -http://cvs.openacs.org/cvs/openacs-4/packages/bug-tracker/tcl/bug-procs.tcl?r1=1.15&r2=1.14 - - -You tranlate an internal key here: - - workflow::case::add_log_data \ - -entry_id $entry_id \ - -key "resolution" \ - -value [db_string select_resolution_code {}] - - workflow::case::add_log_data \ - -entry_id $entry_id \ - -key "[_ bug-tracker.resolution]" \ - -value [db_string select_resolution_code {}] - -(see also bug 1748) - ----------------------------------------- - -I think the key choice here is poor. You should attempt to preserve semantic meaning -of these phrases in the key: - - return "Bug-tracker component maintainer" translated to return "[_ bug-tracker.Bug-tracker]" - - return "Bug-tracker bug info" translated to return "[_ bug-tracker.Bug-tracker_2]" - - return "Bug-tracker project maintainer" translated to return "[_ bug-tracker.Bug-tracker_1]" - - -============================================================ - -http://cvs.openacs.org/cvs/openacs-4/packages/bug-tracker/www/admin/component-ae.tcl?r1=1.10&r2=1.9 - -bad key choice and I am not sure why the Add and Edit words are removed for this: - - if { [info exists component_id] } { - set page_title "Edit [bug_tracker::conn Component]" - } else { - set page_title "Add [bug_tracker::conn Component]" - } - - translated to: - - if { [info exists component_id] } { - set page_title [_ bug-tracker.Edit_1] - } else { - set page_title [_ bug-tracker.Add] - } - - - Edit_1 and Add are: - - <msg key="Edit_1">%component_name%</msg> - <msg key="Add">%component_name%</msg> - ----------------------------------------- - -Where did Name go in label: - - {name:text {html { size 50 }} {label "[bug_tracker::conn Component] Name"}} - translated to - {name:text {html { size 50 }} {label "[_ bug-tracker.Component]"}} - with - <msg key="Component">Component</msg> - - ----------------------------------------- - -Bad key choice: - - {help_text "You can filter by this [bug_tracker::conn component] by viisting [ad_conn package_url]com/this-name/"} - tranlated to - {help_text "[_ bug-tracker.You]"} - - -============================================================ - -http://cvs.openacs.org/cvs/openacs-4/packages/bug-tracker/www/admin/index.adp?r1=1.7&r2=1.6 - - -Convoluted logic in adp, concatenating bug/bugs with a number: - -<if @components.view_bugs_url@ not nil><a href="@components.view_bugs_url@" title="View the @pretty_names.bugs@ for this component"></if>@components.num_bugs@ <if @components.num_bugs@ eq 1>@pretty_names.bug@</if><else>@pretty_names.bugs@</else><if @components.view_bugs_url@ not nil></a></if> - <if @components.view_bugs_url@ not nil><a href="@components.view_bugs_url@" title="#bug-tracker.View_the_bug_fo_component#"></if>@components.num_bugs@ <if @components.num_bugs@ eq 1>@pretty_names.bug@</if><else>@pretty_names.bugs@</else><if @components.view_bugs_url@ not nil></a></if> - -It would probably be better to do this as something like: - -<if @components.view_bugs_url@ not nil> + + It would probably be better to do this as something like: +<if @components.view_bugs_url@ not nil> <if @components.num_bugs@ eq 1> <a href="@components.view_bugs_url@" title="#bug-tracker.View_the_bug_fo_component#">#bug-tracker.one_bug#</a> </if><else> <a href="@components.view_bugs_url@" title="#bug-tracker.View_the_bug_fo_component#">#bug-tracker.N_bugs#</a> </else> -</if> +</if> + -============================================================ + + + Don't combine keys in display text + Converting a phrase from one language to another is usually more complicated than simply replacing each word with an equivalent. When several keys are concatenated, the resulting word order will not be correct for every language. Different languages may use expressions or idioms that don't match the phrase key-for-key. Create complete, distinct keys instead of building text from several keys. For example: + + Original code: + multirow append links "New [bug_tracker::conn Bug]" + Problematic conversion: + multirow append links "[_ bug-tracker.New] [bug_tracker::conn Bug]" + Better conversion: + set bug_label [bug_tracker::conn Bug] +multirow append links "[_ bug-tracker.New_Bug]" "${url_prefix}bug-add" + ... and include the variable in the key: "New %bug_label%". This gives translators more control over the phrase. -http://cvs.openacs.org/cvs/openacs-4/packages/bug-tracker/www/admin/versions.adp?r1=1.6&r2=1.5 -Full name created by concatenating first and last name (admittedly this is pervasive in the toolkit): + In this bad example, full name is created by concatenating first and last name (admittedly this is pervasive in the toolkit): + <a href="@past_version.maintainer_url@" title="#bug-tracker.Email# @past_version.maintainer_email@">@past_version.maintainer_first_names@ @past_version.maintainer_last_name@</a> + + + + + + Avoid unnecessary duplicate keys. + + When phrases are exactly the same in several places, use a single key. + + For common words such as + Yes and No, you can use a library of keys at acs-kernel. + For example, instead of using + myfirstpackage.Yes, you + can use acs-kernel.Yes. + You can also use the Message Key Search facility to find duplicates. + Be careful, however, building up sentences from keys + because grammar and other elements may not be consistent + across different locales. + Additional discussion: Re: + Bug 961 ("Control Panel" displayed instead of + "Administer"), Translation + server upgraded, and Localization questions. + - <a href="@past_version.maintainer_url@" title="#bug-tracker.Email# @past_version.maintainer_email@">@past_version.maintainer_first_names@ @past_version.maintainer_last_name@</a> -Also, title is concatenated as well. + + + Don't internationalize internal code words + Many packages use code words or key words, such as "open" and "closed", which will never be shown to the user. They may match key values in the database, or be used in a switch or if statement. Don't change these. + + For example, the original code is + workflow::case::add_log_data \ + -entry_id $entry_id \ + -key "resolution" \ + -value [db_string select_resolution_code {}] + This is internationalized to + workflow::case::add_log_data \ + -entry_id $entry_id \ + -key "[_ bug-tracker.resolution]" \ + -value [db_string select_resolution_code {}] + But resolution is a keyword in a table and in the code, so this breaks the code. It should not have been internationalized at all. Here's another example of text that should not have been internationalized: + {show_patch_status "open"} + was changed to + {show_patch_status "[_ bug-tracker.open]"} + -============================================================ + + + Fix automatic truncated message keys + The automatic converter may create unique but crytic message keys. Watch out for these and replace them with more descriptive keys. For example: + + +<msg key="You">You can filter by this %component_name% by viisting %filter_url_string%</msg> +<msg key="You_1">You do not have permission to map this patch to a bug. Only the submitter of the patch and users with write permission on this Bug Tracker project (package instance) may do so.</msg> +<msg key="You_2">You do not have permission to edit this patch. Only the submitter of the patch and users with write permission on the Bug Tracker project (package instance) may do so.</msg> + These would be more useful if they were, "you_can_filter", "you_do_not_have_permission_to_map_this_patch", and "you_do_not_have_permission_to_edit_this_patch". Don't worry about exactly matching the english text, because that might change; instead try to capture the meaning of the phrase. Ask yourself, if I was a translator and didn't know how this application worked, would this key and text make translation easy for me? + + Sometimes the automatic converter creates keys that don't semantically match their text. Fix these: + <msg key="Fix">for version</msg> + <msg key="Fix_1">for</msg> + <msg key="Fix_2">for Bugs</msg> -http://cvs.openacs.org/cvs/openacs-4/packages/bug-tracker/www/bug.tcl?r1=1.28&r2=1.27 + Another example: Bug-tracker component maintainer" was converted to "[_ bug-tracker.Bug-tracker]". Instead, it should be bug_tracker_component_maintainer. + -Internal key translated: - {show_patch_status "open"} -to - {show_patch_status "[_ bug-tracker.open]"} + + + Translations in Avoid "clever" message reuse + Translations may need to differ depending on the context in which +the message appears. + + + -Does this work? - set page_title "[bug_tracker::conn Bug] #$bug_number" -tranlated to: - set Bug_name [bug_tracker::conn Bug] - set page_title "[_ bug-tracker.%Bug_name% %bug_number%]" - -I don't see how bug-tracker.%Bug_name% can be expected to exist but maybe I don't -understand the idiom. - -Internal key translated: - - if { [string equal [workflow::action::get_element -action_id $action_id -element short_name] "edit"] } { - -to - - if { [string equal [workflow::action::get_element -action_id $action_id -element short_name] "[_ acs-kernel.common_edit]"] } { - - -============================================================ - -http://cvs.openacs.org/cvs/openacs-4/packages/bug-tracker/www/patch.tcl?r1=1.9&r2=1.8 - -All the mode internal keys were translated. - ----------------------------------------- -I would suggest that the option list be returned from a function so this is not duplicated all over the place: - - -options { { "[_ bug-tracker.Plain]" plain } { "[_ bug-tracker.HTML]" html } { "[_ bug-tracker.Preformatted]" pre } } - -These also seem like a candidate for common keys. - ----------------------------------------- - -mode, status internal keys translated here as well. - - - -I fixed most of the outright bugs in the bug-tracker. - -The other things I ran into were: - -1. Quoting in the message catalog for tcl - - i.e. a string like - set title "Patch \"$patch_summary\" is nice." - being translated to - <msg>Patch \"$patch_summary\" is nice.</msg> - (should remove the \ quoting since its not needed). - - Also, some keys had %var;noquote%, which is not needed since those - variables are not quoted (and in fact the variable won't even be - recognized so you get the literal %var;noquote% in the output). - - -2. Things in literal lists like - - array set names { key "Pretty" ...} - - being translated to - - array set names { key "[_bug-tracker.Pretty]" ...} - - which won't work since the _ func will not be called - - Needs to be - - array set names [list key [_bug-tracker.Pretty] ...] - - (strictly speaking the " does not need to go but "[...]" - pretty much never needs quotes and I mostly find them annoying). - - -3. A lot of the acs-kernel.common_* keys had the wrong case (although - we have keys like acs-kernel.common_edit = "Edit" which I think is - a mistake). - - - -The big remaining issue is that we have things like -bug_tracker::bug::workflow_create with lookups in the spec but -I don't think you want to do those lookups til runtime. - -Also the parameters in bug_tracker::get_default_configurations -should be translated (I am not sure if this gets stuffed into the db -or not). - - + + + Quoting in the message catalog for tcl + Watch out for quoting and escaping when editing text that is also code. For example, the original string + + set title "Patch \"$patch_summary\" is nice." + is broken if converted to + <msg>Patch \"$patch_summary\" is nice.</msg> + instead it should be + <msg>Patch "$patch_summary" is nice.</msg> + Also, some keys had %var;noquote%, which is not needed since those + variables are not quoted (and in fact the variable won't even be + recognized so you get the literal %var;noquote% in the output). - - Code within curly brackets isn't evaluated. This code -return { key "[_ bug-tracker.Key_Desc]" } -... will not process correctly. Instead, it should be -return {??? } - + + + + Be careful with curly brackets + Code within curly brackets isn't evaluated. TCL uses curly brackets as an alternative way to build lists. But TCL also uses curly brackets as an alternative to quotation marks for quoting text. So this original code + + array set names { key "Pretty" ...} + ... if converted to + array set names { key "[_bug-tracker.Pretty]" ...} + ... won't work since the _ func will not be called. Instead, it should be + array set names [list key [_bug-tracker.Pretty] ...] +