Skip to content

Rhaptos Software Development

Personal tools
You are here: Home » Documentation » Developer Documentation » i18n Documentation » Instructions for adding i18n to Rhaptos templates

Instructions for adding i18n to Rhaptos templates

Document Actions
This is an overview of all the steps needed for adding i18n to a Rhaptos template.

i18n Instructions

Prerequisites

Before reading this, you should have a small background knowledge of how i18n works and what its purpose is.  I strongly recommend that you read the Plone i18n docs first.  This document is really meant as a supplement for them, not a replacement.

A lot of our Rhaptos practices will be slightly different than the Plone practices.  Most of that is due to the fact that they have multiple teams for the whole internationalization process, not just one or two people.
  
Important Plone Docs:
  1. http://plone.org/documentation/how-to/i18n-for-developers This is the bulk of the information that you'll need to know.  One thing to note while reading this page, though, is that you cannot actually have a "-" in an i18n:name attribute.  Their main example uses one and that example will not render with the current version of Plone that we use (2.0.5).  I don't know if it is fixed in later versions or not.
  2. http://plone.org/development/teams/i18n/translators-guidelines This page is not as important.  But, since we try not to use empty i18n:translate attributes and assign explicit message ids whenever possible, it is important to read the message id prefixes at the bottom of the page.  We do have a couple other prefixes.  They will be discussed below.

Writing Page Templates for i18n

There are several things that you can do while creating page templates that can make them much easier and much cleaner to translate.
  1. Do not use tal:replace for things that need to be translated.  You cannot have a tal:replace and an i18n:translate on the same tag.  Change the tal:replace to a tal:content.  The main reason to use a tal:replace instead of a tal:content is that it keeps you from having extra tags laying around in the final XHTML.  But, as far as I can tell, the i18n processer will remove unneccesary <span> tags that only i18n:translate on them, anyway.
  2. Seperate text from tags. Several places I saw cases where there was text with other tags as siblings.  I can't think of a particularly good example off the top of my head, but I'll make a bad example for now:
    <div>
      This is a sentence.
       <input />
       <input />
     </div>

    The problem with this is that in order to tag this, it would have to be:

    <div i18n:translate="foo">
      This is a sentence.
      <input i18n:name="bar" />
      <input i18n:name="baz" />
    </div>

    Translating this would give the translator the ability to move the input tags around and reorder them.  In some cases, that may be a good thing because the language needs the text in a different place than in English.  But, in other cases, it gives the translators the ability to change the UI and design of the page, beyond what is neccesary for simply "translating".  Usually, a better option is:

    <div>
      <span i18n:translate="foo">This is a sentence.</span>
      <input />
      <input />
    </div>

i18n Domain

First off, you have to determine the domain of the majority of the msgids in each file.  The domain specifies which translation files are used to find the appropriate translation for a string.  Most Plone templates are in the Plone domain, which already includes 50+ languages.  It is good to try to reuse the translations wherever possible, but in many cases it is not possible.  Most templates that are written for Rhaptos should probably be in the "rhaptos" domain.  Generally,  you'll want to specify i18n:domain="rhaptos" at the top level element and don't override it lower, unless its obvious that it should come from plone.

The i18n domain is set simply with the attribute i18n:domain.  This specifies a the domain for a tag and all of its children, until another one is specified. 

Idealy, specify the i18n domain at the top of a file and only override it if it is very obvious that the translation should come from elsewhere.  Something that Rhaptos overrides from Plone may look like:

<div i18n:domain="plone">
  <span i18n:translate="text_foo">foo</span>
  <span i18n:translate="text_rhaptos_foo" i18n:domain="rhaptos">rhaptos_foo</span>
</div>

In this example, "text_foo" will be looked up in Plone's ".pot" file, whereas "text_rhaptos_foo" will be looked up in Rhaptos's ".pot" file. 

There are cases when using multiple domains causes us to make excessive translations, but it it not really avoidable.

For example, consider that I have a module with 3 actions: "View", "Edit", and "Publish".  All of these are defined in the Plone domain and could be completely covered by:
<div tal:repeat="a here/actions" i18n:domain="plone">
  <span tal:content="a" i18n"translate="">[action name]</span>
</div>

But, if we add a new action, "Rhaptos Validate", that does not exist in the Plone domain, due to the use of a tal:repeat, all the actions must be pulled from the same domain:
<div tal:repeat="a here/actions" i18n:domain="rhaptos">
  <span tal:content="a" i18n"translate="">[action name]</span>
</div>

So, this causes translators to have to retranslate "View", "Edit", and "Publish" for the Rhaptos translations.

Tagging Guidelines

We roughly follow the same tagging guidelines as the Plone instructions (linked above).  The main difference is that we try to avoid literal string translations whenever possible.  Literal translations are easy because you don't have to add a msgid for every little bit of text.  But, they have the problem where you can get incorrect translations due to two string being spelled the same, but meaning something different.

Instead of just marking everything up with empty i18n:translate attributes and then having translators assign msgids later, we should really just assign msgids from the beginning. 

The hard part of assigning msgids is to figure out how general to make the ID.  If it is something that will be reused commonly on many different templates, you want to make sure all the different templates get the same msgid so that the string only has to be translated once.  If the string is very specific, it should recieve a very specific ID so that someone else doesn't attempt to match a different string with the same ID.

We have a rough standard format for msgids that *should* help prevent msgids from overlapping.  It is:
<prefix>_<context>_<string description>

Where <prefix> is a standard msgid prefix from the following list (most come from Plone, but the new ones we have added are in italics).  The prefix helps identify what sort of element they string is being used for as this affects its context and how  it is used.

  • heading_ - for <h> elements
  • description_ - Explanatory text directly below
  • legend_ - Used in <legend> elements
  • label_ - For field labels, input labels, i.e. <label>, and for <a> elements
  • help_ - Any text that provides help for form input.
  • box_ - Content inside portlets.
  • listingheader_ - For headers in tables (normally of class "listing").
  • date_ - For date/time-related stuff. E.g. "Yesterday", "Last week".
  • text_ - Messages that do not fit any other category, normally inside <p>
  • batch_ - for batch-related things - like "Displaying X to Y of Z total documents"
  • summary_ - for table summaries
  • title_ - for titles on all elements
  • message_ - for text used with portal_status_message
  • value_ - for value attributes on all elements
  • xsl_ - for values that are only used in XSL and not actually used in page templates (see below)

The <context> section of a msgid usually refers to what Product, page template, section of the site, or object it is used in/on.  This can be left out for much more general strings that are likely to occur in many different places.

The <string description> section of a msgid should be a short summary of the string.  It can be the text of the string if that is a good summary, or it can be a description of what the string is.  For example, the <string description> for "Click here" would probably just be "click_here".  But for a long paragraph telling about the account terms of use, it would probably be "account_terms_of_use".

Here are some examples of potential msgids.

<h1 i18n:translate="heading_edit">Edit</h1>
(this is fairly general and could show up a lot... keep the msgid general)

<h1 i18n:translate="heading_module_edit">Edit Module</h1>
(this is used in the specific context of a module, so the msgid should reflect that)

<h1 i18n:translate="heading_module_edit_before_publish">Edit this module and then publish it</h1>
(Very specific and should have a very spcific msgid)

<h1 i18n:translate="heading_edit_with_page_number">Edit Page
  <span i18n:name="content_type" tal:replace="here/page_number">
  </span>
</h1>
(Even though when this actual title renders, it'll be very specific "Edit Page 1" or "Edit Page 2", the structure of the outside tag is fairly general and thus the tag should be kep fairly general)

Using translate.py

translate.py is a script included with Plone for handling i18n translations that cannot be translated by i18n attributes on elements.  The main use of this script in Rhaptos is for portal_status_messages that are generated in python scripts and text that needs to have variables passed into it.

Usage:
translate(msgid, variables(dictionary), domain, default)

The deafult translation has to be included in the call to translate.py.  Or else, there will be no default translation when the site is viewed in English.  But, I also like to put it into a file to collect all the default translations of these strings, RhaptosSite/skins/rhaptos_site/rhaptos_i18n_dummy_template.pt.

Here are some examples of what it should look like when used:

1. portal_status_message:
    Original:
        return state.set(state="success", portal_status_message="Item Created.")

    With translate.py:
         msg = context.translate("message_item_created", domain="rhaptos", default="Item Created.")
         return state.set(state="success", portal_status_message=msg)

    Defined in rhaptos_i18n_dummy_template.pt:
        <li i18n:translate="message_item_created">Item Created.</li>

2. Passing in variables:
    Original:
       <a tal:attributes="title string:Email ${user_name}"
            i18n:translate="label_email_user">Email
           <span tal:replace="user_name" i18n:name="user_name"/>
        </a>

    With translate.py:
       <a tal:attributes="title python:here.translate('title_email_user_link', {'user_name':user_name}, domain='rhaptos')"
            i18n:translate="label_email_user">Email
           <span tal:replace="user_name" i18n:name="user_name"/>
        </a>
      
    Defined in rhaptos_i18n_dummy_template.pt:
        <li i18n:translate="title_email_user_link">Email
             <span i18n:name="user_name">user_name</span>
        </li>

Literal Msgids

Literal msgids are when you don't specifically assign a msgid to a string, so it simply uses the text of the string as the msgid.  This would look like:

<span i18n:translate="">Foo</span>

Here, the msgid used for the translation process will end up being "Foo". 

It is a good practice to avoid using literal msgids whenever possible because there is always the possibility that on word has two different meanings and thus must be translated in different ways.

For example, "Home", could refer to a home page of a website, or someone's house.  They probably translate to different words in most other languages.  But, if you only use literal translations for the website, then you can only define one translation for that word.

There are some cases where you have to use literal msgids, though.  That is basically anywhere that has dynamic content, like tal:content or a tal:attributes in a page template (unless you chose to leave them untranslated).  Mark these up with a tal:translate="" and the msgid will be whatever string ends up being used at render time.

To keep track of all these potential msgids that need to be translated, make an entry in RhaptosSite/skins/rhaptos_site/rhaptos_i18n_dummy_template.pt.  Use this file to keep track of all the different literal msgids that need to be available to translators.  By keeping them in this file, they are automatically grabbed by i18ndude everytime the .pot file is generated, and thus, you don't have to add them in by hand afterwards.

XSL Translations

There is another portion of our site which is not covered by the .po/.pot files generated by i18ndude.  This is the text that is generated with XSL in rendering our modules.  There are two files that contain this text, cnxml/style/unibrowser.xsl and RhaptosContent/www/content_render.xsl. 

The solution that we use for translating these was developed by Norman Walsh for DocBook.  Any English text in the XSL needs to be deleted and it needs to be pulled from the gentext template instead.  Here's an example:
<xsl:call-template name="gentext">
          <xsl:with-param name="key">By</xsl:with-param>
          <xsl:with-param name="lang"><xsl:value-of select="/module/metadata/language"/></xsl:with-param>
</xsl:call-template>

Then, the appropriate key needs to be added to cnxmll10n.xsl, with the English translation.  To translate it into other languages, a section needs to be made in that file for that langauge and all the text attributes translated.

All the keys should also be included in RhaptosSite/skins/rhaptos_site/rhaptos_i18n_dummy_template.pt so that they will be included in the .pot file that is handed to translators.  Translating them in the .po files will have no effect on the way the text is rendered, but it will give us a place to store the translations so that they are easy to find. 

Generating the .pot files

To generate the .pot files, navigate to a directory above all the files that you want to translate (like Products/) and run:
i18ndude rebuild-pot --pot rhaptos.pot --create rhaptos .

After building the pot file, the top few lines need to be edited to have the correct Rhaptos/Connexions metadata.  Delete the top three lines and add the following:
# Rhaptos/Connexions POT file for translations
# cnx@cnx.org 2007
#
# This is a template for creating a .po translation file
# Please rename the file to "rhaptos-<lang-code>.po", where
# <lang-code> is the ISO code for the translation you are working on.
# (e.g. rhaptos-es.po)
#
# Translators: Please edit the header information.
# Place contact information in the "Last-Translator"
# and "Language-Team" lines. Set appropriate language
# specific values for "Language-Code" and "Language-Name"
#
# Then, go through each msgid stanza and insert your translation
# of the "Default" line as the value of "msgstr".
# If the "Default" line contains a variable (e.g. ${variable_name})
# place the variable appropriately for your language in the translation.
# Any variables included in the "Default" line must also be in the
# "msgstr" line of the final translation.
#
To generate a sample translation that uses X's for testing translation strings, you'll need the script that I wrote in the i18nday devset, generateXese.py.  run:
python generateXese.py rhaptos.pot

It will create a file called rhaptos-xx.po which is the Xese translation of all rhaptos strings.


Last modified 2007-05-25 11:45