--- a/.installed.cfg Wed Mar 11 11:58:56 2015 +0100
+++ b/.installed.cfg Fri Mar 20 17:28:43 2015 +0100
@@ -1,26 +1,35 @@
[buildout]
-installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-i18n.egg-link
-parts = package i18n pyflakes pyflakesrun test
+installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-file.egg-link
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-i18n.egg-link
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-utils.egg-link
+parts = package i18n pyflakes test
[package]
-__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pserve
+__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pyams_upgrade
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/proutes
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pshell
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pviews
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pdistreport
/home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pcreate
- /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/proutes
- /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pviews
/home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/prequest
- /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pdistreport
- /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pshell
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pserve
/home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/ptweens
-__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.4-py3.4.egg zc.buildout-2.3.1-py3.4.egg
+__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-14.3-py3.4.egg zc.buildout-2.3.1-py3.4.egg
_b = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin
_d = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs
_e = /var/local/env/pyams/eggs
bin-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin
develop-eggs-directory = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs
eggs = pyams_i18n
+ persistent
+ pyams_file
+ pyams_utils
pyramid
zope.component
+ zope.container
zope.interface
+ zope.schema
+ zope.site
eggs-directory = /var/local/env/pyams/eggs
recipe = zc.recipe.egg
@@ -28,7 +37,7 @@
__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pybabel
/home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pot-create
/home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/polint
-__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.4-py3.4.egg zc.buildout-2.3.1-py3.4.egg
+__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-14.3-py3.4.egg zc.buildout-2.3.1-py3.4.egg
_b = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin
_d = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs
_e = /var/local/env/pyams/eggs
@@ -42,7 +51,7 @@
[pyflakes]
__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pyflakes
/home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/pyflakes
-__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.4-py3.4.egg zc.buildout-2.3.1-py3.4.egg
+__buildout_signature__ = zc.recipe.egg-2.0.1-py3.4.egg setuptools-14.3-py3.4.egg zc.buildout-2.3.1-py3.4.egg
_b = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin
_d = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs
_e = /var/local/env/pyams/eggs
@@ -55,17 +64,10 @@
recipe = zc.recipe.egg
scripts = pyflakes
-[pyflakesrun]
-__buildout_installed__ =
-__buildout_signature__ = collective.recipe.cmd-0.9-py3.4.egg zc.buildout-2.3.1-py3.4.egg setuptools-12.0.4-py3.4.egg
-cmds = ./bin/pyflakes
-on_install = true
-recipe = collective.recipe.cmd
-
[test]
__buildout_installed__ = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/parts/test
/home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/test
-__buildout_signature__ = zc.recipe.testrunner-2.0.0-py3.4.egg zc.recipe.egg-2.0.1-py3.4.egg setuptools-12.0.4-py3.4.egg zope.testrunner-4.4.6-py3.4.egg zc.buildout-2.3.1-py3.4.egg zope.interface-4.1.2-py3.4-linux-x86_64.egg zope.exceptions-4.0.7-py3.4.egg six-45a2be65d681713a598787ec39be3290
+__buildout_signature__ = zc.recipe.testrunner-2.0.0-py3.4.egg zc.recipe.egg-2.0.1-py3.4.egg setuptools-14.3-py3.4.egg zope.testrunner-4.4.6-py3.4.egg zc.buildout-2.3.1-py3.4.egg zope.interface-4.1.2-py3.4-linux-x86_64.egg zope.exceptions-4.0.7-py3.4.egg six-1482e89f68d85eea27f4ed7749df2819
_b = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin
_d = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs
_e = /var/local/env/pyams/eggs
@@ -76,3 +78,21 @@
location = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/parts/test
recipe = zc.recipe.testrunner
script = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/bin/test
+
+[buildout]
+installed_develop_eggs = /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-file.egg-link
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/lingua.egg-link
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-i18n.egg-link
+ /home/tflorac/Dropbox/src/PyAMS/pyams_i18n/develop-eggs/pyams-utils.egg-link
+
+[buildout]
+parts = i18n pyflakes test package
+
+[buildout]
+parts = pyflakes test package i18n
+
+[buildout]
+parts = test package i18n pyflakes
+
+[buildout]
+parts = package i18n pyflakes test
--- a/buildout.cfg Wed Mar 11 11:58:56 2015 +0100
+++ b/buildout.cfg Fri Mar 20 17:28:43 2015 +0100
@@ -18,12 +18,13 @@
src = src
develop = .
-
+ /var/local/src/pyams/ext/lingua
+ ../pyams_file
+ ../pyams_utils
parts =
package
i18n
pyflakes
- pyflakesrun
test
[package]
@@ -31,6 +32,7 @@
eggs =
pyams_i18n
persistent
+ pyams_file
pyams_utils
pyramid
zope.component
--- a/setup.py Wed Mar 11 11:58:56 2015 +0100
+++ b/setup.py Fri Mar 20 17:28:43 2015 +0100
@@ -60,6 +60,7 @@
'setuptools',
# -*- Extra requirements: -*-
'persistent',
+ 'pyams_file',
'pyams_utils',
'pyramid',
'zope.component',
--- a/src/pyams_i18n.egg-info/SOURCES.txt Wed Mar 11 11:58:56 2015 +0100
+++ b/src/pyams_i18n.egg-info/SOURCES.txt Fri Mar 20 17:28:43 2015 +0100
@@ -3,10 +3,14 @@
docs/HISTORY.txt
docs/README.txt
src/pyams_i18n/__init__.py
+src/pyams_i18n/attr.py
src/pyams_i18n/configure.zcml
src/pyams_i18n/language.py
src/pyams_i18n/negotiator.py
+src/pyams_i18n/property.py
+src/pyams_i18n/schema.py
src/pyams_i18n/site.py
+src/pyams_i18n/vocabulary.py
src/pyams_i18n.egg-info/PKG-INFO
src/pyams_i18n.egg-info/SOURCES.txt
src/pyams_i18n.egg-info/dependency_links.txt
@@ -16,6 +20,8 @@
src/pyams_i18n.egg-info/requires.txt
src/pyams_i18n.egg-info/top_level.txt
src/pyams_i18n/interfaces/__init__.py
+src/pyams_i18n/interfaces/schema.py
+src/pyams_i18n/interfaces/widget.py
src/pyams_i18n/locales/pyams_i18n.pot
src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.mo
src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.po
@@ -275,6 +281,8 @@
src/pyams_i18n/resources/img/flags/zh_SG.png
src/pyams_i18n/resources/img/flags/zh_TW.png
src/pyams_i18n/resources/img/flags/zu_ZA.png
+src/pyams_i18n/widget/__init__.py
+src/pyams_i18n/widget/templates/i18n-input.pt
src/pyams_i18n/zmi/__init__.py
src/pyams_i18n/zmi/configure.zcml
src/pyams_i18n/zmi/negotiator.py
\ No newline at end of file
--- a/src/pyams_i18n.egg-info/requires.txt Wed Mar 11 11:58:56 2015 +0100
+++ b/src/pyams_i18n.egg-info/requires.txt Fri Mar 20 17:28:43 2015 +0100
@@ -1,5 +1,6 @@
setuptools
persistent
+pyams_file
pyams_utils
pyramid
zope.component
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_i18n/attr.py Fri Mar 20 17:28:43 2015 +0100
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from zope.traversing.interfaces import ITraversable
+
+# import packages
+from pyams_utils.adapter import ContextAdapter, adapter_config
+from pyramid.exceptions import NotFound
+from zope.interface import Interface
+
+
+@adapter_config(name='i18n', context=Interface, provides=ITraversable)
+class I18nAttributeTraverser(ContextAdapter):
+ """++i18n++attr:lang namespace traverser"""
+
+ def traverse(self, name, furtherpath=None):
+ try:
+ attr, lang = name.split(':')
+ return getattr(self.context, attr, {}).get(lang)
+ except AttributeError:
+ raise NotFound
--- a/src/pyams_i18n/interfaces/__init__.py Wed Mar 11 11:58:56 2015 +0100
+++ b/src/pyams_i18n/interfaces/__init__.py Fri Mar 20 17:28:43 2015 +0100
@@ -19,7 +19,7 @@
from zope.interface import Interface, invariant, Invalid
# import packages
-from zope.schema import Choice, Set, Bool
+from zope.schema import Choice, Set, Bool, List
from pyams_i18n import _
@@ -55,8 +55,8 @@
"user select languages which are offered in "
"a skin."""),
value_type=Choice(vocabulary='PyAMS base languages'),
- default={'en', },
- required=False)
+ default={'en'},
+ required=True)
cache_enabled = Bool(title=_("Language caching enabled"),
description=_("Language caching enabled (per request)"),
@@ -77,6 +77,18 @@
"""Clear cached language value"""
+class II18nManager(Interface):
+ """Context languages manager
+
+ This interface is used to handle contents providing several languages
+ """
+
+ languages = List(title=_("Content languages"),
+ description=_("List of languages available for this content"),
+ required=True,
+ value_type=Choice(vocabulary='PyAMS offered languages'))
+
+
class IUserPreferredLanguage(Interface):
"""This interface provides language negotiation based on user preferences"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_i18n/interfaces/schema.py Fri Mar 20 17:28:43 2015 +0100
@@ -0,0 +1,45 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from zope.schema.interfaces import IDict
+
+# import packages
+
+
+class II18nField(IDict):
+ """I18n field marker interface"""
+
+
+class II18nTextLineField(II18nField):
+ """I18n text line field marker interface"""
+
+
+class II18nTextField(II18nField):
+ """I18n text field marker interface"""
+
+
+class II18nFileField(II18nField):
+ """I18n file field marker interface"""
+
+
+class II18nImageField(II18nFileField):
+ """I18n image field marker interface"""
+
+
+class II18nThumbnailImageField(II18nImageField):
+ """I18n image field with thumbnail marker interface"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_i18n/interfaces/widget.py Fri Mar 20 17:28:43 2015 +0100
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from z3c.form.interfaces import IWidget
+
+# import packages
+
+
+class II18nWidget(IWidget):
+ """I18n base widget interface"""
+
+
+class II18nTextLineWidget(II18nWidget):
+ """I18n text line widget interface"""
+
+
+class II18nTextWidget(II18nWidget):
+ """I18n text widget interface"""
+
+
+class II18nFileWidget(II18nWidget):
+ """I18n file widget interface"""
Binary file src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.mo has changed
--- a/src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.po Wed Mar 11 11:58:56 2015 +0100
+++ b/src/pyams_i18n/locales/fr/LC_MESSAGES/pyams_i18n.po Fri Mar 20 17:28:43 2015 +0100
@@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-02-03 21:41+0100\n"
+"POT-Creation-Date: 2015-03-20 17:26+0100\n"
"PO-Revision-Date: 2015-02-03 21:43+0100\n"
"Last-Translator: Thierry Florac <tflorac@ulthar.net>\n"
"Language-Team: French\n"
@@ -1540,7 +1540,11 @@
msgid "Zulu (South Africa)"
msgstr "Zoulou (Afrique du Sud)"
-#: src/pyams_i18n/zmi/negotiator.py:37
+#: src/pyams_i18n/vocabulary.py:43 src/pyams_i18n/vocabulary.py:59
+msgid "<unknown>"
+msgstr ""
+
+#: src/pyams_i18n/zmi/negotiator.py:40
msgid "Update languages negotiator properties"
msgstr "Mise à jour des propriétés du gestionnaire de langues"
@@ -1580,6 +1584,14 @@
msgid "Language caching enabled (per request)"
msgstr "Un cache de langue peut être activé pour chaque requête..."
+#: src/pyams_i18n/interfaces/__init__.py:86
+msgid "Content languages"
+msgstr "Langues proposées"
+
+#: src/pyams_i18n/interfaces/__init__.py:87
+msgid "List of languages available for this content"
+msgstr "Liste des langues disponibles pour la traduction des contenus"
+
#: src/pyams_i18n/interfaces/__init__.py:68
msgid "Unsupported language policy"
msgstr "Cette politique de sélection de langue n'est pas supportée..."
--- a/src/pyams_i18n/locales/pyams_i18n.pot Wed Mar 11 11:58:56 2015 +0100
+++ b/src/pyams_i18n/locales/pyams_i18n.pot Fri Mar 20 17:28:43 2015 +0100
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
-"POT-Creation-Date: 2015-02-03 21:41+0100\n"
+"POT-Creation-Date: 2015-03-20 17:26+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -14,7 +14,7 @@
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Lingua 3.8\n"
+"Generated-By: Lingua 3.10.dev0\n"
#: ./src/pyams_i18n/language.py:28
msgid "Afar"
@@ -1540,7 +1540,11 @@
msgid "Zulu (South Africa)"
msgstr ""
-#: ./src/pyams_i18n/zmi/negotiator.py:37
+#: ./src/pyams_i18n/vocabulary.py:43 ./src/pyams_i18n/vocabulary.py:59
+msgid "<unknown>"
+msgstr ""
+
+#: ./src/pyams_i18n/zmi/negotiator.py:40
msgid "Update languages negotiator properties"
msgstr ""
@@ -1578,6 +1582,14 @@
msgid "Language caching enabled (per request)"
msgstr ""
+#: ./src/pyams_i18n/interfaces/__init__.py:86
+msgid "Content languages"
+msgstr ""
+
+#: ./src/pyams_i18n/interfaces/__init__.py:87
+msgid "List of languages available for this content"
+msgstr ""
+
#: ./src/pyams_i18n/interfaces/__init__.py:68
msgid "Unsupported language policy"
msgstr ""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_i18n/property.py Fri Mar 20 17:28:43 2015 +0100
@@ -0,0 +1,120 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_file.interfaces import DELETED_FILE, IFile, IFileInfo, IFileFieldContainer
+from zope.annotation.interfaces import IAnnotations
+from zope.schema.interfaces import IField
+
+# import packages
+from pyams_file.file import FileFactory
+from pyams_file.property import FILE_CONTAINER_ATTRIBUTES
+from pyramid.threadlocal import get_current_registry
+from z3c.form.interfaces import NOT_CHANGED
+from zope.interface import alsoProvides
+from zope.lifecycleevent import ObjectCreatedEvent, ObjectRemovedEvent, ObjectAddedEvent
+from zope.location import locate
+
+
+_marker = object()
+
+
+class I18nFileProperty(object):
+ """I18n property class used to handle files"""
+
+ def __init__(self, field, name=None, klass=None, **args):
+ if not IField.providedBy(field):
+ raise ValueError("Provided field must implement IField interface...")
+ if name is None:
+ name = field.__name__
+ self.__field = field
+ self.__name = name
+ self.__klass = klass
+ self.__args = args
+
+ def __get__(self, instance, klass):
+ if instance is None:
+ return self
+ value = instance.__dict__.get(self.__name, _marker)
+ if value is _marker:
+ field = self.__field.bind(instance)
+ value = getattr(field, 'default', _marker)
+ if value is _marker:
+ raise AttributeError(self.__name)
+ return value
+
+ def __set__(self, instance, value):
+ registry = get_current_registry()
+ for lang in value:
+ lang_value = value[lang]
+ if (lang_value is DELETED_FILE) or (lang_value is NOT_CHANGED):
+ continue
+ elif lang_value is not None:
+ filename = None
+ # file upload data converter returns a tuple containing
+ # filename and buffered IO stream extracted from FieldStorage...
+ if isinstance(lang_value, tuple):
+ filename, lang_value = lang_value
+ # initialize file through factory
+ if not IFile.providedBy(lang_value):
+ factory = self.__klass or FileFactory
+ file = factory(lang_value, **self.__args)
+ registry.notify(ObjectCreatedEvent(file))
+ if not file.get_size():
+ lang_value.seek(0) # because factory may read until end of file...
+ file.data = lang_value
+ lang_value = file
+ if filename is not None:
+ info = IFileInfo(lang_value)
+ if info is not None:
+ info.filename = filename
+ value[lang] = lang_value
+ field = self.__field.bind(instance)
+ field.validate(value)
+ if field.readonly and instance.__dict__.has_key(self.__name):
+ raise ValueError(self.__name, "Field is readonly")
+ old_value = instance.__dict__.get(self.__name, _marker)
+ if old_value != value:
+ # check for previous value
+ if old_value is _marker:
+ old_value = {}
+ for lang in value:
+ new_lang_value = value.get(lang)
+ if new_lang_value is NOT_CHANGED:
+ continue
+ old_lang_value = old_value.get(lang, _marker)
+ if (old_lang_value is not _marker) and (old_lang_value is not None):
+ registry.notify(ObjectRemovedEvent(old_lang_value))
+ if new_lang_value is DELETED_FILE:
+ if self.__name in instance.__dict__:
+ del old_value[lang]
+ else:
+ # set name of new value
+ name = '++i18n++{0}:{1}'.format(self.__name, lang)
+ if new_lang_value is not None:
+ locate(new_lang_value, instance, name)
+ old_value[lang] = new_lang_value
+ # store file attributes of instance
+ if not IFileFieldContainer.providedBy(instance):
+ alsoProvides(instance, IFileFieldContainer)
+ annotations = IAnnotations(instance)
+ attributes = annotations.get(FILE_CONTAINER_ATTRIBUTES)
+ if attributes is None:
+ attributes = annotations[FILE_CONTAINER_ATTRIBUTES] = set()
+ attributes.add('{0}::{1}'.format(self.__name, lang))
+ registry.notify(ObjectAddedEvent(new_lang_value, instance, name))
+ instance.__dict__[self.__name] = old_value
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_i18n/schema.py Fri Mar 20 17:28:43 2015 +0100
@@ -0,0 +1,136 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_i18n.interfaces.schema import II18nField, II18nTextLineField, II18nTextField, II18nFileField, II18nImageField, \
+ II18nThumbnailImageField
+from zope.schema.interfaces import RequiredMissing
+
+# import packages
+from persistent.mapping import PersistentMapping
+from pyams_file.schema import FileField, ImageField, ThumbnailImageField
+from zope.interface import implementer
+from zope.schema import Dict, TextLine, Text
+
+
+_marker = object()
+
+
+class DefaultValueDict(PersistentMapping):
+ """Persistent mapping with default value"""
+
+ def __init__(self, default=None, *args, **kwargs):
+ super(DefaultValueDict, self).__init__(*args, **kwargs)
+ self._default = default
+
+ def __missing__(self, key):
+ return self._default
+
+ def get(self, key, default=None):
+ result = super(DefaultValueDict, self).get(key, _marker)
+ if result is _marker:
+ if default is not None:
+ return default
+ else:
+ return self._default
+ else:
+ return result
+
+ def copy(self):
+ return DefaultValueDict(default=self._default, **self)
+
+
+@implementer(II18nField)
+class I18nField(Dict):
+ """I18n base field class"""
+
+ def __init__(self, key_type=None, value_type=None, **kwargs):
+ # DefaultValueDict.__init__(self, default)
+ Dict.__init__(self, key_type=TextLine(), value_type=value_type, **kwargs)
+
+ def _validate(self, value):
+ super(I18nField, self)._validate(value)
+ if self.required:
+ if self.default:
+ return
+ if not value:
+ raise RequiredMissing
+ for lang in value.values():
+ if lang:
+ return
+ raise RequiredMissing
+
+
+@implementer(II18nTextLineField)
+class I18nTextLineField(I18nField):
+ """I18n text line field"""
+
+ def __init__(self, key_type=None, value_type=None, default=None,
+ value_constraint=None, value_min_length=0, value_max_length=None, **kwargs):
+ super(I18nTextLineField, self).__init__(value_type=TextLine(constraint=value_constraint,
+ min_length=value_min_length,
+ max_length=value_max_length,
+ default=default,
+ required=False),
+ **kwargs)
+
+
+@implementer(II18nTextField)
+class I18nTextField(I18nField):
+ """I18n text field"""
+
+ def __init__(self, key_type=None, value_type=None, default=None,
+ value_constraint=None, value_min_length=0, value_max_length=None, **kwargs):
+ super(I18nTextField, self).__init__(value_type=Text(constraint=value_constraint,
+ min_length=value_min_length,
+ max_length=value_max_length,
+ default=default,
+ required=False),
+ **kwargs)
+
+
+@implementer(II18nFileField)
+class I18nFileField(I18nField):
+ """I18n file field"""
+
+ def __init__(self, key_type=None, value_type=None, value_min_length=None, value_max_length=None, **kwargs):
+ super(I18nFileField, self).__init__(value_type=FileField(min_length=value_min_length,
+ max_length=value_max_length,
+ required=False),
+ **kwargs)
+
+
+@implementer(II18nImageField)
+class I18nImageField(I18nField):
+ """I18n image field"""
+
+ def __init__(self, key_type=None, value_type=None, value_min_length=None, value_max_length=None, **kwargs):
+ super(I18nImageField, self).__init__(value_type=ImageField(min_length=value_min_length,
+ max_length=value_max_length,
+ required=False),
+ **kwargs)
+
+
+@implementer(II18nThumbnailImageField)
+class I18nThumbnailImageField(I18nField):
+ """I18n thumbnail image field"""
+
+ def __init__(self, key_type=None, value_type=None, value_min_length=None, value_max_length=None, **kwargs):
+ super(I18nThumbnailImageField, self).__init__(value_type=ThumbnailImageField(min_length=value_min_length,
+ max_length=value_max_length,
+ required=False),
+ **kwargs)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_i18n/vocabulary.py Fri Mar 20 17:28:43 2015 +0100
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+from pyams_utils.traversing import get_parent
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_i18n.interfaces import INegotiator, II18nManager
+from zope.schema.interfaces import IVocabularyFactory
+
+# import packages
+from pyams_i18n.language import BASE_LANGUAGES
+from pyams_utils.registry import query_utility
+from pyams_utils.request import check_request
+from zope.interface import provider
+from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm, getVocabularyRegistry
+
+from pyams_i18n import _
+
+
+@provider(IVocabularyFactory)
+class I18nOfferedLanguages(SimpleVocabulary):
+ """I18n offered languages vocabulary"""
+
+ def __init__(self, context):
+ terms = []
+ negotiator = query_utility(INegotiator)
+ if negotiator is not None:
+ translate = check_request().localizer.translate
+ for lang in negotiator.offered_languages:
+ terms.append(SimpleTerm(lang, title=translate(BASE_LANGUAGES.get(lang) or _("<unknown>"))))
+ super(I18nOfferedLanguages, self).__init__(terms)
+
+getVocabularyRegistry().register('PyAMS offered languages', I18nOfferedLanguages)
+
+
+@provider(IVocabularyFactory)
+class I18nContentLanguages(SimpleVocabulary):
+ """I18n content languages vocabulary"""
+
+ def __init__(self, context):
+ terms = []
+ manager = get_parent(context, II18nManager)
+ if manager is not None:
+ translate = check_request().localizer.translate
+ for lang in manager.languages:
+ terms.append(SimpleTerm(lang, title=translate(BASE_LANGUAGES.get(lang) or _("<unknown>"))))
+ super(I18nContentLanguages, self).__init__(terms)
+
+getVocabularyRegistry().register('PyAMS content languages', I18nContentLanguages)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_i18n/widget/__init__.py Fri Mar 20 17:28:43 2015 +0100
@@ -0,0 +1,152 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+from pyramid.decorator import reify
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from pyams_form.interfaces.form import IFormLayer
+from pyams_i18n.interfaces import II18nManager, INegotiator
+from pyams_i18n.interfaces.schema import II18nField, II18nTextLineField, II18nTextField, II18nFileField
+from pyams_i18n.interfaces.widget import II18nWidget, II18nTextLineWidget, II18nTextWidget, II18nFileWidget
+from z3c.form.interfaces import IDataConverter, IFieldWidget, NO_VALUE
+
+# import packages
+from pyams_form.widget import widgettemplate_config
+from pyams_utils.adapter import adapter_config
+from pyams_utils.registry import query_utility
+from pyams_utils.traversing import get_parent
+from z3c.form.browser.widget import HTMLInputWidget
+from z3c.form.converter import BaseDataConverter
+from z3c.form.util import expandPrefix
+from z3c.form.widget import Widget, FieldWidget
+from zope.interface import implementer_only
+
+
+@adapter_config(context=(II18nField, II18nWidget), provides=IDataConverter)
+class I18nDataConverter(BaseDataConverter):
+ """I18n base data converter"""
+
+ def __init__(self, field, widget):
+ super(I18nDataConverter, self).__init__(field, widget)
+
+ def toWidgetValue(self, value):
+ # I18n widget is using a dict where each key is a lang
+ # and each value is matching widget value
+ result = {}
+ registry = self.widget.request.registry
+ for lang, val in value.items():
+ converter = registry.queryMultiAdapter((self.field.value_type, self.widget.get_widget(lang)),
+ IDataConverter)
+ if converter is not None:
+ result[lang] = converter.toWidgetValue(val)
+ return result
+
+ def toFieldValue(self, value):
+ # I18n widget is using a dict where each key is a lang
+ # and each value is matching widget value
+ result = {}
+ registry = self.widget.request.registry
+ for lang in self.widget.langs:
+ converter = registry.queryMultiAdapter((self.field.value_type, self.widget.get_widget(lang)),
+ IDataConverter)
+ if converter is not None:
+ result[lang] = converter.toFieldValue(self.widget.get_value(lang))
+ return result
+
+
+@widgettemplate_config(mode='input', template='templates/i18n-input.pt', widget=II18nWidget, layer=IFormLayer)
+@widgettemplate_config(mode='display', template='templates/i18n-input.pt', widget=II18nWidget, layer=IFormLayer)
+@implementer_only(II18nWidget)
+class I18nWidget(HTMLInputWidget, Widget):
+ """I18n base widget"""
+
+ @reify
+ def langs(self):
+ langs = []
+ negotiator = query_utility(INegotiator)
+ if negotiator is not None:
+ langs.append(negotiator.server_language)
+ manager = get_parent(self.context, II18nManager)
+ if manager is not None:
+ langs.extend(sorted(filter(lambda x: x not in langs, manager.languages)))
+ elif negotiator is not None:
+ langs.extend(sorted(filter(lambda x: x not in langs, negotiator.offered_languages)))
+ else:
+ langs.append('en')
+ return langs
+
+ def update(self):
+ self.widgets = {}
+ for lang in self.langs:
+ widget = self.request.registry.queryMultiAdapter((self.field.value_type, self.request), IFieldWidget)
+ if widget is not None:
+ prefix = expandPrefix(self.form.prefix) + expandPrefix(self.form.widgets.prefix) + expandPrefix(lang)
+ name = self.field.value_type.__name__ = self.field.__name__
+ widget.name = prefix + name
+ widget.id = widget.name.replace('.', '-')
+ widget.form = self.form
+ widget.field = self.field.value_type
+ widget.context = self.context
+ widget.ignoreContext = self.ignoreContext
+ widget.ignoreRequest = self.ignoreRequest
+ widget.lang = lang
+ widget.update()
+ self.widgets[lang] = widget
+ super(I18nWidget, self).update()
+
+ def extract(self, default=NO_VALUE):
+ result = {}
+ [result.setdefault(lang, self.widgets[lang].extract(default)) for lang in self.widgets.keys()]
+ return result
+
+ def get_widget(self, lang):
+ return self.widgets.get(lang)
+
+ def get_value(self, lang):
+ return self.get_widget(lang).value
+
+
+@implementer_only(II18nTextLineWidget)
+class I18nTextLineWidget(I18nWidget):
+ """I18n text line widget"""
+
+
+@adapter_config(context=(II18nTextLineField, IFormLayer), provides=IFieldWidget)
+def I18nTextLineFieldWidget(field, request):
+ """I18n text line field widget factory"""
+ return FieldWidget(field, I18nTextLineWidget(request))
+
+
+@implementer_only(II18nTextWidget)
+class I18nTextWidget(I18nWidget):
+ """I18n text widget"""
+
+
+@adapter_config(context=(II18nTextField, IFormLayer), provides=IFieldWidget)
+def I18nTextFieldWidget(field, request):
+ """I18n text field widget factory"""
+ return FieldWidget(field, I18nTextWidget(request))
+
+
+@implementer_only(II18nFileWidget)
+class I18nFileWidget(I18nWidget):
+ """I18n file widget"""
+
+
+@adapter_config(context=(II18nFileField, IFormLayer), provides=IFieldWidget)
+def I18nFileFieldWidget(field, request):
+ """I18n file field widget factory"""
+ return FieldWidget(field, I18nFileWidget(request))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_i18n/widget/templates/i18n-input.pt Fri Mar 20 17:28:43 2015 +0100
@@ -0,0 +1,32 @@
+<tal:var define="langs view/langs">
+ <tal:if condition="python:len(langs) == 1">
+ <div class="clearfix">
+ <tal:var replace="structure python:view.get_widget(tuple(langs)[0]).render()" />
+ </div>
+ </tal:if>
+ <tal:if condition="python:len(langs) > 1">
+ <ul class="nav nav-tabs">
+ <tal:loop repeat="lang langs">
+ <li tal:define="active python:'active' if repeat['lang'].start() else ''"
+ tal:attributes="class string:small ${active}">
+ <a data-toggle="tab"
+ tal:attributes="href string:#${view/id}-${lang}">
+ <img tal:attributes="src string:/--static--/pyams_i18n/img/flags/${lang}.png" />
+ <i class="fa fa-fw fa-star"
+ tal:define="widget python:view.get_widget(lang)"
+ tal:condition="widget/current_value | widget/value"></i>
+ </a>
+ </li>
+ </tal:loop>
+ </ul>
+ <div class="tab-content bordered nohover">
+ <tal:loop repeat="lang langs">
+ <div tal:define="active python:'active' if repeat['lang'].start() else ''"
+ tal:attributes="class string:clearfix tab-pane ${active} fade in padding-5;
+ id string:${view/id}-${lang};">
+ <tal:var replace="structure python:view.get_widget(lang).render()" />
+ </div>
+ </tal:loop>
+ </div>
+ </tal:if>
+</tal:var>