Append Tag Searching Tub

『niconico』の検索窓にタグ検索タブを追加 / Adds "tag" search tab above search box in "niconico"

目前为 2014-04-05 提交的版本。查看 最新版本

// ==UserScript==
// @name           Append Tag Searching Tub
// @namespace      http://loda.jp/script/
// @id             niconico-adds-search-tab-347021
// @version        3.0.1
// @description    『niconico』の検索窓にタグ検索タブを追加 / Adds "tag" search tab above search box in "niconico"
// @match          http://www.nicovideo.jp/*
// @match          http://seiga.nicovideo.jp/search/*
// @match          http://live.nicovideo.jp/*
// @match          http://watch.live.nicovideo.jp/*
// @match          http://com.nicovideo.jp/*
// @match          http://blog.nicovideo.jp/en_info/*
// @match          http://tw.blog.nicovideo.jp/*
// @match          http://info.nicovideo.jp/psvita/en/*
// @grant          GM_xmlhttpRequest
// @domain         www.nicovideo.jp
// @domain         seiga.nicovideo.jp
// @domain         live.nicovideo.jp
// @domain         watch.live.nicovideo.jp
// @domain         com.nicovideo.jp
// @domain         blog.nicovideo.jp
// @domain         tw.blog.nicovideo.jp
// @domain         info.nicovideo.jp
// @run-at         document-start
// @icon           data:image/vnd.microsoft.icon;base64,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
// @author         100の人 https://userscripts.org/users/347021
// @license        Creative Commons Attribution 3.0 Unported License
// ==/UserScript==

(function () {
'use strict';

polyfill();

// L10N
setLocalizedTexts({
	'en': {
		'静画を検索': 'Search Image',
		'静画': 'Images',
		'生放送を検索': 'Search Live Program',
		'生放送': 'Live',
		'タグ': 'Tags',
		'マンガ': 'Comics',
	},
	'zh': {
		'静画を検索': '搜尋靜畫',
		'静画': '靜畫',
		'生放送を検索': '搜尋生放送',
		'生放送': '生放送',
		'タグ': '標籤',
		'マンガ': '漫畫',
	},
});

/**
 * 検索窓の最大幅
 * @constant {string}
 */
var MAX_SEARCH_BOX_WIDTH = '268px';



var host = window.location.host, pathname = window.location.pathname, pageType,
		targetParentIdFirefox, isTargetParentFirefox, isTargetFirefox;

// 検索ページの種類を取得
switch (host) {
	case 'www.nicovideo.jp':
		if (window.location.pathname === '/') {
			pageType = 'top';
		} else if (/^\/(?:search\/|mylist_search(?:\/|$))/.test(pathname)) {
			pageType = 'video';
		} else if (/^\/(?:(?:tag|related_tag|watch|mylist)\/|(?:recent|newarrival|hotlist|video_top|openlist|playlist|recommendations)(?:\/|$))/.test(pathname)) {
			pageType = 'tag';
		}
		break;
	case 'seiga.nicovideo.jp':
		pageType = 'image';
		break;
	case 'live.nicovideo.jp':
	case 'watch.live.nicovideo.jp':
		if (pathname.startsWith('/search')) {
			pageType = 'live';
		}
		break;
	case 'info.nicovideo.jp':
		 if (pathname.startsWith('/psvita/en/')) {
			 // 英語版PS Vita紹介ページ
			 startScript(prepare,
					function (parent) { return parent.localName === 'body'; },
					function (target) { return target.id === 'header'; },
					function () { return document.getElementById('header'); },
					{
						isTargetParent: function (parent) { return parent.localName === 'html'; },
						isTarget: function (target) { return target.localName === 'body'; },
					});
		 }
		 return;
}

// 上部メニューが追加されるまで待機
switch (host) {
	case 'seiga.nicovideo.jp':
		// 静画
		isTargetFirefox = function (target) { return target.id === 'wrapper'; };
		break;
	case 'live.nicovideo.jp':
		// 生放送
		targetParentIdFirefox = 'body_header';
		break;
	case 'blog.nicovideo.jp':
		// 英語版ニコニコインフォ
		targetParentIdFirefox = 'container-inner';
		break;
	case 'tw.blog.nicovideo.jp':
		// 台湾版ニコニコインフォ
		targetParentIdFirefox = 'header';
		break;
	case 'info.nicovideo.jp':
		break;
}
if (!isTargetParentFirefox) {
	if (targetParentIdFirefox) {
		isTargetParentFirefox = function (parent) { return parent.id === targetParentIdFirefox; };
	} else {
		isTargetParentFirefox = function (parent) { return parent.localName === 'body'; };
	}
}
startScript(prepare,
		function (parent) { return parent.classList.contains('siteHeaderGlovalNavigation'); },
		function (target) { return target.id === 'siteHeaderLeftMenu'; },
		function () { return document.getElementById('siteHeaderLeftMenu'); },
		{
			isTargetParent: isTargetParentFirefox,
			isTarget: isTargetFirefox || function (target) { return target.id === 'siteHeader'; },
		});

function prepare () {
	var parentId, parentIdFirefox, targetId, targetIdFirefox, isTargetParent, isTargetParentFirefox,
			textVideo, harajuku, itemLive, item;

	// ニコニコ生放送ではlang属性値が常にja-JPのため、ニコニコ動画へのリンク文字によって、ページの言語を判定する
	textVideo = document.querySelector('[href^="http://www.nicovideo.jp/video_top"]').textContent;
	if (textVideo.contains('Video')) {
		setlang('en');
	} else if (textVideo.contains('動畫')) {
		setlang('zh');
	}
	
	if (!document.querySelector(pageType === 'image' ? '#siteHeader [href="/?header"], #siteHeader [href="/"]' : '#siteHeader [href^="http://seiga.nicovideo.jp/"], #globalNav [href^="http://seiga.nicovideo.jp/"]')) {
		// ヘッダに静画へのリンクが無ければ
		// 生放送へのリンクを取得
		itemLive = document.querySelector('#siteHeader [href^="http://live.nicovideo.jp/"], #globalNav [href^="http://live.nicovideo.jp/"]').parentNode;
		// 生放送リンクの複製
		item = itemLive.cloneNode(true);
		// リンク文字を変更
		(item.getElementsByTagName('span')[0] || item.getElementsByTagName('a')[0]).textContent = _('静画');
		// アドレスを変更
		item.getElementsByTagName('a')[0].host = 'seiga.nicovideo.jp';
		// ヘッダに静画へのリンクを追加
		itemLive.parentNode.insertBefore(item, itemLive);
	}
	
	// スクリプトを起動
	if (!pageType) {
		return;
	}
	harajuku = document.doctype.publicId;
	switch (pageType) {
		case 'video':
			if (harajuku) {
				// マイリスト検索、キーワード検索
				parentId = 'form_search';
				targetId = 'search_united_form';
				parentIdFirefox = 'PAGEMAIN';
				targetIdFirefox = 'PAGEBODY';
			} else {
				// GINZAバージョンのキーワード検索
				startScript(main,
						function (parent) { return parent.classList.contains('formSearch'); },
						function (target) { return target.id === 'search_united_form'; },
						function () { return document.getElementById('search_united_form'); },
						{
							isTargetParent: function (parent) { return parent.localName === 'body'; },
							isTarget: function (target) { return target.localName === 'section'; },
						});
				return;
			}
			break;
			
		case 'top':
			// トップページ
			main = mainTop;
			parentId = 'searchFormInner';
			targetId = 'searchForm';
			isTargetParentFirefox = function (parent) {
				return parent.id === 'main_container' || parent.localName === 'body';
			};
			targetIdFirefox = 'searchFormWrap';
			break;
			
		case 'image':
			// 静画
			parentId = 'usearch_form';
			targetId = 'usearch_form_input';
			parentIdFirefox = 'wrapper';
			targetIdFirefox = 'main';
			break;
			
		case 'live':
			// 生放送
			isTargetParentFirefox = isTargetParent = function (target) {
				return target.classList.contains('container');
			};
			targetIdFirefox = targetId = 'form_frm_btm';
			break;
			
		case 'tag':
			if (harajuku) {
				// タグ検索等
				main = mainTag;
				parentId = 'search_tab';
				targetId = 'target_m';
				parentIdFirefox = 'PAGEMAIN';
				targetIdFirefox = 'PAGEBODY';
			} else {
				// GINZAバージョンのタグ検索
				startScript(mainTag,
						function (parent) { return parent.classList.contains('videoSearchOption'); },
						function (target) { return target.classList.contains('optMylist'); },
						function () { return document.getElementsByClassName('optMylist')[0]; },
						{
							isTargetParent: function (parent) { return parent.localName === 'body'; },
							isTarget: function (target) { return target.localName === 'header'; },
						});
				return;
			}
	}
	startScript(main,
			isTargetParent || function (parent) { return parent.id === parentId; },
			function (target) { return target.id === targetId; },
			function () { return document.getElementById(targetId); },
			{
				isTargetParent: isTargetParentFirefox || function (parent) { return parent.id === parentIdFirefox; },
				isTarget: function (target) { return target.id === targetIdFirefox; },
			});
}



// タグ検索
function mainTag () {
	var mylistTab, tabList, styleSheet, cssRules, script;
	
	// スタイルの設定
	styleSheet = document.head.appendChild(document.createElement('style')).sheet;
	cssRules = styleSheet.cssRules;
	[
		'#PAGEHEADER > div {'
				+ 'display: flex;'
				+ '}',
		'#head_search {'
				+ 'max-width: ' + MAX_SEARCH_BOX_WIDTH + ';'
				+ 'flex-grow: 1;'
				+ '}',
		'#search_input {'
				+ 'width: 100%;'
				+ 'display: flex;'
				+ '}',
		'#search_input .typeText {'
				+ 'flex-grow: 1;'
				+ '}',
		'#head_ads {'
				+ 'margin-right: -26px;'
				+ '}',
		'#search_input #bar_search {'
				+ '-moz-box-sizing: border-box;'
				+ 'box-sizing: border-box;'
				+ 'width: 100% !important;'
				+ '}',
		// GINZAバージョン
		'.siteHeader > .inner {'
				+ 'display: flex;'
				+ '}',
		'.videoSearch {'
				+ 'max-width: ' + MAX_SEARCH_BOX_WIDTH + ';'
				+ 'flex-grow: 1;'
				+ 'padding-left: 4px;'
				+ 'padding-right: 4px;'
				+ '}',
		'.videoSearch form {'
				+ 'display: flex;'
				+ '}',
		'.videoSearch form .inputText {'
				+ 'flex-grow: 1;'
				+ '}',
	].forEach(function (rule) {
		styleSheet.insertRule(rule, cssRules.length);
	});
	
	// タブリストの取得
	mylistTab = document.querySelector('#target_m, .optMylist');
	tabList = mylistTab.parentNode;
	
	// タブの複製・追加
	[
		{
			type: 'image',
			title: _('静画を検索'),
			uri: 'http://seiga.nicovideo.jp/search',
			text: _('静画'),
		},
		{
			type: 'live',
			title: _('生放送を検索'),
			uri: 'http://live.nicovideo.jp/search',
			text: _('生放送'),
		},
	].forEach(function (option) {
		var tab = mylistTab.cloneNode(true);
		if (mylistTab.classList.contains('optMylist')) {
			// GINZAバージョン
			tab.classList.remove('optMylist');
			tab.classList.add('opt' + option.type[0].toUpperCase() + option.type.slice(1));
			tab.dataset.type = option.type;
			tab.getElementsByTagName('a')[0].textContent = option.text;
		} else {
			// 原宿バージョン
			tab.id = 'target_' + option.type[0];
			tab.title = option.title;
			tab.setAttribute('onclick', tab.getAttribute('onclick').replace(/'.+?'/, '\'' + option.uri + '\''));
			tab.textContent = option.text;
		}
		tabList.appendChild(tab);
	});
	
	if (mylistTab.classList.contains('optMylist')) {
		// GINZAバージョン
		script = document.createElement('script');
		script.text = '(' + (function () {
			eval('Nico.Navigation.HeaderSearch.Controller.search = ' + Nico.Navigation.HeaderSearch.Controller.search.toString().replace(/(switch.+?{.+?)(})/, '$1; break;'
					+ 'case "image":'
						+ 'd = "http://seiga.nicovideo.jp/search/" + e; break;'
					+ 'case "live":'
						+ 'd = "http://live.nicovideo.jp/search/" + e; break;'
					+ '$2'));
		}).toString() + ')();';
		document.head.appendChild(script);
	}
}



// トップページ
function mainTop() {
	var styleSheet, cssRules, refItem, item, anchor;
	
	fixPrototypeJavaScriptFramework();
	
	// スタイルの設定
	styleSheet = document.head.appendChild(document.createElement('style')).sheet;
	cssRules = styleSheet.cssRules;
	[
		'#searchFormInner {'
				+ 'width: auto;'
				+ 'margin-left: 136px;'
				+ '}',
	].forEach(function (rule) {
		styleSheet.insertRule(rule, cssRules.length);
	});
	
	// マイリスト検索ボタンの取得
	refItem = document.getElementsByClassName('sMylist')[0].parentNode;
	
	// マイリスト検索ボタンの複製
	item = refItem.cloneNode(true);
	
	// ボタン名を変更
	anchor = item.getElementsByTagName('a')[0];
	anchor.textContent = _('タグ');
	
	// クラス名を変更
	anchor.className = 'sVideo';
	
	// アドレスを変更
	anchor.href = 'http://www.nicovideo.jp/tag/';
	
	// タグ検索ボタンを追加
	refItem.parentNode.insertBefore(item, refItem);
	
	if (!document.getElementsByClassName('sSeiga')[0]) {
		// 静画検索ボタンが存在しなければ
		// 生放送検索の取得
		refItem = document.getElementsByClassName('sLive')[0].parentNode;
		// 生放送検索の複製
		item = refItem.cloneNode(true);
		// ボタン名を変更
		anchor = item.getElementsByTagName('a')[0];
		anchor.textContent = _('静画');
		// クラス名を変更
		anchor.className = 'sSeiga';
		// アドレスを変更
		anchor.href = 'http://seiga.nicovideo.jp/search/';
		// 静画検索を追加
		refItem.parentNode.insertBefore(item, refItem);
		
		startScript(function () {
			var list, item, anchor;
			// メニューの生放送リンクの取得
			list = document.querySelector('.service_main .live').parentNode.parentNode;
			// 生放送リンクの複製
			item = list.cloneNode(true);
			// リンク文字を変更
			anchor = item.getElementsByTagName('a')[0];
			anchor.title = anchor.textContent = _('静画');
			// クラス名を変更
			anchor.classList.remove('live');
			anchor.classList.add('seiga');
			// アドレスを変更
			item.getElementsByTagName('a')[0].href = 'http://seiga.nicovideo.jp/';
			// メニューに静画へのリンクを追加
			list.parentNode.insertBefore(item, list);

			// サブメニューの複製
			item = document.getElementsByClassName('service_sub')[0].cloneNode(true);
			// 2つ目以降の要素を削除
			Array.prototype.forEach.call(item.querySelectorAll('li:first-child ~ li'), function (item) {
				item.parentNode.removeChild(item);
			});
			// リンク文字を変更
			anchor = item.getElementsByTagName('a')[0];
			anchor.title = anchor.textContent = _('マンガ');
			// アドレスを変更
			item.getElementsByTagName('a')[0].href = 'http://seiga.nicovideo.jp/manga/';
			// メニューに静画のサブメニューへのリンクを追加
			list.parentNode.insertBefore(item, list);
		},
				function (parent) { return parent.id === 'sideNav'; },
				function (target) { return target.id === 'trendyTags'; },
				function () { return document.querySelector('#menuService [href="http://live.nicovideo.jp/timetable/"]'); },
				{
					isTarget: function (target) { return target.id === 'NewServiceList'; },
				});
	}
}



// キーワード検索、マイリスト検索、静画検索、生放送検索
function main() {
	var inactiveTab, mylistTab, tagTab, tabNameNode, searchCount, anchor, searchWords = '', searchWordsPattern;
	
	// マイリスト検索タブの取得
	mylistTab = document.querySelector('.tab_table td:nth-of-type(2), #search_frm_a a:nth-of-type(2), .seachFormA a:nth-of-type(2)');
	
	// マイリスト検索タブの複製
	tagTab = mylistTab.cloneNode(true);
	
	// タブ名を変更
	anchor = tagTab.tagName.toLowerCase() === 'a' ? tagTab : tagTab.getElementsByTagName('a')[0];
	tabNameNode = anchor.getElementsByTagName('div');
	tabNameNode = (tabNameNode.length > 0  ? tabNameNode[0].firstChild : anchor.firstChild);
	tabNameNode.data = _('タグ') + (pageType === 'live' ? '(' : ' ( ');
	
	// クラス名を変更・動画件数をリセット
	searchCount = tagTab.querySelector('strong, span');
	if (pageType === 'image') {
		searchCount.classList.remove('search_value_em');
		searchCount.classList.add('search_value');
	} else if (pageType === 'live') {
		searchCount.classList.remove('Redtxt');
	} else{
		searchCount.style.removeProperty('color');
	}
	searchCount.textContent = '-';
	
	if (searchCount.id) {
		// 生放送
		searchCount.id = 'search_count_tag';
	}

	// 検索語句を取得
	searchWordsPattern = /(?:\/(?:search|tag|mylist_search)\/|[?&]keyword=)([^?&#]+)/g;
	if (searchWords = window.location.href.match(searchWordsPattern)) {
		searchWords = searchWordsPattern.exec(searchWords[pageType === 'live' ? searchWords.length - 1 : 0])[1];
	}
	
	// タグが付いた動画件数を取得・表示
	if (searchWords) {
		GM_xmlhttpRequest({
			method: 'GET',
			url: 'http://www.nicovideo.jp/tag/' + searchWords,
			onload: function (response) {
				var responseDocument, total, trimmedThousandsSep;
				responseDocument = new DOMParser().parseFromString(response.responseText, 'text/html');
				if (!responseDocument) {
					// Blink
					// Issue 265379: DOMParser + text/html does not work <http://code.google.com/p/chromium/issues/detail?id=265379>
					responseDocument = document.implementation.createHTMLDocument();
					responseDocument.documentElement.innerHTML = response.responseText;
				}
				total = responseDocument.title.contains('(原宿)')
						// 原宿バージョン
						? /[,0-9]+/.exec(responseDocument.getElementsByClassName('searchTagTotal')[0].textContent)[0]
						// GINZAバージョン
						: responseDocument.querySelector('.tagCaption .dataValue .num').textContent;
				trimmedThousandsSep = total.replace(/,/g, '');
				if (trimmedThousandsSep >= 100) {
					if (pageType === 'image') {
						searchCount.classList.remove('search_value');
						searchCount.classList.add('search_value_em');
					} else if (pageType === 'live') {
						searchCount.classList.add('Redtxt');
					} else {
						searchCount.style.color = '#CC0000';
					}
				}
				searchCount.textContent = pageType === 'live' ? trimmedThousandsSep : (pageType === 'image' ? total : ' ' + total + ' ');
			}
		});
	}
	
	// 非アクティブタブを取得
	inactiveTab = document.querySelector('.tab_0, .tab1');
	
	// クラス名を変更
	anchor.className = inactiveTab.className;
	
	// アドレスを変更
	anchor.href = 'http://www.nicovideo.jp/tag/' + searchWords + inactiveTab.search;
	
	// タグ検索タブを追加
	mylistTab.parentNode.insertBefore(tagTab, mylistTab);
	if (inactiveTab.classList.contains('tab1')) {
		// GINZAバージョン
		mylistTab.parentNode.insertBefore(tagTab.previousSibling.cloneNode(true), mylistTab);
	}
}



/**
 * 挿入された節の親節が、目印となる節の親節か否かを返すコールバック関数
 * @callback isTargetParent
 * @param {(Document|Element)} parent
 * @returns {boolean}
 */

/**
 * 挿入された節が、目印となる節か否かを返すコールバック関数
 * @callback isTarget
 * @param {(DocumentType|Element)} target
 * @returns {boolean}
 */

/**
 * 目印となる節が文書に存在するか否かを返すコールバック関数
 * @callback existsTarget
 * @returns {boolean}
 */

/**
 * 目印となる節が挿入された直後に関数を実行する
 * @param {Function} main - 実行する関数
 * @param {isTargetParent} isTargetParent
 * @param {isTarget} isTarget
 * @param {existsTarget} existsTarget
 * @param {Object} [callbacksForFirefox] - DOMContentLoaded前のタイミングで1回だけスクリプトを起動させる場合に設定
 * @param {isTargetParent} [callbacksForFirefox.isTargetParent] - FirefoxにおけるisTargetParent
 * @param {isTarget} [callbacksForFirefox.isTarget] - FirefoxにおけるisTarget
 * @version 2013-09-23
 */
function startScript(main, isTargetParent, isTarget, existsTarget, callbacksForFirefox) {
	var observer, flag;
	
	// FirefoxのDOMContentLoaded前のMutationObserverは、要素をまとめて挿入したと見なすため、isTargetParent、isTargetを変更
	if (callbacksForFirefox && window.navigator.userAgent.contains(' Firefox/')) {
		if (callbacksForFirefox.isTargetParent) {
			isTargetParent = callbacksForFirefox.isTargetParent;
		}
		if (callbacksForFirefox.isTarget) {
			isTarget = callbacksForFirefox.isTarget;
		}
	}
	
	// 指定した節が既に存在していれば、即実行
	startMain();
	if (flag) {
		return;
	}
	
	observer = new MutationObserver(mutationCallback);
	observer.observe(document, {
		childList: true,
		subtree: true,
	});
	
	if (callbacksForFirefox) {
		// DOMContentLoadedまでにスクリプトを実行できなかった場合、監視を停止(指定した節が存在するか確認し、存在すれば実行)
		document.addEventListener('DOMContentLoaded', function stopScript(event) {
			event.target.removeEventListener('DOMContentLoaded', stopScript);
			if (observer) {
				observer.disconnect();
			}
			startMain();
			flag = true;
		});
	}
	
	/**
	 * 目印となる節が挿入されたら、監視を停止し、{@link checkExistingTarget}を実行する
	 * @param {MutationRecord[]} mutations - a list of MutationRecord objects
	 * @param {MutationObserver} observer - the constructed MutationObserver object
	 */
	function mutationCallback(mutations, observer) {
		var mutation, target, nodeType, addedNodes, addedNode, i, j, l, l2;
		for (i = 0, l = mutations.length; i < l; i++) {
			mutation = mutations[i];
			target = mutation.target;
			nodeType = target.nodeType;
			if ((nodeType === Node.ELEMENT_NODE || nodeType === Node.DOCUMENT_NODE) && isTargetParent(target)) {
				// 子が追加された節が要素節か文書節で、かつそのノードについてisTargetParentが真を返せば
				addedNodes = Array.prototype.slice.call(mutation.addedNodes);
				for (j = 0, l2 = addedNodes.length; j < l2; j++) {
					addedNode = addedNodes[j];
					nodeType = addedNode.nodeType;
					if ((nodeType === Node.ELEMENT_NODE || nodeType === Node.DOCUMENT_TYPE_NODE) && isTarget(addedNode)) {
						// 追加された子が要素節か文書型節で、かつそのノードについてisTargetが真を返せば
						observer.disconnect();
						checkExistingTarget(0);
						return;
					}
				}
			}
		}
	}
	
	/**
	 * {@link startMain}を実行し、スクリプトが開始されていなければ再度実行
	 * @param {number} count - {@link startMain}を実行した回数
	 */
	function checkExistingTarget(count) {
		var LIMIT = 500, INTERVAL = 10;
		startMain();
		if (!flag && count < LIMIT) {
			window.setTimeout(checkExistingTarget, INTERVAL, count + 1);
		}
	}
	
	/**
	 * 指定した節が存在するか確認し、存在すれば監視を停止しスクリプトを実行
	 */
	function startMain() {
		if (!flag && existsTarget()) {
			flag = true;
			main();
		}
	}
}

/**
 * prototype汚染が行われる Prototype JavaScript Framework (prototype.js) 1.5.1.1 のバグを修正(Tampermonkey用)
 */
function fixPrototypeJavaScriptFramework() {
	[
		[document, 'getElementsByClassName'],
	].forEach(function (objectProperty) {
		delete objectProperty[0][objectProperty[1]];
	});
}

/**
 * 国際化・地域化関数の読み込み、ECMAScript仕様のPolyfill
 */
function polyfill() {
// i18n
(function () {
	/**
	 * 翻訳対象文字列 (msgid) の言語
	 * @constant {string}
	 */
	var ORIGINAL_LOCALE = 'ja';
	
	/**
	 * クライアントの言語の翻訳リソースが存在しないとき、どの言語に翻訳するか
	 * @constant {string}
	 */
	var DEFAULT_LOCALE = 'en';
	
	/**
	 * 以下のような形式の翻訳リソース
	 * {
	 *     'IETF言語タグ': {
	 *         '翻訳前 (msgid)': '翻訳後 (msgstr)',
	 *         ……
	 *     },
	 *     ……
	 * }
	 * @typedef {Object} LocalizedTexts
	 */
	
	/**
	 * クライアントの言語。{@link setlang}から変更される
	 * @type {string}
	 * @access private
	 */
	var langtag = 'ja';
	
	/**
	 * クライアントの言語のlanguage部分。{@link setlang}から変更される
	 * @type {string}
	 * @access private
	 */
	var language = 'ja';
	
	/**
	 * 翻訳リソース。{@link setLocalizedTexts}から変更される
	 * @type {LocalizedTexts}
	 * @access private
	 */
	var multilingualLocalizedTexts = {};
	multilingualLocalizedTexts[ORIGINAL_LOCALE] = {};
	
	/**
	 * テキストをクライアントの言語に変換する
	 * @param {string} message - 翻訳前
	 * @returns {string} 翻訳後
	 */
	window._ = window.gettext = function (message) {
		// クライアントの言語の翻訳リソースが存在すれば、それを返す
		return langtag in multilingualLocalizedTexts && multilingualLocalizedTexts[langtag][message]
				// 地域下位タグを取り除いた言語タグの翻訳リソースが存在すれば、それを返す
				|| language in multilingualLocalizedTexts && multilingualLocalizedTexts[language][message]
				// デフォルト言語の翻訳リソースが存在すれば、それを返す
				|| DEFAULT_LOCALE in multilingualLocalizedTexts && multilingualLocalizedTexts[DEFAULT_LOCALE][message]
				// そのまま返す
				|| message;
	};
	
	/**
	 * {@link gettext}から参照されるクライアントの言語を設定する
	 * @param {string} lang - IETF言語タグ(「language」と「language-REGION」にのみ対応)
	 */
	window.setlang = function (lang) {
		lang = lang.split('-', 2);
		language = lang[0].toLowerCase();
		langtag = language + (lang[1] ? '-' + lang[1].toUpperCase() : '');
	};
	
	/**
	 * {@link gettext}から参照される翻訳リソースを追加する
	 * @param {LocalizedTexts} localizedTexts
	 */
	window.setLocalizedTexts = function (localizedTexts) {
		var localizedText, lang, language, langtag, msgid;
		for (lang in localizedTexts) {
			localizedText = localizedTexts[lang];
			lang = lang.split('-');
			language = lang[0].toLowerCase();
			langtag = language + (lang[1] ? '-' + lang[1].toUpperCase() : '');
			
			if (langtag in multilingualLocalizedTexts) {
				// すでに該当言語の翻訳リソースが存在すれば、統合する(同じmsgidがあれば上書き)
				for (msgid in localizedText) {
					multilingualLocalizedTexts[langtag][msgid] = localizedText[msgid];
				}
			} else {
				multilingualLocalizedTexts[langtag] = localizedText;
			}
			
			if (language !== langtag) {
				// 言語タグに地域下位タグが含まれていれば
				// 地域下位タグを取り除いた言語タグも翻訳リソースとして追加する
				if (language in multilingualLocalizedTexts) {
					// すでに該当言語の翻訳リソースが存在すれば、統合する(同じmsgidがあれば無視)
					for (msgid in localizedText) {
						if (!(msgid in multilingualLocalizedTexts[language])) {
							multilingualLocalizedTexts[language][msgid] = localizedText[msgid];
						}
					}
				} else {
					multilingualLocalizedTexts[language] = localizedText;
				}
			}
			
			// msgidの言語の翻訳リソースを生成
			for (msgid in localizedText) {
				multilingualLocalizedTexts[ORIGINAL_LOCALE][msgid] = msgid;
			}
		}
	};
})();

// Polyfill for Blink
if (!String.prototype.hasOwnProperty('startsWith')) {
	/**
	 * Determines whether a string begins with the characters of another string, returning true or false as appropriate.
	 * @param {string} searchString - The characters to be searched for at the start of this string.
	 * @param {number} [position=0] - The position in this string at which to begin searching for searchString.
	 * @returns {boolean}
	 * @see {@link http://people.mozilla.org/~jorendorff/es6-draft.html#sec-string.prototype.startswith 21.1.3.18 String.prototype.startsWith (searchString [, position ] )}
	 * @see {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith String.startsWith - JavaScript | MDN}
	 * @version polyfill-2013-11-05
	 * @name String.prototype.startsWith
	 */
	Object.defineProperty(String.prototype, 'startsWith', {
		writable: true,
		enumerable: false,
		configurable: true,
		value: function (searchString) {
			var position = arguments[1];
			return this.indexOf(searchString, position) === Math.max(Math.floor(position) || 0, 0);
		},
	});
}

if (!String.prototype.hasOwnProperty('contains')) {
	/**
	 * Determines whether one string may be found within another string, returning true or false as appropriate.
	 * @param {string} searchString - A string to be searched for within this string.
	 * @param {number} [position=0] - The position in this string at which to begin searching for searchString.
	 * @returns {boolean}
	 * @see {@link http://people.mozilla.org/~jorendorff/es6-draft.html#sec-string.prototype.contains 21.1.3.6 String.prototype.contains (searchString, position = 0 )}
	 * @see {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/contains String.contains - JavaScript | MDN}
	 * @version polyfill-2013-11-05
	 * @name String.prototype.contains
	 */
	Object.defineProperty(String.prototype, 'contains', {
		writable: true,
		enumerable: false,
		configurable: true,
		value: function (searchString) {
			return this.indexOf(searchString, arguments[1]) !== -1;
		},
	});
}

}

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址