Wiki source code of L10N Conventions

Last modified by Thomas Mortagne on 2022/07/29 13:34

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
4
5 = Translation key rules =
6
7 == Key format ==
8
9 Keys should have the following format: ##module[.submodule]*[.section]*.element[.part]*##, where:
10
11 * ##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.##.
12 * 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
13 * ##element## identifies the main element being translated, but such an element could have several related parts:
14 * ##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
15
16 Individual parts of the key should use **camelCase** if more than one word is needed to identify the element.
17
18 == Rules for naming elements ==
19
20 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:
21
22 * ##panels.classEditor.currentClass## instead of ##panels.classedit.youare## (for //You are editing X.Y//)
23 * ##faq.search.title## instead of ##faq.searchFAQs## (as the title of a fancy box)
24 * ##faq.search.submit## instead of ##faq.search## (for a submit button)
25 * ##faq.create.authenticationRequired## instead of ##faq.need## (for //You need to login or register//)
26
27 == Rules for standard form elements ==
28
29 * The key prefix should **identify the purpose of the form**: ##faq.create.##, ##scheduler.jobs.create.##, ##search.admin.lucene.indexing.##; this will be referenced with ##<prefix>## below
30 ** For complex forms with multiple sections, use another key level to identify each section
31 * There should be a title for each form (or form section), and its key **must be ##<prefix>.title##**
32 * For class field labels it's recommended to **use the //prettyName// of the class field**, see below how to translate classes
33 * For fields in general, there should be several parts using the same key prefix, where the prefix identifies the field, such as ##scheduler.job.name##:
34 ** ##<prefix>.label## for the label, the name of the field
35 ** ##<prefix>.hint## for the description text between the label and the input (the ##.xHint## form standard)
36 ** ##<prefix>.help## for the title displayed when hovering the icon linking to longer documentation (the ##.xHelp## form standard)
37 ** ##<prefix>.placeholder## for the placeholder text found in the input (the ##placeholder## attribute or the initial ##value## for inputs with the ##withTip## behavior)
38 ** ##<prefix>.tooltip## for the tooltip displayed when hovering the input (the ##title## attribute)
39 ** ##<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
40 * The //submit button// **should always be ##<prefix>.submit##**, not whatever is written on the button, like ##create##, ##publish##, ##send##
41 * Similarly, the //cancel button// **should always be ##<prefix>.cancel##**
42
43 == Rules for translating composite and long messages ==
44
45 **Do not split sentences into smaller translated fragments**. For example, to translate //There are 42 errors in the form//, do not use ##$services.localization.render('prefix.thereAre') $numberOfErrors $services.localization.render.get('prefix.errorsInForm')##, since few languages actually use this topic.
46
47 * This could be represented as number followed by text, which could be translated back to //42 errors are in the form//
48 * 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
49 * 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
50 * 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//
51
52 In XWiki we use [[the MessageFormat class>>http://docs.oracle.com/javase/7/docs/api/java/text/MessageFormat.html]] 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:
53
54 {{code language="none"}}
55 $services.localization.render('module.section.errorReport', [$numberOfErrors])
56
57 module.section.errorReport=There {0,choice,0#are no errors|1#is one error|1<are {0} errors} in the form.
58 {{/code}}
59
60 **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.
61
62 **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:
63
64 {{code language="none"}}
65 module.section.loginOrRegister=You need to {0}login{1} or {2}register{3} to continue.
66
67 $services.localization.render('module.section.loginOrRegister', ["<a href='$xwiki.getURL('XWiki.XWikiLogin', 'login')'>", '</a>', "<a href='$xwiki.getURL('XWiki.Registration')'>", '</a>'])
68 $services.localization.render('module.section.loginOrRegister', ['[[', ">>path:$xwiki.getURL('XWiki.XWikiLogin', 'login')]]", '[[', ">>path:$xwiki.getURL('XWiki.Registration')]]"])
69 {{/code}}
70
71 **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 ##$services.localization.render('module.status.specificThing') $services.localization.render('generic.status.on')##, but as ##$services.localization.render('module.status.specificThing.on')##.
72
73 For large generic text it is better to **translate the whole thing** instead of splitting into sentences.
74
75 == Other general rules ==
76
77 **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;##
78
79 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.
80
81 == Translating XClasses ==
82
83 * To translate the **field prettyName** (label), translate ##Space.ApplicationClass_fieldName##
84 * Individual **static list** or **database 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')##
85 * 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:(((
86 {{code language="none"}}
87 enabled_0=Disabled
88 enabled_1=Enabled
89 {{/code}}
90 )))
91
92 = Updating translations =
93
94 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.
95
96 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.
97
98 = Further reading about internationalization best practices =
99
100 The [[Internationalization Activity at W3C>>http://www.w3.org/International/]] deals with everything related to I18N and L10N. There are good [[resources>>http://www.w3.org/International/articlelist]] on many topics:
101
102 * [[Working with composite messages>>http://www.w3.org/International/articles/composite-messages/]]
103 * [[Reusing strings in scripted context>>http://www.w3.org/International/articles/text-reuse/Overview]]
104 * [[Text size in translations>>http://www.w3.org/International/articles/article-text-size]]
105 * [[Date formats>>http://www.w3.org/International/questions/qa-date-format]]
106 * [[Personal names around the world>>http://www.w3.org/International/questions/qa-personal-names]]
107 * [[The difference between I18N and L10N>>http://www.w3.org/International/questions/qa-i18n]]
108
109 Richard Ishida has some good presentations that are relevant for us, such as [[this one>>http://www.w3.org/International/talks/0906-loc-world-berlin/slides.pdf]].

Get Connected