Commit efaa1597 authored by Ivan Tyagov's avatar Ivan Tyagov

Merge branch 'master' into ivan

parents 807a68c9 dd7bffd1
......@@ -85,7 +85,6 @@ reversal = accounting_module.newContent (\n
mapping={\'title\': unicode(context.getTitleOrId(), \'utf8\'),\n
\'specific_reference\': specific_reference}),\n
resource=context.getResource(),\n
reference=context.getReference(),\n
causality_value_list=causality_value_list,\n
created_by_builder=1 # XXX to prevent init script to create lines\n
)\n
......
1479
\ No newline at end of file
1480
\ No newline at end of file
......@@ -57,7 +57,8 @@ if portal.portal_preferences.getPreferredCredentialRequestAutomaticApproval():\n
result = module.searchFolder(portal_type=\'Credential Request\',\n
validation_state=\'submitted\')\n
for document in result:\n
document.activate().accept()\n
if document.getValidationState() == \'submitted\':\n
document.accept()\n
\n
if portal.portal_preferences.getPreferredPersonCredentialUpdateAutomaticApproval() and \\\n
portal.portal_preferences.getPreferredOrganisationCredentialUpdateAutomaticApproval():\n
......@@ -65,7 +66,8 @@ if portal.portal_preferences.getPreferredPersonCredentialUpdateAutomaticApproval
result = module.searchFolder(portal_type=\'Credential Update\',\n
validation_state=\'submitted\')\n
for document in result:\n
document.activate().accept()\n
if document.getValidationState() == \'submitted\':\n
document.accept()\n
elif portal.portal_preferences.getPreferredPersonCredentialUpdateAutomaticApproval() and \\\n
not portal.portal_preferences.getPreferredOrganisationCredentialUpdateAutomaticApproval():\n
module = context.getDefaultModule(\'Credential Update\')\n
......@@ -73,7 +75,8 @@ elif portal.portal_preferences.getPreferredPersonCredentialUpdateAutomaticApprov
validation_state=\'submitted\')\n
for document in result:\n
if document.getDestinationDecisionValue().getPortalType() == \'Person\':\n
document.activate().accept()\n
if document.getValidationState() == \'submitted\':\n
document.accept()\n
elif not portal.portal_preferences.getPreferredPersonCredentialUpdateAutomaticApproval() and \\\n
portal.portal_preferences.getPreferredOrganisationCredentialUpdateAutomaticApproval():\n
module = context.getDefaultModule(\'Credential Update\')\n
......@@ -81,14 +84,16 @@ elif not portal.portal_preferences.getPreferredPersonCredentialUpdateAutomaticAp
validation_state=\'submitted\')\n
for document in result:\n
if document.getDestinationDecisionValue().getPortalType() == \'Organisation\':\n
document.activate().accept()\n
if document.getValidationState() == \'submitted\':\n
document.accept()\n
\n
if portal.portal_preferences.getPreferredCredentialRecoveryAutomaticApproval():\n
module = context.getDefaultModule(\'Credential Recovery\')\n
result = module.searchFolder(portal_type=\'Credential Recovery\',\n
validation_state=\'submitted\')\n
for document in result:\n
document.activate().accept()\n
if document.getValidationState() == \'submitted\':\n
document.accept()\n
</string> </value>
</item>
<item>
......
415
\ No newline at end of file
416
\ No newline at end of file
......@@ -60,6 +60,9 @@ if me is not None:\n
if section is not None:\n
context.setSourceSectionValue(section)\n
context.setPriceCurrency(section.getPriceCurrency())\n
new_id = context.portal_ids.generateNewLengthId(id_group = "PTGR", default=1)\n
reference = "PTGR-%06d" % (new_id)\n
context.setSourceReference(reference)\n
</string> </value>
</item>
<item>
......
......@@ -101,6 +101,7 @@
<value>
<list>
<string>my_title</string>
<string>my_source_reference</string>
<string>my_reference</string>
<string>my_source_section</string>
<string>my_source_payment</string>
......@@ -111,6 +112,7 @@
<key> <string>right</string> </key>
<value>
<list>
<string>my_destination_reference</string>
<string>my_stop_date</string>
<string>my_payment_transaction_group_type</string>
<string>my_payment_mode</string>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_destination_reference</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_reference</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Destination Reference</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ProxyField" module="Products.ERP5Form.ProxyField"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>delegated_list</string> </key>
<value>
<list>
<string>editable</string>
<string>title</string>
</list>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>my_source_reference</string> </value>
</item>
<item>
<key> <string>message_values</string> </key>
<value>
<dictionary>
<item>
<key> <string>external_validator_failed</string> </key>
<value> <string>The input failed the external validator.</string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>overrides</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>tales</string> </key>
<value>
<dictionary>
<item>
<key> <string>field_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</value>
</item>
<item>
<key> <string>values</string> </key>
<value>
<dictionary>
<item>
<key> <string>editable</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>field_id</string> </key>
<value> <string>my_view_mode_reference</string> </value>
</item>
<item>
<key> <string>form_id</string> </key>
<value> <string>Base_viewFieldLibrary</string> </value>
</item>
<item>
<key> <string>target</string> </key>
<value> <string>Click to edit the target</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string>Source Reference</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
......@@ -22,7 +22,7 @@
<key> <string>actbox_url</string> </key>
<value> <string encoding="cdata"><![CDATA[
payment_transaction_group_module/view?reset:int=1&portal_type=Payment Transaction Group&local_roles=Assignee&validation_state=draft
payment_transaction_group_module?validation_state=draft&portal_type:list=%(portal_type)s&local_roles:list=%(local_roles)s&reset:int=1
]]></string> </value>
</item>
......
......@@ -22,7 +22,7 @@
<key> <string>actbox_url</string> </key>
<value> <string encoding="cdata"><![CDATA[
payment_transaction_group_module/view?reset:int=1&portal_type=Payment Transaction Group&local_roles=Assignee&validation_state=open
payment_transaction_group_module?validation_state=open&portal_type:list=%(portal_type)s&local_roles:list=%(local_roles)s&reset:int=1
]]></string> </value>
</item>
......
......@@ -22,7 +22,7 @@
<key> <string>actbox_url</string> </key>
<value> <string encoding="cdata"><![CDATA[
payment_transaction_group_module/view?reset:int=1&portal_type=Payment Transaction Group&local_roles=Assignor&validation_state=closed
payment_transaction_group_module?validation_state=closed&portal_type:list=%(portal_type)s&local_roles:list=%(local_roles)s&reset:int=1
]]></string> </value>
</item>
......
14
\ No newline at end of file
16
\ No newline at end of file
......@@ -72,7 +72,7 @@ for result in result_list:\n
if strict_check_mode and method() != kw[key]:\n
raise RuntimeError, "One property is not the same that you wanted : you asked \'%s\' and expecting \'%s\' but get \'%s\'" % (key, kw[key], method())\n
# check that every object are owner by you\n
if strict_check_mode and object.Base_getOwnerId() not in [owner_id, functional_test_username, \'__erp5security-=__\', functional_another_test_username]:\n
if strict_check_mode and object.Base_getOwnerId() not in [owner_id, functional_test_username, \'System Processes\', functional_another_test_username]:\n
raise RuntimeError, "You have try to clean an item who haven\'t you as owner : %s is owned by %s and you are %s" % \\\n
(object.getTitle(), object.Base_getOwnerId(), owner_id)\n
\n
......
......@@ -75,7 +75,7 @@
<dictionary>
<item>
<key> <string>text</string> </key>
<value> <string>string:${object_url}/WebIllustration_viewEditor</string> </value>
<value> <string>string:${object_url}/Base_viewSVGEditor</string> </value>
</item>
</dictionary>
</pickle>
......
......@@ -66,7 +66,7 @@
</item>
<item>
<key> <string>init_script</string> </key>
<value> <string>WebPage_init</string> </value>
<value> <string>WebIllustration_init</string> </value>
</item>
<item>
<key> <string>permission</string> </key>
......@@ -82,6 +82,18 @@
<key> <string>type_class</string> </key>
<value> <string>TextDocument</string> </value>
</item>
<item>
<key> <string>type_interface</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>type_mixin</string> </key>
<value>
<tuple/>
</value>
</item>
</dictionary>
</pickle>
</record>
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="PythonScript" module="Products.PythonScripts.PythonScript"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>Script_magic</string> </key>
<value> <int>3</int> </value>
</item>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_container</string> </key>
<value> <string>container</string> </value>
</item>
<item>
<key> <string>name_context</string> </key>
<value> <string>context</string> </value>
</item>
<item>
<key> <string>name_m_self</string> </key>
<value> <string>script</string> </value>
</item>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>_body</string> </key>
<value> <string>context.setContentType(\'image/svg+xml\')\n
</string> </value>
</item>
<item>
<key> <string>_params</string> </key>
<value> <string>*args, **kw</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>WebIllustration_init</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
52
\ No newline at end of file
53
\ No newline at end of file
......@@ -31,8 +31,9 @@
##############################################################################
import logging
import sys
import urllib
from urlparse import urljoin
from z3c.etestbrowser.browser import ExtendedTestBrowser
from zope.testbrowser.browser import onlyOne
......@@ -185,21 +186,73 @@ class Browser(ExtendedTestBrowser):
super(Browser, self).__init__()
def open(self, url_or_path=None, data=None):
def open(self, url_or_path=None, data=None, site_relative=True):
"""
Open a relative (to the ERP5 base URL) or absolute URL. If the
given URL is not given, then it will open the home ERP5 page.
Open a relative (to the ERP5 base URL) or absolute URL. If the given URL
is not given, then it will open the home ERP5 page. If C{site_relative} is
False, it will open the URL within the current context.
@param url_or_path: Relative or absolute URL
@type url_or_path: str
"""
# In case url_or_path is an absolute URL, urljoin() will return
# it, otherwise it is a relative path and will be concatenated to
# ERP5 base URL
absolute_url = urljoin(self._erp5_base_url, url_or_path)
if site_relative:
# In case url_or_path is an absolute URL, urljoin() will return
# it, otherwise it is a relative path and will be concatenated to
# ERP5 base URL
url_or_path = urljoin(self._erp5_base_url, url_or_path)
if isinstance(data, dict):
data = urllib.urlencode(data)
self._logger.debug("Opening url: " + absolute_url)
super(Browser, self).open(absolute_url, data)
self._logger.debug("Opening: " + url_or_path)
super(Browser, self).open(url_or_path, data)
def openNoVisit(self, url_or_path, data=None, site_relative=True):
"""
Copy/paste from zope.testbrowser.Browser.open() to allow opening an URL
without changing the current page. See L{open}.
@see zope.testbrowser.interfaces.IBrowser
"""
if site_relative:
# In case url_or_path is an absolute URL, urljoin() will return
# it, otherwise it is a relative path and will be concatenated to
# ERP5 base URL
url_or_path = urljoin(self._erp5_base_url, url_or_path)
import mechanize
if isinstance(data, dict):
data = urllib.urlencode(data)
response = None
url_or_path = str(url_or_path)
self._logger.debug("Opening: " + url_or_path)
self._start_timer()
try:
try:
try:
response = self.mech_browser.open_novisit(url_or_path, data)
except Exception, e:
fix_exception_name(e)
raise
except mechanize.HTTPError, e:
if e.code >= 200 and e.code <= 299:
# 200s aren't really errors
pass
elif self.raiseHttpErrors:
raise
finally:
self._stop_timer()
# if the headers don't have a status, I suppose there can't be an error
if 'Status' in self.headers:
code, msg = self.headers['Status'].split(' ', 1)
code = int(code)
if self.raiseHttpErrors and code >= 400:
raise mechanize.HTTPError(url_or_path, code, msg, self.headers, fp=None)
return response
def randomSleep(self, minimum, maximum):
"""
......@@ -562,6 +615,33 @@ class Browser(ExtendedTestBrowser):
return activity_counter
def waitForActivity(self, interval=20, maximum_attempt_number=30):
"""
Wait for activities every C{interval} seconds at most
C{maximum_attempt_number} of times and return the waiting time (excluding
loading time to get the number of remaining time).
This is mainly relevant when a setup script triggering activities has to
be executed before running the actual script.
@param interval: Interval between checking for remaining activities
@type interval: int
@param maximum_attempt_number: Number of attempts before failing
@type maximum_attempt_number: int
@return: Number of seconds spent waiting
@rtype: int
"""
current_attempt_counter = 0
while current_attempt_counter < maximum_attempt_number:
if self.getRemainingActivityCounter() == 0:
return current_attempt_counter * interval
time.sleep(interval)
current_attempt_counter += 1
raise AssertionError("Maximum number of attempts reached while waiting "
"for activities to be processed")
from zope.testbrowser.browser import Form, ListControl
class LoginError(Exception):
......@@ -864,6 +944,13 @@ class ContextMainForm(MainForm):
"""
self.submit(name='Folder_create:method')
def submitClone(self):
"""
Clone the previously selected objects. Use the class attribute
rather than the name as the latter is dependent on the context.
"""
self.submit(class_attribute='clone')
def submitDelete(self):
"""
Delete the previously selected objects.
......@@ -1095,6 +1182,44 @@ class ContextMainForm(MainForm):
return control
from zope.testbrowser.browser import SubmitControl
class SubmitControlWithTime(SubmitControl):
"""
Only define to wrap click methods to measure the time spent
"""
__metaclass__ = measurementMetaClass(prefix='click')
from zope.testbrowser.browser import ImageControl
class ImageControlWithTime(ImageControl):
"""
Only define to wrap click methods to measure the time spent
"""
__metaclass__ = measurementMetaClass(prefix='click')
import zope.testbrowser.browser
browser_controlFactory = zope.testbrowser.browser.controlFactory
def controlFactory(control, *args, **kwargs):
"""
Monkey patch controlFactory in zope.testbrowser to get elapsed time on
ImageControl and SubmitControl
"""
try:
t = control.type
except AttributeError:
# This is a subcontrol
pass
else:
if t in ('submit', 'submitbutton'):
return SubmitControlWithTime(control, *args, **kwargs)
elif t == 'image':
return ImageControlWithTime(control, *args, **kwargs)
return browser_controlFactory(control, *args, **kwargs)
zope.testbrowser.browser.controlFactory = controlFactory
from zope.testbrowser.browser import Link
......
......@@ -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()
......
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="SQL Non Continuous Increasing Id Generator" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>default_reference</string> </key>
<value> <string>mysql_non_continuous_increasing_non_zodb</string> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>mysql_non_continuous_increasing_non_zodb</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>SQL Non Continuous Increasing Id Generator</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>store_interval</string> </key>
<value> <int>1</int> </value>
</item>
<item>
<key> <string>stored_in_zodb</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>001</string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
41036
\ No newline at end of file
41040
\ No newline at end of file
......@@ -27,10 +27,6 @@
##############################################################################
from Products.ERP5Type.Tool.WebServiceTool import ConnectionError
from urllib import urlencode
from urllib2 import URLError, HTTPError, Request, \
urlopen, HTTPBasicAuthHandler, build_opener, \
install_opener
from Products.ERP5.ERP5Site import getSite
class MethodWrapper(object):
......@@ -65,7 +61,7 @@ class DocumentConnection:
"""
"""
self.reference=url
def connect(self):
"""Get a handle to a remote connection."""
return self
......
......@@ -70,8 +70,6 @@ except ImportError:
pass
from AccessControl import getSecurityManager
from Products.ERP5Type import Permissions
from DateTime import DateTime
from random import randint
......@@ -186,7 +184,7 @@ class FolderMixIn(ExtensionClass.Base):
Generate id base on date, useful for HBTreeFolder
We also append random id
"""
current_date = str(DateTime().Date()).replace("/", "")
current_date = DateTime().strftime('%Y%m%d')
my_id = self._generateRandomId()
return "%s-%s" %(current_date, my_id)
......@@ -1200,7 +1198,6 @@ class Folder(CopyContainer, CMFBTreeFolder, CMFHBTreeFolder, Base, FolderMixIn):
if isinstance(to_class, type('')):
to_class = getClassFromString(to_class)
folder = self.getObject()
for o in self.listFolderContents():
# Make sure this sub object is not the same as object
if o.getPhysicalPath() != self.getPhysicalPath():
......@@ -1485,7 +1482,6 @@ class Folder(CopyContainer, CMFBTreeFolder, CMFHBTreeFolder, Base, FolderMixIn):
corrected_list = []
for object in from_object_related_object_list:
#LOG('Folder.mergeContent, working on object:',0,object)
object_url = object.getRelativeUrl()
new_category_list = []
found = 0
for category in object.getCategoryList(): # so ('destination/person/1',...)
......
......@@ -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
......
This diff is collapsed.
##############################################################################
#
# 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
......
*.pyc
*.swp
locale/*.mo
locale/*~
# -*- coding: UTF-8 -*-
# Copyright (C) 2000-2002 Juan David Ibáñez Palomar <jdavid@itaapy.com>
#
# 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/>.
"""
Localizer
Adds a new DTML tag:
<dtml-gettext [lang=<language>|lang_expr=<expression>] [verbatim] [catalog=<id>] [data=<expression>]>
...
</dtml-gettext>
"""
# Import from Zope
from DocumentTemplate.DT_Util import Eval, ParseError, parse_params, \
InstanceDict, namespace, render_blocks
# Auxiliar functions
def name_or_expr(mapping, name_attr, expr_attr, default):
name = mapping.get(name_attr, None)
expr = mapping.get(expr_attr, None)
if name is None:
if expr is None:
return default
return Eval(expr)
if expr is None:
return name
raise ParseError, ('%s and %s given' % (name_attr, expr_attr), 'calendar')
class GettextTag:
""" """
name = 'gettext'
blockContinuations = ()
def __init__(self, blocks):
tname, args, section = blocks[0]
self.section = section.blocks
args = parse_params(args, lang=None, lang_expr=None, verbatim=1,
catalog=None, data=None)
self.lang = name_or_expr(args, 'lang', 'lang_expr', None)
self.verbatim = args.get('', None) == 'verbatim' \
or args.get('verbatim', None)
self.catalog = args.get('catalog', None)
self.data = args.get('data', None)
if self.data is not None:
self.data = Eval(self.data)
def __call__(self, md):
# In which language, if any?
lang = self.lang
if lang is not None and type(lang) is not str:
lang = lang.eval(md)
# Get the message!!
ns = namespace(md)[0]
md._push(InstanceDict(ns, md))
message = render_blocks(self.section, md)
md._pop(1)
# Interpret the message as verbatim or not
if not self.verbatim:
message = ' '.join([ x.strip() for x in message.split() ])
# Search in a specific catalog
if self.catalog is None:
gettext = md.getitem('gettext', 0)
else:
gettext = md.getitem(self.catalog, 0).gettext
translation = gettext(message, lang)
# Variable substitution
if self.data is not None:
data = self.data.eval(md)
translation = translation % data
return translation
This diff is collapsed.
This diff is collapsed.
# -*- coding: utf-8 -*-
# Copyright (C) 2000-2005 Juan David Ibáñez Palomar <jdavid@itaapy.com>
#
# 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/>.
"""
Localizer
This module provides several localized classes, that is, classes with the
locale attribute. Currently it only defines the classes LocalDTMLFile and
LocalPageTemplateFile, which should be used instead of DTMLFile and
PageTemplateFile.
"""
# Import from the Standard Library
import os
# Import Zope modules
from App.special_dtml import DTMLFile
# Import from Localizer
from utils import DomainAware
class LocalDTMLFile(DomainAware, DTMLFile):
""" """
def __init__(self, name, _prefix=None, **kw):
DTMLFile.__init__(self, name, _prefix, **kw)
DomainAware.__init__(self, _prefix)
def _exec(self, bound_data, args, kw):
# Add our gettext first
bound_data['gettext'] = self.gettext
return apply(LocalDTMLFile.inheritedAttribute('_exec'),
(self, bound_data, args, kw))
# -*- 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
# 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/>.
# Import from the Standard Library
from urllib import unquote
# Import from Zope
from AccessControl import ClassSecurityInfo
from Acquisition import aq_parent
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
# Import Localizer modules
from interfaces import ILocalizer
from LocalFiles import LocalDTMLFile
from MessageCatalog import MessageCatalog
from utils import lang_negotiator
from LanguageManager import LanguageManager
# Constructors
manage_addLocalizerForm = LocalDTMLFile('ui/Localizer_add', globals())
def manage_addLocalizer(self, title, languages, REQUEST=None, RESPONSE=None):
"""
Add a new Localizer instance.
"""
self._setObject('Localizer', Localizer(title, languages))
if REQUEST is not None:
RESPONSE.redirect('manage_main')
class Localizer(LanguageManager, Folder):
"""
The Localizer meta type lets you customize the language negotiation
policy.
"""
meta_type = 'Localizer'
implements(ILocalizer)
id = 'Localizer'
_properties = ({'id': 'title', 'type': 'string'},
{'id': 'accept_methods', 'type': 'tokens'},
{'id': 'user_defined_languages', 'type': 'lines'},)
accept_methods = ('accept_path', 'accept_cookie', 'accept_url')
security = ClassSecurityInfo()
manage_options = \
(Folder.manage_options[0],) \
+ LanguageManager.manage_options \
+ Folder.manage_options[1:]
user_defined_languages = ()
def __init__(self, title, languages):
self.title = title
self._languages = languages
self._default_language = languages[0]
#######################################################################
# API / Private
#######################################################################
def _getCopy(self, container):
return Localizer.inheritedAttribute('_getCopy')(self, container)
def _needs_upgrade(self):
return not self.hooked()
def _upgrade(self):
# Upgrade to 0.9
if not self.hooked():
self.manage_hook(1)
#######################################################################
# API / Public
#######################################################################
# Get some data
security.declarePublic('get_supported_languages')
def get_supported_languages(self):
"""
Get the supported languages, that is the languages that the
are being working so the site is or will provide to the public.
"""
return self._languages
security.declarePublic('get_selected_language')
def get_selected_language(self):
""" """
return lang_negotiator(self._languages) \
or self._default_language
# Hooking the traversal machinery
# Fix this! a new permission needed?
## security.declareProtected('View management screens', 'manage_hookForm')
## manage_hookForm = LocalDTMLFile('ui/Localizer_hook', globals())
## security.declareProtected('Manage properties', 'manage_hook')
security.declarePrivate('manage_hook')
def manage_hook(self, hook=0):
""" """
if hook != self.hooked():
if hook:
hook = NameCaller(self.id)
registerBeforeTraverse(aq_parent(self), hook, self.meta_type)
else:
unregisterBeforeTraverse(aq_parent(self), self.meta_type)
security.declarePublic('hooked')
def hooked(self):
""" """
if queryBeforeTraverse(aq_parent(self), self.meta_type):
return 1
return 0
# New code to control the language policy
def accept_cookie(self, accept_language):
"""Add the language from a cookie."""
lang = self.REQUEST.cookies.get('LOCALIZER_LANGUAGE', None)
if lang is not None:
accept_language.set(lang, 2.0)
def accept_path(self, accept_language):
"""Add the language from the path."""
stack = self.REQUEST['TraversalRequestNameStack']
if stack and (stack[-1] in self._languages):
lang = stack.pop()
accept_language.set(lang, 3.0)
def accept_url(self, accept_language):
"""Add the language from the URL."""
lang = self.REQUEST.form.get('LOCALIZER_LANGUAGE')
if lang is not None:
accept_language.set(lang, 2.0)
def __call__(self, container, REQUEST):
"""Hooks the traversal path."""
try:
accept_language = REQUEST['AcceptLanguage']
except KeyError:
return
for id in self.accept_methods:
try:
method = getattr(self, id)
method(accept_language)
except:
LOG(self.meta_type, PROBLEM,
'method "%s" raised an exception.' % id)
# Changing the language, useful snippets
security.declarePublic('get_languages_map')
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
security.declarePublic('changeLanguage')
changeLanguageForm = LocalDTMLFile('ui/changeLanguageForm', globals())
def changeLanguage(self, lang, goto=None, expires=None):
""" """
request = self.REQUEST
response = request.RESPONSE
# Changes the cookie (it could be something different)
parent = aq_parent(self)
path = parent.absolute_url()[len(request['SERVER_URL']):] or '/'
if expires is None:
response.setCookie('LOCALIZER_LANGUAGE', lang, path=path)
else:
response.setCookie('LOCALIZER_LANGUAGE', lang, path=path,
expires=unquote(expires))
# Comes back
if goto is None:
goto = request['HTTP_REFERER']
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)
# Hook/unhook the traversal machinery
# Support for copy, cut and paste operations
def Localizer_moved(object, event):
container = event.oldParent
if container is not None:
unregisterBeforeTraverse(container, object.meta_type)
container = event.newParent
if container is not None:
id = object.id
container = container.this()
hook = NameCaller(id)
registerBeforeTraverse(container, hook, object.meta_type)
This diff is collapsed.
This is a fork of the Localizer product, as found originally at:
https://github.com/hforge/Localizer
All functionality not explicitly used by ERP5 has been or will be removed.
Moreover, all monkey-patches, and all dependencies on itools should be
moved into native Zope APIs.
The copyright notice of the original code is as follows:
Author and License
Copyright 2001-2010 J. David Ibáñez (jdavid@itaapy.com)
2001 Universitat Jaume I
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/":http://www.gnu.org/licenses/
# -*- coding: utf-8 -*-
# Copyright (C) 2000-2005 Juan David Ibáñez Palomar <jdavid@itaapy.com>
#
# 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/>.
# Import from the Standard Library
import os.path
# Import from Zope
from App.ImageFile import ImageFile
from DocumentTemplate.DT_String import String
# Import from Localizer
from patches import get_request
import Localizer, MessageCatalog
from LocalFiles import LocalDTMLFile
from GettextTag import GettextTag
misc_ = {'arrow_left': ImageFile('img/arrow_left.gif', globals()),
'arrow_right': ImageFile('img/arrow_right.gif', globals()),
'eye_opened': ImageFile('img/eye_opened.gif', globals()),
'eye_closed': ImageFile('img/eye_closed.gif', globals()),
'obsolete': ImageFile('img/obsolete.gif', globals())}
def initialize(context):
# Check Localizer is not installed with a name different than Localizer
# (this is a common mistake).
filename = os.path.split(os.path.split(__file__)[0])[1]
if filename != 'Localizer':
message = (
"The Localizer product must be installed within the 'Products'"
" folder with the name 'Localizer' (not '%s').") % filename
raise RuntimeError, message
# XXX This code has been written by Cornel Nitu, it may be a solution to
# upgrade instances.
## root = context._ProductContext__app
## for item in root.PrincipiaFind(root, obj_metatypes=['LocalContent'],
## search_sub=1):
## item[1].manage_upgrade()
# Register the Localizer
context.registerClass(Localizer.Localizer,
constructors = (Localizer.manage_addLocalizerForm,
Localizer.manage_addLocalizer),
icon = 'img/localizer.gif')
# Register MessageCatalog
context.registerClass(
MessageCatalog.MessageCatalog,
constructors = (MessageCatalog.manage_addMessageCatalogForm,
MessageCatalog.manage_addMessageCatalog),
icon='img/message_catalog.gif')
context.registerHelp()
# Register the dtml-gettext tag
String.commands['gettext'] = GettextTag
ASCII
ISO-8859-1
ISO-8859-2
ISO-8859-3
ISO-8859-4
ISO-8859-5
ISO-8859-6
ISO-8859-7
ISO-8859-8
ISO-8859-9
ISO-8859-13
ISO-8859-15
KOI8-R
KOI8-U
CP850
CP866
CP874
CP932
CP949
CP950
CP1250
CP1251
CP1252
CP1253
CP1254
CP1255
CP1256
CP1257
GB2312
EUC-JP
EUC-KR
EUC-TW
BIG5
BIG5-HKSCS
GBK
GB18030
SHIFT_JIS
JOHAB
TIS-620
VISCII
UTF-8
<configure xmlns="http://namespaces.zope.org/zope">
<subscriber
for="Products.Localizer.Localizer.Localizer
zope.lifecycleevent.interfaces.IObjectMovedEvent"
handler="Products.Localizer.Localizer.Localizer_moved" />
<subscriber
for="Products.Localizer.MessageCatalog.MessageCatalog
zope.lifecycleevent.interfaces.IObjectMovedEvent"
handler="Products.Localizer.MessageCatalog.MessageCatalog_moved" />
</configure>
_build
figures
autodoc/modules
# -*- coding: utf-8 -*-
#
# Localizer documentation build configuration file, created by
# sphinx-quickstart on Fri Nov 19 19:55:33 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = []
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Localizer'
copyright = u'2010, J. David Ibáñez'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.2'
# The full version, including alpha/beta/rc tags.
release = '1.2.3'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Localizerdoc'
# -- Options for LaTeX output --------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Localizer.tex', u'Localizer Documentation',
u'J. David Ibáñez', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'localizer', u'Localizer Documentation',
[u'J. David Ibáñez'], 1)
]
.. Localizer documentation master file, created by
sphinx-quickstart on Fri Nov 19 19:55:33 2010.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Localizer's documentation!
=====================================
Localizer is a Zope product that allows to build multilingual web
applictaions.
.. note::
This documentation was written for Localizer 0.9, and has not been
updated since. Though most of it should apply to newer versions of
Localizer, because the API has not changed much.
Documentation, table of contents:
.. toctree::
:maxdepth: 2
quick-start
introduction/index
tutorial/index
python-programmers/index
Other documents of interest (external links):
* `List of tips <http://www.zope.org/Members/CodeSyntax/DTML_Localizer>`_, by
Luistxo from Code&Syntax.
* The `slides <http://>`_ and the `example <http://>`_ I used for the
talk about Localizer at the fifth Hispalinux congress held in Madrid
(Spain). The `article
<http://congreso.hispalinux.es/ponencias/index.en.html>`_ is available too.
Everything is in spanish only.
Products based on Localizer
===========================
* `CMFLocalizer <http://zope.org/Members/fafhrd/CMFLocalizer>`_, adds
filesystem based message catalogs to the CMF. By Kim Nikolay.
* `Base18
<http://www.nexedi.org/sections/software/base18_multilingual/view>`_,
explores new ways to
manage multilingual content (for the CMF). By Jean-Paul Smets.
* `Localizer + CMF <http://www.zope.org/Members/rthaden/HowToLocCMFProduct>`_
HowTo and product. Multilingual content for the CMF with Localizer. By
Rainer Thaden.
* `Translation Service
<http://www.zope.org/Members/efge/TranslationService>`_. Lets to use the
i18n ZPT namespace with Localizer. By Florent Guillaume.
Sites powered by Localizer
==========================
Here is a list of multilingual web sites that use Localizer. If you've used
Localizer to build a web site or to develop a multilingual Python product or
for something else, please tell me, send me a url if it's public and a short
explanation of how you used Localizer or your experience with it, and I'll
include it on the list.
* `LLEU <http://lleu2001.uji.es/>`_
I developed Localizer to build this web site, it included workflow for
multilingual documents and an automatic translation system to aid human
translators.
* `Code&Syntax <http://www.codesyntax.com/>`_
Code&Syntax is a Zope company specialized in internationalization and
localization. They're the Localizer users number 1, and have provided me
very valuable feedback. Other multilingual web sites based on Localizer of
this company are:
+ `Udal Euskaltegiak <http://www.udaleuskaltegiak.net/>`_
+ `Egoibarra <http://www.egoibarra.com/>`_
+ `Hotsak.com <http://www.hotsak.com/>`_
* `Castagnari <http://www.castagnari.com/>`_
Credits
=======
This list has not been updated for a while.
+----------------------+----------------------------------+
| Lead developer | **Juan David Ibáñez Palomar** |
+----------------------+----------------------------------+
| Developers | **Andrés Marzal Varó** |
| + +
| | **Bjorn Stabell** |
| + +
| | **Florent Guillaume** |
| + +
| | **Jean-Paul Smets** |
+----------------------+----------------------------------+
| Graphic designers | **Maite Rementeria** |
+----------------------+----------------------------------+
| Product translators | **Arnaud Lefèvre** (french) |
| + +
| | **Danny William Adair** (german) |
| + +
| | **Gabor Suveg** (hungarian) |
| + +
| | **Kazuya Fukamachi** (japanese) |
| + +
| | **Serge Stinckwich** (french) |
| + +
| | **Garikoitz Araolaza** (basque) |
| + +
| | **Luistxo Fernandez** (basque) |
+----------------------+----------------------------------+
Internationalization and localization
=====================================
Software needs to be adapted to the local conditions. For example, a word
processor has labels, menus and buttons that need to be translated, the dates
must be shown in the rigth locale format, the help system and any other text
must be available in the user's language. The software industry has faced this
problem for years, I've tried to learn from other's experience to provide Zope
users a solution for their needs. The name of this solution is Localizer.
To be multilingual
------------------
Imagine that you've developed an application, it provides a user interface
with text for example in english. Then your application gets success and
starts to be used in non-english speaking regions. These new users will begin
to ask you about a version in their language. Then, how to do it?
There're several approaches to solve this problem, you could for example fork
the software and provide a version in english, another in spanish, etc.. This
is the quickest solution as you won't need any special infrastructure to
implement it, just fork. But, of course, fork is not an option, it would be
too much work to keep the different versions in sync.
The best you can do is to look at what other developers have already done for
their software. Then you'll discover that this problem is addressed in two
steps:
* Internationalization (i18n)
The operation by which a program is made aware of and able to support
multiple languages. This is done once and forever.
Of course, you could write internationalized software since the begining.
You'll probably do it this way if you've already developed multilingual
software before.
In written language the **i18n** abbreviation is usually used for the term
internationalization. The letter **i** is the first letter of the term, the
letter **n** is the last letter of the term, **18** is the number of letters
between the **i** and the **n**.
* Localization (l10n)
The operation by which, in a program already internationalized, one gives
the program all the needed information so that it can adapt itself to handle
its input and output in a fashion which is correct for some native language
and cultural habits. This is done once per language.
This information needs to be maintained to keep it updated in every version
of the software.
For the term localization the abbreviation **l10n** is used. I'm sure you
already know where the **l**, the **n** and the **10** come from.
To localize
-----------
But this is not only about being multilingual, this is about adapting your
software, your application, your web site to the local conditions of the
users.
For example, imagine you work for a company that sells computers in the U.S.A.
and in the United Kingdom. The language of the both countries is english, you
don't need to translate the text of the company's web site. But perhaps the
company offers different computer models in each country, the contact address
will probably be different too. This means that the web site will show
different content for each market, you'll need to adapt it to the users local
conditions.
Continue reading please
-----------------------
In this introduction I've tried to answer the question : what
internationalization and localization are about?. In the following sections
we'll see the details of every concrete problem, at least the ones that for
now Localizer address.
Introduction
============
This chapter provides background information about the topic. And also links
if you want to learn more. The sections are:
.. toctree::
:maxdepth: 1
i18n
unicode
multilingual-software
multilingual-data
web
Multilingual data
=================
Internatiolizing software is the easy part, managing multilingual content is
the hard one.
In a multilingual information system there will be people that introduces
content and people that translates it. To keep the information synchronized
and to reduce the translation costs as much as possible are the goals.
The problem
-----------
Here are some elements to be considered:
* A translator aware workflow
When a new document is introduced into the system or when an existing
document changes, the translators must be notified. The same document could
be in different states depending on the language, for example the english
version could be ready to be published while the french version could be
unfinished yet.
Translators are specialized, they can't translate from any language to any
language. For example a translator could do translations from spanish to
french, while another one could translate from english to french and
spanish, or between spanish and french (in both directions).
* Identify the original content.
It's better to translate from the original content than from another
translation because, usually, through the translation process there's a
quality loss. This information is important to improve the translation
process and also for the end user, who always should know which is the
original version of the document, the one that contains the more accurate
data.
* Reduce the translation cost
Translations are expensive, you should use automatic translation systems to
reduce the cost. The output of these systems is not good enough to directly
publish it, but can help a lot to the human translators to do their work
quickly.
It's also important to provide translators the possibility to work off-line,
this is specially important when external translators are used, sometimes
freelancers.
Translation memories
--------------------
The standard solution to address these problems is known as translation
memories systems. Basically, the procedure used is:
1. First the text is splitted in sentences
2. Each sentence is automatically translated, usually using fuzzy matching
against a database. The result is proposed to the translator.
3. The translator corrects the translation and the good one is re-introduced
in the system to improve future translations.
Sophisticated tools for translators with easy to use interfaces, workflow and
versioning systems to manage the content and advanced automatic translation
engines are an important part of the solution.
.. seealso::
Related links
Institutions:
* the `Localisation Research Centre <http://www.localisation.ie/>`_
* the `Localisation Standards Industry Association
<http://www.lisa.org/>`_
Publications:
* `Localisation focus
<http://www.localisation.ie/resources/locfocus/index.htm>`_
Standards:
* the `Translation Memory eXchange <http://www.lisa.org/tmx>`_
standard;
Papers:
* `Evaluation of Natural Language Processing Systems
<http://www.issco.unige.ch/ewg95>`_, final report
Multilingual software
=====================
Software provides always a user interface, doesn't matters wether it's a text
based interface, a graphical application or a web site. The error messages,
the buttons, the labels, etc.. need to be translated.
This is a well known problem for which there're available mature solutions.
But first we'll introduce some basic terminology that will be needed to read
the rest of this guide:
Message
A piece of text that needs to be translated.
Message translation
The translation of a message.
Message catalog
A database that stores the message translations, and provides ways to get
them.
Gettext
-------
In the free software world the de facto standard solution to translate the
user interfaces are the `GNU gettext
<http://www.gnu.org/software/gettext/gettext.html>`_ utilities. Gettext is
used to translate the GNU software and also other projects like KDE.
Initially developed to translate C or C++ programs its usage has grown and now
it's also used with other languages. For example, Python 2.x includes a
gettext module that lets to use the GNU gettext tools, and the last versions
of the well know `Mailman <http://www.list.org/>`_ application use it to
provide a multilingual interface.
Unicode
=======
Encoding systems
----------------
Computers deal with numbers, this means that letters and other characters are
internally represented as numbers. Basically, an encoding system associates
each character with a number. For example, for the encoding named ASCII the
number 97 represents the character "a". So a text is just a sequence of
characters, and for computers it's just a sequence of numbers.
There're many different encoding systems, each one is used to represent
characters from one or more languages. For example the ASCII encoding is used
for english; ISO-8859-1 can be used with spanish, french or german; EUC-JP
represents japanese characters; etc..
The problem is that different encodings can use the same number to represent
different charecters, then they're incompatible. This is a problem for example
if you want to mix different languages in the same text.
Unicode
-------
To solve this problem Unicode appeared. Unicode is an encoding system that is
able to represent all the characters in the world. Using Unicode it's possible
to mix different languages in the same text without problems.
Python
------
The Python programming language provides two types of strings, normal strings
and unicode strings. Internationalized software written in Python always
should use unicode strings for text.
Normal strings represent sequences of bytes while unicode strings represent
sequences of characters. Unicode strings provide a higher abstraction layer
for the programmer that lets to forget, most of the time, about the encoding
issues.
Encoding becomes an issue when an unicode string needs to be serialized, for
example when the server response is sent to the browser. Then an specific
encoding needs to be choosen. For fully multilingual applications this should
be UTF-8, which is a particular representation of the Unicode character set.
.. seealso::
Related links
General information about Unicode:
* `Official Unicode web site <http://www.unicode.org/>`_
* `UTF-8 and Unicode FAQ for Unix/Linux
<http://www.cl.cam.ac.uk/~mgk25/unicode.html>`_
* `Unicode and Multilingual Support in HTML, Fonts, Web Browsers and
Other Applications <http://www.alanwood.net/unicode/>`_
Python resources for Unicode:
* `Python Unicode Tutorial
<http://www.reportlab.com/i18n/python_unicode_tutorial.html>`_
* `Python Internationalization Special Interest Group
<http://www.python.org/sigs/i18n-sig/>`_
The web
=======
A personal computer is configured with what is known as a locale, it specifies
the encoding system, the user language, the timezone and other parameters that
will be used by the applications to show their interfaces adapted to the user.
When an application starts, for example a word processor, it reads the locale
information, it only needs to be done once, at the begining. This process is
more complex in a networking environment like the web, where there are many
computers involved with different locale configurations.
In the web the client must send the locale information to the server each time
it requests a page. With this information the server can decide in which
language send the data.
Things become more complex when there is a cache between the server and the
client, then the needed dialog to choose the language involves more parties,
the server, the client and the cache.
.. seealso::
Related links
* `HTTP 1.1 language negotiation
<http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4>`_
* `Apache content negotiation
<http://httpd.apache.org/docs-2.0/content-negotiation.html>`_
For Python programmers
======================
This chapter explains what you can do with Localizer if you're a Python
programmer. Right now there's only one section, this chapter is far to be
complete.
.. toctree::
:maxdepth: 1
python-products
multilingual-content
Multilingual content
====================
LocalProperty and LocalPropertyManager
--------------------------------------
The multilingual features of the LocalContent class are actually provided by
the LocalPropertyManager mixin class. Now I'm going to explain how to develop
your own multilingual classes.
Let's imagine that you want to manage multilingual articles, each article has
three multilingual properties, the title, the abstract and the body. The
commented code would be::
# Import the needed classes from Localizer
from Products.Localizer import LocalAttribute, LocalPropertyManager
# The class must inherit from LocalPropertyManager
class Article(LocalPropertyManager, SimpleItem):
meta_type = 'Article'
# The multilingual properties are instances of
# LocalAttribute, they can be class variables.
# The constructor takes 1 argument, which must
# be the name of the attribute.
title = LocalAttribute('title')
abstract = LocalAttribute('abstract')
body = LocalAttribute('body')
# LocalPropertyManager needs some metadata,
# this is similar to the PropertyManager mixin class.
_local_properties_metadata = ({'id': 'title', 'type': 'string'},
{'id': 'abstract', 'type': 'text'},
{'id': 'body', 'type': 'text'})
Python products
===============
The Localizer product also provides facilities to internationalize and
localize python products. These facilities are built around the GNU gettext
utilities, the standard in the free software community.
Internationalize
----------------
* DTML and ZPT
The LocalDTMLFile and LocalPageTemplateFile classes must be used instead
of the DTMLfile and PageTemplateFile classes::
from Products.Localizer import LocalDTMLFile, LocalPageTemplateFile
....
manage_addForm = LocalDTMLFile('addForm', globals())
Then the messages will be translated using the gettext method:
.. code-block:: xml
<dtml-var "gettext('Hello world!')">
<span tal:replace="python:gettext('Hello world!')">
Hello world!
</span>
* Python code
Import the needed stuff::
from Products.Localizer import utils
_ = utils.translation(globals())
The underscode (\_) is used to translate messages.
::
def x(self, ...):
return _(u'Hello world!')
Messages must be unicode strings (not byte strings), otherwise the
``zgettext.py`` script (see below) won't detect them.
Localize
--------
The messages and their translations are stored in ".po" and ".mo" files, in
the locale directory within the product.
To help with the localization task Localizer includes the script zgettext.py.
Use it from your product directory, for example:
.. code-block:: sh
../Localizer/zgettext.py *.dtml -l es fr
will create the locale directory and the locale.pot, es.po and fr.po files
inside. Then the human translators will translate the messages in the ".po"
files. Finally, type:
.. code-block:: sh
../Localizer/zgettext.py -m
to compile the ".po" files and generate the ".mo" files that will be used at
run time to get the translations.
Quick start (5 min)
===================
.. highlight:: xml
Output the "Hello world" message in multiple languages
------------------------------------------------------
#. Create a folder named, for example, TestLocalizer, and go inside it.
#. Create a MessageCatalog named, for example, gettext.
#. Go to the management interfaces of the message catalog, go to the tab
languages and add the languages you want.
#. Create a DTML method named test_gettext.
#. In the DTML method type::
<dtml-var "gettext('Hello world!')">
#. View the DTML method.
#. Go to the management screens of the message catalog, provide translations
to the message "Hello world!".
#. View again the DTML method, change the language configuration of your
browser and reload the page to see how the message changes.
Add a language selection box
----------------------------
1. Add a Localizer instance.
2. Edit the DTML method test_gettext and add::
<dtml-var "Localizer.changeLanguageForm()">
3. View again the DTML method and use the selection box to change the language
(you need to active cookies and javascript in your browser).
LocalContent
============
The **LocalContent** meta type is used to store and manage multilingual data.
A single object has different properties, multilingual and monolingual, for
the multilingual ones it's possible to have different language versions.
To explain how to use local content objects we'll go through an step by step
example:
1. Go to the management screens and create a new LocalContent object.
Just type an id, for example "index_html", and a list of languages (iso
codes) separated by spaces, for example "en es fr" for english, spanish,
and french (later you'll be able to add and remove languages through the
management screens).
2. Edit the object, put some text in the "title" and "body" properties for
each language.
3. Create a template to view the object, for example a Page Template named
view
.. code-block:: html
<html>
<head>
<title tal:content="here/title">Title</title>
</head>
<body>
<h1 tal:content="here/title">Title</h1>
<p tal:content="here/body">Body</p>
</body>
</html>
4. View the object with the url <path>/index_html/view. Try to change the
language configuration of your browser and reload the page to see how it
changes.
5. Try to look the url <path>/index_html, it doesn't works. Rename the
template view to default_template and try again.
When no template is specified a default one named default_template is used.
6. You can change the default template too, just go to the object index_html,
go to the Properties tab and add a new property named default_template of
type string, choose for example view as its value. Now rename
default_template to view, and visit the url <path>/index_html again.
This is useful because sometimes different "LocalContent" objects should
have different default templates.
Indexing and searching
----------------------
Local content objects are catalog aware, if you have a catalog named Catalog
in the acquisition path your local content objects will be automatically
cataloged when you create, modify or delete them.
Local content objects have computed attributes of the form <property>_<lang>,
for instance title_es returns the title of the object in spanish and body_en
returns the english version of the body. This feature lets you, for example,
search through all language versions of a local content object simply by using
these attributes as indexes in the catalog.
LocalFolder
===========
The LocalFolder meta type provides a generic solution to internationalize any
Zope object. Use it to provide images in multiple languages, output dates in
locale formats, etc..
To explain how to use local folder objects we will go through an step by step
example:
1. Go to the management screens and create a new LocalFolder object.
Use "datetime" as the id and "en es" (for english and spanish) as the list
of languages (later you'll be able to add and remove languages through the
management screens).
2. Go to the management screens, to the Attributes tab, and add the ids of the
multilingual objects you want to have, for example, add short_date.
3. Go to the Contents tab and add an object for each language. For example,
add two Python scripts, short_date_en and short_date_es (put datetime in
their parameters list). The body of the english version could be::
return datetime.strftime('%Y/%m/%d')
The body of the spanish version could be::
return datetime.strftime('%d/%m/%Y')
4. Now leave the local folder object and create, for example, a DTML method,
call it today. Its body could be:
.. code-block:: xml
<dtml-var standard_html_header>
<dtml-var "datetime.short_date(_.DateTime())">
<dtml-var standard_html_footer>
5. View the today method and change the configuration of your browser between
english and spanish to see how it changes.
In short, what local folder objects do is to let to use different language
versions of any Zope object (Python scripts, images, folders, etc..), which
version is used with each request depends on the language negotiation
facilities. Each multilingual object must be specified in the Attributes tab,
each language version has the form <name>_<language>. For example, if the
multilingual attribute is logo the spanish version's id is logo_es.
Localizer
=========
.. highlight:: xml
By default only the browser configuration (the **AcceptLanguage** header) is
used to choose the language. Localizer implements the HTTP standard, it
represents the user prefered languages as a tree where each node has a quality
between 0.0 and 1.0 (a higher quality means the language is more prefered by
the user). For more information see the HTTP protocol specification.
The **Localizer** meta type lets to use other criterias to choose the
language. Usually there's one instance in the root of the web site. Its id is
always Localizer and can't be changed.
A Localizer object has a property named **accept_methods** (of type tokens)
which contains a list of method ids. Each method specified in this list will
be called every time the folder where the Localizer object lives is traversed.
By default it includes two builtin methods that add two new criterias to
choose the language:
accept_cookie
Lets to specify the language with the cookie LOCALIZER_LANGUAGE, which
must contain a language code. This method assigns a quality of 2.0 to the
language specified in the cookie. This way it takes precedence over the
browser configuration.
accept_path
Lets to specify the language in the url. For example, if there's a
Localizer object at the root of the web site, the url
http://www.example.com/es/index.html will set the quality for spanish to
3.0, this way it will take precedence over the browser configuration and
the cookie.
Customize
---------
To change the language negotiation policy to fit your needs you have to modify
the property accept_methods. You can remove any of the builtin methods (if you
don't want to use cookies or to specify the language in the url) and add new
ones that usually will be implemeted as Python scripts.
The functions that appear in the accept_methods list receive a parameter that
represents the tree of the user prefered languages. This object offers a
complete API, but usually only the method set will be used.
For example, create a Python script within the Localizer instance named
accept_french, add accept_language to its parameter list. The body of the
script could be the line:
.. code-block:: python
accept_language.set("fr", 4.0)
This would set french as the user prefered language with a quality of 4.0,
taking precedence over the browser, the cookie and the laguage specified in
the url. Finally just add its id to the list accept_methods.
The change language form
------------------------
The Localizer object also provides the HTML form changeLanguageForm and its
action, changeLanguage. They provide a quick way to implement a language
selection box, the action modifies the LOCALIZER_LANGUAGE cookie.
For DTML type::
<dtml-var "Localizer.changeLanguageForm()">
For ZPT type::
<tal:block content="structure here/Localizer/changeLanguageForm" />
MessageCatalog
==============
.. highlight:: xml
The Localizer product provides the class MessageCatalog, it stores messages
and their translations and provides a web interface to manage them. They're
useful to translate the application interface: labels, buttons, etc..
The messages are stored in the ZODB, though they can be exported and imported
to and from "po" files. It's also possible to manage a message catalog through
FTP.
Getting translations
--------------------
To get the translations message catalogs provide the gettext method, for
example::
<dtml-var "messages.gettext('Hello world!')">
The message catalog is callable, this means it's possible to use a shorter
version::
<dtml-var "messages('Hello world!')">
The gettext method accepts two optional parameters, it's signature is:
.. code-block:: python
gettext(message, language=None, add=1)
The parameters are:
+----------+--------------------------------------+
| message | The message to be translated. |
+----------+--------------------------------------+
| language | The destination language, if None |
| | (default) the selection language |
| | algortihm will be used to choose |
| | the destination language. |
+----------+--------------------------------------+
| add | If true (default) the message will |
| | be automatically added to the |
| | catalog if it doesn't exists. |
+----------+--------------------------------------+
dtml-gettext
------------
For long messages the tag dtml-gettext can be more comfortable::
<dtml-gettext>
This could be a very long message.
</dtml-gettext>
It accepts several parameters, they're:
+------------------------------+--------------------------------+
| lang [#msg-catalog-rq]_ | The target language (string). |
+------------------------------+--------------------------------+
| lang_expr [#msg-catalog-rq]_ | The target language (string |
| | expression). |
+------------------------------+--------------------------------+
| verbatim | If set the message will be |
| | interpreted as it is, |
| | otherwise (default) any blank |
| | characters will be interpreted |
| | as just one space character. |
+------------------------------+--------------------------------+
| catalog | The id of the catalog from |
| | where the translations will be |
| | obtained. |
+------------------------------+--------------------------------+
| data | If present the message will be |
| | interpreted as a formatted |
| | string and data will contain |
| | the tuple or dictionary to be |
| | passed to the string. |
+------------------------------+--------------------------------+
.. [#msg-catalog-rq] Only one (or none) can be provided.
Zope Page Templates
-------------------
Message catalogs can also be used from ZPT, for example::
<span tal:replace="python:here.messages('Hello world!')">
Hello world!
</span>
Example of a Localizer web site
===============================
Nothing yet...
Tutorial
========
This chapter explains how to use and when to use the four meta types provided
by the Localizer product. They are:
.. toctree::
:maxdepth: 1
MessageCatalog
Localizer
LocalContent
LocalFolder
Finally, to know how to put all this together we're going to see with an
example the typical organization of a Localizer based multilingual web site:
.. toctree::
:maxdepth: 1
example
Localizer - Add Locale Folder
Description
This view allows to create a new Locale Folder.
Controls
Id -- The id of the Locale Folder, must be a language code.
Title -- The optional title for the Locale Folder.
Localizer - Manage languages
Description
This view allows to manage the available languages (add and delete) and
to set the default language.
Controls
Add -- Adds a new language.
Delete -- Removes the selected languages.
Change -- Changes the default language.
Localizer - Upgrade LocalPropertyManager
Description
This view allows to upgrade an object when a new version of its class
has been installed that requires changes to the object.
This feature is specific to the Localizer product.
Controls
Upgrade -- Upgrades the object.
Localizer - Manage local properties
Description
This view allows to manage the local properties.
Add properties
To add a new property enter its name and select its type, then click
the 'Add' button.
Property selector
Below the form to add new properties there's an interface that allows
to modify and delete them.
Only one local property is edited at a time, it's highlighted in the
property selection bar. To select a different one click on it's name.
If there're lots of properties the selection bar will have two buttons
to browse the properties at the right side of the bar.
Delete a property
To delete the property being edited click the 'Delete' button.
Edit properties
The body of the interface shows all the language versions for the
selected property. To modify them introduce the right text and click
the 'Save changes' button.
Localizer - Add Local Content
Description
This view allows to create a new Local Content object.
Controls
Id -- The id of the object.
Languages -- The list of available languages that the Local Content
will have.
\ No newline at end of file
Localizer - Add Local Folder
Description
This view allows to create a new Local Folder.
Controls
Id -- The id of the Local Folder, must be a language code.
Title -- The optional title for the Local Folder.
Languages -- The list of available languages that the local folder will
have.
Localizer - Add Localizer
Description
This view allows to create a new Localizer.
Controls
Id -- The id of the object is 'Localizer'.
Title -- The optional title for the Localizer.
Languages -- The list of available languages that the Localizer will have.
Add -- Adds the Localizer instance.
Localizer - Add Message Catalog
Description
This view allows to create a new Message Catalog.
Controls
'Id' -- The id of the message catalog
Title -- The optional title for the message catalog.
Languages -- The list of available languages that the message catalog
will have.
\ No newline at end of file
Localizer - Import/Export messages
Description
This views allows to import and export messages.
Controls
File / Language -- The file (and language) to be exported.
Export -- Exports the file.
File -- The file that you want to import.
Language -- The language of the translations contained in the file to
be imported.
Import -- Imports the file.
Message Catalog - Manage messages.
Description
This screen lets to manage the message catalog, it lets to remove
messages and to edit the translations.
The screen is divided in two parts, the working area at the left
and the navigation system at the right.
The working area
It is divided in two parts, at the top there is a non editable
text area that contains the message to be translated.
At the bottom there's an editable text area wich lets to change
the translation. Above this text area there is a menu which lets
to change the language that is being edited.
Finally, there are two buttons, one saves the translation and the
other removes the current message.
Navigation system
It lets to browse the messages and is divided in two parts.
At the top there is a search form that lets to show only a subset of
the messages, following two criterias. The first one is wether to show
all the messages or only those that have an empty translation. The
second criteria if used will only show the messages that match the
specified string.
Below the search form there is a list with all the found messages,
which can be used to change the message that is being edited.
# -*- coding: UTF-8 -*-
# Copyright (C) 2010 Juan David Ibáñez Palomar <jdavid@itaapy.com>
#
# 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/>.
# Import from Zope
from zope.interface import Interface
from zope.i18n.interfaces import ITranslationDomain
class ILocalizer(Interface):
pass
class IMessageCatalog(ITranslationDomain):
pass
*~
*.pyc
*.swp
build
dist
MANIFEST
version.txt
locale/*.mo
test/fables/catalog
test/pdf/*.pdf
This diff is collapsed.
This is a stub of the original itools library, containing only the
bare essentials needed for Localizer as used in ERP5. Namely:
* itools.utils
* itools.i18n
The intention is that even these few remaining functionalities should
be replaced by native Zope API calls.
The original itools distribution on which this stub is based is the
0.50 branch of the itools repository at:
https://github.com/hforge/itools/tree/0.50
The original CREDITS.txt file can be seen at:
https://github.com/hforge/itools/blob/0.50/CREDITS
The copyright notice of the original code is as follows:
Copyright
---------
Copyright (C) 2002-2008 Juan David Ibáñez Palomar <jdavid@itaapy.com>
Copyright (C) 2005-2008 Luis Arturo Belmar-Letelier <luis@itaapy.com>
Copyright (C) 2005-2008 Hervé Cauwelier <herve@itaapy.com>
Copyright (C) 2005-2008 Nicolas Deram <nicolas@itaapy.com>
And others. Check the CREDITS file for complete list.
License
-------
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/>.
# -*- coding: utf-8 -*-
# Copyright (C) 2006, 2008 Juan David Ibáñez Palomar <jdavid@itaapy.com>
#
# 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/>.
# -*- coding: UTF-8 -*-
# Copyright (C) 2006-2008 Juan David Ibáñez Palomar <jdavid@itaapy.com>
# Copyright (C) 2008 Henry Obein <henry@itaapy.com>
#
# 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/>.
# Import from itools
from accept import AcceptLanguageType, get_accept, select_language
from accept import init_language_selector
from base import has_language, get_languages, get_language_name
from fuzzy import get_distance, get_similarity, is_similar, get_most_similar
from locale_ import format_date, format_time, format_datetime
from oracle import guess_language, is_asian_character, is_punctuation
__all__ = [
# accept
'AcceptLanguageType',
'get_accept',
'select_language',
'init_language_selector',
# fuzzy
'get_distance',
'get_similarity',
'is_similar',
'get_most_similar',
# locale
'format_date',
'format_time',
'format_datetime',
# oracle
'guess_language',
'is_asian_character',
'is_punctuation',
# languages
'has_language',
'get_languages',
'get_language_name',
]
# -*- coding: utf-8 -*-
# Copyright (C) 2002-2008 Juan David Ibáñez Palomar <jdavid@itaapy.com>
#
# 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/>.
"""
This module implements the Accept-Language request header of the HTTP
protocol.
"""
# Import from the Standard Library
import __builtin__
from decimal import Decimal
from locale import getdefaultlocale
zero = Decimal('0.0')
one = Decimal('1.0')
# FIXME This class belongs to the public API, but it is not exposed since
# we never create it directly (we use AcceptLanguageType).
class AcceptLanguage(dict):
"""Implements the Accept-Language tree.
"""
# Allow unrestricted access to AcceptLanguage and subobjects from Zope
__allow_access_to_unprotected_subobjects__ = 1
def set(self, key, quality):
if not isinstance(quality, Decimal):
if not isinstance(quality, str):
quality = str(quality)
quality = Decimal(quality)
if key == '*':
key = ''
self[key] = quality
def get_quality(self, language):
"""This method returns the quality of the given language. As defined
by the RFC 2616, if a langage is not defined it inherits the quality
from a "parent" language; for instance, if 'fr-FR' is not defined but
'fr' is, then 'fr-FR' inherits the quality from 'fr'.
To make the difference between an exact match and an inherited value,
this method returns a tuple: first value is the quality, and second
value is the number of steps it had to go back in the inheritance
chain (0 for an exact match).
"""
steps = 0
while language and language not in self:
language = '-'.join(language.split('-')[:-1])
steps += 1
return self.get(language, zero), steps
def select_language(self, languages):
"""This is the selection language algorithm, it returns the user
prefered language for the given list of available languages, if the
intersection is void returns None.
The criterias used to select a language are:
1. The quality
2. The distance to the exact match
3. The order in the 'languages' parameter
"""
language, quality, steps = None, zero, 100
for lang in languages:
q, s = self.get_quality(lang)
if q > quality or (q and q == quality and s < steps):
language, quality, steps = lang, q, s
return language
class AcceptLanguageType(object):
@staticmethod
def decode(data):
"""From a string formatted as specified in the RFC2616, it builds a
data structure which provides a high level interface to implement
language negotiation.
"""
data = data.strip()
if not data:
return AcceptLanguage({})
accept = {}
for language in data.lower().split(','):
language = language.strip()
if ';' in language:
language, quality = language.split(';')
# Get the number (remove "q=")
quality = Decimal(quality.strip()[2:])
else:
quality = one
if language == '*':
language = ''
accept[language] = quality
return AcceptLanguage(accept)
@staticmethod
def encode(accept):
# Sort
accept = [ (y, x) for x, y in accept.items() ]
accept.sort()
accept.reverse()
# Encode
data = []
for quality, language in accept:
if language == '':
if quality == zero:
continue
language = '*'
if quality == one:
data.append(language)
else:
data.append('%s;q=%s' % (language, quality))
return ', '.join(data)
def get_accept():
language = getdefaultlocale()[0]
if language is None:
language = ''
elif '_' in language:
language = language.replace('_', '-')
language = '%s, %s;q=0.5' % (language, language.split('-')[0])
return AcceptLanguageType.decode(language)
def select_language(languages=None):
return get_accept().select_language(languages)
def init_language_selector(language_selector=select_language):
__builtin__.__dict__['select_language'] = language_selector
# Set default language selector
init_language_selector(select_language)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
msgid ""
msgstr ""
"Project-Id-Version: 0.3.0\n"
"POT-Creation-Date: 2001-10-13 19:10+CET\n"
"PO-Revision-Date: 2001-07-08 17:07+CET\n"
"Last-Translator: jdavid\n"
"Language-Team: ??\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8-bit\n"
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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