((exports) => {
	// @TODO: DRY hotkey helpers
	// & defines accelerators (hotkeys) in menus and buttons and things, which get underlined in the UI.
	// & can be escaped by doubling it, e.g. "&Taskbar && Start Menu"
	function index_of_hotkey(text) {
		// Returns the index of the ampersand that defines a hotkey, or -1 if not present.

		// return english_text.search(/(?<!&)&(?!&|\s)/); // not enough browser support for negative lookbehind assertions

		// The space here handles beginning-of-string matching and counteracts the offset for the [^&] so it acts like a negative lookbehind
		return ` ${text}`.search(/[^&]&[^&\s]/);
	}
	function has_hotkey(text) {
		return index_of_hotkey(text) !== -1;
	}
	function remove_hotkey(text) {
		return text.replace(/\s?\(&.\)/, "").replace(/([^&]|^)&([^&\s])/, "$1$2");
	}
	function display_hotkey(text) {
		// misnomer: using .menu-hotkey out of laziness, instead of a more general term like .hotkey or .accelerator
		return `<span style="white-space: pre">${text.replace(/([^&]|^)&([^&\s])/, "$1<span class='menu-hotkey'>$2</span>").replace(/&&/g, "&")}</span>`;
	}
	function get_hotkey(text) {
		return text[index_of_hotkey(text) + 1].toUpperCase();
	}

	let localizations = {};
	function localize(english_text, ...interpolations) {
		function find_localization(english_text) {
			const amp_index = index_of_hotkey(english_text);
			if (amp_index > -1) {
				const without_hotkey = remove_hotkey(english_text);
				if (localizations[without_hotkey]) {
					const hotkey_def = english_text.slice(amp_index, amp_index + 2);
					if (localizations[without_hotkey].toUpperCase().indexOf(hotkey_def.toUpperCase()) > -1) {
						return localizations[without_hotkey];
					} else {
						if (has_hotkey(localizations[without_hotkey])) {
							// window.console && console.warn(`Localization has differing accelerator (hotkey) hint: '${localizations[without_hotkey]}' vs '${english_text}'`);
							// @TODO: detect differing accelerator more generally
							return `${remove_hotkey(localizations[without_hotkey])} (${hotkey_def})`;
						}
						return `${localizations[without_hotkey]} (${hotkey_def})`;
					}
				}
			}
			if (localizations[english_text]) {
				return localizations[english_text];
			}
			return english_text;
		}
		function interpolate(text, interpolations) {
			for (let i = 0; i < interpolations.length; i++) {
				text = text.replace(`%${i + 1}`, interpolations[i]);
			}
			return text;
		}
		return interpolate(find_localization(english_text), interpolations);
	}

	const language_storage_key = "jspaint language";
	const accepted_languages = Array.from(navigator.languages || [navigator.language || navigator.userLanguage]);
	try {
		if (localStorage[language_storage_key]) {
			accepted_languages.unshift(localStorage[language_storage_key]);
		}
	} catch (error) {
		// if there's no localStorage access, you can still configure the language via system settings, theoretically
		// TODO: also via URL?
	}

	var language_to_default_region = {
		aa: 'ET',
		ab: 'GE',
		abr: 'GH',
		ace: 'ID',
		ach: 'UG',
		ada: 'GH',
		ady: 'RU',
		ae: 'IR',
		aeb: 'TN',
		af: 'ZA',
		agq: 'CM',
		aho: 'IN',
		ak: 'GH',
		akk: 'IQ',
		aln: 'XK',
		alt: 'RU',
		am: 'ET',
		amo: 'NG',
		aoz: 'ID',
		apd: 'TG',
		ar: 'EG',
		arc: 'IR',
		'arc-Nbat': 'JO',
		'arc-Palm': 'SY',
		arn: 'CL',
		aro: 'BO',
		arq: 'DZ',
		ary: 'MA',
		arz: 'EG',
		as: 'IN',
		asa: 'TZ',
		ase: 'US',
		ast: 'ES',
		atj: 'CA',
		av: 'RU',
		awa: 'IN',
		ay: 'BO',
		az: 'AZ',
		'az-Arab': 'IR',
		ba: 'RU',
		bal: 'PK',
		ban: 'ID',
		bap: 'NP',
		bar: 'AT',
		bas: 'CM',
		bax: 'CM',
		bbc: 'ID',
		bbj: 'CM',
		bci: 'CI',
		be: 'BY',
		bej: 'SD',
		bem: 'ZM',
		bew: 'ID',
		bez: 'TZ',
		bfd: 'CM',
		bfq: 'IN',
		bft: 'PK',
		bfy: 'IN',
		bg: 'BG',
		bgc: 'IN',
		bgn: 'PK',
		bgx: 'TR',
		bhb: 'IN',
		bhi: 'IN',
		bhk: 'PH',
		bho: 'IN',
		bi: 'VU',
		bik: 'PH',
		bin: 'NG',
		bjj: 'IN',
		bjn: 'ID',
		bjt: 'SN',
		bkm: 'CM',
		bku: 'PH',
		blt: 'VN',
		bm: 'ML',
		bmq: 'ML',
		bn: 'BD',
		bo: 'CN',
		bpy: 'IN',
		bqi: 'IR',
		bqv: 'CI',
		br: 'FR',
		bra: 'IN',
		brh: 'PK',
		brx: 'IN',
		bs: 'BA',
		bsq: 'LR',
		bss: 'CM',
		bto: 'PH',
		btv: 'PK',
		bua: 'RU',
		buc: 'YT',
		bug: 'ID',
		bum: 'CM',
		bvb: 'GQ',
		byn: 'ER',
		byv: 'CM',
		bze: 'ML',
		ca: 'ES',
		cch: 'NG',
		ccp: 'BD',
		ce: 'RU',
		ceb: 'PH',
		cgg: 'UG',
		ch: 'GU',
		chk: 'FM',
		chm: 'RU',
		cho: 'US',
		chp: 'CA',
		chr: 'US',
		cja: 'KH',
		cjm: 'VN',
		ckb: 'IQ',
		co: 'FR',
		cop: 'EG',
		cps: 'PH',
		cr: 'CA',
		crh: 'UA',
		crj: 'CA',
		crk: 'CA',
		crl: 'CA',
		crm: 'CA',
		crs: 'SC',
		cs: 'CZ',
		csb: 'PL',
		csw: 'CA',
		ctd: 'MM',
		cu: 'RU',
		'cu-Glag': 'BG',
		cv: 'RU',
		cy: 'GB',
		da: 'DK',
		dak: 'US',
		dar: 'RU',
		dav: 'KE',
		dcc: 'IN',
		de: 'DE',
		den: 'CA',
		dgr: 'CA',
		dje: 'NE',
		dnj: 'CI',
		doi: 'IN',
		dsb: 'DE',
		dtm: 'ML',
		dtp: 'MY',
		dty: 'NP',
		dua: 'CM',
		dv: 'MV',
		dyo: 'SN',
		dyu: 'BF',
		dz: 'BT',
		ebu: 'KE',
		ee: 'GH',
		efi: 'NG',
		egl: 'IT',
		egy: 'EG',
		eky: 'MM',
		el: 'GR',
		en: 'US',
		'en-Shaw': 'GB',
		es: 'ES',
		esu: 'US',
		et: 'EE',
		ett: 'IT',
		eu: 'ES',
		ewo: 'CM',
		ext: 'ES',
		fa: 'IR',
		fan: 'GQ',
		ff: 'SN',
		'ff-Adlm': 'GN',
		ffm: 'ML',
		fi: 'FI',
		fia: 'SD',
		fil: 'PH',
		fit: 'SE',
		fj: 'FJ',
		fo: 'FO',
		fon: 'BJ',
		fr: 'FR',
		frc: 'US',
		frp: 'FR',
		frr: 'DE',
		frs: 'DE',
		fub: 'CM',
		fud: 'WF',
		fuf: 'GN',
		fuq: 'NE',
		fur: 'IT',
		fuv: 'NG',
		fvr: 'SD',
		fy: 'NL',
		ga: 'IE',
		gaa: 'GH',
		gag: 'MD',
		gan: 'CN',
		gay: 'ID',
		gbm: 'IN',
		gbz: 'IR',
		gcr: 'GF',
		gd: 'GB',
		gez: 'ET',
		ggn: 'NP',
		gil: 'KI',
		gjk: 'PK',
		gju: 'PK',
		gl: 'ES',
		glk: 'IR',
		gn: 'PY',
		gom: 'IN',
		gon: 'IN',
		gor: 'ID',
		gos: 'NL',
		got: 'UA',
		grc: 'CY',
		'grc-Linb': 'GR',
		grt: 'IN',
		gsw: 'CH',
		gu: 'IN',
		gub: 'BR',
		guc: 'CO',
		gur: 'GH',
		guz: 'KE',
		gv: 'IM',
		gvr: 'NP',
		gwi: 'CA',
		ha: 'NG',
		hak: 'CN',
		haw: 'US',
		haz: 'AF',
		he: 'IL',
		hi: 'IN',
		hif: 'FJ',
		hil: 'PH',
		hlu: 'TR',
		hmd: 'CN',
		hnd: 'PK',
		hne: 'IN',
		hnj: 'LA',
		hnn: 'PH',
		hno: 'PK',
		ho: 'PG',
		hoc: 'IN',
		hoj: 'IN',
		hr: 'HR',
		hsb: 'DE',
		hsn: 'CN',
		ht: 'HT',
		hu: 'HU',
		hy: 'AM',
		hz: 'NA',
		ia: 'FR',
		iba: 'MY',
		ibb: 'NG',
		id: 'ID',
		ife: 'TG',
		ig: 'NG',
		ii: 'CN',
		ik: 'US',
		ikt: 'CA',
		ilo: 'PH',
		in: 'ID',
		inh: 'RU',
		is: 'IS',
		it: 'IT',
		iu: 'CA',
		iw: 'IL',
		izh: 'RU',
		ja: 'JP',
		jam: 'JM',
		jgo: 'CM',
		ji: 'UA',
		jmc: 'TZ',
		jml: 'NP',
		jut: 'DK',
		jv: 'ID',
		jw: 'ID',
		ka: 'GE',
		kaa: 'UZ',
		kab: 'DZ',
		kac: 'MM',
		kaj: 'NG',
		kam: 'KE',
		kao: 'ML',
		kbd: 'RU',
		kby: 'NE',
		kcg: 'NG',
		kck: 'ZW',
		kde: 'TZ',
		kdh: 'TG',
		kdt: 'TH',
		kea: 'CV',
		ken: 'CM',
		kfo: 'CI',
		kfr: 'IN',
		kfy: 'IN',
		kg: 'CD',
		kge: 'ID',
		kgp: 'BR',
		kha: 'IN',
		khb: 'CN',
		khn: 'IN',
		khq: 'ML',
		kht: 'IN',
		khw: 'PK',
		ki: 'KE',
		kiu: 'TR',
		kj: 'NA',
		kjg: 'LA',
		kk: 'KZ',
		'kk-Arab': 'CN',
		kkj: 'CM',
		kl: 'GL',
		kln: 'KE',
		km: 'KH',
		kmb: 'AO',
		kn: 'IN',
		knf: 'SN',
		ko: 'KR',
		koi: 'RU',
		kok: 'IN',
		kos: 'FM',
		kpe: 'LR',
		krc: 'RU',
		kri: 'SL',
		krj: 'PH',
		krl: 'RU',
		kru: 'IN',
		ks: 'IN',
		ksb: 'TZ',
		ksf: 'CM',
		ksh: 'DE',
		ku: 'TR',
		'ku-Arab': 'IQ',
		kum: 'RU',
		kv: 'RU',
		kvr: 'ID',
		kvx: 'PK',
		kw: 'GB',
		kxm: 'TH',
		kxp: 'PK',
		ky: 'KG',
		'ky-Arab': 'CN',
		'ky-Latn': 'TR',
		la: 'VA',
		lab: 'GR',
		lad: 'IL',
		lag: 'TZ',
		lah: 'PK',
		laj: 'UG',
		lb: 'LU',
		lbe: 'RU',
		lbw: 'ID',
		lcp: 'CN',
		lep: 'IN',
		lez: 'RU',
		lg: 'UG',
		li: 'NL',
		lif: 'NP',
		'lif-Limb': 'IN',
		lij: 'IT',
		lis: 'CN',
		ljp: 'ID',
		lki: 'IR',
		lkt: 'US',
		lmn: 'IN',
		lmo: 'IT',
		ln: 'CD',
		lo: 'LA',
		lol: 'CD',
		loz: 'ZM',
		lrc: 'IR',
		lt: 'LT',
		ltg: 'LV',
		lu: 'CD',
		lua: 'CD',
		luo: 'KE',
		luy: 'KE',
		luz: 'IR',
		lv: 'LV',
		lwl: 'TH',
		lzh: 'CN',
		lzz: 'TR',
		mad: 'ID',
		maf: 'CM',
		mag: 'IN',
		mai: 'IN',
		mak: 'ID',
		man: 'GM',
		'man-Nkoo': 'GN',
		mas: 'KE',
		maz: 'MX',
		mdf: 'RU',
		mdh: 'PH',
		mdr: 'ID',
		men: 'SL',
		mer: 'KE',
		mfa: 'TH',
		mfe: 'MU',
		mg: 'MG',
		mgh: 'MZ',
		mgo: 'CM',
		mgp: 'NP',
		mgy: 'TZ',
		mh: 'MH',
		mi: 'NZ',
		min: 'ID',
		mis: 'IQ',
		mk: 'MK',
		ml: 'IN',
		mls: 'SD',
		mn: 'MN',
		'mn-Mong': 'CN',
		mni: 'IN',
		mnw: 'MM',
		moe: 'CA',
		moh: 'CA',
		mos: 'BF',
		mr: 'IN',
		mrd: 'NP',
		mrj: 'RU',
		mro: 'BD',
		ms: 'MY',
		mt: 'MT',
		mtr: 'IN',
		mua: 'CM',
		mus: 'US',
		mvy: 'PK',
		mwk: 'ML',
		mwr: 'IN',
		mwv: 'ID',
		mxc: 'ZW',
		my: 'MM',
		myv: 'RU',
		myx: 'UG',
		myz: 'IR',
		mzn: 'IR',
		na: 'NR',
		nan: 'CN',
		nap: 'IT',
		naq: 'NA',
		nb: 'NO',
		nch: 'MX',
		nd: 'ZW',
		ndc: 'MZ',
		nds: 'DE',
		ne: 'NP',
		new: 'NP',
		ng: 'NA',
		ngl: 'MZ',
		nhe: 'MX',
		nhw: 'MX',
		nij: 'ID',
		niu: 'NU',
		njo: 'IN',
		nl: 'NL',
		nmg: 'CM',
		nn: 'NO',
		nnh: 'CM',
		no: 'NO',
		nod: 'TH',
		noe: 'IN',
		non: 'SE',
		nqo: 'GN',
		nr: 'ZA',
		nsk: 'CA',
		nso: 'ZA',
		nus: 'SS',
		nv: 'US',
		nxq: 'CN',
		ny: 'MW',
		nym: 'TZ',
		nyn: 'UG',
		nzi: 'GH',
		oc: 'FR',
		om: 'ET',
		or: 'IN',
		os: 'GE',
		osa: 'US',
		otk: 'MN',
		pa: 'IN',
		'pa-Arab': 'PK',
		pag: 'PH',
		pal: 'IR',
		'pal-Phlp': 'CN',
		pam: 'PH',
		pap: 'AW',
		pau: 'PW',
		pcd: 'FR',
		pcm: 'NG',
		pdc: 'US',
		pdt: 'CA',
		peo: 'IR',
		pfl: 'DE',
		phn: 'LB',
		pka: 'IN',
		pko: 'KE',
		pl: 'PL',
		pms: 'IT',
		pnt: 'GR',
		pon: 'FM',
		pra: 'PK',
		prd: 'IR',
		ps: 'AF',
		pt: 'PT',//'BR',
		puu: 'GA',
		qu: 'PE',
		quc: 'GT',
		qug: 'EC',
		raj: 'IN',
		rcf: 'RE',
		rej: 'ID',
		rgn: 'IT',
		ria: 'IN',
		rif: 'MA',
		rjs: 'NP',
		rkt: 'BD',
		rm: 'CH',
		rmf: 'FI',
		rmo: 'CH',
		rmt: 'IR',
		rmu: 'SE',
		rn: 'BI',
		rng: 'MZ',
		ro: 'RO',
		rob: 'ID',
		rof: 'TZ',
		rtm: 'FJ',
		ru: 'RU',
		rue: 'UA',
		rug: 'SB',
		rw: 'RW',
		rwk: 'TZ',
		ryu: 'JP',
		sa: 'IN',
		saf: 'GH',
		sah: 'RU',
		saq: 'KE',
		sas: 'ID',
		sat: 'IN',
		sav: 'SN',
		saz: 'IN',
		sbp: 'TZ',
		sc: 'IT',
		sck: 'IN',
		scn: 'IT',
		sco: 'GB',
		scs: 'CA',
		sd: 'PK',
		'sd-Deva': 'IN',
		'sd-Khoj': 'IN',
		'sd-Sind': 'IN',
		sdc: 'IT',
		sdh: 'IR',
		se: 'NO',
		sef: 'CI',
		seh: 'MZ',
		sei: 'MX',
		ses: 'ML',
		sg: 'CF',
		sga: 'IE',
		sgs: 'LT',
		shi: 'MA',
		shn: 'MM',
		si: 'LK',
		sid: 'ET',
		sk: 'SK',
		skr: 'PK',
		sl: 'SI',
		sli: 'PL',
		sly: 'ID',
		sm: 'WS',
		sma: 'SE',
		smj: 'SE',
		smn: 'FI',
		smp: 'IL',
		sms: 'FI',
		sn: 'ZW',
		snk: 'ML',
		so: 'SO',
		sou: 'TH',
		sq: 'AL',
		sr: 'RS',
		srb: 'IN',
		srn: 'SR',
		srr: 'SN',
		srx: 'IN',
		ss: 'ZA',
		ssy: 'ER',
		st: 'ZA',
		stq: 'DE',
		su: 'ID',
		suk: 'TZ',
		sus: 'GN',
		sv: 'SE',
		sw: 'TZ',
		swb: 'YT',
		swc: 'CD',
		swg: 'DE',
		swv: 'IN',
		sxn: 'ID',
		syl: 'BD',
		syr: 'IQ',
		szl: 'PL',
		ta: 'IN',
		taj: 'NP',
		tbw: 'PH',
		tcy: 'IN',
		tdd: 'CN',
		tdg: 'NP',
		tdh: 'NP',
		te: 'IN',
		tem: 'SL',
		teo: 'UG',
		tet: 'TL',
		tg: 'TJ',
		'tg-Arab': 'PK',
		th: 'TH',
		thl: 'NP',
		thq: 'NP',
		thr: 'NP',
		ti: 'ET',
		tig: 'ER',
		tiv: 'NG',
		tk: 'TM',
		tkl: 'TK',
		tkr: 'AZ',
		tkt: 'NP',
		tl: 'PH',
		tly: 'AZ',
		tmh: 'NE',
		tn: 'ZA',
		to: 'TO',
		tog: 'MW',
		tpi: 'PG',
		tr: 'TR',
		tru: 'TR',
		trv: 'TW',
		ts: 'ZA',
		tsd: 'GR',
		tsf: 'NP',
		tsg: 'PH',
		tsj: 'BT',
		tt: 'RU',
		ttj: 'UG',
		tts: 'TH',
		ttt: 'AZ',
		tum: 'MW',
		tvl: 'TV',
		twq: 'NE',
		txg: 'CN',
		ty: 'PF',
		tyv: 'RU',
		tzm: 'MA',
		udm: 'RU',
		ug: 'CN',
		'ug-Cyrl': 'KZ',
		uga: 'SY',
		uk: 'UA',
		uli: 'FM',
		umb: 'AO',
		und: 'US',
		unr: 'IN',
		'unr-Deva': 'NP',
		unx: 'IN',
		ur: 'PK',
		uz: 'UZ',
		'uz-Arab': 'AF',
		vai: 'LR',
		ve: 'ZA',
		vec: 'IT',
		vep: 'RU',
		vi: 'VN',
		vic: 'SX',
		vls: 'BE',
		vmf: 'DE',
		vmw: 'MZ',
		vot: 'RU',
		vro: 'EE',
		vun: 'TZ',
		wa: 'BE',
		wae: 'CH',
		wal: 'ET',
		war: 'PH',
		wbp: 'AU',
		wbq: 'IN',
		wbr: 'IN',
		wls: 'WF',
		wni: 'KM',
		wo: 'SN',
		wtm: 'IN',
		wuu: 'CN',
		xav: 'BR',
		xcr: 'TR',
		xh: 'ZA',
		xlc: 'TR',
		xld: 'TR',
		xmf: 'GE',
		xmn: 'CN',
		xmr: 'SD',
		xna: 'SA',
		xnr: 'IN',
		xog: 'UG',
		xpr: 'IR',
		xsa: 'YE',
		xsr: 'NP',
		yao: 'MZ',
		yap: 'FM',
		yav: 'CM',
		ybb: 'CM',
		yo: 'NG',
		yrl: 'BR',
		yua: 'MX',
		yue: 'HK',
		'yue-Hans': 'CN',
		za: 'CN',
		zag: 'SD',
		zdj: 'KM',
		zea: 'NL',
		zgh: 'MA',
		zh: 'CN',
		'zh-Bopo': 'TW',
		'zh-Hanb': 'TW',
		'zh-Hant': 'TW',
		zlm: 'TG',
		zmi: 'MY',
		zu: 'ZA',
		zza: 'TR',
	};

	function get_language_emoji(locale) {
		var split = locale.toUpperCase().split(/-|_/);
		var lang = split.shift();
		var code = split.pop();

		if (!/^[A-Z]{2}$/.test(code)) {
			code = language_to_default_region[lang.toLowerCase()];
		}

		if (!code) {
			return '';
		}

		const a = String.fromCodePoint(code.codePointAt(0) - 0x41 + 0x1F1E6);
		const b = String.fromCodePoint(code.codePointAt(1) - 0x41 + 0x1F1E6);
		return a + b;
	}

	const base_language = "en";
	const available_languages = ["ar", "cs", "da", "de", "el", "en", "es", "fi", "fr", "he", "hu", "it", "ja", "ko", "nl", "no", "pl", "pt", "pt-br", "ru", "sk", "sl", "sv", "tr", "zh", "zh-simplified"];
	// spell-checker:disable
	const language_names = {
		// "639-1": [["ISO language name"], ["Native name (endonym)"]],
		ab: [["Abkhazian"], ["Аҧсуа Бызшәа", "Аҧсшәа"]],
		aa: [["Afar"], ["Afaraf"]],
		af: [["Afrikaans"], ["Afrikaans"]],
		ak: [["Akan"], ["Akan"]],
		sq: [["Albanian"], ["Shqip"]],
		am: [["Amharic"], ["አማርኛ"]],
		ar: [["Arabic"], ["العربية"]],
		an: [["Aragonese"], ["Aragonés"]],
		hy: [["Armenian"], ["Հայերեն"]],
		as: [["Assamese"], ["অসমীয়া"]],
		av: [["Avaric"], ["Авар МацӀ", "МагӀарул МацӀ"]],
		ae: [["Avestan"], ["Avesta"]],
		ay: [["Aymara"], ["Aymar Aru"]],
		az: [["Azerbaijani"], ["Azərbaycan Dili"]],
		bm: [["Bambara"], ["Bamanankan"]],
		ba: [["Bashkir"], ["Башҡорт Теле"]],
		eu: [["Basque"], ["Euskara", "Euskera"]],
		be: [["Belarusian"], ["Беларуская Мова"]],
		bn: [["Bengali"], ["বাংলা"]],
		bh: [["Bihari Languages"], ["भोजपुरी"]],
		bi: [["Bislama"], ["Bislama"]],
		bs: [["Bosnian"], ["Bosanski Jezik"]],
		br: [["Breton"], ["Brezhoneg"]],
		bg: [["Bulgarian"], ["Български Език"]],
		my: [["Burmese"], ["ဗမာစာ"]],
		ca: [["Catalan", "Valencian"], ["Català", "Valencià"]],
		ch: [["Chamorro"], ["Chamoru"]],
		ce: [["Chechen"], ["Нохчийн Мотт"]],
		ny: [["Chichewa", "Chewa", "Nyanja"], ["ChiCheŵa", "Chinyanja"]],
		// zh: [["Chinese"], ["中文", "Zhōngwén", "汉语", "漢語"]],
		// The ISO 639-1 code "zh" doesn't refer to Traditional Chinese specifically,
		// but we want to show the distinction between Chinese varieties in the Language menu,
		// so this is overly specific for now.
		// @TODO: do this cleaner by establishing a mapping between ISO codes (such as "zh") and default language IDs (such as "zh-traditional")
		zh: [["Traditional Chinese"], ["繁體中文", "傳統中文", "正體中文", "繁体中文"]],
		"zh-traditional": [["Traditional Chinese"], ["繁體中文", "傳統中文", "正體中文", "繁体中文"]], // made-up ID, not real ISO 639-1
		"zh-simplified": [["Simple Chinese"], ["简体中文"]], // made-up ID, not real ISO 639-1
		cv: [["Chuvash"], ["Чӑваш Чӗлхи"]],
		kw: [["Cornish"], ["Kernewek"]],
		co: [["Corsican"], ["Corsu", "Lingua Corsa"]],
		cr: [["Cree"], ["ᓀᐦᐃᔭᐍᐏᐣ"]],
		hr: [["Croatian"], ["Hrvatski Jezik"]],
		cs: [["Czech"], ["Čeština", "Český Jazyk"]],
		da: [["Danish"], ["Dansk"]],
		dv: [["Divehi", "Dhivehi", "Maldivian"], ["ދިވެހި"]],
		nl: [["Dutch", "Flemish"], ["Nederlands", "Vlaams"]],
		dz: [["Dzongkha"], ["རྫོང་ཁ"]],
		en: [["English"], ["English"]],
		eo: [["Esperanto"], ["Esperanto"]],
		et: [["Estonian"], ["Eesti", "Eesti Keel"]],
		ee: [["Ewe"], ["Eʋegbe"]],
		fo: [["Faroese"], ["Føroyskt"]],
		fj: [["Fijian"], ["Vosa Vakaviti"]],
		fi: [["Finnish"], ["Suomi", "Suomen Kieli"]],
		fr: [["French"], ["Français", "Langue Française"]],
		ff: [["Fulah"], ["Fulfulde", "Pulaar", "Pular"]],
		gl: [["Galician"], ["Galego"]],
		ka: [["Georgian"], ["ქართული"]],
		de: [["German"], ["Deutsch"]],
		el: [["Greek"], ["Ελληνικά"]],
		gn: [["Guarani"], ["Avañe'ẽ"]],
		gu: [["Gujarati"], ["ગુજરાતી"]],
		ht: [["Haitian", "Haitian Creole"], ["Kreyòl Ayisyen"]],
		ha: [["Hausa"], ["هَوُسَ"]],
		he: [["Hebrew"], ["עברית"]],
		hz: [["Herero"], ["Otjiherero"]],
		hi: [["Hindi"], ["हिन्दी", "हिंदी"]],
		ho: [["Hiri Motu"], ["Hiri Motu"]],
		hu: [["Hungarian"], ["Magyar"]],
		ia: [["Interlingua"], ["Interlingua"]],
		id: [["Indonesian"], ["Bahasa Indonesia"]],
		ie: [["Interlingue", "Occidental"], ["Interlingue", "Occidental"]],
		ga: [["Irish"], ["Gaeilge"]],
		ig: [["Igbo"], ["Asụsụ Igbo"]],
		ik: [["Inupiaq"], ["Iñupiaq", "Iñupiatun"]],
		io: [["Ido"], ["Ido"]],
		is: [["Icelandic"], ["Íslenska"]],
		it: [["Italian"], ["Italiano"]],
		iu: [["Inuktitut"], ["ᐃᓄᒃᑎᑐᑦ"]],
		ja: [["Japanese"], ["日本語", "にほんご"]],
		jv: [["Javanese"], ["ꦧꦱꦗꦮ", "Basa Jawa"]],
		kl: [["Kalaallisut", "Greenlandic"], ["Kalaallisut", "Kalaallit Oqaasii"]],
		kn: [["Kannada"], ["ಕನ್ನಡ"]],
		kr: [["Kanuri"], ["Kanuri"]],
		ks: [["Kashmiri"], ["कश्मीरी", "كشميري‎"]],
		kk: [["Kazakh"], ["Қазақ Тілі"]],
		km: [["Central Khmer"], ["ខ្មែរ", "ខេមរភាសា", "ភាសាខ្មែរ"]],
		ki: [["Kikuyu", "Gikuyu"], ["Gĩkũyũ"]],
		rw: [["Kinyarwanda"], ["Ikinyarwanda"]],
		ky: [["Kirghiz", "Kyrgyz"], ["Кыргызча", "Кыргыз Тили"]],
		kv: [["Komi"], ["Коми Кыв"]],
		kg: [["Kongo"], ["Kikongo"]],
		ko: [["Korean"], ["한국어"]],
		ku: [["Kurdish"], ["Kurdî", "کوردی‎"]],
		kj: [["Kuanyama", "Kwanyama"], ["Kuanyama"]],
		la: [["Latin"], ["Latine", "Lingua Latina"]],
		lb: [["Luxembourgish", "Letzeburgesch"], ["Lëtzebuergesch"]],
		lg: [["Ganda"], ["Luganda"]],
		li: [["Limburgan", "Limburger", "Limburgish"], ["Limburgs"]],
		ln: [["Lingala"], ["Lingála"]],
		lo: [["Lao"], ["ພາສາລາວ"]],
		lt: [["Lithuanian"], ["Lietuvių Kalba"]],
		lu: [["Luba-Katanga"], ["Kiluba"]],
		lv: [["Latvian"], ["Latviešu Valoda"]],
		gv: [["Manx"], ["Gaelg", "Gailck"]],
		mk: [["Macedonian"], ["Македонски Јазик"]],
		mg: [["Malagasy"], ["Fiteny Malagasy"]],
		ms: [["Malay"], ["Bahasa Melayu", "بهاس ملايو‎"]],
		ml: [["Malayalam"], ["മലയാളം"]],
		mt: [["Maltese"], ["Malti"]],
		mi: [["Maori"], ["Te Reo Māori"]],
		mr: [["Marathi"], ["मराठी"]],
		mh: [["Marshallese"], ["Kajin M̧ajeļ"]],
		mn: [["Mongolian"], ["Монгол Хэл"]],
		na: [["Nauru"], ["Dorerin Naoero"]],
		nv: [["Navajo", "Navaho"], ["Diné Bizaad"]],
		nd: [["North Ndebele"], ["IsiNdebele"]],
		ne: [["Nepali"], ["नेपाली"]],
		ng: [["Ndonga"], ["Owambo"]],
		nb: [["Norwegian Bokmål"], ["Norsk Bokmål"]],
		nn: [["Norwegian Nynorsk"], ["Norsk Nynorsk"]],
		no: [["Norwegian"], ["Norsk"]],
		ii: [["Sichuan Yi", "Nuosu"], ["ꆈꌠ꒿", "Nuosuhxop"]],
		nr: [["South Ndebele"], ["IsiNdebele"]],
		oc: [["Occitan"], ["Occitan", "Lenga d'Òc"]],
		oj: [["Ojibwa"], ["ᐊᓂᔑᓈᐯᒧᐎᓐ"]],
		cu: [["Church Slavic", "Old Slavonic", "Church Slavonic", "Old Bulgarian", "Old Church Slavonic"], ["Ѩзыкъ Словѣньскъ"]],
		om: [["Oromo"], ["Afaan Oromoo"]],
		or: [["Oriya"], ["ଓଡ଼ିଆ"]],
		os: [["Ossetian", "Ossetic"], ["Ирон Æвзаг"]],
		pa: [["Punjabi", "Panjabi"], ["ਪੰਜਾਬੀ", "پنجابی‎"]],
		pi: [["Pali"], ["पालि", "पाळि"]],
		fa: [["Persian"], ["فارسی"]],
		pl: [["Polish"], ["Język Polski", "Polszczyzna"]],
		ps: [["Pashto", "Pushto"], ["پښتو"]],
		pt: [["Portuguese"], ["Português"]],
		"pt-br": [["Brazilian Portuguese"], ["Português Brasileiro"]],
		"pt-pt": [["Portuguese (Portugal)"], ["Português De Portugal"]],
		qu: [["Quechua"], ["Runa Simi", "Kichwa"]],
		rm: [["Romansh"], ["Rumantsch Grischun"]],
		rn: [["Rundi"], ["Ikirundi"]],
		ro: [["Romanian", "Moldavian", "Moldovan"], ["Română"]],
		ru: [["Russian"], ["Русский"]],
		sa: [["Sanskrit"], ["संस्कृतम्"]],
		sc: [["Sardinian"], ["Sardu"]],
		sd: [["Sindhi"], ["सिन्धी", "سنڌي، سندھی‎"]],
		se: [["Northern Sami"], ["Davvisámegiella"]],
		sm: [["Samoan"], ["Gagana Fa'a Samoa"]],
		sg: [["Sango"], ["Yângâ Tî Sängö"]],
		sr: [["Serbian"], ["Српски Језик"]],
		gd: [["Gaelic", "Scottish Gaelic"], ["Gàidhlig"]],
		sn: [["Shona"], ["ChiShona"]],
		si: [["Sinhala", "Sinhalese"], ["සිංහල"]],
		sk: [["Slovak"], ["Slovenčina", "Slovenský Jazyk"]],
		sl: [["Slovenian"], ["Slovenski Jezik", "Slovenščina"]],
		so: [["Somali"], ["Soomaaliga", "Af Soomaali"]],
		st: [["Southern Sotho"], ["Sesotho"]],
		es: [["Spanish", "Castilian"], ["Español"]],
		su: [["Sundanese"], ["Basa Sunda"]],
		sw: [["Swahili"], ["Kiswahili"]],
		ss: [["Swati"], ["SiSwati"]],
		sv: [["Swedish"], ["Svenska"]],
		ta: [["Tamil"], ["தமிழ்"]],
		te: [["Telugu"], ["తెలుగు"]],
		tg: [["Tajik"], ["Тоҷикӣ", "Toçikī", "تاجیکی‎"]],
		th: [["Thai"], ["ไทย"]],
		ti: [["Tigrinya"], ["ትግርኛ"]],
		bo: [["Tibetan"], ["བོད་ཡིག"]],
		tk: [["Turkmen"], ["Türkmen", "Түркмен"]],
		tl: [["Tagalog"], ["Wikang Tagalog"]],
		tn: [["Tswana"], ["Setswana"]],
		to: [["Tonga"], ["Faka Tonga"]],
		tr: [["Turkish"], ["Türkçe"]],
		ts: [["Tsonga"], ["Xitsonga"]],
		tt: [["Tatar"], ["Татар Теле", "Tatar Tele"]],
		tw: [["Twi"], ["Twi"]],
		ty: [["Tahitian"], ["Reo Tahiti"]],
		ug: [["Uighur", "Uyghur"], ["ئۇيغۇرچە‎", "Uyghurche"]],
		uk: [["Ukrainian"], ["Українська"]],
		ur: [["Urdu"], ["اردو"]],
		uz: [["Uzbek"], ["Oʻzbek", "Ўзбек", "أۇزبېك‎"]],
		ve: [["Venda"], ["Tshivenḓa"]],
		vi: [["Vietnamese"], ["Tiếng Việt"]],
		vo: [["Volapük"], ["Volapük"]],
		wa: [["Walloon"], ["Walon"]],
		cy: [["Welsh"], ["Cymraeg"]],
		wo: [["Wolof"], ["Wollof"]],
		fy: [["Western Frisian"], ["Frysk"]],
		xh: [["Xhosa"], ["IsiXhosa"]],
		yi: [["Yiddish"], ["ייִדיש"]],
		yo: [["Yoruba"], ["Yorùbá"]],
		za: [["Zhuang", "Chuang"], ["Saɯ Cueŋƅ", "Saw Cuengh"]],
		zu: [["Zulu"], ["IsiZulu"]],
	};
	// spell-checker:enable

	function get_iso_language_name(language) {
		return language_names[language][0][0];
	}
	function get_language_endonym(language) {
		return language_names[language][1][0];
	}

	let current_language = base_language;
	for (const accepted_language of accepted_languages) {
		if (available_languages.indexOf(accepted_language) !== -1) {
			current_language = accepted_language;
			break;
		}
	}

	function get_language() {
		return current_language;
	}
	function get_direction(language = current_language) {
		return language.match(/^(ar|dv|fa|ha|he|ks|ku|ms|pa|ps|sd|ug|yi)\b/i) ? "rtl" : "ltr";
	}
	function load_language(language) {
		// const prev_language = current_language;

		const stylesheets = [...document.querySelectorAll(".flippable-layout-stylesheet")];
		for (const stylesheet of stylesheets) {
			let href = stylesheet.getAttribute("href");
			if (get_direction(language) === "rtl") {
				if (href.indexOf(".rtl.css") === -1) {
					href = href.replace(/\.css$/i, ".rtl.css");
				}
			} else {
				if (href.indexOf(".rtl.css") > -1) {
					href = href.replace(/\.rtl\.css$/i, ".css");
				}
			}
			stylesheet.setAttribute("href", href);
			// hack to wait for stylesheet to load
			const img = document.createElement("img");
			img.onerror = () => {
				$(() => {
					$G.triggerHandler("theme-load"); // signal layout change
				});
			};
			img.src = href;
		}

		if (language === base_language) {
			localizations = {};
			current_language = base_language;
			return;
		}
		// fetch(`localization/${language}/localizations.json`)
		// .then((response)=> response.json())
		// .then((new_localizations)=> {
		// 	localizations = new_localizations;
		// 	current_language = language;
		// }).catch((error)=> {
		// 	show_error_message(`Failed to load localizations for ${language_names[language]}.`, error);
		// 	current_language = prev_language;
		// });
		const src = `localization/${language}/localizations.js`;
		document.write(`<script src="${src}"></${""/*(avoiding ending script tag if inlined in HTML)*/}script>`);
	}
	// JSONP callback in the localization files
	window.loaded_localizations = function loaded_localizations(language, mapping) {
		localizations = mapping;
		current_language = language;
	}
	function set_language(language) {
		showMessageBox({
			title: "Reload Required",
			message: "The application needs to reload to change the language.",
			buttons: [
				{ label: localize("OK"), value: "reload", default: true },
				{ label: localize("Cancel"), value: "cancel" },
			],
			windowOptions: {
				innerWidth: 450,
			},
		}).then((result) => {
			if (result === "reload") {
				are_you_sure(() => {
					try {
						localStorage[language_storage_key] = language;
						exit_fullscreen_if_ios();
						location.reload();
					} catch (error) {
						show_error_message("Failed to store language preference. Make sure cookies / local storage is enabled in your browser settings.", error);
					}
				});
			}
		});
	}
	load_language(current_language);

	exports.localize = localize;
	exports.set_language = set_language;
	exports.get_language = get_language;
	exports.get_iso_language_name = get_iso_language_name;
	exports.get_language_endonym = get_language_endonym;
	exports.get_language_emoji = get_language_emoji;
	exports.get_direction = get_direction;
	exports.available_languages = available_languages;

	// @TODO: these should come from os-gui.js (new AccessKeys API)
	exports.remove_hotkey = remove_hotkey;
	exports.display_hotkey = display_hotkey;
	exports.get_hotkey = get_hotkey;

})(window);