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:
parent
490b6216f5
commit
d7efbbdcdf
21 changed files with 537 additions and 67 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -2,3 +2,9 @@
|
|||
$Id$
|
||||
"""
|
||||
|
||||
from cybertools.browser.liquid import Liquid
|
||||
|
||||
|
||||
class Loopz(Liquid):
|
||||
""" The Loopz (neutral enduser) skin """
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
13
common.py
13
common.py
|
@ -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
|
||||
|
|
|
@ -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
125
i18n/README.txt
Normal 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
4
i18n/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
$Id$
|
||||
"""
|
||||
|
98
i18n/browser.py
Normal file
98
i18n/browser.py
Normal 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
99
i18n/common.py
Normal 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
14
i18n/configure.zcml
Normal 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
24
i18n/tests.py
Executable 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')
|
|
@ -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"
|
||||
|
|
|
@ -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,))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue