summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/widgets/translation_dialog.js
blob: efdba7b3a381c2d2f86ae14c92311dd0483a81ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
odoo.define('web.TranslationDialog', function (require) {
    'use strict';

    var core = require('web.core');
    var Dialog = require('web.Dialog');
    var session = require('web.session');

    var _t = core._t;

    var TranslationDialog = Dialog.extend({
        xmlDependencies: (Dialog.prototype.xmlDependencies || [])
            .concat(['/web/static/src/xml/translation_dialog.xml']),
        template: 'TranslationDialog',

        /**
         * @constructor
         * @param {Widget} parent
         * @param {Object} [options]
         * @param {string} [options.domain] the domain needed to get the translation terms
         * @param {string} [options.fieldName] the name of the field currently translated (from the model of the form view)
         * @param {string} [options.searchName] the name of the actual field that is the reference for translation (in the form of model,field)
         * @param {string} [options.userLanguageValue] the value of the translation in the language of the user, as seen in the from view (might be empty)
         * @param {string} [options.dataPointID] the data point id of the record for which we do the translations
         * @param {boolean} [options.isComingFromTranslationAlert] the initiator of the dialog, might be a link on a field or the translation alert on top of the form
         * @param {boolean} [options.isText] is the field a text field (multiline) or char (single line)
         * @param {boolean} [options.showSrc] is the source of the translation should be rendered (for partial translations, i.e. XML content)
         *
         */
        init: function (parent, options) {
            options = options || {};

            this.fieldName = options.fieldName;
            this.domain = options.domain;
            this.searchName = options.searchName;
            this.userLanguageValue = options.userLanguageValue;
            this.domain.push(['name', "=", `${this.searchName}`]);
            this.dataPointID = options.dataPointID;
            this.isComingFromTranslationAlert = options.isComingFromTranslationAlert;
            this.currentInterfaceLanguage = session.user_context.lang;
            this.isText = options.isText;
            this.showSrc = options.showSrc;

            this._super(parent, _.extend({
                size: 'large',
                title: _t('Translate: ') + `${this.fieldName}`,
                buttons: [
                    { text: _t('Save'), classes: 'btn-primary', close: true, click: this._onSave.bind(this) },
                    { text: _t('Discard'), close: true },
                ],
            }, options));
        },
        /**
         * @override
         */
        willStart: function () {
            return Promise.all([
                this._super(),
                this._loadLanguages().then((l) => {
                    this.languages = l;
                    return this._loadTranslations().then((t) => {
                        this.translations = t;
                    });
                }),
            ]).then(() => {
                this.data = this.translations.map((term) => {
                    let relatedLanguage = this.languages.find((language) => language[0] === term.lang);
                    if (!term.value && !this.showSrc) {
                        term.value = term.src;
                    }
                    return {
                        id: term.id,
                        lang: term.lang,
                        langName: relatedLanguage[1],
                        source: term.src,
                        // we set the translation value coming from the database, except for the language
                        // the user is currently utilizing. Then we set the translation value coming
                        // from the value of the field in the form
                        value: (term.lang === this.currentInterfaceLanguage &&
                            !this.showSrc &&
                            !this.isComingFromTranslationAlert) ?
                            this.userLanguageValue : term.value || ''
                    };
                });
                this.data.sort((left, right) =>
                    (left.langName < right.langName || (left.langName === right.langName && left.source < right.source)) ? -1 : 1);
            });
        },

        //--------------------------------------------------------------------------
        // Private
        //--------------------------------------------------------------------------
        /**
         * Load the translation terms for the installed language, for the current model and res_id
         * @private
         */
        _loadTranslations: function () {
            const domain = [...this.domain, ['lang', 'in', this.languages.map(l => l[0])]];
            return this._rpc({
                model: 'ir.translation',
                method: 'search_read',
                fields: ['lang', 'src', 'value'],
                domain: domain,
            });
        },
        /**
         * Load the installed languages long names and code
         *
         * The result of the call is put in cache on the prototype of this dialog.
         * If any new language is installed, a full page refresh will happen,
         * so there is no need invalidate it.
         * @private
         */
        _loadLanguages: function () {
            if (TranslationDialog.prototype.installedLanguagesCache)
                return Promise.resolve(TranslationDialog.prototype.installedLanguagesCache);

            return this._rpc({
                model: 'res.lang',
                method: 'get_installed',
                fields: ['code', 'name', 'iso_code'],
            }).then((installedLanguages) => {
                TranslationDialog.prototype.installedLanguagesCache = installedLanguages;
                return TranslationDialog.prototype.installedLanguagesCache
            });
        },

        //--------------------------------------------------------------------------
        // Handlers
        //--------------------------------------------------------------------------
        /**
         * Save all the terms that have been updated
         * @private
         * @returns a promise that is resolved when all the save have occured
         */
        _onSave: function () {
            var updatedTerm = {};
            var updateFormViewField;

            this.el.querySelectorAll('input[type=text],textarea').forEach((t) => {
                var initialValue = this.data.find((d) => d.id == t.dataset.id);
                if (initialValue.value !== t.value) {
                    updatedTerm[t.dataset.id] = t.value;

                    if (initialValue.lang === this.currentInterfaceLanguage && !this.showSrc) {
                        // when the user has changed the term for the language he is
                        // using in the interface, this change should be reflected
                        // in the form view
                        // partial translations being handled server side are
                        // also ignored
                        var changes = {};
                        changes[this.fieldName] = updatedTerm[initialValue.id];
                        updateFormViewField = {
                            dataPointID: this.dataPointID,
                            changes: changes,
                            doNotSetDirty: false,
                        };
                    }
                }
            });

            // updatedTerm only contains the id and values of the terms that
            // have been updated by the user
            var saveUpdatedTermsProms = Object.keys(updatedTerm).map((id) => {
                var writeTranslation = {
                    model: 'ir.translation',
                    method: 'write',
                    context: this.context,
                    args: [[parseInt(id, 10)], { value: updatedTerm[id] }]
                };
                return this._rpc(writeTranslation);
            });
            return Promise.all(saveUpdatedTermsProms).then(() => {
                // we might have to update the value of the field on the form
                // view that opened the translation dialog
                if (updateFormViewField) {
                    this.trigger_up('field_changed', updateFormViewField);
                }
            });
        }
    });

    return TranslationDialog;
});