static/js/zamboni/l10n.js (312 lines of code) (raw):

import $ from 'jquery'; import _ from 'underscore'; import 'jquery.cookie'; import { format } from '../lib/format'; import { initCharCount } from './global'; // Yes, this is out here for a reason. // We want to hide the non-default locales as fast as possible. let dl = $('body').attr('data-default-locale'); if (dl) { $(format(".trans>:not([lang='{0}'])", dl)).hide(); $(format(".trans [lang='{0}']", dl)).show(); } let currentLocale; let locales = []; let translations = {}; let unsavedModalMsg; let unsavedModal; let rmLocaleModalMsg; let rmLocaleModal; let modalActions; function checkTranslation(e, t) { let $input = e.originalEvent ? $(this) : $(format("[lang='{0}']", [e]), t), $trans = $input.closest('.trans'), lang = e.originalEvent ? $input.attr('lang') : e, $dl = $(format("[lang='{0}']", [dl]), $trans), transKey = $trans.attr('data-name') + '_' + lang; if ($input.length == 0 || $input.is('span')) { // No translation of this element exists for the // requested language. return; } if (!(transKey in translations)) { translations[transKey] = $input.val(); } if (lang != dl) { if ($input.val() == $dl.val() && $input.val().trim().length) { $input.addClass('cloned'); } else if (!$input.val().trim().length) { if (e.originalEvent && e.type == 'focusout') { $input.val($dl.val()).addClass('cloned'); } else { $input.removeClass('cloned'); } } else { $input.removeClass('cloned'); } } if (translations[transKey] != $input.val()) { $input.addClass('unsaved'); } else { $input.removeClass('unsaved'); } } export function refreshL10n(lang) { lang = lang || currentLocale; if (currentLocale != lang) { currentLocale = lang; } if (!_.include(locales, lang)) { locales.push(lang); } let current = $(format("#locale-popup [href='#{0}']", [lang])) .first() .clone(); current.find('em').remove(); $('#change-locale').text(current.text()); $('.trans').each(function () { let $el = $(this), field = $el.attr('data-name'), label = $(format("label[data-for='{0}']", [field])), $ni; if (!$el.find(format("[lang='{0}']", [lang])).length) { if ($el.children('.trans-init').length) { $ni = $el.children('.trans-init').clone(); $ni.attr({ class: '', lang: lang, id: format('id_{0}_{1}', field, lang), name: [field, lang].join('_'), value: $el.find(format("[lang='{0}']", [dl])).val(), }); if (lang != dl) $ni.addClass('cloned'); } else { $ni = $el.find(format("[lang='{0}']", dl)).clone(); $ni.attr({ class: 'cloned', lang: lang, }); } $el.append($ni); } checkTranslation(lang, $el); if (label.length) { label.children('.locale').remove(); label.append( format("<span class='locale'>{0}</span>", [$('#change-locale').text()]), ); const label_for = $el.children(format("[lang='{0}']", [lang])).attr('id'); label.attr('for', label_for); } }); $(format(".trans>:not([lang='{0}'])", currentLocale)).hide(); $(format(".trans [lang='{0}']", currentLocale)).show(); initCharCount(); if ($.cookie('current_locale') != currentLocale && currentLocale != dl) { $.cookie('current_locale', null); $.cookie('current_locale', currentLocale, { expires: 0 }); } } function discoverLocales() { let seen_locales = {}; $('.trans [lang]').each(function () { seen_locales[$(this).attr('lang')] = true; }); locales = _.keys(seen_locales); } $(document).ready(function () { if (!$('#l10n-menu').length) return; currentLocale = dl; (dl = $('body').attr('data-default-locale')), (unsavedModalMsg = $('#modal-l10n-unsaved .msg').html()), (unsavedModal = $('#modal-l10n-unsaved').modal()), (rmLocaleModalMsg = $('#modal-l10n-rm .msg').html()), (rmLocaleModal = $('#modal-l10n-rm').modal()), (modalActions = $('.modal-actions', unsavedModal)); //hold the initial values of the fields to check for changes $('.primary').on( 'change keyup paste blur', '.trans input, .trans textarea', checkTranslation, ); $('form').submit(function () { $(this).find('.trans .cloned').remove(); }); function popuplateTranslations(el) { //load in the initial values of the translations el.find('.trans input[lang], .trans textarea[lang]').each(function () { let $input = $(this), $trans = $input.closest('.trans'), transKey = $trans.attr('data-name') + '_' + $input.attr('lang'); translations[transKey] = $input.val(); }); } function showExistingLocales() { discoverLocales(); const $el = $('#existing_locales').empty(); $('#all_locales li').show(); $.each(_.without(locales, dl), function () { let locale_row = $( format("#all_locales a[href='#{0}']", [this]), ).parent(); if (locale_row.length) { $el.append( format( "<li><a title='{msg}'class='remove' href='#'>x</a>{row}</li>", { msg: gettext('Remove this localization'), row: locale_row.html(), }, ), ); locale_row.hide(); } }); } $('.primary').on('click', '.errorlist .l10n', switchLocale); $('#all_locales').on('switch', 'a', switchLocale); // If the locale switcher is visible, use the cookie. let initLocale = dl; if ($('#l10n-menu:visible').length) { initLocale = $.cookie('current_locale'); } $(format("#all_locales a[href='#{0}']", [initLocale])).trigger('switch'); function switchLocale(e) { e.preventDefault(); const $tgt = $(this); const new_locale = $tgt.attr('data-lang') || $tgt.attr('href').substring(1); const unsaved = $('form .trans .unsaved'); if (unsaved.length && new_locale != currentLocale) { unsavedModal .children('.msg') .html(format(unsavedModalMsg, [$('#change-locale').text()])); unsavedModal.render(); $('#l10n-save-changes') .off() .click(function () { let unsavedForms = $('form:has(.trans .unsaved)'); let numFormsLeft = unsavedForms.length; let erroredForms = 0; modalActions.addClass('ajax-loading'); modalActions.find('button').addClass('disabled'); unsavedForms.each(function () { let $form = $(this); $.ajax({ url: $form.attr('action'), type: 'post', data: $form.serialize(), error: function () { modalActions.removeClass('ajax-loading'); }, success: function (d) { let $resp = $(d); if ( $form.attr('id') && $resp.find('#' + $form.attr('id')).length ) { $resp = $resp.find('#' + $form.attr('id')); } // Add locale names to error messages annotateLocalizedErrors($resp); numFormsLeft--; if ($resp.find('.errorlist').length) { //display errors if they occur $form.html($resp.html()); refreshL10n(); if ( $resp.find( format(".errorlist li[data-lang='{0}']", currentLocale), ).length ) { erroredForms++; } } else { //clean up the errors we inserted popuplateTranslations($form); $form.find('.unsaved').removeClass('unsaved'); $form.find('.errorlist').remove(); } if (numFormsLeft < 1) { if (erroredForms) { window.scrollTo( 0, $('.errorlist .l10n').closest('form').offset().top, ); $('.errorlist') .first() .siblings('.trans') .find('input:visible, textarea:visible') .focus(); } else { refreshL10n(new_locale); } } modalActions.removeClass('ajax-loading'); modalActions.find('button').removeClass('disabled'); unsavedModal.hideMe(); }, }); }); }); $('#l10n-discard-changes').click(function () { $('.trans .unsaved').remove(); refreshL10n(new_locale); unsavedModal.hideMe(); }); $('#l10n-cancel-changes').click(function () { unsavedModal.hideMe(); }); } else { refreshL10n(new_locale); } if (localePopup) { localePopup.hideMe(); } } let localePopup = $('#locale-popup').popup('#change-locale', { pointTo: '#change-locale', width: 200, callback: function () { showExistingLocales(); $('#locale-popup').on('click', 'a:not(.remove)', switchLocale); $('#locale-popup').on('click', 'a.remove', function (e) { e.preventDefault(); e.stopPropagation(); let toRemove = $(this) .closest('li') .find('a:not(.remove)') .attr('href') .substring(1); rmLocaleModal.children('.msg').html(format(rmLocaleModalMsg, toRemove)); rmLocaleModal.render(); $('#l10n-cancel-rm').off().click(rmLocaleModal.hideMe); function cleanUp() { $('.modal-actions', rmLocaleModal).removeClass('ajax-loading'); rmLocaleModal.hideMe(); } $('#l10n-confirm-rm') .off() .click(function (e) { $('.modal-actions', rmLocaleModal).addClass('ajax-loading'); $.ajax({ url: $('#l10n-menu').attr('data-rm-locale'), type: 'post', data: { locale: toRemove }, error: function () { cleanUp(); }, success: function () { if (currentLocale == toRemove) { refreshL10n(dl); } $('.trans [lang=' + toRemove + ']').remove(); cleanUp(); }, }); }); }); return true; }, }); refreshL10n(); }); export function annotateLocalizedErrors($el) { $el.find('.errorlist li[data-lang]:not(.l10n)').each(function () { let err = $(this), t = err.text(), l = $(format("#locale-popup [href='#{0}']", [err.attr('data-lang')])) .first() .text(); err.text(format('{0}: ', [l]) + t).addClass('l10n'); }); }