Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • (Updtask remove if match)
  • (Check contents)
  • Migrate existing translations
  • Chain bundles for simple defaults (chain with generated bundle names, sort of package fallback)
  • Process for contributing and integrating contributed translations
  • Multi/composite fields

Existing uses and inconsistencies

Food for thought: 

Change info.magnolia.ui.form.AbstractFormItem#getMessages - should not be public

info.magnolia.ui.form.AbstractFormItem#getMessage - should not be public

AbstractFormItem defines a semi-arbitrary message bundles chain (see AbstractFormItem#UI_BASENAMES)

Definition objects( TabDefinition, etc) have an i18nBasename property, which is very redundant with that of the "runtimes" objects ( FormTab, ...). Usage seems consistent ( return definition.getI18nBasename();) but I don't know why this isn't implemented in info.magnolia.ui.form.AbstractFormItem.

Definition objects don't have a common interface. If they did, we could move i18nBasename and label in there. OTOH, some of these objects have more than 1 item to translate (label and description, for example).

view.addFormSection(tab.getMessage(tabDefinition.getLabel()), tab.getContainer()); ...translates the message from the tab and passes it translated before it's actually "displayed". (while the method argument is called tabName not tabLabel - but that passed object becomes an argument called caption later down the stack) - the below would make this sort of code much more explicit. You pass an object meant to be a label. You translate it explicitly - most likely at the last possible moment. Or we even extend Vaadin component so that they know about I18nItem.

 

Food for thought: 

Change info.magnolia.ui.form.field.definition.FieldDefinition#getLabel. Node2bean should be made aware of I18nItem class (by registering a transformer)

Code Block
languagejava
titlechange this
String getLabel();

into this

Code Block
languagejava
titleinto this
 I18nItem getLabel();
  • N2B would instanciate these
  • it should be made aware of its "parent" (FieldDefinition etc)
  • via injection, the impl would know what locale to use and delegate to MessagesManager (or its replacement)
  • we'd have a default null-pattern type of implementation (which returns the key or an empty string, or sthg else - this could even be swapped depending on dev mode etc).
  • Would this impact performance/memory usage ? 
  • Quick prototype of this attached - doesn't do anything, and doesn't know about Locales or message bundles - just showing the "structural" change on classes like FieldDefinition (patch file - disregard class names and packages !)

Ideas and topics discussed on 2013-08-06

  • Keys deduction is ok
  • N2B will not work
    • can't really get the parent into the object (children objects are instanciated first) - not without modifying usage code (ie in setLabel() { label.setParent(this) }, which would suck)
    • can't really transform single properties other than with beanutils, which has even less notion of context, and whose usage is currently hardcoded in core 
  • Proxy definition objects ?
    • I18nText will need some sort of context, the definition objects are not enough, since they don't know about user language etc.
  • Basename could also be defined in site definitions (for in-template translations)
  • Bundle languages in separate jars
    • english/master language still bundled with each module, other languages bundled in 1 jar (1 jar per language, containing translations for many modules)
    • maintenance is somewhat easier
    • but at the same time we might get "dependency" issues when modules add/remove keys
    • if we have tools for migration/validation of existing translations, the same tools could be used, perhaps as a maven plugin or sthg.
      • such a tool could potentially help enforcing compatibility between versions (i.e keep a key that was removed in version X+1 of module M)
  • Ideas of supporting things like some.key=${some.other.key
    • mention of the CZ issue where in english a translation would be the same in 10 place but needs to be adapted in czech.
      • somewhat moderate issue with the mechanism is only supported with the same file
    • sorta conflict with the deduction of keys (you would use the most "low" common key for all those same translations)
      • makes the CZ problem perhaps more difficult, since the translator then can't rely on keys being defined in the english file
  • Is basename actually needed ? Keys are long and unique
    • Global chains of message bundles - look into all known bundles
    • Basename helps grouping translation work - "I am now translating module X" - but that doesn't mean the basename has to be specified necessarily
    • Order of message bundles chain would need to be consistent and predictable
  • Enable inline translations within a dialog
  • Have a Magnolia-hosted tool to replace the google spreadsheet
    • some rules like "a translation needs to be validated by 2 other persons to be applied", "once applied it can't be changed directly - only via a 'request'", ...
    • could have a "MessagesManager" impl that fetches translations from this service

Done

Jozef did some work on this topic in the ui-translation branch. See git log master..ui-translation for details.

Suggestions

Differentiate between in-code-translations, vs in-config-translations. Translations in-configuration will require very little code change, but will require some work on update tasks and bootstrap files. IntelliJ (Eclipse too, probably) provides analysis tools to find out where i18n strings have been hardcoded. Attached a sample report executed on a couple of modules: i18-analysis.zip (main, ui, activation, cache, imaging, workflow). Note that the html-exported report is not very useable, but the in-app report is much more practical: Screen Shot 2013-07-31 at 14.21.14.png

In-code translations

I'm not sure we have a lot of those at the moment, but for things that don't need to be configured, we could. We could benefit from a tool like Localizer or GWT's i18n generator tool. These tools generate code (interfaces and impl) based on the keys found in a message bundle file. They are well thought out, in that they provide the correct methods depending on parameters found in the keys, for example. (generate a String getFileCount(int count) based on a file.count=There are {0} files message, for example). This is great for code completion and type-safety. However, their generated code isn't ioc-friendly, and tends to be laced with static dependencies. These tools also don't handle changes to the generated code well. Which means that if you need to add a message - or change its name, or change its "signature" - you need to edit the properties file, rather than the code.

If we have a lot of these, we could think about rolling out our own tool - or write code that behaves similarly.

In-code translations also include things like the login form, where the i18n call might currently be done in the FreeMarker template.

The login form also has this special construct for translating LoginException messages - to be taken into account.

In-template translations

Currently, some template components which need a translated text item which is not meant to be translated by authors; "Skip" or "Read more" type of labels. Those currently suffer from the fact they are in the same message bundle (i18nBasename) as their respective template definition; as such, if one wants to change those text items for a given project, they have to either copy the complete STK message bundles, or customize the components. Neither is ideal. Currently, at least for FreeMarker templates, we pass definition.getI18nBasename() to info.magnolia.freemarker.FreemarkerHelper#addDefaultData, which adds an i18n object to the FM context with the definition's message bundle.

It should be possible for templates to use a different message bundle than the one used to translate the definitions' labels and descriptions.

In-config translations

A complete code review is required to identify all places with a direct String output (such as button labels, column headers, etc.). Such places have to be replaced with a proper i18n mechanism (MessagesManager.getWithDefault(key, defaultMsg), where the original String will be used as defaultMsg).

A complete manual code review is not necessary*. A search for usages of the following (to be completed) should cover 95% of our bases.

  • info.magnolia.ui.form.field.definition.FieldDefinition#getLabel
  • info.magnolia.ui.form.field.definition.FieldDefinition#getDescription
  • info.magnolia.ui.form.definition.TabDefinition#getLabel
  • ...

field.definition.FieldDefinition#getLabel. Node2bean should be made aware of I18nItem class (by registering a transformer)

Code Block
languagejava
titlechange this
String getLabel();

into this

Code Block
languagejava
titleinto this
 I18nItem getLabel();
  • N2B would instanciate these
  • it should be made aware of its "parent" (FieldDefinition etc)
  • via injection, the impl would know what locale to use and delegate to MessagesManager (or its replacement)
  • we'd have a default null-pattern type of implementation (which returns the key or an empty string, or sthg else - this could even be swapped depending on dev mode etc).
  • Would this impact performance/memory usage ? 
  • Quick prototype of this attached - doesn't do anything, and doesn't know about Locales or message bundles - just showing the "structural" change on classes like FieldDefinition (patch file - disregard class names and packages !)

Ideas and topics discussed on 2013-08-06

  • Keys deduction is ok
  • N2B will not work
    • can't really get the parent into the object (children objects are instanciated first) - not without modifying usage code (ie in setLabel() { label.setParent(this) }, which would suck)
    • can't really transform single properties other than with beanutils, which has even less notion of context, and whose usage is currently hardcoded in core 
  • Proxy definition objects ?
    • I18nText will need some sort of context, the definition objects are not enough, since they don't know about user language etc.
  • Basename could also be defined in site definitions (for in-template translations)
  • Bundle languages in separate jars
    • english/master language still bundled with each module, other languages bundled in 1 jar (1 jar per language, containing translations for many modules)
    • maintenance is somewhat easier
    • but at the same time we might get "dependency" issues when modules add/remove keys
    • if we have tools for migration/validation of existing translations, the same tools could be used, perhaps as a maven plugin or sthg.
      • such a tool could potentially help enforcing compatibility between versions (i.e keep a key that was removed in version X+1 of module M)
  • Ideas of supporting things like some.key=${some.other.key
    • mention of the CZ issue where in english a translation would be the same in 10 place but needs to be adapted in czech.
      • somewhat moderate issue with the mechanism is only supported with the same file
    • sorta conflict with the deduction of keys (you would use the most "low" common key for all those same translations)
      • makes the CZ problem perhaps more difficult, since the translator then can't rely on keys being defined in the english file
  • Is basename actually needed ? Keys are long and unique
    • Global chains of message bundles - look into all known bundles
    • Basename helps grouping translation work - "I am now translating module X" - but that doesn't mean the basename has to be specified necessarily
    • Order of message bundles chain would need to be consistent and predictable
  • Enable inline translations within a dialog
  • Have a Magnolia-hosted tool to replace the google spreadsheet
    • some rules like "a translation needs to be validated by 2 other persons to be applied", "once applied it can't be changed directly - only via a 'request'", ...
    • could have a "MessagesManager" impl that fetches translations from this service

Done

Jozef did some work on this topic in the ui-translation branch. See git log master..ui-translation for details.

-----

-- review all the above

...

Different translation "scopes"

We need to differentiate between in-code translatable text, in-template translatable text and in-config translatable text.

In-code translations

Some UI elements are not configured. Their texts can be considered "hardcoded". IntelliJ (Eclipse too, probably) provides analysis tools to find out where i18n strings have been hardcoded. Attached is a sample report executed on a couple of modules: i18-analysis.zip (main, ui, activation, cache, imaging, workflow). Note that the html-exported report is not very useable, but the in-app report is much more practical: Screen Shot 2013-07-31 at 14.21.14.png

My initial thought was that for these, we could benefit from a tool like Localizer or GWT's i18n generator tool. Unfortunately, these tools generate code (interfaces and impl) based on the keys found in a message bundle file. They are well thought out, in that they provide the correct methods depending on parameters found in the keys, for example. (generate a String getFileCount(int count) based on a file.count=There are {0} files message, for example). This is great for code completion and type-safety. However, their generated code isn't ioc-friendly (tends to rely on static/threadlocal for Locale retrieval), and tends to be laced with static dependencies. These tools also don't handle changes to the generated code well. Which means that if you need to add a message - or change its name, or change its "signature" - you need to edit the properties file, rather than the code.

If we end up having a lot of these, or if we have some extra time(wink), we could think about rolling out our own tool - or write code that behaves similarly to that sort of generated code. Typically an interface with methods like String getSomeKey() and String getFileCount(int fileCount) and the tool would generate the implementation and the message bundle file.

In-code translations also include things like the login form and some other AdminCentral templated components, where the i18n call might currently be done in the FreeMarker template. magnolia-ui-admincentral/src/main/resources/mgnl-resources/defaultLoginForm/login.html: it's actually hardcoded.

The login form also has this special construct for translating LoginException messages - to be taken into account.

In-template translations

These are translations that are meant to be "consumed" by a site visitor; i.e used in template scripts. Some template components of STK which need a translated text item are not meant to be translated by authors; "Skip" or "Read more" type of labels.

Those currently suffer from the fact they are in the same message bundle (i18nBasename) as their respective template definition; as such, if one wants to change those text items for a given project, they have to either copy the complete STK message bundles, or customize the components. Neither is ideal. Currently, at least for FreeMarker templates, we pass definition.getI18nBasename() to info.magnolia.freemarker.FreemarkerHelper#addDefaultData, which adds an i18n object to the FM context with the definition's message bundle.

It should be possible for templates to use a different message bundle than the one used to translate the definitions' labels and descriptions.

Ultimately, we probably want authors to be able to translate these items; this is where a JCR-based translation tool (and/or MessageManager implementation) would make the most sense.

In-config translations

Translations in-configuration will require very little change to existing code, but will require some work on update tasks and bootstrap files.

Status

A complete code review is required to identify all places with a direct String output (such as button labels, column headers, etc.). Such places have to be replaced with a proper i18n mechanism (MessagesManager.getWithDefault(key, defaultMsg), where the original String will be used as defaultMsg).

A complete manual code review is not necessary to discover where to apply i18n. A search for usages of the following (to be completed) should cover 95% of our bases.

  • info.magnolia.ui.form.field.definition.FieldDefinition#getLabel
  • info.magnolia.ui.form.field.definition.FieldDefinition#getDescription
  • info.magnolia.ui.form.definition.TabDefinition#getLabel
  • com.vaadin.ui.Component#setCaption (39 usages in the codebase I have)
  • ...

Existing uses and inconsistencies

info.magnolia.ui.form.AbstractFormItem#getMessages - should not be public

info.magnolia.ui.form.AbstractFormItem#getMessage - should not be public

AbstractFormItem defines a semi-arbitrary message bundles chain (see AbstractFormItem#UI_BASENAMES)

Definition objects( TabDefinition, etc) have an i18nBasename property, which is very redundant with that of the "runtimes" objects ( FormTab, ...). Usage seems consistent ( return definition.getI18nBasename();) but I don't know why this isn't implemented in info.magnolia.ui.form.AbstractFormItem.

Definition objects don't have a common interface. If they did, we could move i18nBasename and label in there. OTOH, some of these objects have more than 1 item to translate (label and description, for example).

view.addFormSection(tab.getMessage(tabDefinition.getLabel()), tab.getContainer()); ...translates the message from the tab and passes it translated before it's actually "displayed". (while the method argument is called tabName not tabLabel - but that passed object becomes an argument called caption later down the stack) - the below would make this sort of code much more explicit. You pass an object meant to be a label. You translate it explicitly - most likely at the last possible moment. Or we even extend Vaadin component so that they know about I18nItem.

Proposal

This concept and proposal focuses on in-configuration translations. Hopefully, the concepts can be applied to in-code translations. In-template translations will be taken into account (i.e facilitate the maintenance of the corresponding message files), but changing the mechanism they use might be considered a "next step".*there is an evident need for a complete review of all places impact by the changes proposed in this concept, but the goal would not be to discover where we need to apply i18n. A review could be done before or after, simply with the goal of simplifying, or making more consistent, the existing code base. 

Convention over configuration

...

  • The dialogs prefix is not relevant and noisy. It was historically introduced to separate those labels from the page templates and page components names and description. Indeed, we're likely to have a stkFAQHeader.name somewhere. Currently leaning towards using separate message bundles. Or have an non-mandatory prefix/suffix (i.e there's a chance the component's title/name needs the same label as its first tab ?)
  • The pages.faq statement is arbitrary and is derived from the fact that the dialog in question happens to be configured under an arbitrary folder structure ( pages/faq/ )

Date formats and other localized items

We should make sure things like ColumnFormatter not only use the current user's locale, but also that this is indeed an "enabled" locale. A ui entirely in english but with a date formatted in french would be silly.

Implementation

Proxy .

 

...

------- review all the below

i18nBasename or message bundles

...

getI18nBasename is defined in too many places. It's inconsistent and unintuitive. Why the redundancy between info.magnolia.ui.dialog.Dialog#getI18nBasename and info.magnolia.ui.dialog.definition.DialogDefinition#getI18nBasename for example ?



 

getLabel() vs getI18nBasename() vs usages (question)

...