Language functions

Note

TODO: rework this section.

Description

Accessing and changing the language state of Plone programmatically.

Introduction

Each page view has a language associated with it.

The active language is negotiated by the plone.i18n.negotiator module. Several factors may be involved in determining what the language should be:

  • Cookies (setting from the language selector)

  • The top-level domain name (e.g. .fi for Finnish, .se for Swedish)

  • Context (current content) language

  • Browser language headers

Language is negotiated at the beginning of the page view.

Languages are managed by portal_languagetool.

Getting the current language

Example view/viewlet method of getting the current language.

from Products.Five.browser import BrowserView
from zope.component import getMultiAdapter

class MyView(BrowserView):

    ...

    def language(self):
        """
        @return: Two-letter string, the active language code
        """
        context = self.context.aq_inner
        portal_state = getMultiAdapter((context, self.request), name=u'plone_portal_state')
        current_language = portal_state.language()
        return current_language

Getting language of content item

All content objects don’t necessarily support the Language() look-up defined by the IDublinCore interface. Below is the safe way to extract the served language on the content.

Example BrowserView method:

from Acquisition import aq_inner

def language(self):
    """ Get the language of the context.

    Useful in producing <html> tag.
    You need to output language for every HTML page, see http://www.w3.org/TR/xhtml1/#strict

    @return: The two letter language code of the current content.
    """
    portal_state = self.context.unrestrictedTraverse("@@plone_portal_state")

    return aq_inner(self.context).Language() or portal_state.default_language()

Getting available site languages

Example below:

# Python 2.6 compatible ordered dict
# NOTE: API is not 1:1, but for normal dict access of
# set member, iterate keys and values this is enough
try:
    from collections import OrderedDict
except ImportError:
    from odict import odict as OrderedDict

def getLanguages(self):
    """
    Return list of active langauges as ordered dictionary, the preferred first language as the first.

    Example output::

         {
            u'fi': {u'id' : u'fi', u'flag': u'/++resource++country-flags/fi.gif', u'name': u'Finnish', u'native': u'Suomi'},
            u'de': {u'id' : u'de', u'flag': u'/++resource++country-flags/de.gif', u'name': u'German', u'native': u'Deutsch'},
            u'en': {u'id' : u'en', u'flag': u'/++resource++country-flags/gb.gif', u'name': u'English', u'native': u'English'},
            u'ru': {u'id' : u'ru', u'flag': u'/++resource++country-flags/ru.gif', u'name': u'Russian', u'native': u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439'}
          }
    """
    result = OrderedDict()

    portal_languages = self.context.portal_languages

    # Get barebone language listing from portal_languages tool
    langs = portal_languages.getAvailableLanguages()

    preferred = portal_languages.getPreferredLanguage()

    # Preferred first
    for lang, data in langs.items():
        if lang == preferred:
            result[lang] = data

    # Then other languages
    for lang, data in langs.items():
        if lang != preferred:
            result[lang] = data

    # For convenience, include the language ISO code in the export,
    # so it is easier to iterate data in the templates
    for lang, data in result.items():
        data["id"] = lang

    return result

Simple language conditions in page templates

You can do this if full translation strings are not worth the trouble:

<div class="main-text">
  <a tal:condition="python:context.restrictedTraverse('@@plone_portal_state').language() == 'fi'" href="http://www.saariselka.fi/sisalto?force-web">Siirry täydelle web-sivustolle</a>
  <a tal:condition="python:context.restrictedTraverse('@@plone_portal_state').language() != 'fi'" href="http://www.saariselka.fi/sisalto?force-web">Go to full website</a>
</div>

Set site language settings

Manually:

# Setup site language settings
portal = context.getSite()
ltool = portal.portal_languages
defaultLanguage = 'en'
supportedLanguages = ['en','es']
ltool.manage_setLanguageSettings(defaultLanguage, supportedLanguages,
                                      setUseCombinedLanguageCodes=False)

For unit testing, you need to run this in afterSetUp() after setting up the languages:

# THIS IS FOR UNIT TESTING ONLY
# Normally called by pretraverse hook,
# but must be called manually for the unit tests
# Goes only for the current request
ltool.setLanguageBindings()

Using GenericSetup and propertiestool.xml

<object name="portal_properties" meta_type="Plone Properties Tool">
   <object name="site_properties" meta_type="Plone Property Sheet">
      <property name="default_language" type="string">en</property>
   </object>
</object>

Customizing language selector

Making language flags point to different top level domains

If you use multiple domain names for different languages it is often desirable to make the language selector point to a different domain. Search engines do not really like the dynamic language switchers and will index switching links, messing up your site search results.

Example: TODO

Login-aware language negotiation

By default, language negotiation happens before authentication. Therefore, if you wish to use authenticated credentials in the negotiation, you can do the following.

Hook the after-traversal event.

Example event registration

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser"
    xmlns:zcml="http://namespaces.zope.org/zcml"
    >
    <subscriber handler=".language_negotiation.Negotiator"/>
</configure>

Corresponding event handler:

from zope.interface import Interface
from zope.component import adapter
from ZPublisher.interfaces import IPubEvent,IPubAfterTraversal
from Products.CMFCore.utils import getToolByName
from AccessControl import getSecurityManager
from zope.app.component.hooks import getSite

@adapter(IPubAfterTraversal)
def Negotiator(event):

    # Keep the current request language (negotiated on portal_languages)
    # untouched

    site = getSite()
    ms = getToolByName(site, 'portal_membership')
    member = ms.getAuthenticatedMember()
    if member.getUserName() == 'Anonymous User':
        return

    language = member.language
    if language:
        # Fake new language for all authenticated users
        event.request['LANGUAGE'] = language
        event.request.LANGUAGE_TOOL.LANGUAGE = language
    else:
        lt = getToolByName(site, 'portal_languages')
        event.request['LANGUAGE'] = lt.getDefaultLanguage()
        event.request.LANGUAGE_TOOL.LANGUAGE = lt.getDefaultLanguage()