Localizer: Integrate old monkey-patches directly

Also, use local imports of itools where this is still necessary,  and remove all mention of itools from ERP5 code.
parent 526cafce
......@@ -1111,8 +1111,7 @@ class ActivityTool (Folder, UniqueObject):
# runing unit tests. Recreate it if it does not exist.
if getattr(request.other, 'PARENTS', None) is None:
request.other['PARENTS'] = parents
# XXX: itools (used by Localizer) requires PATH_INFO to be set, and it's
# not when runing unit tests. Recreate it if it does not exist.
# XXX: PATH_INFO might not be set when runing unit tests.
if request.environ.get('PATH_INFO') is None:
request.environ['PATH_INFO'] = '/Control_Panel/timer_service/process_timer'
......
......@@ -268,7 +268,7 @@ class ERP5Site(FolderMixIn, CMFSite, CacheCookieMixin):
def _doTranslationDomainRegistration(self):
from zope.i18n.interfaces import ITranslationDomain
from Products.ERP5Type.patches.Localizer import (
from Products.Localizer.MessageCatalog import (
message_catalog_alias_sources
)
sm = self.getSiteManager()
......
......@@ -44,8 +44,6 @@ from Products.ERP5Type.patches import ActionInformation
from Products.ERP5Type.patches import ActionProviderBase
from Products.ERP5Type.patches import ActionsTool
from Products.ERP5Type.patches import CookieCrumbler
from Products.ERP5Type.patches import i18n
from Products.ERP5Type.patches import Localizer
from Products.ERP5Type.patches import PropertySheets
from Products.ERP5Type.patches import CMFCoreSkinnable
from Products.ERP5Type.patches import CMFCoreSkinsTool
......
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
# Copyright (c) 2002-2006 Nexedi SARL and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from string import Template
from itools import i18n
from Products.ERP5Type.Message import Message
from zLOG import LOG, ERROR
# This patch will not work if Translation Service Zope product exist on the system
try:
from Products import TranslationService
LOG("ERP5Type.patches.Localizer", ERROR, "Translation Service Zope Product"
" (%s) and Translation Service tools must be deleted to let Localizer "
"Patch work." % (repr(TranslationService)))
except ImportError:
pass
# This dict define the alias between old Translation Service catalog id
# and new Localizer Message Catalog.
message_catalog_aliases = { "Default": "default"
, "ui" : "erp5_ui"
, "content": "erp5_content"
}
# "invert" message_catalog_aliases mapping
message_catalog_alias_sources = {}
for name, value in message_catalog_aliases.items():
message_catalog_alias_sources.setdefault(value, []).append(name)
# BACK: This method is not used in Zope 2.12. Drop when we drop support for
# Zope 2.8
def Localizer_translate(self, domain, msgid, lang=None, mapping=None, *args, **kw):
"""
This translate() method use Localizer and support catalog aliases.
"""
# Get the Localizer catalog id
catalog_id = message_catalog_aliases.get(domain, domain)
catalog_obj = self._getOb(catalog_id, None)
if catalog_obj is None:
# No catalog found: use the default one
catalog_obj = self['default']
# Call the Message Catalog gettext method
params = {}
for key in ('add', 'default'):
try:
params[key] = kw[key]
except KeyError:
pass
if lang is not None:
params['lang'] = lang
else:
try:
params['lang'] = kw['target_language']
except KeyError:
pass
translated_str = catalog_obj.gettext(msgid, **params)
# Map the translated string with given parameters
if isinstance(mapping, dict) and mapping:
unicode_mapping = {}
if not isinstance(translated_str, unicode):
translated_str = translated_str.decode('utf8')
# make sure all values in the mapping are unicode
for k, v in mapping.iteritems():
if isinstance(v, str):
v = v.decode('utf8')
elif isinstance(v, Message):
v = str(v).decode('utf8')
unicode_mapping[k] = v
translated_str = Template(translated_str).substitute(unicode_mapping)
return translated_str
# Apply the monkey patch.
from Products.Localizer.Localizer import Localizer
Localizer.translate = Localizer_translate
Localizer.translate__roles__ = None # public
# Fix MessageCatalog's manage_export that fails with unicode strings
from Products.Localizer.MessageCatalog import MessageCatalog
original_manage_export = MessageCatalog.manage_export
def cleanup_and_export(self, x, REQUEST=None, RESPONSE=None):
"""Path manage_export to cleanup messages whose keys are unicode objects
before exporting.
"""
message_keys = self._messages.keys()
for k in message_keys:
if isinstance(k, unicode):
message = self._messages.get(k.encode('utf8'),
self._messages.get(k))
del self._messages[k]
self._messages[k.encode('utf8')] = message
return original_manage_export(self, x, REQUEST=REQUEST, RESPONSE=RESPONSE)
MessageCatalog.manage_export = cleanup_and_export
# Add a feature which allows users to be able to add a new language.
#
# Patch to LanguageManager.py
#
def get_languages_mapping(self):
"""
Returns a list of dictionary, one for each objects language. The
dictionary contains the language code, its name and a boolean
value that tells wether the language is the default one or not.
"""
return [ {'code': x,
'name': self.get_language_name(x),
'default': x == self._default_language}
for x in self._languages ]
def get_language_name(self, id=None):
"""
Returns the name of the given language code.
XXX Kept here for backwards compatibility only
"""
if id is None:
id = self.get_default_language()
language_name = i18n.get_language_name(id)
if language_name=='???':
return self.get_user_defined_language_name(id) or language_name
else:
return language_name
# New method
def get_user_defined_language_name(self, id=None):
"""
Returns the name of the given user defined language code.
"""
for language_dict in self.get_user_defined_languages():
if language_dict['code']==id:
return language_dict['name']
def get_all_languages(self):
"""
Returns all ISO languages, used by 'manage_languages'.
"""
return i18n.get_languages() + self.get_user_defined_languages()
# New method
def get_user_defined_languages(self):
user_define_language_dict_list = []
localizer = getattr(self, 'Localizer', None)
if localizer is not None:
for value in getattr(self, 'user_defined_languages', ()):
splitted_value = value.split(' ', 1)
if len(splitted_value)==2:
user_define_language_dict_list.append(
{'name':splitted_value[0].strip(),
'code':splitted_value[1].strip(),})
return user_define_language_dict_list
# New method
def _add_user_defined_language(self, language_name, language_code):
self.user_defined_languages = (
getattr(self, 'user_defined_languages', ())+
('%s %s' % (language_name, language_code),)
)
self._p_changed = True
# New method
def _del_user_defined_language(self, language_code):
user_defined_languages = []
for language_dict in self.get_user_defined_languages():
if language_dict['code']!=language_code:
user_defined_languages.append('%s %s' %
(language_dict['name'],
language_dict['code']))
self.user_defined_languages = tuple(user_defined_languages)
self._p_changed = True
# Override add_language so that languages are always sorted.
# Otherwise, selected languages can be nearly random.
def add_language(self, language):
"""
Adds a new language.
"""
if language not in self._languages:
new_language_list = tuple(self._languages) + (language,)
new_language_list = tuple(sorted(new_language_list))
self._languages = new_language_list
from Products.Localizer import LanguageManager
LanguageManager.LanguageManager.get_languages_mapping = get_languages_mapping
LanguageManager.LanguageManager.get_language_name = get_language_name
LanguageManager.LanguageManager.get_all_languages = get_all_languages
LanguageManager.LanguageManager.get_user_defined_language_name = get_user_defined_language_name
LanguageManager.LanguageManager.get_user_defined_languages = get_user_defined_languages
LanguageManager.LanguageManager._add_user_defined_language = _add_user_defined_language
LanguageManager.LanguageManager._del_user_defined_language = _del_user_defined_language
LanguageManager.LanguageManager.add_language = add_language
LanguageManager.InitializeClass(LanguageManager.LanguageManager)
#
# Patch to Localizer.py
#
_properties = ({'id': 'title', 'type': 'string'},
{'id': 'accept_methods', 'type': 'tokens'},
{'id': 'user_defined_languages', 'type': 'lines'},)
user_defined_languages = ()
def get_languages_map(self):
"""
Return a list of dictionaries, each dictionary has the language
id, its title and a boolean value to indicate wether it's the
user preferred language, for example:
[{'id': 'en', 'title': 'English', 'selected': 1}]
Used in changeLanguageForm.
"""
# For now only LPM instances are considered to be containers of
# multilingual data.
try:
ob = self.getLocalPropertyManager()
except AttributeError:
ob = self
ob_language = ob.get_selected_language()
ob_languages = ob.get_available_languages()
langs = []
for x in ob_languages:
langs.append({'id': x, 'title': self.get_language_name(x),
'selected': x == ob_language})
return langs
from Products.Localizer import Localizer
Localizer.Localizer._properties = _properties
Localizer.Localizer.user_defined_languages = user_defined_languages
Localizer.Localizer.get_languages_map = get_languages_map
Localizer.InitializeClass(Localizer.Localizer)
# BACK: Can't write a configure.zcml that works on both Zope 2.8 and Zope 2.12
# So we monkeypatch the subscriber instead. When we drop support for Zope 2.8
# write a proper subscriber for MessageCatalog and IObjectMovedEvent
if 1:
from Products.Localizer.MessageCatalog import (MessageCatalog_moved as
MessageCatalog_moved_orig)
from zope.component import getSiteManager
from zope.i18n.interfaces import ITranslationDomain
import Products.Localizer.MessageCatalog
def MessageCatalog_moved(object, event):
"""
Install ITranslationDomain aliases alongside the original
MessageCatalog names
"""
MessageCatalog_moved_orig(object, event)
if event.oldParent is not None:
# unregister old aliases
oldAliases = message_catalog_alias_sources.get(event.oldName, ())
sm = getSiteManager(event.oldParent)
for alias in oldAliases:
sm.unregisterUtility(object, ITranslationDomain, alias)
if event.newParent is not None:
# register new aliases
newAliases = message_catalog_alias_sources.get(event.newName, ())
sm = getSiteManager(event.newParent)
# FIXME: install aliases only if inside an ERP5Site
# but how to do that without causing circular dependencies? ERP5Site
# needs to implement an IERP5Site interface, declared inside
# Products.ERP5Type.interfaces
for alias in newAliases:
sm.registerUtility(object, ITranslationDomain, alias)
Products.Localizer.MessageCatalog.MessageCatalog_moved = MessageCatalog_moved
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
# allow access to AcceptLanguage for itools 0.16.2 or later
from itools.i18n.accept import AcceptLanguage
if getattr(AcceptLanguage, '__allow_access_to_unprotected_subobjects__',
None) is None:
AcceptLanguage.__allow_access_to_unprotected_subobjects__ = 1
del AcceptLanguage
......@@ -53,12 +53,6 @@ Globals.get_request = get_request
from zope.site.hooks import setSite
try:
import itools.zope
itools.zope.get_context = get_context
except ImportError:
pass
from Testing import ZopeTestCase
from Testing.ZopeTestCase import PortalTestCase, user_name
from Products.CMFCore.utils import getToolByName
......
# -*- coding: UTF-8 -*-
# -*- coding: utf-8 -*-
# Copyright (C) 2000-2004 Juan David Ibáñez Palomar <jdavid@itaapy.com>
#
# This program is free software: you can redistribute it and/or modify
......@@ -18,7 +18,7 @@
from urlparse import urlparse
# Import from itools
from itools.i18n import get_language_name, get_languages
from .itools.i18n import get_language_name, get_languages
# Import from Zope
from App.class_init import InitializeClass
......@@ -60,8 +60,11 @@ class LanguageManager(Tabs):
"""Adds a new language.
"""
if language not in self._languages:
self._languages = tuple(self._languages) + (language,)
# Sort the language list, else selected languages
# can be nearly random.
new_language_list = tuple(self._languages) + (language,)
new_language_list = tuple(sorted(new_language_list))
self._languages = new_language_list
def del_language(self, language):
"""Removes a language.
......@@ -77,7 +80,7 @@ class LanguageManager(Tabs):
that tells wether the language is the default one or not.
"""
return [ {'code': x,
'name': get_language_name(x),
'name': self.get_language_name(x),
'default': x == self._default_language}
for x in self._languages ]
......@@ -127,7 +130,11 @@ class LanguageManager(Tabs):
"""
if id is None:
id = self.get_default_language()
return get_language_name(id)
language_name = get_language_name(id)
if language_name=='???':
return self.get_user_defined_language_name(id) or language_name
else:
return language_name
security.declarePublic('get_available_languages')
......@@ -188,7 +195,7 @@ class LanguageManager(Tabs):
"""
Returns all ISO languages, used by 'manage_languages'.
"""
return get_languages()
return get_languages() + self.get_user_defined_languages()
security.declareProtected('Manage languages', 'manage_addLanguage')
......@@ -246,6 +253,45 @@ class LanguageManager(Tabs):
self._upgrade()
RESPONSE.redirect('manage_main')
# Add a feature which allows users to be able to add a new language.
security.declarePublic('get_user_defined_language_name')
def get_user_defined_language_name(self, id=None):
"""
Returns the name of the given user defined language code.
"""
for language_dict in self.get_user_defined_languages():
if language_dict['code']==id:
return language_dict['name']
security.declarePublic('get_user_defined_languages')
def get_user_defined_languages(self):
user_define_language_dict_list = []
localizer = getattr(self, 'Localizer', None)
if localizer is not None:
for value in getattr(self, 'user_defined_languages', ()):
splitted_value = value.split(' ', 1)
if len(splitted_value)==2:
user_define_language_dict_list.append(
{'name':splitted_value[0].strip(),
'code':splitted_value[1].strip(),})
return user_define_language_dict_list
def _add_user_defined_language(self, language_name, language_code):
self.user_defined_languages = (
getattr(self, 'user_defined_languages', ())+
('%s %s' % (language_name, language_code),)
)
self._p_changed = True
def _del_user_defined_language(self, language_code):
user_defined_languages = []
for language_dict in self.get_user_defined_languages():
if language_dict['code']!=language_code:
user_defined_languages.append('%s %s' %
(language_dict['name'],
language_dict['code']))
self.user_defined_languages = tuple(user_defined_languages)
self._p_changed = True
......
# -*- coding: UTF-8 -*-
# -*- coding: utf-8 -*-
# Copyright (C) 2000-2004 Juan David Ibáñez Palomar <jdavid@itaapy.com>
#
# This program is free software: you can redistribute it and/or modify
......@@ -17,9 +17,6 @@
# Import from the Standard Library
from urllib import unquote
# Import from itools
from itools.i18n import get_language_name
# Import from Zope
from AccessControl import ClassSecurityInfo
from Acquisition import aq_parent
......@@ -27,6 +24,7 @@ from App.class_init import InitializeClass
from OFS.Folder import Folder
from zLOG import LOG, ERROR, INFO, PROBLEM
from zope.interface import implements
from zope.i18n import translate
from ZPublisher.BeforeTraverse import registerBeforeTraverse, \
unregisterBeforeTraverse, queryBeforeTraverse, NameCaller
......@@ -63,7 +61,8 @@ class Localizer(LanguageManager, Folder):
id = 'Localizer'
_properties = ({'id': 'title', 'type': 'string'},
{'id': 'accept_methods', 'type': 'tokens'})
{'id': 'accept_methods', 'type': 'tokens'},
{'id': 'user_defined_languages', 'type': 'lines'},)
accept_methods = ('accept_path', 'accept_cookie', 'accept_url')
......@@ -74,6 +73,7 @@ class Localizer(LanguageManager, Folder):
+ LanguageManager.manage_options \
+ Folder.manage_options[1:]
user_defined_languages = ()
def __init__(self, title, languages):
self.title = title
......@@ -207,7 +207,7 @@ class Localizer(LanguageManager, Folder):
langs = []
for x in ob_languages:
langs.append({'id': x, 'title': get_language_name(x),
langs.append({'id': x, 'title': self.get_language_name(x),
'selected': x == ob_language})
return langs
......@@ -234,6 +234,16 @@ class Localizer(LanguageManager, Folder):
response.redirect(goto)
security.declarePublic('translate')
def translate(self, domain, msgid, lang=None, *args, **kw):
"""
backward compatibility shim over zope.i18n.translate. Please avoid.
"""
# parameter reordering/mangling necessary
assert not args
if lang is not None:
kw['target_language'] = lang
return translate(msgid, domain=domain, **kw)
InitializeClass(Localizer)
......
......@@ -568,21 +568,21 @@ class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
# Get the messages, and perhaps its translations.
# Convert keys to unicode for proper sorting.
d = {}
if x == 'locale.pot':
filename = x
for k in self._messages.keys():
d[k] = ""
d[to_unicode(k, encoding=charset)] = u""
else:
filename = '%s.po' % x
for k, v in self._messages.items():
try:
d[k] = v[x]
except KeyError:
d[k] = ""
k = to_unicode(k, encoding=charset)
d[k] = to_unicode(v.get(x, ""), encoding=charset)
# Generate the file
def backslashescape(x):
x = to_str(x)
quote_esc = compile(r'"')
x = quote_esc.sub('\\"', x)
......@@ -606,13 +606,6 @@ class MessageCatalog(LanguageManager, ObjectManager, SimpleItem):
RESPONSE.setHeader('Content-Disposition',
'inline;filename=%s' % filename)
r2 = []
for x in r:
if isinstance(x, unicode):
r2.append(x.encode(charset))
else:
r2.append(x)
return '\n'.join(r2)
......@@ -702,6 +695,17 @@ InitializeClass(MessageCatalog)
InitializeClass(POFile)
# This dict define the alias between old Translation Service catalog id
# and new Localizer Message Catalog.
message_catalog_aliases = { "Default": "default"
, "ui" : "erp5_ui"
, "content": "erp5_content"
}
# "invert" message_catalog_aliases mapping
message_catalog_alias_sources = {}
for name, value in message_catalog_aliases.items():
message_catalog_alias_sources.setdefault(value, []).append(name)
def MessageCatalog_moved(object, event):
# FIXME This does not work if what we move is the folder that contains
......@@ -710,9 +714,19 @@ def MessageCatalog_moved(object, event):
if container is not None:
sm = getSiteManager(container)
sm.unregisterUtility(object, ITranslationDomain, event.oldName)
# unregister old aliases
oldAliases = message_catalog_alias_sources.get(event.oldName, ())
sm = getSiteManager(event.oldParent)
for alias in oldAliases:
sm.unregisterUtility(object, ITranslationDomain, alias)
container = event.newParent
if container is not None:
sm = getSiteManager(container)
sm.registerUtility(object, ITranslationDomain, event.newName)
# register new aliases
newAliases = message_catalog_alias_sources.get(event.newName, ())
sm = getSiteManager(event.newParent)
for alias in newAliases:
sm.registerUtility(object, ITranslationDomain, alias)
# -*- coding: UTF-8 -*-
# -*- coding: utf-8 -*-
# Copyright (C) 2000-2006 Juan David Ibáñez Palomar <jdavid@itaapy.com>
#
# This program is free software: you can redistribute it and/or modify
......@@ -24,7 +24,7 @@ import os
from thread import allocate_lock, get_ident
# Import from itools
from itools.i18n import AcceptLanguageType
from .itools.i18n import AcceptLanguageType
# Import from Zope
import Globals
......
# -*- coding: UTF-8 -*-
# Copyright (C) 2001, 2002 J. David Ibáñez <j-david@noos.fr>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Test suite for the zgettext.py script.
"""
# Import from the Standard Library
import os
import sys
import unittest
from unittest import TestCase, TestSuite, TextTestRunner
# Import from Localizer
sys.path.append(os.path.join(sys.path[0], '../'))
import zgettext
class GettextTagTestCase(TestCase):
def test_caseSimple(self):
"""Test the 'dtml-gettext' tag without any option."""
text = "<dtml-gettext>\n" \
" message\n" \
"</dtml-gettext>"
assert zgettext.parse_dtml(text) == ['message']
def test_caseVerbatim(self):
"""Test the 'dtml-gettext' tag when using the 'verbatim' option."""
text = "<dtml-gettext verbatim>\n" \
" message\n" \
"</dtml-gettext>"
assert zgettext.parse_dtml(text) == ['\n message\n']
if __name__ == '__main__':
unittest.main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment