มีเดียวิกิ:Gadget-TranslationAdder.js

จาก วิกิพจนานุกรม พจนานุกรมเสรี

หมายเหตุ: หลังเผยแพร่ คุณอาจต้องล้างแคชเว็บเบราว์เซอร์ของคุณเพื่อดูการเปลี่ยนแปลง

  • ไฟร์ฟอกซ์ / ซาฟารี: กด Shift ค้างขณะคลิก Reload หรือกด Ctrl-F5 หรือ Ctrl-R (⌘-R บนแมค)
  • กูเกิล โครม: กด Ctrl-Shift-R (⌘-Shift-R บนแมค)
  • อินเทอร์เน็ตเอกซ์พลอเรอร์ และ Edge: กด Ctrl ค้างขณะคลิก Refresh หรือกด Ctrl-F5
  • โอเปร่า: กด Ctrl-F5
// <syntaxhighlight lang="javascript">
// <nowiki>
// implicit dependencies : ext.gadget.Editor,ext.gadget.LegacyScriptsNewNode,mediawiki.cookie,ext.gadget.LanguageUtils,mediawiki.util
/* jshint maxerr:1048576, strict:true, undef:true, latedef:true, sub:true */

/* global mw, newNode, importScript, importScriptURI, $, AdderWrapper, ScriptUtils, LangMetadata */

/**
 * Storage of "string" preferences.
 */
function CookiePreferences(context) {
	//Repeated calls with the same context should get the same preferences object.
	if (arguments.callee[context])
		return arguments.callee[context];
	else
		arguments.callee[context] = this;
	
	
	var storage = {};
	var defaults = {};
	
	/**
	 * Change the value of a preference and store into a cookie.
	 */
	this.set = function (name, value) {
		if (value === null || storage[name] === value)
			return;
		storage[name] = value;
		updateCookie();
	};
	
	/**
	 * Get the value of a preference from the cookie or default
	 *
	 * If the preference isn't set, return the second argument or undefined.
	 */
	this.get = function (name, def) {
		return storage[name] || defaults[name] || def;
	};
	
	/**
	 * let the default for get(name) be value for this session
	 */
	this.setDefault = function (name, value) {
		defaults[name] = value;
	};
	
	// Save storage into the cookie, and if available, the local storage.
	function updateCookie() {
		if ('localStorage' in window && window.localStorage) {
			window.localStorage.setItem('TranslationAdderSettings', JSON.stringify(storage));
		}
		
		var value = "";
		
		for (var name in storage) {
			value += '&' + encodeURIComponent(name) + "=" + encodeURIComponent(storage[name]);
		}
		
		mw.cookie.set('preferences' + context, value, {
			expires: 30
		});
	}
	
	// Load storage from the cookie.
	// NOTE: If you wish to update the cookie format, both loading and storing
	// must continue to work for 30 days.
	function updateStorage() {
		var value;
		if ('localStorage' in window && window.localStorage) {
			value = window.localStorage.getItem('TranslationAdderSettings');
			if (value) {
				try {
					var inLocal = JSON.parse(value);
					for (var key in inLocal) {
						if (inLocal.hasOwnProperty(key)) {
							storage[key] = inLocal[key];
						}
					}
					return;
				} catch (e) { }
			}
		}
		
		value = mw.cookie.set('preferences' + context, value, {
			expires: 30
		}) || '';
		var pairs = value.split('&');
		
		for (var i = 1; i < pairs.length; i++) {
			var val = pairs[i].split('=');
			
			if (storage[val[0]] === val[1])
				continue;
			
			storage[val[0]] = val[1];
		}
	}
	
	//__init__
	updateStorage();
	updateCookie(); // update this too
}


var util = {
	
	getVanillaIndexOf: function (str, text, pos) {
		function getIndex(string, regex, pos) {
			var newPos = string.substring(pos).search(regex);
			return newPos === -1 ? -1 : newPos + pos;
		}
		
		if (!pos)
			pos = 0;
		var cpos = 0,
			mpos = 0;
			tpos = 0,
			wpos = 0,
			spos = 0;
		do {
			cpos = text.indexOf('<!--', pos);
			mpos = getIndex(text, /\{\{\s*multitrans\s*\|/, pos);
			tpos = text.indexOf('{{', pos);
			wpos = text.indexOf('<nowiki>', pos);
			spos = text.indexOf(str, pos);
			
			pos = Math.min(
				cpos == -1 ? Infinity : cpos,
				tpos == -1 ? Infinity : tpos,
				wpos == -1 ? Infinity : wpos,
				spos == -1 ? Infinity : spos
			);
			
			if (pos == spos)
				return pos == Infinity ? -1 : pos;
			
			else if (pos == cpos)
				pos = text.indexOf('-->', pos) + 3;
			
			else if (pos == wpos)
				pos = text.indexOf('<\/nowiki>', pos) + 9;
			
			else if (pos == mpos)
				pos = getIndex(text, /\|\s*data\s*=/, pos);
				
			else if (pos == tpos)
				pos = text.indexOf('}}', pos) + 2;
			
			
		} while (pos < Infinity);
		
		return -1;
	},
	
	validateNoWikisyntax: function (field, nonempty) {
		return function (txt, error) {
			if (/[\{\}#]/.test(txt))
				return error("Please don't use wiki markup ({}#) in the " + field + ".");
			if (nonempty && !txt)
				return error("Please specify a " + field + ".");
			return txt;
		};
	},
	
	// pos is a position in the line containing the gloss
	getWikitextGloss: function (txt, pos) {
		var start = txt.lastIndexOf('\n{{trans-top', pos) + 1;
		var end = txt.indexOf('\n', pos);
		var line = txt.substring(start, end);
		var parametersMatch = /\{\{trans-top[^\|\}]*(\|[^\{\}]+)/.exec(line);
		var gloss = "";
		if (parametersMatch) {
			var parameterText = parametersMatch[1];
			var parameterRegex = /\|(?:([^=]*)=([^\|]+)|([^\|]*))/g;
			var parameterMatch;
			var index = 0;
			while ((parameterMatch = parameterRegex.exec(parameterText))) {
				var key = parameterMatch[1],
					value = parameterMatch[2],
					unnamedValue = parameterMatch[3];
				if (key) {
					if (key === "1") {
						gloss = value;
					}
				} else {
					index++;
					if (index === 1) {
						gloss = unnamedValue;
					}
				}
			}
		}
		return gloss;
	},
	
	// get [start_pos, end_pos] of position of wikitext for trans_table containing node in text
	getTransTable: function (text, node, recursive) {
		var gloss = util.getTransGloss(node);
		var pos = 0;
		var transect = [];
		while (pos > -1) {
			pos = util.getVanillaIndexOf('{{trans-top', text, pos + 1);
			if (pos > -1 && util.matchGloss(util.getWikitextGloss(text, pos), gloss)) {
				transect.push(pos);
			}
		}
		if (transect.length > 1) {
			var poss = transect;
			transect = [];
			for (var i = 0; i < poss.length; i++) {
				pos = poss[i];
				if (util.matchGloss(gloss, util.getWikitextGloss(text, pos))) {
					transect.push(pos);
				}
			}
			
			if (transect.length > 1 && !recursive)
				transect = util.tieBreakTransTable(text, transect, node);
		}
		if (transect.length == 1) {
			pos = transect[0];
			pos = util.getVanillaIndexOf("\n", text, pos) + 1;
			var endpos = text.indexOf('{{trans-bottom}}', pos);
			if (endpos > -1 && pos > 0)
				return [pos, endpos];
		}
		
		console.trace("getTransTable failure; text = %o, node = %o, recursive = %o, gloss = %o", text, node, recursive, gloss);
		
		return false;
	},
	
	// try to narrow down the correct poss if multiple matching trans tables
	tieBreakTransTable: function (text, poss, node) {
		if (node.nodeName == 'DIV') {
			while (node && !(node.classList && node.classList.contains('NavFrame')))
				node = node.parentNode;
			
			var nodes = node.getElementsByTagName('table');
			if (!nodes.length)
				return poss;
			
			node = nodes[0];
		} else {
			while (node && node.nodeName != 'TABLE')
				node = node.parentNode;
		}
		
		var before_count = 0;
		var after_count = 0;
		var is_found = false;
		$("table.translations").each(function () {
			var gloss = util.getTransGloss(this);
			if (gloss == "คำแปลที่ต้องการตรวจสอบ")
				return;
			
			if (this == node) {
				is_found = true;
				return;
			}
			
			var pos = util.getTransTable(text, this, true);
			
			if (pos) {
				for (var j = 0; j < poss.length; j++) {
					if (poss[j] == pos)
						return util.tieBreakTransTable(poss.splice(j, 1), node);
				}
			} else {
				var matched = 0;
				for (var j = 0; j < poss.length; j++) {
					if (util.matchGloss(util.getWikitextGloss(text, poss[j]), gloss) &&
						util.matchGloss(gloss, util.getWikitextGloss(text, poss[j]))) {
						matched++;
					}
				}
				if (matched == poss.length) {
					if (is_found)
						after_count++;
					else
						before_count++;
				}
			}
		});
		
		if (before_count + 1 + after_count == poss.length)
			return [poss[before_count]];
		else
			return poss;
	},
	
	matchGloss: function (line, gloss) {
		if (gloss.match(/^ *$/))
			return !!(line.match(/\{\{trans-top\|? *\}\}/) || line.match(/^ *$/));
		
		var words = gloss.split(/\W+/);
		var pos = 0;
		for (var i = 0; i < words.length; i++) {
			pos = line.indexOf(words[i], pos);
			if (pos == -1)
				return false;
		}
		return pos > -1;
	},
	
	// If the translationNode is a NavHead, this goes up to the NavFrame and then
	// down to the translation table. If the translationNode is the translation
	// table or is inside the translation table, it goes up till it finds the
	// translation table. Then it retrieves the data-gloss="..." attribute
	// that is provided by {{trans-top}} and the other translation header
	// templates.
	// This function returns a gloss after it has been transformed by the parser
	// into HTML. So the gloss will not match the gloss in the wikitext if the
	// gloss in the wikitext contains templates, for instance.
	getTransGloss: function(translationNode) {
		var node = translationNode;
		if (translationNode.nodeType === Node.ELEMENT_NODE
				&& translationNode.nodeName === 'DIV'
				&& translationNode.classList.contains('NavHead')) {
			var navFrame = translationNode.parentNode;
			if (!navFrame.classList.contains('NavFrame')) {
				console.trace('No NavFrame parent of %o', translationNode);
				return '';
			}
			node = navFrame.getElementsByClassName('translations')[0];
			if (!node) {
				console.trace('No child of parent of %o with "translations" class', translationNode);
				return '';
			}
		}
		while (true) {
			if (node.nodeType === Node.ELEMENT_NODE
					&& node.nodeName === 'TABLE'
					&& node.classList.contains('translations')) {
				return node.dataset.gloss;
			}
			node = node.parentNode;
			if (!node || node.id === 'mw-parser-output') break;
		}
		
		console.trace('Found no gloss in <table class="translations" data-gloss="..."> at or above %o', translationNode);
		return '';
	},
	
	isTrreq: function (li) {
		var span = li.getElementsByTagName('span')[0];
		return (span && span.classList.contains("trreq"));
	},
	
	genderList: {
		"s": "เอกพจน์(s)", "d": "ทวิพจน์(d)", "p": "พหูพจน์(p)",
		"m": "เพศชาย(m)", "m-d": "เพศชายทวิพจน์(m-d)", "m-p": "เพศชายพหูพจน์(m-p)",
		"f": "เพศหญิง(f)", "f-d": "เพศหญิงทวิพจน์(f-d)", "f-p": "เพศหญิงพหูพจน์(f-p)",
		"c": "เพศรวม(c)", "c-d": "เพศรวมทวิพจน์(c-d)", "c-p": "เพศรวมพหูพจน์(c-p)",
		"n": "เพศกลาง(n)", "n-d": "เพศกลางทวิพจน์(n-d)", "n-p": "เพศกลางพหูพจน์(n-p)",
		"impf": "การณ์ลักษณะไม่สมบูรณ์(impf)", "pf": "การณ์ลักษณะสมบูรณ์(pf)"
	}
};

// An adder for translations on en.wikt
function TranslationAdders(editor) {
	
	function TranslationLabeller(insertDiv) {
		var original_span;
		var adder_form;
		var initial_value;
		var edit_button;
		
		var editing = false;
		
		var adderInterface = {
			'fields': {
				'gloss': function (txt, error) {
					return util.validateNoWikisyntax('gloss', true)(txt, error);
				}
			},
			'createForm': function () {
				var thisId = "a" + String(Math.random()).replace(".", "");
				adder_form = newNode('form', {
						style: 'display: inline',
						width: 'auto',
						click: kill_event
					},
					newNode('label', {
						'for': thisId
					}, "Gloss: "),
					newNode('input', {
						type: 'text',
						name: 'gloss',
						value: initial_value,
						style: 'width: 50%',
						title: 'ใส่คำอธิบายอย่างย่อของนิยามที่เกี่ยวข้อง',
						id: thisId
					}),
					newNode('input', {
						type: 'submit',
						name: 'preview',
						value: 'Preview'
					}),
					newNode('a', {
						href: '/wiki/Help:Glosses'
					}, 'Help?!')
				);
				return adder_form;
			},
			'onsubmit': function (values, render) {
				render(values.gloss, function (new_html) {
					if (editing)
						toggle_editing(false);
					
					var old_html = original_span.innerHTML;
					editor.addEdit({
						'undo': function () {
							original_span.innerHTML = old_html;
							if (!editing) toggle_editing();
						},
						'redo': function () {
							original_span.innerHTML = new_html;
							if (editing) toggle_editing();
						},
						'edit': function (text) {
							return perform_edit(text, values.gloss);
						},
						'summary': 'tgloss:"' + (values.gloss.length > 50 ? values.gloss.substr(0, 50) + '...' : values.gloss + '"')
					}, original_span);
				});
			}
		};
		
		// The actual modification to the wikitext
		function perform_edit(wikitext, gloss) {
			var pos = util.getTransTable(wikitext, insertDiv)[0] - 4;
			var g_start = wikitext.lastIndexOf('\n{{trans-top', pos) + 1;
			var g_end = wikitext.indexOf('}}\n', pos) + 2;
			
			if (g_start === 0 || wikitext.substr(g_start, g_end - g_start).indexOf("\n") > -1) {
				editor.error("ไม่พบตารางคำแปล");
				return wikitext;
			} else {
				return wikitext.substr(0, g_start) + '{{trans-top|' + gloss + '}}' + wikitext.substr(g_end);
			}
		}
		
		// Don't open and close box when interacting with form.
		function kill_event(e) {
			e.stopPropagation();
		}
		
		// What to do when the +/- button is clicked.
		function toggle_editing() {
			if (editing) {
				adder_form.style.display = "none";
				original_span.style.display = "inline";
				editing = false;
				return;
			}
			editing = true;
			edit_button.text("กำลังโหลด...");
			editor.withCurrentText(function (currentText) {
				var pos = util.getTransTable(currentText, insertDiv);
				edit_button.text('±');
				
				if (!pos)
					return editor.error("ไม่พบตารางคำแปล");
				
				var gloss_line = currentText.substr(currentText.lastIndexOf('\n', pos[0] - 2) + 1);
				gloss_line = gloss_line.substr(0, gloss_line.indexOf('\n'));
				initial_value = gloss_line.replace(/^\{\{trans-top(\|(.*)|)\}\}\s*$/, "$2");
				
				if (initial_value.indexOf("\n") > 0)
					return editor.error("Internal error: guess spanning multiple lines");
				
				if (!original_span) {
					original_span = newNode('span', {
						'class': 'wt-edit-recurse'
					});
					for (var i = 0; i < insertDiv.childNodes.length; i++) {
						var child = insertDiv.childNodes[i];
						if (child != edit_button[0] && child.className !== 'NavToggle') {
							original_span.appendChild(insertDiv.removeChild(child));
							i--;
						}
					}
					insertDiv.appendChild(original_span);
					
					new AdderWrapper(editor, adderInterface, insertDiv, original_span);
				}
				original_span.style.display = "none";
				adder_form.style.display = "inline";
				adder_form.getElementsByTagName('input')[0].focus();
			});
		}
		
		edit_button = $('<a href="#">±</a>')
		//.addClass("translation-gloss-edit-button")
			.attr("title", "Edit table heading").css("padding", "2px").css("margin-left", "-5px")
			.on("click", function (e) {
				if (e && e.preventDefault)
					e.preventDefault();
				kill_event(e);
				toggle_editing();
				return false;
			});
		
		$(insertDiv).prepend(edit_button); //XXX: this places anchor before NavToggle which may not be a good idea
	}
	
	function TranslationAdder(insertUl) {
		// Hippietrail
		var langmetadata = new LangMetadata();
		
		this.fields = {
			lang: function (txt, error) {
				if (txt == 'th')
					return error("กรุณาเลือกภาษาที่ไม่ใช่ภาษาไทย (en, fr, de, es, ฯลฯ)");
				if (txt == 'nds')
					return error("กรุณาใช้รหัส nds-de สำหรับ German Low German หรือ nds-nl สำหรับ Dutch Low Saxon");
				
				if (!/^[a-z]{2,3}(?:-[a-z]{2,3}){0,2}$/.test(txt))
					return error("กรุณาใช้รหัสภาษา (en, fr, de, es, ฯลฯ)");
				
				return txt;
			},
			word: function (txt, error) {
				if (txt == '{{trreq}}')
					return '{{t-needed}}';
				if (txt == '{{t-needed}}')
					return txt;
				
				if (txt.indexOf(',') == -1 || forceComma) {
					forceComma = false;
					if (langmetadata.expectedCase(thiz.elements.lang.value, mw.config.get('wgTitle'), txt) || forceCase) {
						forceCase = false;
						return util.validateNoWikisyntax('translation', true)(txt, error);
					}
					
					if (prefs.get('case-knowledge', 'none') == 'none') {
						return error(newNode('span',
							"คำแปลโดยปกติจะไม่มีอักษรตัวใหญ่ ถ้าคุณแน่ใจว่ามันมี คุณสามารถ ",
							newNode('span', {
								style: "color: blue; text-decoration: underline; cursor: pointer;",
								click: function () {
									forceCase = true;
									inputForm.onsubmit();
									prefs.set('case-knowledge', 'guru');
								}
							}, "ทำต่อโดยกดปุ่มนี้")));
					} else {
						var msg = newNode('span',
							newNode('span', {
								style: "color: blue; text-decoration: underline; cursor: pointer;",
								click: function () {
									prefs.set('case-knowledge', 'none');
									try {
										msg.parentNode.removeChild(msg);
									} catch (e) {
									}
									editor.undo();
								}
							}, "กรุณากดเลิกทำ"), " หากคุณไม่แน่ใจว่าคำแปลนี้มีอักษรตัวใหญ่");
						
						error(msg);
						return txt;
					}
				}
				
				if (prefs.get('comma-knowledge', 'none') == 'none') {
					return error(newNode('span',
						"คุณสามารถเพิ่มคำแปลได้ครั้งละหนึ่งคำ ถ้าคำแปลนี้มีเครื่องหมายจุลภาค คุณสามารถ ",
						newNode('span', {
							style: "color: blue; text-decoration: underline; cursor: pointer;",
							click: function () {
								forceComma = true;
								inputForm.onsubmit();
								prefs.set('comma-knowledge', 'guru');
							}
						}, "เพิ่มโดยกดปุ่มนี้")));
				} else {
					var msg = newNode('span',
						newNode('span', {
							style: "color: blue; text-decoration: underline; cursor: pointer;",
							click: function () {
								prefs.set('comma-knowledge', 'none');
								try {
									msg.parentNode.removeChild(msg);
								} catch (e) {
								}
								editor.undo();
							}
						}, "กรุณากดเลิกทำ"), " หากคุณกำลังลองสร้างรายการคำแปลในคราวเดียว การกระทำนี้ยังไม่รองรับ");
					
					error(msg);
					return txt;
				}
			},
			qual: util.validateNoWikisyntax('qualifier'),
			tr: util.validateNoWikisyntax('transcription'),
			lit: util.validateNoWikisyntax('literal'),
			rawPageName: util.validateNoWikisyntax('raw page name'),
			sc: function (txt, error) {
				if (txt && !/^((?:[a-z][a-z][a-z]?-)?[A-Z][a-z][a-z][a-z]|polytonic|Latinx)$/.test(txt))
					return error(newNode('span', "กรุณาใช้", newNode('a', {
						href: '/wiki/วิกิพจนานุกรม:รายชื่ออักษร'
					}, "รหัสอักษรที่ถูกต้อง"), "(เช่น fa-Arab, Deva, polytonic)"));
				
				var scripts = (new ScriptUtils()).GetScriptsByLangCode(thiz.elements.lang.value);
				if (txt && scripts.length > 0 && scripts.indexOf(txt) == -1) {
					var plural = scripts.length > 1;
					return error(newNode('span',
						"กรุณาใช้รหัสอักษรที่ถูกต้อง รหัสอักษรที่สามารถใช้ได้กับภาษานี้ได้แก่: " + scripts.join(",")));
				}
				
				if (!txt)
					txt = prefs.get('script-' + thiz.elements.lang.value, langmetadata.guessScript(thiz.elements.lang.value) || '');
				if (txt == 'Latn')
					txt = '';
				return txt;
			},
			nested: util.validateNoWikisyntax('nested'),
			"s": 'checkbox',
			"d": 'checkbox',
			"p": 'checkbox',
			"m": 'checkbox',
			"m-d": 'checkbox',
			"m-p": 'checkbox',
			"f": 'checkbox',
			"f-d": 'checkbox',
			"f-p": 'checkbox',
			"c": 'checkbox',
			"c-d": 'checkbox',
			"c-p": 'checkbox',
			"n": 'checkbox',
			"n-d": 'checkbox',
			"n-p": 'checkbox',
			"impf": 'checkbox',
			"pf": 'checkbox',
			nclass1: util.validateNoWikisyntax('noun class'),
			nclass2: util.validateNoWikisyntax('plural class')
		};
		
		this.createForm = function () {
			function createGenderNode(name, text) {
				var inp = document.createElement("input");
				inp.type = "checkbox";
				inp.name = name;
				var lbl = document.createElement("label");
				lbl.appendChild(inp);
				lbl.appendChild(document.createTextNode(text));
				return lbl;
			}
			
			var genderControls = {};
			for (var g in util.genderList) genderControls[g] = createGenderNode(g, util.genderList[g]);
			
			var controls = {
				lang: newNode('input', {
					size: 4,
					type: 'text',
					name: 'lang',
					value: prefs.get('curlang', ''),
					title: 'รหัสภาษา ISO 639 สองหรือสามตัวอักษร'
				}),
				transliteration: newNode('p', newNode('a', {
						href: '/wiki/วิกิพจนานุกรม:การถอดอักษร'
					}, "การถอดอักษร"), ": ",
					newNode('input', {
						name: "tr",
						title: "คำที่ถอดออกมาเป็นอักษรไทย (หรืออักษรละติน)"
					}), " (เช่น อะริงะโต สำหรับ ありがとう)"),
				literal: newNode('p', "คำแปลตรงตัว: ",
					newNode('input', {
						name: "lit",
						title: "คำแปลตรงตัวของศัพท์นี้"
					})),
				qualifier: newNode('p', "ตัวบ่ง: ", newNode('input', {
					name: 'qual',
					title: "ตัวบ่งคุณลักษณะเพื่ออธิบายบริบทของคำ"
				}), " (เช่น แปลตามตัวอักษร, ทางการ, สแลง)"),
				display: newNode('p', "ชื่อหน้าดิบ: ", newNode('input', {
					name: 'rawPageName',
					title: "ลิงก์ไปยังคำหลัก ถ้าคำแปลไม่ใช่คำหลัก"
				}), " (เช่น amo สำหรับ amō)"),
				script: newNode('p', newNode('a', {
						href: '/wiki/วิกิพจนานุกรม:รายชื่ออักษร'
					}, "รหัสอักษร"), ": ",
					newNode('input', {
						name: 'sc',
						size: 6,
						title: "รหัสอักษรที่จะใช้กับคำนี้"
					}), "(เช่น Cyrl สำหรับซีริลลิก, Latn สำหรับละติน)", newNode('br')),
				nested: newNode('p', "แขนง: ", newNode('input', {
					name: 'nested',
					title: "แขนงของภาษานี้"
				}), " (เช่น นอร์เวย์/นือนอสก์)"),
				
				// Genders
				genders: genderControls,
				
				// Noun class
				nclass: newNode('p',
					"คลาสคำนาม: ", newNode('input', {
						type: 'text',
						size: 4,
						name: 'nclass1',
						title: "The noun class of the word in the singular or citation form."
					}),
					" คลาสพหูพจน์: ", newNode('input', {
						type: 'text',
						size: 4,
						name: 'nclass2',
						title: "The noun class of the word in the plural form, if any."
					}))
			};
			
			function createGenderDiv(genderz) {
				var $div = $("<div>");
				for (var i = 1; i < arguments.length; i++) $div.append(genderz[arguments[i]]);
				return $div;
			}
			
			var $mdiv = createGenderDiv(controls.genders, "m", "m-d", "m-p");
			var $fdiv = createGenderDiv(controls.genders, "f", "f-d", "f-p");
			var $cdiv = createGenderDiv(controls.genders, "c", "c-d", "c-p");
			var $ndiv = createGenderDiv(controls.genders, "n", "n-d", "n-p");
			var $sdpdiv = createGenderDiv(controls.genders, "s", "d", "p");
			var $pfdiv = createGenderDiv(controls.genders, "impf", "pf");
			controls.gender = $("<p>").append($mdiv).append($fdiv).append($cdiv).append($ndiv).append($sdpdiv).append($pfdiv)[0];
			langInput = controls.lang;
			
			var showButton = newNode('span', {
				'click': function () {
					if (!advancedMode) {
						advancedMode = true;
						showButton.innerHTML = " Less";
					} else {
						advancedMode = false;
						showButton.innerHTML = " More";
					}
					updateScriptGuess.call(langInput, true);
				},
				'style': "color: #0000FF;cursor: pointer;"
			}, advancedMode ? " Less" : " More");
			
			function autoTransliterate() {
				var rawPageName = langmetadata.computeRawPageName(thiz.elements.lang.value, thiz.elements.word.value);
				if (rawPageName && rawPageName !== thiz.elements.word.value)
					thiz.elements.rawPageName.value = rawPageName;
			}
			
			function updateScriptGuess(preserve) {
				preserve = (preserve === true);
				
				//show all arguments
				function show() {
					for (var i = 0; i < arguments.length; i++) {
						if (arguments[i].nodeName == 'P')
							arguments[i].style.display = "block";
						else
							arguments[i].style.display = "inline";
					}
					
				}
				
				//hide all arguments
				function hide() {
					for (var i = 0; i < arguments.length; i++)
						arguments[i].style.display = "none";
				}
				
				//if the first argument is false hide the remaining arguments, otherwise show them.
				function toggle(condition) {
					if (condition) //eww...
						show.apply(this, [].splice.call(arguments, 1, arguments.length - 1));
					else
						hide.apply(this, [].splice.call(arguments, 1, arguments.length - 1));
				}
				
				if (!preserve)
					langInput.value = langmetadata.cleanLangCode(langInput.value);
				
				var guess = prefs.get('script-' + langInput.value, langmetadata.guessScript(langInput.value || ''));
				if (!preserve) {
					if (guess)
						thiz.elements.sc.value = guess;
					else
						thiz.elements.sc.value = '';
					
					thiz.elements.nested.value = langmetadata.getNested(langInput.value || '');
					
					autoTransliterate();
				}
				
				var lang = langInput.value;
				
				if (!advancedMode) {
					var g = langmetadata.getGenders(lang);
					
					if (!lang) {
						hide(controls.gender);
					}
					else if (g === undefined) {
						hide(controls.gender);
					}
					else {
						for (var genderName in util.genderList) {
							toggle(g.indexOf(genderName) != -1, controls.genders[genderName]);
						}
					}
					
					toggle(g, controls.gender);
					
					toggle(langmetadata.hasNounClasses(lang), controls.nclass);
					
					var hasAutomaticTransliteration = new window.LanguageUtils().HasAutomaticTransliteration(lang);
					toggle(guess && guess != 'Latn' && !hasAutomaticTransliteration, controls.transliteration);
					
					toggle(langmetadata.needsRawPageName(lang), controls.display);
					
					hide(controls.literal, controls.qualifier, controls.nested); //only in more
					
					//should be hidden if the language has only one script
					toggle(langmetadata.getScripts(lang).length != 1, controls.script);
				} else {
					show(
						controls.gender,
						controls.genders['s'], controls.genders['d'], controls.genders['p'],
						controls.genders['m'], controls.genders['m-d'], controls.genders['m-p'],
						controls.genders['f'], controls.genders['f-d'], controls.genders['f-p'],
						controls.genders['c'], controls.genders['c-d'], controls.genders['c-p'],
						controls.genders['n'], controls.genders['n-d'], controls.genders['n-p'],
						controls.genders['impf'], controls.genders['pf'],
						controls.nclass, controls.transliteration, controls.literal, controls.qualifier, controls.display,
						controls.script, controls.nested);
				}
			}
			
			//autocomplete language names
			function langAutoFill(e) {
				e = (e || event).keyCode;
				if ((e >= 33 && e <= 40) || e == 8 || e == 46 || e == 27 || e == 16) {
					return;
				}
				var t = this,
					langPrefix = t.value;
				if (langPrefix.substr(0, 1) != langPrefix.substr(0, 1).toUpperCase()) {
					return;
				}
				new mw.Api().get({
					"action": "expandtemplates",
					"format": "json",
					"text": "{{#invoke:languages/javascript-interface|GetSingleLanguageByLanguagePrefix|" + langPrefix + "}}",
					"prop": "wikitext"
				}).done(function (r) {
					if (r.expandtemplates && r.expandtemplates.wikitext !== "") {
						var langcode = r.expandtemplates.wikitext.split(":")[0];
						var langname = r.expandtemplates.wikitext.split(":")[1];
						var oldLength = t.value.length;
						t.value = langname;
						$(t).data("langcode", langcode);
						if (t.setSelectionRange) {
							t.setSelectionRange(oldLength, langname.length);
						} else if (t.createTextRange) {
							var z = t.createTextRange();
							z.moveEnd('character', 0 - z.move('character', [t.value.length, t.value = langname][0]) + t.value.length);
							z.select();
						}
					} else {
						$(t).removeData("langcode");
					}
				});
			}
			
			langInput.onkeyup = langAutoFill;
			langInput.onblur = function () {
				if ($(this).data("langcode"))
					$(this).val($(this).data("langcode"));
				updateScriptGuess.call(langInput);
			};
			
			window.setTimeout(function () {
				updateScriptGuess.call(langInput);
			}, 0);
			
			inputForm = newNode('form',
				newNode('p', newNode('a', {
						href: "/wiki/User_talk:Conrad.Irwin/editor.js#Usage"
					}, "เพิ่มคำแปล"), ' ',
					langInput, newNode('b', ': '), newNode('input', {
						'name': 'word',
						size: 20,
						keyup: autoTransliterate,
						change: autoTransliterate
					}),
					newNode('input', {
						'type': 'submit',
						'value': 'แสดงตัวอย่างคำแปล'
					}), showButton
				),
				controls.gender,
				controls.nclass,
				controls.transliteration,
				controls.literal,
				controls.display,
				controls.qualifier,
				controls.script,
				controls.nested
			);
			return inputForm;
		};
		
		this.onsubmit = function (values, render) {
			var wikitext;
			
			function callRender() {
				render(wikitext, function (html) {
					registerEdits(values, wikitext, html);
				});
			}
			
			var languagePrefix = '{{subst:#invoke:languages/templates|getByCode|' + values.lang + '|getCanonicalName}}: ';
			if (values.word.indexOf('{{t-needed') === 0) {
				wikitext = languagePrefix + ' {{t-needed|' + values.lang + '}}';
				callRender();
			} else {
				langmetadata.retrieveRawPageName(values.lang, values.word, values.rawPageName, function (rawPageName, needsRawPageName) {
					if (needsRawPageName) {
						values.rawPageName = rawPageName;
						values.wordMinus = '';
					} else {
						values.rawPageName = '';
						values.wordMinus = rawPageName;
					}
					langmetadata.hasWiktionaryWithEntry(values.lang, values.rawPageName || values.wordMinus, function (hasInterwiki) {
						var templateBody = '|' + values.lang + '|' +
							langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) +
							(values.nclass1 ? '|c' + values.nclass1 : '') +
							(values.nclass2 ? '|c' + values.nclass2 : '');
						
						// Add gender codes
						templateBody += [
							's', 'd', 'p', 'm', 'm-d', 'm-p', 'f', 'f-d', 'f-p',
							'c', 'c-d', 'c-p', 'n', 'n-d', 'n-p', 'impf', 'pf'
						].map(function(k) {
							return values[k] ? '|' + k : '';
						}).join("");
						
						templateBody +=
							(values.tr ? '|tr=' + values.tr : '') +
							(values.lit ? '|lit=' + values.lit : '') +
							(values.rawPageName ? '|alt=' + values.word : '');
							/* (values.sc ? '|sc=' + values.sc  : '') */
						var qualifier = values.qual ? ' {{qualifier|' + values.qual + '}}' : '';
						
						wikitext = languagePrefix + "{{" + (hasInterwiki ? "t+" : "t") + templateBody + "}}" + qualifier;
						callRender();
					});
				});
			}
		};
		
		var thiz = this;
		var prefs = new CookiePreferences('EditorJs');
		var langInput;
		var inputForm;
		var advancedMode = prefs.get('more-display', 'none') != 'none';
		var forceComma = false;
		var forceCase = false;
		
		//Reset elements to default values.
		function resetElements() {
			if (prefs.get('more-display', 'none') != advancedMode ? 'block' : 'none')
				prefs.set('more-display', advancedMode ? 'block' : 'none'); //named for compatibility
			thiz.elements.word.value = thiz.elements.nclass1.value = thiz.elements.nclass2.value = thiz.elements.lit.value = thiz.elements.tr.value = thiz.elements.rawPageName.value = thiz.elements.qual.value = '';
			thiz.elements['s'].checked = false;
			thiz.elements['d'].checked = false;
			thiz.elements['p'].checked = false;
			thiz.elements['m'].checked = false;
			thiz.elements['m-d'].checked = false;
			thiz.elements['m-p'].checked = false;
			thiz.elements['f'].checked = false;
			thiz.elements['f-d'].checked = false;
			thiz.elements['f-p'].checked = false;
			thiz.elements['c'].checked = false;
			thiz.elements['c-d'].checked = false;
			thiz.elements['c-p'].checked = false;
			thiz.elements['n'].checked = false;
			thiz.elements['n-d'].checked = false;
			thiz.elements['n-p'].checked = false;
			thiz.elements['impf'].checked = false;
			thiz.elements['pf'].checked = false;
			prefs.set('curlang', thiz.elements.lang.value);
			if ((thiz.elements.sc.value || 'Latn') != (prefs.get('script-' + thiz.elements.lang.value, langmetadata.guessScript(thiz.elements.lang.value) || 'Latn'))) {
				prefs.set('script-' + thiz.elements.lang.value, thiz.elements.sc.value);
				// Uncaught TypeError: Object #<HTMLInputElement> has no method 'update'
				thiz.elements.lang.update();
			}
		}
		
		// This is onsubmit after the wikitext has been rendered to give content
		function registerEdits(values, wikitext, content) {
			var li = newNode('li', {
				'class': 'trans-' + values.lang
			});
			li.innerHTML = content;
			var lang = getLangName(li);
			var x = langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word);
			var resp = "[[" + x + "]]";
			if (x.indexOf("{{") < 0) {
				new mw.Api().get({
					"action": "expandtemplates",
					"format": "json",
					"text": "{{ll|" + values.lang + "|" + x + "}}",
					"prop": "wikitext"
				}, {"async": false}).done(function (r) {
					if (r.expandtemplates && r.expandtemplates.wikitext) {
						// [[page#Language|display]]
						// resp = r.expandtemplates.wikitext;
						// [[page|display]]
						// resp = r.expandtemplates.wikitext.replace(/#[^|]*/, "");
						// [[page]]
						resp = r.expandtemplates.wikitext.replace(/#[^\]]*/, "");
					}
				});
			}
			var summary = 't+' + values.lang + ':' + resp;
			
			var insertBefore = null;
			var nextLanguage = null;
			
			var nestedHeading, nestedLang;
			var nested = values.nested.split('/');
			if (nested.length > 1) {
				nestedHeading = nested[0];
				nestedLang = nested[1];
				if (nestedHeading === '')
					nestedHeading = lang;
				content = content.replace(/.*: /, nestedLang + ": ");
				wikitext = wikitext.replace("subst:", "").replace(/.*: /, nestedLang + ": ");
			} else {
				nestedHeading = values.nested;
				nestedLang = lang;
			}
			var nestedWikitext = "\n* " + nestedHeading + ":\n*: " + wikitext;
			
			function addEdit(edit, span) {
				var fields = ["lang", "word", "nclass1", "nclass2", "rawPageName", "qual", "tr", "lit", "sc"];
				editor.addEdit({
					'undo': function () {
						edit.undo();
						if (thiz.elements.word.value === "" &&
							thiz.elements.nclass1.value === "" &&
							thiz.elements.nclass2.value === "" &&
							thiz.elements.tr.value === "" &&
							thiz.elements.lit.value === "" &&
							thiz.elements.rawPageName.value === "" &&
							thiz.elements.qual.value === "") {
							for (var i = 0; i < fields.length; i++) {
								thiz.elements[fields[i]].value = values[fields[i]];
							}
							
							for (var genderName in util.genderList) {
								thiz.elements[genderName].checked = values[genderName];
							}
						}
					},
					'redo': function () {
						edit.redo();
						for (var i = 0; i < fields.length; i++) {
							if (thiz.elements[fields[i]].value != values[fields[i]])
								return;
						}
						resetElements();
					},
					'edit': edit.edit,
					'summary': summary
				}, span);
			}
			
			
			if (lang) {
				//Get all li's in this table row.
				var lss = $(insertUl).parent().parent().find('li').get();
				var dds = $(insertUl).parent().parent().find('dd').get();
				var lis = lss.concat(dds);
				
				for (var j = 0; j < lis.length; j++) {
					if (lis[j].getElementsByTagName('form').length > 0)
						continue;
					var ln = getLangName(lis[j]);
					if (ln == lang) {
						if (values.word == '{{t-needed}}') {
							addEdit({
								'redo': function () {
								},
								'undo': function () {
								},
								'edit': function () {
									return editor.error("Can not add a translation request for a language with translations");
								}
							});
							return resetElements();
						}
						
						var span = newNode('span');
						var parent = lis[j];
						if (util.isTrreq(parent)) {
							span.innerHTML = content.substr(content.indexOf(':') + 1);
							var trspan = parent.getElementsByTagName('span')[0];
							addEdit({
								'redo': function () {
									parent.removeChild(trspan);
									parent.appendChild(span);
								},
								'undo': function () {
									parent.removeChild(span);
									parent.appendChild(trspan);
								},
								'edit': getEditFunction(values, wikitext, ln, values.lang, true, function (text, ipos) {
									//Converting a Translation request into a translation
									var lineend = text.indexOf('\n', ipos);
									return text.substr(0, ipos) + wikitext + text.substr(lineend);
								})
							}, span);
							return resetElements();
						} else {
							if (parent.getElementsByTagName('ul').length + parent.getElementsByTagName('dl').length == 0) {
								span.innerHTML = ", " + content.substr(content.indexOf(':') + 1);
								addEdit({
									'redo': function () {
										parent.appendChild(span)
									},
									'undo': function () {
										parent.removeChild(span)
									},
									'edit': getEditFunction(values, wikitext, ln, values.lang, false, function (text, ipos) {
										//We are adding the wikitext to a list of translations that already exists.
										var lineend = text.indexOf('\n', ipos);
										var wt = wikitext.replace('subst:#invoke:', '');
										wt = wt.substr(wt.indexOf(':') + 1);
										return text.substr(0, lineend) + "," + wt + text.substr(lineend);
									})
								}, span);
								return resetElements();
							} else {
								var node = parent.firstChild;
								var hastrans = false;
								while (node) {
									if (node.nodeType == 1) {
										var nn = node.nodeName;
										if (nn == 'UL' || nn == 'DL') {
											// If we want to use the dialectical nesting for orthographical nesting
											// then we need to skip this (otherwise perfect) match.
											if (!hastrans && nestedHeading == ln) {
												node = node.nextSibling;
												continue;
											}
											span.innerHTML = (hastrans ? ", " : " ") + content.substr(content.indexOf(':') + 1);
											addEdit({
												'redo': function () {
													parent.insertBefore(span, node)
												},
												'undo': function () {
													parent.removeChild(span)
												},
												'edit': getEditFunction(values, wikitext, ln, values.lang, false, function (text, ipos) {
													//Adding the translation to a language that has nested translations under it
													var lineend = text.indexOf('\n', ipos);
													var wt = wikitext.replace('subst:#invoke:', '');
													wt = wt.substr(wt.indexOf(':') + 1);
													return text.substr(0, lineend) + (hastrans ? "," : "") + wt + text.substr(lineend);
												})
											}, span);
											return resetElements();
										} else {
											hastrans = true;
										}
										
									}
									node = node.nextSibling;
								}
							}
						}
					} else if (ln && ln > lang && (!nextLanguage || ln < nextLanguage) && lis[j].parentNode.parentNode.nodeName != 'LI') {
						nextLanguage = ln;
						var parent = lis[j];
						insertBefore = [{
							'redo': function () {
								parent.parentNode.insertBefore(li, parent);
							},
							'undo': function () {
								parent.parentNode.removeChild(li)
							},
							'edit': getEditFunction(values, wikitext, ln, getLangCode(parent), util.isTrreq(parent), function (text, ipos) {
								//Adding a new language's translation before another language's translation
								var lineend = text.lastIndexOf('\n', ipos);
								return text.substr(0, lineend) + "\n* " + wikitext + text.substr(lineend);
							})
						}, li];
					}
				}
			}
			if (values.nested) {
				nextLanguage = null;
				insertBefore = null;
				li.innerHTML = nestedHeading + ":" + "<dl><dd class=\"trans-" + values.lang + "\">" + content + "</dd></dl>";
				
				var lis = insertUl.parentNode.parentNode.getElementsByTagName('li');
				for (var j = 0; j < lis.length; j++) {
					//Ignore the editor form
					if (lis[j].getElementsByTagName('form').length > 0)
						continue;
					
					//Don't look at nested translations
					if (lis[j].parentNode.parentNode.nodeName != 'TD')
						continue;
					
					var ln = getLangName(lis[j]);
					if (ln == nestedHeading) {
						var sublis = lis[j].getElementsByTagName('li');
						
						if (!sublis.length)
							sublis = lis[j].getElementsByTagName('dd');
						
						if (sublis.length == 0) {
							var parent = lis[j];
							var dd = newNode('dd', {
								'class': 'trans-' + values.lang
							});
							var dl = newNode('dl', dd);
							dd.innerHTML = content;
							
							addEdit({
								'redo': function () {
									parent.appendChild(dl);
								},
								'undo': function () {
									parent.removeChild(dl);
								},
								'edit': getEditFunction(values, wikitext, nestedHeading, getLangCode(parent), util.isTrreq(parent), function (text, ipos) {
									//Adding a new dl to an existing translation line
									var lineend = text.indexOf('\n', ipos);
									return text.substr(0, lineend) + "\n*: " + wikitext + text.substr(lineend);
								})
							}, dd);
							return resetElements();
						} else {
							var dd = newNode(sublis[0].nodeName, {
								'class': 'trans-' + values.lang
							});
							var linestart = dd.nodeName == 'DD' ? '\n*: ' : '\n** ';
							dd.innerHTML = content;
							for (var k = 0; k < sublis.length; k++) {
								var subln = getLangName(sublis[k]);
								var parent = sublis[k];
								if (subln == nestedLang) {
									var span = newNode('span');
									span.innerHTML = ", " + content.substr(content.indexOf(':') + 1);
									addEdit({
										'redo': function () {
											parent.appendChild(span)
										},
										'undo': function () {
											parent.removeChild(span)
										},
										'edit': getEditFunction(values, wikitext, nestedLang, values.lang, false, function (text, ipos) {
											// Adding the wikitext to a list of translations that already exists.
											var lineend = text.indexOf('\n', ipos);
											var wt = wikitext.replace('subst:#invoke:', '');
											wt = wt.substr(wt.indexOf(':') + 1);
											return text.substr(0, lineend) + "," + wt + text.substr(lineend);
										})
									}, span);
									
									return resetElements();
								} else if (langmetadata.nestsBefore(nestedLang, subln)) {
									
									addEdit({
										'redo': function () {
											parent.parentNode.insertBefore(dd, parent);
										},
										'undo': function () {
											parent.parentNode.removeChild(dd);
										},
										'edit': getEditFunction(values, wikitext, subln, getLangCode(parent), util.isTrreq(parent),
											function (text, ipos) {
												// Adding a nested translation in-order
												var lineend = text.lastIndexOf('\n', ipos);
												return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
											})
									}, dd);
									return resetElements();
								}
							}
							
							addEdit({
								'redo': function () {
									parent.parentNode.appendChild(dd);
								},
								'undo': function () {
									parent.parentNode.removeChild(dd);
								},
								'edit': getEditFunction(values, wikitext, subln, getLangCode(parent), util.isTrreq(parent),
									function (text, ipos) {
										// Adding a nested translation at the end of its group
										var lineend = text.indexOf('\n', ipos);
										return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
									})
							}, dd);
							return resetElements();
						}
						
					} else if (ln && ln > nestedHeading && (!nextLanguage || ln < nextLanguage)) {
						nextLanguage = ln;
						var parent = lis[j];
						insertBefore = [{
							'redo': function () {
								parent.parentNode.insertBefore(li, parent);
							},
							'undo': function () {
								parent.parentNode.removeChild(li)
							},
							'edit': getEditFunction(values, wikitext, ln, getLangCode(parent), util.isTrreq(parent), function (text, ipos) {
								//Adding a new nested translation section.
								var lineend = text.lastIndexOf('\n', ipos);
								return text.substr(0, lineend) + nestedWikitext + text.substr(lineend);
							})
						}, li];
					}
				}
				wikitext = nestedHeading + ":\n*: " + wikitext;
			}
			
			li.className = "trans-" + values.lang;
			if (insertBefore) {
				addEdit(insertBefore[0], insertBefore[1]);
			} else {
				//Append the translations to the end (no better way found)
				addEdit({
					'redo': function () {
						insertUl.appendChild(li);
					},
					'undo': function () {
						insertUl.removeChild(li)
					},
					'edit': getEditFunction(values, wikitext)
				}, li);
			}
			return resetElements();
		}
		
		//Get the wikitext modification for the current form submission.
		function getEditFunction(values, wikitext, findLanguage, findLangCode, trreq, callback) {
			return function (text) {
				var p = util.getTransTable(text, insertUl);
				
				if (!p)
					return editor.error("Could not find translation table for '"
						+ values.lang + ":" + values.word
						+ "'. Glosses should be unique");
				
				var stapos = p[0];
				var endpos = p[1];
				var trreqLegacy = false;
				
				if (findLanguage) {
					var ipos = 0;
					if (trreq) {
						ipos = text.substr(stapos).search(new RegExp(mw.util.escapeRegExp(findLanguage) + ":\\s*\\{\\{t-needed(?:\\|" + findLangCode + "}})?")) + stapos;
						if (ipos < stapos || ipos > endpos)
							ipos = text.indexOf('{{trreq|' + findLanguage + '}}', stapos);
						if (ipos < stapos || ipos > endpos)
							ipos = text.indexOf('{{trreq|' + findLangCode + '}}', stapos);
					}
					
					// If we have a nested trreq, then we still need to look for the non-trreq form of the heading language
					if (!trreq || ipos < stapos || ipos > endpos) {
						var escapedLang = mw.util.escapeRegExp(findLanguage);
						//if the translation should not be nested it should not go before any nested language
						var regexByShouldFindSubLanguage = values.nested != "" ? "[:*]?" : "";
						var possibleRegexes = [
							RegExp("\\*" + regexByShouldFindSubLanguage + " ?\\[\\[" + escapedLang + "\\]\\]:"),
							RegExp("\\*" + regexByShouldFindSubLanguage + " ?" + escapedLang + ":"),
							RegExp("\\*" + regexByShouldFindSubLanguage + " ?\\{\\{ttbc\\|" + escapedLang + "}}:")
						];
						ipos = text.substr(stapos).search(possibleRegexes[0]) + stapos;
						if (ipos < stapos || ipos > endpos)
							ipos = text.substr(stapos).search(possibleRegexes[1]) + stapos;
						if (ipos < stapos || ipos > endpos)
							ipos = text.substr(stapos).search(possibleRegexes[2]) + stapos;
						if (ipos < stapos || ipos > endpos)
							ipos = text.indexOf('{{subst:#invoke:languages/templates|getByCode|' + findLangCode + '|getCanonicalName}}:', stapos);
					}
					
					if (ipos >= stapos && ipos < endpos) {
						return callback(text, ipos, trreq);
					} else {
						return editor.error("Could not find translation entry for '" + values.lang + ":" + values.word + "'. Please reformat");
					}
				}
				
				return text.substr(0, endpos) + "* " + wikitext + "\n" + text.substr(endpos);
			};
		}
		
		// For an <li> in well-formed translation sections, return the language name.
		function getLangName(li) {
			var guess = li.textContent || li.innerText;
			
			if (guess)
				guess = guess.substr(0, guess.indexOf(':'));
			
			if (guess == 'แม่แบบ') {
				return false;
			}
			
			return guess.replace(/^[\s\n]*/, '');
		}
		
		// Try to get the language code from an <li> containing {{t}}, {{t+}} or {{t-}}
		function getLangCode(li) {
			if (li.className.indexOf('trans-') === 0)
				return li.className.substr(6);
			var spans = li.getElementsByTagName('span');
			for (var i = 0; i < spans.length; i++) {
				if (spans[i].lang) {
					return spans[i].lang;
				}
				if (spans[i].dataset && spans[i].dataset.lang) {
					return spans[i].dataset.lang;
				}
			}
			return false;
		}
		
	}
	
	function createTranslationAdderRow(li) {
		var $ul = $("<ul>").append(li);
		return $("<tr>")
			.append($("<td>"))
			.append($("<td>"))
			.append($("<td>").css("text-align", "left").append($ul));
	}
	
	$('table.translations').each(function () {
		if (util.getTransGloss(this) != 'คำแปลที่ต้องการตรวจสอบ') {
			var uls = $(this).find("td:not(.mw-hiero-table td) > ul").get();
			
			if (uls.length == 0) {
				$(this).find('td:not(.mw-hiero-table td)')[0].appendChild(newNode('ul'));
				uls = this.getElementsByTagName('ul');
			}
			if (uls.length == 1) {
				var td = $(this).find('td:not(.mw-hiero-table td)')[2];
				if (td) {
					td.appendChild(newNode('ul'));
					uls = this.getElementsByTagName('ul');
				}
			}
			if (uls) {
				var li = document.createElement('li');
				var ul = uls[uls.length - 1];
				$(this).append(createTranslationAdderRow(li));
				new AdderWrapper(editor, new TranslationAdder(ul), li);
				if (true) {// XXX: why not? (new CookiePreferences('EditorJs')).get('labeller') == 'true') {
					var div = $(this).parent().parent().find('div.NavHead')[0];
					if (div) {
						new TranslationLabeller(div)
					}
				}
			}
		}
	});
}


$(function () {
	// Check if we are on a sensible page
	var isOnPrintable = mw.util.getParamValue("printable") !== null;
	var isDiff = mw.util.getParamValue("diff") !== null || mw.util.getParamValue("oldid") !== null;
	if (mw.config.get('wgAction') != 'view' || isOnPrintable || isDiff)
		return;
	
	var editor = new Editor();
	TranslationAdders(editor);
	
	/*
	// Check that we have not been disabled
	var prefs = new CookiePreferences('EditorJs');
	if (prefs.get('enabled', 'true') == 'true') {
		if (!window.loadedEditor) {
			prefs.setDefault('labeller', mw.config.get('wgUserName') ? 'true' : 'false');
			window.loadedEditor = true;
			
		}
	}

	// The enable-disable button on WT:EDIT
	var node = document.getElementById('editor-js-disable-button');

	if (node) {
		node.innerHTML = "";
		var toggle = newNode('span', {
			click: function() {
				if (prefs.get('enabled', 'true') == 'true') {
					toggle.innerHTML = "Enable";
					prefs.set('enabled', 'false');
				} else {
					toggle.innerHTML = "Disable";
					prefs.set('enabled', 'true');
				}

			}
		}, (prefs.get('enabled', 'true') == 'true' ? 'Disable' : 'Enable'))

		node.appendChild(toggle);
	}

	var node = document.getElementById("editor-js-labeller-button");
	if (node) {
		node.innerHTML = "";
		var toggle2 = newNode('span', {
			click: function() {
				if (prefs.get('labeller') == 'true') {
					toggle2.innerHTML = "Enable";
					prefs.set('labeller', 'false');
				} else {
					toggle2.innerHTML = "Disable";
					prefs.set('labeller', 'true');
				}

			}
		}, (prefs.get('labeller') == 'true' ? 'Disable' : 'Enable'))

		node.appendChild(toggle2);
	}*/
});
//</nowiki>
// </syntaxhighlight>