Back to index

plone3  3.1.7
LanguageTool.py
Go to the documentation of this file.
00001 from plone.i18n.locales.interfaces import ICountryAvailability
00002 from plone.i18n.locales.interfaces import IContentLanguageAvailability
00003 from plone.i18n.locales.interfaces import ICcTLDInformation
00004 
00005 from zope.component import getUtility
00006 from zope.component import queryUtility
00007 from zope.interface import implements
00008 
00009 from AccessControl import ClassSecurityInfo
00010 from Globals import InitializeClass
00011 from OFS.SimpleItem import SimpleItem
00012 from Products.CMFCore.interfaces import ISiteRoot
00013 from Products.CMFCore.permissions import ManagePortal
00014 from Products.CMFCore.permissions import View
00015 from Products.CMFCore.utils import registerToolInterface
00016 from Products.CMFCore.utils import getToolByName
00017 from Products.CMFCore.utils import UniqueObject
00018 from Products.CMFPlone.interfaces.Translatable import ITranslatable
00019 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00020 from ZODB.POSException import ConflictError
00021 from ZPublisher import BeforeTraverse
00022 from ZPublisher.HTTPRequest import HTTPRequest
00023 
00024 from Products.PloneLanguageTool.interfaces import ILanguageTool
00025 
00026 try:
00027     from Products.PlacelessTranslationService.Negotiator import registerLangPrefsMethod
00028     _hasPTS = 1
00029 except ImportError:
00030     _hasPTS = 0
00031 
00032 class LanguageTool(UniqueObject, SimpleItem):
00033     """Language Administration Tool For Plone."""
00034 
00035     id  = 'portal_languages'
00036     title = 'Manages available languages'
00037     meta_type = 'Plone Language Tool'
00038 
00039     implements(ILanguageTool)
00040 
00041     security = ClassSecurityInfo()
00042 
00043     supported_langs = ['en']
00044     use_path_negotiation = 1
00045     use_cookie_negotiation = 1
00046     use_request_negotiation = 1
00047     use_cctld_negotiation = 0
00048     use_combined_language_codes = 0
00049     force_language_urls = 1
00050     allow_content_language_fallback = 0
00051     display_flags = 1
00052     start_neutral = 0
00053 
00054     manage_options=(
00055         ({ 'label'  : 'LanguageConfig',
00056            'action' : 'manage_configForm',
00057            },
00058          ) + SimpleItem.manage_options
00059         )
00060 
00061     manage_configForm = PageTemplateFile('www/config', globals())
00062 
00063     def __init__(self):
00064         self.id = 'portal_languages'
00065         self.use_path_negotiation = 1
00066         self.use_cookie_negotiation  = 1
00067         self.use_request_negotiation = 1
00068         self.use_cctld_negotiation = 0
00069         self.use_combined_language_codes = 0
00070         self.force_language_urls = 1
00071         self.allow_content_language_fallback = 0
00072         self.display_flags = 1
00073         self.start_neutral = 0
00074 
00075     def __call__(self, container, req):
00076         """The __before_publishing_traverse__ hook."""
00077         if req.__class__ is not HTTPRequest:
00078             return None
00079         if not req['REQUEST_METHOD'] in ('HEAD', 'GET', 'PUT', 'POST'):
00080             return None
00081         if req.environ.has_key('WEBDAV_SOURCE_PORT'):
00082             return None
00083         # Bind the languages
00084         self.setLanguageBindings()
00085 
00086     security.declareProtected(ManagePortal, 'manage_setLanguageSettings')
00087     def manage_setLanguageSettings(self, defaultLanguage, supportedLanguages,
00088                                    setCookieN=None, setRequestN=None,
00089                                    setPathN=None, setForcelanguageUrls=None,
00090                                    setAllowContentLanguageFallback=None,
00091                                    setUseCombinedLanguageCodes=None,
00092                                    displayFlags=None, startNeutral=None,
00093                                    setCcTLDN=None, REQUEST=None):
00094         """Stores the tool settings."""
00095         if supportedLanguages and type(supportedLanguages) == type([]):
00096             self.supported_langs = supportedLanguages
00097 
00098         if defaultLanguage in self.supported_langs:
00099             self.setDefaultLanguage(defaultLanguage)
00100         else:
00101             self.setDefaultLanguage(self.supported_langs[0])
00102 
00103         if setCookieN:
00104             self.use_cookie_negotiation = 1
00105         else:
00106             self.use_cookie_negotiation = 0
00107 
00108         if setRequestN:
00109             self.use_request_negotiation = 1
00110         else:
00111             self.use_request_negotiation = 0
00112 
00113         if setPathN:
00114             self.use_path_negotiation = 1
00115         else:
00116             self.use_path_negotiation = 0
00117 
00118         if setCcTLDN:
00119             self.use_cctld_negotiation = 1
00120         else:
00121             self.use_cctld_negotiation = 0
00122 
00123         if setForcelanguageUrls:
00124             self.force_language_urls = 1
00125         else:
00126             self.force_language_urls = 0
00127 
00128         if setAllowContentLanguageFallback:
00129             self.allow_content_language_fallback = 1
00130         else:
00131             self.allow_content_language_fallback = 0
00132 
00133         if setUseCombinedLanguageCodes:
00134             self.use_combined_language_codes = 1
00135         else:
00136             self.use_combined_language_codes = 0
00137 
00138         if displayFlags:
00139             self.display_flags = 1
00140         else:
00141             self.display_flags = 0
00142 
00143         if startNeutral:
00144             self.start_neutral = 1
00145         else:
00146             self.start_neutral = 0
00147 
00148         if REQUEST:
00149             REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00150 
00151     security.declarePublic('startNeutral')
00152     def startNeutral(self):
00153         """Checks if the content start as language neutral or using the
00154         preferred language.
00155         """
00156         return self.start_neutral
00157 
00158     security.declarePublic('showFlags')
00159     def showFlags(self):
00160         """Shows the flags in language listings or not."""
00161         return self.display_flags
00162 
00163     security.declareProtected(View, 'getSupportedLanguages')
00164     def getSupportedLanguages(self):
00165         """Returns a list of supported language codes."""
00166         return self.supported_langs
00167 
00168     security.declareProtected(View, 'listSupportedLanguages')
00169     def listSupportedLanguages(self):
00170         """Returns a list of supported language names."""
00171         r = []
00172         available = self.getAvailableLanguages()
00173         for i in self.supported_langs:
00174             if available.get(i):
00175                 r.append((i,available[i][u'name']))
00176         return r
00177 
00178     security.declarePublic('getAvailableLanguages')
00179     def getAvailableLanguages(self):
00180         """Returns the dictionary of available languages.
00181         """
00182         util = queryUtility(IContentLanguageAvailability)
00183         if self.use_combined_language_codes:
00184             languages = util.getLanguages(combined=True)
00185         else:
00186             languages = util.getLanguages()
00187         return languages
00188 
00189     security.declarePublic('getCcTLDInformation')
00190     def getCcTLDInformation(self):
00191         util = queryUtility(ICcTLDInformation)
00192         return util.getTLDs()
00193 
00194     security.declarePublic('listAvailableLanguages')
00195     def listAvailableLanguages(self):
00196         """Returns sorted list of available languages (code, name)."""
00197         util = queryUtility(IContentLanguageAvailability)
00198         if self.use_combined_language_codes:
00199             languages = util.getLanguageListing(combined=True)
00200         else:
00201             languages = util.getLanguageListing()
00202         languages.sort(lambda x, y: cmp(x[1], y[1]))
00203         return languages
00204 
00205     security.declarePublic('listAvailableLanguageInformation')
00206     def listAvailableLanguageInformation(self):
00207         """Returns list of available languages."""
00208         langs = self.getAvailableLanguageInformation()
00209         new_langs = []
00210         for lang in langs:
00211             # add language-code to dict
00212             langs[lang][u'code'] = lang
00213             # flatten outer dict to list to make it sortable
00214             new_langs.append(langs[lang])
00215         new_langs.sort(lambda x, y: cmp(x.get(u'native', x.get(u'name')), y.get(u'native', y.get(u'name'))))
00216         return new_langs
00217 
00218     security.declarePublic('getAvailableLanguageInformation')
00219     def getAvailableLanguageInformation(self):
00220         """Returns the dictionary of available languages."""
00221         util = queryUtility(IContentLanguageAvailability)
00222         if self.use_combined_language_codes:
00223             languages = util.getLanguages(combined=True)
00224         else:
00225             languages = util.getLanguages()
00226 
00227         for lang in languages:
00228             languages[lang]['code'] = lang
00229             if lang in self.supported_langs:
00230                 languages[lang]['selected'] = True
00231             else:
00232                 languages[lang]['selected'] = False
00233         return languages
00234 
00235     security.declareProtected(View, 'getDefaultLanguage')
00236     def getDefaultLanguage(self):
00237         """Returns the default language."""
00238         portal_properties = getToolByName(self, "portal_properties", None)
00239         if portal_properties is None:
00240             return 'en'
00241         site_properties = portal_properties.site_properties
00242         if site_properties.hasProperty('default_language'):
00243             return site_properties.getProperty('default_language')
00244         portal = getUtility(ISiteRoot)
00245         if portal.hasProperty('default_language'):
00246             return portal.getProperty('default_language')
00247         return getattr(self, 'default_lang', 'en')
00248 
00249     security.declareProtected(ManagePortal, 'setDefaultLanguage')
00250     def setDefaultLanguage(self, langCode):
00251         """Sets the default language."""
00252         portal_properties = getToolByName(self, "portal_properties")
00253         site_properties = portal_properties.site_properties
00254         if site_properties.hasProperty('default_language'):
00255             return site_properties._updateProperty('default_language', langCode)
00256         portal = getUtility(ISiteRoot)
00257         if portal.hasProperty('default_language'):
00258             return portal._updateProperty('default_language', langCode)
00259         self.default_lang = langCode
00260 
00261     security.declareProtected(View, 'getNameForLanguageCode')
00262     def getNameForLanguageCode(self, langCode):
00263         """Returns the name for a language code."""
00264         info = self.getAvailableLanguageInformation().get(langCode, None)
00265         if info is not None:
00266             return info.get(u'name', None)
00267         return None
00268 
00269     security.declareProtected(View, 'getFlagForLanguageCode')
00270     def getFlagForLanguageCode(self, langCode):
00271         """Returns the name of the flag for a language code."""
00272         info = self.getAvailableLanguageInformation().get(langCode, None)
00273         if info is not None:
00274             return info.get(u'flag', None)
00275         return None
00276 
00277     security.declareProtected(ManagePortal, 'addSupportedLanguage')
00278     def addSupportedLanguage(self, langCode):
00279         """Registers a language code as supported."""
00280         alist = self.supported_langs[:]
00281         if (langCode in self.getAvailableLanguages().keys()) and not langCode in alist:
00282             alist.append(langCode)
00283             self.supported_langs = alist
00284 
00285     security.declareProtected(ManagePortal, 'removeSupportedLanguages')
00286     def removeSupportedLanguages(self, langCodes):
00287         """Unregisters language codes from supported."""
00288         alist = self.supported_langs[:]
00289         for i in langCodes:
00290             alist.remove(i)
00291         self.supported_langs = alist
00292 
00293     security.declareProtected(View, 'setLanguageCookie')
00294     def setLanguageCookie(self, lang=None, REQUEST=None, noredir=None):
00295         """Sets a cookie for overriding language negotiation."""
00296         res = None
00297         if lang and lang in self.getSupportedLanguages():
00298             if lang != self.getLanguageCookie():
00299                 self.REQUEST.RESPONSE.setCookie('I18N_LANGUAGE', lang, path='/')
00300             res = lang
00301         if noredir is None:
00302             if REQUEST:
00303                 REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])
00304         return res
00305 
00306     security.declareProtected(View, 'getLanguageCookie')
00307     def getLanguageCookie(self):
00308         """Gets the preferred cookie language."""
00309         if not hasattr(self, 'REQUEST'):
00310             return None
00311         langCookie = self.REQUEST.cookies.get('I18N_LANGUAGE')
00312         if langCookie in self.getSupportedLanguages():
00313             return langCookie
00314         return None
00315 
00316     security.declareProtected(View, 'getPreferredLanguage')
00317     def getPreferredLanguage(self):
00318         """Gets the preferred site language."""
00319         l = self.getLanguageBindings()
00320         if l[0]:
00321             if not self.use_combined_language_codes:
00322                 return l[0].split('-')[0]
00323             else:
00324                 return l[0]
00325             return l[0]
00326         # this is the default language
00327         return l[1]
00328 
00329     def manage_beforeDelete(self, item, container):
00330         if item is self:
00331             handle = self.meta_type + '/' + self.getId()
00332             BeforeTraverse.unregisterBeforeTraverse(container, handle)
00333 
00334     def manage_afterAdd(self, item, container):
00335         if item is self:
00336             handle = self.meta_type + '/' + self.getId()
00337             container = container.this()
00338             nc = BeforeTraverse.NameCaller(self.getId())
00339             BeforeTraverse.registerBeforeTraverse(container, nc, handle)
00340 
00341     security.declareProtected(View, 'getPathLanguage')
00342     def getPathLanguage(self):
00343         """Checks if a language is part of the current path."""
00344         if not hasattr(self, 'REQUEST'):
00345             return []
00346         items = self.REQUEST.get('TraversalRequestNameStack')
00347         # XXX Why this try/except?
00348         try:
00349             for item in items:
00350                 if item in self.getSupportedLanguages():
00351                     return item
00352         except (ConflictError, KeyboardInterrupt):
00353             raise
00354         except:
00355             pass
00356         return None
00357 
00358     security.declareProtected(View, 'getCcTLDLanguages')
00359     def getCcTLDLanguages(self):
00360         if not hasattr(self, 'REQUEST'):
00361             return None
00362         request = self.REQUEST
00363         if not "HTTP_HOST" in request:
00364             return None
00365         host=request["HTTP_HOST"].split(":")[0].lower()
00366         tld=host.split(".")[-1]
00367         wanted = self.getCcTLDInformation().get(tld, [])
00368         allowed = self.getSupportedLanguages()
00369         return [lang for lang in wanted if lang in allowed]
00370 
00371     security.declareProtected(View, 'getRequestLanguages')
00372     def getRequestLanguages(self):
00373         """Parses the request and return language list."""
00374 
00375         if not hasattr(self, 'REQUEST'):
00376             return None
00377 
00378         # Get browser accept languages
00379         browser_pref_langs = self.REQUEST.get('HTTP_ACCEPT_LANGUAGE', '')
00380         browser_pref_langs = browser_pref_langs.split(',')
00381 
00382         i = 0
00383         langs = []
00384         length = len(browser_pref_langs)
00385 
00386         # Parse quality strings and build a tuple like
00387         # ((float(quality), lang), (float(quality), lang))
00388         # which is sorted afterwards
00389         # If no quality string is given then the list order
00390         # is used as quality indicator
00391         for lang in browser_pref_langs:
00392             lang = lang.strip().lower().replace('_', '-')
00393             if lang:
00394                 l = lang.split(';', 2)
00395                 quality = []
00396 
00397                 if len(l) == 2:
00398                     try:
00399                         q = l[1]
00400                         if q.startswith('q='):
00401                             q = q.split('=', 2)[1]
00402                             quality = float(q)
00403                     except:
00404                         pass
00405 
00406                 if quality == []:
00407                     quality = float(length-i)
00408 
00409                 language = l[0]
00410                 if self.use_combined_language_codes:
00411                     if language in self.getSupportedLanguages():
00412                         # If allowed the add language
00413                         langs.append((quality, language))
00414                 else:
00415                     # if we only use simply language codes, we should recognize
00416                     # combined codes as their base code. So 'de-de' is treated
00417                     # as 'de'.
00418                     baselanguage = language.split('-')[0]
00419                     if baselanguage in self.getSupportedLanguages():
00420                         langs.append((quality, baselanguage))
00421                 i = i + 1
00422 
00423         # Sort and reverse it
00424         langs.sort()
00425         langs.reverse()
00426 
00427         # Filter quality string
00428         langs = map(lambda x: x[1], langs)
00429 
00430         return langs
00431 
00432     security.declareProtected(View, 'setLanguageBindings')
00433     def setLanguageBindings(self):
00434         """Setups the current language stuff."""
00435         useCcTLD = self.use_cctld_negotiation
00436         usePath = self.use_path_negotiation
00437         useCookie = self.use_cookie_negotiation
00438         useRequest = self.use_request_negotiation
00439         useDefault = 1 # This should never be disabled
00440         if not hasattr(self, 'REQUEST'):
00441             return
00442         binding = self.REQUEST.get('LANGUAGE_TOOL', None)
00443         if not isinstance(binding, LanguageBinding):
00444             # Create new binding instance
00445             binding = LanguageBinding(self)
00446         # Bind languages
00447         lang = binding.setLanguageBindings(usePath, useCookie, useRequest, useDefault, useCcTLD)
00448         # Set LANGUAGE to request
00449         self.REQUEST['LANGUAGE'] = lang
00450         # Set bindings instance to request
00451         self.REQUEST['LANGUAGE_TOOL'] = binding
00452         return lang
00453 
00454     security.declareProtected(View, 'getLanguageBindings')
00455     def getLanguageBindings(self):
00456         """Returns the bound languages.
00457 
00458         (language, default_language, languages_list)
00459         """
00460         if not hasattr(self, 'REQUEST'):
00461             # Can't do anything
00462             return (None, self.getDefaultLanguage(), [])
00463         binding = self.REQUEST.get('LANGUAGE_TOOL', None)
00464         if not isinstance(binding, LanguageBinding):
00465             # Not bound -> bind
00466             self.setLanguageBindings()
00467             binding = self.REQUEST.get('LANGUAGE_TOOL')
00468         return binding.getLanguageBindings()
00469 
00470     security.declarePublic('isTranslatable')
00471     def isTranslatable(self, obj):
00472         """Checks if ITranslatable interface is implemented."""
00473         try:
00474             if obj.checkCreationFlag():
00475                 return False
00476         except NameError:
00477             pass
00478         return ITranslatable.isProvidedBy(obj)
00479 
00480     security.declarePublic('getAvailableCountries')
00481     def getAvailableCountries(self):
00482         """Returns the dictionary of available countries."""
00483         util = queryUtility(ICountryAvailability)
00484         return util.getCountries()
00485 
00486     security.declarePublic('listAvailableCountries')
00487     def listAvailableCountries(self):
00488         """Returns the sorted list of available countries (code, name)."""
00489         util = queryUtility(ICountryAvailability)
00490         countries = util.getCountryListing()
00491         countries.sort(lambda x, y: cmp(x[1], y[1]))
00492         return countries
00493 
00494     security.declareProtected(View, 'getNameForCountryCode')
00495     def getNameForCountryCode(self, countryCode):
00496         """Returns the name for a country code."""
00497         return self.getAvailableCountries().get(countryCode, countryCode)
00498 
00499 
00500 class LanguageBinding:
00501     """Helper which holding language infos in request."""
00502     security = ClassSecurityInfo()
00503     __allow_access_to_unprotected_subobjects__ = 1
00504 
00505     DEFAULT_LANGUAGE = None
00506     LANGUAGE = None
00507     LANGUAGE_LIST = []
00508 
00509     def __init__(self, tool):
00510         self.tool = tool
00511 
00512     security.declarePrivate('setLanguageBindings')
00513     def setLanguageBindings(self, usePath=1, useCookie=1, useRequest=1, useDefault=1, useCcTLD=0):
00514         """Setup the current language stuff."""
00515         langs = []
00516 
00517         if usePath:
00518             # This one is set if there is an allowed language in the current path
00519             langsPath = [self.tool.getPathLanguage()]
00520         else:
00521             langsPath = []
00522 
00523         if useCookie:
00524             # If we are using the cookie stuff we provide the setter here
00525             set_language = self.tool.REQUEST.get('set_language', None)
00526             if set_language:
00527                 langsCookie = [self.tool.setLanguageCookie(set_language)]
00528             else:
00529                 # Get from cookie
00530                 langsCookie = [self.tool.getLanguageCookie()]
00531         else:
00532             langsCookie = []
00533 
00534         if useCcTLD:
00535             langsCcTLD = self.tool.getCcTLDLanguages()
00536         else:
00537             langsCcTLD = []
00538         # Get langs from request
00539         if useRequest:
00540             langsRequest = self.tool.getRequestLanguages()
00541         else:
00542             langsRequest = []
00543 
00544         # Get default
00545         if useDefault:
00546             langsDefault = [self.tool.getDefaultLanguage()]
00547         else:
00548             langsDefault = []
00549 
00550         # Build list
00551         langs = langsPath+langsCookie+langsCcTLD+langsRequest+langsDefault
00552 
00553         # Filter None languages
00554         langs = [lang for lang in langs if lang is not None]
00555 
00556         self.DEFAULT_LANGUAGE = langs[-1]
00557         self.LANGUAGE = langs[0]
00558         self.LANGUAGE_LIST = langs[1:-1]
00559 
00560         return self.LANGUAGE
00561 
00562     security.declarePublic('getLanguageBindings')
00563     def getLanguageBindings(self):
00564         """Returns the bound languages.
00565 
00566         (language, default_language, languages_list)
00567         """
00568         return (self.LANGUAGE, self.DEFAULT_LANGUAGE, self.LANGUAGE_LIST)
00569 
00570 
00571 class PrefsForPTS:
00572     """A preference to hook into PTS."""
00573     def __init__(self, context):
00574         self._env = context
00575         self.languages = []
00576         binding = context.get('LANGUAGE_TOOL')
00577         if not isinstance(binding, LanguageBinding):
00578             return None
00579         self.pref = binding.getLanguageBindings()
00580         self.languages = [self.pref[0]] + self.pref[2] + [self.pref[1]]
00581         return None
00582 
00583     def getPreferredLanguages(self):
00584         """Returns the list of the bound languages."""
00585         return self.languages
00586 
00587 
00588 if _hasPTS:
00589     registerLangPrefsMethod({'klass':PrefsForPTS, 'priority':100 })
00590 
00591 InitializeClass(LanguageTool)