$ cd /home/Lu/

Keep-learning Lu

23 Aug 2020

Translate Hostingjs Site in Chinese

Recently, I was working on translation of slapos console site. In this post, I will explain how to realize the translation of the site(especially the javascript part), invoving in ERP5 system based on Zope. While in these Javascript files, I will play with Renderjs and RSVP to generate the translated site. Besides, unit test is crucial too. I will present a way to simplify the related test zuite in multi languages, making it more automatically.



  1. Translation in ERP5
  2. Javascript Files
  3. translation_data and Localizer
  4. Optimise the Unit Test
  5. Summary

Translation in ERP5

There are translation domains in ERP5:

  1. erp5_ui - Use Localizer's predefined translation for hard-coded messages
  2. erp5_content - Use Localizer's predefined translation for user's input
  3. content translation - Use user-defined translation for a specific document's specific property value entered by user

Localizer is useful to translate a phrase widely used in the system, in other words, it will give the same unified translation across the whole ERP5 for a specific phrase. Localizer's predefined translation system is the best.

Content translation is useful to translate user input which can't be predefined by an ERP5 developer. If a user wants to input a phrase by her/himself and if the phrase has to be translated (for example, product name, person name etc), then content translation is a solution allowing the user to provide translations by her/himself.

Javascript Files

In previous versions, the javascript file contained a large number of strings that needed to be translated.

For example, in rjs_gadget_erp5_page_slap_computer_revoke_certificate_js.js, we have strings like:

‘Certificate is Revoked.’

‘This computer has no certificate to revoke.’

        .push(function (result) {
          var msg;
          if (result) {
            msg = 'Certificate is Revoked.';
          } else {
            msg = 'This computer has no certificate to revoke.';
          return gadget.notifySubmitted({message: msg, status: 'success'})
            .push(function () {
              // Workaround, find a way to open document without break gadget.
              return gadget.redirect({"command": "change",
                                    "options": {"page": "slap_controller"}});

In order to translate them, we certainly can't just translate the string and put the translation back to msg. However, we can turn to some existing API like getTranslationList or getTranslation . Since there are a lot of terms needed translation, I prefer to use getTranslationList, thanks to RenderJS, which is a promise-based JavaScript library developed by Nexedi,

Declare the method at the top:

    .declareAcquiredMethod("getTranslationList", "getTranslationList")

We should have a list for translation then use getTranslationList to get the translation list:

    .declareMethod("render", function (options) {
      var gadget = this,
        translation_list = [
          "Certificate is Revoked.", //result[1][0]
          "This computer has no certificate to revoke.", //result[1][1]
          "Parent Relative Url", //result[1][3]
          "Revoke Computer Certificate" //result[1][4]
      return new RSVP.Queue()
        .push(function () {
          return RSVP.all([
            gadget.getDeclaredGadget('form_view'), //result[0]
            gadget.getTranslationList(translation_list) //result[1]
        .push(function (result) {
          gadget.msg1_translation = result[1][0];
          gadget.msg2_translation = result[1][1];
          page_title_translation = result[1][3];

When the message is out of render method, we are supposed to store the value into gadget so it can be used globally, e.g. gadget.msg1_translation and gadget.msg2_translation. as for page_title_translation, it is out of this push but still in the render method, so don't bother the gadget.

So far, we have finished the work of handling the to-translate strings in Javascript files.

translation_data and Localizer

To inform the localizer, the translation_data should be updated with translation_list. To realise this, we should ask data-i18n for help. Correspondingly, in rjs_gadget_erp5_page_slap_computer_revoke_certificate_html.html:

<!doctype html>
  data-i18n=Certificate is Revoked.
  data-i18n=This computer has no certificate to revoke.
  data-i18n=Parent Relative Url
  data-i18n=Revoke Computer Certificate

Via data-i18n , localizer will have the terms.

Access the localizer with a first Web browser. Go to the test instance. Log-in with the admin account (a.k.a. Manager account in Zope terminology), in order to be able to access the localizer. Use the following page: https://www.myerp5.com/YOURSITEID/manage_main, and click on the Localizer.

Launch another browser and access https://www.myerp5.com/YOURSITEID/view. This is the address of the ERP5 test instance. When on the ERP5 test instance, login as normal user (not Manager). Once logged in, browse the instance. I search for an untranslated word as tra, translate it in the localizer. While translate, I must keep in mind that there are two distinct parts of the Localizer: the Content (_content) and the User Interface (_ui).

  • Content: this section gathers all (almost all) the terms that are located in rolling menus of your instance. If you want to translate them, click on “erp5_content (ERP5 Localized content)".
  • User Interface: this section gathers everything that is not part of the content, eg words that are not in rolling menus. If you want to translate them, click on “erp5_ui (ERP5 Localized interface)".


  • Ensure a language is selected. On the screenshot, French is selected. You will enter the translation here.
  • Above this field, the original term is displayed. Do not edit this field.
  • Use the search zone to find the word you want to translate (case sensitive).
  • Below the search I can see the search results. Click on the term I want to translate and after translating it, click on “Save”

Once a term is translated, you can refresh (F5) the Test Instance in browser to see how it looks like.

Optimise the Unit Test

Testing the translation

Once all the steps of the standard Translation Procedure have been taken, the Translation must be tested before it is released to final customers. To do so, it will have to pass the following set of tests, as described in this document.

  • Make sure there are no different Titles for identical entries in the Glossary;
  • Make sure each term of a given business field has proper Title and Description in the English Glossary;
  • A skilled person in the local language and in the business field makes sure that the terms are well translated in the local language Glossary;
  • An ERP5 script is makes sure that the all terms are translated (Completeness Test)
  • Application developers test the translation before releasing it (Internal Test)
  • A panel of external users tests the translation (External Test)

Once all tests have been taken successfully, the translation is considered as “Good” as described in the Best Practice document, and can be released to final customers.

Unit Test

As a developer, unit testing is also very important on this assignment. The aims of the unit test are:

  1. Ensure the current tests (for English site)not fail
  2. Create the a suite of tests for Chinese site
  3. Ensure the test result is passed

After editing the javascript files, it is possible to have regression on the preview test by mistakes. It can be solved by checking the test result and modify the code.

The test suite for Chinese site can be tricky. My first solution is that I copy-pasted slapos_jio_test and translated every term it used inside the test directly.

				<!-- Test in English version -->
        <!-- <td>//span[@data-i18n="Login and/or password is incorrect."]</td> -->
				<!-- Test in Chinese-->

The solution was simple and crude, but it verified in short order that my code was bug-free.

Although thanks to the API Base_translateString(), making it possible to use localizer translation messages directly. And Thanks to the function getDefaultAvailableLanguage(), so we can get/set a global variable of language in one macro. At the end, the redundant slapos_jio_test can be removed.

Here I present the code about get/set web site's language:

getDefaultAvailableLanguage() for lang

  <tal:block metal:define-macro="slapos_login_base"
             tal:define="lang python: here.web_site_module[web_site_id].getDefaultAvailableLanguage()">

setDefaultAvailableLanguage(language) in functional test(test.erp5.testFunctionalSlapOSUIHalStyle.py) (afterSetup())

  def afterSetUp(self):

Since lang is defined, we can use localizer translation message according to it. test.erp5.testFunctionalSlapOSUIHalStyle.py this file is for launch the test suites, so if we setup the languages here, the test can be whatever language we want. Meanwhile, as long as the Selenium test defines lang, they can be used for multi-languages occasions.

I will take an simple example:

        <!-- Original test content-->
        <!-- <td>//label[contains(text(), "Last Name")]</td> -->
        <!-- Replaced by using Base_translateString() -->
        <td tal:content="python: '//label[contains(text(), \'%s\')]' % (here.Base_translateString('Last Name', lang=lang))"></td>

After modulation, the unit test can be apply to any languages and since they are packaged in macro, it can be easy to call. It really helpful for the future translation work.


Most of work were assigned to me during the quarantine, it did almost kill me to start the work with nothing I had known. However, it went through eventually. I would say the documentations are covered a lot of things, and with GitLab, I can always have some help from different colleges working from different regions and timezones. That helped a lot.

comments powered by Disqus