Multilingual Module
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.


NOT in development. This package is beeing merged on


This package contains the core functionality for the next generation multilingual engine.

These are the main artifacts and its purposes:

  • the canonical organizes the information about a "translation-group"
  • it's using a dictionary with language-codes as keys and uuids

(provided by plone.uuid) as values

  • persistent storage, which holds the canonicals in an IOBTree
  • the OOBTree's key is the UUID of the content, the according value is the canonical
  • adapter for ITranslatable
  • provides the translations API
  • ITranslationLocator - where to put a translation
  • ITranslationIdChooser - generates a valid id for a translation
  • ITranslationCloner - copy the language-independent content to the translation
  • ITranslationFactory - creates the translation

In order to have a test we have a type called Demo that has an adapter called DemoLanguage that will allow to get the language of the object:

>>> from plone.multilingual.interfaces import ITranslationManager
>>> from plone.multilingual.interfaces import ILanguage
>>> from import setRoles, login, TEST_USER_ID, TEST_USER_NAME
>>> from zope.lifecycleevent import modified

>>> portal = layer['portal']
>>> setRoles(portal, TEST_USER_ID, ['Manager'])
>>> login(portal, TEST_USER_NAME)
>>> portal.invokeFactory('Folder', 'ob1', title=u"An archetypes based folder")

>>> ILanguage(portal['ob1']).set_language('ca')
>>> portal['ob1'].reindexObject()
>>> modified(portal['ob1'])

Ensuring that the new object gets its UUID:

>>> from plone.uuid.interfaces import IUUID
>>> ob1_uuid = IUUID(portal['ob1'])
>>> isinstance(ob1_uuid, str)

We create a new translation in 'en' language:

>>> ITranslationManager(portal['ob1']).add_translation('en')

We try to create a new translation in 'ca' that already exists:

>>> ITranslationManager(portal['ob1']).add_translation('ca')
Traceback (most recent call last):
KeyError: 'Translation already exists'

We try to create a new translation without language:

>>> ITranslationManager(portal['ob1']).add_translation(None)
Traceback (most recent call last):
KeyError: 'There is no target language'

We get the 'en' translation:

>>> ITranslationManager(portal['ob1']).get_translation('en')
<ATFolder at /plone/ob1-en>
>>> ILanguage(ITranslationManager(portal['ob1']).get_translation('en')).get_language() == 'en'

let's get all the translations:

>>> ITranslationManager(portal['ob1']).get_translations()
{'ca': <ATFolder at /plone/ob1>, 'en': <ATFolder at /plone/ob1-en>}

let's get only the languages:

>>> ITranslationManager(portal['ob1']).get_translated_languages()
['ca', 'en']


>>> ITranslationManager(portal['ob1']).has_translation('en')

>>> ITranslationManager(portal['ob1']).has_translation('it')

register_translation with invalid language:

>>> ITranslationManager(portal['ob1']).remove_translation('en')
>>> ITranslationManager(portal['ob1']).register_translation(None, portal['ob1-en'])
Traceback (most recent call last):
KeyError: 'There is no target language'

register a translation with content:

>>> ITranslationManager(portal['ob1']).register_translation('en', portal['ob1-en'])
>>> ITranslationManager(portal['ob1']).get_translations()
{'ca': <ATFolder at /plone/ob1>, 'en': <ATFolder at /plone/ob1-en>}

changing the content-language (there should act a subscriber):

>>> ILanguage(portal['ob1-en']).set_language('it')
>>> from zope.event import notify
>>> from zope.lifecycleevent import ObjectModifiedEvent
>>> notify(ObjectModifiedEvent(portal['ob1-en']))
>>> ITranslationManager(portal['ob1']).get_translations()
{'ca': <ATFolder at /plone/ob1>, 'it': <ATFolder at /plone/ob1-en>}

test more translations:

>>> obj_it = ITranslationManager(portal['ob1']).get_translation('it')
>>> ITranslationManager(obj_it).add_translation('fr')
>>> ITranslationManager(obj_it).add_translation('pt')
>>> ITranslationManager(portal['ob1']).get_translated_languages()
['ca', 'it', 'fr', 'pt']
>>> ITranslationManager(obj_it).get_translated_languages()
['ca', 'it', 'fr', 'pt']

test if canonicals objects are the same:

>>> obj_ca = ITranslationManager(obj_it).get_translation('ca')
>>> canonical_it = ITranslationManager(obj_it).query_canonical()
>>> canonical_ca = ITranslationManager(obj_ca).query_canonical()
>>> canonical_it == canonical_ca

Messing up with content

In case that we do mess up things with content (users always do):

>>> from zope.lifecycleevent import modified
>>> portal.invokeFactory('Folder', 'ob2', title=u"An archetypes based doc")
>>> ILanguage(portal['ob2']).set_language('it')
>>> modified(portal['ob2'])
>>> ILanguage(portal['ob2']).get_language()
>>> ITranslationManager(portal['ob2']).add_translation('en')
>>> ob2_en = ITranslationManager(portal['ob2']).get_translation('en')

>>> portal.invokeFactory('Folder', 'ob3', title=u"An archetypes based doc")
>>> ILanguage(portal['ob3']).set_language('it')
>>> modified(portal['ob3'])
>>> ILanguage(portal['ob3']).get_language()
>>> ITranslationManager(portal['ob3']).add_translation('es')
>>> ob3_es = ITranslationManager(portal['ob3']).get_translation('es')

>>> from OFS.event import ObjectWillBeRemovedEvent
>>> notify(ObjectWillBeRemovedEvent(portal['ob2']))
>>> portal.manage_delObjects('ob2')

>>> notify(ObjectWillBeRemovedEvent(ob3_es))
>>> portal.manage_delObjects(

>>> c_old = ITranslationManager(portal['ob3']).query_canonical()
>>> c_new = ITranslationManager(ob2_en).query_canonical()
>>> c_old == c_new

>>> isinstance(c_old, str)
>>> isinstance(c_new, str)

>>> ITranslationManager(ob2_en).register_translation('it', portal['ob3'])

>>> c1 = ITranslationManager(portal['ob3']).query_canonical()
>>> c2 = ITranslationManager(ob2_en).query_canonical()
>>> c1 == c2

Other use case, A('it' + 'en') and B('it' + 'es'), and we want A('en') -> B('es'):

>>> portal.invokeFactory('Folder', 'mess1', title=u"An archetypes based doc")
>>> ILanguage(portal['mess1']).set_language('it')
>>> modified(portal['mess1'])
>>> ILanguage(portal['mess1']).get_language()
>>> ITranslationManager(portal['mess1']).add_translation('en')
>>> mess1_en = ITranslationManager(portal['mess1']).get_translation('en')

>>> portal.invokeFactory('Folder', 'mess2', title=u"An archetypes based doc")
>>> ILanguage(portal['mess2']).set_language('it')
>>> ITranslationManager(portal['mess2']).add_translation('es')
>>> mess2_es = ITranslationManager(portal['mess2']).get_translation('es')

>>> ITranslationManager(mess1_en).register_translation('es', mess2_es)
>>> ITranslationManager(portal['mess2']).get_translation('es')
>>> ITranslationManager(portal['mess1']).get_translation('es')
<ATFolder at /plone/mess2-es>



>>> from plone.multilingual.interfaces import ITranslationIdChooser
>>> chooser = ITranslationIdChooser(portal['ob1-en'])
>>> chooser(portal, 'es')


>>> ITranslationManager(portal['ob1']).add_translation('es')
>>> child_id = portal.ob1.invokeFactory('Folder', 'ob1_child', language="ca")

>>> from plone.multilingual.interfaces import ITranslationLocator
>>> locator = ITranslationLocator(portal['ob1-en'])
>>> locator('es') == portal

>>> child_locator = ITranslationLocator(portal.ob1.ob1_child)
>>> child_locator('es') == portal['ob1-es']

>>> ITranslationManager(portal['ob1']).remove_translation('es')

Convert intids to uuids upgrade step

An upgrade step is available in case of having an existing site with the experimental 0.1 plone.multilingual version:

>>> from plone.multilingual.upgrades.to02 import upgrade


You must reinstall the plone.multilingual package in order to install the required new utility in place before upgrading. If you are using a version of Dexterity below 2.0, you must install the package and enable the Referenceable ( behavior for all your Dexterity content types before you attempt to upgrade your site.

You can run the @@pml-upgrade view at the root of your site or follow the upgrade step in portal_setup > upgrades. If you can't see the upgrade step, press Show old upgrades and select the Convert translation based intids to uuids (0.1 → 02)

Upgrade to catalog

>>> from plone.multilingual.upgrades.to03 import upgrade

we shouldn't find the storage-utility anymore:

>>> from plone.multilingual.interfaces import IMultilingualStorage
>>> gsm = portal.getSiteManager()
>>> gsm.queryUtility(IMultilingualStorage) is None