Utente:Otourly/vector.js

Da Wikizionario, il dizionario a contenuto aperto.

Nota: dopo aver pubblicato, potrebbe essere necessario pulire la cache del proprio browser per vedere i cambiamenti.

  • Firefox / Safari: tieni premuto il tasto delle maiuscole Shift e fai clic su Ricarica, oppure premi Ctrl-F5 o Ctrl-R (⌘-R su Mac)
  • Google Chrome: premi Ctrl-Shift-R (⌘-Shift-R su un Mac)
  • Internet Explorer / Edge: tieni premuto il tasto Ctrl e fai clic su Aggiorna, oppure premi Ctrl-F5
  • Opera: premi Ctrl-F5.
/**
 * ATTENZIONE : USARE CON PRUDENZA
 * 
 * Assistente per aggiungere automaticamente i traduzioni
 *
 * Adattata della versione svedese del gadget
 * [[sv:MediaWiki:Gadget-translation editor.js]] (oldid=1687182)

 *
 * Anche adattano della versione inglese
 * [[en:User:Conrad.Irwin/editor.js]]
 *
 * Autore : [[sv:User:Skalman]]
 * Adaptazione in italiano (tentativa) : [[User:Otourly]] aiutato per [[User:Automatik]] */

mw.loader.load('//sv.wiktionary.org/w/index.php?title=MediaWiki:Gadget-translation_editor.css&action=raw&ctype=text/css', 'text/css');
 
// Local storage wrapper: the same as local storage, but fails silently if it
// is not supported.
window.silentFailStorage = (function () {
	try {
		// Will throw if localStorage isn't supported or if it's disabled
		var l = window.localStorage,
			val = l.getItem('a');
		l.setItem('a', 'b');
		if (l.getItem('a') === 'b') {
			// it works
			if (val !== null)
				l.setItem('a', val);
			else
				l.removeItem('a');
			return l;
		}
	} catch (e) {
	}
 
	function noop() {}
	function rnull() { return null; }
 
	return {
		getItem: rnull,
		setItem: noop,
		removeItem: noop,
		clear: noop,
		key: rnull,
		length: 0
	};
}());

// This editor is inspired by Conrad.Irwin's editor.js.
// https://en.wiktionary.org/wiki/User:Conrad.Irwin/editor.js

// Singleton editor
window.editor = (function () {
	/* global $, mw, wgPageName, wgScript, wgArticleId, wgRevisionId, wgCurRevisionId */

	if (wgRevisionId !== wgCurRevisionId) {
		return {};
	}

	// private variables and functions
	var ed = { enabled: true },
		api,
		elem,
		history,
		// Points at the action that has been carried out
		cursor;

	function update_disabled() {
		elem.find('.ed-undo').prop('disabled', cursor === 0);
		elem.find('.ed-redo').prop('disabled', cursor === history.length - 1);
		elem.find('.ed-save').prop('disabled', history[cursor].save !== 'yes');
	}

	function show() {
		elem.show();
		if (elem.hasClass('ed-highlight')) {
			setTimeout(function () {
				elem.removeClass('ed-highlight');
			}, 500);
		}
	}

	// public methods
	ed.edit = edit;
	function edit(rev) {
		init();
		history.length = cursor + 1;
		if (!rev.save) {
			rev.save = 'yes';
		} else if (rev.save === 'ifprev') {
			rev.save = history[cursor].save;
		}
		history.push(rev);
		redo();
		show();
	}

	ed.undo = undo;
	function undo() {
		history[cursor].undo();
		cursor--;
		update_disabled();
	}

	ed.undo_all = undo_all;
	function undo_all() {
		while (cursor) {
			undo();
		}
		elem.hide();
	}

	ed.redo = redo;
	function redo() {
		history[cursor + 1].redo();
		cursor++;
		update_disabled();
	}

	ed.save = save;
	function save() {
		var wikitext = history[cursor].wikitext;

		// Allow callbacks to make last-minute modifications before saving
		for (var i = cursor; i; i--) {
			if (history[i].onsave) {
				wikitext = history[i].onsave(wikitext);
			}
		}

		var log = $('<div>', {'class': 'ed-save'})
			.append(
				$('<small>', {text: summary()}),
				' ',
				$('<b>', {text: 'registrazione...'})
			).appendTo(elem.find('.ed-inner'));

		api.post({
			action: 'edit',
			title: wgPageName,
			text: wikitext,
			summary: summary(true),
			notminor: '',
			token: mw.user.tokens.values.editToken
		}).done(function (data) {
			if (!data.edit || data.edit.result !== 'Success') {
				log.addClass('error').find('b').text('È impossibile di registrare');
				return;
			}

			$('.ed-added').removeClass('ed-added');

			history.length = 1;
			cursor = 0;
			log.find('b').text('Regitrato');
			log.append(
				' ',
				$('<a>', {
					text: 'vedere la modificazione',
					href: wgScript +
						'?title=' + encodeURIComponent(mw.config.get('wgPageName')) +
						'&diff=' + data.edit.newrevid +
						'&oldid=' + data.edit.oldrevid
				})
			);
			history[0].wikitext = wikitext;
			update_disabled();
		}).fail(function (error) {
			log.find('b').addClass('error').text('È impossibile di registrare');
			return;
		});
	}

	ed.wikitext = wikitext;
	function wikitext() {
		return init() ||
			new $.Deferred()
				.resolve(history[cursor].wikitext)
				.promise();
	}

	ed.summary = summary;
	function summary(add_assisted) {
		var parts = {};
		for (var i = 1; i <= cursor; i++) {
			var h = history[i];
			if (!h.summary) {
				continue;
			}
			if (h.summary_type) {
				parts[h.summary_type] = h.summary;
			} else if (!parts._) {
				parts._ = h.summary;
			} else {
				parts._ += '; ' + h.summary;
			}
		}
		return $.map(parts, function (x) { return x; }).join('; ') +
			(add_assisted ? ' (assistito con '+'[['+'Utente:Otourly/vector.js'+']])' : '');
	}

	ed.init = init;
	function init() {
		if (elem) {
			return;
		}
		// Warn before leaving the page if there are unsaved changes.
		$(window).on('beforeunload', function () {
			if (cursor)
				return 'Ci sono modificazioni non registrati.';
		});
		history = [{
			redo: null,
			undo: null,
			wikitext: null,
			save: 'no'
		}];
		cursor = 0;

		elem = $('<div>', { 'class': 'ed-box ed-highlight' })
			.append(
				'<a class="ed-close" href="#">&times;</a>' +
				'<div class="ed-inner">' +
				'<button class="ed-save" accesskey="s">Registrare la modificazione</button><br>' +
				'<button class="ed-undo">Annulla</button>' +
				'<button class="ed-redo" disabled>Ristora</button>' +
				'</div>');
		elem.find('.ed-close').click(function (e) { undo_all(); e.preventDefault(); });
		elem.find('.ed-save').click(save);
		elem.find('.ed-undo').click(undo);
		elem.find('.ed-redo').click(redo);
		elem.appendTo('body');

		api = new mw.Api();
		return api.get({
			action: 'query',
			prop: 'revisions',
			titles: wgPageName,
			rvprop: 'content',
		}).then(function (data) {
			var wikitext = data.query.pages[wgArticleId].revisions[0]['*'];

			// Some scripts are strict, so remove trailing whitespace on non-empty lines
			wikitext = wikitext.replace(/(\S)[ \t]+(\n|$)/g, '$1$2');

			history[0].wikitext = wikitext;
			return wikitext;
		});
	}

	return ed;
})();

// This script enables adding translations without handling wikitext.
//
// It is very much inspired by User:Conrad.Irwin/editor.js. The reason
// I created this script was because editor.js was difficult to modify
// for the needs of sv-wikt. This script is, currently, no better, with
// sv-wikt's idiosyncrasies and Swedish littered all-over. The goal is,
// however, to move all of that to a configuration section.
//
// Main differences:
// * This uses jQuery, which greatly simplifies a lot of DOM stuff.
// * This handles fewer special cases and is therefore smaller.
// * This uses mw.Api (or $.ajax where mw.Api isn't sufficient).
// * This uses promises, editor.js uses callbacks.
//
// TODO:
// * Move out sv-wikt-specific stuff
// * Structure the code better
// * Handle special cases:
//   - Reference to another page's translations
//   - Parameter halv=
//   - Translations that need to be checked
//   - Warn on capital letters (except for German) and comma, unless the
//     title contains one
//
//
// - Skalman
//
// P.S.
// Please contact me if you have questions!
// https://sv.wiktionary.org/wiki/Anv%C3%A4ndardiskussion:Skalman
'use strict';
 
/* global $, mw, editor, silentFailStorage, addGreenLinks, wgPageName */
 
if (editor.enabled) {
	$('table.translations')
		.each(function (i) {
			add_translation_form(this, i);
			add_heading_updater(this);
		});
}
 
function get_translation_table_index(table) {
	return $.inArray(table, $('table.translations'));
}
 
function get_error_html(message) {
	return '<img src="//upload.wikimedia.org/wikipedia/commons/4/4e/MW-Icon-AlertMark.png"> ' + message;
}
 
var heading_id_counter = 0;
function add_heading_updater(table) {
	var id = heading_id_counter++;
 
	var self = $(table).parent('.NavContent').prev('.NavHead');
 
	var edit_head = $('<a>', {
		href: '#',
		text: '±',
		'class': 'ed-edit-head',
		title: 'Ändra översättningsrubrik'
	}).prependTo(self);
 
	function remove_gloss_nodes() {
		var nodes = [];
		$.each(self[0].childNodes, function (i, node) {
			if (node.className !== 'ed-edit-head' && node.className !== 'NavToggle') {
				nodes.push(node);
			}
		});
		$(nodes).detach();
		return nodes;
	}
 
	var gloss_nodes;
	edit_head.click(function (e) {
		e.preventDefault();
		if (self.find('form').length) {
			self.find('form').remove();
			self.append(gloss_nodes);
			return;
		}
 
		edit_head.text('Caricando...');
 
		editor.wikitext()
		.then(function (wikitext) {
			edit_head.text('±');
			gloss_nodes = remove_gloss_nodes();
			var prev_gloss_nodes = gloss_nodes;
 
			var gloss = translation.get_gloss(wikitext, get_translation_table_index(table));
 
			var form = $('<form>', { html:
				'<label>Definizione: <input name="gloss"></label>' +
				'<button type="submit">Previsualizzazione</button> ' +
				'<span class="ed-loading">Caricando...</span>' +
				'<span class="ed-errors"></span>'
			});
			function error() {
				form.find('.ed-errors')
					.html(get_error_html('Ange antingen hela mallen {{Trad1}} eller undvik helt wikitext ([]{}#|).'));
			}
 
			self.append(form);
 
			form.find('input')
				.val(gloss.standard ? gloss.text : gloss.trans_top)
				.focus();
			form.click(function (e) {
				e.stopPropagation();
			}).submit(function (e) {
				e.preventDefault();
				var gloss_wikitext = this.gloss.value;
				if (!translation.is_trans_top(gloss_wikitext) && translation.contains_wikitext(gloss_wikitext)) {
					error();
					return;
				}
				form.find('.ed-loading').show();
 
				$.when(
					parse_wikitext(translation.make_trans_top(gloss_wikitext)),
 
					// get wikitext again in case it has changed since last time
					editor.wikitext()
				).done(function (gloss_html, wikitext) {
					gloss_html = $(gloss_html);
					var prev_class = self.parent('.NavFrame').attr('class');
					var new_class = gloss_html.filter('.NavFrame').attr('class');
 
					gloss_html = gloss_html.find('.NavHead').contents();
					if (!gloss_html.length) {
						error();
						form.find('.ed-loading').hide();
						return;
					}
 
					form.remove();
					wikitext = translation.set_gloss(
						wikitext,
						get_translation_table_index(table),
						gloss_wikitext
					);
					editor.edit({
						wikitext: wikitext,
						summary: 'översättningsrubrik: "' + gloss_wikitext + '"',
						summary_type: 'gloss' + id,
						redo: function () {
							remove_gloss_nodes();
							$('<span>', {
								'class': 'ed-added',
								html: gloss_html
							}).appendTo(self);
							if (prev_class !== new_class) {
								self.parent('.NavFrame').attr('class', new_class);
							}
						},
						undo: function () {
							remove_gloss_nodes();
							self.append(prev_gloss_nodes);
							if (prev_class !== new_class) {
								self.parent('.NavFrame').attr('class', prev_class);
							}
						}
					});
				});
			});
		});
	});
}
 
function add_balancer(self, i) {
	var row = self.find('tr:first-child');
	if (row.children().length === 1) {
		row.append('<td>', row.children().first().clone().text(''));
	}
	var cell = row.children().eq(1);
	if (cell.find('button').length) {
		return;
	}
	cell.css('vertical-align', 'middle')
	.append(
		$('<button>', { text: '←', click: balance, value: -1 }),
		$('<br>'),
		$('<button>', { text: '→', click: balance, value: 1 })
	);
	var first_list = row.children().first().children('ul');
	var last_list = row.children().last().children('ul');
	if (!first_list.length) {
		first_list = $('<ul>').appendTo(row.children().first());
	}
	if (!last_list.length) {
		last_list = $('<ul>').appendTo(row.children().last());
	}
	function move(direction) {
		if (direction === 1) {
			first_list.children().last().prependTo(last_list);
		} else {
			last_list.children().first().appendTo(first_list);
		}
	}
	function balance() {
		var direction = +this.value;
		editor.wikitext()
		.done(function (wikitext) {
			var balanced_wikitext = translation.balance(wikitext, i, direction);
			if (balanced_wikitext === wikitext) {
				throw new Error('Ingen översättning att flytta');
			}
			editor.edit({
				wikitext: balanced_wikitext,
				summary: 'balansera',
				summary_type: 'balance',
				redo: function () {
					move(direction);
				},
				undo: function () {
					move(-direction);
				}
			});
		});
	}
}
 
function add_translation_form(table) {
	var self = $(table);
	var lang_meta = {
		// en, eo, fi, hu
		'': 'p',
 
		da: 'u n p',
		de: 'm f n p',
		es: 'm f p',
		fo: 'm f n',
		fr: 'm f mf p',
		is: 'm f n p',
		it: 'm f p',
		ja: 'trans',
		la: 'm f n p',
		lv: 'm f p',
		nl: 'm f n p',
		nn: 'm f n p',
		no: 'm f n p',
		pl: 'm f n p',
		pt: 'm f p'
	};
	var options = $.map({
		gender: {
			m: 'masc.',
			f: 'fem.',
			mf: 'masc. & fem.',
			n: 'neutrale',
			u: 'commune'
		},
		number: {
			s: 'singolare',
			d: 'dualis',
			p: 'plurale'
		}
	}, function (items, name) {
		items = $.map(items, function (text, value) {
			return '<label class="ed-' + value + '">' +
				'<input type="radio" name="' + name + '" value="' + value + '">' +
				text +
				'</label>';
		});
		return '<p class="ed-options ed-' + name + '">' + items.join(' ') + '</p>';
	}).join('') +
	'<p class="ed-options"><label class="ed-trans">Translitterazione: <input name="trans"></label></p>' +
	'<p class="ed-options"><label class="ed-note">Not: <input name="note"></label></p>';
 
	var form = $($.parseHTML('<form><ul><li>' +
		'<p><label>Aggiungi una traduzione in' +
		'<input class="ed-lang-code" name="lang_code" size="3" title="Språkkod (två eller tre bokstäver)"></label>: ' +
		'<input class="ed-word" name="word" size="20" title="Ord på det andra språket"> ' +
		'<button type="submit">Aggiungere</button> ' +
		'<a href="#" class="ed-more">Più</a></p>' +
		options +
		'<div class="ed-errors"></div>' +
		'</li></ul></form>'));
 
	// Make radio buttons deselectable
	form.find(':radio').click(function last_click(e) {
		if (last_click[this.name] === this) {
			last_click[this.name] = null;
			this.checked = false;
		} else {
			last_click[this.name] = this;
		}
	});
	var show_all_opts = false;
	form.find('.ed-lang-code')
		.blur(update_options_visibility)
 
		// If the item exists, the value will be used as the value,
		// otherwise it's 'null', which empties (the already empty)
		// text field.
		.val(silentFailStorage.getItem('trans-lang'));
	form.find('.ed-more').click(function (e) {
		e.preventDefault();
		show_all_opts = !show_all_opts;
		$(this).text(show_all_opts ? 'Meno' : 'Più');
		update_options_visibility();
	});
	update_options_visibility();
	function update_options_visibility() {
		var elems = form.find('.ed-options label');
		if (show_all_opts) {
			elems.show();
		} else {
			var opts = lang_meta[form[0].lang_code.value] || lang_meta[''];
			elems
				.hide()
				.filter('.ed-' + opts.replace(/ /g, ', .ed-')).show();
		}
	}
 
	$('<tr>')
		.append('<td>', '<td>', $('<td>').append(form) )
		.appendTo(self);
 
	self.find('input').focus(function () {
		editor.init();
	});
	form.submit(function (e) {
		e.preventDefault();
		var lang_code = this.lang_code.value;
		var word = this.word.value;
		var gender = form.find('.ed-gender input:checked').prop('checked', false).val();
		var number = form.find('.ed-number input:checked').prop('checked', false).val();
		var trans = this.trans.value;
		var note = this.note.value;
 
		if (!lang_code) {
			show_error(new NoInputError('lang-code'));
			return;
		} else if (!word) {
			show_error(new NoInputError('word'));
			return;
		}
 
		var word_options = {
			lang_code: lang_code,
			word: word,
			lang_name: null,
			exists: null,
			gender: gender,
			number: number,
			trans: trans,
			note: note
		};
 
		function show_error(e) {
			form.find('.ed-error').removeClass('ed-error');
			if (!e) {
				form.find('.ed-errors').empty();
				return;
			}
			if (e instanceof NoLangTplError) {
				form.find('.ed-lang-code').addClass('ed-error').focus();
				e = 'Il codice di lingua "' + e.lang_code + '" non esiste o non è usato';
			} else if (e instanceof NoInputError) {
				form.find('.ed-' + e.input).addClass('ed-error').focus();
				if (e.input === 'lang-code') {
					e = 'Scriva il codice di lingua (en, fr, abq)';
				} else if (e.input === 'word') {
					e = 'Scriva traduzione';
				}
			} else if (e instanceof HttpError) {
				e = 'Non si può caricare la traduzione. Sia online?';
			}
			form.find('.ed-errors').html(get_error_html(e));
		}
 
 
		$.when(
			// word_html
			page_exists(lang_code, word)
			.then(function (page_exists) {
				word_options.exists = page_exists;
				return parse_wikitext(translation.get_formatted_word(word_options))
				.then(function (html) {
					return html;
				});
			}),
			// wikitext
			editor.wikitext(),
			// lang_name
			translation.get_language(lang_code)
		).fail(function (error) {
			if (error === 'http') {
				// jQuery HTTP error
				show_error(new HttpError());
			} else {
				show_error(error);
			}
		}).done(function (word_html, wikitext, lang_name) {
			show_error(false);
 
			silentFailStorage.setItem('trans-lang', lang_code);
 
			form[0].word.value = '';
			form[0].trans.value = '';
			form[0].note.value = '';
 
			word_options.lang_name = lang_name;
			var added_elem;
			var index = get_translation_table_index(table);
 
			wikitext = translation.add(wikitext, index, word_options);
 
			add_balancer(self, index);
 
			editor.edit({
				summary: '+' + lang_code + ': [[' + word + ']]',
				wikitext: wikitext,
				redo: function () {
					var translations = self.find('tr:first-child > td > ul > li,' +
						'tr:first-child > td:nth-child(2)');
					translation.add_to_list({
						items: translations,
						add_only_item: function () {
							added_elem = $('<li>', { html: lang_name + ': ' + word_html });
							self.find('tr:first-child > td:first-child > ul').append(added_elem);
						},
						equal_or_before: function (item) {
							var match = /^\s*([^:]+):/.exec($(item).text());
							if (match) {
								if (match[1] === lang_name) {
									return 'equal';
								} else if (match[1] < lang_name) {
									return 'before';
								}
							}
							return false;
						},
						add_to_item: function (item) {
							added_elem = $('<span>', { html: ', ' + word_html})
								.appendTo(item);
						},
						add_after: function (item) {
							added_elem = $('<li>', { html: lang_name + ': ' + word_html })
								.insertAfter(item);
						},
						add_before: function (item) {
							added_elem = $('<li>', { html: lang_name + ': ' + word_html });
							if ($(item).is('td')) {
								// Special case: {{mid}}
								self.find('tr:first-child > td:first-child > ul').append(added_elem);
							} else {
								added_elem.insertBefore(item);
							}
						},
					});
					added_elem.addClass('ed-added');
					if (window.addGreenLinks) addGreenLinks(added_elem);
				},
				undo: function () {
					added_elem.remove();
				}
			});
		});
	});
}
 
function parse_wikitext(wikitext) {
	return new mw.Api().get({
		action: 'parse',
		text: '<div>' + wikitext + '</div>',
		title: wgPageName
	}).then(function (data) {
		var html = data.parse.text['*'];
		// Get only the parts between <div> and </div>
		html = html.substring(
			html.indexOf('<div>') + '<div>'.length,
			html.lastIndexOf('</div>')
		);
		return $.trim(html);
	});
}
 
function page_exists(lang_code, page) {
	var def = $.Deferred();
	$.ajax({
		url: '//' + lang_code + '.wiktionary.org/w/api.php?origin=' + location.protocol + '//' + location.host,
		data: {
			action: 'query',
			titles: page,
			format: 'json'
		},
		dataType: 'json'
	}).fail(function () {
		def.resolve(false);
	}).then(function (data) {
		def.resolve(!data.query.pages[-1]);
	});
	return def.promise();
}
 
var translation = {
	re_wikitext: /[[\]{}#|]/,
 
	contains_wikitext: function (str) {
		return translation.re_wikitext.test(str);
	},
 
	re_gloss: /\{\{Trad1([^\}]*)\}\}/g,
 
	re_section: /(\{\{Trad1[^\}]*\}\})([\s\S]*?)(\{\{Trad2\}\})/g,
 
	is_trans_top: function (gloss) {
		return gloss.replace(translation.re_gloss, '-') === '-';
	},
 
	make_trans_top: function (gloss) {
		if (translation.is_trans_top(gloss)) {
			return gloss;
		} else {
			return '{{Trad1|' + gloss + '}}';
		}
	},
 
	get_gloss: function (wikitext, index) {
		if (wikitext.indexOf('<!--') !== -1) {
			throw new Error('Wikitext innehåller "<!--". Ändra manuellt.');
		}
		translation.re_gloss.lastIndex = 0;
 
		for (var i = 0; i <= index; i++) {
			var match = translation.re_gloss.exec(wikitext);
			if (i === index && match) {
				var standard = /^(|\|[^\|=]*)$/.test(match[1]);
				return {
					trans_top: match[0],
					text: standard ? match[1].substr(1) : void 0,
					standard: standard
				};
			}
		}
		throw new Error('Impossibile di trovare {{trad1}} numero ' + (index+1) + ' nel wikitesto.');
	},
	set_gloss: function (wikitext, index, gloss) {
		index++;
		var count = 0;
		return wikitext.replace(translation.re_gloss, function (match, p1, p2) {
			count++;
			if (count !== index) {
				return match;
			}
			return translation.make_trans_top(gloss);
		});
	},
	get_formatted_word: function (opts) {
		
		return '[[' + opts.word + ']]';
		
		opts.gender && tpl.push(opts.gender);
		opts.number && tpl.push(opts.number);
		opts.trans && tpl.push('trans=' + opts.trans);
		opts.note && tpl.push('not=' + opts.note);
		return '{{' + tpl.join('|') + '}}';
	},
	// Options:
	// - items: Array of items
	// - equal_or_before: Function that returns either 'equal', 'before' or false
	// - add_to_item: Adds a word to an item
	// - add_after: Adds the item after an item
	// - add_before: Adds the item before an item
	add_to_list: function (opts) {
		var items = opts.items;
		if (!items.length) {
			items[0] = opts.add_only_item();
			return items;
		}
		for (var i = items.length - 1; i >= 0; i--) {
			var eq_or_bef = opts.equal_or_before(items[i]);
			if (eq_or_bef === 'equal') {
				items[i] = opts.add_to_item(items[i]);
				return items;
			} else if (eq_or_bef === 'before') {
				items[i] = opts.add_after(items[i]);
				return items;
			}
		}
		items[0] = opts.add_before(items[0]);
		return items;
	},
	add: function (wikitext, index, opts) {
		if (wikitext.indexOf('<!--') !== -1) {
			throw new Error('Wikitext innehåller "<!--". Ändra manuellt.');
		}
		index++;
		var count = 0;
		return wikitext.replace(translation.re_section, function (match, p1, p2, p3) {
			count++;
			if (count !== index) {
				return match;
			}
 
			p2 = $.trim(p2);
 
			var formatted_word = translation.get_formatted_word(opts);
 
			var lines = translation.add_to_list({
				// split into lines
				items: p2 ? p2.split('\n') : [],
				add_only_item: function () {

				return ':*' + '{{' + opts.lang_code + '}}' + ': ' + formatted_word;
				},
				equal_or_before: function (line) {
					var match = /^\*\s*([^:]+):/.exec(line);
					if (match) {
						if (match[1] === opts.lang_name) {
							return 'equal';
						} else if (match[1] < opts.lang_name) {
							return 'before';
						}
					}
					return false;
				},
				add_to_item: function (line) {
					return line + ', ' + formatted_word;
				},
				add_before: function (line) {
					return this.add_only_item() + '\n' + line;
				},
				add_after: function (line) {
					return line + '\n' + this.add_only_item();
				}
			});
 
			if (p2.indexOf('{{mid}}') === -1) {
				lines.push('{{mid}}');
			}
 
			return p1 + '\n' + lines.join('\n') + '\n' + p3;
		});
	},
	get_language: function (lang_code) {
		var known = {
			en: 'en',
			fi: 'fi',
			it: 'it',
		};
		if (lang_code in known) {
			return $.Deferred().resolve(known[lang_code]).promise();
		} else {
			return new mw.Api().get({
				action: 'expandtemplates',
				text: '{{' + lang_code + '}}'
			}).then(function (data) {
				data = data.expandtemplates['*'];
				if (data.substr(0, 3) === '[[:') {
					return $.Deferred().reject(new NoLangTplError(lang_code)).promise();
				}
				return data;
			});
		}
	},
	balance: function (wikitext, index, direction) {
		if (wikitext.indexOf('<!--') !== -1) {
			throw new Error('Wikitext innehåller "<!--". Ändra manuellt.');
		}
		index++;
		var count = 0;
		return wikitext.replace(translation.re_section, function (match, p1, p2, p3) {
			count++;
			if (count !== index) {
				return match;
			}
			if (/(^|\n)(\*\*|\*:|:)/.test(p2)) {
				throw new Error('Kan inte balansera pga. indragna rader');
			}
 
			p2 = $.trim(p2);
 
			var lines = p2 ? p2.split('\n') : [];
			for (var i = 0; i < lines.length; i++) {
				if (lines[i] === '{{mid}}') {
					if (!lines[i - direction]) {
						// nowhere to move
						return match;
					}
					lines[i] = lines[i - direction];
					lines[i - direction] = '{{mid}}';
					return p1 + '\n' + lines.join('\n') + '\n' + p3;
				}
			}
			// couldn't find {{mid}}
			return match;
		});
	}
};
 
 
function extend_error(name, p1_name) {
	function E(p1) {
		this.message = p1;
		if (p1_name) this[p1_name] = p1;
	}
	E.prototype = new Error();
	E.prototype.constructor = E;
	E.prototype.name = name;
	return E;
}
 
var NoLangTplError = extend_error('NoLangTplError', 'lang_code');
var NoInputError = extend_error('NoInputError', 'input');
var HttpError = extend_error('HttpError');
 
// Export some useful components
window.translation = translation;
window.parse_wikitext = parse_wikitext;
window.add_heading_updater = add_heading_updater;