МедияУики:Gadget-AddTranslations.js
Забележка: За да се видят промените, необходимо е след публикуване на страницата, кешът на браузъра да бъде изтрит.
- Firefox / Safari: Задържа се клавиш Shift и се щраква върху Презареждане (Reload) или чрез клавишната комбинация Ctrl-F5 or Ctrl-R (⌘-R за Mac);
- Google Chrome: клавишна комбинация Ctrl-Shift-R (⌘-Shift-R за Mac)
- Internet Explorer / Edge: Задържа се клавиш Ctrl и се щраква върху Refresh или чрез клавишната комбинация Ctrl-F5;
- Opera: Press Ctrl-F5.
// https://bg.wiktionary.org/wiki/MediaWiki:Gadget-AddTranslations.js
// Maintained by User:V111P
(function ($) {
var pageText = '';
var pageTimestamp = '';
var unsaved = [];
var langs;
var editConflictLastTime = false;
var info = mw.config.get(['wgPageName', 'wgUserLanguage', 'wgAction', 'wgScriptPath']);
info.pageNameEncoded = encodeURIComponent(info.wgPageName);
if ( info.wgAction == 'edit' || info.wgAction == 'submit'
|| document.location.search.match(/(&|\?)oldid=/) ) return;
var msgsBg = {
gadgetName: 'Добавяне на превод',
expand: 'Разгръщане',
collapse: 'Свиване',
alreadyPublished: 'Вече публикувани преводи:',
toBePublished: 'Преводи очакващи публикуване:',
addTranslation: 'Добавяне на превод:',
publishBtn: 'Публикуване',
removeAllBtn: 'Изтриване на всички',
lang: 'език',
langHelp: 'име (на бълг. или англ.) или код на езика',
word: 'дума',
translit: 'транслит.',
notReqd: 'незадължително',
gender: 'род',
maleG: 'м.',
femaleG: 'ж.',
neuterG: 'ср.',
commonG: 'общ',
addBtn: 'Добре',
uniddLang: 'неиндентифициран език',
unrecognizedLang: 'Неразпознато име или код на език',
enterLangAndWord: 'Моля, въведете език и дума',
enterLang: 'Моля, въведете език',
enterWord: 'Моля, въведете дума',
errorOnReceing: 'Грешка при получаването на уикитекста на страницата от сървъра',
errorOnSaving: 'Грешка при публикуването',
editConflictRetrying: 'Конфликт на редакциите. Сега опитваме отново...',
editConflictTryAgain: 'Конфликт на редакциите. Опитайте отново.',
langListError: 'Грешка със списъка с езици №%num%',
langListTryIn5: 'Можете да опитате отново след 5 секунди',
langListDWFailed: 'Error: Language list download failed',
notFoundTransTopNum: 'В кода на страницата няма шаблон trans-top номер %N%',
notFoundTransBottomNum: 'В кода на страницата няма {{trans-bottom '
+ 'след шаблон trans-top номер ',
resumeAddTransl: 'добавяне на превод(и) за значение %meaningNum%',
confirmDelAllUnsaved: 'Премахване на всички непубликувани преводи?'
};
var msgsEn = {
gadgetName: 'Add translations',
expand: 'Expand',
collapse: 'Collapse',
alreadyPublished: 'Already published translations:',
toBePublished: 'Translations awaiting publication:',
addTranslation: 'Add a translation:',
publishBtn: 'Publish',
removeAllBtn: 'Remove all',
lang: 'language',
langHelp: 'name or ISO code of the language',
word: 'word',
translit: 'translit.',
notReqd: 'not required',
gender: 'gender',
maleG: 'm.',
femaleG: 'f.',
neuterG: 'n.',
commonG: 'common',
addBtn: 'Next',
uniddLang: 'unidentified language',
unrecognizedLang: 'Unrecognized language name or ISO code',
enterLangAndWord: 'Please, enter both a language and a word',
enterLang: 'Please, enter a language',
enterWord: 'Please, enter a word',
errorOnReceing: 'An error occured when trying to get the wikitext of the page from the server',
errorOnSaving: 'An error occured while trying to publish the translations',
editConflictRetrying: 'Edit conflict detected. Now retrying...',
editConflictTryAgain: 'Edit conflict detected. Please, try again.',
langListError: 'Language list error #%num%',
langListTryIn5: 'You can try again in 5 seconds',
langListDWFailed: 'Error: Language list download failed',
notFoundTransTopNum: 'In the wikitext of the page there is no template trans-top number %N%',
notFoundTransBottomNum: 'In the wikitext of the page there is no {{trans-bottom '
+ 'after template trans-top number ',
resumeAddTransl: 'adding translation(s) for meaning %meaningNum%',
confirmDelAllUnsaved: 'Remove all unpublished translations?'
};
var msgs = ( info.wgUserLanguage == 'en' ? msgsEn : msgsBg );
var classNames =
[ 'mainDiv'
, 'contentDiv'
, 'savedDiv'
, 'unsavedDiv'
, 'addDiv'
, 'savedList'
, 'unsavedList'
, 'publishButton'
, 'delAllButton'
, 'langInput'
, 'langMsgSpan'
, 'wordInput'
, 'transcrInput'
, 'pageInput'
, 'genderDiv'
, 'addButton'
, 'addErrMsgDiv'
, 'publishErrMsgDiv'
, 'expandCollapseLink'
];
var classes = {}; // CSS class names
var sels = {}; // CSS selectors
var classPrefix = 'addTranslations_';
$.each(classNames, function (i, val) {
classes[val] = classPrefix + val;
sels[val] = '.' + classPrefix + val;
});
var mainDivDataName = classes.mainDiv + 'N';
$(sels.mainDiv).remove();
var twoColCss = ''; // '-moz-column-count: 2; column-count: 2; -webkit-column-count: 2';
var $mainDiv = $('<div class="' + classes.mainDiv + '" style="text-align:left"/>');
var $savedDiv = $('<div class="' + classes.savedDiv + '" style="display:none; margin-top:1em"><b>'
+ msgs.alreadyPublished + '</b><div style="' + twoColCss + '"><ul class="'
+ classes.savedList + '"></ul></div></div>');
var $unsavedDiv = $('<div class="' + classes.unsavedDiv + '" style="display:none; margin-top:1em"><b>'
+ msgs.toBePublished + '</b><div style="' + twoColCss + '"><ul class="'
+ classes.unsavedList + '" style="margin-bottom:1em"></ul></div></div>')
.append('<input type="button" class="' + classes.publishButton + '" value="'
+ msgs.publishBtn + '"/> ')
.append('<input type="button" class="' + classes.delAllButton + '" value="'
+ msgs.removeAllBtn + '"/>')
.append('<div class="' + classes.publishErrMsgDiv + '" style="color:red"/>');
var $addDiv = $('<div class="' + classes.addDiv + '" style="margin-top:1em"></div>');
$addDiv
.append(msgs.lang + ': <input type="text" class="' + classes.langInput + '" size="10" maxlength="50"/> <span class="'
+ classes.langMsgSpan + '"> ' + msgs.langHelp + '</span><br/>')
.append(msgs.word + ': <input type="text" class="' + classes.wordInput + '" maxlength="500"/><br/>')
//.append('№: <input type="text" class="' + classes.importanceNInput + '" size="2"/><br/>')
.append(msgs.translit + ': <input type="text" class="' + classes.transcrInput + '" maxlength="500"/> (' + msgs.notReqd + ')<br/>')
//.append('страница: <input type="text" class="' + classes.pageInput + '"/> (ако е различна от думата)<br/>')
/*.append('<div class=' + classes.genderDiv + '">' + msgs.gender + ': '
+ '<input type="checkbox" value="m"/> ' + msgs.maleG + ' '
+ '<input type="checkbox" value="f"/> ' + msgs.femaleG + ' '
+ '<input type="checkbox" value="n"/> ' + msgs.neuterG + ' '
+ '<input type="checkbox" value="c"/> ' + msgs.commonG + ' -- не работят все още</div>'
) */
.append('<input type="button" class="' + classes.addButton + '" value="' + msgs.addBtn + '" style="margin-top:1em">')
.append('<div class="' + classes.addErrMsgDiv + '" style="color:red"/>');
var $contentDiv = $('<div class="' + classes.contentDiv + '" style="display:none; margin-bottom:1em;"/>')
.append($addDiv, $unsavedDiv, $savedDiv);
var $headingRow = $('<div/>').append($('<span/>').append(msgs.addTranslation))
.append(' [<a class="' + classes.expandCollapseLink + '" style="cursor: pointer">'
+ msgs.expand + ' ▼</a>]');
$mainDiv.append($headingRow, $contentDiv);
var els = []; // jQuery objects containing the created by this script DOM elements
$('table.translations').each(function (i, v) {
unsaved[i] = [];
var $mainDivClone = $mainDiv.clone().addClass(classes.mainDiv + i).data(mainDivDataName, i);
$(this).after($mainDivClone);
var elsN = els[i] = {mainDiv: $mainDivClone};
$.each(classNames, function (i_, v) {
if (v == 'mainDiv') return;
elsN[v] = $mainDivClone.find(sels[v]);
});
});
$(sels.expandCollapseLink).click(function () {
var $this = $(this);
var $contentDiv = $this.closest(sels.mainDiv).find(sels.contentDiv);
if ($contentDiv.css('display') == 'none') {
$this.text(msgs.collapse + ' ▲');
$contentDiv.css('display', 'block');
$contentDiv.find(sels.langInput).focus();
}
else {
$this.text(msgs.expand + ' ▼');
$contentDiv.css('display', 'none');
}
});
$(sels.langInput).focus(function () {
var $addDiv = $(this).closest(sels.addDiv);
function callbk($msgBox) {
var lang = $addDiv.find(sels.langInput).val().trim();
if (lang != '') doLangId($msgBox, lang);
}
requireLangList(callbk, $addDiv.find(sels.langMsgSpan));
});
$(sels.langInput).on('change', function () {
var $addDiv = $(this).closest(sels.addDiv);
function callbk($msgBox) {
var lang = $addDiv.find(sels.langInput).val().trim();
if (lang !== '')
doLangId($msgBox, lang);
}
requireLangList(callbk, $(this).closest(sels.addDiv).find(sels.langMsgSpan));
});
function error(msg) {
alert(msgs.gadgetName + ':\n' + msg);
console.log(msg);
}
function extractAndSavePageCode(obj) {
pageText = null;
try {
$.each(obj.query.pages, function (key, val) {
pageText = val.revisions[0]['*'];
pageTimestamp = val.revisions[0].timestamp;
});
}
catch (e) { error(msgs.errorOnReceing + '\n' + e.message); return; }
$(sels.publishButton).removeClass('working');
}
function saveTranslations($mainDiv) {
var n = $mainDiv.data(mainDivDataName);
var elsN = els[n];
var templStart = nthIndex(pageText, '{{trans-top', n + 1);
var editConflictPrevTime = editConflictLastTime;
editConflictLastTime = false;
if (templStart == -1) {
error( msgs.notFoundTransTopNum.replace(/%N%/, n + 1) );
return;
}
var firstLine = pageText.indexOf('\n', templStart) + 1;
var templEnd = pageText.indexOf('{{trans-bottom', templStart);
if (templEnd == -1) {
error( msgs.notFoundTransBottomNum.replace(/%N%/, n + 1) );
return;
}
var t = pageText.slice(templStart, templEnd);
var midStart = pageText.indexOf('{{trans-mid', firstLine);
var midEnd = pageText.indexOf('\n', midStart);
var midStr = pageText.slice(midStart, midEnd); // to save back line exactly as found
var t1 = pageText.slice(firstLine, midStart - 1).trim().replace(/\n\n+/g, '\n');
var t2 = pageText.slice(midEnd + 1, templEnd - 1).trim().replace(/\n\n+/g, '\n');
var tAll = t1 + '\n' + t2;
var tArr = tAll.split('\n');
$.each(tArr, function (i, line) {
// could be a lang name w/out a template:
var lCode = (line.match(/[*: ]*(?:\{\{)?([^}:]+)(?:}}) *:/) || ['', ''])[1];
var l = idLang(lCode, false);
var lSortKey = (l ? l.bgName : lCode);
tArr[i] = {sortKey: lSortKey, line: line};
});
// add the unsaved[n]'s elements
var toBePublishedListMsgLns = [];
var toBePublishedLangCodes = [];
$.each(unsaved[n], function (i, v) {
var sortKey = v.lang.bgName;
var trscr = v.transcr;
var lCode = v.lang.code;
var lineStart = '*{{' + lCode + '}}: ';
var entry = '{{п' + (trscr ? '+' : '') + '|' + lCode + '|'
+ v.word + (trscr ? '|' + trscr : '') + '}}';
var transcrP = ( trscr ? ' (' + trscr + ')' : '' );
var addedToTempl = false;
$.each(tArr, function (i, o) {
if (o.sortKey == sortKey) { // add to this line
o.line = o.line + ', ' + entry;
addedToTempl = true;
return false;
}
});
if (!addedToTempl) {
tArr.push({sortKey: sortKey, line: lineStart + entry});
}
tArr.sort(function (v1, v2) {
if (v1.sortKey < v2.sortKey) return -1;
else if (v1.sortKey == v2.sortKey) return 0;
else return 1;
});
toBePublishedListMsgLns.push(v.lang.name + ' (' + v.lang.code + '): ' + v.word + transcrP);
toBePublishedLangCodes.push(v.lang.code);
});
var sndColIndex = tArr.length;
$.each(tArr, function (i, o) {
if (o.sortKey[0] < 'н') {
sndColIndex = i + 1;
}
tArr[i] = o.line;
});
tArr.splice(sndColIndex, 0, midStr);
var newWikitext = pageText.slice(0, firstLine) + tArr.join('\n')
+ '\n' + pageText.slice(templEnd);
var editRequestObj = {
action: 'edit',
nocreate: '',
title: info.wgPageName,
basetimestamp: pageTimestamp,
summary: msgs.resumeAddTransl.replace(/%meaningNum%/, n + 1) + ': ' + toBePublishedLangCodes.join(', '),
text: newWikitext,
token: mw.user.tokens.get('csrfToken'),
format: 'json'
};
$.post(info.wgScriptPath + '/api.php', editRequestObj, function (data) {
var err = data.error;
var msg = '';
if (err) {
var editConflictMsg = err.code == 'editconflict' ? msgs.editConflict : '';
if (err.code == 'editconflict')
msg = editConflictPrevTime ? msgs.editConflictTryAgain : msgs.editConflictRetrying;
else msg = err.info;
elsN.publishErrMsgDiv.text(msg);
if (err.code == 'editconflict') {
editConflictLastTime = true;
if (!editConflictPrevTime)
elsN.publishButton.click();
else error(msg);
}
console.log( 'Add translations tool: Error while saving: ' + err.info + ' (' + err.code + ')' );
} else {
$.each(toBePublishedListMsgLns, function (i, v) {
elsN.savedList.append('<li>' + v + '</li>');
});
pageText = newWikitext;
elsN.savedDiv.css('display', 'block');
elsN.unsavedDiv.css('display', 'none');
elsN.unsavedList.empty();
unsaved[n] = [];
elsN.publishErrMsgDiv.empty();
}
elsN.publishButton.removeClass('working');
});
}
$(sels.publishButton).click(function (e) {
var $el = $(this).addClass('working');
function go() {
saveTranslations($el.closest(sels.mainDiv));
}
e.preventDefault();
if (pageText != '' && !editConflictLastTime) go();
else {
$.get(info.wgScriptPath
+ '/api.php?action=query&prop=revisions&rvprop=content|timestamp&format=json&titles='
+ info.pageNameEncoded,
function (obj) {
extractAndSavePageCode(obj);
if (pageText != null) go();
}
);
}
});
$(sels.delAllButton).click(function (e) {
var $mainDiv = $(this).closest(sels.mainDiv);
var n = $mainDiv.data(mainDivDataName);
if (unsaved[n].length == 0 || !confirm( msgs.confirmDelAllUnsaved ))
return;
unsaved[n] = [];
els[n].unsavedDiv.css('display', 'none');
els[n].unsavedList.empty();
els[n].publishErrMsgDiv.empty();
});
$(sels.addButton).click(function (e) {
e.preventDefault();
var $mainDiv = $(this).closest(sels.mainDiv);
var n = $mainDiv.data(mainDivDataName);
var elsN = els[n];
var lang = elsN.langInput.val().trim().substr(0, 50);
var word = elsN.wordInput.val().trim().substr(0, 300);
var transcr = elsN.transcrInput.val().trim().substr(0, 300);
var $errorMsgBox = elsN.addErrMsgDiv;
var err = '';
$errorMsgBox.empty();
if (lang == '' && word == '') err = msgs.enterLangAndWord;
else if (lang == '') err = msgs.enterLang;
else if (word == '') err = msgs.enterWord;
if (err) {
$errorMsgBox.text(err);
return;
}
requireLangList(reqLangListCallback, $errorMsgBox, lang, true);
function reqLangListCallback($errorMsgBox, lang) {
var langId = idLang(lang, true);
if (!langId) {
$errorMsgBox.text( msgs.unrecognizedLang + ': ' + lang );
return;
}
elsN.wordInput.val('');
elsN.transcrInput.val('');
elsN.langInput.val('').focus();
elsN.langMsgSpan.text( msgs.langHelp );
var transcrP = ( transcr ? ' (' + transcr + ')' : '' );
elsN.unsavedList.append('<li>' + langId.name + ' (' + langId.code + '): '
+ word + transcrP + '</li>');
unsaved[n].push( {lang: langId, word: word, transcr: transcr} );
elsN.unsavedDiv.css('display', 'block');
}
});
var langListLastRequested = 0;
var langListDWFailed = false;
function requireLangList(callback, $msgBox, param, important) {
if (langs) {
if (callback) callback($msgBox, param);
return;
}
var time = new Date().getTime();
var timeToWait = (important || langListDWFailed) ? 5000 : 15000 // in milliseconds
if ( time - langListLastRequested < timeToWait ) {
if (important) $msgBox.text(msgs.langListTryIn5);
return;
}
if (important) $msgBox.empty();
langListLastRequested = time;
(new mw.Api()).get({
action: 'expandtemplates',
format: 'json',
prop: 'wikitext',
text: '{{#invoke:Langs|getLangsAsJson|userLang= ' + info.wgUserLanguage + ' }}'
}).done(function(data) {
var json = data && data.expandtemplates && data.expandtemplates.wikitext;
if (!json) {
if ($msgBox) $msgBox.text( msgs.langListError.replace(/%num%/, 1) );
console.log('addTranslations.js: requireLangList(): No \'wikitext\' value in response.'
, '\n', json.substr(0, 200));
}
else {
try {
langs = JSON.parse(json);
}
catch (e) {
if ($msgBox) $msgBox.text( msgs.langListError.replace(/%num%/, 2) );
console.log('addTranslations.js: requireLangList(): Error parsing JSON: ', e
, '\n', json.substr(0, 200), data);
return;
}
if (callback) callback($msgBox, param);
}
}).fail(function (e) {
if ($msgBox) $msgBox.text(msgs.langListDWFailed);
console.log('addTranslations.js: requireLangList(): Lang list download failed.');
});
}
var doLangId = function ($msgSpan, val) {
langId = idLang(val, true);
msg = (langId == null
? msgs.uniddLang + ': „' + val + '“'
: langId.name + ' (' + langId.code + ')');
$msgSpan.text(msg);
}
$(sels.langInput).on('keyup', function (e) {
var $input = $(this);
var $msgSpan = els[ $input.closest(sels.mainDiv).data(mainDivDataName) ].langMsgSpan;
var val = $input.val().trim().substr(0, 50);
var langId, msg;
if (val == '') {
msg = msgs.langHelp;
$msgSpan.text(msg);
}
else {
requireLangList(doLangId, $msgSpan, val);
}
});
// by kennebec, http://stackoverflow.com/a/14482123
function nthIndex(str, pat, n) {
var L = str.length, i = -1;
while (n-- && i++ < L) {
i = str.indexOf(pat, i);
if (i < 0) break;
}
return i;
}
function idLang(lang, autocomplete) {
var name = '';
var code = '';
var userLang = info.wgUserLanguage
var namesArr, codesArr, displayName, bgName, doAC;
lang = lang.trim().toLowerCase().replace(/[()[\]{}\\+*?^$|]/g, ''); // used in regexes below
var namesSearchArrName = lang.match(/^[а-я]/) ? 'bgNames' : 'enNames'
var nameDisplayPropertyName = userLang == 'en' ? 'enNames' : 'bgNames'
var latin = lang.match(/^[a-z]/) ? true : false;
var exactMatchFound = false;
$.each(langs, function (i1, langObj) {
namesArr = langObj[namesSearchArrName];
displayName = langObj[nameDisplayPropertyName][0];
codesArr = langObj.codes;
if (latin) { // check for language code match
$.each(codesArr, function (i2, code_) {
doAC = autocomplete && name == '';
exactMatchFound = code_ == lang;
if ( exactMatchFound || ( doAC && code_.match("^" + lang) ) ) {
name = displayName;
bgName = langObj.bgNames[0];
code = codesArr[0];
}
return !exactMatchFound; // return false to break out of $.each()
});
}
if (!exactMatchFound) { // check for language name match
$.each(namesArr, function (i3, name_) {
doAC = autocomplete && name == '' && (lang.length > 1 || !latin);
exactMatchFound = name_ == lang;
if ( exactMatchFound || ( doAC && name_.match("^" + lang) ) ) {
name = displayName;
bgName = langObj.bgNames[0];
code = codesArr[0];
}
return !exactMatchFound; // return false to break out of $.each()
});
}
return !exactMatchFound; // return false to break out of $.each()
});
if (name.length > 0)
name = userLang == 'en' ? toTitleCase(name) : name
return ( name == '' ? null : { name: name, bgName: bgName, code: code } );
}
function toTitleCase(str)
{
return str.replace(/[^\s-]+/g, function(txt) {
return txt[0].toUpperCase() + txt.substr(1); // .toLowerCase();
});
}
return {
};
})($);