Translation key rules

Key format

Keys should have the following format: module[.submodule]*[.section]*.element[.part]*, where:

  • module is the name of the module or application being translated, like blog, faq, statistics... Since a module can have submodules, the module name can be followed by several submodule names. For example the SOLR Search UI is located in xwiki-platform-search/xwiki-platform-solr/xwiki-platform-solr-ui and would have keys starting with search.solr..
  • zero, one or more section parts that further refine the location of the string being translated; for example, a key starting with theme.colors.wizard. refers to a text located in the wizard for the color submodule of the theme application, macro.python.parameter. refers to parameters of the python macro, while a key starting with userdirectory. belongs to the main and only section of the user directory application
  • element identifies the main element being translated, but such an element could have several related parts:
  • part identifies a text related to a main element, such as the label that describes an input, the placeholder found inside that input, the tooltip that appears when hovering that input, the hint that is displayed before the field and provides additional details about what it does, the error.empty or error.invalidFormat displayed when there are validation errors, and so on

Individual parts of the key should use camelCase if more than one word is needed to identify the element.

Rules for naming elements

The main part of the key, the element, should have a semantic meaning describing the purpose of the element, and not just a camelCased version of the English text. For example:

  • panels.classEditor.currentClass instead of panels.classedit.youare (for You are editing X.Y)
  • instead of faq.searchFAQs (as the title of a fancy box)
  • instead of (for a submit button)
  • faq.create.authenticationRequired instead of faq.need (for You need to login or register)

Rules for standard form elements

  • The key prefix should identify the purpose of the form: faq.create.,, search.admin.lucene.indexing.; this will be referenced with <prefix> below
    • For complex forms with multiple sections, use another key level to identify each section
  • There should be a title for each form (or form section), and its key must be <prefix>.title
  • For class field labels it's recommended to use the prettyName of the class field, see below how to translate classes
  • For fields in general, there should be several parts using the same key prefix, where the prefix identifies the field, such as
    • <prefix>.label for the label, the name of the field
    • <prefix>.hint for the description text between the label and the input (the .xHint form standard)
    • <prefix>.help for the title displayed when hovering the icon linking to longer documentation (the .xHelp form standard)
    • <prefix>.placeholder for the placeholder text found in the input (the placeholder attribute or the initial value for inputs with the withTip behavior)
    • <prefix>.tooltip for the tooltip displayed when hovering the input (the title attribute)
    • <prefix>.error[.errorType] for error messages displayed in case of error (the .xErrorMsg form standard); use another level if there are multiple error messages for the same field
  • The submit button should always be <prefix>.submit, not whatever is written on the button, like create, publish, send
  • Similarly, the cancel button should always be <prefix>.cancel

Rules for translating composite and long messages

Do not split sentences into smaller translated fragments. For example, to translate There are 42 errors in the form, do not use $msg.get('prefix.thereAre') $numberOfErrors $msg.get('prefix.errorsInForm'), since few languages actually use this topic.

  • This could be represented as number followed by text, which could be translated back to 42 errors are in the form
  • Even when in another languages the number is between two fragments of text, the two fragments could contain different parts, which could be translated back to In the form there are 42 errors, for example, in which case the two "hints" that we included in the key, thereAre and errorsInForm actually confuse the reader
  • Most of the time, translators work on a list of keys out of context, so they won't actually know that .thereAre is supposed to be followed by .errorsInText, and it actually refers to that specific message. Depending on the full message, different translations might be used to better reflect the specifics of each culture, and a simple "there are" piece of text doesn't give any meaning to the translator
  • In many languages the word for errors will be written differently depending on the actual number of errors; even in English it would be better to say 1 error instead of 1 errors

In XWiki we use the MessageFormat class for translations, which allows us to use parameters for translation messages. This means that just one translation key should be used for full messages, with variable parts given as parameters. The example above would become:

$msg.get('module.section.errorReport', [$numberOfErrors])

module.section.errorReport=There {0,choice,0#are no errors|1#is one error|1<are {0} errors} in the form.

Include punctuation in the translation, don't leave quotes, the final dot or question mark outside the message, since punctuation also differs between cultures. Spanish users will want to include an inverted question or exclamation mark at the beginning, Japanese users will want to use a kuten instead of a dot for a full stop, French users will want to leave white space before punctuation marks, and so on.

Don't include markup in the translation; only text should be in there, no HTML, no wiki syntax. This means that the same text will work in HTML, xwiki/1.0, xwiki/2.0, or any other syntax, if the right parameters are given to the translation, which means that the document can be converted to another syntax without requiring retranslating the messages. For example:

module.section.loginOrRegister=You need to {0}login{1} or {2}register{3} to continue.

$msg.get('module.section.loginOrRegister', ["<a href='$xwiki.getURL('XWiki.XWikiLogin', 'login')'>", '</a>', "<a href='$xwiki.getURL('XWiki.Registration')'>", '</a>'])
$msg.get('module.section.loginOrRegister', ['[[', ">>path:$xwiki.getURL('XWiki.XWikiLogin', 'login')]]", '[[', ">>path:$xwiki.getURL('XWiki.Registration')]]"])

Don't reuse small translated tokens in different sentences, since, for example, in English on can be used to say both that the light is "on", that the stove is "on", that the printer is "on" and that the game is "on", but in Romanian we would say that the light is "aprinsă", the stove is "aprins" or "pornit", the printer is "pornită", and the game is "început" (has begun, actually, since the game is on is an expression that doesn't have an exact equivalent). This means that translations shouldn't be written as $msg.get('module.status.specificThing') $msg.get('generic.status.on'), but as $msg.get('module.status.specificThing.on').

For large generic text it is better to translate the whole thing instead of splitting into sentences.

Other general rules

Do not use UPPER CASE as the translation value, even if it will be displayed in upper case, as is the case with form labels. Use CSS for this: text-transform: uppercase;

Even if a string looks identical in two different places, do not reuse the same key; English has generic words that work in many different situations, but other languages might use different words in different context, either because of inflection, or because an English homonym or homograph translates into different words. In general, a section should only use translation keys that identify that section, and not keys from a different section, or worse, keys from a different application. It is worse to have an UI with bits and pieces translated in your language among many English words than to have a pure English application.

Translating XClasses

  • To translate the field prettyName (label), translate Space.ApplicationClass_fieldName
  • Individual static list values can be translated using Space.ApplicationClass_fieldName_value, where value is the key stored internally; this means that for example the user type will always be stored internally as advanced, even if we display it as Avansat when browsing in Romanian. The internal value can be obtained using $obj.getProperty('propName').value, while the translated string is displayed when using $doc.display('propName'), $obj.propName, or $obj.get('propName')
  • For boolean fields there's also a special way of translating the values for true and false, using the Display type (displayType) meta-property. By default yesno is assumed, but other possible predefined values include truefalse to get True or False, active to get Active or Inactive, and allow to get Allow or Deny. Additional display types can be defined by providing translations for newtype_0 and newtype_1, for example:

Updating translations

It is a bad practice to do a major change on an existing message, such as changing the meaning or adding parameters. This means that existing translations in other languages will continue to use the old meaning, and this might "break" the application. It is better to start from scratch and create a different key.

When deprecating a key in favor of a different one, you should try to see if some of the existing translations can be copied over as well. Of course, if the format of the message has changed, this isn't possible.

Further reading about internationalization best practices

The Internationalization Activity at W3C deals with everything related to I18N and L10N. There are good resources on many topics:

Richard Ishida has some good presentations that are relevant for us, such as this one.

Created by Sergiu Dumitriu on 2012/11/24 03:22

Get Connected