# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import os
from collections import defaultdict
from difflib import SequenceMatcher
from io import BytesIO

import polib
from defusedxml.minidom import parseString
from genshi.filters.i18n import extract as genshi_extract
from lxml import etree
from relatorio.reporting import MIMETemplateLoader
from relatorio.templates.opendocument import get_zip_file
from sql import Column, Literal, Null
from sql.aggregate import Max
from sql.conditionals import Case
from sql.functions import Position, Substring

from trytond.cache import Cache
from trytond.config import config
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.model import Index, ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.pyson import Eval, PYSONEncoder
from trytond.tools import cursor_dict, file_open, grouped_slice
from trytond.tools.string_ import LazyString, StringPartitioned
from trytond.transaction import (
    Transaction, inactive_records, without_check_access)
from trytond.wizard import (
    Button, StateAction, StateTransition, StateView, Wizard)

from .action import ACTION_SELECTION
from .lang import get_parent_language as get_parent

TRANSLATION_TYPE = [
    ('field', 'Field'),
    ('model', 'Model'),
    ('report', 'Report'),
    ('selection', 'Selection'),
    ('view', 'View'),
    ('wizard_button', 'Wizard Button'),
    ('help', 'Help'),
]
INTERNAL_LANG = 'en'
ACTION_MODELS = {'ir.action'} | dict(ACTION_SELECTION).keys()


class OverriddenError(UserError):
    pass


class TrytonPOFile(polib.POFile):

    def sort(self):
        return super(TrytonPOFile, self).sort(
            key=lambda x: (x.msgctxt, x.msgid))


class Translation(
        fields.fmany2one(
            'module_ref', 'module', 'ir.module,name', "Module",
            readonly=True, ondelete='CASCADE'),
        fields.fmany2one(
            'overriding_module_ref', 'overriding_module', 'ir.module,name',
            "Overriding Module", readonly=True),
        ModelSQL, ModelView):
    "Translation"
    __name__ = "ir.translation"

    name = fields.Char('Field Name', required=True)
    res_id = fields.Integer('Resource ID', required=True)
    lang = fields.Selection('get_language', string='Language')
    type = fields.Selection(TRANSLATION_TYPE, string='Type',
       required=True)
    src = fields.Text('Source')
    value = fields.Text('Translation Value')
    module = fields.Char('Module', readonly=True)
    fuzzy = fields.Boolean('Fuzzy')
    model = fields.Function(fields.Char('Model'), 'get_model',
            searcher='search_model')
    overriding_module = fields.Char('Overriding Module', readonly=True)
    _translation_cache = Cache('ir.translation', context=False)
    _translation_report_cache = Cache(
        'ir.translation.get_report', context=False)
    _get_language_cache = Cache('ir.translation.get_language', context=False)

    @classmethod
    def __setup__(cls):
        cls.name.search_unaccented = False
        super().__setup__()
        table = cls.__table__()

        cls._sql_indexes.update({
                Index(
                    table, (Index.Unaccent(table.src), Index.Similarity())),
                Index(
                    table, (Index.Unaccent(table.value), Index.Similarity())),
                Index(
                    table,
                    (table.type, Index.Equality()),
                    (table.name, Index.Equality()),
                    (table.lang, Index.Equality()),
                    (table.res_id, Index.Range()),
                    (table.fuzzy, Index.Equality()),
                    (Index.Unaccent(table.value), Index.Similarity())),
                Index(
                    table,
                    (table.type, Index.Equality()),
                    (table.name, Index.Equality()),
                    (table.lang, Index.Equality()),
                    (table.fuzzy, Index.Equality())),
                Index(
                    table,
                    (table.res_id, Index.Equality()),
                    (table.name, Index.Equality()),
                    (table.lang, Index.Equality()),
                    (table.type, Index.Equality())),
                })

    @classmethod
    def __register__(cls, module_name):
        table = cls.__table_handler__(module_name)

        # Migration from 5.0: remove src_md5
        if table.column_exist('src_md5'):
            table.drop_constraint('translation_md5_uniq')
            table.drop_column('src_md5')

        super(Translation, cls).__register__(module_name)

    @classmethod
    def register_model(cls, model, module_name):
        cursor = Transaction().connection.cursor()
        ir_translation = cls.__table__()

        if not model.__doc__:
            return

        name = model.__name__ + ',name'
        src = model._get_name()
        if not src:
            return
        cursor.execute(*ir_translation.select(ir_translation.id,
                where=(ir_translation.lang == INTERNAL_LANG)
                & (ir_translation.type == 'model')
                & (ir_translation.name == name)
                # Keep searching on all values for migration
                & ((ir_translation.res_id == -1)
                    | (ir_translation.res_id == Null)
                    | (ir_translation.res_id == 0))))
        trans_id = None
        if cursor.rowcount == -1 or cursor.rowcount is None:
            data = cursor.fetchone()
            if data:
                trans_id, = data
        elif cursor.rowcount != 0:
            trans_id, = cursor.fetchone()
        if trans_id is None:
            cursor.execute(*ir_translation.insert(
                    [Column(ir_translation, c)
                        for c in ('name', 'lang', 'type', 'src',
                            'value', 'module', 'fuzzy', 'res_id')],
                    [[
                            name, INTERNAL_LANG, 'model', src,
                            '', module_name, False, -1]]))
        else:
            cursor.execute(*ir_translation.update(
                    [ir_translation.src],
                    [src],
                    where=ir_translation.id == trans_id))

    @classmethod
    def register_fields(cls, model, module_name):
        cursor = Transaction().connection.cursor()
        ir_translation = cls.__table__()

        # Prefetch field translations
        translations = dict(
            field=defaultdict(dict),
            help=defaultdict(dict),
            selection=defaultdict(dict))
        if model._fields:
            names = ['%s,%s' % (model.__name__, f) for f in model._fields]
            cursor.execute(*ir_translation.select(ir_translation.id,
                    ir_translation.name, ir_translation.src,
                    ir_translation.type,
                    where=((ir_translation.lang == INTERNAL_LANG)
                        & ir_translation.type.in_(
                            ('field', 'help', 'selection'))
                        & ir_translation.name.in_(names))))
            for trans in cursor_dict(cursor):
                sources = translations[trans['type']][trans['name']]
                sources[trans['src']] = trans

        columns = [ir_translation.name, ir_translation.lang,
            ir_translation.type, ir_translation.src, ir_translation.value,
            ir_translation.module, ir_translation.fuzzy, ir_translation.res_id]

        def insert(field, type, name, string):
            inserted = False
            for val in string:
                if not val or val in translations[type][name]:
                    continue
                if isinstance(val, LazyString):
                    continue
                assert type not in {'field', 'help'} or not inserted, (
                    "More than one resource "
                    f"for {type} of {name} in {module_name}")
                cursor.execute(
                    *ir_translation.insert(columns,
                        [[
                                name, INTERNAL_LANG, type, val,
                                '', module_name, False, -1]]))
                inserted = True

        for field_name, field in model._fields.items():
            name = model.__name__ + ',' + field_name
            insert(field, 'field', name, field.string)
            insert(field, 'help', name, field.help)
            if (hasattr(field, 'selection')
                    and isinstance(field.selection, (tuple, list))
                    and getattr(field, 'translate_selection', True)):
                selection = [s for _, s in field.selection]
                insert(field, 'selection', name, selection)

    @classmethod
    def register_wizard(cls, wizard, module_name):
        cursor = Transaction().connection.cursor()
        ir_translation = cls.__table__()

        # Prefetch button translations
        cursor.execute(*ir_translation.select(
                ir_translation.id, ir_translation.name, ir_translation.src,
                where=((ir_translation.lang == INTERNAL_LANG)
                    & (ir_translation.type == 'wizard_button')
                    & (ir_translation.name.like(wizard.__name__ + ',%')))))
        trans_buttons = {t['name']: t for t in cursor_dict(cursor)}

        def update_insert_button(state_name, button):
            if not button.string:
                return
            trans_name = '%s,%s,%s' % (
                wizard.__name__, state_name, button.state)
            if trans_name not in trans_buttons:
                cursor.execute(*ir_translation.insert(
                        [ir_translation.name, ir_translation.lang,
                            ir_translation.type, ir_translation.src,
                            ir_translation.value, ir_translation.module,
                            ir_translation.fuzzy, ir_translation.res_id],
                        [[
                                trans_name, INTERNAL_LANG,
                                'wizard_button', button.string,
                                '', module_name,
                                False, -1]]))
            elif trans_buttons[trans_name] != button.string:
                cursor.execute(*ir_translation.update(
                        [ir_translation.src],
                        [button.string],
                        where=ir_translation.id
                        == trans_buttons[trans_name]['id']))

        for state_name, state in wizard.states.items():
            if not isinstance(state, StateView):
                continue
            for button in state.buttons:
                update_insert_button(state_name, button)

    @staticmethod
    def default_fuzzy():
        return False

    @staticmethod
    def default_res_id():
        return -1

    def get_model(self, name):
        return self.name.split(',')[0]

    @classmethod
    def search_rec_name(cls, name, clause):
        clause = tuple(clause)
        if clause[1].startswith('!') or clause[1].startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        return [bool_op,
            ('src',) + clause[1:],
            ('value',) + clause[1:],
            (cls._rec_name,) + clause[1:],
            ]

    @classmethod
    def search_model(cls, name, clause):
        table = cls.__table__()
        _, operator, value = clause
        Operator = fields.SQL_OPERATORS[operator]
        return [('id', 'in', table.select(table.id,
                    where=Operator(Substring(table.name, 1,
                            Case((
                                    Position(',', table.name) > 0,
                                    Position(',', table.name) - 1),
                                else_=0)), value)))]

    @classmethod
    def get_language(cls):
        language = Transaction().language
        result = cls._get_language_cache.get(language)
        if result is not None:
            return result
        pool = Pool()
        Lang = pool.get('ir.lang')
        langs = Lang.search([])
        result = [(lang.code, lang.name) for lang in langs]
        cls._get_language_cache.set(language, result)
        return result

    @classmethod
    def view_attributes(cls):
        return [('/form//field[@name="value"]', 'spell', Eval('lang'))]

    @classmethod
    @without_check_access
    def get_ids(cls, name, ttype, lang, ids, cached_after=None):
        "Return translation for each id"
        pool = Pool()
        ModelFields = pool.get('ir.model.field')
        Model = pool.get('ir.model')
        context = Transaction().context
        fuzzy_translation = context.get('fuzzy_translation', False)

        translations, to_fetch = {}, []
        name = str(name)
        ttype = str(ttype)
        lang = str(lang)
        if name.split(',')[0] in ('ir.model.field', 'ir.model'):
            field_name = name.split(',')[1]
            if name.split(',')[0] == 'ir.model.field':
                if field_name == 'field_description':
                    ttype = 'field'
                else:
                    ttype = 'help'
                records = ModelFields.browse(ids)
            else:
                ttype = 'model'
                records = Model.browse(ids)

            trans_args = []
            for record in records:
                if ttype in ('field', 'help'):
                    name = record.model + ',' + record.name
                else:
                    name = record.model + ',' + field_name
                trans_args.append((name, ttype, lang, None))
            cls.get_sources(trans_args)

            for record in records:
                if ttype in ('field', 'help'):
                    name = record.model + ',' + record.name
                else:
                    name = record.model + ',' + field_name
                translations[record.id] = cls.get_source(name, ttype, lang)
                if translations[record.id] is None:
                    with Transaction().set_context(language=lang):
                        if ttype in {'field', 'help'}:
                            try:
                                field = getattr(
                                    pool.get(record.model), record.name)
                            except KeyError:
                                continue
                            translations[record.id] = ''
                            if ttype == 'field':
                                value = field.string
                            else:
                                value = field.help
                        else:
                            try:
                                model = pool.get(record.model)
                            except KeyError:
                                continue
                            if not model.__doc__:
                                continue
                            value = model._get_name()
                        if isinstance(value, StringPartitioned):
                            for source in value:
                                translations[record.id] += source
                        else:
                            translations[record.id] = value
            return translations

        # Don't use cache for fuzzy translation
        if (not fuzzy_translation
                and (not cached_after
                    or not cls._translation_cache.sync_since(cached_after))):
            for obj_id in ids:
                trans = cls._translation_cache.get((name, ttype, lang, obj_id),
                    -1)
                if trans != -1:
                    translations[obj_id] = trans
                else:
                    to_fetch.append(obj_id)
        else:
            to_fetch = ids

        if to_fetch:
            # Get parent translations
            parent_lang = get_parent(lang)
            if parent_lang:
                translations.update(
                    cls.get_ids(name, ttype, parent_lang, to_fetch))

            if fuzzy_translation:
                fuzzy_clause = []
            else:
                fuzzy_clause = [('fuzzy', '=', False)]
            for sub_to_fetch in grouped_slice(to_fetch):
                for translation in cls.search([
                            ('lang', '=', lang),
                            ('type', '=', ttype),
                            ('name', '=', name),
                            ('value', '!=', ''),
                            ('value', '!=', None),
                            ('res_id', 'in', list(sub_to_fetch)),
                            ] + fuzzy_clause):
                    translations[translation.res_id] = translation.value
            # Don't store fuzzy translation in cache
            if not fuzzy_translation:
                for res_id in to_fetch:
                    value = translations.setdefault(res_id)
                    cls._translation_cache.set(
                        (name, ttype, lang, res_id), value)
        return translations

    @classmethod
    @without_check_access
    def set_ids(cls, name, ttype, lang, ids, values):
        "Set translation for each id"
        pool = Pool()
        ModelFields = pool.get('ir.model.field')
        Model = pool.get('ir.model')
        Config = pool.get('ir.configuration')
        transaction = Transaction()
        in_max = transaction.database.IN_MAX

        if len(ids) > in_max:
            for i in range(0, len(ids), in_max):
                sub_ids = ids[i:i + in_max]
                sub_values = values[i:i + in_max]
                cls.set_ids(name, ttype, lang, sub_ids, sub_values)
            return

        model_name, field_name = name.split(',')
        if model_name in ('ir.model.field', 'ir.model'):
            if model_name == 'ir.model.field':
                if field_name == 'field_description':
                    ttype = 'field'
                else:
                    ttype = 'help'
                with Transaction().set_context(language=INTERNAL_LANG):
                    records = ModelFields.browse(ids)
            else:
                ttype = 'model'
                with Transaction().set_context(language=INTERNAL_LANG):
                    records = Model.browse(ids)

            def get_name(record):
                if ttype in ('field', 'help'):
                    return record.model + ',' + record.name
                else:
                    return record.model + ',' + field_name

            name2translations = defaultdict(list)
            for translation in cls.search([
                        ('lang', '=', lang),
                        ('type', '=', ttype),
                        ('name', 'in', [get_name(r) for r in records]),
                        ]):
                name2translations[translation.name].append(translation)

            to_save, to_delete = [], []
            for record, value in zip(records, values):
                translations = name2translations.get(get_name(record))
                if lang == INTERNAL_LANG:
                    src = value
                else:
                    src = getattr(record, field_name)
                if not translations:
                    if not src and not value:
                        continue
                    translation = cls()
                    translation.name = name
                    translation.lang = lang
                    translation.type = ttype
                    translations.append(translation)
                if not src and not value:
                    to_delete.extend(translations)
                else:
                    for translation in translations:
                        translation.src = src
                        translation.value = value
                        translation.fuzzy = False
                        to_save.append(translation)
            cls.save(to_save)
            cls.delete(to_delete)
            return

        Model = pool.get(model_name)
        with Transaction().set_context(language=Config.get_language()):
            records = Model.browse(ids)

        id2translations = defaultdict(list)
        other_translations = defaultdict(list)
        for translation in cls.search([
                    ('lang', '=', lang),
                    ('type', '=', ttype),
                    ('name', '=', name),
                    ('res_id', 'in', ids),
                    ]):
            id2translations[translation.res_id].append(translation)

        if (lang == Config.get_language()
                and Transaction().context.get('fuzzy_translation', True)):
            for translation in cls.search([
                        ('lang', '!=', lang),
                        ('type', '=', ttype),
                        ('name', '=', name),
                        ('res_id', 'in', ids),
                        ]):
                other_translations[translation.res_id].append(translation)

        to_save, to_delete = [], []
        for record, value in zip(records, values):
            translations = id2translations[record.id]
            if lang == Config.get_language():
                src = value
            else:
                src = getattr(record, field_name)
            if not translations:
                if not src and not value:
                    continue
                translation = cls()
                translation.name = name
                translation.lang = lang
                translation.type = ttype
                translation.res_id = record.id
                translations.append(translation)
            else:
                other_langs = other_translations[record.id]
                if not src and not value:
                    to_delete.extend(other_langs)
                else:
                    for other_lang in other_langs:
                        other_lang.src = src
                        other_lang.fuzzy = True
                        to_save.append(other_lang)
            if not src and not value:
                to_delete.extend(translations)
            else:
                for translation in translations:
                    translation.value = value
                    translation.src = src
                    translation.fuzzy = False
                    to_save.append(translation)
        cls.save(to_save)
        cls.delete(to_delete)

    @classmethod
    @without_check_access
    def delete_ids(cls, model, ttype, ids):
        "Delete translation for each id"
        translations = []
        for sub_ids in grouped_slice(ids):
            translations += cls.search([
                    ('type', '=', ttype),
                    ('name', 'like', model + ',%'),
                    ('res_id', 'in', list(sub_ids)),
                    ])
        cls.delete(translations)

    @classmethod
    def get_source(cls, name, ttype, lang, source=None):
        "Return translation for source"
        args = (name, ttype, lang, source)
        result = cls.get_sources([args])
        return result[args]

    @classmethod
    def get_sources(cls, args):
        '''
        Take a list of (name, ttype, lang, source).
        Add the translations to the cache.
        Return a dict with the translations.
        '''
        res = {}
        parent_args = []
        parent_langs = []
        clauses = []
        transaction = Transaction()
        if len(args) > transaction.database.IN_MAX:
            for sub_args in grouped_slice(args):
                res.update(cls.get_sources(list(sub_args)))
            return res

        to_cache = []
        for name, ttype, lang, source in args:
            name = str(name)
            ttype = str(ttype)
            lang = str(lang)
            if source is not None:
                source = str(source)
            trans = cls._translation_cache.get((name, ttype, lang, source), -1)
            if trans != -1:
                res[(name, ttype, lang, source)] = trans
            else:
                to_cache.append((name, ttype, lang, source))
                parent_lang = get_parent(lang)
                if parent_lang:
                    parent_args.append((name, ttype, parent_lang, source))
                    parent_langs.append(lang)
                res[(name, ttype, lang, source)] = None
                clause = [
                    ('lang', '=', lang),
                    ('type', '=', ttype),
                    ('name', '=', name),
                    ('value', '!=', ''),
                    ('value', '!=', None),
                    ('fuzzy', '=', False),
                    ('res_id', '=', -1),
                    ]
                if source is not None:
                    clause.append(('src', '=', source))
                clauses.append(clause)

        # Get parent transactions
        if parent_args:
            parent_src = cls.get_sources(parent_args)
            for (name, ttype, parent_lang, source), lang in zip(
                    parent_args, parent_langs):
                res[(name, ttype, lang, source)] = parent_src[
                    (name, ttype, parent_lang, source)]

        in_max = transaction.database.IN_MAX // 7
        for sub_clause in grouped_slice(clauses, in_max):
            for translation in cls.search(['OR'] + list(sub_clause)):
                key = (translation.name, translation.type,
                    translation.lang, translation.src)
                if key not in args:
                    key = key[:-1] + (None,)
                res[key] = translation.value
        for key in to_cache:
            cls._translation_cache.set(key, res[key])
        return res

    @classmethod
    def get_report(cls, report_name, text):
        language = Transaction().language
        key = (report_name, language)
        if cls._translation_report_cache.get(key) is None:
            cache = {}
            code = language
            while code:
                translations = cls.search([
                        ('lang', '=', code),
                        ('type', '=', 'report'),
                        ('name', '=', report_name),
                        ('value', '!=', ''),
                        ('value', '!=', None),
                        ('fuzzy', '=', False),
                        ('res_id', '=', -1),
                        ], order=[('module', 'DESC')])
                for translation in translations:
                    cache.setdefault(translation.src, translation.value)
                code = get_parent(code)
            cls._translation_report_cache.set(key, cache)
        return cls._translation_report_cache.get(key, {}).get(text, text)

    @classmethod
    def copy(cls, translations, default=None):
        default = default.copy() if default is not None else {}
        default.setdefault('module')
        default.setdefault('overriding_module')
        return super().copy(translations, default=default)

    @classmethod
    def delete(cls, translations):
        cls.__clear_cache_for(translations)
        return super().delete(translations)

    @classmethod
    def create(cls, vlist):
        vlist = [x.copy() for x in vlist]

        for vals in vlist:
            if not vals.get('module'):
                if Transaction().context.get('module'):
                    vals['module'] = Transaction().context['module']
        translations = super().create(vlist)
        cls.__clear_cache_for(translations)
        return translations

    @classmethod
    def write(cls, *args):
        super().write(*args)
        translations = sum(args[0:None:2], [])
        cls.__clear_cache_for(translations)

    @classmethod
    def __clear_cache_for(cls, translations):
        cls._translation_cache.clear()
        cls._translation_report_cache.clear()
        types = {t.type for t in translations}
        models = {t.model for t in translations}
        cls._clear_cache_for(types, models)

    @classmethod
    def _clear_cache_for(cls, types, models):
        pool = Pool()
        if 'field' in types and 'ir.message' in models:
            pool.get('ir.message')._message_cache.clear()
        if 'field' in types and 'ir.model' in models:
            pool.get('ir.model')._get_names_cache.clear()
        if 'field' in types and 'ir.model.field' in models:
            pool.get('ir.model.field')._get_name_cache.clear()
        if 'field' in types and ACTION_MODELS & models:
            pool.get('ir.action.keyword')._get_keyword_cache.clear()
            ModelView._view_toolbar_get_cache.clear()
        if 'field' in types and {'ir.export', 'ir.email.template'} & models:
            ModelView._view_toolbar_get_cache.clear()
        if 'view' in types:
            ModelView._fields_view_get_cache.clear()

    @classmethod
    def extra_model_data(cls, model_data):
        "Yield extra model linked to the model data"
        if model_data.model in (
                'ir.action.report',
                'ir.action.act_window',
                'ir.action.wizard',
                'ir.action.url',
                ):
            yield 'ir.action'

    @property
    def unique_key(self):
        if self.type == 'model':
            return (self.name, self.res_id, self.type)
        return (self.name, self.res_id, self.type, self.src)

    @classmethod
    def from_poentry(cls, entry):
        'Returns a translation instance for a entry of pofile and its res_id'
        ttype, name, res_id = entry.msgctxt.split(':')
        src = entry.msgid
        value = entry.msgstr
        fuzzy = 'fuzzy' in entry.flags

        translation = cls(name=name, type=ttype, src=src, fuzzy=fuzzy,
            value=value)
        return translation, res_id

    @classmethod
    def translation_import(cls, lang, module, po_path):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        if isinstance(po_path, str):
            po_path = [po_path]
        models_data = ModelData.search([
                ('module', '=', module),
                ])
        fs_id2prop = {}
        for model_data in models_data:
            fs_id2prop.setdefault(model_data.model, {})
            fs_id2prop[model_data.model][model_data.fs_id] = \
                (model_data.db_id, model_data.noupdate)
            for extra_model in cls.extra_model_data(model_data):
                fs_id2prop.setdefault(extra_model, {})
                fs_id2prop[extra_model][model_data.fs_id] = \
                    (model_data.db_id, model_data.noupdate)

        translations = set()
        to_save = []

        id2translation = {}
        key2ids = {}
        module_translations = cls.search([
                ('lang', '=', lang),
                ('module', '=', module),
                ], order=[])
        for translation in module_translations:
            # Migration from 5.0: ignore error type
            if translation.type == 'error':
                continue
            key = translation.unique_key
            if not key:
                raise ValueError('Unknow translation type: %s' %
                    translation.type)
            key2ids.setdefault(key, []).append(translation.id)
            if len(module_translations) <= config.getint('cache', 'record'):
                id2translation[translation.id] = translation

        def override_translation(ressource_id, new_translation):
            res_id_module, res_id = ressource_id.split('.')
            if res_id:
                model_data, = ModelData.search([
                        ('module', '=', res_id_module),
                        ('fs_id', '=', res_id),
                        ])
                res_id = model_data.db_id
            else:
                res_id = -1
            with Transaction().set_context(module=res_id_module):
                domain = [
                    ('name', '=', new_translation.name),
                    ('res_id', '=', res_id),
                    ('lang', '=', new_translation.lang),
                    ('type', '=', new_translation.type),
                    ('module', '=', res_id_module),
                    ]
                if new_translation.type in {
                        'report', 'view', 'wizard_button', 'selection'}:
                    domain.append(('src', '=', new_translation.src))
                translation, = cls.search(domain)
                if translation.value != new_translation.value:
                    translation.value = new_translation.value
                    translation.overriding_module = module
                    translation.fuzzy = new_translation.fuzzy
                    return translation

        # Make a first loop to retreive translation ids in the right order to
        # get better read locality and a full usage of the cache.
        translation_ids = []
        if len(module_translations) <= config.getint('cache', 'record'):
            processes = (True,)
        else:
            processes = (False, True)
        for processing in processes:
            if (processing
                    and len(module_translations) > config.getint('cache',
                        'record')):
                id2translation = dict((t.id, t)
                    for t in cls.browse(translation_ids))
            for pofile in po_path:
                for entry in polib.pofile(pofile):
                    if entry.obsolete:
                        continue
                    translation, res_id = cls.from_poentry(entry)
                    # Migration from 5.0: ignore error type
                    if translation.type == 'error':
                        continue
                    translation.lang = lang
                    translation.module = module
                    noupdate = False

                    if '.' in res_id:
                        to_save.append(override_translation(res_id,
                                translation))
                        continue

                    model = translation.name.split(',')[0]
                    if (model in fs_id2prop
                            and res_id in fs_id2prop[model]):
                        res_id, noupdate = fs_id2prop[model][res_id]

                    if res_id:
                        try:
                            res_id = int(res_id)
                        except ValueError:
                            res_id = None
                    if not res_id:
                        res_id = -1

                    translation.res_id = res_id
                    key = translation.unique_key
                    if not key:
                        raise ValueError('Unknow translation type: %s' %
                            translation.type)
                    ids = key2ids.get(key, [])

                    if not processing:
                        translation_ids.extend(ids)
                        continue

                    if not ids:
                        to_save.append(translation)
                    else:
                        for translation_id in ids:
                            old_translation = id2translation[translation_id]
                            if not noupdate:
                                old_translation.value = translation.value
                                old_translation.fuzzy = translation.fuzzy
                                to_save.append(old_translation)
                            else:
                                translations.add(old_translation)
        cls.save([_f for _f in to_save if _f])
        translations |= set(to_save)

        if translations:
            all_translations = set(cls.search([
                        ('module', '=', module),
                        ('lang', '=', lang),
                        ]))
            translations_to_delete = all_translations - translations
            cls.delete(list(translations_to_delete))
        return len(translations)

    @classmethod
    def translation_export(cls, lang, module):
        pool = Pool()
        ModelData = pool.get('ir.model.data')
        Config = pool.get('ir.configuration')

        models_data = ModelData.search([
                ('module', '=', module),
                ])
        db_id2fs_id = {}
        for model_data in models_data:
            db_id2fs_id.setdefault(model_data.model, {})
            db_id2fs_id[model_data.model][model_data.db_id] = model_data.fs_id
            for extra_model in cls.extra_model_data(model_data):
                db_id2fs_id.setdefault(extra_model, {})
                db_id2fs_id[extra_model][model_data.db_id] = model_data.fs_id

        pofile = TrytonPOFile(wrapwidth=78)
        pofile.metadata = {
            'Content-Type': 'text/plain; charset=utf-8',
            }

        with Transaction().set_context(language=Config.get_language()):
            translations = cls.search([
                ('lang', '=', lang),
                ('module', '=', module),
                ], order=[])
        for translation in translations:
            if (translation.overriding_module
                    and translation.overriding_module != module):
                raise OverriddenError(
                    gettext('ir.msg_translation_overridden',
                        name=translation.name,
                        overriding_module=translation.overriding_module))
            flags = [] if not translation.fuzzy else ['fuzzy']
            trans_ctxt = '%(type)s:%(name)s:' % {
                'type': translation.type,
                'name': translation.name,
                }
            res_id = translation.res_id
            if res_id >= 0:
                model, _ = translation.name.split(',')
                if model in db_id2fs_id:
                    res_id = db_id2fs_id[model].get(res_id)
                else:
                    continue
                trans_ctxt += '%s' % res_id
            entry = polib.POEntry(msgid=(translation.src or ''),
                msgstr=(translation.value or ''), msgctxt=trans_ctxt,
                flags=flags)
            if entry.msgid or entry.msgstr:
                pofile.append(entry)

        if pofile:
            pofile.sort()
            return str(pofile).encode('utf-8')
        else:
            return


class TranslationSetStart(ModelView):
    "Set Translation"
    __name__ = 'ir.translation.set.start'


class TranslationSetSucceed(ModelView):
    "Set Translation"
    __name__ = 'ir.translation.set.succeed'


class TranslationSet(Wizard):
    "Set Translation"
    __name__ = "ir.translation.set"

    start = StateView('ir.translation.set.start',
        'ir.translation_set_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Set', 'set_', 'tryton-ok', default=True),
            ])
    set_ = StateTransition()
    succeed = StateView('ir.translation.set.succeed',
        'ir.translation_set_succeed_view_form', [
            Button('OK', 'end', 'tryton-ok', default=True),
            ])

    def extract_report_opendocument(self, content):
        def extract(node):
            if node.nodeType in {node.CDATA_SECTION_NODE, node.TEXT_NODE}:
                if (node.parentNode
                        and node.parentNode.tagName in {
                            'text:placeholder',
                            'text:page-number',
                            'text:page-count',
                            }):
                    return
                if node.nodeValue:
                    txt = node.nodeValue.strip()
                    if txt:
                        yield txt

            for child in [x for x in node.childNodes]:
                for string in extract(child):
                    yield string

        zip_ = get_zip_file(BytesIO(content))
        for content_xml in [
                zip_.read('content.xml'),
                zip_.read('styles.xml'),
                ]:
            document = parseString(content_xml)
            for string in extract(document.documentElement):
                yield string

    extract_report_odt = extract_report_opendocument
    extract_report_odp = extract_report_opendocument
    extract_report_ods = extract_report_opendocument
    extract_report_odg = extract_report_opendocument

    def extract_report_genshi(template_class):
        def method(self, content,
                keywords=None, comment_tags=None, **options):
            options['template_class'] = template_class
            content = BytesIO(content)
            if keywords is None:
                keywords = []
            if comment_tags is None:
                comment_tags = []

            for _, _, string, _ in genshi_extract(
                    content, keywords, comment_tags, options):
                if string:
                    yield string
        if not template_class:
            raise ValueError('a template class is required')
        return method
    factories = MIMETemplateLoader().factories
    extract_report_txt = extract_report_genshi(factories['text'])
    extract_report_xml = extract_report_genshi(
        factories.get('markup', factories.get('xml')))
    extract_report_html = extract_report_genshi(
        factories.get('markup', factories.get('xml')))
    extract_report_xhtml = extract_report_genshi(
        factories.get('markup', factories.get('xml')))
    del factories

    @inactive_records
    def set_report(self):
        pool = Pool()
        Report = pool.get('ir.action.report')
        Translation = pool.get('ir.translation')

        if self.model == Report:
            reports = self.records
        elif not self.model or self.model.__name__ == 'ir.ui.menu':
            reports = Report.search([('translatable', '=', True)])
        else:
            return

        cursor = Transaction().connection.cursor()
        translation = Translation.__table__()
        report_strings = defaultdict(set)
        for report in reports:
            content = None
            if report.report:
                with file_open(report.report.replace('/', os.sep),
                        mode='rb') as fp:
                    content = fp.read()
            for content, module in [
                    (report.report_content_custom, None),
                    (content, report.module)]:
                if not content:
                    continue

                cursor.execute(*translation.select(
                        translation.id, translation.name, translation.src,
                        where=(translation.lang == INTERNAL_LANG)
                        & (translation.type == 'report')
                        & (translation.name == report.report_name)
                        & (translation.module == module)))
                trans_reports = {t['src']: t for t in cursor_dict(cursor)}

                strings = report_strings[report.report_name, report.module]
                func_name = 'extract_report_%s' % report.template_extension
                strings.update(getattr(self, func_name)(content))

                for string in strings:
                    done = False
                    if string in trans_reports:
                        del trans_reports[string]
                        continue
                    for string_trans in trans_reports:
                        if string_trans in strings:
                            continue
                        seqmatch = SequenceMatcher(lambda x: x == ' ',
                                string, string_trans)
                        if seqmatch.ratio() == 1.0:
                            del trans_reports[report.report_name][string_trans]
                            done = True
                            break
                        if seqmatch.ratio() > 0.6:
                            cursor.execute(*translation.update(
                                    [translation.src, translation.fuzzy],
                                    [string, True],
                                    where=(
                                        translation.name == report.report_name)
                                    & (translation.type == 'report')
                                    & (translation.src == string_trans)
                                    & (translation.module == module)))
                            del trans_reports[string_trans]
                            done = True
                            break
                    if not done:
                        cursor.execute(*translation.insert(
                                [translation.name, translation.lang,
                                    translation.type, translation.src,
                                    translation.value, translation.module,
                                    translation.fuzzy, translation.res_id],
                                [[
                                        report.report_name, INTERNAL_LANG,
                                        'report', string,
                                        '', module,
                                        False, -1]]))
        for (report_name, module), strings in report_strings.items():
            query = translation.delete(
                where=(translation.name == report_name)
                & (translation.type == 'report')
                & (translation.module == module))
            if strings:
                query.where &= ~translation.src.in_(list(strings))
            cursor.execute(*query)

    def _translate_view(self, element):
        strings = []
        for attr in ['string', 'confirm', 'help']:
            if element.get(attr):
                string = element.get(attr)
                if string:
                    strings.append(string)
        for child in element:
            strings.extend(self._translate_view(child))
        return strings

    @inactive_records
    def set_view(self):
        pool = Pool()
        View = pool.get('ir.ui.view')
        Translation = pool.get('ir.translation')

        if self.model == View:
            views = self.records
        elif not self.model or self.model.__name__ == 'ir.ui.menu':
            views = View.search([])
        else:
            return

        cursor = Transaction().connection.cursor()
        translation = Translation.__table__()
        for view in views:
            cursor.execute(*translation.select(
                    translation.id, translation.name, translation.src,
                    where=(translation.lang == INTERNAL_LANG)
                    & (translation.type == 'view')
                    & (translation.name == view.model)
                    & (translation.module == view.module)))
            trans_views = {t['src']: t for t in cursor_dict(cursor)}

            xml = (view.arch or '').strip()
            if not xml:
                continue
            tree = etree.fromstring(xml)
            root_element = tree.getroottree().getroot()
            strings = self._translate_view(root_element)
            views2 = View.search([
                ('model', '=', view.model),
                ('id', '!=', view.id),
                ('module', '=', view.module),
                ])
            for view2 in views2:
                xml2 = view2.arch
                if not xml2:
                    continue
                tree2 = etree.fromstring(xml2)
                root2_element = tree2.getroottree().getroot()
                strings += self._translate_view(root2_element)
            if not strings:
                continue
            for string in set(strings):
                done = False
                if string in trans_views:
                    del trans_views[string]
                    continue
                for string_trans in trans_views:
                    if string_trans in strings:
                        continue
                    seqmatch = SequenceMatcher(lambda x: x == ' ',
                            string, string_trans)
                    if seqmatch.ratio() == 1.0:
                        del trans_views[string_trans]
                        done = True
                        break
                    if seqmatch.ratio() > 0.6:
                        cursor.execute(*translation.update(
                                [translation.src,
                                    translation.fuzzy],
                                [string, True],
                                where=(translation.id
                                    == trans_views[string_trans]['id'])))
                        del trans_views[string_trans]
                        done = True
                        break
                if not done:
                    cursor.execute(*translation.insert(
                            [translation.name, translation.lang,
                                translation.type, translation.src,
                                translation.value, translation.module,
                                translation.fuzzy, translation.res_id],
                            [[
                                    view.model, INTERNAL_LANG,
                                    'view', string,
                                    '', view.module,
                                    False, -1]]))
            if strings:
                cursor.execute(*translation.delete(
                        where=(translation.name == view.model)
                        & (translation.type == 'view')
                        & (translation.module == view.module)
                        & ~translation.src.in_(strings)))

    def transition_set_(self):
        self.set_report()
        self.set_view()
        return 'succeed'


class TranslationCleanStart(ModelView):
    'Clean translation'
    __name__ = 'ir.translation.clean.start'


class TranslationCleanSucceed(ModelView):
    'Clean translation'
    __name__ = 'ir.translation.clean.succeed'


class TranslationClean(Wizard):
    "Clean translation"
    __name__ = 'ir.translation.clean'

    start = StateView('ir.translation.clean.start',
        'ir.translation_clean_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Clean', 'clean', 'tryton-ok', default=True),
            ])
    clean = StateTransition()
    succeed = StateView('ir.translation.clean.succeed',
        'ir.translation_clean_succeed_view_form', [
            Button('OK', 'end', 'tryton-ok', default=True),
            ])

    @staticmethod
    def _clean_field(translation):
        pool = Pool()
        try:
            model_name, field_name = translation.name.split(',', 1)
        except ValueError:
            return True
        try:
            Model = pool.get(model_name)
        except KeyError:
            return True
        field = Model._fields.get(field_name)
        if not field:
            return True
        if translation.src not in list(field.string):
            return True

    @staticmethod
    def _clean_model(translation):
        pool = Pool()
        try:
            model_name, field_name = translation.name.split(',', 1)
        except ValueError:
            return True
        try:
            Model = pool.get(model_name)
        except KeyError:
            return True
        if translation.res_id >= 0:
            if field_name not in Model._fields:
                return True
            field = Model._fields[field_name]
            if (not hasattr(field, 'translate')
                    or not field.translate):
                return True
        elif field_name not in ('name'):
            return True

    @staticmethod
    @inactive_records
    def _clean_report(translation):
        pool = Pool()
        Report = pool.get('ir.action.report')
        if not Report.search([
                    ('report_name', '=', translation.name),
                    ('translatable', '=', True),
                    ]):
            return True

    @staticmethod
    def _clean_selection(translation):
        pool = Pool()
        try:
            model_name, field_name = translation.name.split(',', 1)
        except ValueError:
            return True
        try:
            Model = pool.get(model_name)
        except KeyError:
            return True
        if field_name not in Model._fields:
            return True
        field = Model._fields[field_name]
        if (not hasattr(field, 'selection')
                or not field.selection
                or not getattr(field, 'translate_selection', True)):
            return True
        if (isinstance(field.selection, (tuple, list))
                and translation.src not in dict(field.selection).values()):
            return True

    @staticmethod
    def _clean_view(translation):
        pool = Pool()
        model_name = translation.name
        try:
            pool.get(model_name)
        except KeyError:
            return True

    @staticmethod
    def _clean_wizard_button(translation):
        pool = Pool()
        try:
            wizard_name, state_name, button_name = \
                translation.name.split(',', 2)
        except ValueError:
            return True
        try:
            Wizard = pool.get(wizard_name, type='wizard')
        except KeyError:
            return True
        if not Wizard:
            return True
        state = Wizard.states.get(state_name)
        if not state or not hasattr(state, 'buttons'):
            return True
        if button_name in [b.state for b in state.buttons]:
            return False
        return True

    @staticmethod
    def _clean_help(translation):
        pool = Pool()
        try:
            model_name, field_name = translation.name.split(',', 1)
        except ValueError:
            return True
        try:
            Model = pool.get(model_name)
        except KeyError:
            return True
        field = Model._fields.get(field_name)
        if not field:
            return True
        if not field.help:
            return True
        if translation.src not in list(field.help):
            return True

    def transition_clean(self):
        pool = Pool()
        Translation = pool.get('ir.translation')
        ModelData = pool.get('ir.model.data')

        to_delete = []
        records = defaultdict(lambda: defaultdict(set))
        keys = set()
        translations = Translation.search([])
        for translation in translations:
            if getattr(self, '_clean_%s' % translation.type)(translation):
                to_delete.append(translation.id)
            elif translation.type in ('field', 'model', 'wizard_button',
                    'help'):
                key = (translation.module, translation.lang, translation.type,
                    translation.name, translation.res_id)
                if key in keys:
                    to_delete.append(translation.id)
                else:
                    keys.add(key)
                if translation.type == 'model' and translation.res_id >= 0:
                    model_name, _ = translation.name.split(',', 1)
                    records[model_name][translation.res_id].add(translation.id)

        with inactive_records():
            for model_name, translations in records.items():
                Model = pool.get(model_name)
                for sub_ids in grouped_slice(translations.keys()):
                    sub_ids = list(sub_ids)
                    records = Model.search([('id', 'in', sub_ids)])
                    for record_id in set(sub_ids) - set(map(int, records)):
                        to_delete.extend(translations[record_id])

        # skip translation handled in ir.model.data
        models_data = ModelData.search([
            ('db_id', 'in', to_delete),
            ('model', '=', 'ir.translation'),
            ])
        for mdata in models_data:
            if mdata.db_id in to_delete:
                to_delete.remove(mdata.db_id)

        Translation.delete(Translation.browse(to_delete))
        return 'succeed'


class TranslationUpdateStart(ModelView):
    "Update translation"
    __name__ = 'ir.translation.update.start'

    language = fields.Many2One('ir.lang', 'Language', required=True,
        domain=[('translatable', '=', True)])

    @staticmethod
    def default_language():
        Lang = Pool().get('ir.lang')
        code = Transaction().context.get('language')
        try:
            lang, = Lang.search([
                    ('code', '=', code),
                    ('translatable', '=', True),
                    ], limit=1)
            return lang.id
        except ValueError:
            return None


class TranslationUpdate(Wizard):
    "Update translation"
    __name__ = "ir.translation.update"

    _source_types = ['report', 'view', 'wizard_button', 'selection']
    _ressource_types = ['field', 'model', 'help']
    _updatable_types = ['field', 'model', 'selection', 'help']

    start = StateView('ir.translation.update.start',
        'ir.translation_update_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Update', 'update', 'tryton-ok', default=True),
            ])
    update = StateAction('ir.act_translation_form')

    @staticmethod
    def transition_update():
        return 'end'

    def do_update(self, action):
        pool = Pool()
        Config = pool.get('ir.configuration')
        Translation = pool.get('ir.translation')
        Report = pool.get('ir.action.report')
        View = pool.get('ir.ui.view')
        cursor = Transaction().connection.cursor()
        cursor_update = Transaction().connection.cursor()
        translation = Translation.__table__()
        lang = self.start.language.code
        parent_lang = get_parent(lang)

        if self.model == Report:
            reports = self.records
            source_clause = ((translation.type == 'report')
                & translation.name.in_([r.report_name for r in reports]))
        elif self.model == View:
            views = self.records
            source_clause = ((translation.type == 'view')
                & translation.name.in_([v.model for v in views]))
        else:
            source_clause = Literal(True)
        default_lang = Config.get_language()

        columns = [translation.name.as_('name'),
            translation.res_id.as_('res_id'), translation.type.as_('type'),
            translation.src.as_('src'), translation.module.as_('module')]
        cursor.execute(*(translation.select(*columns,
                    where=(translation.lang == default_lang)
                    & source_clause
                    & translation.type.in_(self._source_types))
                - translation.select(*columns,
                    where=(translation.lang == lang)
                    & source_clause
                    & translation.type.in_(self._source_types))))
        to_create = []
        for row in cursor_dict(cursor):
            to_create.append({
                'name': row['name'],
                'res_id': row['res_id'],
                'lang': lang,
                'type': row['type'],
                'src': row['src'],
                'module': row['module'],
                })
        if to_create:
            Translation.create(to_create)

        if parent_lang:
            columns.append(translation.value)
            cursor.execute(*(translation.select(*columns,
                        where=(translation.lang == parent_lang)
                        & source_clause
                        & translation.type.in_(self._source_types))
                    & translation.select(*columns,
                        where=(translation.lang == lang)
                        & source_clause
                        & translation.type.in_(self._source_types))))
            for row in cursor_dict(cursor):
                cursor_update.execute(*translation.update(
                        [translation.value],
                        [''],
                        where=(translation.name == row['name'])
                        & (translation.res_id == row['res_id'])
                        & (translation.type == row['type'])
                        & (translation.src == row['src'])
                        & (translation.module == row['module'])
                        & (translation.lang == lang)))

        if self.model in {Report, View}:
            return

        columns = [translation.name.as_('name'),
            translation.res_id.as_('res_id'), translation.type.as_('type'),
            translation.module.as_('module')]
        cursor.execute(*(translation.select(*columns,
                    where=(translation.lang == default_lang)
                    & translation.type.in_(self._ressource_types))
                - translation.select(*columns,
                    where=(translation.lang == lang)
                    & translation.type.in_(self._ressource_types))))
        to_create = []
        for row in cursor_dict(cursor):
            to_create.append({
                'name': row['name'],
                'res_id': row['res_id'],
                'lang': lang,
                'type': row['type'],
                'module': row['module'],
                })
        if to_create:
            Translation.create(to_create)

        if parent_lang:
            columns.append(translation.value)
            cursor.execute(*(translation.select(*columns,
                        where=(translation.lang == parent_lang)
                        & translation.type.in_(self._ressource_types))
                    & translation.select(*columns,
                        where=(translation.lang == lang)
                        & translation.type.in_(self._ressource_types))))
            for row in cursor_dict(cursor):
                cursor_update.execute(*translation.update(
                        [translation.value],
                        [''],
                        where=(translation.name == row['name'])
                        & (translation.res_id == row['res_id'])
                        & (translation.type == row['type'])
                        & (translation.module == row['module'])
                        & (translation.lang == lang)))

        columns = [translation.name.as_('name'),
            translation.res_id.as_('res_id'), translation.type.as_('type'),
            translation.src.as_('src'), translation.module.as_('module')]
        cursor.execute(*(translation.select(*columns,
                    where=(translation.lang == default_lang)
                    & translation.type.in_(self._updatable_types))
                - translation.select(*columns,
                    where=(translation.lang == lang)
                    & translation.type.in_(self._updatable_types))))
        for row in cursor_dict(cursor):
            cursor_update.execute(*translation.update(
                    [translation.fuzzy, translation.src],
                    [True, row['src']],
                    where=(translation.name == row['name'])
                    & (translation.type == row['type'])
                    & (translation.lang == lang)
                    & (translation.res_id == (row['res_id'] or -1))
                    & (translation.module == row['module'])))

        cursor.execute(*translation.select(
                translation.src.as_('src'),
                Max(translation.value).as_('value'),
                where=(translation.lang == lang)
                & translation.src.in_(
                    translation.select(translation.src,
                        where=((translation.value == '')
                            | (translation.value == Null))
                        & (translation.lang == lang)
                        & (translation.src != '')
                        & (translation.src != Null)))
                & (translation.value != '')
                & (translation.value != Null),
                group_by=translation.src))

        for row in cursor_dict(cursor):
            cursor_update.execute(*translation.update(
                    [translation.fuzzy, translation.value],
                    [True, row['value']],
                    where=(translation.src == row['src'])
                    & ((translation.value == '') | (translation.value == Null))
                    & (translation.lang == lang)))

        cursor_update.execute(*translation.update(
                [translation.fuzzy],
                [False],
                where=((translation.value == '') | (translation.value == Null))
                & (translation.lang == lang)))

        action['pyson_domain'] = PYSONEncoder().encode([
            ('module', '!=', None),
            ('lang', '=', lang),
        ])
        return action, {}


class TranslationExportStart(ModelView):
    "Export translation"
    __name__ = 'ir.translation.export.start'

    language = fields.Many2One('ir.lang', 'Language', required=True,
        domain=[
            ('translatable', '=', True),
            ('code', '!=', INTERNAL_LANG),
            ])
    module = fields.Many2One('ir.module', 'Module', required=True,
        domain=[
            ('state', 'in', ['activated', 'to upgrade', 'to remove']),
            ])

    @classmethod
    def default_language(cls):
        Lang = Pool().get('ir.lang')
        code = Transaction().context.get('language')
        domain = [('code', '=', code)] + cls.language.domain
        try:
            lang, = Lang.search(domain, limit=1)
            return lang.id
        except ValueError:
            return None


class TranslationExportResult(ModelView):
    "Export translation"
    __name__ = 'ir.translation.export.result'

    language = fields.Many2One('ir.lang', 'Language', readonly=True)
    module = fields.Many2One('ir.module', 'Module', readonly=True)
    file = fields.Binary('File', readonly=True, filename='filename')
    filename = fields.Char('Filename')


class TranslationExport(Wizard):
    "Export translation"
    __name__ = "ir.translation.export"

    start = StateView('ir.translation.export.start',
        'ir.translation_export_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Export', 'export', 'tryton-ok', default=True),
            ])
    export = StateTransition()
    result = StateView('ir.translation.export.result',
        'ir.translation_export_result_view_form', [
            Button('Close', 'end', 'tryton-close'),
            ])

    def transition_export(self):
        pool = Pool()
        Translation = pool.get('ir.translation')
        self.result.file = Translation.translation_export(
            self.start.language.code, self.start.module.name)
        return 'result'

    def default_result(self, fields):
        file_ = self.result.file
        cast = self.result.__class__.file.cast
        self.result.file = False  # No need to store it in session
        return {
            'module': self.start.module.id,
            'language': self.start.language.id,
            'file': cast(file_) if file_ else None,
            'filename': '%s.po' % self.start.language.code,
            }


class TranslationReport(Wizard):
    "Open translations of report"
    __name__ = 'ir.translation.report'
    start_state = 'open_'
    open_ = StateAction('ir.act_translation_report')

    def do_open_(self, action):
        context = Transaction().context
        reports = self.records
        action['pyson_domain'] = PYSONEncoder().encode([
                ('type', '=', 'report'),
                ('name', 'in', [r.report_name for r in reports]),
                ])
        # Behaves like a relate to have name suffix
        action['keyword'] = 'form_relate'
        return action, {
            'model': context['active_model'],
            'ids': context['active_ids'],
            'id': context['active_id'],
            }
