{
- packLink(doc, aTag, i);
+ packLink(doc, aTag);
let isHref = "";
let saveUrl = GM_getValue("saveUrl");
if (saveUrl){
@@ -1534,10 +1536,10 @@ if (window.top != window.self) {
function getPageContent(doc, cb, url){
if(!doc)return i18n.error;
- if(doc.body && !doc.body.children.length)return doc.body.innerText;
if(processFunc){
return processFunc(doc, cb, url);
}
+ if(doc.body && !doc.body.children.length)return doc.body.innerText;
[].forEach.call(doc.querySelectorAll("span,div,ul"),function(item){
var thisStyle=doc.defaultView?doc.defaultView.getComputedStyle(item):item.style;
if(thisStyle && (thisStyle.display=="none" || (item.nodeName=="SPAN" && thisStyle.fontSize=="0px"))){
diff --git a/DownloadAllContent/README.md b/DownloadAllContent/README.md
index 5ac83b0837a..f5de4823ebd 100644
--- a/DownloadAllContent/README.md
+++ b/DownloadAllContent/README.md
@@ -9,6 +9,8 @@
[](https://github.com/hoothin/UserScripts#StarMe) ⭐[Star Me](https://github.com/hoothin/UserScripts#StarMe)
+安裝: [Github📥](https://hoothin.github.io/UserScripts/DownloadAllContent/DownloadAllContent.user.js) / [Greasefork📥](https://greasyfork.org/scripts/25068).
+
---
# 操作說明
@@ -23,7 +25,7 @@

-[怠惰小説下載器 ZIP 擴充](https://greasyfork.org/scripts/476943) 下載時將各章節分別存為 TXT,並打包成 ZIP 檔
+[怠惰小説下載器 ZIP 擴充](https://hoothin.github.io/UserScripts/DownloadAllContent/DownloadAllContentSavaAsZIP.user.js) 下載時將各章節分別存為 TXT,並打包成 ZIP 檔
[圖片驗證碼辨識](https://github.com/hoothin/ImgCodeCheck) 開啟`保留內文圖片的網址`後配合 ZIP 擴充可自動轉換圖片文字,詳閱[愛發電](https://afdian.com/p/c7fc3abc8e8411ee9b1852540025c377)
@@ -102,6 +104,10 @@
``` javascript
main>section ul>li div>a@@@@@@var noval=JSON.parse(doc.querySelector("#meta-preload-data").content).novel;noval[Object.keys(noval)[0]].content;
```
+ 新規則
+ ``` javascript
+main>section ul>li div>a@@novel/show\.php\?id=@@ajax/novel/@@data.json().body.content;
+ ```
+ [📕紅薯中文網](https://g.hongshu.com/chapterlist/91735.do)
> 這個站沒有目錄連結,此時可以遍歷標籤自己創建目錄連結下載
``` javascript
diff --git a/FlashViewer-HTML5 Video/README.md b/FlashViewer-HTML5 Video/README.md
index f066f20599a..94771d645c5 100644
--- a/FlashViewer-HTML5 Video/README.md
+++ b/FlashViewer-HTML5 Video/README.md
@@ -1,4 +1,4 @@
-# [FlashViewer](https://github.com/hoothin/UserScripts/raw/master/FlashViewer-HTML5%20Video/flashViewer.user.js)
+# [FlashViewer⬇️](https://hoothin.github.io/UserScripts/FlashViewer-HTML5%20Video/flashViewer.user.js)
HTML5 視頻增強腳本
原作者 NLF
diff --git a/FlashViewer-HTML5 Video/flashViewer.user.js b/FlashViewer-HTML5 Video/flashViewer.user.js
index f6d953c7643..e3f508f5e90 100644
--- a/FlashViewer-HTML5 Video/flashViewer.user.js
+++ b/FlashViewer-HTML5 Video/flashViewer.user.js
@@ -2,9 +2,9 @@
// @name flashViewer
// @author NLF & Hoothin
// @description 围观Flash,增加 HTML5 视频速度与亮度调整
-// @version 1.2.1.8
+// @version 1.2.1.9
// @created 2013-12-27
-// @lastUpdated 2024-8-10
+// @lastUpdated 2025-9-24
// @grant none
// @run-at document-start
// @namespace http://userscripts.org/users/NLF
@@ -1555,6 +1555,7 @@
minHeight: 150,
scale: function (e) {
+ if (this.pinned || this.maximized) return;
if (e.deltaY > 0) {
this.zoomLevel += -0.1;
if (this.zoomLevel > 3) this.zoomLevel = 1;
@@ -1635,13 +1636,15 @@
var vNodeName = video.nodeName;
// 如果是这些元素,那么pin的时候直接用fixed方式(这些元素随便调整position不会引发重载)
- var fixedPin = /^(?:IFRAME|VIDEO|AUDIO)$/.test(vNodeName);
+ var fixedPin = /^(?:IFRAME|VIDEO|AUDIO|CANVAS)$/.test(vNodeName);
this.fixedPin = fixedPin;
video.fvPopVideo = true;// 标记弹出中。
this.zoomLevel = 1;
video.addEventListener("wheel", this.scale.bind(this));
- video.addEventListener("mousedown", this.videoMouseDown.bind(this), true);
+ if (vNodeName === "VIDEO") {
+ video.addEventListener("mousedown", this.videoMouseDown.bind(this), true);
+ }
// 很多网站加载flash为了兼容现代浏览器和ie,经常使用 object classid嵌套object或者embed的格式
var vPEIsObject;
@@ -3843,7 +3846,7 @@
}
// 可弹出元素
- const availableNode = /^(?:OBJECT|EMBED|VIDEO|AUDIO|IFRAME)$/i;
+ const availableNode = /^(?:OBJECT|EMBED|VIDEO|AUDIO|IFRAME|CANVAS)$/i;
if (!availableNode.test(tNName)) {
target = null;
if (document.elementsFromPoint) {
diff --git a/Pagetual/README.md b/Pagetual/README.md
index d10465d01b4..40854710e73 100644
--- a/Pagetual/README.md
+++ b/Pagetual/README.md
@@ -1,8 +1,8 @@
-[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.123](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version")
+[☯️](https://greasyfork.org/scripts/438684 "Install from greasyfork")東方永頁機 [v.1.9.37.131](https://hoothin.github.io/UserScripts/Pagetual/pagetual.user.js "Latest version")
==
*Pagetual - Perpetual pages. Auto loading paginated web pages for 90% of all web sites !*
-🔧CONFIGURATION PAGE
+🔧CONFIGURATION PAGE
@@ -30,10 +30,7 @@ https://raw.githubusercontent.com/hoothin/UserScripts/master/Pagetual/pagetualRu
| Send 📧email |
- | Made with ❤️ by Hoothin |
-
-
- |
+ ![]() Made with ❤️ by Hoothin |
diff --git a/Pagetual/pagetual.user.js b/Pagetual/pagetual.user.js
index f9c24858c4f..ae4b3ad94f3 100644
--- a/Pagetual/pagetual.user.js
+++ b/Pagetual/pagetual.user.js
@@ -31,7 +31,7 @@
// @name:da Pagetual
// @name:fr-CA Pagetual
// @namespace hoothin
-// @version 1.9.37.123
+// @version 1.9.37.132
// @description Perpetual pages - powerful auto-pager script. Auto fetching next paginated web pages and inserting into current page for infinite scroll. Support thousands of web sites without any rule.
// @description:zh-CN 终极自动翻页 - 加载并拼接下一分页内容至当前页尾,智能适配任意网页
// @description:zh-TW 終極自動翻頁 - 加載並拼接下一分頁內容至當前頁尾,智能適配任意網頁
@@ -137,6 +137,9 @@
if (document.body && getComputedStyle(document.body).display === 'none') {
document.body.style.display = 'block';
}
+ Element.prototype.scrollIntoView = function() {
+ console.log('ScrollIntoView blocked.');
+ };
return;
}
@@ -153,7 +156,7 @@
}
const noRuleTest = false;
- const lang = navigator.appName === "Netscape" ? navigator.language : navigator.userLanguage;
+ var langName = navigator.appName === "Netscape" ? navigator.language : navigator.userLanguage;
const langData = [
{
// English translation update by github.com/https433, admin@abby0666.xyz.
@@ -4135,6 +4138,7 @@
});
var i18nData = langData[0].lang;
function setLang(la) {
+ langName = la;
for (let i = 0; i < langData.length; i++) {
let lang = langData[i];
if (lang && lang.match.indexOf(la) !== -1) {
@@ -4148,7 +4152,7 @@
}
}
}
- setLang(lang);
+ setLang(langName);
var enableDebug = true;
var _GM_xmlhttpRequest, _GM_registerMenuCommand, _GM_notification, _GM_addStyle, _GM_openInTab, _GM_info, _GM_setClipboard;
function i18n(name, param) {
@@ -4165,12 +4169,199 @@
}
}
+ function requestWithFetch(f, onFetchError) {
+ function getHeaderValue(headers, name) {
+ if (!headers || !name) return "";
+ let lowerName = String(name).toLowerCase();
+ if (typeof Headers !== "undefined" && headers instanceof Headers) {
+ return headers.get(lowerName) || "";
+ }
+ if (typeof headers === "object") {
+ for (let key in headers) {
+ if (Object.prototype.hasOwnProperty.call(headers, key) && String(key).toLowerCase() === lowerName) {
+ return headers[key] || "";
+ }
+ }
+ }
+ return "";
+ }
+ function extractCharsetFromContentType(contentType) {
+ if (!contentType || typeof contentType !== "string") return "";
+ let match = contentType.match(/charset\s*=\s*["']?([^;"'\s]+)/i);
+ return match && match[1] ? match[1].trim() : "";
+ }
+ function decodeArrayBufferByCharset(buffer, preferredCharset) {
+ let bytes = new Uint8Array(buffer);
+ let decoderList = [];
+ let normalize = label => String(label || "").trim().toLowerCase();
+ let addAlias = (name, aliases) => {
+ if (aliases.indexOf(normalizedPreferred) !== -1) {
+ pushDecoder(name);
+ for (let i = 0; i < aliases.length; i++) {
+ pushDecoder(aliases[i]);
+ }
+ }
+ };
+ let pushDecoder = label => {
+ let raw = String(label || "").trim();
+ if (raw && decoderList.indexOf(raw) === -1) {
+ decoderList.push(raw);
+ }
+ };
+ let normalizedPreferred = normalize(preferredCharset).replace(/["']/g, "");
+ pushDecoder(preferredCharset);
+ pushDecoder(normalizedPreferred);
+ pushDecoder(normalizedPreferred.replace(/_/g, "-"));
+ pushDecoder(normalizedPreferred.replace(/-/g, "_"));
+ addAlias("shift_jis", ["shiftjis", "shift-jis", "sjis", "ms_kanji", "windows-31j", "cp932", "ms932"]);
+ addAlias("euc-jp", ["eucjp"]);
+ addAlias("iso-2022-jp", ["iso2022jp", "jis"]);
+ addAlias("gb18030", ["gbk", "gb2312", "x-gbk", "cp936", "ms936", "windows-936"]);
+ addAlias("big5", ["big-5", "cn-big5", "x-x-big5"]);
+ addAlias("euc-kr", ["euckr", "ks_c_5601-1987", "ksc5601", "windows-949", "cp949"]);
+ addAlias("windows-1251", ["cp1251"]);
+ addAlias("windows-1252", ["cp1252", "iso-8859-1", "latin1", "latin-1"]);
+ pushDecoder("utf-8");
+ for (let i = 0; i < decoderList.length; i++) {
+ try {
+ return new TextDecoder(decoderList[i]).decode(bytes);
+ } catch (e) {}
+ }
+ try {
+ return new TextDecoder().decode(bytes);
+ } catch (e) {
+ return "";
+ }
+ }
+ function detectCharsetFromHtmlHead(buffer) {
+ if (!buffer || !buffer.byteLength) return "";
+ let scanLen = Math.min(buffer.byteLength, 16384);
+ let bytes = new Uint8Array(buffer, 0, scanLen);
+ let ascii = "";
+ for (let i = 0; i < bytes.length; i++) {
+ let code = bytes[i];
+ ascii += code < 128 ? String.fromCharCode(code) : " ";
+ }
+ let charsetMatch = ascii.match(/]*charset\s*=\s*["']?\s*([a-zA-Z0-9._-]+)/i);
+ if (!charsetMatch) {
+ charsetMatch = ascii.match(/]*content\s*=\s*["'][^"']*charset\s*=\s*([a-zA-Z0-9._-]+)/i);
+ }
+ return charsetMatch && charsetMatch[1] ? charsetMatch[1].trim() : "";
+ }
+ function isUtf8Charset(label) {
+ let normalized = String(label || "").trim().toLowerCase().replace(/_/g, "-");
+ return !normalized || normalized === "utf-8" || normalized === "utf8";
+ }
+ if (!f || !f.url || typeof fetch === "undefined") {
+ if (onFetchError) {
+ onFetchError({error: "Fetch not available"});
+ } else if (f && f.onerror) {
+ f.onerror({error: "Fetch not available"});
+ }
+ return;
+ }
+ let method = (f.method || "GET").toUpperCase();
+ let timeoutId = null;
+ let controller = typeof AbortController !== "undefined" ? new AbortController() : null;
+ if (f.timeout > 0 && controller) {
+ timeoutId = setTimeout(() => {
+ controller.abort();
+ }, f.timeout);
+ }
+ let options = {
+ method: method,
+ headers: f.headers || {}
+ };
+ if (controller) options.signal = controller.signal;
+ if (method !== "GET" && method !== "HEAD" && typeof f.data !== "undefined") {
+ options.body = f.data;
+ }
+ fetch(f.url, options).then(async response => {
+ if (timeoutId) clearTimeout(timeoutId);
+ let responseCharset = extractCharsetFromContentType(response.headers.get("content-type") || "");
+ let overrideCharset = extractCharsetFromContentType(f.overrideMimeType || "");
+ let requestCharset = extractCharsetFromContentType(getHeaderValue(options.headers, "content-type"));
+ let text;
+ let charsetFromHeaders = responseCharset || overrideCharset || requestCharset;
+ if (charsetFromHeaders) {
+ if (isUtf8Charset(charsetFromHeaders)) {
+ text = await response.text();
+ } else {
+ text = decodeArrayBufferByCharset(await response.arrayBuffer(), charsetFromHeaders);
+ }
+ } else {
+ let rawBuffer = await response.arrayBuffer();
+ let metaCharset = detectCharsetFromHtmlHead(rawBuffer);
+ let targetCharset = metaCharset || charset || "utf-8";
+ if (isUtf8Charset(targetCharset)) {
+ text = new TextDecoder("utf-8").decode(new Uint8Array(rawBuffer));
+ } else {
+ text = decodeArrayBufferByCharset(rawBuffer, targetCharset);
+ }
+ }
+ let headers = "";
+ response.headers.forEach((value, key) => {
+ headers += key + ": " + value + "\r\n";
+ });
+ if (f.onload) {
+ f.onload({
+ response: text,
+ responseText: text,
+ status: response.status,
+ statusText: response.statusText,
+ finalUrl: response.url,
+ responseHeaders: headers
+ });
+ }
+ }).catch(error => {
+ if (timeoutId) clearTimeout(timeoutId);
+ if (error && error.name === "AbortError" && f.ontimeout) {
+ f.ontimeout(error);
+ return;
+ }
+ if (onFetchError) {
+ onFetchError(error);
+ } else if (f.onerror) {
+ f.onerror(error);
+ }
+ });
+ }
+ function isSameOriginRequest(f) {
+ if (!f || !f.url || typeof location === "undefined") return false;
+ try {
+ return new URL(f.url, location.href).origin === location.origin;
+ } catch (e) {
+ return false;
+ }
+ }
+ let nativeGMRequest = null;
if (typeof GM_xmlhttpRequest !== 'undefined') {
- _GM_xmlhttpRequest = GM_xmlhttpRequest;
+ nativeGMRequest = GM_xmlhttpRequest;
} else if (typeof GM !== 'undefined' && typeof GM.xmlHttpRequest !== 'undefined') {
- _GM_xmlhttpRequest = GM.xmlHttpRequest;
+ nativeGMRequest = GM.xmlHttpRequest;
+ }
+ if (nativeGMRequest) {
+ _GM_xmlhttpRequest = function(f) {
+ if (!f) return nativeGMRequest(f);
+ if (isSameOriginRequest(f)) {
+ requestWithFetch(f);
+ return;
+ }
+ let originalOnerror = f.onerror;
+ let request = Object.assign({}, f);
+ request.onerror = function(gmError) {
+ requestWithFetch(f, function(fetchError) {
+ if (originalOnerror) {
+ originalOnerror(fetchError || gmError || {error: "Request failed"});
+ }
+ });
+ };
+ return nativeGMRequest(request);
+ };
} else {
- _GM_xmlhttpRequest = (f) => {fetch(f.url, {method: f.method || 'GET', body: f.data, headers: f.headers}).then(response => response.text()).then(data => {f.onload && f.onload({response: data})}).catch(f.onerror && f.onerror())};
+ _GM_xmlhttpRequest = function(f) {
+ requestWithFetch(f);
+ };
}
if (typeof GM_registerMenuCommand !== 'undefined') {
_GM_registerMenuCommand = GM_registerMenuCommand;
@@ -4207,7 +4398,7 @@
} else {
_GM_addStyle = cssStr => {
let styleEle = document.createElement("style");
- styleEle.innerHTML = cssStr;
+ setHTML(styleEle, cssStr);
document.head.appendChild(styleEle);
return styleEle;
};
@@ -4314,12 +4505,15 @@
});
}
const isMobile = ('ontouchstart' in document.documentElement && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
- const configPage = [`https://pagetual.hoothin.com/${lang === 'zh-CN' ? 'cn/' : ''}rule.html`,
+ const cnConfigPage = "https://pagetual.hoothin.com/cn/rule.html";
+ const configPage = ["https://pagetual.hoothin.com/rule.html",
"https://github.com/hoothin/UserScripts/tree/master/Pagetual",
"https://hoothin.github.io/UserScripts/Pagetual/"];
const firstRunPage = "https://pagetual.hoothin.com/firstRun";
+ const wedataRulesUrl = "http://wedata.net/databases/AutoPagerize/items_all.json";
+ const wedataMirrorRulesUrl = "https://hoothin.github.io/UserScripts/Pagetual/items_all.json";
const guidePage = /^https?:\/\/.*pagetual.*rule\.html/i;
- const ruleImportUrlReg = /greasyfork\.org\/.*scripts\/438684(\-[^\/]*)?(\/discussions|\/?$|\/feedback)|github\.com\/hoothin\/UserScripts\/(tree\/master\/Pagetual|issues)|^https:\/\/pagetual\.hoothin\.com\/.*firstRun\.html/i;
+ const ruleImportUrlReg = /greasyfork\.org\/.*scripts\/438684(\-[^\/]*)?(\/discussions|\/?$|\/feedback)|github\.com\/hoothin\/UserScripts\/(tree\/master\/Pagetual|issues)|^https:\/\/pagetual\.hoothin\.com\/.*first(Run|-run)\.html/i;
const allOfBody = "body>*";
const mainSel = ["article,.article","[role=main],main,.main,#main","#results"];
const nextTextReg1 = new RegExp("\u005e\u7ffb\u003f\u005b\u4e0b\u540e\u5f8c\u6b21\u005d\u005b\u4e00\u30fc\u2500\u0031\u005d\u003f\u005b\u9875\u9801\u5f20\u5f35\u005d\u007c\u005e\u006e\u0065\u0078\u0074\u005b\u005c\u0073\u005f\u002d\u005d\u003f\u0070\u0061\u0067\u0065\u005c\u0073\u002a\u005b\u203a\u003e\u2192\u00bb\u005d\u003f\u0024\u007c\u6b21\u306e\u30da\u30fc\u30b8\u007c\u005e\u6b21\u3078\u0024\u007c\u0412\u043f\u0435\u0440\u0435\u0434\u007c\u005e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435", "i");
@@ -4560,12 +4754,211 @@
return segs.length ? '/' + segs.join('/') : null;
}
- const escapeHTMLPolicy = (_unsafeWindow.trustedTypes && _unsafeWindow.trustedTypes.createPolicy) ? _unsafeWindow.trustedTypes.createPolicy('pagetual_default', {
- createHTML: (string, sink) => string
- }) : null;
+ function createHTML(html, doc) {
+ const targetDoc = doc || document;
+ const fragment = targetDoc.createDocumentFragment();
+ if (html === null || html === undefined || html === '') return fragment;
+ parseHTMLToFragment(String(html), fragment, targetDoc);
+ return fragment;
+ }
+ let canDirectSetHTML = true;
+ let canPolicySetHTML = true;
+ let escapeHTMLPolicy;
+ let escapeHTMLCreator;
+ const MY_POLICY_NAME = 'pagetual_default';
+ const SVG_NS = 'http://www.w3.org/2000/svg';
+ const VOID_TAGS = {
+ area: true,
+ base: true,
+ br: true,
+ col: true,
+ embed: true,
+ hr: true,
+ img: true,
+ input: true,
+ link: true,
+ meta: true,
+ param: true,
+ source: true,
+ track: true,
+ wbr: true
+ };
+ const RAW_TEXT_TAGS = {
+ script: true,
+ style: true,
+ textarea: true,
+ title: true,
+ xmp: true,
+ plaintext: true,
+ noscript: true
+ };
+ const HTML_ENTITIES = {
+ amp: '&',
+ lt: '<',
+ gt: '>',
+ quot: '"',
+ apos: "'",
+ nbsp: '\u00A0'
+ };
+ function decodeEntities(text) {
+ return text.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, function(_, code) {
+ if (code[0] === '#') {
+ const isHex = code[1] === 'x' || code[1] === 'X';
+ const num = parseInt(code.slice(isHex ? 2 : 1), isHex ? 16 : 10);
+ if (!isNaN(num)) {
+ try { return String.fromCodePoint(num); } catch(e) {}
+ }
+ return '&' + code + ';';
+ }
+ const key = code.toLowerCase();
+ return (key in HTML_ENTITIES) ? HTML_ENTITIES[key] : '&' + code + ';';
+ });
+ }
+ function parseHTMLToFragment(html, fragment, doc) {
+ const stack = [fragment];
+ const tokenRe = /|]*>|<\/?[a-zA-Z][^>]*>|[^<]+/gi;
+ let match;
+ while ((match = tokenRe.exec(html))) {
+ const token = match[0];
+ if (token[0] !== '<') {
+ const text = decodeEntities(token);
+ if (text) stack[stack.length - 1].appendChild(doc.createTextNode(text));
+ continue;
+ }
+ if (token.indexOf('|]*>|<\/?[a-zA-Z][^>]*>|[^<]+/gi;
+ let match;
+ while ((match = tokenRe.exec(html))) {
+ const token = match[0];
+ if (token[0] !== '<') {
+ const text = decodeEntities(token);
+ if (text) stack[stack.length - 1].appendChild(doc.createTextNode(text));
+ continue;
+ }
+ if (token.indexOf('|]*>|<\/?[a-zA-Z][^>]*>|[^<]+/gi;
+ let match;
+ while ((match = tokenRe.exec(html))) {
+ const token = match[0];
+ if (token[0] !== '<') {
+ const text = decodeEntities(token);
+ if (text) stack[stack.length - 1].appendChild(doc.createTextNode(text));
+ continue;
+ }
+ if (token.indexOf('0){
- if(char_f[x])processChar=char_f[x]+processChar;
+ if (!char_f[x]) break;
+ processChar=char_f[x]+processChar;
+ x++;
}
x=0;
while(badd-->0){
- if(char_b[x])processChar+=char_b[x];
+ if (!char_b[x]) break;
+ processChar+=char_b[x];
+ x++;
}
if(processChar.indexOf(curOther) != -1){
newChar=otherChar;
@@ -944,11 +944,15 @@
var curOther=others[k],fadd=curOther.indexOf(char),badd=curOther.length-1-fadd,x=0;
var processChar=char;
while(fadd-->0){
- if(char_f[x])processChar=char_f[x]+processChar;
+ if (!char_f[x]) break;
+ processChar=char_f[x]+processChar;
+ x++;
}
x=0;
while(badd-->0){
- if(char_b[x])processChar+=char_b[x];
+ if (!char_b[x]) break;
+ processChar+=char_b[x];
+ x++;
}
if(processChar.indexOf(curOther) != -1){
newChar=otherChar;
@@ -1122,6 +1126,17 @@
_unsafeWindow.tc2sc = simplized;
_unsafeWindow.sc2tc = traditionalized;
+ window.addEventListener("message", function(event) {
+ if (event.data && event.data.type === "switchChineseRequest") {
+ const receivedData = event.data.payload;
+ const result = receivedData.target === 'sc' ? simplized(receivedData.str) : traditionalized(receivedData.str);
+ window.postMessage({
+ type: "switchChineseResult",
+ payload: result
+ }, "*");
+ }
+ });
+
var storage = {
supportGM: typeof GM_getValue == 'function' && typeof GM_getValue('a', 'b') != 'undefined',
supportGMPromise: typeof GM != 'undefined' && typeof GM.getValue == 'function' && typeof GM.getValue('a','b') != 'undefined',
@@ -1217,14 +1232,14 @@
curLang = isSimple;
}
curLang=!curLang;
- activeEle.innerHTML=curLang?traditionalized(activeEle.innerHTML):simplized(activeEle.innerHTML);
- activeEle.value=curLang?traditionalized(activeEle.value):simplized(activeEle.value);
+ activeEle.innerHTML=curLang?simplized(activeEle.innerHTML):traditionalized(activeEle.innerHTML);
+ activeEle.value=curLang?simplized(activeEle.value):traditionalized(activeEle.value);
}else if("INPUT"==activeEle.nodeName.toUpperCase()){
if (curInput != activeEle) {
curLang = isSimple;
}
curLang=!curLang;
- activeEle.value=curLang?traditionalized(activeEle.value):simplized(activeEle.value);
+ activeEle.value=curLang?simplized(activeEle.value):traditionalized(activeEle.value);
}else{
var selecter;
if(window.getSelection()){
@@ -1713,12 +1728,16 @@
storage.setItem('sc2tcCombConfig', sc2tcCombConfig);
} catch (e) {
console.log(e);
+ alert("自訂簡繁用語轉換格式錯誤:" + e);
+ return;
}
try {
illiteracyConfig = customIlliteracyInput.value ? JSON.parse(customIlliteracyInput.value) : "";
storage.setItem('illiteracyConfig', illiteracyConfig);
} catch (e) {
console.log(e);
+ alert("通用字詞轉換格式錯誤:" + e);
+ return;
}
storage.setItem('sc2tcCombTree', "");
storage.setItem('tc2scCombTree', "");
@@ -1888,32 +1907,38 @@
function makeCombTree(key, value) {
let curTree=sc2tcCombTree;
for(let i=0;i=12.0.0"
+ }
}
+
diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md
index cfe0e6f52e7..670521a96db 100644
--- a/Switch Traditional Chinese and Simplified Chinese/lib/readme.md
+++ b/Switch Traditional Chinese and Simplified Chinese/lib/readme.md
@@ -1,73 +1,1082 @@
-簡繁自由切換
-===
-> 切換正體/簡體中文,超輕量級,支援自訂詞彙與「一簡多繁」轉換,無任何相依性
+# switch-chinese
+
+[Online Demo](https://tool.hoothin.com/chinese-converter)
+
+[简体中文](#简体中文) | [繁體中文](#繁體中文) | [English](#english)
+
+---
+
+## 简体中文
+
+简繁体中文转换库 - 支持简体中文与繁体中文双向转换,基于词组的智能分词处理「一简多繁」问题
[](https://www.npmjs.com/package/switch-chinese) [](https://www.npmjs.com/package/switch-chinese)
-Install
+### 特性
+
+- **轻量级**:零依赖,体积小,性能优异
+- **智能转换**:支持基于词组的智能分词和「一简多繁」精准转换
+- **多种数据类型**:支持字符串、数组、对象的转换,自动递归处理嵌套结构
+- **自定义词库**:允许用户自定义简繁转换词汇
+- **缓存机制**:支持字典缓存,避免重复初始化
+- **简繁检测**:自动检测文本是简体中文、繁体中文还是未知类型
+- **术语转换**:内置大陆简体与台湾正体的常用术语转换
+- **无依赖**:纯 JavaScript 实现,无需任何第三方依赖
+
+### 安装
+
+使用 npm 安装:
+
+```bash
+npm install switch-chinese
+```
+
+使用 yarn 安装:
+
+```bash
+yarn add switch-chinese
+```
+
+### 快速开始
+
+#### 基础用法
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const { traditionalized, simplized, detect } = stcasc();
+
+// 简体转繁体
+const tc = traditionalized('简繁转换 繁简切换 香烟 香烟袅袅');
+console.log(tc);
+// 输出: 簡繁轉換 繁簡切換 香菸 香煙裊裊
+
+// 繁体转简体
+const sc = simplized('繁體中文');
+console.log(sc);
+// 输出: 繁体中文
+```
+
+#### 检测中文类型
+
+```javascript
+import stcasc, { ChineseType } from 'switch-chinese';
+
+const { detect } = stcasc();
+
+const type1 = detect('简体中文');
+if (type1 === ChineseType.SIMPLIFIED) {
+ console.log('检测到简体中文');
+}
+
+const type2 = detect('繁體中文');
+if (type2 === ChineseType.TRADITIONAL) {
+ console.log('检测到繁体中文');
+}
+
+const type3 = detect('English');
+if (type3 === ChineseType.UNKNOWN) {
+ console.log('未检测到中文');
+}
+```
+
+ChineseType 枚举值:
+- `ChineseType.SIMPLIFIED` (0): 简体中文
+- `ChineseType.TRADITIONAL` (1): 繁体中文
+- `ChineseType.UNKNOWN` (2): 未知类型
+
+#### 转换数组和对象
+
+库支持转换数组和对象中的所有字符串,非字符串值保持原样:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const { traditionalized, simplized } = stcasc();
+
+// 转换数组
+const arr = ['简体中文', '软件', '网络', 123, true, null];
+const arrTc = traditionalized(arr);
+console.log(arrTc);
+// 输出: ['簡體中文', '軟體', '網路', 123, true, null]
+
+// 转换对象
+const obj = {
+ title: '简体中文标题',
+ description: '这是一个简体中文描述',
+ count: 100,
+ active: true,
+ tags: ['软件', '网络', '服务器']
+};
+const objTc = traditionalized(obj);
+console.log(objTc);
+// 输出: {
+// title: '簡體中文標題',
+// description: '這是一個簡體中文描述',
+// count: 100,
+// active: true,
+// tags: ['軟體', '網路', '伺服器']
+// }
+
+// 转换嵌套结构
+const nested = {
+ user: {
+ name: '简体名称',
+ profile: {
+ bio: '这是简体中文简介',
+ skills: ['软件开发', '网络管理']
+ }
+ },
+ count: 42
+};
+const nestedTc = traditionalized(nested);
+// 所有字符串属性值都会被转换,数字等其他类型保持不变
+```
+
+### 高级用法
+
+#### 使用缓存优化性能
+
+在多次调用时,建议使用缓存机制避免重复生成字典,提升性能:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+// 第一次调用,生成字典
+let converter = stcasc();
+const cache = converter.cache;
+
+// 保存缓存到持久化存储(如 localStorage、文件等)
+localStorage.setItem('stcasc-cache', JSON.stringify(cache));
+
+// 后续调用,直接使用缓存
+const cachedData = JSON.parse(localStorage.getItem('stcasc-cache'));
+converter = stcasc(cachedData);
+
+// 现在可以直接使用,无需重新生成字典
+const result = converter.traditionalized('简体中文');
+```
+
+#### 自定义简繁转换词库
+
+可以根据业务需求自定义简繁转换规则:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const customDict = {
+ '身份': '身分',
+ '转义': '跳脫',
+ '转换': '轉檔',
+ '软件': '軟體',
+ '硬件': '硬體',
+ '网络': '網路',
+ '服务器': '伺服器'
+};
+
+const { traditionalized, simplized } = stcasc({}, customDict);
+
+console.log(traditionalized('软件转换'));
+// 输出: 軟體轉檔(使用自定义词库)
+```
+
+#### 禁用术语转换
+
+默认情况下,库会转换一些特定术语(如「知识产权」→「智慧財產權」)。如需禁用此功能:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+// 第三个参数设置为 true 以禁用术语转换
+const { traditionalized } = stcasc({}, {}, true);
+
+console.log(traditionalized('知识产权'));
+// 输出: 知識産權(仅做字符转换,不转换术语)
+```
+
+#### 输出格式选项
+
+库支持多种输出格式:
+
+```javascript
+import stcasc, { OutputFormat } from 'switch-chinese';
+
+const { traditionalized } = stcasc();
+
+// 普通格式(默认)
+const normal = traditionalized('简体中文');
+// 输出: 簡體中文
+
+// 括号格式:同时显示原文和转换后的文本
+const bracket = traditionalized('简体中文', { format: OutputFormat.BRACKET });
+// 输出: 簡(简)體(体)中文
+
+// Ruby 注音格式:适用于 HTML 显示
+const ruby = traditionalized('简体中文', { format: OutputFormat.RUBY });
+// 输出: 簡體中文
+```
+
+OutputFormat 枚举值:
+- `OutputFormat.NORMAL` (0): 只输出转换后的结果(默认)
+- `OutputFormat.BRACKET` (1): 输出「转换(原文)」格式
+- `OutputFormat.RUBY` (2): 输出 `` HTML 标签格式
+
+### API 文档
+
+#### stcasc(cache?, custom?, disableTerms?)
+
+主函数,用于创建转换器实例。
+
+**参数:**
+
+- `cache` (Object, 可选): 缓存对象,用于避免重复生成字典
+- `custom` (Object, 可选): 自定义简繁转换词库
+- `disableTerms` (Boolean, 可选): 是否禁用术语转换,默认 `false`
+
+**返回值:**
+
+返回包含以下方法的对象:
+
+- `traditionalized(input, options?)`: 将简体中文转换为繁体中文
+ - `input`: 可以是字符串、数组或对象
+ - 字符串:直接转换返回新字符串
+ - 数组:转换所有字符串元素,其他类型保持不变
+ - 对象:递归转换所有字符串属性值,其他类型保持不变
+- `simplized(input, options?)`: 将繁体中文转换为简体中文
+ - `input`: 可以是字符串、数组或对象
+ - 支持的数据类型同 `traditionalized`
+- `detect(text)`: 检测文本的中文类型,返回 ChineseType 枚举值
+- `cache`: 字典缓存对象
+
+**Options 参数:**
+
+- `format` (Number, 可选): 输出格式,使用 `OutputFormat` 常量
+
+#### ChineseType
+
+导出的常量对象,用于表示中文类型检测结果:
+
+```javascript
+export const ChineseType = {
+ SIMPLIFIED: 0, // 简体中文
+ TRADITIONAL: 1, // 繁体中文
+ UNKNOWN: 2 // 未知类型
+};
+```
+
+#### OutputFormat
+
+导出的常量对象,用于表示输出格式选项:
+
+```javascript
+export const OutputFormat = {
+ NORMAL: 0, // 只输出转换后的结果
+ BRACKET: 1, // 输出「转换(原文)」格式
+ RUBY: 2 // 输出 HTML 标签格式
+};
+```
+
+### 转换示例
+
+#### 智能词组转换
+
+该库支持基于上下文的智能词组转换,能够正确处理「一简多繁」的情况:
+
+```javascript
+const { traditionalized } = stcasc();
+
+// 智能识别词组边界
+console.log(traditionalized('香烟袅袅'));
+// 输出: 香煙裊裊
+
+console.log(traditionalized('里长面子'));
+// 输出: 里長面子(「里长」是职务名称)
+
+console.log(traditionalized('吃干面'));
+// 输出: 吃乾麵(「干面」是食物)
+
+console.log(traditionalized('把考卷发回来'));
+// 输出: 把考卷發回來(「发」是动词)
+
+console.log(traditionalized('卷发'));
+// 输出: 捲髮(「卷发」是发型)
+```
+
+#### 术语转换
+
+内置常用的大陆简体与台湾正体术语转换:
+
+```javascript
+const { traditionalized } = stcasc();
+
+console.log(traditionalized('知识产权'));
+// 输出: 智慧財產權
+
+console.log(traditionalized('计算机软件'));
+// 输出: 計算機軟體
+
+console.log(traditionalized('网络服务器'));
+// 输出: 網路伺服器
+```
+
+### 技术特点
+
+#### 高性能
+
+- 使用字典树(Trie)优化词组匹配
+- 支持缓存机制,避免重复初始化
+- 纯 JavaScript 实现,执行效率高
+
+#### 准确转换
+
+- 内置大量简繁对照字符
+- 支持「一简多繁」智能识别
+- 基于词组的上下文分析
+
+#### 易于集成
+
+- ES Module 标准导出
+- TypeScript 类型支持(通过常量枚举)
+- 零依赖,兼容性好
+
+### 浏览器支持
+
+支持所有现代浏览器及 Node.js 环境:
+
+- Chrome
+- Firefox
+- Safari
+- Edge
+- Node.js 12+
+
+### 开源协议
+
+MIT License
+
+### 相关链接
+
+- [GitHub 仓库](https://github.com/hoothin/UserScripts/tree/master/Switch%20Traditional%20Chinese%20and%20Simplified%20Chinese/lib)
+- [NPM 包地址](https://www.npmjs.com/package/switch-chinese)
+- [问题反馈](https://github.com/hoothin/UserScripts/issues)
+
+### 关键词
+
+简繁转换, 繁简转换, 简体中文, 繁体中文, 正体中文, 中文转换, 一简多繁, 简繁切换, 繁简切换, 中文检测, 智能分词, 自定义词库, 零依赖, 轻量级
+
---
-``` shell
+
+## English
+
+Lightweight Chinese converter library for bidirectional conversion between Simplified and Traditional Chinese with intelligent word segmentation and one-to-many character mapping support.
+
+[](https://www.npmjs.com/package/switch-chinese) [](https://www.npmjs.com/package/switch-chinese)
+
+### Features
+
+- **Lightweight**: Zero dependencies, small footprint, excellent performance
+- **Intelligent Conversion**: Context-aware word segmentation and accurate one-to-many character mapping
+- **Multiple Data Types**: Supports string, array, and object conversion with automatic recursive processing
+- **Custom Dictionary**: User-defined conversion rules support
+- **Caching Mechanism**: Dictionary caching to avoid repeated initialization
+- **Text Detection**: Automatic detection of Simplified Chinese, Traditional Chinese, or unknown text
+- **Term Conversion**: Built-in mainland China and Taiwan terminology mapping
+- **Zero Dependencies**: Pure JavaScript implementation, no third-party dependencies required
+
+### Installation
+
+Install via npm:
+
+```bash
npm install switch-chinese
```
-演示
+Install via yarn:
+
+```bash
+yarn add switch-chinese
+```
+
+### Quick Start
+
+#### Basic Usage
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const { traditionalized, simplized, detect } = stcasc();
+
+// Simplified to Traditional
+const tc = traditionalized('简繁转换 繁简切换 香烟 香烟袅袅');
+console.log(tc);
+// Output: 簡繁轉換 繁簡切換 香菸 香煙裊裊
+
+// Traditional to Simplified
+const sc = simplized('繁體中文');
+console.log(sc);
+// Output: 繁体中文
+```
+
+#### Text Detection
+
+```javascript
+import stcasc, { ChineseType } from 'switch-chinese';
+
+const { detect } = stcasc();
+
+const type1 = detect('简体中文');
+if (type1 === ChineseType.SIMPLIFIED) {
+ console.log('Detected Simplified Chinese');
+}
+
+const type2 = detect('繁體中文');
+if (type2 === ChineseType.TRADITIONAL) {
+ console.log('Detected Traditional Chinese');
+}
+
+const type3 = detect('English');
+if (type3 === ChineseType.UNKNOWN) {
+ console.log('No Chinese text detected');
+}
+```
+
+ChineseType enumeration values:
+- `ChineseType.SIMPLIFIED` (0): Simplified Chinese
+- `ChineseType.TRADITIONAL` (1): Traditional Chinese
+- `ChineseType.UNKNOWN` (2): Unknown type
+
+#### Converting Arrays and Objects
+
+The library supports converting all strings in arrays and objects, while preserving non-string values:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const { traditionalized, simplized } = stcasc();
+
+// Convert array
+const arr = ['简体中文', '软件', '网络', 123, true, null];
+const arrTc = traditionalized(arr);
+console.log(arrTc);
+// Output: ['簡體中文', '軟體', '網路', 123, true, null]
+
+// Convert object
+const obj = {
+ title: '简体中文标题',
+ description: '这是一个简体中文描述',
+ count: 100,
+ active: true,
+ tags: ['软件', '网络', '服务器']
+};
+const objTc = traditionalized(obj);
+console.log(objTc);
+// Output: {
+// title: '簡體中文標題',
+// description: '這是一個簡體中文描述',
+// count: 100,
+// active: true,
+// tags: ['軟體', '網路', '伺服器']
+// }
+
+// Convert nested structures
+const nested = {
+ user: {
+ name: '简体名称',
+ profile: {
+ bio: '这是简体中文简介',
+ skills: ['软件开发', '网络管理']
+ }
+ },
+ count: 42
+};
+const nestedTc = traditionalized(nested);
+// All string property values will be converted, other types remain unchanged
+```
+
+### Advanced Usage
+
+#### Performance Optimization with Caching
+
+For multiple conversions, use caching mechanism to improve performance:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+// First call, generate dictionary
+let converter = stcasc();
+const cache = converter.cache;
+
+// Save cache to persistent storage (e.g., localStorage, file system)
+localStorage.setItem('stcasc-cache', JSON.stringify(cache));
+
+// Subsequent calls, use cached data
+const cachedData = JSON.parse(localStorage.getItem('stcasc-cache'));
+converter = stcasc(cachedData);
+
+// Now you can use it directly without regenerating dictionary
+const result = converter.traditionalized('简体中文');
+```
+
+#### Custom Conversion Dictionary
+
+Customize conversion rules according to your needs:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const customDict = {
+ '身份': '身分',
+ '转义': '跳脫',
+ '转换': '轉檔',
+ '软件': '軟體',
+ '硬件': '硬體',
+ '网络': '網路',
+ '服务器': '伺服器'
+};
+
+const { traditionalized, simplized } = stcasc({}, customDict);
+
+console.log(traditionalized('软件转换'));
+// Output: 軟體轉檔 (using custom dictionary)
+```
+
+#### Disable Term Conversion
+
+By default, the library converts specific terms (e.g., "知识产权" → "智慧財產權"). To disable this feature:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+// Set the third parameter to true to disable term conversion
+const { traditionalized } = stcasc({}, {}, true);
+
+console.log(traditionalized('知识产权'));
+// Output: 知識産權 (character conversion only, no term conversion)
+```
+
+#### Output Formats
+
+The library supports multiple output formats:
+
+```javascript
+import stcasc, { OutputFormat } from 'switch-chinese';
+
+const { traditionalized } = stcasc();
+
+// Normal format (default)
+const normal = traditionalized('简体中文');
+// Output: 簡體中文
+
+// Bracket format: shows both original and converted text
+const bracket = traditionalized('简体中文', { format: OutputFormat.BRACKET });
+// Output: 簡(简)體(体)中文
+
+// Ruby annotation format: for HTML display
+const ruby = traditionalized('简体中文', { format: OutputFormat.RUBY });
+// Output: 簡體中文
+```
+
+OutputFormat enumeration values:
+- `OutputFormat.NORMAL` (0): Output converted result only (default)
+- `OutputFormat.BRACKET` (1): Output in "converted(original)" format
+- `OutputFormat.RUBY` (2): Output in `` HTML tag format
+
+### API Reference
+
+#### stcasc(cache?, custom?, disableTerms?)
+
+Main function to create a converter instance.
+
+**Parameters:**
+
+- `cache` (Object, optional): Cache object to avoid regenerating dictionary
+- `custom` (Object, optional): Custom Simplified-Traditional conversion dictionary
+- `disableTerms` (Boolean, optional): Whether to disable term conversion, default `false`
+
+**Returns:**
+
+An object containing the following methods:
+
+- `traditionalized(input, options?)`: Convert Simplified Chinese to Traditional Chinese
+ - `input`: Can be a string, array, or object
+ - String: Directly converts and returns a new string
+ - Array: Converts all string elements, other types remain unchanged
+ - Object: Recursively converts all string property values, other types remain unchanged
+- `simplized(input, options?)`: Convert Traditional Chinese to Simplified Chinese
+ - `input`: Can be a string, array, or object
+ - Supports the same data types as `traditionalized`
+- `detect(text)`: Detect Chinese text type, returns ChineseType enumeration value
+- `cache`: Dictionary cache object
+
+**Options parameter:**
+
+- `format` (Number, optional): Output format, use `OutputFormat` constants
+
+#### ChineseType
+
+Exported constant object representing text detection results:
+
+```javascript
+export const ChineseType = {
+ SIMPLIFIED: 0, // Simplified Chinese
+ TRADITIONAL: 1, // Traditional Chinese
+ UNKNOWN: 2 // Unknown type
+};
+```
+
+#### OutputFormat
+
+Exported constant object representing output format options:
+
+```javascript
+export const OutputFormat = {
+ NORMAL: 0, // Output converted result only
+ BRACKET: 1, // Output "converted(original)" format
+ RUBY: 2 // Output HTML tag format
+};
+```
+
+### Conversion Examples
+
+#### Intelligent Word Segmentation
+
+The library supports context-aware intelligent word segmentation, accurately handling one-to-many character mappings:
+
+```javascript
+const { traditionalized } = stcasc();
+
+// Intelligent phrase boundary recognition
+console.log(traditionalized('香烟袅袅'));
+// Output: 香煙裊裊
+
+console.log(traditionalized('里长面子'));
+// Output: 里長面子 ("里长" is a position title)
+
+console.log(traditionalized('吃干面'));
+// Output: 吃乾麵 ("干面" is a food)
+
+console.log(traditionalized('把考卷发回来'));
+// Output: 把考卷發回來 ("发" is a verb in this context)
+
+console.log(traditionalized('卷发'));
+// Output: 捲髮 ("卷发" is a hairstyle)
+```
+
+#### Term Conversion
+
+Built-in conversion for common mainland China and Taiwan terminology:
+
+```javascript
+const { traditionalized } = stcasc();
+
+console.log(traditionalized('知识产权'));
+// Output: 智慧財產權
+
+console.log(traditionalized('计算机软件'));
+// Output: 計算機軟體
+
+console.log(traditionalized('网络服务器'));
+// Output: 網路伺服器
+```
+
+### Technical Highlights
+
+#### High Performance
+
+- Dictionary tree (Trie) optimization for phrase matching
+- Caching mechanism support to avoid repeated initialization
+- Pure JavaScript implementation for high execution efficiency
+
+#### Accurate Conversion
+
+- Extensive built-in Simplified-Traditional character mappings
+- Intelligent recognition for one-to-many character conversions
+- Context-based phrase analysis
+
+#### Easy Integration
+
+- ES Module standard export
+- TypeScript support through constant enumerations
+- Zero dependencies, excellent compatibility
+
+### Browser Support
+
+Supports all modern browsers and Node.js environments:
+
+- Chrome
+- Firefox
+- Safari
+- Edge
+- Node.js 12+
+
+### License
+
+MIT License
+
+### Links
+
+- [GitHub Repository](https://github.com/hoothin/UserScripts/tree/master/Switch%20Traditional%20Chinese%20and%20Simplified%20Chinese/lib)
+- [NPM Package](https://www.npmjs.com/package/switch-chinese)
+- [Issue Tracker](https://github.com/hoothin/UserScripts/issues)
+
+### Keywords
+
+Chinese Converter, Simplified Chinese, Traditional Chinese, Chinese Translation, Character Detection, Text Converter, Language Converter, i18n, Localization, Ruby Annotation, One-to-Many Mapping, Intelligent Word Segmentation, Custom Dictionary, Zero Dependencies, Lightweight
+
---
-+ 基礎用法
-``` js
-const stcasc = Stcasc();
-const sc = "简繁转换 繁简切换 香烟 香烟袅袅 烟雾里 里长面子 吃干面 干 把考卷发回来 卷发 知识产权";
-const tc = stcasc.traditionalized(sc);
+## 繁體中文
+
+簡繁體中文智能轉換庫 - 支援簡體中文與正體中文雙向轉換,基於詞組的智能分詞處理「一簡多繁」問題
+
+[](https://www.npmjs.com/package/switch-chinese) [](https://www.npmjs.com/package/switch-chinese)
+
+### 特色
+
+- **輕量級**:零依賴,體積小,效能優異
+- **智能轉換**:支援基於詞組的智能分詞和「一簡多繁」精準轉換
+- **多種資料類型**:支援字串、陣列、物件的轉換,自動遞迴處理巢狀結構
+- **自訂詞庫**:允許使用者自訂簡繁轉換詞彙
+- **快取機制**:支援字典快取,避免重複初始化
+- **簡繁檢測**:自動檢測文字是簡體中文、繁體中文還是未知類型
+- **術語轉換**:內建大陸簡體與台灣正體的常用術語轉換
+- **無依賴**:純 JavaScript 實現,無需任何第三方依賴
+
+### 安裝
+
+使用 npm 安裝:
+
+```bash
+npm install switch-chinese
+```
+
+使用 yarn 安裝:
+
+```bash
+yarn add switch-chinese
+```
+
+### 快速開始
+
+#### 基礎用法
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const { traditionalized, simplized, detect } = stcasc();
+
+// 簡體轉繁體
+const tc = traditionalized('简繁转换 繁简切换 香烟 香烟袅袅');
console.log(tc);
-//簡繁轉換 繁簡切換 香菸 香煙裊裊 煙霧裡 里長面子 吃乾麵 幹 把考卷發回來 捲髮 智慧財產權
+// 輸出: 簡繁轉換 繁簡切換 香菸 香煙裊裊
+
+// 繁體轉簡體
+const sc = simplized('繁體中文');
+console.log(sc);
+// 輸出: 繁体中文
+```
+
+#### 檢測中文類型
+
+```javascript
+import stcasc, { ChineseType } from 'switch-chinese';
+
+const { detect } = stcasc();
+
+const type1 = detect('简体中文');
+if (type1 === ChineseType.SIMPLIFIED) {
+ console.log('檢測到簡體中文');
+}
+
+const type2 = detect('繁體中文');
+if (type2 === ChineseType.TRADITIONAL) {
+ console.log('檢測到繁體中文');
+}
+
+const type3 = detect('English');
+if (type3 === ChineseType.UNKNOWN) {
+ console.log('未檢測到中文');
+}
+```
+
+ChineseType 列舉值:
+- `ChineseType.SIMPLIFIED` (0): 簡體中文
+- `ChineseType.TRADITIONAL` (1): 繁體中文
+- `ChineseType.UNKNOWN` (2): 未知類型
+
+#### 轉換陣列和物件
+
+函式庫支援轉換陣列和物件中的所有字串,非字串值保持原樣:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+const { traditionalized, simplized } = stcasc();
+
+// 轉換陣列
+const arr = ['简体中文', '软件', '网络', 123, true, null];
+const arrTc = traditionalized(arr);
+console.log(arrTc);
+// 輸出: ['簡體中文', '軟體', '網路', 123, true, null]
+
+// 轉換物件
+const obj = {
+ title: '简体中文标题',
+ description: '这是一个简体中文描述',
+ count: 100,
+ active: true,
+ tags: ['软件', '网络', '服务器']
+};
+const objTc = traditionalized(obj);
+console.log(objTc);
+// 輸出: {
+// title: '簡體中文標題',
+// description: '這是一個簡體中文描述',
+// count: 100,
+// active: true,
+// tags: ['軟體', '網路', '伺服器']
+// }
+
+// 轉換巢狀結構
+const nested = {
+ user: {
+ name: '简体名称',
+ profile: {
+ bio: '这是简体中文简介',
+ skills: ['软件开发', '网络管理']
+ }
+ },
+ count: 42
+};
+const nestedTc = traditionalized(nested);
+// 所有字串屬性值都會被轉換,數字等其他類型保持不變
```
+### 進階用法
+
+#### 使用快取最佳化效能
+
+在多次呼叫時,建議使用快取機制避免重複生成字典,提升效能:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+// 第一次呼叫,生成字典
+let converter = stcasc();
+const cache = converter.cache;
+
+// 將快取儲存至持久化儲存(如 localStorage、檔案等)
+localStorage.setItem('stcasc-cache', JSON.stringify(cache));
+
+// 後續呼叫,直接使用快取
+const cachedData = JSON.parse(localStorage.getItem('stcasc-cache'));
+converter = stcasc(cachedData);
+
+// 現在可以直接使用,無需重新生成字典
+const result = converter.traditionalized('简体中文');
+```
+
+#### 自訂簡繁轉換詞庫
+
+可以根據業務需求自訂簡繁轉換規則:
-+ Import
+```javascript
+import stcasc from 'switch-chinese';
-``` shell
-import Stcasc from 'switch-chinese';
+const customDict = {
+ '身份': '身分',
+ '转义': '跳脫',
+ '转换': '轉檔',
+ '软件': '軟體',
+ '硬件': '硬體',
+ '网络': '網路',
+ '服务器': '伺服器'
+};
+
+const { traditionalized, simplized } = stcasc({}, customDict);
+
+console.log(traditionalized('软件转换'));
+// 輸出: 軟體轉檔(使用自訂詞庫)
```
-+ 轉正體中文
+#### 停用術語轉換
+
+預設情況下,函式庫會轉換一些特定術語(如「知识产权」→「智慧財產權」)。如需停用此功能:
+
+```javascript
+import stcasc from 'switch-chinese';
+
+// 第三個參數設定為 true 以停用術語轉換
+const { traditionalized } = stcasc({}, {}, true);
-``` js
-stcasc.traditionalized("简体中文");
-//簡體中文
+console.log(traditionalized('知识产权'));
+// 輸出: 知識産權(僅做字元轉換,不轉換術語)
```
-+ 轉簡體中文
+#### 輸出格式選項
+
+函式庫支援多種輸出格式:
+
+```javascript
+import stcasc, { OutputFormat } from 'switch-chinese';
+
+const { traditionalized } = stcasc();
+
+// 普通格式(預設)
+const normal = traditionalized('简体中文');
+// 輸出: 簡體中文
+
+// 括號格式:同時顯示原文和轉換後的文字
+const bracket = traditionalized('简体中文', { format: OutputFormat.BRACKET });
+// 輸出: 簡(简)體(体)中文
-``` js
-stcasc.simplized("繁體中文");
-//繁体中文
+// Ruby 注音格式:適用於 HTML 顯示
+const ruby = traditionalized('简体中文', { format: OutputFormat.RUBY });
+// 輸出: 簡體中文
```
-+ 添加快取,避免重複生成字典
+OutputFormat 列舉值:
+- `OutputFormat.NORMAL` (0): 只輸出轉換後的結果(預設)
+- `OutputFormat.BRACKET` (1): 輸出「轉換(原文)」格式
+- `OutputFormat.RUBY` (2): 輸出 `` HTML 標籤格式
-``` js
-let cache = loadCacheAtYourWay();
-let stcasc = Stcasc(cache);
-saveCacheAtYourWay(stcasc.cache);
+### API 文件
+
+#### stcasc(cache?, custom?, disableTerms?)
+
+主函式,用於建立轉換器實例。
+
+**參數:**
+
+- `cache` (Object, 可選): 快取物件,用於避免重複生成字典
+- `custom` (Object, 可選): 自訂簡繁轉換詞庫
+- `disableTerms` (Boolean, 可選): 是否停用術語轉換,預設 `false`
+
+**回傳值:**
+
+回傳包含以下方法的物件:
+
+- `traditionalized(input, options?)`: 將簡體中文轉換為繁體中文
+ - `input`: 可以是字串、陣列或物件
+ - 字串:直接轉換並回傳新字串
+ - 陣列:轉換所有字串元素,其他類型保持不變
+ - 物件:遞迴轉換所有字串屬性值,其他類型保持不變
+- `simplized(input, options?)`: 將繁體中文轉換為簡體中文
+ - `input`: 可以是字串、陣列或物件
+ - 支援的資料類型同 `traditionalized`
+- `detect(text)`: 檢測文字的中文類型,回傳 ChineseType 列舉值
+- `cache`: 字典快取物件
+
+**Options 參數:**
+
+- `format` (Number, 可選): 輸出格式,使用 `OutputFormat` 常數
+
+#### ChineseType
+
+匯出的常數物件,用於表示中文類型檢測結果:
+
+```javascript
+export const ChineseType = {
+ SIMPLIFIED: 0, // 簡體中文
+ TRADITIONAL: 1, // 繁體中文
+ UNKNOWN: 2 // 未知類型
+};
```
-+ 自訂簡繁切換
+#### OutputFormat
+
+匯出的常數物件,用於表示輸出格式選項:
-``` js
-const custom = {
- "身份": "身分",
- "转义": "跳脫",
- "转换": "轉檔",
- "软件": "軟體"
+```javascript
+export const OutputFormat = {
+ NORMAL: 0, // 只輸出轉換後的結果
+ BRACKET: 1, // 輸出「轉換(原文)」格式
+ RUBY: 2 // 輸出 HTML 標籤格式
};
-const stcasc = Stcasc(cache, custom);
```
-+ 禁用用語轉換
+### 轉換範例
+
+#### 智能詞組轉換
+
+本函式庫支援基於上下文的智能詞組轉換,能夠正確處理「一簡多繁」的情況:
+
+```javascript
+const { traditionalized } = stcasc();
+
+// 智能識別詞組邊界
+console.log(traditionalized('香烟袅袅'));
+// 輸出: 香煙裊裊
+
+console.log(traditionalized('里长面子'));
+// 輸出: 里長面子(「里長」是職務名稱)
+
+console.log(traditionalized('吃干面'));
+// 輸出: 吃乾麵(「乾麵」是食物)
+
+console.log(traditionalized('把考卷发回来'));
+// 輸出: 把考卷發回來(「發」是動詞)
+
+console.log(traditionalized('卷发'));
+// 輸出: 捲髮(「捲髮」是髮型)
+```
+
+#### 術語轉換
+
+內建常用的大陸簡體與台灣正體術語轉換:
+
+```javascript
+const { traditionalized } = stcasc();
+
+console.log(traditionalized('知识产权'));
+// 輸出: 智慧財產權
+
+console.log(traditionalized('计算机软件'));
+// 輸出: 計算機軟體
-``` js
-const stcasc = Stcasc({}, {}, true);
-const sc = "知识产权";
-console.log(stcasc.traditionalized(sc));
-//知識産權
+console.log(traditionalized('网络服务器'));
+// 輸出: 網路伺服器
```
+
+### 技術特點
+
+#### 高效能
+
+- 使用字典樹(Trie)最佳化詞組比對
+- 支援快取機制,避免重複初始化
+- 純 JavaScript 實現,執行效率高
+
+#### 精準轉換
+
+- 內建大量簡繁對照字元
+- 支援「一簡多繁」智能識別
+- 基於詞組的上下文分析
+
+#### 易於整合
+
+- ES Module 標準匯出
+- TypeScript 類型支援(透過常數列舉)
+- 零依賴,相容性佳
+
+### 瀏覽器支援
+
+支援所有現代瀏覽器及 Node.js 環境:
+
+- Chrome
+- Firefox
+- Safari
+- Edge
+- Node.js 12+
+
+### 開源授權
+
+MIT License
+
+### 相關連結
+
+- [GitHub 儲存庫](https://github.com/hoothin/UserScripts/tree/master/Switch%20Traditional%20Chinese%20and%20Simplified%20Chinese/lib)
+- [NPM 套件位址](https://www.npmjs.com/package/switch-chinese)
+- [問題回報](https://github.com/hoothin/UserScripts/issues)
+
+### 關鍵字
+
+簡繁轉換, 繁簡轉換, 簡體中文, 繁體中文, 正體中文, 中文轉換, 一簡多繁, 簡繁切換, 繁簡切換, 中文檢測, 智能分詞, 自訂詞庫, 零依賴, 輕量級
diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts
new file mode 100644
index 00000000000..350324519b8
--- /dev/null
+++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.d.ts
@@ -0,0 +1,167 @@
+/**
+ * Chinese character type enumeration
+ */
+export declare const ChineseType: {
+ /** Simplified Chinese */
+ readonly SIMPLIFIED: 0;
+ /** Traditional Chinese */
+ readonly TRADITIONAL: 1;
+ /** Unknown type */
+ readonly UNKNOWN: 2;
+};
+
+/**
+ * Output format enumeration
+ */
+export declare const OutputFormat: {
+ /** Normal output - only the converted result */
+ readonly NORMAL: 0;
+ /** Bracket format - outputs「Simplified(Traditional)」or「Traditional(Simplified)」*/
+ readonly BRACKET: 1;
+ /** Ruby annotation format - outputs Simplified */
+ readonly RUBY: 2;
+};
+
+/**
+ * Conversion options
+ */
+export interface ConversionOptions {
+ /**
+ * Output format
+ * @default OutputFormat.NORMAL
+ */
+ format?: 0 | 1 | 2;
+}
+
+/**
+ * Tree node for combination dictionary
+ */
+interface CombTreeNode {
+ end?: string;
+ [key: string]: CombTreeNode | string | undefined;
+}
+
+/**
+ * Cache object for storing conversion dictionaries
+ */
+export interface ConversionCache {
+ sc2tcCombTree?: Record;
+ tc2scCombTree?: Record;
+ stDict?: Record;
+ tsDict?: Record;
+}
+
+/**
+ * Custom dictionary mapping
+ * Key: source text (simplified or traditional)
+ * Value: target text (can be string or array of strings for multiple mappings)
+ */
+export type CustomDictionary = Record;
+
+/**
+ * Result object returned by stcasc function
+ */
+export interface StcascConverter {
+ /**
+ * Convert traditional Chinese to simplified Chinese
+ * @param text - String to convert
+ * @param options - Conversion options
+ * @returns Converted simplified Chinese string
+ */
+ simplized(text: string, options?: ConversionOptions): string;
+
+ /**
+ * Convert traditional Chinese to simplified Chinese (array version)
+ * @param data - Array to convert (converts all strings in the array)
+ * @param options - Conversion options
+ * @returns Array with converted strings
+ */
+ simplized(data: T, options?: ConversionOptions): T;
+
+ /**
+ * Convert traditional Chinese to simplified Chinese (object version)
+ * @param data - Object to convert (converts all string property values)
+ * @param options - Conversion options
+ * @returns Object with converted string values
+ */
+ simplized>(data: T, options?: ConversionOptions): T;
+
+ /**
+ * Convert simplified Chinese to traditional Chinese
+ * @param text - String to convert
+ * @param options - Conversion options
+ * @returns Converted traditional Chinese string
+ */
+ traditionalized(text: string, options?: ConversionOptions): string;
+
+ /**
+ * Convert simplified Chinese to traditional Chinese (array version)
+ * @param data - Array to convert (converts all strings in the array)
+ * @param options - Conversion options
+ * @returns Array with converted strings
+ */
+ traditionalized(data: T, options?: ConversionOptions): T;
+
+ /**
+ * Convert simplified Chinese to traditional Chinese (object version)
+ * @param data - Object to convert (converts all string property values)
+ * @param options - Conversion options
+ * @returns Object with converted string values
+ */
+ traditionalized>(data: T, options?: ConversionOptions): T;
+
+ /**
+ * Detect Chinese text type
+ * @param text - Text to detect
+ * @returns ChineseType value (0=SIMPLIFIED, 1=TRADITIONAL, 2=UNKNOWN)
+ */
+ detect(text: string): 0 | 1 | 2;
+
+ /**
+ * Cached conversion dictionaries
+ */
+ cache: ConversionCache;
+}
+
+/**
+ * Initialize Chinese converter with optional cache and custom dictionary
+ *
+ * @param cache - Optional cache object to reuse conversion dictionaries
+ * @param custom - Optional custom dictionary for special term conversions
+ * @param disableTerms - If true, disables built-in term conversions
+ * @returns Converter object with conversion methods
+ *
+ * @example
+ * ```typescript
+ * import stcasc from 'switch-chinese';
+ *
+ * const converter = stcasc();
+ * const traditional = converter.traditionalized('简体中文');
+ * const simplified = converter.simplized('繁體中文');
+ * const type = converter.detect('繁體中文');
+ * ```
+ *
+ * @example
+ * ```typescript
+ * // With custom dictionary
+ * const converter = stcasc({}, {
+ * '自定义词': '自訂詞',
+ * '程序': ['程式', '程序']
+ * });
+ * ```
+ *
+ * @example
+ * ```typescript
+ * // With output format
+ * const converter = stcasc();
+ * const result = converter.traditionalized('简体', { format: 1 });
+ * // Output: 簡體(简体)
+ * ```
+ */
+declare function stcasc(
+ cache?: ConversionCache,
+ custom?: CustomDictionary,
+ disableTerms?: boolean
+): StcascConverter;
+
+export default stcasc;
diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js
index 5ec6942ab14..2256dc7222a 100644
--- a/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js
+++ b/Switch Traditional Chinese and Simplified Chinese/lib/stcasc.lib.js
@@ -1,4 +1,16 @@
'use strict';
+export const ChineseType = {
+ SIMPLIFIED: 0,
+ TRADITIONAL: 1,
+ UNKNOWN: 2
+};
+
+export const OutputFormat = {
+ NORMAL: 0, // 只输出转换后的结果
+ BRACKET: 1, // 输出「简(繁)」或「繁(简)」格式
+ RUBY: 2 // 输出 简 格式
+};
+
const scStr = '万与丑专业丛东丝丢两严丧个丰临为为丽举么么义乌乐乔习乡书买乱争于亏云亘亚产产亩亲亵亸亿仅仆从仑仓仪们价众众优伙会伛伞伟传伡伣伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬侭俣俦俨俩俪俫俭借债倾偬偻偾偿傤傥傧储傩儿克兑兖党兰关兴兹养兽冁内冈册冗写军农冢冯冲冲决况冻净凄准凉凌减凑凛几凤处凫凭凯凶击凿刍划刘则刚创删别刬刭制刹刽刾刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勖勚匀匦匮区医华协单卖卜卢卤卧卫却卷厂厅历历厉压厌厍厐厕厘厠厢厣厦厨厩厮县叁参叆叇双发发变叙叠只台叶号叹叽吁后吓吕吗吨听启吴呆呐呒呓呕呖呗员呙呛呜周咏咙咛咝咤咨咸响哑哒哓哔哕哗哙哜哝哟唇唉唛唝唠唡唢唤啧啬啭啮啯啰啴啸喂喷喽喾嗫嗳嘘嘤嘱噜嚣团园囱围囵国图圆圣圹场坏块坚坛坛坛坛坜坝坞坟坠垄垅垆垒垦垩垫垭垯垱垲垴埘埙埚堑堕塆墙墻壊壮声壳壶壸処备复复够头夸夹夺奁奂奋奖奥奬妆妇妈妩妪妫姗姜姹娄娅娆娇娈娱娲娴婳婴婵婶媪媭嫒嫔嫱嬀嬷孙学孪宁宁宝实宠审宪宫宽宽宾寝对寻导寿将尔尘尝尧尴尸尽尽层屃屉届属屡屦屿岁岂岖岗岘岚岛岩岭岳岽岿峃峄峡峣峤峥峦峰崂崃崄崭嵘嵚嵝巅巨巩巯币布帅师帏帐帘帜带帧帮帱帻帼幂干干并并广庄庆庐庑库应庙庞废庼廏廪开异弃弑张弥弪弯弹强归当录彝彟彦彨彻征径徕御忆忏志忧念忾怀态怂怃怄怅怆怜总怼怿恋恒恳恶恶恸恹恺恻恼恽悦悫悬悭悮悯惊惧惨惩惫惬惭惮惯愠愤愦愿慑慭懑懒懔戆戋戏戗战戬户扎扑托扦执扩扪扫扬扰抚抛抟抠抡抢护报抬抻担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捂捝捞损捡换捣据捻掳掴掷掸掺掼揽揾揿搀搁搂搄搅携摄摅摆摇摈摊撄撑撵撷撸撺擜擞攒敌敍敚敛敩数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权杠条来杨杩杰松板极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桠桡桢档桤桥桦桧桨桩桪梁梦梼梾梿检棁棂棱椁椝椟椠椢椤椫椭椮楼榄榅榇榈榉榝槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴殻毁毂毕毙毡毵氇气氢氩氲汇汇汉污汤汹沟没沣沤沥沦沧沨沩沪泄泞注泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涚涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溁溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪潆潇潋潍潙潜潨潴澛澜濑濒灏灭灯灵灶灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烟烦烧烨烩烫烬热焕焖焘煴爱爷牍牦牵牺犊状犷犸犹狈狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珐珑珰珲琎琏琐琼瑶瑷瑸璎瓒瓮瓯産电画畅畴疖疗疟疠疡疬疭疮疯疱疴痈痉痒痖痨痪痫痴痹瘅瘆瘉瘗瘘瘪瘫瘾瘿癞癣癫皋皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑睾瞆瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硵硷碍碛碜碱礼祃祎祢祯祷祸禀禄禅离秃秆种秘积称秸秽秾稆税稣稳穑穞穷窃窍窎窑窜窝窥窦窭竖竞竪笃笋笔笕笺笼笾筑筚筛筜筝筹筼签签筿简箓箦箧箨箩箪箫篑篓篮篯篱簖籁籴类籼粜粝粤粪粮糁糇糍系系紧绝絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络絶绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡群翘翙翚翱耢耧耸耻聂聋职聍联聩聪肃肠肤肮肴肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑膻臜致舆舍舣舰舱舻艰艳艺节芈芗芜芦苁苇苈苋苌苍苎苏苧苹范茎茏茑茔茕茧荆荐荙荚荛荜荝荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒀蒇蒉蒋蒌蒏蓝蓟蓠蓣蓥蓦蔂蔷蔹蔺蔼蕰蕲蕴薮藓蘖虏虑虚虫虬虮虱虽虾虿蚀蚁蚂蚃蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衆衔补表衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯訚詟誉誊说说谣讠计订讣认讥讦讧讨让讪讫讬训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谉谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贜贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞跡践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯軆輼车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辟辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郏郐郑郓郦郧郸酂酝酦酱酽酾酿采释里里鈎鉴鉴銮鋭録錾钅钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钜钝钞钟钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铇铈铉铊铋铌铍铎铏铐铑铒铓铔铕铖铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铩铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链链铿销锁锂锃锄锅锆锇锈锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗锘错锚锛锜锝锞锟锠锡锢锣锤锥锦锧锨锩锪锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镄镅镆镇镈镉镊镋镌镍镎镏镐镑镒镓镔镕镖镗镘镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镵镶长门闩闪闫闬闭问闯闰闱闲闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陦陧陨险随隐隶隽难雇雏雠雳雾霁霉霡霭靓靔静面靥鞑鞒鞯鞲韦韧韨韩韪韫韬韵頽页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饣饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕駡马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓鬶魇魉鱼鱽鱾鱿鲀鲁鲂鲃鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鳤鷀鷄鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹙鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹮鹯鹰鹱鹲鹳鹴鹾麦麸麹麽黄黉黡黩黪黾鼋鼌鼍鼹齐齑齿龀龁龂龃龄龅龆龇龈龉龊龋龌龙龚龛龟酸';
const tcStr = '萬與醜專業叢東絲丟兩嚴喪個豐臨為爲麗舉麽麼義烏樂喬習鄉書買亂爭於虧雲亙亞産產畝親褻嚲億僅僕從侖倉儀們價衆眾優夥會傴傘偉傳俥俔傷倀倫傖僞佇體餘傭僉俠侶僥偵側僑儈儕儂儘俁儔儼倆儷倈儉藉債傾傯僂僨償儎儻儐儲儺兒剋兌兗黨蘭關興茲養獸囅內岡冊宂寫軍農塚馮沖衝決況凍淨淒準涼淩減湊凜幾鳳處鳧憑凱兇擊鑿芻劃劉則剛創刪別剗剄製剎劊㓨劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳勗勩勻匭匱區醫華協單賣蔔盧鹵臥衛卻捲廠廳曆歷厲壓厭厙龎廁釐廁廂厴廈廚廄廝縣叄參靉靆雙發髮變敘疊隻臺葉號嘆嘰籲後嚇呂嗎噸聽啟吳獃吶嘸囈嘔嚦唄員咼嗆嗚週詠嚨嚀噝吒諮鹹響啞噠嘵嗶噦嘩噲嚌噥喲脣欸嘜嗊嘮啢嗩喚嘖嗇囀齧嘓囉嘽嘯餵噴嘍嚳囁噯噓嚶囑嚕囂團園囪圍圇國圖圓聖壙場壞塊堅壇罎罈壜壢壩塢墳墜壟壠壚壘墾堊墊埡墶壋塏堖塒壎堝塹墮壪牆牆壞壯聲殼壺壼處備複復夠頭誇夾奪奩奐奮獎奧獎妝婦媽嫵嫗媯姍薑奼婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬃嬡嬪嬙媯嬤孫學孿寧甯寶實寵審憲宮寬寛賓寢對尋導壽將爾塵嘗堯尷屍盡儘層屭屜屆屬屢屨嶼歲豈嶇崗峴嵐島巖嶺嶽崬巋嶨嶧峽嶢嶠崢巒峯嶗崍嶮嶄嶸嶔嶁巔鉅鞏巰幣佈帥師幃帳簾幟帶幀幫幬幘幗冪幹乾並併廣莊慶廬廡庫應廟龐廢廎廄廩開異棄弒張彌弳彎彈強歸當錄彜彠彥彲徹徵徑徠禦憶懺誌憂唸愾懷態慫憮慪悵愴憐總懟懌戀恆懇惡噁慟懨愷惻惱惲悅愨懸慳悞憫驚懼慘懲憊愜慚憚慣慍憤憒願懾憖懣懶懍戇戔戲戧戰戩戶紮撲託扡執擴捫掃揚擾撫拋摶摳掄搶護報擡捵擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏摀挩撈損撿換搗據撚擄摑擲撣摻摜攬搵撳攙擱摟揯攪攜攝攄擺搖擯攤攖撐攆擷擼攛㩵擻攢敵敘敓斂斆數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權槓條來楊榪傑鬆闆極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒椏橈楨檔榿橋樺檜槳樁樳樑夢檮棶槤檢梲櫺稜槨槼櫝槧槶欏樿橢槮樓欖榲櫬櫚櫸樧檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆殼毀轂畢斃氈毿氌氣氫氬氳彙匯漢汙湯洶溝沒灃漚瀝淪滄渢溈滬洩濘註淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧涗濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕濚潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦瀠瀟瀲濰溈潛潀瀦瀂瀾瀨瀕灝滅燈靈竈災燦煬爐燉煒熗點煉熾爍爛烴燭煙菸煩燒燁燴燙燼熱煥燜燾熅愛爺牘犛牽犧犢狀獷獁猶狽獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽琺瓏璫琿璡璉瑣瓊瑤璦璸瓔瓚甕甌產電畫暢疇癤療瘧癘瘍癧瘲瘡瘋皰痾癰痙癢瘂癆瘓癇癡痺癉瘮癒瘞瘻癟癱癮癭癩癬癲臯皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼睪瞶瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確磠鹼礙磧磣堿禮禡禕禰禎禱禍稟祿禪離禿稈種祕積稱稭穢穠穭稅穌穩穡穭窮竊竅窵窯竄窩窺竇窶豎競豎篤筍筆筧箋籠籩築篳篩簹箏籌篔簽籤篠簡籙簀篋籜籮簞簫簣簍籃籛籬籪籟糴類秈糶糲粵糞糧糝餱餈係繫緊絕縶糹糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺紲紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏縧繼綈績緒綾緓續綺緋綽鞝緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨羣翹翽翬翺耮耬聳恥聶聾職聹聯聵聰肅腸膚骯餚腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏羶臢緻輿捨艤艦艙艫艱豔藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇薴蘋範莖蘢蔦塋煢繭荊薦薘莢蕘蓽萴蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蒕蕆蕢蔣蔞醟藍薊蘺蕷鎣驀虆薔蘞藺藹薀蘄蘊藪蘚櫱虜慮虛蟲虯蟣蝨雖蝦蠆蝕蟻螞蠁蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁眾銜補錶襯袞襖裊褘襪襲襏裝襠褌褳襝褲襉褸襤襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶誾讋譽謄說説謡訁計訂訃認譏訐訌討讓訕訖託訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談讅誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗謚謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶贓貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗贊讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒蹟踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀體轀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭闢辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郟鄶鄭鄆酈鄖鄲酇醞醱醬釅釃釀採釋裏裡鉤鑒鑑鑾銳錄鏨釒釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鉅鈍鈔鍾鐘鈉鋇鋼鈑鈐鑰欽鈞鎢鈎鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鉋鈰鉉鉈鉍鈮鈹鐸鉶銬銠鉺鋩錏銪鋮鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鎩鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鍊鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銹銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺鍩錯錨錛錡鍀錁錕錩錫錮鑼錘錐錦鑕鍁錈鍃錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鐨鎇鏌鎮鎛鎘鑷钂鐫鎳鎿鎦鎬鎊鎰鎵鑌鎔鏢鏜鏝鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔钀鑞鑱鑲長門閂閃閆閈閉問闖閏闈閑閒閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隯隉隕險隨隱隸雋難僱雛讎靂霧霽黴霢靄靚靝靜麵靨韃鞽韉韝韋韌韍韓韙韞韜韻頹頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顔顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飈飛饗饜飠飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢罵馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢鬹魘魎魚魛魢魷魨魯魴䰾魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鰺鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鼈鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣䲘鶿雞鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鷽鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鵾鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶖鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺䴉鸇鷹鸌鸏鸛鸘鹺麥麩麴麼黃黌黶黷黲黽黿鼂鼉鼴齊齏齒齔齕齗齟齡齙齠齜齦齬齪齲齷龍龔龕龜痠';
const oc2tc = {
@@ -84,10 +96,6 @@ const sc2tc = {
'栗',
['慄','战栗','颤栗','不寒而栗']
],
- '凄':[
- '淒',
- ['悽','凄厉','凄惨','悲凄','凄苦']
- ],
'沈':[
'沈',
['瀋','沈阳']
@@ -321,7 +329,7 @@ const sc2tc = {
'干':[
'幹',
['乾','口干','吃干','吐干','吮干','吸干','吹干','呷干','喉干','喝干','嘴干','太干','干井','干似','干冰','干冷','干化','干咳','干咽','干品','干哥','干嚎','干土','干坤','干妹','干姊','干姐','干姜','干娘','干爹','干爸','干妈','干季','干巴','干布','干干','干式','干弟','干性','干料','干旱','干杯','干果','干枝','干枯','干柴','干梅','干沙','干泥','干洗','干涸','干渴','干焦','干熬','干燥','干爽','干球','干疤','干瘦','干眼','干瞪','干硬','干窘','干笑','干等','干粉','干耗','干肉','干股','干脆','干花','干草','干菜','干薪','干衣','干裂','干透','干酪','干醋','干隆','干面','弄干','很干','抹干','抽干','揩干','擦干','晾干','朝干','未干','杯干','果干','桑干','榨干','水干','流干','海干','滴干','炒干','烘干','烤干','焙干','焦干','煨干','熨干','略干','碗干','粉干','耗干','肉干','舔干','菜干','蒸干','速干','干儿','干哑','干呕','干坛','干孙','干尸','干搁','干晒','干净','干涩','干涧','干湿','干热','干烧','干瘪','干瘾','干发','干粮','干结','干丝','干声','干叶','干号','干货','干阳','干饭','拧干','晒干','极干','泪干','沥干','烧干','烩干','发干','笋干','绞干','阴干','难干','风干','饮干','饼干','鱼干','唇干'],
- ['干','干系','天干','干涉','干扰','干戈','相干']
+ ['干','干系','天干','干涉','干扰','干戈','相干','干我什','干我事','干我的事','干你什','干你事','干你的事','干他什','干他事','干他的事','干她什','干她事','干她的事']
],
'了':[
'了',
@@ -610,12 +618,12 @@ let sc2tcComb = {
var stDict = {}, tsDict = {};
var sc2tcCombTree = {}, tc2scCombTree = {};
-function traditionalized(orgStr) {
+function traditionalizedString(orgStr, format) {
if (!orgStr) return "";
var str = '', char;
for (var i = 0; i < orgStr.length; i++) {
char = orgStr.charAt(i);
- let search = sc2tcCombTree[char], searchIndex = i, hasMatch = false;
+ let search = sc2tcCombTree[char], searchIndex = i, hasMatch = false, startIndex = i;
while (search && searchIndex < orgStr.length) {
let downTree = null;
if (searchIndex < orgStr.length - 1) {
@@ -625,7 +633,19 @@ function traditionalized(orgStr) {
if (search.end) {
hasMatch = true;
i = searchIndex;
- str += search.end;
+ if (format === OutputFormat.NORMAL) {
+ str += search.end;
+ } else {
+ const originalText = orgStr.substring(startIndex, searchIndex + 1);
+ const convertedText = search.end;
+ if (format === OutputFormat.BRACKET && originalText !== convertedText) {
+ str += convertedText + '(' + originalText + ')';
+ } else if (format === OutputFormat.RUBY && originalText !== convertedText) {
+ str += '' + convertedText + '';
+ } else {
+ str += convertedText;
+ }
+ }
}
break;
}
@@ -657,11 +677,15 @@ function traditionalized(orgStr) {
var curOther = others[k], fadd = curOther.indexOf(char), badd = curOther.length - 1 - fadd, x = 0;
var processChar = char;
while (fadd-- > 0) {
- if (char_f[x]) processChar = char_f[x] + processChar;
+ if (!char_f[x]) break;
+ processChar = char_f[x] + processChar;
+ x++;
}
x = 0;
while (badd-- > 0) {
- if (char_b[x]) processChar += char_b[x];
+ if (!char_b[x]) break;
+ processChar += char_b[x];
+ x++;
}
if (processChar.indexOf(curOther) != -1) {
newChar = otherChar;
@@ -675,7 +699,13 @@ function traditionalized(orgStr) {
} else {
newChar = tChar;
}
- str += newChar;
+ if (format === OutputFormat.BRACKET && char !== newChar) {
+ str += newChar + '(' + char + ')';
+ } else if (format === OutputFormat.RUBY && char !== newChar) {
+ str += '' + newChar + '';
+ } else {
+ str += newChar;
+ }
} else str += char;
}
else str += char;
@@ -683,12 +713,44 @@ function traditionalized(orgStr) {
return str;
}
-function simplized(orgStr) {
+function traditionalized(input, options) {
+ options = options || {};
+ const format = options.format !== undefined ? options.format : OutputFormat.NORMAL;
+
+ // Handle null/undefined
+ if (input == null) return input;
+
+ // Handle string
+ if (typeof input === 'string') {
+ return traditionalizedString(input, format);
+ }
+
+ // Handle array
+ if (Array.isArray(input)) {
+ return input.map(item => traditionalized(item, options));
+ }
+
+ // Handle object
+ if (typeof input === 'object') {
+ const result = {};
+ for (const key in input) {
+ if (input.hasOwnProperty(key)) {
+ result[key] = traditionalized(input[key], options);
+ }
+ }
+ return result;
+ }
+
+ // Return other types as-is (number, boolean, etc.)
+ return input;
+}
+
+function simplizedString(orgStr, format) {
if (!orgStr) return "";
var str = '', char;
for (var i = 0; i < orgStr.length; i++) {
char = orgStr.charAt(i);
- let search = tc2scCombTree[char], searchIndex = i, hasMatch = false;
+ let search = tc2scCombTree[char], searchIndex = i, hasMatch = false, startIndex = i;
while (search && searchIndex < orgStr.length) {
let downTree = null;
if (searchIndex < orgStr.length - 1) {
@@ -698,7 +760,19 @@ function simplized(orgStr) {
if (search.end) {
hasMatch = true;
i = searchIndex;
- str += search.end;
+ if (format === OutputFormat.NORMAL) {
+ str += search.end;
+ } else {
+ const originalText = orgStr.substring(startIndex, searchIndex + 1);
+ const convertedText = search.end;
+ if (format === OutputFormat.BRACKET && originalText !== convertedText) {
+ str += convertedText + '(' + originalText + ')';
+ } else if (format === OutputFormat.RUBY && originalText !== convertedText) {
+ str += '' + convertedText + '';
+ } else {
+ str += convertedText;
+ }
+ }
}
break;
}
@@ -730,11 +804,15 @@ function simplized(orgStr) {
var curOther = others[k], fadd = curOther.indexOf(char), badd = curOther.length - 1 - fadd, x = 0;
var processChar = char;
while (fadd-- > 0) {
- if (char_f[x]) processChar = char_f[x] + processChar;
+ if (!char_f[x]) break;
+ processChar = char_f[x] + processChar;
+ x++;
}
x = 0;
while (badd-- > 0) {
- if (char_b[x]) processChar += char_b[x];
+ if (!char_b[x]) break;
+ processChar += char_b[x];
+ x++;
}
if (processChar.indexOf(curOther) != -1) {
newChar = otherChar;
@@ -748,7 +826,13 @@ function simplized(orgStr) {
} else {
newChar = sChar;
}
- str += newChar;
+ if (format === OutputFormat.BRACKET && char !== newChar) {
+ str += newChar + '(' + char + ')';
+ } else if (format === OutputFormat.RUBY && char !== newChar) {
+ str += '' + newChar + '';
+ } else {
+ str += newChar;
+ }
} else str += char;
}
else str += char;
@@ -756,85 +840,149 @@ function simplized(orgStr) {
return str;
}
-function Stcasc(cache, custom, disableTerms) {
- if (!cache) cache = {};
- if (cache.sc2tcCombTree && cache.tc2scCombTree) {
- sc2tcCombTree = cache.sc2tcCombTree;
+function simplized(input, options) {
+ options = options || {};
+ const format = options.format !== undefined ? options.format : OutputFormat.NORMAL;
+
+ // Handle null/undefined
+ if (input == null) return input;
+
+ // Handle string
+ if (typeof input === 'string') {
+ return simplizedString(input, format);
+ }
+
+ // Handle array
+ if (Array.isArray(input)) {
+ return input.map(item => simplized(item, options));
+ }
+
+ // Handle object
+ if (typeof input === 'object') {
+ const result = {};
+ for (const key in input) {
+ if (input.hasOwnProperty(key)) {
+ result[key] = simplized(input[key], options);
+ }
+ }
+ return result;
+ }
+
+ // Return other types as-is (number, boolean, etc.)
+ return input;
+}
+
+function detect(text) {
+ if (!text) return ChineseType.UNKNOWN;
+
+ for (let i = 0; i < text.length; i++) {
+ const char = text.charAt(i);
+ if (char.charCodeAt(0) > 10000) {
+ const scChar = tsDict[char];
+ if (scChar && scChar !== char) {
+ return ChineseType.TRADITIONAL;
+ }
+ }
+ }
+
+ for (let i = 0; i < text.length; i++) {
+ const char = text.charAt(i);
+ if (char.charCodeAt(0) > 10000) {
+ const tcChar = stDict[char];
+ if (tcChar && tcChar !== char) {
+ return ChineseType.SIMPLIFIED;
+ }
+ }
+ }
+
+ return ChineseType.UNKNOWN;
+}
+
+function stcasc(cache, custom, disableTerms) {
+ if (!cache) cache = {};
+ if (cache.sc2tcCombTree && cache.tc2scCombTree) {
+ sc2tcCombTree = cache.sc2tcCombTree;
tc2scCombTree = cache.tc2scCombTree;
- } else {
+ } else {
if (disableTerms) sc2tcComb = {};
- if (custom && custom.length) {
- for (let sc in custom) {
- sc2tcComb[sc] = custom[sc];
- }
- }
- function makeCombTree(key, value) {
- let curTree = sc2tcCombTree;
- for (let i = 0; i < key.length; i++) {
- let newTree = {};
- if (i == key.length - 1) {
- newTree = {"end": value};
- }
- let curKey = key.charAt(i);
- let branch = curTree[curKey];
- if (!branch) {
- curTree[curKey] = newTree;
- curTree = newTree;
- } else {
- curTree = branch;
- }
- }
- curTree = tc2scCombTree;
- for (let i = 0; i < value.length; i++) {
- let newTree = {};
- if (i == value.length - 1) {
- newTree = {"end": key};
- }
- let curKey = value.charAt(i);
- let branch = curTree[curKey];
- if (!branch) {
- curTree[curKey] = newTree;
- curTree = newTree;
- } else {
- curTree = branch;
- }
- }
- }
- for (let key in sc2tcComb) {
- let value = sc2tcComb[key];
- if (Array.isArray(value)) {
- value.forEach(v => {
- makeCombTree(key, v);
- });
- } else {
- makeCombTree(key, value);
- }
- }
- cache.sc2tcCombTree = sc2tcCombTree;
+ if (custom && custom.length) {
+ for (let sc in custom) {
+ sc2tcComb[sc] = custom[sc];
+ }
+ }
+ function makeCombTree(key, value) {
+ let curTree = sc2tcCombTree;
+ for (let i = 0; i < key.length; i++) {
+ let curKey = key.charAt(i);
+ let branch = curTree[curKey];
+ let newTree = {};
+ if (i == key.length - 1) {
+ newTree = {"end": value};
+ if (branch) {
+ branch.end = value;
+ }
+ }
+ if (branch) {
+ curTree = branch;
+ } else {
+ curTree[curKey] = newTree;
+ curTree = newTree;
+ }
+ }
+ curTree = tc2scCombTree;
+ for (let i = 0; i < value.length; i++) {
+ let curKey = value.charAt(i);
+ let branch = curTree[curKey];
+ let newTree = {};
+ if (i == value.length - 1) {
+ newTree = {"end": key};
+ if (branch) {
+ branch.end = key;
+ }
+ }
+ if (branch) {
+ curTree = branch;
+ } else {
+ curTree[curKey] = newTree;
+ curTree = newTree;
+ }
+ }
+ }
+ for (let key in sc2tcComb) {
+ let value = sc2tcComb[key];
+ if (Array.isArray(value)) {
+ value.forEach(v => {
+ makeCombTree(key, v);
+ });
+ } else {
+ makeCombTree(key, value);
+ }
+ }
+ cache.sc2tcCombTree = sc2tcCombTree;
cache.tc2scCombTree = tc2scCombTree;
- }
- if (cache.stDict && cache.tsDict) {
- stDict = cache.stDict;
+ }
+ if (cache.stDict && cache.tsDict) {
+ stDict = cache.stDict;
tsDict = cache.tsDict;
- } else {
- for (let i = 0; i < scStr.length; i++) {
- let _sc = scStr[i];
- let _tc = tcStr[i];
- if (!stDict[_sc]) stDict[_sc] = _tc;
- if (!tsDict[_tc]) tsDict[_tc] = _sc;
- }
- Object.keys(oc2tc).forEach(key => {
- let ocList = oc2tc[key];
- for (let i = 0; i < ocList.length; i++) {
- let oc = ocList[i];
- stDict[oc] = key;
- tsDict[oc] = tsDict[key] || key;
- }
- })
- cache.stDict = stDict;
+ } else {
+ for (let i = 0; i < scStr.length; i++) {
+ let _sc = scStr[i];
+ let _tc = tcStr[i];
+ if (!stDict[_sc]) stDict[_sc] = _tc;
+ if (!tsDict[_tc]) tsDict[_tc] = _sc;
+ }
+ Object.keys(oc2tc).forEach(key => {
+ let ocList = oc2tc[key];
+ for (let i = 0; i < ocList.length; i++) {
+ let oc = ocList[i];
+ stDict[oc] = key;
+ tsDict[oc] = tsDict[key] || key;
+ }
+ })
+ cache.stDict = stDict;
cache.tsDict = tsDict;
- }
- return {simplized, traditionalized, cache};
+ }
+ return {simplized, traditionalized, detect, cache};
}
-export default Stcasc;
\ No newline at end of file
+export default stcasc;
diff --git a/Switch Traditional Chinese and Simplified Chinese/lib/test.js b/Switch Traditional Chinese and Simplified Chinese/lib/test.js
new file mode 100644
index 00000000000..55337889052
--- /dev/null
+++ b/Switch Traditional Chinese and Simplified Chinese/lib/test.js
@@ -0,0 +1,215 @@
+import stcasc, { ChineseType, OutputFormat } from './stcasc.lib.js';
+
+console.log('========== 简繁转换库测试 ==========\n');
+
+// 初始化
+const { traditionalized, simplized, detect } = stcasc();
+
+// 测试 1: 基础简体转繁体
+console.log('测试 1: 基础简体转繁体');
+const sc1 = '简繁转换 繁简切换 香烟 香烟袅袅 烟雾里 里长面子';
+const tc1 = traditionalized(sc1);
+console.log('输入:', sc1);
+console.log('输出:', tc1);
+console.log('');
+
+// 测试 2: 基础繁体转简体
+console.log('测试 2: 基础繁体转简体');
+const tc2 = '繁體中文 簡體中文';
+const sc2 = simplized(tc2);
+console.log('输入:', tc2);
+console.log('输出:', sc2);
+console.log('');
+
+// 测试 3: 检测简体中文
+console.log('测试 3: 检测简体中文');
+const text1 = '这是简体中文';
+const type1 = detect(text1);
+console.log('文本:', text1);
+console.log('类型:', type1 === ChineseType.SIMPLIFIED ? '简体中文' : '未知');
+console.log('枚举值:', type1);
+console.log('');
+
+// 测试 4: 检测繁体中文
+console.log('测试 4: 检测繁体中文');
+const text2 = '這是繁體中文';
+const type2 = detect(text2);
+console.log('文本:', text2);
+console.log('类型:', type2 === ChineseType.TRADITIONAL ? '繁体中文' : '未知');
+console.log('枚举值:', type2);
+console.log('');
+
+// 测试 5: 检测非中文
+console.log('测试 5: 检测非中文');
+const text3 = 'English Text';
+const type3 = detect(text3);
+console.log('文本:', text3);
+console.log('类型:', type3 === ChineseType.UNKNOWN ? '未知' : '中文');
+console.log('枚举值:', type3);
+console.log('');
+
+// 测试 6: 输出格式 - BRACKET(括号格式)
+console.log('测试 6: 输出格式 - BRACKET(括号格式)');
+const sc3 = '简体中文转换';
+const tc3 = traditionalized(sc3, { format: OutputFormat.BRACKET });
+console.log('输入:', sc3);
+console.log('输出:', tc3);
+console.log('');
+
+// 测试 7: 输出格式 - RUBY(HTML ruby标签格式)
+console.log('测试 7: 输出格式 - RUBY(HTML ruby标签格式)');
+const sc4 = '简体中文';
+const tc4 = traditionalized(sc4, { format: OutputFormat.RUBY });
+console.log('输入:', sc4);
+console.log('输出:', tc4);
+console.log('');
+
+// 测试 8: 繁体转简体 - BRACKET格式
+console.log('测试 8: 繁体转简体 - BRACKET格式');
+const tc5 = '繁體中文';
+const sc5 = simplized(tc5, { format: OutputFormat.BRACKET });
+console.log('输入:', tc5);
+console.log('输出:', sc5);
+console.log('');
+
+// 测试 9: 繁体转简体 - RUBY格式
+console.log('测试 9: 繁体转简体 - RUBY格式');
+const tc6 = '繁體中文';
+const sc6 = simplized(tc6, { format: OutputFormat.RUBY });
+console.log('输入:', tc6);
+console.log('输出:', sc6);
+console.log('');
+
+// 测试 10: 复杂文本转换 - 正常格式
+console.log('测试 10: 复杂文本转换 - 正常格式');
+const complex = '吃干面 把考卷发回来 卷发 知识产权';
+const complexTc = traditionalized(complex);
+console.log('输入:', complex);
+console.log('输出:', complexTc);
+console.log('');
+
+// 测试 11: 复杂文本转换 - BRACKET格式
+console.log('测试 11: 复杂文本转换 - BRACKET格式');
+const complexBracket = traditionalized(complex, { format: OutputFormat.BRACKET });
+console.log('输入:', complex);
+console.log('输出:', complexBracket);
+console.log('');
+
+// 测试 12: 术语转换
+console.log('测试 12: 术语转换');
+const terms = '软件 硬盘 网络 服务器 鼠标';
+const termsTc = traditionalized(terms);
+console.log('输入:', terms);
+console.log('输出:', termsTc);
+console.log('');
+
+// 测试 13: 混合文本
+console.log('测试 13: 混合文本');
+const mixed = 'This is 简体中文 and English';
+const mixedTc = traditionalized(mixed);
+console.log('输入:', mixed);
+console.log('输出:', mixedTc);
+console.log('');
+
+// ========== 新增测试:Array/Object 支持 ==========
+console.log('========== Array/Object 测试 ==========\n');
+
+// 测试 14: 转换数组(简体->繁体)
+console.log('测试 14: 转换数组(简体->繁体)');
+const arr1 = ['简体中文', '软件', '硬盘', 123, true, null];
+const arr1Tc = traditionalized(arr1);
+console.log('输入:', arr1);
+console.log('输出:', arr1Tc);
+console.log('');
+
+// 测试 15: 转换数组(繁体->简体)
+console.log('测试 15: 转换数组(繁体->简体)');
+const arr2 = ['繁體中文', '軟體', '硬碟', 456, false];
+const arr2Sc = simplized(arr2);
+console.log('输入:', arr2);
+console.log('输出:', arr2Sc);
+console.log('');
+
+// 测试 16: 转换对象(简体->繁体)
+console.log('测试 16: 转换对象(简体->繁体)');
+const obj1 = {
+ title: '简体中文标题',
+ description: '这是一个简体中文描述',
+ count: 100,
+ active: true,
+ tags: ['软件', '网络', '服务器']
+};
+const obj1Tc = traditionalized(obj1);
+console.log('输入:', JSON.stringify(obj1, null, 2));
+console.log('输出:', JSON.stringify(obj1Tc, null, 2));
+console.log('');
+
+// 测试 17: 转换对象(繁体->简体)
+console.log('测试 17: 转换对象(繁体->简体)');
+const obj2 = {
+ title: '繁體中文標題',
+ description: '這是一個繁體中文描述',
+ price: 99.99,
+ items: ['軟體', '硬碟']
+};
+const obj2Sc = simplized(obj2);
+console.log('输入:', JSON.stringify(obj2, null, 2));
+console.log('输出:', JSON.stringify(obj2Sc, null, 2));
+console.log('');
+
+// 测试 18: 转换嵌套数组
+console.log('测试 18: 转换嵌套数组');
+const nestedArr = [
+ '简体中文',
+ ['软件', '硬盘'],
+ [['网络', '服务器']]
+];
+const nestedArrTc = traditionalized(nestedArr);
+console.log('输入:', JSON.stringify(nestedArr));
+console.log('输出:', JSON.stringify(nestedArrTc));
+console.log('');
+
+// 测试 19: 转换嵌套对象
+console.log('测试 19: 转换嵌套对象');
+const nestedObj = {
+ user: {
+ name: '简体名称',
+ profile: {
+ bio: '这是简体中文简介',
+ skills: ['软件开发', '网络管理']
+ }
+ },
+ count: 42
+};
+const nestedObjTc = traditionalized(nestedObj);
+console.log('输入:', JSON.stringify(nestedObj, null, 2));
+console.log('输出:', JSON.stringify(nestedObjTc, null, 2));
+console.log('');
+
+// 测试 20: 数组转换 - BRACKET 格式
+console.log('测试 20: 数组转换 - BRACKET 格式');
+const arr3 = ['简体', '中文'];
+const arr3Bracket = traditionalized(arr3, { format: OutputFormat.BRACKET });
+console.log('输入:', arr3);
+console.log('输出:', arr3Bracket);
+console.log('');
+
+// 测试 21: 对象转换 - RUBY 格式
+console.log('测试 21: 对象转换 - RUBY 格式');
+const obj3 = {
+ text1: '简体',
+ text2: '中文'
+};
+const obj3Ruby = traditionalized(obj3, { format: OutputFormat.RUBY });
+console.log('输入:', JSON.stringify(obj3, null, 2));
+console.log('输出:', JSON.stringify(obj3Ruby, null, 2));
+console.log('');
+
+// 测试 22: 处理 null 和 undefined
+console.log('测试 22: 处理 null 和 undefined');
+console.log('null 输入:', traditionalized(null));
+console.log('undefined 输入:', traditionalized(undefined));
+console.log('包含 null 的数组:', traditionalized(['简体', null, '中文']));
+console.log('');
+
+console.log('========== 测试完成 ==========');
diff --git a/X-Downloader/X-Downloader.user.js b/X-Downloader/X-Downloader.user.js
index c488f1f1ef4..72531dfbd90 100644
--- a/X-Downloader/X-Downloader.user.js
+++ b/X-Downloader/X-Downloader.user.js
@@ -15,19 +15,20 @@
// @match https://twitter.com/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
-// @downloadURL https://update.greasyfork.org/scripts/545186/X-Downloader.user.js
-// @updateURL https://update.greasyfork.org/scripts/545186/X-Downloader.meta.js
+// @downloadURL https://update.greasyfork.org/scripts/545186/X-Downloader-Script.user.js
+// @updateURL https://update.greasyfork.org/scripts/545186/X-Downloader-Script.meta.js
// ==/UserScript==
(function() {
'use strict';
- let downloadBtn = document.createElement("a"), touch = false;
+ let downloadBtn = document.createElement("a"), touch = false, simpleClick = false;
downloadBtn.target = "_blank";
downloadBtn.style.cssText = "background: #000000aa; border-radius: 50%; transition: opacity ease 0.3s; position: absolute; top: 0; right: 0px; cursor: pointer; opacity: 0; padding: 5px;";
downloadBtn.innerHTML = ``;
downloadBtn.addEventListener("mousedown", e => {
let parent = downloadBtn.parentNode;
if (!parent) return;
+ simpleClick = false;
let img = parent.querySelector('[data-testid="tweetPhoto"]>img,[data-testid="card.layoutLarge.media"] img');
if (img) {
let newsrc = img.src.replace("_normal.",".").replace("_200x200.",".").replace("_mini.",".").replace("_bigger.",".").replace(/_x\d+\./,"."), imgname;
@@ -59,7 +60,8 @@
imgname = `${user.innerText} ${time.innerText.replace(/(.*) · (.*)/, "$2 $1")}.${ext}`;
}
downloadBtn.href = newsrc;
- if (e.altKey || touch) {
+ if ((e.button === 0 && !e.ctrlKey) || touch) {
+ simpleClick = true;
downloadByFetch(newsrc, imgname);
}
} else {
@@ -72,7 +74,7 @@
if (parent) {
downloadBtn.removeAttribute('download');
let link = parent.querySelector('a[role="link"][aria-label][href^="/"]');
- downloadBtn.href = `https://twitter.hoothin.com/?url=${encodeURIComponent(link ? link.href : document.location.href)}`;
+ downloadBtn.href = `https://twitter.luopo.org/?url=${encodeURIComponent(link ? link.href : document.location.href)}`;
if (e.altKey || touch) {
window.open(downloadBtn.href, "_blank");
}
@@ -80,7 +82,7 @@
}
});
downloadBtn.addEventListener("click", e => {
- if (e.altKey || touch) {
+ if (simpleClick || e.altKey || touch) {
e.preventDefault();
e.stopPropagation();
}