provide i18n module for multi-language concepts, starting with glossary items

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@2236 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2007-12-11 18:44:46 +00:00
parent 490b6216f5
commit d7efbbdcdf
21 changed files with 537 additions and 67 deletions

View file

@ -51,6 +51,7 @@ from cybertools.relation.interfaces import IRelationRegistry
from cybertools.text import mimetypes
from cybertools.typology.interfaces import IType, ITypeManager
from loops.common import adapted
from loops.i18n.browser import I18NView
from loops.interfaces import IView
from loops.resource import Resource
from loops.type import ITypeConcept
@ -96,7 +97,7 @@ class EditForm(form.EditForm):
return parentUrl + '/contents.html'
class BaseView(GenericView):
class BaseView(GenericView, I18NView):
actions = {} # default only, don't update

View file

@ -34,25 +34,27 @@ from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve
from zope.event import notify
from zope.formlib.form import EditForm, FormFields
from zope.formlib.form import EditForm, FormFields, setUpEditWidgets
from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import implements
from zope.publisher.interfaces import BadRequest
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import IIterableSource
from zope.security.proxy import removeSecurityProxy
from zope.traversing.api import getName
from cybertools.typology.interfaces import IType, ITypeManager
from loops.interfaces import IConcept
from loops.interfaces import ITypeConcept
from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList
from loops.browser.common import EditForm, BaseView, LoopsTerms, conceptMacrosTemplate
from loops.common import adapted
from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList
from loops.i18n.browser import I18NView
from loops.interfaces import IConcept, IConceptSchema, ITypeConcept
from loops import util
from loops.util import _
from loops.versioning.util import getVersion
class ConceptEditForm(EditForm):
class ConceptEditForm(EditForm, I18NView):
@Lazy
def typeInterface(self):
@ -71,12 +73,16 @@ class ConceptEditForm(EditForm):
return fields
def setUpWidgets(self, ignore_request=False):
super(ConceptEditForm, self).setUpWidgets(ignore_request)
adapter = adapted(self.context, self.languageInfo)
self.adapters = {self.typeInterface: adapter,
IConceptSchema: adapter}
self.widgets = setUpEditWidgets(
self.form_fields, self.prefix, self.context, self.request,
adapters=self.adapters, ignore_request=ignore_request)
desc = self.widgets.get('description')
if desc:
desc.height = 2
class ConceptView(BaseView):
template = ViewPageTemplateFile('concept_macros.pt')
@ -98,11 +104,24 @@ class ConceptView(BaseView):
subMacro=self.template.macros['parents'],
position=0, info=self)
@Lazy
def adapted(self):
return adapted(self.context, self.languageInfo)
@Lazy
def title(self):
return self.adapted.title or getName(self.context)
@Lazy
def description(self):
return self.adapted.description
def fieldData(self):
# TODO: use cybertools.composer.schema.instance, see loops.browser.form
ti = IType(self.context).typeInterface
if not ti:
return
adapter = ti(self.context)
adapter = self.adapted
for n, f in schema.getFieldsInOrder(ti):
if n in ('title', 'description',): # already shown in header
continue
@ -119,17 +138,21 @@ class ConceptView(BaseView):
cm = self.loopsRoot.getConceptManager()
hasType = cm.getTypePredicate()
standard = cm.getDefaultPredicate()
rels = self.context.getChildRelations()
#rels = self.context.getChildRelations()
rels = (ConceptRelationView(r, self.request, contextIsSecond=True)
for r in self.context.getChildRelations(sort=None))
rels = sorted(rels, key=lambda r: (r.order, r.title.lower()))
for r in rels:
if r.predicate == hasType:
# only show top-level entries for type instances:
skip = False
for parent in r.second.getParents((standard,)):
for parent in r.context.getParents((standard,)):
if parent.conceptType == self.context:
skip = True
break
if skip: continue
yield ConceptRelationView(r, self.request, contextIsSecond=True)
yield r
#yield ConceptRelationView(r, self.request, contextIsSecond=True)
def parents(self):
rels = sorted(self.context.getParentRelations(),
@ -310,6 +333,18 @@ class ConceptRelationView(BaseView):
self.relation = relation
self.request = request
@Lazy
def adapted(self):
return adapted(self.context, self.languageInfo)
@Lazy
def title(self):
return self.adapted.title or getName(self.context)
@Lazy
def description(self):
return self.adapted.description
@Lazy
def token(self):
return ':'.join((self.loopsRoot.getLoopsUri(self.context),

View file

@ -586,6 +586,13 @@
permission="zope.ManageContent"
/>
<page
name="inner_concept_edit_form.html"
for="loops.interfaces.INode"
class="loops.browser.form.InnerConceptEditForm"
permission="zope.ManageContent"
/>
<zope:adapter
name="create_resource"
for="loops.browser.node.NodeView

View file

@ -22,7 +22,7 @@ View class(es) for Flash user interface.
$Id$
"""
from zope.app.traversing.browser.absoluteurl import absoluteURL
from zope.traversing.browser.absoluteurl import absoluteURL
from zope.cachedescriptors.property import Lazy

View file

@ -52,6 +52,7 @@ from loops.interfaces import IConcept, IConceptSchema, IResourceManager, IDocume
from loops.interfaces import IFile, IExternalFile, INote, ITextDocument
from loops.browser.node import NodeView
from loops.browser.concept import ConceptRelationView
from loops.i18n.browser import I18NView
from loops.query import ConceptQuery
from loops.resource import Resource
from loops.type import ITypeConcept
@ -84,7 +85,7 @@ class ObjectForm(NodeView):
@Lazy
def adapted(self):
return adapted(self.target)
return adapted(self.target, self.languageInfo)
@Lazy
def typeInterface(self):
@ -305,9 +306,16 @@ class InnerConceptForm(CreateConceptForm):
return self.fieldRenderers['fields']
class InnerConceptEditForm(EditConceptForm):
@property
def macro(self):
return self.fieldRenderers['fields']
# processing form input
class EditObject(FormController):
class EditObject(FormController, I18NView):
""" Note that ``self.context`` of this adapter may be different from
``self.object``, the object it acts upon, e.g. when this object
is created during the update processing.
@ -321,7 +329,7 @@ class EditObject(FormController):
@Lazy
def adapted(self):
return adapted(self.object)
return adapted(self.object, self.languageInfo)
@Lazy
def typeInterface(self):

View file

@ -2,17 +2,38 @@
$Id$ -->
<metal:block define-macro="edit" i18n:domain="loops">
<form method="post" enctype="multipart/form-data" id="dialog_form">
<form method="post" enctype="multipart/form-data" id="dialog_form"
tal:define="langInfo view/languageInfo;
languages langInfo/availableLanguages;
language langInfo/language;
useI18N view/useI18N;
innerForm request/inner_form | string:inner_concept_edit_form.html;">
<input type="hidden" name="form.action" value="edit"
tal:attributes="value view/form_action" />
<input type="hidden" name="version"
tal:attributes="value request/version | nothing" />
<table cellpadding="3" class="form">
<tbody><tr><th colspan="5"><br />
<tbody>
<tr>
<th colspan="5"
tal:attributes="colspan python: useI18N and 4 or 5"><br />
<span tal:replace="view/title"
i18n:translate="">Edit Information Object
</span>
</th></tr></tbody>
i18n:translate="">Edit Information Object</span>
</th>
<th tal:condition="useI18N"
style="vertical-align: bottom; text-align: right;
padding-right: 1em">
<select name="loops.language" id="loops.language"
tal:attributes="onChange
string:return replaceFieldsNodeForLanguage(
'form.fields', 'loops.language',
'${view/virtualTargetUrl}/$innerForm')">
<option tal:repeat="lang languages"
tal:content="lang"
tal:attributes="selected python: lang == language;">en</option>
</select>
</th>
</tr></tbody>
<tbody><tr><td colspan="5" style="padding-right: 15px">
<div id="form.fields">

View file

@ -19,6 +19,12 @@ function replaceFieldsNode(targetId, typeId, url) {
dojo.io.updateNode(targetId, uri);
}
function replaceFieldsNodeForLanguage(targetId, langId, url) {
lang = dojo.byId(langId).value;
uri = url + '?loops.language=' + lang;
dojo.io.updateNode(targetId, uri);
}
function submitReplacing(targetId, formId, actionUrl) {
dojo.io.updateNode(targetId, {
url: actionUrl,

View file

@ -2,3 +2,9 @@
$Id$
"""
from cybertools.browser.liquid import Liquid
class Loopz(Liquid):
""" The Loopz (neutral enduser) skin """

View file

@ -6,21 +6,23 @@
i18n_domain="zope"
>
<layer name="loopz" />
<skin name="Loopz"
layers="loopz
cybertools.browser.liquid.liquid
rotterdam default" />
<zope:interface
interface="loops.browser.skin.Loopz"
type="zope.publisher.interfaces.browser.IBrowserSkinType"
name="Loopz"
/>
<page for="*"
name="body.html"
class="loops.browser.skin.browser.View"
permission="zope.View"
layer="loopz" />
layer="loops.browser.skin.Loopz" />
<resource name="custom.css" file="custom.css" layer="loopz" />
<resource name="favicon.png" file="loops_favicon.png" layer="loopz" />
<resource name="logo.png" file="loops_logo.png" layer="loopz" />
<resource name="custom.css" file="custom.css"
layer="loops.browser.skin.Loopz" />
<resource name="favicon.png" file="loops_favicon.png"
layer="loops.browser.skin.Loopz" />
<resource name="logo.png" file="loops_logo.png"
layer="loops.browser.skin.Loopz" />
</configure>

View file

@ -22,13 +22,15 @@ Common stuff.
$Id$
"""
from zope import component
from zope.app.container.contained import NameChooser as BaseNameChooser
from zope.cachedescriptors.property import Lazy
from zope.component import adapts
from zope.dublincore.interfaces import IZopeDublinCore
from zope.dublincore.annotatableadapter import ZDCAnnotatableAdapter
from zope.dublincore.zopedublincore import ScalarProperty
from zope.component import adapts
from zope.interface import implements
from zope.cachedescriptors.property import Lazy
from zope.security.proxy import isinstance
from cybertools.storage.interfaces import IStorageInfo
from cybertools.typology.interfaces import IType
@ -36,12 +38,15 @@ from loops.interfaces import ILoopsObject, ILoopsContained, IConcept, IResource
from loops.interfaces import IResourceAdapter
def adapted(obj):
def adapted(obj, langInfo=None):
t = IType(obj, None)
if t is not None:
ti = t.typeInterface
if ti is not None:
adapted = ti(obj, None)
adapted = component.queryAdapter(obj, ti)
from loops.i18n.common import I18NAdapterBase
if isinstance(adapted, I18NAdapterBase):
adapted.languageInfo = langInfo
if adapted is not None:
return adapted
return obj

View file

@ -55,7 +55,7 @@
<class class="loops.base.Loops">
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
interface="zope.annotation.interfaces.IAttributeAnnotatable" />
<factory id="loops.Loops"
description="loops top-level container" />
@ -122,7 +122,7 @@
<class class=".concept.Concept">
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
interface="zope.annotation.interfaces.IAttributeAnnotatable" />
<factory
id="loops.Concept"
@ -166,7 +166,7 @@
<class class=".resource.Resource">
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
interface="zope.annotation.interfaces.IAttributeAnnotatable" />
<factory
id="loops.Resource"
@ -175,7 +175,7 @@
<require
permission="zope.View"
interface=".interfaces.IBaseResource
zope.app.size.interfaces.ISized" />
zope.size.interfaces.ISized" />
<require
permission="zope.ManageContent"
@ -190,7 +190,7 @@
<class class=".resource.Document">
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
interface="zope.annotation.interfaces.IAttributeAnnotatable" />
<factory
id="loops.Document"
@ -199,7 +199,7 @@
<require
permission="zope.View"
interface=".interfaces.IDocument
zope.app.size.interfaces.ISized" />
zope.size.interfaces.ISized" />
<require
permission="zope.ManageContent"
@ -214,7 +214,7 @@
<class class=".resource.MediaAsset">
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
interface="zope.annotation.interfaces.IAttributeAnnotatable" />
<factory
id="loops.MediaAsset"
@ -223,7 +223,7 @@
<require
permission="zope.View"
interface=".interfaces.IBaseResource
zope.app.size.interfaces.ISized" />
zope.size.interfaces.ISized" />
<require
@ -234,7 +234,7 @@
<!--<adapter
factory="zope.app.file.image.ImageSized"
provides="zope.app.size.interfaces.ISized"
provides="zope.size.interfaces.ISized"
for=".interfaces.IMediaAsset"
/>-->
@ -269,7 +269,7 @@
interface="loops.interfaces.ILoopsObject" />-->
<implements
interface="zope.app.annotation.interfaces.IAttributeAnnotatable" />
interface="zope.annotation.interfaces.IAttributeAnnotatable" />
<factory
id="loops.Node"
@ -293,21 +293,21 @@
<adapter factory="loops.common.LoopsDCAdapter"
for="loops.interfaces.INode"
provides="zope.app.dublincore.interfaces.IZopeDublinCore"
provides="zope.dublincore.interfaces.IZopeDublinCore"
trusted="True" />
<adapter factory="loops.common.LoopsDCAdapter"
for="loops.interfaces.IConcept"
provides="zope.app.dublincore.interfaces.IZopeDublinCore"
provides="zope.dublincore.interfaces.IZopeDublinCore"
trusted="True" />
<adapter factory="loops.common.LoopsDCAdapter"
for="loops.interfaces.IResource"
provides="zope.app.dublincore.interfaces.IZopeDublinCore"
provides="zope.dublincore.interfaces.IZopeDublinCore"
trusted="True" />
<class class="loops.common.LoopsDCAdapter">
<require like_class="zope.app.dublincore.annotatableadapter.ZDCAnnotatableAdapter" />
<require like_class="zope.dublincore.annotatableadapter.ZDCAnnotatableAdapter" />
</class>
<adapter factory="loops.concept.IndexAttributes" trusted="True" />
@ -457,7 +457,22 @@
factory="cybertools.storage.filesystem.fullPathStorage"
name="fullpath" />
<vocabulary
<utility
provides="zope.schema.interfaces.IVocabularyFactory"
component="loops.concept.ConceptTypeSourceList"
name="loops.conceptTypeSource" />
<utility
provides="zope.schema.interfaces.IVocabularyFactory"
component="loops.resource.ResourceTypeSourceList"
name="loops.resourceTypeSource" />
<utility
provides="zope.schema.interfaces.IVocabularyFactory"
component="loops.type.TypeInterfaceSourceList"
name="loops.TypeInterfaceSource" />
<!--<vocabulary
factory="loops.concept.ConceptTypeSourceList"
name="loops.conceptTypeSource"
/>
@ -465,16 +480,11 @@
<vocabulary
factory="loops.resource.ResourceTypeSourceList"
name="loops.resourceTypeSource"
/>
<vocabulary
factory="loops.type.TypeInterfaceSourceList"
name="loops.TypeInterfaceSource"
/>
/>-->
<!--<vocabulary
factory="loops.concept.PredicateSourceList"
name="loops.PredicateSource"
factory="loops.type.TypeInterfaceSourceList"
name="loops.TypeInterfaceSource"
/>-->
<utility component="loops.concept.PredicateSourceList"
@ -484,6 +494,7 @@
<include package=".browser" />
<include package=".classifier" />
<include package=".i18n" />
<include package=".integrator" />
<include package=".knowledge" />
<include package=".organize" />

125
i18n/README.txt Normal file
View file

@ -0,0 +1,125 @@
===============================================================
loops - Linked Objects for Organization and Processing Services
===============================================================
($Id$)
Let's do some basic set up
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
>>> site = placefulSetUp(True)
>>> from zope import component, interface
and setup a simple loops site with a concept manager and some concepts
(with all the type machinery, what in real life is done via standard
ZCML setup):
>>> from loops.interfaces import ILoops, IConcept
>>> from loops.concept import Concept
>>> from loops.setup import ISetupManager
>>> from loops.knowledge.setup import SetupManager
>>> component.provideAdapter(SetupManager, (ILoops,), ISetupManager,
... name='knowledge')
>>> from loops.tests.setup import TestSite
>>> t = TestSite(site)
>>> concepts, resources, views = t.setup()
>>> loopsRoot = site['loops']
>>> from loops.knowledge.knowledge import Topic
>>> component.provideAdapter(Topic)
For testing and demonstration purposes let's create a topic.
>>> topic = concepts['topic']
>>> topic01 = concepts['topic01'] = Concept(u'loops for Zope 3')
>>> topic01.conceptType = topic
Content Internationalization
============================
Let's look at a certain concept that should contain i18n-alized data.
>>> topic01.title
u'loops for Zope 3'
We can query the available languages, the current language setting and
the default language using a LanguageInfo object that is similar to a view.
>>> from zope.publisher.browser import TestRequest
>>> from loops.i18n.browser import LanguageInfo
>>> langInfo = LanguageInfo(topic01, TestRequest())
>>> langInfo.availableLanguages
[]
>>> langInfo.language is None
True
>>> langInfo.defaultLanguage is None
True
In order to use content i18n we have to define the available languages
as an option on the loops root object.
>>> loopsRoot.options = ['languages:en,de,it']
>>> langInfo = LanguageInfo(topic01, TestRequest())
>>> langInfo.availableLanguages
['en', 'de', 'it']
>>> langInfo.defaultLanguage
'en'
>>> langInfo.language
'en'
By setting an appropriate value in the URI we can select a certaing
language for processing of the current request.
>>> input = {'loops.language': 'it'}
>>> langInfo = LanguageInfo(topic01, TestRequest(form=input))
>>> langInfo.availableLanguages
['en', 'de', 'it']
>>> langInfo.defaultLanguage
'en'
>>> langInfo.language
'it'
Let's now use a form to edit an i18n-sensible attribute. For this we have
to set up some components needed by the zope.formlib machinery.
>>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> from zope.app.form.browser import TextWidget, ChoiceInputWidget, DropdownWidget
>>> from zope.schema.interfaces import ITextLine, IText, IChoice
>>> from zope.app.form.interfaces import IInputWidget
>>> component.provideAdapter(TextWidget, (IText, IBrowserRequest),
... IInputWidget)
>>> from loops.concept import ConceptTypeSourceList
>>> from zope.schema.vocabulary import getVocabularyRegistry
>>> getVocabularyRegistry().register('loops.conceptTypeSource', ConceptTypeSourceList)
>>> component.provideAdapter(ChoiceInputWidget,
... (IChoice, IBrowserRequest), IInputWidget)
>>> component.provideAdapter(DropdownWidget,
... (IChoice, ConceptTypeSourceList, IBrowserRequest), IInputWidget)
We also have to mark the attributes that should be stored in multiple
languages on the type object.
>>> from loops.common import adapted
>>> tTopic = adapted(topic)
>>> tTopic.options = ['i18nattributes:title,description']
Now we are ready to enter a language-specific title.
>>> from loops.browser.concept import ConceptEditForm
>>> input = {'form.title': 'loops per Zope 3', 'loops.language': 'it',
... 'form.actions.apply': 'Change'}
>>> form = ConceptEditForm(topic01, TestRequest(form=input))
>>> form.update()
>>> topic01.title
{'it': u'loops per Zope 3'}
Fin de partie
=============
>>> placefulTearDown()

4
i18n/__init__.py Normal file
View file

@ -0,0 +1,4 @@
"""
$Id$
"""

98
i18n/browser.py Normal file
View file

@ -0,0 +1,98 @@
#
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
View extension for support of i18n content.
$Id$
"""
from zope import interface, component
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.cachedescriptors.property import Lazy
from zope.i18n.interfaces import IUserPreferredLanguages
from zope.i18n.negotiator import negotiator
from loops.common import adapted
class LanguageInfo(object):
def __init__(self, context, request):
self.context = context
self.request = request
@Lazy
def loopsRoot(self):
return self.context.getLoopsRoot()
@Lazy
def availableLanguages(self):
for opt in self.loopsRoot.options:
if opt.startswith('languages:'):
return opt[len('languages:'):].split(',')
return []
@Lazy
def defaultLanguage(self):
langs = self.availableLanguages
return langs and langs[0] or None
@Lazy
def language(self):
lang = self.request.get('loops.language')
if lang is not None and lang in self.availableLanguages:
return lang
return (negotiator.getLanguage(self.availableLanguages, self.request)
or self.defaultLanguage)
class I18NView(object):
""" View mix-in class.
"""
@Lazy
def languageInfo(self):
return LanguageInfo(self.context, self.request)
@Lazy
def useI18N(self):
return (self.languageInfo.availableLanguages
and getattr(self.adapted, 'i18nAttributes', None))
@Lazy
def adapted(self):
return adapted(self.context, self.languageInfo)
def checkLanguage(self):
# get language from session
self.setPreferredLanguage()
def setLanguage(self, lang=None):
lang = lang or self.request.form.get('lang')
if lang:
upl = IUserPreferredLanguages(self.request)
upl.setPreferredLanguages([lang])
def switchLanguage(self, lang=None, keep=False):
keep = self.request.form.get('keep')
if keep:
pass # set in session
self.setPreferredLanguage(lang)
return self()

99
i18n/common.py Normal file
View file

@ -0,0 +1,99 @@
#
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
"""
Common stuff.
$Id$
"""
from zope import component
from zope.component import adapts
from zope.interface import implements
from zope.cachedescriptors.property import Lazy
from zope.security.proxy import removeSecurityProxy
from persistent.mapping import PersistentMapping
from cybertools.typology.interfaces import IType
from loops.common import adapted, AdapterBase
# support for i18n content
class I18NValue(PersistentMapping):
""" A dictionary to be used for storing values for different languages.
"""
def lower(self):
return str(self).lower()
def __str__(self):
return self.values()[0]
def getI18nValue(obj, attr, langInfo=None):
obj = removeSecurityProxy(obj)
value = getattr(obj, attr, None)
lang = None
if isinstance(value, I18NValue):
lang = langInfo and langInfo.language or value.keys()[0]
value = value.get(lang)
#print '*** getI18nValue', attr, langInfo, lang, getattr(obj, attr, None), value
return value
def setI18nValue(obj, attr, value, langInfo=None):
obj = removeSecurityProxy(obj)
old = getattr(obj, attr, None)
if langInfo is None:
setattr(obj, attr, value)
return
lang = langInfo.language
if isinstance(old, I18NValue):
old[lang] = value
else:
setattr(obj, attr, I18NValue(((lang, value),)))
#print '*** setI18nValue', attr, langInfo, lang, value, getattr(obj, attr, None)
class I18NAdapterBase(AdapterBase):
""" Base (or mix-in) class for concept adapters for internationalization of
context attributes.
"""
_adapterAttributes = AdapterBase._adapterAttributes + ('languageInfo',)
languageInfo = None
@Lazy
def i18nAttributes(self):
tp = IType(self.context)
attrs = tp.optionsDict.get('i18nattributes', '')
return [attr.strip() for attr in attrs.split(',')]
def __getattr__(self, attr):
self.checkAttr(attr)
langInfo = attr in self.i18nAttributes and self.languageInfo or None
return getI18nValue(self.context, '_' + attr, langInfo)
def __setattr__(self, attr, value):
if attr in self._adapterAttributes:
object.__setattr__(self, attr, value)
else:
langInfo = attr in self.i18nAttributes and self.languageInfo or None
self.checkAttr(attr)
setI18nValue(self.context, '_' + attr, value, langInfo)

14
i18n/configure.zcml Normal file
View file

@ -0,0 +1,14 @@
<!-- $Id$ -->
<configure
xmlns:zope="http://namespaces.zope.org/zope"
xmlns="http://namespaces.zope.org/browser"
i18n_domain="zope">
<page for="loops.interfaces.INode"
name="language_switch"
class="loops.browser.node.NodeView"
attribute="switchLanguage"
permission="zope.Public" />
</configure>

24
i18n/tests.py Executable file
View file

@ -0,0 +1,24 @@
# $Id$
import unittest, doctest
from zope.testing.doctestunit import DocFileSuite
from zope.app.testing import ztapi
from zope.interface.verify import verifyClass
class Test(unittest.TestCase):
"Basic tests for the i18n sub-package."
def testSomething(self):
pass
def test_suite():
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
return unittest.TestSuite((
unittest.makeSuite(Test),
DocFileSuite('README.txt', optionflags=flags),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')

View file

@ -32,7 +32,10 @@
<zope:class class="loops.knowledge.knowledge.Topic">
<require permission="zope.View"
interface="loops.knowledge.interfaces.ITopic" />
interface="loops.knowledge.interfaces.ITopic"
set_attributes="languageInfo" />
<require permission="zope.ManageContent"
set_schema="loops.knowledge.interfaces.ITopic" />
</zope:class>
<zope:adapter factory="loops.knowledge.knowledge.Task"

View file

@ -1,5 +1,5 @@
#
# Copyright (c) 2006 Helmut Merz helmutm@cy55.de
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
#
# 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
@ -33,6 +33,7 @@ from cybertools.typology.interfaces import IType
from cybertools.knowledge.interfaces import IKnowledgeElement, IKnowledgeProvider
from cybertools.knowledge.knowing import Knowing
from loops.interfaces import IConcept, IResource
from loops.i18n.common import I18NAdapterBase
from loops.knowledge.interfaces import IPerson, ITask, ITopic
from loops.organize.party import Person as BasePerson
from loops.organize.task import Task as BaseTask
@ -94,13 +95,13 @@ class Person(BasePerson, Knowing, KnowledgeAdapterMixin):
self.context.deassignParent(obj.context, (self.knowsPred,))
class Topic(AdapterBase, KnowledgeAdapterMixin):
class Topic(I18NAdapterBase, KnowledgeAdapterMixin):
""" A typeInterface adapter for concepts of type 'topic' that
may act as a knowledge element.
"""
implements(ITopic)
_adapterAttributes = ('context', '__parent__', 'parent')
_adapterAttributes = I18NAdapterBase._adapterAttributes + ('parent',)
def getParent(self):
parents = self.context.getParents((self.standardPred,))

View file

@ -28,7 +28,7 @@ from zope.interface import implements, Interface
from cybertools.knowledge.interfaces import IKnowledgeElement
from loops.concept import Concept
from loops.interfaces import ITypeConcept
from loops.knowledge.interfaces import IPerson, ITask
from loops.knowledge.interfaces import IPerson, ITask, ITopic
from loops.setup import SetupManager as BaseSetupManager
@ -44,7 +44,7 @@ class SetupManager(BaseSetupManager):
ITypeConcept(person).typeInterface = IPerson # this may override other packages!
topic = self.addObject(concepts, Concept, 'topic', title=u'Topic',
conceptType=type)
ITypeConcept(topic).typeInterface = IKnowledgeElement
ITypeConcept(topic).typeInterface = ITopic
task = self.addObject(concepts, Concept, 'task', title=u'Task',
conceptType=type)
ITypeConcept(task).typeInterface = ITask
@ -57,5 +57,5 @@ class SetupManager(BaseSetupManager):
conceptType=predicate)
provides = self.addObject(concepts, Concept, 'provides', title=u'provides',
conceptType=predicate)

View file

@ -31,7 +31,7 @@ from zope.interface import implements
from zope import schema
from zope.security.proxy import removeSecurityProxy
from zope.app.event.objectevent import ObjectModifiedEvent, Attributes
from zope.lifecycleevent import ObjectModifiedEvent, Attributes
from zope.event import notify
from loops.interfaces import ILoopsObject, IResource, IDocument, IMediaAsset