[PATCH 1 of 4 i18nfield V2] upgrade to new-style cube

noe.gaumont at logilab.fr noe.gaumont at logilab.fr
Thu Mar 19 11:53:53 CET 2020


# HG changeset patch
# User Noe Gaumont <ngaumont at logilab.fr>
# Date 1584355791 -3600
#      Mon Mar 16 11:49:51 2020 +0100
# Node ID a94a106c338a29b5ab4ac1aef44d1dab98442f1a
# Parent  bd9050cdaf103018f28ce3f68145a40fec7d1b2b
# Available At https://hg.logilab.org/review/cubes/i18nfield
#              hg pull https://hg.logilab.org/review/cubes/i18nfield -r a94a106c338a
upgrade to new-style cube

diff --git a/MANIFEST.in b/MANIFEST.in
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,9 +1,9 @@
+include README
 include *.py
 include */*.py
+recursive-include cubicweb_i18nfield *.py
+recursive-include cubicweb_i18nfield/data *.gif *.png *.ico *.css *.js
+recursive-include cubicweb_i18nfield/i18n *.po
+recursive-include test/data bootstrap_cubes *.py
 include tox.ini
-include test/data/bootstrap_cubes
-recursive-include data *.gif *.png *.ico *.css *.js
-recursive-include i18n *.po
-recursive-include test *.py
-recursive-include wdoc *
 prune debian
diff --git a/__init__.py b/__init__.py
deleted file mode 100644
--- a/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-"""cubicweb-i18nfield application package
-
-Provides a way to translate entity fields individually.
-"""
diff --git a/__pkginfo__.py b/__pkginfo__.py
deleted file mode 100644
--- a/__pkginfo__.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-# pylint: disable=W0622
-"""cubicweb-i18nfield application packaging information"""
-
-from os import listdir as _listdir
-from os.path import join, isdir
-from glob import glob
-
-modname = 'i18nfield'
-distname = 'cubicweb-i18nfield'
-
-numversion = (0, 1, 1)
-version = '.'.join(str(num) for num in numversion)
-
-license = 'LGPL'
-author = 'Florent Cayré (Villejuif, FRANCE)'
-author_email = 'Florent Cayré <florent.cayre at gmail.com>'
-description = 'Provides a way to translate entity fields individually.'
-web = 'http://www.cubicweb.org/project/%s' % distname
-
-__depends__ = {
-    'cubicweb': '>= 3.24',
-    'cubicweb-card': '>= 0.5',
-}
-
-__recommends__ = {}
-
-
-THIS_CUBE_DIR = join('share', 'cubicweb', 'cubes', modname)
-
-
-def listdir(dirpath):
-    return [join(dirpath, fname) for fname in _listdir(dirpath)
-            if fname[0] != '.' and not fname.endswith('.pyc')
-            and not fname.endswith('~')
-            and not isdir(join(dirpath, fname))]
-
-
-data_files = [
-    # common files
-    [THIS_CUBE_DIR, [fname for fname in glob('*.py') if fname != 'setup.py']],
-]
-
-# check for possible extended cube layout
-for dname in ('entities', 'views', 'sobjects', 'hooks', 'schema', 'data',
-              'wdoc', 'i18n', 'migration'):
-    if isdir(dname):
-        data_files.append([join(THIS_CUBE_DIR, dname), listdir(dname)])
-
-# Note: here, you'll need to add subdirectories if you want
-# them to be included in the debian package
diff --git a/cubicweb_i18nfield/__init__.py b/cubicweb_i18nfield/__init__.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/__init__.py
@@ -0,0 +1,4 @@
+"""cubicweb-i18nfield application package
+
+Provides a way to translate entity fields individually.
+"""
diff --git a/cubicweb_i18nfield/__pkginfo__.py b/cubicweb_i18nfield/__pkginfo__.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/__pkginfo__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=W0622
+"""cubicweb-i18nfield application packaging information"""
+
+modname = 'i18nfield'
+distname = 'cubicweb-i18nfield'
+
+numversion = (0, 1, 1)
+version = '.'.join(str(num) for num in numversion)
+
+license = 'LGPL'
+author = 'Florent Cayré (Villejuif, FRANCE)'
+author_email = 'Florent Cayré <florent.cayre at gmail.com>'
+description = 'Provides a way to translate entity fields individually.'
+web = 'http://www.cubicweb.org/project/%s' % distname
+
+__depends__ = {
+    'cubicweb': '>= 3.24',
+    'cubicweb-card': '>= 0.5',
+}
+
+__recommends__ = {}
+classifiers = [
+    'Environment :: Web Environment',
+    'Framework :: CubicWeb',
+    'Programming Language :: Python',
+    'Programming Language :: JavaScript',
+]
diff --git a/cubicweb_i18nfield/data/cubes.i18nfield.css b/cubicweb_i18nfield/data/cubes.i18nfield.css
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/data/cubes.i18nfield.css
@@ -0,0 +1,7 @@
+.cw_i18nfield_orig_lang {
+  size:0.9
+}
+
+.cw_i18nfield_orig_value {
+ font-weight: bold
+}
\ No newline at end of file
diff --git a/cubicweb_i18nfield/entities.py b/cubicweb_i18nfield/entities.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/entities.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
+# contact http://www.cubicweb.org/project/cubicweb-i18nfield
+# mailto:Florent Cayré <florent.cayre at gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 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 Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""cubicweb-i18nfield entity's classes"""
+
+from six import text_type as unicode
+import pytz
+
+from logilab.common.decorators import cached
+
+from cubicweb.entity import _marker
+from cubicweb.entities import AnyEntity, fetch_config
+from cubicweb.view import EntityAdapter
+
+
+def target_lang_code(req):
+    # warning, req can be a server session (with no 'form' attr)
+    return req.data.setdefault(
+        'i18nfield_target_lang',
+        getattr(req, 'form', {}).get('lang_code', req.lang))
+
+
+class I18nLang(AnyEntity):
+    __regid__ = 'I18nLang'
+    fetch_attrs, cw_fetch_order = fetch_config(['code'])
+
+
+class I18nField(AnyEntity):
+    __regid__ = 'I18nField'
+    fetch_attrs, cw_fetch_order = fetch_config(['field_name'])
+
+    def original_value(self):
+        return getattr(self.i18nfield_of[0], self.field_name)
+
+    def translation(self, lang_code):
+        rql = ('Any T,L,V WHERE T lang L, T value V, L code %(l)s, '
+               'T of_field F, F eid %(f)s')
+        rset = self._cw.execute(rql, {'l': lang_code, 'f': self.eid})
+        if rset:
+            return rset.get_entity(0, 0)
+        else:
+            return None
+
+
+def is_outdated(tdate1, tdate2):
+    """keep compatibility with old cw versions"""
+    try:
+        tdate1 = pytz.UTC.localize(tdate1)
+    except ValueError:
+        pass
+    try:
+        tdate2 = pytz.UTC.localize(tdate2)
+    except ValueError:
+        pass
+    return tdate1 < tdate2
+
+
+class Translation(AnyEntity):
+    __regid__ = 'Translation'
+
+    @property
+    def last_edited(self):
+        return self.of_field[0].last_edited
+
+    def is_outdated(self):
+        return is_outdated(self.modification_date, self.last_edited)
+
+
+class _TranslatableEntityAdapter(EntityAdapter):
+    __regid__ = 'translatable_entity'
+
+    @property
+    def ref_code(self):
+        return self.entity.ref_lang[0].code
+
+    @cached
+    def i18nfield(self, attr):
+        ctx = {'e': self.entity.eid, 'f': attr}
+        rql = ('Any F,N,D '
+               'WHERE F is I18nField, F field_name N, F last_edited D, '
+               'F i18nfield_of E, E eid %(e)s, F field_name %(f)s')
+        rset = self._cw.execute(rql, ctx)
+        return rset.rowcount and rset.get_entity(0, 0) or None
+
+    @cached
+    def translation(self, attr, lang_code=None):
+        lang_code = lang_code or self._cw.lang
+        ctx = {'l': lang_code, 'f': attr, 'e': self.entity.eid}
+        rset = self._cw.execute('Any T WHERE T is Translation, '
+                                'T value V, T modification_date TMD, '
+                                'T lang L, L code %(l)s, T of_field F, '
+                                'F field_name %(f)s, F last_edited FMD, '
+                                'F i18nfield_of E, E eid %(e)s', ctx)
+        return rset.get_entity(0, 0) if rset.rowcount else None
+
+    @cached
+    def translated_attr(self, attr, lang_code):
+        ctx = {'l': lang_code, 'f': attr, 'e': self.entity.eid}
+        rset = self._cw.execute('Any V,TMD,FMD WHERE T is Translation, '
+                                'T value V, T modification_date TMD, '
+                                'T lang L, L code %(l)s, T of_field F, '
+                                'F field_name %(f)s, F last_edited FMD, '
+                                'F i18nfield_of E, E eid %(e)s', ctx)
+        if rset.rowcount:
+            value, tdate, fdate = rset[0]
+            if is_outdated(tdate, fdate):
+                self.warning('Could not find up-to-date %s translation for '
+                             'field %s of entity %s; returning version of %s',
+                             lang_code, attr, self.entity.eid, tdate)
+        else:
+            self.warning('Could not find %s translation for field %s '
+                         'of entity %s', lang_code, attr, self.entity.eid)
+            value = getattr(self.entity, attr)
+        return value
+
+    def translation_infos(self):
+        '''for each language defined in the system, returns infos about current
+        entity translations in a (lang, infos) list, where infos holds
+        Translation and I18nField instances if a translation exists, and
+        None otherwise.
+        '''
+        all_langs = self._cw.execute('Any L,C,N WHERE L is I18nLang, '
+                                     'L code C, L name N').entities(0)
+        rql = ('Any L,T,TD,F,FN,FD WHERE L is I18nLang, T lang L, '
+               'T modification_date TD, T of_field F, F last_edited FD, '
+               'F field_name FN, F i18nfield_of E, E eid %(e)s')
+        rset = self._cw.execute(rql, {'e': self.entity.eid})
+        infos = {}
+        for i, row in enumerate(rset.rows):
+            translation, field = rset.get_entity(i, 1), rset.get_entity(i, 3)
+            infos.setdefault(row[0], []).append((translation, field))
+        return [(lang, infos.get(lang.eid)) for lang in all_langs
+                if lang.eid != self.entity.ref_lang[0].eid]
+
+
+class TranslatableEntityMixin(object):
+    i18nfields = ()
+
+    def printable_value(self, attr, value=_marker, attrtype=None,
+                        format='text/html', displaytime=True):
+        uattr = unicode(attr)  # attr could be a rschema object
+        if uattr in self.i18nfields and value == _marker:
+            adapted = self.cw_adapt_to('translatable_entity')
+            tlang_code = target_lang_code(self._cw)
+            if adapted.ref_code != tlang_code:
+                value = adapted.translated_attr(uattr, tlang_code)
+        return super(TranslatableEntityMixin, self).printable_value(
+            attr, value, attrtype, format)
diff --git a/cubicweb_i18nfield/hooks.py b/cubicweb_i18nfield/hooks.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/hooks.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
+# contact http://www.cubicweb.org/project/cubicweb-i18nfield
+# mailto:Florent Cayré <florent.cayre at gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 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 Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""cubicweb-i18nfield specific hooks and operations"""
+
+from cubicweb.predicates import adaptable, is_instance
+from cubicweb.server.hook import Hook, DataOperationMixIn, Operation
+
+from cubicweb_i18nfield.utils import set_lang, remove_lang
+
+
+# translatable entities related classes
+
+class TranslatableEntityAddHook(Hook):
+    __regid__ = 'i18nfield.translatable_entity_add'
+    __select__ = Hook.__select__ & adaptable('translatable_entity')
+    events = ('after_add_entity',)
+
+    def __call__(self):
+        langs = self._cw.transaction_data.get('i18nfield_lang', {})
+        for field in self.entity.i18nfields:
+            lang = langs.get(field, u'en')
+            ctx = {'e': self.entity.eid, 'c': lang, 'f': field}
+            rql = 'Any L WHERE L is I18nLang, L code %(c)s'
+            rset = self._cw.execute(rql, ctx)
+            if rset.rowcount == 0:
+                raise ValueError(self._cw._('language "%s" not found') % lang)
+            rset.get_entity(0, 0)
+            self._cw.create_entity('I18nField', field_name=field,
+                                   i18nfield_of=self.entity)
+
+
+class TranslatableEntityUpdateHook(Hook):
+    """
+    @test_translatable_entity_udpate
+    """
+    __regid__ = 'i18nfield.translatable_entity_udpate'
+    __select__ = Hook.__select__ & adaptable('translatable_entity')
+    events = ('before_update_entity',)
+    query = ('SET F last_edited NOW WHERE F is I18nField, '
+             'F field_name IN (%s), F i18nfield_of X, '
+             'X eid %%(x)s')
+
+    def __call__(self):
+        attrs = set(self.entity.i18nfields).intersection(self.entity.cw_edited)
+        if attrs:
+            field_names = ','.join("'%s'" % a for a in attrs)
+            query = self.query % field_names
+            self._cw.execute(query % {'x': self.entity.eid})
+
+
+# lang cache update related classes
+
+class LangCacheUpdateMixin(object):
+
+    def lang_dict(self, lang):
+        lang.complete()
+        lang_dict = lang.cw_attr_cache.copy()
+        del lang_dict['cwuri']
+        lang_dict['eid'] = lang.eid
+        return lang_dict
+
+    def update_cache(self, lang, op):
+        if op == 'remove':
+            remove_lang(lang.eid)
+        elif op == 'set':
+            set_lang(lang.eid, self.lang_dict(lang))
+
+
+class UpdateLangCacheOp(LangCacheUpdateMixin, DataOperationMixIn, Operation):
+
+    def precommit_event(self):
+        for (lang, op) in self.get_data():
+            self.update_cache(lang, op)
+
+
+class AddUpdateI18nLangHook(Hook):
+    __regid__ = 'i18nfield.add_update_lang'
+    __select__ = Hook.__select__ & is_instance('I18nLang')
+    events = ('after_update_entity', 'after_add_entity')
+
+    def __call__(self):
+        UpdateLangCacheOp.get_instance(self._cw).add_data((self.entity, 'set'))
+
+
+class RemoveI18nLangHook(Hook):
+    __regid__ = 'i18nfield.remove_lang'
+    __select__ = Hook.__select__ & is_instance('I18nLang')
+    events = ('after_delete_entity',)
+
+    def __call__(self):
+        UpdateLangCacheOp.get_instance(self._cw).add_data(
+            (self.entity, 'remove'))
+
+
+class ServerStartupHook(LangCacheUpdateMixin, Hook):
+    """fill-in language caches"""
+    __regid__ = 'i18nfield.server-startup'
+    events = ('server_startup',)
+
+    def __call__(self):
+        with self.repo.internal_cnx() as cnx:
+            try:
+                UpdateLangCacheOp.get_instance(cnx)
+                for lang in cnx.execute('Any L,C,N WHERE L is I18nLang, '
+                                        'L code C, L name N').entities(0):
+                    self.update_cache(lang, 'set')
+            except Exception:
+                pass
diff --git a/cubicweb_i18nfield/i18n/de.po b/cubicweb_i18nfield/i18n/de.po
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/i18n/de.po
@@ -0,0 +1,187 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: cubicweb 3.14.0\n"
+"PO-Revision-Date: 2008-03-28 18:14+0100\n"
+"Last-Translator: Logilab Team <contact at logilab.fr>\n"
+"Language-Team: fr <contact at logilab.fr>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: cubicweb-devtools\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#, python-format
+msgid "%s translation"
+msgstr ""
+
+# schema pot file, generated on 2011-09-19 10:17:10
+#
+# singular and plural forms for each entity type
+msgid "I18nField"
+msgstr ""
+
+msgid "I18nField_plural"
+msgstr ""
+
+msgid "I18nLang"
+msgstr ""
+
+msgid "I18nLang_plural"
+msgstr ""
+
+msgid "ISO 639.2 Code"
+msgstr ""
+
+msgid "ISO description"
+msgstr ""
+
+msgid "New I18nField"
+msgstr ""
+
+msgid "New I18nLang"
+msgstr ""
+
+msgid "New Translation"
+msgstr ""
+
+msgid "Original version"
+msgstr ""
+
+msgid ""
+"Partly taken from i18ncontent ; note this makes current cube and i18ncontent "
+"incompatible. registered language for an application. See http://www.loc.gov/"
+"standards/iso639-2 for available codes."
+msgstr ""
+
+msgid "This I18nField"
+msgstr ""
+
+msgid "This I18nLang"
+msgstr ""
+
+msgid "This Translation"
+msgstr ""
+
+msgid "Translation"
+msgstr ""
+
+msgctxt "inlined:I18nField.of_field.object"
+msgid "Translation"
+msgstr ""
+
+msgid "Translation_plural"
+msgstr ""
+
+msgid "add Translation of_field I18nField object"
+msgstr ""
+
+msgctxt "inlined:I18nField.of_field.object"
+msgid "add a Translation"
+msgstr ""
+
+# subject and object forms for each relation type
+# (no object form for final or symmetric relation types)
+msgid "code"
+msgstr ""
+
+msgctxt "I18nLang"
+msgid "code"
+msgstr ""
+
+msgid "creating Translation (Translation of_field I18nField %(linkto)s)"
+msgstr ""
+
+msgid "creation"
+msgstr ""
+
+msgid "edit"
+msgstr ""
+
+msgid "edition"
+msgstr ""
+
+msgid "field_name"
+msgstr ""
+
+msgctxt "I18nField"
+msgid "field_name"
+msgstr ""
+
+msgid "i18nfield_of"
+msgstr ""
+
+msgid "i18nfield_of_object"
+msgstr ""
+
+msgid "incomplete"
+msgstr ""
+
+msgid "lang"
+msgstr ""
+
+msgctxt "Translation"
+msgid "lang"
+msgstr ""
+
+msgid "lang_object"
+msgstr ""
+
+msgctxt "I18nLang"
+msgid "lang_object"
+msgstr ""
+
+#, python-format
+msgid "language \"%s\" not found"
+msgstr ""
+
+msgid "last_edited"
+msgstr ""
+
+msgctxt "I18nField"
+msgid "last_edited"
+msgstr ""
+
+msgid "manage translations"
+msgstr ""
+
+msgctxt "I18nLang"
+msgid "name"
+msgstr ""
+
+msgid "of_field"
+msgstr ""
+
+msgctxt "Translation"
+msgid "of_field"
+msgstr ""
+
+msgid "of_field_object"
+msgstr ""
+
+msgctxt "I18nField"
+msgid "of_field_object"
+msgstr ""
+
+msgid "outdated"
+msgstr ""
+
+msgid "ref_lang"
+msgstr ""
+
+msgid "ref_lang_object"
+msgstr ""
+
+msgid "translation"
+msgstr ""
+
+msgid "translations"
+msgstr ""
+
+#, python-format
+msgid "unknown language code \"%s\""
+msgstr ""
+
+msgctxt "Translation"
+msgid "value"
+msgstr ""
diff --git a/cubicweb_i18nfield/i18n/en.po b/cubicweb_i18nfield/i18n/en.po
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/i18n/en.po
@@ -0,0 +1,182 @@
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#, python-format
+msgid "%s translation"
+msgstr ""
+
+# schema pot file, generated on 2011-09-19 10:17:10
+#
+# singular and plural forms for each entity type
+msgid "I18nField"
+msgstr "I18nField"
+
+msgid "I18nField_plural"
+msgstr "I18nFields"
+
+msgid "I18nLang"
+msgstr "I18nLang"
+
+msgid "I18nLang_plural"
+msgstr "I18nLangs"
+
+msgid "ISO 639.2 Code"
+msgstr ""
+
+msgid "ISO description"
+msgstr ""
+
+msgid "New I18nField"
+msgstr ""
+
+msgid "New I18nLang"
+msgstr ""
+
+msgid "New Translation"
+msgstr ""
+
+msgid "Original version"
+msgstr ""
+
+msgid ""
+"Partly taken from i18ncontent ; note this makes current cube and i18ncontent "
+"incompatible. registered language for an application. See http://www.loc.gov/"
+"standards/iso639-2 for available codes."
+msgstr ""
+
+msgid "This I18nField"
+msgstr ""
+
+msgid "This I18nLang"
+msgstr ""
+
+msgid "This Translation"
+msgstr ""
+
+msgid "Translation"
+msgstr ""
+
+msgctxt "inlined:I18nField.of_field.object"
+msgid "Translation"
+msgstr ""
+
+msgid "Translation_plural"
+msgstr ""
+
+msgid "add Translation of_field I18nField object"
+msgstr ""
+
+msgctxt "inlined:I18nField.of_field.object"
+msgid "add a Translation"
+msgstr ""
+
+# subject and object forms for each relation type
+# (no object form for final or symmetric relation types)
+msgid "code"
+msgstr ""
+
+msgctxt "I18nLang"
+msgid "code"
+msgstr ""
+
+msgid "creating Translation (Translation of_field I18nField %(linkto)s)"
+msgstr ""
+
+msgid "creation"
+msgstr ""
+
+msgid "edit"
+msgstr ""
+
+msgid "edition"
+msgstr ""
+
+msgid "field_name"
+msgstr "field name"
+
+msgctxt "I18nField"
+msgid "field_name"
+msgstr ""
+
+msgid "i18nfield_of"
+msgstr "field of"
+
+msgid "i18nfield_of_object"
+msgstr "target fields"
+
+msgid "incomplete"
+msgstr ""
+
+msgid "lang"
+msgstr ""
+
+msgctxt "Translation"
+msgid "lang"
+msgstr ""
+
+msgid "lang_object"
+msgstr ""
+
+msgctxt "I18nLang"
+msgid "lang_object"
+msgstr ""
+
+#, python-format
+msgid "language \"%s\" not found"
+msgstr ""
+
+msgid "last_edited"
+msgstr "last edited"
+
+msgctxt "I18nField"
+msgid "last_edited"
+msgstr ""
+
+msgid "manage translations"
+msgstr ""
+
+msgctxt "I18nLang"
+msgid "name"
+msgstr ""
+
+msgid "of_field"
+msgstr ""
+
+msgctxt "Translation"
+msgid "of_field"
+msgstr ""
+
+msgid "of_field_object"
+msgstr "field translation"
+
+msgctxt "I18nField"
+msgid "of_field_object"
+msgstr ""
+
+msgid "outdated"
+msgstr ""
+
+msgid "ref_lang"
+msgstr "original language"
+
+msgid "ref_lang_object"
+msgstr ""
+
+msgid "translation"
+msgstr ""
+
+msgid "translations"
+msgstr ""
+
+#, python-format
+msgid "unknown language code \"%s\""
+msgstr ""
+
+msgctxt "Translation"
+msgid "value"
+msgstr ""
diff --git a/cubicweb_i18nfield/i18n/es.po b/cubicweb_i18nfield/i18n/es.po
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/i18n/es.po
@@ -0,0 +1,182 @@
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#, python-format
+msgid "%s translation"
+msgstr ""
+
+# schema pot file, generated on 2011-09-19 10:17:10
+#
+# singular and plural forms for each entity type
+msgid "I18nField"
+msgstr ""
+
+msgid "I18nField_plural"
+msgstr ""
+
+msgid "I18nLang"
+msgstr ""
+
+msgid "I18nLang_plural"
+msgstr ""
+
+msgid "ISO 639.2 Code"
+msgstr ""
+
+msgid "ISO description"
+msgstr ""
+
+msgid "New I18nField"
+msgstr ""
+
+msgid "New I18nLang"
+msgstr ""
+
+msgid "New Translation"
+msgstr ""
+
+msgid "Original version"
+msgstr ""
+
+msgid ""
+"Partly taken from i18ncontent ; note this makes current cube and i18ncontent "
+"incompatible. registered language for an application. See http://www.loc.gov/"
+"standards/iso639-2 for available codes."
+msgstr ""
+
+msgid "This I18nField"
+msgstr ""
+
+msgid "This I18nLang"
+msgstr ""
+
+msgid "This Translation"
+msgstr ""
+
+msgid "Translation"
+msgstr ""
+
+msgctxt "inlined:I18nField.of_field.object"
+msgid "Translation"
+msgstr ""
+
+msgid "Translation_plural"
+msgstr ""
+
+msgid "add Translation of_field I18nField object"
+msgstr ""
+
+msgctxt "inlined:I18nField.of_field.object"
+msgid "add a Translation"
+msgstr ""
+
+# subject and object forms for each relation type
+# (no object form for final or symmetric relation types)
+msgid "code"
+msgstr ""
+
+msgctxt "I18nLang"
+msgid "code"
+msgstr ""
+
+msgid "creating Translation (Translation of_field I18nField %(linkto)s)"
+msgstr ""
+
+msgid "creation"
+msgstr ""
+
+msgid "edit"
+msgstr ""
+
+msgid "edition"
+msgstr ""
+
+msgid "field_name"
+msgstr ""
+
+msgctxt "I18nField"
+msgid "field_name"
+msgstr ""
+
+msgid "i18nfield_of"
+msgstr ""
+
+msgid "i18nfield_of_object"
+msgstr ""
+
+msgid "incomplete"
+msgstr ""
+
+msgid "lang"
+msgstr ""
+
+msgctxt "Translation"
+msgid "lang"
+msgstr ""
+
+msgid "lang_object"
+msgstr ""
+
+msgctxt "I18nLang"
+msgid "lang_object"
+msgstr ""
+
+#, python-format
+msgid "language \"%s\" not found"
+msgstr ""
+
+msgid "last_edited"
+msgstr ""
+
+msgctxt "I18nField"
+msgid "last_edited"
+msgstr ""
+
+msgid "manage translations"
+msgstr ""
+
+msgctxt "I18nLang"
+msgid "name"
+msgstr ""
+
+msgid "of_field"
+msgstr ""
+
+msgctxt "Translation"
+msgid "of_field"
+msgstr ""
+
+msgid "of_field_object"
+msgstr ""
+
+msgctxt "I18nField"
+msgid "of_field_object"
+msgstr ""
+
+msgid "outdated"
+msgstr ""
+
+msgid "ref_lang"
+msgstr ""
+
+msgid "ref_lang_object"
+msgstr ""
+
+msgid "translation"
+msgstr ""
+
+msgid "translations"
+msgstr ""
+
+#, python-format
+msgid "unknown language code \"%s\""
+msgstr ""
+
+msgctxt "Translation"
+msgid "value"
+msgstr ""
diff --git a/cubicweb_i18nfield/i18n/fr.po b/cubicweb_i18nfield/i18n/fr.po
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/i18n/fr.po
@@ -0,0 +1,206 @@
+msgid ""
+msgstr ""
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: pygettext.py 1.5\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#, python-format
+msgid "%s translation"
+msgstr ""
+
+# schema pot file, generated on 2011-09-19 10:17:10
+#
+# singular and plural forms for each entity type
+msgid "I18nField"
+msgstr "Champ à traduire"
+
+msgid "I18nField_plural"
+msgstr "Champs à traduire"
+
+msgid "I18nLang"
+msgstr ""
+
+msgid "I18nLang_plural"
+msgstr ""
+
+msgid "ISO 639.2 Code"
+msgstr "Code ISO 639.2"
+
+msgid "ISO description"
+msgstr "description ISO"
+
+msgid "New I18nField"
+msgstr "Nouveau champ à traduire"
+
+msgid "New I18nLang"
+msgstr ""
+
+msgid "New Translation"
+msgstr "Nouvelle traduction"
+
+msgid "Original version"
+msgstr "Version originale"
+
+msgid ""
+"Partly taken from i18ncontent ; note this makes current cube and i18ncontent "
+"incompatible. registered language for an application. See http://www.loc.gov/"
+"standards/iso639-2 for available codes."
+msgstr ""
+
+msgid "This I18nField"
+msgstr "Ce champ à traduire"
+
+msgid "This I18nLang"
+msgstr ""
+
+msgid "This Translation"
+msgstr "Cette traduction"
+
+msgid "Translation"
+msgstr "Traduction"
+
+msgctxt "inlined:I18nField.of_field.object"
+msgid "Translation"
+msgstr "Traduction"
+
+msgid "Translation_plural"
+msgstr "Traductions"
+
+msgid "add Translation of_field I18nField object"
+msgstr "un champ à traduire de cette traduction"
+
+msgctxt "inlined:I18nField.of_field.object"
+msgid "add a Translation"
+msgstr "une traduction"
+
+# subject and object forms for each relation type
+# (no object form for final or symmetric relation types)
+msgid "code"
+msgstr "code"
+
+msgctxt "I18nLang"
+msgid "code"
+msgstr ""
+
+msgid "creating Translation (Translation of_field I18nField %(linkto)s)"
+msgstr "création d'une traduction (associée au champ à traduire %(linkto)s)"
+
+msgid "creation"
+msgstr "création"
+
+msgid "edit"
+msgstr "éditer"
+
+msgid "edition"
+msgstr "édition"
+
+msgid "field_name"
+msgstr "nom du champ"
+
+msgctxt "I18nField"
+msgid "field_name"
+msgstr "nom du champ"
+
+msgid "i18nfield_of"
+msgstr "champ de"
+
+msgid "i18nfield_of_object"
+msgstr "champ cible"
+
+msgid "incomplete"
+msgstr "incomplète"
+
+msgid "lang"
+msgstr "langue"
+
+msgctxt "Translation"
+msgid "lang"
+msgstr "langue"
+
+msgid "lang_object"
+msgstr "langue cible"
+
+msgctxt "I18nLang"
+msgid "lang_object"
+msgstr ""
+
+#, python-format
+msgid "language \"%s\" not found"
+msgstr "langue \"%s\" introuvable"
+
+msgid "last_edited"
+msgstr "dernière mise à jour"
+
+msgctxt "I18nField"
+msgid "last_edited"
+msgstr "dernière mise à jour"
+
+msgid "manage translations"
+msgstr "gérer les traductions"
+
+msgctxt "I18nLang"
+msgid "name"
+msgstr ""
+
+msgid "of_field"
+msgstr "champ associé"
+
+msgctxt "Translation"
+msgid "of_field"
+msgstr "champ associé"
+
+msgid "of_field_object"
+msgstr "traduction"
+
+msgctxt "I18nField"
+msgid "of_field_object"
+msgstr "champ cible"
+
+msgid "outdated"
+msgstr "obsolète"
+
+msgid "ref_lang"
+msgstr "langue de référence"
+
+msgid "ref_lang_object"
+msgstr "Langue cible"
+
+msgid "translation"
+msgstr "traduction"
+
+msgid "translations"
+msgstr "traductions"
+
+#, python-format
+msgid "unknown language code \"%s\""
+msgstr "code de langue inconnu \"%s\""
+
+msgctxt "Translation"
+msgid "value"
+msgstr "valeur"
+
+#~ msgid "Language"
+#~ msgstr "Langue"
+
+#~ msgid "Language_plural"
+#~ msgstr "Langues"
+
+#~ msgid "New Language"
+#~ msgstr "Nouvelle langue"
+
+#~ msgid "This Language"
+#~ msgstr "Cette langue"
+
+#~ msgctxt "Language"
+#~ msgid "code"
+#~ msgstr "code"
+
+#~ msgctxt "Language"
+#~ msgid "lang_object"
+#~ msgstr "langue cible"
+
+#~ msgctxt "Language"
+#~ msgid "name"
+#~ msgstr "nom"
diff --git a/cubicweb_i18nfield/migration/0.1.1_Any.py b/cubicweb_i18nfield/migration/0.1.1_Any.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/migration/0.1.1_Any.py
@@ -0,0 +1,2 @@
+
+change_attribute_type('I18nField', 'last_edited', 'TZDatetime', ask_confirm=False)
diff --git a/cubicweb_i18nfield/migration/postcreate.py b/cubicweb_i18nfield/migration/postcreate.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/migration/postcreate.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
+# contact http://www.cubicweb.org/project/cubicweb-i18nfield
+# mailto:Florent Cayré <florent.cayre at gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 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 Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""cubicweb-i18nfield postcreate script, executed at instance creation time or
+when the cube is added to an existing instance.
+
+You could setup site properties or a workflow here for example.
+"""
diff --git a/cubicweb_i18nfield/schema.py b/cubicweb_i18nfield/schema.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/schema.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
+# contact http://www.cubicweb.org/project/cubicweb-i18nfield
+# mailto:Florent Cayré <florent.cayre at gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 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 Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""cubicweb-i18nfield schema"""
+
+from six import text_type as unicode
+from yams.buildobjs import (EntityType, RelationType,
+                            String, TZDatetime, SubjectRelation)
+
+from cubicweb.schema import RQLConstraint
+
+_ = unicode
+
+
+class I18nLang(EntityType):
+    """Partly taken from i18ncontent ; note this makes current cube
+    and i18ncontent incompatible.
+
+    registered language for an application.
+
+    See http://www.loc.gov/standards/iso639-2 for available codes.
+    """
+    code = String(required=True, maxsize=2, unique=True,
+                  description=_('ISO 639.2 Code'))
+    name = String(required=True, internationalizable=True, maxsize=37,
+                  description=_('ISO description'))
+
+
+class I18nField(EntityType):
+    __unique_together__ = [('field_name', 'i18nfield_of')]
+    __permissions__ = {'read': ('managers', 'users', 'guests',),
+                       'add': (),
+                       'update': (),
+                       'delete': ()}
+    field_name = String(required=True, maxsize=64)
+    last_edited = TZDatetime(required=True, default='NOW')
+
+
+other_lang_cstr = RQLConstraint(
+    'S of_field F, F i18nfield_of E, E ref_lang L, '
+    'NOT O identity L')
+
+
+class Translation(EntityType):
+    __unique_together__ = [('lang', 'of_field')]
+    value = String()
+    lang = SubjectRelation('I18nLang', cardinality='1*', inlined=True,
+                           constraints=[other_lang_cstr])
+    of_field = SubjectRelation('I18nField', cardinality='1*', inlined=True,
+                               composite='object')
+
+
+STRICTLY_AUTO_REL_PERMS = {'read': ('managers', 'users', 'guests',),
+                           'add': (),
+                           'delete': ()}
+
+
+class ref_lang(RelationType):
+    object = 'I18nLang'
+    cardinality = '1*'
+    inlined = True
+
+
+class i18nfield_of(RelationType):
+    __permissions__ = STRICTLY_AUTO_REL_PERMS
+    subject = 'I18nField'
+    cardinality = '1*'
+    inlined = True
+    composite = 'object'
diff --git a/cubicweb_i18nfield/uiprops.py b/cubicweb_i18nfield/uiprops.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/uiprops.py
@@ -0,0 +1,3 @@
+# flake8: noqa
+
+STYLESHEETS.append(data('cubes.i18nfield.css'))
diff --git a/cubicweb_i18nfield/utils.py b/cubicweb_i18nfield/utils.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/utils.py
@@ -0,0 +1,18 @@
+LANGS_BY_CODE = {}  # see hooks.py
+LANGS_BY_EID = {}  # see hooks.py
+
+
+def set_lang(lang_eid, lang_dict):
+    try:
+        old_code = LANGS_BY_EID[lang_eid]['code']
+        if lang_dict['code'] != old_code:
+            del LANGS_BY_CODE[old_code]
+    except KeyError:
+        pass  # creation case
+    LANGS_BY_CODE[lang_dict['code']] = lang_dict
+    LANGS_BY_EID[lang_eid] = lang_dict
+
+
+def remove_lang(lang_eid):
+    del LANGS_BY_CODE[LANGS_BY_EID[lang_eid]['code']]
+    del LANGS_BY_EID[lang_eid]
diff --git a/cubicweb_i18nfield/views.py b/cubicweb_i18nfield/views.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_i18nfield/views.py
@@ -0,0 +1,378 @@
+# -*- coding: utf-8 -*-
+# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
+# contact http://www.cubicweb.org/project/cubicweb-i18nfield
+# mailto:Florent Cayré <florent.cayre at gmail.com>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 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 Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""cubicweb-i18nfield views/forms/actions/components for web ui"""
+
+from six import text_type as unicode
+from logilab.mtconverter import xml_escape
+from logilab.common.decorators import iclassmethod
+
+from cubicweb import typed_eid, tags
+from cubicweb.predicates import (adaptable, has_permission, is_instance,
+                                 match_form_params, one_line_rset,
+                                 match_kwargs, specified_etype_implements)
+from cubicweb.web.views import uicfg
+from cubicweb.web.action import Action
+from cubicweb.web.form import FieldNotFound
+from cubicweb.web.formfields import guess_field
+from cubicweb.web.formwidgets import HiddenInput
+from cubicweb.web.views.editforms import EditionFormView
+from cubicweb.web.views.autoform import (AutomaticEntityForm as AutoForm,
+                                         InlineEntityEditionFormView,
+                                         InlineEntityCreationFormView)
+from cubicweb.web.views.formrenderers import EntityInlinedFormRenderer
+
+
+from cubicweb.web.views import tableview as cw_tableview
+from cubicweb.uilib import sgml_attributes
+from cubicweb.utils import UStringIO
+
+from cubicweb_i18nfield.utils import LANGS_BY_EID
+
+from cubicweb.view import StartupView
+from cubicweb.web.httpcache import NoHTTPCacheManager
+from cubicweb.web.views.urlrewrite import SimpleReqRewriter, rgx
+
+_ = unicode  # make pylint happier
+
+_AFF = uicfg.autoform_field
+_AFF_KWARGS = uicfg.autoform_field_kwargs
+_AFS = uicfg.autoform_section
+_AFFK = uicfg.autoform_field_kwargs
+
+
+def lang_from_code(req, code=None):
+    code = code or req.form.get('lang_code', req.lang)
+    if code is None:
+        return None
+    rql = 'Any L,N WHERE L is I18nLang, L name N, L code %(l)s'
+    try:
+        return req.execute(rql, {'l': code}).get_entity(0, 0)
+    except IndexError:
+        raise ValueError(req._('unknown language code "%s"') % code)
+
+
+class TranslateEntityAction(Action):
+    __regid__ = 'translate_entity'
+    __select__ = (adaptable('translatable_entity') & has_permission('update')
+                  & one_line_rset())
+
+    submenu = _('translations')
+    category = 'mainactions'
+
+    def actual_actions(self):
+        entity = self.cw_rset.get_entity(0, 0)
+        _infos = entity.cw_adapt_to('translatable_entity').translation_infos()
+        for lang, infos in _infos:
+            url = entity.absolute_url(vid='translate_entity',
+                                      lang_code=lang.code)
+            if infos is None:
+                action = self._cw._('creation')
+            else:
+                # add translation state in action text (outdated, incomplete)
+                action = []
+                if len([1 for t, _f in infos if t.is_outdated()]):
+                    action.append(self._cw._('outdated'))
+                if len(infos) < len(entity.i18nfields):
+                    action.append(self._cw._('incomplete'))
+                action = u', '.join(action) or self._cw._('edition')
+            title = u'%s (%s)' % (self._cw._(lang.name), action)
+            yield self.build_action(title, url)
+        yield self.build_action(self._cw._('manage translations'),
+                                self._cw.build_url('i18n'))
+
+    def fill_menu(self, box, menu):
+        menu.append_anyway = True
+        super(TranslateEntityAction, self).fill_menu(box, menu)
+
+
+class ManageTranslationsView(StartupView):
+    __regid__ = 'i18nfield.manage_translations'
+    http_cache_manager = NoHTTPCacheManager
+
+    def select_rql(self):
+        return ('Any X,GROUP_CONCAT(C) GROUPBY X ORDERBY MD DESC '
+                'WHERE X modification_date MD, NOT X ref_lang L, L name C, ')
+
+    def incomplete_rql(self):
+        return (self.select_rql()
+                + 'EXISTS(F i18nfield_of X, NOT EXISTS(T of_field F, T lang L))')
+
+    def outdated_rql(self):
+        return (self.select_rql()
+                + 'EXISTS(F i18nfield_of X, F last_edited FD, T of_field F,'
+                ' T modification_date < FD, T lang L)')
+
+    def lang_html(self, entity, lang_eids):
+        html = []
+        for eid in lang_eids.split(','):
+            infos = LANGS_BY_EID[typed_eid(eid)]
+            href = entity.absolute_url(vid='translate_entity',
+                                       lang_code=infos['code'])
+            html.append(u'%s (%s)' % (xml_escape(infos['name']),
+                                      tags.a(self._cw._('edit'), href=href)))
+        return ', '.join(html)
+
+    def call(self, *args, **kwargs):
+        sections = (('incomplete translations', self.incomplete_rql),
+                    ('outdated translations', self.outdated_rql))
+        for title, rql_method in sections:
+            rset = self._cw.execute(rql_method())
+            if rset.rowcount:
+                self.w(u'<div class="section translations">'
+                       u'<h2>%s</h2>' % self._cw._(title))
+                self.wview('table.translations', rset)
+                self.w(u'</div>')
+
+
+class ManageTranslationTableView(cw_tableview.RsetTableView):
+    __regid__ = 'table.translations'
+    layout_id = 'table.table-layout'
+    headers = (_('entities'), _('languages'))
+    layout_args = {'display_filter': 'top', 'hide_filter': False}
+
+
+class TableLayout(cw_tableview.TableLayout):
+    __regid__ = 'table.table-layout'
+    cssclass = 'table table-default listing'
+    page_size = 20
+
+    def render_table(self, w, actions, paginate):
+        view = self.view
+        divid = view.domid
+        if divid is not None:
+            w(u'<div id="%s">' % divid)
+        else:
+            assert not (actions or paginate)
+        nav_html = UStringIO()
+        if paginate:
+            #  customization :  add page_size=self.page_size
+            view.paginate(w=nav_html.write, page_size=self.page_size,
+                          show_all_option=self.show_all_option)
+        w(nav_html.getvalue())
+        if actions and self.display_actions == 'top':
+            self.render_actions(w, actions)
+        colrenderers = view.build_column_renderers()
+        attrs = self.table_attributes()
+        w(u'<table %s>' % sgml_attributes(attrs))
+        if self.view.has_headers:
+            self.render_table_headers(w, colrenderers)
+        self.render_table_body(w, colrenderers)
+        w(u'</table>')
+        if actions and self.display_actions == 'bottom':
+            self.render_actions(w, actions)
+        if divid is not None:
+            w(u'</div>')
+
+
+class TranslateEntityView(EditionFormView):
+    __regid__ = 'translate_entity'
+    __select__ = (EditionFormView.__select__ & adaptable('translatable_entity')
+                  & match_form_params('lang_code'))
+    form_id = 'translate_entity'
+
+    @property
+    def title(self):
+        lang = lang_from_code(self._cw)
+        return self._cw._('%s translation') % self._cw._(lang.name)
+
+
+class TranslateEntityForm(AutoForm):
+    __regid__ = 'translate_entity'
+    __select__ = AutoForm.__select__ & adaptable('translatable_entity')
+
+    def editable_attributes(self):
+        return []
+
+    def inlined_relations(self):
+        rschema = self._cw.vreg.schema['i18nfield_of']
+        ttype = self._cw.vreg.schema['I18nField']
+        return [(rschema, (ttype,), 'object')]
+
+    def inline_edition_form_view(self, rschema, ttype, role):
+        """overloaded method to force uneditable I18nField instance forms to
+        be displayed, so that underlying Translation entities can be created
+        or edited.
+        """
+        assert str(rschema) == 'i18nfield_of'
+        entity = self.edited_entity
+        rset = entity.has_eid() and entity.related(rschema, role)
+        if rset:
+            fields = entity.i18nfields
+            related = sorted(rset.entities(),
+                             key=lambda f: fields.index(f.field_name))
+            vvreg = self._cw.vreg['views']
+            for relentity in related:
+                yield vvreg.select('inline-edition', self._cw,
+                                   rset=relentity.as_rset(), row=0, col=0,
+                                   etype=ttype, rtype=rschema, role=role,
+                                   peid=entity.eid, pform=self)
+
+
+class I18nFieldInlineEditionView(InlineEntityEditionFormView):
+    __select__ = (InlineEntityEditionFormView.__select__
+                  & is_instance('I18nField') & match_form_params('lang_code'))
+
+    def form_title(self, entity, i18nctx):
+        return entity.field_name
+
+    def _get_removejs(self):
+        return None
+
+
+class I18nFieldEditionForm(AutoForm):
+    __select__ = (AutoForm.__select__ & is_instance('I18nField')
+                  & match_form_params('lang_code'))
+
+    def inlined_relations(self):
+        rschema = self._cw.vreg.schema['of_field']
+        ttype = self._cw.vreg.schema['Translation']
+        return [(rschema, (ttype,), 'object')]
+
+    def inline_edition_form_view(self, rschema, ttype, role):
+        assert str(rschema) == 'of_field'
+        translation = self.edited_entity.translation(
+            self._cw.form['lang_code'])
+        vvreg = self._cw.vreg['views']
+        if translation:
+            yield vvreg.select('inline-edition', self._cw,
+                               rset=translation.as_rset(), row=0, col=0,
+                               etype=ttype, rtype=rschema, role=role,
+                               peid=self.edited_entity.eid, pform=self)
+        else:
+            yield vvreg.select('inline-creation', self._cw,
+                               etype=ttype, rtype=rschema, role=role,
+                               petype=self.edited_entity.e_schema,
+                               peid=self.edited_entity.eid,
+                               pform=self)
+
+    def should_display_inline_creation_form(self, rschema, existant, card):
+        return not existant
+
+    def should_display_add_new_relation_link(self, rschema, existant, card):
+        return False
+
+
+class I18nTranslationInlineViewMixin(object):
+
+    def form_title(self, entity, i18nctx):
+        title = super(I18nTranslationInlineViewMixin, self).form_title(
+            entity, i18nctx)
+        if entity.has_eid() and entity.is_outdated():
+            title += u' (%s)' % self._cw._('outdated')
+        return title
+
+
+class I18nTranslationInlinedFormRenderer(EntityInlinedFormRenderer):
+    __select__ = (EntityInlinedFormRenderer.__select__
+                  & is_instance('Translation'))
+
+    def render_title(self, w, form, values):
+        return
+
+
+class I18nFieldInlinedFormRenderer(EntityInlinedFormRenderer):
+    __select__ = (EntityInlinedFormRenderer.__select__
+                  & is_instance('I18nField'))
+    title_template = u"""\
+    <div class="{css_value}">{orig_val}</div>
+    <div class="{css_lang}">{label} : {orig_lang}</div>
+    """
+
+    def render_title(self, w, form, values):
+        ent = form.edited_entity
+        template_parameters = {
+            'orig_val': xml_escape(ent.original_value() or u''),
+            'orig_lang': self._cw._(ent.i18nfield_of[0].ref_lang[0].name),
+            'label': self._cw._('Original version'),
+            'css_lang': 'cw_i18nfield_orig_lang',
+            'css_value': 'cw_i18nfield_orig_value',
+        }
+        w(self.title_template.format(**template_parameters))
+
+
+class I18nTranslationInlineEditionView(I18nTranslationInlineViewMixin,
+                                       InlineEntityEditionFormView):
+    __select__ = (InlineEntityEditionFormView.__select__
+                  & is_instance('Translation'))
+
+
+class I18nTranslationInlineCreationView(I18nTranslationInlineViewMixin,
+                                        InlineEntityCreationFormView):
+    __select__ = (InlineEntityCreationFormView.__select__
+                  & specified_etype_implements('Translation'))
+
+
+class TranslationEditionForm(AutoForm):
+    __select__ = (AutoForm.__select__ & is_instance('Translation')
+                  & match_kwargs('pform'))
+
+    @iclassmethod
+    def field_by_name(cls_or_self, name, role=None, eschema=None):
+        '''make the field used for translation value the same than the one
+        used for the field in the translated entity itself'''
+        if (name, role) == ('value', 'subject'):
+            try:
+                return super(
+                    TranslationEditionForm, cls_or_self).field_by_name(
+                        name, role)
+            except FieldNotFound:
+                if eschema is None:
+                    raise
+            i18nfield = cls_or_self.parent_form.edited_entity
+            orig_eschema = i18nfield.i18nfield_of[0].e_schema
+            rschema = orig_eschema.schema.rschema(i18nfield.field_name)
+            tschemas = rschema.targets(orig_eschema, role)
+            fieldcls = _AFF.etype_get(orig_eschema, rschema, role, tschemas[0])
+            kwargs = _AFF_KWARGS.etype_get(orig_eschema, rschema,
+                                           role, tschemas[0])
+            if kwargs is None:
+                kwargs = {}
+            if fieldcls:
+                if not isinstance(fieldcls, type):
+                    return fieldcls  # already and instance
+                return fieldcls(name=name, role=role, eidparam=True, **kwargs)
+            field = guess_field(orig_eschema, rschema, role,
+                                eidparam=True, **kwargs)
+            if field is None:
+                raise
+            field.name = name
+            if getattr(field, 'get_format_field', None):
+                field.get_format_field = lambda form: None
+            return field
+        else:
+            return super(TranslationEditionForm, cls_or_self).field_by_name(
+                name, role, eschema)
+
+
+def translation_form_lang(form, field):
+    lang = lang_from_code(form._cw)
+    return lang is not None and lang.eid or None
+
+
+_AFS.tag_object_of(('Translation', 'of_field', 'I18nField'), 'main', 'inlined')
+
+_AFFK.tag_subject_of(('Translation', 'lang', 'I18nLang'),
+                     {'widget': HiddenInput, 'value': translation_form_lang})
+
+
+class i18nfieldReqRewriter(SimpleReqRewriter):
+    rules = [
+        (rgx('/i18n'),
+         dict(vid='i18nfield.manage_translations')),
+    ]
diff --git a/data/cubes.i18nfield.css b/data/cubes.i18nfield.css
deleted file mode 100644
--- a/data/cubes.i18nfield.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.cw_i18nfield_orig_lang {
-  size:0.9
-}
-
-.cw_i18nfield_orig_value {
- font-weight: bold
-}
\ No newline at end of file
diff --git a/entities.py b/entities.py
deleted file mode 100644
--- a/entities.py
+++ /dev/null
@@ -1,163 +0,0 @@
-# -*- coding: utf-8 -*-
-# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
-# contact http://www.cubicweb.org/project/cubicweb-i18nfield
-# mailto:Florent Cayré <florent.cayre at gmail.com>
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 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 Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""cubicweb-i18nfield entity's classes"""
-
-from six import text_type as unicode
-import pytz
-
-from logilab.common.decorators import cached
-
-from cubicweb.entity import _marker
-from cubicweb.entities import AnyEntity, fetch_config
-from cubicweb.view import EntityAdapter
-
-
-def target_lang_code(req):
-    # warning, req can be a server session (with no 'form' attr)
-    return req.data.setdefault(
-        'i18nfield_target_lang',
-        getattr(req, 'form', {}).get('lang_code', req.lang))
-
-
-class I18nLang(AnyEntity):
-    __regid__ = 'I18nLang'
-    fetch_attrs, cw_fetch_order = fetch_config(['code'])
-
-
-class I18nField(AnyEntity):
-    __regid__ = 'I18nField'
-    fetch_attrs, cw_fetch_order = fetch_config(['field_name'])
-
-    def original_value(self):
-        return getattr(self.i18nfield_of[0], self.field_name)
-
-    def translation(self, lang_code):
-        rql = ('Any T,L,V WHERE T lang L, T value V, L code %(l)s, '
-               'T of_field F, F eid %(f)s')
-        rset = self._cw.execute(rql, {'l': lang_code, 'f': self.eid})
-        if rset:
-            return rset.get_entity(0, 0)
-        else:
-            return None
-
-
-def is_outdated(tdate1, tdate2):
-    """keep compatibility with old cw versions"""
-    try:
-        tdate1 = pytz.UTC.localize(tdate1)
-    except ValueError:
-        pass
-    try:
-        tdate2 = pytz.UTC.localize(tdate2)
-    except ValueError:
-        pass
-    return tdate1 < tdate2
-
-
-class Translation(AnyEntity):
-    __regid__ = 'Translation'
-
-    @property
-    def last_edited(self):
-        return self.of_field[0].last_edited
-
-    def is_outdated(self):
-        return is_outdated(self.modification_date, self.last_edited)
-
-
-class _TranslatableEntityAdapter(EntityAdapter):
-    __regid__ = 'translatable_entity'
-
-    @property
-    def ref_code(self):
-        return self.entity.ref_lang[0].code
-
-    @cached
-    def i18nfield(self, attr):
-        ctx = {'e': self.entity.eid, 'f': attr}
-        rql = ('Any F,N,D '
-               'WHERE F is I18nField, F field_name N, F last_edited D, '
-               'F i18nfield_of E, E eid %(e)s, F field_name %(f)s')
-        rset = self._cw.execute(rql, ctx)
-        return rset.rowcount and rset.get_entity(0, 0) or None
-
-    @cached
-    def translation(self, attr, lang_code=None):
-        lang_code = lang_code or self._cw.lang
-        ctx = {'l': lang_code, 'f': attr, 'e': self.entity.eid}
-        rset = self._cw.execute('Any T WHERE T is Translation, '
-                                'T value V, T modification_date TMD, '
-                                'T lang L, L code %(l)s, T of_field F, '
-                                'F field_name %(f)s, F last_edited FMD, '
-                                'F i18nfield_of E, E eid %(e)s', ctx)
-        return rset.get_entity(0, 0) if rset.rowcount else None
-
-    @cached
-    def translated_attr(self, attr, lang_code):
-        ctx = {'l': lang_code, 'f': attr, 'e': self.entity.eid}
-        rset = self._cw.execute('Any V,TMD,FMD WHERE T is Translation, '
-                                'T value V, T modification_date TMD, '
-                                'T lang L, L code %(l)s, T of_field F, '
-                                'F field_name %(f)s, F last_edited FMD, '
-                                'F i18nfield_of E, E eid %(e)s', ctx)
-        if rset.rowcount:
-            value, tdate, fdate = rset[0]
-            if is_outdated(tdate, fdate):
-                self.warning('Could not find up-to-date %s translation for '
-                             'field %s of entity %s; returning version of %s',
-                             lang_code, attr, self.entity.eid, tdate)
-        else:
-            self.warning('Could not find %s translation for field %s '
-                         'of entity %s', lang_code, attr, self.entity.eid)
-            value = getattr(self.entity, attr)
-        return value
-
-    def translation_infos(self):
-        '''for each language defined in the system, returns infos about current
-        entity translations in a (lang, infos) list, where infos holds
-        Translation and I18nField instances if a translation exists, and
-        None otherwise.
-        '''
-        all_langs = self._cw.execute('Any L,C,N WHERE L is I18nLang, '
-                                     'L code C, L name N').entities(0)
-        rql = ('Any L,T,TD,F,FN,FD WHERE L is I18nLang, T lang L, '
-               'T modification_date TD, T of_field F, F last_edited FD, '
-               'F field_name FN, F i18nfield_of E, E eid %(e)s')
-        rset = self._cw.execute(rql, {'e': self.entity.eid})
-        infos = {}
-        for i, row in enumerate(rset.rows):
-            translation, field = rset.get_entity(i, 1), rset.get_entity(i, 3)
-            infos.setdefault(row[0], []).append((translation, field))
-        return [(lang, infos.get(lang.eid)) for lang in all_langs
-                if lang.eid != self.entity.ref_lang[0].eid]
-
-
-class TranslatableEntityMixin(object):
-    i18nfields = ()
-
-    def printable_value(self, attr, value=_marker, attrtype=None,
-                        format='text/html', displaytime=True):
-        uattr = unicode(attr)  # attr could be a rschema object
-        if uattr in self.i18nfields and value == _marker:
-            adapted = self.cw_adapt_to('translatable_entity')
-            tlang_code = target_lang_code(self._cw)
-            if adapted.ref_code != tlang_code:
-                value = adapted.translated_attr(uattr, tlang_code)
-        return super(TranslatableEntityMixin, self).printable_value(
-            attr, value, attrtype, format)
diff --git a/hooks.py b/hooks.py
deleted file mode 100644
--- a/hooks.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# -*- coding: utf-8 -*-
-# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
-# contact http://www.cubicweb.org/project/cubicweb-i18nfield
-# mailto:Florent Cayré <florent.cayre at gmail.com>
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 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 Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""cubicweb-i18nfield specific hooks and operations"""
-
-from cubicweb.predicates import adaptable, is_instance
-from cubicweb.server.hook import Hook, DataOperationMixIn, Operation
-
-from cubes.i18nfield.utils import set_lang, remove_lang
-
-
-# translatable entities related classes
-
-class TranslatableEntityAddHook(Hook):
-    __regid__ = 'i18nfield.translatable_entity_add'
-    __select__ = Hook.__select__ & adaptable('translatable_entity')
-    events = ('after_add_entity',)
-
-    def __call__(self):
-        langs = self._cw.transaction_data.get('i18nfield_lang', {})
-        for field in self.entity.i18nfields:
-            lang = langs.get(field, u'en')
-            ctx = {'e': self.entity.eid, 'c': lang, 'f': field}
-            rql = 'Any L WHERE L is I18nLang, L code %(c)s'
-            rset = self._cw.execute(rql, ctx)
-            if rset.rowcount == 0:
-                raise ValueError(self._cw._('language "%s" not found') % lang)
-            rset.get_entity(0, 0)
-            self._cw.create_entity('I18nField', field_name=field,
-                                   i18nfield_of=self.entity)
-
-
-class TranslatableEntityUpdateHook(Hook):
-    """
-    @test_translatable_entity_udpate
-    """
-    __regid__ = 'i18nfield.translatable_entity_udpate'
-    __select__ = Hook.__select__ & adaptable('translatable_entity')
-    events = ('before_update_entity',)
-    query = ('SET F last_edited NOW WHERE F is I18nField, '
-             'F field_name IN (%s), F i18nfield_of X, '
-             'X eid %%(x)s')
-
-    def __call__(self):
-        attrs = set(self.entity.i18nfields).intersection(self.entity.cw_edited)
-        if attrs:
-            field_names = ','.join("'%s'" % a for a in attrs)
-            query = self.query % field_names
-            self._cw.execute(query % {'x': self.entity.eid})
-
-
-# lang cache update related classes
-
-class LangCacheUpdateMixin(object):
-
-    def lang_dict(self, lang):
-        lang.complete()
-        lang_dict = lang.cw_attr_cache.copy()
-        del lang_dict['cwuri']
-        lang_dict['eid'] = lang.eid
-        return lang_dict
-
-    def update_cache(self, lang, op):
-        if op == 'remove':
-            remove_lang(lang.eid)
-        elif op == 'set':
-            set_lang(lang.eid, self.lang_dict(lang))
-
-
-class UpdateLangCacheOp(LangCacheUpdateMixin, DataOperationMixIn, Operation):
-
-    def precommit_event(self):
-        for (lang, op) in self.get_data():
-            self.update_cache(lang, op)
-
-
-class AddUpdateI18nLangHook(Hook):
-    __regid__ = 'i18nfield.add_update_lang'
-    __select__ = Hook.__select__ & is_instance('I18nLang')
-    events = ('after_update_entity', 'after_add_entity')
-
-    def __call__(self):
-        UpdateLangCacheOp.get_instance(self._cw).add_data((self.entity, 'set'))
-
-
-class RemoveI18nLangHook(Hook):
-    __regid__ = 'i18nfield.remove_lang'
-    __select__ = Hook.__select__ & is_instance('I18nLang')
-    events = ('after_delete_entity',)
-
-    def __call__(self):
-        UpdateLangCacheOp.get_instance(self._cw).add_data(
-            (self.entity, 'remove'))
-
-
-class ServerStartupHook(LangCacheUpdateMixin, Hook):
-    """fill-in language caches"""
-    __regid__ = 'i18nfield.server-startup'
-    events = ('server_startup',)
-
-    def __call__(self):
-        with self.repo.internal_cnx() as cnx:
-            try:
-                UpdateLangCacheOp.get_instance(cnx)
-                for lang in cnx.execute('Any L,C,N WHERE L is I18nLang, '
-                                        'L code C, L name N').entities(0):
-                    self.update_cache(lang, 'set')
-            except Exception:
-                pass
diff --git a/i18n/de.po b/i18n/de.po
deleted file mode 100644
--- a/i18n/de.po
+++ /dev/null
@@ -1,187 +0,0 @@
-msgid ""
-msgstr ""
-"Project-Id-Version: cubicweb 3.14.0\n"
-"PO-Revision-Date: 2008-03-28 18:14+0100\n"
-"Last-Translator: Logilab Team <contact at logilab.fr>\n"
-"Language-Team: fr <contact at logilab.fr>\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: cubicweb-devtools\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#, python-format
-msgid "%s translation"
-msgstr ""
-
-# schema pot file, generated on 2011-09-19 10:17:10
-#
-# singular and plural forms for each entity type
-msgid "I18nField"
-msgstr ""
-
-msgid "I18nField_plural"
-msgstr ""
-
-msgid "I18nLang"
-msgstr ""
-
-msgid "I18nLang_plural"
-msgstr ""
-
-msgid "ISO 639.2 Code"
-msgstr ""
-
-msgid "ISO description"
-msgstr ""
-
-msgid "New I18nField"
-msgstr ""
-
-msgid "New I18nLang"
-msgstr ""
-
-msgid "New Translation"
-msgstr ""
-
-msgid "Original version"
-msgstr ""
-
-msgid ""
-"Partly taken from i18ncontent ; note this makes current cube and i18ncontent "
-"incompatible. registered language for an application. See http://www.loc.gov/"
-"standards/iso639-2 for available codes."
-msgstr ""
-
-msgid "This I18nField"
-msgstr ""
-
-msgid "This I18nLang"
-msgstr ""
-
-msgid "This Translation"
-msgstr ""
-
-msgid "Translation"
-msgstr ""
-
-msgctxt "inlined:I18nField.of_field.object"
-msgid "Translation"
-msgstr ""
-
-msgid "Translation_plural"
-msgstr ""
-
-msgid "add Translation of_field I18nField object"
-msgstr ""
-
-msgctxt "inlined:I18nField.of_field.object"
-msgid "add a Translation"
-msgstr ""
-
-# subject and object forms for each relation type
-# (no object form for final or symmetric relation types)
-msgid "code"
-msgstr ""
-
-msgctxt "I18nLang"
-msgid "code"
-msgstr ""
-
-msgid "creating Translation (Translation of_field I18nField %(linkto)s)"
-msgstr ""
-
-msgid "creation"
-msgstr ""
-
-msgid "edit"
-msgstr ""
-
-msgid "edition"
-msgstr ""
-
-msgid "field_name"
-msgstr ""
-
-msgctxt "I18nField"
-msgid "field_name"
-msgstr ""
-
-msgid "i18nfield_of"
-msgstr ""
-
-msgid "i18nfield_of_object"
-msgstr ""
-
-msgid "incomplete"
-msgstr ""
-
-msgid "lang"
-msgstr ""
-
-msgctxt "Translation"
-msgid "lang"
-msgstr ""
-
-msgid "lang_object"
-msgstr ""
-
-msgctxt "I18nLang"
-msgid "lang_object"
-msgstr ""
-
-#, python-format
-msgid "language \"%s\" not found"
-msgstr ""
-
-msgid "last_edited"
-msgstr ""
-
-msgctxt "I18nField"
-msgid "last_edited"
-msgstr ""
-
-msgid "manage translations"
-msgstr ""
-
-msgctxt "I18nLang"
-msgid "name"
-msgstr ""
-
-msgid "of_field"
-msgstr ""
-
-msgctxt "Translation"
-msgid "of_field"
-msgstr ""
-
-msgid "of_field_object"
-msgstr ""
-
-msgctxt "I18nField"
-msgid "of_field_object"
-msgstr ""
-
-msgid "outdated"
-msgstr ""
-
-msgid "ref_lang"
-msgstr ""
-
-msgid "ref_lang_object"
-msgstr ""
-
-msgid "translation"
-msgstr ""
-
-msgid "translations"
-msgstr ""
-
-#, python-format
-msgid "unknown language code \"%s\""
-msgstr ""
-
-msgctxt "Translation"
-msgid "value"
-msgstr ""
diff --git a/i18n/en.po b/i18n/en.po
deleted file mode 100644
--- a/i18n/en.po
+++ /dev/null
@@ -1,182 +0,0 @@
-msgid ""
-msgstr ""
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#, python-format
-msgid "%s translation"
-msgstr ""
-
-# schema pot file, generated on 2011-09-19 10:17:10
-#
-# singular and plural forms for each entity type
-msgid "I18nField"
-msgstr "I18nField"
-
-msgid "I18nField_plural"
-msgstr "I18nFields"
-
-msgid "I18nLang"
-msgstr "I18nLang"
-
-msgid "I18nLang_plural"
-msgstr "I18nLangs"
-
-msgid "ISO 639.2 Code"
-msgstr ""
-
-msgid "ISO description"
-msgstr ""
-
-msgid "New I18nField"
-msgstr ""
-
-msgid "New I18nLang"
-msgstr ""
-
-msgid "New Translation"
-msgstr ""
-
-msgid "Original version"
-msgstr ""
-
-msgid ""
-"Partly taken from i18ncontent ; note this makes current cube and i18ncontent "
-"incompatible. registered language for an application. See http://www.loc.gov/"
-"standards/iso639-2 for available codes."
-msgstr ""
-
-msgid "This I18nField"
-msgstr ""
-
-msgid "This I18nLang"
-msgstr ""
-
-msgid "This Translation"
-msgstr ""
-
-msgid "Translation"
-msgstr ""
-
-msgctxt "inlined:I18nField.of_field.object"
-msgid "Translation"
-msgstr ""
-
-msgid "Translation_plural"
-msgstr ""
-
-msgid "add Translation of_field I18nField object"
-msgstr ""
-
-msgctxt "inlined:I18nField.of_field.object"
-msgid "add a Translation"
-msgstr ""
-
-# subject and object forms for each relation type
-# (no object form for final or symmetric relation types)
-msgid "code"
-msgstr ""
-
-msgctxt "I18nLang"
-msgid "code"
-msgstr ""
-
-msgid "creating Translation (Translation of_field I18nField %(linkto)s)"
-msgstr ""
-
-msgid "creation"
-msgstr ""
-
-msgid "edit"
-msgstr ""
-
-msgid "edition"
-msgstr ""
-
-msgid "field_name"
-msgstr "field name"
-
-msgctxt "I18nField"
-msgid "field_name"
-msgstr ""
-
-msgid "i18nfield_of"
-msgstr "field of"
-
-msgid "i18nfield_of_object"
-msgstr "target fields"
-
-msgid "incomplete"
-msgstr ""
-
-msgid "lang"
-msgstr ""
-
-msgctxt "Translation"
-msgid "lang"
-msgstr ""
-
-msgid "lang_object"
-msgstr ""
-
-msgctxt "I18nLang"
-msgid "lang_object"
-msgstr ""
-
-#, python-format
-msgid "language \"%s\" not found"
-msgstr ""
-
-msgid "last_edited"
-msgstr "last edited"
-
-msgctxt "I18nField"
-msgid "last_edited"
-msgstr ""
-
-msgid "manage translations"
-msgstr ""
-
-msgctxt "I18nLang"
-msgid "name"
-msgstr ""
-
-msgid "of_field"
-msgstr ""
-
-msgctxt "Translation"
-msgid "of_field"
-msgstr ""
-
-msgid "of_field_object"
-msgstr "field translation"
-
-msgctxt "I18nField"
-msgid "of_field_object"
-msgstr ""
-
-msgid "outdated"
-msgstr ""
-
-msgid "ref_lang"
-msgstr "original language"
-
-msgid "ref_lang_object"
-msgstr ""
-
-msgid "translation"
-msgstr ""
-
-msgid "translations"
-msgstr ""
-
-#, python-format
-msgid "unknown language code \"%s\""
-msgstr ""
-
-msgctxt "Translation"
-msgid "value"
-msgstr ""
diff --git a/i18n/es.po b/i18n/es.po
deleted file mode 100644
--- a/i18n/es.po
+++ /dev/null
@@ -1,182 +0,0 @@
-msgid ""
-msgstr ""
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#, python-format
-msgid "%s translation"
-msgstr ""
-
-# schema pot file, generated on 2011-09-19 10:17:10
-#
-# singular and plural forms for each entity type
-msgid "I18nField"
-msgstr ""
-
-msgid "I18nField_plural"
-msgstr ""
-
-msgid "I18nLang"
-msgstr ""
-
-msgid "I18nLang_plural"
-msgstr ""
-
-msgid "ISO 639.2 Code"
-msgstr ""
-
-msgid "ISO description"
-msgstr ""
-
-msgid "New I18nField"
-msgstr ""
-
-msgid "New I18nLang"
-msgstr ""
-
-msgid "New Translation"
-msgstr ""
-
-msgid "Original version"
-msgstr ""
-
-msgid ""
-"Partly taken from i18ncontent ; note this makes current cube and i18ncontent "
-"incompatible. registered language for an application. See http://www.loc.gov/"
-"standards/iso639-2 for available codes."
-msgstr ""
-
-msgid "This I18nField"
-msgstr ""
-
-msgid "This I18nLang"
-msgstr ""
-
-msgid "This Translation"
-msgstr ""
-
-msgid "Translation"
-msgstr ""
-
-msgctxt "inlined:I18nField.of_field.object"
-msgid "Translation"
-msgstr ""
-
-msgid "Translation_plural"
-msgstr ""
-
-msgid "add Translation of_field I18nField object"
-msgstr ""
-
-msgctxt "inlined:I18nField.of_field.object"
-msgid "add a Translation"
-msgstr ""
-
-# subject and object forms for each relation type
-# (no object form for final or symmetric relation types)
-msgid "code"
-msgstr ""
-
-msgctxt "I18nLang"
-msgid "code"
-msgstr ""
-
-msgid "creating Translation (Translation of_field I18nField %(linkto)s)"
-msgstr ""
-
-msgid "creation"
-msgstr ""
-
-msgid "edit"
-msgstr ""
-
-msgid "edition"
-msgstr ""
-
-msgid "field_name"
-msgstr ""
-
-msgctxt "I18nField"
-msgid "field_name"
-msgstr ""
-
-msgid "i18nfield_of"
-msgstr ""
-
-msgid "i18nfield_of_object"
-msgstr ""
-
-msgid "incomplete"
-msgstr ""
-
-msgid "lang"
-msgstr ""
-
-msgctxt "Translation"
-msgid "lang"
-msgstr ""
-
-msgid "lang_object"
-msgstr ""
-
-msgctxt "I18nLang"
-msgid "lang_object"
-msgstr ""
-
-#, python-format
-msgid "language \"%s\" not found"
-msgstr ""
-
-msgid "last_edited"
-msgstr ""
-
-msgctxt "I18nField"
-msgid "last_edited"
-msgstr ""
-
-msgid "manage translations"
-msgstr ""
-
-msgctxt "I18nLang"
-msgid "name"
-msgstr ""
-
-msgid "of_field"
-msgstr ""
-
-msgctxt "Translation"
-msgid "of_field"
-msgstr ""
-
-msgid "of_field_object"
-msgstr ""
-
-msgctxt "I18nField"
-msgid "of_field_object"
-msgstr ""
-
-msgid "outdated"
-msgstr ""
-
-msgid "ref_lang"
-msgstr ""
-
-msgid "ref_lang_object"
-msgstr ""
-
-msgid "translation"
-msgstr ""
-
-msgid "translations"
-msgstr ""
-
-#, python-format
-msgid "unknown language code \"%s\""
-msgstr ""
-
-msgctxt "Translation"
-msgid "value"
-msgstr ""
diff --git a/i18n/fr.po b/i18n/fr.po
deleted file mode 100644
--- a/i18n/fr.po
+++ /dev/null
@@ -1,206 +0,0 @@
-msgid ""
-msgstr ""
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: pygettext.py 1.5\n"
-"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-
-#, python-format
-msgid "%s translation"
-msgstr ""
-
-# schema pot file, generated on 2011-09-19 10:17:10
-#
-# singular and plural forms for each entity type
-msgid "I18nField"
-msgstr "Champ à traduire"
-
-msgid "I18nField_plural"
-msgstr "Champs à traduire"
-
-msgid "I18nLang"
-msgstr ""
-
-msgid "I18nLang_plural"
-msgstr ""
-
-msgid "ISO 639.2 Code"
-msgstr "Code ISO 639.2"
-
-msgid "ISO description"
-msgstr "description ISO"
-
-msgid "New I18nField"
-msgstr "Nouveau champ à traduire"
-
-msgid "New I18nLang"
-msgstr ""
-
-msgid "New Translation"
-msgstr "Nouvelle traduction"
-
-msgid "Original version"
-msgstr "Version originale"
-
-msgid ""
-"Partly taken from i18ncontent ; note this makes current cube and i18ncontent "
-"incompatible. registered language for an application. See http://www.loc.gov/"
-"standards/iso639-2 for available codes."
-msgstr ""
-
-msgid "This I18nField"
-msgstr "Ce champ à traduire"
-
-msgid "This I18nLang"
-msgstr ""
-
-msgid "This Translation"
-msgstr "Cette traduction"
-
-msgid "Translation"
-msgstr "Traduction"
-
-msgctxt "inlined:I18nField.of_field.object"
-msgid "Translation"
-msgstr "Traduction"
-
-msgid "Translation_plural"
-msgstr "Traductions"
-
-msgid "add Translation of_field I18nField object"
-msgstr "un champ à traduire de cette traduction"
-
-msgctxt "inlined:I18nField.of_field.object"
-msgid "add a Translation"
-msgstr "une traduction"
-
-# subject and object forms for each relation type
-# (no object form for final or symmetric relation types)
-msgid "code"
-msgstr "code"
-
-msgctxt "I18nLang"
-msgid "code"
-msgstr ""
-
-msgid "creating Translation (Translation of_field I18nField %(linkto)s)"
-msgstr "création d'une traduction (associée au champ à traduire %(linkto)s)"
-
-msgid "creation"
-msgstr "création"
-
-msgid "edit"
-msgstr "éditer"
-
-msgid "edition"
-msgstr "édition"
-
-msgid "field_name"
-msgstr "nom du champ"
-
-msgctxt "I18nField"
-msgid "field_name"
-msgstr "nom du champ"
-
-msgid "i18nfield_of"
-msgstr "champ de"
-
-msgid "i18nfield_of_object"
-msgstr "champ cible"
-
-msgid "incomplete"
-msgstr "incomplète"
-
-msgid "lang"
-msgstr "langue"
-
-msgctxt "Translation"
-msgid "lang"
-msgstr "langue"
-
-msgid "lang_object"
-msgstr "langue cible"
-
-msgctxt "I18nLang"
-msgid "lang_object"
-msgstr ""
-
-#, python-format
-msgid "language \"%s\" not found"
-msgstr "langue \"%s\" introuvable"
-
-msgid "last_edited"
-msgstr "dernière mise à jour"
-
-msgctxt "I18nField"
-msgid "last_edited"
-msgstr "dernière mise à jour"
-
-msgid "manage translations"
-msgstr "gérer les traductions"
-
-msgctxt "I18nLang"
-msgid "name"
-msgstr ""
-
-msgid "of_field"
-msgstr "champ associé"
-
-msgctxt "Translation"
-msgid "of_field"
-msgstr "champ associé"
-
-msgid "of_field_object"
-msgstr "traduction"
-
-msgctxt "I18nField"
-msgid "of_field_object"
-msgstr "champ cible"
-
-msgid "outdated"
-msgstr "obsolète"
-
-msgid "ref_lang"
-msgstr "langue de référence"
-
-msgid "ref_lang_object"
-msgstr "Langue cible"
-
-msgid "translation"
-msgstr "traduction"
-
-msgid "translations"
-msgstr "traductions"
-
-#, python-format
-msgid "unknown language code \"%s\""
-msgstr "code de langue inconnu \"%s\""
-
-msgctxt "Translation"
-msgid "value"
-msgstr "valeur"
-
-#~ msgid "Language"
-#~ msgstr "Langue"
-
-#~ msgid "Language_plural"
-#~ msgstr "Langues"
-
-#~ msgid "New Language"
-#~ msgstr "Nouvelle langue"
-
-#~ msgid "This Language"
-#~ msgstr "Cette langue"
-
-#~ msgctxt "Language"
-#~ msgid "code"
-#~ msgstr "code"
-
-#~ msgctxt "Language"
-#~ msgid "lang_object"
-#~ msgstr "langue cible"
-
-#~ msgctxt "Language"
-#~ msgid "name"
-#~ msgstr "nom"
diff --git a/migration/0.1.1_Any.py b/migration/0.1.1_Any.py
deleted file mode 100644
--- a/migration/0.1.1_Any.py
+++ /dev/null
@@ -1,2 +0,0 @@
-
-change_attribute_type('I18nField', 'last_edited', 'TZDatetime', ask_confirm=False)
diff --git a/migration/postcreate.py b/migration/postcreate.py
deleted file mode 100644
--- a/migration/postcreate.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# -*- coding: utf-8 -*-
-# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
-# contact http://www.cubicweb.org/project/cubicweb-i18nfield
-# mailto:Florent Cayré <florent.cayre at gmail.com>
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 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 Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""cubicweb-i18nfield postcreate script, executed at instance creation time or
-when the cube is added to an existing instance.
-
-You could setup site properties or a workflow here for example.
-"""
diff --git a/schema.py b/schema.py
deleted file mode 100644
--- a/schema.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
-# contact http://www.cubicweb.org/project/cubicweb-i18nfield
-# mailto:Florent Cayré <florent.cayre at gmail.com>
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 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 Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""cubicweb-i18nfield schema"""
-
-from six import text_type as unicode
-from yams.buildobjs import (EntityType, RelationType,
-                            String, TZDatetime, SubjectRelation)
-
-from cubicweb.schema import RQLConstraint
-
-_ = unicode
-
-
-class I18nLang(EntityType):
-    """Partly taken from i18ncontent ; note this makes current cube
-    and i18ncontent incompatible.
-
-    registered language for an application.
-
-    See http://www.loc.gov/standards/iso639-2 for available codes.
-    """
-    code = String(required=True, maxsize=2, unique=True,
-                  description=_('ISO 639.2 Code'))
-    name = String(required=True, internationalizable=True, maxsize=37,
-                  description=_('ISO description'))
-
-
-class I18nField(EntityType):
-    __unique_together__ = [('field_name', 'i18nfield_of')]
-    __permissions__ = {'read': ('managers', 'users', 'guests',),
-                       'add': (),
-                       'update': (),
-                       'delete': ()}
-    field_name = String(required=True, maxsize=64)
-    last_edited = TZDatetime(required=True, default='NOW')
-
-
-other_lang_cstr = RQLConstraint(
-    'S of_field F, F i18nfield_of E, E ref_lang L, '
-    'NOT O identity L')
-
-
-class Translation(EntityType):
-    __unique_together__ = [('lang', 'of_field')]
-    value = String()
-    lang = SubjectRelation('I18nLang', cardinality='1*', inlined=True,
-                           constraints=[other_lang_cstr])
-    of_field = SubjectRelation('I18nField', cardinality='1*', inlined=True,
-                               composite='object')
-
-
-STRICTLY_AUTO_REL_PERMS = {'read': ('managers', 'users', 'guests',),
-                           'add': (),
-                           'delete': ()}
-
-
-class ref_lang(RelationType):
-    object = 'I18nLang'
-    cardinality = '1*'
-    inlined = True
-
-
-class i18nfield_of(RelationType):
-    __permissions__ = STRICTLY_AUTO_REL_PERMS
-    subject = 'I18nField'
-    cardinality = '1*'
-    inlined = True
-    composite = 'object'
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # pylint: disable=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611
 #
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2020 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact at logilab.fr
 #
 # This file is part of CubicWeb tag cube.
@@ -18,190 +18,65 @@
 #
 # You should have received a copy of the GNU Lesser General Public License along
 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-import __pkginfo__
-"""Generic Setup script, takes package info from __pkginfo__.py file
-"""
-__docformat__ = "restructuredtext en"
-
-import os
-import sys
-import shutil
-from os.path import isdir, exists, join, walk
-
-try:
-    if os.environ.get('NO_SETUPTOOLS'):
-        raise ImportError()  # do as there is no setuptools
-    from setuptools import setup
-    from setuptools.command import install_lib
-    USE_SETUPTOOLS = True
-except ImportError:
-    from distutils.core import setup
-    from distutils.command import install_lib
-    USE_SETUPTOOLS = False
-from distutils.command import install_data
-
-# import required features
-from __pkginfo__ import modname, version, license, description, web, \
-    author, author_email
-
-if exists('README'):
-    long_description = file('README').read()
-else:
-    long_description = ''
+"""cubicweb_i18nfield setup module using data from
+cubicweb_i18nfield/__pkginfo__.py file"""
 
-# import optional features
-if USE_SETUPTOOLS:
-    requires = {}
-    for entry in ("__depends__",):  # "__recommends__"):
-        requires.update(getattr(__pkginfo__, entry, {}))
-    install_requires = [("%s %s" % (d, v and v or "")).strip()
-                       for d, v in requires.iteritems()]
-else:
-    install_requires = []
-
-distname = getattr(__pkginfo__, 'distname', modname)
-scripts = getattr(__pkginfo__, 'scripts', ())
-include_dirs = getattr(__pkginfo__, 'include_dirs', ())
-data_files = getattr(__pkginfo__, 'data_files', None)
-ext_modules = getattr(__pkginfo__, 'ext_modules', None)
-dependency_links = getattr(__pkginfo__, 'dependency_links', ())
-
-BASE_BLACKLIST = ('CVS', '.svn', '.hg', 'debian', 'dist', 'build')
-IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc', '~')
-
-
-def ensure_scripts(linux_scripts):
-    """
-    Creates the proper script names required for each platform
-    (taken from 4Suite)
-    """
-    from distutils import util
-    if util.get_platform()[:3] == 'win':
-        scripts_ = [script + '.bat' for script in linux_scripts]
-    else:
-        scripts_ = linux_scripts
-    return scripts_
+from os.path import join, dirname
+from setuptools import find_packages, setup
 
 
-def export(from_dir, to_dir,
-           blacklist=BASE_BLACKLIST,
-           ignore_ext=IGNORED_EXTENSIONS,
-           verbose=True):
-    """make a mirror of from_dir in to_dir, omitting directories and files
-    listed in the black list
-    """
-    def make_mirror(arg, directory, fnames):
-        """walk handler"""
-        for norecurs in blacklist:
-            try:
-                fnames.remove(norecurs)
-            except ValueError:
-                pass
-        for filename in fnames:
-            # don't include binary files
-            if filename[-4:] in ignore_ext:
-                continue
-            if filename[-1] == '~':
-                continue
-            src = join(directory, filename)
-            dest = to_dir + src[len(from_dir):]
-            if verbose:
-                print >> sys.stderr, src, '->', dest
-            if os.path.isdir(src):
-                if not exists(dest):
-                    os.mkdir(dest)
-            else:
-                if exists(dest):
-                    os.remove(dest)
-                shutil.copy2(src, dest)
-    try:
-        os.mkdir(to_dir)
-    except OSError, ex:
-        # file exists ?
-        import errno
-        if ex.errno != errno.EEXIST:
-            raise
-    walk(from_dir, make_mirror, None)
+here = dirname(__file__)
+
+# load metadata from the __pkginfo__.py file so there is no risk of conflict
+# see https://packaging.python.org/en/latest/single_source_version.html
+
+pkginfo = join(here, 'cubicweb_i18nfield', '__pkginfo__.py')
+__pkginfo__ = {}
+with open(pkginfo) as f:
+    exec(f.read(), __pkginfo__)
 
-
-class MyInstallLib(install_lib.install_lib):
-    """extend install_lib command to handle  package __init__.py and
-    include_dirs variable if necessary
-    """
+# get required metadatas
+distname = __pkginfo__['distname']
+version = __pkginfo__['version']
+license = __pkginfo__['license']
+description = __pkginfo__['description']
+web = __pkginfo__['web']
+author = __pkginfo__['author']
+author_email = __pkginfo__['author_email']
+classifiers = __pkginfo__['classifiers']
 
-    def run(self):
-        """overridden from install_lib class"""
-        install_lib.install_lib.run(self)
-        # manually install included directories if any
-        if include_dirs:
-            base = modname
-            for directory in include_dirs:
-                dest = join(self.install_dir, base, directory)
-                export(directory, dest, verbose=False)
+with open(join(here, 'README')) as f:
+    long_description = f.read()
+
+# get optional metadatas
+data_files = __pkginfo__.get('data_files', None)
+dependency_links = __pkginfo__.get('dependency_links', ())
+
+requires = {}
+for entry in ("__depends__",):  # "__recommends__"):
+    requires.update(__pkginfo__.get(entry, {}))
+
+install_requires = ["{0} {1}".format(d, v and v or "").strip()
+                    for d, v in requires.items()]
 
 
-# re-enable copying data files in sys.prefix
-old_install_data = install_data.install_data
-if USE_SETUPTOOLS:
-    # overwrite InstallData to use sys.prefix instead of the egg directory
-    class MyInstallData(old_install_data):
-        """A class that manages data files installation"""
-
-        def run(self):
-            _old_install_dir = self.install_dir
-            if self.install_dir.endswith('egg'):
-                self.install_dir = sys.prefix
-            old_install_data.run(self)
-            self.install_dir = _old_install_dir
-    try:
-        import setuptools.command.easy_install  # only if easy_install avaible
-        # monkey patch: Crack SandboxViolation verification
-        from setuptools.sandbox import DirectorySandbox as DS
-        old_ok = DS._ok
-
-        def _ok(self, path):
-            """Return True if ``path`` can be written during installation."""
-            out = old_ok(self, path)  # here for side effect from setuptools
-            realpath = os.path.normcase(os.path.realpath(path))
-            allowed_path = os.path.normcase(sys.prefix)
-            if realpath.startswith(allowed_path):
-                out = True
-            return out
-        DS._ok = _ok
-    except ImportError:
-        pass
-
-
-def install(**kwargs):
-    """setup entry point"""
-    if USE_SETUPTOOLS:
-        if '--force-manifest' in sys.argv:
-            sys.argv.remove('--force-manifest')
-    # install-layout option was introduced in 2.5.3-1~exp1
-    elif sys.version_info < (2, 5, 4) and '--install-layout=deb' in sys.argv:
-        sys.argv.remove('--install-layout=deb')
-    cmdclass = {'install_lib': MyInstallLib}
-    if USE_SETUPTOOLS:
-        kwargs['install_requires'] = install_requires
-        kwargs['dependency_links'] = dependency_links
-        kwargs['zip_safe'] = False
-        cmdclass['install_data'] = MyInstallData
-
-    return setup(name=distname,
-                 version=version,
-                 license=license,
-                 description=description,
-                 long_description=long_description,
-                 author=author,
-                 author_email=author_email,
-                 url=web,
-                 scripts=ensure_scripts(scripts),
-                 data_files=data_files,
-                 ext_modules=ext_modules,
-                 cmdclass=cmdclass,
-                 **kwargs
-                 )
-
-
-if __name__ == '__main__':
-    install()
+setup(
+    name=distname,
+    version=version,
+    license=license,
+    description=description,
+    long_description=long_description,
+    author=author,
+    author_email=author_email,
+    url=web,
+    classifiers=classifiers,
+    packages=find_packages(exclude=['test']),
+    install_requires=install_requires,
+    include_package_data=True,
+    entry_points={
+        'cubicweb.cubes': [
+            'i18nfield=cubicweb_i18nfield',
+        ],
+    },
+    zip_safe=False,
+)
diff --git a/test/test_i18nfield.py b/test/test_i18nfield.py
--- a/test/test_i18nfield.py
+++ b/test/test_i18nfield.py
@@ -22,7 +22,7 @@ from cubicweb import ValidationError, Un
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web.views.autoform import InlinedFormField
 
-from cubes.i18nfield.utils import LANGS_BY_CODE, LANGS_BY_EID
+from cubicweb_i18nfield.utils import LANGS_BY_CODE, LANGS_BY_EID
 
 
 class I18nFieldTC(CubicWebTC):
diff --git a/uiprops.py b/uiprops.py
deleted file mode 100644
--- a/uiprops.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# flake8: noqa
-
-STYLESHEETS.append(data('cubes.i18nfield.css'))
diff --git a/utils.py b/utils.py
deleted file mode 100644
--- a/utils.py
+++ /dev/null
@@ -1,18 +0,0 @@
-LANGS_BY_CODE = {}  # see hooks.py
-LANGS_BY_EID = {}  # see hooks.py
-
-
-def set_lang(lang_eid, lang_dict):
-    try:
-        old_code = LANGS_BY_EID[lang_eid]['code']
-        if lang_dict['code'] != old_code:
-            del LANGS_BY_CODE[old_code]
-    except KeyError:
-        pass  # creation case
-    LANGS_BY_CODE[lang_dict['code']] = lang_dict
-    LANGS_BY_EID[lang_eid] = lang_dict
-
-
-def remove_lang(lang_eid):
-    del LANGS_BY_CODE[LANGS_BY_EID[lang_eid]['code']]
-    del LANGS_BY_EID[lang_eid]
diff --git a/views.py b/views.py
deleted file mode 100644
--- a/views.py
+++ /dev/null
@@ -1,378 +0,0 @@
-# -*- coding: utf-8 -*-
-# copyright 2011 Florent Cayré (Villejuif, FRANCE), all rights reserved.
-# contact http://www.cubicweb.org/project/cubicweb-i18nfield
-# mailto:Florent Cayré <florent.cayre at gmail.com>
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 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 Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""cubicweb-i18nfield views/forms/actions/components for web ui"""
-
-from six import text_type as unicode
-from logilab.mtconverter import xml_escape
-from logilab.common.decorators import iclassmethod
-
-from cubicweb import typed_eid, tags
-from cubicweb.predicates import (adaptable, has_permission, is_instance,
-                                 match_form_params, one_line_rset,
-                                 match_kwargs, specified_etype_implements)
-from cubicweb.web.views import uicfg
-from cubicweb.web.action import Action
-from cubicweb.web.form import FieldNotFound
-from cubicweb.web.formfields import guess_field
-from cubicweb.web.formwidgets import HiddenInput
-from cubicweb.web.views.editforms import EditionFormView
-from cubicweb.web.views.autoform import (AutomaticEntityForm as AutoForm,
-                                         InlineEntityEditionFormView,
-                                         InlineEntityCreationFormView)
-from cubicweb.web.views.formrenderers import EntityInlinedFormRenderer
-
-
-from cubicweb.web.views import tableview as cw_tableview
-from cubicweb.uilib import sgml_attributes
-from cubicweb.utils import UStringIO
-
-from cubes.i18nfield.utils import LANGS_BY_EID
-
-from cubicweb.view import StartupView
-from cubicweb.web.httpcache import NoHTTPCacheManager
-from cubicweb.web.views.urlrewrite import SimpleReqRewriter, rgx
-
-_ = unicode  # make pylint happier
-
-_AFF = uicfg.autoform_field
-_AFF_KWARGS = uicfg.autoform_field_kwargs
-_AFS = uicfg.autoform_section
-_AFFK = uicfg.autoform_field_kwargs
-
-
-def lang_from_code(req, code=None):
-    code = code or req.form.get('lang_code', req.lang)
-    if code is None:
-        return None
-    rql = 'Any L,N WHERE L is I18nLang, L name N, L code %(l)s'
-    try:
-        return req.execute(rql, {'l': code}).get_entity(0, 0)
-    except IndexError:
-        raise ValueError(req._('unknown language code "%s"') % code)
-
-
-class TranslateEntityAction(Action):
-    __regid__ = 'translate_entity'
-    __select__ = (adaptable('translatable_entity') & has_permission('update')
-                  & one_line_rset())
-
-    submenu = _('translations')
-    category = 'mainactions'
-
-    def actual_actions(self):
-        entity = self.cw_rset.get_entity(0, 0)
-        _infos = entity.cw_adapt_to('translatable_entity').translation_infos()
-        for lang, infos in _infos:
-            url = entity.absolute_url(vid='translate_entity',
-                                      lang_code=lang.code)
-            if infos is None:
-                action = self._cw._('creation')
-            else:
-                # add translation state in action text (outdated, incomplete)
-                action = []
-                if len([1 for t, _f in infos if t.is_outdated()]):
-                    action.append(self._cw._('outdated'))
-                if len(infos) < len(entity.i18nfields):
-                    action.append(self._cw._('incomplete'))
-                action = u', '.join(action) or self._cw._('edition')
-            title = u'%s (%s)' % (self._cw._(lang.name), action)
-            yield self.build_action(title, url)
-        yield self.build_action(self._cw._('manage translations'),
-                                self._cw.build_url('i18n'))
-
-    def fill_menu(self, box, menu):
-        menu.append_anyway = True
-        super(TranslateEntityAction, self).fill_menu(box, menu)
-
-
-class ManageTranslationsView(StartupView):
-    __regid__ = 'i18nfield.manage_translations'
-    http_cache_manager = NoHTTPCacheManager
-
-    def select_rql(self):
-        return ('Any X,GROUP_CONCAT(C) GROUPBY X ORDERBY MD DESC '
-                'WHERE X modification_date MD, NOT X ref_lang L, L name C, ')
-
-    def incomplete_rql(self):
-        return (self.select_rql()
-                + 'EXISTS(F i18nfield_of X, NOT EXISTS(T of_field F, T lang L))')
-
-    def outdated_rql(self):
-        return (self.select_rql()
-                + 'EXISTS(F i18nfield_of X, F last_edited FD, T of_field F,'
-                ' T modification_date < FD, T lang L)')
-
-    def lang_html(self, entity, lang_eids):
-        html = []
-        for eid in lang_eids.split(','):
-            infos = LANGS_BY_EID[typed_eid(eid)]
-            href = entity.absolute_url(vid='translate_entity',
-                                       lang_code=infos['code'])
-            html.append(u'%s (%s)' % (xml_escape(infos['name']),
-                                      tags.a(self._cw._('edit'), href=href)))
-        return ', '.join(html)
-
-    def call(self, *args, **kwargs):
-        sections = (('incomplete translations', self.incomplete_rql),
-                    ('outdated translations', self.outdated_rql))
-        for title, rql_method in sections:
-            rset = self._cw.execute(rql_method())
-            if rset.rowcount:
-                self.w(u'<div class="section translations">'
-                       u'<h2>%s</h2>' % self._cw._(title))
-                self.wview('table.translations', rset)
-                self.w(u'</div>')
-
-
-class ManageTranslationTableView(cw_tableview.RsetTableView):
-    __regid__ = 'table.translations'
-    layout_id = 'table.table-layout'
-    headers = (_('entities'), _('languages'))
-    layout_args = {'display_filter': 'top', 'hide_filter': False}
-
-
-class TableLayout(cw_tableview.TableLayout):
-    __regid__ = 'table.table-layout'
-    cssclass = 'table table-default listing'
-    page_size = 20
-
-    def render_table(self, w, actions, paginate):
-        view = self.view
-        divid = view.domid
-        if divid is not None:
-            w(u'<div id="%s">' % divid)
-        else:
-            assert not (actions or paginate)
-        nav_html = UStringIO()
-        if paginate:
-            #  customization :  add page_size=self.page_size
-            view.paginate(w=nav_html.write, page_size=self.page_size,
-                          show_all_option=self.show_all_option)
-        w(nav_html.getvalue())
-        if actions and self.display_actions == 'top':
-            self.render_actions(w, actions)
-        colrenderers = view.build_column_renderers()
-        attrs = self.table_attributes()
-        w(u'<table %s>' % sgml_attributes(attrs))
-        if self.view.has_headers:
-            self.render_table_headers(w, colrenderers)
-        self.render_table_body(w, colrenderers)
-        w(u'</table>')
-        if actions and self.display_actions == 'bottom':
-            self.render_actions(w, actions)
-        if divid is not None:
-            w(u'</div>')
-
-
-class TranslateEntityView(EditionFormView):
-    __regid__ = 'translate_entity'
-    __select__ = (EditionFormView.__select__ & adaptable('translatable_entity')
-                  & match_form_params('lang_code'))
-    form_id = 'translate_entity'
-
-    @property
-    def title(self):
-        lang = lang_from_code(self._cw)
-        return self._cw._('%s translation') % self._cw._(lang.name)
-
-
-class TranslateEntityForm(AutoForm):
-    __regid__ = 'translate_entity'
-    __select__ = AutoForm.__select__ & adaptable('translatable_entity')
-
-    def editable_attributes(self):
-        return []
-
-    def inlined_relations(self):
-        rschema = self._cw.vreg.schema['i18nfield_of']
-        ttype = self._cw.vreg.schema['I18nField']
-        return [(rschema, (ttype,), 'object')]
-
-    def inline_edition_form_view(self, rschema, ttype, role):
-        """overloaded method to force uneditable I18nField instance forms to
-        be displayed, so that underlying Translation entities can be created
-        or edited.
-        """
-        assert str(rschema) == 'i18nfield_of'
-        entity = self.edited_entity
-        rset = entity.has_eid() and entity.related(rschema, role)
-        if rset:
-            fields = entity.i18nfields
-            related = sorted(rset.entities(),
-                             key=lambda f: fields.index(f.field_name))
-            vvreg = self._cw.vreg['views']
-            for relentity in related:
-                yield vvreg.select('inline-edition', self._cw,
-                                   rset=relentity.as_rset(), row=0, col=0,
-                                   etype=ttype, rtype=rschema, role=role,
-                                   peid=entity.eid, pform=self)
-
-
-class I18nFieldInlineEditionView(InlineEntityEditionFormView):
-    __select__ = (InlineEntityEditionFormView.__select__
-                  & is_instance('I18nField') & match_form_params('lang_code'))
-
-    def form_title(self, entity, i18nctx):
-        return entity.field_name
-
-    def _get_removejs(self):
-        return None
-
-
-class I18nFieldEditionForm(AutoForm):
-    __select__ = (AutoForm.__select__ & is_instance('I18nField')
-                  & match_form_params('lang_code'))
-
-    def inlined_relations(self):
-        rschema = self._cw.vreg.schema['of_field']
-        ttype = self._cw.vreg.schema['Translation']
-        return [(rschema, (ttype,), 'object')]
-
-    def inline_edition_form_view(self, rschema, ttype, role):
-        assert str(rschema) == 'of_field'
-        translation = self.edited_entity.translation(
-            self._cw.form['lang_code'])
-        vvreg = self._cw.vreg['views']
-        if translation:
-            yield vvreg.select('inline-edition', self._cw,
-                               rset=translation.as_rset(), row=0, col=0,
-                               etype=ttype, rtype=rschema, role=role,
-                               peid=self.edited_entity.eid, pform=self)
-        else:
-            yield vvreg.select('inline-creation', self._cw,
-                               etype=ttype, rtype=rschema, role=role,
-                               petype=self.edited_entity.e_schema,
-                               peid=self.edited_entity.eid,
-                               pform=self)
-
-    def should_display_inline_creation_form(self, rschema, existant, card):
-        return not existant
-
-    def should_display_add_new_relation_link(self, rschema, existant, card):
-        return False
-
-
-class I18nTranslationInlineViewMixin(object):
-
-    def form_title(self, entity, i18nctx):
-        title = super(I18nTranslationInlineViewMixin, self).form_title(
-            entity, i18nctx)
-        if entity.has_eid() and entity.is_outdated():
-            title += u' (%s)' % self._cw._('outdated')
-        return title
-
-
-class I18nTranslationInlinedFormRenderer(EntityInlinedFormRenderer):
-    __select__ = (EntityInlinedFormRenderer.__select__
-                  & is_instance('Translation'))
-
-    def render_title(self, w, form, values):
-        return
-
-
-class I18nFieldInlinedFormRenderer(EntityInlinedFormRenderer):
-    __select__ = (EntityInlinedFormRenderer.__select__
-                  & is_instance('I18nField'))
-    title_template = u"""\
-    <div class="{css_value}">{orig_val}</div>
-    <div class="{css_lang}">{label} : {orig_lang}</div>
-    """
-
-    def render_title(self, w, form, values):
-        ent = form.edited_entity
-        template_parameters = {
-            'orig_val': xml_escape(ent.original_value() or u''),
-            'orig_lang': self._cw._(ent.i18nfield_of[0].ref_lang[0].name),
-            'label': self._cw._('Original version'),
-            'css_lang': 'cw_i18nfield_orig_lang',
-            'css_value': 'cw_i18nfield_orig_value',
-        }
-        w(self.title_template.format(**template_parameters))
-
-
-class I18nTranslationInlineEditionView(I18nTranslationInlineViewMixin,
-                                       InlineEntityEditionFormView):
-    __select__ = (InlineEntityEditionFormView.__select__
-                  & is_instance('Translation'))
-
-
-class I18nTranslationInlineCreationView(I18nTranslationInlineViewMixin,
-                                        InlineEntityCreationFormView):
-    __select__ = (InlineEntityCreationFormView.__select__
-                  & specified_etype_implements('Translation'))
-
-
-class TranslationEditionForm(AutoForm):
-    __select__ = (AutoForm.__select__ & is_instance('Translation')
-                  & match_kwargs('pform'))
-
-    @iclassmethod
-    def field_by_name(cls_or_self, name, role=None, eschema=None):
-        '''make the field used for translation value the same than the one
-        used for the field in the translated entity itself'''
-        if (name, role) == ('value', 'subject'):
-            try:
-                return super(
-                    TranslationEditionForm, cls_or_self).field_by_name(
-                        name, role)
-            except FieldNotFound:
-                if eschema is None:
-                    raise
-            i18nfield = cls_or_self.parent_form.edited_entity
-            orig_eschema = i18nfield.i18nfield_of[0].e_schema
-            rschema = orig_eschema.schema.rschema(i18nfield.field_name)
-            tschemas = rschema.targets(orig_eschema, role)
-            fieldcls = _AFF.etype_get(orig_eschema, rschema, role, tschemas[0])
-            kwargs = _AFF_KWARGS.etype_get(orig_eschema, rschema,
-                                           role, tschemas[0])
-            if kwargs is None:
-                kwargs = {}
-            if fieldcls:
-                if not isinstance(fieldcls, type):
-                    return fieldcls  # already and instance
-                return fieldcls(name=name, role=role, eidparam=True, **kwargs)
-            field = guess_field(orig_eschema, rschema, role,
-                                eidparam=True, **kwargs)
-            if field is None:
-                raise
-            field.name = name
-            if getattr(field, 'get_format_field', None):
-                field.get_format_field = lambda form: None
-            return field
-        else:
-            return super(TranslationEditionForm, cls_or_self).field_by_name(
-                name, role, eschema)
-
-
-def translation_form_lang(form, field):
-    lang = lang_from_code(form._cw)
-    return lang is not None and lang.eid or None
-
-
-_AFS.tag_object_of(('Translation', 'of_field', 'I18nField'), 'main', 'inlined')
-
-_AFFK.tag_subject_of(('Translation', 'lang', 'I18nLang'),
-                     {'widget': HiddenInput, 'value': translation_form_lang})
-
-
-class i18nfieldReqRewriter(SimpleReqRewriter):
-    rules = [
-        (rgx('/i18n'),
-         dict(vid='i18nfield.manage_translations')),
-    ]


More information about the cubicweb-devel mailing list