[saem-devel] [PATCH eac V2] [pkg] move to new "cube as package" layout allowed by cubicweb 3.24

Philippe Pepiot philippe.pepiot at logilab.fr
Wed Feb 22 10:50:52 CET 2017


# HG changeset patch
# User Philippe Pepiot <philippe.pepiot at logilab.fr>
# Date 1487327035 -3600
#      Fri Feb 17 11:23:55 2017 +0100
# Node ID 2c94728b7f595aeb88cf7de76a11d9b932533f37
# Parent  157d5d0f2b232d04e5d909b77da589ba816b3e6e
# Available At https://hg.logilab.org/review/cubes/eac
#              hg pull https://hg.logilab.org/review/cubes/eac -r 2c94728b7f59
# Tested at https://jenkins.logilab.org/job/cubicweb-eac/52/
[pkg] move to new "cube as package" layout allowed by cubicweb 3.24

Require setuptools for building rpm and debian package.
Add a check-manifest test.

diff --git a/MANIFEST.in b/MANIFEST.in
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,12 @@
-include *.py
-include */*.py
-recursive-include data *.gif *.png *.ico *.css *.js
-recursive-include i18n *.po
-recursive-include wdoc *
+recursive-include cubicweb_eac/data *.gif *.jpg *.png *.ico *.css *.js *.xml
+recursive-include test/data *.xml *.xsd *.rdf *.csv
+include test/*.py test/*.rst test/data/bootstrap_cubes
+include cubicweb_eac/i18n/*.po
+include cubicweb_eac/i18n/*.pot
+recursive-include cubicweb_eac *.py
+include doc/Makefile
+include doc/*.rst *.py
+include tox.ini
+prune __pkginfo__.py
+prune debian
+prune cubicweb-eac.spec
diff --git a/__init__.py b/__init__.py
deleted file mode 100644
--- a/__init__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-"""cubicweb-eac application package
-
-Implementation of Encoded Archival Context for CubicWeb
-"""
-
-
-# EAC mappings
-
-TYPE_MAPPING = {
-    'corporateBody': u'authority',
-    'person': u'person',
-    'family': u'family',
-}
-
-MAINTENANCETYPE_MAPPING = {
-    'created': u'create',
-    'revised': u'modify',
-}
-
-# Order matters for this one in order to export correctly
-ADDRESS_MAPPING = [
-    ('StreetName', 'street'),
-    ('PostCode', 'postalcode'),
-    ('CityName', 'city'),
-]
diff --git a/__pkginfo__.py b/__pkginfo__.py
old mode 100644
new mode 120000
--- a/__pkginfo__.py
+++ b/__pkginfo__.py
@@ -1,58 +1,1 @@
-# pylint: disable=W0622
-"""cubicweb-eac application packaging information"""
-
-from os import listdir as _listdir
-from os.path import join, isdir
-from glob import glob
-
-
-modname = 'eac'
-distname = 'cubicweb-eac'
-
-numversion = (0, 3, 0)
-version = '.'.join(str(num) for num in numversion)
-
-license = 'LGPL'
-author = 'LOGILAB S.A. (Paris, FRANCE)'
-author_email = 'contact at logilab.fr'
-description = 'Implementation of Encoded Archival Context for CubicWeb'
-web = 'http://www.cubicweb.org/project/%s' % distname
-
-__depends__ = {
-    'cubicweb': '>= 3.24.0',
-    'six': '>= 1.4.0',
-    'cubicweb-prov': None,
-    'cubicweb-skos': None,
-    'cubicweb-addressbook': None,
-    'python-dateutil': None,
-}
-__recommends__ = {}
-
-classifiers = [
-    'Environment :: Web Environment',
-    'Framework :: CubicWeb',
-    'Programming Language :: Python',
-    'Programming Language :: JavaScript',
-]
-
-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
+cubicweb_eac/__pkginfo__.py
\ No newline at end of file
diff --git a/ccplugin.py b/ccplugin.py
deleted file mode 100644
--- a/ccplugin.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# 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-ctl plugin introducing eac-import command."""
-
-from __future__ import print_function
-
-from os.path import basename
-from time import time
-
-from six import text_type
-
-from cubicweb.toolsutils import Command, underline_title
-from cubicweb.cwctl import CWCTL
-from cubicweb.utils import admincnx
-from cubicweb.dataimport.importer import SimpleImportLog
-from cubicweb.web.views.cwsources import REVERSE_SEVERITIES
-
-from cubes.eac.sobjects import init_extid2eid_index
-
-
-class ImportEacData(Command):
-    """Import some EAC files.
-
-    <instance>
-      identifier of the instance into which the scheme will be imported. Should use the eac cube.
-
-    <filepath>
-      path to the EAC files to import.
-
-    """
-    arguments = '[options] <instance> <filepath>...'
-    name = 'eac-import'
-    min_args = 2
-
-    def run(self, args):
-        print(u'\n%s' % underline_title('Importing EAC files'))
-        appid = args[0]
-        with admincnx(appid) as cnx:
-            eac_import_files(cnx, args[1:])
-        cnx.repo.shutdown()
-
-
-CWCTL.register(ImportEacData)
-
-
-def eac_import_files(cnx, fpaths, store=None, **kwargs):
-    """High-level import function, given an connection to a cubicweb repository and a list of files
-    to import.
-    """
-    start_time = time()
-    imported = created = updated = 0
-    if store is None:
-        store = _store(cnx)
-    service = cnx.vreg['services'].select('eac.import', cnx, **kwargs)
-    extid2eid = init_extid2eid_index(cnx, cnx.repo.system_source)
-    try:
-        for fpath in fpaths:
-            _created, _updated = eac_import_file(service, store, fpath, extid2eid)
-            if _created or _updated:
-                imported += 1
-                created += len(_created)
-                updated += len(_updated)
-            store.flush()
-            store.commit()
-    finally:
-        store.finish()
-
-    output_str = ('\nImported {imported}/{total} files ({created} entities + '
-                  '{updated} updates) in {time:.1f} seconds using {store}')
-    print(output_str.format(
-        imported=imported,
-        created=created,
-        updated=updated,
-        total=len(fpaths),
-        time=time() - start_time,
-        store=store.__class__.__name__))
-
-
-def eac_import_file(service, store, fpath, extid2eid, raise_on_error=False):
-    import_log = MyImportLog(basename(fpath))
-    with open(fpath) as stream:
-        try:
-            created, updated, record_eid, not_visited = service.import_eac_stream(
-                stream, import_log, extid2eid=extid2eid, store=store)
-            return created, updated
-        except:
-            if raise_on_error:
-                raise
-            return 0, 0
-
-
-class MyImportLog(SimpleImportLog):
-    def _log(self, severity, msg, path, line):
-        if isinstance(msg, text_type):
-            msg = msg.encode('utf8')
-        print('[{severity}] {path}:{line}: {msg}'.format(
-            severity=REVERSE_SEVERITIES[severity],
-            path=self.filename, line=line or 0,
-            msg=msg))
-
-
-def _store(cnx):
-    if cnx.repo.system_source.dbdriver == 'postgres':
-        from cubicweb.dataimport.stores import MetadataGenerator
-        from cubicweb.dataimport.massive_store import MassiveObjectStore
-        MetadataGenerator.META_RELATIONS = (MetadataGenerator.META_RELATIONS
-                                            - set(['owned_by', 'created_by']))
-        metagen = MetadataGenerator(cnx)
-        return MassiveObjectStore(cnx, metagen=metagen, eids_seq_range=1000)
-    else:
-        from cubicweb.dataimport.stores import NoHookRQLObjectStore
-        return NoHookRQLObjectStore(cnx)
diff --git a/cubicweb-eac.spec b/cubicweb-eac.spec
--- a/cubicweb-eac.spec
+++ b/cubicweb-eac.spec
@@ -36,15 +36,16 @@ Implementation of Encoded Archival Conte
 find . -name '*.py' -type f -print0 |  xargs -0 sed -i '1,3s;^#!.*python.*$;#! /usr/bin/python2.6;'
 %endif
 
+%build
+%{__python} setup.py build
+
 %install
-NO_SETUPTOOLS=1 %{__python} setup.py --quiet install --no-compile --prefix=%{_prefix} --root="$RPM_BUILD_ROOT"
-# remove generated .egg-info file
-rm -rf $RPM_BUILD_ROOT/usr/lib/python*
-
+%{__python} setup.py install --no-compile --skip-build --root $RPM_BUILD_ROOT
 
 %clean
 rm -rf $RPM_BUILD_ROOT
 
 %files
 %defattr(-, root, root)
-%{_prefix}/share/cubicweb/cubes/*
+%{_python_sitelib}/*
+
diff --git a/cubicweb_eac/__init__.py b/cubicweb_eac/__init__.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/__init__.py
@@ -0,0 +1,25 @@
+"""cubicweb-eac application package
+
+Implementation of Encoded Archival Context for CubicWeb
+"""
+
+
+# EAC mappings
+
+TYPE_MAPPING = {
+    'corporateBody': u'authority',
+    'person': u'person',
+    'family': u'family',
+}
+
+MAINTENANCETYPE_MAPPING = {
+    'created': u'create',
+    'revised': u'modify',
+}
+
+# Order matters for this one in order to export correctly
+ADDRESS_MAPPING = [
+    ('StreetName', 'street'),
+    ('PostCode', 'postalcode'),
+    ('CityName', 'city'),
+]
diff --git a/cubicweb_eac/__pkginfo__.py b/cubicweb_eac/__pkginfo__.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/__pkginfo__.py
@@ -0,0 +1,31 @@
+# pylint: disable=W0622
+"""cubicweb-eac application packaging information"""
+
+modname = 'eac'
+distname = 'cubicweb-eac'
+
+numversion = (0, 3, 0)
+version = '.'.join(str(num) for num in numversion)
+
+license = 'LGPL'
+author = 'LOGILAB S.A. (Paris, FRANCE)'
+author_email = 'contact at logilab.fr'
+description = 'Implementation of Encoded Archival Context for CubicWeb'
+web = 'http://www.cubicweb.org/project/%s' % distname
+
+__depends__ = {
+    'cubicweb': '>= 3.24.0',
+    'six': '>= 1.4.0',
+    'cubicweb-prov': None,
+    'cubicweb-skos': None,
+    'cubicweb-addressbook': None,
+    'python-dateutil': None,
+}
+__recommends__ = {}
+
+classifiers = [
+    'Environment :: Web Environment',
+    'Framework :: CubicWeb',
+    'Programming Language :: Python',
+    'Programming Language :: JavaScript',
+]
diff --git a/cubicweb_eac/ccplugin.py b/cubicweb_eac/ccplugin.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/ccplugin.py
@@ -0,0 +1,125 @@
+# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# 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-ctl plugin introducing eac-import command."""
+
+from __future__ import print_function
+
+from os.path import basename
+from time import time
+
+from six import text_type
+
+from cubicweb.toolsutils import Command, underline_title
+from cubicweb.cwctl import CWCTL
+from cubicweb.utils import admincnx
+from cubicweb.dataimport.importer import SimpleImportLog
+from cubicweb.web.views.cwsources import REVERSE_SEVERITIES
+
+from cubicweb_eac.sobjects import init_extid2eid_index
+
+
+class ImportEacData(Command):
+    """Import some EAC files.
+
+    <instance>
+      identifier of the instance into which the scheme will be imported. Should use the eac cube.
+
+    <filepath>
+      path to the EAC files to import.
+
+    """
+    arguments = '[options] <instance> <filepath>...'
+    name = 'eac-import'
+    min_args = 2
+
+    def run(self, args):
+        print(u'\n%s' % underline_title('Importing EAC files'))
+        appid = args[0]
+        with admincnx(appid) as cnx:
+            eac_import_files(cnx, args[1:])
+        cnx.repo.shutdown()
+
+
+CWCTL.register(ImportEacData)
+
+
+def eac_import_files(cnx, fpaths, store=None, **kwargs):
+    """High-level import function, given an connection to a cubicweb repository and a list of files
+    to import.
+    """
+    start_time = time()
+    imported = created = updated = 0
+    if store is None:
+        store = _store(cnx)
+    service = cnx.vreg['services'].select('eac.import', cnx, **kwargs)
+    extid2eid = init_extid2eid_index(cnx, cnx.repo.system_source)
+    try:
+        for fpath in fpaths:
+            _created, _updated = eac_import_file(service, store, fpath, extid2eid)
+            if _created or _updated:
+                imported += 1
+                created += len(_created)
+                updated += len(_updated)
+            store.flush()
+            store.commit()
+    finally:
+        store.finish()
+
+    output_str = ('\nImported {imported}/{total} files ({created} entities + '
+                  '{updated} updates) in {time:.1f} seconds using {store}')
+    print(output_str.format(
+        imported=imported,
+        created=created,
+        updated=updated,
+        total=len(fpaths),
+        time=time() - start_time,
+        store=store.__class__.__name__))
+
+
+def eac_import_file(service, store, fpath, extid2eid, raise_on_error=False):
+    import_log = MyImportLog(basename(fpath))
+    with open(fpath) as stream:
+        try:
+            created, updated, record_eid, not_visited = service.import_eac_stream(
+                stream, import_log, extid2eid=extid2eid, store=store)
+            return created, updated
+        except:
+            if raise_on_error:
+                raise
+            return 0, 0
+
+
+class MyImportLog(SimpleImportLog):
+    def _log(self, severity, msg, path, line):
+        if isinstance(msg, text_type):
+            msg = msg.encode('utf8')
+        print('[{severity}] {path}:{line}: {msg}'.format(
+            severity=REVERSE_SEVERITIES[severity],
+            path=self.filename, line=line or 0,
+            msg=msg))
+
+
+def _store(cnx):
+    if cnx.repo.system_source.dbdriver == 'postgres':
+        from cubicweb.dataimport.stores import MetadataGenerator
+        from cubicweb.dataimport.massive_store import MassiveObjectStore
+        MetadataGenerator.META_RELATIONS = (MetadataGenerator.META_RELATIONS
+                                            - set(['owned_by', 'created_by']))
+        metagen = MetadataGenerator(cnx)
+        return MassiveObjectStore(cnx, metagen=metagen, eids_seq_range=1000)
+    else:
+        from cubicweb.dataimport.stores import NoHookRQLObjectStore
+        return NoHookRQLObjectStore(cnx)
diff --git a/cubicweb_eac/dataimport.py b/cubicweb_eac/dataimport.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/dataimport.py
@@ -0,0 +1,915 @@
+# copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# 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-eac dataimport utilities for EAC-CPF (Encoded Archival
+Context -- Corporate Bodies, Persons, and Families).
+"""
+
+from collections import deque
+import datetime
+from functools import wraps
+import inspect
+import logging
+from uuid import uuid4
+
+from six import text_type
+
+from dateutil.parser import parse as parse_date
+from lxml import etree
+
+from cubicweb.dataimport.importer import ExtEntity
+
+from cubes.skos import to_unicode
+
+from cubicweb_eac import TYPE_MAPPING, ADDRESS_MAPPING, MAINTENANCETYPE_MAPPING
+
+
+TYPE_MAPPING = TYPE_MAPPING.copy()
+TYPE_MAPPING['human'] = u'person'
+
+ETYPES_ORDER_HINT = ('AgentKind', 'PhoneNumber', 'PostalAddress', 'AuthorityRecord',
+                     'AgentPlace', 'Mandate', 'LegalStatus', 'History',
+                     'Structure', 'AgentFunction', 'Occupation', 'GeneralContext',
+                     'AssociationRelation', 'ChronologicalRelation', 'HierarchicalRelation',
+                     'EACResourceRelation', 'ExternalUri', 'EACSource',
+                     'Activity')
+
+
+class InvalidEAC(RuntimeError):
+    """EAC input is malformed."""
+
+
+class InvalidXML(RuntimeError):
+    """EAC input has an invalid XML format"""
+
+
+class MissingTag(RuntimeError):
+    """Mandatory tag is missing in EAC input"""
+
+    def __init__(self, tag, tag_parent=None):
+        super(MissingTag, self).__init__()
+        self.tag = tag
+        self.tag_parent = tag_parent
+
+
+def external_uri(uri):
+    values = [text_type(uri)]
+    return ExtEntity('ExternalUri', uri, {'uri': set(values), 'cwuri': set(values)})
+
+
+def filter_none(func):
+    """Filter None value from a generator function."""
+    def wrapped(*args, **kwargs):
+        for x in func(*args, **kwargs):
+            if x is not None:
+                yield x
+    return wraps(func)(wrapped)
+
+
+def filter_empty(func):
+    """Filter out empty ExtEntity (i.e. with empty ``values`` attribute)."""
+    @wraps(func)
+    def wrapper(self, *args, **kwargs):
+        for extentity in func(self, *args, **kwargs):
+            if extentity.values:
+                yield extentity
+    return wrapper
+
+
+def elem_maybe_none(func):
+    """Method decorator for external entity builder function handling the case
+    of `elem` being None.
+    """
+    if inspect.isgeneratorfunction(func):
+        def wrapped(self, elem, *args, **kwargs):
+            if elem is None:
+                return
+            for extentity in func(self, elem, *args, **kwargs):
+                yield extentity
+    else:
+        def wrapped(self, elem, *args, **kwargs):
+            if elem is None:
+                return None
+            else:
+                return func(self, elem, *args, **kwargs)
+    return wraps(func)(wrapped)
+
+
+def add_xml_wrap_for(*etypes):
+    """Add an `xml_wrap` attribute in ExtEntity's values dictionnary."""
+    def decorator(func):
+        def wrapped(self, elem):
+            objectXMLWrap = self._elem_find(elem, 'eac:objectXMLWrap')
+            xmlwrap = None
+            if objectXMLWrap is not None:
+                nchildren = len(objectXMLWrap)
+                if nchildren >= 1:
+                    xmlwrap = objectXMLWrap[0]
+                if nchildren > 1:
+                    msg = self._('multiple children elements found in {0}').format(
+                        objectXMLWrap)
+                    self.import_log.record_warning(msg, line=objectXMLWrap.line)
+            attribute_added = False
+            for extentity in func(self, elem):
+                if xmlwrap is not None and extentity.etype in etypes:
+                    # prevent association of xmlwrap to several extentities.
+                    assert not attribute_added, 'xml_wrap attribute already added'
+                    extentity.values.setdefault('xml_wrap', set([])).add(
+                        etree.tostring(xmlwrap, encoding='utf-8'))
+                    attribute_added = True
+                yield extentity
+        return wraps(func)(wrapped)
+    return decorator
+
+
+def relate_to_record_through(etype, rtype):
+    """Add an ``rtype`` relationship from ``etype`` to the imported record."""
+    def decorator(func):
+        if inspect.isgeneratorfunction(func):
+            def wrapper(self, *args, **kwargs):
+                for extentity in func(self, *args, **kwargs):
+                    if extentity.etype == etype:
+                        extentity.values.setdefault(rtype, set()).add(self.record.extid)
+                    yield extentity
+        else:
+            def wrapper(self, *args, **kwargs):
+                extentity = func(self, *args, **kwargs)
+                if extentity and extentity.etype == etype:
+                    extentity.values.setdefault(rtype, set()).add(self.record.extid)
+                return extentity
+        return wraps(func)(wrapper)
+    return decorator
+
+
+def add_citations_for(etype):
+    """Handle import of citation tag for `etype` ExtEntity that is yielded by
+    decorated method.
+    """
+    def decorator(func):
+        @wraps(func)
+        def wrapper(self, elem):
+            for extentity in func(self, elem):
+                if extentity.etype == etype:
+                    for citation in self.build_citation(elem):
+                        extentity.values.setdefault(
+                            'has_citation', set()).add(citation.extid)
+                        yield citation
+                yield extentity
+        return wrapper
+    return decorator
+
+
+def require_tag(tagname):
+    """Method decorator handling a mandatory tag within a XML element."""
+    def warn(self, elem):
+        self.import_log.record_warning(
+            self._('expecting a %s tag in element %s, found none') %
+            (tagname, elem.tag), line=elem.sourceline)
+
+    def decorator(func):
+        # pylint: disable=protected-access
+        if inspect.isgeneratorfunction(func):
+            def wrapped(self, elem, *args, **kwargs):
+                if self._elem_find(elem, tagname) is None:
+                    warn(self, elem)
+                    return
+                for extentity in func(self, elem, *args, **kwargs):
+                    yield extentity
+        else:
+            def wrapped(self, elem, *args, **kwargs):
+                if self._elem_find(elem, tagname) is None:
+                    warn(self, elem)
+                    return None
+                return func(self, elem, *args, **kwargs)
+        return wraps(func)(wrapped)
+
+    return decorator
+
+
+def trace_extentity(instance):
+    """Decorator for `build_` methods tracing ExtEntities built from a given
+    XML element.
+    """
+    def decorator(func):
+        if inspect.isgeneratorfunction(func):
+            def wrapper(elem, *args, **kwargs):
+                for extentity in func(elem, *args, **kwargs):
+                    instance.record_visited(elem, extentity)
+                    yield extentity
+        else:
+            def wrapper(elem, *args, **kwargs):
+                extentity = func(elem, *args, **kwargs)
+                if extentity is not None:
+                    instance.record_visited(elem, extentity)
+                return extentity
+        return wraps(func)(wrapper)
+    return decorator
+
+
+def equivalent_concept(tagname, etype):
+    """Method decorator indicating that a sub-tag may have a vocabularySource attribute indicating
+    that a relation to some ExternalUri object should be drown from any entity of type `etype` built
+    by decorated method.
+    """
+    def decorator(func):
+        @wraps(func)
+        def wrapped(self, elem, *args, **kwargs):
+            subelem = self._elem_find(elem, tagname)
+            if subelem is not None:
+                extid = subelem.attrib.get('vocabularySource')
+                if extid is not None:
+                    yield external_uri(extid)
+            else:
+                extid = None
+
+            def update_extentity(extentity):
+                if extid is not None and extentity.etype == etype:
+                    extentity.values['equivalent_concept'] = set([extid])
+
+            if inspect.isgeneratorfunction(func):
+                for extentity in func(self, elem, *args, **kwargs):
+                    update_extentity(extentity)
+                    yield extentity
+            else:
+                extentity = func(self, elem, *args, **kwargs)
+                update_extentity(extentity)
+                yield extentity
+        return wrapped
+    return decorator
+
+
+class EACCPFImporter(object):
+    """Importer for EAC-CPF data.
+
+    The importer will generate `extid`s using the `extid_generator` function
+    if specified or use `uuid.uuid4` to generate unique `extid`s.
+
+    During import the `record` attribute is set to the external entity of the
+    imported AuthorityRecord.
+
+    Ref: http://eac.staatsbibliothek-berlin.de/fileadmin/user_upload/schema/cpfTagLibrary.html
+    """
+    def __init__(self, fpath, import_log, _=text_type, extid_generator=None):
+        try:
+            tree = etree.parse(fpath)
+        except etree.XMLSyntaxError as exc:
+            raise InvalidXML(str(exc))
+        self._ = _
+        self._root = tree.getroot()
+        self.namespaces = self._root.nsmap.copy()
+        # remove default namespaces, not supported by .xpath method we'll use later
+        self.namespaces.pop(None, None)
+        self.namespaces['eac'] = 'urn:isbn:1-931666-33-4'
+        self.namespaces.setdefault('xlink', 'http://www.w3.org/1999/xlink')
+        self.import_log = import_log
+        if extid_generator is None:
+            def extid_generator():
+                return str(uuid4())
+        self._gen_extid = extid_generator
+        self.record = ExtEntity('AuthorityRecord', None, {})
+        # Store a mapping of XML elements to produced ExtEntities
+        self._visited = {}
+
+    def __getattribute__(self, name):
+        attr = super(EACCPFImporter, self).__getattribute__(name)
+        if name.startswith('build_'):
+            return trace_extentity(self)(attr)
+        return attr
+
+    def record_visited(self, elem, extentity):
+        assert extentity.extid, extentity
+        self._visited.setdefault(elem, set([])).add(extentity.extid)
+
+    def not_visited(self):
+        """Yield (tagname, sourceline) items corresponding to XML elements not
+        used to build any ExtEntity.
+        """
+        visited = self._visited
+        ns = self.namespaces['eac']
+        queue = deque(self._root)
+        # These elements contain other ones which are known to be handled.
+        container_tags = ['control', 'cpfDescription', 'identity',
+                          'maintenanceHistory', 'sources', 'description',
+                          'mandates', 'places', 'legalStatuses', 'occupations', 'relations']
+        containers = ['{{{0}}}{1}'.format(ns, tag) for tag in container_tags]
+        while queue:
+            elem = queue.popleft()
+            if (not isinstance(elem, etree._Element) or
+                    isinstance(elem, etree._Comment)):
+                continue
+            if elem in visited:
+                continue
+            if elem.tag not in containers:
+                yield elem.tag.replace('{' + ns + '}', ''), elem.sourceline
+            else:
+                queue.extend(elem)
+
+    def _elem_find(self, elem, path, method='find'):
+        """Wrapper around lxml.etree.Element find* methods to support
+        namespaces also for old lxml versions.
+        """
+        finder = getattr(elem, method)
+        try:
+            return finder(path, self.namespaces)
+        except TypeError:
+            # In old lxml, find() does not accept namespaces argument.
+            path = path.split(':', 1)
+            try:
+                ns, path = path
+            except ValueError:
+                # path has no namespace
+                pass
+            else:
+                path = '{' + self.namespaces[ns] + '}' + path
+            return finder(path)
+
+    def _elem_findall(self, *args):
+        return self._elem_find(*args, method='findall')
+
+    @filter_empty
+    def external_entities(self):
+        """Parse a EAC XML file to and yield external entities."""
+        # control element.
+        control = self._elem_find(self._root, 'eac:control')
+        if control is None:
+            raise MissingTag('control')
+        for extentity in self.parse_control(control):
+            yield extentity
+        # Records (identity tags) are within cpfDescription tag.
+        cpf_desc = self._elem_find(self._root, 'eac:cpfDescription')
+        if cpf_desc is None:
+            raise MissingTag('cpfDescription')
+        # identity element.
+        identity = self._elem_find(cpf_desc, 'eac:identity')
+        if identity is None:
+            raise MissingTag('identity', 'cpfDescription')
+        for extentity in self.parse_identity(identity):
+            yield extentity
+        # description element.
+        description = self._elem_find(cpf_desc, 'eac:description')
+        if description is not None:
+            for extentity in self.parse_description(description):
+                yield extentity
+        # relations element.
+        for extentity in self.parse_relations(cpf_desc):
+            yield extentity
+        # Record is complete.
+        self.record_visited(self._root, self.record)
+        yield self.record
+
+    def parse_identity(self, identity):
+        """Parse the `identity` tag and yield external entities, possibly
+        updating record's `values` dict.
+        """
+        # entityId
+        isni = self._elem_find(identity, 'eac:entityId')
+        if isni is not None and isni.text:
+            self.record_visited(isni, self.record)
+            self.record.values['isni'] = set([text_type(isni.text)])
+        # entityType
+        akind = self._elem_find(identity, 'eac:entityType')
+        if akind is None:
+            raise MissingTag('entityType', 'identity')
+        agent_kind = self.build_agent_kind(akind)
+        yield agent_kind
+        self.record.values['agent_kind'] = set([agent_kind.extid])
+        name_entry = None
+        name_entries = self._elem_findall(identity, 'eac:nameEntry')
+        if not name_entries:
+            raise MissingTag('nameEntry', 'identity')
+        for name_entry in name_entries:
+            yield self.build_name_entry(name_entry)
+
+    @filter_none
+    def parse_description(self, description):
+        """Parse the `description` tag and yield external entities, possibly
+        updating record's `values` dict.
+        """
+        # dates.
+        daterange = description.xpath('eac:existDates/eac:dateRange',
+                                      namespaces=self.namespaces)
+        if daterange:
+            elem = daterange[0]
+            self.record_visited(elem, self.record)
+            self.record_visited(elem.getparent(), self.record)
+            dates = self.parse_daterange(elem)
+            if dates:
+                self.record.values.update(dates)
+        # address.
+        for place in self.find_nested(description, 'eac:place', 'eac:places'):
+            for extentity in self.build_place(place):
+                yield extentity
+        # additional EAC-CPF information.
+        for legal_status in self.find_nested(description, 'eac:legalStatus', 'eac:legalStatuses'):
+            for extentity in self.build_legal_status(legal_status):
+                yield extentity
+        # mandate
+        for mandate in self.find_nested(description, 'eac:mandate', 'eac:mandates'):
+            for extentity in self.build_mandate(mandate):
+                yield extentity
+        # history
+        for history in self._elem_findall(description, 'eac:biogHist'):
+            for extentity in self.build_history(history):
+                yield extentity
+        # structure
+        for structure in self._elem_findall(description, 'eac:structureOrGenealogy'):
+            yield self.build_structure(structure)
+        # function
+        for function in self.find_nested(description, 'eac:function', 'eac:functions'):
+            for extentity in self.build_function(function):
+                yield extentity
+        # occupation
+        for occupation in self.find_nested(description, 'eac:occupation', 'eac:occupations'):
+            for extentity in self.build_occupation(occupation):
+                yield extentity
+        # general context
+        for context in self._elem_findall(description, 'eac:generalContext'):
+            for extentity in self.build_generalcontext(context):
+                yield extentity
+
+    def find_nested(self, elem, tagname, innertag):
+        """Return a list of element with `tagname` within `element` possibly
+        nested within `innertag`.
+        """
+        all_elems = self._elem_findall(elem, tagname)
+        wrapper = self._elem_find(elem, innertag)
+        if wrapper is not None:
+            all_elems.extend(self._elem_findall(wrapper, tagname))
+        return all_elems
+
+    def parse_tag_description(self, elem, tagname='eac:descriptiveNote',
+                              attrname='description'):
+        """Return a dict with `attrname` and `attrname`_format retrieved from
+        a description-like tag.
+        """
+        elems = self._elem_findall(elem, tagname)
+        if len(elems) > 1:
+            self.import_log.record_warning(self._(
+                'found multiple %s tag within %s element, only one will be '
+                'used.') % (tagname, elem.tag), line=elem.sourceline)
+        elem = elems[0] if elems else None
+        values = {}
+        if elem is not None:
+            parsed = self.parse_tag_content(elem)
+            values.update(zip((attrname, attrname + '_format'),
+                              (set([p]) for p in parsed)))
+        return values
+
+    def parse_tag_content(self, elem):
+        """Parse the content of an element be it plain text or HTML and return
+        the content along with MIME type.
+        """
+        assert elem is not None, 'unexpected empty element'
+        text = elem.text.strip() if elem.text else None
+        if text:
+            desc, desc_format = text_type(text), u'text/plain'
+        else:
+            ptag = '{%(eac)s}p' % self.namespaces
+            desc = '\n'.join(etree.tostring(child, encoding=text_type,
+                                            method='html').strip()
+                             for child in elem.iterchildren(ptag)
+                             if len(child) != 0 or child.text)
+            if desc:
+                desc_format = u'text/html'
+            else:
+                self.import_log.record_warning(self._(
+                    'element %s has no text nor children, no content '
+                    'extracted') % elem.tag, line=elem.sourceline)
+                desc, desc_format = None, None
+        return desc, desc_format
+
+    @relate_to_record_through('NameEntry', 'name_entry_for')
+    def build_name_entry(self, element):
+        """Build a NameEntry external entity."""
+        self.record_visited(element, self.record)
+        parts = self._elem_findall(element, 'eac:part')
+        if not parts:
+            raise MissingTag('part', 'nameEntry')
+        # Join all "part" tags into a single "parts" attribute.
+        values = {'parts': set([u', '.join(text_type(p.text) for p in parts)])}
+        # Consider first authorizedForm and then alternativeForm, missing
+        # possible combinations which cannot be handled until the model is
+        # complete.
+        if self._elem_find(element, 'eac:authorizedForm') is not None:
+            values['form_variant'] = set([u'authorized'])
+        elif self._elem_find(element, 'eac:alternativeForm') is not None:
+            values['form_variant'] = set([u'alternative'])
+        return ExtEntity('NameEntry', self._gen_extid(), values)
+
+    @elem_maybe_none
+    def build_agent_kind(self, elem):
+        """Build a AgentKind external entity"""
+        # Map EAC entity types to our terminolgy.
+        kind = TYPE_MAPPING.get(elem.text, elem.text)
+        agentkind_id = 'agentkind/' + kind
+        return ExtEntity('AgentKind', agentkind_id, {'name': set([text_type(kind)])})
+
+    @elem_maybe_none
+    @relate_to_record_through('LegalStatus', 'legal_status_agent')
+    @filter_empty
+    @add_citations_for('LegalStatus')
+    @equivalent_concept('eac:term', 'LegalStatus')
+    def build_legal_status(self, elem, **kwargs):
+        """Build a `LegalStatus` external entity.
+
+        Extra `kwargs` are passed to `parse_tag_description`.
+        """
+        values = self.parse_tag_description(elem, **kwargs)
+        term = self._elem_find(elem, 'eac:term')
+        if term is not None and term.text:
+            values['term'] = set([text_type(term.text)])
+        dateelem = self._elem_find(elem, 'eac:dateRange')
+        dates = self.parse_daterange(dateelem)
+        if dates:
+            values.update(dates)
+        legalstatus = ExtEntity('LegalStatus', self._gen_extid(), values)
+        if dateelem is not None:
+            self.record_visited(dateelem, legalstatus)
+        yield legalstatus
+
+    @elem_maybe_none
+    @relate_to_record_through('Mandate', 'mandate_agent')
+    @filter_empty
+    @add_citations_for('Mandate')
+    @equivalent_concept('eac:term', 'Mandate')
+    def build_mandate(self, elem, **kwargs):
+        """Build a `Mandate` external entity.
+
+        Extra `kwargs` are passed to `parse_tag_description`.
+        """
+        values = self.parse_tag_description(elem, **kwargs)
+        term = self._elem_find(elem, 'eac:term')
+        if term is not None and term.text:
+            values['term'] = set([text_type(term.text)])
+        dateelem = self._elem_find(elem, 'eac:dateRange')
+        dates = self.parse_daterange(dateelem)
+        if dates:
+            values.update(dates)
+        yield ExtEntity('Mandate', self._gen_extid(), values)
+
+    @elem_maybe_none
+    def build_citation(self, elem):
+        """Build a `Citation` external entity."""
+        for citation_elem in self._elem_findall(elem, "eac:citation"):
+            note = citation_elem.text.strip() if citation_elem.text else u''
+            uri = citation_elem.attrib.get('{%(xlink)s}href' % self.namespaces)
+            if not note and not uri:
+                msg = self._('element {0} has no text nor (valid) link').format(
+                    etree.tostring(citation_elem))
+                self.import_log.record_warning(msg, line=citation_elem.sourceline)
+                return
+            values = {}
+            if uri:
+                values['uri'] = set([text_type(uri)])
+            if note:
+                values['note'] = set([text_type(note)])
+                if u'<span>' in note:
+                    values['note_format'] = set([u'text/html'])
+            yield ExtEntity('Citation', self._gen_extid(), values)
+
+    @relate_to_record_through('History', 'history_agent')
+    @add_citations_for('History')
+    @elem_maybe_none
+    def build_history(self, elem):
+        """Build a `History` external entity."""
+        desc, desc_format = self.parse_tag_content(elem)
+        if desc:
+            values = {'text': set([desc]),
+                      'text_format': set([desc_format])}
+            yield ExtEntity('History', self._gen_extid(), values)
+
+    @elem_maybe_none
+    @relate_to_record_through('Structure', 'structure_agent')
+    def build_structure(self, elem):
+        """Build a `Structure` external entity."""
+        desc, desc_format = self.parse_tag_content(elem)
+        if desc:
+            values = {'description': set([desc]),
+                      'description_format': set([desc_format])}
+            return ExtEntity('Structure', self._gen_extid(), values)
+
+    @relate_to_record_through('AgentPlace', 'place_agent')
+    @filter_empty
+    @add_citations_for('AgentPlace')
+    @equivalent_concept('eac:placeEntry', 'AgentPlace')
+    def build_place(self, elem):
+        """Build a AgentPlace external entity"""
+        values = {}
+        role = self._elem_find(elem, 'eac:placeRole')
+        if role is not None:
+            values['role'] = set([text_type(role.text)])
+        entry = self._elem_find(elem, 'eac:placeEntry')
+        if entry is not None:
+            values['name'] = set([text_type(entry.text)])
+        for address in self._elem_findall(elem, 'eac:address'):
+            for extentity in self.build_address(address):
+                if extentity.values:
+                    values['place_address'] = set([extentity.extid])
+                    yield extentity
+        yield ExtEntity('AgentPlace', self._gen_extid(), values)
+
+    def build_address(self, elem):
+        """Build `PostalAddress`s external entity"""
+        address_entity = {}
+        for line in self._elem_findall(elem, 'eac:addressLine'):
+            if 'localType' in line.attrib:
+                attr = dict(ADDRESS_MAPPING).get(line.attrib['localType'])
+                if attr:
+                    address_entity.setdefault(attr, set()).add(
+                        text_type(line.text))
+        yield ExtEntity('PostalAddress', self._gen_extid(), address_entity)
+
+    @relate_to_record_through('AgentFunction', 'function_agent')
+    @filter_empty
+    @add_citations_for('AgentFunction')
+    @equivalent_concept('eac:term', 'AgentFunction')
+    def build_function(self, elem):
+        """Build a `AgentFunction`s external entities"""
+        values = self.parse_tag_description(elem)
+        term = self._elem_find(elem, 'eac:term')
+        if term is not None:
+            values['name'] = set([text_type(term.text)])
+        yield ExtEntity('AgentFunction', self._gen_extid(), values)
+
+    @relate_to_record_through('Occupation', 'occupation_agent')
+    @filter_empty
+    @add_citations_for('Occupation')
+    @equivalent_concept('eac:term', 'Occupation')
+    def build_occupation(self, elem):
+        """Build a `Occupation`s external entities"""
+        values = self.parse_tag_description(elem)
+        term = self._elem_find(elem, 'eac:term')
+        if term is not None:
+            values['term'] = set([text_type(term.text)])
+        dateelem = self._elem_find(elem, 'eac:dateRange')
+        dates = self.parse_daterange(dateelem)
+        if dates:
+            values.update(dates)
+        yield ExtEntity('Occupation', self._gen_extid(), values)
+
+    @relate_to_record_through('GeneralContext', 'general_context_of')
+    @add_citations_for('GeneralContext')
+    def build_generalcontext(self, elem):
+        """Build a `GeneralContext` external entity"""
+        content, content_format = self.parse_tag_content(elem)
+        if content:
+            values = {'content': set([content]),
+                      'content_format': set([content_format])}
+            yield ExtEntity('GeneralContext', self._gen_extid(), values)
+
+    @elem_maybe_none
+    def parse_daterange(self, elem):
+        """Parse a `dateRange` tag and return a dict mapping `start_date` and
+        `end_date` to parsed date range.
+        """
+        values = {}
+        for eactag, attrname in zip(('eac:fromDate', 'eac:toDate'),
+                                    ('start_date', 'end_date')):
+            date = self.parse_date(self._elem_find(elem, eactag))
+            if date:
+                values[attrname] = set([date])
+        return values
+
+    @elem_maybe_none
+    def parse_date(self, elem):
+        """Parse a date-like element"""
+        def record_warning(msg):
+            self.import_log.record_warning(msg % {'e': etree.tostring(elem)},
+                                           line=elem.sourceline)
+        standard_date = elem.attrib.get('standardDate')
+        if standard_date:
+            date = standard_date
+        else:
+            for attr in ('notBefore', 'notAfter'):
+                if elem.attrib.get(attr):
+                    record_warning(self._('found an unsupported %s attribute in date '
+                                          'element %%(e)s') % attr)
+            # Using element's text.
+            date = elem.text
+        # Set a default value for month and day; the year should always be
+        # given.
+        default = datetime.datetime(9999, 1, 1)
+        try:
+            pdate = parse_date(date, default=default)
+        except ValueError as exc:
+            record_warning(self._('could not parse date %(e)s'))
+            return None
+        except Exception as exc:  # pylint: disable=broad-except
+            # Usually a bug in dateutil.parser.
+            record_warning(self._(
+                'unexpected error during parsing of date %%(e)s: %s') %
+                to_unicode(exc))
+            logger = logging.getLogger('cubes.eac')
+            logger.exception(self._('unhandled exception while parsing date %r'), date)
+            return None
+        else:
+            if pdate.year == default.year:
+                record_warning(self._('could not parse a year from date element %(e)s'))
+                return None
+            return pdate.date()
+
+    def parse_relations(self, cpf_description):
+        """Parse the `relations` tag and yield external entities, possibly
+        updating record's `values` dict.
+        """
+        relations = self._elem_find(cpf_description, 'eac:relations')
+        if relations is None:
+            return
+        # cpfRelation.
+        for cpfrel in self._elem_findall(relations, 'eac:cpfRelation'):
+            for extentity in self.build_relation(cpfrel):
+                yield extentity
+        # resourceRelation.
+        for rrel in self._elem_findall(relations, 'eac:resourceRelation'):
+            for extentity in self.build_resource_relation(rrel):
+                yield extentity
+
+    @add_xml_wrap_for('AssociationRelation', 'ChronologicalRelation',
+                      'HierarchicalRelation')
+    def build_relation(self, elem):
+        """Build a relation between records external entity (with proper type)."""
+        relationship = elem.attrib.get('cpfRelationType')
+        if relationship is None:
+            self.import_log.record_warning(self._(
+                'found no cpfRelationType attribute in element %s, defaulting '
+                'to associative') % etree.tostring(elem),
+                line=elem.sourceline)
+            relationship = 'associative'
+        try:
+            # "other_role" (resp. "agent_role") role designates the object of the relation (resp.
+            # the agent described in the EAC-CPF instance).
+            # See: http://eac.staatsbibliothek-berlin.de/fileadmin/user_upload/schema/cpfTagLibrary.html#cpfRelationType # noqa pylint: disable=line-too-long
+            # In case the EAC relation is not qualified, we assume the object is the "parent" (or
+            # oldest) in the relation.
+            etype, other_role, agent_role = {
+                'hierarchical': ('HierarchicalRelation',
+                                 'hierarchical_parent', 'hierarchical_child'),
+                'hierarchical-parent': ('HierarchicalRelation',
+                                        'hierarchical_parent', 'hierarchical_child'),
+                'hierarchical-child': ('HierarchicalRelation',
+                                       'hierarchical_child', 'hierarchical_parent'),
+                'temporal': ('ChronologicalRelation',
+                             'chronological_predecessor', 'chronological_successor'),
+                'temporal-earlier': ('ChronologicalRelation',
+                                     'chronological_predecessor', 'chronological_successor'),
+                'temporal-later': ('ChronologicalRelation',
+                                   'chronological_successor', 'chronological_predecessor'),
+                'associative': ('AssociationRelation',
+                                'association_to', 'association_from'),
+            }[relationship]
+        except KeyError:
+            self.import_log.record_warning(self._(
+                'unsupported cpfRelationType %s in element %s, skipping')
+                % (relationship, etree.tostring(elem)),
+                line=elem.sourceline)
+            return
+        obj_uri = elem.attrib.get('{%(xlink)s}href' % self.namespaces)
+        if obj_uri is None:
+            self.import_log.record_warning(self._('found a cpfRelation without any object (no '
+                                                  'xlink attribute), skipping'),
+                                           line=elem.sourceline)
+            return
+        yield external_uri(obj_uri)
+        values = {agent_role: set([self.record.extid]), other_role: set([obj_uri])}
+        rentry = self._elem_find(elem, 'eac:relationEntry')
+        if rentry is not None and rentry.text.strip():
+            values['entry'] = set([text_type(rentry.text)])
+        dates = self.parse_daterange(
+            self._elem_find(elem, 'eac:dateRange'))
+        if dates:
+            values.update(dates)
+        values.update(self.parse_tag_description(elem))
+        yield ExtEntity(etype, self._gen_extid(), values)
+
+    @add_xml_wrap_for('EACResourceRelation')
+    def build_resource_relation(self, elem):
+        """Build a `EACResourceRelation` external entity (along with
+        ExternalUri entities).
+        """
+        obj_uri = elem.attrib.get('{%(xlink)s}href' % self.namespaces)
+        if obj_uri is None:
+            self.import_log.record_warning(self._(
+                'found a resourceRelation without any object (no xlink '
+                'attribute), skipping'), line=elem.sourceline)
+            return
+        yield external_uri(obj_uri)
+        values = {
+            'resource_relation_resource': set([obj_uri]),
+            'resource_relation_agent': set([self.record.extid]),
+        }
+        resource_role = elem.attrib.get('{%(xlink)s}role' % self.namespaces)
+        if resource_role:
+            values['resource_role'] = set([text_type(resource_role)])
+        agent_role = elem.attrib.get('resourceRelationType')
+        if agent_role:
+            values['agent_role'] = set([text_type(agent_role)])
+        dates = self.parse_daterange(self._elem_find(elem, 'eac:dateRange'))
+        if dates:
+            values.update(dates)
+        values.update(self.parse_tag_description(elem))
+        yield ExtEntity('EACResourceRelation', self._gen_extid(), values)
+
+    @filter_none
+    def parse_control(self, control):
+        """Parse the `control` tag."""
+        record_id = self._elem_find(control, 'eac:recordId')
+        if record_id is not None and record_id.text and record_id.text.strip():
+            self.record.extid = record_id.text.strip()
+            self.record.values['record_id'] = set([to_unicode(record_id.text)])
+            self.record_visited(record_id, self.record)
+        else:
+            self.record.extid = self._gen_extid()
+            self.import_log.record_warning(self._(
+                'found no recordId element in control tag, using %s as cwuri') %
+                self.record.extid, line=control.sourceline)
+        for other_record_id in self._elem_findall(control, 'eac:otherRecordId'):
+            other_id = other_record_id.text.strip()
+            if other_id:
+                values = {'eac_other_record_id_of': set([self.record.extid]),
+                          'value': set([text_type(other_id)])}
+                if other_record_id.attrib.get('localType'):
+                    values['local_type'] = set([text_type(other_record_id.attrib['localType'])])
+                extentity = ExtEntity('EACOtherRecordId', self._gen_extid(), values)
+                self.record_visited(other_record_id, extentity)
+                yield extentity
+        for elem in control.xpath('eac:sources/eac:source',
+                                  namespaces=self.namespaces):
+            for extentity in self.build_source(elem):
+                yield extentity
+        for elem in control.xpath('eac:maintenanceHistory/eac:maintenanceEvent',
+                                  namespaces=self.namespaces):
+            for extentity in self.build_maintenance_event(elem):
+                yield extentity
+
+    def build_maintenance_event(self, elem):
+        """Parse a `maintenanceEvent` tag, yielding a prov:Activity external
+        entity along with necessary Records.
+        """
+        values = {'generated': set([self.record.extid])}
+        event_type = self.parse_event_type(self._elem_find(elem, 'eac:eventType'))
+        if event_type is not None:
+            values['type'] = set([event_type])
+        date = self._elem_find(elem, 'eac:eventDateTime')
+        if date is not None:
+            dtattr = date.attrib.get('standardDateTime')
+            if dtattr:
+                try:
+                    event_date = parse_date(dtattr)
+                except ValueError:
+                    self.import_log.record_warning(
+                        self._('could not parse date from %s') % etree.tostring(date),
+                        line=date.sourceline)
+                else:
+                    values['start'] = set([event_date])
+                    values['end'] = set([event_date])
+        values.update(self.parse_tag_description(elem, 'eac:eventDescription'))
+        agent = self._elem_find(elem, 'eac:agent')
+        if agent is not None and agent.text:
+            values['agent'] = set([text_type(agent.text)])
+        yield ExtEntity('Activity', self._gen_extid(), values)
+
+    @elem_maybe_none
+    def parse_event_type(self, elem):
+        """Parse an `eventType` element and try to match a prov:type to build a
+        prov:Activity.
+        """
+        event_type = elem.text.strip() if elem.text else None
+        if event_type:
+            type_mapping = MAINTENANCETYPE_MAPPING.copy()
+            type_mapping['derived'] = u'create'
+            type_mapping['updated'] = u'modify'
+            try:
+                event_type = type_mapping[event_type.lower()]
+            except KeyError:
+                self.import_log.record_warning(self._(
+                    'eventType %s does not match the PROV-O vocabulary, respective Activity will '
+                    'not have a `type` attribute set.') % event_type, line=elem.sourceline)
+                return None
+            return event_type
+
+    @relate_to_record_through('EACSource', 'source_agent')
+    @filter_empty
+    @add_xml_wrap_for('EACSource')
+    def build_source(self, elem):
+        """Parse a `source` tag, yielding EACSource external entities.
+        """
+        values = self.parse_tag_description(elem)
+        url = elem.attrib.get('{%(xlink)s}href' % self.namespaces)
+        if url is not None:
+            values['url'] = set([text_type(url)])
+        entry = self._elem_find(elem, 'eac:sourceEntry')
+        if entry is not None and entry.text:
+            values['title'] = set([text_type(entry.text)])
+        yield ExtEntity('EACSource', self._gen_extid(), values)
diff --git a/cubicweb_eac/entities.py b/cubicweb_eac/entities.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/entities.py
@@ -0,0 +1,600 @@
+# coding: utf-8
+# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# 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-eac entity's classes"""
+
+from functools import wraps
+
+from six import text_type
+
+from lxml import etree
+
+from logilab.common.date import ustrftime
+
+from cubicweb.predicates import is_instance
+from cubicweb.entities import AnyEntity, fetch_config
+from cubicweb.view import EntityAdapter
+
+from cubicweb_eac import TYPE_MAPPING, ADDRESS_MAPPING, MAINTENANCETYPE_MAPPING
+
+
+class AuthorityRecord(AnyEntity):
+    __regid__ = 'AuthorityRecord'
+    fetch_attrs, cw_fetch_order = fetch_config(('agent_kind', 'ark'))
+
+    def dc_title(self):
+        """A NameEntry of "authorized" form variant, if any; otherwise any
+        NameEntry.
+        """
+        rset = self._cw.find('NameEntry', name_entry_for=self,
+                             form_variant=u'authorized')
+        if not rset:
+            rset = self._cw.find('NameEntry', name_entry_for=self)
+        return next(rset.entities()).parts
+
+    @property
+    def kind(self):
+        """The kind of agent"""
+        return self.agent_kind[0].name
+
+    @property
+    def printable_kind(self):
+        """The kind of agent, for display"""
+        return self.agent_kind[0].printable_value('name')
+
+    @property
+    def other_record_ids(self):
+        return dict((orec.local_type, orec.value) for orec in self.reverse_eac_other_record_id_of)
+
+
+class AgentKind(AnyEntity):
+    __regid__ = 'AgentKind'
+    fetch_attrs, cw_fetch_order = fetch_config(('name',))
+
+
+class ChronologicalRelation(AnyEntity):
+    __regid__ = 'ChronologicalRelation'
+
+    def dc_description(self):
+        if self.description:
+            return self.description
+
+
+class EACResourceRelation(AnyEntity):
+    __regid__ = 'EACResourceRelation'
+    fetch_attrs, cw_fetch_order = fetch_config(('agent_role', 'resource_role', 'description'))
+
+    @property
+    def record(self):
+        return self.resource_relation_agent[0]
+
+    @property
+    def resource(self):
+        return self.resource_relation_resource[0]
+
+    def dc_title(self):
+        agent_title = self.agent.dc_title()
+        if self.agent_role:
+            agent_title += u' (%s)' % self.printable_value('agent_role')
+        resource_title = self.resource.dc_title()
+        if self.resource_role:
+            resource_title += u' (%s)' % self.printable_value('resource_role')
+        return (self._cw._('Relation from %(from)s to %(to)s ') %
+                {'from': agent_title,
+                 'to': resource_title})
+
+
+class SameAsMixIn(object):
+    """Mix-in class for entity types supporting vocabulary_source and
+    equivalent_concept relations.
+    """
+
+    @property
+    def scheme(self):
+        return self.vocabulary_source and self.vocabulary_source[0] or None
+
+    @property
+    def concept(self):
+        return self.equivalent_concept and self.equivalent_concept[0] or None
+
+
+class AgentPlace(SameAsMixIn, AnyEntity):
+    __regid__ = 'AgentPlace'
+    fetch_attrs, cw_fetch_order = fetch_config(('name', 'role'))
+
+
+class AgentFunction(SameAsMixIn, AnyEntity):
+    __regid__ = 'AgentFunction'
+    fetch_attrs, cw_fetch_order = fetch_config(('name', 'description'))
+
+
+class Citation(SameAsMixIn, AnyEntity):
+    __regid__ = 'Citation'
+    fetch_attrs, cw_fetch_order = fetch_config(('uri', 'note'))
+
+
+class Mandate(SameAsMixIn, AnyEntity):
+    __regid__ = 'Mandate'
+    fetch_attrs, cw_fetch_order = fetch_config(('term', 'description'))
+
+
+class LegalStatus(SameAsMixIn, AnyEntity):
+    __regid__ = 'LegalStatus'
+
+
+class Occupation(SameAsMixIn, AnyEntity):
+    __regid__ = 'Occupation'
+    fetch_attrs, cw_fetch_order = fetch_config(('term', 'description'))
+
+
+class EACSource(AnyEntity):
+    __regid__ = 'EACSource'
+    fetch_attrs, cw_fetch_order = fetch_config(('title', 'url', 'description'))
+
+
+class EACOtherRecordId(AnyEntity):
+    __regid__ = 'EACOtherRecordId'
+    fetch_attrs, cw_fetch_order = fetch_config(('local_type', 'value'))
+
+
+# XML export
+
+def substitute_xml_prefix(prefix_name, namespaces):
+    """Given an XML prefixed name in the form `'ns:name'`, return the string `'{<ns_uri>}name'`
+    where `<ns_uri>` is the URI for the namespace prefix found in `namespaces`.
+
+    This new string is then suitable to build an LXML etree.Element object.
+
+    Example::
+
+        >>> substitude_xml_prefix('xlink:href', {'xlink': 'http://wwww.w3.org/1999/xlink'})
+        '{http://www.w3.org/1999/xlink}href'
+
+    """
+    try:
+        prefix, name = prefix_name.split(':', 1)
+    except ValueError:
+        return prefix_name
+    assert prefix in namespaces, 'Unknown namespace prefix: {0}'.format(prefix)
+    return '{{{0}}}'.format(namespaces[prefix]) + name
+
+
+class AbstractXmlAdapter(EntityAdapter):
+    """Abstract adapter to produce XML documents."""
+
+    content_type = 'text/xml'
+    encoding = 'utf-8'
+    namespaces = {}
+
+    @property
+    def file_name(self):
+        """Return a file name for the dump."""
+        raise NotImplementedError
+
+    def dump(self):
+        """Return an XML string for the adapted entity."""
+        raise NotImplementedError
+
+    def element(self, tag, parent=None, attributes=None, text=None):
+        """Generic function to build a XSD element tag.
+
+        Params:
+
+        * `name`, value for the 'name' attribute of the xsd:element
+
+        * `parent`, the parent etree node
+
+        * `attributes`, dictionary of attributes
+        """
+        attributes = attributes or {}
+        tag = substitute_xml_prefix(tag, self.namespaces)
+        for attr, value in attributes.items():
+            newattr = substitute_xml_prefix(attr, self.namespaces)
+            attributes[newattr] = value
+            if newattr != attr:
+                attributes.pop(attr)
+        if parent is None:
+            elt = etree.Element(tag, attributes, nsmap=self.namespaces)
+        else:
+            elt = etree.SubElement(parent, tag, attributes)
+        if text is not None:
+            elt.text = text
+        return elt
+
+    @staticmethod
+    def cwuri_url(entity):
+        """Return an absolute URL for entity's cwuri, necessary for one head ahead application
+        handling relative path in cwuri.
+        """
+        return entity.cwuri
+
+
+def add_object_xml_wrap(func):
+    """Add an objectXMLWrap sub-element to the element returned by `func`."""
+    def wrapper(self, entity, *args, **kwargs):
+        elem = func(self, entity, *args, **kwargs)
+        if entity.xml_wrap is not None:
+            objectXMLWrap = self.element('objectXMLWrap', parent=elem)
+            objectXMLWrap.append(etree.parse(entity.xml_wrap).getroot())
+        return elem
+    return wraps(func)(wrapper)
+
+
+def add_descriptive_note(func):
+    @wraps(func)
+    def wrapper(self, entity, *args, **kwargs):
+        element = func(self, entity, *args, **kwargs)
+        if element is not None and entity.description:
+            self.element('descriptiveNote', parent=element).extend(
+                self._eac_richstring_paragraph_elements(entity, "description"))
+        return element
+    return wrapper
+
+
+def add_citation(func):
+    @wraps(func)
+    def wrapper(self, entity, *args, **kwargs):
+        element = func(self, entity, *args, **kwargs)
+        for citation in entity.has_citation:
+            attrs = {'xlink:type': 'simple'}
+            if citation.uri:
+                attrs['xlink:href'] = citation.uri
+            self.element('citation', parent=element, attributes=attrs, text=citation.note)
+        return element
+    return wrapper
+
+
+class AuthorityRecordEACAdapter(AbstractXmlAdapter):
+    __regid__ = 'EAC-CPF'
+    __select__ = is_instance('AuthorityRecord')
+
+    namespaces = {
+        None: u'urn:isbn:1-931666-33-4',
+        u'xsi': u'http://www.w3.org/2001/XMLSchema-instance',
+        u'xlink': u'http://www.w3.org/1999/xlink',
+    }
+    datetime_fmt = '%Y-%m-%dT%H:%M:%S'
+
+    @property
+    def file_name(self):
+        """Return a file name for the dump."""
+        if self.entity.isni:
+            name = self.entity.isni.replace("/", "_")
+        else:
+            name = text_type(self.entity.eid)
+        return u'EAC_{0}.xml'.format(name)
+
+    def dump(self):
+        """Return an XML string representing the given agent using the EAC-CPF schema."""
+        # Keep related activities since they are used multiple times
+        self.activities = sorted(self.entity.reverse_generated, key=lambda x: x.start, reverse=True)
+        # Root element
+        eac_cpf_elt = self.element('eac-cpf', attributes={
+            'xsi:schemaLocation': ('urn:isbn:1-931666-33-4 '
+                                   'http://eac.staatsbibliothek-berlin.de/schema/cpf.xsd')
+        })
+        # Top elements: control & cpfDescription
+        self.control_element(eac_cpf_elt)
+        self.cpfdescription_element(eac_cpf_elt)
+        tree = etree.ElementTree(eac_cpf_elt)
+        return etree.tostring(tree, xml_declaration=True, encoding=self.encoding, pretty_print=True)
+
+    def control_element(self, eac_cpf_elt):
+        control_elt = self.element('control', parent=eac_cpf_elt)
+        record_id = self.entity.record_id
+        if record_id is None:
+            record_id = text_type(self.entity.eid)
+        self.element('recordId', parent=control_elt, text=record_id)
+        for local_type, value in sorted(self.entity.other_record_ids.items()):
+            attrs = {}
+            if local_type is not None:
+                attrs['localType'] = local_type
+            self.element('otherRecordId', parent=control_elt, attributes=attrs,
+                         text=text_type(value))
+        self.maintenance_status_element(control_elt)
+        self.publication_status_element(control_elt)
+        self.maintenance_agency_element(control_elt)
+        self.language_declaration_element(control_elt)
+        self.maintenance_history_element(control_elt)
+        self.sources_element(control_elt)
+
+    def cpfdescription_element(self, eac_cpf_elt):
+        cpfdescription_elt = self.element('cpfDescription', parent=eac_cpf_elt)
+        self.identity_element(cpfdescription_elt)
+        self.description_element(cpfdescription_elt)
+        self.relations_element(cpfdescription_elt)
+
+    def maintenance_status_element(self, control_elt):
+        if any(activity.type == 'modify' for activity in self.activities):
+            status = 'revised'
+        else:
+            status = 'new'
+        self.element('maintenanceStatus', parent=control_elt, text=status)
+
+    def publication_status_element(self, control_elt):
+        self.element('publicationStatus', parent=control_elt, text='inProcess')  # or approved?
+
+    def maintenance_agency_element(self, control_elt):
+        agency_elt = self.element('maintenanceAgency', parent=control_elt)
+        self.element('agencyName', parent=agency_elt)
+
+    def language_declaration_element(self, control_elt):
+        lang_decl_elt = self.element('languageDeclaration', parent=control_elt)
+        self.element('language', parent=lang_decl_elt,
+                     attributes={'languageCode': 'fre'}, text=u'français')
+        self.element('script', parent=lang_decl_elt, attributes={'scriptCode': 'Latn'},
+                     text='latin')
+
+    def maintenance_history_element(self, control_elt):
+        if self.activities:
+            history_elt = self.element('maintenanceHistory', parent=control_elt)
+            for activity in self.activities:
+                self.maintenance_event_element(activity, history_elt)
+
+    def sources_element(self, control_elt):
+        sources_elt = self.element('sources')
+        for eac_source in self.entity.reverse_source_agent:
+            sources_elt.append(self.source_element(eac_source))
+        if len(sources_elt):
+            control_elt.append(sources_elt)
+
+    def identity_element(self, cpfdescription_elt):
+        identity_elt = self.element('identity', parent=cpfdescription_elt)
+        self._elt_text_from_attr('entityId', self.entity, 'isni', parent=identity_elt)
+        type_mapping = dict((v, k) for k, v in TYPE_MAPPING.items())
+        eac_type = type_mapping.get(self.entity.kind)
+        self.element('entityType', parent=identity_elt, text=eac_type)
+        for name_entry in self.entity.reverse_name_entry_for:
+            elem = self.element('nameEntry', parent=identity_elt)
+            for part in name_entry.parts.split(u', '):
+                self.element('part', parent=elem, text=part)
+            if name_entry.form_variant == u'authorized':
+                self.element('authorizedForm', text=u'conventionDeclaration',
+                             parent=elem)
+            if name_entry.form_variant == u'alternative':
+                self.element('alternativeForm', text=u'conventionDeclaration',
+                             parent=elem)
+
+    def description_element(self, cpfdescription_elt):
+        agent = self.entity
+        description_elt = self.element('description', parent=cpfdescription_elt)
+        self.exist_dates_element(description_elt)
+        for relation, group_tagname, get_element in [
+            ('reverse_place_agent', 'places', self.place_element),
+            ('reverse_function_agent', 'functions', self.function_element),
+            ('reverse_legal_status_agent', 'legalStatuses', self.legal_status_element),
+            ('reverse_occupation_agent', 'occupations', self.occupation_element),
+            ('reverse_mandate_agent', 'mandates', self.mandate_element),
+        ]:
+            relateds = getattr(agent, relation)
+            if relateds:
+                group_elt = self.element(group_tagname, parent=description_elt)
+                for related in relateds:
+                    group_elt.append(get_element(related))
+        for structure in agent.reverse_structure_agent:
+            structure_elt = self.structure_element(structure)
+            if len(structure_elt):
+                description_elt.append(structure_elt)
+        for generalcontext in agent.reverse_general_context_of:
+            generalcontext_elt = self.generalcontext_element(generalcontext)
+            if len(generalcontext_elt):
+                description_elt.append(generalcontext_elt)
+        for history in agent.reverse_history_agent:
+            bioghist_elt = self.bioghist_element(history)
+            if len(bioghist_elt):
+                description_elt.append(bioghist_elt)
+
+    def relations_element(self, cpfdescription_elt):
+        relations_elt = self.element('relations')
+        for rtype, rel_rtype, eac_rtype in [
+            ('reverse_hierarchical_child', 'hierarchical_parent', 'hierarchical-parent'),
+            ('reverse_hierarchical_parent', 'hierarchical_child', 'hierarchical-child'),
+            ('reverse_chronological_successor', 'chronological_predecessor', 'temporal-earlier'),
+            ('reverse_chronological_predecessor', 'chronological_successor', 'temporal-later'),
+            ('reverse_association_to', 'association_from', 'associative'),
+            ('reverse_association_from', 'association_to', 'associative')
+        ]:
+            for relation in getattr(self.entity, rtype):
+                relations_elt.append(
+                    self.cpfrelation_element(relation, rel_rtype, eac_rtype))
+        for resource_relation in self.entity.reverse_resource_relation_agent:
+            relations_elt.append(
+                self.resource_relation_element(resource_relation))
+        if len(relations_elt):
+            cpfdescription_elt.append(relations_elt)
+
+    def maintenance_event_element(self, activity, history_elt):
+        event_elt = self.element('maintenanceEvent', parent=history_elt)
+        type_mapping = dict((v, k) for k, v in MAINTENANCETYPE_MAPPING.items())
+        activity_type = type_mapping.get(activity.type, 'created')
+        self.element('eventType', parent=event_elt, text=activity_type)
+        self.element('eventDateTime', parent=event_elt,
+                     attributes={'standardDateTime': ustrftime(activity.start,
+                                                               fmt=self.datetime_fmt)},
+                     text=ustrftime(activity.start, fmt=self.datetime_fmt))
+        self.agent_element(activity, event_elt)
+        self._elt_text_from_attr('eventDescription', activity, 'description', parent=event_elt)
+
+    @add_descriptive_note
+    @add_object_xml_wrap
+    def source_element(self, eac_source):
+        url = eac_source.url
+        attributes = {'xlink:href': url, 'xlink:type': 'simple'} if url else None
+        source_elt = self.element('source', attributes=attributes)
+        self.element('sourceEntry', parent=source_elt, text=eac_source.title)
+        return source_elt
+
+    def exist_dates_element(self, description_elt):
+        date_range = self._eac_date_range_xml_elt(self.entity.start_date, self.entity.end_date)
+        if date_range is not None:
+            exist_dates = self.element('existDates', parent=description_elt)
+            exist_dates.append(date_range)
+
+    @add_citation
+    def place_element(self, place):
+        place_elt = self.element('place')
+        for attr, eac_name in [('role', 'placeRole'), ('name', 'placeEntry')]:
+            eac_elt = self._elt_text_from_attr(eac_name, place, attr, parent=place_elt)
+            if eac_elt is not None and eac_name == 'placeEntry' and place.equivalent_concept:
+                eac_elt.attrib['vocabularySource'] = self.cwuri_url(place.equivalent_concept[0])
+        for address in place.place_address:
+            self.address_element(address, place_elt)
+        return place_elt
+
+    @add_descriptive_note
+    @add_citation
+    def function_element(self, function):
+        function_elt = self.element('function')
+        term_elt = self._elt_text_from_attr('term', function, 'name', parent=function_elt)
+        if term_elt is not None and function.equivalent_concept:
+            term_elt.attrib['vocabularySource'] = self.cwuri_url(function.equivalent_concept[0])
+        return function_elt
+
+    @add_descriptive_note
+    @add_citation
+    def legal_status_element(self, legal_status):
+        legal_status_elt = self.element('legalStatus')
+        self._elt_text_from_attr('term', legal_status, 'term', parent=legal_status_elt)
+        return legal_status_elt
+
+    @add_descriptive_note
+    @add_citation
+    def occupation_element(self, occupation):
+        occupation_elt = self.element('occupation')
+        term_elt = self._elt_text_from_attr('term', occupation, 'term', parent=occupation_elt)
+        if term_elt is not None and occupation.equivalent_concept:
+            term_elt.attrib['vocabularySource'] = self.cwuri_url(occupation.equivalent_concept[0])
+        self._eac_date_range_xml_elt(occupation.start_date, occupation.end_date,
+                                     parent=occupation_elt)
+        return occupation_elt
+
+    @add_descriptive_note
+    @add_citation
+    def mandate_element(self, mandate):
+        mandate_elt = self.element('mandate')
+        term_elt = self._elt_text_from_attr('term', mandate, 'term', parent=mandate_elt)
+        if term_elt is not None and mandate.equivalent_concept:
+            term_elt.attrib['vocabularySource'] = self.cwuri_url(mandate.equivalent_concept[0])
+        return mandate_elt
+
+    def structure_element(self, structure):
+        structure_elt = self.element('structureOrGenealogy')
+        if structure.description:
+            structure_elt.extend(self._eac_richstring_paragraph_elements(structure, "description"))
+        return structure_elt
+
+    @add_citation
+    def generalcontext_element(self, context):
+        context_elt = self.element('generalContext')
+        if context.content:
+            context_elt.extend(self._eac_richstring_paragraph_elements(context, 'content'))
+        return context_elt
+
+    @add_citation
+    def bioghist_element(self, history):
+        bioghist_elt = self.element('biogHist')
+        if history.text:
+            bioghist_elt.extend(self._eac_richstring_paragraph_elements(history, "text"))
+        return bioghist_elt
+
+    @add_descriptive_note
+    @add_object_xml_wrap
+    def cpfrelation_element(self, relation, cw_rtype, eac_rtype):
+        related = relation.related(cw_rtype).one()  # exactly one target (schema)
+        relation_elt = self.element('cpfRelation',
+                                    attributes={'cpfRelationType': eac_rtype,
+                                                'xlink:href': related.cwuri,
+                                                'xlink:type': 'simple'})
+        if relation.entry:
+            self.element('relationEntry', parent=relation_elt, text=relation.entry)
+        self._eac_date_range_xml_elt(getattr(relation, 'start_date', None),
+                                     getattr(relation, 'end_date', None),
+                                     parent=relation_elt)
+        return relation_elt
+
+    @add_object_xml_wrap
+    def resource_relation_element(self, resource_relation):
+        resource = resource_relation.resource_relation_resource[0]
+        attrs = {
+            'xlink:href': resource.uri,
+            'xlink:type': 'simple',
+        }
+        if resource_relation.resource_role:
+            attrs['xlink:role'] = resource_relation.resource_role
+        if resource_relation.agent_role:
+            attrs['resourceRelationType'] = resource_relation.agent_role
+        res_rel_elt = self.element('resourceRelation', attributes=attrs)
+        self._eac_date_range_xml_elt(resource_relation.start_date, resource_relation.end_date,
+                                     parent=res_rel_elt)
+        return res_rel_elt
+
+    def agent_element(self, activity, maintenance_event_elt):
+        if activity.agent:
+            self.element('agentType', maintenance_event_elt, text='human')
+            self.element('agent', maintenance_event_elt, text=activity.agent)
+        else:  # These tags must be present, even if name is empty
+            agent_type_elt = self.element('agentType', text='machine')
+            maintenance_event_elt.append(agent_type_elt)
+            maintenance_event_elt.append(self.element('agent'))
+
+    def address_element(self, address, place_elt):
+        address_elt = self.element('address', parent=place_elt)
+        for eac_name, attr in ADDRESS_MAPPING:
+            self._elt_text_from_attr('addressLine', address, attr,
+                                     attributes={'localType': eac_name}, parent=address_elt)
+
+    # helper methods for lxml
+
+    def _elt_text_from_attr(self, tag_name, entity, attr_name, parent=None, attributes=None):
+        """Return an lxml `Element` whose text is the value of the given attribute on the given
+        entity.
+
+        If this element is not empty and if ``parent`` is not ``None``, the element will also be
+        inserted in the parent XML element.
+
+        If ``attributes`` is not ``None``, these attributes will be added to the returned element.
+
+        Return ``None`` if element is empty.
+        """
+        value = getattr(entity, attr_name)
+        if value is not None:
+            elt = self.element(tag_name, parent=parent, attributes=attributes, text=value)
+            return elt
+
+    def _eac_date_range_xml_elt(self, start_date, end_date, parent=None):
+        """Return an EAC lxml ``'dateRange'`` ``Element`` with the given boundaries."""
+        if not start_date and not end_date:
+            return
+        date_range = self.element('dateRange', parent=parent)
+        for dt, eac_name in [(start_date, 'fromDate'), (end_date, 'toDate')]:
+            if not dt:
+                continue
+            self.element(eac_name, parent=date_range,
+                         attributes={'standardDate': dt.isoformat()}, text=dt.isoformat())
+        return date_range
+
+    def _eac_richstring_paragraph_elements(self, entity, attr_name):
+        fmt = getattr(entity, attr_name + "_format")
+        if fmt == "text/plain":
+            value = getattr(entity, attr_name)
+            if not value:
+                return []
+            return [self.element('p', text=value)]
+        else:
+            value = entity.printable_value(attr_name)
+            if not value:
+                return []
+            return list(etree.fromstring(u"<root>{0}</root>".format(value)))
diff --git a/cubicweb_eac/hooks.py b/cubicweb_eac/hooks.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/hooks.py
@@ -0,0 +1,45 @@
+# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# 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-eac specific hooks and operations"""
+
+from cubicweb.server import hook
+
+
+# ensure that equivalent_concept Concept has vocabulary_source defined ####################
+
+class EnsureVocabularySource(hook.Hook):
+    """When a equivalent_concept relation is set and targets a Concept, ensure that the
+    vocabulary_source relation is set to the concept's scheme. This should not be necessary when
+    using the UI where the workflow enforce setting the scheme first, but it's necessary during
+    e.g. EAC import.
+    """
+    __select__ = hook.Hook.__select__ & hook.match_rtype('equivalent_concept',
+                                                         toetypes=('Concept',))
+    __regid__ = 'eac.add_equivalent_concept'
+    events = ('after_add_relation', )
+
+    def __call__(self):
+        EnsureVocabularySourceOp.get_instance(self._cw).add_data((self.eidfrom, self.eidto))
+
+
+class EnsureVocabularySourceOp(hook.DataOperationMixIn, hook.Operation):
+    """Ensure X equivalent_concept target Concept as proper X vocabulary_source Scheme"""
+
+    def precommit_event(self):
+        cnx = self.cnx
+        for eid, concept_eid in self.get_data():
+            cnx.execute('SET X vocabulary_source SC WHERE NOT X vocabulary_source SC, '
+                        'C in_scheme SC, C eid %(c)s, X eid %(x)s', {'x': eid, 'c': concept_eid})
diff --git a/cubicweb_eac/i18n/en.po b/cubicweb_eac/i18n/en.po
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/i18n/en.po
@@ -0,0 +1,1477 @@
+msgid ""
+msgstr ""
+"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
+"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"
+
+msgid "A source used to establish the description of an AuthorityRecord"
+msgstr ""
+
+msgid "AgentFunction"
+msgstr "Function"
+
+msgid "AgentFunction_plural"
+msgstr "Functions"
+
+msgid "AgentKind"
+msgstr "Agent kind"
+
+msgid "AgentKind_plural"
+msgstr "Agent kinds"
+
+msgid "AgentPlace"
+msgstr "Place"
+
+msgid "AgentPlace_plural"
+msgstr "Places"
+
+msgid "Association relation between authority records"
+msgstr ""
+
+msgid "AssociationRelation"
+msgstr "Association relation"
+
+msgid "AssociationRelation_plural"
+msgstr "Association relations"
+
+msgid "AuthorityRecord"
+msgstr ""
+
+msgid "AuthorityRecord_plural"
+msgstr ""
+
+msgid "Biographical or historical information"
+msgstr ""
+
+msgid "Chronological relation between authority records"
+msgstr ""
+
+msgid "ChronologicalRelation"
+msgstr "Chronological relation"
+
+msgid "ChronologicalRelation_plural"
+msgstr "Chronological relations"
+
+msgid "Citation"
+msgstr ""
+
+msgid "Citation_plural"
+msgstr "Citations"
+
+msgid "EAC export"
+msgstr ""
+
+msgid "EAC import failed"
+msgstr ""
+
+msgid "EAC-CPF file"
+msgstr ""
+
+#, python-format
+msgid "EAC-CPF import completed: %s"
+msgstr ""
+
+msgid "EACOtherRecordId"
+msgstr "Alternative identifier"
+
+msgid "EACOtherRecordId_plural"
+msgstr "Alternative identifiers"
+
+msgid "EACResourceRelation"
+msgstr "EAC resource relation"
+
+msgid "EACResourceRelation_plural"
+msgstr "EAC resource relations"
+
+msgid "EACSource"
+msgstr "Source"
+
+msgid "EACSource_plural"
+msgstr "Sources"
+
+msgid "GeneralContext"
+msgstr "General context"
+
+msgid "GeneralContext_plural"
+msgstr "General contexts"
+
+msgid "Hierarchical relation between authority records"
+msgstr ""
+
+msgid "HierarchicalRelation"
+msgstr "Hierarchical relation"
+
+msgid "HierarchicalRelation_plural"
+msgstr "Hierarchical relations"
+
+msgid "History"
+msgstr "Piece of historical information"
+
+msgid "History_plural"
+msgstr "Pieces of historical information"
+
+msgid "Importing an AuthorityRecord from a EAC-CPF file"
+msgstr ""
+
+msgid ""
+"Information about the general social and cultural context of an authority "
+"record"
+msgstr ""
+
+msgid "Information about the structure of an authority"
+msgstr ""
+
+msgid "Information relative to the legal status of an authority"
+msgstr ""
+
+msgid "International Standard Name Identifier"
+msgstr ""
+
+msgid "Invalid XML file"
+msgstr ""
+
+msgid ""
+"Kind of an authority record (e.g. \"person\", \"authority\" or \"family\")"
+msgstr ""
+
+msgid "LegalStatus"
+msgstr "Legal status"
+
+msgid "LegalStatus_plural"
+msgstr "Legal status"
+
+msgid "Mandate"
+msgstr ""
+
+msgid "Mandate_plural"
+msgstr "Mandates"
+
+#, python-format
+msgid "Missing tag %(tag)s in XML file"
+msgstr ""
+
+#, python-format
+msgid "Missing tag %(tag)s within element %(parent)s in XML file"
+msgstr ""
+
+msgid "NameEntry"
+msgstr "Name entry"
+
+msgid "NameEntry_plural"
+msgstr "Name entries"
+
+msgid "New AgentFunction"
+msgstr "New function"
+
+msgid "New AgentKind"
+msgstr "New agent kind"
+
+msgid "New AgentPlace"
+msgstr "New place"
+
+msgid "New AssociationRelation"
+msgstr "New association relation"
+
+msgid "New AuthorityRecord"
+msgstr ""
+
+msgid "New ChronologicalRelation"
+msgstr "New chronological relation"
+
+msgid "New Citation"
+msgstr "New citation"
+
+msgid "New EACOtherRecordId"
+msgstr "New alternative identifier"
+
+msgid "New EACResourceRelation"
+msgstr "New EAC resource relation"
+
+msgid "New EACSource"
+msgstr "New source"
+
+msgid "New GeneralContext"
+msgstr "New general context"
+
+msgid "New HierarchicalRelation"
+msgstr "New hierarchical relation"
+
+msgid "New History"
+msgstr "New piece of historical information"
+
+msgid "New LegalStatus"
+msgstr "New legal status"
+
+msgid "New Mandate"
+msgstr "New mandate"
+
+msgid "New NameEntry"
+msgstr "New name entry"
+
+msgid "New Occupation"
+msgstr "New occupation"
+
+msgid "New Structure"
+msgstr "New piece of structure information"
+
+msgid "Occupation"
+msgstr ""
+
+msgid "Occupation_plural"
+msgstr "Occupations"
+
+msgid "Qualified relation between an AuthorityRecord and a PostalAddress"
+msgstr ""
+
+msgid "Reference text coming from an authority"
+msgstr ""
+
+#, python-format
+msgid "Relation from %(from)s to %(to)s "
+msgstr ""
+
+msgid "Represent a nameEntry tag of an EAC-CPF document."
+msgstr ""
+
+msgid ""
+"Represent a relation between an AuthorityRecord and a remote resource in the "
+"EAC-CPF model."
+msgstr ""
+
+msgid "Structure"
+msgstr "Piece of Structure information"
+
+msgid "Structure_plural"
+msgstr "Pieces of structure information"
+
+msgid "The function of an AuthorityRecord"
+msgstr ""
+
+msgid "This AgentFunction"
+msgstr "This function"
+
+msgid "This AgentFunction:"
+msgstr "This function:"
+
+msgid "This AgentKind"
+msgstr "This agent kind"
+
+msgid "This AgentKind:"
+msgstr "This agent kind:"
+
+msgid "This AgentPlace"
+msgstr "This place"
+
+msgid "This AgentPlace:"
+msgstr "This place:"
+
+msgid "This AssociationRelation"
+msgstr "This association relation"
+
+msgid "This AssociationRelation:"
+msgstr "This association relation:"
+
+msgid "This AuthorityRecord"
+msgstr ""
+
+msgid "This AuthorityRecord:"
+msgstr ""
+
+msgid "This ChronologicalRelation"
+msgstr "This chronological relation"
+
+msgid "This ChronologicalRelation:"
+msgstr "This chronological relation:"
+
+msgid "This Citation"
+msgstr "This citation"
+
+msgid "This Citation:"
+msgstr "This citation:"
+
+msgid "This EACOtherRecordId"
+msgstr "This alternative identifier"
+
+msgid "This EACOtherRecordId:"
+msgstr "This alternative identifier:"
+
+msgid "This EACResourceRelation"
+msgstr ""
+
+msgid "This EACResourceRelation:"
+msgstr ""
+
+msgid "This EACSource"
+msgstr "This source"
+
+msgid "This EACSource:"
+msgstr "This source:"
+
+msgid "This GeneralContext"
+msgstr "This general context"
+
+msgid "This GeneralContext:"
+msgstr "This general context:"
+
+msgid "This HierarchicalRelation"
+msgstr "This hierarchical relation"
+
+msgid "This HierarchicalRelation:"
+msgstr "This hierarchical relation:"
+
+msgid "This History"
+msgstr "This piece of historical information"
+
+msgid "This History:"
+msgstr "This piece of historical information:"
+
+msgid "This LegalStatus"
+msgstr "This legal status"
+
+msgid "This LegalStatus:"
+msgstr "This legal status:"
+
+msgid "This Mandate"
+msgstr "This mandate"
+
+msgid "This Mandate:"
+msgstr "This mandate:"
+
+msgid "This NameEntry"
+msgstr "This name entry"
+
+msgid "This NameEntry:"
+msgstr "This name entry:"
+
+msgid "This Occupation"
+msgstr "This occupation"
+
+msgid "This Occupation:"
+msgstr "This occupation:"
+
+msgid "This Structure"
+msgstr "This piece of structure information"
+
+msgid "This Structure:"
+msgstr "This piece of structure information:"
+
+msgid "XML elements not contained in EAC-CPF namespace"
+msgstr ""
+
+msgid "add AgentFunction function_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add AgentFunction has_citation Citation subject"
+msgstr ""
+
+msgid "add AgentPlace has_citation Citation subject"
+msgstr ""
+
+msgid "add AgentPlace place_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add EACOtherRecordId eac_other_record_id_of AuthorityRecord object"
+msgstr ""
+
+msgid "add EACResourceRelation resource_relation_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add EACSource source_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add GeneralContext general_context_of AuthorityRecord object"
+msgstr ""
+
+msgid "add GeneralContext has_citation Citation subject"
+msgstr ""
+
+msgid "add History has_citation Citation subject"
+msgstr ""
+
+msgid "add History history_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add LegalStatus has_citation Citation subject"
+msgstr ""
+
+msgid "add LegalStatus legal_status_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add Mandate has_citation Citation subject"
+msgstr ""
+
+msgid "add Mandate mandate_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add NameEntry name_entry_for AuthorityRecord object"
+msgstr "a name entry"
+
+msgid "add Occupation has_citation Citation subject"
+msgstr ""
+
+msgid "add Occupation occupation_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add Structure structure_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add a AgentFunction"
+msgstr ""
+
+msgid "add a AgentKind"
+msgstr ""
+
+msgid "add a AgentPlace"
+msgstr ""
+
+msgid "add a AssociationRelation"
+msgstr "add an association relation"
+
+msgid "add a AuthorityRecord"
+msgstr ""
+
+msgid "add a ChronologicalRelation"
+msgstr "add a chronological relation"
+
+msgid "add a Citation"
+msgstr "add a citation"
+
+msgid "add a EACOtherRecordId"
+msgstr "add an alternative identifier"
+
+msgid "add a EACResourceRelation"
+msgstr ""
+
+msgid "add a EACSource"
+msgstr "add a source"
+
+msgid "add a GeneralContext"
+msgstr ""
+
+msgid "add a HierarchicalRelation"
+msgstr "add a hierarchical relation"
+
+msgid "add a History"
+msgstr ""
+
+msgid "add a LegalStatus"
+msgstr ""
+
+msgid "add a Mandate"
+msgstr ""
+
+msgid "add a NameEntry"
+msgstr ""
+
+msgid "add a Occupation"
+msgstr ""
+
+msgid "add a Structure"
+msgstr ""
+
+# subject and object forms for each relation type
+# (no object form for final or symmetric relation types)
+msgid "agent"
+msgstr ""
+
+msgctxt "Activity"
+msgid "agent"
+msgstr ""
+
+# subject and object forms for each relation type
+# (no object form for final or symmetric relation types)
+msgid "agent_kind"
+msgstr "agent kind"
+
+msgctxt "AuthorityRecord"
+msgid "agent_kind"
+msgstr ""
+
+msgid "agent_kind_object"
+msgstr "agent of this kind"
+
+msgctxt "AgentKind"
+msgid "agent_kind_object"
+msgstr ""
+
+msgid "agent_role"
+msgstr "agent role"
+
+msgctxt "EACResourceRelation"
+msgid "agent_role"
+msgstr ""
+
+msgid "alternative"
+msgstr ""
+
+msgid "association_from"
+msgstr "associates"
+
+msgctxt "AssociationRelation"
+msgid "association_from"
+msgstr ""
+
+msgid "association_from_object"
+msgstr "association relation"
+
+msgctxt "AuthorityRecord"
+msgid "association_from_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "association_from_object"
+msgstr ""
+
+msgid "association_to"
+msgstr "to"
+
+msgctxt "AssociationRelation"
+msgid "association_to"
+msgstr ""
+
+msgid "association_to_object"
+msgstr "association relation"
+
+msgctxt "AuthorityRecord"
+msgid "association_to_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "association_to_object"
+msgstr ""
+
+msgid "authority"
+msgstr "authority"
+
+msgid "authorized"
+msgstr ""
+
+msgid "chronological_predecessor"
+msgstr "predecessor"
+
+msgctxt "ChronologicalRelation"
+msgid "chronological_predecessor"
+msgstr ""
+
+msgid "chronological_predecessor_object"
+msgstr "predecessor in chronological relation"
+
+msgctxt "AuthorityRecord"
+msgid "chronological_predecessor_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "chronological_predecessor_object"
+msgstr ""
+
+msgid "chronological_successor"
+msgstr "successor"
+
+msgctxt "ChronologicalRelation"
+msgid "chronological_successor"
+msgstr ""
+
+msgid "chronological_successor_object"
+msgstr "successor in chronological relation"
+
+msgctxt "AuthorityRecord"
+msgid "chronological_successor_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "chronological_successor_object"
+msgstr ""
+
+msgid "concatenation of part tags within a nameEntry"
+msgstr ""
+
+msgid "content"
+msgstr ""
+
+msgctxt "GeneralContext"
+msgid "content"
+msgstr ""
+
+msgid "content_format"
+msgstr ""
+
+msgctxt "GeneralContext"
+msgid "content_format"
+msgstr ""
+
+msgid ""
+"contextual role the address has in relation with the agent (e.g. \"home\")"
+msgstr ""
+
+#, python-format
+msgid "could not parse a year from date element %(e)s"
+msgstr ""
+
+#, python-format
+msgid "could not parse date %(e)s"
+msgstr ""
+
+#, python-format
+msgid "could not parse date from %s"
+msgstr ""
+
+msgid ""
+"creating AgentFunction (AgentFunction function_agent AuthorityRecord "
+"%(linkto)s)"
+msgstr ""
+
+msgid "creating AgentPlace (AgentPlace place_agent AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid "creating Citation (AgentFunction %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (AgentPlace %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (GeneralContext %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (History %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (LegalStatus %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (Mandate %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (Occupation %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid ""
+"creating EACOtherRecordId (EACOtherRecordId eac_other_record_id_of "
+"AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid ""
+"creating EACResourceRelation (EACResourceRelation resource_relation_agent "
+"AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid "creating EACSource (EACSource source_agent AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid ""
+"creating GeneralContext (GeneralContext general_context_of AuthorityRecord "
+"%(linkto)s)"
+msgstr ""
+
+msgid "creating History (History history_agent AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid ""
+"creating LegalStatus (LegalStatus legal_status_agent AuthorityRecord "
+"%(linkto)s)"
+msgstr ""
+
+msgid "creating Mandate (Mandate mandate_agent AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid ""
+"creating NameEntry (NameEntry name_entry_for AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid ""
+"creating Occupation (Occupation occupation_agent AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid ""
+"creating Structure (Structure structure_agent AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid "ctxcomponents_eac.xml_wrap"
+msgstr ""
+
+msgid "ctxcomponents_eac.xml_wrap_description"
+msgstr ""
+
+msgctxt "AgentFunction"
+msgid "description"
+msgstr ""
+
+msgctxt "AssociationRelation"
+msgid "description"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "description"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "description"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "description"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "description"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "description"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "description"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "description"
+msgstr ""
+
+msgctxt "Structure"
+msgid "description"
+msgstr ""
+
+msgctxt "AgentFunction"
+msgid "description_format"
+msgstr ""
+
+msgctxt "AssociationRelation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "description_format"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "description_format"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "description_format"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "Structure"
+msgid "description_format"
+msgstr ""
+
+msgid "do_import"
+msgstr "Import"
+
+msgid "eac_other_record_id_of"
+msgstr "alternative identifier of"
+
+msgctxt "EACOtherRecordId"
+msgid "eac_other_record_id_of"
+msgstr ""
+
+msgid "eac_other_record_id_of_object"
+msgstr "alternative identifiers"
+
+msgctxt "AuthorityRecord"
+msgid "eac_other_record_id_of_object"
+msgstr ""
+
+#, python-format
+msgid "element %s has no text nor children, no content extracted"
+msgstr ""
+
+#, python-brace-format
+msgid "element {0} has no text nor (valid) link"
+msgstr ""
+
+#, python-brace-format
+msgid "element {0} not parsed"
+msgstr ""
+
+msgid "encoded information about the address (e.g. \"Paris, France\")"
+msgstr ""
+
+msgid "end_date"
+msgstr "end date"
+
+msgctxt "AssociationRelation"
+msgid "end_date"
+msgstr ""
+
+msgctxt "AuthorityRecord"
+msgid "end_date"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "end_date"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "end_date"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "end_date"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "end_date"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "end_date"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "end_date"
+msgstr ""
+
+msgid "entry"
+msgstr ""
+
+msgctxt "AssociationRelation"
+msgid "entry"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "entry"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "entry"
+msgstr ""
+
+msgid "equivalent_concept"
+msgstr ""
+
+msgctxt "AgentFunction"
+msgid "equivalent_concept"
+msgstr "equivalent concept"
+
+msgctxt "AgentPlace"
+msgid "equivalent_concept"
+msgstr "equivalent concept"
+
+msgctxt "LegalStatus"
+msgid "equivalent_concept"
+msgstr "equivalent concept"
+
+msgctxt "Mandate"
+msgid "equivalent_concept"
+msgstr "equivalent concept"
+
+msgctxt "Occupation"
+msgid "equivalent_concept"
+msgstr "equivalent concept"
+
+msgid "equivalent_concept_object"
+msgstr ""
+
+msgctxt "Concept"
+msgid "equivalent_concept_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "equivalent_concept_object"
+msgstr ""
+
+#, python-format
+msgid ""
+"eventType %s does not match the PROV-O vocabulary, respective Activity will "
+"not have a `type` attribute set."
+msgstr ""
+
+#, python-format
+msgid "expecting a %s tag in element %s, found none"
+msgstr ""
+
+msgid "family"
+msgstr ""
+
+msgid "form_variant"
+msgstr "form variant"
+
+msgctxt "NameEntry"
+msgid "form_variant"
+msgstr ""
+
+msgid "found a cpfRelation without any object (no xlink attribute), skipping"
+msgstr ""
+
+msgid ""
+"found a resourceRelation without any object (no xlink attribute), skipping"
+msgstr ""
+
+#, python-format
+msgid "found an unsupported %s attribute in date element %%(e)s"
+msgstr ""
+
+#, python-format
+msgid "found multiple %s tag within %s element, only one will be used."
+msgstr ""
+
+#, python-format
+msgid ""
+"found no cpfRelationType attribute in element %s, defaulting to associative"
+msgstr ""
+
+#, python-format
+msgid "found no recordId element in control tag, using %s as cwuri"
+msgstr ""
+
+msgid "function_agent"
+msgstr "agent"
+
+msgctxt "AgentFunction"
+msgid "function_agent"
+msgstr ""
+
+msgid "function_agent_object"
+msgstr "function"
+
+msgctxt "AuthorityRecord"
+msgid "function_agent_object"
+msgstr ""
+
+msgid "general_context_of"
+msgstr "agent"
+
+msgctxt "GeneralContext"
+msgid "general_context_of"
+msgstr ""
+
+msgid "general_context_of_object"
+msgstr "general context"
+
+msgctxt "AuthorityRecord"
+msgid "general_context_of_object"
+msgstr ""
+
+msgctxt "Activity"
+msgid "generated"
+msgstr ""
+
+msgctxt "AuthorityRecord"
+msgid "generated_object"
+msgstr ""
+
+msgid "has_citation"
+msgstr "citation"
+
+msgctxt "AgentFunction"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "AgentPlace"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "GeneralContext"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "History"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "has_citation"
+msgstr ""
+
+msgid "has_citation_object"
+msgstr "citation of"
+
+msgctxt "Citation"
+msgid "has_citation_object"
+msgstr ""
+
+msgid "hierarchical_child"
+msgstr "child"
+
+msgctxt "HierarchicalRelation"
+msgid "hierarchical_child"
+msgstr ""
+
+msgid "hierarchical_child_object"
+msgstr "child in hierarchical relation"
+
+msgctxt "AuthorityRecord"
+msgid "hierarchical_child_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "hierarchical_child_object"
+msgstr ""
+
+msgid "hierarchical_parent"
+msgstr "parent"
+
+msgctxt "HierarchicalRelation"
+msgid "hierarchical_parent"
+msgstr ""
+
+msgid "hierarchical_parent_object"
+msgstr "parent in hierarchical relation"
+
+msgctxt "AuthorityRecord"
+msgid "hierarchical_parent_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "hierarchical_parent_object"
+msgstr ""
+
+msgid "history_agent"
+msgstr "agent"
+
+msgctxt "History"
+msgid "history_agent"
+msgstr ""
+
+msgid "history_agent_object"
+msgstr "historical information"
+
+msgctxt "AuthorityRecord"
+msgid "history_agent_object"
+msgstr ""
+
+msgid "information about the history of an authority record"
+msgstr ""
+
+msgid "information about the structure of an authority record"
+msgstr ""
+
+msgid "isni"
+msgstr "ISNI identifier"
+
+msgctxt "AuthorityRecord"
+msgid "isni"
+msgstr ""
+
+msgid "legal status of an authority record"
+msgstr ""
+
+msgid "legal_status_agent"
+msgstr "agent"
+
+msgctxt "LegalStatus"
+msgid "legal_status_agent"
+msgstr ""
+
+msgid "legal_status_agent_object"
+msgstr "legal status"
+
+msgctxt "AuthorityRecord"
+msgid "legal_status_agent_object"
+msgstr ""
+
+msgid "local_type"
+msgstr "local type"
+
+msgctxt "EACOtherRecordId"
+msgid "local_type"
+msgstr ""
+
+msgid "mandate of an authority record"
+msgstr ""
+
+msgid "mandate_agent"
+msgstr "agent"
+
+msgctxt "Mandate"
+msgid "mandate_agent"
+msgstr "agent"
+
+msgid "mandate_agent_object"
+msgstr "mandate"
+
+msgctxt "AuthorityRecord"
+msgid "mandate_agent_object"
+msgstr ""
+
+#, python-brace-format
+msgid "multiple children elements found in {0}"
+msgstr ""
+
+msgctxt "AgentFunction"
+msgid "name"
+msgstr "title"
+
+msgctxt "AgentKind"
+msgid "name"
+msgstr "title"
+
+msgctxt "AgentPlace"
+msgid "name"
+msgstr "title"
+
+msgid "name_entry_for"
+msgstr "name for"
+
+msgctxt "NameEntry"
+msgid "name_entry_for"
+msgstr "name for"
+
+msgid "name_entry_for_object"
+msgstr "name entries"
+
+msgctxt "AuthorityRecord"
+msgid "name_entry_for_object"
+msgstr ""
+
+msgid "note"
+msgstr ""
+
+msgctxt "Citation"
+msgid "note"
+msgstr ""
+
+msgid "note_format"
+msgstr "format"
+
+msgctxt "Citation"
+msgid "note_format"
+msgstr ""
+
+msgid "occupation in which the person works or has worked"
+msgstr ""
+
+msgid "occupation_agent"
+msgstr "agent"
+
+msgctxt "Occupation"
+msgid "occupation_agent"
+msgstr ""
+
+msgid "occupation_agent_object"
+msgstr "occupations"
+
+msgctxt "AuthorityRecord"
+msgid "occupation_agent_object"
+msgstr ""
+
+msgid "parts"
+msgstr ""
+
+msgctxt "NameEntry"
+msgid "parts"
+msgstr ""
+
+msgid "person"
+msgstr ""
+
+msgid "place_address"
+msgstr "address"
+
+msgctxt "AgentPlace"
+msgid "place_address"
+msgstr ""
+
+msgid "place_address_object"
+msgstr "place"
+
+msgctxt "PostalAddress"
+msgid "place_address_object"
+msgstr ""
+
+msgid "place_agent"
+msgstr "agent"
+
+msgctxt "AgentPlace"
+msgid "place_agent"
+msgstr ""
+
+msgid "place_agent_object"
+msgstr "place"
+
+msgctxt "AuthorityRecord"
+msgid "place_agent_object"
+msgstr ""
+
+msgid "postal_address"
+msgstr "postal address"
+
+msgctxt "AuthorityRecord"
+msgid "postal_address"
+msgstr ""
+
+msgid "postal_address_object"
+msgstr "agent"
+
+msgctxt "PostalAddress"
+msgid "postal_address_object"
+msgstr "agent"
+
+msgid "record_id"
+msgstr "record identifier"
+
+msgctxt "AuthorityRecord"
+msgid "record_id"
+msgstr "record identifier"
+
+msgid "reference to an external citation resource"
+msgstr ""
+
+msgid "resource_relation_agent"
+msgstr "agent"
+
+msgctxt "EACResourceRelation"
+msgid "resource_relation_agent"
+msgstr ""
+
+msgid "resource_relation_agent_object"
+msgstr "relation with a resource"
+
+msgctxt "AuthorityRecord"
+msgid "resource_relation_agent_object"
+msgstr ""
+
+msgid "resource_relation_resource"
+msgstr "resource"
+
+msgctxt "EACResourceRelation"
+msgid "resource_relation_resource"
+msgstr ""
+
+msgid "resource_relation_resource_object"
+msgstr "resource of relation"
+
+msgctxt "ExternalUri"
+msgid "resource_relation_resource_object"
+msgstr ""
+
+msgid "resource_role"
+msgstr "resource role"
+
+msgctxt "EACResourceRelation"
+msgid "resource_role"
+msgstr ""
+
+msgid "role"
+msgstr ""
+
+msgctxt "AgentPlace"
+msgid "role"
+msgstr ""
+
+msgid "source_agent"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "source_agent"
+msgstr ""
+
+msgid "source_agent_object"
+msgstr "source"
+
+msgctxt "AuthorityRecord"
+msgid "source_agent_object"
+msgstr ""
+
+msgid "start date must be less than end date"
+msgstr ""
+
+msgid "start_date"
+msgstr "start date"
+
+msgctxt "AssociationRelation"
+msgid "start_date"
+msgstr ""
+
+msgctxt "AuthorityRecord"
+msgid "start_date"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "start_date"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "start_date"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "start_date"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "start_date"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "start_date"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "start_date"
+msgstr ""
+
+msgid "structure_agent"
+msgstr "agent"
+
+msgctxt "Structure"
+msgid "structure_agent"
+msgstr ""
+
+msgid "structure_agent_object"
+msgstr "structure information"
+
+msgctxt "AuthorityRecord"
+msgid "structure_agent_object"
+msgstr ""
+
+msgid "term"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "term"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "term"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "term"
+msgstr ""
+
+msgid "text"
+msgstr ""
+
+msgctxt "History"
+msgid "text"
+msgstr ""
+
+msgid "text_format"
+msgstr ""
+
+msgctxt "History"
+msgid "text_format"
+msgstr ""
+
+msgid "the agent responsible for this activity"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "title"
+msgstr ""
+
+msgid "type of relation the agent has to the resource"
+msgstr ""
+
+msgid "type or nature of the remote resource"
+msgstr ""
+
+#, python-format
+msgid "unexpected error during parsing of date %%(e)s: %s"
+msgstr ""
+
+#, python-format
+msgid "unhandled exception while parsing date %r"
+msgstr ""
+
+msgid "unknown-agent-kind"
+msgstr "unknown"
+
+#, python-format
+msgid "unsupported cpfRelationType %s in element %s, skipping"
+msgstr ""
+
+msgctxt "Citation"
+msgid "uri"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "url"
+msgstr ""
+
+msgctxt "Activity"
+msgid "used"
+msgstr ""
+
+msgctxt "AuthorityRecord"
+msgid "used_object"
+msgstr ""
+
+msgctxt "EACOtherRecordId"
+msgid "value"
+msgstr ""
+
+msgid "vocabulary_source"
+msgstr "vocabulary source"
+
+msgctxt "AgentFunction"
+msgid "vocabulary_source"
+msgstr ""
+
+msgctxt "AgentPlace"
+msgid "vocabulary_source"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "vocabulary_source"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "vocabulary_source"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "vocabulary_source"
+msgstr ""
+
+msgid "vocabulary_source_object"
+msgstr "source of"
+
+msgctxt "ConceptScheme"
+msgid "vocabulary_source_object"
+msgstr ""
+
+msgid "xml syntax error: "
+msgstr ""
+
+msgid "xml_wrap"
+msgstr "extra XML elements"
+
+msgctxt "AssociationRelation"
+msgid "xml_wrap"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "xml_wrap"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "xml_wrap"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "xml_wrap"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "xml_wrap"
+msgstr ""
diff --git a/cubicweb_eac/i18n/fr.po b/cubicweb_eac/i18n/fr.po
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/i18n/fr.po
@@ -0,0 +1,1494 @@
+msgid ""
+msgstr ""
+"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
+"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"
+
+msgid "A source used to establish the description of an AuthorityRecord"
+msgstr ""
+"Une source utilisée pour l'établissement de la description d'une notice"
+
+msgid "AgentFunction"
+msgstr "Fonction"
+
+msgid "AgentFunction_plural"
+msgstr "Fonctions"
+
+msgid "AgentKind"
+msgstr "Type de notice"
+
+msgid "AgentKind_plural"
+msgstr "Types de notice"
+
+msgid "AgentPlace"
+msgstr "Lieu"
+
+msgid "AgentPlace_plural"
+msgstr "Lieux"
+
+msgid "Association relation between authority records"
+msgstr ""
+
+msgid "AssociationRelation"
+msgstr "Relation d'association"
+
+msgid "AssociationRelation_plural"
+msgstr "Relations d'association"
+
+msgid "AuthorityRecord"
+msgstr "Notice d'autorité"
+
+msgid "AuthorityRecord_plural"
+msgstr "Notices d'autorité"
+
+msgid "Biographical or historical information"
+msgstr "Information biographique ou historique"
+
+msgid "Chronological relation between authority records"
+msgstr ""
+
+msgid "ChronologicalRelation"
+msgstr "Relation chronologique"
+
+msgid "ChronologicalRelation_plural"
+msgstr "Relations chronologiques"
+
+msgid "Citation"
+msgstr ""
+
+msgid "Citation_plural"
+msgstr "Citations"
+
+msgid "EAC export"
+msgstr "export EAC"
+
+msgid "EAC import failed"
+msgstr "Échec de l'importation EAC"
+
+msgid "EAC-CPF file"
+msgstr "fichier EAC-CPF"
+
+#, python-format
+msgid "EAC-CPF import completed: %s"
+msgstr "Import EAC-CPF terminé : %s"
+
+msgid "EACOtherRecordId"
+msgstr "Identifiant alternatif"
+
+msgid "EACOtherRecordId_plural"
+msgstr "Identifiants alternatifs"
+
+msgid "EACResourceRelation"
+msgstr "Relation vers une ressource EAC"
+
+msgid "EACResourceRelation_plural"
+msgstr "Relations vers une ressource EAC"
+
+msgid "EACSource"
+msgstr "Source"
+
+msgid "EACSource_plural"
+msgstr "Sources"
+
+msgid "GeneralContext"
+msgstr "Contexte général"
+
+msgid "GeneralContext_plural"
+msgstr "Contextes généraux"
+
+msgid "Hierarchical relation between authority records"
+msgstr ""
+
+msgid "HierarchicalRelation"
+msgstr "Relation hiérarchique"
+
+msgid "HierarchicalRelation_plural"
+msgstr "Relations hiérarchiques"
+
+msgid "History"
+msgstr "Élément d'information historique"
+
+msgid "History_plural"
+msgstr "Éléments d'information historique"
+
+msgid "Importing an AuthorityRecord from a EAC-CPF file"
+msgstr "Import d'une notice d'autorité depuis un fichier EAC-CPF"
+
+msgid ""
+"Information about the general social and cultural context of an authority "
+"record"
+msgstr "Information au sujet du contexte social et culturel d'un agent"
+
+msgid "Information about the structure of an authority"
+msgstr "Élément d'organisation interne d'une collectivité"
+
+msgid "Information relative to the legal status of an authority"
+msgstr "Information sur le statut juridique d'une collectivité"
+
+msgid "International Standard Name Identifier"
+msgstr ""
+
+msgid "Invalid XML file"
+msgstr "Fichier XML malformé"
+
+msgid ""
+"Kind of an authority record (e.g. \"person\", \"authority\" or \"family\")"
+msgstr "Un type d'agent (par. \"personne\", \"collectivité\" ou \"famille\""
+
+msgid "LegalStatus"
+msgstr "Statut juridique"
+
+msgid "LegalStatus_plural"
+msgstr "Statuts juridiques"
+
+msgid "Mandate"
+msgstr "Texte de référence"
+
+msgid "Mandate_plural"
+msgstr "Textes de référence"
+
+#, python-format
+msgid "Missing tag %(tag)s in XML file"
+msgstr "Tag %(tag)s manquant dans le fichier XML"
+
+#, python-format
+msgid "Missing tag %(tag)s within element %(parent)s in XML file"
+msgstr "Tag %(tag)s manquant dans l'élément %(parent)s du fichier XML "
+
+msgid "NameEntry"
+msgstr "Forme du nom"
+
+msgid "NameEntry_plural"
+msgstr "Formes du nom"
+
+msgid "New AgentFunction"
+msgstr "Nouvelle fonction"
+
+msgid "New AgentKind"
+msgstr "Nouveau type de notice"
+
+msgid "New AgentPlace"
+msgstr "Nouveau lieu"
+
+msgid "New AssociationRelation"
+msgstr "Nouvelle relation d'association"
+
+msgid "New AuthorityRecord"
+msgstr "Nouvelle notice d'autorité"
+
+msgid "New ChronologicalRelation"
+msgstr "Nouvelle relation chronologique"
+
+msgid "New Citation"
+msgstr "Nouvelle citation"
+
+msgid "New EACOtherRecordId"
+msgstr "Nouvel identifiant alternatif"
+
+msgid "New EACResourceRelation"
+msgstr "Nouvelle relation vers une ressource"
+
+msgid "New EACSource"
+msgstr "Nouvelle source"
+
+msgid "New GeneralContext"
+msgstr "Nouveau contexte général"
+
+msgid "New HierarchicalRelation"
+msgstr "Nouvelle relation hiérarchique"
+
+msgid "New History"
+msgstr "Nouvel élément d'information historique"
+
+msgid "New LegalStatus"
+msgstr "Nouveau statut juridique"
+
+msgid "New Mandate"
+msgstr "Nouveau mandat"
+
+msgid "New NameEntry"
+msgstr "Nouvelle forme du nom"
+
+msgid "New Occupation"
+msgstr "Nouvelle profession"
+
+msgid "New Structure"
+msgstr "Nouvel élément d'organisation interne"
+
+msgid "Occupation"
+msgstr "Profession"
+
+msgid "Occupation_plural"
+msgstr "Professions"
+
+msgid "Qualified relation between an AuthorityRecord and a PostalAddress"
+msgstr "Relation qualifiée entre une notice et une adresse postale"
+
+msgid "Reference text coming from an authority"
+msgstr "Texte de référence émis par une collectivité"
+
+#, python-format
+msgid "Relation from %(from)s to %(to)s "
+msgstr "Relation de %(from)s à %(to)s"
+
+msgid "Represent a nameEntry tag of an EAC-CPF document."
+msgstr "Représente la balise nameEntry d'un document EAC-CPF"
+
+msgid ""
+"Represent a relation between an AuthorityRecord and a remote resource in the "
+"EAC-CPF model."
+msgstr ""
+"Représente une relation entre une notice et une ressource distante dans le "
+"modèle EAC-CPF."
+
+msgid "Structure"
+msgstr "Élément d'organisation interne"
+
+msgid "Structure_plural"
+msgstr "Éléments d'organisation interne"
+
+msgid "The function of an AuthorityRecord"
+msgstr "La fonction d'une notice"
+
+msgid "This AgentFunction"
+msgstr "Cette fonction"
+
+msgid "This AgentFunction:"
+msgstr "Cette fonction :"
+
+msgid "This AgentKind"
+msgstr "Ce type de notice"
+
+msgid "This AgentKind:"
+msgstr "Ce type de notice :"
+
+msgid "This AgentPlace"
+msgstr "Ce lieu"
+
+msgid "This AgentPlace:"
+msgstr "Ce lieu :"
+
+msgid "This AssociationRelation"
+msgstr "Cette relation d'association"
+
+msgid "This AssociationRelation:"
+msgstr "Cette relation d'association :"
+
+msgid "This AuthorityRecord"
+msgstr "Cette notice d'autorité"
+
+msgid "This AuthorityRecord:"
+msgstr "Cette notice d'autorité :"
+
+msgid "This ChronologicalRelation"
+msgstr "Cette relation chronologique"
+
+msgid "This ChronologicalRelation:"
+msgstr "Cette relation chronologique :"
+
+msgid "This Citation"
+msgstr "Cette citation"
+
+msgid "This Citation:"
+msgstr "Cette citation :"
+
+msgid "This EACOtherRecordId"
+msgstr "Cet identifiant alternatif"
+
+msgid "This EACOtherRecordId:"
+msgstr "Cet identifiant alternatif :"
+
+msgid "This EACResourceRelation"
+msgstr ""
+
+msgid "This EACResourceRelation:"
+msgstr ""
+
+msgid "This EACSource"
+msgstr "Cette source"
+
+msgid "This EACSource:"
+msgstr "Cette source :"
+
+msgid "This GeneralContext"
+msgstr "Ce contexte général"
+
+msgid "This GeneralContext:"
+msgstr "Ce contexte général :"
+
+msgid "This HierarchicalRelation"
+msgstr "Cette relation hiérarchique"
+
+msgid "This HierarchicalRelation:"
+msgstr "Cette relation hiérarchique :"
+
+msgid "This History"
+msgstr "Cet élément d'information historique"
+
+msgid "This History:"
+msgstr "Cet élément d'information historique :"
+
+msgid "This LegalStatus"
+msgstr "Ce statut juridique"
+
+msgid "This LegalStatus:"
+msgstr "Ce statut juridique :"
+
+msgid "This Mandate"
+msgstr "Ce texte de référence"
+
+msgid "This Mandate:"
+msgstr "Ce texte de référence :"
+
+msgid "This NameEntry"
+msgstr "Cette forme du nom"
+
+msgid "This NameEntry:"
+msgstr "Cette forme du nom :"
+
+msgid "This Occupation"
+msgstr "Cette profession"
+
+msgid "This Occupation:"
+msgstr "Cette profession :"
+
+msgid "This Structure"
+msgstr "Cet élément d'organisation interne"
+
+msgid "This Structure:"
+msgstr "Cet élément d'organisation interne :"
+
+msgid "XML elements not contained in EAC-CPF namespace"
+msgstr "éléments XML non contenus dans l'espace des noms EAC-CPF"
+
+msgid "add AgentFunction function_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add AgentFunction has_citation Citation subject"
+msgstr ""
+
+msgid "add AgentPlace has_citation Citation subject"
+msgstr ""
+
+msgid "add AgentPlace place_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add EACOtherRecordId eac_other_record_id_of AuthorityRecord object"
+msgstr ""
+
+msgid "add EACResourceRelation resource_relation_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add EACSource source_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add GeneralContext general_context_of AuthorityRecord object"
+msgstr ""
+
+msgid "add GeneralContext has_citation Citation subject"
+msgstr ""
+
+msgid "add History has_citation Citation subject"
+msgstr ""
+
+msgid "add History history_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add LegalStatus has_citation Citation subject"
+msgstr ""
+
+msgid "add LegalStatus legal_status_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add Mandate has_citation Citation subject"
+msgstr ""
+
+msgid "add Mandate mandate_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add NameEntry name_entry_for AuthorityRecord object"
+msgstr "une forme du nom"
+
+msgid "add Occupation has_citation Citation subject"
+msgstr ""
+
+msgid "add Occupation occupation_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add Structure structure_agent AuthorityRecord object"
+msgstr ""
+
+msgid "add a AgentFunction"
+msgstr ""
+
+msgid "add a AgentKind"
+msgstr ""
+
+msgid "add a AgentPlace"
+msgstr ""
+
+msgid "add a AssociationRelation"
+msgstr "ajouter une relation d'association"
+
+msgid "add a AuthorityRecord"
+msgstr ""
+
+msgid "add a ChronologicalRelation"
+msgstr "ajouter une relation chronologique"
+
+msgid "add a Citation"
+msgstr "ajouter une citation"
+
+msgid "add a EACOtherRecordId"
+msgstr "ajouter un identifiant alternatif"
+
+msgid "add a EACResourceRelation"
+msgstr ""
+
+msgid "add a EACSource"
+msgstr "ajouter une source"
+
+msgid "add a GeneralContext"
+msgstr ""
+
+msgid "add a HierarchicalRelation"
+msgstr "ajouter une relation hiérarchique"
+
+msgid "add a History"
+msgstr ""
+
+msgid "add a LegalStatus"
+msgstr ""
+
+msgid "add a Mandate"
+msgstr "ajouter un texte de référence"
+
+msgid "add a NameEntry"
+msgstr "ajouter une forme du nom"
+
+msgid "add a Occupation"
+msgstr "ajouter une profession"
+
+msgid "add a Structure"
+msgstr ""
+
+# subject and object forms for each relation type
+# (no object form for final or symmetric relation types)
+msgid "agent"
+msgstr ""
+
+msgctxt "Activity"
+msgid "agent"
+msgstr ""
+
+# subject and object forms for each relation type
+# (no object form for final or symmetric relation types)
+msgid "agent_kind"
+msgstr "type d'agent"
+
+msgctxt "AuthorityRecord"
+msgid "agent_kind"
+msgstr "type"
+
+msgid "agent_kind_object"
+msgstr "notices de ce type"
+
+msgctxt "AgentKind"
+msgid "agent_kind_object"
+msgstr ""
+
+msgid "agent_role"
+msgstr "rôle de l'agent"
+
+msgctxt "EACResourceRelation"
+msgid "agent_role"
+msgstr ""
+
+msgid "alternative"
+msgstr "alternative"
+
+msgid "association_from"
+msgstr "associe"
+
+msgctxt "AssociationRelation"
+msgid "association_from"
+msgstr ""
+
+msgid "association_from_object"
+msgstr "relation d'association"
+
+msgctxt "AuthorityRecord"
+msgid "association_from_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "association_from_object"
+msgstr ""
+
+msgid "association_to"
+msgstr "à"
+
+msgctxt "AssociationRelation"
+msgid "association_to"
+msgstr ""
+
+msgid "association_to_object"
+msgstr "relation d'association"
+
+msgctxt "AuthorityRecord"
+msgid "association_to_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "association_to_object"
+msgstr ""
+
+msgid "authority"
+msgstr "collectivité"
+
+msgid "authorized"
+msgstr "autorisée"
+
+msgid "chronological_predecessor"
+msgstr "prédécesseur"
+
+msgctxt "ChronologicalRelation"
+msgid "chronological_predecessor"
+msgstr ""
+
+msgid "chronological_predecessor_object"
+msgstr "prédécesseur dans la relation chronologique"
+
+msgctxt "AuthorityRecord"
+msgid "chronological_predecessor_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "chronological_predecessor_object"
+msgstr ""
+
+msgid "chronological_successor"
+msgstr "successeur"
+
+msgctxt "ChronologicalRelation"
+msgid "chronological_successor"
+msgstr ""
+
+msgid "chronological_successor_object"
+msgstr "successeur dans la relation chronologique"
+
+msgctxt "AuthorityRecord"
+msgid "chronological_successor_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "chronological_successor_object"
+msgstr ""
+
+msgid "concatenation of part tags within a nameEntry"
+msgstr "concaténation des éléments 'part' de la balise 'nameEntry'"
+
+msgid "content"
+msgstr ""
+
+msgctxt "GeneralContext"
+msgid "content"
+msgstr ""
+
+msgid "content_format"
+msgstr ""
+
+msgctxt "GeneralContext"
+msgid "content_format"
+msgstr ""
+
+msgid ""
+"contextual role the address has in relation with the agent (e.g. \"home\")"
+msgstr ""
+"rôle contextuel de l'adresse vis-à-vis de l'agent (par ex. \"domicile\")"
+
+#, python-format
+msgid "could not parse a year from date element %(e)s"
+msgstr "ne peux identifier une année dans la date %(e)s"
+
+#, python-format
+msgid "could not parse date %(e)s"
+msgstr "ne peut reconnaitre la date %(e)s"
+
+#, python-format
+msgid "could not parse date from %s"
+msgstr "impossible de décoder une date à partir de %s"
+
+msgid ""
+"creating AgentFunction (AgentFunction function_agent AuthorityRecord "
+"%(linkto)s)"
+msgstr "création d'une fonction (pour la notice %(linkto)s)"
+
+msgid "creating AgentPlace (AgentPlace place_agent AuthorityRecord %(linkto)s)"
+msgstr "création d'un lieu (pour la notice %(linkto)s)"
+
+msgid "creating Citation (AgentFunction %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (AgentPlace %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (GeneralContext %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (History %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (LegalStatus %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (Mandate %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid "creating Citation (Occupation %(linkto)s has_citation Citation)"
+msgstr ""
+
+msgid ""
+"creating EACOtherRecordId (EACOtherRecordId eac_other_record_id_of "
+"AuthorityRecord %(linkto)s)"
+msgstr ""
+
+msgid ""
+"creating EACResourceRelation (EACResourceRelation resource_relation_agent "
+"AuthorityRecord %(linkto)s)"
+msgstr "création d'une relation vers une ressource (pour la notice %(linkto)s)"
+
+msgid "creating EACSource (EACSource source_agent AuthorityRecord %(linkto)s)"
+msgstr "création d'une source (pour la notice %(linkto)s)"
+
+msgid ""
+"creating GeneralContext (GeneralContext general_context_of AuthorityRecord "
+"%(linkto)s)"
+msgstr "création d'un contexte général (pour la notice %(linkto)s)"
+
+msgid "creating History (History history_agent AuthorityRecord %(linkto)s)"
+msgstr ""
+"création d'un élément d'information historique (pour la notice %(linkto)s)"
+
+msgid ""
+"creating LegalStatus (LegalStatus legal_status_agent AuthorityRecord "
+"%(linkto)s)"
+msgstr "création d'un statut juridique (pour la notice %(linkto)s)"
+
+msgid "creating Mandate (Mandate mandate_agent AuthorityRecord %(linkto)s)"
+msgstr "création d'un texte de référence (pour la notice %(linkto)s)"
+
+msgid ""
+"creating NameEntry (NameEntry name_entry_for AuthorityRecord %(linkto)s)"
+msgstr "création d'une forme du nom (pour la notice %(linkto)s)"
+
+msgid ""
+"creating Occupation (Occupation occupation_agent AuthorityRecord %(linkto)s)"
+msgstr "création d'une profession (pour la notice %(linkto)s)"
+
+msgid ""
+"creating Structure (Structure structure_agent AuthorityRecord %(linkto)s)"
+msgstr "création d'un élément d'organisation interne pour la notice %(linkto)s"
+
+msgid "ctxcomponents_eac.xml_wrap"
+msgstr "Données XML supplémentaires"
+
+msgid "ctxcomponents_eac.xml_wrap_description"
+msgstr ""
+
+msgctxt "AgentFunction"
+msgid "description"
+msgstr ""
+
+msgctxt "AssociationRelation"
+msgid "description"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "description"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "description"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "description"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "description"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "description"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "description"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "description"
+msgstr ""
+
+msgctxt "Structure"
+msgid "description"
+msgstr ""
+
+msgctxt "AgentFunction"
+msgid "description_format"
+msgstr ""
+
+msgctxt "AssociationRelation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "description_format"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "description_format"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "description_format"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "description_format"
+msgstr ""
+
+msgctxt "Structure"
+msgid "description_format"
+msgstr ""
+
+msgid "do_import"
+msgstr "Importer"
+
+msgid "eac_other_record_id_of"
+msgstr "identifiant alternatif de"
+
+msgctxt "EACOtherRecordId"
+msgid "eac_other_record_id_of"
+msgstr ""
+
+msgid "eac_other_record_id_of_object"
+msgstr "identifiants alternatifs"
+
+msgctxt "AuthorityRecord"
+msgid "eac_other_record_id_of_object"
+msgstr ""
+
+#, python-format
+msgid "element %s has no text nor children, no content extracted"
+msgstr "l'élément %s n'a pas de texte ni d'enfant, aucun contenu extrait"
+
+#, python-brace-format
+msgid "element {0} has no text nor (valid) link"
+msgstr "l'élément {0} n'a ni texte ni de lien valide"
+
+#, fuzzy, python-brace-format
+msgid "element {0} not parsed"
+msgstr "l'élément {0} n'a pas été pris en compte"
+
+msgid "encoded information about the address (e.g. \"Paris, France\")"
+msgstr "information (encodée) sur l'adresse (par ex. \"Paris, France\")"
+
+msgid "end_date"
+msgstr "date de fin"
+
+msgctxt "AssociationRelation"
+msgid "end_date"
+msgstr ""
+
+msgctxt "AuthorityRecord"
+msgid "end_date"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "end_date"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "end_date"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "end_date"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "end_date"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "end_date"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "end_date"
+msgstr ""
+
+msgid "entry"
+msgstr "entrée"
+
+msgctxt "AssociationRelation"
+msgid "entry"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "entry"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "entry"
+msgstr ""
+
+msgid "equivalent_concept"
+msgstr "concept équivalent"
+
+msgctxt "AgentFunction"
+msgid "equivalent_concept"
+msgstr ""
+
+msgctxt "AgentPlace"
+msgid "equivalent_concept"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "equivalent_concept"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "equivalent_concept"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "equivalent_concept"
+msgstr ""
+
+msgid "equivalent_concept_object"
+msgstr "référencé par"
+
+msgctxt "Concept"
+msgid "equivalent_concept_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "equivalent_concept_object"
+msgstr ""
+
+#, python-format
+msgid ""
+"eventType %s does not match the PROV-O vocabulary, respective Activity will "
+"not have a `type` attribute set."
+msgstr ""
+"le type d'événement %s ne correspond pas au vocabulaire PROV-O, l'attribut "
+"type de l'activité associée ne sera pas défini"
+
+#, python-format
+msgid "expecting a %s tag in element %s, found none"
+msgstr "une balise %s était attendue dans l'élément %s, aucune n'a été trouvée"
+
+msgid "family"
+msgstr "famille"
+
+msgid "form_variant"
+msgstr "variante"
+
+msgctxt "NameEntry"
+msgid "form_variant"
+msgstr ""
+
+msgid "found a cpfRelation without any object (no xlink attribute), skipping"
+msgstr ""
+"une cpfRelation sans objet (pas d'attribut xlink) a été trouvée, elle est "
+"ignorée"
+
+msgid ""
+"found a resourceRelation without any object (no xlink attribute), skipping"
+msgstr ""
+"une resourceRelation sans objet (pas d'attribut xlink) a été trouvée, elle "
+"est ignorée"
+
+#, python-format
+msgid "found an unsupported %s attribute in date element %%(e)s"
+msgstr "attribut %s non supporté dans l'élément data %%(e)s"
+
+#, python-format
+msgid "found multiple %s tag within %s element, only one will be used."
+msgstr ""
+"plusieurs balises %s ont été trouvées dans l'élément %s, une seule a été "
+"retenue."
+
+#, python-format
+msgid ""
+"found no cpfRelationType attribute in element %s, defaulting to associative"
+msgstr ""
+"aucun attribut cpfRelationType n'a été trouvé dans l'élément %s, utilisation "
+"d'une relation d'association"
+
+#, python-format
+msgid "found no recordId element in control tag, using %s as cwuri"
+msgstr ""
+"aucun élément recordId n'a été trouvé dans le tag control en utilisant %s "
+"comme cwuri"
+
+msgid "function_agent"
+msgstr "notice"
+
+msgctxt "AgentFunction"
+msgid "function_agent"
+msgstr ""
+
+msgid "function_agent_object"
+msgstr "fonctions"
+
+msgctxt "AuthorityRecord"
+msgid "function_agent_object"
+msgstr ""
+
+msgid "general_context_of"
+msgstr "notice"
+
+msgctxt "GeneralContext"
+msgid "general_context_of"
+msgstr ""
+
+msgid "general_context_of_object"
+msgstr "contexte général"
+
+msgctxt "AuthorityRecord"
+msgid "general_context_of_object"
+msgstr ""
+
+msgctxt "Activity"
+msgid "generated"
+msgstr "a généré"
+
+msgctxt "AuthorityRecord"
+msgid "generated_object"
+msgstr "activité"
+
+msgid "has_citation"
+msgstr "citation"
+
+msgctxt "AgentFunction"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "AgentPlace"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "GeneralContext"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "History"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "has_citation"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "has_citation"
+msgstr ""
+
+msgid "has_citation_object"
+msgstr "citation pour"
+
+msgctxt "Citation"
+msgid "has_citation_object"
+msgstr ""
+
+msgid "hierarchical_child"
+msgstr "enfant"
+
+msgctxt "HierarchicalRelation"
+msgid "hierarchical_child"
+msgstr ""
+
+msgid "hierarchical_child_object"
+msgstr "enfant dans la relation hiérarchique"
+
+msgctxt "AuthorityRecord"
+msgid "hierarchical_child_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "hierarchical_child_object"
+msgstr ""
+
+msgid "hierarchical_parent"
+msgstr "parent"
+
+msgctxt "HierarchicalRelation"
+msgid "hierarchical_parent"
+msgstr ""
+
+msgid "hierarchical_parent_object"
+msgstr "parent dans la relation hiérarchique"
+
+msgctxt "AuthorityRecord"
+msgid "hierarchical_parent_object"
+msgstr ""
+
+msgctxt "ExternalUri"
+msgid "hierarchical_parent_object"
+msgstr ""
+
+msgid "history_agent"
+msgstr "notice"
+
+msgctxt "History"
+msgid "history_agent"
+msgstr ""
+
+msgid "history_agent_object"
+msgstr "informations historiques"
+
+msgctxt "AuthorityRecord"
+msgid "history_agent_object"
+msgstr ""
+
+msgid "information about the history of an authority record"
+msgstr "information historique d'une notice"
+
+msgid "information about the structure of an authority record"
+msgstr "élément d'organisation interne d'une notice"
+
+msgid "isni"
+msgstr "identifiant ISNI"
+
+msgctxt "AuthorityRecord"
+msgid "isni"
+msgstr ""
+
+msgid "legal status of an authority record"
+msgstr "statut juridique d'une notice"
+
+msgid "legal_status_agent"
+msgstr "notice"
+
+msgctxt "LegalStatus"
+msgid "legal_status_agent"
+msgstr ""
+
+msgid "legal_status_agent_object"
+msgstr "statut juridique"
+
+msgctxt "AuthorityRecord"
+msgid "legal_status_agent_object"
+msgstr ""
+
+msgid "local_type"
+msgstr "type local"
+
+msgctxt "EACOtherRecordId"
+msgid "local_type"
+msgstr ""
+
+msgid "mandate of an authority record"
+msgstr "texte de référence d'une notice"
+
+msgid "mandate_agent"
+msgstr "notice"
+
+msgctxt "Mandate"
+msgid "mandate_agent"
+msgstr ""
+
+msgid "mandate_agent_object"
+msgstr "textes de référence"
+
+msgctxt "AuthorityRecord"
+msgid "mandate_agent_object"
+msgstr ""
+
+#, python-brace-format
+msgid "multiple children elements found in {0}"
+msgstr "{0} a plusieurs éléments enfants"
+
+msgctxt "AgentFunction"
+msgid "name"
+msgstr "intitulé"
+
+msgctxt "AgentKind"
+msgid "name"
+msgstr "intitulé"
+
+msgctxt "AgentPlace"
+msgid "name"
+msgstr "intitulé"
+
+msgid "name_entry_for"
+msgstr "nom de"
+
+msgctxt "NameEntry"
+msgid "name_entry_for"
+msgstr ""
+
+msgid "name_entry_for_object"
+msgstr "formes du nom"
+
+msgctxt "AuthorityRecord"
+msgid "name_entry_for_object"
+msgstr ""
+
+msgid "note"
+msgstr ""
+
+msgctxt "Citation"
+msgid "note"
+msgstr ""
+
+msgid "note_format"
+msgstr "format"
+
+msgctxt "Citation"
+msgid "note_format"
+msgstr ""
+
+msgid "occupation in which the person works or has worked"
+msgstr "profession actuelle ou passé de l'agent"
+
+msgid "occupation_agent"
+msgstr "notice"
+
+msgctxt "Occupation"
+msgid "occupation_agent"
+msgstr ""
+
+msgid "occupation_agent_object"
+msgstr "professions"
+
+msgctxt "AuthorityRecord"
+msgid "occupation_agent_object"
+msgstr ""
+
+msgid "parts"
+msgstr "parties"
+
+msgctxt "NameEntry"
+msgid "parts"
+msgstr ""
+
+msgid "person"
+msgstr "personne"
+
+msgid "place_address"
+msgstr "adresse"
+
+msgctxt "AgentPlace"
+msgid "place_address"
+msgstr ""
+
+msgid "place_address_object"
+msgstr "lieu"
+
+msgctxt "PostalAddress"
+msgid "place_address_object"
+msgstr ""
+
+msgid "place_agent"
+msgstr "notice"
+
+msgctxt "AgentPlace"
+msgid "place_agent"
+msgstr ""
+
+msgid "place_agent_object"
+msgstr "lieux"
+
+msgctxt "AuthorityRecord"
+msgid "place_agent_object"
+msgstr ""
+
+msgid "postal_address"
+msgstr "adresse postale"
+
+msgctxt "AuthorityRecord"
+msgid "postal_address"
+msgstr ""
+
+msgid "postal_address_object"
+msgstr "notice"
+
+msgctxt "PostalAddress"
+msgid "postal_address_object"
+msgstr ""
+
+msgid "record_id"
+msgstr "identifiant"
+
+msgctxt "AuthorityRecord"
+msgid "record_id"
+msgstr "identifiant"
+
+msgid "reference to an external citation resource"
+msgstr "référence à une ressource externe"
+
+msgid "resource_relation_agent"
+msgstr "notice"
+
+msgctxt "EACResourceRelation"
+msgid "resource_relation_agent"
+msgstr ""
+
+msgid "resource_relation_agent_object"
+msgstr "relations vers une ressource"
+
+msgctxt "AuthorityRecord"
+msgid "resource_relation_agent_object"
+msgstr ""
+
+msgid "resource_relation_resource"
+msgstr "ressource"
+
+msgctxt "EACResourceRelation"
+msgid "resource_relation_resource"
+msgstr ""
+
+msgid "resource_relation_resource_object"
+msgstr "ressource d'une relation"
+
+msgctxt "ExternalUri"
+msgid "resource_relation_resource_object"
+msgstr ""
+
+msgid "resource_role"
+msgstr "rôle de la ressource"
+
+msgctxt "EACResourceRelation"
+msgid "resource_role"
+msgstr ""
+
+msgid "role"
+msgstr "rôle"
+
+msgctxt "AgentPlace"
+msgid "role"
+msgstr ""
+
+msgid "source_agent"
+msgstr "notice"
+
+msgctxt "EACSource"
+msgid "source_agent"
+msgstr ""
+
+msgid "source_agent_object"
+msgstr "sources"
+
+msgctxt "AuthorityRecord"
+msgid "source_agent_object"
+msgstr ""
+
+msgid "start date must be less than end date"
+msgstr "la date de début doit être inférieure à la date de fin"
+
+msgid "start_date"
+msgstr "date de début"
+
+msgctxt "AssociationRelation"
+msgid "start_date"
+msgstr ""
+
+msgctxt "AuthorityRecord"
+msgid "start_date"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "start_date"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "start_date"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "start_date"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "start_date"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "start_date"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "start_date"
+msgstr ""
+
+msgid "structure_agent"
+msgstr "notice"
+
+msgctxt "Structure"
+msgid "structure_agent"
+msgstr ""
+
+msgid "structure_agent_object"
+msgstr "organisations internes"
+
+msgctxt "AuthorityRecord"
+msgid "structure_agent_object"
+msgstr ""
+
+msgid "term"
+msgstr "terme"
+
+msgctxt "LegalStatus"
+msgid "term"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "term"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "term"
+msgstr ""
+
+msgid "text"
+msgstr "texte"
+
+msgctxt "History"
+msgid "text"
+msgstr ""
+
+msgid "text_format"
+msgstr "format du texte"
+
+msgctxt "History"
+msgid "text_format"
+msgstr ""
+
+msgid "the agent responsible for this activity"
+msgstr "l'agent responsable de cette activité"
+
+msgctxt "EACSource"
+msgid "title"
+msgstr "titre"
+
+msgid "type of relation the agent has to the resource"
+msgstr "type de relation que l'agent entretient avec la ressource"
+
+msgid "type or nature of the remote resource"
+msgstr "type ou nature de la ressource distante"
+
+#, python-format
+msgid "unexpected error during parsing of date %%(e)s: %s"
+msgstr "erreur inattendue lors de l'analyse de la date %%(e)s: %s"
+
+#, python-format
+msgid "unhandled exception while parsing date %r"
+msgstr "exception non gérée lors de l'analyse de la date %r"
+
+msgid "unknown-agent-kind"
+msgstr "inconnu"
+
+#, python-format
+msgid "unsupported cpfRelationType %s in element %s, skipping"
+msgstr "cpfRelationType %s non supportée dans l'élément %s, elle est ignorée"
+
+msgctxt "Citation"
+msgid "uri"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "url"
+msgstr ""
+
+msgctxt "Activity"
+msgid "used"
+msgstr "a utilisé"
+
+msgctxt "AuthorityRecord"
+msgid "used_object"
+msgstr "utilisé par"
+
+msgctxt "EACOtherRecordId"
+msgid "value"
+msgstr ""
+
+msgid "vocabulary_source"
+msgstr "source du vocabulaire"
+
+msgctxt "AgentFunction"
+msgid "vocabulary_source"
+msgstr ""
+
+msgctxt "AgentPlace"
+msgid "vocabulary_source"
+msgstr ""
+
+msgctxt "LegalStatus"
+msgid "vocabulary_source"
+msgstr ""
+
+msgctxt "Mandate"
+msgid "vocabulary_source"
+msgstr ""
+
+msgctxt "Occupation"
+msgid "vocabulary_source"
+msgstr ""
+
+msgid "vocabulary_source_object"
+msgstr "source de"
+
+msgctxt "ConceptScheme"
+msgid "vocabulary_source_object"
+msgstr ""
+
+msgid "xml syntax error: "
+msgstr "erreur de syntaxe xml : "
+
+msgid "xml_wrap"
+msgstr "éléments XML supplémentaires"
+
+msgctxt "AssociationRelation"
+msgid "xml_wrap"
+msgstr ""
+
+msgctxt "ChronologicalRelation"
+msgid "xml_wrap"
+msgstr ""
+
+msgctxt "EACResourceRelation"
+msgid "xml_wrap"
+msgstr ""
+
+msgctxt "EACSource"
+msgid "xml_wrap"
+msgstr ""
+
+msgctxt "HierarchicalRelation"
+msgid "xml_wrap"
+msgstr ""
diff --git a/migration/0.2.0_Any.py b/cubicweb_eac/migration/0.2.0_Any.py
rename from migration/0.2.0_Any.py
rename to cubicweb_eac/migration/0.2.0_Any.py
diff --git a/migration/0.3.0_Any.py b/cubicweb_eac/migration/0.3.0_Any.py
rename from migration/0.3.0_Any.py
rename to cubicweb_eac/migration/0.3.0_Any.py
diff --git a/migration/0.3.1_Any.py b/cubicweb_eac/migration/0.3.1_Any.py
rename from migration/0.3.1_Any.py
rename to cubicweb_eac/migration/0.3.1_Any.py
diff --git a/migration/postcreate.py b/cubicweb_eac/migration/postcreate.py
rename from migration/postcreate.py
rename to cubicweb_eac/migration/postcreate.py
diff --git a/cubicweb_eac/schema.py b/cubicweb_eac/schema.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/schema.py
@@ -0,0 +1,397 @@
+# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# 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-eac schema"""
+
+from yams.buildobjs import (EntityType, RelationDefinition, String, Date, Bytes,
+                            RichString, ComputedRelation)
+from yams.constraints import BoundaryConstraint, Attribute
+
+from cubicweb import _
+from cubicweb.schema import RRQLExpression, RQLVocabularyConstraint
+
+from cubes.prov.schema import Activity
+
+
+def dated_entity_type(cls):
+    """Class decorator adding `start_date` and `end_date` attribute to an
+    EntityType.
+    """
+    cls.add_relation(Date(constraints=[BoundaryConstraint(
+        '<=', Attribute('end_date'), msg=_('start date must be less than end date'))]),
+        name='start_date')
+    cls.add_relation(Date(), name='end_date')
+    return cls
+
+
+def xml_wrap(cls):
+    """Class decorator adding an `xml_wrap` attribute to an EntityType."""
+    desc = _('XML elements not contained in EAC-CPF namespace')
+    cls.add_relation(Bytes(description=desc), name='xml_wrap')
+    return cls
+
+
+Activity.add_relation(String(description=_('the agent responsible for this activity'),
+                             indexed=True, fulltextindexed=True), name='agent')
+
+
+ at dated_entity_type
+class AuthorityRecord(EntityType):
+    record_id = String(indexed=True)
+    isni = String(unique=True,
+                  description=_('International Standard Name Identifier'))
+
+
+class NameEntry(EntityType):
+    """Represent a nameEntry tag of an EAC-CPF document."""
+    parts = String(
+        required=True, fulltextindexed=True,
+        description=_('concatenation of part tags within a nameEntry'))
+    form_variant = String(internationalizable=True,
+                          vocabulary=[_('authorized'), _('alternative')])
+
+
+class name_entry_for(RelationDefinition):
+    subject = 'NameEntry'
+    object = 'AuthorityRecord'
+    cardinality = '1+'
+    composite = 'object'
+    fulltext_container = 'object'
+    inlined = True
+
+
+class EACOtherRecordId(EntityType):
+    value = String(required=True, fulltextindexed=True, indexed=True)
+    local_type = String(indexed=True)
+    __unique_together__ = [('value', 'eac_other_record_id_of')]
+
+
+class eac_other_record_id_of(RelationDefinition):
+    subject = 'EACOtherRecordId'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    composite = 'object'
+    fulltext_container = 'object'
+    inlined = True
+
+
+class AgentFunction(EntityType):
+    """The function of an AuthorityRecord"""
+    name = String(fulltextindexed=True, internationalizable=True)
+    description = RichString(fulltextindexed=True)
+    __unique_together__ = [('name', 'function_agent')]
+
+
+class function_agent(RelationDefinition):
+    subject = 'AgentFunction'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    composite = 'object'
+    fulltext_container = 'object'
+    inlined = True
+
+
+class AgentPlace(EntityType):
+    """Qualified relation between an AuthorityRecord and a PostalAddress"""
+    name = String(fulltextindexed=True,
+                  description=_('encoded information about the address (e.g. '
+                                '"Paris, France")'))
+    role = String(description=_('contextual role the address has in relation '
+                                'with the agent (e.g. "home")'),
+                  internationalizable=True)
+    __unique_together__ = [('role', 'place_agent', 'place_address')]
+
+
+class place_address(RelationDefinition):
+    subject = 'AgentPlace'
+    object = 'PostalAddress'
+    cardinality = '?1'
+    inlined = True
+    composite = 'subject'
+    fulltext_container = 'subject'
+
+
+class place_agent(RelationDefinition):
+    subject = 'AgentPlace'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    inlined = True
+    composite = 'object'
+    fulltext_container = 'object'
+
+
+class postal_address(ComputedRelation):
+    rule = 'P place_agent S, P place_address O'
+
+
+class AgentKind(EntityType):
+    """Kind of an authority record (e.g. "person", "authority" or "family")"""
+    __permissions__ = {
+        'read': ('managers', 'users', 'guests'),
+        'add': ('managers', ),
+        'update': (),
+        'delete': (),
+    }
+    name = String(required=True, unique=True, internationalizable=True)
+
+
+class agent_kind(RelationDefinition):
+    __permissions__ = {
+        'read': ('managers', 'users', 'guests'),
+        'add': ('managers', 'users'),
+        'delete': (RRQLExpression('O name "unknown-agent-kind"'),),
+    }
+    subject = 'AuthorityRecord'
+    object = 'AgentKind'
+    cardinality = '1*'
+    inlined = True
+
+
+class GeneralContext(EntityType):
+    """Information about the general social and cultural context of an authority record"""
+    content = RichString(fulltextindexed=True)
+
+
+class general_context_of(RelationDefinition):
+    subject = 'GeneralContext'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    inlined = True
+    composite = 'object'
+    fulltext_container = 'object'
+
+
+class _agent_relation(RelationDefinition):
+    """Abstract relation between authority record"""
+    subject = None
+    object = ('AuthorityRecord', 'ExternalUri')
+    cardinality = '1*'
+    inlined = True
+
+
+ at xml_wrap
+ at dated_entity_type
+class AssociationRelation(EntityType):
+    """Association relation between authority records"""
+    entry = String()
+    description = RichString()
+
+
+class association_from(_agent_relation):
+    subject = 'AssociationRelation'
+
+
+class association_to(_agent_relation):
+    subject = 'AssociationRelation'
+
+
+ at xml_wrap
+ at dated_entity_type
+class ChronologicalRelation(EntityType):
+    """Chronological relation between authority records"""
+    entry = String()
+    description = RichString()
+
+
+class chronological_predecessor(_agent_relation):
+    subject = 'ChronologicalRelation'
+
+
+class chronological_successor(_agent_relation):
+    subject = 'ChronologicalRelation'
+
+
+ at xml_wrap
+ at dated_entity_type
+class HierarchicalRelation(EntityType):
+    """Hierarchical relation between authority records"""
+    entry = String()
+    description = RichString()
+
+
+class hierarchical_parent(_agent_relation):
+    subject = 'HierarchicalRelation'
+
+
+class hierarchical_child(_agent_relation):
+    subject = 'HierarchicalRelation'
+
+
+class generated(RelationDefinition):
+    subject = 'Activity'
+    object = 'AuthorityRecord'
+
+
+class used(RelationDefinition):
+    subject = 'Activity'
+    object = 'AuthorityRecord'
+
+
+ at dated_entity_type
+class Mandate(EntityType):
+    """Reference text coming from an authority"""
+    term = String(fulltextindexed=True)
+    description = RichString(fulltextindexed=True)
+
+
+ at dated_entity_type
+class LegalStatus(EntityType):
+    """Information relative to the legal status of an authority"""
+    term = String(fulltextindexed=True)
+    description = RichString(fulltextindexed=True)
+
+
+class History(EntityType):
+    """Biographical or historical information"""
+    text = RichString(fulltextindexed=True)
+
+
+class Structure(EntityType):
+    """Information about the structure of an authority"""
+    description = RichString(fulltextindexed=True)
+
+
+ at dated_entity_type
+class Occupation(EntityType):
+    term = String(fulltextindexed=True)
+    description = RichString(fulltextindexed=True)
+
+
+class occupation_agent(RelationDefinition):
+    subject = 'Occupation'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    composite = 'object'
+    fulltext_container = 'object'
+    inlined = True
+    description = _('occupation in which the person works or has worked')
+
+
+class mandate_agent(RelationDefinition):
+    subject = 'Mandate'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    composite = 'object'
+    fulltext_container = 'object'
+    inlined = True
+    description = _('mandate of an authority record')
+
+
+class legal_status_agent(RelationDefinition):
+    subject = 'LegalStatus'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    composite = 'object'
+    fulltext_container = 'object'
+    inlined = True
+    description = _('legal status of an authority record')
+
+
+class structure_agent(RelationDefinition):
+    subject = 'Structure'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    composite = 'object'
+    fulltext_container = 'object'
+    inlined = True
+    description = _('information about the structure of an authority record')
+
+
+class history_agent(RelationDefinition):
+    subject = 'History'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    composite = 'object'
+    fulltext_container = 'object'
+    inlined = True
+    description = _('information about the history of an authority record')
+
+
+class Citation(EntityType):
+    note = RichString()
+    uri = String()
+
+
+class has_citation(RelationDefinition):
+    subject = ('GeneralContext', 'Mandate', 'Occupation', 'History',
+               'AgentFunction', 'LegalStatus', 'AgentPlace')
+    object = 'Citation'
+    cardinality = '*1'
+    composite = 'subject'
+    description = _('reference to an external citation resource')
+
+
+ at xml_wrap
+ at dated_entity_type
+class EACResourceRelation(EntityType):
+    """Represent a relation between an AuthorityRecord and a remote resource in the
+    EAC-CPF model.
+    """
+    agent_role = String(description=_('type of relation the agent has to the resource'),
+                        internationalizable=True)
+    resource_role = String(description=_('type or nature of the remote resource'),
+                           internationalizable=True)
+    description = RichString(fulltextindexed=True)
+
+
+class resource_relation_agent(RelationDefinition):
+    subject = 'EACResourceRelation'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    inlined = True
+    composite = 'object'
+    fulltext_container = 'object'
+
+
+class resource_relation_resource(RelationDefinition):
+    subject = 'EACResourceRelation'
+    object = 'ExternalUri'
+    cardinality = '1*'
+    inlined = True
+
+
+ at xml_wrap
+class EACSource(EntityType):
+    """A source used to establish the description of an AuthorityRecord"""
+    title = String(fulltextindexed=True)
+    url = String()
+    description = RichString(fulltextindexed=True)
+
+
+class source_agent(RelationDefinition):
+    subject = 'EACSource'
+    object = 'AuthorityRecord'
+    cardinality = '1*'
+    composite = 'object'
+    fulltext_container = 'object'
+    inlined = True
+
+
+class vocabulary_source(RelationDefinition):
+    subject = ('Mandate', 'LegalStatus', 'AgentFunction', 'AgentPlace',
+               'Occupation')
+    object = 'ConceptScheme'
+    cardinality = '?*'
+
+
+class equivalent_concept(RelationDefinition):
+    subject = ('Mandate', 'LegalStatus', 'AgentFunction', 'AgentPlace',
+               'Occupation')
+    object = ('ExternalUri', 'Concept')
+    constraints = [RQLVocabularyConstraint('S vocabulary_source SC, O in_scheme SC')]
+    cardinality = '?*'
+    # relation with 'ExternalUri' as object can't be inlined because of a limitation of
+    # data-import's (massive) store
+    inlined = False
diff --git a/cubicweb_eac/sobjects.py b/cubicweb_eac/sobjects.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/sobjects.py
@@ -0,0 +1,119 @@
+# copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# 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-eac server objects"""
+
+from six import text_type
+
+from cubicweb import ValidationError
+from cubicweb.predicates import match_user_groups
+from cubicweb.server import Service
+from cubicweb.dataimport import RQLObjectStore
+from cubicweb.dataimport.importer import ExtEntitiesImporter, cwuri2eid
+
+from cubes.skos import to_unicode
+
+from cubicweb_eac import dataimport
+
+
+def init_extid2eid_index(cnx, source):
+    """Return the dictionary mapping external id to eid for entities already in the database."""
+    # only consider the system source for EAC related entity types
+    extid2eid = cwuri2eid(cnx, dataimport.ETYPES_ORDER_HINT, source_eid=source.eid)
+    # though concepts may come from any source
+    extid2eid.update(cwuri2eid(cnx, ('Concept',)))
+    return extid2eid
+
+
+class EACImportService(Service):
+    """Service to import an AuthorityRecord from an EAC XML file."""
+
+    __regid__ = 'eac.import'
+    __select__ = match_user_groups('managers', 'users')
+
+    def call(self, stream, import_log, **kwargs):
+        store = RQLObjectStore(self._cw)
+        try:
+            created, updated, record_eid, not_visited = self.import_eac_stream(
+                stream, import_log, store, **kwargs)
+        except Exception as exc:
+            self._cw.rollback()
+            raise
+        else:
+            try:
+                self._cw.commit()
+            except ValidationError as exc:
+                import_log.record_error('validation error: ' + to_unicode(exc))
+                raise
+            else:
+                import_log.record_info('%s entities created' % len(created))
+                import_log.record_info('%s entities updated' % len(updated))
+        msg = self._cw._('element {0} not parsed')
+        for tagname, sourceline in not_visited:
+            import_log.record_warning(msg.format(tagname, sourceline), line=sourceline)
+        return created, updated, record_eid
+
+    def external_entities_generator(self, stream, import_log):
+        return dataimport.EACCPFImporter(stream, import_log, self._cw._)
+
+    def import_eac_stream(self, stream, import_log, store, extid2eid=None, **kwargs):
+        try:
+            return self._import_eac_stream(
+                stream, import_log, store, extid2eid=extid2eid, **kwargs)
+        except Exception as exc:
+            import_log.record_fatal(to_unicode(exc))
+            raise
+
+    def _import_eac_stream(self, stream, import_log, store, extid2eid=None, **kwargs):
+        source = self._cw.repo.system_source
+        if extid2eid is None:
+            extid2eid = init_extid2eid_index(self._cw, source)
+        importer = ExtEntitiesImporter(self._cw.vreg.schema, store,
+                                       import_log=import_log,
+                                       extid2eid=extid2eid,
+                                       etypes_order_hint=dataimport.ETYPES_ORDER_HINT,
+                                       **kwargs)
+        generator = self.external_entities_generator(stream, import_log)
+        extentities = self.external_entities_stream(generator.external_entities(), extid2eid)
+        importer.import_entities(extentities)
+        if generator.record is not None:
+            extid = generator.record.extid
+            record_eid = importer.extid2eid[extid]
+        else:
+            record_eid = None
+        return importer.created, importer.updated, record_eid, generator.not_visited()
+
+    def external_entities_stream(self, extentities, extid2eid):
+        """Extracted method allowing to plug transformation into the external entities generator.
+        """
+        extentities = (ee for ee in extentities
+                       if not (ee.etype == 'ExternalUri' and ee.extid in extid2eid))
+
+        def handle_agent_kind(extentities):
+            """Create agent kind when necessary and remove them from the entity stream, allowing to
+            set cwuri properly without attempt to update.
+            """
+            for extentity in extentities:
+                if extentity.etype == 'AgentKind':
+                    if extentity.extid not in extid2eid:
+                        name = next(iter(extentity.values['name']))
+                        kind = self._cw.create_entity('AgentKind', name=name,
+                                                      cwuri=text_type(extentity.extid))
+                        extid2eid[extentity.extid] = kind.eid
+                else:
+                    yield extentity
+
+        extentities = handle_agent_kind(extentities)
+        return extentities
diff --git a/cubicweb_eac/testutils.py b/cubicweb_eac/testutils.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/testutils.py
@@ -0,0 +1,66 @@
+# copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# 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-eac common test tools"""
+
+from __future__ import print_function
+
+from os.path import basename
+
+from doctest import Example
+
+from lxml import etree
+from lxml.doctestcompare import LXMLOutputChecker
+
+from cubicweb.dataimport.importer import SimpleImportLog
+
+
+def authority_record(cnx, name, kind=u'person', **kwargs):
+    """Return an AuthorityRecord with specified kind and name."""
+    kind_eid = cnx.find('AgentKind', name=kind)[0][0]
+    if 'reverse_name_entry_for' not in kwargs:
+        kwargs['reverse_name_entry_for'] = cnx.create_entity(
+            'NameEntry', parts=name, form_variant=u'authorized')
+    return cnx.create_entity('AuthorityRecord', agent_kind=kind_eid, **kwargs)
+
+
+def eac_import(cnx, fpath):
+    import_log = SimpleImportLog(basename(fpath))
+    created, updated, _ = cnx.call_service(
+        'eac.import', stream=fpath, import_log=import_log,
+        raise_on_error=True)
+    return created, updated
+
+
+class XmlTestMixin(object):
+    """Mixin class provinding additional assertion methods for checking XML data."""
+
+    def assertXmlEqual(self, actual, expected):
+        """Check that both XML strings represent the same XML tree."""
+        checker = LXMLOutputChecker()
+        if not checker.check_output(expected, actual, 0):
+            message = checker.output_difference(Example("", expected), actual, 0)
+            self.fail(message)
+
+    def assertXmlValid(self, xml_data, xsd_filename, debug=False):
+        """Validate an XML file (.xml) according to an XML schema (.xsd)."""
+        with open(xsd_filename) as xsd:
+            xmlschema = etree.XMLSchema(etree.parse(xsd))
+        # Pretty-print xml_data to get meaningful line information.
+        xml_data = etree.tostring(etree.fromstring(xml_data), pretty_print=True)
+        xml_data = etree.fromstring(xml_data)
+        if debug and not xmlschema.validate(xml_data):
+            print(etree.tostring(xml_data, pretty_print=True))
+        xmlschema.assertValid(xml_data)
diff --git a/cubicweb_eac/views.py b/cubicweb_eac/views.py
new file mode 100644
--- /dev/null
+++ b/cubicweb_eac/views.py
@@ -0,0 +1,242 @@
+# copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact at logilab.fr
+#
+# 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-eac views, mostly for import/export of AuthorityRecord from/to EAC.
+
+Using only this cube, UI will be hardly usable to handle the complexity of the EAC model. You'll
+find an UI implementation for EAC as part of the `saem_ref`_ cube.
+
+.. _`saem_ref`: https://www.cubicweb.org/project/cubicweb-saem_ref
+"""
+
+from functools import partial
+import os.path
+
+from six import text_type
+
+from cubicweb import tags, _
+from cubicweb.view import View
+from cubicweb.predicates import is_instance, match_user_groups, one_line_rset, score_entity
+from cubicweb.dataimport.importer import HTMLImportLog
+from cubicweb.web import action, component, formfields as ff, formwidgets as fw, httpcache
+from cubicweb.web.views import actions, calendar, cwsources, forms, idownloadable, uicfg
+
+from cubes.skos import to_unicode
+
+from cubicweb_eac import dataimport
+
+
+pvdc = uicfg.primaryview_display_ctrl
+afs = uicfg.autoform_section
+affk = uicfg.autoform_field_kwargs
+
+
+# Edit / View setup
+
+actions.CopyAction.__select__ &= ~is_instance('AuthorityRecord')
+
+pvdc.tag_subject_of(('AuthorityRecord', 'agent_kind', '*'), {'vid': 'text'})
+
+for etype, attr in (
+    ('AuthorityRecord', 'isni'),
+    ('AgentPlace', 'name'),
+    ('AgentPlace', 'role'),
+    ('Activity', 'type'),
+    ('Mandate', 'term'),
+    ('LegalStatus', 'term'),
+    ('EACResourceRelation', 'agent_role'),
+    ('EACResourceRelation', 'resource_role'),
+    ('EACSource', 'title'),
+    ('EACSource', 'url'),
+):
+    affk.set_field_kwargs(etype, attr, widget=fw.TextInput({'size': 80}))
+
+
+affk.set_field_kwargs('NameEntry', 'form_variant', value=u'authorized')
+
+
+def unrelated_authorityrecord(rtype, form, field, **kwargs):
+    """Choices function returning AuthorityRecord choices.
+
+    Choices values are unrelated through `rtype` to edited entity thus
+    filtering out possible ExternalURI targets. It also account for __linkto
+    info to build a restricted choice when needed.
+    """
+    try:
+        linkto = form.linked_to[rtype, 'subject'][0]
+    except (KeyError, IndexError):
+        rset = form.edited_entity.unrelated(rtype, targettype='AuthorityRecord')
+        return [(x.dc_title(), text_type(x.eid)) for x in rset.entities()]
+    else:
+        entity = form._cw.entity_from_eid(linkto)
+        return [(entity.dc_title(), text_type(entity.eid))]
+
+
+# Set fields order for authority records relations (autoform and primary view)
+for etype, from_rdef, to_rdef in (
+    ('AssociationRelation', 'association_from', 'association_to'),
+    ('ChronologicalRelation', 'chronological_predecessor', 'chronological_successor'),
+    ('HierarchicalRelation', 'hierarchical_parent', 'hierarchical_child'),
+):
+    affk.set_fields_order(etype, ('description', 'start_date', 'end_date',
+                                  from_rdef, to_rdef))
+    for rtype in (from_rdef, to_rdef):
+        affk.tag_subject_of((etype, rtype, '*'),
+                            {'choices': partial(unrelated_authorityrecord, rtype)})
+
+    pvdc.set_fields_order(etype, ('description', 'start_date',
+                                  'end_date', from_rdef, to_rdef))
+
+
+class XMLWrapComponent(component.EntityCtxComponent):
+    """CtxComponent to display xml_wrap of entities."""
+    __select__ = (
+        component.EntityCtxComponent.__select__
+        & score_entity(lambda x: getattr(x, 'xml_wrap', None))
+    )
+    __regid__ = 'eac.xml_wrap'
+    context = 'navcontentbottom'
+    title = _('xml_wrap')
+
+    def render_body(self, w):
+        sourcemt, targetmt = 'text/xml', 'text/html'
+        data = self.entity.xml_wrap
+        w(self.entity._cw_mtc_transform(data.getvalue(),
+                                        sourcemt, targetmt, 'utf-8'))
+
+
+class AuthorityRecordICalendarableAdapter(calendar.ICalendarableAdapter):
+    """ICalendarable adapter for AuthorityRecord entity type."""
+    __select__ = calendar.ICalendarableAdapter.__select__ & is_instance('AuthorityRecord')
+
+    @property
+    def start(self):
+        return self.entity.start_date
+
+    @property
+    def stop(self):
+        return self.entity.end_date
+
+
+# Import
+
+class EACImportMixin(object):
+    __regid__ = 'eac.import'
+    __select__ = match_user_groups('managers', 'users')
+
+
+class EACImportForm(EACImportMixin, forms.FieldsForm):
+    """File import form for EAC-CPF"""
+    filefield = ff.FileField(name='file', label=_('EAC-CPF file'),
+                             required=True)
+    form_buttons = [fw.SubmitButton(label=_('do_import'))]
+
+    @property
+    def action(self):
+        return self._cw.build_url(vid=self.__regid__)
+
+
+class EACImportView(EACImportMixin, View):
+    """EAC-CPF import controller"""
+
+    def call(self):
+        self.w(tags.h1(self._cw._('Importing an AuthorityRecord from a EAC-CPF file')))
+        form = self._cw.vreg['forms'].select(self.__regid__, self._cw)
+        if form.posting:
+            posted = form.process_posted()
+            stream = posted['file']
+            import_log = HTMLImportLog(os.path.basename(stream.filename))
+            try:
+                _, _, eid = self._cw.cnx.call_service(self.__regid__, stream=stream,
+                                                      import_log=import_log,
+                                                      **self.service_kwargs(posted))
+            except dataimport.InvalidXML as exc:
+                msg = self._cw._('Invalid XML file')
+                self.exception('error while importing %s', stream.filename)
+                mtype = 'danger'
+                import_log.record_fatal(
+                    self._cw._('xml syntax error: ') + to_unicode(exc))
+                self._cw.status_out = 400
+            except dataimport.MissingTag as exc:
+                if exc.tag_parent:
+                    err = self._cw._('Missing tag %(tag)s within element %(parent)s in XML file')
+                    params = {'tag': exc.tag, 'parent': exc.tag_parent}
+                else:
+                    err = self._cw._('Missing tag %(tag)s in XML file')
+                    params = {'tag': exc.tag}
+                msg = err % params
+                self.exception('error while importing %s', stream.filename)
+                mtype = 'danger'
+                import_log.record_fatal(msg)
+                self._cw.status_out = 400
+            except Exception as exc:  # pylint: disable=broad-except
+                msg = self._cw._('EAC import failed')
+                self.exception('error while importing %s', stream.filename)
+                mtype = 'danger'
+                self._cw.status_out = 500
+            else:
+                record = self._cw.find('AuthorityRecord', eid=eid).one()
+                msg = (self._cw._('EAC-CPF import completed: %s') %
+                       record.view('oneline'))
+                mtype = 'success'
+            self.w(tags.div(msg, klass="alert alert-%s" % mtype))
+            if import_log.logs:
+                self._cw.view('cw.log.table',
+                              pyvalue=cwsources.log_to_table(
+                                  self._cw, u''.join(import_log.logs)),
+                              default_level='Warning', w=self.w)
+        else:
+            form.render(w=self.w)
+
+    def service_kwargs(self, posted):
+        """Subclass access point to provide extra arguments to the service (e.g. saem_ref cube).
+        """
+        return {}
+
+
+# Export
+
+class EACExportAction(action.Action):
+    __regid__ = 'eac.export'
+    __select__ = (action.Action.__select__
+                  & one_line_rset()
+                  & is_instance('AuthorityRecord'))
+
+    title = _('EAC export')
+    category = 'moreactions'
+
+    def url(self):
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        return entity.absolute_url(vid=self.__regid__)
+
+
+class EACDownloadView(idownloadable.DownloadView):
+    """EAC download view"""
+    __regid__ = 'eac.export'
+    __select__ = one_line_rset() & is_instance('AuthorityRecord')
+
+    http_cache_manager = httpcache.NoHTTPCacheManager
+    adapter_id = 'EAC-CPF'
+
+    def set_request_content_type(self):
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        adapter = entity.cw_adapt_to(self.adapter_id)
+        self._cw.set_content_type(adapter.content_type, filename=adapter.file_name,
+                                  encoding=adapter.encoding, disposition='attachment')
+
+    def call(self):
+        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+        adapter = entity.cw_adapt_to(self.adapter_id)
+        self.w(adapter.dump())
diff --git a/dataimport.py b/dataimport.py
deleted file mode 100644
--- a/dataimport.py
+++ /dev/null
@@ -1,915 +0,0 @@
-# copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# 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-eac dataimport utilities for EAC-CPF (Encoded Archival
-Context -- Corporate Bodies, Persons, and Families).
-"""
-
-from collections import deque
-import datetime
-from functools import wraps
-import inspect
-import logging
-from uuid import uuid4
-
-from six import text_type
-
-from dateutil.parser import parse as parse_date
-from lxml import etree
-
-from cubicweb.dataimport.importer import ExtEntity
-
-from cubes.skos import to_unicode
-
-from cubes.eac import TYPE_MAPPING, ADDRESS_MAPPING, MAINTENANCETYPE_MAPPING
-
-
-TYPE_MAPPING = TYPE_MAPPING.copy()
-TYPE_MAPPING['human'] = u'person'
-
-ETYPES_ORDER_HINT = ('AgentKind', 'PhoneNumber', 'PostalAddress', 'AuthorityRecord',
-                     'AgentPlace', 'Mandate', 'LegalStatus', 'History',
-                     'Structure', 'AgentFunction', 'Occupation', 'GeneralContext',
-                     'AssociationRelation', 'ChronologicalRelation', 'HierarchicalRelation',
-                     'EACResourceRelation', 'ExternalUri', 'EACSource',
-                     'Activity')
-
-
-class InvalidEAC(RuntimeError):
-    """EAC input is malformed."""
-
-
-class InvalidXML(RuntimeError):
-    """EAC input has an invalid XML format"""
-
-
-class MissingTag(RuntimeError):
-    """Mandatory tag is missing in EAC input"""
-
-    def __init__(self, tag, tag_parent=None):
-        super(MissingTag, self).__init__()
-        self.tag = tag
-        self.tag_parent = tag_parent
-
-
-def external_uri(uri):
-    values = [text_type(uri)]
-    return ExtEntity('ExternalUri', uri, {'uri': set(values), 'cwuri': set(values)})
-
-
-def filter_none(func):
-    """Filter None value from a generator function."""
-    def wrapped(*args, **kwargs):
-        for x in func(*args, **kwargs):
-            if x is not None:
-                yield x
-    return wraps(func)(wrapped)
-
-
-def filter_empty(func):
-    """Filter out empty ExtEntity (i.e. with empty ``values`` attribute)."""
-    @wraps(func)
-    def wrapper(self, *args, **kwargs):
-        for extentity in func(self, *args, **kwargs):
-            if extentity.values:
-                yield extentity
-    return wrapper
-
-
-def elem_maybe_none(func):
-    """Method decorator for external entity builder function handling the case
-    of `elem` being None.
-    """
-    if inspect.isgeneratorfunction(func):
-        def wrapped(self, elem, *args, **kwargs):
-            if elem is None:
-                return
-            for extentity in func(self, elem, *args, **kwargs):
-                yield extentity
-    else:
-        def wrapped(self, elem, *args, **kwargs):
-            if elem is None:
-                return None
-            else:
-                return func(self, elem, *args, **kwargs)
-    return wraps(func)(wrapped)
-
-
-def add_xml_wrap_for(*etypes):
-    """Add an `xml_wrap` attribute in ExtEntity's values dictionnary."""
-    def decorator(func):
-        def wrapped(self, elem):
-            objectXMLWrap = self._elem_find(elem, 'eac:objectXMLWrap')
-            xmlwrap = None
-            if objectXMLWrap is not None:
-                nchildren = len(objectXMLWrap)
-                if nchildren >= 1:
-                    xmlwrap = objectXMLWrap[0]
-                if nchildren > 1:
-                    msg = self._('multiple children elements found in {0}').format(
-                        objectXMLWrap)
-                    self.import_log.record_warning(msg, line=objectXMLWrap.line)
-            attribute_added = False
-            for extentity in func(self, elem):
-                if xmlwrap is not None and extentity.etype in etypes:
-                    # prevent association of xmlwrap to several extentities.
-                    assert not attribute_added, 'xml_wrap attribute already added'
-                    extentity.values.setdefault('xml_wrap', set([])).add(
-                        etree.tostring(xmlwrap, encoding='utf-8'))
-                    attribute_added = True
-                yield extentity
-        return wraps(func)(wrapped)
-    return decorator
-
-
-def relate_to_record_through(etype, rtype):
-    """Add an ``rtype`` relationship from ``etype`` to the imported record."""
-    def decorator(func):
-        if inspect.isgeneratorfunction(func):
-            def wrapper(self, *args, **kwargs):
-                for extentity in func(self, *args, **kwargs):
-                    if extentity.etype == etype:
-                        extentity.values.setdefault(rtype, set()).add(self.record.extid)
-                    yield extentity
-        else:
-            def wrapper(self, *args, **kwargs):
-                extentity = func(self, *args, **kwargs)
-                if extentity and extentity.etype == etype:
-                    extentity.values.setdefault(rtype, set()).add(self.record.extid)
-                return extentity
-        return wraps(func)(wrapper)
-    return decorator
-
-
-def add_citations_for(etype):
-    """Handle import of citation tag for `etype` ExtEntity that is yielded by
-    decorated method.
-    """
-    def decorator(func):
-        @wraps(func)
-        def wrapper(self, elem):
-            for extentity in func(self, elem):
-                if extentity.etype == etype:
-                    for citation in self.build_citation(elem):
-                        extentity.values.setdefault(
-                            'has_citation', set()).add(citation.extid)
-                        yield citation
-                yield extentity
-        return wrapper
-    return decorator
-
-
-def require_tag(tagname):
-    """Method decorator handling a mandatory tag within a XML element."""
-    def warn(self, elem):
-        self.import_log.record_warning(
-            self._('expecting a %s tag in element %s, found none') %
-            (tagname, elem.tag), line=elem.sourceline)
-
-    def decorator(func):
-        # pylint: disable=protected-access
-        if inspect.isgeneratorfunction(func):
-            def wrapped(self, elem, *args, **kwargs):
-                if self._elem_find(elem, tagname) is None:
-                    warn(self, elem)
-                    return
-                for extentity in func(self, elem, *args, **kwargs):
-                    yield extentity
-        else:
-            def wrapped(self, elem, *args, **kwargs):
-                if self._elem_find(elem, tagname) is None:
-                    warn(self, elem)
-                    return None
-                return func(self, elem, *args, **kwargs)
-        return wraps(func)(wrapped)
-
-    return decorator
-
-
-def trace_extentity(instance):
-    """Decorator for `build_` methods tracing ExtEntities built from a given
-    XML element.
-    """
-    def decorator(func):
-        if inspect.isgeneratorfunction(func):
-            def wrapper(elem, *args, **kwargs):
-                for extentity in func(elem, *args, **kwargs):
-                    instance.record_visited(elem, extentity)
-                    yield extentity
-        else:
-            def wrapper(elem, *args, **kwargs):
-                extentity = func(elem, *args, **kwargs)
-                if extentity is not None:
-                    instance.record_visited(elem, extentity)
-                return extentity
-        return wraps(func)(wrapper)
-    return decorator
-
-
-def equivalent_concept(tagname, etype):
-    """Method decorator indicating that a sub-tag may have a vocabularySource attribute indicating
-    that a relation to some ExternalUri object should be drown from any entity of type `etype` built
-    by decorated method.
-    """
-    def decorator(func):
-        @wraps(func)
-        def wrapped(self, elem, *args, **kwargs):
-            subelem = self._elem_find(elem, tagname)
-            if subelem is not None:
-                extid = subelem.attrib.get('vocabularySource')
-                if extid is not None:
-                    yield external_uri(extid)
-            else:
-                extid = None
-
-            def update_extentity(extentity):
-                if extid is not None and extentity.etype == etype:
-                    extentity.values['equivalent_concept'] = set([extid])
-
-            if inspect.isgeneratorfunction(func):
-                for extentity in func(self, elem, *args, **kwargs):
-                    update_extentity(extentity)
-                    yield extentity
-            else:
-                extentity = func(self, elem, *args, **kwargs)
-                update_extentity(extentity)
-                yield extentity
-        return wrapped
-    return decorator
-
-
-class EACCPFImporter(object):
-    """Importer for EAC-CPF data.
-
-    The importer will generate `extid`s using the `extid_generator` function
-    if specified or use `uuid.uuid4` to generate unique `extid`s.
-
-    During import the `record` attribute is set to the external entity of the
-    imported AuthorityRecord.
-
-    Ref: http://eac.staatsbibliothek-berlin.de/fileadmin/user_upload/schema/cpfTagLibrary.html
-    """
-    def __init__(self, fpath, import_log, _=text_type, extid_generator=None):
-        try:
-            tree = etree.parse(fpath)
-        except etree.XMLSyntaxError as exc:
-            raise InvalidXML(str(exc))
-        self._ = _
-        self._root = tree.getroot()
-        self.namespaces = self._root.nsmap.copy()
-        # remove default namespaces, not supported by .xpath method we'll use later
-        self.namespaces.pop(None, None)
-        self.namespaces['eac'] = 'urn:isbn:1-931666-33-4'
-        self.namespaces.setdefault('xlink', 'http://www.w3.org/1999/xlink')
-        self.import_log = import_log
-        if extid_generator is None:
-            def extid_generator():
-                return str(uuid4())
-        self._gen_extid = extid_generator
-        self.record = ExtEntity('AuthorityRecord', None, {})
-        # Store a mapping of XML elements to produced ExtEntities
-        self._visited = {}
-
-    def __getattribute__(self, name):
-        attr = super(EACCPFImporter, self).__getattribute__(name)
-        if name.startswith('build_'):
-            return trace_extentity(self)(attr)
-        return attr
-
-    def record_visited(self, elem, extentity):
-        assert extentity.extid, extentity
-        self._visited.setdefault(elem, set([])).add(extentity.extid)
-
-    def not_visited(self):
-        """Yield (tagname, sourceline) items corresponding to XML elements not
-        used to build any ExtEntity.
-        """
-        visited = self._visited
-        ns = self.namespaces['eac']
-        queue = deque(self._root)
-        # These elements contain other ones which are known to be handled.
-        container_tags = ['control', 'cpfDescription', 'identity',
-                          'maintenanceHistory', 'sources', 'description',
-                          'mandates', 'places', 'legalStatuses', 'occupations', 'relations']
-        containers = ['{{{0}}}{1}'.format(ns, tag) for tag in container_tags]
-        while queue:
-            elem = queue.popleft()
-            if (not isinstance(elem, etree._Element) or
-                    isinstance(elem, etree._Comment)):
-                continue
-            if elem in visited:
-                continue
-            if elem.tag not in containers:
-                yield elem.tag.replace('{' + ns + '}', ''), elem.sourceline
-            else:
-                queue.extend(elem)
-
-    def _elem_find(self, elem, path, method='find'):
-        """Wrapper around lxml.etree.Element find* methods to support
-        namespaces also for old lxml versions.
-        """
-        finder = getattr(elem, method)
-        try:
-            return finder(path, self.namespaces)
-        except TypeError:
-            # In old lxml, find() does not accept namespaces argument.
-            path = path.split(':', 1)
-            try:
-                ns, path = path
-            except ValueError:
-                # path has no namespace
-                pass
-            else:
-                path = '{' + self.namespaces[ns] + '}' + path
-            return finder(path)
-
-    def _elem_findall(self, *args):
-        return self._elem_find(*args, method='findall')
-
-    @filter_empty
-    def external_entities(self):
-        """Parse a EAC XML file to and yield external entities."""
-        # control element.
-        control = self._elem_find(self._root, 'eac:control')
-        if control is None:
-            raise MissingTag('control')
-        for extentity in self.parse_control(control):
-            yield extentity
-        # Records (identity tags) are within cpfDescription tag.
-        cpf_desc = self._elem_find(self._root, 'eac:cpfDescription')
-        if cpf_desc is None:
-            raise MissingTag('cpfDescription')
-        # identity element.
-        identity = self._elem_find(cpf_desc, 'eac:identity')
-        if identity is None:
-            raise MissingTag('identity', 'cpfDescription')
-        for extentity in self.parse_identity(identity):
-            yield extentity
-        # description element.
-        description = self._elem_find(cpf_desc, 'eac:description')
-        if description is not None:
-            for extentity in self.parse_description(description):
-                yield extentity
-        # relations element.
-        for extentity in self.parse_relations(cpf_desc):
-            yield extentity
-        # Record is complete.
-        self.record_visited(self._root, self.record)
-        yield self.record
-
-    def parse_identity(self, identity):
-        """Parse the `identity` tag and yield external entities, possibly
-        updating record's `values` dict.
-        """
-        # entityId
-        isni = self._elem_find(identity, 'eac:entityId')
-        if isni is not None and isni.text:
-            self.record_visited(isni, self.record)
-            self.record.values['isni'] = set([text_type(isni.text)])
-        # entityType
-        akind = self._elem_find(identity, 'eac:entityType')
-        if akind is None:
-            raise MissingTag('entityType', 'identity')
-        agent_kind = self.build_agent_kind(akind)
-        yield agent_kind
-        self.record.values['agent_kind'] = set([agent_kind.extid])
-        name_entry = None
-        name_entries = self._elem_findall(identity, 'eac:nameEntry')
-        if not name_entries:
-            raise MissingTag('nameEntry', 'identity')
-        for name_entry in name_entries:
-            yield self.build_name_entry(name_entry)
-
-    @filter_none
-    def parse_description(self, description):
-        """Parse the `description` tag and yield external entities, possibly
-        updating record's `values` dict.
-        """
-        # dates.
-        daterange = description.xpath('eac:existDates/eac:dateRange',
-                                      namespaces=self.namespaces)
-        if daterange:
-            elem = daterange[0]
-            self.record_visited(elem, self.record)
-            self.record_visited(elem.getparent(), self.record)
-            dates = self.parse_daterange(elem)
-            if dates:
-                self.record.values.update(dates)
-        # address.
-        for place in self.find_nested(description, 'eac:place', 'eac:places'):
-            for extentity in self.build_place(place):
-                yield extentity
-        # additional EAC-CPF information.
-        for legal_status in self.find_nested(description, 'eac:legalStatus', 'eac:legalStatuses'):
-            for extentity in self.build_legal_status(legal_status):
-                yield extentity
-        # mandate
-        for mandate in self.find_nested(description, 'eac:mandate', 'eac:mandates'):
-            for extentity in self.build_mandate(mandate):
-                yield extentity
-        # history
-        for history in self._elem_findall(description, 'eac:biogHist'):
-            for extentity in self.build_history(history):
-                yield extentity
-        # structure
-        for structure in self._elem_findall(description, 'eac:structureOrGenealogy'):
-            yield self.build_structure(structure)
-        # function
-        for function in self.find_nested(description, 'eac:function', 'eac:functions'):
-            for extentity in self.build_function(function):
-                yield extentity
-        # occupation
-        for occupation in self.find_nested(description, 'eac:occupation', 'eac:occupations'):
-            for extentity in self.build_occupation(occupation):
-                yield extentity
-        # general context
-        for context in self._elem_findall(description, 'eac:generalContext'):
-            for extentity in self.build_generalcontext(context):
-                yield extentity
-
-    def find_nested(self, elem, tagname, innertag):
-        """Return a list of element with `tagname` within `element` possibly
-        nested within `innertag`.
-        """
-        all_elems = self._elem_findall(elem, tagname)
-        wrapper = self._elem_find(elem, innertag)
-        if wrapper is not None:
-            all_elems.extend(self._elem_findall(wrapper, tagname))
-        return all_elems
-
-    def parse_tag_description(self, elem, tagname='eac:descriptiveNote',
-                              attrname='description'):
-        """Return a dict with `attrname` and `attrname`_format retrieved from
-        a description-like tag.
-        """
-        elems = self._elem_findall(elem, tagname)
-        if len(elems) > 1:
-            self.import_log.record_warning(self._(
-                'found multiple %s tag within %s element, only one will be '
-                'used.') % (tagname, elem.tag), line=elem.sourceline)
-        elem = elems[0] if elems else None
-        values = {}
-        if elem is not None:
-            parsed = self.parse_tag_content(elem)
-            values.update(zip((attrname, attrname + '_format'),
-                              (set([p]) for p in parsed)))
-        return values
-
-    def parse_tag_content(self, elem):
-        """Parse the content of an element be it plain text or HTML and return
-        the content along with MIME type.
-        """
-        assert elem is not None, 'unexpected empty element'
-        text = elem.text.strip() if elem.text else None
-        if text:
-            desc, desc_format = text_type(text), u'text/plain'
-        else:
-            ptag = '{%(eac)s}p' % self.namespaces
-            desc = '\n'.join(etree.tostring(child, encoding=text_type,
-                                            method='html').strip()
-                             for child in elem.iterchildren(ptag)
-                             if len(child) != 0 or child.text)
-            if desc:
-                desc_format = u'text/html'
-            else:
-                self.import_log.record_warning(self._(
-                    'element %s has no text nor children, no content '
-                    'extracted') % elem.tag, line=elem.sourceline)
-                desc, desc_format = None, None
-        return desc, desc_format
-
-    @relate_to_record_through('NameEntry', 'name_entry_for')
-    def build_name_entry(self, element):
-        """Build a NameEntry external entity."""
-        self.record_visited(element, self.record)
-        parts = self._elem_findall(element, 'eac:part')
-        if not parts:
-            raise MissingTag('part', 'nameEntry')
-        # Join all "part" tags into a single "parts" attribute.
-        values = {'parts': set([u', '.join(text_type(p.text) for p in parts)])}
-        # Consider first authorizedForm and then alternativeForm, missing
-        # possible combinations which cannot be handled until the model is
-        # complete.
-        if self._elem_find(element, 'eac:authorizedForm') is not None:
-            values['form_variant'] = set([u'authorized'])
-        elif self._elem_find(element, 'eac:alternativeForm') is not None:
-            values['form_variant'] = set([u'alternative'])
-        return ExtEntity('NameEntry', self._gen_extid(), values)
-
-    @elem_maybe_none
-    def build_agent_kind(self, elem):
-        """Build a AgentKind external entity"""
-        # Map EAC entity types to our terminolgy.
-        kind = TYPE_MAPPING.get(elem.text, elem.text)
-        agentkind_id = 'agentkind/' + kind
-        return ExtEntity('AgentKind', agentkind_id, {'name': set([text_type(kind)])})
-
-    @elem_maybe_none
-    @relate_to_record_through('LegalStatus', 'legal_status_agent')
-    @filter_empty
-    @add_citations_for('LegalStatus')
-    @equivalent_concept('eac:term', 'LegalStatus')
-    def build_legal_status(self, elem, **kwargs):
-        """Build a `LegalStatus` external entity.
-
-        Extra `kwargs` are passed to `parse_tag_description`.
-        """
-        values = self.parse_tag_description(elem, **kwargs)
-        term = self._elem_find(elem, 'eac:term')
-        if term is not None and term.text:
-            values['term'] = set([text_type(term.text)])
-        dateelem = self._elem_find(elem, 'eac:dateRange')
-        dates = self.parse_daterange(dateelem)
-        if dates:
-            values.update(dates)
-        legalstatus = ExtEntity('LegalStatus', self._gen_extid(), values)
-        if dateelem is not None:
-            self.record_visited(dateelem, legalstatus)
-        yield legalstatus
-
-    @elem_maybe_none
-    @relate_to_record_through('Mandate', 'mandate_agent')
-    @filter_empty
-    @add_citations_for('Mandate')
-    @equivalent_concept('eac:term', 'Mandate')
-    def build_mandate(self, elem, **kwargs):
-        """Build a `Mandate` external entity.
-
-        Extra `kwargs` are passed to `parse_tag_description`.
-        """
-        values = self.parse_tag_description(elem, **kwargs)
-        term = self._elem_find(elem, 'eac:term')
-        if term is not None and term.text:
-            values['term'] = set([text_type(term.text)])
-        dateelem = self._elem_find(elem, 'eac:dateRange')
-        dates = self.parse_daterange(dateelem)
-        if dates:
-            values.update(dates)
-        yield ExtEntity('Mandate', self._gen_extid(), values)
-
-    @elem_maybe_none
-    def build_citation(self, elem):
-        """Build a `Citation` external entity."""
-        for citation_elem in self._elem_findall(elem, "eac:citation"):
-            note = citation_elem.text.strip() if citation_elem.text else u''
-            uri = citation_elem.attrib.get('{%(xlink)s}href' % self.namespaces)
-            if not note and not uri:
-                msg = self._('element {0} has no text nor (valid) link').format(
-                    etree.tostring(citation_elem))
-                self.import_log.record_warning(msg, line=citation_elem.sourceline)
-                return
-            values = {}
-            if uri:
-                values['uri'] = set([text_type(uri)])
-            if note:
-                values['note'] = set([text_type(note)])
-                if u'<span>' in note:
-                    values['note_format'] = set([u'text/html'])
-            yield ExtEntity('Citation', self._gen_extid(), values)
-
-    @relate_to_record_through('History', 'history_agent')
-    @add_citations_for('History')
-    @elem_maybe_none
-    def build_history(self, elem):
-        """Build a `History` external entity."""
-        desc, desc_format = self.parse_tag_content(elem)
-        if desc:
-            values = {'text': set([desc]),
-                      'text_format': set([desc_format])}
-            yield ExtEntity('History', self._gen_extid(), values)
-
-    @elem_maybe_none
-    @relate_to_record_through('Structure', 'structure_agent')
-    def build_structure(self, elem):
-        """Build a `Structure` external entity."""
-        desc, desc_format = self.parse_tag_content(elem)
-        if desc:
-            values = {'description': set([desc]),
-                      'description_format': set([desc_format])}
-            return ExtEntity('Structure', self._gen_extid(), values)
-
-    @relate_to_record_through('AgentPlace', 'place_agent')
-    @filter_empty
-    @add_citations_for('AgentPlace')
-    @equivalent_concept('eac:placeEntry', 'AgentPlace')
-    def build_place(self, elem):
-        """Build a AgentPlace external entity"""
-        values = {}
-        role = self._elem_find(elem, 'eac:placeRole')
-        if role is not None:
-            values['role'] = set([text_type(role.text)])
-        entry = self._elem_find(elem, 'eac:placeEntry')
-        if entry is not None:
-            values['name'] = set([text_type(entry.text)])
-        for address in self._elem_findall(elem, 'eac:address'):
-            for extentity in self.build_address(address):
-                if extentity.values:
-                    values['place_address'] = set([extentity.extid])
-                    yield extentity
-        yield ExtEntity('AgentPlace', self._gen_extid(), values)
-
-    def build_address(self, elem):
-        """Build `PostalAddress`s external entity"""
-        address_entity = {}
-        for line in self._elem_findall(elem, 'eac:addressLine'):
-            if 'localType' in line.attrib:
-                attr = dict(ADDRESS_MAPPING).get(line.attrib['localType'])
-                if attr:
-                    address_entity.setdefault(attr, set()).add(
-                        text_type(line.text))
-        yield ExtEntity('PostalAddress', self._gen_extid(), address_entity)
-
-    @relate_to_record_through('AgentFunction', 'function_agent')
-    @filter_empty
-    @add_citations_for('AgentFunction')
-    @equivalent_concept('eac:term', 'AgentFunction')
-    def build_function(self, elem):
-        """Build a `AgentFunction`s external entities"""
-        values = self.parse_tag_description(elem)
-        term = self._elem_find(elem, 'eac:term')
-        if term is not None:
-            values['name'] = set([text_type(term.text)])
-        yield ExtEntity('AgentFunction', self._gen_extid(), values)
-
-    @relate_to_record_through('Occupation', 'occupation_agent')
-    @filter_empty
-    @add_citations_for('Occupation')
-    @equivalent_concept('eac:term', 'Occupation')
-    def build_occupation(self, elem):
-        """Build a `Occupation`s external entities"""
-        values = self.parse_tag_description(elem)
-        term = self._elem_find(elem, 'eac:term')
-        if term is not None:
-            values['term'] = set([text_type(term.text)])
-        dateelem = self._elem_find(elem, 'eac:dateRange')
-        dates = self.parse_daterange(dateelem)
-        if dates:
-            values.update(dates)
-        yield ExtEntity('Occupation', self._gen_extid(), values)
-
-    @relate_to_record_through('GeneralContext', 'general_context_of')
-    @add_citations_for('GeneralContext')
-    def build_generalcontext(self, elem):
-        """Build a `GeneralContext` external entity"""
-        content, content_format = self.parse_tag_content(elem)
-        if content:
-            values = {'content': set([content]),
-                      'content_format': set([content_format])}
-            yield ExtEntity('GeneralContext', self._gen_extid(), values)
-
-    @elem_maybe_none
-    def parse_daterange(self, elem):
-        """Parse a `dateRange` tag and return a dict mapping `start_date` and
-        `end_date` to parsed date range.
-        """
-        values = {}
-        for eactag, attrname in zip(('eac:fromDate', 'eac:toDate'),
-                                    ('start_date', 'end_date')):
-            date = self.parse_date(self._elem_find(elem, eactag))
-            if date:
-                values[attrname] = set([date])
-        return values
-
-    @elem_maybe_none
-    def parse_date(self, elem):
-        """Parse a date-like element"""
-        def record_warning(msg):
-            self.import_log.record_warning(msg % {'e': etree.tostring(elem)},
-                                           line=elem.sourceline)
-        standard_date = elem.attrib.get('standardDate')
-        if standard_date:
-            date = standard_date
-        else:
-            for attr in ('notBefore', 'notAfter'):
-                if elem.attrib.get(attr):
-                    record_warning(self._('found an unsupported %s attribute in date '
-                                          'element %%(e)s') % attr)
-            # Using element's text.
-            date = elem.text
-        # Set a default value for month and day; the year should always be
-        # given.
-        default = datetime.datetime(9999, 1, 1)
-        try:
-            pdate = parse_date(date, default=default)
-        except ValueError as exc:
-            record_warning(self._('could not parse date %(e)s'))
-            return None
-        except Exception as exc:  # pylint: disable=broad-except
-            # Usually a bug in dateutil.parser.
-            record_warning(self._(
-                'unexpected error during parsing of date %%(e)s: %s') %
-                to_unicode(exc))
-            logger = logging.getLogger('cubes.eac')
-            logger.exception(self._('unhandled exception while parsing date %r'), date)
-            return None
-        else:
-            if pdate.year == default.year:
-                record_warning(self._('could not parse a year from date element %(e)s'))
-                return None
-            return pdate.date()
-
-    def parse_relations(self, cpf_description):
-        """Parse the `relations` tag and yield external entities, possibly
-        updating record's `values` dict.
-        """
-        relations = self._elem_find(cpf_description, 'eac:relations')
-        if relations is None:
-            return
-        # cpfRelation.
-        for cpfrel in self._elem_findall(relations, 'eac:cpfRelation'):
-            for extentity in self.build_relation(cpfrel):
-                yield extentity
-        # resourceRelation.
-        for rrel in self._elem_findall(relations, 'eac:resourceRelation'):
-            for extentity in self.build_resource_relation(rrel):
-                yield extentity
-
-    @add_xml_wrap_for('AssociationRelation', 'ChronologicalRelation',
-                      'HierarchicalRelation')
-    def build_relation(self, elem):
-        """Build a relation between records external entity (with proper type)."""
-        relationship = elem.attrib.get('cpfRelationType')
-        if relationship is None:
-            self.import_log.record_warning(self._(
-                'found no cpfRelationType attribute in element %s, defaulting '
-                'to associative') % etree.tostring(elem),
-                line=elem.sourceline)
-            relationship = 'associative'
-        try:
-            # "other_role" (resp. "agent_role") role designates the object of the relation (resp.
-            # the agent described in the EAC-CPF instance).
-            # See: http://eac.staatsbibliothek-berlin.de/fileadmin/user_upload/schema/cpfTagLibrary.html#cpfRelationType # noqa pylint: disable=line-too-long
-            # In case the EAC relation is not qualified, we assume the object is the "parent" (or
-            # oldest) in the relation.
-            etype, other_role, agent_role = {
-                'hierarchical': ('HierarchicalRelation',
-                                 'hierarchical_parent', 'hierarchical_child'),
-                'hierarchical-parent': ('HierarchicalRelation',
-                                        'hierarchical_parent', 'hierarchical_child'),
-                'hierarchical-child': ('HierarchicalRelation',
-                                       'hierarchical_child', 'hierarchical_parent'),
-                'temporal': ('ChronologicalRelation',
-                             'chronological_predecessor', 'chronological_successor'),
-                'temporal-earlier': ('ChronologicalRelation',
-                                     'chronological_predecessor', 'chronological_successor'),
-                'temporal-later': ('ChronologicalRelation',
-                                   'chronological_successor', 'chronological_predecessor'),
-                'associative': ('AssociationRelation',
-                                'association_to', 'association_from'),
-            }[relationship]
-        except KeyError:
-            self.import_log.record_warning(self._(
-                'unsupported cpfRelationType %s in element %s, skipping')
-                % (relationship, etree.tostring(elem)),
-                line=elem.sourceline)
-            return
-        obj_uri = elem.attrib.get('{%(xlink)s}href' % self.namespaces)
-        if obj_uri is None:
-            self.import_log.record_warning(self._('found a cpfRelation without any object (no '
-                                                  'xlink attribute), skipping'),
-                                           line=elem.sourceline)
-            return
-        yield external_uri(obj_uri)
-        values = {agent_role: set([self.record.extid]), other_role: set([obj_uri])}
-        rentry = self._elem_find(elem, 'eac:relationEntry')
-        if rentry is not None and rentry.text.strip():
-            values['entry'] = set([text_type(rentry.text)])
-        dates = self.parse_daterange(
-            self._elem_find(elem, 'eac:dateRange'))
-        if dates:
-            values.update(dates)
-        values.update(self.parse_tag_description(elem))
-        yield ExtEntity(etype, self._gen_extid(), values)
-
-    @add_xml_wrap_for('EACResourceRelation')
-    def build_resource_relation(self, elem):
-        """Build a `EACResourceRelation` external entity (along with
-        ExternalUri entities).
-        """
-        obj_uri = elem.attrib.get('{%(xlink)s}href' % self.namespaces)
-        if obj_uri is None:
-            self.import_log.record_warning(self._(
-                'found a resourceRelation without any object (no xlink '
-                'attribute), skipping'), line=elem.sourceline)
-            return
-        yield external_uri(obj_uri)
-        values = {
-            'resource_relation_resource': set([obj_uri]),
-            'resource_relation_agent': set([self.record.extid]),
-        }
-        resource_role = elem.attrib.get('{%(xlink)s}role' % self.namespaces)
-        if resource_role:
-            values['resource_role'] = set([text_type(resource_role)])
-        agent_role = elem.attrib.get('resourceRelationType')
-        if agent_role:
-            values['agent_role'] = set([text_type(agent_role)])
-        dates = self.parse_daterange(self._elem_find(elem, 'eac:dateRange'))
-        if dates:
-            values.update(dates)
-        values.update(self.parse_tag_description(elem))
-        yield ExtEntity('EACResourceRelation', self._gen_extid(), values)
-
-    @filter_none
-    def parse_control(self, control):
-        """Parse the `control` tag."""
-        record_id = self._elem_find(control, 'eac:recordId')
-        if record_id is not None and record_id.text and record_id.text.strip():
-            self.record.extid = record_id.text.strip()
-            self.record.values['record_id'] = set([to_unicode(record_id.text)])
-            self.record_visited(record_id, self.record)
-        else:
-            self.record.extid = self._gen_extid()
-            self.import_log.record_warning(self._(
-                'found no recordId element in control tag, using %s as cwuri') %
-                self.record.extid, line=control.sourceline)
-        for other_record_id in self._elem_findall(control, 'eac:otherRecordId'):
-            other_id = other_record_id.text.strip()
-            if other_id:
-                values = {'eac_other_record_id_of': set([self.record.extid]),
-                          'value': set([text_type(other_id)])}
-                if other_record_id.attrib.get('localType'):
-                    values['local_type'] = set([text_type(other_record_id.attrib['localType'])])
-                extentity = ExtEntity('EACOtherRecordId', self._gen_extid(), values)
-                self.record_visited(other_record_id, extentity)
-                yield extentity
-        for elem in control.xpath('eac:sources/eac:source',
-                                  namespaces=self.namespaces):
-            for extentity in self.build_source(elem):
-                yield extentity
-        for elem in control.xpath('eac:maintenanceHistory/eac:maintenanceEvent',
-                                  namespaces=self.namespaces):
-            for extentity in self.build_maintenance_event(elem):
-                yield extentity
-
-    def build_maintenance_event(self, elem):
-        """Parse a `maintenanceEvent` tag, yielding a prov:Activity external
-        entity along with necessary Records.
-        """
-        values = {'generated': set([self.record.extid])}
-        event_type = self.parse_event_type(self._elem_find(elem, 'eac:eventType'))
-        if event_type is not None:
-            values['type'] = set([event_type])
-        date = self._elem_find(elem, 'eac:eventDateTime')
-        if date is not None:
-            dtattr = date.attrib.get('standardDateTime')
-            if dtattr:
-                try:
-                    event_date = parse_date(dtattr)
-                except ValueError:
-                    self.import_log.record_warning(
-                        self._('could not parse date from %s') % etree.tostring(date),
-                        line=date.sourceline)
-                else:
-                    values['start'] = set([event_date])
-                    values['end'] = set([event_date])
-        values.update(self.parse_tag_description(elem, 'eac:eventDescription'))
-        agent = self._elem_find(elem, 'eac:agent')
-        if agent is not None and agent.text:
-            values['agent'] = set([text_type(agent.text)])
-        yield ExtEntity('Activity', self._gen_extid(), values)
-
-    @elem_maybe_none
-    def parse_event_type(self, elem):
-        """Parse an `eventType` element and try to match a prov:type to build a
-        prov:Activity.
-        """
-        event_type = elem.text.strip() if elem.text else None
-        if event_type:
-            type_mapping = MAINTENANCETYPE_MAPPING.copy()
-            type_mapping['derived'] = u'create'
-            type_mapping['updated'] = u'modify'
-            try:
-                event_type = type_mapping[event_type.lower()]
-            except KeyError:
-                self.import_log.record_warning(self._(
-                    'eventType %s does not match the PROV-O vocabulary, respective Activity will '
-                    'not have a `type` attribute set.') % event_type, line=elem.sourceline)
-                return None
-            return event_type
-
-    @relate_to_record_through('EACSource', 'source_agent')
-    @filter_empty
-    @add_xml_wrap_for('EACSource')
-    def build_source(self, elem):
-        """Parse a `source` tag, yielding EACSource external entities.
-        """
-        values = self.parse_tag_description(elem)
-        url = elem.attrib.get('{%(xlink)s}href' % self.namespaces)
-        if url is not None:
-            values['url'] = set([text_type(url)])
-        entry = self._elem_find(elem, 'eac:sourceEntry')
-        if entry is not None and entry.text:
-            values['title'] = set([text_type(entry.text)])
-        yield ExtEntity('EACSource', self._gen_extid(), values)
diff --git a/debian/control b/debian/control
--- a/debian/control
+++ b/debian/control
@@ -3,9 +3,10 @@ Section: web
 Priority: optional
 Maintainer: LOGILAB S.A. (Paris, FRANCE) <contact at logilab.fr>
 Build-Depends:
- debhelper (>= 7),
+ debhelper (>= 9),
  python (>= 2.6.5),
-Standards-Version: 3.9.3
+ python-setuptools,
+Standards-Version: 3.9.6
 X-Python-Version: >= 2.6
 
 Package: cubicweb-eac
diff --git a/entities.py b/entities.py
deleted file mode 100644
--- a/entities.py
+++ /dev/null
@@ -1,600 +0,0 @@
-# coding: utf-8
-# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# 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-eac entity's classes"""
-
-from functools import wraps
-
-from six import text_type
-
-from lxml import etree
-
-from logilab.common.date import ustrftime
-
-from cubicweb.predicates import is_instance
-from cubicweb.entities import AnyEntity, fetch_config
-from cubicweb.view import EntityAdapter
-
-from cubes.eac import TYPE_MAPPING, ADDRESS_MAPPING, MAINTENANCETYPE_MAPPING
-
-
-class AuthorityRecord(AnyEntity):
-    __regid__ = 'AuthorityRecord'
-    fetch_attrs, cw_fetch_order = fetch_config(('agent_kind', 'ark'))
-
-    def dc_title(self):
-        """A NameEntry of "authorized" form variant, if any; otherwise any
-        NameEntry.
-        """
-        rset = self._cw.find('NameEntry', name_entry_for=self,
-                             form_variant=u'authorized')
-        if not rset:
-            rset = self._cw.find('NameEntry', name_entry_for=self)
-        return next(rset.entities()).parts
-
-    @property
-    def kind(self):
-        """The kind of agent"""
-        return self.agent_kind[0].name
-
-    @property
-    def printable_kind(self):
-        """The kind of agent, for display"""
-        return self.agent_kind[0].printable_value('name')
-
-    @property
-    def other_record_ids(self):
-        return dict((orec.local_type, orec.value) for orec in self.reverse_eac_other_record_id_of)
-
-
-class AgentKind(AnyEntity):
-    __regid__ = 'AgentKind'
-    fetch_attrs, cw_fetch_order = fetch_config(('name',))
-
-
-class ChronologicalRelation(AnyEntity):
-    __regid__ = 'ChronologicalRelation'
-
-    def dc_description(self):
-        if self.description:
-            return self.description
-
-
-class EACResourceRelation(AnyEntity):
-    __regid__ = 'EACResourceRelation'
-    fetch_attrs, cw_fetch_order = fetch_config(('agent_role', 'resource_role', 'description'))
-
-    @property
-    def record(self):
-        return self.resource_relation_agent[0]
-
-    @property
-    def resource(self):
-        return self.resource_relation_resource[0]
-
-    def dc_title(self):
-        agent_title = self.agent.dc_title()
-        if self.agent_role:
-            agent_title += u' (%s)' % self.printable_value('agent_role')
-        resource_title = self.resource.dc_title()
-        if self.resource_role:
-            resource_title += u' (%s)' % self.printable_value('resource_role')
-        return (self._cw._('Relation from %(from)s to %(to)s ') %
-                {'from': agent_title,
-                 'to': resource_title})
-
-
-class SameAsMixIn(object):
-    """Mix-in class for entity types supporting vocabulary_source and
-    equivalent_concept relations.
-    """
-
-    @property
-    def scheme(self):
-        return self.vocabulary_source and self.vocabulary_source[0] or None
-
-    @property
-    def concept(self):
-        return self.equivalent_concept and self.equivalent_concept[0] or None
-
-
-class AgentPlace(SameAsMixIn, AnyEntity):
-    __regid__ = 'AgentPlace'
-    fetch_attrs, cw_fetch_order = fetch_config(('name', 'role'))
-
-
-class AgentFunction(SameAsMixIn, AnyEntity):
-    __regid__ = 'AgentFunction'
-    fetch_attrs, cw_fetch_order = fetch_config(('name', 'description'))
-
-
-class Citation(SameAsMixIn, AnyEntity):
-    __regid__ = 'Citation'
-    fetch_attrs, cw_fetch_order = fetch_config(('uri', 'note'))
-
-
-class Mandate(SameAsMixIn, AnyEntity):
-    __regid__ = 'Mandate'
-    fetch_attrs, cw_fetch_order = fetch_config(('term', 'description'))
-
-
-class LegalStatus(SameAsMixIn, AnyEntity):
-    __regid__ = 'LegalStatus'
-
-
-class Occupation(SameAsMixIn, AnyEntity):
-    __regid__ = 'Occupation'
-    fetch_attrs, cw_fetch_order = fetch_config(('term', 'description'))
-
-
-class EACSource(AnyEntity):
-    __regid__ = 'EACSource'
-    fetch_attrs, cw_fetch_order = fetch_config(('title', 'url', 'description'))
-
-
-class EACOtherRecordId(AnyEntity):
-    __regid__ = 'EACOtherRecordId'
-    fetch_attrs, cw_fetch_order = fetch_config(('local_type', 'value'))
-
-
-# XML export
-
-def substitute_xml_prefix(prefix_name, namespaces):
-    """Given an XML prefixed name in the form `'ns:name'`, return the string `'{<ns_uri>}name'`
-    where `<ns_uri>` is the URI for the namespace prefix found in `namespaces`.
-
-    This new string is then suitable to build an LXML etree.Element object.
-
-    Example::
-
-        >>> substitude_xml_prefix('xlink:href', {'xlink': 'http://wwww.w3.org/1999/xlink'})
-        '{http://www.w3.org/1999/xlink}href'
-
-    """
-    try:
-        prefix, name = prefix_name.split(':', 1)
-    except ValueError:
-        return prefix_name
-    assert prefix in namespaces, 'Unknown namespace prefix: {0}'.format(prefix)
-    return '{{{0}}}'.format(namespaces[prefix]) + name
-
-
-class AbstractXmlAdapter(EntityAdapter):
-    """Abstract adapter to produce XML documents."""
-
-    content_type = 'text/xml'
-    encoding = 'utf-8'
-    namespaces = {}
-
-    @property
-    def file_name(self):
-        """Return a file name for the dump."""
-        raise NotImplementedError
-
-    def dump(self):
-        """Return an XML string for the adapted entity."""
-        raise NotImplementedError
-
-    def element(self, tag, parent=None, attributes=None, text=None):
-        """Generic function to build a XSD element tag.
-
-        Params:
-
-        * `name`, value for the 'name' attribute of the xsd:element
-
-        * `parent`, the parent etree node
-
-        * `attributes`, dictionary of attributes
-        """
-        attributes = attributes or {}
-        tag = substitute_xml_prefix(tag, self.namespaces)
-        for attr, value in attributes.items():
-            newattr = substitute_xml_prefix(attr, self.namespaces)
-            attributes[newattr] = value
-            if newattr != attr:
-                attributes.pop(attr)
-        if parent is None:
-            elt = etree.Element(tag, attributes, nsmap=self.namespaces)
-        else:
-            elt = etree.SubElement(parent, tag, attributes)
-        if text is not None:
-            elt.text = text
-        return elt
-
-    @staticmethod
-    def cwuri_url(entity):
-        """Return an absolute URL for entity's cwuri, necessary for one head ahead application
-        handling relative path in cwuri.
-        """
-        return entity.cwuri
-
-
-def add_object_xml_wrap(func):
-    """Add an objectXMLWrap sub-element to the element returned by `func`."""
-    def wrapper(self, entity, *args, **kwargs):
-        elem = func(self, entity, *args, **kwargs)
-        if entity.xml_wrap is not None:
-            objectXMLWrap = self.element('objectXMLWrap', parent=elem)
-            objectXMLWrap.append(etree.parse(entity.xml_wrap).getroot())
-        return elem
-    return wraps(func)(wrapper)
-
-
-def add_descriptive_note(func):
-    @wraps(func)
-    def wrapper(self, entity, *args, **kwargs):
-        element = func(self, entity, *args, **kwargs)
-        if element is not None and entity.description:
-            self.element('descriptiveNote', parent=element).extend(
-                self._eac_richstring_paragraph_elements(entity, "description"))
-        return element
-    return wrapper
-
-
-def add_citation(func):
-    @wraps(func)
-    def wrapper(self, entity, *args, **kwargs):
-        element = func(self, entity, *args, **kwargs)
-        for citation in entity.has_citation:
-            attrs = {'xlink:type': 'simple'}
-            if citation.uri:
-                attrs['xlink:href'] = citation.uri
-            self.element('citation', parent=element, attributes=attrs, text=citation.note)
-        return element
-    return wrapper
-
-
-class AuthorityRecordEACAdapter(AbstractXmlAdapter):
-    __regid__ = 'EAC-CPF'
-    __select__ = is_instance('AuthorityRecord')
-
-    namespaces = {
-        None: u'urn:isbn:1-931666-33-4',
-        u'xsi': u'http://www.w3.org/2001/XMLSchema-instance',
-        u'xlink': u'http://www.w3.org/1999/xlink',
-    }
-    datetime_fmt = '%Y-%m-%dT%H:%M:%S'
-
-    @property
-    def file_name(self):
-        """Return a file name for the dump."""
-        if self.entity.isni:
-            name = self.entity.isni.replace("/", "_")
-        else:
-            name = text_type(self.entity.eid)
-        return u'EAC_{0}.xml'.format(name)
-
-    def dump(self):
-        """Return an XML string representing the given agent using the EAC-CPF schema."""
-        # Keep related activities since they are used multiple times
-        self.activities = sorted(self.entity.reverse_generated, key=lambda x: x.start, reverse=True)
-        # Root element
-        eac_cpf_elt = self.element('eac-cpf', attributes={
-            'xsi:schemaLocation': ('urn:isbn:1-931666-33-4 '
-                                   'http://eac.staatsbibliothek-berlin.de/schema/cpf.xsd')
-        })
-        # Top elements: control & cpfDescription
-        self.control_element(eac_cpf_elt)
-        self.cpfdescription_element(eac_cpf_elt)
-        tree = etree.ElementTree(eac_cpf_elt)
-        return etree.tostring(tree, xml_declaration=True, encoding=self.encoding, pretty_print=True)
-
-    def control_element(self, eac_cpf_elt):
-        control_elt = self.element('control', parent=eac_cpf_elt)
-        record_id = self.entity.record_id
-        if record_id is None:
-            record_id = text_type(self.entity.eid)
-        self.element('recordId', parent=control_elt, text=record_id)
-        for local_type, value in sorted(self.entity.other_record_ids.items()):
-            attrs = {}
-            if local_type is not None:
-                attrs['localType'] = local_type
-            self.element('otherRecordId', parent=control_elt, attributes=attrs,
-                         text=text_type(value))
-        self.maintenance_status_element(control_elt)
-        self.publication_status_element(control_elt)
-        self.maintenance_agency_element(control_elt)
-        self.language_declaration_element(control_elt)
-        self.maintenance_history_element(control_elt)
-        self.sources_element(control_elt)
-
-    def cpfdescription_element(self, eac_cpf_elt):
-        cpfdescription_elt = self.element('cpfDescription', parent=eac_cpf_elt)
-        self.identity_element(cpfdescription_elt)
-        self.description_element(cpfdescription_elt)
-        self.relations_element(cpfdescription_elt)
-
-    def maintenance_status_element(self, control_elt):
-        if any(activity.type == 'modify' for activity in self.activities):
-            status = 'revised'
-        else:
-            status = 'new'
-        self.element('maintenanceStatus', parent=control_elt, text=status)
-
-    def publication_status_element(self, control_elt):
-        self.element('publicationStatus', parent=control_elt, text='inProcess')  # or approved?
-
-    def maintenance_agency_element(self, control_elt):
-        agency_elt = self.element('maintenanceAgency', parent=control_elt)
-        self.element('agencyName', parent=agency_elt)
-
-    def language_declaration_element(self, control_elt):
-        lang_decl_elt = self.element('languageDeclaration', parent=control_elt)
-        self.element('language', parent=lang_decl_elt,
-                     attributes={'languageCode': 'fre'}, text=u'français')
-        self.element('script', parent=lang_decl_elt, attributes={'scriptCode': 'Latn'},
-                     text='latin')
-
-    def maintenance_history_element(self, control_elt):
-        if self.activities:
-            history_elt = self.element('maintenanceHistory', parent=control_elt)
-            for activity in self.activities:
-                self.maintenance_event_element(activity, history_elt)
-
-    def sources_element(self, control_elt):
-        sources_elt = self.element('sources')
-        for eac_source in self.entity.reverse_source_agent:
-            sources_elt.append(self.source_element(eac_source))
-        if len(sources_elt):
-            control_elt.append(sources_elt)
-
-    def identity_element(self, cpfdescription_elt):
-        identity_elt = self.element('identity', parent=cpfdescription_elt)
-        self._elt_text_from_attr('entityId', self.entity, 'isni', parent=identity_elt)
-        type_mapping = dict((v, k) for k, v in TYPE_MAPPING.items())
-        eac_type = type_mapping.get(self.entity.kind)
-        self.element('entityType', parent=identity_elt, text=eac_type)
-        for name_entry in self.entity.reverse_name_entry_for:
-            elem = self.element('nameEntry', parent=identity_elt)
-            for part in name_entry.parts.split(u', '):
-                self.element('part', parent=elem, text=part)
-            if name_entry.form_variant == u'authorized':
-                self.element('authorizedForm', text=u'conventionDeclaration',
-                             parent=elem)
-            if name_entry.form_variant == u'alternative':
-                self.element('alternativeForm', text=u'conventionDeclaration',
-                             parent=elem)
-
-    def description_element(self, cpfdescription_elt):
-        agent = self.entity
-        description_elt = self.element('description', parent=cpfdescription_elt)
-        self.exist_dates_element(description_elt)
-        for relation, group_tagname, get_element in [
-            ('reverse_place_agent', 'places', self.place_element),
-            ('reverse_function_agent', 'functions', self.function_element),
-            ('reverse_legal_status_agent', 'legalStatuses', self.legal_status_element),
-            ('reverse_occupation_agent', 'occupations', self.occupation_element),
-            ('reverse_mandate_agent', 'mandates', self.mandate_element),
-        ]:
-            relateds = getattr(agent, relation)
-            if relateds:
-                group_elt = self.element(group_tagname, parent=description_elt)
-                for related in relateds:
-                    group_elt.append(get_element(related))
-        for structure in agent.reverse_structure_agent:
-            structure_elt = self.structure_element(structure)
-            if len(structure_elt):
-                description_elt.append(structure_elt)
-        for generalcontext in agent.reverse_general_context_of:
-            generalcontext_elt = self.generalcontext_element(generalcontext)
-            if len(generalcontext_elt):
-                description_elt.append(generalcontext_elt)
-        for history in agent.reverse_history_agent:
-            bioghist_elt = self.bioghist_element(history)
-            if len(bioghist_elt):
-                description_elt.append(bioghist_elt)
-
-    def relations_element(self, cpfdescription_elt):
-        relations_elt = self.element('relations')
-        for rtype, rel_rtype, eac_rtype in [
-            ('reverse_hierarchical_child', 'hierarchical_parent', 'hierarchical-parent'),
-            ('reverse_hierarchical_parent', 'hierarchical_child', 'hierarchical-child'),
-            ('reverse_chronological_successor', 'chronological_predecessor', 'temporal-earlier'),
-            ('reverse_chronological_predecessor', 'chronological_successor', 'temporal-later'),
-            ('reverse_association_to', 'association_from', 'associative'),
-            ('reverse_association_from', 'association_to', 'associative')
-        ]:
-            for relation in getattr(self.entity, rtype):
-                relations_elt.append(
-                    self.cpfrelation_element(relation, rel_rtype, eac_rtype))
-        for resource_relation in self.entity.reverse_resource_relation_agent:
-            relations_elt.append(
-                self.resource_relation_element(resource_relation))
-        if len(relations_elt):
-            cpfdescription_elt.append(relations_elt)
-
-    def maintenance_event_element(self, activity, history_elt):
-        event_elt = self.element('maintenanceEvent', parent=history_elt)
-        type_mapping = dict((v, k) for k, v in MAINTENANCETYPE_MAPPING.items())
-        activity_type = type_mapping.get(activity.type, 'created')
-        self.element('eventType', parent=event_elt, text=activity_type)
-        self.element('eventDateTime', parent=event_elt,
-                     attributes={'standardDateTime': ustrftime(activity.start,
-                                                               fmt=self.datetime_fmt)},
-                     text=ustrftime(activity.start, fmt=self.datetime_fmt))
-        self.agent_element(activity, event_elt)
-        self._elt_text_from_attr('eventDescription', activity, 'description', parent=event_elt)
-
-    @add_descriptive_note
-    @add_object_xml_wrap
-    def source_element(self, eac_source):
-        url = eac_source.url
-        attributes = {'xlink:href': url, 'xlink:type': 'simple'} if url else None
-        source_elt = self.element('source', attributes=attributes)
-        self.element('sourceEntry', parent=source_elt, text=eac_source.title)
-        return source_elt
-
-    def exist_dates_element(self, description_elt):
-        date_range = self._eac_date_range_xml_elt(self.entity.start_date, self.entity.end_date)
-        if date_range is not None:
-            exist_dates = self.element('existDates', parent=description_elt)
-            exist_dates.append(date_range)
-
-    @add_citation
-    def place_element(self, place):
-        place_elt = self.element('place')
-        for attr, eac_name in [('role', 'placeRole'), ('name', 'placeEntry')]:
-            eac_elt = self._elt_text_from_attr(eac_name, place, attr, parent=place_elt)
-            if eac_elt is not None and eac_name == 'placeEntry' and place.equivalent_concept:
-                eac_elt.attrib['vocabularySource'] = self.cwuri_url(place.equivalent_concept[0])
-        for address in place.place_address:
-            self.address_element(address, place_elt)
-        return place_elt
-
-    @add_descriptive_note
-    @add_citation
-    def function_element(self, function):
-        function_elt = self.element('function')
-        term_elt = self._elt_text_from_attr('term', function, 'name', parent=function_elt)
-        if term_elt is not None and function.equivalent_concept:
-            term_elt.attrib['vocabularySource'] = self.cwuri_url(function.equivalent_concept[0])
-        return function_elt
-
-    @add_descriptive_note
-    @add_citation
-    def legal_status_element(self, legal_status):
-        legal_status_elt = self.element('legalStatus')
-        self._elt_text_from_attr('term', legal_status, 'term', parent=legal_status_elt)
-        return legal_status_elt
-
-    @add_descriptive_note
-    @add_citation
-    def occupation_element(self, occupation):
-        occupation_elt = self.element('occupation')
-        term_elt = self._elt_text_from_attr('term', occupation, 'term', parent=occupation_elt)
-        if term_elt is not None and occupation.equivalent_concept:
-            term_elt.attrib['vocabularySource'] = self.cwuri_url(occupation.equivalent_concept[0])
-        self._eac_date_range_xml_elt(occupation.start_date, occupation.end_date,
-                                     parent=occupation_elt)
-        return occupation_elt
-
-    @add_descriptive_note
-    @add_citation
-    def mandate_element(self, mandate):
-        mandate_elt = self.element('mandate')
-        term_elt = self._elt_text_from_attr('term', mandate, 'term', parent=mandate_elt)
-        if term_elt is not None and mandate.equivalent_concept:
-            term_elt.attrib['vocabularySource'] = self.cwuri_url(mandate.equivalent_concept[0])
-        return mandate_elt
-
-    def structure_element(self, structure):
-        structure_elt = self.element('structureOrGenealogy')
-        if structure.description:
-            structure_elt.extend(self._eac_richstring_paragraph_elements(structure, "description"))
-        return structure_elt
-
-    @add_citation
-    def generalcontext_element(self, context):
-        context_elt = self.element('generalContext')
-        if context.content:
-            context_elt.extend(self._eac_richstring_paragraph_elements(context, 'content'))
-        return context_elt
-
-    @add_citation
-    def bioghist_element(self, history):
-        bioghist_elt = self.element('biogHist')
-        if history.text:
-            bioghist_elt.extend(self._eac_richstring_paragraph_elements(history, "text"))
-        return bioghist_elt
-
-    @add_descriptive_note
-    @add_object_xml_wrap
-    def cpfrelation_element(self, relation, cw_rtype, eac_rtype):
-        related = relation.related(cw_rtype).one()  # exactly one target (schema)
-        relation_elt = self.element('cpfRelation',
-                                    attributes={'cpfRelationType': eac_rtype,
-                                                'xlink:href': related.cwuri,
-                                                'xlink:type': 'simple'})
-        if relation.entry:
-            self.element('relationEntry', parent=relation_elt, text=relation.entry)
-        self._eac_date_range_xml_elt(getattr(relation, 'start_date', None),
-                                     getattr(relation, 'end_date', None),
-                                     parent=relation_elt)
-        return relation_elt
-
-    @add_object_xml_wrap
-    def resource_relation_element(self, resource_relation):
-        resource = resource_relation.resource_relation_resource[0]
-        attrs = {
-            'xlink:href': resource.uri,
-            'xlink:type': 'simple',
-        }
-        if resource_relation.resource_role:
-            attrs['xlink:role'] = resource_relation.resource_role
-        if resource_relation.agent_role:
-            attrs['resourceRelationType'] = resource_relation.agent_role
-        res_rel_elt = self.element('resourceRelation', attributes=attrs)
-        self._eac_date_range_xml_elt(resource_relation.start_date, resource_relation.end_date,
-                                     parent=res_rel_elt)
-        return res_rel_elt
-
-    def agent_element(self, activity, maintenance_event_elt):
-        if activity.agent:
-            self.element('agentType', maintenance_event_elt, text='human')
-            self.element('agent', maintenance_event_elt, text=activity.agent)
-        else:  # These tags must be present, even if name is empty
-            agent_type_elt = self.element('agentType', text='machine')
-            maintenance_event_elt.append(agent_type_elt)
-            maintenance_event_elt.append(self.element('agent'))
-
-    def address_element(self, address, place_elt):
-        address_elt = self.element('address', parent=place_elt)
-        for eac_name, attr in ADDRESS_MAPPING:
-            self._elt_text_from_attr('addressLine', address, attr,
-                                     attributes={'localType': eac_name}, parent=address_elt)
-
-    # helper methods for lxml
-
-    def _elt_text_from_attr(self, tag_name, entity, attr_name, parent=None, attributes=None):
-        """Return an lxml `Element` whose text is the value of the given attribute on the given
-        entity.
-
-        If this element is not empty and if ``parent`` is not ``None``, the element will also be
-        inserted in the parent XML element.
-
-        If ``attributes`` is not ``None``, these attributes will be added to the returned element.
-
-        Return ``None`` if element is empty.
-        """
-        value = getattr(entity, attr_name)
-        if value is not None:
-            elt = self.element(tag_name, parent=parent, attributes=attributes, text=value)
-            return elt
-
-    def _eac_date_range_xml_elt(self, start_date, end_date, parent=None):
-        """Return an EAC lxml ``'dateRange'`` ``Element`` with the given boundaries."""
-        if not start_date and not end_date:
-            return
-        date_range = self.element('dateRange', parent=parent)
-        for dt, eac_name in [(start_date, 'fromDate'), (end_date, 'toDate')]:
-            if not dt:
-                continue
-            self.element(eac_name, parent=date_range,
-                         attributes={'standardDate': dt.isoformat()}, text=dt.isoformat())
-        return date_range
-
-    def _eac_richstring_paragraph_elements(self, entity, attr_name):
-        fmt = getattr(entity, attr_name + "_format")
-        if fmt == "text/plain":
-            value = getattr(entity, attr_name)
-            if not value:
-                return []
-            return [self.element('p', text=value)]
-        else:
-            value = entity.printable_value(attr_name)
-            if not value:
-                return []
-            return list(etree.fromstring(u"<root>{0}</root>".format(value)))
diff --git a/hooks.py b/hooks.py
deleted file mode 100644
--- a/hooks.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# 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-eac specific hooks and operations"""
-
-from cubicweb.server import hook
-
-
-# ensure that equivalent_concept Concept has vocabulary_source defined ####################
-
-class EnsureVocabularySource(hook.Hook):
-    """When a equivalent_concept relation is set and targets a Concept, ensure that the
-    vocabulary_source relation is set to the concept's scheme. This should not be necessary when
-    using the UI where the workflow enforce setting the scheme first, but it's necessary during
-    e.g. EAC import.
-    """
-    __select__ = hook.Hook.__select__ & hook.match_rtype('equivalent_concept',
-                                                         toetypes=('Concept',))
-    __regid__ = 'eac.add_equivalent_concept'
-    events = ('after_add_relation', )
-
-    def __call__(self):
-        EnsureVocabularySourceOp.get_instance(self._cw).add_data((self.eidfrom, self.eidto))
-
-
-class EnsureVocabularySourceOp(hook.DataOperationMixIn, hook.Operation):
-    """Ensure X equivalent_concept target Concept as proper X vocabulary_source Scheme"""
-
-    def precommit_event(self):
-        cnx = self.cnx
-        for eid, concept_eid in self.get_data():
-            cnx.execute('SET X vocabulary_source SC WHERE NOT X vocabulary_source SC, '
-                        'C in_scheme SC, C eid %(c)s, X eid %(x)s', {'x': eid, 'c': concept_eid})
diff --git a/i18n/en.po b/i18n/en.po
deleted file mode 100644
--- a/i18n/en.po
+++ /dev/null
@@ -1,1477 +0,0 @@
-msgid ""
-msgstr ""
-"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
-"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"
-
-msgid "A source used to establish the description of an AuthorityRecord"
-msgstr ""
-
-msgid "AgentFunction"
-msgstr "Function"
-
-msgid "AgentFunction_plural"
-msgstr "Functions"
-
-msgid "AgentKind"
-msgstr "Agent kind"
-
-msgid "AgentKind_plural"
-msgstr "Agent kinds"
-
-msgid "AgentPlace"
-msgstr "Place"
-
-msgid "AgentPlace_plural"
-msgstr "Places"
-
-msgid "Association relation between authority records"
-msgstr ""
-
-msgid "AssociationRelation"
-msgstr "Association relation"
-
-msgid "AssociationRelation_plural"
-msgstr "Association relations"
-
-msgid "AuthorityRecord"
-msgstr ""
-
-msgid "AuthorityRecord_plural"
-msgstr ""
-
-msgid "Biographical or historical information"
-msgstr ""
-
-msgid "Chronological relation between authority records"
-msgstr ""
-
-msgid "ChronologicalRelation"
-msgstr "Chronological relation"
-
-msgid "ChronologicalRelation_plural"
-msgstr "Chronological relations"
-
-msgid "Citation"
-msgstr ""
-
-msgid "Citation_plural"
-msgstr "Citations"
-
-msgid "EAC export"
-msgstr ""
-
-msgid "EAC import failed"
-msgstr ""
-
-msgid "EAC-CPF file"
-msgstr ""
-
-#, python-format
-msgid "EAC-CPF import completed: %s"
-msgstr ""
-
-msgid "EACOtherRecordId"
-msgstr "Alternative identifier"
-
-msgid "EACOtherRecordId_plural"
-msgstr "Alternative identifiers"
-
-msgid "EACResourceRelation"
-msgstr "EAC resource relation"
-
-msgid "EACResourceRelation_plural"
-msgstr "EAC resource relations"
-
-msgid "EACSource"
-msgstr "Source"
-
-msgid "EACSource_plural"
-msgstr "Sources"
-
-msgid "GeneralContext"
-msgstr "General context"
-
-msgid "GeneralContext_plural"
-msgstr "General contexts"
-
-msgid "Hierarchical relation between authority records"
-msgstr ""
-
-msgid "HierarchicalRelation"
-msgstr "Hierarchical relation"
-
-msgid "HierarchicalRelation_plural"
-msgstr "Hierarchical relations"
-
-msgid "History"
-msgstr "Piece of historical information"
-
-msgid "History_plural"
-msgstr "Pieces of historical information"
-
-msgid "Importing an AuthorityRecord from a EAC-CPF file"
-msgstr ""
-
-msgid ""
-"Information about the general social and cultural context of an authority "
-"record"
-msgstr ""
-
-msgid "Information about the structure of an authority"
-msgstr ""
-
-msgid "Information relative to the legal status of an authority"
-msgstr ""
-
-msgid "International Standard Name Identifier"
-msgstr ""
-
-msgid "Invalid XML file"
-msgstr ""
-
-msgid ""
-"Kind of an authority record (e.g. \"person\", \"authority\" or \"family\")"
-msgstr ""
-
-msgid "LegalStatus"
-msgstr "Legal status"
-
-msgid "LegalStatus_plural"
-msgstr "Legal status"
-
-msgid "Mandate"
-msgstr ""
-
-msgid "Mandate_plural"
-msgstr "Mandates"
-
-#, python-format
-msgid "Missing tag %(tag)s in XML file"
-msgstr ""
-
-#, python-format
-msgid "Missing tag %(tag)s within element %(parent)s in XML file"
-msgstr ""
-
-msgid "NameEntry"
-msgstr "Name entry"
-
-msgid "NameEntry_plural"
-msgstr "Name entries"
-
-msgid "New AgentFunction"
-msgstr "New function"
-
-msgid "New AgentKind"
-msgstr "New agent kind"
-
-msgid "New AgentPlace"
-msgstr "New place"
-
-msgid "New AssociationRelation"
-msgstr "New association relation"
-
-msgid "New AuthorityRecord"
-msgstr ""
-
-msgid "New ChronologicalRelation"
-msgstr "New chronological relation"
-
-msgid "New Citation"
-msgstr "New citation"
-
-msgid "New EACOtherRecordId"
-msgstr "New alternative identifier"
-
-msgid "New EACResourceRelation"
-msgstr "New EAC resource relation"
-
-msgid "New EACSource"
-msgstr "New source"
-
-msgid "New GeneralContext"
-msgstr "New general context"
-
-msgid "New HierarchicalRelation"
-msgstr "New hierarchical relation"
-
-msgid "New History"
-msgstr "New piece of historical information"
-
-msgid "New LegalStatus"
-msgstr "New legal status"
-
-msgid "New Mandate"
-msgstr "New mandate"
-
-msgid "New NameEntry"
-msgstr "New name entry"
-
-msgid "New Occupation"
-msgstr "New occupation"
-
-msgid "New Structure"
-msgstr "New piece of structure information"
-
-msgid "Occupation"
-msgstr ""
-
-msgid "Occupation_plural"
-msgstr "Occupations"
-
-msgid "Qualified relation between an AuthorityRecord and a PostalAddress"
-msgstr ""
-
-msgid "Reference text coming from an authority"
-msgstr ""
-
-#, python-format
-msgid "Relation from %(from)s to %(to)s "
-msgstr ""
-
-msgid "Represent a nameEntry tag of an EAC-CPF document."
-msgstr ""
-
-msgid ""
-"Represent a relation between an AuthorityRecord and a remote resource in the "
-"EAC-CPF model."
-msgstr ""
-
-msgid "Structure"
-msgstr "Piece of Structure information"
-
-msgid "Structure_plural"
-msgstr "Pieces of structure information"
-
-msgid "The function of an AuthorityRecord"
-msgstr ""
-
-msgid "This AgentFunction"
-msgstr "This function"
-
-msgid "This AgentFunction:"
-msgstr "This function:"
-
-msgid "This AgentKind"
-msgstr "This agent kind"
-
-msgid "This AgentKind:"
-msgstr "This agent kind:"
-
-msgid "This AgentPlace"
-msgstr "This place"
-
-msgid "This AgentPlace:"
-msgstr "This place:"
-
-msgid "This AssociationRelation"
-msgstr "This association relation"
-
-msgid "This AssociationRelation:"
-msgstr "This association relation:"
-
-msgid "This AuthorityRecord"
-msgstr ""
-
-msgid "This AuthorityRecord:"
-msgstr ""
-
-msgid "This ChronologicalRelation"
-msgstr "This chronological relation"
-
-msgid "This ChronologicalRelation:"
-msgstr "This chronological relation:"
-
-msgid "This Citation"
-msgstr "This citation"
-
-msgid "This Citation:"
-msgstr "This citation:"
-
-msgid "This EACOtherRecordId"
-msgstr "This alternative identifier"
-
-msgid "This EACOtherRecordId:"
-msgstr "This alternative identifier:"
-
-msgid "This EACResourceRelation"
-msgstr ""
-
-msgid "This EACResourceRelation:"
-msgstr ""
-
-msgid "This EACSource"
-msgstr "This source"
-
-msgid "This EACSource:"
-msgstr "This source:"
-
-msgid "This GeneralContext"
-msgstr "This general context"
-
-msgid "This GeneralContext:"
-msgstr "This general context:"
-
-msgid "This HierarchicalRelation"
-msgstr "This hierarchical relation"
-
-msgid "This HierarchicalRelation:"
-msgstr "This hierarchical relation:"
-
-msgid "This History"
-msgstr "This piece of historical information"
-
-msgid "This History:"
-msgstr "This piece of historical information:"
-
-msgid "This LegalStatus"
-msgstr "This legal status"
-
-msgid "This LegalStatus:"
-msgstr "This legal status:"
-
-msgid "This Mandate"
-msgstr "This mandate"
-
-msgid "This Mandate:"
-msgstr "This mandate:"
-
-msgid "This NameEntry"
-msgstr "This name entry"
-
-msgid "This NameEntry:"
-msgstr "This name entry:"
-
-msgid "This Occupation"
-msgstr "This occupation"
-
-msgid "This Occupation:"
-msgstr "This occupation:"
-
-msgid "This Structure"
-msgstr "This piece of structure information"
-
-msgid "This Structure:"
-msgstr "This piece of structure information:"
-
-msgid "XML elements not contained in EAC-CPF namespace"
-msgstr ""
-
-msgid "add AgentFunction function_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add AgentFunction has_citation Citation subject"
-msgstr ""
-
-msgid "add AgentPlace has_citation Citation subject"
-msgstr ""
-
-msgid "add AgentPlace place_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add EACOtherRecordId eac_other_record_id_of AuthorityRecord object"
-msgstr ""
-
-msgid "add EACResourceRelation resource_relation_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add EACSource source_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add GeneralContext general_context_of AuthorityRecord object"
-msgstr ""
-
-msgid "add GeneralContext has_citation Citation subject"
-msgstr ""
-
-msgid "add History has_citation Citation subject"
-msgstr ""
-
-msgid "add History history_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add LegalStatus has_citation Citation subject"
-msgstr ""
-
-msgid "add LegalStatus legal_status_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add Mandate has_citation Citation subject"
-msgstr ""
-
-msgid "add Mandate mandate_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add NameEntry name_entry_for AuthorityRecord object"
-msgstr "a name entry"
-
-msgid "add Occupation has_citation Citation subject"
-msgstr ""
-
-msgid "add Occupation occupation_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add Structure structure_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add a AgentFunction"
-msgstr ""
-
-msgid "add a AgentKind"
-msgstr ""
-
-msgid "add a AgentPlace"
-msgstr ""
-
-msgid "add a AssociationRelation"
-msgstr "add an association relation"
-
-msgid "add a AuthorityRecord"
-msgstr ""
-
-msgid "add a ChronologicalRelation"
-msgstr "add a chronological relation"
-
-msgid "add a Citation"
-msgstr "add a citation"
-
-msgid "add a EACOtherRecordId"
-msgstr "add an alternative identifier"
-
-msgid "add a EACResourceRelation"
-msgstr ""
-
-msgid "add a EACSource"
-msgstr "add a source"
-
-msgid "add a GeneralContext"
-msgstr ""
-
-msgid "add a HierarchicalRelation"
-msgstr "add a hierarchical relation"
-
-msgid "add a History"
-msgstr ""
-
-msgid "add a LegalStatus"
-msgstr ""
-
-msgid "add a Mandate"
-msgstr ""
-
-msgid "add a NameEntry"
-msgstr ""
-
-msgid "add a Occupation"
-msgstr ""
-
-msgid "add a Structure"
-msgstr ""
-
-# subject and object forms for each relation type
-# (no object form for final or symmetric relation types)
-msgid "agent"
-msgstr ""
-
-msgctxt "Activity"
-msgid "agent"
-msgstr ""
-
-# subject and object forms for each relation type
-# (no object form for final or symmetric relation types)
-msgid "agent_kind"
-msgstr "agent kind"
-
-msgctxt "AuthorityRecord"
-msgid "agent_kind"
-msgstr ""
-
-msgid "agent_kind_object"
-msgstr "agent of this kind"
-
-msgctxt "AgentKind"
-msgid "agent_kind_object"
-msgstr ""
-
-msgid "agent_role"
-msgstr "agent role"
-
-msgctxt "EACResourceRelation"
-msgid "agent_role"
-msgstr ""
-
-msgid "alternative"
-msgstr ""
-
-msgid "association_from"
-msgstr "associates"
-
-msgctxt "AssociationRelation"
-msgid "association_from"
-msgstr ""
-
-msgid "association_from_object"
-msgstr "association relation"
-
-msgctxt "AuthorityRecord"
-msgid "association_from_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "association_from_object"
-msgstr ""
-
-msgid "association_to"
-msgstr "to"
-
-msgctxt "AssociationRelation"
-msgid "association_to"
-msgstr ""
-
-msgid "association_to_object"
-msgstr "association relation"
-
-msgctxt "AuthorityRecord"
-msgid "association_to_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "association_to_object"
-msgstr ""
-
-msgid "authority"
-msgstr "authority"
-
-msgid "authorized"
-msgstr ""
-
-msgid "chronological_predecessor"
-msgstr "predecessor"
-
-msgctxt "ChronologicalRelation"
-msgid "chronological_predecessor"
-msgstr ""
-
-msgid "chronological_predecessor_object"
-msgstr "predecessor in chronological relation"
-
-msgctxt "AuthorityRecord"
-msgid "chronological_predecessor_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "chronological_predecessor_object"
-msgstr ""
-
-msgid "chronological_successor"
-msgstr "successor"
-
-msgctxt "ChronologicalRelation"
-msgid "chronological_successor"
-msgstr ""
-
-msgid "chronological_successor_object"
-msgstr "successor in chronological relation"
-
-msgctxt "AuthorityRecord"
-msgid "chronological_successor_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "chronological_successor_object"
-msgstr ""
-
-msgid "concatenation of part tags within a nameEntry"
-msgstr ""
-
-msgid "content"
-msgstr ""
-
-msgctxt "GeneralContext"
-msgid "content"
-msgstr ""
-
-msgid "content_format"
-msgstr ""
-
-msgctxt "GeneralContext"
-msgid "content_format"
-msgstr ""
-
-msgid ""
-"contextual role the address has in relation with the agent (e.g. \"home\")"
-msgstr ""
-
-#, python-format
-msgid "could not parse a year from date element %(e)s"
-msgstr ""
-
-#, python-format
-msgid "could not parse date %(e)s"
-msgstr ""
-
-#, python-format
-msgid "could not parse date from %s"
-msgstr ""
-
-msgid ""
-"creating AgentFunction (AgentFunction function_agent AuthorityRecord "
-"%(linkto)s)"
-msgstr ""
-
-msgid "creating AgentPlace (AgentPlace place_agent AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid "creating Citation (AgentFunction %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (AgentPlace %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (GeneralContext %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (History %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (LegalStatus %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (Mandate %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (Occupation %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid ""
-"creating EACOtherRecordId (EACOtherRecordId eac_other_record_id_of "
-"AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid ""
-"creating EACResourceRelation (EACResourceRelation resource_relation_agent "
-"AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid "creating EACSource (EACSource source_agent AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid ""
-"creating GeneralContext (GeneralContext general_context_of AuthorityRecord "
-"%(linkto)s)"
-msgstr ""
-
-msgid "creating History (History history_agent AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid ""
-"creating LegalStatus (LegalStatus legal_status_agent AuthorityRecord "
-"%(linkto)s)"
-msgstr ""
-
-msgid "creating Mandate (Mandate mandate_agent AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid ""
-"creating NameEntry (NameEntry name_entry_for AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid ""
-"creating Occupation (Occupation occupation_agent AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid ""
-"creating Structure (Structure structure_agent AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid "ctxcomponents_eac.xml_wrap"
-msgstr ""
-
-msgid "ctxcomponents_eac.xml_wrap_description"
-msgstr ""
-
-msgctxt "AgentFunction"
-msgid "description"
-msgstr ""
-
-msgctxt "AssociationRelation"
-msgid "description"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "description"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "description"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "description"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "description"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "description"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "description"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "description"
-msgstr ""
-
-msgctxt "Structure"
-msgid "description"
-msgstr ""
-
-msgctxt "AgentFunction"
-msgid "description_format"
-msgstr ""
-
-msgctxt "AssociationRelation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "description_format"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "description_format"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "description_format"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "Structure"
-msgid "description_format"
-msgstr ""
-
-msgid "do_import"
-msgstr "Import"
-
-msgid "eac_other_record_id_of"
-msgstr "alternative identifier of"
-
-msgctxt "EACOtherRecordId"
-msgid "eac_other_record_id_of"
-msgstr ""
-
-msgid "eac_other_record_id_of_object"
-msgstr "alternative identifiers"
-
-msgctxt "AuthorityRecord"
-msgid "eac_other_record_id_of_object"
-msgstr ""
-
-#, python-format
-msgid "element %s has no text nor children, no content extracted"
-msgstr ""
-
-#, python-brace-format
-msgid "element {0} has no text nor (valid) link"
-msgstr ""
-
-#, python-brace-format
-msgid "element {0} not parsed"
-msgstr ""
-
-msgid "encoded information about the address (e.g. \"Paris, France\")"
-msgstr ""
-
-msgid "end_date"
-msgstr "end date"
-
-msgctxt "AssociationRelation"
-msgid "end_date"
-msgstr ""
-
-msgctxt "AuthorityRecord"
-msgid "end_date"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "end_date"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "end_date"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "end_date"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "end_date"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "end_date"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "end_date"
-msgstr ""
-
-msgid "entry"
-msgstr ""
-
-msgctxt "AssociationRelation"
-msgid "entry"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "entry"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "entry"
-msgstr ""
-
-msgid "equivalent_concept"
-msgstr ""
-
-msgctxt "AgentFunction"
-msgid "equivalent_concept"
-msgstr "equivalent concept"
-
-msgctxt "AgentPlace"
-msgid "equivalent_concept"
-msgstr "equivalent concept"
-
-msgctxt "LegalStatus"
-msgid "equivalent_concept"
-msgstr "equivalent concept"
-
-msgctxt "Mandate"
-msgid "equivalent_concept"
-msgstr "equivalent concept"
-
-msgctxt "Occupation"
-msgid "equivalent_concept"
-msgstr "equivalent concept"
-
-msgid "equivalent_concept_object"
-msgstr ""
-
-msgctxt "Concept"
-msgid "equivalent_concept_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "equivalent_concept_object"
-msgstr ""
-
-#, python-format
-msgid ""
-"eventType %s does not match the PROV-O vocabulary, respective Activity will "
-"not have a `type` attribute set."
-msgstr ""
-
-#, python-format
-msgid "expecting a %s tag in element %s, found none"
-msgstr ""
-
-msgid "family"
-msgstr ""
-
-msgid "form_variant"
-msgstr "form variant"
-
-msgctxt "NameEntry"
-msgid "form_variant"
-msgstr ""
-
-msgid "found a cpfRelation without any object (no xlink attribute), skipping"
-msgstr ""
-
-msgid ""
-"found a resourceRelation without any object (no xlink attribute), skipping"
-msgstr ""
-
-#, python-format
-msgid "found an unsupported %s attribute in date element %%(e)s"
-msgstr ""
-
-#, python-format
-msgid "found multiple %s tag within %s element, only one will be used."
-msgstr ""
-
-#, python-format
-msgid ""
-"found no cpfRelationType attribute in element %s, defaulting to associative"
-msgstr ""
-
-#, python-format
-msgid "found no recordId element in control tag, using %s as cwuri"
-msgstr ""
-
-msgid "function_agent"
-msgstr "agent"
-
-msgctxt "AgentFunction"
-msgid "function_agent"
-msgstr ""
-
-msgid "function_agent_object"
-msgstr "function"
-
-msgctxt "AuthorityRecord"
-msgid "function_agent_object"
-msgstr ""
-
-msgid "general_context_of"
-msgstr "agent"
-
-msgctxt "GeneralContext"
-msgid "general_context_of"
-msgstr ""
-
-msgid "general_context_of_object"
-msgstr "general context"
-
-msgctxt "AuthorityRecord"
-msgid "general_context_of_object"
-msgstr ""
-
-msgctxt "Activity"
-msgid "generated"
-msgstr ""
-
-msgctxt "AuthorityRecord"
-msgid "generated_object"
-msgstr ""
-
-msgid "has_citation"
-msgstr "citation"
-
-msgctxt "AgentFunction"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "AgentPlace"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "GeneralContext"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "History"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "has_citation"
-msgstr ""
-
-msgid "has_citation_object"
-msgstr "citation of"
-
-msgctxt "Citation"
-msgid "has_citation_object"
-msgstr ""
-
-msgid "hierarchical_child"
-msgstr "child"
-
-msgctxt "HierarchicalRelation"
-msgid "hierarchical_child"
-msgstr ""
-
-msgid "hierarchical_child_object"
-msgstr "child in hierarchical relation"
-
-msgctxt "AuthorityRecord"
-msgid "hierarchical_child_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "hierarchical_child_object"
-msgstr ""
-
-msgid "hierarchical_parent"
-msgstr "parent"
-
-msgctxt "HierarchicalRelation"
-msgid "hierarchical_parent"
-msgstr ""
-
-msgid "hierarchical_parent_object"
-msgstr "parent in hierarchical relation"
-
-msgctxt "AuthorityRecord"
-msgid "hierarchical_parent_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "hierarchical_parent_object"
-msgstr ""
-
-msgid "history_agent"
-msgstr "agent"
-
-msgctxt "History"
-msgid "history_agent"
-msgstr ""
-
-msgid "history_agent_object"
-msgstr "historical information"
-
-msgctxt "AuthorityRecord"
-msgid "history_agent_object"
-msgstr ""
-
-msgid "information about the history of an authority record"
-msgstr ""
-
-msgid "information about the structure of an authority record"
-msgstr ""
-
-msgid "isni"
-msgstr "ISNI identifier"
-
-msgctxt "AuthorityRecord"
-msgid "isni"
-msgstr ""
-
-msgid "legal status of an authority record"
-msgstr ""
-
-msgid "legal_status_agent"
-msgstr "agent"
-
-msgctxt "LegalStatus"
-msgid "legal_status_agent"
-msgstr ""
-
-msgid "legal_status_agent_object"
-msgstr "legal status"
-
-msgctxt "AuthorityRecord"
-msgid "legal_status_agent_object"
-msgstr ""
-
-msgid "local_type"
-msgstr "local type"
-
-msgctxt "EACOtherRecordId"
-msgid "local_type"
-msgstr ""
-
-msgid "mandate of an authority record"
-msgstr ""
-
-msgid "mandate_agent"
-msgstr "agent"
-
-msgctxt "Mandate"
-msgid "mandate_agent"
-msgstr "agent"
-
-msgid "mandate_agent_object"
-msgstr "mandate"
-
-msgctxt "AuthorityRecord"
-msgid "mandate_agent_object"
-msgstr ""
-
-#, python-brace-format
-msgid "multiple children elements found in {0}"
-msgstr ""
-
-msgctxt "AgentFunction"
-msgid "name"
-msgstr "title"
-
-msgctxt "AgentKind"
-msgid "name"
-msgstr "title"
-
-msgctxt "AgentPlace"
-msgid "name"
-msgstr "title"
-
-msgid "name_entry_for"
-msgstr "name for"
-
-msgctxt "NameEntry"
-msgid "name_entry_for"
-msgstr "name for"
-
-msgid "name_entry_for_object"
-msgstr "name entries"
-
-msgctxt "AuthorityRecord"
-msgid "name_entry_for_object"
-msgstr ""
-
-msgid "note"
-msgstr ""
-
-msgctxt "Citation"
-msgid "note"
-msgstr ""
-
-msgid "note_format"
-msgstr "format"
-
-msgctxt "Citation"
-msgid "note_format"
-msgstr ""
-
-msgid "occupation in which the person works or has worked"
-msgstr ""
-
-msgid "occupation_agent"
-msgstr "agent"
-
-msgctxt "Occupation"
-msgid "occupation_agent"
-msgstr ""
-
-msgid "occupation_agent_object"
-msgstr "occupations"
-
-msgctxt "AuthorityRecord"
-msgid "occupation_agent_object"
-msgstr ""
-
-msgid "parts"
-msgstr ""
-
-msgctxt "NameEntry"
-msgid "parts"
-msgstr ""
-
-msgid "person"
-msgstr ""
-
-msgid "place_address"
-msgstr "address"
-
-msgctxt "AgentPlace"
-msgid "place_address"
-msgstr ""
-
-msgid "place_address_object"
-msgstr "place"
-
-msgctxt "PostalAddress"
-msgid "place_address_object"
-msgstr ""
-
-msgid "place_agent"
-msgstr "agent"
-
-msgctxt "AgentPlace"
-msgid "place_agent"
-msgstr ""
-
-msgid "place_agent_object"
-msgstr "place"
-
-msgctxt "AuthorityRecord"
-msgid "place_agent_object"
-msgstr ""
-
-msgid "postal_address"
-msgstr "postal address"
-
-msgctxt "AuthorityRecord"
-msgid "postal_address"
-msgstr ""
-
-msgid "postal_address_object"
-msgstr "agent"
-
-msgctxt "PostalAddress"
-msgid "postal_address_object"
-msgstr "agent"
-
-msgid "record_id"
-msgstr "record identifier"
-
-msgctxt "AuthorityRecord"
-msgid "record_id"
-msgstr "record identifier"
-
-msgid "reference to an external citation resource"
-msgstr ""
-
-msgid "resource_relation_agent"
-msgstr "agent"
-
-msgctxt "EACResourceRelation"
-msgid "resource_relation_agent"
-msgstr ""
-
-msgid "resource_relation_agent_object"
-msgstr "relation with a resource"
-
-msgctxt "AuthorityRecord"
-msgid "resource_relation_agent_object"
-msgstr ""
-
-msgid "resource_relation_resource"
-msgstr "resource"
-
-msgctxt "EACResourceRelation"
-msgid "resource_relation_resource"
-msgstr ""
-
-msgid "resource_relation_resource_object"
-msgstr "resource of relation"
-
-msgctxt "ExternalUri"
-msgid "resource_relation_resource_object"
-msgstr ""
-
-msgid "resource_role"
-msgstr "resource role"
-
-msgctxt "EACResourceRelation"
-msgid "resource_role"
-msgstr ""
-
-msgid "role"
-msgstr ""
-
-msgctxt "AgentPlace"
-msgid "role"
-msgstr ""
-
-msgid "source_agent"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "source_agent"
-msgstr ""
-
-msgid "source_agent_object"
-msgstr "source"
-
-msgctxt "AuthorityRecord"
-msgid "source_agent_object"
-msgstr ""
-
-msgid "start date must be less than end date"
-msgstr ""
-
-msgid "start_date"
-msgstr "start date"
-
-msgctxt "AssociationRelation"
-msgid "start_date"
-msgstr ""
-
-msgctxt "AuthorityRecord"
-msgid "start_date"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "start_date"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "start_date"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "start_date"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "start_date"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "start_date"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "start_date"
-msgstr ""
-
-msgid "structure_agent"
-msgstr "agent"
-
-msgctxt "Structure"
-msgid "structure_agent"
-msgstr ""
-
-msgid "structure_agent_object"
-msgstr "structure information"
-
-msgctxt "AuthorityRecord"
-msgid "structure_agent_object"
-msgstr ""
-
-msgid "term"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "term"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "term"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "term"
-msgstr ""
-
-msgid "text"
-msgstr ""
-
-msgctxt "History"
-msgid "text"
-msgstr ""
-
-msgid "text_format"
-msgstr ""
-
-msgctxt "History"
-msgid "text_format"
-msgstr ""
-
-msgid "the agent responsible for this activity"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "title"
-msgstr ""
-
-msgid "type of relation the agent has to the resource"
-msgstr ""
-
-msgid "type or nature of the remote resource"
-msgstr ""
-
-#, python-format
-msgid "unexpected error during parsing of date %%(e)s: %s"
-msgstr ""
-
-#, python-format
-msgid "unhandled exception while parsing date %r"
-msgstr ""
-
-msgid "unknown-agent-kind"
-msgstr "unknown"
-
-#, python-format
-msgid "unsupported cpfRelationType %s in element %s, skipping"
-msgstr ""
-
-msgctxt "Citation"
-msgid "uri"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "url"
-msgstr ""
-
-msgctxt "Activity"
-msgid "used"
-msgstr ""
-
-msgctxt "AuthorityRecord"
-msgid "used_object"
-msgstr ""
-
-msgctxt "EACOtherRecordId"
-msgid "value"
-msgstr ""
-
-msgid "vocabulary_source"
-msgstr "vocabulary source"
-
-msgctxt "AgentFunction"
-msgid "vocabulary_source"
-msgstr ""
-
-msgctxt "AgentPlace"
-msgid "vocabulary_source"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "vocabulary_source"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "vocabulary_source"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "vocabulary_source"
-msgstr ""
-
-msgid "vocabulary_source_object"
-msgstr "source of"
-
-msgctxt "ConceptScheme"
-msgid "vocabulary_source_object"
-msgstr ""
-
-msgid "xml syntax error: "
-msgstr ""
-
-msgid "xml_wrap"
-msgstr "extra XML elements"
-
-msgctxt "AssociationRelation"
-msgid "xml_wrap"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "xml_wrap"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "xml_wrap"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "xml_wrap"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "xml_wrap"
-msgstr ""
diff --git a/i18n/fr.po b/i18n/fr.po
deleted file mode 100644
--- a/i18n/fr.po
+++ /dev/null
@@ -1,1494 +0,0 @@
-msgid ""
-msgstr ""
-"PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
-"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"
-
-msgid "A source used to establish the description of an AuthorityRecord"
-msgstr ""
-"Une source utilisée pour l'établissement de la description d'une notice"
-
-msgid "AgentFunction"
-msgstr "Fonction"
-
-msgid "AgentFunction_plural"
-msgstr "Fonctions"
-
-msgid "AgentKind"
-msgstr "Type de notice"
-
-msgid "AgentKind_plural"
-msgstr "Types de notice"
-
-msgid "AgentPlace"
-msgstr "Lieu"
-
-msgid "AgentPlace_plural"
-msgstr "Lieux"
-
-msgid "Association relation between authority records"
-msgstr ""
-
-msgid "AssociationRelation"
-msgstr "Relation d'association"
-
-msgid "AssociationRelation_plural"
-msgstr "Relations d'association"
-
-msgid "AuthorityRecord"
-msgstr "Notice d'autorité"
-
-msgid "AuthorityRecord_plural"
-msgstr "Notices d'autorité"
-
-msgid "Biographical or historical information"
-msgstr "Information biographique ou historique"
-
-msgid "Chronological relation between authority records"
-msgstr ""
-
-msgid "ChronologicalRelation"
-msgstr "Relation chronologique"
-
-msgid "ChronologicalRelation_plural"
-msgstr "Relations chronologiques"
-
-msgid "Citation"
-msgstr ""
-
-msgid "Citation_plural"
-msgstr "Citations"
-
-msgid "EAC export"
-msgstr "export EAC"
-
-msgid "EAC import failed"
-msgstr "Échec de l'importation EAC"
-
-msgid "EAC-CPF file"
-msgstr "fichier EAC-CPF"
-
-#, python-format
-msgid "EAC-CPF import completed: %s"
-msgstr "Import EAC-CPF terminé : %s"
-
-msgid "EACOtherRecordId"
-msgstr "Identifiant alternatif"
-
-msgid "EACOtherRecordId_plural"
-msgstr "Identifiants alternatifs"
-
-msgid "EACResourceRelation"
-msgstr "Relation vers une ressource EAC"
-
-msgid "EACResourceRelation_plural"
-msgstr "Relations vers une ressource EAC"
-
-msgid "EACSource"
-msgstr "Source"
-
-msgid "EACSource_plural"
-msgstr "Sources"
-
-msgid "GeneralContext"
-msgstr "Contexte général"
-
-msgid "GeneralContext_plural"
-msgstr "Contextes généraux"
-
-msgid "Hierarchical relation between authority records"
-msgstr ""
-
-msgid "HierarchicalRelation"
-msgstr "Relation hiérarchique"
-
-msgid "HierarchicalRelation_plural"
-msgstr "Relations hiérarchiques"
-
-msgid "History"
-msgstr "Élément d'information historique"
-
-msgid "History_plural"
-msgstr "Éléments d'information historique"
-
-msgid "Importing an AuthorityRecord from a EAC-CPF file"
-msgstr "Import d'une notice d'autorité depuis un fichier EAC-CPF"
-
-msgid ""
-"Information about the general social and cultural context of an authority "
-"record"
-msgstr "Information au sujet du contexte social et culturel d'un agent"
-
-msgid "Information about the structure of an authority"
-msgstr "Élément d'organisation interne d'une collectivité"
-
-msgid "Information relative to the legal status of an authority"
-msgstr "Information sur le statut juridique d'une collectivité"
-
-msgid "International Standard Name Identifier"
-msgstr ""
-
-msgid "Invalid XML file"
-msgstr "Fichier XML malformé"
-
-msgid ""
-"Kind of an authority record (e.g. \"person\", \"authority\" or \"family\")"
-msgstr "Un type d'agent (par. \"personne\", \"collectivité\" ou \"famille\""
-
-msgid "LegalStatus"
-msgstr "Statut juridique"
-
-msgid "LegalStatus_plural"
-msgstr "Statuts juridiques"
-
-msgid "Mandate"
-msgstr "Texte de référence"
-
-msgid "Mandate_plural"
-msgstr "Textes de référence"
-
-#, python-format
-msgid "Missing tag %(tag)s in XML file"
-msgstr "Tag %(tag)s manquant dans le fichier XML"
-
-#, python-format
-msgid "Missing tag %(tag)s within element %(parent)s in XML file"
-msgstr "Tag %(tag)s manquant dans l'élément %(parent)s du fichier XML "
-
-msgid "NameEntry"
-msgstr "Forme du nom"
-
-msgid "NameEntry_plural"
-msgstr "Formes du nom"
-
-msgid "New AgentFunction"
-msgstr "Nouvelle fonction"
-
-msgid "New AgentKind"
-msgstr "Nouveau type de notice"
-
-msgid "New AgentPlace"
-msgstr "Nouveau lieu"
-
-msgid "New AssociationRelation"
-msgstr "Nouvelle relation d'association"
-
-msgid "New AuthorityRecord"
-msgstr "Nouvelle notice d'autorité"
-
-msgid "New ChronologicalRelation"
-msgstr "Nouvelle relation chronologique"
-
-msgid "New Citation"
-msgstr "Nouvelle citation"
-
-msgid "New EACOtherRecordId"
-msgstr "Nouvel identifiant alternatif"
-
-msgid "New EACResourceRelation"
-msgstr "Nouvelle relation vers une ressource"
-
-msgid "New EACSource"
-msgstr "Nouvelle source"
-
-msgid "New GeneralContext"
-msgstr "Nouveau contexte général"
-
-msgid "New HierarchicalRelation"
-msgstr "Nouvelle relation hiérarchique"
-
-msgid "New History"
-msgstr "Nouvel élément d'information historique"
-
-msgid "New LegalStatus"
-msgstr "Nouveau statut juridique"
-
-msgid "New Mandate"
-msgstr "Nouveau mandat"
-
-msgid "New NameEntry"
-msgstr "Nouvelle forme du nom"
-
-msgid "New Occupation"
-msgstr "Nouvelle profession"
-
-msgid "New Structure"
-msgstr "Nouvel élément d'organisation interne"
-
-msgid "Occupation"
-msgstr "Profession"
-
-msgid "Occupation_plural"
-msgstr "Professions"
-
-msgid "Qualified relation between an AuthorityRecord and a PostalAddress"
-msgstr "Relation qualifiée entre une notice et une adresse postale"
-
-msgid "Reference text coming from an authority"
-msgstr "Texte de référence émis par une collectivité"
-
-#, python-format
-msgid "Relation from %(from)s to %(to)s "
-msgstr "Relation de %(from)s à %(to)s"
-
-msgid "Represent a nameEntry tag of an EAC-CPF document."
-msgstr "Représente la balise nameEntry d'un document EAC-CPF"
-
-msgid ""
-"Represent a relation between an AuthorityRecord and a remote resource in the "
-"EAC-CPF model."
-msgstr ""
-"Représente une relation entre une notice et une ressource distante dans le "
-"modèle EAC-CPF."
-
-msgid "Structure"
-msgstr "Élément d'organisation interne"
-
-msgid "Structure_plural"
-msgstr "Éléments d'organisation interne"
-
-msgid "The function of an AuthorityRecord"
-msgstr "La fonction d'une notice"
-
-msgid "This AgentFunction"
-msgstr "Cette fonction"
-
-msgid "This AgentFunction:"
-msgstr "Cette fonction :"
-
-msgid "This AgentKind"
-msgstr "Ce type de notice"
-
-msgid "This AgentKind:"
-msgstr "Ce type de notice :"
-
-msgid "This AgentPlace"
-msgstr "Ce lieu"
-
-msgid "This AgentPlace:"
-msgstr "Ce lieu :"
-
-msgid "This AssociationRelation"
-msgstr "Cette relation d'association"
-
-msgid "This AssociationRelation:"
-msgstr "Cette relation d'association :"
-
-msgid "This AuthorityRecord"
-msgstr "Cette notice d'autorité"
-
-msgid "This AuthorityRecord:"
-msgstr "Cette notice d'autorité :"
-
-msgid "This ChronologicalRelation"
-msgstr "Cette relation chronologique"
-
-msgid "This ChronologicalRelation:"
-msgstr "Cette relation chronologique :"
-
-msgid "This Citation"
-msgstr "Cette citation"
-
-msgid "This Citation:"
-msgstr "Cette citation :"
-
-msgid "This EACOtherRecordId"
-msgstr "Cet identifiant alternatif"
-
-msgid "This EACOtherRecordId:"
-msgstr "Cet identifiant alternatif :"
-
-msgid "This EACResourceRelation"
-msgstr ""
-
-msgid "This EACResourceRelation:"
-msgstr ""
-
-msgid "This EACSource"
-msgstr "Cette source"
-
-msgid "This EACSource:"
-msgstr "Cette source :"
-
-msgid "This GeneralContext"
-msgstr "Ce contexte général"
-
-msgid "This GeneralContext:"
-msgstr "Ce contexte général :"
-
-msgid "This HierarchicalRelation"
-msgstr "Cette relation hiérarchique"
-
-msgid "This HierarchicalRelation:"
-msgstr "Cette relation hiérarchique :"
-
-msgid "This History"
-msgstr "Cet élément d'information historique"
-
-msgid "This History:"
-msgstr "Cet élément d'information historique :"
-
-msgid "This LegalStatus"
-msgstr "Ce statut juridique"
-
-msgid "This LegalStatus:"
-msgstr "Ce statut juridique :"
-
-msgid "This Mandate"
-msgstr "Ce texte de référence"
-
-msgid "This Mandate:"
-msgstr "Ce texte de référence :"
-
-msgid "This NameEntry"
-msgstr "Cette forme du nom"
-
-msgid "This NameEntry:"
-msgstr "Cette forme du nom :"
-
-msgid "This Occupation"
-msgstr "Cette profession"
-
-msgid "This Occupation:"
-msgstr "Cette profession :"
-
-msgid "This Structure"
-msgstr "Cet élément d'organisation interne"
-
-msgid "This Structure:"
-msgstr "Cet élément d'organisation interne :"
-
-msgid "XML elements not contained in EAC-CPF namespace"
-msgstr "éléments XML non contenus dans l'espace des noms EAC-CPF"
-
-msgid "add AgentFunction function_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add AgentFunction has_citation Citation subject"
-msgstr ""
-
-msgid "add AgentPlace has_citation Citation subject"
-msgstr ""
-
-msgid "add AgentPlace place_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add EACOtherRecordId eac_other_record_id_of AuthorityRecord object"
-msgstr ""
-
-msgid "add EACResourceRelation resource_relation_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add EACSource source_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add GeneralContext general_context_of AuthorityRecord object"
-msgstr ""
-
-msgid "add GeneralContext has_citation Citation subject"
-msgstr ""
-
-msgid "add History has_citation Citation subject"
-msgstr ""
-
-msgid "add History history_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add LegalStatus has_citation Citation subject"
-msgstr ""
-
-msgid "add LegalStatus legal_status_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add Mandate has_citation Citation subject"
-msgstr ""
-
-msgid "add Mandate mandate_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add NameEntry name_entry_for AuthorityRecord object"
-msgstr "une forme du nom"
-
-msgid "add Occupation has_citation Citation subject"
-msgstr ""
-
-msgid "add Occupation occupation_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add Structure structure_agent AuthorityRecord object"
-msgstr ""
-
-msgid "add a AgentFunction"
-msgstr ""
-
-msgid "add a AgentKind"
-msgstr ""
-
-msgid "add a AgentPlace"
-msgstr ""
-
-msgid "add a AssociationRelation"
-msgstr "ajouter une relation d'association"
-
-msgid "add a AuthorityRecord"
-msgstr ""
-
-msgid "add a ChronologicalRelation"
-msgstr "ajouter une relation chronologique"
-
-msgid "add a Citation"
-msgstr "ajouter une citation"
-
-msgid "add a EACOtherRecordId"
-msgstr "ajouter un identifiant alternatif"
-
-msgid "add a EACResourceRelation"
-msgstr ""
-
-msgid "add a EACSource"
-msgstr "ajouter une source"
-
-msgid "add a GeneralContext"
-msgstr ""
-
-msgid "add a HierarchicalRelation"
-msgstr "ajouter une relation hiérarchique"
-
-msgid "add a History"
-msgstr ""
-
-msgid "add a LegalStatus"
-msgstr ""
-
-msgid "add a Mandate"
-msgstr "ajouter un texte de référence"
-
-msgid "add a NameEntry"
-msgstr "ajouter une forme du nom"
-
-msgid "add a Occupation"
-msgstr "ajouter une profession"
-
-msgid "add a Structure"
-msgstr ""
-
-# subject and object forms for each relation type
-# (no object form for final or symmetric relation types)
-msgid "agent"
-msgstr ""
-
-msgctxt "Activity"
-msgid "agent"
-msgstr ""
-
-# subject and object forms for each relation type
-# (no object form for final or symmetric relation types)
-msgid "agent_kind"
-msgstr "type d'agent"
-
-msgctxt "AuthorityRecord"
-msgid "agent_kind"
-msgstr "type"
-
-msgid "agent_kind_object"
-msgstr "notices de ce type"
-
-msgctxt "AgentKind"
-msgid "agent_kind_object"
-msgstr ""
-
-msgid "agent_role"
-msgstr "rôle de l'agent"
-
-msgctxt "EACResourceRelation"
-msgid "agent_role"
-msgstr ""
-
-msgid "alternative"
-msgstr "alternative"
-
-msgid "association_from"
-msgstr "associe"
-
-msgctxt "AssociationRelation"
-msgid "association_from"
-msgstr ""
-
-msgid "association_from_object"
-msgstr "relation d'association"
-
-msgctxt "AuthorityRecord"
-msgid "association_from_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "association_from_object"
-msgstr ""
-
-msgid "association_to"
-msgstr "à"
-
-msgctxt "AssociationRelation"
-msgid "association_to"
-msgstr ""
-
-msgid "association_to_object"
-msgstr "relation d'association"
-
-msgctxt "AuthorityRecord"
-msgid "association_to_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "association_to_object"
-msgstr ""
-
-msgid "authority"
-msgstr "collectivité"
-
-msgid "authorized"
-msgstr "autorisée"
-
-msgid "chronological_predecessor"
-msgstr "prédécesseur"
-
-msgctxt "ChronologicalRelation"
-msgid "chronological_predecessor"
-msgstr ""
-
-msgid "chronological_predecessor_object"
-msgstr "prédécesseur dans la relation chronologique"
-
-msgctxt "AuthorityRecord"
-msgid "chronological_predecessor_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "chronological_predecessor_object"
-msgstr ""
-
-msgid "chronological_successor"
-msgstr "successeur"
-
-msgctxt "ChronologicalRelation"
-msgid "chronological_successor"
-msgstr ""
-
-msgid "chronological_successor_object"
-msgstr "successeur dans la relation chronologique"
-
-msgctxt "AuthorityRecord"
-msgid "chronological_successor_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "chronological_successor_object"
-msgstr ""
-
-msgid "concatenation of part tags within a nameEntry"
-msgstr "concaténation des éléments 'part' de la balise 'nameEntry'"
-
-msgid "content"
-msgstr ""
-
-msgctxt "GeneralContext"
-msgid "content"
-msgstr ""
-
-msgid "content_format"
-msgstr ""
-
-msgctxt "GeneralContext"
-msgid "content_format"
-msgstr ""
-
-msgid ""
-"contextual role the address has in relation with the agent (e.g. \"home\")"
-msgstr ""
-"rôle contextuel de l'adresse vis-à-vis de l'agent (par ex. \"domicile\")"
-
-#, python-format
-msgid "could not parse a year from date element %(e)s"
-msgstr "ne peux identifier une année dans la date %(e)s"
-
-#, python-format
-msgid "could not parse date %(e)s"
-msgstr "ne peut reconnaitre la date %(e)s"
-
-#, python-format
-msgid "could not parse date from %s"
-msgstr "impossible de décoder une date à partir de %s"
-
-msgid ""
-"creating AgentFunction (AgentFunction function_agent AuthorityRecord "
-"%(linkto)s)"
-msgstr "création d'une fonction (pour la notice %(linkto)s)"
-
-msgid "creating AgentPlace (AgentPlace place_agent AuthorityRecord %(linkto)s)"
-msgstr "création d'un lieu (pour la notice %(linkto)s)"
-
-msgid "creating Citation (AgentFunction %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (AgentPlace %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (GeneralContext %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (History %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (LegalStatus %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (Mandate %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid "creating Citation (Occupation %(linkto)s has_citation Citation)"
-msgstr ""
-
-msgid ""
-"creating EACOtherRecordId (EACOtherRecordId eac_other_record_id_of "
-"AuthorityRecord %(linkto)s)"
-msgstr ""
-
-msgid ""
-"creating EACResourceRelation (EACResourceRelation resource_relation_agent "
-"AuthorityRecord %(linkto)s)"
-msgstr "création d'une relation vers une ressource (pour la notice %(linkto)s)"
-
-msgid "creating EACSource (EACSource source_agent AuthorityRecord %(linkto)s)"
-msgstr "création d'une source (pour la notice %(linkto)s)"
-
-msgid ""
-"creating GeneralContext (GeneralContext general_context_of AuthorityRecord "
-"%(linkto)s)"
-msgstr "création d'un contexte général (pour la notice %(linkto)s)"
-
-msgid "creating History (History history_agent AuthorityRecord %(linkto)s)"
-msgstr ""
-"création d'un élément d'information historique (pour la notice %(linkto)s)"
-
-msgid ""
-"creating LegalStatus (LegalStatus legal_status_agent AuthorityRecord "
-"%(linkto)s)"
-msgstr "création d'un statut juridique (pour la notice %(linkto)s)"
-
-msgid "creating Mandate (Mandate mandate_agent AuthorityRecord %(linkto)s)"
-msgstr "création d'un texte de référence (pour la notice %(linkto)s)"
-
-msgid ""
-"creating NameEntry (NameEntry name_entry_for AuthorityRecord %(linkto)s)"
-msgstr "création d'une forme du nom (pour la notice %(linkto)s)"
-
-msgid ""
-"creating Occupation (Occupation occupation_agent AuthorityRecord %(linkto)s)"
-msgstr "création d'une profession (pour la notice %(linkto)s)"
-
-msgid ""
-"creating Structure (Structure structure_agent AuthorityRecord %(linkto)s)"
-msgstr "création d'un élément d'organisation interne pour la notice %(linkto)s"
-
-msgid "ctxcomponents_eac.xml_wrap"
-msgstr "Données XML supplémentaires"
-
-msgid "ctxcomponents_eac.xml_wrap_description"
-msgstr ""
-
-msgctxt "AgentFunction"
-msgid "description"
-msgstr ""
-
-msgctxt "AssociationRelation"
-msgid "description"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "description"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "description"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "description"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "description"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "description"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "description"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "description"
-msgstr ""
-
-msgctxt "Structure"
-msgid "description"
-msgstr ""
-
-msgctxt "AgentFunction"
-msgid "description_format"
-msgstr ""
-
-msgctxt "AssociationRelation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "description_format"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "description_format"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "description_format"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "description_format"
-msgstr ""
-
-msgctxt "Structure"
-msgid "description_format"
-msgstr ""
-
-msgid "do_import"
-msgstr "Importer"
-
-msgid "eac_other_record_id_of"
-msgstr "identifiant alternatif de"
-
-msgctxt "EACOtherRecordId"
-msgid "eac_other_record_id_of"
-msgstr ""
-
-msgid "eac_other_record_id_of_object"
-msgstr "identifiants alternatifs"
-
-msgctxt "AuthorityRecord"
-msgid "eac_other_record_id_of_object"
-msgstr ""
-
-#, python-format
-msgid "element %s has no text nor children, no content extracted"
-msgstr "l'élément %s n'a pas de texte ni d'enfant, aucun contenu extrait"
-
-#, python-brace-format
-msgid "element {0} has no text nor (valid) link"
-msgstr "l'élément {0} n'a ni texte ni de lien valide"
-
-#, fuzzy, python-brace-format
-msgid "element {0} not parsed"
-msgstr "l'élément {0} n'a pas été pris en compte"
-
-msgid "encoded information about the address (e.g. \"Paris, France\")"
-msgstr "information (encodée) sur l'adresse (par ex. \"Paris, France\")"
-
-msgid "end_date"
-msgstr "date de fin"
-
-msgctxt "AssociationRelation"
-msgid "end_date"
-msgstr ""
-
-msgctxt "AuthorityRecord"
-msgid "end_date"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "end_date"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "end_date"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "end_date"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "end_date"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "end_date"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "end_date"
-msgstr ""
-
-msgid "entry"
-msgstr "entrée"
-
-msgctxt "AssociationRelation"
-msgid "entry"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "entry"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "entry"
-msgstr ""
-
-msgid "equivalent_concept"
-msgstr "concept équivalent"
-
-msgctxt "AgentFunction"
-msgid "equivalent_concept"
-msgstr ""
-
-msgctxt "AgentPlace"
-msgid "equivalent_concept"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "equivalent_concept"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "equivalent_concept"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "equivalent_concept"
-msgstr ""
-
-msgid "equivalent_concept_object"
-msgstr "référencé par"
-
-msgctxt "Concept"
-msgid "equivalent_concept_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "equivalent_concept_object"
-msgstr ""
-
-#, python-format
-msgid ""
-"eventType %s does not match the PROV-O vocabulary, respective Activity will "
-"not have a `type` attribute set."
-msgstr ""
-"le type d'événement %s ne correspond pas au vocabulaire PROV-O, l'attribut "
-"type de l'activité associée ne sera pas défini"
-
-#, python-format
-msgid "expecting a %s tag in element %s, found none"
-msgstr "une balise %s était attendue dans l'élément %s, aucune n'a été trouvée"
-
-msgid "family"
-msgstr "famille"
-
-msgid "form_variant"
-msgstr "variante"
-
-msgctxt "NameEntry"
-msgid "form_variant"
-msgstr ""
-
-msgid "found a cpfRelation without any object (no xlink attribute), skipping"
-msgstr ""
-"une cpfRelation sans objet (pas d'attribut xlink) a été trouvée, elle est "
-"ignorée"
-
-msgid ""
-"found a resourceRelation without any object (no xlink attribute), skipping"
-msgstr ""
-"une resourceRelation sans objet (pas d'attribut xlink) a été trouvée, elle "
-"est ignorée"
-
-#, python-format
-msgid "found an unsupported %s attribute in date element %%(e)s"
-msgstr "attribut %s non supporté dans l'élément data %%(e)s"
-
-#, python-format
-msgid "found multiple %s tag within %s element, only one will be used."
-msgstr ""
-"plusieurs balises %s ont été trouvées dans l'élément %s, une seule a été "
-"retenue."
-
-#, python-format
-msgid ""
-"found no cpfRelationType attribute in element %s, defaulting to associative"
-msgstr ""
-"aucun attribut cpfRelationType n'a été trouvé dans l'élément %s, utilisation "
-"d'une relation d'association"
-
-#, python-format
-msgid "found no recordId element in control tag, using %s as cwuri"
-msgstr ""
-"aucun élément recordId n'a été trouvé dans le tag control en utilisant %s "
-"comme cwuri"
-
-msgid "function_agent"
-msgstr "notice"
-
-msgctxt "AgentFunction"
-msgid "function_agent"
-msgstr ""
-
-msgid "function_agent_object"
-msgstr "fonctions"
-
-msgctxt "AuthorityRecord"
-msgid "function_agent_object"
-msgstr ""
-
-msgid "general_context_of"
-msgstr "notice"
-
-msgctxt "GeneralContext"
-msgid "general_context_of"
-msgstr ""
-
-msgid "general_context_of_object"
-msgstr "contexte général"
-
-msgctxt "AuthorityRecord"
-msgid "general_context_of_object"
-msgstr ""
-
-msgctxt "Activity"
-msgid "generated"
-msgstr "a généré"
-
-msgctxt "AuthorityRecord"
-msgid "generated_object"
-msgstr "activité"
-
-msgid "has_citation"
-msgstr "citation"
-
-msgctxt "AgentFunction"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "AgentPlace"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "GeneralContext"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "History"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "has_citation"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "has_citation"
-msgstr ""
-
-msgid "has_citation_object"
-msgstr "citation pour"
-
-msgctxt "Citation"
-msgid "has_citation_object"
-msgstr ""
-
-msgid "hierarchical_child"
-msgstr "enfant"
-
-msgctxt "HierarchicalRelation"
-msgid "hierarchical_child"
-msgstr ""
-
-msgid "hierarchical_child_object"
-msgstr "enfant dans la relation hiérarchique"
-
-msgctxt "AuthorityRecord"
-msgid "hierarchical_child_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "hierarchical_child_object"
-msgstr ""
-
-msgid "hierarchical_parent"
-msgstr "parent"
-
-msgctxt "HierarchicalRelation"
-msgid "hierarchical_parent"
-msgstr ""
-
-msgid "hierarchical_parent_object"
-msgstr "parent dans la relation hiérarchique"
-
-msgctxt "AuthorityRecord"
-msgid "hierarchical_parent_object"
-msgstr ""
-
-msgctxt "ExternalUri"
-msgid "hierarchical_parent_object"
-msgstr ""
-
-msgid "history_agent"
-msgstr "notice"
-
-msgctxt "History"
-msgid "history_agent"
-msgstr ""
-
-msgid "history_agent_object"
-msgstr "informations historiques"
-
-msgctxt "AuthorityRecord"
-msgid "history_agent_object"
-msgstr ""
-
-msgid "information about the history of an authority record"
-msgstr "information historique d'une notice"
-
-msgid "information about the structure of an authority record"
-msgstr "élément d'organisation interne d'une notice"
-
-msgid "isni"
-msgstr "identifiant ISNI"
-
-msgctxt "AuthorityRecord"
-msgid "isni"
-msgstr ""
-
-msgid "legal status of an authority record"
-msgstr "statut juridique d'une notice"
-
-msgid "legal_status_agent"
-msgstr "notice"
-
-msgctxt "LegalStatus"
-msgid "legal_status_agent"
-msgstr ""
-
-msgid "legal_status_agent_object"
-msgstr "statut juridique"
-
-msgctxt "AuthorityRecord"
-msgid "legal_status_agent_object"
-msgstr ""
-
-msgid "local_type"
-msgstr "type local"
-
-msgctxt "EACOtherRecordId"
-msgid "local_type"
-msgstr ""
-
-msgid "mandate of an authority record"
-msgstr "texte de référence d'une notice"
-
-msgid "mandate_agent"
-msgstr "notice"
-
-msgctxt "Mandate"
-msgid "mandate_agent"
-msgstr ""
-
-msgid "mandate_agent_object"
-msgstr "textes de référence"
-
-msgctxt "AuthorityRecord"
-msgid "mandate_agent_object"
-msgstr ""
-
-#, python-brace-format
-msgid "multiple children elements found in {0}"
-msgstr "{0} a plusieurs éléments enfants"
-
-msgctxt "AgentFunction"
-msgid "name"
-msgstr "intitulé"
-
-msgctxt "AgentKind"
-msgid "name"
-msgstr "intitulé"
-
-msgctxt "AgentPlace"
-msgid "name"
-msgstr "intitulé"
-
-msgid "name_entry_for"
-msgstr "nom de"
-
-msgctxt "NameEntry"
-msgid "name_entry_for"
-msgstr ""
-
-msgid "name_entry_for_object"
-msgstr "formes du nom"
-
-msgctxt "AuthorityRecord"
-msgid "name_entry_for_object"
-msgstr ""
-
-msgid "note"
-msgstr ""
-
-msgctxt "Citation"
-msgid "note"
-msgstr ""
-
-msgid "note_format"
-msgstr "format"
-
-msgctxt "Citation"
-msgid "note_format"
-msgstr ""
-
-msgid "occupation in which the person works or has worked"
-msgstr "profession actuelle ou passé de l'agent"
-
-msgid "occupation_agent"
-msgstr "notice"
-
-msgctxt "Occupation"
-msgid "occupation_agent"
-msgstr ""
-
-msgid "occupation_agent_object"
-msgstr "professions"
-
-msgctxt "AuthorityRecord"
-msgid "occupation_agent_object"
-msgstr ""
-
-msgid "parts"
-msgstr "parties"
-
-msgctxt "NameEntry"
-msgid "parts"
-msgstr ""
-
-msgid "person"
-msgstr "personne"
-
-msgid "place_address"
-msgstr "adresse"
-
-msgctxt "AgentPlace"
-msgid "place_address"
-msgstr ""
-
-msgid "place_address_object"
-msgstr "lieu"
-
-msgctxt "PostalAddress"
-msgid "place_address_object"
-msgstr ""
-
-msgid "place_agent"
-msgstr "notice"
-
-msgctxt "AgentPlace"
-msgid "place_agent"
-msgstr ""
-
-msgid "place_agent_object"
-msgstr "lieux"
-
-msgctxt "AuthorityRecord"
-msgid "place_agent_object"
-msgstr ""
-
-msgid "postal_address"
-msgstr "adresse postale"
-
-msgctxt "AuthorityRecord"
-msgid "postal_address"
-msgstr ""
-
-msgid "postal_address_object"
-msgstr "notice"
-
-msgctxt "PostalAddress"
-msgid "postal_address_object"
-msgstr ""
-
-msgid "record_id"
-msgstr "identifiant"
-
-msgctxt "AuthorityRecord"
-msgid "record_id"
-msgstr "identifiant"
-
-msgid "reference to an external citation resource"
-msgstr "référence à une ressource externe"
-
-msgid "resource_relation_agent"
-msgstr "notice"
-
-msgctxt "EACResourceRelation"
-msgid "resource_relation_agent"
-msgstr ""
-
-msgid "resource_relation_agent_object"
-msgstr "relations vers une ressource"
-
-msgctxt "AuthorityRecord"
-msgid "resource_relation_agent_object"
-msgstr ""
-
-msgid "resource_relation_resource"
-msgstr "ressource"
-
-msgctxt "EACResourceRelation"
-msgid "resource_relation_resource"
-msgstr ""
-
-msgid "resource_relation_resource_object"
-msgstr "ressource d'une relation"
-
-msgctxt "ExternalUri"
-msgid "resource_relation_resource_object"
-msgstr ""
-
-msgid "resource_role"
-msgstr "rôle de la ressource"
-
-msgctxt "EACResourceRelation"
-msgid "resource_role"
-msgstr ""
-
-msgid "role"
-msgstr "rôle"
-
-msgctxt "AgentPlace"
-msgid "role"
-msgstr ""
-
-msgid "source_agent"
-msgstr "notice"
-
-msgctxt "EACSource"
-msgid "source_agent"
-msgstr ""
-
-msgid "source_agent_object"
-msgstr "sources"
-
-msgctxt "AuthorityRecord"
-msgid "source_agent_object"
-msgstr ""
-
-msgid "start date must be less than end date"
-msgstr "la date de début doit être inférieure à la date de fin"
-
-msgid "start_date"
-msgstr "date de début"
-
-msgctxt "AssociationRelation"
-msgid "start_date"
-msgstr ""
-
-msgctxt "AuthorityRecord"
-msgid "start_date"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "start_date"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "start_date"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "start_date"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "start_date"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "start_date"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "start_date"
-msgstr ""
-
-msgid "structure_agent"
-msgstr "notice"
-
-msgctxt "Structure"
-msgid "structure_agent"
-msgstr ""
-
-msgid "structure_agent_object"
-msgstr "organisations internes"
-
-msgctxt "AuthorityRecord"
-msgid "structure_agent_object"
-msgstr ""
-
-msgid "term"
-msgstr "terme"
-
-msgctxt "LegalStatus"
-msgid "term"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "term"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "term"
-msgstr ""
-
-msgid "text"
-msgstr "texte"
-
-msgctxt "History"
-msgid "text"
-msgstr ""
-
-msgid "text_format"
-msgstr "format du texte"
-
-msgctxt "History"
-msgid "text_format"
-msgstr ""
-
-msgid "the agent responsible for this activity"
-msgstr "l'agent responsable de cette activité"
-
-msgctxt "EACSource"
-msgid "title"
-msgstr "titre"
-
-msgid "type of relation the agent has to the resource"
-msgstr "type de relation que l'agent entretient avec la ressource"
-
-msgid "type or nature of the remote resource"
-msgstr "type ou nature de la ressource distante"
-
-#, python-format
-msgid "unexpected error during parsing of date %%(e)s: %s"
-msgstr "erreur inattendue lors de l'analyse de la date %%(e)s: %s"
-
-#, python-format
-msgid "unhandled exception while parsing date %r"
-msgstr "exception non gérée lors de l'analyse de la date %r"
-
-msgid "unknown-agent-kind"
-msgstr "inconnu"
-
-#, python-format
-msgid "unsupported cpfRelationType %s in element %s, skipping"
-msgstr "cpfRelationType %s non supportée dans l'élément %s, elle est ignorée"
-
-msgctxt "Citation"
-msgid "uri"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "url"
-msgstr ""
-
-msgctxt "Activity"
-msgid "used"
-msgstr "a utilisé"
-
-msgctxt "AuthorityRecord"
-msgid "used_object"
-msgstr "utilisé par"
-
-msgctxt "EACOtherRecordId"
-msgid "value"
-msgstr ""
-
-msgid "vocabulary_source"
-msgstr "source du vocabulaire"
-
-msgctxt "AgentFunction"
-msgid "vocabulary_source"
-msgstr ""
-
-msgctxt "AgentPlace"
-msgid "vocabulary_source"
-msgstr ""
-
-msgctxt "LegalStatus"
-msgid "vocabulary_source"
-msgstr ""
-
-msgctxt "Mandate"
-msgid "vocabulary_source"
-msgstr ""
-
-msgctxt "Occupation"
-msgid "vocabulary_source"
-msgstr ""
-
-msgid "vocabulary_source_object"
-msgstr "source de"
-
-msgctxt "ConceptScheme"
-msgid "vocabulary_source_object"
-msgstr ""
-
-msgid "xml syntax error: "
-msgstr "erreur de syntaxe xml : "
-
-msgid "xml_wrap"
-msgstr "éléments XML supplémentaires"
-
-msgctxt "AssociationRelation"
-msgid "xml_wrap"
-msgstr ""
-
-msgctxt "ChronologicalRelation"
-msgid "xml_wrap"
-msgstr ""
-
-msgctxt "EACResourceRelation"
-msgid "xml_wrap"
-msgstr ""
-
-msgctxt "EACSource"
-msgid "xml_wrap"
-msgstr ""
-
-msgctxt "HierarchicalRelation"
-msgid "xml_wrap"
-msgstr ""
diff --git a/schema.py b/schema.py
deleted file mode 100644
--- a/schema.py
+++ /dev/null
@@ -1,397 +0,0 @@
-# copyright 2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# 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-eac schema"""
-
-from yams.buildobjs import (EntityType, RelationDefinition, String, Date, Bytes,
-                            RichString, ComputedRelation)
-from yams.constraints import BoundaryConstraint, Attribute
-
-from cubicweb import _
-from cubicweb.schema import RRQLExpression, RQLVocabularyConstraint
-
-from cubes.prov.schema import Activity
-
-
-def dated_entity_type(cls):
-    """Class decorator adding `start_date` and `end_date` attribute to an
-    EntityType.
-    """
-    cls.add_relation(Date(constraints=[BoundaryConstraint(
-        '<=', Attribute('end_date'), msg=_('start date must be less than end date'))]),
-        name='start_date')
-    cls.add_relation(Date(), name='end_date')
-    return cls
-
-
-def xml_wrap(cls):
-    """Class decorator adding an `xml_wrap` attribute to an EntityType."""
-    desc = _('XML elements not contained in EAC-CPF namespace')
-    cls.add_relation(Bytes(description=desc), name='xml_wrap')
-    return cls
-
-
-Activity.add_relation(String(description=_('the agent responsible for this activity'),
-                             indexed=True, fulltextindexed=True), name='agent')
-
-
- at dated_entity_type
-class AuthorityRecord(EntityType):
-    record_id = String(indexed=True)
-    isni = String(unique=True,
-                  description=_('International Standard Name Identifier'))
-
-
-class NameEntry(EntityType):
-    """Represent a nameEntry tag of an EAC-CPF document."""
-    parts = String(
-        required=True, fulltextindexed=True,
-        description=_('concatenation of part tags within a nameEntry'))
-    form_variant = String(internationalizable=True,
-                          vocabulary=[_('authorized'), _('alternative')])
-
-
-class name_entry_for(RelationDefinition):
-    subject = 'NameEntry'
-    object = 'AuthorityRecord'
-    cardinality = '1+'
-    composite = 'object'
-    fulltext_container = 'object'
-    inlined = True
-
-
-class EACOtherRecordId(EntityType):
-    value = String(required=True, fulltextindexed=True, indexed=True)
-    local_type = String(indexed=True)
-    __unique_together__ = [('value', 'eac_other_record_id_of')]
-
-
-class eac_other_record_id_of(RelationDefinition):
-    subject = 'EACOtherRecordId'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    composite = 'object'
-    fulltext_container = 'object'
-    inlined = True
-
-
-class AgentFunction(EntityType):
-    """The function of an AuthorityRecord"""
-    name = String(fulltextindexed=True, internationalizable=True)
-    description = RichString(fulltextindexed=True)
-    __unique_together__ = [('name', 'function_agent')]
-
-
-class function_agent(RelationDefinition):
-    subject = 'AgentFunction'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    composite = 'object'
-    fulltext_container = 'object'
-    inlined = True
-
-
-class AgentPlace(EntityType):
-    """Qualified relation between an AuthorityRecord and a PostalAddress"""
-    name = String(fulltextindexed=True,
-                  description=_('encoded information about the address (e.g. '
-                                '"Paris, France")'))
-    role = String(description=_('contextual role the address has in relation '
-                                'with the agent (e.g. "home")'),
-                  internationalizable=True)
-    __unique_together__ = [('role', 'place_agent', 'place_address')]
-
-
-class place_address(RelationDefinition):
-    subject = 'AgentPlace'
-    object = 'PostalAddress'
-    cardinality = '?1'
-    inlined = True
-    composite = 'subject'
-    fulltext_container = 'subject'
-
-
-class place_agent(RelationDefinition):
-    subject = 'AgentPlace'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    inlined = True
-    composite = 'object'
-    fulltext_container = 'object'
-
-
-class postal_address(ComputedRelation):
-    rule = 'P place_agent S, P place_address O'
-
-
-class AgentKind(EntityType):
-    """Kind of an authority record (e.g. "person", "authority" or "family")"""
-    __permissions__ = {
-        'read': ('managers', 'users', 'guests'),
-        'add': ('managers', ),
-        'update': (),
-        'delete': (),
-    }
-    name = String(required=True, unique=True, internationalizable=True)
-
-
-class agent_kind(RelationDefinition):
-    __permissions__ = {
-        'read': ('managers', 'users', 'guests'),
-        'add': ('managers', 'users'),
-        'delete': (RRQLExpression('O name "unknown-agent-kind"'),),
-    }
-    subject = 'AuthorityRecord'
-    object = 'AgentKind'
-    cardinality = '1*'
-    inlined = True
-
-
-class GeneralContext(EntityType):
-    """Information about the general social and cultural context of an authority record"""
-    content = RichString(fulltextindexed=True)
-
-
-class general_context_of(RelationDefinition):
-    subject = 'GeneralContext'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    inlined = True
-    composite = 'object'
-    fulltext_container = 'object'
-
-
-class _agent_relation(RelationDefinition):
-    """Abstract relation between authority record"""
-    subject = None
-    object = ('AuthorityRecord', 'ExternalUri')
-    cardinality = '1*'
-    inlined = True
-
-
- at xml_wrap
- at dated_entity_type
-class AssociationRelation(EntityType):
-    """Association relation between authority records"""
-    entry = String()
-    description = RichString()
-
-
-class association_from(_agent_relation):
-    subject = 'AssociationRelation'
-
-
-class association_to(_agent_relation):
-    subject = 'AssociationRelation'
-
-
- at xml_wrap
- at dated_entity_type
-class ChronologicalRelation(EntityType):
-    """Chronological relation between authority records"""
-    entry = String()
-    description = RichString()
-
-
-class chronological_predecessor(_agent_relation):
-    subject = 'ChronologicalRelation'
-
-
-class chronological_successor(_agent_relation):
-    subject = 'ChronologicalRelation'
-
-
- at xml_wrap
- at dated_entity_type
-class HierarchicalRelation(EntityType):
-    """Hierarchical relation between authority records"""
-    entry = String()
-    description = RichString()
-
-
-class hierarchical_parent(_agent_relation):
-    subject = 'HierarchicalRelation'
-
-
-class hierarchical_child(_agent_relation):
-    subject = 'HierarchicalRelation'
-
-
-class generated(RelationDefinition):
-    subject = 'Activity'
-    object = 'AuthorityRecord'
-
-
-class used(RelationDefinition):
-    subject = 'Activity'
-    object = 'AuthorityRecord'
-
-
- at dated_entity_type
-class Mandate(EntityType):
-    """Reference text coming from an authority"""
-    term = String(fulltextindexed=True)
-    description = RichString(fulltextindexed=True)
-
-
- at dated_entity_type
-class LegalStatus(EntityType):
-    """Information relative to the legal status of an authority"""
-    term = String(fulltextindexed=True)
-    description = RichString(fulltextindexed=True)
-
-
-class History(EntityType):
-    """Biographical or historical information"""
-    text = RichString(fulltextindexed=True)
-
-
-class Structure(EntityType):
-    """Information about the structure of an authority"""
-    description = RichString(fulltextindexed=True)
-
-
- at dated_entity_type
-class Occupation(EntityType):
-    term = String(fulltextindexed=True)
-    description = RichString(fulltextindexed=True)
-
-
-class occupation_agent(RelationDefinition):
-    subject = 'Occupation'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    composite = 'object'
-    fulltext_container = 'object'
-    inlined = True
-    description = _('occupation in which the person works or has worked')
-
-
-class mandate_agent(RelationDefinition):
-    subject = 'Mandate'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    composite = 'object'
-    fulltext_container = 'object'
-    inlined = True
-    description = _('mandate of an authority record')
-
-
-class legal_status_agent(RelationDefinition):
-    subject = 'LegalStatus'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    composite = 'object'
-    fulltext_container = 'object'
-    inlined = True
-    description = _('legal status of an authority record')
-
-
-class structure_agent(RelationDefinition):
-    subject = 'Structure'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    composite = 'object'
-    fulltext_container = 'object'
-    inlined = True
-    description = _('information about the structure of an authority record')
-
-
-class history_agent(RelationDefinition):
-    subject = 'History'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    composite = 'object'
-    fulltext_container = 'object'
-    inlined = True
-    description = _('information about the history of an authority record')
-
-
-class Citation(EntityType):
-    note = RichString()
-    uri = String()
-
-
-class has_citation(RelationDefinition):
-    subject = ('GeneralContext', 'Mandate', 'Occupation', 'History',
-               'AgentFunction', 'LegalStatus', 'AgentPlace')
-    object = 'Citation'
-    cardinality = '*1'
-    composite = 'subject'
-    description = _('reference to an external citation resource')
-
-
- at xml_wrap
- at dated_entity_type
-class EACResourceRelation(EntityType):
-    """Represent a relation between an AuthorityRecord and a remote resource in the
-    EAC-CPF model.
-    """
-    agent_role = String(description=_('type of relation the agent has to the resource'),
-                        internationalizable=True)
-    resource_role = String(description=_('type or nature of the remote resource'),
-                           internationalizable=True)
-    description = RichString(fulltextindexed=True)
-
-
-class resource_relation_agent(RelationDefinition):
-    subject = 'EACResourceRelation'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    inlined = True
-    composite = 'object'
-    fulltext_container = 'object'
-
-
-class resource_relation_resource(RelationDefinition):
-    subject = 'EACResourceRelation'
-    object = 'ExternalUri'
-    cardinality = '1*'
-    inlined = True
-
-
- at xml_wrap
-class EACSource(EntityType):
-    """A source used to establish the description of an AuthorityRecord"""
-    title = String(fulltextindexed=True)
-    url = String()
-    description = RichString(fulltextindexed=True)
-
-
-class source_agent(RelationDefinition):
-    subject = 'EACSource'
-    object = 'AuthorityRecord'
-    cardinality = '1*'
-    composite = 'object'
-    fulltext_container = 'object'
-    inlined = True
-
-
-class vocabulary_source(RelationDefinition):
-    subject = ('Mandate', 'LegalStatus', 'AgentFunction', 'AgentPlace',
-               'Occupation')
-    object = 'ConceptScheme'
-    cardinality = '?*'
-
-
-class equivalent_concept(RelationDefinition):
-    subject = ('Mandate', 'LegalStatus', 'AgentFunction', 'AgentPlace',
-               'Occupation')
-    object = ('ExternalUri', 'Concept')
-    constraints = [RQLVocabularyConstraint('S vocabulary_source SC, O in_scheme SC')]
-    cardinality = '?*'
-    # relation with 'ExternalUri' as object can't be inlined because of a limitation of
-    # data-import's (massive) store
-    inlined = False
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -1,10 +1,10 @@
 #!/usr/bin/env python
 # pylint: disable=W0142,W0403,W0404,W0613,W0622,W0622,W0704,R0904,C0103,E0611
 #
-# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 # contact http://www.logilab.fr/ -- mailto:contact at logilab.fr
 #
-# This file is part of a CubicWeb cube.
+# This file is part of a cubicweb-eac.
 #
 # CubicWeb 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
@@ -18,195 +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/>.
-"""Generic Setup script, takes package info from __pkginfo__.py file
+"""cubicweb_eac setup module using data from
+cubicweb_eac/__pkginfo__.py file
 """
 
-import os
-import sys
-import shutil
-from os.path import exists, join, dirname
+from os.path import join, dirname
 
-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
+from setuptools import find_packages, setup
 
 
+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
-base_dir = dirname(__file__)
-pkginfo = {}
-with open(join(base_dir, "__pkginfo__.py")) as f:
-    exec(f.read(), pkginfo)
+pkginfo = join(here, 'cubicweb_eac', '__pkginfo__.py')
+__pkginfo__ = {}
+with open(pkginfo) as f:
+    exec(f.read(), __pkginfo__)
 
 # get required metadatas
-modname = pkginfo['modname']
-version = pkginfo['version']
-license = pkginfo['license']
-description = pkginfo['description']
-web = pkginfo['web']
-author = pkginfo['author']
-author_email = pkginfo['author_email']
-classifiers = pkginfo['classifiers']
+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']
 
-with open(join(base_dir, 'README')) as f:
+with open(join(here, 'README')) as f:
     long_description = f.read()
 
 # get optional metadatas
-distname = pkginfo.get('distname', modname)
-scripts = pkginfo.get('scripts', ())
-include_dirs = pkginfo.get('include_dirs', ())
-data_files = pkginfo.get('data_files', None)
-ext_modules = pkginfo.get('ext_modules', None)
-dependency_links = pkginfo.get('dependency_links', ())
-
-if USE_SETUPTOOLS:
-    requires = {}
-    for entry in ("__depends__",):  # "__recommends__"):
-        requires.update(pkginfo.get(entry, {}))
-    install_requires = [("%s %s" % (d, v and v or "")).strip()
-                        for d, v in requires.items()]
-else:
-    install_requires = []
-
-BASE_BLACKLIST = ('CVS', '.svn', '.hg', '.git', '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_
-
+data_files = __pkginfo__.get('data_files', None)
+dependency_links = __pkginfo__.get('dependency_links', ())
 
-def export(from_dir, to_dir,
-           blacklist=BASE_BLACKLIST,
-           ignore_ext=IGNORED_EXTENSIONS,
-           verbose=True):
-    try:
-        os.mkdir(to_dir)
-    except OSError as ex:
-        # file exists ?
-        import errno
-        if ex.errno != errno.EEXIST:
-            raise
-    for dirpath, dirnames, filenames in os.walk(from_dir):
-        for norecurs in blacklist:
-            try:
-                dirnames.remove(norecurs)
-            except ValueError:
-                pass
-        for dir_name in dirnames:
-            dest = join(to_dir, dir_name)
-            if not exists(dest):
-                os.mkdir(dest)
-        for filename in filenames:
-            # don't include binary files
-            src = join(dirpath, filename)
-            dest = to_dir + src[len(from_dir):]
-            if filename[-4:] in ignore_ext:
-                continue
-            if filename[-1] == '~':
-                continue
-            if exists(dest):
-                os.remove(dest)
-            if verbose:
-                sys.stderr.write('%s -> %s\n' % (src, dest))
-            shutil.copy2(src, dest)
+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()]
 
 
-class MyInstallLib(install_lib.install_lib):
-    """extend install_lib command to handle  package __init__.py and
-    include_dirs variable if necessary
-    """
-    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)
-
-
-# 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:
-        # only if easy_install available
-        import setuptools.command.easy_install  # noqa
-        # 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,
-                 classifiers=classifiers,
-                 **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': [
+            'eac=cubicweb_eac',
+        ],
+    },
+    zip_safe=False,
+)
diff --git a/sobjects.py b/sobjects.py
deleted file mode 100644
--- a/sobjects.py
+++ /dev/null
@@ -1,119 +0,0 @@
-# copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# 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-eac server objects"""
-
-from six import text_type
-
-from cubicweb import ValidationError
-from cubicweb.predicates import match_user_groups
-from cubicweb.server import Service
-from cubicweb.dataimport import RQLObjectStore
-from cubicweb.dataimport.importer import ExtEntitiesImporter, cwuri2eid
-
-from cubes.skos import to_unicode
-
-from cubes.eac import dataimport
-
-
-def init_extid2eid_index(cnx, source):
-    """Return the dictionary mapping external id to eid for entities already in the database."""
-    # only consider the system source for EAC related entity types
-    extid2eid = cwuri2eid(cnx, dataimport.ETYPES_ORDER_HINT, source_eid=source.eid)
-    # though concepts may come from any source
-    extid2eid.update(cwuri2eid(cnx, ('Concept',)))
-    return extid2eid
-
-
-class EACImportService(Service):
-    """Service to import an AuthorityRecord from an EAC XML file."""
-
-    __regid__ = 'eac.import'
-    __select__ = match_user_groups('managers', 'users')
-
-    def call(self, stream, import_log, **kwargs):
-        store = RQLObjectStore(self._cw)
-        try:
-            created, updated, record_eid, not_visited = self.import_eac_stream(
-                stream, import_log, store, **kwargs)
-        except Exception as exc:
-            self._cw.rollback()
-            raise
-        else:
-            try:
-                self._cw.commit()
-            except ValidationError as exc:
-                import_log.record_error('validation error: ' + to_unicode(exc))
-                raise
-            else:
-                import_log.record_info('%s entities created' % len(created))
-                import_log.record_info('%s entities updated' % len(updated))
-        msg = self._cw._('element {0} not parsed')
-        for tagname, sourceline in not_visited:
-            import_log.record_warning(msg.format(tagname, sourceline), line=sourceline)
-        return created, updated, record_eid
-
-    def external_entities_generator(self, stream, import_log):
-        return dataimport.EACCPFImporter(stream, import_log, self._cw._)
-
-    def import_eac_stream(self, stream, import_log, store, extid2eid=None, **kwargs):
-        try:
-            return self._import_eac_stream(
-                stream, import_log, store, extid2eid=extid2eid, **kwargs)
-        except Exception as exc:
-            import_log.record_fatal(to_unicode(exc))
-            raise
-
-    def _import_eac_stream(self, stream, import_log, store, extid2eid=None, **kwargs):
-        source = self._cw.repo.system_source
-        if extid2eid is None:
-            extid2eid = init_extid2eid_index(self._cw, source)
-        importer = ExtEntitiesImporter(self._cw.vreg.schema, store,
-                                       import_log=import_log,
-                                       extid2eid=extid2eid,
-                                       etypes_order_hint=dataimport.ETYPES_ORDER_HINT,
-                                       **kwargs)
-        generator = self.external_entities_generator(stream, import_log)
-        extentities = self.external_entities_stream(generator.external_entities(), extid2eid)
-        importer.import_entities(extentities)
-        if generator.record is not None:
-            extid = generator.record.extid
-            record_eid = importer.extid2eid[extid]
-        else:
-            record_eid = None
-        return importer.created, importer.updated, record_eid, generator.not_visited()
-
-    def external_entities_stream(self, extentities, extid2eid):
-        """Extracted method allowing to plug transformation into the external entities generator.
-        """
-        extentities = (ee for ee in extentities
-                       if not (ee.etype == 'ExternalUri' and ee.extid in extid2eid))
-
-        def handle_agent_kind(extentities):
-            """Create agent kind when necessary and remove them from the entity stream, allowing to
-            set cwuri properly without attempt to update.
-            """
-            for extentity in extentities:
-                if extentity.etype == 'AgentKind':
-                    if extentity.extid not in extid2eid:
-                        name = next(iter(extentity.values['name']))
-                        kind = self._cw.create_entity('AgentKind', name=name,
-                                                      cwuri=text_type(extentity.extid))
-                        extid2eid[extentity.extid] = kind.eid
-                else:
-                    yield extentity
-
-        extentities = handle_agent_kind(extentities)
-        return extentities
diff --git a/test/test_dataimport.py b/test/test_dataimport.py
--- a/test/test_dataimport.py
+++ b/test/test_dataimport.py
@@ -29,7 +29,7 @@ from six.moves import map
 from cubicweb.dataimport.importer import ExtEntity, SimpleImportLog
 from cubicweb.devtools.testlib import CubicWebTC
 
-from cubes.eac import dataimport, testutils
+from cubicweb_eac import dataimport, testutils
 
 
 def mock_(string):
diff --git a/test/test_entities.py b/test/test_entities.py
--- a/test/test_entities.py
+++ b/test/test_entities.py
@@ -21,7 +21,7 @@ from lxml import etree
 
 from cubicweb.devtools.testlib import CubicWebTC
 
-from cubes.eac import testutils
+from cubicweb_eac import testutils
 
 
 class EACExportTC(CubicWebTC):
diff --git a/test/test_export.py b/test/test_export.py
--- a/test/test_export.py
+++ b/test/test_export.py
@@ -4,7 +4,7 @@ import lxml
 
 from cubicweb.devtools.testlib import CubicWebTC
 
-from cubes.eac import testutils
+from cubicweb_eac import testutils
 
 if not os.environ.get('EAC_TEXT_DIFF'):
     lxml.doctestcompare.install()
diff --git a/test/test_schema.py b/test/test_schema.py
--- a/test/test_schema.py
+++ b/test/test_schema.py
@@ -23,7 +23,7 @@ from contextlib import contextmanager
 from cubicweb import ValidationError
 from cubicweb.devtools.testlib import CubicWebTC
 
-from cubes.eac import testutils
+from cubicweb_eac import testutils
 
 
 @contextmanager
diff --git a/test/test_views.py b/test/test_views.py
--- a/test/test_views.py
+++ b/test/test_views.py
@@ -26,7 +26,7 @@ from cubicweb import Binary
 from cubicweb.devtools.testlib import CubicWebTC
 from cubicweb.web.views.actions import CopyAction
 
-from cubes.eac import testutils
+from cubicweb_eac import testutils
 
 
 class FuncViewsTC(CubicWebTC):
@@ -130,7 +130,7 @@ class AuthorityRecordFormsTC(CubicWebTC)
             self.assertNotIn(CopyAction, actions['moreactions'])
 
     def test_unrelated_authorityrecord(self):
-        from cubes.eac.views import unrelated_authorityrecord
+        from cubicweb_eac.views import unrelated_authorityrecord
         with self.admin_access.cnx() as cnx:
             ar1 = testutils.authority_record(cnx, u'1').eid
             ar2 = testutils.authority_record(cnx, u'2').eid
diff --git a/testutils.py b/testutils.py
deleted file mode 100644
--- a/testutils.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# 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-eac common test tools"""
-
-from __future__ import print_function
-
-from os.path import basename
-
-from doctest import Example
-
-from lxml import etree
-from lxml.doctestcompare import LXMLOutputChecker
-
-from cubicweb.dataimport.importer import SimpleImportLog
-
-
-def authority_record(cnx, name, kind=u'person', **kwargs):
-    """Return an AuthorityRecord with specified kind and name."""
-    kind_eid = cnx.find('AgentKind', name=kind)[0][0]
-    if 'reverse_name_entry_for' not in kwargs:
-        kwargs['reverse_name_entry_for'] = cnx.create_entity(
-            'NameEntry', parts=name, form_variant=u'authorized')
-    return cnx.create_entity('AuthorityRecord', agent_kind=kind_eid, **kwargs)
-
-
-def eac_import(cnx, fpath):
-    import_log = SimpleImportLog(basename(fpath))
-    created, updated, _ = cnx.call_service(
-        'eac.import', stream=fpath, import_log=import_log,
-        raise_on_error=True)
-    return created, updated
-
-
-class XmlTestMixin(object):
-    """Mixin class provinding additional assertion methods for checking XML data."""
-
-    def assertXmlEqual(self, actual, expected):
-        """Check that both XML strings represent the same XML tree."""
-        checker = LXMLOutputChecker()
-        if not checker.check_output(expected, actual, 0):
-            message = checker.output_difference(Example("", expected), actual, 0)
-            self.fail(message)
-
-    def assertXmlValid(self, xml_data, xsd_filename, debug=False):
-        """Validate an XML file (.xml) according to an XML schema (.xsd)."""
-        with open(xsd_filename) as xsd:
-            xmlschema = etree.XMLSchema(etree.parse(xsd))
-        # Pretty-print xml_data to get meaningful line information.
-        xml_data = etree.tostring(etree.fromstring(xml_data), pretty_print=True)
-        xml_data = etree.fromstring(xml_data)
-        if debug and not xmlschema.validate(xml_data):
-            print(etree.tostring(xml_data, pretty_print=True))
-        xmlschema.assertValid(xml_data)
diff --git a/tox.ini b/tox.ini
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py27,flake8
+envlist = py27,flake8,check-manifest
 
 [testenv]
 sitepackages = true
@@ -10,6 +10,13 @@ deps =
 commands =
   {envpython} -m pytest {posargs:test}
 
+[testenv:check-manifest]
+skip_install = true
+deps =
+  check-manifest
+commands =
+  {envpython} -m check_manifest {toxinidir}
+
 [testenv:flake8]
 skip_install = true
 whitelist_externals =
@@ -19,5 +26,5 @@ deps =
 commands = flake8
 
 [flake8]
-exclude = migration/*,.tox/*
+exclude = cubicweb_eac/migration/*,.tox/*
 max-line-length = 100
diff --git a/views.py b/views.py
deleted file mode 100644
--- a/views.py
+++ /dev/null
@@ -1,242 +0,0 @@
-# copyright 2015-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# 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-eac views, mostly for import/export of AuthorityRecord from/to EAC.
-
-Using only this cube, UI will be hardly usable to handle the complexity of the EAC model. You'll
-find an UI implementation for EAC as part of the `saem_ref`_ cube.
-
-.. _`saem_ref`: https://www.cubicweb.org/project/cubicweb-saem_ref
-"""
-
-from functools import partial
-import os.path
-
-from six import text_type
-
-from cubicweb import tags, _
-from cubicweb.view import View
-from cubicweb.predicates import is_instance, match_user_groups, one_line_rset, score_entity
-from cubicweb.dataimport.importer import HTMLImportLog
-from cubicweb.web import action, component, formfields as ff, formwidgets as fw, httpcache
-from cubicweb.web.views import actions, calendar, cwsources, forms, idownloadable, uicfg
-
-from cubes.skos import to_unicode
-
-from cubes.eac import dataimport
-
-
-pvdc = uicfg.primaryview_display_ctrl
-afs = uicfg.autoform_section
-affk = uicfg.autoform_field_kwargs
-
-
-# Edit / View setup
-
-actions.CopyAction.__select__ &= ~is_instance('AuthorityRecord')
-
-pvdc.tag_subject_of(('AuthorityRecord', 'agent_kind', '*'), {'vid': 'text'})
-
-for etype, attr in (
-    ('AuthorityRecord', 'isni'),
-    ('AgentPlace', 'name'),
-    ('AgentPlace', 'role'),
-    ('Activity', 'type'),
-    ('Mandate', 'term'),
-    ('LegalStatus', 'term'),
-    ('EACResourceRelation', 'agent_role'),
-    ('EACResourceRelation', 'resource_role'),
-    ('EACSource', 'title'),
-    ('EACSource', 'url'),
-):
-    affk.set_field_kwargs(etype, attr, widget=fw.TextInput({'size': 80}))
-
-
-affk.set_field_kwargs('NameEntry', 'form_variant', value=u'authorized')
-
-
-def unrelated_authorityrecord(rtype, form, field, **kwargs):
-    """Choices function returning AuthorityRecord choices.
-
-    Choices values are unrelated through `rtype` to edited entity thus
-    filtering out possible ExternalURI targets. It also account for __linkto
-    info to build a restricted choice when needed.
-    """
-    try:
-        linkto = form.linked_to[rtype, 'subject'][0]
-    except (KeyError, IndexError):
-        rset = form.edited_entity.unrelated(rtype, targettype='AuthorityRecord')
-        return [(x.dc_title(), text_type(x.eid)) for x in rset.entities()]
-    else:
-        entity = form._cw.entity_from_eid(linkto)
-        return [(entity.dc_title(), text_type(entity.eid))]
-
-
-# Set fields order for authority records relations (autoform and primary view)
-for etype, from_rdef, to_rdef in (
-    ('AssociationRelation', 'association_from', 'association_to'),
-    ('ChronologicalRelation', 'chronological_predecessor', 'chronological_successor'),
-    ('HierarchicalRelation', 'hierarchical_parent', 'hierarchical_child'),
-):
-    affk.set_fields_order(etype, ('description', 'start_date', 'end_date',
-                                  from_rdef, to_rdef))
-    for rtype in (from_rdef, to_rdef):
-        affk.tag_subject_of((etype, rtype, '*'),
-                            {'choices': partial(unrelated_authorityrecord, rtype)})
-
-    pvdc.set_fields_order(etype, ('description', 'start_date',
-                                  'end_date', from_rdef, to_rdef))
-
-
-class XMLWrapComponent(component.EntityCtxComponent):
-    """CtxComponent to display xml_wrap of entities."""
-    __select__ = (
-        component.EntityCtxComponent.__select__
-        & score_entity(lambda x: getattr(x, 'xml_wrap', None))
-    )
-    __regid__ = 'eac.xml_wrap'
-    context = 'navcontentbottom'
-    title = _('xml_wrap')
-
-    def render_body(self, w):
-        sourcemt, targetmt = 'text/xml', 'text/html'
-        data = self.entity.xml_wrap
-        w(self.entity._cw_mtc_transform(data.getvalue(),
-                                        sourcemt, targetmt, 'utf-8'))
-
-
-class AuthorityRecordICalendarableAdapter(calendar.ICalendarableAdapter):
-    """ICalendarable adapter for AuthorityRecord entity type."""
-    __select__ = calendar.ICalendarableAdapter.__select__ & is_instance('AuthorityRecord')
-
-    @property
-    def start(self):
-        return self.entity.start_date
-
-    @property
-    def stop(self):
-        return self.entity.end_date
-
-
-# Import
-
-class EACImportMixin(object):
-    __regid__ = 'eac.import'
-    __select__ = match_user_groups('managers', 'users')
-
-
-class EACImportForm(EACImportMixin, forms.FieldsForm):
-    """File import form for EAC-CPF"""
-    filefield = ff.FileField(name='file', label=_('EAC-CPF file'),
-                             required=True)
-    form_buttons = [fw.SubmitButton(label=_('do_import'))]
-
-    @property
-    def action(self):
-        return self._cw.build_url(vid=self.__regid__)
-
-
-class EACImportView(EACImportMixin, View):
-    """EAC-CPF import controller"""
-
-    def call(self):
-        self.w(tags.h1(self._cw._('Importing an AuthorityRecord from a EAC-CPF file')))
-        form = self._cw.vreg['forms'].select(self.__regid__, self._cw)
-        if form.posting:
-            posted = form.process_posted()
-            stream = posted['file']
-            import_log = HTMLImportLog(os.path.basename(stream.filename))
-            try:
-                _, _, eid = self._cw.cnx.call_service(self.__regid__, stream=stream,
-                                                      import_log=import_log,
-                                                      **self.service_kwargs(posted))
-            except dataimport.InvalidXML as exc:
-                msg = self._cw._('Invalid XML file')
-                self.exception('error while importing %s', stream.filename)
-                mtype = 'danger'
-                import_log.record_fatal(
-                    self._cw._('xml syntax error: ') + to_unicode(exc))
-                self._cw.status_out = 400
-            except dataimport.MissingTag as exc:
-                if exc.tag_parent:
-                    err = self._cw._('Missing tag %(tag)s within element %(parent)s in XML file')
-                    params = {'tag': exc.tag, 'parent': exc.tag_parent}
-                else:
-                    err = self._cw._('Missing tag %(tag)s in XML file')
-                    params = {'tag': exc.tag}
-                msg = err % params
-                self.exception('error while importing %s', stream.filename)
-                mtype = 'danger'
-                import_log.record_fatal(msg)
-                self._cw.status_out = 400
-            except Exception as exc:  # pylint: disable=broad-except
-                msg = self._cw._('EAC import failed')
-                self.exception('error while importing %s', stream.filename)
-                mtype = 'danger'
-                self._cw.status_out = 500
-            else:
-                record = self._cw.find('AuthorityRecord', eid=eid).one()
-                msg = (self._cw._('EAC-CPF import completed: %s') %
-                       record.view('oneline'))
-                mtype = 'success'
-            self.w(tags.div(msg, klass="alert alert-%s" % mtype))
-            if import_log.logs:
-                self._cw.view('cw.log.table',
-                              pyvalue=cwsources.log_to_table(
-                                  self._cw, u''.join(import_log.logs)),
-                              default_level='Warning', w=self.w)
-        else:
-            form.render(w=self.w)
-
-    def service_kwargs(self, posted):
-        """Subclass access point to provide extra arguments to the service (e.g. saem_ref cube).
-        """
-        return {}
-
-
-# Export
-
-class EACExportAction(action.Action):
-    __regid__ = 'eac.export'
-    __select__ = (action.Action.__select__
-                  & one_line_rset()
-                  & is_instance('AuthorityRecord'))
-
-    title = _('EAC export')
-    category = 'moreactions'
-
-    def url(self):
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        return entity.absolute_url(vid=self.__regid__)
-
-
-class EACDownloadView(idownloadable.DownloadView):
-    """EAC download view"""
-    __regid__ = 'eac.export'
-    __select__ = one_line_rset() & is_instance('AuthorityRecord')
-
-    http_cache_manager = httpcache.NoHTTPCacheManager
-    adapter_id = 'EAC-CPF'
-
-    def set_request_content_type(self):
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        adapter = entity.cw_adapt_to(self.adapter_id)
-        self._cw.set_content_type(adapter.content_type, filename=adapter.file_name,
-                                  encoding=adapter.encoding, disposition='attachment')
-
-    def call(self):
-        entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
-        adapter = entity.cw_adapt_to(self.adapter_id)
-        self.w(adapter.dump())


More information about the saem-devel mailing list