MediaWiki:AnonymousI18N.js

From Wikimania

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 * AnonymousI18N
 *
 * This hijacks the ULS into using userlang=.
 *
 * Heavyly derived from https://commons.wikimedia.org/wiki/MediaWiki:AnonymousI18N.js
 */
/*jshint curly:false, smarttabs:true, undef:true, browser:true */
/*global mw, $, wpAvailableLanguages, wgAnonUserLanguage, AnonymousI18N */
(function () {
	var conf = mw.config.get([
		'skin',
		'wgUserLanguage',
		'wgContentLanguage',
		'wgSiteName'
	]);

	// Don't load twice
	if (window.AnonymousI18N) {
		return;
	}

	// Hook for tools to suggest a language based on another system
	// Commented out, other scripts may set it at any point in time
	// (before this script loads).
	// This commented-out declaration is here as a reminder.
	//window.AnonymousI18N_suggestlang = null;

	/**
	 * AnonymousI18N.js
	 *
	 * Created 2010-12-06
	 *
	 * Suggests, presents, stores and applies uselang for anonymous users
	 * Only sets a cookie if needed (when suggestion notice or dropdown is clicked)
	 * If no cookie is set but ?uselang= is set it maintains that uselang but doesn't save anything
	 *
	 * @author [[User:Krinkle]]
	 * @license Triple licensed: Creative Commons Attribution 3.0[1], GFDL[2] and GPL[3].
	 * [1] http://creativecommons.org/licenses/by-sa/3.0/
	 * [2] http://www.gnu.org/licenses/fdl.html
	 * [3] http://www.gnu.org/copyleft/gpl-3.0.html
	 */
	window.AnonymousI18N = {

		// Current revision, used for development, cache clearing and tracking
		rev: 'r16',

		// Name of the cookie that stores the language of choice
		cookie_lang: 'AnonymousI18N_lang',

		// Name of the cookie that stores wether the user has declined the notice
		cookie_decline: 'AnonymousI18N_decline',

		// Number of days new cookie's get as expiration
		cookie_expiration: 10,

		// Redirect location to another language of the current page
		redirectCurrentPageToLanguage: function(lang) {
			var path;

			// Be smart about where you redirect; just adding the query string changes
			// the interface language, which makes any future Special:MyLanguage links
			// work - but won't actually redirect the page the user is currently
			// looking at to that language as the user would expect.
			path = location.pathname.replace('/wiki/', '/wiki/Special:MyLanguage/');

			// If there is no lang passed, redirect to uselang-less page
			if (!lang) {
				location.href = path;
				return true;
			}

			// If there is no query string yet, create one
			if (location.search === '') {
				location.href = path + '?uselang=' + encodeURIComponent(lang) + location.hash;

				// There is a query string already
			} else {

				// If there's a uselang= already, replace it
				if (location.search.indexOf('uselang=') !== -1) {
					var query = location.search.split('uselang=' + encodeURIComponent(conf.wgUserLanguage)).join('uselang=' + encodeURIComponent(lang));
					location.href = path + query + location.hash;

					// Otherwise, add it
				} else {
					location.href = path + location.search + '&uselang=' + encodeURIComponent(lang) + location.hash;
				}
			}
		},

		// Redirect location to another language of a given url
		getUrlForLanguage: function(url, lang) {

			// Cut out hash if present
			if (url.indexOf('#') !== -1) {
				url = url.substr(0, url.indexOf('#'));
			}

			// Check if URL contains query string
			var m = url.match(/\?[^#]*/);
			if (m !== null && m[0] !== null) {

				// There is a query string already
				if (url.indexOf('uselang=') !== -1) {
					// If there's a uselang= already, replace it
					return url.split('uselang=' + encodeURIComponent(conf.wgUserLanguage)).join('uselang=' + encodeURIComponent(lang));
				} else {
					// Otherwise, add it
					return url + '&uselang=' + encodeURIComponent(lang);
				}
			} else {
				// If there is no query string yet, create one
				return url + '?uselang=' + encodeURIComponent(lang);
			}
		},

		/**
		 * Get user language
		 *
		 * Priority:
		 *
		 * 1. Cookie
		 * 2. Hook (optional)
		 * 3. Browser language
		 * 4. Default user language (fallback)
		 */
		getUserLanguage: function() {
			/*global AnonymousI18N_suggestlang */

			// Get cookie value
			var language = $.cookie(this.cookie_lang);

			// If there's a different script providing suggestions through the hook, use it
			if (!language && typeof AnonymousI18N_suggestlang === 'string') {
				language = AnonymousI18N_suggestlang;
			}

			// Get user language (uselang=)
			if (!language && conf.wgUserLanguage !== conf.wgContentLanguage) {
				language = conf.wgUserLanguage;
			}

			// Default
			if (!language) {
				language = 'en';
			}

			// Convert to lower case
			language = language.toLowerCase();

			// Check if the (possibly dashed) code is an available language code
			if (typeof wpAvailableLanguages[language] === 'string') {
				return language;
			} else {
				// convert 'aa-BB' to 'aa' etc.
				language = language.split("-")[0];
				if (typeof wpAvailableLanguages[language] === 'string') {
					return language;
				} else {
					// The extracted code isn't available, fallback to default
					return conf.wgUserLanguage;
				}
			}

		},

		init: function() {
			if (!mw.user.isAnon()) {
				return;
			}

			window.wgAnonUserLanguage = this.getUserLanguage();
			mw.config.set('wgULSAnonCanChangeLanguage', true);
			if (typeof mw.uls.init === 'function') {
				mw.uls.init(); // (Re)initialize ULS
			}
			mw.uls.changeLanguage = function ( language ) {
				if (language !== mw.config.get('wgUserLanguage')) {
					$.cookie(AnonymousI18N.cookie_lang, language, {
						expires: AnonymousI18N.cookie_expiration,
						path: '/'
					});
					AnonymousI18N.redirectCurrentPageToLanguage(language);
				}
			};

			// Don't manipulate all anchor-elements
			// Instead listen for any anchor-clicks and maintain uselang when and where needed
			// This way also dynamically added links (such as tabs and toolbox-links) are accounted for
			$(document).on('click', 'a[href]', function(e) {

				var href_attr = $(this).attr('href'),
					// by going to element directlty instead of attribute the urls become complete
					// this makes '/wiki/Article' into 'http://domain.org/wiki/Article'
					// but also '#' into 'http://domain.org/wiki/Article#' !
					href_full = this.href;

				// If
				// * the link destination is a true url (ie. don't interrupt #toc links or 'javascript:call()' stuff)
				// * links to a domain within wikimedia such as http://nl.wiktionary, http://en.wikipedia
				// * The choosen language is not the default (which makes uselang redundant)
				if (href_attr.substr(0, 1) !== '#' &&
					href_attr !== '' &&
					href_full.substr(0, 4) === 'http' &&
					href_full.indexOf('.wik') !== -1 &&
					href_full.indexOf('.org') !== -1 &&
					conf.wgUserLanguage !== conf.wgContentLanguage) {
					// Prevent the default linking functionalty and instead use getUrlForLanguage()
					e.preventDefault();
					location.href = AnonymousI18N.getUrlForLanguage(href_full, wgAnonUserLanguage);

				} // Else: Don't interrupt and follow link regularly
			});

			// Don't manipulate all form-elements
			// Listen for submit()'s and work the uselang in there
			// Works on edit pages (Save, Preview, Diff) - not for <inputbox>'es and Search (yet) though
			$(document).on('submit', 'form', function() {
				var $el = $(this);
				$el.attr('action', function() {
					var action = $el.attr('action');
					// Browsers ignore any query parameter that is appended to the action attribute when using GET
					if (action) action = AnonymousI18N.getUrlForLanguage($el.attr('action'), wgAnonUserLanguage);
					// so also inject an input. See http://stackoverflow.com/a/1116026/2683737
					if (!$el.find('input[name="uselang"]').length) $('<input type="hidden" name="uselang" />').val(wgAnonUserLanguage).appendTo($el);
					return action;
				});
			});
			
			// Final bit of trickery, set the language to the language.  This is
			// a noop unless the user arrives here with a cookie but no uselang=
			// parameter; then it switches to the preferred language straight
			// away.
			mw.uls.changeLanguage(window.wgAnonUserLanguage);
		}
	};

	/**
	 * referrerWikiUselang
	 * @author Mdale
	 * @created December 2, 2010
	 *
	 * Make a best guess at the user language based on the referring wiki.
	 * To get the language call: referrerWikiUselang.getReferrerLang();
	 * Returns a string with the language code or null if nothing was found in the referral.
	 */
	var referrerWikiUselang = window.referrerWikiUselang = {
		// The supported list of projects that include language codes at the start of their url
		projectList: ['wikipedia', 'wiktionary', 'wikinews', 'wikibooks', 'wikisource', 'wikiversity', 'wikiquote', 'wikivoyage'],

		// Some other common sites for which we happen to know its language
		alternateSites: {
			'http://www.wikiskripta.eu': 'cs'
		},

		/**
		 * Check if the language is supported
		 * @param {string} uselang The language code to check
		 * @return {boolean} true if language is supported, false if the language is not supported
		 */
		isSupportedRewriteLanguage: function(useLang) {
			// Check that the language is supported
			if (!(useLang in wpAvailableLanguages)) {
				return false;
			}
			// Just in case someone did not see documentation above ( don't rewrite default language )
			if (useLang === conf.wgContentLanguage) {
				return false;
			}
			return true;
		},
		/**
		 * Get the language code from wikimedia site the user came from
		 * @return {string} the referrer language code
		 */
		getReferrerLang: function() {
			if (document.referrer) {
				// We should use mw.parseUri ( for now regEx )
				var matches = document.referrer.match(/^https?\:\/\/([^\.]*)\.([^\.]*)([^\/:]*)/);

				if (matches && $.inArray(matches[2], this.projectList) !== -1) {
					this.referrerLang = matches[1];
					if (this.isSupportedRewriteLanguage(this.referrerLang)) {
						return this.referrerLang;
					} else {
						return null;
					}
				} else if (matches && this.alternateSites[matches[0]] !== undefined) {
					return this.alternateSites[matches[0]];
				} else {
					return null;
				}
			}
			return null;
		}
	};


	$(function() {
		// referrerWikiUselang could theoretically be broken, verify that it's there
		if (referrerWikiUselang && referrerWikiUselang.getReferrerLang) {
			// Get the langcode of the referring wiki if there is one
			// And add it as an extra suggestion in the AnonymousI18N.getUserLanguage()
			window.AnonymousI18N_suggestlang = referrerWikiUselang.getReferrerLang();
		}
		// Verify the wpAvailableLanguages is set properly, then init stuff
		if (wpAvailableLanguages.en === 'English') {
			AnonymousI18N.init();
		}
	});
})();