Jump to content

User:V111P/js/wikiTranslTools.js

From Wikipedia, the free encyclopedia
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//<nowiki>
/* Wiki Translation Tools v. September 02, 2015
 * home page: https://en.wikipedia.org/wiki/User:V111P/js/Wiki_Translation_Tools
 * CC0 Public Domain Dedication:
 * http://creativecommons.org/publicdomain/zero/1.0/
 * If you use large parts of this code, please let me know.
 * You should also let others know where the code originates:
 * //en.wikipedia.org/wiki/User:V111P/js/wikiTranslTools.js
 * Thanks!
 */
window.wikiTranslTools = (function ()
{
    "use strict";
    var THIS_ARTICLE = mw.config.get('wgPageName').replace(/_/g, ' ');
    var nl = '\n';
    var localStorageItemName = 'WikiTranslationTools';

    // Target-wiki links will be added only to these namespaces:
    // Namespases: main=0, user=2, wikipedia/wp=4, template=10, help=12, category=14, portal=100
    var NamespacesWithInterwikis = [0, 2, 4, 10, 12, 14, 100];


    /*******
     * Target-Wiki Config
     ******/
    var targetWiki = {
        lang: '',
        server: '',
        translatedPageTemplate: '{{translated page|%sourceWiki%|%pageTitle%|version=%revisionId%}}'
    };

    var skin = { // values must be CSS/jQuery selectors
        interwikiDiv: '#p-lang',
        catLinks: '#mw-normal-catlinks' // the <div> with the non-hidden categories
        //  Leave empty to use the built-in wgCategories array (which includes hidden cats)
    };

    /*******
     * Local-Wiki Config
     ******/
    var thisWiki = {
        lang: mw.config.get('wgContentLanguage'),

        catNS: mw.config.get('wgFormattedNamespaces')[14] // The local name of the Category namespace
    };

    /******
     * Other customizable options
     ******/
    var options = {
        helpUrl: '//en.wikipedia.org/wiki/User:V111P/js/Wiki_Translation_Tools',

        defaultWiki: 'en', // used if targetWiki.lang is not set

        // number of rows for the output textarea
        taRows: 10,

        // this has no effect in Monobook and other skins
        taCols: 80,

        // if the article has that many categories or less, they will be auto-expanded
        //   when the Category Browser is shown for the first time on the page.
        maxCatsToAutoExpand: 5,

        // If more than that number of cats are in the target-wiki article, hide them (user needs to click a link)
        nOfCurrTargetCatsToShow: 5,

        // A function called with the wiki code just before displaying it in the text area
        // Must return the wiki code to be displayed
        hook: void(0)
    };


    /*******
     * The Messages
     ******/
    var msgs = {
        error: 'error',
        okButtonLabel: 'OK',
        translatedPageOption: 'Translated page',
        commonsOption: 'Commons',
        interwikis: 'Interwikis',
        categories: 'Categories',
        showPanelLink: 'Cat/Tr/Comm',
        showPanelTitle: 'Categories, {{Translated page}}, {{Commons}} ',
        show: 'show',
        hide: 'hide',
        alreadyListed: 'already listed',
        alreadyInLinkedArticle: 'already in linked article',
        moreThanOneInterwiki: 'More than one article in the target wiki links to this one.',
        catsAlreadyInTarget: 'Categories already in %targetArticle%',
        linksToLang: 'Links to %targetWiki%',
        enterTargetWiki: 'Please, enter the language code of the other wiki:',
        targetWiki: 'target wiki: %targetWiki%'
    };


    /*******
     * Private Variables
     ******/
    var siteDomain = mw.config.get('wgServer').replace(/^[^.]+./, ''); // e.g. wikipedia.org
    var allCats; // used for the Category Browser
    var currTargetWikiArticleCats; // used for the Category Browser


    // a shortcut for document.createTextNode()
    function textNode(str)
    {
        return document.createTextNode(str);
    }


    /*************************
     ***  INTERWIKIS
     ***
     ***/


    // reads the interwikis from the element with the specified in SKIN.INTERWIKI_DIV id
    function getInterwikiCode()
    {
        var interwikis = getInterwikis();
        var links = interwikis.links;
        var langs = interwikis.langs;

        /* adding this page's wikilink to the array */
        links[thisWiki.lang] = THIS_ARTICLE;
        langs.push(thisWiki.lang);
        langs.sort();

        var interwikisStr = '';
        for (var i = 0; i < langs.length; i++)
            interwikisStr += '[[' + langs[i] + ':' + links[langs[i]] + ']]' + nl;

        return interwikisStr;
    }


    function getInterwikis()
    {
        var links = [], langs = [];
        $(skin.interwikiDiv + ' a').each(function (){
            var result = $(this).attr('href').match(/\/\/([^\.]+)\.[^\/]+\/wiki\/(.+)/);
            if (!result || result[1] == 'www')
                return;
            var lang = result[1];
            var title = decodeURI(result[2]).replace(/_/g, ' ');
            if (lang != targetWiki.lang) {
                links[lang] = title;
                langs.push(lang);
            }
        });
        return {langs: langs, links: links};
    }


    /*************************
     ***  COMMONS templates
     ***
     ***/


    function getCommonsTemplates() {
        var commonsStr = '', template;
        var $commonsLinks = $('a[href*="//commons.wikimedia.org/wiki/"]');
        var commonsLinksArr = [];
        $commonsLinks.each(function (i, el) {
            var href = $(this).attr('href');
            if (href.match(/wiki\/(file|image|media):/i) || href.match(/Special:UploadWizard/))
                return;
            href = href.replace(/_/g, ' ').replace(/\?.*$/, '');
            var title = decodeURIComponent(href.replace(/.+?\/wiki\//, ''));
            var page = title.replace(/^Category:/, '');
            if (title != href) {
                if (title.match(/^Atlas of/))
                    template = '* {{wikiatlas|' + title.replace(/^Atlas of./, '') + '}}';
                else
                    template = '{{commons' + (page != title ? 'cat' : '')
                        + '|' + page + '}}';
                if ($.inArray(template, commonsLinksArr) == -1)
                    commonsLinksArr.push(template);
            }
        });
        commonsStr = commonsLinksArr.join('\n');
        if (commonsStr) commonsStr += '\n\n';

        return commonsStr;
    }


    /*************************
     ***  OPTIONS PANEL
     ***
     ***/


    /* PUBLIC */
    // called when the user clicks on the "Cat/Tr/FA/Comm" portlet link
    function showPanel()
    {
        $('#wikiTranslToolsCodeBox').remove();

        if (!$('#wikiTranslToolsPanel').show()[0])
            createThePanel();
    }


    function createThePanel()
    {
        if (!targetWiki.lang)
            setTargetWiki(prompt(msgs.enterTargetWiki, options.defaultWiki));

        var $wikiTranslToolsPanel = $('<div/>', {
            css: {
                color: '#000',
                backgroundColor: '#f4f4f4',
                fontSize: 'small',
                position: 'relative',
                margin: '1em auto'
            },
            id: 'wikiTranslToolsPanel'
        }).append(
            $('<a/>', {
                text: msgs.targetWiki.replace(/%targetWiki%/, (targetWiki.lang || options.defaultWiki)),
                href: '#',
                'class': 'wtt_targetWikiLangLink',
                css: {
                    marginRight: '1em',
                    position: 'absolute',
                    bottom: '2px',
                    right: '2px'
                },
                click: function (e) {
                    e.preventDefault();
                    setTargetWiki(prompt(msgs.enterTargetWiki, (targetWiki.lang || options.defaultWiki)));
                }
            }),
            $('<div/>', {
                css: {position: 'absolute', right: '2px', top: '2px'}
            }).append(
                $('<a/>', {
                    text: '[?]',
                    target: '_blank',
                    href: options.helpUrl
                }),
                $('<a/>', {
                    text: '[X]',
                    href: '#',
                    click: function (e) {
                        e.preventDefault();
                        $wikiTranslToolsPanel.hide();
                    }
                })
            )
        );

        var optionCheckboxes = [
            {val: 'translatedPage', checked: true, label: msgs.translatedPageOption},
            {val: 'commons', checked: true, label: msgs.commonsOption},
            {val: 'interwikis', checked: false, label: msgs.interwikis}
        ];

        if (targetWiki.translatedPageTemplate == 'none') {
            optionCheckboxes[0].checked = null;
            optionCheckboxes[0].disabled = 'disabled';
        }

        var $form = $('<form/>', {
            id: 'wikiTranslToolsForm',
            submit: function (e) {
                e.preventDefault();
                formOk(this);
            }
        })
        .append('<input type="submit" value="' + msgs.okButtonLabel + '"/>');

        $.each(optionCheckboxes, function (i, val) {
            $form.append($('<label style="margin-left:0.5em;"/>').append($('<input type="checkbox"/>').attr({
                name: 'options',
                value: val.val,
                checked: (val.checked ? 'checked' : null),
                disabled: (val.disabled ? 'disabled' : null)
            }), textNode(val.label)));
        });

        $form.append('<div style="margin-top:1em;"><b>'
                       + msgs.categories + '</b></div>',
            createTheCategoryBrowser(),
            '<input type="submit" value="' + msgs.okButtonLabel + '" style="margin-top:1em;"/>'
        );
        display($wikiTranslToolsPanel.append($form));
    } // createThePanel()


    function formOk(frm)
    {
        var checkedOptions = {};
        var optionCheckboxes = $('input:checkbox[name=options]:checked', frm);

        $.each(optionCheckboxes, function (i, val) {
            checkedOptions[val.value] = true;
        });

        var catsStr = getCheckedCats(frm);
        $('#wikiTranslToolsPanel').hide();

        displayCode(
            (checkedOptions.commons ? getCommonsTemplates() : '')
            + (checkedOptions.translatedPage ? targetWiki.translatedPageTemplate + nl + nl : '')
            + (catsStr ? catsStr + nl + nl : '')
            + (checkedOptions.interwikis ? getInterwikiCode() : '')
        );
    }


    /*************************
     ***  CATEGORIES
     ***
     ***/


    function createTheCategoryBrowser() {
        var catArr = [];

        // if possible, fill catArr with the non-hidden cats only,
        //  otherwise use wgCategories
        if (skin.catLinks)
            catArr = $(skin.catLinks + ' a').map(function(i) {
                if (i == 0) return null; // skip the 'Categories:' link
                return $(this).text();
            }).toArray();
        else
            catArr = mw.config.get('wgCategories');

        if (catArr.length == 0)
            return $([]);

        allCats = {};

        getTargetInterwiki();

        // create the main UL and sub li's with the category names
        var $top_ul = $('<ul/>', {'class': 'wtt_catBrowser'});

        var $top_li;
        for (var i = 0; i < catArr.length; i++)
        {
            var expandLink = createExpandCatLink();

            $top_li = $('<li/>').append(expandLink, textNode(' '), '<span>' + catArr[i] + '</span>');
            allCats[catArr[i]] = {
                name: catArr[i],
                liElement: $top_li // the innermost <li> within which the category's name is shown
            };

            $top_ul.append($top_li);

            if (catArr.length <= options.maxCatsToAutoExpand)
                expandLink.click();
        }

        return $top_ul[0];
    } // createTheCategoryBrowser


    function getTargetInterwiki() {
        var tLang = targetWiki.lang;
        var targetPgA = $(skin.interwikiDiv + " a[href^='//" + tLang + ".']");

        var siz = targetPgA.length;
        if (siz === 0)
            return;

        if (siz > 1) {
            // more than one articles in the target wiki is linked to this article
            // this is rare, so it's not supported here
            $('.wtt_catBrowser')
            .after($('<div>' + msgs.moreThanOneInterwiki + '</div>'));
            return;
        }

        var targetPg = (targetPgA.attr('href').match('.org/wiki/(.+)') || ['',''])[1];
        targetWiki.interwikiLinkedArticle = targetPg;

        var apiRequestUrl = '//' + tLang + '.' + siteDomain + '/w/api.php?action=query&prop=categories&titles='
               + targetPg.replace(/_/g, '%20')
               + '&cllimit=200&redirects&format=json'
               + '&callback=window.wikiTranslTools.wikiTranslToolsTargetArticleCatsReceived';
        $('body').append('<script src="' + apiRequestUrl + '"></script>');
    }


    function wikiTranslToolsTargetArticleCatsReceived(catsReply) {
        var pages = (catsReply && catsReply.query && catsReply.query.pages);
        var catObjs = [];

        if (!pages) {
            console.warn("wikiTranslTools: debug message: targetArticleCatsReceived(): !pages");
        }

        for (var key in pages) {
            if (pages.hasOwnProperty(key) && pages[key].categories) {
                catObjs = pages[key].categories;
                break;
            }
        }

        var cats = [];
        for (var i = 0; i < catObjs.length; i++) {
            cats.push(catObjs[i].title);
        }

        currTargetWikiArticleCats = cats; // copy to global var

        // In case there are already some target-wiki categories and checkboxes shown,
        //   check if any of these cats are already in the interwiki-linked target-wiki article
        //   and mark them
        $('.wtt_catBrowser input').filter(function (i, el) {
            return ( ($.inArray(el.value, cats) > -1) ? true : false )
        }).after($('<b title="' + msgs.alreadyInLinkedArticle + '"> @ </b>'));

        var targetSiteArticlePath = '//' + targetWiki.lang + '.' + siteDomain + '/wiki/';

        var $span = $('<span class="wtt_currCats"/>');
        for (var i = 0; i < cats.length; i++) {
            if (i > 0) $span.append(textNode(', '));
            $span.append($('<a target="_blank" href="'
                          + targetSiteArticlePath + cats[i].replace(/ /g, '_') + '">'
                          + cats[i].replace(/^[^:]+:/, '') + '</a>') );
        }
        $span.append(textNode(' '), $('<a/>', {
            href: '#',
            click: function (e) {
                e.preventDefault();
                $('.wtt_currCats').hide();
                $('.wtt_currCatsLnk').show();
            },
            title: msgs.hide,
            text: '<<'
        }));

        var iwlaName = targetWiki.interwikiLinkedArticle;
        var iwlaDisplayName = decodeURI(iwlaName).replace(/_/g, ' ');
        var iwlaUrl = targetSiteArticlePath + iwlaName;

        var catsAlreadyHtml = msgs.catsAlreadyInTarget
                     .replace(/%targetArticle%/, targetWiki.lang
                         + ':<a target="_blank" href="' + iwlaUrl + '">' + iwlaDisplayName
                         + '</a> (' + cats.length + '): ');

        $('.wtt_catBrowser')
        .after($('<div>' + catsAlreadyHtml + '</div>')
            .append($span, $('<a/>', {
                href: '#',
                click: function (e) {
                    e.preventDefault();
                    $('.wtt_currCats').show();
                    $('.wtt_currCatsLnk').hide();
                },
                'class': 'wtt_currCatsLnk',
                title: msgs.show,
                text: '...'
        })))
        .after('<br/>');

        if (cats.length < options.nOfCurrTargetCatsToShow)
            $('.wtt_currCatsLnk').hide();
        else
            $('.wtt_currCats').hide();
    }


    function createExpandCatLink()
    {
        return $('<a/>', {
            text: '[+]',
            href: '#',
            click: function() {
                expandCat(this);
                return false;
            },
            css: {textDecoration: 'none'}
        });
    }


    // Called when the user clicks on a [+] expand link in the Category Browser
    function expandCat(startCatExpandLinkEl)
    {
        var startCatLink = $(startCatExpandLinkEl);
        var catSpan = $('span', startCatLink.parent()); // it contains the name of the cat to be expanded

        startCatLink.off('click').text('...'); // replace "[+]" with "..."
        startCatLink.attr('class', 'waitingExpandLink');
        var cat = allCats[$(catSpan[0]).text()];
        var catNsAndNameEscaped = encodeURIComponent(thisWiki.catNS + ':' + cat.name);
        $.ajax({
            url: '/w/api.php?action=query&prop=langlinks|categories&titles='
                + catNsAndNameEscaped
                + '&lllang=' + (targetWiki.lang || options.defaultWiki)
                + '&cllimit=200&redirects&format=json',
            dataType: 'json',
            success: catInfoReceived(cat),
            error: function (XMLHttpRequest) {
                showCatParents(cat);
            }
        });
    }


    // creates and returns a function to be called when the requested Category page information is received
    function catInfoReceived(cat)
    {
        return function(data) {
            function parentCats(data) {
                var pages = data && data.query && data.query.pages;
                if (!pages)
                    return;

                var pg;
                for (var p in pages) {
                    if (pages.hasOwnProperty(p))
                        pg = pages[p];
                }

                var catObjs = pg && pg.categories;
                if (!catObjs)
                    return;

                var cats = [];
                for (var i = 0; i < catObjs.length; i++) {
                    cats.push(catObjs[i].title.replace(/^[^:]+:/, ''));
                }
                return cats;
            }

            showCatParents(cat, parentCats(data), getTargetwikiFromJson(data));
        };
    } // catInfoReceived


    // called by expandCat() to add the parent categories
    //  of the specified category to the displayed list.
    function showCatParents(cat, parents, targetWikiCat)
    {
        var top_li = cat.liElement;
        var server = mw.config.get('wgServer');

        $('.waitingExpandLink', top_li).remove();

        if (targetWikiCat) {
            var targetWikiLink = $('<a/>', {
                text: targetWikiCat.replace(/^[^:]+:/, ''),
                href: targetWiki.server + '/wiki/' + targetWikiCat,
                target: '_blank'
            });

            var alreadyInArticle = $.inArray(targetWikiCat, currTargetWikiArticleCats) > -1;
            top_li.append(textNode(' : '),
                '<input type="checkbox" name="targetWikiCats" value="' + targetWikiCat + '" />',
                (alreadyInArticle
                    ? $('<b title="' + msgs.alreadyInLinkedArticle + '"> @ </b>')
                    : textNode(' ')),
                targetWikiLink);
        }
        else if (parents && parents.length > 0) {
            var new_ul = $('<ul/>');

            var new_li;
            var alreadyListed;
            for (var i = 0; i < parents.length; i++) {
                new_li = $('<li/>');

                alreadyListed = false;

                if (allCats[parents[i]])
                    alreadyListed = $('<em>(' + msgs.alreadyListed + ')</em>');
                else {
                    new_li.append(createExpandCatLink(), textNode(' '));
                    allCats[parents[i]] = {
                        name: parents[i],
                        liElement: new_li // the innermost <li> within which the category's name is shown
                    };
                }

                new_li.append('<span>' + parents[i] + '</span>');
                if (alreadyListed)
                    new_li.append(textNode(' '), alreadyListed);
                new_ul.append(new_li);
            }

            top_li.append(new_ul);
        } // if (parents.length > 0)
        else { // if no target-wiki cat and no parent cats found, create a link to the cat,
            //  so the user can check manually
            var catSpan = $('span', top_li).empty();
            catSpan.append($('<a/>', {
                href: server + '/wiki/'
                    + thisWiki.catNS + ':' + cat.name,
                target: '_blank',
                text: cat.name
            }))
            .append(textNode(' '),
                // no parents arr is passed if the cat doesn't exist (404 or other error received)
                $('<em>(' + msgs.error + ')</em>')
            );
        }
    } // showCatParents


    function getCheckedCats(frm)
    {
        var targetWikiCats = $('input:checkbox[name=targetWikiCats]:checked', frm).map(function(){
            return this.value;
        }).toArray();

        if (targetWikiCats.length == 0)
            return '';
        else
            return '[[' + targetWikiCats.join(']]' + nl + '[[') + ']]';
    }


    /*************************
     ***  TRANSLATE LINKS
     ***
     ***/


    /* PUBLIC */
    // called when the user clicks on the "Links to (language code)" portlet link
    function translateLinks()
    {
        var namespaceIds = mw.config.get('wgNamespaceIds');
        var hWLs = $('.wtt_targetWikiLink');
        if (hWLs.length > 0) {
            hWLs.toggle(); // previously shown for the current language
            return;
        }

        if (!targetWiki.lang) {
            setTargetWiki(prompt(msgs.enterTargetWiki, options.defaultWiki));
            if (!targetWiki.lang)
                return;
        }

        $($('#mw-content-text')[0] || $('body')).find('a')
        .not('#wikiTranslToolsPanel a').not(skin.catLinks + ' a').not('a.external')
        .filter('[href^="/wiki/"]').after(function () {
            var article = this.href.match(/wiki\/([^#?]+)/);

            if (!article)
                return null;
            article = article[1];

            // Check if the namespace is in the array of approved namespaces: NamespacesWithInterwikis
            var ns = article.match(/[^:]+(?=:)/);
            if (ns) {
                var nsNum = namespaceIds[ns[0].toLowerCase()];
                if (nsNum && ($.inArray(nsNum, NamespacesWithInterwikis) == -1))
                    return null;
            }

            var span = $('<sup/>', {
                text: ' [[',
                'class': 'wtt_targetWikiLink reference'
            }).append($('<a/>', {
                text: targetWiki.lang + ':?',
                href: '#',
                data: {articleName: article},
                click: function () {
                    findTargetWikiLink($(this));
                    return false;
                }
            })).append(textNode(']]'));

            return span;
        });
    }


    // This function is called when the user clicks on one of the [[lang:?]] links
    function findTargetWikiLink($clickedLink)
    {
        var articleName = $clickedLink.data('articleName');
        $.ajax({
            url: '/w/api.php?action=query&prop=langlinks&titles=' + articleName
                + '&lllang=' + targetWiki.lang + '&redirects&format=json',
            dataType: 'json',
            success: function (data) {
                interwikiReceived(data, $clickedLink);
            }
        });
    }


    function interwikiReceived(data, $clickedLink)
    {
        var targetWikiPage = getTargetwikiFromJson(data);
        if (!targetWikiPage) {
            $clickedLink.replaceWith($('<span/>', {
                text: targetWiki.lang,
                css: {textDecoration: 'line-through'}
            }));
            return;
        }

        var newLink = $('<a/>', {
            href: targetWiki.server + '/wiki/' + targetWikiPage.replace(/ /, '_'),
            target: '_blank',
            text: targetWiki.lang + ':'
        });

        var input = $('<input type="text" style="vertical-align: bottom; direction: ltr;">').attr({
            value: targetWikiPage,
            size: Math.floor(targetWikiPage.length * 4/3),
            readonly: 'readonly'
        });

        var redirects = data && data.query && data.query.redirects;
        if (redirects)
            for (var i = 0; i < redirects.length; i++) {
                $clickedLink.before($('<span title="' + redirects[i].to + '">&gt;</span>'));
            }
        $clickedLink.after(input);
        $clickedLink.replaceWith(newLink);
        input.select();
    }


    /*************************
     ***  Other Functions
     ***
     ***/


    function getTargetwikiFromJson(data) {
        //{"query":{"pages":{"5152":{"pageid":5152,"ns":0,"title":"2007","langlinks":[{"lang":"bg","*":"2007"}]}}}}
        var pages = data && data.query && data.query.pages;
        if (!pages)
            return;

        var pg;
        for (var p in pages) {
            if (pages.hasOwnProperty(p))
                pg = pages[p];
        }

        return pg && pg.langlinks && pg.langlinks[0] && pg.langlinks[0]['*'];
    }


    function display($el) {
        // any normal skin || Cologne Blue skin:
        $('#contentSub').prepend($el)[0] || $('#mw-content-text').prepend($el);
        //    mw.util.$content.prepend($el) doesn't work in IE 7 (layout bug)
        $('html, body').scrollTop(0);
    }


    function displayCode(str)
    {
        str = $.trim(str);
        if ($.isFunction(options.hook))
            str = options.hook(str);

        var $ta = $('<textarea/>', {
            css: {direction: 'ltr'},
            cols: options.taCols,
            rows: options.taRows
        });

        var $codeBoxDiv = $('<div/>', {
            id: 'wikiTranslToolsCodeBox',
            css: {width: '80%', margin: '1em auto', position: 'relative'}
        }).append($ta,
            $('<div/>')
            .css({position: 'absolute', top: 0, right: '-4em', fontSize: 'small'})
            .append(
                $('<a/>', {
                    href: '#',
                    text: '[<]',
                    click: function (e) {
                        e.preventDefault();
                        $codeBoxDiv.remove();
                        showPanel();
                    }
                }),
                $('<a/>', {
                    href: options.helpUrl,
                    target: '_blank',
                    text: '[?]'
                }),
                $('<a/>', {
                    href: '#',
                    text: '[X]',
                    click: function (e) {
                        e.preventDefault();
                        $codeBoxDiv.remove();
                    }
                })
            )
        );

        display($codeBoxDiv);
        $ta.focus().val(str).select().scrollTop(0);
    }


    function addPortletLinks()
    {
        var ns = mw.config.get('wgCanonicalNamespace');
        var linksToLang = msgs.linksToLang.replace(/%targetWiki%/, (targetWiki.lang || '?'));

        if (ns != 'Special' && ns != 'MediaWiki') {
            if (!$('#wtt_showPanelLink')[0])
                mw.util.addPortletLink('p-tb', 'javascript:wikiTranslTools.showPanel(); void(0);',
                    msgs.showPanelLink,
                    'wtt_showPanelLink',
                    msgs.showPanelTitle
                );
        }
        if (!$('#wtt_translateLinksLink')[0])
            mw.util.addPortletLink('p-tb', 'javascript:wikiTranslTools.translateLinks(); void(0);',
                linksToLang,
                'wtt_translateLinksLink',
                linksToLang
            );
    }


    function targetWikiLangAtLocalStorage(valToWrite) {
        if (!window.localStorage) return '';

        if (valToWrite === undefined) {
            var storedVal = JSON.parse(localStorage.getItem(localStorageItemName) || 'null');
            return ( (storedVal && storedVal.targetWiki && storedVal.targetWiki.lang) || '' );
        }

        var obj = { targetWiki: { lang: valToWrite } };
        localStorage.setItem(localStorageItemName, JSON.stringify(obj));
    }


    /* PUBLIC */
    // Call it after updating window.wikiTranslToolsConfig
    // Will delete localStorage entry if wikiTranslToolsConfig.targetWiki.lang is set
    function init(noPortletLinks)
    {
        $('#wtt_showPanelLink').remove();
        $('#wtt_translateLinksLink').remove();
        $('#wikiTranslToolsPanel').remove();
        $('.wtt_targetWikiLink').remove();

        var wttC = window.wikiTranslToolsConfig;
        if (wttC) {
            var oldTargetWikiLang = targetWiki.lang;
            $.extend(targetWiki, wttC.targetWiki);
            $.extend(true, thisWiki, wttC.thisWiki);
            $.extend(skin, wttC.skin);
            $.extend(msgs, wttC.msgs);
            $.extend(options, wttC.options);

            if (targetWiki.lang != oldTargetWikiLang && checkIfValidWiki(targetWiki.lang))
                setTargetWiki(targetWiki.lang, true);
            else
                targetWiki.lang = oldTargetWikiLang;
        }

        if (!targetWiki.lang) {
            var savedTargetWikiLang = targetWikiLangAtLocalStorage();
            if (savedTargetWikiLang) setTargetWiki(savedTargetWikiLang, true);
        }

        if (!checkIfValidWiki(options.defaultWiki))
            options.defaultWiki = 'en';

        targetWiki.server = mw.config.get('wgServer') // needed if setTargetWiki isn't called above
            .replace(thisWiki.lang, (targetWiki.lang || options.defaultWiki));

        targetWiki.translatedPageTemplate = targetWiki.translatedPageTemplate
        .replace(/%sourceWiki%/g, thisWiki.lang)
        .replace(/%pageTitle%/g, THIS_ARTICLE)
        .replace(/%revisionId%/g, mw.config.get('wgRevisionId'));

        if (!noPortletLinks && mw.config.get('wgAction') == 'view')
            $(addPortletLinks);
    }


    function checkIfValidWiki(wikiCode)
    {
        return (wikiCode && !/[^A-Za-z0-9-]/.test(wikiCode));
    }


    // localStorage entry is set to newTargetWiki (the wiki's language code)
    // call with newTargetWiki == '' to delete localStorage entry without changing current targetWiki.lang
    function setTargetWiki(newTargetWiki, dontTouchLocalStorage)
    {
        newTargetWiki = $.trim(newTargetWiki);

        if (!dontTouchLocalStorage && newTargetWiki === '' && window.localStorage) {
            window.localStorage.removeItem(localStorageItemName);
            return;
        }

        if (!checkIfValidWiki(newTargetWiki)) {
            return;
        }

        targetWiki.lang = newTargetWiki;
        targetWiki.server = mw.config.get('wgServer').replace(thisWiki.lang, newTargetWiki);
        var targetLangInCfg = window.wikiTranslToolsConfig && window.wikiTranslToolsConfig.targetWiki
                           && window.wikiTranslToolsConfig.targetWiki.lang;
        if ( !checkIfValidWiki(targetLangInCfg) && !dontTouchLocalStorage )
            targetWikiLangAtLocalStorage(newTargetWiki);

        $('.wtt_targetWikiLink').remove();
        var newLinksToLangPortletText = msgs.linksToLang.replace(/%targetWiki%/, targetWiki.lang);
        $('#wtt_translateLinksLink a').attr('title', newLinksToLangPortletText).text(newLinksToLangPortletText);
        var $panel = $('#wikiTranslToolsPanel');
        if ($panel[0]) {
            $panel.find('.wtt_targetWikiLangLink').text(msgs.targetWiki.replace(/%targetWiki%/, targetWiki.lang));
            $panel.find('.wtt_catBrowser').replaceWith(createTheCategoryBrowser());
        }
    }


    init();


    /* All public member functions */
    return {
        init:               init,
        showPanel:          showPanel,
        translateLinks:     translateLinks,
        wikiTranslToolsTargetArticleCatsReceived: wikiTranslToolsTargetArticleCatsReceived
    };
}());
//</nowiki>