User:So9q/translation-editor.js

From Wiktionary, the free dictionary
Jump to navigation Jump to search

Note: You may have to bypass your browser’s cache to see the changes. In addition, after saving a sitewide CSS file such as MediaWiki:Common.css, it will take 5-10 minutes before the changes take effect, even if you clear your cache.

  • Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
  • Konqueror and Chrome: click Reload or press F5;
  • Opera: clear the cache in Tools → Preferences;
  • Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.

/**
 * Assistant pour l'ajout de traductions
 *
 * Adapté de la version suédoise du gadget
 * [[sv:MediaWiki:Gadget-translation editor.js]] (oldid=1687182) - auteur : [[sv:User:Skalman]]
 *
 * elle-même inspirée de la version anglaise
 * [[en:User:Conrad.Irwin/editor.js]]
 *
 * Auteur : [[fr:User:Automatik]] ([[Utilisateur:Automatik/translation editor.js/Adaptation]])
 * 
 * Forked 24/10-2019 from FR:WT https://fr.wiktionary.org/w/index.php?title=MediaWiki:Gadget-translation_editor.js&oldid=26033393 by So9q
 */

/* global jQuery, mediaWiki, editor, silentFailStorage, CrTr_ChangerLiensRouges, wgPageName */

// load stuff before run
$.when(
	mw.loader.getScript( 
	'/w/index.php?title=User:So9q/User:So9q/CreerTrad.js&action=raw&ctype=text/javascript'),
	mw.loader.getScript( 
	'/w/index.php?title=User:So9q/silent-fail-storage.js&action=raw&ctype=text/javascript'),
	mw.loader.getScript( 
	'/w/index.php?title=User:So9q/editor.js&action=raw&ctype=text/javascript'))
.done(function () {
	//debug
	console.info('start script');	
	
	( function( mw, $ ) { // Closure (fermée à la toute fin du script)
	'use strict';
	
	var
		// Tableau de correspondance langue <-> code langue
		// que l'on va charger à la prise de focus sur un formulaire
		tab_langues,
		// Id récupéré par la fonction add_heading_updater() pour
		// typer correctement les résumés d'édition
		heading_id_counter = 0;

	// run in so9qs sandbox only
	if (mw.config.get('wgPageName') === 'User:So9q/sandbox')
		editor.enabled = true;	

	if (editor.enabled) {
		$('div.translations')
			.each(function (i) {
				add_translation_form(this, i);
				add_heading_updater(this);
			});
		// Cache les liens d'ébauche traductions s'il y en a
		$('.trad-stub').hide();
		$('.trad-stub').parent('li').hide();
	}
	
	// Ce qu'on appelle 'table' est en fait un 'div' sur fr.wikt
	// mais peu importe (ça a l'apparence d'un tableau de valeurs)
	function get_translation_table_index(table) {
		return $.inArray(table, $('div.translations'));
	}
	
	function get_error_html(message) {
		return '<img src="//upload.wikimedia.org/wikipedia/commons/4/4e/MW-Icon-AlertMark.png"> ' + message;
	}
	
	function get_info_html(message) {
		return '<img src="//upload.wikimedia.org/wikipedia/commons/f/f8/Farm-Fresh_information.png"> ' + message;
	}
	
	function add_heading_updater(table) {
		var id = heading_id_counter++;
	
		var self = $(table).parent().parent('.NavContent').prev('.NavHead');
	
		var edit_head = $('<a>', {
			href: '#',
			text: '±',
			'class': 'ed-edit-head',
			title: 'Changer la description'
		}).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('Chargement…');
	
			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 gloss_to_handle = gloss.standard ? gloss.text : gloss.trans_top;
	
				var form = $('<form>', { html:
					'<label>Définition: <input name="gloss"></label>' +
					'<button type="submit">Prévisualiser</button> ' +
					'<span class="ed-loading">Chargement…</span>' +
					'<span class="ed-errors"></span>'
				});
				function error(msg) {
						form.find('.ed-errors')
							.html(get_error_html(msg));
				}
	
				self.append(form);
	
				form.find('input')
					.val(gloss_to_handle)
					.focus();
				form.click(function (e) {
					e.stopPropagation();
				}).submit(function (e) {
					e.preventDefault();
					if (this.gloss.value === gloss_to_handle) {
						error('La description n\'a pas changé');
						return;
					}
					var gloss_wikitext = $.trim(this.gloss.value);
					if (!translation.is_trans_top(gloss_wikitext) &&
					    translation.contains_wikitext(gloss_wikitext)) {
						error('La description n\'a pas un format correct : elle' +
						      ' contient du wikitexte ([]{}#|)' +
							  ' (alors que {{trans-top/new}} est absent)');
						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();
	
						form.remove();
						wikitext = translation.set_gloss(
							wikitext,
							get_translation_table_index(table),
							gloss_wikitext
						);
						editor.edit({
							wikitext: wikitext,
							summary: 'en-tête : "' + 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_translation_form(table) {
		var
			self = $(table),
			nav_content = self.parent().parent();
			
		// s'il n'y a aucune traduction dans la boite, on rajoute le 'ul'
		// pour pouvoir y attacher le formulaire
		if ( self.children().length === 0 ||
		     self.children().first().prop('nodeName').toLowerCase() !== 'ul' ) {
			self.append( $( '<ul></ul>' ) );
		}
		
		// Options de langues
		var lang_meta = {
			'': '', // par défaut, ni genre ni nombre à cocher
	
			'allemand': 'm f n p',
			'arabe': 'm f d p trans',
			'aragonais': 'm f p',
			'arménien': 'trans',
			'arménien ancien': 'trans',
			'asturien': 'm f n p',
			'bengali': 'trans',
			'biélorusse': 'm f n p trans',
			'birman': 'trans',
			'breton': 'm f p',
			'bulgare': 'm f n p trans',
			'catalan': 'm f p',
			'chinois': 'trans tradi',
			'copte': 'm f trans',
			'coréen': 'trans tradi',
			'corse': 'm f p',
			'danois': 'c n p',
			'dzongkha': 'trans',
			'erza': 'trans',
			'espagnol': 'm f p',
			'féroïen': 'm f n',
			'frioulan': 'm f p',
			'géorgien': 'trans',
			'gaélique écossais': 'm f d p',
			'gaélique irlandais': 'm f p',
			'galicien': 'm f p',
			'gallois': 'm f p',
			'gotique': 'm f n p trans',
			'goudjarati': 'trans',
			'grec': 'trans m f n p',
			'grec ancien': 'trans m f n d p',
			'grec byzantin': 'trans',
			'hébreu': 'm f trans',
			'hébreu ancien': 'm f trans',
			'hindi': 'm f trans',
			'inuktitut': 'trans',
			'islandais': 'm f n p',
			'italien': 'm f mf p',
			'japonais': 'trans',
			'kazakh': 'trans',
			'khmer': 'trans',
			'laotien': 'trans',
			'latin': 'm f n p',
			'letton': 'm f p',
			'ligure': 'm f p',
			'lituanien': 'm f p',
			'luxembourgeois': 'm f n p',
			'macédonien': 'm f n p trans',
			'malayalam': 'trans',
			'mandarin': 'trans tradi',
			'marathe': 'trans',
			'minnan': 'tradi',
			'mirandais': 'm f p',
			'mongol': 'trans',
			'néerlandais': 'm f n p',
			'népalais': 'trans',
			'norvégien (bokmål)': 'm f n p mfp',
			'norvégien (nynorsk)': 'm f n p mfp',
			'ossète': 'trans',
			'persan': 'trans',
			'piémontais': 'm f p',
			'plodarisch': 'm f n p',
			'polonais': 'm f n p',
			'portugais': 'm f p',
			'romanche': 'm f p',
			'roumain': 'm f n p',
			'russe': 'm f n trans',
			'sanskrit': 'm f n d p trans',
			'sarde': 'm f p',
			'serbe': 'm f n p trans',
			'sherpa': 'trans',
			'sicilien': 'm f p',
			'slovaque': 'm f n',
			'slovène': 'm f n',
			'suédois': 'c n',
			'tamoul': 'trans',
			'tadjik': 'trans',
			'tatare': 'trans',
			'thaï': 'trans',
			'tibétain': 'trans',
			'ukrainien': 'm f n p trans',
			'vénitien': 'm f p',
			'vieil irlandais': 'm f n d p',
			'vieux norrois': 'm f n p',
			'vieux slave': 'm f n d p',
			'yiddish': 'm f n p trans',
		};
		
		// Les traductions dans ces langues peuvent contenir une majuscule
		// sans que cela ne produise d'erreur quand le titre de la page n'en a pas
		var expected_case = [
			'allemand',
			'alémanique alsacien',
			'vieux-francique',
			'francique mosellan',
			'francique rhénan',
			'francique ripuaire',
			'luxembourgeois'
		];
		var options = $.map({
			gender: {
				m: 'masc.',
				f: 'fém.',
				mf: 'masc. & fém.',
				n: 'neutre',
				c: 'commun',
				s: 'singulier',
				d: 'duel',
				p: 'pluriel',
				mp: 'masc. plur.',
				fp: 'fém. plur.',
				np: 'neutre plur.',
				mfp: 'masc. & fém. plur.'
			}
		}, 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">Translittération : ' +
		   '<input name="trans" placeholder="ex : khimera pour химера"></label></p>' +
		'<p class="ed-options"><label class="ed-tradi">Écriture traditionnelle : ' +
		  '<input name="tradi" placeholder="ex : 軍團 pour 군단"></label></p>' +
		'<p class="ed-options"><label class="ed-pagename">Nom de page<span title="' +
				'Si la traduction ne correspond pas à un nom de page valide sur le ' +
				'Wiktionnaire, il est possible de préciser le nom de page à utiliser ici ' +
				'(le lien sur la traduction visera alors cette page)">(?)</span> : ' +
				'<input name="pagename" placeholder="ex : amo pour amō"></label></p>';
	
		var form = $($.parseHTML('<form>' +
			'<p><label><span class="txt-label">Ajouter une traduction en </span>' +
			'<input class="ed-lang-name" name="lang_name" size="13" title="' +
			  'Nom de langue (anglais,…)" placeholder="langue à choisir"></label> : ' +
			'<input class="ed-word" name="word" size="20" title="traduction" placeholder="traduction"> ' +
			'<button type="submit">Ajouter</button> ' +
			'<a href="#" class="ed-more">Plus</a></p>' +
			options +
			'<p><span class="ed-feedback">Signaler une anomalie – Suggérer une amélioration</span></p>' +
			'<div class="ed-errors"></div>' +
			'<div class="ed-info-msg"></div>' +
			'</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-name')
			// remplacement - si nécessaire - du code langue en nom de langue
			.blur(remplace_code_langue)
	
			.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 ? 'Moins' : 'Plus');
			update_options_visibility();
		});
		form.find('.ed-feedback').click(function(e) {
			mw.loader.using('mediawiki.feedback', function() {
				var
					mwTitle = new mw.Title('Discussion_MediaWiki:Gadget-translation_editor.js'),
					feedback = new mw.Feedback({
						title: mwTitle
					});
				feedback.launch({subject: 'Traductions dans \"' + mw.config.get('wgPageName').replace(/_/g, ' ') + '\"'});
				// Afficher un placeholder pour le message
				var placeholder_feedback = 'Veuillez entrer votre message ici';
				if ($('.mw-feedbackDialog-feedback-form textarea').length) {
					$('.mw-feedbackDialog-feedback-form textarea').get(0).placeholder = placeholder_feedback;
				}
				// Cacher le bouton "Signaler un bogue technique"
				$('.oo-ui-processDialog-actions-other').hide();
			});
		});
		update_options_visibility();
		function update_options_visibility() {
			var elems = form.find('.ed-options label');
			if (show_all_opts) {
				elems.show();
				form.find('.ed-feedback').show();
	
				// On affiche le champ pour l'écriture traditionnelle
				// uniquement en chinois et en coréen
				var lang_name = form.find('.ed-lang-name').val();
				if ($.inArray(lang_name, ['chinois', 'coréen']) === -1) {
					form.find('.ed-tradi').hide();
				}
			} else {
				var opts = lang_meta[form[0].lang_name.value] || lang_meta[''];
				elems
					.hide()
					.filter('.ed-' + opts.replace(/ /g, ', .ed-')).show();
				form.find('.ed-feedback').hide();
			}
		}
	
		// fonction pour déterminer si une chaine
		// correspond à un nom de langue dans le tableau
		function is_lang_name(str) {
			if (tab_langues === undefined) {
				return undefined;
			}
			for (var code in tab_langues) {
				if (tab_langues[code] === str) {
					return true;
				}
			}
			return false;
		}
	
		// fonction de remplacement code langue -> nom de langue
		// lancée à la perte de focus du champ du code langue
		function remplace_code_langue() {
			var elem = form.find('.ed-lang-name');
			var val = elem.val();
			if (val === '') {
				elem.attr('title', 'Nom de langue (ex : anglais)');
				return;
			}
			// Si le nom de langue est donné avec une majuscule initiale, on l'enlève
			if (val.charAt(0).toLowerCase() !== val.charAt(0)) {
				val = val.charAt(0).toLowerCase() + val.substr(1);
				elem.val(val);
			}
			// Parfois, le code hu est utilisé pour le hongrois
			// (or c'est aussi un nom de langue et les noms de langue sont gardés comme tels)
			// On le remplace donc par hongrois (aucune traduction en hu n'a jamais été ajoutée via le gadget),
			// en prévenant l'utilisateur
			if (val == 'hu') {
				elem.val('hongrois');
				var msg = 'Il a été supposé que vous voulez ajouter une traduction en hongrois.<br>' +
				'Si vous vouliez en fait ajouter une traduction en hu, <span class="ed-info-case ed-click">cliquer ici</span>.';
				form.find('.ed-info-msg').html(get_info_html(msg));
				$('.ed-info-case').click( function() {
					elem.val('hu');
					form.find('.ed-info-msg').empty();
				} );
				return;
			}
			if (is_lang_name(val)) {
				return;
			}
			if (tab_langues !== undefined && tab_langues.hasOwnProperty(val)) {
				elem.val(tab_langues[val]);
				return;
			}
			elem.attr('title', 'Nom de langue (ex : anglais)');
		}
	
		form.appendTo(nav_content);
	
		nav_content.find('input').focus(function () {
			editor.init();
			// On charge le tableau de correspondance langues <-> codes langue
			// si ce n'est pas déjà fait
			if (tab_langues === undefined) {
				var api = new mw.Api();
				return api.get({
					action: 'query',
					format: 'json',
					titles: 'User:So9q/translation-editor.js/languages.json',
					prop: 'revisions',
					rvprop: 'content'
				}).then(function (data) {
					// Get the first (and only) page from data.query.pages
					for (var pageid in data.query.pages) break;
					tab_langues = JSON.parse(data.query.pages[pageid].revisions[0]['*']);
	
					// On crée le tableau des noms de langues pour l'autocomplétion
					var tab_lang_names = [];
					for (var code in tab_langues) {
						tab_lang_names.push(tab_langues[code]);
					}
	
					// Autocomplétion du nom de langue
					mw.loader.using('jquery.ui', function() {
						$('.ed-lang-name').autocomplete({
							source: function( request, response ) {
								var matcher = new RegExp( "^" +
								              $.ui.autocomplete.escapeRegex( request.term ),
								              "i" );
								response( $.grep( tab_lang_names, function( item ){
									return matcher.test( item );
								}) );
							},
							minLength: 2
						});
					});
				});
			}
		});
	
		form.submit(function (e) {
			e.preventDefault();
			// On épure les valeurs afin d'enlever les éventuels espaces superflus
			// autour de la chaine (copiée-collée depuis un texte…)
			var lang_name = $.trim(this.lang_name.value);
			var word = $.trim(this.word.value);
			var gender = form.find('.ed-gender input:checked').prop('checked', false).val();
			var trans = $.trim(this.trans.value) !== $.trim(this.trans.getAttribute('placeholder')) ?
			                                         $.trim(this.trans.value) : '';
			var tradi = $.trim(this.tradi.value) !== $.trim(this.tradi.getAttribute('placeholder')) ?
			                                         $.trim(this.tradi.value) : '';
			var pagename = $.trim(this.pagename.value) !== $.trim(this.pagename.getAttribute('placeholder')) ?
			                                               $.trim(this.pagename.value) : '';
	
			silentFailStorage.setItem('trans-lang', lang_name);
	
			var title = mw.config.get('wgTitle');
	
			if (!lang_name) {
				show_error(new NoInputError('lang-name'));
				return;
			} else if (!word) {
				show_error(new NoInputError('word'));
				return;
			}
			// Virgule ou point-virgule dans la traduction alors que le titre
			// n'en contient pas
			else if (word.indexOf(',') !== -1 &&
			         title.indexOf(',') === -1 &&
			         silentFailStorage.getItem('comma-knowledge') !== 'ok'
			) {
				show_error(new CommaWordError('une virgule'));
				return;
			} else if (word.indexOf('،') !== -1 &&
			           title.indexOf('،') === -1 &&
			           silentFailStorage.getItem('comma-knowledge') !== 'ok'
			) {
				show_error(new CommaWordError('une virgule'));
				return;
			} else if (word.indexOf(';') !== -1 &&
			           title.indexOf(';') === -1 &&
			           silentFailStorage.getItem('comma-knowledge') !== 'ok'
			) {
				show_error(new CommaWordError('un point-virgule'));
				return;
			} else if (word.indexOf('/') !== -1 &&
			           title.indexOf('/') === -1 &&
			           silentFailStorage.getItem('comma-knowledge') !== 'ok'
			) {
				show_error(new CommaWordError('une barre oblique'));
				return;
			} else if (word.charAt(0) !== word.charAt(0).toLowerCase() &&
			           title.charAt(0) === title.charAt(0).toLowerCase() &&
			           expected_case.indexOf(lang_name) === -1 &&
			           silentFailStorage.getItem('case-knowledge') !== 'ok'
			) {
				show_error(new CaseWordError());
				return;
			} else if (lang_name === 'français') {
				show_error(new BadLangNameError());
				return;
			} else if (translation.re_wikitext.test(word)) {
				show_error(new BadFormatError());
				return;
			}
	
			var word_options = {
				lang_name: lang_name,
				lang_code: null,
				word: word,
				exists: null,
				gender: gender,
				trans: trans,
				tradi: tradi,
				pagename: pagename
			};
	
			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-name').addClass('ed-error').focus();
					e = 'La langue « ' + e.lang_name + ' » n\'est pas définie.';
				} else if (e instanceof NoInputError) {
					form.find('.ed-' + e.input).addClass('ed-error').focus();
					if (e.input === 'lang-name') {
						e = 'Entrez le nom de langue (anglais, suédois,…)';
					} else if (e.input === 'word') {
						e = 'Entrez la traduction';
					}
				} else if (e instanceof BadLangNameError) {
					form.find('.ed-lang-name').addClass('ed-error').focus();
					e = 'Il n\'est pas possible d\'ajouter une traduction en français. ' +
					'À la place, veuillez utiliser la section « Synonymes » ou ' +
					'« Variantes dialectales ».';
				} else if (e instanceof CommaWordError) {
					form.find('.ed-word').addClass('ed-error').focus();
					e = 'Êtes-vous certain que la traduction contient ' + e.message + ' ? ' +
					'Si tel n\'est pas le cas, <br />veuillez insérer les traductions une par une, ' +
					'en cliquant sur le bouton « Ajouter » après chaque insertion de traduction.<br />' +
					'Si vous êtes sûr de votre coup, veuillez <span class="ed-error-comma ed-click">cliquer ici</span>.';
				} else if (e instanceof CaseWordError) {
					form.find('.ed-word').addClass('ed-error').focus();
					e = 'Êtes-vous certain que la majuscule fait partie de la traduction ?<br />' +
					'Si tel n\'est pas le cas, merci de corriger cela.<br />' +
					'Sinon, veuillez <span class="ed-error-case ed-click">cliquer ici</span>.';
				} else if (e instanceof BadFormatError) {
					form.find('.ed-word').addClass('ed-error').focus();
					e = 'La traduction n\'est pas dans un format correct : ' +
					'elle contient du wikitexte ([]{}|=)';
				} else if (e instanceof HttpError) {
					e = 'Vous ne pouvez pas charger la traduction. Êtes-vous en ligne ?';
				}
				form.find('.ed-errors').html(get_error_html(e));
				// En cas de virgule détectée
				$('.ed-error-comma')
					.click(function() {
						silentFailStorage.setItem('comma-knowledge', 'ok');
						form.find(':submit').click();
					});
				// En cas de majuscule détectée
				$('.ed-error-case')
					.click(function() {
						silentFailStorage.setItem('case-knowledge', 'ok');
						form.find(':submit').click();
					});
			}
	
			var lang_code;
			// on convertit le nom de langue en code
			if (tab_langues !== undefined) {
				// Le tableau des langues contient-il le nom de langues donné ?
				for (var code in tab_langues) {
					if (tab_langues[code] === lang_name) {
						lang_code = code;
						break;
					}
				}
				// Si le nom de langue donné n'est pas répertorié
				// on regarde si ce n'est pas un code langue
				if (lang_code === undefined) {
					if (tab_langues[lang_name]) {
						lang_code = lang_name;
						lang_name = tab_langues[lang_code];
						word_options.lang_name = lang_name;
					} else {
						show_error(new NoLangTplError(lang_name));
						return;
					}
				}
			} else {
				throw new Error('Le tableau des langues n\'est pas défini.');
			}
	
			word_options.lang_code = lang_code;
	
			$.when(
				// word_html
				page_exists(lang_code, pagename ? pagename : 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()
			).fail(function (error) {
				if (error === 'http') {
					// jQuery HTTP error
					show_error(new HttpError());
				} else {
					show_error(error);
				}
			}).done(function (word_html, wikitext) {
				show_error(false);
				form.find('.ed-info-msg').empty();
	
				silentFailStorage.setItem('trans-lang', lang_name);
	
				form[0].word.value = '';
				form[0].trans.value = '';
				form[0].tradi.value = '';
				form[0].pagename.value = '';
	
				var added_elem;
				var index = get_translation_table_index(table);
	
				wikitext = translation.add(wikitext, index, word_options);
	
				editor.edit({
					summary: '+' + lang_name + ' : [[' + (pagename ? pagename : word) + ']]',
					summary_type: 'trad',
					wikitext: wikitext,
					redo: function () {
						var translations = self.find('ul > li');
						translation.add_to_list({
							items: translations,
							add_only_item: function () {
								added_elem = $('<li>', { html: '<span class="trad-' + lang_code +
								'">' + lang_name.charAt(0).toUpperCase() + lang_name.substr(1) +
								'</span> : ' + word_html });
								self.find('ul').prepend(added_elem);
							},
							equal_or_before: function (item) {
								var match = /^\s*(.+)/.exec($(item).children().first().text());
								if (match) {
									// conventions internationales en premier
									if (match[1] === 'Conventions internationales' &&
									   lang_name != 'conventions internationales') {
									   	return 'before';
									}
									if (lang_name == 'conventions internationales' &&
									   match[1] != 'Conventions internationales') {
									   	return false;
									}
									// on ignore les {{ébauche-trad}}
									if (match[1] === 'Traductions manquantes.') return false;
									if (sortkey(match[1].toLowerCase()) === sortkey(lang_name)) {
										if ($(item).children('dl').length) { // si la langue
										// a des dialectes à des niveaux différents
										// (en wikicode, avec une ligne commençant par *:),
										// l'ajout de langue n'est pas géré
											mw.notify('Il n\'est pas possible d\'ajouter' +
											' une traduction dans cette langue,' +
											' car le format est inhabituel.');
											throw new Error('Format incorrect : le wikicode' +
											' des traductions dans cette langue ' +
											'est inhabituel et la traduction ne peut' +
											' donc pas être ajoutée par le gadget.');
										}
										return 'equal';
									} else if (sortkey(match[1].toLowerCase()) < sortkey(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: '<span class="trad-' +
								lang_code + '">' + lang_name.charAt(0).toUpperCase() +
								lang_name.substr(1) + '</span> : ' + word_html })
									.insertAfter(item);
							},
							add_before: function (item) {
								added_elem = $('<li>', { html: '<span class="trad-' +
								lang_code + '">' + lang_name.charAt(0).toUpperCase() +
								lang_name.substr(1) + '</span> : ' + word_html });
								added_elem.insertBefore(item);
							},
						});
						added_elem.addClass('ed-added');
						// Si le gadget de création de traductions est activé,
						// on colore directement en bleu les traductions à créer
						// pour éviter d'avoir à recharger la page
						if (window.CrTr_ChangerLiensRouges) window.CrTr_ChangerLiensRouges();
					},
					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
			domain,
			wm_liens = {
				'cmn': 'zh',
				'fra-nor': 'nrm',
				'gsw': 'als',
				'ko-Hani': 'ko',
				'lzh': 'zh-classical',
				'nan': 'zh-min-nan',
				'nb': 'no',
				'nn': 'no',
				'rup': 'roa-rup',
				'yue': 'zh-yue'
			},
			wiktios = [
				'en', 'mg', 'fr', 'zh', 'lt', 'ru', 'es', 'el', 'pl', 'sv', 'ko',
				'nl', 'de', 'tr', 'ku', 'ta', 'io', 'kn', 'fi', 'vi', 'hu', 'pt',
				'chr', 'no', 'ml', 'my', 'id', 'it', 'li', 'ro', 'et', 'ja', 'te',
				'jv', 'fa', 'cs', 'ca', 'ar', 'eu', 'gl', 'lo', 'uk', 'br', 'fj',
				'eo', 'bg', 'hr', 'th', 'oc', 'is', 'vo', 'ps', 'zh-min-nan',
				'simple', 'cy', 'uz', 'scn', 'sr', 'af', 'ast', 'az', 'da', 'sw',
				'fy', 'tl', 'he', 'nn', 'wa', 'ur', 'la', 'sq', 'hy', 'sm', 'sl',
				'ka', 'pnb', 'nah', 'hi', 'tt', 'bs', 'lb', 'lv', 'tk', 'sk', 'hsb',
				'nds', 'kk', 'ky', 'be', 'km', 'mk', 'ga', 'wo', 'ms', 'ang', 'co',
				'sa', 'gn', 'mr', 'csb', 'ug', 'st', 'ia', 'sd', 'sh', 'si', 'mn',
				'tg', 'or', 'kl', 'vec', 'jbo', 'an', 'ln', 'fo', 'zu', 'gu', 'kw',
				'gv', 'rw', 'qu', 'ss', 'ie', 'mt', 'om', 'bn', 'pa', 'roa-rup',
				'iu', 'so', 'am', 'su', 'za', 'gd', 'mi', 'tpi', 'ne', 'yi', 'ti',
				'sg', 'na', 'dv', 'tn', 'ts', 'ha', 'ks', 'ay'
			];
	
		if (wm_liens.hasOwnProperty(lang_code)) {
			domain = wm_liens[lang_code] + '.wiktionary';
		} else if (lang_code == 'conv') {
			domain = 'species.wikimedia';
		} else if ($.inArray(lang_code, wiktios) !== -1) {
			domain = lang_code + '.wiktionary';
		}
			
		// Si le Wiktionnaire n'existe pas, inutile de faire une requête HTTP
		if (!domain) {
			return $.Deferred().resolve('trad--').promise();
		}
		
		// traiter l'apostrophe typographique comme une apostrophe dactylographique
		// dans les liens interwikis, les autres wiktionnaires privilégiant la seconde
		page = page.replace('’', '\'');
		page = page.replace('ʼ', '\'');
		
		var def = $.Deferred();
		$.ajax({
			url: '//' + domain + '.org/w/api.php?origin=' + location.protocol + '//' + location.host,
			data: {
				action: 'query',
				titles: page,
				format: 'json'
			},
			dataType: 'json'
		}).fail(function () {
			def.resolve('trad');
		}).then(function (data) {
			def.resolve(data.query.pages[-1] ? 't-' : 't+');
		});
		return def.promise();
	}
	
	// Ajout pour le Wiktionnaire francophone,
	// adapté de [[MediaWiki:Gadget-CommonWikt.js]], fonction CleDeTri
	function sortkey(word) {
		var key = word.toLowerCase();
		key = key.replace( /[àáâãäå]/g, "a" );
		key = key.replace( /[æ]/g,      "ae" );
		key = key.replace( /[çćċč]/g,   "c" );
		key = key.replace( /[ĉ]/g,      "cx" );
		key = key.replace( /[èéêë]/g,   "e" );
		key = key.replace( /[ĝ]/g,      "gx" );
		key = key.replace( /[ĥ]/g,      "hx" );
		key = key.replace( /[ìíîï]/g,   "i" );
		key = key.replace( /[ĵ]/g,      "jx" );
		key = key.replace( /[ñ]/g,      "n" );
		key = key.replace( /[òóôõö]/g,  "o" );
		key = key.replace( /[œ]/g,      "oe" );
		key = key.replace( /[òóôõö]/g,  "o" );
		key = key.replace( /[ŝ]/g,      "sx" );
		key = key.replace( /[ùúûü]/g,   "u" );
		key = key.replace( /[ŭ]/g,      "ux" );
		key = key.replace( /[ýÿ]/g,     "y" );
		key = key.replace( /['’)(]/g,   "" );
		key = key.replace( /[-\/]/g,    " " );
		return key;
	}
	
	var translation = {
		re_wikitext: /[[\]{}#|=]/,
	
		contains_wikitext: function (str) {
			return translation.re_wikitext.test(str);
		},
	
		re_gloss: /\{\{trans-top\/new(.*)\}\}/g,
	
		re_section: /(\{\{trans-top\/new.*\}\})([\s\S]*?)(\{\{trans-bottom\/new\}\})/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 '{{trans-top/new|' + gloss + '}}';
			}
		},
	
		get_gloss: function (wikitext, index) {
			// s'il y a un commentaire, l'analyse ne va pas être possible
			if (/\{\{trans-top\/new\|.*?(?:<!--|-->)/.test(wikitext)) {
				mw.notify('Le wikitexte contient un commentaire "<!--". ' +
				'La page doit être modifiée manuellement.');
				throw new Error('Le wikitexte contient un commentaire "<!--". ' +
				'La page doit être modifiée manuellement.');
			}
			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('Le ' + (index+1) + '-ème {{trans-top}} n\'a pas été trouvé dans le wikitexte.');
		},
		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) {
			var tpl = [
				opts.exists,
				opts.lang_code,
				opts.pagename ? opts.pagename : opts.word
			];
			opts.gender && tpl.push(opts.gender);
			opts.trans && tpl.push('tr=' + opts.trans);
			opts.tradi && tpl.push('tradi=' + opts.tradi);
			opts.pagename && tpl.push('dif=' + opts.word);
			var res = '{{' + tpl.join('|') + '}}';
			opts.lang_code == 'conv' && (res = "''" + res + "''"); // Conventions internationales en italique
			return res;
		},
		// 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) {
			var match = wikitext.match(translation.re_section);
			if (match === null) {
				mw.notify('Il manque probablement un modèle {{trans-bottom/new}} dans le wikicode.');
				throw new Error('Wikicode incorrect : la page doit être modifiée manuellement');
			}
			if (match[index].indexOf('<!--') !== -1) {
				mw.notify('Le wikitexte contient un commentaire "<!--". La page doit être' +
				          ' modifiée manuellement.');
				throw new Error('Le wikitexte contient un commentaire "<!--". La page doit être' +
				                ' modifiée manuellement.');
			}
			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 '* {{T|' + opts.lang_code + '}} : ' + formatted_word;
					},
					equal_or_before: function (line) {
						// Le regex suivant contient [(:] car on peut avoir un dialecte
						// précisé entre parenthèses après le nom de langue
						var match_T = /^\*\s*\{\{T\|([^}|]+?)(?:\|trier)?\}\}\s*[(:]/.exec(line);
						// Il importe aussi de prendre en compte les cas rares comme les traductions en chinois
						// dans [[arrière-grand-mère]], qui sont indentées à la ligne via *: {{trad|...}}
						var match_trad_indente = /^\*:\s*\{\{trad[+-]{0,2}\|([^}|]+?)\|/.exec(line);
						if (match_T || match_trad_indente) {
							var match = match_T || match_trad_indente;
							var code_langue = match[1];
							// conventions internationales en premier
							if (code_langue === 'conv' && opts.lang_code != 'conv') {
								return 'before';
							}
							if (opts.lang_code == 'conv' && code_langue != 'conv') {
								return false;
							}
							// Le code langue contenu dans le modèle T est peut-être une redirection
							// vers un autre code, auquel cas on le remplace par cet autre code
							// On accède pour cela au champ "redirects" de la liste des langues
							// qui contient toutes les redirections de code langue (à la fin de la liste)
							if (tab_langues[code_langue] === undefined) {
									if (tab_langues.redirects[code_langue]) {
										code_langue = tab_langues.redirects[code_langue];
									} else {
										// sinon, c'est que le code est indéfini
										return false;
									}
							}
							if (sortkey(tab_langues[code_langue]) === sortkey(opts.lang_name)) {
								// si une ligne de traduction finit brutalement par « : »
								// (et ne comporte donc que le nom de langue sans traduction à la suite),
								// c'est probablement qu'on a affaire à une langue
								// avec des dialectes qui seront introduits sur une
								// nouvelle ligne par « *: » → cas à gérer manuellement
								if (/^\*\s*\{\{T\|[^}|]+?(?:\|trier)?\}\}\s*:\s*$/.exec(line)) {
									mw.notify('Il n\'est pas possible d\'ajouter une traduction' +
									          ' dans cette langue, car le format est inhabituel.');
									throw new Error('Format incorrect : le wikicode des traductions' +
									' dans cette langue est inhabituel et la traduction' +
									' ne peut donc pas être ajoutée par le gadget.');
								}
								return 'equal';
							} else if (sortkey(tab_langues[code_langue]) < sortkey(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();
					}
				});
	
				return p1 + '\n' + lines.join('\n') + '\n' + p3;
			});
		}
	};
	
	
	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_name');
	var NoInputError = extend_error('NoInputError', 'input');
	var BadLangNameError = extend_error('BadLangNameError');
	var CommaWordError = extend_error('CommaWordError');
	var CaseWordError = extend_error('CaseWordError');
	var BadFormatError = extend_error('BadFormatError');
	var HttpError = extend_error('HttpError');
	
	// Export some useful components
	window.translation = translation;
	window.parse_wikitext = parse_wikitext;
	window.add_heading_updater = add_heading_updater;
	window.add_translation_form = add_translation_form;
	
	} ( mediaWiki, jQuery ) ); // Fin de la closure ouverte au tout début
}) //close load function