[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Ryan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# 梦想划词翻译（Chrome & Firefox 扩展程序）\n这是一款精心雕琢并免费开源的划词翻译扩展，也是一款致力于改善「中国式哑巴英语」而设计的一款英语练习扩展程序。\n\n### 扩展安装\n- Chrome 安装：[梦想划词翻译—聚合词典搜索 - Chrome 网上应用店](https://chrome.google.com/webstore/detail/odfgigmpkhhhieicogijogfobfipijjh)\n- Edge 安装：[梦想划词翻译—聚合词典搜索 - Microsoft Edge 扩展商店](https://microsoftedge.microsoft.com/addons/detail/ogleeipbbcokpeabliinildngejmdneg)\n- Firefox 安装：[梦想划词翻译—聚合词典搜索 – Firefox 扩展商店](https://addons.mozilla.org/zh-CN/firefox/addon/dream_translate/)\n\n### 程序特点\n考虑到程序的维护时间成本，本程序采用纯原生 javascript 开发，拥有更好的性能，同时占用内存更小，没有对各种技术栈的依赖，减少了 bug 的产生和维护时间的投入。\n\n自主设计的轻量级翻译查词对话框，占用内存小，且可拖动大小，移动位置，固定和全屏窗口，使用起来简单便捷，顺心应手。\n\n自主设计了大量字体图标，零图片使用，不仅减少了 IO 请求，内存占用也更低，还降低了程序业务逻辑和维护成本。\n\n程序代码短小精干，没有那么多花里胡哨的功能；也没有像剥洋葱一样的代码，读源码不会丈二和尚摸不着头脑。\n\n### 快捷键\n目前支持 6 个快捷键，为便于记忆，默认预设置 4 个快捷键。\n\n划词翻译开关 Ctrl+Shift+X 「常用，很多时候都不需要开启划词翻译，这时关闭就行；类似\"剪切\"快捷键，记忆技巧：剪一下开，剪一下关，剪一下开啊，剪一下关。」\n\n打开翻译窗口 Ctrl+Shift+9 「较常用，这是一个全局快捷键，在使用任何软件时，都可以通过这个快捷键召唤翻译小窗口；记忆技巧：久的谐音，长长久久。」（目前 Firefox 不支持全局快捷键）\n\n截图识别翻译 Ctrl+Shift+A 「较常用，对页面局部切图后，识别图片内容，然后进行翻译。」\n\n打开翻译面板 Alt+D 「不常用，一般通过鼠标点击就可以了，但也提供给喜欢使用快捷键人群一个方便；记忆技巧：字母 D 是 Dream 首字母，梦想成真！」\n\n剪贴板内容翻译 「需要自定义启用，从剪贴板获取文本内容进行翻译。」\n\n停止播放声音 「需要自定义启用，有时朗读声音时，想要停止播放，可以通过此快捷键，快速停止播放声音。」\n\n最后，如果这些快捷键和你电脑快捷键冲突了，那就只能自定义符合自己习惯的快捷键了。^_^\n\n## 聚合的词典&翻译\n### 在线翻译（国内）\n百度翻译 https://fanyi.baidu.com\n\n谷歌翻译 https://translate.google.cn\n\n必应翻译 https://cn.bing.com/translator\n\n有道翻译 https://fanyi.youdao.com\n\n腾讯翻译 https://fanyi.qq.com\n\n搜狗翻译 https://fanyi.sogou.com\n\n阿里翻译 https://translate.alibaba.com\n\n360翻译 https://fanyi.so.com\n\n### 在线翻译（国外）\nDeepL翻译 https://www.deepl.com/translator (维基百科：专家认为它比 Google 翻译更准确自然)\n\n### 在线词典（国内）\n有道词典 https://www.youdao.com\n\n必应词典 https://cn.bing.com/dict\n\n海词词典 https://dict.cn\n\n金山词典 https://www.iciba.com\n\n欧路词典 https://dict.eudic.net\n\n人人词典 https://www.91dict.com\n\n沪江词典 https://www.hjdict.com\n\n译典词典 https://www.dreye.com.cn\n\n### 在线词典（国外）\n朗文词典 https://www.ldoceonline.com\n\n剑桥词典 https://dictionary.cambridge.org\n\n韦氏词典 https://www.merriam-webster.com\n\n柯林斯词典 https://www.collinsdictionary.com\n\n牛津词典 https://www.oxfordlearnersdictionaries.com\n\n自由词典 https://www.thefreedictionary.com\n\n词曲词典 https://www.lexico.com\n\n在线词典 https://www.dictionary.com\n\n词汇词典 https://www.vocabulary.com\n\n语言词典 https://www.wordreference.com\n\n词源词典 https://www.etymonline.com\n\n城市词典 https://www.urbandictionary.com\n\n麦克米伦词典 https://www.macmillandictionary.com\n"
  },
  {
    "path": "release.sh",
    "content": "#!/bin/bash\n\nDIST=./dist\nDIST2=./dist_firefox\n\n# ======== Chrome & Edge ========\n# 拷贝\necho \"cp to ... $DIST\"\nmkdir -p $DIST\n\\cp -af ./src/ $DIST\n\n# 打包 (Chrome Edge)\nsed -i \"\" -e \"s/const isDebug = true/const isDebug = false/g\" $DIST/js/common.js\nzip -rq dist.zip $DIST\n\n# ======== Firefox ========\n# 拷贝\necho \"cp to ... $DIST2\"\nmkdir -p $DIST2\n\\cp -af ./src/ $DIST2\n\n# 替换\nsed -i \"\" -e \"s/const isDebug = true/const isDebug = false/g\" $DIST2/js/common.js\nsed -i \"\" -e '16,21d' $DIST2/css/content.css # 清理 CSS 样式\nsed -i \"\" -e '/\"tts\",/d' $DIST2/manifest.json\nsed -i \"\" -e '/\"global\": true,/d' $DIST2/manifest.json\n\n# 打包 (Firefox)\nzip -rq dist_firefox.zip $DIST2\n\n# 清理目录\nrm -rf $DIST\nrm -rf $DIST2\n"
  },
  {
    "path": "src/conf/conf.json",
    "content": "{\n  \"setting\": {\n    \"scribble\": \"direct\",\n    \"excludeChinese\": \"\",\n    \"excludeSymbol\": \"\",\n    \"excludeNumber\": \"\",\n    \"position\": \"fixed\",\n    \"allowSelect\": \"\",\n    \"autoCopy\": \"\",\n    \"autoPaste\": \"\",\n    \"autoChange\": \"\",\n    \"autoWords\": \"\",\n    \"cutHumpName\": \"on\",\n    \"translateList\": [\n      \"baidu\",\n      \"google\",\n      \"youdao\"\n    ],\n    \"translateTTSList\": [],\n    \"localSoundReplace\": \"\",\n    \"translateOCR\": \"\",\n    \"ocrType\": \"auto\",\n    \"translateThin\": \"\",\n    \"hideOriginal\": \"\",\n    \"autoLanguage\": \"on\",\n    \"autoConfirm\": \"on\",\n    \"dictionaryList\": [\n      \"youdao\",\n      \"bing\",\n      \"longman\"\n    ],\n    \"dictionarySoundList\": [],\n    \"dictionaryReader\": \"us\",\n    \"searchList\": [\n      \"百度搜索\",\n      \"谷歌搜索\",\n      \"必应搜索\",\n      \"360搜索\",\n      \"搜狗搜索\",\n      \"谷歌图片\",\n      \"必应图片\",\n      \"百度图片\",\n      \"维基百科\",\n      \"百度百科\"\n    ],\n    \"searchMenus\": [],\n    \"searchSide\": [],\n    \"ttsConf\": {}\n  },\n  \"translateList\": {\n    \"baidu\": \"百度翻译\",\n    \"google\": \"谷歌翻译\",\n    \"youdao\": \"有道翻译\",\n    \"bing\": \"必应翻译\",\n    \"qq\": \"腾讯翻译\",\n    \"alibaba\": \"阿里翻译\",\n    \"sogou\": \"搜狗翻译\",\n    \"so\": \"360翻译\",\n    \"deepl\": \"DeepL翻译\"\n  },\n  \"translateTTSList\": {\n    \"baidu\": \"百度朗读\",\n    \"google\": \"谷歌朗读\",\n    \"youdao\": \"有道朗读\",\n    \"bing\": \"必应朗读\",\n    \"qq\": \"腾讯朗读\",\n    \"sogou\": \"搜狗朗读\",\n    \"frdic\": \"欧路朗读\",\n    \"local\": \"本机朗读\"\n  },\n  \"dictionaryList\": {\n    \"youdao\": \"有道词典\",\n    \"bing\": \"必应词典\",\n    \"dictcn\": \"海词词典\",\n    \"iciba\": \"金山词典\",\n    \"eudic\": \"欧路词典\",\n    \"rrdict\": \"人人词典\",\n    \"hjdict\": \"沪江词典\",\n    \"dreye\": \"译典词典\",\n    \"longman\": \"朗文词典\",\n    \"cambridge\": \"剑桥词典\",\n    \"merriam\": \"韦氏词典\",\n    \"collins\": \"柯林斯词典\",\n    \"oxford\": \"牛津词典\",\n    \"thefree\": \"自由词典\",\n    \"lexico\": \"词曲词典\",\n    \"dictionary\": \"在线词典\",\n    \"vocabulary\": \"词汇词典\",\n    \"wordreference\": \"语言词典\",\n    \"etymonline\": \"词源词典\",\n    \"urban\": \"城市词典\",\n    \"macmillan\": \"麦克米伦词典\"\n  },\n  \"dictionaryCSS\": [\n    \"longman\"\n  ],\n  \"dictionarySoundExcluded\": [\n    \"etymonline\",\n    \"urban\"\n  ],\n  \"ttsList\": {\n    \"ara\": \"ar-SA\",\n    \"cs\": \"cs-CZ\",\n    \"dan\": \"da-DK\",\n    \"de\": \"de-DE\",\n    \"el\": \"el-GR\",\n    \"en\": \"en-US\",\n    \"spa\": \"es-ES\",\n    \"fin\": \"fi-FI\",\n    \"frn\": \"fr-CA\",\n    \"fra\": \"fr-FR\",\n    \"heb\": \"he-IL\",\n    \"hi\": \"hi-IN\",\n    \"hu\": \"hu-HU\",\n    \"id\": \"id-ID\",\n    \"it\": \"it-IT\",\n    \"jp\": \"ja-JP\",\n    \"kor\": \"ko-KR\",\n    \"nor\": \"nb-NO\",\n    \"nl\": \"nl-NL\",\n    \"pl\": \"pl-PL\",\n    \"pot\": \"pt-BR\",\n    \"pt\": \"pt-PT\",\n    \"rom\": \"ro-RO\",\n    \"ru\": \"ru-RU\",\n    \"sk\": \"sk-SK\",\n    \"swe\": \"sv-SE\",\n    \"th\": \"th-TH\",\n    \"tr\": \"tr-TR\",\n    \"zh\": \"zh-CN\",\n    \"yue\": \"zh-HK\",\n    \"cht\": \"zh-TW\"\n  },\n  \"dialogConf\": {\n    \"width\": 500,\n    \"height\": 470,\n    \"source\": \"auto\",\n    \"target\": \"zh\",\n    \"action\": \"translate\"\n  }\n}\n"
  },
  {
    "path": "src/conf/langSpeak.json",
    "content": "{\n  \"ar-AE\": {\n    \"zhName\": \"阿拉伯语(阿拉伯联合酋长国)\",\n    \"enName\": \"Arabic(United Arab Emirates)\"\n  },\n  \"ar-BH\": {\n    \"zhName\": \"阿拉伯语(巴林)\",\n    \"enName\": \"Arabic(Bahrain)\"\n  },\n  \"ar-EG\": {\n    \"zhName\": \"阿拉伯语(埃及)\",\n    \"enName\": \"Arabic(Egypt)\"\n  },\n  \"ar-IQ\": {\n    \"zhName\": \"阿拉伯语(伊拉克)\",\n    \"enName\": \"Arabic(Iraq)\"\n  },\n  \"ar-JO\": {\n    \"zhName\": \"阿拉伯语(约旦)\",\n    \"enName\": \"Arabic(Jordan)\"\n  },\n  \"ar-KW\": {\n    \"zhName\": \"阿拉伯语(科威特)\",\n    \"enName\": \"Arabic(Kuwait)\"\n  },\n  \"ar-LB\": {\n    \"zhName\": \"阿拉伯语(黎巴嫩)\",\n    \"enName\": \"Arabic(Lebanon)\"\n  },\n  \"ar-OM\": {\n    \"zhName\": \"阿拉伯语(阿曼)\",\n    \"enName\": \"Arabic(Oman)\"\n  },\n  \"ar-QA\": {\n    \"zhName\": \"阿拉伯语(卡塔尔)\",\n    \"enName\": \"Arabic(Qatar)\"\n  },\n  \"ar-SA\": {\n    \"zhName\": \"阿拉伯语(沙特阿拉伯)\",\n    \"enName\": \"Arabic(Saudi Arabia)\"\n  },\n  \"ar-SY\": {\n    \"zhName\": \"阿拉伯语(叙利亚)\",\n    \"enName\": \"Arabic(Syria)\"\n  },\n  \"bg-BG\": {\n    \"zhName\": \"保加利亚语(保加利亚)\",\n    \"enName\": \"Bulgarian(Bulgaria)\"\n  },\n  \"ca-ES\": {\n    \"zhName\": \"加泰罗尼亚语(西班牙)\",\n    \"enName\": \"Catalan(Spain)\"\n  },\n  \"cs-CZ\": {\n    \"zhName\": \"捷克语(捷克共和国)\",\n    \"enName\": \"Czech(Czech Republic)\"\n  },\n  \"da-DK\": {\n    \"zhName\": \"丹麦语(丹麦)\",\n    \"enName\": \"Danish(Denmark)\"\n  },\n  \"de-AT\": {\n    \"zhName\": \"德语(奥地利)\",\n    \"enName\": \"German(Austria)\"\n  },\n  \"de-CH\": {\n    \"zhName\": \"德语(瑞士)\",\n    \"enName\": \"German(Switzerland)\"\n  },\n  \"de-DE\": {\n    \"zhName\": \"德语(德国)\",\n    \"enName\": \"German(Germany)\"\n  },\n  \"el-GR\": {\n    \"zhName\": \"希腊语(希腊)\",\n    \"enName\": \"Greek(Greece)\"\n  },\n  \"en\": {\n    \"zhName\": \"英语\",\n    \"enName\": \"English\"\n  },\n  \"en-AU\": {\n    \"zhName\": \"英语(澳大利亚)\",\n    \"enName\": \"English(Australia)\"\n  },\n  \"en-CA\": {\n    \"zhName\": \"英语(加拿大)\",\n    \"enName\": \"English(Canada)\"\n  },\n  \"en-GB\": {\n    \"zhName\": \"英语(英国)\",\n    \"enName\": \"English(United Kingdom)\"\n  },\n  \"en-HK\": {\n    \"zhName\": \"英语(香港)\",\n    \"enName\": \"English(Hong Kong)\"\n  },\n  \"en-IE\": {\n    \"zhName\": \"英语(爱尔兰)\",\n    \"enName\": \"English(Ireland)\"\n  },\n  \"en-IN\": {\n    \"zhName\": \"英语(印度)\",\n    \"enName\": \"English(India)\"\n  },\n  \"en-NZ\": {\n    \"zhName\": \"英语(新西兰)\",\n    \"enName\": \"English(New Zealand)\"\n  },\n  \"en-PH\": {\n    \"zhName\": \"英语(菲律宾)\",\n    \"enName\": \"English(Philippines)\"\n  },\n  \"en-SG\": {\n    \"zhName\": \"英语(新加坡)\",\n    \"enName\": \"English(Singapore)\"\n  },\n  \"en-US\": {\n    \"zhName\": \"英语(美国)\",\n    \"enName\": \"English(United States)\"\n  },\n  \"en-ZA\": {\n    \"zhName\": \"英语(南非)\",\n    \"enName\": \"English(South Africa)\"\n  },\n  \"es\": {\n    \"zhName\": \"西班牙语\",\n    \"enName\": \"Spanish\"\n  },\n  \"es-AR\": {\n    \"zhName\": \"西班牙语(阿根廷)\",\n    \"enName\": \"Spanish(Argentina)\"\n  },\n  \"es-BO\": {\n    \"zhName\": \"西班牙语(玻利维亚)\",\n    \"enName\": \"Spanish(Bolivia)\"\n  },\n  \"es-CL\": {\n    \"zhName\": \"西班牙语(智利)\",\n    \"enName\": \"Spanish(Chile)\"\n  },\n  \"es-CO\": {\n    \"zhName\": \"西班牙语(哥伦比亚)\",\n    \"enName\": \"Spanish(Colombia)\"\n  },\n  \"es-CR\": {\n    \"zhName\": \"西班牙语(哥斯达黎加)\",\n    \"enName\": \"Spanish(Costa Rica)\"\n  },\n  \"es-CU\": {\n    \"zhName\": \"西班牙语(古巴)\",\n    \"enName\": \"Spanish(Cuba)\"\n  },\n  \"es-DO\": {\n    \"zhName\": \"西班牙语(多米尼加共和国)\",\n    \"enName\": \"Spanish(Dominican Republic)\"\n  },\n  \"es-EC\": {\n    \"zhName\": \"西班牙语(厄瓜多尔)\",\n    \"enName\": \"Spanish(Ecuador)\"\n  },\n  \"es-ES\": {\n    \"zhName\": \"西班牙语(西班牙)\",\n    \"enName\": \"Spanish(Spain)\"\n  },\n  \"es-GT\": {\n    \"zhName\": \"西班牙语(危地马拉)\",\n    \"enName\": \"Spanish(Guatemala)\"\n  },\n  \"es-HN\": {\n    \"zhName\": \"西班牙语(洪都拉斯)\",\n    \"enName\": \"Spanish(Honduras)\"\n  },\n  \"es-MX\": {\n    \"zhName\": \"西班牙语(墨西哥)\",\n    \"enName\": \"Spanish(Mexico)\"\n  },\n  \"es-NI\": {\n    \"zhName\": \"西班牙语(尼加拉瓜)\",\n    \"enName\": \"Spanish(Nicaragua)\"\n  },\n  \"es-PA\": {\n    \"zhName\": \"西班牙语(巴拿马)\",\n    \"enName\": \"Spanish(Panama)\"\n  },\n  \"es-PE\": {\n    \"zhName\": \"西班牙语(秘鲁)\",\n    \"enName\": \"Spanish(Peru)\"\n  },\n  \"es-PR\": {\n    \"zhName\": \"西班牙语(波多黎各)\",\n    \"enName\": \"Spanish(Puerto Rico)\"\n  },\n  \"es-PY\": {\n    \"zhName\": \"西班牙语(巴拉圭)\",\n    \"enName\": \"Spanish(Paraguay)\"\n  },\n  \"es-SV\": {\n    \"zhName\": \"西班牙语(萨尔瓦多)\",\n    \"enName\": \"Spanish(El Salvador)\"\n  },\n  \"es-US\": {\n    \"zhName\": \"西班牙语(美国)\",\n    \"enName\": \"Spanish(USA)\"\n  },\n  \"es-UY\": {\n    \"zhName\": \"西班牙语(乌拉圭)\",\n    \"enName\": \"Spanish(Uruguay)\"\n  },\n  \"es-VE\": {\n    \"zhName\": \"西班牙语(委内瑞拉)\",\n    \"enName\": \"Spanish(Venezuela)\"\n  },\n  \"et-EE\": {\n    \"zhName\": \"爱沙尼亚语(爱沙尼亚)\",\n    \"enName\": \"Estonian(Estonia)\"\n  },\n  \"fi-FI\": {\n    \"zhName\": \"芬兰语(芬兰)\",\n    \"enName\": \"Finnish(Finland)\"\n  },\n  \"fr-CA\": {\n    \"zhName\": \"法语(加拿大)\",\n    \"enName\": \"French(Canada)\"\n  },\n  \"fr-CH\": {\n    \"zhName\": \"法语(瑞士)\",\n    \"enName\": \"French(Switzerland)\"\n  },\n  \"fr-FR\": {\n    \"zhName\": \"法语(法国)\",\n    \"enName\": \"French(France)\"\n  },\n  \"ga-IE\": {\n    \"zhName\": \"爱尔兰语(爱尔兰)\",\n    \"enName\": \"Irish(Ireland)\"\n  },\n  \"gu-IN\": {\n    \"zhName\": \"古吉拉特语(印度)\",\n    \"enName\": \"Gujarati(Indian)\"\n  },\n  \"he-IL\": {\n    \"zhName\": \"希伯来语(以色列)\",\n    \"enName\": \"Hebrew(Israel)\"\n  },\n  \"hi-IN\": {\n    \"zhName\": \"印地语(印度)\",\n    \"enName\": \"Hindi(India)\"\n  },\n  \"hr-HR\": {\n    \"zhName\": \"克罗地亚语(克罗地亚)\",\n    \"enName\": \"Croatian(Croatia)\"\n  },\n  \"hu-HU\": {\n    \"zhName\": \"匈牙利语(匈牙利)\",\n    \"enName\": \"Hungarian(Hungary)\"\n  },\n  \"id-ID\": {\n    \"zhName\": \"印度尼西亚语(印度尼西亚)\",\n    \"enName\": \"Indonesian(Indonesia)\"\n  },\n  \"it-IT\": {\n    \"zhName\": \"意大利语(意大利)\",\n    \"enName\": \"Italian(Italy)\"\n  },\n  \"ja-JP\": {\n    \"zhName\": \"日语(日本)\",\n    \"enName\": \"Japanese(Japan)\"\n  },\n  \"ko-KR\": {\n    \"zhName\": \"韩语(韩国)\",\n    \"enName\": \"Korean(Korea)\"\n  },\n  \"lt-LT\": {\n    \"zhName\": \"立陶宛语(立陶宛)\",\n    \"enName\": \"Lithuanian(Lithuania)\"\n  },\n  \"lv-LV\": {\n    \"zhName\": \"拉脱维亚语(拉脱维亚)\",\n    \"enName\": \"Latvian(Latvia)\"\n  },\n  \"mr-IN\": {\n    \"zhName\": \"马拉地语(印度)\",\n    \"enName\": \"Marathi(India)\"\n  },\n  \"ms-MY\": {\n    \"zhName\": \"马来语(马来西亚)\",\n    \"enName\": \"Malay(Malaysia)\"\n  },\n  \"mt-MT\": {\n    \"zhName\": \"马耳他语(马耳他)\",\n    \"enName\": \"Maltese(Malta)\"\n  },\n  \"nb-NO\": {\n    \"zhName\": \"挪威语(博克马尔语，挪威)\",\n    \"enName\": \"Norwegian(Bokmål, Norway)\"\n  },\n  \"nl\": {\n    \"zhName\": \"荷兰语\",\n    \"enName\": \"Dutch\"\n  },\n  \"nl-BE\": {\n    \"zhName\": \"荷兰语(比利时)\",\n    \"enName\": \"Dutch(Belgium)\"\n  },\n  \"nl-NL\": {\n    \"zhName\": \"荷兰语(荷兰)\",\n    \"enName\": \"Dutch(Netherlands)\"\n  },\n  \"pl-PL\": {\n    \"zhName\": \"波兰语(波兰)\",\n    \"enName\": \"Polish(Poland)\"\n  },\n  \"pt-BR\": {\n    \"zhName\": \"葡萄牙语(巴西)\",\n    \"enName\": \"Portuguese(Brazil)\"\n  },\n  \"pt-PT\": {\n    \"zhName\": \"葡萄牙语(葡萄牙)\",\n    \"enName\": \"Portuguese(Portugal)\"\n  },\n  \"ro-RO\": {\n    \"zhName\": \"罗马尼亚语(罗马尼亚)\",\n    \"enName\": \"Romanian(Romania)\"\n  },\n  \"ru-RU\": {\n    \"zhName\": \"俄语(俄罗斯)\",\n    \"enName\": \"Russian(Russia)\"\n  },\n  \"sk-SK\": {\n    \"zhName\": \"斯洛伐克语(斯洛伐克)\",\n    \"enName\": \"Slovak(Slovakia)\"\n  },\n  \"sl-SI\": {\n    \"zhName\": \"斯洛文尼亚语(斯洛文尼亚)\",\n    \"enName\": \"Slovenian(Slovenia)\"\n  },\n  \"sv-SE\": {\n    \"zhName\": \"瑞典语(瑞典)\",\n    \"enName\": \"Swedish(Sweden)\"\n  },\n  \"ta-IN\": {\n    \"zhName\": \"泰米尔语(印度)\",\n    \"enName\": \"Tamil(India)\"\n  },\n  \"te-IN\": {\n    \"zhName\": \"泰卢固语(印度)\",\n    \"enName\": \"Telugu(India)\"\n  },\n  \"th-TH\": {\n    \"zhName\": \"泰语(泰国)\",\n    \"enName\": \"Thai(Thailand)\"\n  },\n  \"tr-TR\": {\n    \"zhName\": \"土耳其语(土耳其)\",\n    \"enName\": \"Turkish(Turkey)\"\n  },\n  \"vi-VN\": {\n    \"zhName\": \"越南语(越南)\",\n    \"enName\": \"Vietnamese(Vietnam)\"\n  },\n  \"zh-CN\": {\n    \"zhName\": \"中文(简体)\",\n    \"enName\": \"Chinese(Simplified)\"\n  },\n  \"zh-HK\": {\n    \"zhName\": \"中文(粤语)\",\n    \"enName\": \"Chinese(Cantonese)\"\n  },\n  \"zh-TW\": {\n    \"zhName\": \"中文(繁体)\",\n    \"enName\": \"Chinese(Traditional)\"\n  }\n}\n"
  },
  {
    "path": "src/conf/language.json",
    "content": "{\n  \"auto\": {\n    \"zhName\": \"自动检测\",\n    \"enName\": \"Auto Detect\"\n  },\n  \"zh\": {\n    \"zhName\": \"中文(简体)\",\n    \"enName\": \"Chinese\"\n  },\n  \"en\": {\n    \"zhName\": \"英语\",\n    \"enName\": \"English\"\n  },\n  \"jp\": {\n    \"zhName\": \"日语\",\n    \"enName\": \"Japanese\"\n  },\n  \"th\": {\n    \"zhName\": \"泰语\",\n    \"enName\": \"Thai\"\n  },\n  \"spa\": {\n    \"zhName\": \"西班牙语\",\n    \"enName\": \"Spanish\"\n  },\n  \"ara\": {\n    \"zhName\": \"阿拉伯语\",\n    \"enName\": \"Arabic\"\n  },\n  \"fra\": {\n    \"zhName\": \"法语\",\n    \"enName\": \"French\"\n  },\n  \"kor\": {\n    \"zhName\": \"韩语\",\n    \"enName\": \"Korean\"\n  },\n  \"ru\": {\n    \"zhName\": \"俄语\",\n    \"enName\": \"Russian\"\n  },\n  \"de\": {\n    \"zhName\": \"德语\",\n    \"enName\": \"German\"\n  },\n  \"pt\": {\n    \"zhName\": \"葡萄牙语\",\n    \"enName\": \"Portuguese\"\n  },\n  \"it\": {\n    \"zhName\": \"意大利语\",\n    \"enName\": \"Italian\"\n  },\n  \"el\": {\n    \"zhName\": \"希腊语\",\n    \"enName\": \"Greek\"\n  },\n  \"nl\": {\n    \"zhName\": \"荷兰语\",\n    \"enName\": \"Dutch\"\n  },\n  \"pl\": {\n    \"zhName\": \"波兰语\",\n    \"enName\": \"Polish\"\n  },\n  \"fin\": {\n    \"zhName\": \"芬兰语\",\n    \"enName\": \"Finnish\"\n  },\n  \"cs\": {\n    \"zhName\": \"捷克语\",\n    \"enName\": \"Czech\"\n  },\n  \"bul\": {\n    \"zhName\": \"保加利亚语\",\n    \"enName\": \"Bulgarian\"\n  },\n  \"dan\": {\n    \"zhName\": \"丹麦语\",\n    \"enName\": \"Danish\"\n  },\n  \"est\": {\n    \"zhName\": \"爱沙尼亚语\",\n    \"enName\": \"Estonian\"\n  },\n  \"hu\": {\n    \"zhName\": \"匈牙利语\",\n    \"enName\": \"Hungarian\"\n  },\n  \"rom\": {\n    \"zhName\": \"罗马尼亚语\",\n    \"enName\": \"Romanian\"\n  },\n  \"slo\": {\n    \"zhName\": \"斯洛文尼亚语\",\n    \"enName\": \"Slovenian\"\n  },\n  \"swe\": {\n    \"zhName\": \"瑞典语\",\n    \"enName\": \"Swedish\"\n  },\n  \"vie\": {\n    \"zhName\": \"越南语\",\n    \"enName\": \"Vietnamese\"\n  },\n  \"yue\": {\n    \"zhName\": \"中文(粤语)\",\n    \"enName\": \"Cantonese\"\n  },\n  \"cht\": {\n    \"zhName\": \"中文(繁体)\",\n    \"enName\": \"Traditional Chinese\"\n  },\n  \"wyw\": {\n    \"zhName\": \"中文(文言文)\",\n    \"enName\": \"Classical Chinese\"\n  },\n  \"afr\": {\n    \"zhName\": \"南非荷兰语\",\n    \"enName\": \"Afrikaans\"\n  },\n  \"alb\": {\n    \"zhName\": \"阿尔巴尼亚语\",\n    \"enName\": \"Albanian\"\n  },\n  \"amh\": {\n    \"zhName\": \"阿姆哈拉语\",\n    \"enName\": \"Amharic\"\n  },\n  \"arm\": {\n    \"zhName\": \"亚美尼亚语\",\n    \"enName\": \"Armenian\"\n  },\n  \"asm\": {\n    \"zhName\": \"阿萨姆语\",\n    \"enName\": \"Assamese\"\n  },\n  \"ast\": {\n    \"zhName\": \"阿斯图里亚斯语\",\n    \"enName\": \"Asturian\"\n  },\n  \"aze\": {\n    \"zhName\": \"阿塞拜疆语\",\n    \"enName\": \"Azerbaijani\"\n  },\n  \"baq\": {\n    \"zhName\": \"巴斯克语\",\n    \"enName\": \"Basque\"\n  },\n  \"bel\": {\n    \"zhName\": \"白俄罗斯语\",\n    \"enName\": \"Belarusian\"\n  },\n  \"ben\": {\n    \"zhName\": \"孟加拉语\",\n    \"enName\": \"Bengali\"\n  },\n  \"bos\": {\n    \"zhName\": \"波斯尼亚语\",\n    \"enName\": \"Bosnian\"\n  },\n  \"bur\": {\n    \"zhName\": \"缅甸语\",\n    \"enName\": \"Burmese\"\n  },\n  \"cat\": {\n    \"zhName\": \"加泰罗尼亚语\",\n    \"enName\": \"Catalan\"\n  },\n  \"ceb\": {\n    \"zhName\": \"宿务语\",\n    \"enName\": \"Cebuano\"\n  },\n  \"hrv\": {\n    \"zhName\": \"克罗地亚语\",\n    \"enName\": \"Croatian\"\n  },\n  \"epo\": {\n    \"zhName\": \"世界语\",\n    \"enName\": \"Esperanto\"\n  },\n  \"fao\": {\n    \"zhName\": \"法罗语\",\n    \"enName\": \"Faroese\"\n  },\n  \"fil\": {\n    \"zhName\": \"菲律宾语\",\n    \"enName\": \"Filipino\"\n  },\n  \"glg\": {\n    \"zhName\": \"加利西亚语\",\n    \"enName\": \"Galician\"\n  },\n  \"geo\": {\n    \"zhName\": \"格鲁吉亚语\",\n    \"enName\": \"Georgian\"\n  },\n  \"guj\": {\n    \"zhName\": \"古吉拉特语\",\n    \"enName\": \"Gujarati\"\n  },\n  \"hau\": {\n    \"zhName\": \"豪萨语\",\n    \"enName\": \"Hausa\"\n  },\n  \"heb\": {\n    \"zhName\": \"希伯来语\",\n    \"enName\": \"Hebrew\"\n  },\n  \"hi\": {\n    \"zhName\": \"印地语\",\n    \"enName\": \"Hindi\"\n  },\n  \"ice\": {\n    \"zhName\": \"冰岛语\",\n    \"enName\": \"Icelandic\"\n  },\n  \"ibo\": {\n    \"zhName\": \"伊博语\",\n    \"enName\": \"Igbo\"\n  },\n  \"id\": {\n    \"zhName\": \"印尼语\",\n    \"enName\": \"Indonesian\"\n  },\n  \"gle\": {\n    \"zhName\": \"爱尔兰语\",\n    \"enName\": \"Irish\"\n  },\n  \"kan\": {\n    \"zhName\": \"卡纳达语\",\n    \"enName\": \"Kannada\"\n  },\n  \"kaz\": {\n    \"zhName\": \"哈萨克语\",\n    \"enName\": \"Kazakh\"\n  },\n  \"kli\": {\n    \"zhName\": \"克林贡语\",\n    \"enName\": \"Klingon\"\n  },\n  \"kur\": {\n    \"zhName\": \"库尔德语\",\n    \"enName\": \"Kurdish\"\n  },\n  \"lao\": {\n    \"zhName\": \"老挝语\",\n    \"enName\": \"Lao\"\n  },\n  \"lat\": {\n    \"zhName\": \"拉丁语\",\n    \"enName\": \"Latin\"\n  },\n  \"lav\": {\n    \"zhName\": \"拉脱维亚语\",\n    \"enName\": \"Latvian\"\n  },\n  \"lit\": {\n    \"zhName\": \"立陶宛语\",\n    \"enName\": \"Lithuanian\"\n  },\n  \"ltz\": {\n    \"zhName\": \"卢森堡语\",\n    \"enName\": \"Luxembourgish\"\n  },\n  \"mac\": {\n    \"zhName\": \"马其顿语\",\n    \"enName\": \"Macedonian\"\n  },\n  \"mg\": {\n    \"zhName\": \"马拉加斯语\",\n    \"enName\": \"Malagasy\"\n  },\n  \"may\": {\n    \"zhName\": \"马来语\",\n    \"enName\": \"Malay\"\n  },\n  \"mal\": {\n    \"zhName\": \"马拉雅拉姆语\",\n    \"enName\": \"Malayalam\"\n  },\n  \"mlt\": {\n    \"zhName\": \"马耳他语\",\n    \"enName\": \"Maltese\"\n  },\n  \"mar\": {\n    \"zhName\": \"马拉地语\",\n    \"enName\": \"Marathi\"\n  },\n  \"nep\": {\n    \"zhName\": \"尼泊尔语\",\n    \"enName\": \"Nepali\"\n  },\n  \"nno\": {\n    \"zhName\": \"新挪威语\",\n    \"enName\": \"Nynorsk\"\n  },\n  \"per\": {\n    \"zhName\": \"波斯语\",\n    \"enName\": \"Persian\"\n  },\n  \"srd\": {\n    \"zhName\": \"萨丁尼亚语\",\n    \"enName\": \"Sardinian\"\n  },\n  \"srp\": {\n    \"zhName\": \"塞尔维亚语(拉丁文)\",\n    \"enName\": \"Serbian\"\n  },\n  \"sin\": {\n    \"zhName\": \"僧伽罗语\",\n    \"enName\": \"Sinhala\"\n  },\n  \"sk\": {\n    \"zhName\": \"斯洛伐克语\",\n    \"enName\": \"Slovak\"\n  },\n  \"som\": {\n    \"zhName\": \"索马里语\",\n    \"enName\": \"Somali\"\n  },\n  \"swa\": {\n    \"zhName\": \"斯瓦希里语\",\n    \"enName\": \"Swahili\"\n  },\n  \"tgl\": {\n    \"zhName\": \"他加禄语\",\n    \"enName\": \"Tagalog\"\n  },\n  \"tgk\": {\n    \"zhName\": \"塔吉克语\",\n    \"enName\": \"Tajik\"\n  },\n  \"tam\": {\n    \"zhName\": \"泰米尔语\",\n    \"enName\": \"Tamil\"\n  },\n  \"tat\": {\n    \"zhName\": \"鞑靼语\",\n    \"enName\": \"Tatar\"\n  },\n  \"tel\": {\n    \"zhName\": \"泰卢固语\",\n    \"enName\": \"Telugu\"\n  },\n  \"tr\": {\n    \"zhName\": \"土耳其语\",\n    \"enName\": \"Turkish\"\n  },\n  \"tuk\": {\n    \"zhName\": \"土库曼语\",\n    \"enName\": \"Turkmen\"\n  },\n  \"ukr\": {\n    \"zhName\": \"乌克兰语\",\n    \"enName\": \"Ukrainian\"\n  },\n  \"urd\": {\n    \"zhName\": \"乌尔都语\",\n    \"enName\": \"Urdu\"\n  },\n  \"uzb\": {\n    \"zhName\": \"乌兹别克语\",\n    \"enName\": \"Uzbek\"\n  },\n  \"oci\": {\n    \"zhName\": \"奥克语\",\n    \"enName\": \"Occitan\"\n  },\n  \"kir\": {\n    \"zhName\": \"吉尔吉斯语\",\n    \"enName\": \"Kyrgyz\"\n  },\n  \"pus\": {\n    \"zhName\": \"普什图语\",\n    \"enName\": \"Pashto\"\n  },\n  \"hkm\": {\n    \"zhName\": \"高棉语\",\n    \"enName\": \"Khmer\"\n  },\n  \"ht\": {\n    \"zhName\": \"海地语\",\n    \"enName\": \"Haitian Creole\"\n  },\n  \"nob\": {\n    \"zhName\": \"书面挪威语\",\n    \"enName\": \"Bokmål\"\n  },\n  \"pan\": {\n    \"zhName\": \"旁遮普语\",\n    \"enName\": \"Punjabi\"\n  },\n  \"arq\": {\n    \"zhName\": \"阿尔及利亚阿拉伯语\",\n    \"enName\": \"Algerian Arabic\"\n  },\n  \"bis\": {\n    \"zhName\": \"比斯拉马语\",\n    \"enName\": \"Bislama\"\n  },\n  \"frn\": {\n    \"zhName\": \"加拿大法语\",\n    \"enName\": \"Canadian French\"\n  },\n  \"hak\": {\n    \"zhName\": \"哈卡钦语\",\n    \"enName\": \"Hakha Chin\"\n  },\n  \"hup\": {\n    \"zhName\": \"胡帕语\",\n    \"enName\": \"Hupa\"\n  },\n  \"ing\": {\n    \"zhName\": \"印古什语\",\n    \"enName\": \"Ingush\"\n  },\n  \"lag\": {\n    \"zhName\": \"拉特加莱语\",\n    \"enName\": \"Latgalian\"\n  },\n  \"mau\": {\n    \"zhName\": \"毛里求斯克里奥尔语\",\n    \"enName\": \"Mauritian Creole\"\n  },\n  \"mot\": {\n    \"zhName\": \"黑山语\",\n    \"enName\": \"Montenegrin\"\n  },\n  \"pot\": {\n    \"zhName\": \"巴西葡萄牙语\",\n    \"enName\": \"Brazilian Portuguese\"\n  },\n  \"ruy\": {\n    \"zhName\": \"卢森尼亚语\",\n    \"enName\": \"Rusyn\"\n  },\n  \"sec\": {\n    \"zhName\": \"塞尔维亚-克罗地亚语\",\n    \"enName\": \"Serbo-Croatian\"\n  },\n  \"sil\": {\n    \"zhName\": \"西里西亚语\",\n    \"enName\": \"Silesian\"\n  },\n  \"tua\": {\n    \"zhName\": \"突尼斯阿拉伯语\",\n    \"enName\": \"Tunisian Arabic\"\n  },\n  \"ach\": {\n    \"zhName\": \"亚齐语\",\n    \"enName\": \"Achinese\"\n  },\n  \"aka\": {\n    \"zhName\": \"阿肯语\",\n    \"enName\": \"Akan\"\n  },\n  \"arg\": {\n    \"zhName\": \"阿拉贡语\",\n    \"enName\": \"Aragonese\"\n  },\n  \"aym\": {\n    \"zhName\": \"艾马拉语\",\n    \"enName\": \"Aymara\"\n  },\n  \"bal\": {\n    \"zhName\": \"俾路支语\",\n    \"enName\": \"Baluchi\"\n  },\n  \"bak\": {\n    \"zhName\": \"巴什基尔语\",\n    \"enName\": \"Bashkir\"\n  },\n  \"bem\": {\n    \"zhName\": \"本巴语\",\n    \"enName\": \"Bemba\"\n  },\n  \"ber\": {\n    \"zhName\": \"柏柏尔语\",\n    \"enName\": \"Berber languages\"\n  },\n  \"bho\": {\n    \"zhName\": \"博杰普尔语\",\n    \"enName\": \"Bhojpuri\"\n  },\n  \"bli\": {\n    \"zhName\": \"比林语\",\n    \"enName\": \"Blin\"\n  },\n  \"bre\": {\n    \"zhName\": \"布列塔尼语\",\n    \"enName\": \"Breton\"\n  },\n  \"chr\": {\n    \"zhName\": \"切罗基语\",\n    \"enName\": \"Cherokee\"\n  },\n  \"nya\": {\n    \"zhName\": \"齐切瓦语\",\n    \"enName\": \"Chichewa\"\n  },\n  \"chv\": {\n    \"zhName\": \"楚瓦什语\",\n    \"enName\": \"Chuvash\"\n  },\n  \"cor\": {\n    \"zhName\": \"康瓦尔语\",\n    \"enName\": \"Cornish\"\n  },\n  \"cos\": {\n    \"zhName\": \"科西嘉语\",\n    \"enName\": \"Corsican\"\n  },\n  \"cre\": {\n    \"zhName\": \"克里克语\",\n    \"enName\": \"Creek\"\n  },\n  \"cri\": {\n    \"zhName\": \"克里米亚鞑靼语\",\n    \"enName\": \"Crimean Tatar\"\n  },\n  \"div\": {\n    \"zhName\": \"迪维希语\",\n    \"enName\": \"Divehi\"\n  },\n  \"eno\": {\n    \"zhName\": \"古英语\",\n    \"enName\": \"Old English\"\n  },\n  \"frm\": {\n    \"zhName\": \"中古法语\",\n    \"enName\": \"Middle French\"\n  },\n  \"fri\": {\n    \"zhName\": \"弗留利语\",\n    \"enName\": \"Friulian\"\n  },\n  \"ful\": {\n    \"zhName\": \"富拉尼语\",\n    \"enName\": \"Fulani\"\n  },\n  \"gla\": {\n    \"zhName\": \"盖尔语\",\n    \"enName\": \"Gaelic\"\n  },\n  \"lug\": {\n    \"zhName\": \"卢干达语\",\n    \"enName\": \"Luganda\"\n  },\n  \"gra\": {\n    \"zhName\": \"古希腊语\",\n    \"enName\": \"Ancient Greek\"\n  },\n  \"grn\": {\n    \"zhName\": \"瓜拉尼语\",\n    \"enName\": \"Guarani\"\n  },\n  \"haw\": {\n    \"zhName\": \"夏威夷语\",\n    \"enName\": \"Hawaiian\"\n  },\n  \"hil\": {\n    \"zhName\": \"希利盖农语\",\n    \"enName\": \"Hiligaynon\"\n  },\n  \"ido\": {\n    \"zhName\": \"伊多语\",\n    \"enName\": \"Ido\"\n  },\n  \"ina\": {\n    \"zhName\": \"因特语\",\n    \"enName\": \"Interlingua\"\n  },\n  \"iku\": {\n    \"zhName\": \"伊努克提图特语\",\n    \"enName\": \"Inuktitut\"\n  },\n  \"jav\": {\n    \"zhName\": \"爪哇语\",\n    \"enName\": \"Javanese\"\n  },\n  \"kab\": {\n    \"zhName\": \"卡拜尔语\",\n    \"enName\": \"Kabyle\"\n  },\n  \"kal\": {\n    \"zhName\": \"格陵兰语\",\n    \"enName\": \"Kalaallisut\"\n  },\n  \"kau\": {\n    \"zhName\": \"卡努里语\",\n    \"enName\": \"Kanuri\"\n  },\n  \"kas\": {\n    \"zhName\": \"克什米尔语\",\n    \"enName\": \"Kashmiri\"\n  },\n  \"kah\": {\n    \"zhName\": \"卡舒比语\",\n    \"enName\": \"Kashubian\"\n  },\n  \"kin\": {\n    \"zhName\": \"卢旺达语\",\n    \"enName\": \"Kinyarwanda\"\n  },\n  \"kon\": {\n    \"zhName\": \"刚果语\",\n    \"enName\": \"Kongo\"\n  },\n  \"kok\": {\n    \"zhName\": \"孔卡尼语\",\n    \"enName\": \"Konkani\"\n  },\n  \"lim\": {\n    \"zhName\": \"林堡语\",\n    \"enName\": \"Limburgish\"\n  },\n  \"lin\": {\n    \"zhName\": \"林加拉语\",\n    \"enName\": \"Lingala\"\n  },\n  \"loj\": {\n    \"zhName\": \"逻辑语\",\n    \"enName\": \"Lojban\"\n  },\n  \"log\": {\n    \"zhName\": \"低地德语\",\n    \"enName\": \"Low German\"\n  },\n  \"los\": {\n    \"zhName\": \"下索布语\",\n    \"enName\": \"Lower Sorbian\"\n  },\n  \"mai\": {\n    \"zhName\": \"迈蒂利语\",\n    \"enName\": \"Maithili\"\n  },\n  \"glv\": {\n    \"zhName\": \"曼克斯语\",\n    \"enName\": \"Manx\"\n  },\n  \"mao\": {\n    \"zhName\": \"毛利语\",\n    \"enName\": \"Maori\"\n  },\n  \"mah\": {\n    \"zhName\": \"马绍尔语\",\n    \"enName\": \"Marshallese\"\n  },\n  \"nbl\": {\n    \"zhName\": \"南恩德贝莱语\",\n    \"enName\": \"Southern Ndebele\"\n  },\n  \"nea\": {\n    \"zhName\": \"那不勒斯语\",\n    \"enName\": \"Neapolitan\"\n  },\n  \"nqo\": {\n    \"zhName\": \"西非书面语\",\n    \"enName\": \"N'Ko\"\n  },\n  \"sme\": {\n    \"zhName\": \"北方萨米语\",\n    \"enName\": \"Northern Sami\"\n  },\n  \"nor\": {\n    \"zhName\": \"挪威语\",\n    \"enName\": \"Norwegian\"\n  },\n  \"oji\": {\n    \"zhName\": \"奥杰布瓦语\",\n    \"enName\": \"Ojibwa\"\n  },\n  \"ori\": {\n    \"zhName\": \"奥里亚语\",\n    \"enName\": \"Oriya\"\n  },\n  \"orm\": {\n    \"zhName\": \"奥罗莫语\",\n    \"enName\": \"Oromo\"\n  },\n  \"oss\": {\n    \"zhName\": \"奥塞梯语\",\n    \"enName\": \"Ossetian\"\n  },\n  \"pam\": {\n    \"zhName\": \"邦板牙语\",\n    \"enName\": \"Pampanga\"\n  },\n  \"pap\": {\n    \"zhName\": \"帕皮阿门托语\",\n    \"enName\": \"Papiamento\"\n  },\n  \"ped\": {\n    \"zhName\": \"北索托语\",\n    \"enName\": \"Northern Sotho\"\n  },\n  \"que\": {\n    \"zhName\": \"克丘亚语\",\n    \"enName\": \"Quechua\"\n  },\n  \"roh\": {\n    \"zhName\": \"罗曼什语\",\n    \"enName\": \"Romansh\"\n  },\n  \"ro\": {\n    \"zhName\": \"罗姆语\",\n    \"enName\": \"Romany\"\n  },\n  \"sm\": {\n    \"zhName\": \"萨摩亚语\",\n    \"enName\": \"Samoan\"\n  },\n  \"san\": {\n    \"zhName\": \"梵语\",\n    \"enName\": \"Sanskrit\"\n  },\n  \"sco\": {\n    \"zhName\": \"苏格兰语\",\n    \"enName\": \"Scots\"\n  },\n  \"sha\": {\n    \"zhName\": \"掸语\",\n    \"enName\": \"Shan\"\n  },\n  \"sna\": {\n    \"zhName\": \"修纳语\",\n    \"enName\": \"Shona\"\n  },\n  \"snd\": {\n    \"zhName\": \"信德语\",\n    \"enName\": \"Sindhi\"\n  },\n  \"sol\": {\n    \"zhName\": \"桑海语\",\n    \"enName\": \"Songhai languages\"\n  },\n  \"sot\": {\n    \"zhName\": \"南索托语\",\n    \"enName\": \"Southern Sotho\"\n  },\n  \"syr\": {\n    \"zhName\": \"叙利亚语\",\n    \"enName\": \"Syriac\"\n  },\n  \"tet\": {\n    \"zhName\": \"德顿语\",\n    \"enName\": \"Tetum\"\n  },\n  \"tir\": {\n    \"zhName\": \"提格利尼亚语\",\n    \"enName\": \"Tigrinya\"\n  },\n  \"tso\": {\n    \"zhName\": \"聪加语\",\n    \"enName\": \"Tsonga\"\n  },\n  \"twi\": {\n    \"zhName\": \"契维语\",\n    \"enName\": \"Twi\"\n  },\n  \"ups\": {\n    \"zhName\": \"高地索布语\",\n    \"enName\": \"Upper Sorbian\"\n  },\n  \"ven\": {\n    \"zhName\": \"文达语\",\n    \"enName\": \"Venda\"\n  },\n  \"wln\": {\n    \"zhName\": \"瓦隆语\",\n    \"enName\": \"Walloon\"\n  },\n  \"wel\": {\n    \"zhName\": \"威尔士语\",\n    \"enName\": \"Welsh\"\n  },\n  \"fry\": {\n    \"zhName\": \"西弗里斯语\",\n    \"enName\": \"Western Frisian\"\n  },\n  \"wol\": {\n    \"zhName\": \"沃洛夫语\",\n    \"enName\": \"Wolof\"\n  },\n  \"xho\": {\n    \"zhName\": \"科萨语\",\n    \"enName\": \"Xhosa\"\n  },\n  \"yid\": {\n    \"zhName\": \"意第绪语\",\n    \"enName\": \"Yiddish\"\n  },\n  \"yor\": {\n    \"zhName\": \"约鲁巴语\",\n    \"enName\": \"Yoruba\"\n  },\n  \"zaz\": {\n    \"zhName\": \"扎扎其语\",\n    \"enName\": \"Zaza\"\n  },\n  \"zul\": {\n    \"zhName\": \"祖鲁语\",\n    \"enName\": \"Zulu\"\n  },\n  \"sun\": {\n    \"zhName\": \"巽他语\",\n    \"enName\": \"BasaSunda\"\n  },\n  \"hmn\": {\n    \"zhName\": \"苗语\",\n    \"enName\": \"Hmong\"\n  },\n  \"src\": {\n    \"zhName\": \"塞尔维亚语(西里尔文)\",\n    \"enName\": \"Serb(Cyrillic)\"\n  }\n}\n"
  },
  {
    "path": "src/conf/searchText.txt",
    "content": "百度搜索|https://www.baidu.com/s?wd={0}\n谷歌搜索|https://www.google.com/search?q={0}\n必应搜索|https://cn.bing.com/search?q={0}\n360搜索|https://www.so.com/s?q={0}\n搜狗搜索|https://www.sogou.com/web?query={0}\n谷歌图片|https://www.google.com/search?q={0}&tbm=isch\n必应图片|https://cn.bing.com/images/search?q={0}&form=BESBTB&first=1&tsc=ImageBasicHover&ensearch=1\n百度图片|https://image.baidu.com/search/index?tn=baiduimage&fm=result&ie=utf-8&word={0}\n维基百科|https://zh.wikipedia.org/w/index.php?search={0}\n百度百科|https://baike.baidu.com/search/word?word={0}\n必应词典|https://cn.bing.com/dict/search?q={0}\n有道词典|https://www.youdao.com/w/eng/{0}\n金山词典|https://www.iciba.com/word?w={0}\n牛津词典|https://www.oxfordlearnersdictionaries.com/search/english/?q={0}\n剑桥词典|https://dictionary.cambridge.org/dictionary/english-chinese-simplified/{0}\n格林斯词典|https://www.collinsdictionary.com/search/?dictCode=english&q={0}\n豆瓣读书|https://search.douban.com/book/subject_search?search_text={0}\n京东搜索|https://search.jd.com/Search?keyword={0}\n淘宝搜索|https://s.taobao.com/search?q={0}\nMDN搜索|https://developer.mozilla.org/zh-CN/search?q={0}\nStackOverflow|https://stackoverflow.com/search?q={0}\n"
  },
  {
    "path": "src/css/content.css",
    "content": ".dmx_unselectable {\n    -webkit-user-select: none !important;\n    -moz-user-select: none !important;\n    -ms-user-select: none !important;\n    user-select: none !important;\n}\n\n.dmx_overflow_hidden {\n    overflow: hidden;\n}\n\ndiv[mx-name=\"dream-translation\"] {\n    all: initial;\n}\n\n@font-face {\n    font-family: \"dmx-icon\";\n    /*src: url('dmx_icon.woff2') format('woff2');*/\n    src: url('chrome-extension://__MSG_@@extension_id__/css/dmx_icon.woff2') format('woff2');\n}\n\n/* Firefox */\n@-moz-document url-prefix() {\n    @font-face {\n        font-family: \"dmx-icon\";\n        src: url('moz-extension://__MSG_@@extension_id__/css/dmx_icon.woff2') format('woff2');\n    }\n}\n\n.dmx-icon {\n    font-family: \"dmx-icon\", serif !important;\n    font-size: 16px;\n    font-style: normal;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale;\n    user-select: none;\n}\n\n.dmx-icon-crop:before {\n    content: \"\\e6c8\";\n}\n\n.dmx-icon-logo:before {\n    content: \"\\e6c7\";\n}\n\n.dmx-icon-move:before {\n    content: \"\\e6c6\";\n}\n\n.dmx-icon-folder:before {\n    content: \"\\e6c4\";\n}\n\n.dmx-icon-folder-open:before {\n    content: \"\\e6c5\";\n}\n\n.dmx-icon-delete:before {\n    content: \"\\e6c2\";\n}\n\n.dmx-icon-edit:before {\n    content: \"\\e6c3\";\n}\n\n.dmx-icon-export:before {\n    content: \"\\e6c0\";\n}\n\n.dmx-icon-import:before {\n    content: \"\\e6c1\";\n}\n\n.dmx-icon-history:before {\n    content: \"\\e6bf\";\n}\n\n.dmx-icon-oxford:before {\n    content: \"\\e6be\";\n}\n\n.dmx-icon-thefree:before {\n    content: \"\\e6bd\";\n}\n\n.dmx-icon-wikipedia:before {\n    content: \"\\e6b0\";\n}\n\n.dmx-icon-dreye:before {\n    content: \"\\e6bc\";\n}\n\n.dmx-icon-eudic:before {\n    content: \"\\e6bb\";\n}\n\n.dmx-icon-etymonline:before {\n    content: \"\\e6b1\";\n}\n\n.dmx-icon-rrdict:before {\n    content: \"\\e6b9\";\n}\n\n.dmx-icon-lexico:before {\n    content: \"\\e6ba\";\n}\n\n.dmx-icon-urban:before {\n    content: \"\\e6b8\";\n}\n\n.dmx-icon-macmillan:before {\n    content: \"\\e6b7\";\n}\n\n.dmx-icon-vocabulary:before {\n    content: \"\\e6b6\";\n}\n\n.dmx-icon-wordreference:before {\n    content: \"\\e6b5\";\n}\n\n.dmx-icon-collins:before {\n    content: \"\\e6b4\";\n}\n\n.dmx-icon-iciba:before {\n    content: \"\\e6b3\";\n}\n\n.dmx-icon-hjdict:before {\n    content: \"\\e6b2\";\n}\n\n.dmx-icon-dictionary:before {\n    content: \"\\e6af\";\n}\n\n.dmx-icon-so:before {\n    content: \"\\e6aa\";\n}\n\n.dmx-icon-merriam:before {\n    content: \"\\e6ab\";\n}\n\n.dmx-icon-longman:before {\n    content: \"\\e6ac\";\n}\n\n.dmx-icon-cambridge:before {\n    content: \"\\e6ad\";\n}\n\n.dmx-icon-dictcn:before {\n    content: \"\\e6ae\";\n}\n\n.dmx-icon-sound-us:before {\n    content: \"\\e674\";\n}\n\n.dmx-icon-sound-zh:before {\n    content: \"\\e675\";\n}\n\n.dmx-icon-sound-fr:before {\n    content: \"\\e676\";\n}\n\n.dmx-icon-sound-de:before {\n    content: \"\\e677\";\n}\n\n.dmx-icon-sound-th:before {\n    content: \"\\e69c\";\n}\n\n.dmx-icon-sound-ru:before {\n    content: \"\\e69d\";\n}\n\n.dmx-icon-sound-pt:before {\n    content: \"\\e69e\";\n}\n\n.dmx-icon-sound-uk:before {\n    content: \"\\e69f\";\n}\n\n.dmx-icon-sound-jp:before {\n    content: \"\\e6a0\";\n}\n\n.dmx-icon-sound-el:before {\n    content: \"\\e6a1\";\n}\n\n.dmx-icon-sound-cn:before {\n    content: \"\\e6a2\";\n}\n\n.dmx-icon-sound-nl:before {\n    content: \"\\e6a3\";\n}\n\n.dmx-icon-sound-it:before {\n    content: \"\\e6a4\";\n}\n\n.dmx-icon-sound-sp:before {\n    content: \"\\e6a5\";\n}\n\n.dmx-icon-sound-ko:before {\n    content: \"\\e6a6\";\n}\n\n.dmx-icon-sound-ar:before {\n    content: \"\\e6a7\";\n}\n\n.dmx-icon-sound-en:before {\n    content: \"\\e6a8\";\n}\n\n.dmx-icon-sound-pl:before {\n    content: \"\\e6a9\";\n}\n\n.dmx-icon-youdao:before {\n    content: \"\\e693\";\n}\n\n.dmx-icon-sound-square:before {\n    content: \"\\e69b\";\n}\n\n.dmx-icon-sound:before {\n    content: \"\\e67a\";\n}\n\n.dmx-icon-user:before {\n    content: \"\\e699\";\n}\n\n.dmx-icon-angle-double-right:before {\n    content: \"\\e696\";\n}\n\n.dmx-icon-angle-double-left:before {\n    content: \"\\e697\";\n}\n\n.dmx-icon-angle-double-up:before {\n    content: \"\\e698\";\n}\n\n.dmx-icon-angle-double-down:before {\n    content: \"\\e69a\";\n}\n\n.dmx-icon-youdaonote:before {\n    content: \"\\e691\";\n}\n\n.dmx-icon-youtube:before {\n    content: \"\\e692\";\n}\n\n.dmx-icon-windows:before {\n    content: \"\\e694\";\n}\n\n.dmx-icon-yahoo:before {\n    content: \"\\e695\";\n}\n\n.dmx-icon-voice-off:before {\n    content: \"\\e68a\";\n}\n\n.dmx-icon-voice-on:before {\n    content: \"\\e68b\";\n}\n\n.dmx-icon-voice:before {\n    content: \"\\e68c\";\n}\n\n.dmx-icon-video:before {\n    content: \"\\e68d\";\n}\n\n.dmx-icon-vip-crown:before {\n    content: \"\\e68e\";\n}\n\n.dmx-icon-vip-fill:before {\n    content: \"\\e68f\";\n}\n\n.dmx-icon-vip-card:before {\n    content: \"\\e690\";\n}\n\n.dmx-icon-up:before {\n    content: \"\\e688\";\n}\n\n.dmx-icon-up-circle:before {\n    content: \"\\e689\";\n}\n\n.dmx-icon-trash:before {\n    content: \"\\e682\";\n}\n\n.dmx-icon-twitter:before {\n    content: \"\\e683\";\n}\n\n.dmx-icon-tool:before {\n    content: \"\\e684\";\n}\n\n.dmx-icon-telegram:before {\n    content: \"\\e685\";\n}\n\n.dmx-icon-tinder:before {\n    content: \"\\e686\";\n}\n\n.dmx-icon-twitter-circle:before {\n    content: \"\\e687\";\n}\n\n.dmx-icon-switch:before {\n    content: \"\\e67f\";\n}\n\n.dmx-icon-switch-on:before {\n    content: \"\\e680\";\n}\n\n.dmx-icon-switch-off:before {\n    content: \"\\e681\";\n}\n\n.dmx-icon-success:before {\n    content: \"\\e67d\";\n}\n\n.dmx-icon-suse:before {\n    content: \"\\e67e\";\n}\n\n.dmx-icon-star:before {\n    content: \"\\e67b\";\n}\n\n.dmx-icon-stop:before {\n    content: \"\\e67c\";\n}\n\n.dmx-icon-sound-big:before {\n    content: \"\\e673\";\n}\n\n.dmx-icon-sound-line:before {\n    content: \"\\e678\";\n}\n\n.dmx-icon-sound-horn:before {\n    content: \"\\e679\";\n}\n\n.dmx-icon-sogou:before {\n    content: \"\\e66d\";\n}\n\n.dmx-icon-smile:before {\n    content: \"\\e66e\";\n}\n\n.dmx-icon-skull:before {\n    content: \"\\e66f\";\n}\n\n.dmx-icon-search:before {\n    content: \"\\e670\";\n}\n\n.dmx-icon-s:before {\n    content: \"\\e671\";\n}\n\n.dmx-icon-setting:before {\n    content: \"\\e672\";\n}\n\n.dmx-icon-reply:before {\n    content: \"\\e668\";\n}\n\n.dmx-icon-redhat:before {\n    content: \"\\e669\";\n}\n\n.dmx-icon-right:before {\n    content: \"\\e66a\";\n}\n\n.dmx-icon-reload:before {\n    content: \"\\e66b\";\n}\n\n.dmx-icon-refresh:before {\n    content: \"\\e66c\";\n}\n\n.dmx-icon-plus:before {\n    content: \"\\e665\";\n}\n\n.dmx-icon-play:before {\n    content: \"\\e666\";\n}\n\n.dmx-icon-qq:before {\n    content: \"\\e667\";\n}\n\n.dmx-icon-plane-send:before {\n    content: \"\\e65c\";\n}\n\n.dmx-icon-plane-departure:before {\n    content: \"\\e65d\";\n}\n\n.dmx-icon-plane-up:before {\n    content: \"\\e65e\";\n}\n\n.dmx-icon-plane-paper:before {\n    content: \"\\e65f\";\n}\n\n.dmx-icon-plane:before {\n    content: \"\\e660\";\n}\n\n.dmx-icon-plane-fly:before {\n    content: \"\\e661\";\n}\n\n.dmx-icon-plane-up-fill:before {\n    content: \"\\e662\";\n}\n\n.dmx-icon-plane-send-fill:before {\n    content: \"\\e663\";\n}\n\n.dmx-icon-plane-takeoff:before {\n    content: \"\\e664\";\n}\n\n.dmx-icon-picture-fill:before {\n    content: \"\\e658\";\n}\n\n.dmx-icon-pin:before {\n    content: \"\\e659\";\n}\n\n.dmx-icon-pin-left:before {\n    content: \"\\e65a\";\n}\n\n.dmx-icon-picture:before {\n    content: \"\\e65b\";\n}\n\n.dmx-icon-microsoft:before {\n    content: \"\\e651\";\n}\n\n.dmx-icon-m:before {\n    content: \"\\e652\";\n}\n\n.dmx-icon-modx:before {\n    content: \"\\e653\";\n}\n\n.dmx-icon-meetup:before {\n    content: \"\\e654\";\n}\n\n.dmx-icon-mule:before {\n    content: \"\\e655\";\n}\n\n.dmx-icon-music:before {\n    content: \"\\e656\";\n}\n\n.dmx-icon-more:before {\n    content: \"\\e657\";\n}\n\n.dmx-icon-loading:before {\n    content: \"\\e650\";\n}\n\n.dmx-icon-l:before {\n    content: \"\\e64a\";\n}\n\n.dmx-icon-itunes:before {\n    content: \"\\e64b\";\n}\n\n.dmx-icon-info:before {\n    content: \"\\e64c\";\n}\n\n.dmx-icon-laugh:before {\n    content: \"\\e64d\";\n}\n\n.dmx-icon-left:before {\n    content: \"\\e64e\";\n}\n\n.dmx-icon-list:before {\n    content: \"\\e64f\";\n}\n\n.dmx-icon-help:before {\n    content: \"\\e647\";\n}\n\n.dmx-icon-horn:before {\n    content: \"\\e648\";\n}\n\n.dmx-icon-home:before {\n    content: \"\\e649\";\n}\n\n.dmx-icon-headset:before {\n    content: \"\\e644\";\n}\n\n.dmx-icon-headset-c:before {\n    content: \"\\e645\";\n}\n\n.dmx-icon-heart:before {\n    content: \"\\e646\";\n}\n\n.dmx-icon-good:before {\n    content: \"\\e642\";\n}\n\n.dmx-icon-google:before {\n    content: \"\\e643\";\n}\n\n.dmx-icon-fullscreen-exit:before {\n    content: \"\\e63e\";\n}\n\n.dmx-icon-fullscreen:before {\n    content: \"\\e63f\";\n}\n\n.dmx-icon-fan:before {\n    content: \"\\e640\";\n}\n\n.dmx-icon-flag:before {\n    content: \"\\e641\";\n}\n\n.dmx-icon-eye-left:before {\n    content: \"\\e639\";\n}\n\n.dmx-icon-error:before {\n    content: \"\\e63a\";\n}\n\n.dmx-icon-eye-right:before {\n    content: \"\\e63b\";\n}\n\n.dmx-icon-exchange:before {\n    content: \"\\e63c\";\n}\n\n.dmx-icon-edge:before {\n    content: \"\\e63d\";\n}\n\n.dmx-icon-down:before {\n    content: \"\\e634\";\n}\n\n.dmx-icon-diaspora:before {\n    content: \"\\e635\";\n}\n\n.dmx-icon-devil:before {\n    content: \"\\e636\";\n}\n\n.dmx-icon-degree:before {\n    content: \"\\e637\";\n}\n\n.dmx-icon-d:before {\n    content: \"\\e638\";\n}\n\n.dmx-icon-close:before {\n    content: \"\\e62e\";\n}\n\n.dmx-icon-circle:before {\n    content: \"\\e62f\";\n}\n\n.dmx-icon-cry-fill:before {\n    content: \"\\e630\";\n}\n\n.dmx-icon-copy:before {\n    content: \"\\e631\";\n}\n\n.dmx-icon-cry:before {\n    content: \"\\e632\";\n}\n\n.dmx-icon-close-bg:before {\n    content: \"\\e633\";\n}\n\n.dmx-icon-bing:before {\n    content: \"\\e62b\";\n}\n\n.dmx-icon-bell:before {\n    content: \"\\e62c\";\n}\n\n.dmx-icon-baidu:before {\n    content: \"\\e62d\";\n}\n\n.dmx-icon-arrow-right:before {\n    content: \"\\e625\";\n}\n\n.dmx-icon-arrow-left:before {\n    content: \"\\e626\";\n}\n\n.dmx-icon-arrow-up:before {\n    content: \"\\e627\";\n}\n\n.dmx-icon-arrow-down:before {\n    content: \"\\e628\";\n}\n\n.dmx-icon-arrow-thin-down:before {\n    content: \"\\e629\";\n}\n\n.dmx-icon-arrow-thin-up:before {\n    content: \"\\e62a\";\n}\n\n.dmx-icon-android:before {\n    content: \"\\e622\";\n}\n\n.dmx-icon-apple:before {\n    content: \"\\e623\";\n}\n\n.dmx-icon-angular:before {\n    content: \"\\e624\";\n}\n\n.dmx-icon-alibaba:before {\n    content: \"\\e621\";\n}\n"
  },
  {
    "path": "src/css/dmx_dialog.css",
    "content": "p, h1, h2, h3, h4, h5, h6, ul, li, ol, dl, dt, dd {\n    margin: 0;\n    padding: 0;\n    list-style: none;\n}\n\nh1, h2, h3, h4, h5, h6 {\n    font-size: 18px;\n}\n\nblockquote {\n    margin: 10px 0;\n    padding-left: 5px;\n    border-left: 6px solid #e4dfca;\n}\n\n.dmx_unselectable {\n    -webkit-user-select: none !important;\n    -moz-user-select: none !important;\n    -ms-user-select: none !important;\n    user-select: none !important;\n}\n\n.dmx_overflow_hidden {\n    overflow: hidden;\n}\n\n.dmx_hide {\n    display: none;\n}\n\n.dmx_show {\n    display: block;\n}\n\n#dmx_mouse_icon, #dmx_crop_bg, #dmx_crop_fg {\n    display: none;\n    position: fixed;\n    top: 0;\n    left: 0;\n}\n\n#dmx_mouse_icon {\n    z-index: 999999999999;\n    font-family: \"dmx-icon\", serif !important;\n    width: 32px;\n    height: 32px;\n    font-size: 18px;\n    font-style: normal;\n    background: #4395ff;\n    border: 1px solid #3589ff;\n    box-shadow: 0 1px 3px rgba(0, 0, 0, .19), inset 0 1px 0 rgba(255, 255, 255, .4);\n    border-radius: 50%;\n    color: #fff;\n    box-sizing: border-box;\n    justify-content: center;\n    align-items: center;\n    cursor: pointer;\n    transition: transform 0.25s ease;\n}\n\n#dmx_mouse_icon:before {\n    content: \"\\e661\";\n}\n\n#dmx_mouse_icon:hover {\n    background: linear-gradient(#FF416C, #FF4B2B);\n    border: 1px solid #FF416C;\n}\n\n#dmx_mouse_icon:hover:before {\n    transform: rotate(-30deg);\n}\n\n#dmx_crop_bg {\n    z-index: 9999999999998;\n    width: 100%;\n    height: 100%;\n    background: #000;\n    opacity: .2;\n    cursor: crosshair;\n}\n\n#dmx_crop_fg {\n    z-index: 9999999999999;\n    width: 1px;\n    height: 1px;\n    background: #fff;\n    opacity: .7;\n    border: 1px dashed #000;\n}\n\n#dmx_dialog {\n    font-family: Roboto, 'Segoe UI', Arial, 'Microsoft Yahei', sans-serif;\n    /*font-family: Helvetica Neue, PingFang SC, Microsoft YaHei, sans-serif;*/\n    all: initial;\n    position: fixed;\n    z-index: 999999999998;\n    width: 500px;\n    height: 500px;\n    /*overflow: hidden;*/\n    background: #f5f8fa;\n    border: 1px solid #cfd0d2;\n    border-radius: 6px;\n    /*top: calc(50% - 250px);*/\n    /*left: calc(50% - 250px);*/\n    right: 0;\n    bottom: 0;\n    box-sizing: border-box;\n    box-shadow: 0 0 12px -2px rgba(32, 32, 32, .5);\n    display: none;\n}\n\n#dmx_dialog.dmx_keep_right {\n    height: auto;\n    top: 0;\n    right: 0;\n    bottom: 0;\n}\n\n#dmx_dialog.dmx_popup {\n    top: 0;\n    left: 0;\n    width: 520px;\n    height: 500px;\n    border: 0;\n    border-radius: 0;\n}\n\n#dmx_dialog.dmx_popup.fullscreen {\n    width: 100%;\n    height: 100%;\n    min-width: 450px;\n    min-height: 300px;\n}\n\n#dmx_dialog_resize_n {\n    cursor: ns-resize;\n    position: absolute;\n    z-index: 999999999999;\n    left: 0;\n    top: -5px;\n    width: 100%;\n    height: 8px\n}\n\n#dmx_dialog_resize_e {\n    cursor: ew-resize;\n    position: absolute;\n    z-index: 999999999999;\n    right: -7px;\n    top: 0;\n    width: 8px;\n    height: 100%\n}\n\n#dmx_dialog_resize_s {\n    cursor: ns-resize;\n    position: absolute;\n    z-index: 999999999999;\n    left: 0;\n    bottom: -5px;\n    width: 100%;\n    height: 8px\n}\n\n#dmx_dialog_resize_w {\n    cursor: ew-resize;\n    position: absolute;\n    z-index: 999999999999;\n    left: -5px;\n    top: 0;\n    width: 8px;\n    height: 100%\n}\n\n#dmx_dialog_resize_nw {\n    cursor: nwse-resize;\n    position: absolute;\n    z-index: 999999999999;\n    left: -5px;\n    top: -5px;\n    width: 17px;\n    height: 17px;\n}\n\n#dmx_dialog_resize_ne {\n    cursor: nesw-resize;\n    position: absolute;\n    z-index: 999999999999;\n    right: -5px;\n    top: -5px;\n    width: 10px;\n    height: 10px\n}\n\n#dmx_dialog_resize_sw {\n    cursor: nesw-resize;\n    position: absolute;\n    z-index: 999999999999;\n    left: -5px;\n    bottom: -5px;\n    width: 10px;\n    height: 10px\n}\n\n#dmx_dialog_resize_se {\n    cursor: nwse-resize;\n    position: absolute;\n    z-index: 999999999999;\n    right: -5px;\n    bottom: -5px;\n    width: 17px;\n    height: 17px;\n}\n\n#dmx_dialog_title {\n    height: 32px;\n    line-height: 32px;\n    padding: 0 10px;\n    background: #f5f8fa;\n    border-bottom: 1px solid #cfd0d2;\n    border-radius: 6px 6px 0 0;\n    cursor: move;\n}\n\n#dmx_dialog_title .dmx-icon {\n    cursor: pointer;\n    color: #232B33;\n}\n\n#dmx_dialog_title .dmx-icon:hover {\n    opacity: 0.7;\n}\n\n#dmx_dialog_title .dmx_left {\n    float: left;\n}\n\n#dmx_dialog_title .dmx_left > div {\n    float: left;\n    margin-right: 8px;\n}\n\n#dmx_dialog_title #dmx_close .dmx-icon {\n    color: #FC4A4A;\n    font-size: 18px;\n}\n\n#dmx_dialog_title #dmx_pin:before {\n    content: \"\\e65a\";\n    padding: 2px;\n    font-size: 18px;\n}\n\n#dmx_dialog_title #dmx_pin.active:before {\n    content: \"\\e659\";\n    background: #e0e3e6;\n    border-radius: 2px;\n}\n\n#dmx_dialog_title #dmx_fullscreen:before {\n    content: \"\\e63f\";\n}\n\n#dmx_dialog_title #dmx_fullscreen.active:before {\n    content: \"\\e63e\";\n}\n\n#dmx_dialog_title .dmx-icon-voice {\n    font-size: 18px;\n}\n\n#dmx_dialog_title .dmx-icon-heart {\n    color: #FC4A4A;\n}\n\n#dmx_dialog_title #dmx_history {\n    margin: 2px 0 0 15px;\n    border: 1px solid gray;\n    border-radius: 30px;\n    height: 26px;\n    overflow: hidden;\n}\n\n#dmx_dialog_title #dmx_history .dmx-icon {\n    display: block;\n    float: left;\n    margin: 0;\n    line-height: 26px;\n}\n\n#dmx_dialog_title #dmx_history .dmx-icon:hover {\n    background: #252525;\n    color: #fff;\n}\n\n#dmx_dialog_title #dmx_history .dmx-icon-left {\n    padding: 0 8px;\n    border-right: 1px solid gray;\n}\n\n#dmx_dialog_title #dmx_history .dmx-icon-right {\n    padding: 0 8px;\n}\n\n#dmx_dialog_title #dmx_history .dmx-icon.disabled {\n    color: #9EA0A3;\n    background: #F3F6F9;\n    opacity: 0.8;\n}\n\n#dmx_dialog.dmx_popup #dmx_history {\n    margin-left: 0;\n}\n\n#dmx_dialog_title #dmx_navigate {\n    margin: 1px 0 0 15px;\n}\n\n#dmx_dialog_title #dmx_navigate u {\n    float: left;\n    display: block;\n    background: #F3F6F9;\n    border: 1px solid #C4C5C8;\n    border-radius: 5px 5px 0 0;\n    padding: 0 8px;\n    margin-right: 5px;\n    height: 30px;\n    line-height: 30px;\n    overflow: hidden;\n    font-size: 15px;\n    font-weight: 500;\n    text-decoration: none;\n    cursor: pointer;\n}\n\n#dmx_dialog_title #dmx_navigate u.active {\n    border-bottom: 1px solid #F3F6F9;\n    opacity: 1;\n}\n\n#dmx_dialog_title .dmx_right {\n    float: right;\n}\n\n#dmx_dialog_title .dmx_right > div {\n    float: right;\n    margin-left: 8px;\n}\n\n#dmx_dialog_content {\n    box-sizing: border-box;\n    position: relative;\n    margin-top: 2px;\n    width: 100%;\n    height: calc(100% - 35px);\n    overflow: auto;\n}\n\n#dmx_iframe {\n    box-sizing: border-box;\n    width: 100%;\n    height: calc(100% - 6px);\n    border: none;\n}\n\n#dmx_head {\n    /*position: sticky;*/\n    box-sizing: border-box;\n    width: 100%;\n    height: 45px;\n    padding: 2px 10px 5px;\n    background: #F3F6F9;\n    border-bottom: 1px solid rgba(32, 33, 36, 0.28);\n    box-shadow: 0 1px 4px 0 rgba(32, 33, 36, 0.18);\n}\n\n.dmx_content {\n    box-sizing: border-box;\n    position: absolute;\n    left: 0;\n    top: 45px;\n    width: 100%;\n    height: calc(100% - 45px);\n    overflow: auto;\n}\n\n.dmx_main {\n    margin: 0 auto;\n    padding: 10px;\n    min-width: 100px;\n    font-size: 15px;\n    line-height: 1.8;\n}\n\n.dmx_main_trans {\n    padding-bottom: 0;\n}\n\n.dmx_main_search {\n    padding: 10px 0 0 10px;\n}\n\n#dmx_dialog_left {\n    position: absolute;\n    bottom: 0;\n    left: -37px;\n    width: 37px;\n}\n\n#dmx_dialog_left a {\n    display: block;\n    width: 32px;\n    height: 32px;\n    line-height: 30px;\n    font-size: 16px;\n    background: #4395ff;\n    border: 1px solid #3589ff;\n    box-shadow: 0 1px 3px rgba(0, 0, 0, .19), inset 0 1px 0 rgba(255, 255, 255, .4);\n    border-radius: 50%;\n    color: #fff;\n    box-sizing: border-box;\n    text-align: center;\n    margin-top: 5px;\n}\n\n.dmx_loading {\n    border: 3px solid #bdc3c7;\n    border-top: 3px solid #2c3e50;\n    border-bottom: 3px solid #2c3e50;\n    border-radius: 50%;\n    display: block;\n    width: 16px;\n    height: 16px;\n    animation: spin 1.2s linear infinite;\n}\n\n@keyframes spin {\n    0% {\n        transform: rotate(0deg);\n    }\n    100% {\n        transform: rotate(360deg);\n    }\n}\n\n::-webkit-scrollbar {\n    width: 5px;\n    height: 5px;\n}\n\n::-webkit-scrollbar-track {\n    /*background-color: #E2E4E6;*/\n}\n\n::-webkit-scrollbar-thumb {\n    background-color: #bdc3c7;\n    border-radius: 5px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n    background-color: #9da5ac;\n}\n\n::-webkit-scrollbar-thumb:active {\n    background-color: #2c3e50;\n}\n\np, h1, h2, h3, h4, h5, h6 {\n    margin: 0;\n    padding: 0;\n}\n\na {\n    text-decoration: none;\n    color: #333;\n}\n\na:hover {\n    color: #1a73e8;\n}\n\n.fx:after {\n    display: block;\n    content: '';\n    clear: both;\n}\n\n.mt-1 {\n    margin-top: 10px;\n}\n\n.mb-1 {\n    margin-bottom: 10px;\n}\n\n/* ==================== translate ==================== */\n#translate_input {\n    background: #fff;\n    color: #4e5053;\n    min-height: 26px;\n    line-height: 1.6;\n    padding: 5px 10px;\n    outline: none;\n    overflow: auto;\n}\n\n#translate_input:focus {\n    border-color: #409eff;\n}\n\n#translate_input.big {\n    min-height: 52px;\n}\n\n.language_box {\n    position: relative;\n}\n\n.language_box > div {\n    float: left;\n}\n\n.language_button {\n    background: #ffffff;\n    border: 1px solid #cfd0d2;\n    border-radius: 8px;\n    margin-top: 10px;\n    padding: 2px 10px;\n    min-width: 80px;\n    height: 28px;\n    line-height: 28px;\n    text-align: center;\n    user-select: none;\n    cursor: pointer;\n}\n\n.language_button:hover {\n    color: #1a73e8;\n    box-shadow: -2px -2px 2px 0 #d0d7de;\n}\n\n.language_button:after {\n    font-size: 8px;\n    margin-left: 6px;\n    content: \"\\e629\";\n}\n\n.language_button.active {\n    z-index: 999;\n    position: relative;\n    border-radius: 8px 8px 0 0;\n    border-bottom: 1px solid #fff;\n    box-shadow: -2px 0 0 0 #d0d7de;\n    height: 31px;\n}\n\n.language_button.active:after {\n    content: \"\\e62a\";\n}\n\n#language_dropdown {\n    z-index: 998;\n    position: absolute;\n    left: 0;\n    top: 46px;\n    box-sizing: border-box;\n    width: 100%;\n    background: #fff;\n    border: 1px solid #cfd0d2;\n    border-radius: 0 8px 8px 8px;\n    padding: 10px 10px 5px 10px;\n    margin-bottom: 3px;\n    box-shadow: 0 2px 2px 2px #d0d7de;\n    display: none;\n}\n\n#language_dropdown.dropdown_target {\n    border-radius: 8px;\n}\n\n#language_dropdown.dropdown_target u:first-child {\n    display: none;\n}\n\n#language_dropdown u {\n    float: left;\n    display: block;\n    text-decoration: none;\n    background: #f5f5f5;\n    border: 1px solid #d3d3d3;\n    border-radius: 5px;\n    color: rgba(0, 0, 0, 0.87);\n    padding: 2px 5px;\n    margin: 0 5px 5px 0;\n    cursor: pointer;\n}\n\n#language_dropdown u:hover {\n    background: #414345;\n    border: 1px solid #232526;\n    color: #fff;\n}\n\n#language_dropdown u.active {\n    background: #e8f0fe;\n    border: 1px solid #185abc;\n    color: #185abc;\n}\n\n#language_dropdown u.disabled {\n    background: #f5f5f5;\n    border: 1px solid #d3d3d3;\n    color: #ddd;\n    cursor: not-allowed;\n}\n\n#language_exchange {\n    margin-top: 10px;\n    padding: 2px 8px;\n    cursor: pointer;\n}\n\n#language_exchange.disabled {\n    color: #ddd;\n    cursor: not-allowed;\n}\n\n#translate_crop {\n    float: right;\n    background: #fff;\n    border: 1px solid #dcdfe6;\n    color: #606266;\n    border-radius: 8px;\n    height: 28px;\n    line-height: 28px;\n    padding: 2px 7px;\n    margin: 10px 10px 0 0;\n    cursor: pointer;\n}\n\n#translate_button {\n    float: right;\n    background: #4395ff;\n    border: 1px solid #3589ff;\n    border-radius: 8px;\n    color: #fff;\n    height: 28px;\n    line-height: 28px;\n    margin-top: 10px;\n    padding: 2px 15px;\n    cursor: pointer;\n    user-select: none;\n    transition: .1s;\n}\n\n#translate_button:hover, #translate_crop:hover {\n    box-shadow: -2px -2px 2px 0 #d0d7de;\n}\n\n#translate_button:active {\n    opacity: .7;\n}\n\n.case {\n    font-size: 16px;\n    margin-top: 10px;\n    border-radius: 8px;\n    background: #ffffff;\n    border: 1px solid #cfd0d2;\n    transition: all .2s linear;\n}\n\n.case:hover {\n    box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);\n    /*transform: scale(1.005);*/\n}\n\n.case:first-child {\n    margin-top: 0;\n}\n\n.case .dmx-icon {\n    cursor: pointer;\n}\n\n.case .dmx-icon:hover {\n    color: #1a73e8;\n}\n\n.case_copy, .case_bilingual {\n    display: none;\n}\n\n.case:hover .case_copy, .case:hover .case_bilingual {\n    display: block;\n}\n\n.case_content {\n    padding: 7px 10px;\n    line-height: 1.6;\n}\n\n.case_content > div, .case_dd > div {\n    margin-top: 5px;\n}\n\n.case_content > div:first-child, .case_dd > div:first-child {\n    padding-top: 0 !important;\n}\n\n.case_content [data-search=\"true\"] {\n    cursor: pointer;\n}\n\n.case_content p.source_text {\n    color: #7398e6;\n    margin-top: 5px;\n}\n\n.case_content p.source_text:first-child {\n    margin-top: 0;\n}\n\n.case_dd {\n    margin-top: 10px;\n    border-top: thin dashed #ccc;\n}\n\n.case_dd_head {\n    padding: 10px 0 0;\n    font-weight: bold;\n    font-size: 110%;\n}\n\n.case_dd_ph .dmx-icon {\n    margin-left: 5px;\n}\n\n.case_dd_parts p {\n    padding: 3px 0 0;\n}\n\n.case_dd_parts p:first-child {\n    padding: 0;\n}\n\n.case_dd_parts p b {\n    color: #409eff;\n    background: #ecf5ff;\n    border: 1px solid #d9ecff;\n    border-radius: 4px;\n    padding: 0 5px;\n    margin-right: 10px;\n}\n\n.case_dd_exchange b {\n    font-weight: 500;\n}\n\n.case_dd_exchange u {\n    text-decoration: none;\n    margin-right: 10px;\n}\n\n.case_dd_exchange u a {\n    margin-left: 5px;\n}\n\n.case_dd_chart {\n    color: #e6a23c;\n    background-color: #fdf6ec;\n    border: 1px solid #faecd8;\n    border-radius: 5px;\n    padding: 5px;\n}\n\n.case_dd_chart u {\n    text-decoration: none;\n    margin: 0 10px 0 5px;\n    color: #ffc942;\n}\n\n.case_dd_example em {\n    font-style: normal;\n    color: #e6a23c;\n}\n\n.case_dd_tags u {\n    text-decoration: none;\n    display: inline-block;\n    margin-right: 5px;\n    padding: 0 5px;\n    color: #909399;\n    background: #f4f4f5;\n    border: 1px solid #e9e9eb;\n    border-radius: 4px;\n    font-size: 90%;\n}\n\n.case_dd_img a {\n    margin-right: 5px;\n}\n\n.case_dd_img img {\n    border: 1px solid #ccc;\n    width: 80px;\n    height: 80px;\n    object-fit: contain;\n}\n\n.case_top {\n    padding: 2px 10px;\n    border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n.case_bottom {\n    padding: 2px 10px;\n    border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n.case_left {\n    float: left;\n    margin-right: 10px;\n}\n\n.case_left:last-child {\n    margin-right: 0;\n}\n\n.case_right {\n    float: right;\n    margin-left: 15px;\n}\n\n.case_right:last-child {\n    margin-left: 0;\n}\n\n.case_right a {\n    font-weight: bold;\n}\n\n.case_right a .dmx-icon {\n    margin-right: 3px;\n}\n\n.case_bilingual {\n    cursor: pointer;\n}\n\n.case_bilingual:after {\n    font-size: 22px;\n    line-height: 1;\n    display: inline-block;\n    vertical-align: middle;\n    margin-left: 3px;\n    content: \"\\e681\";\n    color: rgba(0, 0, 0, 0.26);\n}\n\n.case_bilingual.active:after {\n    content: \"\\e680\";\n    color: #1a73e8;\n}\n\n.dmx_ripple {\n    position: relative;\n    display: inline-block;\n    vertical-align: middle;\n    text-align: center;\n    width: 26px;\n    height: 26px;\n    line-height: 26px;\n    font-size: 20px;\n    color: #535353;\n    cursor: pointer;\n    transition: all .2s;\n}\n\n.dmx_ripple:hover {\n    color: #1a73e8;\n    transform: scale(1.3);\n}\n\n.dmx_ripple.active {\n    color: #e53935;\n}\n\n.dmx_ripple.active:before, .dmx_ripple.active:after {\n    display: block;\n    position: absolute;\n    content: '';\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    border-radius: 50%;\n    border: 1px solid #e35d5b;\n}\n\n.dmx_ripple.active:before {\n    animation: dmx_ripple 1s linear infinite;\n}\n\n.dmx_ripple.active:after {\n    animation: dmx_ripple 1s linear 0.5s infinite;\n}\n\n.dmx_reverse:before, .dmx_reverse:after {\n    animation-direction: reverse !important;\n}\n\n@keyframes dmx_ripple {\n    0% {\n        transform: scale(1);\n    }\n    50% {\n        transform: scale(1.75);\n        opacity: 0.5;\n    }\n    75% {\n        transform: scale(1.95);\n        opacity: 0.2;\n    }\n    100% {\n        transform: scale(2.05);\n        opacity: 0;\n    }\n}\n\n.dmx_pink {\n    color: deeppink;\n}\n\n/* ==================== search ==================== */\n.search_box {\n    background: #fff;\n    color: #4e5053;\n    height: 30px;\n    line-height: 30px;\n    padding: 3px 60px 3px 8px;\n    position: relative;\n}\n\n.search_box:hover {\n    border-color: #409eff;\n}\n\n.search_box > input {\n    display: block;\n    width: 100%;\n    height: 28px;\n    font-size: 17px;\n    outline: none;\n    border: none;\n}\n\n.search_box #search_but {\n    position: absolute;\n    top: calc(50% - 15px);\n    right: 10px;\n}\n\n.search_box #search_remove {\n    position: absolute;\n    top: calc(50% - 15px);\n    right: 35px;\n    display: none;\n}\n\n.search_box:hover #search_remove {\n    display: block;\n}\n\n/* ==================== alert ==================== */\n#dmx_alert {\n    z-index: 999999999999;\n    position: fixed;\n    top: 0;\n    left: 50%;\n    transform: translateX(-50%);\n    width: 300px;\n}\n\n#dmx_alert > div {\n    position: relative;\n    padding: 10px;\n    font-size: 15px;\n    line-height: 1.5;\n    box-sizing: border-box;\n    color: #ffffff;\n    background-color: #414345;\n    border: 1px solid #232526;\n    border-radius: 4px;\n    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);\n    transition: all .3s;\n    overflow: hidden;\n}\n\n#dmx_alert > div.an_top {\n    margin-top: 10px;\n}\n\n#dmx_alert > div.an_delete {\n    opacity: 0.1;\n}\n\n#dmx_alert > div .dmx-icon {\n    margin-right: 5px;\n}\n\n#dmx_alert .dxm_alert_success {\n    background-color: #6FBD39;\n    border: 1px solid #56ab2f;\n}\n\n#dmx_alert .dxm_alert_error {\n    background-color: #ef473a;\n    border: 1px solid #b31217;\n}\n\n/* ==================== button ==================== */\n.dmx_button {\n    display: inline-block;\n    background: #4395ff;\n    border: 1px solid #3589ff;\n    border-radius: 6px;\n    color: #fff;\n    line-height: 1;\n    margin: 0 10px 10px 0;\n    padding: 8px 12px;\n    white-space: nowrap;\n    text-align: center;\n    cursor: pointer;\n    user-select: none;\n    transition: .1s;\n}\n\n.dmx_button:last-child {\n    margin: 0;\n}\n\n.dmx_button:hover {\n    box-shadow: 1px 4px 6px 0 #d0d7de;\n}\n\n.dmx_button:active {\n    opacity: .7;\n}\n\n.dmx_button .dmx-icon {\n    margin-right: 5px;\n}\n\n.dmx_button_default {\n    background: #fff;\n    border: 1px solid #dcdfe6;\n    color: #606266;\n}\n\n.dmx_button_success {\n    background-color: #67c23a;\n    border-color: #67c23a;\n    color: #fff;\n}\n\n.dmx_button_warning {\n    background-color: #e6a23c;\n    border-color: #e6a23c;\n    color: #fff;\n}\n\n.dmx_button_danger {\n    background-color: #ef473a;\n    border-color: #dd1419;\n    color: #fff;\n}\n\n/* dict */\n.dict_cambridge .sense-body {\n    margin-top: 10px;\n}\n\n.dict_cambridge .dxref {\n    margin-right: 3px;\n    padding: 2px 6px;\n    color: #fff;\n    font-weight: 700;\n    font-size: .75rem;\n    text-align: center;\n    background: #1d2956;\n    border-radius: 50px;\n}\n\n.dict_cambridge .ddef_h {\n    margin-bottom: 10px;\n    font-weight: 700;\n}\n\n.dict_cambridge .dsense_h, .dict_cambridge .ca_h {\n    margin-top: 10px;\n    font-weight: 700;\n    color: #5d2fc1;\n}\n\n.dict_cambridge .dtrans {\n    display: block;\n    color: #0580e8;\n}\n\n.dict_collins .dictlink {\n    margin-top: 10px;\n}\n\n.dict_collins .dictlink:first-child {\n    margin-top: 0;\n}\n\n.dict_collins .sensenum, .dict_collins .def, .dict_collins .word-frequency-container .label {\n    display: inline-block;\n}\n\n.dict_collins .title_frequency_container {\n    color: #c12d30;\n}\n\n.dict_collins .pos {\n    text-transform: uppercase;\n    font-weight: bold;\n    color: #c12d30;\n}\n\n.dict_collins .hom {\n    margin-top: 10px;\n}\n\n.dict_collins .hom:first-of-type {\n    margin-top: 0;\n}\n\n.dict_collins .imageRight {\n    float: right;\n    width: 30%;\n    min-width: 150px;\n    padding: 2px;\n    border: 1px solid #bbb;\n    border-radius: 5px;\n    margin: 0 0 5px 5px;\n}\n\n.dict_etymonline [class^=\"word__name--\"] {\n    font-size: 120%;\n    font-weight: 700;\n    color: #0580e8;\n}\n\n.dict_oxford .examples {\n    margin-left: 10px;\n}\n\n.dict_wr .rh_pdef {\n    display: block;\n}\n\n.dict_macmillan .dflex {\n    display: flex;\n    flex: 1 1 auto;\n    flex-wrap: nowrap;\n}\n\n.dict_macmillan .SENSE-NUM, .dict_macmillan .note {\n    display: inline-block;\n    min-width: 1.2em;\n    height: 1.2em;\n    line-height: 1.2em;\n    padding: .2em;\n    margin-right: .2em;\n    border-radius: 1em;\n    background: #d70a20;\n    text-align: center;\n    font-weight: bold;\n    color: white;\n}\n"
  },
  {
    "path": "src/css/longman.css",
    "content": ".longman_dict .Sense img {\n    float: right;\n    width: 30%;\n    max-width: 400px;\n    margin-left: 5px;\n}\n\n.longman_dict .Sense:after {\n    display: block;\n    content: '';\n    clear: both;\n}\n\n.longman_dict .EXAMPLE .dmx-icon {\n    margin-right: 5px;\n}\n\n.longman_dict .infls,\n.longman_dict .description,\n.longman_dict .title,\n.longman_dict .url,\n.longman_dict .summary,\n.longman_dict .og,\n.longman_dict .infls,\n.longman_dict .description,\n.longman_dict .title,\n.longman_dict .url,\n.longman_dict .summary,\n.longman_dict .og,\n.longman_dict .assetref,\n.longman_dict .assetref,\n.longman_dict .dictentry,\n.longman_dict .dictionary_intro,\n.longman_dict .topic_intro,\n.longman_dict .exaGroup,\n.longman_dict .exaGroup .exaEntry,\n.longman_dict .exaGroup .title,\n.longman_dict .exaGroup .exa,\n.ldoceEntry .Entry,\n.ldoceEntry .ErrorBox,\n.ldoceEntry .EXAMPLE,\n.ldoceEntry .GramExa,\n.ldoceEntry .PhrVbEntry,\n.ldoceEntry .RunOn,\n.ldoceEntry .Sense,\n.ldoceEntry .Subsense,\n.ldoceEntry .ColloBox,\n.ldoceEntry .ThesBox,\n.ldoceEntry .F2NBox,\n.ldoceEntry .GramBox,\n.ldoceEntry .HEADING.newline,\n.ldoceEntry .Collocate,\n.ldoceEntry .Exponent,\n.ldoceEntry .EXPL,\n.ldoceEntry .COLLEXA,\n.ldoceEntry .THESEXA,\n.ldoceEntry .LearnerItem,\n.ldoceEntry .ColloExa,\n.bussdictEntry .Box,\n.bussdictEntry .Entry,\n.bussdictEntry .ColloExa,\n.bussdictEntry .ErrorBox,\n.bussdictEntry .EXAMPLE,\n.bussdictEntry .GramExa,\n.bussdictEntry .PhrVbEntry,\n.bussdictEntry .Sense,\n.bussdictEntry .Subsense,\n.bussdictEntry .ColloBox,\n.bussdictEntry .ThesBox,\n.bussdictEntry .F2NBox,\n.bussdictEntry .GramBox,\n.bussdictEntry .UsageBox,\n.bussdictEntry .HEADING.newline,\n.bussdictEntry .SECHEADING,\n.bussdictEntry .subheading,\n.bussdictEntry .Collocate.newline,\n.bussdictEntry .Exponent,\n.bussdictEntry .GramBox .EXPL,\n.bussdictEntry .EXPL.newline,\n.bussdictEntry .LearnerItem,\n.bussdictEntry .CompareWord,\n.bussdictEntry .EXP,\n.bussdictEntry .boxheader,\n.bussdictEntry .SubEntry {\n    display: block;\n}\n\n.ldoceEntry .FIELD,\n.ldoceEntry .HWD,\n.ldoceEntry .NOTE,\n.ldoceEntry .Noteprompt,\n.ldoceEntry .PIC,\n.ldoceEntry .PICCAL,\n.ldoceEntry .ACTIV,\n.ldoceEntry .BOX,\n.ldoceEntry .COMMENT,\n.ldoceEntry .USAGE,\n.bussdictEntry .ACTIV,\n.bussdictEntry .COMMENT,\n.bussdictEntry .FIELD,\n.bussdictEntry .HWD,\n.bussdictEntry .NOTE,\n.bussdictEntry .Noteprompt,\n.bussdictEntry .PIC,\n.bussdictEntry .PICCAL,\n.bussdictEntry .FIELDXX,\n.bussdictEntry .USAGE,\n.bussdictEntry .Crossrefto .REFLEX,\n.longman_dict .suppressed,\n.longman_dict .verbTable .view_less,\n.longman_dict .verbTable .next_tenses {\n    display: none;\n}\n\n.bussdictEntry .SubEntry .HWD,\n.bussdictEntry .GramBox.nobox,\n.bussdictEntry .GramBox.nobox .EXPL,\n.bussdictEntry .Collocate.inline {\n    display: inline;\n}\n\n.longman_dict .Crossrefto,\n.longman_dict .assettype,\n.longman_dict .exaGroup .title,\n.longman_dict .exaGroup .NodeW,\n.longman_dict .wordfams,\n.longman_dict .etym .Head,\n.longman_dict .verbTable .lemma,\n.longman_dict .verbTable .Simple_Form .aux,\n.longman_dict .verbTable .col1,\n.longman_dict .verbTable .view_more,\n.longman_dict .verbTable .view_less,\n.longman_dict .verbTable .verb_form,\n.ldoceEntry .ABBR,\n.ldoceEntry .AMEQUIV,\n.ldoceEntry .BREQUIV,\n.ldoceEntry .COLLO,\n.ldoceEntry .COLLOINEXA,\n.ldoceEntry .COMP,\n.ldoceEntry .Crossrefto,\n.ldoceEntry .DERIV,\n.ldoceEntry .FULLFORM,\n.ldoceEntry .GRAM,\n.ldoceEntry .HINTBOLD,\n.ldoceEntry .HINTTITLE,\n.ldoceEntry .HOMNUM,\n.ldoceEntry .HYPHENATION,\n.ldoceEntry .PHRVBHWD,\n.ldoceEntry .LEXUNIT,\n.ldoceEntry .LEXVAR,\n.ldoceEntry .OPP,\n.ldoceEntry .ORTHVAR,\n.ldoceEntry .PASTPART,\n.ldoceEntry .PASTTENSE,\n.ldoceEntry .PLURALFORM,\n.ldoceEntry .POS,\n.ldoceEntry .PRESPART,\n.ldoceEntry .PRESPARTX,\n.ldoceEntry .PROPFORM,\n.ldoceEntry .PROPFORMPREP,\n.ldoceEntry .PTandPP,\n.ldoceEntry .PTandPPX,\n.ldoceEntry .REFHWD,\n.ldoceEntry .REFLEX,\n.ldoceEntry .RELATEDWD,\n.ldoceEntry .SIGNPOST,\n.ldoceEntry .SUPERL,\n.ldoceEntry .SYN,\n.ldoceEntry .T3PERSSING,\n.ldoceEntry .T3PERSSINGX,\n.ldoceEntry .UNCLASSIFIED,\n.ldoceEntry .warning,\n.ldoceEntry .sensenum,\n.ldoceEntry .synopp,\n.ldoceEntry .FREQ,\n.ldoceEntry .AC,\n.ldoceEntry .HEADING,\n.ldoceEntry .heading,\n.ldoceEntry .SECHEADING,\n.ldoceEntry .subheading,\n.ldoceEntry .COLLOC,\n.ldoceEntry .EXP,\n.ldoceEntry .EXPR,\n.ldoceEntry .keycollo,\n.ldoceEntry .THESPROPFORM,\n.ldoceEntry .DEFBOLD,\n.bussdictEntry .GRAM,\n.bussdictEntry .POS,\n.bussdictEntry .ABBR,\n.bussdictEntry .AMEQUIV,\n.bussdictEntry .BREQUIV,\n.bussdictEntry .COLLO,\n.bussdictEntry .COMP,\n.bussdictEntry .COLLOINEXA,\n.bussdictEntry .DERIV,\n.bussdictEntry .FREQ,\n.bussdictEntry .LEVEL,\n.bussdictEntry .FULLFORM,\n.bussdictEntry .HINTBOLD,\n.bussdictEntry .HINTTITLE,\n.bussdictEntry .HOMNUM,\n.bussdictEntry .HYPHENATION,\n.bussdictEntry .LEXUNIT,\n.bussdictEntry .LEXVAR,\n.bussdictEntry .OPP,\n.bussdictEntry .ORTHVAR,\n.bussdictEntry .PASTPART,\n.bussdictEntry .PASTTENSE,\n.bussdictEntry .PHRVBHWD,\n.bussdictEntry .PLURALFORM,\n.bussdictEntry .PRESPART,\n.bussdictEntry .PRESPARTX,\n.bussdictEntry .PROPFORM,\n.bussdictEntry .PROPFORMPREP,\n.bussdictEntry .PTandPP,\n.bussdictEntry .PTandPPX,\n.bussdictEntry .REFLEX,\n.bussdictEntry .RELATEDWD,\n.bussdictEntry .SIGNPOST,\n.bussdictEntry .SUPERL,\n.bussdictEntry .SYN,\n.bussdictEntry .T3PERSSING,\n.bussdictEntry .T3PERSSINGX,\n.bussdictEntry .UNCLASSIFIED,\n.bussdictEntry .THESPROPFORM,\n.bussdictEntry .SECHEADING,\n.bussdictEntry .subheading,\n.bussdictEntry .HEADING,\n.bussdictEntry .heading,\n.bussdictEntry .warning,\n.bussdictEntry .sensenum,\n.bussdictEntry .synopp,\n.bussdictEntry .Gramref,\n.bussdictEntry .COLLOC,\n.bussdictEntry .EXP,\n.bussdictEntry .EXPR,\n.bussdictEntry .keycollo,\n.bussdictEntry .DEFBOLD,\n.bussdictEntry .boxheader,\n.bussdictEntry .SubEntry .HWD {\n    font-weight: bold;\n}\n\n.ldoceEntry .REFHOMNUM,\n.bussdictEntry .REFHOMNUM {\n    vertical-align: super;\n    font-size: 60%;\n}\n\n.bussdictEntry .HOMNUM,\n.longman_dict .etym .Head .HOMNUM {\n    vertical-align: super;\n    font-size: 12pt;\n}\n\n.ldoceEntry .AC,\n.ldoceEntry .synopp {\n    color: #fff;\n    border-color: #f1d600;\n    background-color: #f1d600;\n}\n\n.ldoceEntry .EXPL .cross,\n.ldoceEntry .GramBox .EXPL .dont_say,\n.ldoceEntry .BADEXA {\n    color: red;\n}\n\n.ldoceEntry .FREQ,\n.bussdictEntry .FREQ,\n.bussdictEntry .LEVEL {\n    color: red;\n    border-color: red;\n}\n\n.ldoceEntry .LEVEL {\n    color: red;\n    font-size: 120%;\n}\n\n.ldoceEntry .frequent .HOMNUM,\n.ldoceEntry .HOMNUM {\n    color: red;\n    vertical-align: super;\n    font-size: 12pt;\n}\n\n.ldoceEntry .frequent .HYPHENATION,\n.ldoceEntry .HYPHENATION,\n.ldoceEntry .PHRVBHWD {\n    color: red;\n    font-size: 160%;\n}\n\n.ldoceEntry .warning,\n.bussdictEntry .warning {\n    color: red;\n    font-style: normal;\n}\n\n.ldoceEntry .GramBox .CROSS .neutral {\n    color: red;\n    margin-right: 10px;\n}\n\n.Entry .topic,\n.topicCloud .topic_other {\n    color: red;\n    text-decoration: underline;\n}\n\n.ldoceEntry .GRAM,\n.bussdictEntry .GRAM {\n    color: green;\n    margin: 0 5px 10px 3px;\n}\n\n.ldoceEntry .POS,\n.bussdictEntry .POS {\n    color: green;\n    margin: 0 0 0 10px;\n}\n\n.longman_dict .pos {\n    color: green;\n    font-weight: normal;\n}\n\n.ldoceEntry .REGISTERLAB {\n    color: purple;\n    font-style: italic;\n}\n\n.ldoceEntry .SIGNPOST {\n    background-color: #f18500;\n    color: white;\n    font-size: 79%;\n    text-transform: uppercase;\n    border-radius: 5px;\n    padding: 0 3px;\n    letter-spacing: 1px;\n}\n\n.longman_dict .Crossrefto,\n.ldoceEntry .RELATEDWD {\n    color: blue;\n}\n\n.bussdictEntry .synopp {\n    font-style: normal;\n    color: darkblue;\n}\n\n.longman_dict .assettype,\n.ldoceEntry .Thesref,\n.ldoceEntry .GEO,\n.ldoceEntry .geo,\n.ldoceEntry .GLOSS,\n.ldoceEntry .LINKWORD,\n.ldoceEntry .keycollo,\n.bussdictEntry .LINKWORD,\n.bussdictEntry .COLLOC.key,\n.bussdictEntry .keycollo {\n    color: #364395;\n}\n\n.bussdictEntry .Box,\n.ldoceEntry .ColloBox,\n.ldoceEntry .ThesBox,\n.ldoceEntry .F2NBox,\n.ldoceEntry .GramBox {\n    border-radius: 5px;\n    border: 1px solid #364395;\n    padding: 10px;\n    margin: 8px 0;\n}\n\n.ldoceEntry .HEADING,\n.ldoceEntry .heading,\n.bussdictEntry .SubEntry .HWD {\n    color: #364395;\n    font-size: 120%;\n}\n\n.bussdictEntry .GEO,\n.bussdictEntry .geo,\n.bussdictEntry .REGISTERLAB {\n    color: #364395;\n    font-style: italic;\n}\n\n.bussdictEntry .GLOSS {\n    color: #364395;\n    font-weight: normal;\n    font-style: normal;\n}\n\n.bussdictEntry .PHRVBHWD,\n.bussdictEntry .HEADING,\n.bussdictEntry .heading,\n.bussdictEntry .Gramref {\n    color: #364395;\n    font-size: 120%;\n}\n\n.bussdictEntry .boxheader {\n    background-color: #364395;\n    color: #fff;\n}\n\n.longman_dict .dictionary_intro,\n.longman_dict .topic_intro {\n    background-color: #35a3ff;\n    color: #fff;\n    padding-left: 10px;\n    margin: 5px 0 10px -7px;\n}\n\n.longman_dict .assets_intro,\n.longman_dict .asset_intro {\n    border: 1px solid #f1d600;\n    background-color: #f1d600;\n    color: #fff;\n    font-weight: normal;\n    border-radius: 5px;\n    padding-left: 3px;\n    padding-right: 3px;\n}\n\n.longman_dict .verbTable table {\n    background-color: #fae660;\n    margin-bottom: 10px;\n    border-collapse: collapse;\n    width: 100%;\n}\n\n.ldoceEntry .SECHEADING,\n.ldoceEntry .subheading {\n    display: table;\n    border-radius: 5px;\n    border: solid #6f469d 2px;\n    padding-left: 4px;\n    padding-right: 15px;\n    margin: 10px 0;\n    color: white;\n    text-transform: uppercase;\n    background-color: #6f469d;\n}\n\n.ldoceEntry .amefile {\n    color: #4693db;\n    font-size: 130%;\n    padding-left: 5px;\n}\n\n.ldoceEntry .brefile {\n    color: #fa6360;\n    font-size: 130%;\n    padding-left: 5px;\n}\n\n.ldoceEntry .neutral,\n.verbTable .aux,\n.verbTable .verb_form,\n.ldoceEntry .sensenum {\n    color: black;\n}\n\n.ldoceEntry .sensenum {\n    font-style: normal;\n}\n\n.longman_dict .GramBox {\n    color: black;\n    background-color: #fff;\n}\n\n.bussdictEntry .Box .EXAMPLE {\n    color: black;\n    display: inline;\n    margin-left: 0;\n    font-style: italic;\n}\n\n.verbTable .lemma {\n    color: black;\n    font-size: 120%;\n    margin: 0 0 0 10px;\n}\n\n.verbTable .view_more,\n.verbTable .view_less {\n    color: black;\n    text-align: center;\n    padding: 20px 0 10px;\n}\n\n.verbTable .geo {\n    color: black;\n    font-style: italic;\n    font-weight: normal;\n}\n\n.longman_dict .exaGroup .title {\n    color: black;\n    font-size: 110%;\n    margin: 10px 0 0 5px;\n}\n\n.longman_dict .Entry .related_topics {\n    color: black;\n    padding: 0 4px 1px 0;\n    font-size: 16px;\n}\n\n.ldoceEntry .COLLEXA,\n.ldoceEntry .THESEXA {\n    color: gray;\n}\n\n.ldoceEntry .exafile {\n    color: gray;\n    font-style: normal;\n    font-size: 120%;\n    padding: 5px;\n}\n\n.ldoceEntry .EXAMPLE,\n.bussdictEntry .EXAMPLE,\n.longman_dict .exaGroup .exa {\n    color: gray;\n    margin-left: 10px;\n}\n\n.bussdictEntry .supp,\n.bussdictEntry .SIGNPOST {\n    background-color: gray;\n}\n\n.longman_dict .verbTable .col2 {\n    color: gray;\n    width: 150px\n}\n\n.longman_dict a {\n    border-bottom: thin dotted gray;\n}\n\n.ldoceEntry .RunOn {\n    margin-bottom: 10px;\n}\n\n.longman_dict .right_col .yellow_box {\n    margin-bottom: 10px;\n    display: inline-block;\n}\n\n.longman_dict .dictentry,\n.longman_dict .exaGroup .exaEntry,\n.longman_dict .exaGroup .exaGroup,\n.longman_dict .verbTable .entry {\n    margin-bottom: 20px;\n}\n\n.ldoceEntry .Sense,\n.bussdictEntry .Sense,\n.longman_dict .verbTable .entry {\n    margin-left: 20px;\n    margin-bottom: 15px;\n}\n\n.bussdictEntry .SubEntry.embedded {\n    margin-top: -5px;\n    margin-left: 20px;\n    margin-bottom: 0;\n}\n\n.bussdictEntry .SubEntry {\n    margin-top: 2px;\n    margin-bottom: 2px;\n}\n\n.longman_dict .yellow_box {\n    margin-top: 20px;\n    display: table;\n}\n\n.ldoceEntry .Entry,\n.bussdictEntry .Entry {\n    font-size: 12pt;\n    text-align: justify;\n    margin-top: 8px;\n}\n\n.ldoceEntry .Subsense,\n.bussdictEntry .ColloExa,\n.bussdictEntry .GramExa,\n.bussdictEntry .Subsense {\n    margin-left: 10px;\n}\n\n.ldoceEntry .PROPFORM,\n.ldoceEntry .PROPFORMPREP,\n.ldoceEntry .COLLO,\n.bussdictEntry .SubEntry {\n    margin-left: 20px;\n}\n\n.ldoceEntry .Crossrefto,\n.ldoceEntry .DERIV {\n    font-size: 120%;\n}\n\n.ldoceEntry .Entry {\n    font-size: 11pt;\n    text-align: justify;\n}\n\n.ldoceEntry .HINTITALIC,\n.ldoceEntry .STRONG,\n.ldoceEntry .GOODCOLLO,\n.bussdictEntry .COLLOINEXA,\n.bussdictEntry .HINTITALIC,\n.bussdictEntry .STRONG,\n.bussdictEntry .COLLEXA,\n.bussdictEntry .THESEXA,\n.bussdictEntry .GOODCOLLO,\n.bussdictEntry .SECHEADING,\n.bussdictEntry .subheading {\n    font-style: italic;\n}\n\n.ldoceEntry .italic,\n.ldoceEntry .infllab,\n.bussdictEntry .italic,\n.bussdictEntry .infllab {\n    font-style: italic;\n    font-weight: normal;\n}\n\n.bussdictEntry .SECHEADING,\n.bussdictEntry .subheading,\n.bussdictEntry .UNDERLINE {\n    text-decoration: underline;\n}\n\n.ldoceEntry .OBJECT {\n    font-weight: normal;\n}\n\n.ldoceEntry .synopp,\n.ldoceEntry .FREQ,\n.ldoceEntry .AC {\n    display: inline-block;\n    font-style: normal;\n    text-transform: uppercase;\n    border-radius: 5px;\n    border: solid 1px;\n    padding-left: 4px;\n    padding-right: 4px;\n}\n\n.longman_dict .GramBox .boxheader,\n.longman_dict .ThesBox .heading,\n.longman_dict .ColloBox .heading {\n    line-height: 2em;\n}\n\n.longman_dict .ColloBox .heading {\n    margin: 0 10px 0 0;\n}\n\n.ldoceEntry .Collocate,\n.ldoceEntry .Exponent {\n    margin: 15px 0 0 5px;\n}\n\n.ldoceEntry .BADCOLLO {\n    text-decoration: line-through;\n}\n\n.bussdictEntry .DERIV {\n    font-size: 120%;\n}\n\n.bussdictEntry .HYPHENATION {\n    font-size: 160%;\n}\n\n.bussdictEntry .OBJECT {\n    font-weight: normal;\n}\n\n.bussdictEntry .REFHWD {\n    text-transform: lowercase;\n    font-style: normal;\n    /*font-variant: small-caps;*/\n}\n\n.bussdictEntry .neutral {\n    font-style: normal;\n    font-weight: normal;\n    font-variant: normal;\n}\n\n.bussdictEntry .sensenum {\n    font-style: normal;\n    margin-right: 5px;\n    margin-left: 3px;\n}\n\n.bussdictEntry .gramref {\n    margin: 5px 0;\n}\n\n.bussdictEntry .BADCOLLO,\n.bussdictEntry .BADEXA {\n    text-decoration: line-through;\n}\n\n.verbTable td {\n    padding: 0 10px;\n}\n\n.verbTable .col1 {\n    color: #333333;\n    padding: 20px 10px 0;\n    font-size: 14px;\n    text-decoration: underline\n}\n\n.verbTable .view_more span,\n.verbTable .view_less span {\n    cursor: pointer;\n}\n\n.verbTable .header {\n    background-color: #333333;\n    color: #fff;\n    padding: 0 0 0 10px;\n    font-size: 120%;\n}\n\n.longman_dict .wordfams .crossRef,\n.longman_dict .wordfams .w {\n    margin: 0 6px;\n}\n\n.longman_dict .asset {\n    margin-top: 20px;\n}\n\n.longman_dict .Entry .topics_container {\n    display: table;\n}\n"
  },
  {
    "path": "src/css/main.css",
    "content": "* {\n    box-sizing: border-box;\n    outline-color: #448aff;\n}\n\nhtml, body, p, h1, h2, h3, h4, h5, h6, ul, li {\n    margin: 0;\n    padding: 0;\n}\n\nul, li {\n    list-style: none;\n}\n\nbody {\n    font-family: Roboto, 'Segoe UI', Arial, 'Microsoft Yahei', sans-serif;\n    /*font-family: Helvetica Neue, PingFang SC, Microsoft YaHei, sans-serif;*/\n    background: #f5f8fa;\n    color: #333;\n    line-height: 1.8;\n}\n\n::-webkit-scrollbar {\n    width: 5px;\n    height: 5px;\n}\n\n::-webkit-scrollbar-track {\n    /*background-color: #E2E4E6;*/\n}\n\n::-webkit-scrollbar-thumb {\n    background-color: #bdc3c7;\n    border-radius: 5px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n    background-color: #9da5ac;\n}\n\n::-webkit-scrollbar-thumb:active {\n    background-color: #2c3e50;\n}\n\na {\n    text-decoration: none;\n    color: #576b95;\n}\n\na:hover {\n    color: #1a73e8;\n}\n\n.fx:after {\n    display: block;\n    content: '';\n    clear: both;\n}\n\n.dmx_hide {\n    display: none !important;\n}\n\n.dmx_show {\n    display: block !important;\n}\n\n.mt_1 {\n    margin-top: 10px;\n}\n\n.mt_2 {\n    margin-top: 20px;\n}\n\n.ml_1 {\n    margin-left: 10px;\n}\n\n.ml_2 {\n    margin-left: 20px;\n}\n\n.main {\n    margin: 0 auto;\n    padding: 10px;\n    min-width: 300px;\n    font-size: 15px;\n}\n\nlabel {\n    position: relative;\n    display: inline-block;\n    margin-right: 10px;\n    line-height: 1.6;\n    /*user-select: none;*/\n}\n\nlabel u {\n    text-decoration: none;\n    margin-left: 20px;\n    color: #a2a2a2;\n    font-size: 14px;\n}\n\n.label_block label {\n    display: block;\n    margin: 0;\n}\n\ninput[type=radio], input[type=checkbox] {\n    width: 18px;\n    height: 18px;\n    margin: 0 5px 0 5px;\n    position: relative;\n    bottom: -4px;\n}\n\n/* Firefox */\n@-moz-document url-prefix() {\n    input[type=radio], input[type=checkbox] {\n        bottom: auto;\n    }\n}\n\nselect {\n    min-width: 100px;\n    max-width: 160px;\n    height: 30px;\n    padding: 3px;\n    font-size: 15px;\n    border: 1px solid #cfd0d2;\n    border-radius: 4px;\n}\n\n.dmx_button {\n    display: inline-block;\n    background: #4395ff;\n    border: 1px solid #3589ff;\n    border-radius: 4px;\n    color: #fff;\n    line-height: 1;\n    padding: 8px 12px;\n    font-size: 14px;\n    white-space: nowrap;\n    text-align: center;\n    outline: none;\n    box-sizing: border-box;\n    user-select: none;\n    cursor: pointer;\n    transition: .1s;\n}\n\n.dmx_button:hover {\n    box-shadow: 2px 2px 5px 0 #a6aab0;\n}\n\n.dmx_button:active {\n    opacity: .7;\n}\n\n.dmx_button.medium {\n    font-size: 105%;\n    padding: 10px 13px;\n}\n\n.dmx_button.big {\n    font-size: 125%;\n    padding: 10px 16px;\n}\n\n.dmx_button .dmx-icon {\n    margin-right: 5px;\n}\n\n.dmx_button_default {\n    background: #fff;\n    border: 1px solid #dcdfe6;\n    color: #606266;\n}\n\n.dmx_button_success {\n    background-color: #67c23a;\n    border-color: #67c23a;\n    color: #fff;\n}\n\n.dmx_button_warning {\n    background-color: #e6a23c;\n    border-color: #e6a23c;\n    color: #fff;\n}\n\n.dmx_button_danger {\n    background-color: #ef473a;\n    border-color: #dd1419;\n    color: #fff;\n}\n\n.dmx_button:disabled, .dmx_button:disabled:hover {\n    cursor: not-allowed;\n    opacity: .3;\n    box-shadow: none;\n}\n\n.divider {\n    position: relative;\n    text-align: center;\n    margin: 10px 0;\n}\n\n.divider:after {\n    position: absolute;\n    top: 50%;\n    display: block;\n    content: '';\n    width: 100%;\n    border-bottom: 1px solid #b9b9b9;\n}\n\n.divider b {\n    z-index: 1;\n    display: inline-block;\n    padding: 0 10px;\n    position: relative;\n    background: #F1F4F6;\n    font-size: 120%;\n    font-weight: 400;\n}\n\n.item_input, .item_textarea {\n    background: #fff;\n    border-radius: 4px;\n    border: 1px solid #dcdfe6;\n    box-sizing: border-box;\n    color: #606266;\n    display: inline-block;\n    height: 40px;\n    line-height: 40px;\n    outline: none;\n    font-size: 16px;\n    padding: 0 15px;\n    transition: border-color .2s cubic-bezier(.645, .045, .355, 1);\n    width: 100%;\n}\n\n.item_input:hover, .item_textarea:hover {\n    border-color: #c0c4cc;\n}\n\n.item_input:focus, .item_textarea:focus {\n    border-color: #409eff;\n}\n\n.item_textarea {\n    padding: 5px 15px;\n    height: auto;\n    min-height: 42px;\n    line-height: 26px;\n    resize: vertical;\n}\n\n.dmx_form_item {\n    display: flex;\n    flex-wrap: nowrap;\n    align-items: center;\n    justify-content: flex-start;\n    margin-top: 10px;\n}\n\n.dmx_form_item .item_label {\n    padding: 0 10px 0 0;\n    color: #606266;\n    font-size: 16px;\n    box-sizing: border-box;\n    white-space: nowrap;\n}\n\n.dmx_form_item .item_content {\n    width: 100%;\n}\n\n.dmx_form_item .item_content.number {\n    width: 100px;\n}\n\n.dmx_form_item .item_content.number input[type=\"number\"] {\n    padding-right: 2px;\n}\n\n.dmx_form_item.small .item_label {\n    color: #333;\n    font-size: 14px;\n}\n\n.dmx_form_item.small .item_input {\n    height: 32px;\n    line-height: 32px;\n    font-size: 14px;\n    padding: 0 8px;\n}\n\n/* ==================== player ==================== */\n.dmx_player {\n    margin-top: 10px;\n    position: relative;\n    width: 100%;\n    height: 120px;\n    max-width: 920px;\n    background: #d7dde8;\n    border-radius: 5px;\n}\n\n.dmx_player .dmx_surfer {\n    width: 100%;\n    height: calc(100% - 26px);\n    overflow: hidden;\n    border-radius: 0 0 5px 5px;\n}\n\n.dmx_player .dmx_controls {\n    display: flex;\n    position: absolute;\n    top: 26px;\n    left: 0;\n    width: 100%;\n    height: calc(100% - 26px);\n    z-index: 999;\n}\n\n.dmx_player .dmx_p_top {\n    background: #757F9A;\n    color: #FFFFFF;\n    height: 26px;\n    line-height: 26px;\n    padding: 0 10px;\n    border-radius: 5px 5px 0 0;\n}\n\n.dmx_player .dmx_p_top .dmx_p_title {\n    float: left;\n    font-size: 16px;\n}\n\n.dmx_player .dmx_p_top .dmx_p_time {\n    float: right;\n    font-size: 14px;\n    font-family: Consolas, serif;\n}\n\n.dmx_player .dmx_controls button {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    position: relative;\n    top: 50%;\n    left: 50%;\n    min-width: 100px;\n    height: 60px;\n    white-space: nowrap;\n    cursor: pointer;\n    border-radius: 10px;\n    outline: 0;\n    padding: 0 10px;\n    margin-top: -30px;\n    margin-left: -50px;\n    font-size: 32px;\n    background-color: #FF4B2B;\n    border-color: #FF4B2B;\n    color: #FFF;\n}\n\n.dmx_player .dmx_controls button:hover {\n    background: #f78989;\n    border-color: #f78989;\n    color: #FFF;\n}\n\n.dmx_player .dmx_controls button.disabled {\n    cursor: not-allowed;\n    background: #fab6b6;\n    border-color: #fab6b6;\n    color: #FFF;\n}\n\n.dmx_circle {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    height: 64px;\n    width: 64px;\n    position: relative;\n    top: 50%;\n    left: 50%;\n    border-radius: 50%;\n    background: linear-gradient(#e9eaeb, #D3CCE3);\n    border: 1px solid #bdc3c7;\n    box-shadow: 0 1px 3px rgba(0, 0, 0, .19), inset 0 1px 0 rgba(255, 255, 255, .4);\n    transition: height 0.25s ease, width 0.25s ease;\n    transform: translate(-50%, -50%);\n}\n\n.dmx_circle:hover {\n    outline: none;\n}\n\n.dmx_circle:before, .dmx_circle:after {\n    display: block;\n    position: absolute;\n    content: \"\";\n    top: 0;\n    right: 0;\n    bottom: 0;\n    left: 0;\n    border-radius: 50%;\n}\n\n.dmx_circle .dmx-icon {\n    font-size: 46px;\n    color: #333;\n}\n\n.dmx_circle.dmx_on {\n    background: linear-gradient(#FF416C, #FF4B2B);\n    border: 1px solid #FF4B2B;\n}\n\n.dmx_circle.dmx_on .dmx-icon {\n    color: #fff;\n}\n\n.dmx_circle.dmx_on:before, .dmx_circle.dmx_on:after {\n    border: 0.015625rem solid #FF4B2B;\n}\n\n.dmx_circle.dmx_on:before {\n    animation: dmx_ripple 2s linear infinite;\n}\n\n.dmx_circle.dmx_on:after {\n    animation: dmx_ripple 2s linear 1s infinite;\n}\n\n.dmx_reverse.dmx_on:before, .dmx_reverse.dmx_on:after {\n    animation-direction: reverse;\n}\n\n.dmx_reverse.dmx_on {\n    cursor: pointer;\n}\n\n@keyframes dmx_ripple {\n    0% {\n        transform: scale(1);\n    }\n    50% {\n        transform: scale(1.5);\n        opacity: 0.5;\n    }\n    75% {\n        transform: scale(1.75);\n        opacity: 0.2;\n    }\n    100% {\n        transform: scale(1.95);\n        opacity: 0;\n    }\n}\n\n/* ==================== setting ==================== */\n.tab u {\n    display: block;\n    float: left;\n    text-decoration: none;\n    background: #fff;\n    border: 1px solid #D5D7DF;\n    border-left: 0;\n    padding: 6px 10px;\n    line-height: 1;\n    cursor: pointer;\n}\n\n.tab u:first-child {\n    border-radius: 5px 0 0 5px;\n    border-left: 1px solid #D5D7DF;\n}\n\n.tab u:last-child {\n    border-radius: 0 5px 5px 0;\n}\n\n.tab u:hover {\n    color: #1a73e8;\n}\n\n.tab u.active {\n    color: #fff;\n    background-color: #409eff;\n    border-color: #409eff;\n}\n\n.setting_box {\n    padding: 0 3px;\n    display: none;\n}\n\n.setting_option {\n    margin-top: 15px;\n}\n\n.setting_option .option_title {\n    position: relative;\n}\n\n.setting_option .option_title:after {\n    position: absolute;\n    top: 50%;\n    display: block;\n    content: '';\n    width: 100%;\n    border-bottom: 1px solid #b9b9b9;\n}\n\n.setting_option b {\n    z-index: 1;\n    position: relative;\n    background: #F1F4F6;\n    padding-right: 5px;\n}\n\n.setting_option .option_box {\n    padding-top: 10px;\n}\n\n.setting_option .second_box {\n    margin: 10px 0 0 5px;\n}\n\n.setting_option .dmx-icon-setting {\n    margin-left: 5px;\n    color: #7b848c;\n    cursor: pointer;\n}\n\n.setting_dialog {\n    display: none;\n    z-index: 99;\n    position: fixed;\n    left: 0;\n    top: 0;\n    width: 100%;\n    height: 100%;\n    background: #f5f8fa;\n    overflow: auto;\n    padding: 10px;\n    box-sizing: border-box;\n}\n\n#local_tts_dialog select {\n    float: left;\n    max-width: 180px;\n}\n\n#local_tts_reset_setting {\n    margin-left: 220px;\n}\n\n.local_list_name {\n    display: inline-block;\n    float: left;\n    min-width: 220px;\n    text-align: right;\n    padding-right: 10px;\n}\n\n.local_list_box {\n    display: inline-block;\n}\n\n.lang_list {\n    color: #9da5ac;\n}\n\n.lang_list_err {\n    color: #FF416C;\n}\n\n.dialog_back {\n    position: fixed;\n    left: 10px;\n    top: 2px;\n    cursor: pointer;\n}\n\n.dialog_back .dmx-icon {\n    font-size: 20px;\n}\n\n/* ==================== speak ==================== */\n.speak_body {\n    margin: 62px 10px 10px;\n}\n\n#speak_input, textarea {\n    display: block;\n    border-radius: 4px;\n    border: 1px solid #cfd0d2;\n    background: #fff;\n    color: #4e5053;\n    min-height: 88px;\n    line-height: 22px;\n    padding: 5px 10px;\n    font-size: 16px;\n    outline: none;\n    overflow: auto;\n    resize: vertical;\n}\n\n#search_list_dialog textarea {\n    width: 100%;\n    min-height: 280px;\n}\n\n#speak_input:focus {\n    border-color: #409eff;\n    box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);\n}\n\n.speak_box select {\n    margin: 10px 10px 0 0;\n}\n\n#speak_button {\n    float: right;\n}\n\n/* ==================== history ==================== */\n.history_box {\n    padding: 60px 10px 10px;\n}\n\n.history_box > .card {\n    margin: 0 auto;\n    min-width: 860px;\n    max-width: 1200px;\n}\n\n/* ==================== more ==================== */\n.more_list .dmx_button {\n    float: left;\n    margin: 0 10px 10px 0;\n}\n\n/* ==================== record ==================== */\n.record_main {\n    margin: 0 auto;\n    padding: 0 10px 10px;\n    min-width: 500px;\n    max-width: 920px;\n}\n\n.dmx_center {\n    margin: 20px 0;\n    text-align: center;\n}\n\n.dmx_left {\n    margin: 20px 0;\n    text-align: left;\n}\n\n.dmx_right {\n    margin: 20px 0;\n    text-align: right;\n}\n\n.learn_points {\n    margin-top: 10px;\n    font-size: 16px;\n    line-height: 1.8;\n    color: #b3b3b3;\n}\n\n.learn_points .title {\n    position: relative;\n}\n\n.learn_points .title:after {\n    position: absolute;\n    top: 50%;\n    display: block;\n    content: '';\n    width: 100%;\n    border-bottom: 1px solid #b9b9b9;\n}\n\n.learn_points b {\n    z-index: 1;\n    position: relative;\n    background: #F1F4F6;\n    padding-right: 5px;\n}\n\n.learn_points .case {\n    padding: 5px;\n}\n\n/* ==================== favorite ==================== */\n.head {\n    z-index: 2;\n    position: fixed;\n    top: 0;\n    background: #F6F7F9;\n    box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 2px 6px 2px rgba(60, 64, 67, .15);\n    width: 100%;\n    height: 50px;\n    overflow: hidden;\n}\n\n.head .logo {\n    float: left;\n    color: #1a73e8;\n    text-shadow: -1px -1px white, 1px 1px #175fbf;\n    letter-spacing: 2px;\n    font-size: x-large;\n    font-weight: bold;\n    height: 50px;\n    line-height: 50px;\n    margin: 0 15px;\n}\n\n.head .logo .dmx-icon {\n    font-size: x-large;\n    text-shadow: none;\n    margin-right: 5px;\n}\n\n.head .nav {\n    float: left;\n    display: flex;\n    align-items: center;\n    margin-left: 25px;\n}\n\n.head .nav li {\n    padding: 0 15px;\n}\n\n.head .nav li a {\n    position: relative;\n    display: inline-block;\n    font-size: 16px;\n    height: 50px;\n    line-height: 50px;\n    color: #8590a6;\n}\n\n.head .nav li a:hover, .head .nav li a.active {\n    color: #121212;\n    font-weight: 600;\n}\n\n.head .nav li a.active:after {\n    position: absolute;\n    right: 0;\n    bottom: -1px;\n    left: 0;\n    height: 4px;\n    background: #06f;\n    content: '';\n}\n\n.head .tool {\n    float: right;\n    margin-right: 10px;\n    height: 50px;\n    display: flex;\n    align-items: center;\n}\n\n.head .tool li {\n    padding: 0 10px;\n    font-size: 15px;\n    cursor: pointer;\n}\n\n.head .tool li .dmx-icon {\n    margin-right: 5px;\n}\n\n.cate {\n    z-index: 1;\n    position: fixed;\n    top: 50px;\n    bottom: 0;\n    width: 250px;\n    background: #F3F6F9;\n    border-right: 1px solid #AFB3B5;\n    overflow: auto;\n}\n\n.cate .dmx-icon:hover, .dmx_hover .dmx-icon:hover {\n    color: #06f;\n    cursor: pointer;\n}\n\n#create_cate_but {\n    position: fixed;\n    top: 52px;\n    left: 217px;\n}\n\n#create_cate_but .dmx-icon {\n    font-size: 20px;\n}\n\n.cate ul {\n    padding: 5px 0;\n}\n\n.cate ul li {\n    line-height: 32px;\n    font-size: 15px;\n    padding: 0 10px 0 15px;\n    cursor: pointer;\n}\n\n.cate ul li .dmx-icon {\n    color: #9EA9B7;\n    margin-right: 5px;\n}\n\n.cate ul li:hover {\n    background: #EBECEE;\n}\n\n.cate ul li.active {\n    background: #EBECEE;\n}\n\n.cate ul li.active a, .cate ul li.active .dmx-icon {\n    color: #1a73e8;\n}\n\n.cate ul li.active .dmx-icon:before {\n    content: \"\\e6c5\";\n}\n\n.favorite_box {\n    min-width: 1000px;\n    margin: 60px 10px 10px 260px;\n}\n\n.card {\n    background: #FFFFFF;\n    border: 1px solid #CCCFD0;\n    border-radius: 10px;\n    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);\n}\n\n.card_head {\n    display: flex;\n    justify-content: left;\n    align-items: center;\n    height: 56px;\n    padding: 10px 15px;\n    font-size: 18px;\n    border-bottom: 1px solid #CCCFD0;\n}\n\n.card_head > div {\n    margin-right: 15px;\n}\n\n.card_head .dmx_vis {\n    visibility: hidden;\n}\n\n.card_head:hover .dmx_vis {\n    visibility: visible;\n}\n\n.card_head .item_num {\n    font-size: 14px;\n    color: #b1b1b1;\n}\n\n.card_head .dmx_button {\n    margin-left: 10px;\n}\n\n.card_head .extra {\n    display: none;\n}\n\n.card_body {\n    padding-bottom: 5px;\n}\n\ntable.dmx_table {\n    width: 100%;\n    min-width: 800px;\n    border-collapse: collapse;\n}\n\ntable.dmx_table tr:hover td {\n    background: #F2F5F9;\n}\n\ntable.dmx_table th, table.dmx_table td {\n    text-align: left;\n    background: #fff;\n    border-top: 1px solid #ddd;\n    padding: 8px;\n    word-break: break-all;\n    font-size: 15px;\n}\n\ntable.dmx_table th {\n    background: #F5F6F9;\n}\n\ntable.dmx_table thead th {\n    border-top: none;\n}\n\ntable.dmx_table .tb_checkbox {\n    width: 44px;\n}\n\ntable.dmx_table .tb_index {\n    width: 80px;\n}\n\ntable.dmx_table .tb_words {\n    width: 240px;\n}\n\ntable.dmx_table .tb_records, table.dmx_table .tb_days {\n    width: 90px;\n}\n\ntable.dmx_table .tb_date {\n    width: 110px;\n}\n\ntable.dmx_table .tb_operate {\n    width: 200px;\n}\n\ntable.dmx_table .tb_operate2 {\n    width: 140px;\n}\n\ntable.dmx_table .table_empty {\n    padding: 15px 10px;\n    text-align: center;\n    /*font-weight: bold;*/\n}\n\n.player_box {\n    min-width: 500px;\n    max-width: 920px;\n    margin: 0 auto;\n}\n\n#player_sentence {\n    font-size: 20px;\n    margin-top: 10px;\n    padding: 5px;\n}\n\n#player_sentence.hide {\n    color: #F1F4F6;\n}\n\n#player_sentence.hide:hover {\n    color: #333;\n}\n\n#player_sentence u, .tb_sentence u {\n    text-decoration: none;\n    color: red;\n}\n\n#player_sentence.hide u {\n    color: inherit;\n}\n\n#player_sentence.hide:hover u {\n    color: red;\n}\n\n/* ==================== dal ==================== */\n.dal, .dal_bg, .ddi, .ddi_bg {\n    position: fixed;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    right: 0;\n}\n\n.dal_bg, .ddi_bg {\n    z-index: 999999999998;\n    background: rgba(0, 0, 0, .6);\n}\n\n.dal, .ddi {\n    z-index: 999999999999;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n.dal_modal, .ddi_modal {\n    position: relative;\n    width: 500px;\n    background: #fff;\n    border-radius: 5px;\n    padding: 20px;\n    font-size: 16px;\n    max-height: 95%;\n    overflow: auto;\n}\n\n.dal_text {\n    margin: 15px 0;\n}\n\n.dal_text .dmx-icon {\n    margin-right: 8px;\n    font-size: 130%;\n}\n\n.dal_text .dmx-icon-info {\n    color: #e6a23c;\n}\n\n.dal_text .dmx-icon-close {\n    color: #D50007;\n}\n\n.dal_text .dmx-icon-success {\n    color: #67c23a;\n}\n\n.dal_foot {\n    width: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: flex-end;\n}\n\n.dal_foot .dmx_button {\n    margin-left: 10px;\n}\n\n.ddi_dialog {\n    width: 600px;\n    padding: 0;\n}\n\n.ddi_dialog.fullscreen {\n    width: 100%;\n    height: 100%;\n    max-height: 100%;\n    border-radius: 0;\n    background: #F1F4F6;\n}\n\n.ddi_head {\n    width: 100%;\n    height: 38px;\n    line-height: 38px;\n    padding-left: 10px;\n    font-weight: bold;\n}\n\n.ddi_head .dmx-icon-close {\n    display: block;\n    width: 20px;\n    height: 20px;\n    line-height: 20px;\n    position: absolute;\n    top: 10px;\n    right: 10px;\n    color: #FC4A4A;\n    font-size: 18px;\n}\n\n.ddi_head .dmx-icon:hover {\n    opacity: .8;\n    cursor: pointer;\n}\n\n.ddi_body {\n    margin: 0 15px 10px;\n}\n\n.ddi_loading {\n    color: #fff;\n    font-size: 18px;\n    text-align: center;\n}\n\n.ddi_loading_inner {\n    border: 5px solid #bdc3c7;\n    border-top: 5px solid #2c3e50;\n    border-bottom: 3px solid #2c3e50;\n    border-radius: 50%;\n    display: block;\n    width: 64px;\n    height: 64px;\n    margin: 0 auto;\n    animation: spin 1.2s linear infinite;\n}\n\n.dmx-icon-loading {\n    animation: spin 1.2s linear infinite;\n}\n\n@keyframes spin {\n    0% {\n        transform: rotate(0deg);\n    }\n    100% {\n        transform: rotate(360deg);\n    }\n}\n\n@media only screen and (max-width: 789px) {\n    .dal_modal {\n        width: 430px;\n    }\n}\n"
  },
  {
    "path": "src/css/popup.css",
    "content": "html, body {\n    margin: 0;\n    padding: 0;\n    overflow: hidden;\n    background: #f5f8fa;\n}\n\n.dmx_popup {\n    width: 520px;\n    height: 500px;\n}\n\n.dmx_popup body {\n    width: 520px;\n}\n"
  },
  {
    "path": "src/html/favorite.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"referrer\" content=\"no-referrer\">\n    <link rel=\"stylesheet\" href=\"../css/content.css\">\n    <link rel=\"stylesheet\" href=\"../css/main.css\">\n    <title>我的收藏</title>\n</head>\n<body>\n<div class=\"head fx\">\n    <div class=\"logo\"><i class=\"dmx-icon dmx-icon-logo\"></i>梦想划词翻译</div>\n    <ul class=\"nav\">\n        <li><a href=\"favorite.html\" class=\"active\">我的收藏</a></li>\n        <li><a href=\"history.html\">历史记录</a></li>\n        <li><a href=\"speak.html\">朗读助手</a></li>\n    </ul>\n    <ul class=\"tool\">\n        <li><a id=\"export\"><i class=\"dmx-icon dmx-icon-export\"></i>导出</a></li>\n        <li><a id=\"import\"><i class=\"dmx-icon dmx-icon-import\"></i>导入</a></li>\n        <li><a id=\"setting\"><i class=\"dmx-icon dmx-icon-setting\"></i>设置</a></li>\n    </ul>\n</div>\n\n<div class=\"cate\">\n    <div id=\"create_cate_but\"><i class=\"dmx-icon dmx-icon-plus\" title=\"新增分类\"></i></div>\n    <ul id=\"cate_box\"></ul>\n</div>\n\n<div class=\"card favorite_box\">\n    <div class=\"card_head\">\n        <div id=\"cate_name\"></div>\n        <div class=\"item_num\"><span id=\"sentences\">0</span> 条</div>\n        <div id=\"cate_edit\" class=\"dmx_vis dmx_hover\" title=\"编辑分类\"><i class=\"dmx-icon dmx-icon-edit\"></i></div>\n        <div id=\"cate_delete\" class=\"dmx_vis dmx_hover\" title=\"删除分类\"><i class=\"dmx-icon dmx-icon-delete\"></i></div>\n        <div class=\"extra\" id=\"extra_but\">\n            <div class=\"dmx_button dmx_button_warning\" id=\"sentence_move\"><i class=\"dmx-icon dmx-icon-move\"></i>移动</div>\n            <div class=\"dmx_button dmx_button_danger\" id=\"sentence_delete\"><i class=\"dmx-icon dmx-icon-delete\"></i>删除</div>\n        </div>\n    </div>\n    <div class=\"card_body\">\n        <table class=\"dmx_table\" id=\"sentence_box\">\n            <thead>\n            <tr>\n                <th class=\"tb_checkbox\"><input type=\"checkbox\" id=\"selectAll\"></th>\n                <th class=\"tb_sentence\">句子</th>\n                <th class=\"tb_records\">练习次数</th>\n                <th class=\"tb_days\">练习天数</th>\n                <th class=\"tb_date\">添加时间</th>\n                <th class=\"tb_operate\">操作</th>\n            </tr>\n            </thead>\n            <tbody></tbody>\n        </table>\n    </div>\n</div>\n</body>\n<script type=\"text/javascript\" src=\"../js/lib/wavesurfer.min.js\"></script>\n<script type=\"text/javascript\" src=\"../js/lib/wavesurfer.microphone.min.js\"></script>\n<script type=\"text/javascript\" src=\"../js/lib/RecordRTC.min.js\"></script>\n<script type=\"text/javascript\" src=\"../js/lib/jszip.min.js\"></script>\n<script type=\"text/javascript\" src=\"../js/common.js\"></script>\n<script type=\"text/javascript\" src=\"../js/player.js\"></script>\n<script type=\"text/javascript\" src=\"../js/content.js\"></script>\n<script type=\"text/javascript\" src=\"../js/db.js\"></script>\n<script type=\"text/javascript\" src=\"../js/favorite.js\"></script>\n</html>\n"
  },
  {
    "path": "src/html/history.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <link rel=\"stylesheet\" href=\"../css/content.css\">\n    <link rel=\"stylesheet\" href=\"../css/main.css\">\n    <title>历史记录</title>\n</head>\n<body>\n<div class=\"head fx\">\n    <div class=\"logo\"><i class=\"dmx-icon dmx-icon-logo\"></i>梦想划词翻译</div>\n    <ul class=\"nav\">\n        <li><a href=\"favorite.html\">我的收藏</a></li>\n        <li><a href=\"history.html\" class=\"active\">历史记录</a></li>\n        <li><a href=\"speak.html\">朗读助手</a></li>\n    </ul>\n    <ul class=\"tool\">\n        <li><a id=\"setting\"><i class=\"dmx-icon dmx-icon-setting\"></i>设置</a></li>\n    </ul>\n</div>\n\n<div class=\"history_box\">\n    <div class=\"card\">\n        <div class=\"card_head\">\n            <div>历史记录</div>\n            <div class=\"item_num\"><span id=\"historyNum\">0</span> 条</div>\n            <div class=\"extra\" id=\"extra_but\">\n                <div class=\"dmx_button dmx_button_danger\" id=\"delete_multiple\"><i class=\"dmx-icon dmx-icon-delete\"></i>删除</div>\n            </div>\n        </div>\n        <div class=\"card_body\">\n            <table class=\"dmx_table\" id=\"history_box\">\n                <thead>\n                <tr>\n                    <th class=\"tb_checkbox\"><input type=\"checkbox\" id=\"selectAll\"></th>\n                    <th class=\"tb_sentence\">内容</th>\n                    <th class=\"tb_date\">时间</th>\n                    <th class=\"tb_operate2\">操作</th>\n                </tr>\n                </thead>\n                <tbody id=\"history_body\"></tbody>\n            </table>\n        </div>\n    </div>\n</div>\n\n</body>\n<script type=\"text/javascript\" src=\"../js/common.js\"></script>\n<script type=\"text/javascript\" src=\"../js/content.js\"></script>\n<script type=\"text/javascript\" src=\"../js/db.js\"></script>\n<script type=\"text/javascript\" src=\"../js/history.js\"></script>\n</html>\n"
  },
  {
    "path": "src/html/more.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>More</title>\n    <link rel=\"stylesheet\" href=\"../css/content.css\">\n    <link rel=\"stylesheet\" href=\"../css/main.css\">\n</head>\n<body>\n<div class=\"main\">\n    <div class=\"more_list fx\">\n        <div class=\"dmx_button\" id=\"trans_window\"><i class=\"dmx-icon dmx-icon-plane-paper\"></i>翻译窗口</div>\n        <div class=\"dmx_button\" id=\"allow_select\" title=\"解除页面选中文字和右键限制\"><i class=\"dmx-icon dmx-icon-devil\"></i>解除限制</div>\n        <div class=\"dmx_button\" data-href=\"favorite.html\"><i class=\"dmx-icon dmx-icon-heart\"></i>我的收藏</div>\n        <div class=\"dmx_button\" data-href=\"history.html\"><i class=\"dmx-icon dmx-icon-history\"></i>历史记录</div>\n        <div class=\"dmx_button\" data-href=\"speak.html\"><i class=\"dmx-icon dmx-icon-headset-c\"></i>朗读助手</div>\n        <div class=\"dmx_button\" data-href=\"https://mengxiang.net/tool/phonetic/\"><i class=\"dmx-icon dmx-icon-sound\"></i>英语音标</div>\n        <div class=\"dmx_button\" data-href=\"https://mengxiang.net/tool/kana/\"><i class=\"dmx-icon dmx-icon-sound-square\"></i>日语假名</div>\n        <div class=\"dmx_button\" data-href=\"https://mengxiang.net/tool/pdfjs/\"><i class=\"dmx-icon dmx-icon-folder\"></i>PDF阅读器</div>\n    </div>\n</div>\n</body>\n<script type=\"text/javascript\" src=\"../js/common.js\"></script>\n<script type=\"text/javascript\" src=\"../js/more.js\"></script>\n</html>\n"
  },
  {
    "path": "src/html/popup.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>梦想翻译小助手</title>\n    <link rel=\"stylesheet\" href=\"../css/content.css\">\n    <link rel=\"stylesheet\" href=\"../css/popup.css\">\n</head>\n<body>\n</body>\n<script type=\"text/javascript\" src=\"../js/common.js\"></script>\n<script type=\"text/javascript\" src=\"../js/popup.js\"></script>\n<script type=\"text/javascript\" src=\"../js/content.js\"></script>\n</html>\n"
  },
  {
    "path": "src/html/record.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"referrer\" content=\"no-referrer\">\n    <title>练习发音</title>\n    <link rel=\"stylesheet\" href=\"../css/content.css\">\n    <link rel=\"stylesheet\" href=\"../css/main.css\">\n</head>\n<body>\n<div class=\"record_main\" id=\"record_box\">\n    <div id=\"player_listen\"></div>\n    <div id=\"player_record\"></div>\n    <div id=\"player_compare\"></div>\n    <div class=\"divider\"><b>练习 <span id=\"practice_num\">0</span> 次</b></div>\n    <div class=\"dmx_center\">\n        <div class=\"dmx_button dmx_button_danger big\" id=\"favorite_but\">添加收藏</div>\n        <a class=\"dmx_button dmx_button_default big ml_1\" href=\"favorite.html\" target=\"_blank\">我的收藏</a>\n    </div>\n</div>\n\n<div class=\"record_main dmx_hide\" id=\"favorite_form\">\n    <div id=\"player_listen2\"></div>\n    <form class=\"mt_2\" id=\"sentence_form\">\n        <div class=\"dmx_form_item\">\n            <div class=\"item_label\">句子</div>\n            <div class=\"item_content\"><input name=\"sentence\" type=\"text\" autocomplete=\"off\" required class=\"item_input\" placeholder=\"如：She has been extremely difficult every step of the way.\"></div>\n        </div>\n        <div class=\"dmx_form_item\">\n            <div class=\"item_label\">生词</div>\n            <div class=\"item_content\"><textarea name=\"words\" autocomplete=\"off\" class=\"item_textarea\" placeholder=\"一行一词。如：extremely\"></textarea></div>\n        </div>\n        <div class=\"dmx_form_item\">\n            <div class=\"item_label\">音频</div>\n            <div class=\"item_content\"><input name=\"url\" type=\"url\" autocomplete=\"off\" required class=\"item_input\"></div>\n        </div>\n        <div class=\"dmx_center\">\n            <button class=\"dmx_button big\" type=\"submit\"><i class=\"dmx-icon dmx-icon-plus\"></i>添加</button>\n            <div class=\"dmx_button dmx_button_default big ml_1\" id=\"back_but\"><i class=\"dmx-icon dmx-icon-reply\"></i>返回</div>\n        </div>\n    </form>\n</div>\n</body>\n<script type=\"text/javascript\" src=\"../js/lib/wavesurfer.min.js\"></script>\n<script type=\"text/javascript\" src=\"../js/lib/wavesurfer.microphone.min.js\"></script>\n<script type=\"text/javascript\" src=\"../js/lib/RecordRTC.min.js\"></script>\n<script type=\"text/javascript\" src=\"../js/common.js\"></script>\n<script type=\"text/javascript\" src=\"../js/player.js\"></script>\n<script type=\"text/javascript\" src=\"../js/db.js\"></script>\n<script type=\"text/javascript\" src=\"../js/record.js\"></script>\n</html>\n"
  },
  {
    "path": "src/html/setting.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Setting</title>\n    <link rel=\"stylesheet\" href=\"../css/content.css\">\n    <link rel=\"stylesheet\" href=\"../css/main.css\">\n</head>\n<body>\n<div class=\"main\">\n    <div id=\"navigate\" class=\"tab fx\">\n        <u target=\"base_box\" class=\"active\">基本设置</u>\n        <u target=\"translate_box\">翻译设置</u>\n        <u target=\"dictionary_box\">词典设置</u>\n        <u target=\"search_box\">搜索设置</u>\n        <u target=\"help_box\">联系作者</u>\n    </div>\n\n    <div id=\"base_box\" class=\"setting_box\">\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>划词功能</b></div>\n            <div class=\"option_box label_block\">\n                <label><input type=\"radio\" name=\"scribble\" value=\"off\">关闭划词</label>\n                <label><input type=\"radio\" name=\"scribble\" value=\"direct\">直接显示<u>选中文字后，即显示结果</u></label>\n                <label><input type=\"radio\" name=\"scribble\" value=\"clickIcon\">点击图标<u>选中文字后，先显示图标，点击图标再显示结果</u></label>\n            </div>\n        </div>\n\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>划词过滤</b></div>\n            <div class=\"option_box label_block\">\n                <label><input type=\"checkbox\" name=\"excludeChinese\" value=\"on\">排除中文<u>如果选中的内容中包含中文，不激活划词功能</u></label>\n                <label><input type=\"checkbox\" name=\"excludeSymbol\" value=\"on\">排除符号<u>如果选中的内容中只有符号，不激活划词功能</u></label>\n                <label><input type=\"checkbox\" name=\"excludeNumber\" value=\"on\">排除数字<u>如果选中的内容中只有数字，不激活划词功能</u></label>\n            </div>\n        </div>\n\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>对话框位置</b></div>\n            <div class=\"option_box\">\n                <label><input type=\"radio\" name=\"position\" value=\"fixed\">固定位置</label>\n                <label><input type=\"radio\" name=\"position\" value=\"follow\">跟随选区</label>\n                <label><input type=\"radio\" name=\"position\" value=\"right\">右侧靠边</label>\n            </div>\n        </div>\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>实用功能</b></div>\n            <div class=\"option_box label_block\">\n                <label><input type=\"checkbox\" name=\"autoWords\" value=\"on\">鼠标取词<u>鼠标停留在英文单词上时，自动查词</u></label>\n                <label><input type=\"checkbox\" name=\"allowSelect\" value=\"on\">自动解限<u>自动解除访问页面选中文字和右键限制</u></label>\n                <label><input type=\"checkbox\" name=\"autoCopy\" value=\"on\">自动复制<u>选中文字后，自动复制选中内容</u></label>\n                <label><input type=\"checkbox\" name=\"autoPaste\" value=\"on\">自动粘贴<u>打开翻译窗口时，自动粘贴内容到输入框</u></label>\n                <label><input type=\"checkbox\" name=\"autoChange\" value=\"on\">自动切换<u>根据空格数，自动切换词典或翻译查询</u></label>\n                <label><input type=\"checkbox\" name=\"cutHumpName\" value=\"on\">驼峰断词<u>自动切分驼峰命名词组，程序员的小助手</u></label>\n            </div>\n        </div>\n        <div id=\"clearSetting\" class=\"dmx_button dmx_button_default mt_2\">恢复默认设置</div>\n    </div>\n\n    <div id=\"translate_box\" class=\"setting_box\">\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>启用翻译</b></div>\n            <div id=\"setting_translate_list\" class=\"option_box\"></div>\n            <div id=\"setting_translate_sort\" class=\"second_box\"></div>\n        </div>\n\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>自动朗读</b></div>\n            <div id=\"setting_translate_tts_list\" class=\"option_box\"></div>\n            <div id=\"setting_translate_tts_sort\" class=\"second_box\"></div>\n        </div>\n\n        <div class=\"setting_option\" id=\"local_box\">\n            <div class=\"option_title\"><b>本机朗读</b></div>\n            <div class=\"option_box label_block\">\n                替换朗读\n                <select name=\"localSoundReplace\" class=\"ml_1\"></select>\n            </div>\n        </div>\n\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>截图识别</b></div>\n            <div class=\"option_box label_block\">\n                识别语言\n                <select name=\"translateOCR\" class=\"ml_1\">\n                    <option value=\"CHN_ENG\">中英文混合</option>\n                    <option value=\"ENG\">英文</option>\n                    <option value=\"JAP\">日语</option>\n                    <option value=\"KOR\">韩语</option>\n                    <option value=\"FRE\">法语</option>\n                    <option value=\"SPA\">西班牙语</option>\n                    <option value=\"POR\">葡萄牙语</option>\n                    <option value=\"GER\">德语</option>\n                    <option value=\"ITA\">意大利语</option>\n                    <option value=\"RUS\">俄语</option>\n                </select>\n            </div>\n        </div>\n\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>识别引擎</b></div>\n            <div class=\"option_box label_block\">\n                <label><input type=\"radio\" name=\"ocrType\" value=\"auto\">免费文字识别<u>可能存在不稳定或失效问题</u></label>\n                <label><input type=\"radio\" name=\"ocrType\" value=\"baidu\">百度文字识别<u>需要到百度云平台人工申请，<a href=\"https://mengxiang.net/post/baidu_ocr.html\" target=\"_blank\">申请教程</a></u></label>\n            </div>\n            <div class=\"dmx_hide\" id=\"baidu_ocr_box\">\n                <div class=\"dmx_form_item small\">\n                    <div class=\"item_label\">百度云应用AK</div>\n                    <div class=\"item_content\"><input name=\"baidu_orc_ak\" type=\"text\" autocomplete=\"off\" class=\"item_input\" placeholder=\"请填写 API Key\"></div>\n                </div>\n                <div class=\"dmx_form_item small\">\n                    <div class=\"item_label\">百度云应用SK</div>\n                    <div class=\"item_content\"><input name=\"baidu_orc_sk\" type=\"text\" autocomplete=\"off\" class=\"item_input\" placeholder=\"请填写 Secret Key\"></div>\n                </div>\n            </div>\n        </div>\n\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>更多功能</b></div>\n            <div class=\"option_box label_block\">\n                <label><input type=\"checkbox\" name=\"translateThin\" value=\"on\">精简显示<u>仅显示最基本的翻译内容</u></label>\n                <label><input type=\"checkbox\" name=\"hideOriginal\" value=\"on\">隐藏原文<u>隐藏原文框减小占用空间</u></label>\n                <label><input type=\"checkbox\" name=\"autoLanguage\" value=\"on\">检测中文<u>如果为中文自动中英切换</u></label>\n                <label><input type=\"checkbox\" name=\"autoConfirm\" value=\"on\">自动确认<u>键盘输入停止 2 秒后自动确认翻译</u></label>\n            </div>\n        </div>\n    </div>\n\n    <div id=\"dictionary_box\" class=\"setting_box\">\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>启用词典</b></div>\n            <div id=\"setting_dictionary_list\" class=\"option_box\"></div>\n            <div id=\"setting_dictionary_sort\" class=\"second_box\"></div>\n        </div>\n\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>自动朗读</b></div>\n            <div id=\"setting_dictionary_sound_list\" class=\"option_box\"></div>\n            <div id=\"setting_dictionary_sound_sort\" class=\"second_box\"></div>\n            <div id=\"setting_dictionary_reader\" class=\"second_box\">\n                朗读：\n                <label><input type=\"radio\" name=\"dictionaryReader\" value=\"uk\">英音</label>\n                <label><input type=\"radio\" name=\"dictionaryReader\" value=\"us\">美音</label>\n            </div>\n        </div>\n    </div>\n\n    <div id=\"search_box\" class=\"setting_box\">\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>启用搜索</b></div>\n            <div id=\"setting_search_list\" class=\"option_box\"></div>\n            <div id=\"setting_search_sort\" class=\"second_box\"></div>\n        </div>\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>右键菜单</b></div>\n            <div id=\"setting_search_menus\" class=\"option_box\"></div>\n            <div id=\"setting_search_menus_sort\" class=\"second_box\"></div>\n        </div>\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>左侧漂浮</b></div>\n            <div id=\"setting_search_side\" class=\"option_box\"></div>\n            <div id=\"setting_search_side_sort\" class=\"second_box\"></div>\n        </div>\n        <div id=\"search_setting_but\" class=\"dmx_button dmx_button_default mt_2\">搜索管理</div>\n    </div>\n\n    <div id=\"help_box\" class=\"setting_box\">\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>联系作者</b></div>\n            <div class=\"option_box\">\n                如果您有任何建议和问题反馈，请邮件发送到 Dream39999@gmail.com\n            </div>\n        </div>\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>开发原因</b></div>\n            <div class=\"option_box\">\n                每当看到一些容易误导人的背单词学习英语广告，文章，视频，软件，就实在忍不住想做点什么，于是就有了这款程序。\n            </div>\n        </div>\n        <div class=\"setting_option\">\n            <div class=\"option_title\"><b>学习方法</b></div>\n            <div class=\"option_box\">\n                如果你对「正确学习英语的方法」感兴趣，请阅读《<a href=\"https://mengxiang.net/post/english.html\" target=\"_blank\">英语学习秘籍</a>》。\n            </div>\n        </div>\n    </div>\n\n    <div id=\"local_tts_dialog\" class=\"setting_dialog\">\n        <div class=\"dialog_back\"><i class=\"dmx-icon dmx-icon-arrow-left\"></i></div>\n        <div class=\"fx\">\n            <div class=\"local_list_name\">朗读速度</div>\n            <select key=\"speak_rate\">\n                <option value=\"\">默认</option>\n                <option value=\"0.5\">0.5X</option>\n                <option value=\"0.75\">0.75X</option>\n                <option value=\"1\">1X</option>\n                <option value=\"1.25\">1.25X</option>\n                <option value=\"1.5\">1.5X</option>\n                <option value=\"1.75\">1.75X</option>\n                <option value=\"2\">2X</option>\n                <option value=\"2.5\">2.5X</option>\n                <option value=\"3\">3X</option>\n            </select>\n        </div>\n        <div class=\"fx mt_1\">\n            <div class=\"local_list_name\">朗读音调</div>\n            <select key=\"speak_pitch\">\n                <option value=\"\">默认</option>\n                <option value=\"0.5\">0.5</option>\n                <option value=\"0.75\">0.75</option>\n                <option value=\"1\">1</option>\n                <option value=\"1.25\">1.25</option>\n                <option value=\"1.5\">1.5</option>\n                <option value=\"1.75\">1.75</option>\n                <option value=\"2\">2</option>\n            </select>\n        </div>\n        <div id=\"local_tts_list\"></div>\n        <div id=\"local_tts_reset_setting\" class=\"dmx_button dmx_button_default mt_1\">恢复默认设置</div>\n    </div>\n\n    <div id=\"search_list_dialog\" class=\"setting_dialog\">\n        <div class=\"search_list_text\"><textarea name=\"search_text\"></textarea></div>\n        <div id=\"search_list_save\" class=\"dmx_button mt_1\">保存</div>\n        <div id=\"search_list_back\" class=\"dmx_button dmx_button_default mt_1\">返回</div>\n\n        <div class=\"learn_points\">\n            <div class=\"title\"><b>设置说明</b></div>\n            <div class=\"case\">每行一条，“|”分隔，前面为名称，后面为搜索链接，{0} 代表搜索关键字。</div>\n        </div>\n    </div>\n</div>\n</body>\n<script type=\"text/javascript\" src=\"../js/common.js\"></script>\n<script type=\"text/javascript\" src=\"../js/setting.js\"></script>\n</html>\n"
  },
  {
    "path": "src/html/speak.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>朗读助手</title>\n    <link rel=\"stylesheet\" href=\"../css/content.css\">\n    <link rel=\"stylesheet\" href=\"../css/main.css\">\n</head>\n<body>\n<div class=\"head fx\">\n    <div class=\"logo\"><i class=\"dmx-icon dmx-icon-logo\"></i>梦想划词翻译</div>\n    <ul class=\"nav\">\n        <li><a href=\"favorite.html\">我的收藏</a></li>\n        <li><a href=\"history.html\">历史记录</a></li>\n        <li><a href=\"speak.html\" class=\"active\">朗读助手</a></li>\n    </ul>\n</div>\n\n<div class=\"speak_body\">\n    <div id=\"speak_input\" contenteditable=\"true\"></div>\n    <div class=\"speak_box fx\">\n        <select id=\"speak_voice\">\n            <option value=\"\">朗读名称</option>\n        </select>\n        <select id=\"speak_rate\">\n            <option value=\"\">朗读速度</option>\n            <option value=\"0.5\">0.5X</option>\n            <option value=\"0.75\">0.75X</option>\n            <option value=\"1\">1X</option>\n            <option value=\"1.25\">1.25X</option>\n            <option value=\"1.5\">1.5X</option>\n            <option value=\"1.75\">1.75X</option>\n            <option value=\"2\">2X</option>\n            <option value=\"2.5\">2.5X</option>\n            <option value=\"3\">3X</option>\n        </select>\n        <select id=\"speak_pitch\">\n            <option value=\"\">朗读音调</option>\n            <option value=\"0.5\">0.5</option>\n            <option value=\"0.75\">0.75</option>\n            <option value=\"1\">1</option>\n            <option value=\"1.25\">1.25</option>\n            <option value=\"1.5\">1.5</option>\n            <option value=\"1.75\">1.75</option>\n            <option value=\"2\">2</option>\n        </select>\n        <div id=\"speak_button\" class=\"dmx_button mt_1\">朗读</div>\n    </div>\n</div>\n</body>\n<script type=\"text/javascript\" src=\"../js/common.js\"></script>\n<script type=\"text/javascript\" src=\"../js/speak.js\"></script>\n</html>\n"
  },
  {
    "path": "src/html/video.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"referrer\" content=\"no-referrer\">\n    <title>Video</title>\n</head>\n<body style=\"margin:0;padding:0;overflow:hidden;background:#000\"></body>\n<script type=\"text/javascript\" src=\"../js/video.js\"></script>\n</html>\n"
  },
  {
    "path": "src/js/background.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nlet conf, setting, sdk = {}\nlet searchText, searchList\nlet ocrToken = '', ocrExpires = 0\nvar textTmp = ''\nvar historyMax = 3000\ndocument.addEventListener('DOMContentLoaded', async function () {\n    let languageList = '', dialogCSS = '', dictionaryCSS = {}\n    await fetch('../conf/conf.json').then(r => r.json()).then(r => {\n        conf = r\n    })\n    await fetch('../conf/searchText.txt').then(r => r.text()).then(str => {\n        searchText = str\n    })\n    await fetch('../conf/language.json').then(r => r.text()).then(s => {\n        languageList += s\n    })\n    await fetch('../css/dmx_dialog.css').then(r => r.text()).then(s => {\n        dialogCSS += minCss(s)\n    })\n    for (let name of conf.dictionaryCSS) {\n        await fetch(`../css/${name}.css`).then(r => r.text()).then(s => {\n            dictionaryCSS[name] = minCss(s)\n        })\n    }\n    storageLocalSet({conf, languageList, dialogCSS, dictionaryCSS}).catch(err => debug(`save error: ${err}`))\n\n    await storageSyncGet(['setting', 'searchText']).then(function (r) {\n        saveSettingAll(r.setting, true) // 初始设置参数\n        searchList = getSearchList(r.searchText || searchText)\n        if (!r.searchText) saveSearchText('') // 如果为空，设置默认值\n    })\n\n    // 最大保存历史记录数\n    if (localStorage['historyMax']) historyMax = Number(localStorage['historyMax'])\n\n    // 加载 js\n    loadJs(uniqueArray(Object.keys(conf.translateList).concat(Object.keys(conf.translateTTSList))), 'translate')\n    loadJs(Object.keys(conf.dictionaryList), 'dictionary')\n\n    // 添加菜单\n    setting.searchMenus.forEach(name => {\n        let url = searchList[name]\n        url && addMenu(name, name, url)\n    })\n\n    // 初始数据库\n    idb('favorite', 1, initFavorite) // 否则第一次安装时，\"我的收藏\"需要刷新一下才能正常看到数据。\n\n    // 查看全部数据\n    storageShowAll()\n})\n\n// 添加上下文菜单\nB.contextMenus.create({\n    title: \"梦想翻译“%s”\",\n    contexts: [\"selection\"],\n    onclick: function (info, tab) {\n        if (!info.selectionText) return\n        let msg = {action: 'contextMenus', text: info.selectionText}\n        if (tab && tab.id > 0) {\n            sendTabMessage(tab.id, msg)\n        } else {\n            getActiveTabId().then(tabId => sendTabMessage(tabId, msg))\n        }\n    }\n})\n\n// 监听消息\nB.onMessage.addListener(function (m, sender, sendResponse) {\n    sendResponse()\n    debug('request:', m)\n    debug('sender:', sender && sender.url ? sender.url : sender)\n    let tabId = getJSONValue(sender, 'tab.id')\n    if (!tabId) tabId = 'popup'\n\n    if (m.action === 'translate') {\n        createHistory(m) // 保存历史记录\n        runTranslate(tabId, m)\n    } else if (m.action === 'translateTTS') {\n        runTranslateTTS(tabId, m)\n    } else if (m.action === 'dictionary') {\n        runDictionary(tabId, m)\n    } else if (m.action === 'playSound') {\n        runPlaySound(tabId, m)\n    } else if (m.action === 'menu') {\n        changeMenu(m.name, m.isAdd)\n    } else if (m.action === 'saveSetting') {\n        saveSettingAll(m.setting, m.updateIcon, m.resetDialog)\n    } else if (m.action === 'copy') {\n        execCopy(m.text) // 后台复制，页面才不会失去焦点\n    } else if (m.action === 'transWindow') {\n        openTransWindow()\n    } else if (m.action === 'onRecord') {\n        openRecord()\n    } else if (m.action === 'openUrl') {\n        openTab(m.url)\n    } else if (m.action === 'onAllowSelect') {\n        sendAllowSelect()\n    } else if (m.action === 'onCropImg') {\n        cropImageSendMsg()\n    } else if (m.action === 'onSaveSearchText') {\n        saveSearchText(m.searchText)\n    } else if (m.action === 'onCapture') {\n        setTimeout(_ => capturePic(sender.tab, m), 100)\n    } else if (m.action === 'img2text') {\n        getOcrText(tabId, m.base64).catch()\n    } else if (m.action === 'textTmp') {\n        textTmp = m.text // 划词文字缓存\n    }\n})\n\n// 监听快捷键\nB.commands.onCommand.addListener(function (command) {\n    command = command + ''\n    if (command === 'openWindow') {\n        openTransWindow()\n    } else if (command === 'cropImage') {\n        cropImageSendMsg()\n    } else if (command === 'stopPlayAudio') {\n        stopAudio()\n    } else if (command === 'clipboardTrans') {\n        clipboardTrans()\n    } else if (command === 'toggleScribble') {\n        let scribbleTmp = window.scribbleTmp || 'off'\n        if (['direct', 'clickIcon'].includes(setting.scribble)) scribbleTmp = 'off';\n        else if (setting.scribble === 'off' && scribbleTmp === 'off') scribbleTmp = 'direct';\n        [setting.scribble, scribbleTmp] = [scribbleTmp, setting.scribble] // 交换\n        window.scribbleTmp = scribbleTmp\n        saveSettingAll(setting, true) // 保存\n    }\n})\n\nasync function runTranslate(tabId, m) {\n    let {action, text, srcLan, tarLan} = m\n    if (srcLan === 'auto') {\n        srcLan = await autoLang(text)\n        if (srcLan === tarLan) tarLan = srcLan === 'zh' ? 'en' : 'zh'\n    } else if (setting.autoLanguage) {\n        if (/\\p{Script=Han}/u.test(text)) srcLan = 'zh'\n        if (srcLan === tarLan) tarLan = srcLan === 'zh' ? 'en' : 'zh'\n    }\n    setting.translateList.forEach(name => {\n        sdkInit(`${name}Translate`).then(sd => {\n            sd.query(text, srcLan, tarLan).then(result => {\n                debug(`${name}:`, result)\n                sandFgMessage(tabId, {action, name, result})\n            }).catch(error => {\n                sandFgMessage(tabId, {action, name, text, error})\n            })\n\n            // 链接\n            let link = sd.link(text, srcLan, tarLan)\n            sandFgMessage(tabId, {action: 'link', type: action, name, link})\n        })\n    })\n\n    // 自动朗读\n    autoPlayTTS(tabId, text, srcLan).then(_ => null)\n}\n\nfunction runTranslateTTS(tabId, m) {\n    let list = conf.translateList\n    let tList = conf.translateTTSList\n    let {name, type, text, lang} = m\n    let message = {action: 'playSound', nav: 'translate', name, type, status: 'end'}\n    playTTS(name === setting.localSoundReplace ? 'local' : name, text, lang).then(() => {\n        sandFgMessage(tabId, message)\n    }).catch(err => {\n        debug(`${name} sound error:`, err)\n        let errMsg = `${tList[name] ? tList[name] : list[name] + '朗读'}出错`\n        sandFgMessage(tabId, Object.assign({}, message, {error: errMsg}))\n    })\n}\n\nfunction runDictionary(tabId, m) {\n    let {action, text} = m\n    window.dictionarySounds = {} // 返回的发音缓存\n    setting.dictionaryList.forEach(name => {\n        sdkInit(`${name}Dictionary`).then(sd => {\n            sd.query(text).then(result => {\n                debug(`${name}:`, result)\n                let {sound} = result\n                if (sound && sound.length > 0) dictionarySounds[name] = sound // 记录发音\n                sandFgMessage(tabId, {action, name, result})\n            }).catch(error => {\n                sandFgMessage(tabId, {action, name, text, error})\n            })\n\n            // 链接\n            sandFgMessage(tabId, {action: 'link', type: action, name, link: sd.link(text)})\n        })\n    })\n\n    // 自动朗读\n    setTimeout(() => {\n        autoPlayAudio(tabId, text).then(_ => null)\n    }, 300)\n}\n\nfunction runPlaySound(tabId, m) {\n    let {action, nav, name, type, url} = m\n    playAudio(url).then(() => {\n        sandFgMessage(tabId, {action, nav, name, type, status: 'end'})\n    }).catch(err => {\n        debug(`${name} sound error:`, err)\n        let title = conf.dictionaryList[name] || conf.translateList[name] || ''\n        sandFgMessage(tabId, {action, nav, name, type, error: `${title}发音出错`})\n    })\n}\n\nfunction cropImageSendMsg() {\n    getActiveTabId().then(tabId => tabId && sendTabMessage(tabId, {action: 'onCrop'}))\n}\n\nfunction capturePic(tab, m) {\n    B.tabs.captureVisibleTab(tab.windowId, {}, function (data) {\n        let im = document.createElement(\"img\")\n        im.onload = function () {\n            let ca = document.createElement(\"canvas\")\n            ca.width = m.width\n            ca.height = m.height\n            let ca2d = ca.getContext(\"2d\")\n            let t = im.height / m.innerHeight\n            ca2d.drawImage(im, m.startX * t, m.startY * t, m.width * t, m.height * t, 0, 0, m.width, m.height)\n            let b = ca.toDataURL(\"image/jpeg\")\n            getOcrText(tab.id, b).catch()\n        }\n        im.src = data\n    })\n}\n\n// 图片文字识别\nasync function getOcrText(tabId, base64) {\n    // 获取 token\n    let access_token = ''\n    await getOcrToken().then(token => {\n        access_token = token\n    }).catch(err => {\n        sandFgMessage(tabId, {action: 'onAlert', message: err, type: 'error'})\n    })\n    if (!access_token) return\n\n    // see https://cloud.baidu.com/doc/OCR/s/zk3h7xz52\n    let url = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token=' + access_token\n    let b = base64.substr(base64.indexOf(\",\") + 1)\n    let p = new URLSearchParams(`image=${encodeURIComponent(b)}&detect_language=true&language_type=${setting.translateOCR || 'CHN_ENG'}`)\n    httpPost({url, body: p.toString()}).then(r => {\n        let wordsRes = getJSONValue(r, 'words_result')\n        if (wordsRes && wordsRes.length > 0) {\n            let text = ''\n            for (let v of wordsRes) text += v.words + '\\n'\n            sandFgMessage(tabId, {action: 'contextMenus', text: text.trim()})\n        } else {\n            sandFgMessage(tabId, {action: 'onAlert', message: '百度图片识别失败', type: 'error'})\n        }\n    }).catch(e => {\n        sandFgMessage(tabId, {action: 'onAlert', message: '百度图片识别 API 出错', type: 'error'})\n        debug('baidu ocr error:', e)\n    })\n}\n\nfunction getOcrToken() {\n    return new Promise((resolve, reject) => {\n        if (localStorage['clearOcrExpires'] !== 'true') {\n            localStorage['clearOcrExpires'] = 'false'\n            ocrExpires = 0\n        }\n        if (ocrExpires - 10 > getTimestamp()) return resolve(ocrToken)\n        if (setting.ocrType === 'baidu') {\n            let url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${setting.baidu_orc_ak}&client_secret=${setting.baidu_orc_sk}`\n            httpGet(url, 'json').then(r => {\n                if (r.expires_in > 0 && r.access_token) {\n                    ocrExpires = getTimestamp() + r.expires_in\n                    ocrToken = r.access_token\n                    resolve(ocrToken)\n                } else if (r.error_description) {\n                    reject(r.error_description)\n                } else {\n                    reject('请求百度接口网路错误')\n                }\n            }).catch(e => {\n                debug('aip.baidubce.com api error!', e)\n                reject('百度获取 TOKEN 接口错误')\n            })\n        } else {\n            httpGet('https://mengxiang.net/api/getBaiduOcrToken.json', 'json').then(r => {\n                if (r.expires > 0 && r.token) {\n                    ocrExpires = r.expires\n                    ocrToken = r.token\n                    resolve(ocrToken)\n                } else {\n                    reject('请求官方接口网路错误')\n                }\n            }).catch(e => {\n                debug('mengxiang.net api error!', e)\n                reject('获取免费 TOKEN 接口错误')\n            })\n        }\n    })\n}\n\nfunction saveSearchText(s) {\n    if (!s) s = searchText\n    storageSyncSet({searchText: s})\n    searchList = getSearchList(s)\n}\n\nfunction saveSettingAll(data, updateIcon, resetDialog) {\n    setting = Object.assign({}, conf.setting, data)\n    updateIcon && changeBrowserIcon(setting.scribble) // 是否显示关闭划词图标\n    let options = resetDialog ? {setting, dialogConf: {}} : {setting}\n    if (resetDialog) saveSearchText('')\n    storageSyncSet(options)\n}\n\nfunction changeBrowserIcon(scribble) {\n    B.browserAction.setIcon({path: `icon/128${scribble === 'off' ? '_off' : ''}.png`})\n    // setBrowserAction(scribble === 'off' ? 'OFF' : '')\n}\n\nfunction setBrowserAction(text) {\n    B.browserAction.setBadgeText({text: text || ''})\n    B.browserAction.setBadgeBackgroundColor({color: 'red'})\n    isFirefox && B.browserAction.setBadgeTextColor({color: 'white'})\n}\n\nfunction changeMenu(name, isAdd) {\n    let url = searchList[name]\n    if (url) isAdd ? addMenu(name, name, url) : removeMenu(name)\n}\n\nfunction addMenu(name, title, url) {\n    // {type: \"separator\"}\n    let mid = md5(name)\n    B.contextMenus.create({\n        id: 'page_' + mid,\n        title: title + '首页',\n        contexts: [\"page\"],\n        onclick: function () {\n            B.tabs.create({url: (new URL(url)).origin})\n        }\n    })\n    B.contextMenus.create({\n        id: 'selection_' + mid,\n        title: title + \"“%s”\",\n        contexts: [\"selection\"],\n        onclick: function (info) {\n            B.tabs.create({url: url.format(decodeURIComponent(info.selectionText))})\n        }\n    })\n}\n\nfunction removeMenu(name) {\n    let mid = md5(name)\n    B.contextMenus.remove('page_' + mid)\n    B.contextMenus.remove('selection_' + mid)\n}\n\nfunction openTransWindow() {\n    openWindow('trans', 600, 520, B.root + 'html/popup.html?fullscreen=1')\n}\n\nfunction clipboardTrans() {\n    openWindow('trans', 600, 520, B.root + 'html/popup.html?fullscreen=1&clipboardRead=1', true)\n}\n\nfunction openRecord() {\n    openWindow('record', 600, 520, B.root + 'html/record.html', true)\n}\n\nfunction openWindow(wid, width, height, url, reopen) {\n    let name = `_window_${wid}`\n    let openFn = function (width, height, left, top) {\n        let o = {type: 'popup', width, height, url}\n\n        // 居中\n        let screen = window.screen\n        o.left = Math.floor(left > 0 ? left : (screen.width - o.width) / 2)\n        o.top = Math.floor(top > 0 ? top : (screen.height - o.height) / 2)\n\n        B.windows.create(o, w => window[name] = w.id)\n    }\n    let id = window[name]\n    if (id) {\n        B.windows.get(id, function (w) {\n            if (!B.runtime.lastError && w.id) {\n                if (reopen) {\n                    B.windows.remove(w.id)\n                    setTimeout(() => openFn(w.width, w.height, w.left, w.top), 100)\n                } else {\n                    B.windows.update(w.id, {focused: true})\n                }\n            } else {\n                openFn(width, height)\n            }\n        })\n    } else {\n        openFn(width, height)\n    }\n}\n\nfunction openTab(url) {\n    B.tabs.create({url})\n}\n\nfunction sendAllowSelect() {\n    getActiveTabId().then(tabId => {\n        tabId && sendTabMessage(tabId, {action: 'allowSelect'})\n    })\n}\n\n// 保存历史记录\nfunction createHistory(m) {\n    if (historyMax < 1) return // 如果为 0，则不再保存历史记录\n    let {text, formUrl, formTitle} = m\n\n    // 空内容不保存\n    text = text.trim()\n    if (!text) return\n\n    // 排除和最后一次记录相同内容\n    if (window.lastHistory === text) return\n    window.lastHistory = text\n\n    // 排除扩展内查询\n    if (formUrl) {\n        if (formUrl.indexOf(B.root + 'html/favorite.html') === 0) return\n        if (formUrl.indexOf(B.root + 'html/history.html') === 0) return\n    }\n\n    idb('history', 1, initHistory).then(db => {\n        db.create('history', {\n            content: text,\n            formTitle: formTitle || '',\n            formUrl: formUrl || '',\n            createDate: new Date().toJSON(),\n        }).then(() => {\n            // 清理早期数据\n            db.count('history').then(n => {\n                if (n <= historyMax) return\n                db.find('history', {direction: 'prev', offset: historyMax}).then(arr => {\n                    for (let v of arr) db.delete('history', v.id)\n                })\n            })\n        }).catch(e => {\n            debug('history create error:', e)\n        })\n    })\n}\n\n// 历史记录设置\nfunction settingHistory(n) {\n    historyMax = Number(n)\n    localStorage.setItem('historyMax', n)\n}\n\nfunction minCss(s) {\n    s = s.replace(/\\/\\*.*?\\*\\//g, '')\n    s = s.replace(/\\s+/g, ' ')\n    s = s.replace(/\\s*([:;{}!,])\\s*/g, '$1')\n    s = s.replace(/;}/g, '}')\n    s = s.replace(/;}/g, '}')\n    return s\n}\n\nasync function autoLang(text) {\n    let lang = 'en' // 默认值\n    await httpPost({\n        url: `https://fanyi.baidu.com/langdetect`,\n        body: `query=${encodeURI(text)}`\n    }).then(r => {\n        if (r && r.lan) lang = r.lan\n    }).catch(err => {\n        debug(err)\n    })\n    return lang\n}\n\nasync function autoPlayTTS(tabId, text, lang) {\n    let list = conf.translateTTSList || {}\n    let arr = setting.translateTTSList || []\n    if (lang === 'auto') lang = await autoLang(text)\n    for (let name of arr) {\n        let message = {action: 'playSound', nav: 'translate', name, type: 'source', status: 'end'}\n        await sandFgMessage(tabId, Object.assign({}, message, {status: 'start'}))\n        await playTTS(name, text, lang).then(() => {\n            sandFgMessage(tabId, message)\n        }).catch(err => {\n            debug(`${name} sound error:`, err)\n            sandFgMessage(tabId, Object.assign({}, message, {error: `${list[name] || '发音'}出错`}))\n        })\n    }\n}\n\nfunction playTTS(name, text, lang) {\n    return new Promise((resolve, reject) => {\n        stopAudio()\n        sdkInit(`${name}Translate`).then(sd => {\n            sd.tts(text, lang).then(val => {\n                if (name === 'local') return resolve()\n                if (Array.isArray(val)) {\n                    (async function () {\n                        let ok = false\n                        let err = new Error()\n                        for (let i = 0; i < val.length; i++) {\n                            await playAudio(val[i]).then(() => {\n                                if (!ok) ok = true // 为更好的兼容，只要有一次播放成功就算播放成功\n                            }).catch(e => {\n                                err = e\n                            })\n                        }\n                        ok ? resolve() : reject(err)\n                    })()\n                } else {\n                    playAudio(val).then(() => {\n                        resolve()\n                    }).catch(err => {\n                        reject(err)\n                    })\n                }\n            }).catch(err => {\n                reject(err)\n            })\n        })\n    })\n}\n\nasync function autoPlayAudio(tabId, text) {\n    let list = conf.dictionaryList || {}\n    let sounds = window.dictionarySounds\n    let type = setting.dictionaryReader || 'us'\n    let arr = setting.dictionarySoundList || []\n    for (let name of arr) {\n        let message = {action: 'playSound', nav: 'dictionary', name, type, status: 'end'}\n        await sandFgMessage(tabId, Object.assign({}, message, {status: 'start'})) // 显示开始朗读图标\n        let url = ''\n        if (sounds[name]) {\n            url = getSoundUrl(sounds[name], type) // 缓存中获取\n        } else {\n            let sdkRun = {}\n            await sdkInit(`${name}Dictionary`).then(r => {\n                sdkRun = r\n            })\n            await sdkRun.query(text).then(r => {\n                if (r.sound) url = getSoundUrl(r.sound, type) // 接口中获取\n            }).catch(error => {\n                debug(`${name} dictionary error:`, error)\n            })\n        }\n        debug('_playAudio_', name, type, url)\n        if (!url) continue // 没有发音跳过\n\n        // 播放声音\n        await playAudio(url).then(() => {\n            sandFgMessage(tabId, message)\n        }).catch(err => {\n            debug(`${name} sound error:`, err)\n            sandFgMessage(tabId, Object.assign({}, message, {error: `${list[name] || ''}发音出错`}))\n        })\n    }\n    debug('_playAudio_ finish.')\n}\n\nfunction getSoundUrl(arr, type) {\n    for (let v of arr) if (v.type === type && !v.isWoman) return v.url\n    return ''\n}\n\nfunction stopAudio() {\n    let a = window._Audio\n    if (a) a.pause()\n    if (B.tts && B.tts.stop) B.tts.stop()\n}\n\nfunction playAudio(url) {\n    return new Promise((resolve, reject) => {\n        if (!window._Audio) window._Audio = new Audio()\n        let a = window._Audio\n        let blobUrl = null\n        if (typeof url === 'string') {\n            a.src = url\n        } else if (typeof url === 'object') {\n            blobUrl = URL.createObjectURL(url)\n            a.src = blobUrl\n        } else {\n            return reject('Audio url error:', url)\n        }\n        a.onended = function () {\n            // if (blobUrl) URL.revokeObjectURL(blobUrl) // 释放内存\n            resolve()\n            let url = a.src // 记录最后一次播放的链接\n            window.audioSrc = {url}\n            getAudioBlob(url).then(b => window.audioSrc.blob = b)\n        }\n        a.onerror = function (err) {\n            reject(err)\n        }\n        let playPromise = a.play()\n        if (playPromise !== undefined) {\n            playPromise.catch(err => {\n                // reject(err)\n                resolve()\n            })\n        }\n    })\n}\n\n// 缓存音频文件\nasync function getAudioBlob(url, retry) {\n    let b\n    retry = retry || 3\n    for (let i = 0; i < retry; i++) {\n        await httpGet(url, 'blob').then(blob => {\n            // console.log(blob)\n            b = blob\n        }).catch(err => {\n            console.warn('httpGet:' + err)\n        })\n        if (b) return b\n    }\n    return null\n}\n\nfunction sdkInit(name) {\n    return new Promise((resolve, reject) => {\n        if (sdk[name]) return resolve(sdk[name])\n        if (typeof window[name] === 'function') {\n            sdk[name] = new window[name]().init()\n            resolve(sdk[name])\n        } else {\n            let err = name + ' not exist!'\n            debug('sdkInit error:', err)\n            reject(err)\n        }\n    })\n}\n\nfunction loadJs(arr, type) {\n    arr.forEach(k => {\n        let el = document.createElement(\"script\")\n        el.type = 'text/javascript'\n        el.src = `/js/${type || 'translate'}/${k}.js`\n        document.head.appendChild(el)\n    })\n}\n\nfunction invertObject(obj) {\n    let r = {}\n    for (const [key, value] of Object.entries(obj)) {\n        r[value] = key\n    }\n    return r\n}\n\n// 清理元素属性\nfunction cleanAttr(el, attrs) {\n    el.querySelectorAll('*').forEach(e => {\n        for (let i = e.attributes.length - 1; i >= 0; i--) {\n            let v = e.attributes[i]\n            if (typeof attrs === 'object') {\n                if (!attrs.includes(v.name)) e.removeAttribute(v.name) // 过滤白名单\n            } else {\n                e.removeAttribute(v.name) // 全部删除\n            }\n        }\n    })\n}\n\n// 检测返回结果是否正确，如果不正确，则重试\nasync function checkRetry(callback, times) {\n    times = times || 3 // 默认 3 次\n    let isOk = false\n    let p\n    for (let i = 0; i < times; i++) {\n        p = callback(i)\n        await p.then(r => {\n            if (r.data && r.data.length > 0) isOk = true\n        }).catch(_ => null)\n        if (isOk) return p\n        await sleep(300)\n    }\n    return p\n}\n\n/*function openBgPage(id, url, timeout) {\n    isFirefox ? openIframe(id, url, timeout) : openPopup(id, url, timeout)\n}\n\nfunction removeBgPage(id) {\n    isFirefox ? removeIframe(id) : removePopup(id)\n}\n\n// 打开一个几乎不可见的 popup (此方法只在 macOS 下有用，在 windows 系统下无效，firefox 浏览器也无效)\nfunction openPopup(id, url, timeout) {\n    timeout = timeout || 20 * 1000 // 默认 20 秒\n    removePopup(id)\n    let popupId = `_popup_${id || 'one'}`\n    B.windows.create({type: 'popup', focused: false, width: 1, height: 1, url}, w => window[popupId] = w.id)\n\n    // 定时关闭窗口，减少内存占用\n    _setTimeout(id, () => {\n        removePopup(id)\n        cleanPopup(url)  // 清理所有小窗口\n    }, timeout)\n}\n\nfunction removePopup(id) {\n    let popupId = `_popup_${id || 'one'}`\n    let wid = window[popupId]\n    if (!wid) return\n    B.windows.remove(wid, () => B.runtime.lastError) // 关闭\n    window[popupId] = null\n}\n\nfunction cleanPopup(url) {\n    B.windows.getAll({populate: true}, function (windows) {\n        let curOri = new URL(url).origin\n        windows.forEach(w => {\n            if (w.type === 'popup' && w.width === 1 && w.tabs.length === 1) {\n                // console.log('url:', w.tabs[0].url)\n                if (!w.tabs[0].url || new URL(w.tabs[0].url).origin === curOri) {\n                    B.windows.remove(w.id, () => B.runtime.lastError) // 关闭\n                }\n            }\n        })\n    })\n}*/\n\n// 创建一个临时标签\nfunction createTmpTab(id, url, timeout) {\n    let tabName = `_tmp_tab_${id || 'one'}`\n    removeTmpTab(id) // 关闭\n    B.tabs.create({active: false, url}, tab => window[tabName] = tab.id)\n    if (timeout > 1000) _setTimeout(id, () => removeTmpTab(id), timeout)  // 定时关闭窗口，减少内存占用\n}\n\n// 关闭临时标签\nfunction removeTmpTab(id) {\n    let tabName = `_tmp_tab_${id || 'one'}`\n    let tabId = window[tabName]\n    if (!tabId) return\n    B.tabs.remove(tabId, () => B.runtime.lastError) // 关闭\n    window[tabName] = null\n}\n\n// 强制刷新临时标签\nfunction reloadTmpTab(id) {\n    let tabName = `_tmp_tab_${id || 'one'}`\n    let tabId = window[tabName]\n    if (tabId) B.tabs.reload(tabId, {bypassCache: true}, () => B.runtime.lastError) // 重新加载\n}\n\nfunction openIframe(id, url, timeout) {\n    timeout = timeout || 20 * 1000 // 默认 20 秒\n    let ifrId = `_iframe_${id || 'one'}`\n    let el = document.getElementById(ifrId)\n    if (!el) {\n        el = document.createElement('iframe')\n        el.id = ifrId\n        el.src = url\n        document.body.appendChild(el)\n    } else {\n        el.src = url\n    }\n\n    // 定时删除，减小内存占用\n    _setTimeout(id, () => el && el.remove(), timeout)\n    return el\n}\n\nfunction removeIframe(id) {\n    let ifrId = `_iframe_${id || 'one'}`\n    let el = document.getElementById(ifrId)\n    el && el.remove()\n}\n\nfunction sliceStr(text, maxLen) {\n    let r = []\n    if (text.length <= maxLen) {\n        r.push(text)\n    } else {\n        // 根据优先级截取字符串，详细符号见：https://zh.wikipedia.org/wiki/%E6%A0%87%E7%82%B9%E7%AC%A6%E5%8F%B7\n        let separators = `?!;.-…,/\"`\n        separators += `？！；。－－＿～﹏·，：、`\n        separators += `“”﹃﹄「」﹁﹂『』﹃﹄（）［］〔〕【】《》〈〉()[]{}`\n        let separatorArr = [...separators]\n        let arr = text.split('\\n')\n        arr.forEach(s => {\n            s = s.trim()\n            if (!s) return\n\n            if (s.length <= maxLen) {\n                r.push(s)\n            } else {\n                do {\n                    if (s.length <= maxLen) {\n                        r.push(s)\n                        break\n                    }\n                    let end = false\n                    for (let i = 0; i < separatorArr.length; i++) {\n                        if (i + 1 === separatorArr.length) end = true\n                        let symbol = separatorArr[i]\n                        let n = s.indexOf(symbol)\n                        if (n === -1) continue\n                        if (n > maxLen) continue\n                        let s2 = s.substring(0, n).trim()\n                        s2 && r.push(s2)\n                        s = s.substring(n + 1).trim()\n                        break\n                    }\n                    if (!end) continue\n                    if (!s) break\n                    if (s.length <= maxLen) {\n                        r.push(s)\n                        break\n                    }\n\n                    let s1 = s.substring(0, maxLen)\n                    let s2 = s.substring(maxLen)\n                    let n = s1.lastIndexOf(' ')\n                    if (n !== -1) {\n                        // 处理英文\n                        let s3 = s1.substring(0, n)\n                        let s4 = s1.substring(n)\n                        r.push(s3)\n                        s = (s4 + s2).trim()\n                    } else {\n                        // 没有空格，就硬切（这种情况一般是中文）\n                        r.push(s1)\n                        s = s2\n                    }\n                } while (s)\n            }\n        })\n    }\n    return r\n}\n"
  },
  {
    "path": "src/js/common.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\n/*!\n * 浏览器统一兼容\n * 参考：\n * https://github.com/mozilla/webextension-polyfill\n * https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities\n * https://developer.chrome.com/docs/extensions/reference/\n * https://crxdoc-zh.appspot.com/extensions/\n */\nconst isDebug = true\nwindow.isFirefox = navigator.userAgent.includes(\"Firefox\")\n// window.isFirefox = typeof browser !== \"undefined\" && Object.getPrototypeOf(browser) === Object.prototype\nconst B = {\n    extension: chrome.extension,\n    getBackgroundPage: chrome.extension.getBackgroundPage,\n    windows: chrome.windows,\n    commands: chrome.commands,\n    runtime: chrome.runtime,\n    id: chrome.runtime.id,\n    root: chrome.runtime.getURL(''),\n    onMessage: chrome.runtime.onMessage,\n    sendMessage: chrome.runtime.sendMessage,\n    storage: chrome.storage,\n    browserAction: chrome.browserAction,\n    contextMenus: chrome.contextMenus,\n    webRequest: chrome.webRequest,\n    cookies: chrome.cookies,\n    tabs: chrome.tabs,\n    tts: chrome.tts,\n    app: chrome.app,\n}\nString.prototype.format = function () {\n    let args = arguments\n    return this.replace(/{(\\d+)}/g, function (match, number) {\n        return typeof args[number] != 'undefined' ? args[number] : match\n    })\n}\n\nfunction storageLocalGet(options) {\n    return storage('local', 'get', options)\n}\n\nfunction storageLocalSet(options) {\n    return storage('local', 'set', options)\n}\n\nfunction storageSyncGet(options) {\n    return storage('sync', 'get', options)\n}\n\nfunction storageSyncSet(options) {\n    return storage('sync', 'set', options)\n}\n\nfunction storageShowAll() {\n    if (!isDebug) return\n    !isFirefox && storageSyncGet(null).then(function (r) {\n        debug(`all sync storage:`, r)\n    })\n    storageLocalGet(null).then(function (r) {\n        debug(`all local storage:`, r)\n    })\n}\n\nfunction storage(type, method, options) {\n    return new Promise((resolve, reject) => {\n        if (!isFirefox) {\n            if (typeof B.app.isInstalled === 'undefined') return reject('The extension has been updated!')\n            let callback = function (r) {\n                let err = B.runtime.lastError\n                err ? reject(err) : resolve(r)\n            }\n            let api = type === 'sync' ? B.storage.sync : B.storage.local\n            if (method === 'get') {\n                api.get(options, callback)\n            } else if (method === 'set') {\n                api.set(options, callback)\n            }\n        } else {\n            let api = isDebug ? browser.storage.local : type === 'sync' ? browser.storage.sync : browser.storage.local\n            if (method === 'get') {\n                api.get(options).then(r => resolve(r), err => reject(err))\n            } else if (method === 'set') {\n                api.set(options).then(r => resolve(r), err => reject(err))\n            }\n        }\n    })\n}\n\nfunction cookies(method, options) {\n    return new Promise((resolve, reject) => {\n        if (!isFirefox) {\n            if (typeof B.app.isInstalled === 'undefined') return reject('The extension has been updated!')\n            let callback = function (r) {\n                let err = B.runtime.lastError\n                err ? reject(err) : resolve(r)\n            }\n            if (method === 'get') {\n                B.cookies.get(options, callback)\n            } else if (method === 'getAll') {\n                B.cookies.getAll(options, callback)\n            } else if (method === 'set') {\n                B.cookies.set(options, callback)\n            } else if (method === 'remove') {\n                B.cookies.remove(options, callback)\n            }\n        } else {\n            if (method === 'get') {\n                browser.cookies.get(options).then(r => resolve(r), err => reject(err))\n            } else if (method === 'getAll') {\n                browser.cookies.getAll(options).then(r => resolve(r), err => reject(err))\n            } else if (method === 'set') {\n                browser.cookies.set(options).then(r => resolve(r), err => reject(err))\n            } else if (method === 'remove') {\n                browser.cookies.remove(options).then(r => resolve(r), err => reject(err))\n            }\n        }\n    })\n}\n\nfunction sendMessage(message) {\n    return new Promise((resolve, reject) => {\n        if (!isFirefox) {\n            if (typeof B.app.isInstalled === 'undefined') return reject('The extension has been updated!')\n            B.sendMessage(message, r => B.runtime.lastError ? reject(B.runtime.lastError) : resolve(r))\n        } else {\n            browser.runtime.sendMessage(message).then(r => resolve(r), err => reject(err))\n        }\n    })\n}\n\nfunction sendTabMessage(tabId, message) {\n    return new Promise((resolve, reject) => {\n        if (!isFirefox) {\n            if (typeof B.app.isInstalled === 'undefined') return reject('The extension has been updated!')\n            tabId && B.tabs.sendMessage(tabId, message, r => B.runtime.lastError ? reject(B.runtime.lastError) : resolve(r))\n        } else {\n            tabId && browser.tabs.sendMessage(tabId, message).catch(err => debug('send error:', err))\n        }\n        resolve()\n    })\n}\n\nfunction sandFgMessage(id, message) {\n    if (id === 'popup') {\n        let popup = B.extension.getViews({type: 'popup'})\n        if (popup.length > 0) {\n            return sendMessage(message)\n        } else {\n            return Promise.resolve()\n        }\n    } else {\n        return sendTabMessage(id, message)\n    }\n}\n\nfunction getActiveTabId() {\n    return new Promise((resolve, reject) => {\n        if (!isFirefox) {\n            B.tabs.query({currentWindow: true, active: true}, tab => {\n                resolve(getJSONValue(tab, '0.id')) // todo: 还有优化空间\n            })\n        } else {\n            browser.tabs.query({currentWindow: true, active: true}).then(tab => {\n                let tabId = tab[0] && resolve(tab[0].id)\n                resolve(tabId)\n            }, err => reject(err))\n        }\n    })\n}\n\nfunction onBeforeSendHeadersAddListener(callback, filter, opt_extraInfoSpec) {\n    if (!opt_extraInfoSpec) opt_extraInfoSpec = Object.values(B.webRequest.OnBeforeSendHeadersOptions)\n    B.webRequest.onBeforeSendHeaders.addListener(callback, filter, opt_extraInfoSpec)\n}\n\nfunction onBeforeSendHeadersRemoveListener(callback) {\n    B.webRequest.onBeforeSendHeaders.removeListener(callback)\n}\n\nfunction requestHeadersFormat(s) {\n    let r = []\n    let arr = s.split('\\n')\n    arr && arr.forEach(v => {\n        v = v.trim()\n        if (!v) return\n        let a = v.split(': ')\n        if (a.length === 2) r.push({name: a[0].trim(), value: a[1].trim()})\n    })\n    return r\n}\n\nfunction onBeforeRequestAddListener(callback, filter, extraInfoSpec) {\n    if (!extraInfoSpec) {\n        extraInfoSpec = [\"blocking\", \"extraHeaders\", \"requestBody\"] // 解决 chrome 审核机制太垃圾，提示没有使用到 webRequestBlocking\n        extraInfoSpec = Object.values(B.webRequest.OnBeforeRequestOptions)\n    }\n    B.webRequest.onBeforeRequest.addListener(callback, filter, extraInfoSpec)\n}\n\nfunction onBeforeRequestRemoveListener(callback) {\n    B.webRequest.onBeforeRequest.removeListener(callback)\n}\n\nfunction onHeadersReceivedAddListener(callback, filter, extraInfoSpec) {\n    if (!extraInfoSpec) extraInfoSpec = Object.values(B.webRequest.OnHeadersReceivedOptions)\n    B.webRequest.onHeadersReceived.addListener(callback, filter, extraInfoSpec)\n}\n\nfunction onHeadersReceivedRemoveListener(callback) {\n    B.webRequest.onHeadersReceived.removeListener(callback)\n}\n\nfunction onCompletedAddListener(callback, filter, extraInfoSpec) {\n    if (!extraInfoSpec) extraInfoSpec = Object.values(B.webRequest.OnCompletedOptions)\n    B.webRequest.onCompleted.addListener(callback, filter, extraInfoSpec)\n}\n\nfunction onCompletedRemoveListener(callback) {\n    B.webRequest.onCompleted.removeListener(callback)\n}\n\nfunction onRemoveFrame(details) {\n    let headers = Object.assign([], details.responseHeaders)\n    for (let i = 0; i < headers.length; i++) {\n        let name = headers[i].name.toLowerCase()\n        if (name.includes('frame-options') || name.includes('content-security-policy')) {\n            headers.splice(i, 1)\n            // break\n        }\n    }\n    return {responseHeaders: headers}\n}\n\nfunction onRemoveCross(details) {\n    let headers = Object.assign([], details.responseHeaders)\n    for (let i = 0; i < headers.length; i++) {\n        let name = headers[i].name.toLowerCase()\n        if (name.includes('cross-origin-resource-policy') || name.includes('cross-origin-opener-policy')) {\n            headers.splice(i, 1)\n            // break\n        }\n    }\n    return {responseHeaders: headers}\n}\n\n// 获得所有语音的列表 (firefox 不支持)\nfunction getVoices() {\n    return new Promise((resolve, reject) => {\n        if (!B.tts || !B.tts.getVoices) return reject(\"I won't support it!\")\n\n        B.tts.getVoices(function (voices) {\n            let list = {}\n            for (let i = 0; i < voices.length; i++) {\n                // debug('Voice ' + i + ':', JSON.stringify(voices[i]))\n                let v = voices[i]\n                if (!list[v.lang]) list[v.lang] = []\n                list[v.lang].push({lang: v.lang, voiceName: v.voiceName, remote: v.remote})\n            }\n            resolve(list)\n        })\n    })\n}\n\nfunction getTimestamp() {\n    return Date.parse(new Date() + '') / 1000\n}\n\nfunction addClass(el, className) {\n    if (!el || !className) return\n    className = className.trim()\n    let oldClassName = el.className.trim()\n    if (!oldClassName) {\n        el.className = className\n    } else if (` ${oldClassName} `.indexOf(` ${className} `) === -1) {\n        el.className += ' ' + className\n    }\n}\n\nfunction rmClass(el, className) {\n    if (!el.className) return\n    className = className.trim()\n    let newClassName = el.className.trim()\n    if ((` ${newClassName} `).indexOf(` ${className} `) === -1) return\n    newClassName = newClassName.replace(new RegExp('(?:^|\\\\s)' + className + '(?:\\\\s|$)', 'g'), ' ').trim()\n    if (newClassName) {\n        el.className = newClassName\n    } else {\n        el.removeAttribute('class')\n    }\n}\n\nfunction hasClass(el, className) {\n    if (!el.className) return false\n    return (` ${el.className.trim()} `).indexOf(` ${className.trim()} `) > -1\n}\n\nfunction sleep(delay) {\n    return new Promise(r => setTimeout(r, delay))\n}\n\nfunction getDate(value, isDate) {\n    let d = value ? new Date(value) : new Date()\n    d.setMinutes(-d.getTimezoneOffset() + d.getMinutes(), d.getSeconds(), 0)\n    let s = d.toISOString()\n    if (isDate) {\n        s = s.substring(0, 10)\n    } else {\n        s = s.replace('T', ' ')\n        s = s.replace('.000Z', '')\n    }\n    return s\n}\n\n// 补零\nfunction zero(value, digits) {\n    digits = digits || 2\n    let isNegative = Number(value) < 0\n    let s = value.toString()\n    if (isNegative) s = s.slice(1)\n    let size = digits - s.length + 1\n    s = new Array(size).join('0').concat(s)\n    return (isNegative ? '-' : '') + s\n}\n\nfunction $(id) {\n    return document.getElementById(id)\n}\n\nfunction N(name) {\n    return document.getElementsByName(name)\n}\n\nfunction S(s) {\n    return document.querySelector(s)\n}\n\nfunction D(s) {\n    return document.querySelectorAll(s)\n}\n\nfunction onD(el, type, listener, options) {\n    el.forEach(v => {\n        v.addEventListener(type, listener, options)\n    })\n}\n\nfunction unD(el, type, listener, options) {\n    el.forEach(v => {\n        v.removeEventListener(type, listener, options)\n    })\n}\n\nfunction removeD(el) {\n    el.forEach(e => e.remove())\n}\n\nfunction rmClassD(el, className) {\n    el.forEach(v => rmClass(v, className))\n}\n\nfunction inArray(val, arr) {\n    // return arr.indexOf(val) !== -1\n    return arr.includes(val)\n}\n\nfunction isObject(o) {\n    return Object.prototype.toString.call(o) === '[object Object]'\n}\n\nfunction isArray(o) {\n    return Object.prototype.toString.call(o) === '[object Array]'\n}\n\nfunction isString(o) {\n    return Object.prototype.toString.call(o) === '[object String]'\n}\n\nfunction isNumber(o) {\n    return Object.prototype.toString.call(o) === '[object Number]'\n}\n\nfunction isDate(o) {\n    return Object.prototype.toString.call(o) === '[object Date]'\n}\n\nfunction isRegExp(o) {\n    return Object.prototype.toString.call(o) === '[object RegExp]'\n}\n\nfunction isError(o) {\n    return Object.prototype.toString.call(o) === '[object Error]'\n}\n\nfunction isSymbol(o) {\n    return Object.prototype.toString.call(o) === '[object Symbol]'\n}\n\nfunction isArrayBuffer(o) {\n    return Object.prototype.toString.call(o) === '[object ArrayBuffer]'\n}\n\nfunction isFunction(o) {\n    return Object.prototype.toString.call(o) === '[object Function]'\n}\n\nfunction getSearchList(s) {\n    s = s.trim()\n    let arr = s.split('\\n')\n    let r = {}\n    for (let v of arr) {\n        v = v.trim()\n        let a = v.split('|')\n        let key = a[0] && a[0].trim()\n        let val = a[1] && a[1].trim()\n        if (key && val) r[key] = val\n    }\n    return r\n}\n\n// 解决 JSON 太深问题\nfunction getJSONValue(data, keys, value) {\n    // if (!data || !isObject(data)) return value // 默认值\n    if (!data) return value // 默认值\n    keys = keys.trim()\n    let arr = keys.split('.')\n    let val = Object.assign({}, data)\n    for (let key of arr) {\n        if (!val[key]) return value // 默认值\n        val = val[key]\n    }\n    return val\n}\n\n// 添加 DOM 元素\nfunction addEl(options) {\n    let {tagName, id, className, text, title, onClick} = options\n    let el = document.createElement(tagName)\n    if (id) el.id = id\n    if (className) el.className = className\n    if (text) el.textContent = text\n    if (title) el.title = title\n    if (onClick) el.addEventListener('click', onClick)\n    return el\n}\n\nfunction createTextarea() {\n    let t = document.createElement(\"textarea\")\n    t.style.position = 'fixed'\n    t.style.top = '-200%'\n    document.body.appendChild(t)\n    return t\n}\n\nfunction execCopy(s) {\n    let t = createTextarea()\n    t.value = s\n    t.select()\n    document.execCommand(\"copy\")\n    document.body.removeChild(t)\n}\n\nfunction execPaste() {\n    let t = createTextarea()\n    t.focus()\n    document.execCommand(\"paste\")\n    let v = t.value\n    document.body.removeChild(t)\n    return v\n}\n\n// dream alert\nfunction dal(text, type, onSubmit) {\n    let icon = {\n        info: '<i class=\"dmx-icon dmx-icon-info\"></i>',\n        error: '<i class=\"dmx-icon dmx-icon-close\"></i>',\n        success: '<i class=\"dmx-icon dmx-icon-success\"></i>',\n    }\n    D('.dal_bg,.dal').forEach(e => e.remove()) // 只允许存在一个\n    document.body.insertAdjacentHTML('beforeend', `<div class=\"dal_bg\"></div>\n<div class=\"dal\">\n    <div class=\"dal_modal\">\n        <div class=\"dal_text\">${(icon[type] || icon.info) + text}</div>\n        <div class=\"dal_foot\">\n            <button class=\"dmx_button\" data-type=\"submit\">确认</button>\n        </div>\n    </div>\n</div>`)\n    let rmFn = () => D('.dal_bg,.dal').forEach(e => e.remove())\n    S('[data-type=\"submit\"]').addEventListener('click', rmFn)\n    if (typeof onSubmit === 'function') S('[data-type=\"submit\"]').addEventListener('click', onSubmit)\n}\n\n// dream confirm\nfunction dco(text, onSubmit, onCancel) {\n    D('.dal_bg,.dal').forEach(e => e.remove()) // 只允许存在一个\n    document.body.insertAdjacentHTML('beforeend', `<div class=\"dal_bg\"></div>\n<div class=\"dal\">\n    <div class=\"dal_modal\">\n        <div class=\"dal_text\"><i class=\"dmx-icon dmx-icon-info\"></i>${text}</div>\n        <div class=\"dal_foot\">\n            <button class=\"dmx_button\" data-type=\"submit\">确认</button>\n            <button class=\"dmx_button dmx_button_default\" data-type=\"cancel\">取消</button>\n        </div>\n    </div>\n</div>`)\n    let submitEl = S('[data-type=\"submit\"]')\n    let cancelEl = S('[data-type=\"cancel\"]')\n    let rmFn = () => D('.dal_bg,.dal').forEach(e => e.remove())\n    submitEl.addEventListener('click', rmFn)\n    cancelEl.addEventListener('click', rmFn)\n    if (typeof onSubmit === 'function') submitEl.addEventListener('click', onSubmit)\n    if (typeof onCancel === 'function') cancelEl.addEventListener('click', onCancel)\n}\n\n// dream dialog\nfunction ddi(option) {\n    let o = Object.assign({\n        title: '',\n        body: '',\n        fullscreen: false,\n        onClose: null,\n    }, option || {})\n    let el = S('.ddi .ddi_body')\n    if (el) {\n        el.innerHTML = o.body\n    } else {\n        document.body.insertAdjacentHTML('beforeend', `<div class=\"ddi_bg\"></div>\n<div class=\"ddi\">\n    <div class=\"ddi_modal ddi_dialog${o.fullscreen ? ' fullscreen' : ''}\">\n        <div class=\"ddi_head\">${o.title}<i class=\"dmx-icon dmx-icon-close\"></i></div>\n        <div class=\"ddi_body\">${o.body}</div>\n    </div>\n</div>`)\n        addClass(document.body, 'dmx_overflow_hidden')\n        S('.ddi_head .dmx-icon-close').addEventListener('click', () => {\n            rmClass(document.body, 'dmx_overflow_hidden')\n            removeDdi()\n            if (typeof o.onClose === 'function') o.onClose()\n        })\n    }\n}\n\n// remove dream dialog\nfunction removeDdi() {\n    rmClass(document.body, 'dmx_overflow_hidden')\n    D('.ddi_bg,.ddi').forEach(e => e.remove())\n}\n\nfunction loading(text) {\n    document.body.insertAdjacentHTML('beforeend', `<div class=\"ddi_bg\"></div>\n<div class=\"ddi\">\n    <div class=\"ddi_loading\">\n        <div class=\"ddi_loading_inner\"></div>\n        <div class=\"mt_2\">${text || 'loading...'}</div>\n    </div>\n</div>`)\n}\n\n// 过滤 HTML，防止XSS\nfunction HTMLEncode(s) {\n    let d = document.createElement('div')\n    d.textContent = s\n    return d.innerHTML || ''\n}\n\nfunction uniqueArray(arr) {\n    return [...new Set(arr)]\n}\n\nfunction httpGet(url, type, headers, notStrict) {\n    return new Promise((resolve, reject) => {\n        let c = new XMLHttpRequest()\n        c.responseType = type || 'text'\n        c.timeout = 20000\n        c.onload = function (e) {\n            if (notStrict) {\n                resolve(this.response)\n            } else {\n                if (this.status === 200) {\n                    resolve(this.response)\n                } else {\n                    reject(e)\n                }\n            }\n        }\n        c.ontimeout = function (e) {\n            reject(e)\n        }\n        c.onerror = function (e) {\n            reject(e)\n        }\n        c.open(\"GET\", url)\n        headers && headers.forEach(v => {\n            c.setRequestHeader(v.name, v.value)\n        })\n        c.send()\n    })\n}\n\nfunction httpPost(options) {\n    let o = Object.assign({\n        url: '',\n        responseType: 'json',\n        type: 'form',\n        body: null,\n        timeout: 30000,\n        headers: [],\n    }, options)\n    return new Promise((resolve, reject) => {\n        let c = new XMLHttpRequest()\n        c.responseType = o.responseType\n        c.timeout = o.timeout\n        c.onload = function (e) {\n            if (this.status === 200 && this.response !== null) {\n                resolve(this.response)\n            } else {\n                reject(e)\n            }\n        }\n        c.ontimeout = function (e) {\n            reject(e)\n        }\n        c.onerror = function (e) {\n            reject(e)\n        }\n        c.open(\"POST\", o.url)\n        if (o.type === 'form') {\n            c.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded; charset=UTF-8\")\n        } else if (o.type === 'json') {\n            c.setRequestHeader(\"Content-Type\", \"application/json; charset=UTF-8\")\n        } else if (o.type === 'xml') {\n            c.setRequestHeader(\"Content-Type\", \"application/ssml+xml\")\n        }\n        o.headers.length > 0 && o.headers.forEach(v => {\n            c.setRequestHeader(v.name, v.value)\n        })\n        c.send(o.body)\n    })\n}\n\n// 时间范围内，只执行最后一次回调函数\nfunction _setTimeout(tid, callback, timeout) {\n    tid = `mx_timeoutId_${tid}`\n    _clearTimeout(tid)\n    return window[tid] = setTimeout(callback, timeout)\n}\n\nfunction _clearTimeout(tid) {\n    let id = window[tid]\n    if (id) {\n        clearTimeout(id)\n        window[tid] = null\n    }\n}\n\nfunction encodeURI(s) {\n    s = encodeURIComponent(s)\n    s = s.replace(/#/g, '%23')\n    s = s.replace(/&/g, '%26')\n    return s\n}\n\nfunction debug(...data) {\n    isDebug && console.log('[DMX DEBUG]', ...data)\n}\n"
  },
  {
    "path": "src/js/content.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nlet isPopup = window.isPopup\nlet isFullscreen, isClipboardRead, isSome\nlet dialog, shadow,\n    setting, conf, dialogConf,\n    languageList, dialogCSS = '', dictionaryCSS = {},\n    iconBut, iconText,\n    msgList = {},\n    root = B.root\nlet dQuery = {action: '', text: '', source: '', target: ''}\nlet textTmp = ''\nlet history = [], historyIndex = 0, disHistory = false\nlet searchText\ndocument.addEventListener('DOMContentLoaded', async function () {\n    let u = new URL(location.href)\n    isFullscreen = u.searchParams.get('fullscreen') === '1'\n    isClipboardRead = u.searchParams.get('clipboardRead') === '1'\n    isSome = location.href.indexOf(root) === 0\n\n    await storageLocalGet(['conf', 'languageList', 'dialogCSS', 'dictionaryCSS']).then(function (r) {\n        conf = r.conf\n        languageList = JSON.parse(r.languageList)\n        dialogCSS = r.dialogCSS\n        dictionaryCSS = r.dictionaryCSS\n    })\n\n    await storageSyncGet(['setting', 'dialogConf', 'searchText']).then(function (r) {\n        setting = r.setting\n        dialogConf = Object.assign({}, conf.dialogConf, r.dialogConf)\n        searchText = r.searchText\n    })\n\n    // 初始对话框\n    initDialog()\n\n    // 初始对话框CSS\n    initDictionaryCSS()\n\n    // 是否开启自动解除选中现在\n    if (setting.allowSelect === 'on' && !isSome) allowUserSelect()\n\n    // 查看全部数据\n    storageShowAll()\n})\n\n// 监听消息\nB.onMessage.addListener(function (m, sender, sendResponse) {\n    sendResponse()\n    debug('request:', m)\n    // debug('sender:', sender)\n    if (m.action === 'translate') {\n        msgList[m.name] = m.result\n        resultTranslate(m.name)\n    } else if (m.action === 'dictionary') {\n        resultDictionary(m)\n    } else if (m.action === 'playSound') {\n        resultSound(m)\n    } else if (m.action === 'link') {\n        resultLink(m)\n    } else if (m.action === 'allowSelect') {\n        allowUserSelect()\n    } else if (m.action === 'onCrop') {\n        initCrop()\n    } else if (m.action === 'onAlert') {\n        dmxAlert(m.message, m.type)\n    } else if (m.action === 'contextMenus') {\n        sendQuery(m.text) // 右键查询\n        showDialog()\n    }\n})\n\n// 监听 frame 消息\nwindow.addEventListener(\"message\", function (m) {\n    let d = m.data\n    if (d.text && typeof d.clientX === 'number' && typeof d.clientY === 'number') initQuery(d.text, d.clientX, d.clientY)\n})\n\n// 监听设置修改\nB.storage.onChanged.addListener(function (data) {\n    let keys = Object.keys(data)\n    keys.forEach(k => {\n        let v = data[k].newValue\n        if (k === 'setting') {\n            setting = v\n            debug('new setting:', v)\n\n            // 初始对话框CSS\n            initDictionaryCSS()\n        } else if (k === 'searchText') {\n            searchText = v\n            debug('new searchText:', v)\n        }\n    })\n})\n\n// 初始对话框\nfunction initDialog() {\n    let isChange = false\n    let options = {\n        cssText: dialogCSS,\n        width: dialogConf.width,\n        height: dialogConf.height,\n        minWidth: 450,\n        onResize: function (style) {\n            const {width, height} = style\n            if (width) dialogConf.width = width\n            if (height) dialogConf.height = height\n            isChange = true\n        }\n    }\n    if (isPopup) {\n        options.width = 'auto'\n        options.height = 'auto'\n        options.show = true\n        options.autoHide = false\n        options.isMove = false\n        options.isResize = false\n        options.onResize = null\n    }\n    dialog = dmxDialog(options)\n\n    // 保存窗口大小\n    dialog.el.addEventListener('mouseup', function () {\n        if (isChange) {\n            saveDialogConf()\n            isChange = false\n        }\n    })\n\n    // 影子元素\n    shadow = dialog.shadow\n\n    // 小屏窗口\n    if (isPopup) {\n        addClass(dialog.el, 'dmx_popup')\n        isFullscreen ? addClass(dialog.el, 'fullscreen') : addClass(document.documentElement, 'dmx_popup')\n        A('#dmx_close,#dmx_pin,#dmx_fullscreen').forEach(e => e.remove())\n        if (B.getBackgroundPage) textTmp = B.getBackgroundPage().textTmp // 读取后台缓存\n    }\n\n    // 划词查询\n    document.addEventListener('mouseup', function (e) {\n        let text = window.getSelection().toString().trim()\n        initQuery(text, e.clientX, e.clientY)\n    })\n\n    // 鼠标图标\n    iconBut = I('dmx_mouse_icon')\n    iconBut.onclick = function (e) {\n        iconBut.style.display = 'none'\n        sendQuery(iconText)  // 点击图标查询\n        showDialog(e.clientX + 10, e.clientY - 35)\n    }\n    iconBut.onmousedown = function (e) {\n        e.preventDefault()\n    }\n\n    // 绑定事件\n    let nav = I('dmx_navigate')\n    let uEl = nav.querySelectorAll('u')\n    uEl.forEach(e => {\n        e.addEventListener('click', function () {\n            let action = this.getAttribute('action')\n            if (!['translate', 'dictionary', 'search'].includes(action)) return\n            if (dQuery.action === action) return\n            rmClassD(uEl, 'active')\n            addClass(this, 'active')\n            setDialogConf('action', action) // 保存设置\n            if (action === 'translate') {\n                initTranslate()\n            } else if (action === 'dictionary') {\n                initDictionary()\n            } else if (action === 'search') {\n                initSearch()\n            }\n            sendQuery(dQuery.text) // 切换导航查询\n        })\n    })\n\n    // 初始模块\n    let action = dialogConf.action\n    if (action) {\n        if (!['translate', 'dictionary', 'search'].includes(action)) action = 'translate'\n        let actionEl = nav.querySelector(`u[action=\"${action}\"]`)\n        if (actionEl) actionEl.click()\n    }\n\n    // 设置按钮\n    I('dmx_setting').addEventListener('click', function () {\n        rmClassD(uEl, 'active')\n        initSetting()\n        dQuery.action = 'setting'\n    })\n\n    // 更多功能\n    I('dmx_more').addEventListener('click', function () {\n        rmClassD(uEl, 'active')\n        initMore()\n        dQuery.action = 'more'\n    })\n\n    // 录音练习\n    I('dmx_voice').addEventListener('click', function () {\n        sendMessage({action: 'onRecord'})\n    })\n\n    // 鼠标停留取词\n    document.addEventListener('mousemove', (e) => {\n        if (setting.autoWords && setting.scribble !== 'off') _setTimeout('_mouseWords', () => mouseWords(e), 300)\n    })\n\n    // 历史记录\n    let hEl = I('dmx_history')\n    let hlEl = hEl.querySelector('.dmx-icon-left')\n    let hrEl = hEl.querySelector('.dmx-icon-right')\n    let loadHistory = function (index) {\n        if (index < 0 || index >= history.length) return\n        disHistory = true\n        historyIndex = index\n\n        let className = 'disabled'\n        rmClass(hlEl, className)\n        rmClass(hrEl, className)\n        if (index === 0) {\n            addClass(hlEl, className)\n        } else if (index === history.length - 1) {\n            addClass(hrEl, className)\n        }\n\n        let data = history[index]\n        debug('current:', historyIndex, data, history)\n        let action = data.action\n        let text = data.text\n        dialogConf.action = action\n        dialogConf.source = data.source\n        dialogConf.target = data.target\n        dQuery.action !== action && setDialogConf('action', action) // 保存设置\n        rmClassD(uEl, 'active')\n        addClass(nav.querySelector(`u[action=\"${action}\"]`), 'active')\n        if (action === 'translate') {\n            initTranslate()\n        } else if (action === 'dictionary') {\n            initDictionary()\n        } else if (action === 'search') {\n            initSearch()\n        }\n        sendQuery(text) // 历史记录查询\n    }\n    hlEl.addEventListener('click', () => loadHistory(historyIndex - 1))\n    hrEl.addEventListener('click', () => loadHistory(historyIndex + 1))\n}\n\nfunction initTranslate() {\n    let l = languageList, langList = ''\n    for (let k in l) {\n        if (l.hasOwnProperty(k)) langList += `<u value=\"${k}\">${l[k].zhName}</u>`\n    }\n    dialog.contentHTML(`<div class=\"dmx_main dmx_main_trans\">\n    <div class=\"case${isPopup || isFullscreen ? ' big' : ''}\" id=\"translate_input\" contenteditable=\"true\"></div>\n    <div class=\"language_box fx\">\n        <div id=\"language_source\" class=\"language_button dmx-icon\"></div>\n        <div id=\"language_exchange\"><i class=\"dmx-icon dmx-icon-exchange\"></i></div>\n        <div id=\"language_target\" class=\"language_button dmx-icon\"></div>\n        <div id=\"translate_button\">翻 译</div>\n        <div id=\"translate_crop\"><i class=\"dmx-icon dmx-icon-crop\"></i></div>\n        <div id=\"language_dropdown\" class=\"fx\">${langList}</div>\n    </div>\n</div>\n<div id=\"case_list\" class=\"dmx_main fx\"></div>`)\n\n    // 绑定事件\n    let sourceEl = I('language_source')\n    let targetEl = I('language_target')\n    let exchangeEl = I('language_exchange')\n    let inputEl = I('translate_input')\n    let translateEl = I('translate_button')\n    let cropEl = I('translate_crop')\n    let dropdownEl = I('language_dropdown')\n    let dropdownU = dropdownEl.querySelectorAll('u')\n    let contentEl = I('dmx_dialog_content')\n    let tmpEl\n    let onButton = function () {\n        let el = this\n        if (tmpEl === el && dropdownEl.style.display === 'block') {\n            dropdownEl.style.display = 'none'\n            rmClass(el, 'active')\n        } else {\n            tmpEl = el\n            addClass(el, 'active')\n            let sourceVal = sourceEl.getAttribute('value')\n            let targetVal = targetEl.getAttribute('value')\n            let isSource = el === sourceEl\n            if (isSource) {\n                rmClass(targetEl, 'active')\n                rmClass(dropdownEl, 'dropdown_target')\n            } else {\n                rmClass(sourceEl, 'active')\n                addClass(dropdownEl, 'dropdown_target')\n            }\n            dropdownEl.style.display = 'block'\n            dropdownU.forEach(e => {\n                rmClass(e, 'active')\n                rmClass(e, 'disabled')\n                let val = e.getAttribute('value')\n                if (isSource) {\n                    if (sourceVal === val) {\n                        addClass(e, 'active')\n                    } else if (targetVal === val) {\n                        addClass(e, 'disabled')\n                    }\n                } else {\n                    if (targetVal === val) {\n                        addClass(e, 'active')\n                    } else if (sourceVal === val) {\n                        addClass(e, 'disabled')\n                    }\n                }\n            })\n        }\n    }\n    sourceEl.addEventListener('click', onButton)\n    targetEl.addEventListener('click', onButton)\n    exchangeEl.addEventListener('click', function () {\n        let sourceVal = sourceEl.getAttribute('value')\n        let sourceText = sourceEl.innerText\n        let targetVal = targetEl.getAttribute('value')\n        let targetText = targetEl.innerText\n        if (sourceVal === 'auto') return\n        sourceEl.setAttribute('value', targetVal)\n        sourceEl.innerText = targetText\n        targetEl.setAttribute('value', sourceVal)\n        targetEl.innerText = sourceText\n        setDialogConf('source', targetVal)\n        setDialogConf('target', sourceVal)\n        rmClass(sourceEl, 'active')\n        rmClass(targetEl, 'active')\n        dropdownEl.style.display = 'none'\n    })\n    translateEl.addEventListener('click', function () {\n        rmClass(sourceEl, 'active')\n        rmClass(targetEl, 'active')\n        dropdownEl.style.display = 'none'\n        let text = inputEl.innerText.trim()\n        sendQuery(text) // 翻译按钮查询\n    })\n    cropEl.addEventListener('click', function () {\n        sendBgMessage({action: 'onCropImg'}).then(_ => {\n            location.href.indexOf(root + 'html/popup.html') === 0 && window.close()\n        })\n    })\n    dropdownU.forEach(e => {\n        e.addEventListener('click', function () {\n            let v = this.getAttribute('value')\n            let s = this.innerText\n            let isSource = !hasClass(dropdownEl, 'dropdown_target')\n            let el = isSource ? sourceEl : targetEl\n            rmClass(el, 'active')\n            el.setAttribute('value', v)\n            el.innerText = s\n            dropdownEl.style.display = 'none'\n            contentEl.scrollTop = 0\n            if (isSource) {\n                (v === 'auto' ? addClass : rmClass)(exchangeEl, 'disabled')\n                setDialogConf('source', v)\n            } else {\n                setDialogConf('target', v)\n            }\n        })\n    })\n\n    // 粘贴事件\n    inputEl.addEventListener('paste', function (e) {\n        e.stopPropagation()\n        e.preventDefault()\n        let d = e.clipboardData || window.clipboardData\n        if (d && d.items.length > 0) {\n            let f = d.items[0].getAsFile()\n            if (f && f.type.indexOf('image') === 0) {\n                // 如果粘贴内容是图片，进行图片文字识别\n                let fr = new FileReader()\n                fr.readAsDataURL(f)\n                fr.onload = function (e) {\n                    let base64 = e.target.result\n                    sendBgMessage({action: 'img2text', base64})\n                }\n            } else {\n                // 如果是文本，则清理一下\n                this.innerText = d.getData('Text')\n            }\n        }\n    })\n    inputEl.addEventListener('blur', function () {\n        textTmp = this.innerText\n    })\n    inputEl.addEventListener('keyup', function () {\n        if (isPopup && setting.autoConfirm) _setTimeout('translate', () => translateEl.click(), 2000) // 定时自动开始翻译\n    })\n    isPopup && focusLast(inputEl) // 光标移到结尾\n\n    // 隐藏原文框，减少占用空间\n    if (setting.hideOriginal) {\n        if (!isPopup && !isFullscreen) { // 排除弹窗\n            // translateEl.style.display = 'none'\n            inputEl.style.display = 'none'\n            E('.dmx_main_trans').style.paddingTop = '0'\n        }\n    }\n\n    // 隐藏截图框(在独立窗口的时候)\n    if (isFullscreen) cropEl.style.display = 'none'\n\n    // 初始值\n    let source = dialogConf.source\n    let target = dialogConf.target\n    if (source === 'auto') addClass(exchangeEl, 'disabled')\n    sourceEl.setAttribute('value', source)\n    sourceEl.innerText = l[source].zhName\n    targetEl.setAttribute('value', target)\n    targetEl.innerText = l[target].zhName\n}\n\nfunction initDictionary() {\n    dialog.contentHTML(`<div id=\"dmx_head\">\n    <div class=\"case search_box\">\n        <input id=\"dictionary_input\" type=\"text\" maxlength=\"100\" autocomplete=\"off\">\n        <div id=\"search_remove\"><i class=\"dmx-icon dmx-icon-error\"></i></div>\n        <div id=\"search_but\"><i class=\"dmx-icon dmx-icon-search\"></i></div>\n    </div>\n</div>\n<div id=\"case_list\" class=\"dmx_main dmx_content fx\"></div>`)\n\n    let inpEl = I('dictionary_input')\n    let rmEl = I('search_remove')\n    let butEl = I('search_but')\n    rmEl.onclick = function () {\n        inpEl.value = ''\n        inpEl.focus()\n    }\n    butEl.onclick = function () {\n        let text = inpEl.value.trim()\n        sendQuery(text) // 词典按钮查询\n    }\n    inpEl.addEventListener('change', function () {\n        textTmp = this.value\n    })\n    inpEl.addEventListener('keyup', function (e) {\n        e.key === 'Enter' && butEl.click()\n        if (isPopup) _setTimeout('dictionary', () => butEl.click(), 1000) // 定时自动开始查词\n    })\n    setTimeout(() => inpEl.focus(), 100)\n}\n\nfunction initSearch() {\n    dialog.contentHTML(`<div id=\"dmx_head\">\n    <div class=\"case search_box\">\n        <input id=\"search_input\" type=\"text\" maxlength=\"100\" autocomplete=\"off\">\n        <div id=\"search_remove\"><i class=\"dmx-icon dmx-icon-error\"></i></div>\n        <div id=\"search_but\"><i class=\"dmx-icon dmx-icon-search\"></i></div>\n    </div>\n</div>\n<div id=\"case_list\" class=\"dmx_main dmx_content dmx_main_search fx\"></div>`)\n\n    let inpEl = I('search_input')\n    let rmEl = I('search_remove')\n    let butEl = I('search_but')\n    rmEl.onclick = function () {\n        inpEl.value = ''\n        inpEl.focus()\n    }\n    butEl.onclick = function () {\n        let el = I('case_list').querySelector('[data-search]')\n        if (el) el.click()\n    }\n    inpEl.addEventListener('change', function () {\n        textTmp = this.value\n    })\n    inpEl.addEventListener('keyup', function (e) {\n        e.key === 'Enter' && butEl.click()\n    })\n    setTimeout(() => inpEl.focus(), 100)\n\n    // 创建按钮\n    let s = ''\n    let sList = setting.searchList\n    let cList = getSearchList(searchText)\n    for (let name of sList) {\n        if (cList[name]) s += `<div class=\"dmx_button\" data-search=\"${name}\">${name}</div>`\n    }\n    I('case_list').innerHTML = s\n\n    // 绑定点击事件\n    onD(A('[data-search]'), 'click', function () {\n        let name = this.dataset.search\n        let url = cList[name]\n        if (!url) return\n        let text = I('search_input').value.trim()\n        if (text) {\n            open(url.format(decodeURIComponent(text)))\n        } else {\n            open((new URL(url)).origin)\n        }\n    })\n}\n\nfunction initSetting() {\n    dialog.contentHTML(`<iframe id=\"dmx_iframe\" src=\"${root + 'html/setting.html'}\" importance=\"high\"></iframe>`)\n}\n\nfunction initMore() {\n    dialog.contentHTML(`<iframe id=\"dmx_iframe\" src=\"${root + 'html/more.html?isSome=' + isSome}\" importance=\"high\"></iframe>`)\n}\n\nfunction initDictionaryCSS() {\n    let styleEl = E('style')\n    conf.dictionaryCSS.forEach(name => {\n        if (!setting.dictionaryList.includes(name) || !dictionaryCSS[name] || E(`style[data-name=\"${name}\"]`)) return\n        let s = `<style data-name=\"${name}\">${dictionaryCSS[name]}</style>`\n        styleEl.insertAdjacentHTML('afterend', s)\n    })\n}\n\nfunction initCrop() {\n    let startX = 0, startY = 0\n    let bgEl = I('dmx_crop_bg')\n    let fgEl = I('dmx_crop_fg')\n    bgEl.style.display = 'block'\n    let funDown = (e) => {\n        startX = e.clientX\n        startY = e.clientY\n        fgEl.style.display = 'block'\n        fgEl.style.left = startX + 'px'\n        fgEl.style.top = startY + 'px'\n        document.addEventListener('mousemove', funMove)\n        document.addEventListener('mouseup', funUp)\n    }\n    let funMove = (e) => {\n        let w = e.clientX - startX\n        let h = e.clientY - startY\n        if (w > 0) {\n            fgEl.style.width = w + 'px'\n        } else {\n            fgEl.style.left = e.clientX + 'px'\n            fgEl.style.width = -w + 'px'\n        }\n        if (h > 0) {\n            fgEl.style.height = h + 'px'\n        } else {\n            fgEl.style.top = e.clientY + 'px'\n            fgEl.style.height = -h + 'px'\n        }\n    }\n    let funUp = (e) => {\n        bgEl.removeAttribute('style')\n        fgEl.removeAttribute('style')\n        bgEl.removeEventListener('mousedown', funDown)\n        document.removeEventListener('mousemove', funMove)\n        document.removeEventListener('mouseup', funUp)\n        let width = e.clientX - startX\n        let height = e.clientY - startY\n        if (width < 0) {\n            width = -width\n            startX = e.clientX\n        }\n        if (height < 0) {\n            height = -height\n            startY = e.clientY\n        }\n        let innerHeight = window.innerHeight || document.documentElement.offsetHeight\n        if (width > 15 && height > 15) {\n            sendBgMessage({action: 'onCapture', startX, startY, width, height, innerHeight})\n            dmxAlert('截图文字识别中...', 'success')\n        } else {\n            dmxAlert('截图太小，取消识别', 'error')\n        }\n    }\n    bgEl.addEventListener('mousedown', funDown)\n}\n\nfunction loadingTranslate() {\n    let el = I('case_list')\n    let cList = conf.translateList\n    let sList = setting.translateList\n    if (sList.length < 1) {\n        el.innerHTML = `<div class=\"case case_content\"><i class=\"dmx-icon dmx-icon-info\"></i> 您未启用任何翻译模块</div>`\n        return\n    }\n\n    let s = ''\n    sList.forEach(name => {\n        s += `<div class=\"case\" id=\"${name}_translate_case\">\n    <div class=\"case_top fx\">\n        <div class=\"case_left case_language\"></div>\n        <div class=\"case_right\">\n            <a class=\"case_link\"><i class=\"dmx-icon dmx-icon-${name}\"></i>${cList[name]}</a>\n        </div>\n        <div class=\"case_right case_copy\" title=\"复制\"><i class=\"dmx-icon dmx-icon-copy\"></i></div>\n        <div class=\"case_right case_bilingual dmx-icon\">双语</div>\n    </div>\n    <div class=\"case_content\"><div class=\"dmx_loading\"></div></div>\n</div>`\n    })\n    el.innerHTML = s\n\n    // 绑定事件\n    sList.forEach(name => {\n        let caseEl = I(`${name}_translate_case`)\n        let copyEl = caseEl.querySelector('.case_copy')\n        let bilingualEl = caseEl.querySelector('.case_bilingual')\n        let contentEl = caseEl.querySelector('.case_content')\n        copyEl.addEventListener('click', () => {\n            let text = contentEl.innerText.replace(/\\n{2}/g, '\\n').trim()\n            execCopy(text)\n            dmxAlert('复制成功', 'success')\n        })\n        bilingualEl.addEventListener('click', () => {\n            if (hasClass(bilingualEl, 'active')) {\n                resultTranslate(name, false)\n                rmClass(bilingualEl, 'active')\n            } else {\n                resultTranslate(name, true)\n                addClass(bilingualEl, 'active')\n            }\n        })\n    })\n}\n\nfunction loadingDictionary() {\n    let el = I('case_list')\n    let cList = conf.dictionaryList\n    let sList = setting.dictionaryList\n    if (sList.length < 1) {\n        el.innerHTML = `<div class=\"case case_content\"><i class=\"dmx-icon dmx-icon-info\"></i> 您未启用任何词典模块</div>`\n        return\n    }\n\n    let s = ''\n    sList.forEach(name => {\n        s += `<div class=\"case\" id=\"${name}_dictionary_case\">\n        <div class=\"case_top fx\">\n            <div class=\"case_right\">\n                <a class=\"case_link\"><i class=\"dmx-icon dmx-icon-${name}\"></i>${cList[name]}</a>\n            </div>\n            <div class=\"case_left case_pronunciation\"></div>\n        </div>\n        <div class=\"case_content\"><div class=\"dmx_loading\"></div></div>\n    </div>`\n    })\n    el.innerHTML = s\n}\n\nfunction resultTranslate(name, isBilingual) {\n    let el = I(`${name}_translate_case`)\n    if (!el) return\n    let {srcLan, tarLan, lanTTS, data, extra} = msgList[name] || {}\n\n    // 显示发音图标\n    if (srcLan && tarLan) {\n        let sourceStr = soundIconHTML(srcLan, lanTTS, 'source')\n        let targetStr = soundIconHTML(tarLan, lanTTS, 'target')\n        el.querySelector('.case_language').innerHTML = `${sourceStr} » ${targetStr}`\n\n        let sourceEl = el.querySelector('[data-type=source]')\n        let targetEl = el.querySelector('[data-type=target]')\n        sourceEl && sourceEl.addEventListener('click', function () {\n            activeRipple(this)\n            sendPlayTTS(name, 'source', srcLan, dQuery.text) // 播放原音\n        })\n        targetEl && targetEl.addEventListener('click', function () {\n            activeRipple(this)\n            let s = ''\n            data && data.forEach(v => {\n                s += v.tarText + '\\n'\n            })\n            s && sendPlayTTS(name, 'target', tarLan, s) // 播放译音\n        })\n    }\n\n    // 显示翻译结果\n    let s = ''\n    data && data.forEach(v => {\n        if (isBilingual) {\n            s += `<p class=\"source_text\">${v.srcText}</p><p>${v.tarText}</p>`\n        } else {\n            s += `<p>${v.tarText}</p>`\n        }\n    })\n    if (extra) s += extra // 重点词汇 && 单词含义\n    if (!s) s = '网络错误，请稍后再试'\n    el.querySelector('.case_content').innerHTML = s\n\n    // 绑定点击搜索\n    resultBindEvent(el, 'translate', name)\n}\n\nfunction resultDictionary(m) {\n    let {name, result, error} = m\n    let el = I(`${name}_dictionary_case`)\n    if (!el) return\n    let cEl = el.querySelector('.case_content')\n    if (error) {\n        cEl.innerHTML = '网络错误，请稍后再试'\n        return\n    }\n\n    let {html, phonetic, sound} = result || {}\n\n    // 音标\n    let pron = ''\n    if (phonetic) {\n        let {uk, us} = phonetic\n        if (uk && us) {\n            pron += `[${uk} $ ${us}]`\n        } else if (uk) {\n            pron += `[${uk}]`\n        } else if (us) {\n            pron += `[$ ${us}]`\n        }\n    }\n\n    // 发音\n    sound && sound.forEach(v => {\n        let {isWoman, type, url, title} = v\n        let className = isWoman ? 'dmx_pink' : ''\n        if (!title) title = type === 'uk' ? '英音' : type === 'us' ? '美音' : ''\n        pron += ` <i class=\"dmx-icon dmx_ripple ${className}\" data-type=\"${type}\" data-src-mp3=\"${url}\" title=\"${title}\"></i>`\n    })\n\n    if (!html) html = 'Sorry! 没有查询到结果。'\n    el.querySelector('.case_content').innerHTML = html\n    el.querySelector('.case_pronunciation').innerHTML = pron\n\n    resultBindEvent(el, 'dictionary', m.name)\n}\n\nfunction resultBindEvent(el, nav, name) {\n    // 绑定播放音频\n    el.querySelectorAll('[data-src-mp3]').forEach(e => {\n        let obj = {uk: '&#xe69f;', us: '&#xe674;', en: '&#xe6a8;', other: '&#xe67a;'}\n        let type = e.getAttribute('data-type')\n        if (!obj[type]) {\n            type = 'other'\n            e.setAttribute('data-type', type)\n        }\n        e.innerHTML = obj[type] // 喇叭字体\n        e.addEventListener('click', function () {\n            activeRipple(this)\n            let type = this.getAttribute('data-type')\n            let url = this.getAttribute('data-src-mp3')\n            sendPlaySound(nav, name, type, url)\n        })\n    })\n\n    // 绑定点击搜索\n    el.querySelectorAll('[data-search=true]').forEach(e => {\n        e.addEventListener('click', function () {\n            let text = this.innerText && this.innerText.trim()\n            sendQuery(text) // 结果点击查询\n        })\n    })\n}\n\nfunction resultLink(m) {\n    let el = I(`${m.name}_${m.type}_case`)\n    if (!el) return\n    let sEl = el.querySelector(`.case_link`)\n    if (sEl) {\n        sEl.setAttribute('href', m.link)\n        sEl.setAttribute('target', '_blank')\n        sEl.setAttribute('referrerPolicy', 'no-referrer')\n    }\n}\n\nfunction resultSound(m) {\n    let {nav, name, type, status, error} = m\n    // if (error) dmxAlert(error, 'error') // 播放声音出错提示\n    let el = I(`${name}_${nav}_case`)\n    if (!el) return\n    if (status === 'start') {\n        let sEl = el.querySelector(`[data-type=${type}]`)\n        if (sEl) addClass(sEl, 'active')\n    } else {\n        let dEl = el.querySelectorAll(`[data-type=${type}]`)\n        if (dEl) rmClassD(dEl, 'active')\n    }\n    addClass(I('dmx_voice'), 'dmx_show')\n}\n\nfunction activeRipple(el) {\n    rmClassD(A('.dmx_ripple'), 'active')\n    addClass(el, 'active')\n}\n\nfunction soundIconHTML(lan, lanArr, type) {\n    let title = languageList[lan] ? languageList[lan].zhName : ''\n    let arr = {\n        'zh': '&#xe675;',\n        'en': '&#xe6a8;',\n        'jp': '&#xe6a0;',\n        'th': '&#xe69c;',\n        'spa': '&#xe6a5;',\n        'ara': '&#xe6a7;',\n        'fra': '&#xe676;',\n        'kor': '&#xe6a6;',\n        'ru': '&#xe69d;',\n        'de': '&#xe677;',\n        'pt': '&#xe69e;',\n        'it': '&#xe6a4;',\n        'el': '&#xe6a1;',\n        'nl': '&#xe6a3;',\n        'pl': '&#xe6a9;'\n    }\n    let iconStr = arr[lan] || '&#xe67a;'\n    let s = title\n    if (!lanArr || inArray(lan, lanArr)) {\n        s += ` <i class=\"dmx-icon dmx_ripple\" data-type=\"${type}\" title=\"${title}发音\">${iconStr}</i>`\n    }\n    return s\n}\n\n// 发送到后台缓存起来\nfunction sendBgCache(text) {\n    if (window.textRepeat !== text) {\n        window.textRepeat = text\n        sendBgMessage({action: 'textTmp', text})\n    }\n}\n\n// 自动切换翻译或词典\nfunction autoChangeAction(text) {\n    if (!setting.autoChange) return\n    let arr = text.match(/\\s+/g)\n    let isWord = !arr || arr.length < 3 // 是否为单词活词组\n\n    A('#dmx_navigate > u').forEach(el => rmClass(el, 'active')) // 去掉选中\n    let onEl = E(`#dmx_navigate > u[action=\"${isWord ? 'dictionary' : 'translate'}\"]`)\n    if (onEl) {\n        // 选中翻译或词典\n        addClass(onEl, 'active')\n        onEl.click()\n    }\n}\n\nfunction initQuery(text, clientX, clientY) {\n    // Unicode property escapes 正则表达式:\n    // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions/Unicode_Property_Escapes\n    // https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt\n    // https://tc39.es/ecma262/#table-nonbinary-unicode-properties\n    // https://keqingrong.github.io/blog/2020-01-29-regexp-unicode-property-escapes\n    if (setting.excludeChinese && /\\p{Script=Han}/u.test(text)) return // 排除中文\n    if (setting.excludeSymbol && /^[\\p{S}\\p{P}^$.*+\\-?=!:|\\\\/！？。；－＿～﹏，：、·;…,\"“”﹃﹄「」﹁﹂『』﹃﹄（）［］〔〕【】《》〈〉()\\[\\]{}<>\\s]+$/u.test(text)) return // 排除纯符号\n    if (setting.excludeNumber && /^\\d+$/u.test(text)) return // 排除纯数字\n\n    sendBgCache(text)\n    if (!text) {\n        iconBut.style.display = 'none'\n        return\n    }\n    debug('text:', text)\n\n    // 自动复制功能\n    if (setting.autoCopy === 'on') {\n        sendBgMessage({action: 'copy', text})\n        dmxAlert('复制成功', 'success')\n    }\n\n    if (setting.scribble === 'off') return\n    if (setting.scribble === 'direct') {\n        autoChangeAction(text) // 自动切换翻译或词典\n        sendQuery(text) // 划词查询\n        showDialog(clientX + 30, clientY - 60)\n    } else if (setting.scribble === 'clickIcon') {\n        iconText = text\n        let x = clientX + 10\n        let y = clientY - 45\n        x = x + 42 < document.documentElement.clientWidth ? x : clientX - 42\n        y = y > 10 ? y : clientY + 10\n        iconBut.style.transform = `translate(${x}px, ${y}px)`\n        iconBut.style.display = 'flex'\n    }\n}\n\nfunction sendQuery(text) {\n    if (!text) {\n        if (isClipboardRead) text = execPaste() // 自动读取粘贴板内容\n        if (!text && isPopup && setting.autoPaste === 'on') text = execPaste()\n        if (!text) text = textTmp\n        if (!text) return\n    }\n    let el = I('dmx_navigate')\n    let action = el.querySelector('.active') && el.querySelector('.active').getAttribute('action')\n    if (!action) {\n        action = dialogConf.action\n        let actionEl = el.querySelector(`u[action=\"${action}\"]`)\n        if (actionEl) actionEl.click()\n    }\n    if (!checkChange(action, text)) return\n\n    let message = null\n    if (setting.cutHumpName === 'on' && action !== 'search') text = cutHumpName(text)\n    if (action === 'translate') {\n        let inputEl = I(`translate_input`)\n        inputEl.innerText = text\n        isPopup && focusLast(inputEl)\n        loadingTranslate()\n        message = {action: action, text: text, srcLan: dialogConf.source, tarLan: dialogConf.target}\n    } else if (action === 'dictionary') {\n        I(`dictionary_input`).value = text\n        loadingDictionary()\n        message = {action: action, text: text}\n    } else if (action === 'search') {\n        I(`search_input`).value = text\n    }\n    showSearchSide(text)\n    if (message) {\n        message = Object.assign({formTitle: document.title, formUrl: location.href}, message)\n        sendBgMessage(message)\n    }\n}\n\n// 切分驼峰词组\nfunction cutHumpName(s) {\n    let isCapital = function (s) {\n        let c = s.charAt(0)\n        return c >= 'A' && c <= 'Z'\n    }\n    return s.replace(/\\w+[A-Z]\\w?/g, (...args) => {\n        let str = args[0]\n        let newStr = ''\n        for (let i = 0; i < str.length; i++) {\n            let l = str[i]\n            if (l === '_') l = ' '\n            else if (isCapital(l) && (i - 1 > 0 && str[i - 1] !== '_' && !isCapital(str[i - 1]))) newStr += ' '\n            newStr += l\n        }\n        return newStr\n    })\n}\n\nfunction mouseWords(e) {\n    let x = e.clientX\n    let y = e.clientY\n    if (!x || !y) return\n\n    let textNode, offset, arr\n    if (document.caretPositionFromPoint) {\n        let p = document.caretPositionFromPoint(x, y)\n        if (!p) return\n        textNode = p.offsetNode\n        offset = p.offset\n    } else if (document.caretRangeFromPoint) {\n        let p = document.caretRangeFromPoint(x, y)\n        if (!p) return\n        textNode = p.startContainer\n        offset = p.startOffset\n    }\n    if (!textNode || textNode.nodeType !== 3) return\n\n    let str = textNode.data\n    let before = (arr = str.slice(0, offset).match(/[a-z’']+$/i)) ? arr[0] : ''\n    let after = (arr = str.slice(offset).match(/^[a-z’']+/i)) ? arr[0] : ''\n    if (before.length === 0 && after.length === 0) return\n\n    let range = document.createRange()\n    range.setStart(textNode, offset - before.length)\n    range.setEnd(textNode, offset + after.length)\n    let bcr = range.getBoundingClientRect()\n    if (x >= bcr.left && x <= bcr.right && y >= bcr.top && y <= bcr.bottom) {\n        let se = window.getSelection()\n        se.removeAllRanges()\n        se.addRange(range)\n\n        let text = before + after\n        if (text && window.textRepeat !== text) {\n            sendBgCache(text)\n            sendQuery(text) // 鼠标停留获取单词\n            showDialog()\n        }\n    }\n    range.detach()\n}\n\nfunction showSearchSide(text) {\n    let arr = setting.searchSide\n    let s = ''\n    if (text && isArray(arr) && arr.length > 0) {\n        let sList = getSearchList(searchText)\n        for (let name of arr) {\n            let url = sList[name]\n            if (url) s += `<a href=\"${url.format(decodeURIComponent(text))}\" title=\"${name}\" target=\"_blank\">${name[0]}</a>`\n        }\n    }\n    I('dmx_dialog_left').innerHTML = s\n}\n\nfunction showDialog(left, top) {\n    let options = null\n    let position = setting.position\n    if (isPopup) {\n        // 跳过\n    } else if (position === 'follow') {\n        options = {left, top}\n    } else if (position === 'right') {\n        dialog.el.removeAttribute('style')\n        dialog.el.style.width = dialogConf.width + 'px'\n        dialog.el.className = 'dmx_keep_right'\n    }\n    dialog.show(options)\n}\n\nfunction checkChange(action, text) {\n    let d = dQuery\n    let source = dialogConf.source\n    let target = dialogConf.target\n    if (d.action === action && d.text === text && d.source === source && d.target === target) return false\n    dQuery = {action, text, source, target}\n    addHistory(dQuery)\n    return true\n}\n\nfunction addHistory(dQuery) {\n    if (disHistory) return disHistory = false\n    if (!['translate', 'dictionary', 'search'].includes(dQuery.action)) return\n    if (historyIndex < history.length - 1) {\n        history.splice(historyIndex + 1, history.length)\n    } else if (history.length >= 1000) {\n        history.shift() // 最多只保留 1000 条\n    }\n    history.push(dQuery)\n    historyIndex = history.length - 1\n    debug('history:', history, historyIndex)\n    if (history.length > 1) {\n        let hEl = I('dmx_history')\n        rmClass(hEl.querySelector('.dmx-icon-left'), 'disabled')\n        addClass(hEl.querySelector('.dmx-icon-right'), 'disabled')\n    }\n}\n\nfunction focusLast(el) {\n    setTimeout(() => {\n        el.focus()\n        let range = window.getSelection()\n        range.selectAllChildren(el)\n        range.collapseToEnd() // 光标移到结尾\n    }, 200)\n}\n\nfunction I(id) {\n    return shadow.getElementById(id)\n}\n\nfunction E(s) {\n    return shadow.querySelector(s)\n}\n\nfunction A(s) {\n    return shadow.querySelectorAll(s)\n}\n\nfunction saveDialogConf() {\n    storageSyncSet({'dialogConf': dialogConf})\n}\n\nfunction setDialogConf(name, value) {\n    dialogConf[name] = value\n    saveDialogConf()\n}\n\nfunction allowUserSelect() {\n    dmxAlert('解除页面限制完成', 'success')\n    let styleEl = document.getElementById('_dream_style_all')\n    if (!styleEl) {\n        let sEl = document.createElement('style')\n        sEl.id = '_dream_style_all'\n        sEl.textContent = `* {-webkit-user-select:text!important;-moz-user-select:text!important;user-select:text!important;pointer-events:auto!important;}`\n        document.head.appendChild(sEl)\n    }\n\n    let onAllow = function (el, event) {\n        if (el.getAttribute && el.getAttribute(event)) el.setAttribute(event, () => true)\n    }\n    onAllow(document, 'oncontextmenu')\n    onAllow(document, 'onselectstart')\n\n    // 清除再添加，排除重复事件\n    let onClean = function (e) {\n        e.stopPropagation()\n        let el = e.target\n        while (el) {\n            onAllow(el, 'on' + e.type)\n            el = el.parentNode\n        }\n    }\n    document.removeEventListener('contextmenu', onClean, true)\n    document.removeEventListener('selectstart', onClean, true)\n    document.addEventListener('contextmenu', onClean, true)\n    document.addEventListener('selectstart', onClean, true)\n}\n\nfunction sendPlayTTS(name, type, lang, text) {\n    sendBgMessage({action: 'translateTTS', name, type, lang, text})\n}\n\nfunction sendPlaySound(nav, name, type, url) {\n    sendBgMessage({action: 'playSound', nav, name, type, url})\n}\n\nfunction sendBgMessage(message) {\n    return new Promise((resolve, reject) => {\n        sendMessage(message).then(_ => {\n            resolve()\n        }).catch(err => {\n            // 减少错误提示框\n            if (getTimestamp() > (window.dmxUpdateDate || 0)) {\n                window.dmxUpdateDate = getTimestamp() + 5\n                dmxAlert('梦想翻译已升级，请刷新页面激活。', 'error')\n            }\n            debug('sendBgMessage error:', err)\n            // reject(err)\n            resolve()\n        })\n    })\n}\n\nfunction dmxAlert(message, type, timeout) {\n    type = type || 'info'\n    timeout = timeout || 2500\n    let el = I('dmx_alert')\n    if (!el) {\n        el = document.createElement('div')\n        el.id = 'dmx_alert'\n        shadow.appendChild(el)\n    }\n    let icon = {\n        info: '<i class=\"dmx-icon dmx-icon-info\"></i>',\n        error: '<i class=\"dmx-icon dmx-icon-close\"></i>',\n        success: '<i class=\"dmx-icon dmx-icon-success\"></i>',\n    }\n    let m = document.createElement('div')\n    m.className = `dxm_alert_${type}`\n    m.innerHTML = (icon[type] || '') + message\n    el.appendChild(m)\n    setTimeout(() => {\n        addClass(m, 'an_top')\n    }, 10)\n    setTimeout(() => {\n        addClass(m, 'an_delete')\n        setTimeout(() => {\n            el.removeChild(m)\n        }, 300)\n    }, timeout)\n}\n\nfunction dmxDialog(options) {\n    if (window._MxDialog) return window._MxDialog\n    let o = Object.assign({\n        width: 500,\n        height: 300,\n        minWidth: 200,\n        minHeight: 200,\n        show: false,\n        autoHide: true,\n        isMove: true,\n        isResize: true,\n        onResize: null,\n        cssText: '',\n        contentHTML: '',\n    }, options || {})\n\n    let d = document.createElement('div')\n    d.setAttribute('mx-name', 'dream-translation')\n    document.documentElement.appendChild(d)\n    shadow = d.attachShadow({mode: 'closed'})\n    shadow.innerHTML = `<link rel=\"stylesheet\" href=\"${root + 'css/content.css'}\">\n<style>${o.cssText}</style>\n<div id=\"dmx_dialog\">\n    <div id=\"dmx_dialog_title\">\n        <div class=\"dmx_left\">\n            <div id=\"dmx_close\"><i class=\"dmx-icon dmx-icon-close\"></i></div>\n            <div id=\"dmx_pin\" class=\"dmx-icon\"></div>\n            <div id=\"dmx_fullscreen\" class=\"dmx-icon\"></div>\n            <div id=\"dmx_history\">\n                <i class=\"dmx-icon disabled dmx-icon-left\"></i>\n                <i class=\"dmx-icon disabled dmx-icon-right\"></i>\n            </div>\n            <div id=\"dmx_navigate\">\n                <u class=\"dmx-icon\" action=\"translate\">翻译</u>\n                <u class=\"dmx-icon\" action=\"dictionary\">词典</u>\n                <u class=\"dmx-icon\" action=\"search\">搜索</u>\n            </div>\n        </div>\n        <div class=\"dmx_right\">\n            <div id=\"dmx_setting\"><i class=\"dmx-icon dmx-icon-setting\"></i></div>\n            <div id=\"dmx_more\"><i class=\"dmx-icon dmx-icon-more\"></i></div>\n            <div id=\"dmx_voice\" class=\"dmx_hide\"><i class=\"dmx-icon dmx-icon-voice\"></i></div>\n            <div id=\"dmx_heart\" class=\"dmx_hide\"><i class=\"dmx-icon dmx-icon-heart\"></i></div>\n        </div>\n    </div>\n    <div id=\"dmx_dialog_content\">${o.contentHTML}</div>\n    <div id=\"dmx_dialog_resize_n\"></div>\n    <div id=\"dmx_dialog_resize_e\"></div>\n    <div id=\"dmx_dialog_resize_s\"></div>\n    <div id=\"dmx_dialog_resize_w\"></div>\n    <div id=\"dmx_dialog_resize_nw\"></div>\n    <div id=\"dmx_dialog_resize_ne\"></div>\n    <div id=\"dmx_dialog_resize_sw\"></div>\n    <div id=\"dmx_dialog_resize_se\"></div>\n    <div id=\"dmx_dialog_left\"></div>\n</div>\n<div id=\"dmx_mouse_icon\"></div>\n<div id=\"dmx_crop_bg\"></div>\n<div id=\"dmx_crop_fg\"></div>`\n\n    let el = I('dmx_dialog')\n    let clientX, clientY, elX, elY, elW, elH, docW, docH, mid\n    let _m = function (e) {\n        let left = e.clientX - (clientX - elX)\n        let top = e.clientY - (clientY - elY)\n        let maxLeft = docW - elW\n        let maxTop = docH - elH\n        left = Math.max(0, Math.min(left, maxLeft))\n        top = Math.max(0, Math.min(top, maxTop))\n        el.style.left = left + 'px'\n        el.style.top = top + 'px'\n    }\n    let _n = function (e) {\n        let top = e.clientY - (clientY - elY)\n        let height = elY - top + elH\n        if (height > o.minHeight && top >= 0) {\n            el.style.top = top + 'px'\n            el.style.height = height + 'px'\n            typeof o.onResize === 'function' && o.onResize({height: height})\n        }\n    }\n    let _e = function (e) {\n        let left = e.clientX - (clientX - elX)\n        let width = left - elX + elW\n        if (width > o.minWidth && e.clientX < docW - (elW - (clientX - elX))) {\n            el.style.width = width + 'px'\n            typeof o.onResize === 'function' && o.onResize({width: width})\n        }\n    }\n    let _s = function (e) {\n        let top = e.clientY - (clientY - elY)\n        let height = top - elY + elH\n        if (e.clientY < docH - (elH - (clientY - elY)) && height > o.minHeight) {\n            el.style.height = height + 'px'\n            typeof o.onResize === 'function' && o.onResize({height: height})\n        }\n    }\n    let _w = function (e) {\n        let left = e.clientX - (clientX - elX)\n        let width = elW - (left - elX)\n        if (left >= 0 && width > o.minWidth) {\n            el.style.left = left + 'px'\n            el.style.width = width + 'px'\n            typeof o.onResize === 'function' && o.onResize({width: width})\n        }\n    }\n    let onMousedown = function (e) {\n        e.stopPropagation()\n        mid = this.id\n        clientX = e.clientX\n        clientY = e.clientY\n        let b = el.getBoundingClientRect()\n        elX = b.left || el.offsetLeft\n        elY = b.top || el.offsetTop\n        elW = b.width || el.offsetWidth\n        elH = b.height || el.offsetHeight\n        docW = document.documentElement.clientWidth\n        docH = document.documentElement.clientHeight\n        addClass(document.body, 'dmx_unselectable')\n        addClass(el, 'dmx_unselectable')\n    }\n    let onMouseup = function (e) {\n        // e.stopPropagation() // 和B站播放进度条冲突\n        mid = null\n        rmClass(document.body, 'dmx_unselectable')\n        rmClass(el, 'dmx_unselectable')\n    }\n    let onMousemove = function (e) {\n        if (mid === 'dmx_dialog_title') {\n            _m(e)\n        } else if (mid === 'dmx_dialog_resize_n') {\n            _n(e)\n        } else if (mid === 'dmx_dialog_resize_e') {\n            _e(e)\n        } else if (mid === 'dmx_dialog_resize_s') {\n            _s(e)\n        } else if (mid === 'dmx_dialog_resize_w') {\n            _w(e)\n        } else if (mid === 'dmx_dialog_resize_nw') {\n            _n(e)\n            _w(e)\n        } else if (mid === 'dmx_dialog_resize_ne') {\n            _n(e)\n            _e(e)\n        } else if (mid === 'dmx_dialog_resize_sw') {\n            _s(e)\n            _w(e)\n        } else if (mid === 'dmx_dialog_resize_se') {\n            _s(e)\n            _e(e)\n        }\n    }\n    document.addEventListener('mousemove', onMousemove)\n    document.addEventListener('mouseup', onMouseup)\n    document.addEventListener('mouseleave', onMouseup) // 鼠标离开浏览器\n    el.addEventListener('mouseup', function (e) {\n        e.stopPropagation() // 解决点击面板闪耀问题\n        onMouseup()\n    })\n\n    let elArr = ['n', 'e', 's', 'w', 'nw', 'ne', 'sw', 'se']\n    let fsTmp = {} // 全屏设置临时缓存\n    let D = {}\n    D.el = el\n    D.shadow = shadow\n    D.destroy = function () {\n        document.removeEventListener('mousemove', onMousemove)\n        document.removeEventListener('mouseup', onMouseup)\n        document.removeEventListener('mouseup', D.hide)\n        el.remove()\n    }\n    D.show = function (o) {\n        setTimeout(() => {\n            el.style.display = 'block'\n            if (!o || typeof o.left !== 'number' || typeof o.top !== 'number') return\n            let d = document.documentElement\n            let b = el.getBoundingClientRect()\n            el.style.left = Math.max(0, Math.min(o.left, d.clientWidth - b.width)) + 'px'\n            el.style.top = Math.max(0, Math.min(o.top, d.clientHeight - b.height)) + 'px'\n        }, 80)\n    }\n    D.hide = function () {\n        el.style.display = 'none'\n    }\n    D.enableMove = function () {\n        let e = I('dmx_dialog_title')\n        e.style.cursor = 'move'\n        e.addEventListener('mousedown', onMousedown)\n    }\n    D.disableMove = function () {\n        let e = I('dmx_dialog_title')\n        e.style.cursor = 'auto'\n        e.removeEventListener('mousedown', onMousedown)\n    }\n    D.enableResize = function () {\n        elArr.forEach(v => {\n            let e = I(`dmx_dialog_resize_${v}`)\n            e.removeAttribute('style')\n            e.addEventListener('mousedown', onMousedown)\n        })\n    }\n    D.disableResize = function () {\n        elArr.forEach(v => {\n            let e = I(`dmx_dialog_resize_${v}`)\n            e.style.display = 'none'\n            e.removeEventListener('mousedown', onMousedown)\n        })\n    }\n    D.fullScreen = function () {\n        addClass(I('dmx_fullscreen'), 'active')\n        addClass(document.body, 'dmx_overflow_hidden')\n        fsTmp = {top: el.style.top, left: el.style.left, width: el.style.width, height: el.style.height}\n        el.style.top = '0'\n        el.style.left = '0'\n        el.style.width = document.documentElement.clientWidth + 'px'\n        el.style.height = document.documentElement.clientHeight + 'px'\n    }\n    D.fullScreenExit = function () {\n        rmClass(I('dmx_fullscreen'), 'active')\n        rmClass(document.body, 'dmx_overflow_hidden')\n        if (typeof fsTmp.top === 'string') el.style.top = fsTmp.top\n        if (typeof fsTmp.left === 'string') el.style.left = fsTmp.left\n        if (typeof fsTmp.width === 'string') el.style.width = fsTmp.width\n        if (typeof fsTmp.height === 'string') el.style.height = fsTmp.height\n    }\n    D.pin = function () {\n        addClass(I('dmx_pin'), 'active')\n        document.removeEventListener('mouseup', D.hide)\n    }\n    D.pinCancel = function () {\n        rmClass(I('dmx_pin'), 'active')\n        document.addEventListener('mouseup', D.hide) // 点击 body 隐藏 dialog\n    }\n    D.contentHTML = function (s) {\n        I('dmx_dialog_content').innerHTML = s\n    }\n    window._MxDialog = D\n\n    // 初始设置\n    if (o.width !== 'auto') el.style.width = Number(o.width) + 'px'\n    if (o.height !== 'auto') el.style.height = Number(o.height) + 'px'\n    o.show ? D.show() : D.hide()\n    o.autoHide ? D.pinCancel() : D.pin()\n    o.isMove ? D.enableMove() : D.disableMove()\n    o.isResize ? D.enableResize() : D.disableResize()\n\n    // 顶部按钮事件\n    I('dmx_close').onclick = function () {\n        D.hide()\n        rmClass(document.body, 'dmx_overflow_hidden')\n    }\n    I('dmx_pin').onclick = function () {\n        hasClass(this, 'active') ? D.pinCancel() : D.pin()\n    }\n    I('dmx_fullscreen').onclick = function () {\n        hasClass(this, 'active') ? D.fullScreenExit() : D.fullScreen()\n    }\n\n    // 阻止冒泡\n    shadow.querySelectorAll('.dmx-icon').forEach(v => {\n        v.addEventListener('mousedown', e => e.stopPropagation())\n    })\n\n    return D\n}\n"
  },
  {
    "path": "src/js/db.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction idb(dbName, version, onupgradeneeded) {\n    return new Promise((resolve, reject) => {\n        let req = window.indexedDB.open(dbName, version)\n        req.onupgradeneeded = onupgradeneeded // 首次创建或更高版本号时执行\n        req.onerror = (e) => reject(e)\n        req.onsuccess = () => {\n            let db = req.result\n            resolve({\n                db,\n                rStore(storeName) {\n                    return db.transaction([storeName], 'readonly').objectStore(storeName)\n                },\n                wStore(storeName) {\n                    return db.transaction([storeName], 'readwrite').objectStore(storeName)\n                },\n                read(storeName, id) {\n                    return new Promise((resolve, reject) => {\n                        let row = this.rStore(storeName).get(id)\n                        row.onsuccess = () => resolve(row.result)\n                        row.onerror = (e) => reject(e)\n                    })\n                },\n                readByIndex(storeName, indexName, key) {\n                    return new Promise((resolve, reject) => {\n                        let row = this.rStore(storeName).index(indexName).get(key)\n                        row.onsuccess = () => resolve(row.result)\n                        row.onerror = (e) => reject(e)\n                    })\n                },\n                create(storeName, data) {\n                    return new Promise((resolve, reject) => {\n                        let row = this.wStore(storeName).add(data)\n                        row.onsuccess = (e) => resolve(e)\n                        row.onerror = (e) => reject(e)\n                    })\n                },\n                update(storeName, id, data) {\n                    return new Promise((resolve, reject) => {\n                        let wStore = this.wStore(storeName)\n                        let row = wStore.get(id)\n                        row.onsuccess = () => {\n                            if (!row.result) return reject('result empty!')\n                            let newData = Object.assign(row.result, data) // 覆盖\n                            let r = wStore.put(newData)\n                            r.onsuccess = (e) => resolve(e)\n                            r.onerror = (e) => reject(e)\n                        }\n                        row.onerror = (e) => reject(e)\n                    })\n                },\n                delete(storeName, id) {\n                    return new Promise((resolve, reject) => {\n                        let r = this.wStore(storeName).delete(id)\n                        r.onsuccess = (e) => resolve(e)\n                        r.onerror = (e) => reject(e)\n                    })\n                },\n                clear(storeName) {\n                    return new Promise((resolve, reject) => {\n                        let r = this.wStore(storeName).clear()\n                        r.onsuccess = (e) => resolve(e)\n                        r.onerror = (e) => reject(e)\n                    })\n                },\n                count(storeName, indexName, query) {\n                    return new Promise((resolve, reject) => {\n                        let store = this.rStore(storeName)\n                        let r = indexName ? store.index(indexName).count(query) : store.count(query)\n                        r.onsuccess = () => resolve(r.result)\n                        r.onerror = (e) => reject(e)\n                    })\n                },\n                getAll(storeName, indexName, query, count) {\n                    return new Promise((resolve, reject) => {\n                        let store = this.rStore(storeName)\n                        let req = indexName ? store.index(indexName).getAll(query, count) : store.getAll(query, count)\n                        req.onsuccess = () => resolve(req.result)\n                        req.onerror = (e) => reject(e)\n                    })\n                },\n                find(storeName, option) {\n                    let {indexName, query, direction, offset, limit} = option || {}\n                    return new Promise((resolve, reject) => {\n                        let arr = []\n                        let store = this.rStore(storeName)\n                        let req = indexName ? store.index(indexName).openCursor(query, direction) : store.openCursor(query, direction)\n                        let isAdvance = false\n                        req.onsuccess = (e) => {\n                            // let row = e.target.result\n                            let row = req.result\n                            if (row) {\n                                // 偏移量\n                                if (offset && !isAdvance) {\n                                    row.advance(offset)\n                                    isAdvance = true\n                                    return\n                                }\n\n                                arr.push(row.value) // 返回值\n                                if (limit && arr.length >= limit) {\n                                    resolve(arr)\n                                } else {\n                                    row.continue()\n                                }\n                            } else {\n                                resolve(arr)\n                            }\n                        }\n                        req.onerror = (e) => reject(e)\n                    })\n                },\n            })\n        }\n    })\n}\n\nfunction rmIdb(dbName) {\n    return new Promise((resolve, reject) => {\n        let db = window.indexedDB.deleteDatabase(dbName)\n        db.onsuccess = (e) => resolve(e)\n        db.onerror = (e) => reject(e)\n        setTimeout(_ => reject('time out'), 2000)\n    })\n}\n\n// 创建存储对象: 收藏\nfunction initFavorite(e) {\n    let store, db = e.target.result\n\n    // sentence\n    store = db.createObjectStore('sentence', {keyPath: 'id', autoIncrement: true})\n    store.createIndex('id', 'id', {unique: true})\n    store.createIndex('cateId', 'cateId') // 分类ID\n    store.createIndex('sentence', 'sentence', {unique: true}) // 句子\n    store.createIndex('words', 'words') // 生词，一行一个\n    store.createIndex('remark', 'remark') // 备注\n    store.createIndex('records', 'records') // 练习次数\n    store.createIndex('days', 'days') // 练习天数\n    store.createIndex('url', 'url') // 音频 URL\n    store.createIndex('blob', 'blob') // 音频二进制文件\n    store.createIndex('practiceDate', 'practiceDate') // 最后练习时间\n    store.createIndex('createDate', 'createDate') // 创建时间\n\n    // cate\n    store = db.createObjectStore('cate', {keyPath: 'cateId', autoIncrement: true})\n    store.createIndex('cateId', 'cateId', {unique: true}) // 分类ID\n    store.createIndex('cateName', 'cateName', {unique: true}) // 分类名称\n    store.createIndex('updateDate', 'updateDate') // 更新时间\n    store.createIndex('createDate', 'createDate') // 创建时间\n\n    // cate 初始分类\n    setTimeout(() => {\n        let d = new Date().toJSON()\n        let row = {cateId: 0, cateName: '最新收藏', updateDate: d, createDate: d}\n        db.transaction(['cate'], 'readwrite').objectStore('cate').add(row)\n    }, 100)\n}\n\n// 创建存储对象: 历史\nfunction initHistory(e) {\n    let store, db = e.target.result\n\n    // history\n    store = db.createObjectStore('history', {keyPath: 'id', autoIncrement: true})\n    store.createIndex('id', 'id', {unique: true})\n    store.createIndex('content', 'content')\n    store.createIndex('formTitle', 'formTitle')\n    store.createIndex('formUrl', 'formUrl')\n    store.createIndex('createDate', 'createDate') // 创建时间\n}\n"
  },
  {
    "path": "src/js/dictionary/bing.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction bingDictionary() {\n    return {\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let phonetic = {} // 音标\n            let sound = [] // 发音\n            let el = r.querySelector('.lf_area')\n\n            // 查询单词\n            let wordEl = el.querySelector('#headword')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText.trim()}</div>`\n\n            // 音标\n            let prUK = el.querySelector('.hd_pr')\n            if (prUK) {\n                let ph = prUK.innerText ? prUK.innerText.replace(/^UK|[\\[\\]美英]/g, '').trim() : ''\n                if (ph) phonetic.uk = ph\n            }\n            let prUS = el.querySelector('.hd_prUS')\n            if (prUS) {\n                let ph = prUS.innerText ? prUS.innerText.replace(/^US|[\\[\\]美英]/g, '').trim() : ''\n                if (ph) phonetic.us = ph\n            }\n            if (phonetic.us && phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n\n            // 发音\n            let tfEl = el.querySelectorAll('.hd_tf')\n            if (tfEl && tfEl.length >= 2) {\n                let getSoundUrl = function (e) {\n                    let url = ''\n                    let aEl = e.querySelector('a')\n                    if (!aEl) return url\n                    let str = aEl.getAttribute('onclick')\n                    str && str.replace(/'(http[^']+)'/, r => url = r.replace(/'/g, ''))\n                    return url\n                }\n                let ukUrl = getSoundUrl(tfEl[1])\n                if (ukUrl) sound.push({type: 'uk', url: ukUrl})\n                let usUrl = getSoundUrl(tfEl[0])\n                if (usUrl) sound.push({type: 'us', url: usUrl})\n            }\n\n            // 释义\n            let liEl = el.querySelectorAll('.qdef > ul > li')\n            if (liEl && liEl.length > 0) {\n                s += `<div class=\"case_dd_parts\">`\n                liEl.forEach(e => {\n                    let bEl = e.querySelector('span.pos')\n                    let tEl = e.querySelector('span.b_regtxt')\n                    let bStr = bEl && bEl.innerText ? `<b>${bEl.innerText.trim()}</b>` : ''\n                    let part = tEl && tEl.innerText ? tEl.innerText.trim() : ''\n                    if (part) s += `<p>${bStr}${part}</p>`\n                })\n                s += `</div>`\n            } else {\n                let str = ''\n                el.querySelectorAll('div[class^=\"p1-\"]').forEach(e => {\n                    let tex = e.textContent && e.textContent.trim()\n                    if (tex) str += `<p>${tex}</p>`\n                })\n                if (str) s += `<div class=\"case_dd_parts\">${str}</div>`\n            }\n\n            // 单词形态\n            let shapeEl = el.querySelector('.hd_div1')\n            if (shapeEl) {\n                let shapeStr = ''\n                shapeEl.querySelectorAll('span,a').forEach(e => {\n                    if (e.tagName === 'SPAN') {\n                        shapeStr += `<b>${e.innerText}</b>`\n                    } else if (e.tagName === 'A') {\n                        shapeStr += `<u><a data-search=\"true\">${e.innerText}</a></u>`\n                    }\n                })\n                if (shapeStr) s += `<div class=\"case_dd_exchange\">${shapeStr}</div>`\n            }\n\n            // 单词图片\n            let imgEl = el.querySelectorAll('.img_area > .simg > a')\n            if (imgEl && imgEl.length > 0) {\n                let imgStr = ''\n                imgEl.forEach(e => {\n                    let url = e.getAttribute('href')\n                    let iEl = e.querySelector('img')\n                    if (url && iEl) {\n                        let src = iEl.getAttribute('src')\n                        imgStr += `<a href=\"https://cn.bing.com${url}\" target=\"_blank\" rel=\"noreferrer\"><img src=\"${src}\" rel=\"noreferrer\"></a>`\n                    }\n                })\n                if (imgStr) s += `<div class=\"case_dd_img\">${imgStr}</div>`\n            }\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                // if (q.length > 100) return reject('The text is too large!')\n                let url = `https://cn.bing.com/dict/search?q=${encodeURIComponent(q)}`\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('bing error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return `https://cn.bing.com/dict/search?q=${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/cambridge.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction cambridgeDictionary() {\n    return {\n        enUrl: 'https://dictionary.cambridge.org/dictionary/english/',\n        zHUrl: 'https://dictionary.cambridge.org/dictionary/english-chinese-simplified/',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let phonetic = {} // 音标\n            let sound = [] // 发音\n            let el = r.querySelector('.entry-body')\n\n            let posHeadEl = el.querySelector('.pos-header')\n            if (posHeadEl) {\n                // 查询单词\n                let wordEl = posHeadEl.querySelector('.di-title')\n                if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText}</div>`\n\n                posHeadEl.querySelectorAll('.dpron-i').forEach(e => {\n                    let pEl = e.querySelector('.ipa')\n                    let mEl = e.querySelector('source[type=\"audio/mpeg\"]')\n                    let ph = pEl && pEl.innerText && pEl.innerText.trim()\n                    let src = mEl && mEl.getAttribute('src') || ''\n                    let pre = 'https://dictionary.cambridge.org/'\n                    let type = ''\n                    if (e.className.includes('uk')) {\n                        type = 'uk'\n                        if (ph) phonetic.uk = ph\n                    } else if (e.className.includes('us')) {\n                        type = 'us'\n                        if (ph) phonetic.us = ph\n                    } else {\n                        type = 'en'\n                        if (ph) phonetic.uk = ph\n                    }\n                    if (src) sound.push({type, url: pre + src})\n                })\n                if (phonetic.us && phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n            }\n\n            // 释义\n            let part = ''\n            let posEl = el.querySelector('.pos-header .posgram')\n            if (posEl) part += `<div>${posEl.innerText}</div>`\n            let transEl = el.querySelectorAll('.pos-body .dsense')\n            if (transEl && transEl.length > 0) {\n                transEl.forEach(tEl => {\n                    cleanAttr(tEl, ['title', 'class', 'href'])\n                    el.querySelectorAll('a[href]').forEach(e => {\n                        if (e.href.includes('dictionary/english-chinese-simplified/')) e.setAttribute('data-search', 'true')\n                        e.removeAttribute('href')\n                    })\n                    part += tEl.innerHTML\n                })\n            }\n            if (part) s += `<div class=\"dict_cambridge\">${part}</div>`\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.zHUrl + encodeURIComponent(q)\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('dictionary.cambridge.org error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.zHUrl + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/collins.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction collinsDictionary() {\n    return {\n        // enUrl: 'https://www.collinsdictionary.com/dictionary/english/',\n        enUrl: 'https://www.collinsdictionary.com/search/?dictCode=english&q=',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let part = ''\n            let phonetic = {}\n            let sound = []\n            let el = r.querySelector('.page')\n\n            // 视频\n            let videoEl = el.querySelector('#videos .youtube-video[data-embed]')\n            if (videoEl) {\n                part += `<div style=\"margin:0 auto 10px;width:400px;height:224px;background:#000\"><iframe width=\"400\" height=\"224\" src=\"https://www.youtube.com/embed/${videoEl.dataset.embed}\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe></div>`\n            }\n\n            // 图片\n            let imgEl = el.querySelector('#images img[data-image]')\n            if (imgEl) {\n                part += `<img class=\"imageRight\" src=\"https://www.collinsdictionary.com${imgEl.dataset.image}\">`\n            }\n\n            // 释义\n            let dEl = el.querySelectorAll('.dictionaries > .dictentry')\n            if (dEl && dEl.length > 0) {\n                dEl.forEach(vEl => {\n                    // 类型\n                    let type = ''\n                    let tEl = vEl.querySelector('.title_container .dictname')\n                    if (tEl) {\n                        let tStr = tEl.innerText\n                        if (tStr.includes('in British English')) type = 'uk'\n                        else if (tStr.includes('in American English')) type = 'us'\n                        else type = 'en'\n                    }\n\n                    // 音标\n                    let pEl = vEl.querySelector('.mini_h2 .pron')\n                    if (pEl) {\n                        let pStr = pEl.innerText && pEl.innerText.trim()\n                        if (pStr) {\n                            if (type === 'uk') phonetic.uk = pStr\n                            else if (type === 'us') phonetic.us = pStr\n                        }\n                        let srcEl = pEl.querySelector('a[data-src-mp3]')\n                        if (srcEl && ['uk', 'us'].includes(type)) {\n                            let url = srcEl.getAttribute('data-src-mp3')\n                            sound.push({type, url})\n                        }\n                    }\n\n                    vEl.querySelectorAll('a.share-button,.share-overlay,.popup-overlay').forEach(e => e.remove())\n                    vEl.querySelectorAll('.word-frequency-img > .roundRed').forEach(e => addClass(e, 'dmx-icon dmx-icon-star'))\n\n                    // 喇叭\n                    vEl.querySelectorAll('[data-src-mp3]').forEach(e => {\n                        e.className = 'dmx-icon dmx_ripple'\n                        e.setAttribute('data-type', 'en')\n                    })\n\n                    cleanAttr(vEl, ['title', 'class', 'href', 'data-src-mp3', 'data-type'])\n                    vEl.querySelectorAll('a[href]').forEach(e => {\n                        if (e.href.includes('/dictionary/english/')) e.setAttribute('data-search', 'true')\n                        e.setAttribute('_href', e.href)\n                        e.removeAttribute('href')\n                    })\n\n                    part += vEl.innerHTML\n                })\n            }\n            if (part) s += `<div class=\"dict_collins\">${part}</div>`\n            if (phonetic.us && phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.enUrl + encodeURIComponent(q)\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('collinsdictionary.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.enUrl + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/dictcn.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction dictcnDictionary() {\n    return {\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let el = r.querySelector('#content > .main')\n            let s = ''\n\n            // 查询单词\n            let wordEl = el.querySelector('.word-cont > .keyword')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText}</div>`\n\n            let phonetic = {} // 音标\n            let sound = [] // 发音\n            el.querySelectorAll('.phonetic > span').forEach(spanEl => {\n                let spStr = spanEl.innerText || ''\n                let bdoEl = spanEl.querySelector('bdo')\n                let ph = bdoEl && bdoEl.innerText && bdoEl.innerText.replace(/(^\\[|]$)/g, '')\n                let type = ''\n                if (spStr.includes('美')) {\n                    type = 'us'\n                    if (ph) phonetic.us = ph\n                } else {\n                    type = 'uk'\n                    if (ph) phonetic.uk = ph\n                }\n\n                spanEl.querySelectorAll('.sound').forEach(e => {\n                    let title = e.getAttribute('title') || ''\n                    let url = 'https://audio.dict.cn/' + e.getAttribute('naudio')\n                    let isWoman = e.className && e.className.includes('fsound')\n                    sound.push({type, title, url, isWoman})\n                })\n            })\n            if (phonetic.us && phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n\n            // 释义\n            let partStr = ''\n            let liEl = el.querySelectorAll('.basic ul li')\n            if (liEl && liEl.length > 0) {\n                liEl.forEach(e => {\n                    let bEl = e.querySelector('span')\n                    let tEl = e.querySelector('strong')\n                    let bStr = bEl && bEl.innerText ? `<b>${bEl.innerText.trim()}</b>` : ''\n                    let part = tEl && tEl.innerText ? tEl.innerText.trim() : ''\n                    if (part) partStr += `<p>${bStr}${part}</p>`\n                })\n            } else {\n                let liEl = el.querySelectorAll('.layout ul li')\n                if (liEl && liEl.length > 0) {\n                    liEl.forEach(e => {\n                        let part = e.innerText && e.innerText.trim()\n                        if (part) partStr += `<p>${part}</p>`\n                    })\n                } else {\n                    partStr += 'Sorry，没有找到与此相符的结果'\n                }\n            }\n            s += `<div class=\"case_dd_parts\">${partStr}</div>`\n\n            let getChart = function (sel) {\n                try {\n                    let e = el.querySelector(sel)\n                    if (!e) return\n                    let d = e.getAttribute('data')\n                    d = decodeURIComponent(d)\n                    d = JSON.parse(d)\n                    let arr = Object.values(d)\n                    if (arr && arr.length > 0) {\n                        let str = ''\n                        for (let v of arr) {\n                            let {sense, percent, pos} = v\n                            str += `${sense || pos || ''}<u>${percent}%</u>`\n                        }\n                        if (str) s += `<div class=\"case_dd_chart\">${str}</div>`\n                    }\n                } catch (e) {\n                }\n            }\n            getChart('#dict-chart-basic') // 单词常用度\n            getChart('#dict-chart-examples') // 词性常用度\n\n            // 单词形态\n            let shapeEl = el.querySelector('.shape')\n            if (shapeEl) {\n                let shapeStr = ''\n                shapeEl.querySelectorAll('label,a').forEach(e => {\n                    if (e.tagName === 'LABEL') {\n                        shapeStr += `<b>${e.innerText}</b>`\n                    } else if (e.tagName === 'A') {\n                        shapeStr += `<a data-search=\"true\">${e.innerText}</a>`\n                    }\n                })\n                if (shapeStr) s += `<div class=\"case_dd_exchange\">${shapeStr}</div>`\n            }\n\n            // 单词标签\n            let levelEl = el.querySelector('span.level-title')\n            if (levelEl) {\n                let level = levelEl.getAttribute('level') || ''\n                if (level) s += `<div class=\"case_dd_tags\"><u>${level}</u></div>`\n            }\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                httpGet(`https://dict.cn/${encodeURIComponent(q)}`, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('dict.cn error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return `https://dict.cn/${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/dictionary.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction dictionaryDictionary() {\n    return {\n        url: 'https://www.dictionary.com/browse/',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let el = r.querySelector('#base-pw > main > section > section > div')\n\n            // 音标\n            let phonetic = {}\n            let pronEl = r.querySelector('.pron-spell-content')\n            if (pronEl) phonetic.us = pronEl.textContent.replace(/\\[|]/g, '').trim()\n\n            // 发音\n            let sound = []\n            let soundEl = r.querySelector('source[type=\"audio/mpeg\"]')\n            if (soundEl) sound.push({type: 'us', url: soundEl.src})\n\n            removeD(el.querySelectorAll('script,style,img,#top-definitions-section,.expandable-control')) // 清理\n            cleanAttr(el, ['title'])\n\n            return {text: q, phonetic, sound, html: el.innerHTML}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.url + encodeURIComponent(q)\n                httpGet(url, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('dictionary.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.url + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/dreye.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction dreyeDictionary() {\n    return {\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let el = r.querySelector('.q_middle')\n            let s = ''\n\n            // 查询单词\n            let wordEl = el.querySelector('#display_word > span')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText.trim()}</div>`\n\n            let phonetic = {} // 音标\n            let phEl = el.querySelector('.q_m_left > .phonetic')\n            if (phEl) {\n                let phStr = phEl.innerText && phEl.innerText.trim()\n                if (phStr) {\n                    // 这词典太老了，不想弄~~~\n                    let ukArr = phStr.match(/DJ:\\[(.+?)]/)\n                    let usArr = phStr.match(/KK:\\[(.+?)]/)\n                    if (ukArr && ukArr.length === 2) phonetic.uk = ukArr[1]\n                    if (usArr && usArr.length === 2) phonetic.us = usArr[1]\n                    if (phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n                }\n            }\n\n            let sound = [] // 发音\n            let scrEl = r.querySelector('script[language=\"javascript\"]')\n            if (scrEl) {\n                let scrStr = scrEl.textContent || ''\n                let ukArr = scrStr.match(/var RealSoundPath = \"(.+?)\";/)\n                let usArr = scrStr.match(/var F_RealSoundPath = \"(.+?)\";/)\n                let roUrl = 'https://www.dreye.com.cn'\n                if (ukArr && ukArr.length === 2) sound.push({type: 'uk', url: roUrl + ukArr[1]})\n                if (usArr && usArr.length === 2) sound.push({type: 'us', url: roUrl + usArr[1]})\n            }\n\n            // 释义\n            let liEl = el.querySelectorAll('#digest > ul > li')\n            let partStr = ''\n            if (liEl && liEl.length > 0) {\n                liEl.forEach(e => {\n                    let part = e.innerText && e.innerText.trim()\n                    part = part.replace(/^[a-zA-Z]+\\.\\s+/, function (str, k) {\n                        return k === 0 ? `<b>${str.trim()}</b>` : str\n                    })\n                    if (part) partStr += `<p>${part}</p>`\n                })\n            } else {\n                el.querySelectorAll('.q_middle_bd > .ews_sys_msg').forEach(e => {\n                    let str = e.textContent\n                    if (str) {\n                        str = str.replace(/[a-z]+'?[a-z]+/ig, function (str) {\n                            return `<a data-search=\"true\">${str}</a>`\n                        })\n                        partStr += `<p>${str}</p>`\n                    }\n                })\n            }\n            if (partStr) s += `<div class=\"case_dd_parts\">${partStr}</div>`\n\n            // 单词形态\n            let pEl = el.querySelectorAll('#digest > p')\n            if (pEl && pEl.length > 0) {\n                let str = ''\n                pEl.forEach(e => {\n                    let s2 = e.innerText.trim()\n                    s2 = s2.replace(/[a-z]+/ig, function (s3) {\n                        return `<a data-search=\"true\">${s3}</a>`\n                    })\n                    if (s2) str += `<p>${s2}</p>`\n                })\n                if (str) s += `<div class=\"case_dd_exchange\">${str}</div>`\n            }\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = `https://www.dreye.com.cn/dict_new/dict.php?w=${encodeURIComponent(q)}`\n                httpGet(url, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('dreye.com.cn error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return `https://www.dreye.com.cn/dict_new/dict.php?w=${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/etymonline.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction etymonlineDictionary() {\n    return {\n        url: 'https://www.etymonline.com/word/',\n        // url: 'https://www.etymonline.com/search?q=',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            // let el = r.querySelector('#root > div > div > div.main > div > div:nth-child(2) > div:nth-child(2) > object')\n            let s = ''\n            r.querySelectorAll('#root div.main div[class^=\"word--\"]').forEach(el => {\n                cleanAttr(el, ['title', 'class'])\n                s += el.innerHTML\n            })\n            if (!s) s += `The ${q} you're looking for can't be found.`\n            return {text: q, phonetic: {}, sound: [], html: `<div class=\"dict_etymonline\">${s}</div>`}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.url + encodeURIComponent(q)\n                httpGet(url, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('etymonline.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.url + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/eudic.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction eudicDictionary() {\n    return {\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let phonetic = {} // 音标\n            let sound = [] // 发音\n            let el = r.querySelector('#dict-body')\n\n            // 查询单词\n            let wordEl = el.querySelector('h1.explain-Word .word')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText.trim()}</div>`\n\n            el.querySelectorAll('.phonitic-line .Phonitic').forEach((e, k) => {\n                let ph = e.innerText && e.innerText.replace(/\\//g, '').trim() || ''\n                if (!ph) return\n                if (k === 0) phonetic.uk = ph\n                else phonetic.us = ph\n            })\n            if (phonetic.us && phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n\n            el.querySelectorAll('.phonitic-line a.voice-button-en').forEach(e => {\n                let rel = e.getAttribute('data-rel')\n                if (!rel) return\n                let type = 'en'\n                if (rel.includes('_uk_')) type = 'uk'\n                else if (rel.includes('_us_')) type = 'us'\n                sound.push({type, url: 'https://api.frdic.com/api/v2/speech/speakweb?' + rel})\n            })\n\n            // 释义\n            let liEl = el.querySelectorAll('#ExpFCChild li')\n            if (liEl && liEl.length > 0) {\n                let str = ''\n                liEl.forEach(e => {\n                    let tex = e.innerText && e.innerText.trim()\n                    tex = tex.replace(/^[a-zA-Z]+\\.\\s+/, function (str, k) {\n                        return k === 0 ? `<b>${str.trim()}</b>` : str\n                    })\n                    if (tex) str += `<p>${tex}</p>`\n                })\n                if (str) s += `<div class=\"case_dd_parts\">${str}</div>`\n            } else {\n                let expEl = el.querySelector('#ExpFCChild')\n                if (expEl) {\n                    expEl.querySelectorAll('script,style,#word-thumbnail-image').forEach(e => e.remove())\n                    cleanAttr(expEl, ['title', 'class'])\n                    if (expEl.innerHTML) s += `<div class=\"case_dd_parts\">${expEl.innerHTML}</div>`\n                }\n            }\n\n            // 单词形态\n            let transEl = el.querySelector('#trans')\n            if (transEl) {\n                let shapeStr = transEl.innerText.trim()\n                shapeStr = shapeStr.replace(/[a-z]+/ig, function (str) {\n                    return `<a data-search=\"true\">${str}</a>`\n                })\n                s += `<div class=\"case_dd_exchange\">${shapeStr}</div>`\n            }\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = `https://dict.eudic.net/dicts/en/${encodeURIComponent(q)}`\n                httpGet(url, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('dict.eudic.net error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return `https://dict.eudic.net/dicts/en/${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/hjdict.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction hjdictDictionary() {\n    return {\n        isFirst: true,\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let el = r.querySelector('.word-details')\n            let s = ''\n            let phonetic = {} // 音标\n            let sound = [] // 发音\n\n            // 没有找到结果\n            if (!el) {\n                let notEl = r.querySelector('.word-notfound-inner h2')\n                if (notEl) return {text: q, phonetic, sound, html: notEl.innerText, error: true}\n            }\n\n            // 查询单词\n            let wordEl = el.querySelector('.word-info .word-text h2')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText.trim()}</div>`\n\n            let pronEl = el.querySelector('.word-info .pronounces')\n            if (pronEl && pronEl.childNodes.length > 0) {\n                let ukEl = pronEl.querySelector('.pronounce-value-en')\n                let usEl = pronEl.querySelector('.pronounce-value-us')\n                let auEl = pronEl.querySelectorAll('.word-audio')\n                if (ukEl && ukEl.innerText) phonetic.uk = ukEl.innerText.replace(/[\\[\\]]/g, '').trim()\n                if (usEl && usEl.innerText) phonetic.us = usEl.innerText.replace(/[\\[\\]]/g, '').trim()\n                if (auEl && auEl.length === 2) {\n                    auEl.forEach((e, k) => {\n                        let type = k === 0 ? 'uk' : 'us'\n                        let url = e.dataset.src\n                        if (url) sound.push({type, url})\n                    })\n                } else {\n                    auEl.forEach(e => {\n                        let type = e.className.includes('word-audio-us') ? 'us' : e.className.includes('word-audio-en') ? 'uk' : 'us'\n                        let url = e.dataset.src\n                        if (url) sound.push({type, url})\n                    })\n                }\n            }\n            if (phonetic.us && phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n\n            // 释义\n            let liEl = el.querySelectorAll('.simple > p')\n            if (liEl && liEl.length > 0) {\n                s += `<div class=\"case_dd_parts\">`\n                liEl.forEach(e => {\n                    let part = e.innerText && e.innerText.trim()\n                    part = part.replace(/^[a-zA-Z]+\\.\\s+/, function (str, k) {\n                        return k === 0 ? `<b>${str.trim()}</b>` : str\n                    })\n                    if (part) s += `<p>${part}</p>`\n                })\n                s += `</div>`\n            }\n\n            // 详细释义\n            /*let detailEl = el.querySelectorAll('.word-details-item-content > .detail-groups')\n            if (detailEl && detailEl.length > 0) {\n                detailEl.forEach(e => {\n                    let str = e.innerHTML && e.innerHTML.trim()\n                    if (str) s += str\n                })\n            }*/\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = `https://www.hjdict.com/w/${encodeURIComponent(q)}`\n                httpGet(url, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('hjdict.com empty!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return `https://www.hjdict.com/w/${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/iciba.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction icibaDictionary() {\n    return {\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let phonetic = {} // 音标\n            let sound = [] // 发音\n            let el = r.querySelector('#__next > main > div > div[class^=Content_center]')\n\n            // JSON 数据\n            let data = {}\n            let basic = {}\n            let jEl = r.querySelector('#__NEXT_DATA__')\n            if (jEl) {\n                try {\n                    data = JSON.parse(jEl.textContent)\n                    if (data) basic = getJSONValue(data, 'props.pageProps.initialReduxState.word.wordInfo.baesInfo', {})\n                } catch (e) {\n                }\n            }\n\n            // 查询单词\n            let wordEl = el.querySelector('h1')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText.trim()}</div>`\n\n            el.querySelectorAll('ul[class^=Mean_symbols] > li').forEach(e => {\n                let ph = e.innerText && e.innerText.replace(/[\\[\\]英美]/g, '').trim() || ''\n                let type = ''\n                if (e.innerText.includes('美')) {\n                    if (ph) phonetic.us = ph\n                    type = 'us'\n                } else {\n                    if (ph) phonetic.uk = ph\n                    type = 'uk'\n                }\n\n                // 发音\n                let symbols = getJSONValue(basic, 'symbols.0')\n                if (symbols) {\n                    let url = ''\n                    let {ph_am_mp3, ph_am_mp3_bk, ph_en_mp3, ph_en_mp3_bk, ph_tts_mp3, ph_tts_mp3_bk} = symbols\n                    if (type === 'us') {\n                        url = ph_am_mp3 || ph_am_mp3_bk || ph_tts_mp3_bk\n                    } else {\n                        url = ph_en_mp3 || ph_en_mp3_bk || ph_tts_mp3\n                    }\n                    if (url) sound.push({type, url})\n                }\n            })\n            if (phonetic.us && phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n\n            // 释义\n            let liEl = el.querySelectorAll('ul[class^=Mean_part] > li')\n            if (liEl && liEl.length > 0) {\n                s += `<div class=\"case_dd_parts\">`\n                liEl.forEach(e => {\n                    let bEl = e.querySelector('i')\n                    let tEl = e.querySelector('div')\n                    let bStr = bEl && bEl.innerText ? `<b>${bEl.innerText.trim()}</b>` : ''\n                    let part = tEl && tEl.innerText ? tEl.innerText.trim() : ''\n                    if (bStr && part) {\n                        s += `<p>${bStr}${part}</p>`\n                    } else {\n                        let part = e.innerText && e.innerText.trim()\n                        if (part) s += `<p>${part}</p>`\n                    }\n                })\n                s += `</div>`\n            } else {\n                let str = ''\n                let senEl = el.querySelector('h2[class^=Mean_sentence]')\n                if (senEl) str += `<p>${senEl.textContent}</p>`\n                let transEl = el.querySelector('div[class^=Mean_trans] > p')\n                if (transEl) str += `<p>${transEl.textContent}</p>`\n                if (str) s += `<div class=\"case_dd_parts\">${str}</div>`\n            }\n\n            // 单词形态\n            let shapeEl = el.querySelector('ul[class^=Morphology_morphology]')\n            if (shapeEl) {\n                let shapeStr = shapeEl.innerText.trim()\n                shapeStr = shapeStr.replace(/;/g, ' ')\n                shapeStr = shapeStr.replace(/[a-z]+/ig, function (str) {\n                    return `<a data-search=\"true\">${str}</a>`\n                })\n                s += `<div class=\"case_dd_exchange\">${shapeStr}</div>`\n            }\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                // if (q.length > 100) return reject('The text is too large!')\n                let url = `https://www.iciba.com/word?w=${encodeURIComponent(q)}`\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('iciba.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return `https://www.iciba.com/word?w=${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/lexico.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction lexicoDictionary() {\n    return {\n        ukUrl: 'https://www.thefreedictionary.com/',\n        usUrl: 'https://www.lexico.com/en/definition/',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let phonetic = {}\n            let sound = []\n            let el = r.querySelector('.entryWrapper')\n\n            // 查询单词\n            let wordEl = el.querySelector('.hwg > .hw')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText.trim()}</div>`\n\n            // 音标\n            let pronEl = el.querySelector('.pronunciations .phoneticspelling')\n            if (pronEl) {\n                let pron = pronEl.innerText && pronEl.innerText.replace(/\\//g, '')\n                if (pron) phonetic.us = pron\n            }\n\n            // 发音\n            let mp3El = el.querySelector('.pronunciations audio[src]')\n            if (mp3El) sound.push({type: 'us', url: mp3El.src})\n\n            // 释义\n            let liEl = el.querySelectorAll('.gramb')\n            if (liEl && liEl.length > 0) {\n                liEl.forEach(e => {\n                    removeD(e.querySelectorAll('script,style,.moreInfo')) // 清理\n                    cleanAttr(e, ['title', 'class'])\n                    s += e.innerHTML\n                })\n            } else {\n                let simEl = el.querySelector('.similar-results')\n                if (simEl) {\n                    cleanAttr(simEl, ['title', 'class', 'href'])\n                    simEl.querySelectorAll('a[href]').forEach(e => {\n                        e.setAttribute('data-search', 'true')\n                        e.removeAttribute('href')\n                    })\n                    s += simEl.innerHTML\n                }\n            }\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.usUrl + encodeURIComponent(q)\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('lexico.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.usUrl + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/longman.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction longmanDictionary() {\n    return {\n        init() {\n            return this\n        },\n        unify(r, q) {\n            // console.log(r)\n            // let arr = r.match(/<div class=\"dictionary\">([\\s\\S]*?)<\\/div><!-- End of DIV dictionary-->/m)\n            // console.log(arr)\n            let el = r.querySelector('.dictionary')\n            if (!el) {\n                el = r.querySelector('.page_content')\n                if (!el) return {text: q, html: 'Failed to get data!'}\n\n                let html = ''\n                let tEl = el.querySelector('.search_title')\n                if (tEl) html += `<div><b>${tEl.innerText}</b></div>`\n                el.querySelectorAll('.didyoumean > li > a').forEach(e => {\n                    let href = e.getAttribute('href')\n                    html += `<div data-search=\"true\" _href=\"${href}\">${e.innerText}</div>`\n                })\n                return {text: q, html: html}\n            }\n\n            // 音标\n            let headEl = el.querySelector('.Head')\n            headEl.querySelector('.PronCodes > .AMEVARPRON > .neutral')?.remove()\n            let ukPron = headEl.querySelector('.PronCodes > .PRON')?.innerText?.trim()\n            let usPron = headEl.querySelector('.PronCodes > .AMEVARPRON')?.innerText?.trim()\n            headEl.querySelector('.PronCodes')?.remove()\n            let phonetic = {}\n            if (ukPron) phonetic.uk = ukPron\n            if (usPron) phonetic.us = usPron\n\n            // 发音\n            let sound = []\n            headEl.querySelectorAll('[data-src-mp3]').forEach(e => {\n                let title = e.getAttribute('title')\n                let src = e.getAttribute('data-src-mp3')\n                if (title && src) {\n                    if (title.includes('British')) sound.push({type: 'uk', title: title, url: src})\n                    else if (title.includes('American')) sound.push({type: 'us', title: title, url: src})\n                }\n                e.remove()\n            })\n\n            // 喇叭\n            el.querySelectorAll('[data-src-mp3]').forEach(e => {\n                e.className = 'dmx-icon dmx_ripple'\n                let v = 'en'\n                let title = e.getAttribute('title')\n                if (title) {\n                    if (title.includes('British')) v = 'uk'\n                    else if (title.includes('American')) v = 'us'\n                }\n                e.setAttribute('data-type', v)\n            })\n\n            // 图片\n            el.querySelectorAll('img').forEach(e => {\n                e.setAttribute('referrerPolicy', 'no-referrer')\n            })\n\n            // 链接\n            el.querySelectorAll('a').forEach(e => {\n                let href = e.getAttribute('href')\n                let s = q.replace(/\\W/g, '-')\n                if (e.className === 'crossRef' && href.includes(`/dictionary/${s}#${s}`)) {\n                    e.remove() // 清理掉本页跳转链接\n                    return\n                }\n                e.setAttribute('_href', href)\n                e.removeAttribute('href')\n                e.setAttribute('data-search', 'true')\n            })\n\n            // 清理\n            el.querySelectorAll('[id]').forEach(e => {\n                e.removeAttribute('id')\n            })\n            el.querySelectorAll('script,style,.dictionary_intro').forEach(e => {\n                e.remove()\n            })\n            el.className = 'longman_dict'\n            return {text: q, phonetic: phonetic, sound: sound, html: el.outerHTML}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = `https://www.ldoceonline.com/search/english/direct/?q=${encodeURIComponent(q)}`\n                // q = q.trim().replace(/\\s+/g, ' ').replace(/\\W/g, '-')\n                // let url = `https://www.ldoceonline.com/dictionary/${q}`\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('longman error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            // return `https://www.ldoceonline.com/dictionary/${encodeURIComponent(q)}`\n            return `https://www.ldoceonline.com/search/english/direct/?q=${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/macmillan.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction macmillanDictionary() {\n    return {\n        url: 'https://www.macmillandictionary.com/search/british/direct/?auto=complete&q=',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let phonetic = {}\n            let sound = []\n            let el = r.querySelector('#entryContent > div > div.left-content')\n\n            // 查询单词\n            let wordEl = el.querySelector('.big-title .BASE')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText.trim()}</div>`\n\n            // 音标\n            let pronEl = r.querySelector('.PRON')\n            if (pronEl) {\n                pronEl.querySelectorAll('span').forEach(e => e.remove())\n                let pronStr = pronEl.textContent && pronEl.textContent.trim()\n                if (pronStr) phonetic.uk = pronStr\n            }\n\n            // 发音\n            let soundEl = r.querySelector('.PRONS span[data-src-mp3]')\n            if (soundEl) sound.push({type: 'uk', url: soundEl.dataset.srcMp3})\n\n            // 释义\n            let sensesEl = el.querySelector('.senses')\n            if (sensesEl) {\n                removeD(sensesEl.querySelectorAll('script,style,button'))\n                cleanAttr(sensesEl, ['title', 'class', 'href'])\n                sensesEl.querySelectorAll('a[href]').forEach(e => {\n                    // e.setAttribute('data-search', 'true')\n                    e.removeAttribute('href')\n                })\n                s += sensesEl.innerHTML\n            }\n\n            return {text: q, phonetic, sound, html: `<ol class=\"dict_macmillan\">${s}</ol>`}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.url + encodeURIComponent(q)\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('macmillandictionary.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.url + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/merriam.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction merriamDictionary() {\n    return {\n        dUrl: 'https://www.learnersdictionary.com/definition/',\n        mUrl: 'https://www.merriam-webster.com/dictionary/',\n        mp3Url: 'https://media.merriam-webster.com/audio/prons/en/us/mp3',\n        init() {\n            return this\n        },\n        getMp3(file) {\n            return `${this.mp3Url}/${file.substring(0, 1)}/${file}.mp3`\n        },\n        unify(r, q) {\n            let s = ''\n            let phonetic = {}\n            let sound = []\n            let el = r.querySelector('#ld_entries_v2_all')\n\n            // 音标\n            let pronEl = el.querySelector('.hpron_word')\n            if (pronEl) {\n                let pron = pronEl.textContent && pronEl.textContent.replace(/\\//g, '').trim()\n                if (pron) phonetic.uk = pron\n            }\n\n            // 发音\n            let pEl = el.querySelector('a.play_pron[data-file]')\n            if (pEl) {\n                let file = pEl.getAttribute('data-file')\n                sound.push({type: 'us', url: this.getMp3(file)})\n            }\n\n            // 释义\n            let part = ''\n            let deepCommentRemove = function (el) {\n                for (let v of el.childNodes) {\n                    if (v.nodeType === 8) v.remove() // 删除注释\n                    if (v.childNodes.length > 0) deepCommentRemove(v)\n                }\n            }\n            let v2El = el.querySelectorAll('.entry_v2')\n            if (v2El && v2El.length > 0) {\n                v2El.forEach(vEl => {\n                    deepCommentRemove(vEl)\n                    vEl.querySelectorAll('.vi_more,.d_hidden').forEach(e => e.remove())\n\n                    // 喇叭\n                    vEl.querySelectorAll('a.play_pron[data-file]').forEach(e => {\n                        e.className = 'dmx-icon dmx_ripple'\n                        let file = e.getAttribute('data-file')\n                        e.setAttribute('data-src-mp3', this.getMp3(file))\n                        e.setAttribute('data-type', 'us')\n                    })\n\n                    cleanAttr(vEl, ['title', 'class', 'href', 'data-type', 'data-src-mp3'])\n                    vEl.querySelectorAll('a[href]').forEach(e => {\n                        if (e.href.includes('/definition/') && !e.getAttribute('data-src-mp3')) {\n                            e.setAttribute('data-search', 'true')\n                        }\n                        e.setAttribute('_href', e.href)\n                        e.removeAttribute('href')\n                    })\n                    vEl.querySelectorAll('.sn_block_num').forEach(e => e.style.float = 'left')\n                    vEl.querySelectorAll('.sblock_c').forEach(e => e.style.marginTop = '10px')\n                    vEl.querySelectorAll('.sgram_internal').forEach(e => e.style.color = '#757575')\n                    vEl.querySelectorAll('.hw_d').forEach(e => {\n                        e.style.color = '#0580e8'\n                        e.style.fontSize = '110%'\n                    })\n                    part += vEl.innerHTML.replace(/\\s+/g, ' ')\n                })\n            }\n            if (part) s += `<div class=\"dict_merriam\">${part}</div>`\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.dUrl + encodeURIComponent(q)\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('merriam-webster.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.dUrl + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/oxford.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction oxfordDictionary() {\n    return {\n        url: 'https://www.oxfordlearnersdictionaries.com/search/english/?q=',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let phonetic = {}\n            let sound = []\n            let el = r.querySelector('#entryContent')\n            if (!el) {\n                let el = r.querySelector('#main_column')\n                cleanAttr(el, ['title', 'class', 'href'])\n                el.querySelectorAll('a[href]').forEach(e => {\n                    e.setAttribute('data-search', 'true')\n                    e.removeAttribute('href')\n                })\n                return {text: q, phonetic, sound, html: el.innerHTML}\n            }\n\n            // 查询单词\n            let wordEl = el.querySelector('.headword')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText.trim()}</div>`\n\n            // 音标 && 发音\n            let ukEl = el.querySelector('.webtop .phonetics .phons_br')\n            let usEl = el.querySelector('.webtop .phonetics .phons_n_am')\n            if (ukEl) {\n                let pron = ukEl.textContent.replace(/\\//g, '').trim()\n                if (pron) phonetic.uk = pron\n                let srcEl = ukEl.querySelector('.sound[data-src-mp3]')\n                if (srcEl) sound.push({type: 'uk', url: srcEl.getAttribute('data-src-mp3')})\n            }\n            if (usEl) {\n                let pron = usEl.textContent.replace(/\\//g, '').trim()\n                if (pron) phonetic.us = pron\n                let srcEl = ukEl.querySelector('.sound[data-src-mp3]')\n                if (srcEl) sound.push({type: 'us', url: srcEl.getAttribute('data-src-mp3')})\n            }\n\n            // 释义\n            let part = ''\n            let sensesEl = el.querySelector('.senses_multiple')\n            if (!sensesEl) sensesEl = el.querySelector('.sense_single')\n            if (sensesEl) {\n                sensesEl.querySelectorAll('script,style,span.sensetop').forEach(e => e.remove()) // 清理\n                cleanAttr(sensesEl, ['title', 'class', 'href'])\n                el.querySelectorAll('a[href]').forEach(e => {\n                    e.setAttribute('data-search', 'true')\n                    e.removeAttribute('href')\n                })\n                part += sensesEl.innerHTML.replace(/\\s+/g, ' ')\n            }\n            if (part) s += `<div class=\"dict_oxford\">${part}</div>`\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.url + encodeURIComponent(q)\n                httpGet(url, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('oxfordlearnersdictionaries.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.url + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/rrdict.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction rrdictDictionary() {\n    return {\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let el = r.querySelector('section.content')\n            let s = ''\n\n            // 查询单词\n            let wordEl = el.querySelector('.text')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText.trim()}</div>`\n\n            let phonetic = {} // 音标\n            let sound = [] // 发音\n            el.querySelectorAll('.vos > span').forEach((e, k) => {\n                let ph = e.innerText && e.innerText.replace(/[\\[\\]英美]/g, '').trim() || ''\n                if (!ph) return\n                let type = ''\n                if (k === 0) {\n                    phonetic.uk = ph\n                    type = 'uk'\n                } else {\n                    phonetic.us = ph\n                    type = 'us'\n                }\n                let auEl = e.querySelector('audio[src]')\n                if (!auEl) return\n                sound.push({type, url: auEl.src})\n            })\n            if (phonetic.us && phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n\n            // 释义\n            let liEl = el.querySelector('.tmInfo .listBox')\n            let interStr = ''\n            if (liEl && liEl.childNodes.length > 0) {\n                let poEl = liEl.querySelector('.poBottom')\n                if (poEl) poEl.remove()\n                let liHtml = liEl.innerHTML.trim()\n                for (let part of liHtml.split('<br>')) {\n                    part = part.trim()\n                    if (part.includes('<a href=')) continue\n                    part = part.replace(/^[a-zA-Z]+\\.\\s+/, function (str, k) {\n                        return k === 0 ? `<b>${str.trim()}</b>` : str\n                    })\n                    if (part) interStr += `<p>${part}</p>`\n                }\n            }\n            if (!interStr) {\n                let vosEl = el.querySelector('.vos')\n                if (vosEl) interStr += vosEl.innerText.trim()\n            }\n            if (interStr) s += `<div class=\"case_dd_parts\">${interStr}</div>`\n\n            // 单词形态\n            let transEl = el.querySelector('.tmInfo .listBox:nth-child(2)')\n            if (transEl) {\n                let shapeStr = transEl.innerText.trim()\n                shapeStr = shapeStr.replace(/[a-z]+/ig, function (str) {\n                    return `<a data-search=\"true\">${str}</a>`\n                })\n                s += `<div class=\"case_dd_exchange\">${shapeStr}</div>`\n            }\n\n            // 场景例句\n            let flexEl = el.querySelectorAll('#flexslider_2 ul li')\n            if (flexEl && flexEl.length > 0) {\n                s += `<div class=\"case_dd_example\">`\n                flexEl.forEach(e => {\n                    let imgBox = e.querySelector('.imgMainbox')\n                    if (imgBox) {\n                        // let imgEl = imgBox.querySelector('img[src]')\n                        let auEl = imgBox.querySelector('audio[src]')\n                        let enEl = imgBox.querySelector('.mBottom')\n                        let zhEl = imgBox.querySelector('.mFoot')\n                        let fromEl = imgBox.querySelector('.mTop')\n                        let url = auEl.src\n                        if (url && enEl && zhEl && fromEl) {\n                            let form = fromEl.innerText ? ' —— ' + fromEl.innerText.trim() : ''\n                            s += `<p class=\"mt-1\"><i class=\"dmx-icon dmx_ripple\" data-type=\"en\" data-src-mp3=\"${url}\"></i>${enEl.innerHTML}</p><p>${zhEl.innerText}${form}</p>`\n                        }\n                    }\n                    // e.querySelectorAll('.mTextend > .box').forEach(bEl => {\n                    //     let tEl = bEl.querySelector('.sty1')\n                    //     let cEl = bEl.querySelector('.sty2')\n                    // })\n                })\n                s += `</div>`\n            }\n\n            // 单词标签\n            let tagEl = el.querySelectorAll('.tag > a')\n            if (tagEl && tagEl.length > 0) {\n                s += `<div class=\"case_dd_tags\">`\n                tagEl.forEach(e => {\n                    let tag = e.innerText && e.innerText.trim()\n                    if (tag) s += `<u>${tag}</u>`\n                })\n                s += `</div>`\n            }\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = `https://www.91dict.com/words?w=${encodeURIComponent(q)}`\n                httpGet(url, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('91dict.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return `https://www.91dict.com/words?w=${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/thefree.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction thefreeDictionary() {\n    return {\n        url: 'https://www.thefreedictionary.com/',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let phonetic = {}\n            let sound = []\n            let el = r.querySelector('#content')\n\n            // 音标\n            let pronEl = r.querySelector('span.pron')\n            if (pronEl) {\n                let pron = pronEl.innerText && pronEl.innerText.replace(/^\\(|\\)$/g, '')\n                if (pron) phonetic.uk = pron\n            }\n\n            // 发音\n            let ukEl = el.querySelector('.snd2[data-snd^=\"en/UK/\"]')\n            let usEl = el.querySelector('.snd2[data-snd^=\"en/US/\"]')\n            if (ukEl) sound.push({type: 'uk', url: `https://img2.tfd.com/pron/mp3/${ukEl.dataset.snd}.mp3`})\n            if (usEl) sound.push({type: 'us', url: `https://img2.tfd.com/pron/mp3/${usEl.dataset.snd}.mp3`})\n\n            let defEl = el.querySelector('#Definition')\n            if (defEl) {\n                removeD(defEl.querySelectorAll('script,style,select.verbtables')) // 清理\n                defEl.querySelectorAll('span.snd[data-snd]').forEach(e => {\n                    e.className = 'dmx-icon dmx_ripple'\n                    e.setAttribute('data-src-mp3', `https://img.tfd.com/hm/mp3/${e.dataset.snd}.mp3`)\n                    e.setAttribute('data-type', 'en')\n                })\n                cleanAttr(defEl, ['title', 'class', 'href', 'data-snd', 'data-type', 'data-src-mp3'])\n                defEl.querySelectorAll('span[class=\"hvr\"]').forEach(e => {\n                    e.setAttribute('data-search', 'true')\n                })\n                defEl.querySelectorAll('a[href]').forEach(e => {\n                    e.setAttribute('data-search', 'true')\n                    e.setAttribute('_href', e.href)\n                    e.removeAttribute('href')\n                })\n                s += defEl.innerHTML\n            } else {\n                let txtEl = r.querySelector('#MainTxt')\n                cleanAttr(txtEl, ['title', 'class', 'href'])\n                txtEl.querySelectorAll('a[href]').forEach(e => {\n                    e.setAttribute('data-search', 'true')\n                    e.removeAttribute('href')\n                })\n                s += txtEl.innerHTML\n            }\n\n            return {text: q, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.url + encodeURIComponent(q)\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('thefreedictionary.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.url + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/urban.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction urbanDictionary() {\n    return {\n        url: 'https://www.urbandictionary.com/define.php?term=',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let el = r.querySelector('#content > .def-panel')\n\n            removeD(el.querySelectorAll('script,style,.row,.def-footer,a.mug-ad'))\n            cleanAttr(el, ['title', 'class', 'autoplay', 'loop', 'muted', 'playsinline', 'src', 'width', 'height'])\n            el.querySelectorAll('.def-header').forEach(e => {\n                e.style.fontSize = '120%'\n                e.style.fontWeight = '700'\n            })\n\n            return {text: q, phonetic: {}, sound: [], html: el.innerHTML}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.url + encodeURIComponent(q)\n                httpGet(url, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('urbandictionary.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.url + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/vocabulary.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction vocabularyDictionary() {\n    return {\n        url: 'https://www.vocabulary.com/dictionary/',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let el = r.querySelector('.centeredContent')\n\n            // 发音\n            let sound = []\n            let soundEl = r.querySelector('a.audio[data-audio]')\n            if (soundEl) sound.push({type: 'us', url: `https://audio.vocab.com/1.0/us/${soundEl.dataset.audio}.mp3`})\n\n            // 清理\n            removeD(el.querySelectorAll('script,style,img'))\n            cleanAttr(el, ['title', 'class'])\n            el.querySelectorAll('.groupNumber').forEach(e => {\n                e.style.marginTop = '10px'\n                e.style.fontWeight = '700'\n            })\n\n            return {text: q, phonetic: {}, sound, html: el.innerHTML}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.url + encodeURIComponent(q)\n                httpGet(url, 'document', null, true).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('vocabulary.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.url + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/wordreference.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction wordreferenceDictionary() {\n    return {\n        url: 'https://www.wordreference.com/definition/',\n        init() {\n            return this\n        },\n        unify(r, q) {\n            let s = ''\n            let el = r.querySelector('#centercolumn')\n\n            // 发音\n            let sound = []\n            let ukEl = r.querySelector('source[src^=\"/audio/en/uk\"]')\n            let usEl = r.querySelector('source[src^=\"/audio/en/us\"]')\n            if (ukEl) sound.push({type: 'uk', url: ukEl.src})\n            if (usEl) sound.push({type: 'us', url: usEl.src})\n\n            // 清理\n            let artEl = el.querySelector('#article')\n            if (artEl) {\n                removeD(artEl.querySelectorAll('script,style,img,br,.small1'))\n                cleanAttr(artEl, ['title', 'class'])\n                let artStr = artEl.innerHTML\n                artStr = artStr.trim()\n                artStr = artStr.replace(/^\\s*<br>\\s*<br>/g, '')\n                s += artStr\n            }\n\n            return {text: q, phonetic: {}, sound, html: `<div class=\"dict_wr\">${s}</div>`}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 100) return reject('The text is too large!')\n                let url = this.url + encodeURIComponent(q)\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('wordreference.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return this.url + encodeURIComponent(q)\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/dictionary/youdao.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction youdaoDictionary() {\n    return {\n        init() {\n            return this\n        },\n        unify(r, text) {\n            let s = ''\n            let phonetic = {} // 音标\n            let sound = [] // 发音\n            let el = r.querySelector('#results-contents')\n\n            // 查询单词\n            let wordEl = el.querySelector('.wordbook-js .keyword')\n            if (wordEl) s = `<div class=\"case_dd_head\">${wordEl.innerText}</div>`\n\n            el.querySelectorAll('.wordbook-js .baav .pronounce').forEach(e => {\n                let pEl = e.querySelector('.phonetic')\n                let aEl = e.querySelector('a')\n                if (!aEl) return\n                let phStr = pEl && pEl.innerText && pEl.innerText.replace(/(^\\[|]$)/g, '')\n                let rel = aEl.getAttribute('data-rel') || ''\n                let voice = aEl.getAttribute('data-4log') || ''\n                let url = 'https://dict.youdao.com/dictvoice?audio=' + rel\n                let type = ''\n                if (voice.includes('.uk.')) {\n                    type = 'uk'\n                    if (phStr) phonetic.uk = phStr\n                } else if (voice.includes('.us.')) {\n                    type = 'us'\n                    if (phStr) phonetic.us = phStr\n                } else {\n                    type = 'en'\n                    if (phStr) phonetic.uk = phStr\n                }\n                sound.push({type, url})\n            })\n            if (phonetic.us && phonetic.uk === phonetic.us) delete phonetic.us // 如果音标一样，只保留一个\n\n            // 释义\n            let transEl = el.querySelector('#phrsListTab .trans-container')\n            if (transEl) {\n                let str = ''\n                let liEl = transEl.querySelectorAll('li')\n                if (liEl && liEl.length > 0) {\n                    liEl.forEach(e => {\n                        let part = e.innerText && e.innerText.trim()\n                        part = part.replace(/^[a-zA-Z]+\\.\\s+/, function (str, k) {\n                            return k === 0 ? `<b>${str.trim()}</b>` : str\n                        })\n                        if (part) str += `<p>${part}</p>`\n                    })\n                } else {\n                    let wordEl = el.querySelector('.wordGroup')\n                    if (wordEl) {\n                        let part = wordEl.innerText && wordEl.innerText.trim()\n                        part = part.replace(/^[a-zA-Z]+\\.\\s+/, function (str, k) {\n                            return k === 0 ? `<b>${str.trim()}</b>` : str\n                        })\n                        if (part) str += `<p>${part}</p>`\n                    }\n                }\n                if (str) s += `<div class=\"case_dd_parts\">${str}</div>`\n\n                // 单词形态\n                let addiEl = transEl.querySelector('.additional')\n                if (addiEl) {\n                    let shapeStr = addiEl.innerText.trim()\n                    shapeStr = shapeStr.replace(/^\\[|]$/g, '')\n                    shapeStr = shapeStr.trim()\n                    shapeStr = shapeStr.replace(/[a-z]+/ig, function (str) {\n                        return `<a data-search=\"true\">${str}</a>`\n                    })\n                    s += `<div class=\"case_dd_exchange\">${shapeStr}</div>`\n                }\n            } else {\n                let transEl = el.querySelector('#fanyiToggle .trans-container')\n                if (transEl) {\n                    transEl.querySelectorAll('p:last-child').forEach(e => e.remove()) // 清理最后一行\n                    s += `<div class=\"case_dd_exchange\">${transEl.innerHTML}</div>`\n                }\n            }\n\n            return {text, phonetic, sound, html: s}\n        },\n        query(q) {\n            return new Promise((resolve, reject) => {\n                // if (q.length > 100) return reject('The text is too large!')\n                let url = `https://www.youdao.com/w/eng/${encodeURIComponent(q)}`\n                httpGet(url, 'document').then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q))\n                    } else {\n                        reject('youdao.com error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        link(q) {\n            return `https://www.youdao.com/w/eng/${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/favorite.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nlet db, cateId = 0\nlet sentenceData = {}\nlet listen, record, compare\ndocument.addEventListener('DOMContentLoaded', async function () {\n    await idb('favorite', 1, initFavorite).then(r => db = r)\n\n    initCate() // 加载分类\n    createCate() // 添加分类\n    updateCate() // 编辑分类\n    deleteCate() // 删除分类\n\n    selectAll() // 全选/取消全选\n    moveSentence() // 批量移动句子\n    deleteBatchSentence() // 批量删除句子\n\n    exportZip() // 导出\n    importZip() // 导入\n    openSetting() // 设置\n})\n\n// 添加分类\nfunction createCate() {\n    $('create_cate_but').addEventListener('click', function () {\n        ddi({\n            title: '新增分类', body: `<div class=\"dmx_form_item\">\n            <div class=\"item_label\">分类名称</div>\n                <div class=\"item_content\"><input id=\"create_cateName\" type=\"text\" autocomplete=\"off\" required class=\"item_input\"></div>\n            </div>\n            <div class=\"dmx_right\">\n                <button class=\"dmx_button\" id=\"create_cate\">添加</button>\n            </div>`\n        })\n        $('create_cate').addEventListener('click', () => {\n            let cateName = $('create_cateName').value.trim()\n            if (!cateName) return dal('请填写分类名称', 'error')\n            let d = new Date().toJSON()\n            db.create('cate', {cateName, updateDate: d, createDate: d}).then(_ => {\n                removeDdi()\n                initCate()\n            }).catch(e => {\n                let err = e.target.error.message\n                let msg = '添加失败'\n                if (err && err.includes('uniqueness requirements')) msg = '分类已存在，请勿重复添加'\n                dal(msg, 'error')\n            })\n        })\n    })\n}\n\n// 编辑分类\nfunction updateCate() {\n    $('cate_edit').addEventListener('click', function () {\n        ddi({\n            title: '编辑分类', body: `<div class=\"dmx_form_item\">\n            <div class=\"item_label\">分类名称</div>\n                <div class=\"item_content\"><input id=\"update_cateName\" type=\"text\" autocomplete=\"off\" required class=\"item_input\"></div>\n            </div>\n            <div class=\"dmx_right\">\n                <button class=\"dmx_button\" id=\"update_cate\">保存</button>\n            </div>`\n        })\n        let updateEl = $('update_cateName')\n        updateEl.value = $('cate_name').innerText\n        $('update_cate').addEventListener('click', () => {\n            let cateName = updateEl.value.trim()\n            if (!cateName) return dal('分类名称不能为空', 'error')\n            db.update('cate', cateId, {cateName}).then(_ => {\n                removeDdi()\n                initCate()\n            }).catch(e => {\n                let err = e.target.error.message\n                let msg = '修改失败'\n                if (err && err.includes('uniqueness requirements')) msg = '分类名称不允许重名'\n                dal(msg, 'error')\n            })\n        })\n    })\n}\n\n// 删除分类\nfunction deleteCate() {\n    $('cate_delete').addEventListener('click', function () {\n        dco('删除分类不可恢复，确认删除吗？', () => {\n            if (cateId < 1) return dal('系统分类不允许删除', 'error')\n            db.count('sentence', 'cateId', cateId).then(n => {\n                if (n > 0) return dal('分类存在数据，请先清空数据', 'error')\n                db.delete('cate', cateId).then(_ => initCate(0)).catch(_ => dal('删除失败', 'error'))\n            })\n        })\n    })\n}\n\n// 批量移动句子\nfunction moveSentence() {\n    $('sentence_move').addEventListener('click', function () {\n        db.getAll('cate').then(arr => {\n            let s = '<option value=\"-1\">选择分类</option>'\n            arr.forEach(v => {\n                if (v.cateId === cateId) return // 排除当前分类\n                s += `<option value=\"${v.cateId}\">${v.cateName}</option>`\n            })\n\n            ddi({\n                title: '移动到', body: `<div class=\"dmx_form_item\">\n                <div class=\"item_label\">分类</div>\n                    <div class=\"item_content\"><select id=\"move_cateId\">${s}</select></div>\n                </div>\n                <div class=\"dmx_right\">\n                    <button class=\"dmx_button\" id=\"move_cate_but\">确认</button>\n                </div>`\n            })\n            $('move_cate_but').addEventListener('click', () => {\n                let cateId = Number($('move_cateId').value)\n                if (cateId < 0) return dal('请选择分类', 'error')\n\n                let eList = D('td.tb_checkbox input[type=\"checkbox\"]:checked')\n                eList.forEach(el => {\n                    db.update('sentence', Number(el.value), {cateId}).catch(_ => dal('移动失败', 'error'))\n                })\n                setTimeout(() => {\n                    removeDdi()\n                    initCate(cateId)\n                    selectCancel()\n                }, 1000)\n            })\n        })\n    })\n}\n\n// 批量删除句子\nfunction deleteBatchSentence() {\n    $('sentence_delete').addEventListener('click', function () {\n        let eList = D('td.tb_checkbox input[type=\"checkbox\"]:checked')\n        dco(`删除不可恢复，您确认要删除这 ${eList.length} 条数据吗？`, () => {\n            eList.forEach(el => {\n                db.delete('sentence', Number(el.value)).catch(_ => dal('删除失败', 'error'))\n            })\n            setTimeout(() => {\n                initCate()\n                selectCancel()\n            }, 1000)\n        })\n    })\n}\n\n// 加载分类\nfunction initCate(id) {\n    db.getAll('cate').then(arr => {\n        let s = ''\n        arr.forEach(v => {\n            s += `<li data-id=\"${v.cateId}\"><a><i class=\"dmx-icon dmx-icon-folder\"></i>${v.cateName}</a></li>`\n        })\n        $('cate_box').innerHTML = s\n\n        // 分类筛选\n        D('#cate_box li').forEach(el => {\n            el.addEventListener('click', () => {\n                cateId = Number(el.dataset.id)\n                initSentence(cateId)\n                D('#cate_box li.active').forEach(e => rmClass(e, 'active'))\n                addClass(el, 'active')\n            })\n        })\n\n        // 初始\n        let firstEl = S(`#cate_box li[data-id=\"${typeof id === 'number' ? id : cateId || 0}\"]`)\n        if (firstEl) firstEl.click()\n    })\n}\n\n// 加载句子\nfunction initSentence(cateId) {\n    let thLen = D('#sentence_box thead th').length\n    let tbodyEl = S('#sentence_box tbody')\n    if (!tbodyEl.innerHTML) tbodyEl.innerHTML = `<tr><td class=\"table_empty\" colspan=\"${thLen}\"><div class=\"dmx-icon dmx-icon-loading\"></div></td></tr>`\n\n    db.read('cate', cateId).then(cate => $('cate_name').innerText = cate.cateName)\n    db.count('sentence', 'cateId', cateId).then(n => $('sentences').innerText = n)\n\n    let orderBy = localStorage['orderBy']\n    let direction = orderBy === 'reverse' ? 'prev' : 'next'\n    db.find('sentence', {indexName: 'cateId', query: cateId, direction}).then(arr => {\n        if (arr.length < 1) {\n            tbodyEl.innerHTML = `<tr><td class=\"table_empty\" colspan=\"${thLen}\">暂无内容</td></tr>`\n            return\n        }\n\n        if (orderBy === 'random') shuffle(arr) // 随机\n\n        // console.log(JSON.stringify(arr))\n        let s = ''\n        arr.forEach((v, k) => {\n            s += `<tr>\n                <td class=\"tb_checkbox\"><input type=\"checkbox\" value=\"${v.id}\"></td>\n                <td class=\"tb_sentence\">${pointSentence(v.sentence, v.words)}</td>\n                <td class=\"tb_records\">${v.records}</td>\n                <td class=\"tb_days\">${v.days}</td>\n                <td class=\"tb_date\" title=\"${getDate(v.createDate)}\">${getDate(v.createDate, true)}</td>\n                <td class=\"tb_operate\" data-id=\"${v.id}\" data-key=\"${k}\">\n                    <div class=\"dmx_button\" data-action=\"skill\">练习</div>\n                    <div class=\"dmx_button dmx_button_warning\" data-action=\"edit\">修改</div>\n                    <div class=\"dmx_button dmx_button_danger\" data-action=\"delete\">删除</div>\n                </td>\n            </tr>`\n        })\n        tbodyEl.innerHTML = s\n        sentenceData = arr\n        selectBind()\n        exerciseSentence() // 练习句子\n        editSentence()\n        deleteSentence()\n    })\n}\n\n// 练习句子\nfunction exerciseSentence() {\n    D('.dmx_button[data-action=\"skill\"]').forEach(el => {\n        el.addEventListener('click', () => {\n            ddi({\n                fullscreen: true,\n                title: '',\n                body: `<div class=\"player_box\">\n                        <div class=\"tab fx\">\n                            <u data-type=\"skill\" class=\"active\">朗读练习</u>\n                            <u data-type=\"record\">发音练习</u>\n                            <u data-type=\"listen\">听力练习</u>\n                        </div>\n                        <div id=\"skill_box\"></div>\n                    </div>`,\n                onClose: () => {\n                    listen.stop()\n                    initSentence(cateId)\n                }\n            })\n\n            // 绑定事件\n            let tabEl = D('.player_box u[data-type]')\n            let boxEl = $('skill_box')\n            tabEl.forEach(e => {\n                e.addEventListener('click', () => {\n                    // 练习状态时，不允许切换菜单，防止冲突\n                    if (window.isExercising) {\n                        dal('练习状态时，不允许切换菜单', 'error')\n                        return\n                    }\n                    let len = sentenceData.length\n                    let key = Number(el.parentNode.dataset.key)\n                    let type = e.dataset.type\n                    let s = '<div id=\"player_sentence\"></div>'\n                    if (type === 'skill') {\n                        s += '<div id=\"player_listen\" style=\"display:none\"></div><div id=\"player_record\"></div><div id=\"player_compare\"></div>'\n                    } else if (type === 'record') {\n                        s += '<div id=\"player_listen\"></div><div id=\"player_record\"></div><div id=\"player_compare\"></div>'\n                    } else if (type === 'listen') {\n                        s += '<div id=\"player_listen\"></div>'\n                    }\n                    s += `<div class=\"divider\"><b><span id=\"practice_num\">0</span> 次</b></div></div>`\n                    s += `<div class=\"dmx_center${type === 'listen' ? ' dmx_hide' : ''}\"><button class=\"dmx_button medium\" id=\"next_but\">下一句 (<span>${key + 1}</span>/${len})</button></div>`\n                    if (type === 'listen') {\n                        s += `<div class=\"dmx_left dmx_form_item\">\n                            <div class=\"item_label\">播放次数</div>\n                            <div class=\"item_content number\"><input id=\"player_num\" type=\"number\" value=\"2\" class=\"item_input\"></div>\n                            <div class=\"ml_1\"><div class=\"dmx_button dmx_button_danger medium\" id=\"stop_but\">停止播放</div></div>\n                        </div>`\n                    }\n                    s += window.playerTips\n                    boxEl.innerHTML = s\n                    rmClassD(tabEl, 'active')\n                    addClass(e, 'active')\n                    playerInit(key, type)\n\n                    // 下一句\n                    $('next_but').addEventListener('click', function () {\n                        let nextKey = Number(el.parentNode.dataset.key) + 1\n                        let newKey = nextKey >= len ? 0 : nextKey\n                        el.parentNode.dataset.key = String(newKey)\n                        this.querySelector('span').innerText = String(newKey + 1)\n                        $('practice_num').innerText = 0\n                        rmClass($('player_sentence'), 'hide')\n                        playerInit(newKey, type)\n                    })\n\n                    // 停止播放\n                    if (type === 'listen') {\n                        $('stop_but').addEventListener('click', () => {\n                            listen.stop()\n                            listen.showControls()\n                        })\n                    }\n                })\n            })\n\n            // 初始\n            setTimeout(() => {\n                let el = S('.player_box u[data-type=\"skill\"]')\n                if (el) el.click()\n            }, 100)\n        })\n    })\n}\n\n// 加载播放器\nfunction playerInit(key, type) {\n    let maxDuration = 5000\n    let practiceNum = 0\n    let row = sentenceData[key] || {}\n    let sentence = row.sentence || ''\n    let words = row.words || ''\n    let records = row.records || 0\n    let days = row.days || 0\n    let practiceDate = row.practiceDate || ''\n\n    let senEl = $('player_sentence')\n    let nextEl = $('next_but')\n\n    // 显示句子\n    senEl.innerHTML = pointSentence(sentence, words, type === 'record')\n\n    // 练习次数\n    let setPracticeNum = function (n, isUpdate) {\n        let el = $('practice_num')\n        if (el) el.innerText = n\n\n        // 更新 DB\n        if (isUpdate) {\n            records++\n            if (practiceDate) {\n                let oldDate = getDate(practiceDate, true).replace(/\\D/g, '')\n                let nowDate = getDate(null, true).replace(/\\D/g, '')\n                if (oldDate < nowDate) days++\n            } else {\n                days++\n            }\n            practiceDate = new Date().toJSON()\n            row.records = records\n            row.days = days\n            row.practiceDate = practiceDate\n            db.update('sentence', row.id, {records, days, practiceDate})\n        }\n    }\n\n    // 加载完成\n    if (type === 'skill') {\n        listen = playerListen('player_listen', {\n            onReady: function (duration) {\n                let times = 2\n                if (duration > 10) times *= 2.5 // 时间越长，模仿越难\n                maxDuration = Math.ceil(duration * times) * 1000\n                record.setMaxDuration(maxDuration)\n            }\n        })\n        listen.loadBlob(row.blob)\n        record = playerRecord('player_record', {\n            showStartBut: true,\n            maxDuration,\n            onStart: () => {\n                window.isExercising = true // 用来限制练习状态时，不允许切换菜单\n                nextEl.disabled = true\n            },\n            onStop: () => {\n                compare.loadBlob(row.blob)\n                compare.once('finish', () => {\n                    let t = setTimeout(() => record.showStartBut(), maxDuration + 1000)\n                    setTimeout(() => {\n                        compare.loadBlob(record.blob)\n                        compare.once('finish', () => {\n                            clearTimeout(t)\n                            record.showStartBut()\n                            window.isExercising = false // 解除限制\n                            nextEl.disabled = false // 解除禁用\n                            setPracticeNum(++practiceNum, true) // 练习次数\n                            if (practiceNum === 10) addClass(senEl, 'hide') // 提升难度，隐藏文字\n                        })\n                    }, 100)\n                })\n            },\n        })\n        compare = playerCompare('player_compare')\n    } else if (type === 'record') {\n        listen = playerListen('player_listen', {\n            onReady: function (duration) {\n                let times = 2\n                if (duration > 10) times *= 2.5 // 时间越长，模仿越难\n                maxDuration = Math.ceil(duration * times) * 1000\n                record.setMaxDuration(maxDuration)\n            },\n            onPlay: () => {\n                window.isExercising = true // 用来限制练习状态时，不允许切换菜单\n                nextEl.disabled = true\n            },\n            onFinish: () => record.start(), // 开始录音\n        })\n        listen.loadBlob(row.blob)\n        record = playerRecord('player_record', {\n            maxDuration,\n            onStop: () => {\n                compare.loadBlob(row.blob)\n                compare.once('finish', () => {\n                    let t = setTimeout(() => listen.showControls(), maxDuration + 1000) // 显示开始录音按钮\n                    setTimeout(() => {\n                        compare.loadBlob(record.blob)\n                        compare.once('finish', () => {\n                            clearTimeout(t)\n                            listen.showControls() // 显示播放按钮\n                            window.isExercising = false // 解除限制\n                            nextEl.disabled = false // 解除禁用\n                            setPracticeNum(++practiceNum, true) // 练习次数\n                            if (practiceNum === 5) senEl.innerHTML = pointSentence(sentence, words) // 降低难度，显示文字\n                            if (practiceNum === 10) addClass(senEl, 'hide') // 提升难度，隐藏文字\n                        })\n                    }, 100)\n                })\n            }\n        })\n        compare = playerCompare('player_compare')\n    } else if (type === 'listen') {\n        listen = playerListen('player_listen', {\n            onFinish: () => {\n                listen.play()\n                let nEl = $('player_num')\n                let n = nEl && nEl.value ? Number(nEl.value) : 2\n                setPracticeNum(++practiceNum) // 练习次数\n                if (practiceNum > 10) addClass(senEl, 'hide') // 提升难度，隐藏文字\n                if (practiceNum >= n) {\n                    $('next_but').click()\n                    setTimeout(() => listen.play(), 100)\n                }\n            }\n        })\n        listen.loadBlob(row.blob)\n    }\n}\n\n// 解析重点词汇\nfunction pointSentence(sentence, words, isUnderscore) {\n    let s = HTMLEncode(sentence)\n    let arr = words.split('\\n')\n    arr = uniqueArray(arr)\n    for (let v of arr) {\n        v = HTMLEncode(v.trim())\n        if (!v) continue\n        v = v.replace(/([.?+*])/g, '\\\\$1')\n\n        let reg = new RegExp(`^(${v})\\\\W|\\\\W(${v})\\\\W|\\\\W(${v})$|^(${v})$`, 'g')\n        // console.log(reg)\n        s = s.replace(reg, (...args) => {\n            let str = args[0]\n            let word = args.slice(1, -2).join('')\n            if (isUnderscore) {\n                return str.replace(word, '___')\n            } else {\n                if (word === 'u') return str\n                return str.replace(word, `<u>${word}</u>`)\n            }\n        })\n    }\n\n    return s\n}\n\n// 修改句子\nfunction editSentence() {\n    D('.dmx_button[data-action=\"edit\"]').forEach(el => {\n        el.addEventListener('click', () => {\n            ddi({\n                title: '修改',\n                body: `<div id=\"sentence_form\">\n        <div class=\"dmx_form_item\">\n            <div class=\"item_label\">句子</div>\n            <div class=\"item_content\"><input name=\"sentence\" type=\"text\" autocomplete=\"off\" required class=\"item_input\"></div>\n        </div>\n        <div class=\"dmx_form_item\">\n            <div class=\"item_label\">生词</div>\n            <div class=\"item_content\"><textarea name=\"words\" autocomplete=\"off\" class=\"item_textarea\"></textarea></div>\n        </div>\n        <div class=\"dmx_form_item\">\n            <div class=\"item_label\">备注</div>\n            <div class=\"item_content\"><textarea name=\"remark\" autocomplete=\"off\" class=\"item_textarea\"></textarea></div>\n        </div>\n        <div class=\"dmx_right\">\n            <button class=\"dmx_button\" type=\"submit\">确认</button>\n        </div>\n    </div>`\n            })\n            let id = Number(el.parentNode.dataset.id)\n            let formEl = $('sentence_form')\n            let sentenceEl = formEl.querySelector('[name=\"sentence\"]')\n            let wordsEl = formEl.querySelector('[name=\"words\"]')\n            let remarkEl = formEl.querySelector('[name=\"remark\"]')\n            let submitEl = formEl.querySelector('button[type=\"submit\"]')\n            db.read('sentence', id).then(row => {\n                sentenceEl.value = row.sentence\n                wordsEl.value = row.words\n                remarkEl.value = row.remark\n            })\n            submitEl.addEventListener('click', () => {\n                let sentence = sentenceEl.value.trim()\n                if (!sentence) return dal('句子内容不能为空', 'error')\n                db.update('sentence', id, {\n                    sentence,\n                    words: wordsEl.value,\n                    remark: remarkEl.value,\n                }).then(_ => {\n                    removeDdi()\n                    initSentence(cateId)\n                }).catch(_ => dal('修改失败', 'error'))\n            })\n        })\n    })\n}\n\n// 删除句子\nfunction deleteSentence() {\n    D('.dmx_button[data-action=\"delete\"]').forEach(el => {\n        el.addEventListener('click', () => {\n            let id = Number(el.parentNode.dataset.id)\n            dco(`删除不可恢复，您确认要删除吗？`, () => {\n                db.delete('sentence', id).then(_ => initSentence(cateId)).catch(_ => dal('删除失败', 'error'))\n            })\n        })\n    })\n}\n\n// 显示批量操作按钮\nfunction selectBind() {\n    let eList = D('td.tb_checkbox input[type=\"checkbox\"]')\n    eList.forEach(el => {\n        el.addEventListener('click', () => {\n            let len = 0\n            eList.forEach(e => e.checked && len++)\n            ;(len > 0 ? addClass : rmClass)($('extra_but'), 'dmx_show')\n        })\n    })\n}\n\n// 导出\nfunction exportZip() {\n    $('export').addEventListener('click', async function () {\n        loading('打包下载...')\n        let zip = new JSZip()\n\n        // cate\n        await db.find('cate').then(arr => {\n            zip.file(`cate.json`, JSON.stringify(arr))\n        })\n\n        // sentence\n        let sentence = {}\n        let typeArr = {}\n        await db.find('sentence').then(arr => {\n            for (let v of arr) {\n                // zip.file(`${v.id}.json`, JSON.stringify(v))\n                zip.file(`mp3/${v.id}.mp3`, v.blob)\n                typeArr[v.id] = v.blob.type\n                delete v.blob\n            }\n            sentence = arr\n        })\n        zip.file(`sentence.json`, JSON.stringify(sentence))\n        zip.file(`mp3Type.json`, JSON.stringify(typeArr))\n\n        debug('zip generateAsync ...')\n        await zip.generateAsync({type: 'blob'}).then(function (blob) {\n            downloadZip(blob)\n            removeDdi()\n        }).catch(err => console.warn('zip generateAsync error:', err))\n    })\n}\n\n// 下载 ZIP\nfunction downloadZip(blob) {\n    let el = document.createElement('a')\n    el.href = URL.createObjectURL(blob)\n    el.download = `梦想划词翻译-${getDate().replace(/\\D/g, '')}.zip`\n    el.click()\n}\n\n// 导入\nfunction importZip() {\n    $('import').addEventListener('click', function () {\n        ddi({\n            title: '导入', body: `<div class=\"dmx_form_item\">\n                <div class=\"item_label\">清空数据</div>\n                <div class=\"item_content\"><input type=\"checkbox\" id=\"import_clear\"></div>\n            </div>\n            <div class=\"dmx_form_item\">\n                <div class=\"item_label\">初始统计</div>\n                <div class=\"item_content\"><input type=\"checkbox\" id=\"import_initial\"></div>\n            </div>\n            <div class=\"dmx_form_item\" style=\"padding:5px 0 15px\">\n                <button class=\"dmx_button\" id=\"upload_but\">选择文件...</button>\n            </div>`\n        })\n        let butEl = $('upload_but')\n        butEl.addEventListener('click', () => {\n            let inp = document.createElement('input')\n            inp.type = 'file'\n            inp.accept = 'application/zip'\n            inp.onchange = function () {\n                let files = this.files\n                if (files.length < 1) return\n                let f = files[0]\n                // if (f.type !== 'application/zip') return // windows 系统识别类型为 application/x-zip-compressed，从而导致 bug\n                if (!f.type.includes('zip')) {\n                    dal('请选择正确的压缩包文件！', 'error')\n                    return\n                }\n\n                butEl.disabled = true\n                butEl.innerText = '正在导入...'\n\n                let tStart = new Date()\n                let isClear = $('import_clear').checked\n                let isInitial = $('import_initial').checked\n                JSZip.loadAsync(f).then(async function (zip) {\n                    // zip.forEach((filename, file) => console.log(filename, file)) // zip 详情\n\n                    let errStr = ''\n                    let errNum = 0\n                    let errAppend = (e) => {\n                        errNum++\n                        errStr += e + JSON.stringify(e) + '\\n'\n                    }\n\n                    // mp3Type\n                    let mp3TypeObj = {}\n                    try {\n                        let mp3Type = await zip.file('mp3Type.json').async('text')\n                        mp3TypeObj = JSON.parse(mp3Type)\n                    } catch (e) {\n                        errAppend(e)\n                    }\n\n                    // cate\n                    let cateArr = []\n                    try {\n                        let cate = await zip.file('cate.json').async('text')\n                        cateArr = JSON.parse(cate)\n                    } catch (e) {\n                        errAppend(e)\n                    }\n\n                    // sentence\n                    let sentenceNum = 0\n                    let sentenceRepeat = 0 // 重复的句子\n                    let sentenceArr = []\n                    try {\n                        let sentence = await zip.file('sentence.json').async('text')\n                        sentenceArr = JSON.parse(sentence)\n                    } catch (e) {\n                        errAppend(e)\n                    }\n\n                    if (isClear) {\n                        // 清空数据\n                        db.clear('sentence').then(_ => debug('sentence clear finish.')).catch(e => errAppend(e))\n                        db.clear('cate').then(_ => debug('cate clear ok.')).catch(e => errAppend(e))\n\n                        // cate\n                        for (let v of cateArr) db.create('cate', v).catch(e => errAppend(e))\n\n                        // sentence\n                        for (let v of sentenceArr) {\n                            await zip.file(`mp3/${v.id}.mp3`).async('blob').then(b => {\n                                v.blob = b.slice(0, b.size, mp3TypeObj[v.id] || 'audio/mpeg') // 设置 blob 类型\n                            })\n                            if (isInitial) {\n                                v.records = 0\n                                v.days = 0\n                            }\n                            await db.create('sentence', v).then(r => {\n                                sentenceNum++\n                                butEl.innerText = `正在导入... ${sentenceNum}/${sentenceArr.length}`\n                                debug('sentence create:', v.id, r)\n                            }).catch(e => errAppend(e))\n                        }\n                    } else {\n                        // cate 对应表\n                        let cateMap = {}\n                        for (let v of cateArr) {\n                            let row = null\n                            await db.readByIndex('cate', 'cateName', v.cateName.trim()).then(r => row = r).catch(e => errAppend(e))\n                            if (!row) {\n                                // 不存在就创建\n                                let oldId = v.cateId\n                                delete v.cateId\n                                await db.create('cate', v).then(r => {\n                                    cateMap[oldId] = r.target.result // 对应新创建的ID\n                                }).catch(e => errAppend(e))\n                            } else {\n                                cateMap[v.cateId] = row.cateId // 存在就记录对应的ID\n                            }\n                        }\n\n                        // sentence\n                        for (let v of sentenceArr) {\n                            // 判断句子是否存在\n                            let sentence = null\n                            await db.readByIndex('sentence', 'sentence', v.sentence.trim()).then(r => sentence = r).catch(e => errAppend(e))\n                            if (sentence) {\n                                sentenceRepeat++\n                                continue // 如果存在，就跳过\n                            }\n\n                            // 初始统计\n                            if (isInitial) {\n                                v.records = 0\n                                v.days = 0\n                            }\n\n                            // 获取音频\n                            await zip.file(`mp3/${v.id}.mp3`).async('blob').then(b => {\n                                v.blob = b.slice(0, b.size, mp3TypeObj[v.id] || 'audio/mpeg') // 设置 blob 类型\n                            })\n\n                            // 写入数据库\n                            v.cateId = cateMap[v.cateId] || 0\n                            delete v.id\n                            await db.create('sentence', v).then(r => {\n                                sentenceNum++\n                                butEl.innerText = `正在导入... ${sentenceNum}/${sentenceArr.length}`\n                                debug('create sentenceId:', r.target.result)\n                            }).catch(e => errAppend(e))\n                        }\n                    }\n\n                    let okMsg = `导入完成<br> 导入：${sentenceNum} 条`\n                    if (sentenceRepeat > 0) okMsg += `，重复：${sentenceRepeat} 条`\n                    if (errNum > 0) {\n                        okMsg += `，错误：${errNum} 次`\n                        console.warn('errStr:', errStr)\n                    }\n                    okMsg += `<br>耗时：${new Date() - tStart} ms`\n                    dal(okMsg, 'success', () => {\n                        // location.reload()\n                        removeDdi()\n                        initCate(cateId)\n                        initSentence(cateId)\n                    })\n                }).catch(e => {\n                    dal('读取压缩包失败', 'error', () => removeDdi())\n                    debug('loadAsync error:', e)\n                })\n            }\n            inp.click()\n        })\n    })\n}\n\n// 设置\nfunction openSetting() {\n    $('setting').addEventListener('click', function () {\n        ddi({\n            title: '设置', body: `<div class=\"dmx_form_item\">\n            <div class=\"item_label\">展示顺序</div>\n                <div class=\"item_content number\">\n                    <select id=\"order_by\">\n                        <option value=\"obverse\">正序</option>\n                        <option value=\"reverse\">倒序</option>\n                        <option value=\"random\">随机</option>\n                    </select>\n                </div>\n            </div>\n            <div class=\"dmx_right\">\n                <button class=\"dmx_button\" id=\"save_but\">保存</button>\n            </div>`\n        })\n        $('order_by').value = localStorage['orderBy'] || 'obverse'\n        $('save_but').addEventListener('click', () => {\n            localStorage.setItem('orderBy', $('order_by').value)\n            removeDdi()\n            initSentence(cateId)\n        })\n    })\n}\n\n// 全选/取消全选\nfunction selectAll() {\n    $('selectAll').addEventListener('click', function () {\n        let eList = D('td.tb_checkbox input[type=\"checkbox\"]')\n        eList.forEach(el => el.checked = this.checked)\n        ;(this.checked && eList.length > 0 ? addClass : rmClass)($('extra_but'), 'dmx_show')\n    })\n}\n\n// 取消选中\nfunction selectCancel() {\n    $('selectAll').checked = false\n    rmClass($('extra_but'), 'dmx_show')\n}\n\n// 随机数组\nfunction shuffle(arr) {\n    for (let k = 0; k < arr.length; k++) {\n        let i = Math.floor(Math.random() * arr.length);\n        [arr[k], arr[i]] = [arr[i], arr[k]]\n    }\n    return arr\n}\n"
  },
  {
    "path": "src/js/frame.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\n;(function () {\n    let frame = window.frameElement\n    if (frame) {\n        let top = null\n        try {\n            top = window.top || window.parent\n        } catch (e) {\n            return\n        }\n        if (!top) return\n        document.addEventListener('mouseup', function (e) {\n            let bcr = frame.getBoundingClientRect()\n            let clientX = e.clientX + bcr.left\n            let clientY = e.clientY + bcr.top\n            let text = window.getSelection().toString().trim()\n            if (text) top.postMessage({text: text, clientX: clientX, clientY: clientY}, '*')\n        })\n        document.addEventListener('mouseup', function () {\n            top._MxDialog && top._MxDialog.hide()\n        })\n    }\n})()\n"
  },
  {
    "path": "src/js/history.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nlet db\nlet bg = B.getBackgroundPage()\ndocument.addEventListener('DOMContentLoaded', async function () {\n    await idb('history', 1, initHistory).then(r => db = r)\n\n    historyList() // 展示列表\n    selectAll() // 全选/取消全选\n    deleteMultiple() // 批量删除\n    openSetting() // 设置\n})\n\nfunction historyList() {\n    let thLen = D('#history_box thead th').length\n    let tbodyEl = S('#history_box tbody')\n    tbodyEl.innerHTML = `<tr><td class=\"table_empty\" colspan=\"${thLen}\"><div class=\"dmx-icon dmx-icon-loading\"></div></td></tr>`\n    db.count('history').then(n => $('historyNum').innerText = n)\n    db.find('history', {direction: 'prev'}).then(arr => {\n        if (arr.length < 1) {\n            tbodyEl.innerHTML = `<tr><td class=\"table_empty\" colspan=\"${thLen}\">暂无内容</td></tr>`\n            return\n        }\n\n        // console.log(JSON.stringify(arr))\n        let s = ''\n        arr.forEach((v, k) => {\n            s += `<tr>\n                <td class=\"tb_checkbox\"><input type=\"checkbox\" value=\"${v.id}\"></td>\n                <td class=\"tb_sentence\">${HTMLEncode(v.content)}</td>\n                <td class=\"tb_date\" title=\"${getDate(v.createDate)}\">${getDate(v.createDate, true)}</td>\n                <td class=\"tb_operate2\" data-id=\"${v.id}\" data-key=\"${k}\">\n                    <a class=\"dmx_button dmx_button_default\" title=\"${v.formTitle}\" href=\"${v.formUrl}\" target=\"_blank\">来源</a>\n                    <div class=\"dmx_button dmx_button_danger\" data-action=\"delete\">删除</div>\n                </td>\n            </tr>`\n        })\n        tbodyEl.innerHTML = s\n\n        // 选中\n        let eList = D('#history_body input[type=\"checkbox\"]')\n        eList.forEach(el => {\n            el.addEventListener('click', () => {\n                let len = 0\n                eList.forEach(e => e.checked && len++)\n                ;(len > 0 ? addClass : rmClass)($('extra_but'), 'dmx_show') // 是否显示批量删除按钮\n            })\n        })\n\n        // 删除\n        D('.dmx_button[data-action=\"delete\"]').forEach(el => {\n            el.addEventListener('click', () => {\n                let id = Number(el.parentNode.dataset.id)\n                dco(`删除不可恢复，您确认要删除吗？`, () => {\n                    db.delete('history', id).then(_ => historyList()).catch(_ => dal('删除失败', 'error'))\n                })\n            })\n        })\n    })\n}\n\n// 批量删除\nfunction deleteMultiple() {\n    $('delete_multiple').addEventListener('click', function () {\n        let eList = D('#history_body input[type=\"checkbox\"]:checked')\n        dco(`删除不可恢复，您确认要删除这 ${eList.length} 条数据吗？`, () => {\n            eList.forEach(el => {\n                db.delete('history', Number(el.value)).catch(_ => dal('删除失败', 'error'))\n            })\n            setTimeout(_ => historyList(), 1000)\n\n            // 取消\n            rmClass($('extra_but'), 'dmx_show')\n            $('selectAll').checked = false\n        })\n    })\n}\n\n// 全选/取消全选\nfunction selectAll() {\n    $('selectAll').addEventListener('click', function () {\n        let eList = D('#history_body input[type=\"checkbox\"]')\n        eList.forEach(el => el.checked = this.checked)\n        ;(this.checked && eList.length > 0 ? addClass : rmClass)($('extra_but'), 'dmx_show') // 是否显示批量删除按钮\n    })\n}\n\n// 设置\nfunction openSetting() {\n    $('setting').addEventListener('click', function () {\n        ddi({\n            title: '设置', body: `<div class=\"dmx_form_item\">\n            <div class=\"item_label\">最大记录数</div>\n                <div class=\"item_content number\"><input id=\"history_max\" type=\"number\" value=\"${bg.historyMax}\" min=\"0\" class=\"item_input\"></div>\n            </div>\n            <div class=\"dmx_right\">\n                <button class=\"dmx_button\" id=\"save_but\">保存</button>\n            </div>`\n        })\n        $('save_but').addEventListener('click', () => {\n            let maxNum = $('history_max').value\n            bg.settingHistory(maxNum)\n            removeDdi()\n        })\n    })\n}\n"
  },
  {
    "path": "src/js/more.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\ndocument.addEventListener('DOMContentLoaded', function () {\n    $('trans_window').addEventListener('click', () => sendMessage({action: 'transWindow'}))\n    $('allow_select').addEventListener('click', () => {\n        sendMessage({action: 'onAllowSelect'}).then(_ => {\n            if (new URL(location.href).searchParams.get('isSome') === 'true') parent.window.close()\n        })\n    })\n    D('[data-href]').forEach(e => e.addEventListener('click', () => {\n        let url = e.dataset.href\n        if (url.substr(0, 4) !== 'http') url = B.root + 'html/' + url\n        sendMessage({action: 'openUrl', url})\n    }))\n    if (isFirefox) S('[data-href=\"speak.html\"]').remove() // Firefox 不支持这个功能\n})\n"
  },
  {
    "path": "src/js/player.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nwindow.playerTips = `<div class=\"learn_points\">\n    <div class=\"title\"><b>练习要点</b></div>\n    <div class=\"case\">\n        1、带上耳机听发音，更有助听清声音中的细节。<br>\n        2、练句子，把生词融入句子中。<br>\n        3、先理解句子含义。如有生词时，应先根据经验猜，然后才查词典确认猜的对不对。首选查英英词典，推荐朗文或格林斯之类的词典，把英语当工具使用，用英语思维去学习生词。如果还没达到这个能力，才考虑查中英词典。当理解含义后，忘掉所有文字，不管中文还是英文，理解含义为最终目的。<br>\n        4、认真听句子发音，并模仿发音。模仿发音时，需要忘掉所有文字，脑海里应浮现出句子运用的场景，把自己置身在场景中，投入感情和动用感官（视觉，听觉，嗅觉，味觉，触觉）去模仿。<br>\n        5、认真听自己的发音和原音的差异。<br>\n        6、重复第 4-5 步，自我修正发音。直到你感觉语速能跟上，发音接近，并且很流利为止。「练习次数多多益善」\n    </div>\n</div>\n\n<div class=\"learn_points\">\n    <div class=\"title\"><b>语速问题</b></div>\n    <div class=\"case\">\n        如果感觉语速跟不上，这证明缺乏锻炼，不要想着去降低播放速度，而需要鼓励自己，锻炼你的耳朵和嘴巴。开始练习发音吧，当你重复练习 N 次后，你会感觉语速变慢了。相信自己，你可以的！\n    </div>\n</div>\n\n<div class=\"learn_points\">\n    <div class=\"title\"><b>语言运用</b></div>\n    <div class=\"case\">\n        语言是技能，最重要的是实战运用。所以句子练熟之后呢？就需要去使用这些句子，如果你有个友好耐心的老外朋友当陪练那当然最好，没有也不用特别在意，在学习外语过程中，用外语跟自己说话，要比跟别人交流更重要。\n    </div>\n</div>\n\n<div class=\"learn_points\">\n    <div class=\"title\"><b>学习方法</b></div>\n    <div class=\"case\">\n        如果你对「正确学习英语的方法」感兴趣，请阅读《<a href=\"https://mengxiang.net/post/english.html\" target=\"_blank\">英语学习秘籍</a>》。\n    </div>\n</div>`\n\n// 播放\nfunction playerListen(id, options) {\n    if (!window._playerListen) window._playerListen = []\n    let p = window._playerListen\n    if (p[id]) {\n        p[id].destroy()\n    }\n\n    // 创建元素\n    let did = document.getElementById(id)\n    let wid = id + '_waveform'\n    did.innerHTML = `<div class=\"dmx_player\">\n    <div class=\"dmx_p_top\">\n        <div class=\"dmx_p_title\">Listen</div>\n        <div class=\"dmx_p_time\"><span class=\"dmx_p_current\"></span><span class=\"dmx_p_duration\"></span></div>\n    </div>\n    <div class=\"dmx_surfer\" id=\"${wid}\"></div>\n    <div class=\"dmx_controls\"><button type=\"button\">Play</button></div>\n</div>`\n\n    // 初始参数\n    let o = Object.assign({\n        url: '',\n        onReady: null,\n        onPlay: null,\n        onFinish: null,\n    }, options)\n\n    // 基本元素\n    let p_current = did.querySelector('.dmx_p_current')\n    let p_duration = did.querySelector('.dmx_p_duration')\n    let p_controls = did.querySelector('.dmx_controls')\n\n    // 创建播放器\n    let wsId = document.getElementById(wid)\n    let height = wsId.clientHeight\n    let ws, maxDuration\n    ws = WaveSurfer.create({\n        container: wsId,\n        height: height,\n        barWidth: 3,\n        barHeight: 2,\n        backend: 'WebAudio',\n        backgroundColor: '#66CCCC', // 背景色\n        waveColor: '#CCFF66', // 波纹色\n        progressColor: '#FF9900', // 填充色(播放后)\n        cursorColor: '#666633', // 指针色\n        hideScrollbar: true,\n    })\n    o.url && ws.load(o.url)\n    ws.hideControls = function () {\n        p_controls.style.display = 'none'\n    }\n    ws.showControls = function () {\n        p_controls.style.display = 'flex'\n    }\n    ws.on('ready', function () {\n        maxDuration = ws.getDuration()\n        if (maxDuration > 0) {\n            p_duration.innerText = ' / ' + humanTime(maxDuration)\n            p_current.innerText = '00:00:000'\n        }\n        typeof o.onReady === 'function' && o.onReady(maxDuration)\n    })\n    ws.on('loading', function (percents) {\n        p_controls.style.display = percents === 100 ? 'flex' : 'none'\n    })\n    ws.on('audioprocess', function (duration) {\n        p_current.innerText = humanTime(duration)\n    })\n    ws.on('play', function () {\n        ws.hideControls()\n        typeof o.onPlay === 'function' && o.onPlay.call(ws)\n    })\n    ws.on('finish', function () {\n        p_current.innerText = humanTime(maxDuration)\n        typeof o.onFinish === 'function' ? o.onFinish.call(ws) : ws.showControls()\n    })\n    p_controls.addEventListener('click', ws.playPause.bind(ws)) // 绑定事件\n    window._playerListen[id] = ws\n    return ws\n}\n\n// 录音\nfunction playerRecord(id, options) {\n    if (!navigator.mediaDevices) return\n    if (!window._playerRecord) window._playerRecord = []\n    let p = window._playerRecord\n    if (p[id]) {\n        if (p[id].ws) p[id].ws.destroy()\n        if (p[id].recorder) p[id].recorder.destroy()\n    }\n\n    // 创建元素\n    let did = document.getElementById(id)\n    let wid = id + '_waveform'\n    did.innerHTML = `<div class=\"dmx_player\">\n    <div class=\"dmx_p_top\">\n        <div class=\"dmx_p_title\">Record</div>\n        <div class=\"dmx_p_time\"><span class=\"dmx_p_current\"></span><span class=\"dmx_p_duration\"></span></div>\n    </div>\n    <div class=\"dmx_surfer\" id=\"${wid}\"></div>\n    <div class=\"dmx_controls\">\n        <div class=\"dmx_circle dmx_reverse\"><i class=\"dmx-icon dmx-icon-voice\"></i></div>\n        <button type=\"button\" style=\"display:none\">Record</button>\n    </div>\n</div>`\n\n    // 初始参数\n    let o = Object.assign({\n        showStartBut: false,\n        maxDuration: 5 * 1000,\n        mp3Enable: true, // safari 浏览器才启用\n        onStart: null,\n        onStop: null,\n    }, options)\n\n    // 元素\n    let p_current = did.querySelector('.dmx_p_current')\n    let p_duration = did.querySelector('.dmx_p_duration')\n    let p_circle = did.querySelector('.dmx_circle')\n    let p_start = did.querySelector('.dmx_controls button')\n    let wsId = document.getElementById(wid)\n    let height = wsId.clientHeight\n\n    // 初始对象\n    let obj = {\n        duration: 0,\n        recordStartTime: 0, // 开始录制时间\n        recorder: null,\n        microphone: null,\n        ws: null,\n        active: false,\n        ButEl: {},\n        blob: null,\n    }\n\n    // 录音中按钮效果\n    obj.ButEl.start = () => addClass(p_circle, 'dmx_on')\n\n    // 录音停止按钮效果\n    obj.ButEl.stop = () => rmClass(p_circle, 'dmx_on')\n\n    // 绑定开始录音事件\n    p_start.addEventListener('click', function () {\n        !obj.active && obj.start()\n    })\n\n    // 绑定停止录音事件\n    p_circle.addEventListener('click', function () {\n        if (!obj.active) return\n\n        // 限制最短录音时长\n        let minTime = 500\n        if (!obj.recordStartTime || ((new Date() * 1) - obj.recordStartTime < minTime)) return\n\n        obj.stop()\n    })\n\n    obj.showStartBut = function () {\n        p_start.style.display = 'flex'\n        p_circle.style.display = 'none'\n    }\n    obj.hideStartBut = function () {\n        p_start.style.display = 'none'\n        p_circle.style.display = 'flex'\n    }\n\n    // 初始按钮显示\n    o.showStartBut ? obj.showStartBut() : obj.hideStartBut()\n\n    // 定时器\n    let t, tEnd\n    let timeOutStart = function () {\n        obj.recordStartTime = new Date() * 1 // 开始录制时间\n        tEnd = (new Date() * 1) + Number(o.maxDuration)\n        t = setInterval(function () {\n            let remain = tEnd - (new Date() * 1)\n            if (remain > 0) {\n                p_current.innerText = humanTime((o.maxDuration - remain) / 1000)\n            } else {\n                obj.stop()\n                clearInterval(t)\n                p_current.innerText = humanTime(o.maxDuration / 1000)\n            }\n        }, 30)\n    }\n    let timeOutStop = function () {\n        if (tEnd < (new Date() * 1)) return\n        let remain = tEnd - (new Date() * 1)\n        if (remain > 0) {\n            p_current.innerText = humanTime((o.maxDuration - remain) / 1000)\n            clearInterval(t)\n        }\n    }\n\n    // 设置最大录音时长\n    obj.setMaxDuration = function (maxDuration) {\n        o.maxDuration = Number(maxDuration)\n    }\n\n    // 捕获麦克风\n    obj.captureMicrophone = function (callback) {\n        navigator.mediaDevices.getUserMedia({audio: true}).then(function (stream) {\n            obj.microphone = stream\n            callback(obj.microphone)\n        })\n    }\n\n    // 停止麦克风\n    obj.stopMicrophone = function () {\n        if (!obj.microphone) return\n        if (obj.microphone.getTracks) {\n            // console.log('microphone getTracks stop...');\n            obj.microphone.getTracks().forEach(stream => stream.stop())\n        } else if (obj.microphone.stop) {\n            // console.log('microphone stop...');\n            obj.microphone.stop()\n        }\n        obj.microphone = null\n    }\n\n    // 销毁\n    obj.destroy = function () {\n        obj.stopMicrophone()\n        if (obj.recorder) {\n            obj.recorder.destroy()\n            obj.recorder = null\n        }\n        if (obj.ws) {\n            obj.ws.destroy()\n            obj.ws = null\n        }\n    }\n\n    // 开始录制\n    obj.start = function () {\n        if (obj.active) return\n        obj.active = true\n        obj.recordStartTime = 0\n\n        // 切换按钮显示\n        if (o.showStartBut) obj.hideStartBut()\n\n        // 开始录音回调\n        typeof o.onStart === 'function' && o.onStart.call(obj)\n\n        // 初始时间\n        p_duration.innerText = ' / ' + humanTime(o.maxDuration / 1000)\n        p_current.innerText = '00:00:000'\n\n        if (obj.recorder) obj.recorder.destroy()\n        if (obj.ws === null) {\n            obj.ws = WaveSurfer.create({\n                container: wsId,\n                height: height,\n                barWidth: 3,\n                barHeight: 2,\n                cursorColor: '#CED5E2', // 指针色\n                hideScrollbar: true,\n                interact: false,\n                plugins: [WaveSurfer.microphone.create()]\n            })\n            obj.ws.microphone.on('deviceReady', function (stream) {\n                obj.microphone = stream\n                setTimeout(() => {\n                    let options = isFirefox ? {disableLogs: true} : {type: 'audio', disableLogs: true}\n                    obj.recorder = window.RecordRTC(stream, options)\n                    obj.recorder.startRecording()\n\n                    timeOutStart() // 定时器\n                    obj.ButEl.start() // 录音中\n                }, 300)\n            })\n            obj.ws.microphone.on('deviceError', function (code) {\n                console.warn('Device error: ' + code)\n            })\n            obj.ws.microphone.start()\n        } else {\n            !obj.ws.microphone.active && obj.ws.microphone.start()\n        }\n    }\n\n    // 停止录音\n    obj.stop = function () {\n        if (!obj.active) return\n        obj.active = false\n\n        timeOutStop() // 停止定时器\n        obj.ButEl.stop() // 停止录音\n\n        // 停止录音器波纹\n        obj.ws.microphone.active && obj.ws.microphone.stop()\n\n        // 停止录音\n        obj.recorder.stopRecording(function () {\n            // obj.url = this.toURL();\n            obj.blob = this.getBlob()\n            typeof o.onStop === 'function' && o.onStop.call(obj) // 停止录音回调\n        })\n    }\n    window._playerRecord[id] = obj\n    return obj\n}\n\n// 对比\nfunction playerCompare(id, options) {\n    if (!window._playerCompare) window._playerCompare = []\n    let p = window._playerCompare\n    if (p[id]) {\n        p[id].destroy()\n    }\n\n    let did = document.getElementById(id)\n    let wid = id + '_waveform'\n    did.innerHTML = `<div class=\"dmx_player\">\n    <div class=\"dmx_p_top\">\n        <div class=\"dmx_p_title\">Compare</div>\n        <div class=\"dmx_p_time\"><span class=\"dmx_p_current\"></span><span class=\"dmx_p_duration\"></span></div>\n    </div>\n    <div class=\"dmx_surfer\" id=\"${wid}\"></div>\n    <div class=\"dmx_controls\"><div class=\"dmx_circle\"><i class=\"dmx-icon dmx-icon-headset-c\"></i></div></div>\n</div>`\n\n    // 初始参数\n    let o = Object.assign({\n        url: '',\n        autoPlay: true,\n    }, options)\n\n    // 初始化\n    let p_current = did.querySelector('.dmx_p_current')\n    let p_duration = did.querySelector('.dmx_p_duration')\n    let but = did.querySelector('.dmx_circle')\n\n    // 创建播放器\n    let wsId = document.getElementById(wid)\n    let height = wsId.clientHeight\n    let ws = WaveSurfer.create({\n        container: wsId,\n        height: height,\n        barWidth: 3,\n        barHeight: 2,\n        waveColor: '#FFFF66', // 波纹色\n        progressColor: '#FFCC99', // 填充色(播放后)\n        cursorColor: '#333', // 指针色\n        hideScrollbar: true,\n        interact: false,\n    })\n    o.url && ws.load(o.url)\n    let maxDuration, isClickPlay\n    ws.on('ready', function () {\n        maxDuration = ws.getDuration()\n        if (maxDuration > 0) {\n            p_duration.innerText = ' / ' + humanTime(maxDuration)\n            p_current.innerText = '00:00:000'\n        }\n        ws.setBackgroundColor('#66b1ff')\n\n        // 自动播放\n        if (o.autoPlay) {\n            isClickPlay = true\n            ws.play()\n        }\n    })\n    ws.on('audioprocess', function (duration) {\n        p_current.innerText = humanTime(duration)\n    })\n    ws.on('play', function () {\n        addClass(but, 'dmx_on')\n    })\n    ws.on('finish', function () {\n        isClickPlay = false\n        p_current.innerText = humanTime(maxDuration)\n        ws.setBackgroundColor('')\n        ws.empty()\n        rmClass(but, 'dmx_on')\n    })\n    window._playerCompare[id] = ws\n\n    // 解决 Safari 浏览器自动播放音频失败问题\n    // but.addEventListener('click', () => {\n    //     isClickPlay && ws.play()\n    // })\n    return ws\n}\n\nfunction humanTime(s, isSecond) {\n    if (s <= 0) return isSecond ? '00:00:00' : '00:00:000'\n    let hs = Math.floor(s / 3600)\n    let ms = hs > 0 ? Math.floor((s - hs * 3600) / 60) : Math.floor(s / 60)\n    if (isSecond) {\n        return zero(hs) + ':' + zero(ms) + ':' + zero(Math.floor(s % 60))\n    } else {\n        let se = (s % 60).toFixed(3).replace('.', ':')\n        if (hs > 0) {\n            return zero(hs) + ':' + zero(ms) + ':' + zero(se, 6)\n        } else {\n            return zero(ms) + ':' + zero(se, 6)\n        }\n    }\n}\n"
  },
  {
    "path": "src/js/popup.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nwindow.isPopup = true\n"
  },
  {
    "path": "src/js/record.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nlet bg = B.getBackgroundPage()\nlet audioSrc = bg.audioSrc || {}\nlet maxDuration = 5000\nlet practiceNum = 0\nlet listen = {}, listen2 = {}, record, compare\ndocument.addEventListener('DOMContentLoaded', function () {\n    playerInit()\n\n    // 加载音频\n    setTimeout(() => {\n        if (audioSrc.blob) {\n            listen.loadBlob(audioSrc.blob)\n        } else if (audioSrc.url) {\n            bg.getAudioBlob(audioSrc.url).then(b => {\n                listen.loadBlob(b)\n                audioSrc.blob = b\n            })\n        }\n    }, 200)\n\n    let record_box = $('record_box')\n    let favorite_form = $('favorite_form')\n    let favorite_but = $('favorite_but')\n    let sentence_form = $('sentence_form')\n    let back_but = $('back_but')\n    let sentenceInp = S('input[name=\"sentence\"]')\n    let urlInp = S('input[name=\"url\"]')\n    let wordsTex = S('textarea[name=\"words\"]')\n\n    // 练习提示\n    record_box.insertAdjacentHTML('beforeend', window.playerTips)\n\n    // 添加收藏\n    favorite_but.addEventListener('click', () => {\n        addClass(record_box, 'dmx_hide')\n        addClass(favorite_form, 'dmx_show')\n\n        if (bg.textTmp) sentenceInp.value = bg.textTmp\n\n        let {url, blob} = audioSrc\n        listen2 = playerListen('player_listen2')\n        if (blob) listen2.loadBlob(blob)\n        if (url) urlInp.value = url\n    })\n\n    // 修改链接\n    urlInp.addEventListener('blur', () => {\n        let url = urlInp.value.trim()\n        if (url && url !== audioSrc.url) bg.getAudioBlob(url).then(blob => listen2.loadBlob(blob))\n    })\n\n    // 返回\n    back_but.addEventListener('click', () => {\n        rmClass(record_box, 'dmx_hide')\n        rmClass(favorite_form, 'dmx_show')\n    })\n\n    // 提交表单\n    sentence_form.addEventListener('submit', (e) => {\n        e.preventDefault()\n        idb('favorite', 1, initFavorite).then(async db => {\n            // 如果链接修改过，重新获取二进制文件\n            let url = urlInp.value.trim()\n            if (url && url !== audioSrc.url) await bg.getAudioBlob(url).then(b => audioSrc.blob = b)\n\n            await db.create('sentence', {\n                cateId: 0,\n                sentence: sentenceInp.value.trim(),\n                words: wordsTex.value.trim(),\n                remark: '',\n                records: 0,\n                days: 0,\n                url,\n                blob: audioSrc.blob,\n                practiceDate: '',\n                createDate: new Date().toJSON(),\n            }).then(() => {\n                dal('添加完成', 'success', () => {\n                    sentenceInp.value = ''\n                    wordsTex.value = ''\n                    back_but.click()\n                })\n            }).catch(e => {\n                // console.log(e)\n                let err = e.target.error.message\n                let msg = '添加失败'\n                if (err && err.includes('uniqueness requirements')) msg = '句子已存在，请勿重复添加'\n                dal(msg, 'error')\n            })\n        })\n    })\n})\n\n// 重新渲染\nwindow.addEventListener('resize', function (e) {\n    _setTimeout('resize', () => {\n        if (audioSrc.blob) listen.loadBlob(audioSrc.blob)\n    }, 1000)\n})\n\nfunction playerInit() {\n    listen = playerListen('player_listen', {\n        onReady: function (duration) {\n            let times = 2\n            if (duration > 10) times *= 2.5 // 时间越长，模仿越难\n            maxDuration = Math.ceil(duration * times) * 1000\n            record.setMaxDuration(maxDuration)\n        },\n        onFinish: () => {\n            record.start() // 开始录音\n        },\n    })\n\n    record = playerRecord('player_record', {\n        maxDuration,\n        onStop: () => {\n            compare.loadBlob(audioSrc.blob)\n            compare.once('finish', () => {\n                let t = setTimeout(() => listen.showControls(), maxDuration + 1000)  // 显示播放按钮\n                setTimeout(() => {\n                    // compare.load(URL.createObjectURL(record.blob))\n                    compare.loadBlob(record.blob)\n                    compare.once('finish', () => {\n                        clearTimeout(t)\n                        listen.showControls() // 显示播放按钮\n                        $('practice_num').innerText = ++practiceNum // 练习次数\n                    })\n                }, 100)\n            })\n        },\n    })\n\n    compare = playerCompare('player_compare')\n}\n"
  },
  {
    "path": "src/js/setting.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nlet conf, setting\nlet searchText, searchList\ndocument.addEventListener('DOMContentLoaded', async function () {\n    await fetch('../conf/conf.json').then(r => r.json()).then(r => {\n        conf = r\n    })\n    await storageSyncGet(['setting', 'searchText']).then(function (r) {\n        setting = r.setting\n        searchText = r.searchText || ''\n    })\n    await fetch('../conf/searchText.txt').then(r => r.text()).then(r => {\n        searchText = searchText || r.trim()\n        searchList = getSearchKey(searchText)\n    })\n    init()\n    // debug('conf:', conf)\n    // debug('setting:', setting)\n})\n\nfunction init() {\n    // 词典发音列表\n    let dictionarySoundList = {}\n    for (let [k, v] of Object.entries(conf.dictionaryList)) {\n        if (conf.dictionarySoundExcluded.includes(k)) continue // 排除\n        dictionarySoundList[k] = v\n    }\n\n    // 绑定导航\n    navigate('navigate', '.setting_box')\n\n    // 初始参数\n    settingBoxHTML('setting_translate_list', 'translateList', conf.translateList)\n    settingBoxHTML('setting_translate_tts_list', 'translateTTSList', conf.translateTTSList)\n    settingBoxHTML('setting_dictionary_list', 'dictionaryList', conf.dictionaryList)\n    settingBoxHTML('setting_dictionary_sound_list', 'dictionarySoundList', dictionarySoundList)\n\n    // 初始可替换的本机朗读参数\n    if (isFirefox) {\n        $('local_box').style.display = 'none'\n    } else {\n        initLocalSoundReplace()\n    }\n\n    // 设置值 & 绑定事件\n    setBindValue('scribble', setting.scribble)\n    setBindValue('excludeChinese', setting.excludeChinese)\n    setBindValue('excludeSymbol', setting.excludeSymbol)\n    setBindValue('excludeNumber', setting.excludeNumber)\n    setBindValue('position', setting.position)\n    setBindValue('allowSelect', setting.allowSelect)\n    setBindValue('autoCopy', setting.autoCopy)\n    setBindValue('autoPaste', setting.autoPaste)\n    setBindValue('autoChange', setting.autoChange)\n    setBindValue('autoWords', setting.autoWords)\n    setBindValue('cutHumpName', setting.cutHumpName)\n    setBindValue('translateList', setting.translateList)\n    setBindValue('translateTTSList', setting.translateTTSList)\n    setBindValue('localSoundReplace', setting.localSoundReplace)\n    setBindValue('translateOCR', setting.translateOCR || 'CHN_ENG')\n    setBindValue('ocrType', setting.ocrType)\n    setBindValue('translateThin', setting.translateThin)\n    setBindValue('hideOriginal', setting.hideOriginal)\n    setBindValue('autoLanguage', setting.autoLanguage)\n    setBindValue('autoConfirm', setting.autoConfirm)\n    setBindValue('dictionaryList', setting.dictionaryList)\n    setBindValue('dictionarySoundList', setting.dictionarySoundList)\n    setBindValue('dictionaryReader', setting.dictionaryReader)\n\n    // 绑定顺序展示\n    bindSortHTML('展示顺序：', 'setting_translate_sort', 'translateList', setting.translateList, conf.translateList)\n    bindSortHTML('朗读顺序：', 'setting_translate_tts_sort', 'translateTTSList', setting.translateTTSList, conf.translateTTSList)\n    bindSortHTML('展示顺序：', 'setting_dictionary_sort', 'dictionaryList', setting.dictionaryList, conf.dictionaryList)\n    bindSortHTML('朗读顺序：', 'setting_dictionary_sound_sort', 'dictionarySoundList', setting.dictionarySoundList, dictionarySoundList)\n\n    // 搜索设置功能\n    initSearch()\n\n    // 绑定是否显示\"朗读\"参数\n    bindShow('setting_dictionary_reader', 'dictionarySoundList', setting.dictionarySoundList)\n\n    // 本机 TTS 设置\n    localTtsSetting()\n    searchListSetting()\n\n    // 文字识别设置\n    settingOcr()\n\n    // 重置设置\n    $('clearSetting').addEventListener('click', clearSetting)\n}\n\nfunction initSearch() {\n    settingBoxHTML('setting_search_list', 'searchList', searchList)\n    settingBoxHTML('setting_search_menus', 'searchMenus', searchList)\n    settingBoxHTML('setting_search_side', 'searchSide', searchList)\n\n    setBindValue('searchList', setting.searchList)\n    setBindValue('searchMenus', setting.searchMenus)\n    setBindValue('searchSide', setting.searchSide)\n\n    bindSortHTML('展示顺序：', 'setting_search_sort', 'searchList', setting.searchList, searchList)\n    bindSortHTML('展示顺序：', 'setting_search_menus_sort', 'searchMenus', setting.searchMenus, searchList)\n    bindSortHTML('展示顺序：', 'setting_search_side_sort', 'searchSide', setting.searchSide, searchList)\n\n    // 绑定右键菜单设置\n    bindSearchMenus()\n}\n\nfunction initLocalSoundReplace() {\n    let s = '<option value=\"\">默认</option>'\n    let list = conf.translateList\n    Object.keys(list).forEach(k => {\n        s += `<option value=\"${k}\">${list[k]}朗读</option>`\n    })\n    N('localSoundReplace')[0].innerHTML = s\n}\n\nfunction getSearchKey(s) {\n    let r = {}\n    Object.keys(getSearchList(s)).forEach(k => r[k] = k)\n    return r\n}\n\nfunction navigate(navId, contentSel) {\n    let nav = $(navId)\n    let el = nav.querySelectorAll('u')\n    let conEl = document.querySelectorAll(contentSel)\n    el.forEach(fn => {\n        fn.addEventListener('click', function () {\n            // 修改活动样式\n            el.forEach(elu => {\n                rmClass(elu, 'active')\n            })\n            addClass(this, 'active')\n\n            // 显示对应框\n            conEl.forEach(elc => {\n                elc.style.display = 'none'\n            })\n            let target = this.getAttribute('target')\n            $(target).style.display = 'block'\n        })\n    })\n    nav.querySelector('u.active').click() // 激活初始值\n}\n\nfunction setBindValue(name, value) {\n    setValue(name, value)\n    bindValue(name, value)\n}\n\nfunction setValue(name, value) {\n    let isArr = isArray(value)\n    let el = N(name)\n    el && el.forEach(v => {\n        let nodeName = v.nodeName\n        if (nodeName === 'SELECT') {\n            v.value = value\n        } else if (nodeName === 'INPUT') {\n            if (isArr) {\n                let checked = false\n                for (let val of value) {\n                    if (v.value === val) {\n                        checked = true\n                        break\n                    }\n                }\n                v.checked = checked\n            } else {\n                if (v.value === value) v.checked = true\n            }\n        }\n    })\n}\n\nfunction bindValue(name, value) {\n    let isArr = isArray(value)\n    let el = N(name)\n    el && el.forEach(v => {\n        v.addEventListener('change', function () {\n            let val = this.value\n            let nodeName = this.nodeName\n            if (nodeName === 'SELECT') {\n                value = val\n            } else if (nodeName === 'INPUT') {\n                if (isArr) {\n                    if (this.checked) {\n                        value.push(val)\n                    } else {\n                        for (let k in value) {\n                            if (value.hasOwnProperty(k) && val === value[k]) {\n                                value.splice(k, 1)\n                                break\n                            }\n                        }\n                    }\n                } else {\n                    value = this.checked ? val : ''\n                }\n            }\n\n            // 保存设置\n            setSetting(name, value)\n        })\n    })\n}\n\nfunction bindSearchMenus() {\n    N('searchMenus').forEach(v => {\n        v.addEventListener('change', function () {\n            // firefox 在 iframe 下功能缺失，只能通过 message 处理\n            sendMessage({action: 'menu', name: this.value, isAdd: this.checked})\n        })\n    })\n}\n\nfunction bindSortHTML(textName, id, name, value, list) {\n    sortShow(textName, id, value, list) // 初始值\n    let el = N(name)\n    el && el.forEach(v => {\n        v.addEventListener('change', function () {\n            sortShow(textName, id, value, list)\n        })\n    })\n}\n\nfunction sortShow(textName, id, value, list) {\n    let s = ''\n    if (isArray(value) && value.length > 0) {\n        s = textName\n        value.forEach((v, k) => {\n            s += (k > 0 ? ' > ' : '') + list[v]\n        })\n    }\n    $(id).innerHTML = s\n}\n\nfunction bindShow(id, name, value) {\n    let el = N(name)\n    el && el.forEach(v => {\n        v.addEventListener('change', function () {\n            $(id).style.display = (!value || value.length === 0) ? 'none' : 'block'\n        })\n    })\n    $(id).style.display = (!value || value.length === 0) ? 'none' : 'block'\n}\n\nfunction settingBoxHTML(id, name, list) {\n    let s = ''\n    Object.keys(list).forEach(v => {\n        s += `<label><input type=\"checkbox\" name=\"${name}\" value=\"${v}\">${list[v]}</label>`\n    })\n    let el = $(id)\n    el.innerHTML = s\n}\n\nfunction settingOcr() {\n    let boxEl = $('baidu_ocr_box')\n    let akEl = S('input[name=\"baidu_orc_ak\"]')\n    let skEl = S('input[name=\"baidu_orc_sk\"]')\n    let clearFn = () => localStorage['clearOcrExpires'] = 'true'\n    let el = N('ocrType')\n    el && el.forEach(v => {\n        v.addEventListener('change', function () {\n            (this.value === 'baidu' ? addClass : rmClass)(boxEl, 'dmx_show')\n            clearFn()\n        })\n    })\n    if (setting.ocrType === 'baidu') addClass(boxEl, 'dmx_show')\n    akEl.value = setting.baidu_orc_ak || ''\n    skEl.value = setting.baidu_orc_sk || ''\n    akEl.onblur = () => {\n        setSetting('baidu_orc_ak', akEl.value)\n        clearFn()\n    }\n    skEl.onblur = () => {\n        setSetting('baidu_orc_sk', skEl.value)\n        clearFn()\n    }\n}\n\nfunction searchListSetting() {\n    let dialogEl = $('search_list_dialog')\n    let butEl = $('search_setting_but')\n    let saveEl = $('search_list_save')\n    let textEl = S('textarea[name=\"search_text\"]')\n    butEl.onclick = () => {\n        dialogEl.style.display = 'block'\n        addClass(document.body, 'dmx_overflow_hidden')\n        textEl.value = searchText\n    }\n    saveEl.onclick = () => {\n        searchText = textEl.value.trim()\n        searchList = getSearchKey(searchText)\n\n        // 清理不存在的设置\n        let keyArr = Object.keys(searchList)\n        let funNewArr = function (arr, isMenu) {\n            let newArr = []\n            arr.forEach(v => {\n                if (keyArr.includes(v)) {\n                    newArr.push(v)\n                } else if (isMenu) {\n                    // 移除右键设置\n                    sendMessage({action: 'menu', name: v, isAdd: false})\n                }\n            })\n            return newArr\n        }\n        setting.searchList = funNewArr(setting.searchList)\n        setting.searchMenus = funNewArr(setting.searchMenus)\n        setting.searchSide = funNewArr(setting.searchSide)\n        setSetting('searchList', setting.searchList)\n        setSetting('searchMenus', setting.searchMenus)\n        setSetting('searchSide', setting.searchSide)\n\n        // 重新初始化\n        initSearch()\n\n        sendMessage({action: 'onSaveSearchText', searchText})\n        dal('保存成功')\n    }\n\n    // 关闭设置\n    $('search_list_back').onclick = function () {\n        dialogEl.style.display = 'none'\n        rmClass(document.body, 'dmx_overflow_hidden')\n    }\n}\n\nfunction localTtsSetting() {\n    let listEl = $('local_tts_list')\n    let dialogEl = $('local_tts_dialog')\n    let butEl = S('[name=\"translateTTSList\"][value=\"local\"]')\n    if (isFirefox) {\n        butEl.parentElement.style.display = 'none'\n        return\n    }\n\n    // 关闭设置\n    dialogEl.querySelector('.dialog_back').onclick = function () {\n        dialogEl.style.display = 'none'\n        rmClass(document.body, 'dmx_overflow_hidden')\n    }\n\n    // 打开设置\n    let i = document.createElement('i')\n    i.className = 'dmx-icon dmx-icon-setting'\n    i.title = '本机朗读设置'\n    i.onclick = function (e) {\n        e.preventDefault()\n        dialogEl.style.display = 'block'\n        addClass(document.body, 'dmx_overflow_hidden')\n    }\n    butEl.parentNode.appendChild(i)\n\n    // 初始设置\n    let langList = {}, voices = {}\n    ;(async () => {\n        // 语音包\n        await fetch('../conf/langSpeak.json').then(r => r.json()).then(r => {\n            langList = r\n        })\n\n        // 获取发音列表\n        await getVoices().then(r => {\n            voices = r\n        })\n\n        // 归类发音列表\n        let specialLang = ['en', 'es', 'nl']\n        let voiceList = voiceListSort(voices, specialLang)\n\n        // 创建发音列表\n        let s1 = '', s2 = ''\n        let ttsKeys = Object.values(conf.ttsList)\n        for (const [key, val] of Object.entries(voiceList)) {\n            let preName = langList[key] ? langList[key].zhName : key\n            let select = `<select key=\"${key}\"><option value=\"\">默认</option>`\n            val.forEach(v => {\n                let name = v.voiceName + (v.remote ? ' | 远程' : '')\n                if (specialLang.includes(key)) name = (langList[v.lang] ? langList[v.lang].zhName : v.lang) + ' | ' + name\n                select += `<option value=\"${v.voiceName}\">${name}</option>`\n            })\n            select += '</select>'\n            let row = `<div class=\"fx mt_1\"><div class=\"local_list_name\">${preName}</div>${select}</div>`\n            if (ttsKeys.includes(key) || specialLang.includes(key)) {\n                s1 += row\n            } else {\n                s2 += row\n            }\n        }\n        listEl.insertAdjacentHTML('beforeend', `<div class=\"lang_list\">${s1}</div><div class=\"lang_list_err\">${s2}</div>`)\n\n        // 初始发音设置\n        if (!setting.ttsConf) setting.ttsConf = {}\n        for (let [k, v] of Object.entries(setting.ttsConf)) {\n            let vEl = dialogEl.querySelector(`select[key=\"${k}\"]`)\n            if (vEl) vEl.value = v\n        }\n\n        // 修改发音设置\n        let sEl = dialogEl.querySelectorAll('select')\n        sEl.forEach(fn => {\n            fn.onchange = function () {\n                let key = fn.getAttribute('key')\n                setting.ttsConf[key] = this.value\n                setSetting('ttsConf', setting.ttsConf) // 保存设置\n            }\n        })\n\n        // 重置发音设置\n        $('local_tts_reset_setting').onclick = function () {\n            setSetting('ttsConf', {})\n            sEl.forEach(fn => {\n                fn.value = ''\n            })\n        }\n    })()\n}\n\nfunction voiceListSort(voices, specialLang) {\n    let kArr = Object.keys(voices)\n    kArr = kArr.sort()\n    let r = {}\n    kArr.forEach(k => {\n        let v = voices[k]\n        for (let i = 0; i < specialLang.length; i++) {\n            let lan = specialLang[i]\n            if (k === lan || (new RegExp(`^${lan}-`)).test(k)) {\n                if (!r[lan]) r[lan] = []\n                v.forEach(val => r[lan].push(val))\n                return\n            }\n        }\n        r[k] = v\n    })\n    return r\n}\n\nfunction setSetting(name, value) {\n    setting[name] = value\n    sendSetting(setting, name === 'scribble')\n}\n\nfunction clearSetting() {\n    sendSetting({}, true, true)\n    setTimeout(() => {\n        let url = new URL(location.href)\n        url.searchParams.set('r', Date.now() + '')\n        location.href = url.toString()\n    }, 300)\n}\n\nfunction sendSetting(setting, updateIcon, resetDialog) {\n    if (B.getBackgroundPage) {\n        B.getBackgroundPage().saveSettingAll(setting, updateIcon, resetDialog)\n    } else {\n        // firefox 在 iframe 下功能缺失，所以通过 message 处理\n        sendMessage({action: 'saveSetting', setting, updateIcon, resetDialog})\n    }\n}\n"
  },
  {
    "path": "src/js/speak.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nlet langList, voices, conf = {}\nlet voiceEl = $('speak_voice')\nlet rateEl = $('speak_rate')\nlet pitchEl = $('speak_pitch')\nlet inputEl = $('speak_input')\nlet buttonEl = $('speak_button')\ndocument.addEventListener('DOMContentLoaded', async function () {\n    if (isFirefox) {\n        let d = document.createElement('div')\n        d.textContent = 'Firefox 不支持本地朗读功能'\n        d.setAttribute('style', 'padding:5px;text-align:center;color:red;font-weight:bold;font-size:20px')\n        document.body.appendChild(d)\n        return\n    }\n\n    // 语音包\n    await fetch('../conf/langSpeak.json').then(r => r.json()).then(r => {\n        langList = r\n    })\n\n    // 获取发音列表\n    await getVoices().then(r => {\n        voices = r\n    })\n\n    // 添加发音列表\n    let voiceList = voiceListSort(voices)\n    for (const [key, val] of Object.entries(voiceList)) {\n        val.forEach(v => {\n            let op = document.createElement('option')\n            op.value = v.voiceName\n            op.innerText = `${langList[key] ? langList[key].zhName : key} | ${v.voiceName}${v.remote ? ' | 远程' : ''}`\n            voiceEl.appendChild(op)\n        })\n    }\n\n    // 初始设置\n    loadConf()\n\n    // 修改设置\n    voiceEl.addEventListener('change', function () {\n        setConf('voiceName', this.value)\n    })\n    rateEl.addEventListener('change', function () {\n        setConf('rate', this.value)\n    })\n    pitchEl.addEventListener('change', function () {\n        setConf('pitch', this.value)\n    })\n\n    // 粘贴事件\n    inputEl.addEventListener('paste', function (e) {\n        e.stopPropagation()\n        e.preventDefault()\n        this.innerText = (e.clipboardData || window.clipboardData).getData('Text')\n    })\n\n    // 开始朗读\n    buttonEl.addEventListener('click', function () {\n        let text = inputEl.innerText\n        let voiceName = voiceEl.value\n        let rate = rateEl.value\n        let pitch = pitchEl.value\n        let options = {}\n        if (voiceName) options.voiceName = voiceName\n        if (rate) options.rate = Number(rate)\n        if (pitch) options.pitch = Number(pitch)\n        speak(text, options)\n    })\n\n    // 停止朗读\n    document.addEventListener('keyup', function (e) {\n        if (e.key === 'Escape') B.tts.stop()\n    })\n})\n\nfunction voiceListSort(list) {\n    if (!list) return {}\n    let kArr = Object.keys(list)\n    // console.log(kArr.length)\n    // console.log(JSON.stringify(kArr.sort()))\n    kArr = kArr.sort() // 排序\n    let r = {}\n    if (list['zh-CN']) r['zh-CN'] = list['zh-CN'] // 中文简体放最前面\n    kArr.forEach(k => {\n        if (k === 'zh-CN') return\n        r[k] = list[k]\n    })\n    return r\n}\n\nfunction speak(text, options) {\n    // console.log(text, options)\n    if (text) {\n        let arr = B.getBackgroundPage().sliceStr(text, 128)\n        arr.forEach((v, k) => {\n            if (k === 0) {\n                B.tts.speak(v, options)\n            } else {\n                B.tts.speak(v, Object.assign({enqueue: true}, options))\n            }\n        })\n    }\n}\n\nfunction setConf(key, value) {\n    conf[key] = value\n    localStorage.setItem('speakConf', JSON.stringify(conf))\n}\n\nfunction loadConf() {\n    let s = localStorage.getItem('speakConf')\n    if (!s) return\n    conf = JSON.parse(s)\n    if (conf.voiceName) voiceEl.value = conf.voiceName\n    if (conf.rate) rateEl.value = conf.rate\n    if (conf.pitch) pitchEl.value = conf.pitch\n}\n"
  },
  {
    "path": "src/js/translate/alibaba.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction alibabaTranslate() {\n    return {\n        langMap: {\n            \"auto\": \"auto\",\n            \"en\": \"en\",\n            \"zh\": \"zh\",\n            \"ru\": \"ru\",\n            \"tr\": \"tr\",\n            \"pt\": \"pt\",\n            \"th\": \"th\",\n            \"id\": \"id\",\n            \"it\": \"it\",\n            \"spa\": \"es\",\n            \"fra\": \"fr\",\n            \"ara\": \"ar\",\n            \"vie\": \"vi\"\n        },\n        langMapInvert: {},\n        pairMap: {\n            \"auto\": [\"en\"],\n            \"en\": [\"zh\", \"ru\", \"es\", \"fr\", \"ar\", \"tr\", \"pt\", \"th\", \"id\", \"vi\"],\n            \"zh\": [\"en\", \"vi\"],\n            \"ru\": [\"en\", \"es\", \"tr\", \"it\", \"fr\", \"pt\"],\n            \"es\": [\"en\", \"ru\", \"tr\", \"it\", \"fr\", \"pt\"],\n            \"fr\": [\"en\", \"ru\", \"tr\", \"it\", \"es\", \"pt\"],\n            \"ar\": [\"en\", \"zh\"],\n            \"tr\": [\"en\", \"ru\", \"fr\", \"it\", \"es\", \"pt\"],\n            \"pt\": [\"en\", \"ru\", \"fr\", \"it\", \"es\", \"tr\"],\n            \"it\": [\"en\", \"ru\", \"fr\", \"pt\", \"es\", \"tr\"],\n            \"th\": [\"en\", \"zh\"],\n            \"id\": [\"en\", \"zh\"],\n            \"vi\": [\"en\", \"zh\"]\n        },\n        init() {\n            this.langMapInvert = invertObject(this.langMap)\n            return this\n        },\n        addListenerRequest() {\n            onBeforeSendHeadersAddListener(this.onChangeHeaders,\n                {urls: ['*://translate.alibaba.com/*'], types: ['xmlhttprequest']})\n        },\n        removeListenerRequest() {\n            onBeforeSendHeadersRemoveListener(this.onChangeHeaders)\n        },\n        onChangeHeaders(details) {\n            let s = `origin: https://translate.alibaba.com\nreferer: https://translate.alibaba.com/\nsec-fetch-site: same-origin`\n            return {requestHeaders: details.requestHeaders.concat(requestHeadersFormat(s))}\n        },\n        trans(q, srcLan, tarLan) {\n            srcLan = this.langMap[srcLan] || 'auto'\n            tarLan = this.langMap[tarLan] || 'zh'\n            if (!inArray(tarLan, this.pairMap[srcLan])) tarLan = this.pairMap[srcLan][0]\n            return new Promise((resolve, reject) => {\n                if (q.length > 5000) return reject('The text is too large!')\n                this.addListenerRequest()\n                let url = `https://translate.alibaba.com/translationopenseviceapp/trans/TranslateTextAddAlignment.do`\n                let p = new URLSearchParams(`srcLanguage=${srcLan}&tgtLanguage=${tarLan}&srcText=${q}&viewType=&source=&bizType=message`)\n                httpPost({url: url, body: p.toString()}).then(r => {\n                    this.removeListenerRequest()\n                    if (r) {\n                        resolve(this.unify(r, q, srcLan, tarLan))\n                    } else {\n                        reject('alibaba trans error!')\n                    }\n                }).catch(e => {\n                    this.removeListenerRequest()\n                    reject(e)\n                })\n            })\n        },\n        unify(r, q, srcLan, tarLan) {\n            // console.log('alibaba:', r, q, srcLan, tarLan)\n            if (srcLan === 'auto' && r.recognizeLanguage) srcLan = r.recognizeLanguage\n            let map = this.langMapInvert\n            srcLan = map[srcLan] || 'auto'\n            tarLan = map[tarLan] || ''\n            let ret = {text: q, srcLan: srcLan, tarLan: tarLan, lanTTS: null, data: []}\n            let srcArr = q.split('\\n')\n            let tarArr = []\n            let arr = r && r.listTargetText\n            arr && arr.forEach(v => {\n                tarArr = Object.assign(tarArr, v.split('\\n'))\n            })\n            tarArr.forEach((v, k) => {\n                ret.data.push({srcText: srcArr[k] || '', tarText: v})\n            })\n            return ret\n        },\n        async query(q, srcLan, tarLan) {\n            return checkRetry(() => this.trans(q, srcLan, tarLan))\n        },\n        tts(q, lan) {\n            lan = this.langMap[lan] || 'en'\n            return new Promise((resolve) => {\n                // 阿里云 TTS 有点慢，发音效果也不是太理想，懒得解密了，偷懒直接用搜狗的。\n                let getUrl = (s) => {\n                    return `https://fanyi.sogou.com/reventondc/synthesis?text=${encodeURIComponent(s)}&speed=1&lang=${lan}&from=translateweb&speaker=3`\n                }\n                let r = []\n                let arr = sliceStr(q, 128)\n                arr.forEach(text => {\n                    r.push(getUrl(text))\n                })\n                resolve(r)\n            })\n        },\n        link(q, srcLan, tarLan) {\n            return `https://translate.alibaba.com/?d_sl=${srcLan}&d_tl=${tarLan}&d_text=${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/translate/baidu.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction baiduTranslate() {\n    return {\n        token: {\n            gtk: '',\n            token: '',\n            date: 0,\n        },\n        lanTTS: [\"en\", \"zh\", \"yue\", \"ara\", \"kor\", \"jp\", \"th\", \"pt\", \"spa\", \"fra\", \"ru\", \"de\"],\n        sign(t, e) {\n            let ye = function (t, e) {\n                for (let r = 0; r < e.length - 2; r += 3) {\n                    let n = e.charAt(r + 2)\n                    n = n >= \"a\" ? n.charCodeAt(0) - 87 : Number(n)\n                    n = \"+\" === e.charAt(r + 1) ? t >>> n : t << n\n                    t = \"+\" === e.charAt(r) ? t + n & 4294967295 : t ^ n\n                }\n                return t\n            }\n            let he = '', r = t.length\n            r > 30 && (t = \"\" + t.substr(0, 10) + t.substr(Math.floor(r / 2) - 5, 10) + t.substr(-10, 10))\n            let n = ('' !== he ? he : (he = e || \"\") || \"\").split(\".\"), o = Number(n[0]) || 0, a = Number(n[1]) || 0\n            let c = [], i = 0, u = 0\n            for (; u < t.length; u++) {\n                let s = t.charCodeAt(u)\n                128 > s ? c[i++] = s : (2048 > s ? c[i++] = s >> 6 | 192 : (55296 === (64512 & s) && u + 1 < t.length && 56320 === (64512 & t.charCodeAt(u + 1)) ?\n                    (s = 65536 + ((1023 & s) << 10) + (1023 & t.charCodeAt(++u)), c[i++] = s >> 18 | 240, c[i++] = s >> 12 & 63 | 128) :\n                    c[i++] = s >> 12 | 224, c[i++] = s >> 6 & 63 | 128), c[i++] = 63 & s | 128)\n            }\n            let f = o, l = 0\n            for (; l < c.length; l++) f = ye(f += c[l], \"+-a^+6\")\n            return f = ye(f, \"+-3^+b+-f\"), 0 > (f ^= a) && (f = 2147483648 + (2147483647 & f)), (f %= 1e6).toString() + \".\" + (f ^ o)\n        },\n        init() {\n            let str = localStorage.getItem('baiduToken')\n            if (str) this.token = JSON.parse(str)\n            return this\n        },\n        setToken(options) {\n            this.token = Object.assign(this.token, options)\n            localStorage.setItem('baiduToken', JSON.stringify(this.token))\n        },\n        getToken() {\n            return new Promise((resolve, reject) => {\n                httpGet('https://fanyi.baidu.com/').then(r => {\n                    let arr = r.match(/window\\.gtk\\s=\\s['\"]([^'\"]+)['\"];/)\n                    let tArr = r.match(/token:\\s'([^']+)'/)\n                    if (!arr) return reject('baidu gtk empty!')\n                    if (!tArr) return reject('baidu token empty!')\n                    let token = {gtk: arr[1], token: tArr[1], date: Math.floor(Date.now() / 36e5)}\n                    this.setToken(token)\n                    resolve(token)\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        trans(q, srcLan, tarLan) {\n            return new Promise((resolve, reject) => {\n                if (q.length > 5000) return reject('The text is too large!')\n                if (!this.token.gtk) return reject('baidu gtk empty!')\n                if (!this.token.token) return reject('baidu token empty!')\n                let sign = this.sign(q, this.token.gtk)\n                let token = this.token.token\n                let p = new URLSearchParams(`from=${srcLan}&to=${tarLan}&query=${q}&simple_means_flag=3&sign=${sign}&token=${token}&domain=common`)\n                httpPost({\n                    url: `https://fanyi.baidu.com/v2transapi?from=${srcLan}&to=${tarLan}`,\n                    body: p.toString()\n                }).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q, srcLan, tarLan))\n                    } else {\n                        reject('baidu translate error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        unify(r, text, srcLan, tarLan) {\n            // console.log('baidu:', r, text, srcLan, tarLan)\n            // console.log(JSON.stringify(r))\n            let res = getJSONValue(r, 'trans_result', {})\n            let data = []\n            if (res.data) {\n                res.data.forEach(v => {\n                    if (v.src && v.dst) data.push({srcText: v.src, tarText: v.dst})\n                })\n            }\n            if (setting.translateThin) return {text, srcLan, tarLan, lanTTS: this.lanTTS, data} // 精简显示\n\n            // 重点词汇\n            let s = ''\n            if (res.keywords && res.keywords.length > 0) {\n                s += `<div class=\"case_dd\"><div class=\"case_dd_head\">重点词汇</div>`\n                s += `<div class=\"case_dd_parts\">`\n                res.keywords.forEach(v => {\n                    if (v.word && v.means) s += `<p><b data-search=\"true\">${v.word}</b>${v.means.join('；')}</p>`\n                })\n                s += `</div></div>`\n            }\n\n            // 百度支持牛津，格林斯，英英等，如果全显示，会很复杂，小框显示也会很乱，所以只显示最简单的部分即可。\n            // 在翻译领域，除了国际巨头谷歌，在国内做的最好的非百度莫属，然后是搜狗，有道；如今搜狗被腾讯收购，或许未来会改名。-- 2021.1.6\n            let simple_means = getJSONValue(r, 'dict_result.simple_means')\n            if (simple_means) {\n                s += `<div class=\"case_dd\">`\n                let {word_name, symbols, word_means, exchange, memory_skill, tags} = simple_means\n                if (word_name) s += `<div class=\"case_dd_head\">${word_name}</div>`  // 查询的单词\n\n                let getIconHTML = function (type, text, title) {\n                    let lan = type === 'uk' ? 'uk' : 'en'\n                    let src = `https://fanyi.baidu.com/gettts?lan=${lan}&text=${encodeURIComponent(text)}&spd=3&source=web`\n                    return `<i class=\"dmx-icon dmx_ripple\" data-type=\"${type}\" data-src-mp3=\"${src}\" title=\"${title}\"></i>`\n                }\n                let hasParts = false\n                if (symbols) {\n                    symbols.forEach(sym => {\n                        // 音标\n                        let {ph_en, ph_am, parts} = sym\n                        if (ph_en || ph_am) {\n                            s += `<div class=\"case_dd_ph\">`\n                            s += `[${ph_en}${ph_am && ph_en !== ph_am ? ' $ ' + ph_am : ''}]`\n                            s += getIconHTML('uk', text, '英音')\n                            s += getIconHTML('us', text, '美音')\n                            s += `</div>`\n                        }\n\n                        // 释义\n                        if (parts && parts.length > 0) {\n                            hasParts = true\n                            s += `<div class=\"case_dd_parts\">`\n                            parts.forEach(v => {\n                                let {part, means} = v\n                                let firstVal = getJSONValue(means, '0')\n                                if (firstVal && isString(firstVal)) {\n                                    s += `<p>${part ? `<b>${part}</b>` : ''}${means.join('；')}</p>`\n                                } else {\n                                    let firstVal = getJSONValue(means, '0.text')\n                                    if (firstVal && isString(firstVal)) {\n                                        for (let mv of means) {\n                                            let {text, part, means} = mv\n                                            s += `<p>${part ? `<b>${part}</b>` : ''}${text} ${means ? means.join('；') : ''}</p>`\n                                        }\n                                    }\n                                }\n                            })\n                            s += `</div>`\n                        }\n                    })\n                }\n                if (!hasParts && word_means) s += `<div class=\"case_dd_parts\"><p>${word_means.join('；')}</p></div>`\n\n                // 单词形态\n                if (exchange) {\n                    let exchangeObj = {\n                        word_third: '第三人称单数',\n                        word_pl: '复数',\n                        word_ing: '现在分词',\n                        word_past: '过去式',\n                        word_done: '过去分词',\n                        word_er: '比较级',\n                        word_est: '最高级',\n                        word_proto: '原型',\n                    }\n                    s += `<div class=\"case_dd_exchange\">`\n                    for (let [k, v] of Object.entries(exchange)) {\n                        if (!v) continue\n                        let wordStr = ''\n                        v.forEach(word => {\n                            if (word) wordStr += `<a data-search=\"true\">${word}</a>`\n                        })\n                        s += `<b>${exchangeObj[k] || '其他'}</b><u>${wordStr}</u>`\n                    }\n                    s += `</div>`\n                }\n\n                // 记忆技巧\n                if (memory_skill) {\n                    s += `<div class=\"case_dd_parts\"><b>记忆技巧：</b>${memory_skill}</div>`\n                }\n\n                // 单词标签\n                if (tags) {\n                    s += `<div class=\"case_dd_tags\">`\n                    for (let [k, v] of Object.entries(tags)) {\n                        let tagStr = ''\n                        v.forEach(tag => {\n                            if (tag) tagStr += `<u>${tag}</u>`\n                        })\n                        s += tagStr\n                    }\n                    s += `</div>`\n                }\n\n                s += `</div>`\n            }\n\n            // 视频显示，如果有的话。\n            let videoObj = getJSONValue(r, 'dict_result.queryExplainVideo')\n            if (videoObj && videoObj.thumbUrl && videoObj.videoUrl) {\n                // s += `<div style=\"margin:10px auto;width:400px;height:224px;background:#000\"><video width=\"400\" height=\"224\" src=\"${videoObj.videoUrl}\" poster=\"${videoObj.thumbUrl}\" controls=\"controls\" rel=\"noreferrer\"></video></div>`\n                let src = B.root + 'html/video.html?' + new URLSearchParams(`thumbUrl=${videoObj.thumbUrl}&videoUrl=${videoObj.videoUrl}`)\n                s += `<div style=\"margin:10px auto;width:400px;height:224px;background:#000\"><iframe width=\"400\" height=\"224\" src=\"${src}\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe></div>`\n            }\n\n            return {text, srcLan, tarLan, lanTTS: this.lanTTS, data, extra: s}\n        },\n        async query(q, srcLan, tarLan, noCache) {\n            if (srcLan === 'auto') {\n                srcLan = 'en' // 默认值\n                await httpPost({\n                    url: `https://fanyi.baidu.com/langdetect`,\n                    body: `query=${encodeURIComponent(q)}`\n                }).then(r => {\n                    if (r.lan) srcLan = r.lan\n                }).catch(err => {\n                    debug(err)\n                })\n            }\n            if (srcLan === tarLan) tarLan = srcLan === 'zh' ? 'en' : 'zh'\n\n            return checkRetry(async (i) => {\n                let t = Math.floor(Date.now() / 36e5)\n                let d = this.token.date\n                if (i > 0) noCache = true\n                if (noCache || !d || Number(d) !== t) {\n                    await this.getToken().catch(err => {\n                        debug(err)\n                    })\n                }\n                return this.trans(q, srcLan, tarLan)\n            })\n        },\n        tts(q, lan) {\n            return new Promise((resolve, reject) => {\n                if (!inArray(lan, this.lanTTS)) return reject('This language is not supported!')\n                if (lan === 'yue') lan = 'cte' // 粤语\n                // https://tts.baidu.com/text2audio?tex=%E6%98%8E(ming2)%E7%99%BD(bai2)&cuid=baike&lan=ZH&ctp=1&pdt=31&vol=9&spd=4&per=4100\n                let getUrl = (s) => {\n                    return `https://fanyi.baidu.com/gettts?lan=${lan}&text=${encodeURIComponent(s)}&spd=3&source=web`\n                }\n                let r = []\n                let arr = sliceStr(q, 128)\n                arr.forEach(text => {\n                    r.push(getUrl(text))\n                })\n                resolve(r)\n            })\n        },\n        link(q, srcLan, tarLan) {\n            return `https://fanyi.baidu.com/#${srcLan}/${tarLan}/${encodeURIComponent(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/translate/bing.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction bingTranslate() {\n    return {\n        token: {\n            ig: '',\n            iid: '',\n            num: 0,\n            date: 0,\n            paramsToken: '',\n            paramsKey: 0,\n            ttsToken: '',\n            ttsRegion: '',\n            ttsExpiry: 0,\n        },\n        langCheck: '',\n        langMap: {\n            \"auto\": \"auto-detect\",\n            \"yue\": \"yue\",\n            \"cs\": \"cs\",\n            \"nl\": \"nl\",\n            \"en\": \"en\",\n            \"fil\": \"fil\",\n            \"de\": \"de\",\n            \"el\": \"el\",\n            \"ht\": \"ht\",\n            \"hi\": \"hi\",\n            \"hu\": \"hu\",\n            \"id\": \"id\",\n            \"it\": \"it\",\n            \"mg\": \"mg\",\n            \"pl\": \"pl\",\n            \"ro\": \"ro\",\n            \"ru\": \"ru\",\n            \"sm\": \"sm\",\n            \"sk\": \"sk\",\n            \"th\": \"th\",\n            \"tr\": \"tr\",\n            \"afr\": \"af\",\n            \"ara\": \"ar\",\n            \"asm\": \"as\",\n            \"bos\": \"bs\",\n            \"bul\": \"bg\",\n            \"cat\": \"ca\",\n            \"hrv\": \"hr\",\n            \"dan\": \"da\",\n            \"est\": \"et\",\n            \"fin\": \"fi\",\n            \"fra\": \"fr\",\n            \"guj\": \"gu\",\n            \"heb\": \"he\",\n            \"ice\": \"is\",\n            \"gle\": \"ga\",\n            \"jp\": \"ja\",\n            \"kan\": \"kn\",\n            \"kaz\": \"kk\",\n            \"kor\": \"ko\",\n            \"lav\": \"lv\",\n            \"lit\": \"lt\",\n            \"may\": \"ms\",\n            \"mal\": \"ml\",\n            \"mlt\": \"mt\",\n            \"mao\": \"mi\",\n            \"mar\": \"mr\",\n            \"nor\": \"nb\",\n            \"pus\": \"ps\",\n            \"per\": \"fa\",\n            \"pan\": \"pa\",\n            \"slo\": \"sl\",\n            \"spa\": \"es\",\n            \"swa\": \"sw\",\n            \"swe\": \"sv\",\n            \"tam\": \"ta\",\n            \"tel\": \"te\",\n            \"ukr\": \"uk\",\n            \"urd\": \"ur\",\n            \"vie\": \"vi\",\n            \"wel\": \"cy\",\n            \"zh\": \"zh-Hans\",\n            \"cht\": \"zh-Hant\",\n            \"frn\": \"fr-ca\",\n            \"hmn\": \"mww\",\n            \"pot\": \"pt\",\n            \"pt\": \"pt-pt\",\n            \"srp\": \"sr-Latn\"\n        },\n        langMapInvert: {},\n        lanTTS: [\"zh\", \"en\", \"jp\", \"th\", \"spa\", \"ara\", \"fra\", \"kor\", \"ru\", \"de\", \"pt\", \"it\", \"el\", \"nl\", \"pl\", \"fin\", \"cs\", \"bul\"],\n        init() {\n            this.langMapInvert = invertObject(this.langMap)\n            let str = localStorage.getItem('bingToken')\n            if (str) this.token = JSON.parse(str)\n            return this\n        },\n        setToken(options) {\n            this.token = Object.assign(this.token, options)\n            localStorage.setItem('bingToken', JSON.stringify(this.token))\n        },\n        getToken() {\n            return new Promise((resolve, reject) => {\n                httpGet('https://cn.bing.com/translator').then(r => {\n                    let arr = r.match(/,IG:\"([^\"]+)\",/)\n                    let tArr = r.match(/_iid=\"([^\"]+)\"/)\n                    let paramsArr = r.match(/var params_RichTranslateHelper = \\[(\\d+),\"([^\"]+)\",/)\n                    if (!arr) return reject('bing IG empty!')\n                    if (!tArr) return reject('bing IID empty!')\n                    if (!paramsArr) return reject('bing paramsArr empty!')\n                    let token = {\n                        ig: arr[1],\n                        iid: tArr[1],\n                        num: 0,\n                        paramsToken: paramsArr[2],\n                        paramsKey: paramsArr[1],\n                        date: Math.floor(Date.now() / 36e5)\n                    }\n                    this.setToken(token)\n                    resolve(token)\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        trans(q, srcLan, tarLan) {\n            srcLan = this.langMap[srcLan] || 'auto-detect'\n            tarLan = this.langMap[tarLan] || 'zh-Hans'\n            return new Promise((resolve, reject) => {\n                if (q.length > 5000) return reject('The text is too large!')\n                if (!this.token.ig) return reject('bing ig empty!')\n                if (!this.token.iid) return reject('bing iid empty!')\n                let ig = this.token.ig\n                let iid = this.token.iid\n                let num = ++this.token.num\n                let paramsToken = this.token.paramsToken\n                let paramsKey = this.token.paramsKey\n                let url = `https://cn.bing.com/ttranslatev3?isVertical=1&&IG=${ig}&IID=${iid}.${num}`\n                let p = new URLSearchParams(`&fromLang=${srcLan}&text=${q}&to=${tarLan}&token=${paramsToken}&key=${paramsKey}`)\n                httpPost({url: url, body: p.toString()}).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q, srcLan, tarLan))\n                    } else {\n                        reject('bing trans error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        unify(r, q, srcLan, tarLan) {\n            // console.log('bing:', r, q, srcLan, tarLan)\n            if (srcLan === 'auto-detect' && r[0].detectedLanguage) srcLan = r[0].detectedLanguage.language\n            let map = this.langMapInvert\n            srcLan = map[srcLan] || 'auto'\n            tarLan = map[tarLan] || ''\n            let ret = {text: q, srcLan: srcLan, tarLan: tarLan, lanTTS: this.lanTTS, data: []}\n            let srcArr = q.split('\\n')\n            let tarArr = []\n            let arr = r && r[0] && r[0].translations\n            arr && arr.forEach(v => {\n                if (v.text) tarArr = Object.assign(tarArr, v.text.split('\\n'))\n            })\n            tarArr.forEach((v, k) => {\n                ret.data.push({srcText: srcArr[k] || '', tarText: v})\n            })\n            return ret\n        },\n        async query(q, srcLan, tarLan, noCache) {\n            return checkRetry(async (i) => {\n                let t = Math.floor(Date.now() / 36e5)\n                let d = this.token.date\n                if (i > 0) noCache = true\n                if (noCache || !d || Number(d) !== t) {\n                    await this.getToken().catch(err => console.warn(err))\n                }\n                return this.trans(q, srcLan, tarLan)\n            })\n        },\n        tts(q, lan) {\n            // see: https://docs.microsoft.com/zh-cn/azure/cognitive-services/speech-service/language-support\n            // 英语（美国）\ten-US\tFemale\ten-US-JessaRUS\n            // 普通话（简体中文，中国）\tzh-CN\tFemale\tzh-CN-HuihuiRUS\n            let arr = {\n                zh: {lang: 'zh-CN', gender: 'Female', name: 'zh-CN-HuihuiRUS'},\n                en: {lang: 'en-US', gender: 'Female', name: 'en-US-JessaRUS'},\n                jp: {lang: 'ja-JP', gender: 'Female', name: 'ja-JP-Ayumi'},\n                th: {lang: 'th-TH', gender: 'Male', name: 'th-TH-Pattara'},\n                spa: {lang: 'es-ES', gender: 'Female', name: 'es-ES-Laura'},\n                ara: {lang: 'ar-SA', gender: 'Male', name: 'ar-SA-Naayf'},\n                fra: {lang: 'fr-FR', gender: 'Female', name: 'fr-FR-Julie-Apollo'},\n                kor: {lang: 'ko-KR', gender: 'Female', name: 'ko-KR-HeamiRUS'},\n                ru: {lang: 'ru-RU', gender: 'Female', name: 'ru-RU-Irina-Apollo'},\n                de: {lang: 'de-DE', gender: 'Female', name: 'de-DE-Hedda'},\n                pt: {lang: 'pt-PT', gender: 'Female', name: 'pt-PT-HeliaRUS'},\n                it: {lang: 'it-IT', gender: 'Female', name: 'it-IT-Cosimo-Apollo'},\n                el: {lang: 'el-GR', gender: 'Male', name: 'el-GR-Stefanos'},\n                nl: {lang: 'nl-NL', gender: 'Female', name: 'nl-NL-HannaRUS'},\n                pl: {lang: 'pl-PL', gender: 'Female', name: 'pl-PL-PaulinaRUS'},\n                fin: {lang: 'fi-FI', gender: 'Female', name: 'fi-FI-HeidiRUS'},\n                cs: {lang: 'cs-CZ', gender: 'Male', name: 'cs-CZ-Jakub'},\n                bul: {lang: 'bg-BG', gender: 'Male', name: 'bg-BG-Ivan'},\n            }\n            return new Promise((resolve, reject) => {\n                if (!inArray(lan, this.lanTTS)) return reject('This language is not supported!')\n                let l = arr[lan] || arr.en\n\n                if (!this.token.ig) return reject('bing ig empty!')\n                if (!this.token.iid) return reject('bing iid empty!')\n                let ig = this.token.ig\n                let iid = this.token.iid\n                let num = this.token.num\n                let paramsToken = this.token.paramsToken\n                let paramsKey = this.token.paramsKey\n                let ttsToken = this.token.ttsToken\n                let ttsRegion = this.token.ttsRegion\n                let expiry = this.token.ttsExpiry\n\n                let ttsBlob = (q, ttsToken, ttsRegion) => {\n                    httpPost({\n                        url: `https://${ttsRegion}.tts.speech.microsoft.com/cognitiveservices/v1`,\n                        type: 'xml',\n                        responseType: 'blob',\n                        headers: [\n                            {name: 'X-MICROSOFT-OutputFormat', value: 'audio-16khz-32kbitrate-mono-mp3'},\n                            {name: 'Authorization', value: `Bearer ${ttsToken}`},\n                        ],\n                        body: `<speak version='1.0' xml:lang='${l.lang}'><voice xml:lang='${l.lang}' xml:gender='${l.gender}' name='${l.name}'><prosody rate='-20.00%'>${q}</prosody></voice></speak>`,\n                    }).then(r => {\n                        if (r) {\n                            resolve(r)\n                        } else {\n                            reject('bing tts api error!')\n                        }\n                    }).catch(e => {\n                        reject(e)\n                    })\n                }\n\n                let t = Math.floor(Date.now() / 1000)\n                if (expiry - 60 > t) {\n                    ttsBlob(q, ttsToken, ttsRegion)\n                } else {\n                    let p = new URLSearchParams(`token=${paramsToken}&key=${paramsKey}`)\n                    httpPost({\n                        url: `https://cn.bing.com/tfetspktok?isVertical=1&=&IG=${ig}&IID=${iid}.${num}`,\n                        body: p.toString()\n                    }).then(r => {\n                        if (r && r.token && r.region && r.expiry && r.statusCode === 200) {\n                            this.setToken({ttsToken: r.token, ttsRegion: r.region, ttsExpiry: r.expiry * 1})\n                            ttsBlob(q, r.token, r.region)\n                        } else {\n                            reject('bing tts token api error!')\n                        }\n                    }).catch(e => {\n                        reject(e)\n                    })\n                }\n            })\n        },\n        link(q, srcLan, tarLan) {\n            return `https://cn.bing.com/translator?d_sl=${srcLan}&d_tl=${tarLan}&d_text=${encodeURI(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/translate/deepl.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction deeplTranslate() {\n    return {\n        langMap: {\n            \"auto\": \"auto\",\n            \"zh\": \"zh\",\n            \"en\": \"en\",\n            \"de\": \"de\",\n            \"fra\": \"fr\",\n            \"spa\": \"es\",\n            \"pt\": \"pt\",\n            \"it\": \"it\",\n            \"nl\": \"nl\",\n            \"pl\": \"pl\",\n            \"ru\": \"ru\",\n            \"jp\": \"ja\",\n        },\n        langMapInvert: {},\n        isData: false,\n        init() {\n            this.langMapInvert = invertObject(this.langMap)\n            return this\n        },\n        trans(q, srcLan, tarLan) {\n            srcLan = this.langMap[srcLan] || 'auto'\n            tarLan = this.langMap[tarLan] || 'zh'\n            return new Promise((resolve, reject) => {\n                if (q.length > 5000) return reject('The text is too large!')\n\n                // 取消 Frame 嵌入限制\n                onHeadersReceivedAddListener(onRemoveFrame, {urls: [\"*://*.deepl.com/*\"]})\n\n                // popup 框\n                let pageId = 'fy_DeepL'\n                let url = `https://www.deepl.com/translator#${srcLan}/${tarLan}/${encodeURI(q)}`\n                // console.log('url:', url)\n                openIframe(pageId, url, 60 * 1000)\n\n                // 获取请求参数\n                let filter = {urls: ['*://*.deepl.com/jsonrpc*'], types: ['xmlhttprequest']} // 请求参数\n                this.isData = false\n                let onBeforeRequest = (details) => {\n                    if (this.isData) return\n                    let {requestBody, url} = details\n                    let bytes = getJSONValue(requestBody, 'raw.0.bytes')\n                    if (!bytes) return\n                    let body = new TextDecoder().decode(bytes)\n\n                    // 获取数据\n                    _setTimeout('trans_DeepL', () => {\n                        onBeforeSendHeadersAddListener(onBeforeSendHeaders, filter)\n                        let options = {url, body, type: 'json'}\n                        httpPost(options).then(r => {\n                            removeListener()\n                            if (r) {\n                                // 超时报错\n                                let outId = _setTimeout('trans_DeepL_reject', () => {\n                                    reject('DeepL result error!')\n                                }, 20 * 1000)\n                                let res = this.unify(r, q, srcLan, tarLan)\n                                if (res.data && res.data.length > 0) {\n                                    if (this.isData) return\n                                    this.isData = true // 表示有数据了\n                                    resolve(res)\n                                    _clearTimeout(outId)\n                                }\n                            } else {\n                                reject('DeepL error!')\n                            }\n                        }).catch(e => {\n                            removeListener()\n                            reject(e)\n                        })\n                    }, 200)\n                    // return {cancel: true}\n                }\n                onBeforeRequestAddListener(onBeforeRequest, filter)\n\n                // 请求接口数据修改\n                function onBeforeSendHeaders(details) {\n                    let h = details.requestHeaders\n                    h.push({name: 'Host', value: 'www.deepl.com'})\n                    h.push({name: 'Origin', value: 'https://www.deepl.com'})\n                    h.push({name: 'Referer', value: 'https://www.deepl.com/'})\n                    h.push({name: 'sec-fetch-dest', value: 'document'})\n                    h.push({name: 'sec-fetch-mode', value: 'navigate'})\n                    h.push({name: 'sec-fetch-site', value: 'same-origin'})\n                    return {requestHeaders: h}\n                }\n\n                // 销毁\n                function removeListener() {\n                    onHeadersReceivedRemoveListener(onRemoveFrame)\n                    onBeforeSendHeadersRemoveListener(onBeforeSendHeaders)\n                    onBeforeRequestRemoveListener(onBeforeRequest)\n                }\n            })\n        },\n        unify(r, text, srcLan, tarLan) {\n            // console.log('DeepL:', r, text, srcLan, tarLan)\n            // console.log(JSON.stringify(r))\n            // v1.0 2021.1.10\n            if (srcLan === 'auto' && r.source_lang) srcLan = r.source_lang.toLowerCase()\n            let map = this.langMapInvert\n            srcLan = map[srcLan] || 'auto'\n            tarLan = map[tarLan] || ''\n\n            let data = []\n            let extra = ''\n            let trans = getJSONValue(r, 'result.translations')\n            if (trans && trans.length > 0) {\n                let srcArr = text.split('\\n')\n                trans.forEach(tv => {\n                    if (!tv.beams) return\n                    tv.beams.forEach((v, k) => {\n                        let tarText = v.postprocessed_sentence\n                        if (tarText) {\n                            if (k === 0) {\n                                data.push({srcText: srcArr[k] || '', tarText})\n                            } else {\n                                extra += `<p>${tarText}</p>`\n                            }\n                        }\n                    })\n                })\n            }\n            if (extra) extra = `<div class=\"case_dd\"><div class=\"case_dd_parts\">${extra}</div></div>`\n            return {text, srcLan, tarLan, lanTTS: this.lanTTS, data, extra}\n        },\n        async query(q, srcLan, tarLan) {\n            return checkRetry(() => this.trans(q, srcLan, tarLan), 1)\n        },\n        tts(q, lan) {\n            lan = this.langMap[lan] || 'en'\n            return new Promise((resolve) => {\n                let getUrl = (s) => {\n                    return `https://fanyi.sogou.com/reventondc/synthesis?text=${encodeURI(s)}&speed=1&lang=${lan}&from=translateweb&speaker=4`\n                }\n                let r = []\n                let arr = sliceStr(q, 128)\n                arr.forEach(text => {\n                    r.push(getUrl(text))\n                })\n                resolve(r)\n            })\n        },\n        link(q, srcLan, tarLan) {\n            srcLan = this.langMap[srcLan] || 'auto'\n            tarLan = this.langMap[tarLan] || 'zh'\n            return `https://www.deepl.com/translator#${srcLan}/${tarLan}/${encodeURI(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/translate/frdic.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction frdicTranslate() {\n    return {\n        init() {\n            return this\n        },\n        addListenerRequest() {\n            onBeforeSendHeadersAddListener(this.onChangeHeaders, {urls: ['*://api.frdic.com/api/*']})\n        },\n        removeListenerRequest() {\n            onBeforeSendHeadersRemoveListener(this.onChangeHeaders)\n        },\n        onChangeHeaders(details) {\n            let h = details.requestHeaders\n            h.push({name: 'Origin', value: 'https://dict.eudic.net/'})\n            h.push({name: 'Referer', value: 'https://dict.eudic.net/'})\n            return {requestHeaders: h}\n        },\n        onRequest() {\n            this.addListenerRequest()\n            if (this.timeoutId) {\n                clearTimeout(this.timeoutId)\n                this.timeoutId = null\n            }\n            this.timeoutId = setTimeout(this.removeListenerRequest, 30000)\n        },\n        encode(s) {\n            let Base64 = {\n                _keyStr: \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\",\n                encode: function (n) {\n                    let f = '', e, t, i, s, h, o, r, u = 0\n                    for (n = Base64._utf8_encode(n); u < n.length;)\n                        e = n.charCodeAt(u++), t = n.charCodeAt(u++), i = n.charCodeAt(u++), s = e >> 2,\n                            h = (e & 3) << 4 | t >> 4, o = (t & 15) << 2 | i >> 6, r = i & 63, isNaN(t) ? o = r = 64 : isNaN(i) && (r = 64),\n                            f = f + Base64._keyStr.charAt(s) + Base64._keyStr.charAt(h) + Base64._keyStr.charAt(o) + Base64._keyStr.charAt(r)\n                    return f\n                },\n                decode: function (n) {\n                    let t = '', e, o, s, h, u, r, f, i = 0\n                    for (n = n.replace(/[^A-Za-z0-9\\+\\/\\=]/g, \"\"); i < n.length;)\n                        h = Base64._keyStr.indexOf(n.charAt(i++)),\n                            u = Base64._keyStr.indexOf(n.charAt(i++)),\n                            r = Base64._keyStr.indexOf(n.charAt(i++)),\n                            f = Base64._keyStr.indexOf(n.charAt(i++)),\n                            e = h << 2 | u >> 4,\n                            o = (u & 15) << 4 | r >> 2,\n                            s = (r & 3) << 6 | f,\n                            t = t + String.fromCharCode(e),\n                        r !== 64 && (t = t + String.fromCharCode(o)),\n                        f !== 64 && (t = t + String.fromCharCode(s))\n                    return Base64._utf8_decode(t)\n                },\n                _utf8_encode: function (n) {\n                    let i, r, t\n                    for (n = n.replace(/\\r\\n/g, \"\\n\"), i = \"\", r = 0; r < n.length; r++)\n                        t = n.charCodeAt(r), t < 128 ? i += String.fromCharCode(t) : t > 127 && t < 2048 ?\n                            (i += String.fromCharCode(t >> 6 | 192), i += String.fromCharCode(t & 63 | 128)) :\n                            (i += String.fromCharCode(t >> 12 | 224), i += String.fromCharCode(t >> 6 & 63 | 128), i += String.fromCharCode(t & 63 | 128))\n                    return i\n                },\n                _utf8_decode: function (n) {\n                    let r = '', t = 0, i = 0, c2 = 0, c3 = 0\n                    for (; t < n.length;)\n                        i = n.charCodeAt(t), i < 128 ? (r += String.fromCharCode(i), t++) : i > 191 && i < 224 ?\n                            (c2 = n.charCodeAt(t + 1), r += String.fromCharCode((i & 31) << 6 | c2 & 63), t += 2) :\n                            (c2 = n.charCodeAt(t + 1), c3 = n.charCodeAt(t + 2), r += String.fromCharCode((i & 15) << 12 | (c2 & 63) << 6 | c3 & 63), t += 3)\n                    return r\n                }\n            }\n            let fix = function (s) {\n                return encodeURI(s).replace(/[!'()]/g, escape).replace(/\\*/g, \"%2A\")\n            }\n            return fix(Base64.encode(s))\n        },\n        tts(q, lan) {\n            return new Promise((resolve, reject) => {\n                if (lan === 'auto') lan = 'en'\n                let lanArr = {'en': 'en', 'zh': 'zh', 'fra': 'fr', 'de': 'de', 'spa': 'es', 'jp': 'jp'}\n                if (!lanArr[lan]) return reject('This language is not supported!')\n                lan = lanArr[lan]\n                let getUrl = (s) => {\n                    return `https://api.frdic.com/api/v2/speech/speakweb?langid=${lan}&txt=QYN${this.encode(s)}`\n                }\n                let r = []\n                let arr = sliceStr(q, 128)\n                arr.forEach(text => {\n                    r.push(getUrl(text))\n                })\n                this.onRequest()\n                resolve(r)\n            })\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/translate/google.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction googleTranslate() {\n\treturn {\n\t\tlangMap: {\n\t\t\t\"auto\": \"auto\",\n\t\t\t\"pl\": \"pl\",\n\t\t\t\"de\": \"de\",\n\t\t\t\"ru\": \"ru\",\n\t\t\t\"ht\": \"ht\",\n\t\t\t\"nl\": \"nl\",\n\t\t\t\"cs\": \"cs\",\n\t\t\t\"ro\": \"ro\",\n\t\t\t\"mg\": \"mg\",\n\t\t\t\"hmn\": \"hmn\",\n\t\t\t\"pt\": \"pt\",\n\t\t\t\"sm\": \"sm\",\n\t\t\t\"sk\": \"sk\",\n\t\t\t\"ceb\": \"ceb\",\n\t\t\t\"th\": \"th\",\n\t\t\t\"tr\": \"tr\",\n\t\t\t\"el\": \"el\",\n\t\t\t\"haw\": \"haw\",\n\t\t\t\"hu\": \"hu\",\n\t\t\t\"it\": \"it\",\n\t\t\t\"hi\": \"hi\",\n\t\t\t\"id\": \"id\",\n\t\t\t\"en\": \"en\",\n\t\t\t\"alb\": \"sq\",\n\t\t\t\"ara\": \"ar\",\n\t\t\t\"amh\": \"am\",\n\t\t\t\"aze\": \"az\",\n\t\t\t\"gle\": \"ga\",\n\t\t\t\"est\": \"et\",\n\t\t\t\"baq\": \"eu\",\n\t\t\t\"bel\": \"be\",\n\t\t\t\"bul\": \"bg\",\n\t\t\t\"ice\": \"is\",\n\t\t\t\"bos\": \"bs\",\n\t\t\t\"per\": \"fa\",\n\t\t\t\"tat\": \"tt\",\n\t\t\t\"dan\": \"da\",\n\t\t\t\"fra\": \"fr\",\n\t\t\t\"fil\": \"tl\",\n\t\t\t\"fin\": \"fi\",\n\t\t\t\"hkm\": \"km\",\n\t\t\t\"geo\": \"ka\",\n\t\t\t\"guj\": \"gu\",\n\t\t\t\"kaz\": \"kk\",\n\t\t\t\"kor\": \"ko\",\n\t\t\t\"hau\": \"ha\",\n\t\t\t\"kir\": \"ky\",\n\t\t\t\"glg\": \"gl\",\n\t\t\t\"cat\": \"ca\",\n\t\t\t\"kan\": \"kn\",\n\t\t\t\"cos\": \"co\",\n\t\t\t\"hrv\": \"hr\",\n\t\t\t\"kur\": \"ku\",\n\t\t\t\"lat\": \"la\",\n\t\t\t\"lav\": \"lv\",\n\t\t\t\"lao\": \"lo\",\n\t\t\t\"lit\": \"lt\",\n\t\t\t\"ltz\": \"lb\",\n\t\t\t\"kin\": \"rw\",\n\t\t\t\"mlt\": \"mt\",\n\t\t\t\"mar\": \"mr\",\n\t\t\t\"mal\": \"ml\",\n\t\t\t\"may\": \"ms\",\n\t\t\t\"mac\": \"mk\",\n\t\t\t\"mao\": \"mi\",\n\t\t\t\"ben\": \"bn\",\n\t\t\t\"bur\": \"my\",\n\t\t\t\"nep\": \"ne\",\n\t\t\t\"nor\": \"no\",\n\t\t\t\"pan\": \"pa\",\n\t\t\t\"pus\": \"ps\",\n\t\t\t\"nya\": \"ny\",\n\t\t\t\"jp\": \"ja\",\n\t\t\t\"swe\": \"sv\",\n\t\t\t\"sin\": \"si\",\n\t\t\t\"epo\": \"eo\",\n\t\t\t\"slo\": \"sl\",\n\t\t\t\"swa\": \"sw\",\n\t\t\t\"som\": \"so\",\n\t\t\t\"tgk\": \"tg\",\n\t\t\t\"tel\": \"te\",\n\t\t\t\"tam\": \"ta\",\n\t\t\t\"tuk\": \"tk\",\n\t\t\t\"wel\": \"cy\",\n\t\t\t\"urd\": \"ur\",\n\t\t\t\"ukr\": \"uk\",\n\t\t\t\"uzb\": \"uz\",\n\t\t\t\"spa\": \"es\",\n\t\t\t\"heb\": \"iw\",\n\t\t\t\"snd\": \"sd\",\n\t\t\t\"sna\": \"sn\",\n\t\t\t\"arm\": \"hy\",\n\t\t\t\"ibo\": \"ig\",\n\t\t\t\"yid\": \"yi\",\n\t\t\t\"yor\": \"yo\",\n\t\t\t\"vie\": \"vi\",\n\t\t\t\"afr\": \"af\",\n\t\t\t\"xho\": \"xh\",\n\t\t\t\"zul\": \"zu\",\n\t\t\t\"srp\": \"sr\",\n\t\t\t\"jav\": \"jw\",\n\t\t\t\"zh\": \"zh-CN\",\n\t\t\t\"fry\": \"fy\",\n\t\t\t\"sco\": \"gd\",\n\t\t\t\"sun\": \"su\",\n\t\t\t\"or\": \"or\",\n\t\t\t\"mn\": \"mn\",\n\t\t\t\"st\": \"st\",\n\t\t\t\"ug\": \"ug\"\n\t\t},\n\t\tlangMapInvert: {},\n\n\t\tinit() {\n\t\t\tthis.langMapInvert = invertObject(this.langMap)\n\t\t\treturn this\n\t\t},\n\n\t\tunify(r, q, srcLan, tarLan) {\n\t\t\t// console.log('google:', r, q, srcLan, tarLan)\n\n\t\t\t// 翻译的语言参数\n\t\t\tif (srcLan === 'auto' && r.sourceLanguage) srcLan = r.sourceLanguage; // 源语言\n\t\t\tlet map = this.langMapInvert\n\t\t\tsrcLan = map[srcLan] || 'auto'\n\t\t\ttarLan = map[tarLan] || ''\n\n\t\t\t// 翻译结果\n\t\t\tlet data = [];\n\t\t\tr.sentences && r.sentences.forEach(v => {\n\t\t\t\tif (v.trans && v.orig) data.push({srcText: v.orig, tarText: v.trans})\n\t\t\t})\n\n\t\t\t// 额外信息，如单词释义等\n\t\t\tlet extra = '';\n\t\t\tif (!setting.translateThin && r.bilingualDictionary && isArray(r.bilingualDictionary)) {\n\t\t\t\tr.bilingualDictionary.forEach(v => {\n\t\t\t\t\tif (v.pos && v.entry) {\n\t\t\t\t\t\tlet entryArr = [];\n\t\t\t\t\t\tif (isArray(v.entry) && v.entry.length > 0) {\n\t\t\t\t\t\t\tv.entry.map(v => {\n\t\t\t\t\t\t\t\tentryArr.push(v.word);\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (entryArr.length > 0) {\n\t\t\t\t\t\t\textra += `<p><b>${v.pos}</b>${entryArr.join('；')}</p>`\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tif (extra) extra = `<div class=\"case_dd\"><div class=\"case_dd_parts\">${extra}</div></div>`\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\ttext: q, // 需要翻译的原始文本\n\t\t\t\tsrcLan: srcLan, // 源语言代码，如 en, zh-CN 等\n\t\t\t\ttarLan: tarLan, // 目标语言代码，如 en, zh-CN 等\n\t\t\t\tlanTTS: null,\n\t\t\t\tdata: data, // 翻译结果，如 [{srcText: 'hello', tarText: '你好'}]\n\t\t\t\textra: extra, // 额外信息，如单词释义等\n\t\t\t}\n\t\t},\n\n\t\ttrans(q, srcLan, tarLan) {\n\t\t\tsrcLan = this.langMap[srcLan] || 'auto'\n\t\t\ttarLan = this.langMap[tarLan] || 'zh-CN'\n\t\t\treturn new Promise(async (resolve, reject) => {\n\t\t\t\tif (q.length > 1000) return reject('The text is too large!')\n\n\t\t\t\t// 翻译接口来源于，官方的 Google 翻译插件\n\t\t\t\tconst url = `https://translate-pa.googleapis.com/v1/translate?params.client=gtx` +\n\t\t\t\t\t`&query.source_language=${srcLan}` +\n\t\t\t\t\t`&query.target_language=${tarLan}` +\n\t\t\t\t\t`&query.display_language=${tarLan}` +\n\t\t\t\t\t`&query.text=${encodeURIComponent(q)}` +\n\t\t\t\t\t'&key=AIzaSyDLEeFI5OtFBwYBIoK_jj5m32rZK5CkCXA' +\n\t\t\t\t\t'&data_types=TRANSLATION' +\n\t\t\t\t\t'&data_types=SENTENCE_SPLITS' +\n\t\t\t\t\t'&data_types=BILINGUAL_DICTIONARY_FULL';\n\t\t\t\tawait httpGet(url, 'json').then(r => {\n\t\t\t\t\tif (r) {\n\t\t\t\t\t\tresolve(this.unify(r, q, srcLan, tarLan))\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject('google translate error!')\n\t\t\t\t\t}\n\t\t\t\t}).catch(function (e) {\n\t\t\t\t\treject(e)\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\n\t\tasync query(q, srcLan, tarLan) {\n\t\t\treturn checkRetry(() => this.trans(q, srcLan, tarLan), 2)\n\t\t},\n\n\t\ttts(q, lan) {\n\t\t\tlan = this.langMap[lan] || 'en'\n\t\t\treturn new Promise(async (resolve, reject) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst url = 'https://translate-pa.googleapis.com/v1/textToSpeech?client=gtx' +\n\t\t\t\t\t\t'&language=' + lan +\n\t\t\t\t\t\t'&text=' + encodeURIComponent(q) +\n\t\t\t\t\t\t'&voice_speed=1' +\n\t\t\t\t\t\t'&key=AIzaSyDLEeFI5OtFBwYBIoK_jj5m32rZK5CkCXA';\n\t\t\t\t\tlet data = await httpGet(url, 'json');\n\t\t\t\t\tlet blobUrl = this.base64ToBlobUrl(data.audioContent); // 将 Base64 编码的数据转换为 Blob 对象并创建一个指向该 Blob 的 URL\n\t\t\t\t\tresolve([blobUrl])\n\t\t\t\t} catch (e) {\n\t\t\t\t\treject(e)\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t// 将 Base64 编码的数据转换为 Blob 对象并创建一个指向该 Blob 的 URL\n\t\tbase64ToBlobUrl(base64Data) {\n\t\t\tconst base64WithoutPrefix = base64Data.replace(/^data:.+;base64,/, '');\n\t\t\tconst binaryData = atob(base64WithoutPrefix);\n\t\t\tconst arrayBuffer = new ArrayBuffer(binaryData.length);\n\t\t\tconst uint8Array = new Uint8Array(arrayBuffer);\n\t\t\tfor (let i = 0; i < binaryData.length; i++) {\n\t\t\t\tuint8Array[i] = binaryData.charCodeAt(i);\n\t\t\t}\n\t\t\tconst blob = new Blob([uint8Array], {type: 'audio/mp3'});\n\t\t\treturn URL.createObjectURL(blob);\n\t\t},\n\n\t\tlink(q, srcLan, tarLan) {\n\t\t\tsrcLan = this.langMap[srcLan] || 'auto'\n\t\t\ttarLan = this.langMap[tarLan] || 'zh-CN'\n\t\t\treturn `https://translate.google.com/?sl=${srcLan}&tl=${tarLan}&text=${encodeURIComponent(q)}&op=translate`\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "src/js/translate/local.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction localTranslate() {\n    return {\n        voiceList: null,\n        init() {\n            return this\n        },\n        tts(q, lan) {\n            return new Promise(async (resolve, reject) => {\n                if (!this.voiceList) await getVoices().then(r => this.voiceList = r)\n\n                let ttsConf = setting.ttsConf || {}\n                let lang = getJSONValue(conf, `ttsList.${lan}`)\n                if (!lang || !this.voiceList || !this.voiceList[lang]) return reject('This language is not supported!')\n\n                let options = {}\n                if (ttsConf['speak_rate']) options.rate = Number(ttsConf['speak_rate'])\n                if (ttsConf['speak_pitch']) options.pitch = Number(ttsConf['speak_pitch'])\n                if (ttsConf[lang]) {\n                    options.voiceName = ttsConf[lang]\n                } else if (['en-US', 'es-ES', 'nl-NL'].includes(lang)) {\n                    let a = {'en-US': 'en', 'es-ES': 'es', 'nl-NL': 'nl'}\n                    lang = a[lang]\n                    if (ttsConf[lang]) {\n                        options.voiceName = ttsConf[lang]\n                    } else {\n                        options.lang = lang\n                    }\n                } else {\n                    options.lang = lang\n                }\n                let arr = sliceStr(q, 128)\n                let lastKey = arr.length - 1\n                arr.forEach((v, k) => {\n                    options.onEvent = function (e) {\n                        // console.log('onEvent:', lastKey, k, v, e.type, options)\n                        if (e.type === 'end') {\n                            if (k === lastKey) resolve()\n                        } else if (e.type === 'error') {\n                            debug('tts.speak error:', e.errorMessage)\n                            reject(e.errorMessage)\n                        }\n                    }\n                    if (k === 0) {\n                        B.tts.speak(v, options)\n                    } else {\n                        B.tts.speak(v, Object.assign({enqueue: true}, options))\n                    }\n                })\n            })\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/translate/qq.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction qqTranslate() {\n    return {\n        token: {\n            qtv: '',\n            qtk: '',\n        },\n        cookie: {},\n        langMap: {\n            \"auto\": \"auto\",\n            \"zh\": \"zh\",\n            \"en\": \"en\",\n            \"jp\": \"jp\",\n            \"it\": \"it\",\n            \"de\": \"de\",\n            \"tr\": \"tr\",\n            \"ru\": \"ru\",\n            \"pt\": \"pt\",\n            \"id\": \"id\",\n            \"th\": \"th\",\n            \"hi\": \"hi\",\n            \"kor\": \"kr\",\n            \"fra\": \"fr\",\n            \"spa\": \"es\",\n            \"vie\": \"vi\",\n            \"ara\": \"ar\",\n            \"may\": \"ms\"\n        },\n        langMapInvert: {},\n        pairMap: {\n            auto: [\"zh\", \"en\", \"jp\", \"kr\", \"fr\", \"es\", \"it\", \"de\", \"tr\", \"ru\", \"pt\", \"vi\", \"id\", \"th\", \"ms\"],\n            en: [\"zh\", \"fr\", \"es\", \"it\", \"de\", \"tr\", \"ru\", \"pt\", \"vi\", \"id\", \"th\", \"ms\", \"ar\", \"hi\"],\n            zh: [\"en\", \"jp\", \"kr\", \"fr\", \"es\", \"it\", \"de\", \"tr\", \"ru\", \"pt\", \"vi\", \"id\", \"th\", \"ms\"],\n            fr: [\"zh\", \"en\", \"es\", \"it\", \"de\", \"tr\", \"ru\", \"pt\"],\n            es: [\"zh\", \"en\", \"fr\", \"it\", \"de\", \"tr\", \"ru\", \"pt\"],\n            it: [\"zh\", \"en\", \"fr\", \"es\", \"de\", \"tr\", \"ru\", \"pt\"],\n            de: [\"zh\", \"en\", \"fr\", \"es\", \"it\", \"tr\", \"ru\", \"pt\"],\n            tr: [\"zh\", \"en\", \"fr\", \"es\", \"it\", \"de\", \"ru\", \"pt\"],\n            ru: [\"zh\", \"en\", \"fr\", \"es\", \"it\", \"de\", \"tr\", \"pt\"],\n            pt: [\"zh\", \"en\", \"fr\", \"es\", \"it\", \"de\", \"tr\", \"ru\"],\n            vi: [\"zh\", \"en\"],\n            id: [\"zh\", \"en\"],\n            ms: [\"zh\", \"en\"],\n            th: [\"zh\", \"en\"],\n            jp: [\"zh\"],\n            kr: [\"zh\"],\n            ar: [\"en\"],\n            hi: [\"en\"]\n        },\n        init() {\n            this.langMapInvert = invertObject(this.langMap)\n            let str = localStorage.getItem('qqToken')\n            if (str) this.token = JSON.parse(str)\n            this.getCookieAll()\n\n            // 30s刷新页面\n            setInterval(() => {\n                this.getToken().catch(err => debug('qq getToken error:', err))\n            }, 30 * 1000)\n            return this\n        },\n        setToken(options) {\n            this.token = Object.assign(this.token, options)\n            localStorage.setItem('qqToken', JSON.stringify(this.token))\n        },\n        getToken() {\n            return new Promise((resolve, reject) => {\n                httpGet('https://fanyi.qq.com/').then(r => {\n                    let arr = r.match(/var reauthuri = \"(.+)\";/)\n                    if (!arr) return reject('qq reauthuri empty!')\n                    let reauthuri = arr[1]\n\n                    let qtv = this.token.qtv\n                    let qtk = this.token.qtk\n                    let body = ''\n                    if (qtv && qtk) body = `qtv=${this.rep(qtv)}&qtk=${this.rep(qtk)}`\n                    httpPost({url: 'https://fanyi.qq.com/api/' + reauthuri, body: body}).then(r => {\n                        if (r) {\n                            let token = {qtv: r.qtv, qtk: r.qtk}\n                            this.setToken(token)\n                            this.setCookie('qtk', r.qtk)\n                            this.setCookie('qtv', r.qtv)\n                            resolve(token)\n                        } else {\n                            reject('qq reaauth error!')\n                        }\n                    }).catch(e => {\n                        reject(e)\n                    })\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        rep(s) {\n            return s.replace(/\\+/g, '%2B')\n        },\n        // addListenerRequest() {\n        //     onBeforeSendHeadersAddListener(this.onChangeHeaders, {urls: ['*://fanyi.qq.com/api/*']})\n        // },\n        /*onChangeHeaders(details) {\n            // 获取最新 auth 链接\n            if (details.url && details.url.includes('auth')) {\n                localStorage['qqAuthUrl'] = details.url\n            }\n\n            let s = `Host: fanyi.qq.com\nOrigin: https://fanyi.qq.com\nReferer: https://fanyi.qq.com\nSec-Fetch-Dest: empty\nSec-Fetch-Mode: cors\nSec-Fetch-Site: same-origin`\n            return {requestHeaders: details.requestHeaders.concat(requestHeadersFormat(s))}\n        },*/\n        trans(q, srcLan, tarLan) {\n            srcLan = this.langMap[srcLan] || 'auto'\n            tarLan = this.langMap[tarLan] || 'zh'\n            if (!inArray(tarLan, this.pairMap[srcLan])) tarLan = this.pairMap[srcLan][0]\n            return new Promise((resolve, reject) => {\n                if (q.length > 5000) return reject('The text is too large!')\n                let qtv = this.token.qtv\n                let qtk = this.token.qtk\n                let uuid = 'translate_uuid' + (new Date).getTime()\n                let p = `source=${srcLan}&target=${tarLan}&sourceText=${encodeURI(q)}&qtv=${this.rep(qtv)}&qtk=${this.rep(qtk)}&ticket=&randstr=&sessionUuid=${uuid}`\n                httpPost({url: 'https://fanyi.qq.com/api/translate', body: p}).then(r => {\n                    if (r) {\n                        resolve(this.unify(r, q, srcLan, tarLan))\n                    } else {\n                        reject('qq translate error!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        unify(r, q, srcLan, tarLan) {\n            // console.log('qq:', r, q, srcLan, tarLan)\n            if (srcLan === 'auto' && r.translate && r.translate.source) srcLan = r.translate.source\n            let map = this.langMapInvert\n            srcLan = map[srcLan] || 'auto'\n            tarLan = map[tarLan] || ''\n            let ret = {text: q, srcLan: srcLan, tarLan: tarLan, lanTTS: null, data: []}\n            let arr = r && r.translate && r.translate.records\n            arr && arr.forEach(v => {\n                let srcText = v.sourceText ? v.sourceText.trim() : ''\n                let tarText = v.targetText ? v.targetText.trim() : ''\n                if (srcText && tarText) ret.data.push({srcText: srcText, tarText: tarText})\n            })\n            return ret\n        },\n        async query(q, srcLan, tarLan) {\n            return checkRetry(async () => {\n                return this.trans(q, srcLan, tarLan)\n            }, 2)\n        },\n        setCookie(name, value) {\n            let domain = 'fanyi.qq.com'\n            cookies('set', {url: `https://${domain}`, name: name, value: value, domain: domain, path: '/'}).then(v => {\n                this.cookie[v.name] = v.value\n            })\n        },\n        getCookieAll(callback) {\n            cookies('getAll', {domain: 'fanyi.qq.com'}).then(arr => {\n                arr.forEach(v => {\n                    this.cookie[v.name] = v.value\n                })\n                typeof callback === 'function' && callback()\n            })\n        },\n        getCookie(name) {\n            return this.cookie[name] || ''\n        },\n        tts(q, lan) {\n            lan = this.langMap[lan] || 'en'\n            return new Promise((resolve) => {\n                let guid = this.getCookie('fy_guid')\n                // todo: 腾讯 TTS 服务很不稳定\n                resolve(`https://fanyi.qq.com/api/tts?platform=PC_Website&lang=${lan}&text=${encodeURI(q)}&guid=${guid}`)\n            })\n        },\n        link(q, srcLan, tarLan) {\n            return `https://fanyi.qq.com/?d_sl=${srcLan}&d_tl=${tarLan}&d_text=${encodeURI(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/translate/so.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction soTranslate() {\n    return {\n        data: {},\n        init() {\n            return this\n        },\n        addListenerRequest() {\n            onBeforeSendHeadersAddListener(this.onChangeHeaders, {urls: ['*://fanyi.so.com/*']})\n        },\n        removeListenerRequest() {\n            onBeforeSendHeadersRemoveListener(this.onChangeHeaders)\n        },\n        onChangeHeaders(details) {\n            let s = `Host: fanyi.so.com\nOrigin: https://fanyi.so.com\npro: fanyi\nReferer: https://fanyi.so.com/\nSec-Fetch-Dest: empty\nSec-Fetch-Mode: cors\nSec-Fetch-Site: same-origin`\n            return {requestHeaders: details.requestHeaders.concat(requestHeadersFormat(s))}\n        },\n        trans(q, srcLan, tarLan) {\n            if (/\\p{Script=Han}/u.test(q)) srcLan = 'zh'\n            tarLan = srcLan === 'zh' ? 'en' : 'zh'\n            let eng = srcLan === 'en' ? 1 : 0\n            return new Promise((resolve, reject) => {\n                if (q.length > 5000) return reject('The text is too large!')\n                this.addListenerRequest()\n                let url = `https://fanyi.so.com/index/search?eng=${eng}&validate=&ignore_trans=0&query=${encodeURI(q)}`\n                let p = new URLSearchParams(`eng=${eng}&validate=&ignore_trans=0&query=${encodeURI(q)}`)\n                httpPost({url: url, body: p.toString()}).then(r => {\n                    this.removeListenerRequest()\n                    if (r) {\n                        resolve(this.unify(r, q, srcLan, tarLan))\n                    } else {\n                        reject('so trans error!')\n                    }\n                }).catch(e => {\n                    this.removeListenerRequest()\n                    reject(e)\n                })\n            })\n        },\n        unify(r, text, srcLan, tarLan) {\n            // console.log('so:', r, q, srcLan, tarLan)\n            let ret = {text, srcLan, tarLan, lanTTS: null, data: []}\n            let data = r && r.data\n            if (data) {\n                this.data = data\n                if (data.fanyi) ret.data.push({srcText: text, tarText: data.fanyi})\n            }\n            return ret\n        },\n        async query(q, srcLan, tarLan) {\n            return checkRetry(() => this.trans(q, srcLan, tarLan))\n        },\n        tts(q, lan) {\n            return new Promise((resolve, reject) => {\n                let isEn = lan === 'en'\n                let r = this.data && this.data.speak_url\n                if (r) {\n                    let arr = {}\n                    if (r.word_type === 'en2zh') {\n                        arr['en'] = r.speak_url\n                        arr['zh'] = r.tSpeak_url\n                    } else {\n                        arr['zh'] = r.speak_url\n                        arr['en'] = r.tSpeak_url\n                    }\n                    resolve(`https://fanyi.so.com` + (isEn ? arr['en'] : arr['zh']))\n                } else {\n                    reject('speak url empty')\n                }\n            })\n        },\n        link(q, srcLan, tarLan) {\n            return `https://fanyi.so.com/?src=dream_translate#${q}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/translate/sogou.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction sogouTranslate() {\n    return {\n        langMap: {\n            \"auto\": \"auto\",\n            \"pl\": \"pl\",\n            \"de\": \"de\",\n            \"ru\": \"ru\",\n            \"fil\": \"fil\",\n            \"ht\": \"ht\",\n            \"nl\": \"nl\",\n            \"cs\": \"cs\",\n            \"ro\": \"ro\",\n            \"mg\": \"mg\",\n            \"pt\": \"pt\",\n            \"sk\": \"sk\",\n            \"sm\": \"sm\",\n            \"th\": \"th\",\n            \"tr\": \"tr\",\n            \"el\": \"el\",\n            \"hu\": \"hu\",\n            \"en\": \"en\",\n            \"it\": \"it\",\n            \"hi\": \"hi\",\n            \"id\": \"id\",\n            \"yue\": \"yue\",\n            \"ara\": \"ar\",\n            \"est\": \"et\",\n            \"bul\": \"bg\",\n            \"bos\": \"bs-Latn\",\n            \"per\": \"fa\",\n            \"dan\": \"da\",\n            \"fra\": \"fr\",\n            \"fin\": \"fi\",\n            \"kor\": \"ko\",\n            \"kli\": \"tlh\",\n            \"hrv\": \"hr\",\n            \"lav\": \"lv\",\n            \"lit\": \"lt\",\n            \"may\": \"ms\",\n            \"mlt\": \"mt\",\n            \"ben\": \"bn\",\n            \"afr\": \"af\",\n            \"nor\": \"no\",\n            \"jp\": \"ja\",\n            \"swe\": \"sv\",\n            \"slo\": \"sl\",\n            \"srp\": \"sr-Latn\",\n            \"src\": \"sr-Cyrl\",\n            \"swa\": \"sw\",\n            \"wel\": \"cy\",\n            \"ukr\": \"uk\",\n            \"urd\": \"ur\",\n            \"spa\": \"es\",\n            \"heb\": \"he\",\n            \"vie\": \"vi\",\n            \"cat\": \"ca\",\n            \"zh\": \"zh-CHS\",\n            \"cht\": \"zh-CHT\"\n        },\n        langMapInvert: {},\n        init() {\n            this.langMapInvert = invertObject(this.langMap)\n            return this\n        },\n        // 2020.12.03 刚写完就改版，白破解了！要吐了。。。\n        /*transOld(q, srcLan, tarLan) {\n            srcLan = this.langMap[srcLan] || 'auto'\n            tarLan = this.langMap[tarLan] || 'zh-CHS'\n            return new Promise((resolve, reject) => {\n                if (q.length > 5000) return reject('The text is too large!')\n\n                // 取消 Frame 嵌入限制\n                onHeadersReceivedAddListener(onRemoveFrame, {urls: [\"*://fanyi.sogou.com/*\"]})\n\n                // Frame 请求\n                let url = `https://fanyi.sogou.com/?keyword=${encodeURI(q)}&transfrom=${srcLan}&transto=${tarLan}&model=general`\n                openIframe('iframe_soGou', url)\n\n                // 获取请求参数\n                let urls = ['*://fanyi.sogou.com/reventondc/translateV*']\n                let isFirst = false\n                let onBeforeRequest = function (details) {\n                    if (isFirst) return\n                    isFirst = true\n\n                    let data = details.requestBody.formData\n                    // console.log(data)\n                    let url = details.url\n                    setTimeout(() => {\n                        post(url, data)\n                    }, 200)\n                    return {cancel: true}\n                }\n                onBeforeRequestAddListener(onBeforeRequest, {urls: urls})\n\n                // 请求接口数据修改\n                let onBeforeSendHeaders = function (details) {\n                    let h = details.requestHeaders\n                    h.push({name: 'Host', value: 'fanyi.sogou.com'})\n                    h.push({name: 'Origin', value: 'https://fanyi.sogou.com'})\n                    h.push({name: 'Referer', value: url})\n                    h.push({name: 'Sec-Fetch-Site', value: 'same-origin'})\n                    return {requestHeaders: h}\n                }\n\n                // 销毁\n                let removeListener = function () {\n                    // el.remove()\n                    onHeadersReceivedRemoveListener(onRemoveFrame)\n                    onBeforeSendHeadersRemoveListener(onBeforeSendHeaders)\n                    onBeforeRequestRemoveListener(onBeforeRequest)\n                }\n\n                // 获取数据\n                let post = (url, data) => {\n                    onBeforeSendHeadersAddListener(onBeforeSendHeaders, {urls: urls})\n                    let p = new URLSearchParams(data)\n                    httpPost({url: url, body: p.toString()}).then(r => {\n                        removeListener()\n                        if (r) {\n                            resolve(this.unify(r, q, srcLan, tarLan))\n                        } else {\n                            reject('sogou trans error!')\n                        }\n                    }).catch(e => {\n                        removeListener()\n                        reject(e)\n                    })\n                }\n            })\n        },*/\n        trans(q, srcLan, tarLan) {\n            srcLan = this.langMap[srcLan] || 'auto'\n            tarLan = this.langMap[tarLan] || 'zh-CHS'\n            return new Promise((resolve, reject) => {\n                if (q.length > 5000) return reject('The text is too large!')\n\n                let url = `https://fanyi.sogou.com/?keyword=${encodeURI(q)}&transfrom=${srcLan}&transto=${tarLan}&model=general`\n                let pageId = 'fy_soGou'\n                openIframe(pageId, url, 60 * 1000)\n                httpGet(url, 'document').then(r => {\n                    // 获取翻译结果\n                    let data\n                    let sEl = r.querySelectorAll('script')\n                    for (let i = 0; i < sEl.length; i++) {\n                        let el = sEl[i]\n                        if (el.getAttribute('src')) continue\n                        let s = el.textContent\n                        if (!s) continue\n                        let arr = s.match(/window\\.__INITIAL_STATE__=(.*?);\\(function\\(\\){var s;/m)\n                        if (!arr || arr.length < 2) continue\n                        try {\n                            data = JSON.parse(arr[1])\n                            if (data) break\n                        } catch (e) {\n                            debug('json error!')\n                            return reject('JSON.parse Error!')\n                        }\n                    }\n                    if (data) {\n                        resolve(this.unify(data, q, srcLan, tarLan))\n                    } else {\n                        reject('Get data is empty!')\n                    }\n                }).catch(e => {\n                    reject(e)\n                })\n            })\n        },\n        unify(r, text, srcLan, tarLan) {\n            // console.log('sogou:', r, text, srcLan, tarLan)\n            // console.log(JSON.stringify(r))\n            // 修正改版 2021.1.8\n            if (srcLan === 'auto') {\n                let str = getJSONValue(r, 'textTranslate.translateData.detect.detect')\n                if (str && isString(str)) srcLan = str\n            }\n            let map = this.langMapInvert\n            srcLan = map[srcLan] || 'auto'\n            tarLan = map[tarLan] || ''\n            let data = []\n            let tar = getJSONValue(r, 'textTranslate.result')\n            if (tar) {\n                let srcArr = text.split('\\n')\n                let tarArr = tar.split('\\n')\n                tarArr.forEach((tar, key) => {\n                    if (tar) data.push({srcText: srcArr[key] || '', tarText: tar})\n                })\n            }\n            if (setting.translateThin) return {text, srcLan, tarLan, lanTTS: null, data} // 精简显示\n\n            // 重点词汇\n            let s = ''\n            let keywords = getJSONValue(r, 'textTranslate.translateData.keywords')\n            if (keywords && keywords.length > 0) {\n                s += `<div class=\"case_dd\"><div class=\"case_dd_head\">重点词汇</div>`\n                s += `<div class=\"case_dd_parts\">`\n                keywords.forEach(v => {\n                    if (v.key && v.value) s += `<p><b data-search=\"true\">${v.key}</b>${v.value}</p>`\n                })\n                s += `</div></div>`\n            }\n\n            // 音标\n            let phonetic = getJSONValue(r, 'textTranslate.translateData.voice.phonetic')\n            let phStr = ''\n            if (phonetic && phonetic.length > 0) {\n                let getIconHTML = function (type, filename) {\n                    if (type !== 'uk') type = 'us'\n                    let title = type === 'uk' ? '英音' : '美音'\n                    filename = (filename.substring(0, 2) === '//' ? 'https:' : 'https://fanyi.sogou.com') + filename\n                    return `<i class=\"dmx-icon dmx_ripple\" data-type=\"${type}\" data-src-mp3=\"${filename}\" title=\"${title}\"></i>`\n                }\n                let ph_uk = '', ph_us = '', ph_mp3 = ''\n                phonetic.forEach(v => {\n                    if (!v.text || !v.type) return\n                    if (!v.filename) {\n                        v.filename = `/reventondc/synthesis?text=${encodeURI(text)}&speed=1&lang=${srcLan}&from=translateweb`\n                    }\n                    if (v.type === 'uk') ph_uk = v.text\n                    if (v.type === 'usa') ph_us = v.text\n                    ph_mp3 += getIconHTML(v.type, v.filename)\n                })\n                if (ph_uk && ph_mp3) phStr += `<div class=\"case_dd_ph\">[${ph_uk}${ph_uk !== ph_us ? ' $ ' + ph_us : ''}]${ph_mp3}</div>`\n            }\n\n            // 搜狗用的牛津词典 (上一个版本，层级太深，这次改版简化了。)\n            let wordCard = getJSONValue(r, 'textTranslate.translateData.wordCard')\n            if (isObject(wordCard) && wordCard.usualDict) {\n                s += `<div class=\"case_dd\">`\n                s += `<div class=\"case_dd_head\">${text}</div>`  // 查询的单词\n                s += phStr\n\n                // 释义\n                let {usualDict, exchange, levelList} = wordCard\n                if (usualDict && usualDict.length > 0) {\n                    s += `<div class=\"case_dd_parts\">`\n                    usualDict.forEach(v => {\n                        s += `<p>${v.pos ? `<b>${v.pos}</b>` : ''}${isArray(v.values) ? v.values.join('；') : v.values}</p>`\n                    })\n                    s += `</div>`\n                }\n\n                // 单词形态\n                if (exchange) {\n                    s += `<div class=\"case_dd_exchange\">`\n                    let exchangeObj = {\n                        word_third: '第三人称单数',\n                        word_pl: '复数',\n                        word_ing: '现在分词',\n                        word_past: '过去式',\n                        word_done: '过去分词',\n                        word_er: '比较级',\n                        word_est: '最高级',\n                        word_proto: '原型',\n                    }\n                    for (let [k, v] of Object.entries(exchange)) {\n                        let wordStr = ''\n                        v.forEach(word => {\n                            if (word) wordStr += `<a data-search=\"true\">${word}</a>`\n                        })\n                        s += `<b>${exchangeObj[k] || '其他'}</b><u>${wordStr}</u>`\n                    }\n                    s += `</div>`\n                }\n\n                // 单词标签\n                if (levelList && levelList.length > 0) {\n                    s += `<div class=\"case_dd_tags\">`\n                    levelList.forEach(tag => {\n                        if (tag) s += `<u>${tag}</u>`\n                    })\n                    s += `</div>`\n                }\n                s += `</div>`\n            }\n\n            return {text, srcLan, tarLan, lanTTS: null, data, extra: s}\n        },\n        async query(q, srcLan, tarLan) {\n            return checkRetry(() => this.trans(q, srcLan, tarLan), 2)\n        },\n        tts(q, lan) {\n            lan = this.langMap[lan] || 'en'\n            return new Promise((resolve) => {\n                let getUrl = (s) => {\n                    return `https://fanyi.sogou.com/reventondc/synthesis?text=${encodeURI(s)}&speed=1&lang=${lan}&from=translateweb&speaker=1`\n                }\n                let r = []\n                let arr = sliceStr(q, 128)\n                arr.forEach(text => {\n                    r.push(getUrl(text))\n                })\n                resolve(r)\n            })\n        },\n        link(q, srcLan, tarLan) {\n            srcLan = this.langMap[srcLan] || 'auto'\n            tarLan = this.langMap[tarLan] || 'zh-CHS'\n            return `https://fanyi.sogou.com/?keyword=${encodeURI(q)}&transfrom=${srcLan}&transto=${tarLan}&model=general`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/translate/youdao.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nfunction youdaoTranslate() {\n    return {\n        token: {\n            token: '',\n            date: 0,\n        },\n        langMap: {\n            \"en\": \"en\",\n            \"ru\": \"ru\",\n            \"pt\": \"pt\",\n            \"hi\": \"hi\",\n            \"de\": \"de\",\n            \"el\": \"el\",\n            \"it\": \"it\",\n            \"id\": \"id\",\n            \"nl\": \"nl\",\n            \"kor\": \"ko\",\n            \"jp\": \"ja\",\n            \"fra\": \"fr\",\n            \"spa\": \"es\",\n            \"ara\": \"ar\",\n            \"dan\": \"da\",\n            \"fin\": \"fi\",\n            \"may\": \"ms\",\n            \"vie\": \"vi\",\n            \"zh\": \"zh-CHS\"\n        },\n        langMapInvert: {},\n        lanTTS: [\"en\", \"zh\", \"jp\", \"kor\", \"fra\"],\n        md5(e) {\n            var n = function (e, t) {\n                return e << t | e >>> 32 - t\n            }, r = function (e, t) {\n                var n, r, i, a, o\n                return i = 2147483648 & e,\n                    a = 2147483648 & t,\n                    n = 1073741824 & e,\n                    r = 1073741824 & t,\n                    o = (1073741823 & e) + (1073741823 & t),\n                    n & r ? 2147483648 ^ o ^ i ^ a : n | r\n                        ? 1073741824 & o ? 3221225472 ^ o ^ i ^ a\n                            : 1073741824 ^ o ^ i ^ a : o ^ i ^ a\n            }, i = function (e, t, n) {\n                return e & t | ~e & n\n            }, a = function (e, t, n) {\n                return e & n | t & ~n\n            }, o = function (e, t, n) {\n                return e ^ t ^ n\n            }, s = function (e, t, n) {\n                return t ^ (e | ~n)\n            }, l = function (e, t, a, o, s, l, c) {\n                return e = r(e, r(r(i(t, a, o), s), c)), r(n(e, l), t)\n            }, c = function (e, t, i, o, s, l, c) {\n                return e = r(e, r(r(a(t, i, o), s), c)), r(n(e, l), t)\n            }, u = function (e, t, i, a, s, l, c) {\n                return e = r(e, r(r(o(t, i, a), s), c)), r(n(e, l), t)\n            }, d = function (e, t, i, a, o, l, c) {\n                return e = r(e, r(r(s(t, i, a), o), c)), r(n(e, l), t)\n            }, f = function (e) {\n                for (var t, n = e.length, r = n + 8, i = 16 * ((r - r % 64) / 64 + 1), a = Array(i - 1), o = 0, s = 0; s < n;) {\n                    o = s % 4 * 8, a[t = (s - s % 4) / 4] = a[t] | e.charCodeAt(s) << o, s++\n                }\n                return t = (s - s % 4) / 4, o = s % 4 * 8, a[t] = a[t] | 128 << o, a[i - 2] = n << 3, a[i - 1] = n >>> 29, a\n            }, p = function (e) {\n                var t, n = \"\", r = \"\"\n                for (t = 0; t <= 3; t++) n += (r = \"0\" + (e >>> 8 * t & 255).toString(16)).substr(r.length - 2, 2)\n                return n\n            }, h = function (e) {\n                e = e.replace(/\\x0d\\x0a/g, \"\\n\")\n                for (var t = \"\", n = 0; n < e.length; n++) {\n                    var r = e.charCodeAt(n)\n                    if (r < 128) {\n                        t += String.fromCharCode(r)\n                    } else if (r > 127 && r < 2048) {\n                        t += String.fromCharCode(r >> 6 | 192)\n                        t += String.fromCharCode(63 & r | 128)\n                    } else if (r >= 55296 && r <= 56319) {\n                        if (n + 1 < e.length) {\n                            var i = e.charCodeAt(n + 1)\n                            if (i >= 56320 && i <= 57343) {\n                                var a = 1024 * (r - 55296) + (i - 56320) + 65536\n                                t += String.fromCharCode(240 | a >> 18 & 7),\n                                    t += String.fromCharCode(128 | a >> 12 & 63),\n                                    t += String.fromCharCode(128 | a >> 6 & 63),\n                                    t += String.fromCharCode(128 | 63 & a), n++\n                            }\n                        }\n                    } else {\n                        t += String.fromCharCode(r >> 12 | 224),\n                            t += String.fromCharCode(r >> 6 & 63 | 128),\n                            t += String.fromCharCode(63 & r | 128)\n                    }\n                }\n                return t\n            }, m = function (e) {\n                var t, n, i, a, o, s, m, g, v, y = Array()\n                e = h(e), y = f(e), s = 1732584193, m = 4023233417, g = 2562383102, v = 271733878\n                for (t = 0; t < y.length; t += 16) {\n                    n = s, i = m, a = g, o = v, s = l(s, m, g, v, y[t + 0], 7, 3614090360),\n                        v = l(v, s, m, g, y[t + 1], 12, 3905402710),\n                        g = l(g, v, s, m, y[t + 2], 17, 606105819),\n                        m = l(m, g, v, s, y[t + 3], 22, 3250441966),\n                        s = l(s, m, g, v, y[t + 4], 7, 4118548399),\n                        v = l(v, s, m, g, y[t + 5], 12, 1200080426),\n                        g = l(g, v, s, m, y[t + 6], 17, 2821735955),\n                        m = l(m, g, v, s, y[t + 7], 22, 4249261313),\n                        s = l(s, m, g, v, y[t + 8], 7, 1770035416),\n                        v = l(v, s, m, g, y[t + 9], 12, 2336552879),\n                        g = l(g, v, s, m, y[t + 10], 17, 4294925233),\n                        m = l(m, g, v, s, y[t + 11], 22, 2304563134),\n                        s = l(s, m, g, v, y[t + 12], 7, 1804603682),\n                        v = l(v, s, m, g, y[t + 13], 12, 4254626195),\n                        g = l(g, v, s, m, y[t + 14], 17, 2792965006),\n                        m = l(m, g, v, s, y[t + 15], 22, 1236535329),\n                        s = c(s, m, g, v, y[t + 1], 5, 4129170786),\n                        v = c(v, s, m, g, y[t + 6], 9, 3225465664),\n                        g = c(g, v, s, m, y[t + 11], 14, 643717713),\n                        m = c(m, g, v, s, y[t + 0], 20, 3921069994),\n                        s = c(s, m, g, v, y[t + 5], 5, 3593408605),\n                        v = c(v, s, m, g, y[t + 10], 9, 38016083),\n                        g = c(g, v, s, m, y[t + 15], 14, 3634488961),\n                        m = c(m, g, v, s, y[t + 4], 20, 3889429448),\n                        s = c(s, m, g, v, y[t + 9], 5, 568446438),\n                        v = c(v, s, m, g, y[t + 14], 9, 3275163606),\n                        g = c(g, v, s, m, y[t + 3], 14, 4107603335),\n                        m = c(m, g, v, s, y[t + 8], 20, 1163531501),\n                        s = c(s, m, g, v, y[t + 13], 5, 2850285829),\n                        v = c(v, s, m, g, y[t + 2], 9, 4243563512),\n                        g = c(g, v, s, m, y[t + 7], 14, 1735328473),\n                        m = c(m, g, v, s, y[t + 12], 20, 2368359562),\n                        s = u(s, m, g, v, y[t + 5], 4, 4294588738),\n                        v = u(v, s, m, g, y[t + 8], 11, 2272392833),\n                        g = u(g, v, s, m, y[t + 11], 16, 1839030562),\n                        m = u(m, g, v, s, y[t + 14], 23, 4259657740),\n                        s = u(s, m, g, v, y[t + 1], 4, 2763975236),\n                        v = u(v, s, m, g, y[t + 4], 11, 1272893353),\n                        g = u(g, v, s, m, y[t + 7], 16, 4139469664),\n                        m = u(m, g, v, s, y[t + 10], 23, 3200236656),\n                        s = u(s, m, g, v, y[t + 13], 4, 681279174),\n                        v = u(v, s, m, g, y[t + 0], 11, 3936430074),\n                        g = u(g, v, s, m, y[t + 3], 16, 3572445317),\n                        m = u(m, g, v, s, y[t + 6], 23, 76029189),\n                        s = u(s, m, g, v, y[t + 9], 4, 3654602809),\n                        v = u(v, s, m, g, y[t + 12], 11, 3873151461),\n                        g = u(g, v, s, m, y[t + 15], 16, 530742520),\n                        m = u(m, g, v, s, y[t + 2], 23, 3299628645),\n                        s = d(s, m, g, v, y[t + 0], 6, 4096336452),\n                        v = d(v, s, m, g, y[t + 7], 10, 1126891415),\n                        g = d(g, v, s, m, y[t + 14], 15, 2878612391),\n                        m = d(m, g, v, s, y[t + 5], 21, 4237533241),\n                        s = d(s, m, g, v, y[t + 12], 6, 1700485571),\n                        v = d(v, s, m, g, y[t + 3], 10, 2399980690),\n                        g = d(g, v, s, m, y[t + 10], 15, 4293915773),\n                        m = d(m, g, v, s, y[t + 1], 21, 2240044497),\n                        s = d(s, m, g, v, y[t + 8], 6, 1873313359),\n                        v = d(v, s, m, g, y[t + 15], 10, 4264355552),\n                        g = d(g, v, s, m, y[t + 6], 15, 2734768916),\n                        m = d(m, g, v, s, y[t + 13], 21, 1309151649),\n                        s = d(s, m, g, v, y[t + 4], 6, 4149444226),\n                        v = d(v, s, m, g, y[t + 11], 10, 3174756917),\n                        g = d(g, v, s, m, y[t + 2], 15, 718787259),\n                        m = d(m, g, v, s, y[t + 9], 21, 3951481745),\n                        s = r(s, n), m = r(m, i), g = r(g, a), v = r(v, o)\n                }\n                return (p(s) + p(m) + p(g) + p(v)).toLowerCase()\n            }\n            return m(e)\n        },\n        init() {\n            this.langMapInvert = invertObject(this.langMap)\n            let str = localStorage.getItem('youdaoToken')\n            if (str) this.token = JSON.parse(str)\n            return this\n        },\n        setToken(options) {\n            this.token = Object.assign(this.token, options)\n            localStorage.setItem('youdaoToken', JSON.stringify(this.token))\n        },\n        getToken() {\n            return new Promise((resolve, reject) => {\n                httpGet('https://fanyi.youdao.com/').then(r => {\n                    let arr = r.match(/<script.*?src=\"(http[^\"]+fanyi\\.min\\.js)\"/)\n                    if (arr) {\n                        httpGet(arr[1]).then(r => {\n                            let tArr = r.match(/sign:n\\.md5\\(\"fanyideskweb\"\\+e\\+i\\+\"([^\"]+)\"\\)/)\n                            if (tArr) {\n                                let token = {token: tArr[1], date: Math.floor(Date.now() / 36e5)}\n                                this.setToken(token)\n                                resolve(token)\n                            } else {\n                                reject('youdao token error!')\n                            }\n                        }).catch(e => {\n                            reject('youdao js error:', e)\n                        })\n                    } else {\n                        reject('youdao *.min.js error!')\n                    }\n                }).catch(e => {\n                    reject('youdao home error:', e)\n                })\n            })\n        },\n        addListenerRequest() {\n            onBeforeSendHeadersAddListener(this.onChangeHeaders,\n                {urls: ['*://fanyi.youdao.com/*'], types: ['xmlhttprequest']})\n        },\n        removeListenerRequest() {\n            onBeforeSendHeadersRemoveListener(this.onChangeHeaders)\n        },\n        onChangeHeaders(details) {\n            let h = details.requestHeaders\n            /*h.some((v, k) => {\n                if (v.name.toLowerCase() === 'referer') {\n                    h.splice(k, 1)\n                    return true\n                }\n            })*/\n            h.push({name: 'Origin', value: 'https://fanyi.youdao.com'})\n            h.push({name: 'Referer', value: 'https://fanyi.youdao.com'})\n            return {requestHeaders: h}\n        },\n        trans(q, srcLan, tarLan) {\n            srcLan = this.langMap[srcLan] || 'AUTO'\n            tarLan = this.langMap[tarLan] || 'zh-CHS'\n            if (srcLan !== 'zh-CHS') tarLan = 'zh-CHS' // 有道只支持单一中文互换翻译\n            return new Promise((resolve, reject) => {\n                if (q.length > 5000) return reject('The text is too large!')\n                if (!this.token.token) return reject('youdao token empty!')\n                this.addListenerRequest()\n                let bv = this.md5(navigator.appVersion), ts = '' + (new Date).getTime(),\n                    salt = ts + Math.floor(10 * Math.random())\n                let sign = this.md5(\"fanyideskweb\" + q + salt + this.token.token)\n                let p = new URLSearchParams(`i=${q}&from=${srcLan}&to=${tarLan}&smartresult=dict&client=fanyideskweb&salt=${salt}&sign=${sign}&lts=${ts}&bv=${bv}&doctype=json&version=2.1&keyfrom=fanyi.web&action=FY_BY_CLICKBUTTION`)\n                let url = 'https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'\n                httpPost({url: url, body: p.toString()}).then(r => {\n                    this.removeListenerRequest()\n                    if (r) {\n                        resolve(this.unify(r, q, srcLan, tarLan))\n                    } else {\n                        reject('youdao trans error!')\n                    }\n                }).catch(e => {\n                    this.removeListenerRequest()\n                    reject(e)\n                })\n            })\n        },\n        unify(r, q, srcLan, tarLan) {\n            // console.log('youdao:', r, q, srcLan, tarLan)\n            let lanArr = r.type.split('2')\n            if (lanArr.length > 1) srcLan = lanArr[0]\n            let map = this.langMapInvert\n            srcLan = map[srcLan] || 'auto'\n            tarLan = map[tarLan] || ''\n            let ret = {text: q, srcLan: srcLan, tarLan: tarLan, lanTTS: this.lanTTS, data: []}\n            let arr = r && r.translateResult\n            arr && arr.forEach(val => {\n                val.forEach(v => {\n                    if (v.tgt && v.src) ret.data.push({srcText: v.src, tarText: v.tgt})\n                })\n            })\n            return ret\n        },\n        async query(q, srcLan, tarLan, noCache) {\n            return checkRetry(async (i) => {\n                let t = Math.floor(Date.now() / 36e5)\n                let d = this.token.date\n                if (i > 0) noCache = true\n                if (noCache || !d || Number(d) !== t) {\n                    await this.getToken().catch(err => console.warn(err))\n                }\n                return this.trans(q, srcLan, tarLan)\n            })\n        },\n        tts(q, lan) {\n            return new Promise((resolve, reject) => {\n                if (!inArray(lan, this.lanTTS)) return reject('This language is not supported!')\n                let lanArr = {en: \"eng\", zh: 'zh-CHS', jp: \"jap\", kor: \"ko\", fra: \"fr\"}\n                let le = lanArr[lan] || lanArr.en\n                // resolve(`https://tts.youdao.com/fanyivoice?word=${encodeURI(q)}&le=eng&keyfrom=speaker-target`)\n                let getUrl = (s) => {\n                    return `https://dict.youdao.com/dictvoice?audio=${encodeURIComponent(s)}&type=2`\n                }\n                let r = []\n                let arr = sliceStr(q, 128)\n                arr.forEach(text => {\n                    r.push(getUrl(text))\n                })\n                resolve(r)\n            })\n        },\n        link(q, srcLan, tarLan) {\n            return `https://fanyi.youdao.com/?d_sl=${srcLan}&d_tl=${tarLan}&d_text=${encodeURI(q)}`\n        },\n    }\n}\n"
  },
  {
    "path": "src/js/video.js",
    "content": "'use strict'\n\n/**\n * Dream Translate\n * https://github.com/ryanker/dream_translate\n * @Author Ryan <dream39999@gmail.com>\n * @license MIT License\n */\n\nlet u = new URL(location.href)\nlet video = document.createElement('video')\nvideo.setAttribute('style', 'width:400px;height:224px;outline:0')\nvideo.controls = true\nvideo.poster = u.searchParams.get('thumbUrl')\nvideo.src = u.searchParams.get('videoUrl')\ndocument.body.appendChild(video)\n"
  },
  {
    "path": "src/manifest.json",
    "content": "{\n  \"name\": \"梦想划词翻译—聚合词典搜索\",\n  \"description\": \"梦想划词翻译是为阅读和学习外语而开发的一款翻译和查词工具，聚合数十款在线词典和在线翻译。\",\n  \"version\": \"1.6.27\",\n  \"manifest_version\": 2,\n  \"icons\": {\n    \"128\": \"icon/128.png\"\n  },\n  \"background\": {\n    \"scripts\": [\n      \"js/lib/md5.min.js\",\n      \"js/common.js\",\n      \"js/db.js\",\n      \"js/background.js\"\n    ]\n  },\n  \"content_scripts\": [\n    {\n      \"matches\": [\n        \"\\u003Call_urls>\"\n      ],\n      \"js\": [\n        \"js/common.js\",\n        \"js/content.js\"\n      ],\n      \"css\": [\n        \"css/content.css\"\n      ],\n      \"run_at\": \"document_start\"\n    },\n    {\n      \"all_frames\": true,\n      \"matches\": [\n        \"\\u003Call_urls>\"\n      ],\n      \"js\": [\n        \"js/frame.js\"\n      ]\n    }\n  ],\n  \"browser_action\": {\n    \"default_popup\": \"html/popup.html\",\n    \"default_title\": \"梦想划词翻译\",\n    \"default_icon\": \"icon/128.png\"\n  },\n  \"web_accessible_resources\": [\n    \"css/*\",\n    \"html/*\"\n  ],\n  \"permissions\": [\n    \"storage\",\n    \"clipboardWrite\",\n    \"clipboardRead\",\n    \"cookies\",\n    \"contextMenus\",\n    \"webRequest\",\n    \"webRequestBlocking\",\n    \"unlimitedStorage\",\n    \"tts\",\n    \"\\u003Call_urls>\"\n  ],\n  \"homepage_url\": \"https://github.com/ryanker/dream_translate\",\n  \"commands\": {\n    \"_execute_browser_action\": {\n      \"description\": \"打开翻译面板\",\n      \"suggested_key\": {\n        \"default\": \"Alt+D\"\n      }\n    },\n    \"openWindow\": {\n      \"description\": \"打开翻译窗口\",\n      \"global\": true,\n      \"suggested_key\": {\n        \"default\": \"Ctrl+Shift+9\"\n      }\n    },\n    \"toggleScribble\": {\n      \"description\": \"划词翻译开关\",\n      \"suggested_key\": {\n        \"default\": \"Ctrl+Shift+X\"\n      }\n    },\n    \"cropImage\": {\n      \"description\": \"截图识别翻译\",\n      \"suggested_key\": {\n        \"default\": \"Ctrl+Shift+A\"\n      }\n    },\n    \"stopPlayAudio\": {\n      \"description\": \"停止播放声音\"\n    },\n    \"clipboardTrans\": {\n      \"description\": \"剪贴板内容翻译\"\n    }\n  }\n}\n\n\n"
  },
  {
    "path": "tool/alibaba.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <style>\n        table {\n            box-sizing: border-box;\n            border-color: grey;\n            font-size: 14px;\n        }\n\n        table tr td {\n            min-width: 120px;\n        }\n    </style>\n</head>\n<body>\n<ul class=\"source-list\" style=\"display: none;\">\n    <li><a href=\"javascript:;\" data-lang=\"en\">英语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"zh\">中文</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"ru\" data-spm-anchor-id=\"a271w.8016606.0.0\">俄罗斯语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"es\">西班牙语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"fr\">法语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"ar\">阿拉伯语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"tr\">土耳其语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"pt\">葡萄牙语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"th\">泰语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"id\">印尼语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"it\">意大利语</a></li>\n    <li><a href=\"javascript:;\" data-lang=\"vi\">越南语</a></li>\n</ul>\n<script src=\"bd.js\"></script>\n<script>\n    // alibaba language\n    let aliObj = {}\n    document.querySelectorAll('[data-lang]').forEach(el => {\n        let lang = el.getAttribute('data-lang')\n        let text = el.innerText\n        // console.log(lang, text)\n        aliObj[lang] = text\n    })\n    console.log(aliObj)\n\n    // baidu language list\n    let bdToObj = function (name) {\n        let i = 0\n        let o = {}\n        for (let k in bdList) {\n            i++\n            let n = bdList[k][name]\n            if (n) o[n] = k\n        }\n        console.log(JSON.stringify(o))\n        console.log(i)\n        return o\n    }\n    let bdObj = bdToObj('zhName')\n\n    let err = 0\n    let a1 = [], a2 = [], ae = []\n    for (const [k, v] of Object.entries(aliObj)) {\n        // console.log(k, v)\n        if (bdList[k]) {\n            // key 一样的情况\n            a1.push({key: k, aliName: v, bdName: bdList[k].zhName})\n        } else if (bdObj[v]) {\n            // 中文名 一样的情况\n            a2.push({bdKey: bdObj[v], aliKey: k, name: v})\n        } else {\n            // 都没有的情况\n            err++\n            ae.push({key: k, name: v})\n        }\n    }\n    // console.log(JSON.stringify(a3))\n    console.log('err:', err)\n\n    let obj = {} // 最终的对应表\n    // key 一致的数据\n    document.write(`<table style=\"color:green\">`)\n    a1.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.aliName}</td><td>${v.bdName}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // name 一致的数据\n    document.write(`<table>`)\n    a2.forEach((v, k) => {\n        obj[v.bdKey] = v.aliKey\n        document.write(`<tr><td>${k + 1}</td><td>${v.bdKey}</td><td>${v.aliKey}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // 都没有的情况\n    document.write(`<table style=\"color:red\">`)\n    ae.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // document.write(`<hr>`)\n    console.log(JSON.stringify(obj))\n\n    // 支持转换的语言\n    // see https://translate.alibaba.com/translationopenseviceapp/trans/acquire_supportLanguage.do\n    let supportLanguage = {\n        \"sourceLanguage\": [\n            \"en\",\n            \"zh\",\n            \"ru\",\n            \"es\",\n            \"fr\",\n            \"ar\",\n            \"tr\",\n            \"pt\",\n            \"th\",\n            \"id\",\n            \"it\",\n            \"vi\"\n        ],\n        \"targetLanguage\": [\n            \"en\",\n            \"zh\",\n            \"ru\",\n            \"es\",\n            \"fr\",\n            \"ar\",\n            \"tr\",\n            \"pt\",\n            \"th\",\n            \"id\",\n            \"it\",\n            \"vi\"\n        ],\n        \"languageMap\": [\n            {\n                \"sourceLuange\": \"en\",\n                \"targetLanguages\": [\n                    \"zh\",\n                    \"ru\",\n                    \"es\",\n                    \"fr\",\n                    \"ar\",\n                    \"tr\",\n                    \"pt\",\n                    \"th\",\n                    \"id\",\n                    \"vi\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"zh\",\n                \"targetLanguages\": [\n                    \"en\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"ru\",\n                \"targetLanguages\": [\n                    \"en\",\n                    \"es\",\n                    \"tr\",\n                    \"it\",\n                    \"fr\",\n                    \"pt\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"es\",\n                \"targetLanguages\": [\n                    \"en\",\n                    \"ru\",\n                    \"tr\",\n                    \"it\",\n                    \"fr\",\n                    \"pt\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"fr\",\n                \"targetLanguages\": [\n                    \"en\",\n                    \"ru\",\n                    \"tr\",\n                    \"it\",\n                    \"es\",\n                    \"pt\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"ar\",\n                \"targetLanguages\": [\n                    \"en\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"tr\",\n                \"targetLanguages\": [\n                    \"en\",\n                    \"ru\",\n                    \"fr\",\n                    \"it\",\n                    \"es\",\n                    \"pt\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"pt\",\n                \"targetLanguages\": [\n                    \"en\",\n                    \"ru\",\n                    \"fr\",\n                    \"it\",\n                    \"es\",\n                    \"tr\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"it\",\n                \"targetLanguages\": [\n                    \"en\",\n                    \"ru\",\n                    \"fr\",\n                    \"pt\",\n                    \"es\",\n                    \"tr\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"th\",\n                \"targetLanguages\": [\n                    \"en\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"id\",\n                \"targetLanguages\": [\n                    \"en\"\n                ]\n            },\n            {\n                \"sourceLuange\": \"vi\",\n                \"targetLanguages\": [\n                    \"en\"\n                ]\n            }\n        ]\n    }\n\n    let pairMap = {}\n    let M = supportLanguage.languageMap\n    for (const k in M) {\n        let v = M[k]\n        // console.log(v)\n        pairMap[v.sourceLuange] = v.targetLanguages\n    }\n    console.log(JSON.stringify(pairMap))\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tool/baidu.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <style>\n        table {\n            box-sizing: border-box;\n            border-color: grey;\n            font-size: 14px;\n        }\n\n        table tr td {\n            min-width: 150px;\n        }\n    </style>\n</head>\n<body>\n<script src=\"bd.js\"></script>\n<script>\n    for (let k in bdList) {\n        bdList[k].zhName = bdList[k].zhName.trim()\n        bdList[k].enName = bdList[k].enName.trim()\n        delete bdList[k].pinyin\n        delete bdList[k].popularity\n    }\n    console.log(JSON.stringify(bdList))\n\n    document.write(`<table style=\"color:green\">`)\n    for (let k in bdList) {\n        let v = bdList[k]\n        document.write(`<tr><td>${k}</td><td>${v.zhName}</td><td>${v.enName}</td></tr>`)\n    }\n    document.write(`</table>`)\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tool/bd.js",
    "content": "var bdList = {\n    auto: {\n        zhName: \"自动检测\",\n        enName: \"Auto Detect\",\n        pinyin: \"zidongjiance\",\n        popularity: {\n            zh: 201,\n            en: 201\n        }\n    },\n    zh: {\n        zhName: \"中文(简体)\",\n        enName: \"Chinese\",\n        pinyin: \"zhongwenjianti\",\n        popularity: {\n            zh: 193,\n            en: 30\n        }\n    },\n    en: {\n        zhName: \"英语\",\n        enName: \"English\",\n        pinyin: \"yingyu\",\n        popularity: {\n            zh: 179,\n            en: 48\n        }\n    },\n    jp: {\n        zhName: \"日语\",\n        enName: \"Japanese\",\n        pinyin: \"riyu\",\n        popularity: {\n            zh: 132,\n            en: 82\n        }\n    },\n    th: {\n        zhName: \"泰语\",\n        enName: \"Thai\",\n        pinyin: \"taiyu\",\n        popularity: {\n            zh: 148,\n            en: 173\n        }\n    },\n    spa: {\n        zhName: \"西班牙语\",\n        enName: \"Spanish\",\n        pinyin: \"xibanyayu\",\n        popularity: {\n            zh: 164,\n            en: 151\n        }\n    },\n    ara: {\n        zhName: \"阿拉伯语\",\n        enName: \"Arabic\",\n        pinyin: \"alaboyu\",\n        popularity: {\n            zh: 1,\n            en: 1\n        }\n    },\n    fra: {\n        zhName: \"法语\",\n        enName: \"French\",\n        pinyin: \"fayu\",\n        popularity: {\n            zh: 46,\n            en: 51\n        }\n    },\n    kor: {\n        zhName: \"韩语\",\n        enName: \"Korean\",\n        pinyin: \"hanyu\",\n        popularity: {\n            zh: 63,\n            en: 84\n        }\n    },\n    ru: {\n        zhName: \"俄语\",\n        enName: \"Russian\",\n        pinyin: \"eyu\",\n        popularity: {\n            zh: 45,\n            en: 146\n        }\n    },\n    de: {\n        zhName: \"德语\",\n        enName: \"German\",\n        pinyin: \"deyu\",\n        popularity: {\n            zh: 39,\n            en: 57\n        }\n    },\n    pt: {\n        zhName: \"葡萄牙语\",\n        enName: \"Portuguese\",\n        pinyin: \"putaoyayu\",\n        popularity: {\n            zh: 125,\n            en: 138\n        }\n    },\n    it: {\n        zhName: \"意大利语\",\n        enName: \"Italian\",\n        pinyin: \"yidaliyu\",\n        popularity: {\n            zh: 181,\n            en: 73\n        }\n    },\n    el: {\n        zhName: \"希腊语\",\n        enName: \"Greek\",\n        pinyin: \"xilayu\",\n        popularity: {\n            zh: 166,\n            en: 58\n        }\n    },\n    nl: {\n        zhName: \"荷兰语\",\n        enName: \"Dutch\",\n        pinyin: \"helanyu\",\n        popularity: {\n            zh: 64,\n            en: 45\n        }\n    },\n    pl: {\n        zhName: \"波兰语\",\n        enName: \"Polish\",\n        pinyin: \"bolanyu\",\n        popularity: {\n            zh: 18,\n            en: 137\n        }\n    },\n    fin: {\n        zhName: \"芬兰语\",\n        enName: \"Finnish\",\n        pinyin: \"fenlanyu\",\n        popularity: {\n            zh: 47,\n            en: 52\n        }\n    },\n    cs: {\n        zhName: \"捷克语\",\n        enName: \"Czech\",\n        pinyin: \"jiekeyu\",\n        popularity: {\n            zh: 71,\n            en: 33\n        }\n    },\n    bul: {\n        zhName: \"保加利亚语\",\n        enName: \"Bulgarian\",\n        pinyin: \"baojialiyayu\",\n        popularity: {\n            zh: 19,\n            en: 15\n        }\n    },\n    dan: {\n        zhName: \"丹麦语\",\n        enName: \"Danish\",\n        pinyin: \"danmaiyu\",\n        popularity: {\n            zh: 40,\n            en: 46\n        }\n    },\n    est: {\n        zhName: \"爱沙尼亚语\",\n        enName: \"Estonian\",\n        pinyin: \"aishaniyayu\",\n        popularity: {\n            zh: 2,\n            en: 49\n        }\n    },\n    hu: {\n        zhName: \"匈牙利语\",\n        enName: \"Hungarian\",\n        pinyin: \"xiongyaliyu\",\n        popularity: {\n            zh: 165,\n            en: 64\n        }\n    },\n    rom: {\n        zhName: \"罗马尼亚语\",\n        enName: \"Romanian\",\n        pinyin: \"luomaniyayu\",\n        popularity: {\n            zh: 91,\n            en: 147\n        }\n    },\n    slo: {\n        zhName: \"斯洛文尼亚语\",\n        enName: \"Slovenian\",\n        pinyin: \"siluowenniyayu\",\n        popularity: {\n            zh: 134,\n            en: 153\n        }\n    },\n    swe: {\n        zhName: \"瑞典语\",\n        enName: \"Swedish\",\n        pinyin: \"ruidianyu\",\n        popularity: {\n            zh: 133,\n            en: 152\n        }\n    },\n    vie: {\n        zhName: \"越南语\",\n        enName: \"Vietnamese\",\n        pinyin: \"yuenanyu\",\n        popularity: {\n            zh: 180,\n            en: 190\n        }\n    },\n    yue: {\n        zhName: \"中文(粤语)\",\n        enName: \"Cantonese\",\n        pinyin: \"zhongwenyueyu\",\n        popularity: {\n            zh: 195,\n            en: 31\n        }\n    },\n    cht: {\n        zhName: \"中文(繁体)\",\n        enName: \"Traditional Chinese\",\n        pinyin: \"zhongwenfanti\",\n        popularity: {\n            zh: 194,\n            en: 172\n        }\n    },\n    wyw: {\n        zhName: \"中文(文言文)\",\n        enName: \"Classical Chinese\",\n        pinyin: \"zhongwenwenyanwen\",\n        popularity: {\n            zh: 196,\n            en: 32\n        }\n    },\n    afr: {\n        zhName: \"南非荷兰语\",\n        enName: \"Afrikaans\",\n        pinyin: \"nanfeihelanyu\",\n        popularity: {\n            zh: 121,\n            en: 7\n        }\n    },\n    alb: {\n        zhName: \"阿尔巴尼亚语\",\n        enName: \"Albanian\",\n        pinyin: \"aerbaniyayu\",\n        popularity: {\n            zh: 4,\n            en: 3\n        }\n    },\n    amh: {\n        zhName: \"阿姆哈拉语\",\n        enName: \"Amharic\",\n        pinyin: \"amuhalayu\",\n        popularity: {\n            zh: 6,\n            en: 5\n        }\n    },\n    arm: {\n        zhName: \"亚美尼亚语\",\n        enName: \"Armenian\",\n        pinyin: \"yameiniyayu\",\n        popularity: {\n            zh: 184,\n            en: 4\n        }\n    },\n    asm: {\n        zhName: \"阿萨姆语\",\n        enName: \"Assamese\",\n        pinyin: \"asamuyu\",\n        popularity: {\n            zh: 7,\n            en: 6\n        }\n    },\n    ast: {\n        zhName: \"阿斯图里亚斯语\",\n        enName: \"Asturian\",\n        pinyin: \"asituliyasiyu\",\n        popularity: {\n            zh: 12,\n            en: 13\n        }\n    },\n    aze: {\n        zhName: \"阿塞拜疆语\",\n        enName: \"Azerbaijani\",\n        pinyin: \"asaibaijiangyu\",\n        popularity: {\n            zh: 3,\n            en: 2\n        }\n    },\n    baq: {\n        zhName: \"巴斯克语\",\n        enName: \"Basque\",\n        pinyin: \"basikeyu\",\n        popularity: {\n            zh: 23,\n            en: 20\n        }\n    },\n    bel: {\n        zhName: \"白俄罗斯语\",\n        enName: \"Belarusian\",\n        pinyin: \"baieluosiyu\",\n        popularity: {\n            zh: 21,\n            en: 18\n        }\n    },\n    ben: {\n        zhName: \"孟加拉语\",\n        enName: \"Bengali\",\n        pinyin: \"mengjialayu\",\n        popularity: {\n            zh: 108,\n            en: 17\n        }\n    },\n    bos: {\n        zhName: \"波斯尼亚语\",\n        enName: \"Bosnian\",\n        pinyin: \"bosiniyayu\",\n        popularity: {\n            zh: 22,\n            en: 19\n        }\n    },\n    bur: {\n        zhName: \"缅甸语\",\n        enName: \"Burmese\",\n        pinyin: \"miandianyu\",\n        popularity: {\n            zh: 106,\n            en: 16\n        }\n    },\n    cat: {\n        zhName: \"加泰罗尼亚语\",\n        enName: \"Catalan\",\n        pinyin: \"jiatailuoniyayu\",\n        popularity: {\n            zh: 72,\n            en: 34\n        }\n    },\n    ceb: {\n        zhName: \"宿务语\",\n        enName: \"Cebuano\",\n        pinyin: \"suwuyu\",\n        popularity: {\n            zh: 168,\n            en: 36\n        }\n    },\n    hrv: {\n        zhName: \"克罗地亚语\",\n        enName: \"Croatian\",\n        pinyin: \"keluodiyayu\",\n        popularity: {\n            zh: 76,\n            en: 35\n        }\n    },\n    epo: {\n        zhName: \"世界语\",\n        enName: \"Esperanto\",\n        pinyin: \"shijieyu\",\n        popularity: {\n            zh: 141,\n            en: 50\n        }\n    },\n    fao: {\n        zhName: \"法罗语\",\n        enName: \"Faroese\",\n        pinyin: \"faluoyu\",\n        popularity: {\n            zh: 50,\n            en: 55\n        }\n    },\n    fil: {\n        zhName: \"菲律宾语\",\n        enName: \"Filipino\",\n        pinyin: \"feilvbinyu\",\n        popularity: {\n            zh: 48,\n            en: 53\n        }\n    },\n    glg: {\n        zhName: \"加利西亚语\",\n        enName: \"Galician\",\n        pinyin: \"jialixiyayu\",\n        popularity: {\n            zh: 73,\n            en: 60\n        }\n    },\n    geo: {\n        zhName: \"格鲁吉亚语\",\n        enName: \"Georgian\",\n        pinyin: \"gelujiyayu\",\n        popularity: {\n            zh: 54,\n            en: 59\n        }\n    },\n    guj: {\n        zhName: \"古吉拉特语\",\n        enName: \"Gujarati\",\n        pinyin: \"gujilateyu\",\n        popularity: {\n            zh: 55,\n            en: 61\n        }\n    },\n    hau: {\n        zhName: \"豪萨语\",\n        enName: \"Hausa\",\n        pinyin: \"haosayu\",\n        popularity: {\n            zh: 67,\n            en: 68\n        }\n    },\n    heb: {\n        zhName: \"希伯来语\",\n        enName: \"Hebrew\",\n        pinyin: \"xibolaiyu\",\n        popularity: {\n            zh: 167,\n            en: 65\n        }\n    },\n    hi: {\n        zhName: \"印地语\",\n        enName: \"Hindi\",\n        pinyin: \"yindiyu\",\n        popularity: {\n            zh: 183,\n            en: 66\n        }\n    },\n    ice: {\n        zhName: \"冰岛语\",\n        enName: \"Icelandic\",\n        pinyin: \"bingdaoyu\",\n        popularity: {\n            zh: 24,\n            en: 76\n        }\n    },\n    ibo: {\n        zhName: \"伊博语\",\n        enName: \"Igbo\",\n        pinyin: \"yiboyu\",\n        popularity: {\n            zh: 186,\n            en: 75\n        }\n    },\n    id: {\n        zhName: \"印尼语\",\n        enName: \"Indonesian\",\n        pinyin: \"yinniyu\",\n        popularity: {\n            zh: 182,\n            en: 74\n        }\n    },\n    gle: {\n        zhName: \"爱尔兰语\",\n        enName: \"Irish\",\n        pinyin: \"aierlanyu\",\n        popularity: {\n            zh: 5,\n            en: 77\n        }\n    },\n    kan: {\n        zhName: \"卡纳达语\",\n        enName: \"Kannada\",\n        pinyin: \"kanadayu\",\n        popularity: {\n            zh: 77,\n            en: 87\n        }\n    },\n    kaz: {\n        zhName: \"哈萨克语\",\n        enName: \"Kazakh\",\n        pinyin: \"hasakeyu\",\n        popularity: {\n            zh: 65,\n            en: 85\n        }\n    },\n    kli: {\n        zhName: \"克林贡语\",\n        enName: \"Klingon\",\n        pinyin: \"kelingongyu\",\n        popularity: {\n            zh: 89,\n            en: 98\n        }\n    },\n    kur: {\n        zhName: \"库尔德语\",\n        enName: \"Kurdish\",\n        pinyin: \"kuerdeyu\",\n        popularity: {\n            zh: 80,\n            en: 88\n        }\n    },\n    lao: {\n        zhName: \"老挝语\",\n        enName: \"Lao\",\n        pinyin: \"laozhuayu\",\n        popularity: {\n            zh: 92,\n            en: 99\n        }\n    },\n    lat: {\n        zhName: \"拉丁语\",\n        enName: \"Latin\",\n        pinyin: \"ladingyu\",\n        popularity: {\n            zh: 93,\n            en: 100\n        }\n    },\n    lav: {\n        zhName: \"拉脱维亚语\",\n        enName: \"Latvian\",\n        pinyin: \"latuoweiyayu\",\n        popularity: {\n            zh: 95,\n            en: 102\n        }\n    },\n    lit: {\n        zhName: \"立陶宛语\",\n        enName: \"Lithuanian\",\n        pinyin: \"litaowanyu\",\n        popularity: {\n            zh: 94,\n            en: 101\n        }\n    },\n    ltz: {\n        zhName: \"卢森堡语\",\n        enName: \"Luxembourgish\",\n        pinyin: \"lusenbaoyu\",\n        popularity: {\n            zh: 96,\n            en: 103\n        }\n    },\n    mac: {\n        zhName: \"马其顿语\",\n        enName: \"Macedonian\",\n        pinyin: \"maqidunyu\",\n        popularity: {\n            zh: 109,\n            en: 112\n        }\n    },\n    mg: {\n        zhName: \"马拉加斯语\",\n        enName: \"Malagasy\",\n        pinyin: \"malajiasiyu\",\n        popularity: {\n            zh: 114,\n            en: 118\n        }\n    },\n    may: {\n        zhName: \"马来语\",\n        enName: \"Malay\",\n        pinyin: \"malaiyu\",\n        popularity: {\n            zh: 107,\n            en: 111\n        }\n    },\n    mal: {\n        zhName: \"马拉雅拉姆语\",\n        enName: \"Malayalam\",\n        pinyin: \"malayalamuyu\",\n        popularity: {\n            zh: 111,\n            en: 115\n        }\n    },\n    mlt: {\n        zhName: \"马耳他语\",\n        enName: \"Maltese\",\n        pinyin: \"maertayu\",\n        popularity: {\n            zh: 112,\n            en: 116\n        }\n    },\n    mar: {\n        zhName: \"马拉地语\",\n        enName: \"Marathi\",\n        pinyin: \"maladiyu\",\n        popularity: {\n            zh: 110,\n            en: 114\n        }\n    },\n    nep: {\n        zhName: \"尼泊尔语\",\n        enName: \"Nepali\",\n        pinyin: \"niboeryu\",\n        popularity: {\n            zh: 120,\n            en: 125\n        }\n    },\n    nno: {\n        zhName: \"新挪威语\",\n        enName: \"Nynorsk\",\n        pinyin: \"xinnuoweiyu\",\n        popularity: {\n            zh: 178,\n            en: 130\n        }\n    },\n    per: {\n        zhName: \"波斯语\",\n        enName: \"Persian\",\n        pinyin: \"bosiyu\",\n        popularity: {\n            zh: 20,\n            en: 139\n        }\n    },\n    srd: {\n        zhName: \"萨丁尼亚语\",\n        enName: \"Sardinian\",\n        pinyin: \"sadingniyayu\",\n        popularity: {\n            zh: 143,\n            en: 167\n        }\n    },\n    srp: {\n        zhName: \"塞尔维亚语(拉丁文)\",\n        enName: \"Serbian\",\n        pinyin: \"saierweiyayu\",\n        popularity: {\n            zh: 137,\n            en: 156\n        }\n    },\n    sin: {\n        zhName: \"僧伽罗语 \",\n        enName: \"Sinhala\",\n        pinyin: \"sengqieluoyu\",\n        popularity: {\n            zh: 136,\n            en: 155\n        }\n    },\n    sk: {\n        zhName: \"斯洛伐克语\",\n        enName: \"Slovak\",\n        pinyin: \"siluofakeyu\",\n        popularity: {\n            zh: 135,\n            en: 154\n        }\n    },\n    som: {\n        zhName: \"索马里语\",\n        enName: \"Somali\",\n        pinyin: \"suomaliyu\",\n        popularity: {\n            zh: 139,\n            en: 159\n        }\n    },\n    swa: {\n        zhName: \"斯瓦希里语\",\n        enName: \"Swahili\",\n        pinyin: \"siwaxiliyu\",\n        popularity: {\n            zh: 138,\n            en: 158\n        }\n    },\n    tgl: {\n        zhName: \"他加禄语\",\n        enName: \"Tagalog\",\n        pinyin: \"tajialuyu\",\n        popularity: {\n            zh: 154,\n            en: 181\n        }\n    },\n    tgk: {\n        zhName: \"塔吉克语\",\n        enName: \"Tajik\",\n        pinyin: \"tajikeyu\",\n        popularity: {\n            zh: 152,\n            en: 176\n        }\n    },\n    tam: {\n        zhName: \"泰米尔语\",\n        enName: \"Tamil\",\n        pinyin: \"taimieryu\",\n        popularity: {\n            zh: 150,\n            en: 175\n        }\n    },\n    tat: {\n        zhName: \"鞑靼语\",\n        enName: \"Tatar\",\n        pinyin: \"dadayu\",\n        popularity: {\n            zh: 43,\n            en: 182\n        }\n    },\n    tel: {\n        zhName: \"泰卢固语\",\n        enName: \"Telugu\",\n        pinyin: \"tailuguyu\",\n        popularity: {\n            zh: 153,\n            en: 179\n        }\n    },\n    tr: {\n        zhName: \"土耳其语\",\n        enName: \"Turkish\",\n        pinyin: \"tuerqiyu\",\n        popularity: {\n            zh: 149,\n            en: 174\n        }\n    },\n    tuk: {\n        zhName: \"土库曼语\",\n        enName: \"Turkmen\",\n        pinyin: \"tukumanyu\",\n        popularity: {\n            zh: 151,\n            en: 177\n        }\n    },\n    ukr: {\n        zhName: \"乌克兰语\",\n        enName: \"Ukrainian\",\n        pinyin: \"wukelanyu\",\n        popularity: {\n            zh: 157,\n            en: 186\n        }\n    },\n    urd: {\n        zhName: \"乌尔都语\",\n        enName: \"Urdu\",\n        pinyin: \"wuerduyu\",\n        popularity: {\n            zh: 158,\n            en: 187\n        }\n    },\n    uzb: {\n        zhName: \"乌兹别克语\",\n        enName: \"Uzbek\",\n        pinyin: \"wuzibiekeyu\",\n        popularity: {\n            zh: 159,\n            en: 188\n        }\n    },\n    oci: {\n        zhName: \"奥克语\",\n        enName: \"Occitan\",\n        pinyin: \"aokeyu\",\n        popularity: {\n            zh: 15,\n            en: 132\n        }\n    },\n    kir: {\n        zhName: \"吉尔吉斯语\",\n        enName: \"Kyrgyz\",\n        pinyin: \"jierjisiyu\",\n        popularity: {\n            zh: 74,\n            en: 93\n        }\n    },\n    pus: {\n        zhName: \"普什图语\",\n        enName: \"Pashto\",\n        pinyin: \"pushituyu\",\n        popularity: {\n            zh: 126,\n            en: 140\n        }\n    },\n    hkm: {\n        zhName: \"高棉语\",\n        enName: \"Khmer\",\n        pinyin: \"gaomianyu\",\n        popularity: {\n            zh: 53,\n            en: 86\n        }\n    },\n    ht: {\n        zhName: \"海地语\",\n        enName: \"Haitian Creole\",\n        pinyin: \"haidiyu\",\n        popularity: {\n            zh: 68,\n            en: 69\n        }\n    },\n    nob: {\n        zhName: \"书面挪威语\",\n        enName: \"Bokmål\",\n        pinyin: \"shumiannuoweiyu\",\n        popularity: {\n            zh: 147,\n            en: 28\n        }\n    },\n    pan: {\n        zhName: \"旁遮普语\",\n        enName: \"Punjabi\",\n        pinyin: \"pangzhepuyu\",\n        popularity: {\n            zh: 127,\n            en: 141\n        }\n    },\n    arq: {\n        zhName: \"阿尔及利亚阿拉伯语\",\n        enName: \"Algerian Arabic\",\n        pinyin: \"aerjiliyaalaboyu\",\n        popularity: {\n            zh: 9,\n            en: 10\n        }\n    },\n    bis: {\n        zhName: \"比斯拉马语\",\n        enName: \"Bislama\",\n        pinyin: \"bisilamayu\",\n        popularity: {\n            zh: 26,\n            en: 21\n        }\n    },\n    frn: {\n        zhName: \"加拿大法语\",\n        enName: \"Canadian French\",\n        pinyin: \"jianadafayu\",\n        popularity: {\n            zh: 75,\n            en: 39\n        }\n    },\n    hak: {\n        zhName: \"哈卡钦语\",\n        enName: \"Hakha Chin\",\n        pinyin: \"hakaqinyu\",\n        popularity: {\n            zh: 69,\n            en: 70\n        }\n    },\n    hup: {\n        zhName: \"胡帕语\",\n        enName: \"Hupa\",\n        pinyin: \"hupayu\",\n        popularity: {\n            zh: 70,\n            en: 72\n        }\n    },\n    ing: {\n        zhName: \"印古什语\",\n        enName: \"Ingush\",\n        pinyin: \"yingushiyu\",\n        popularity: {\n            zh: 192,\n            en: 79\n        }\n    },\n    lag: {\n        zhName: \"拉特加莱语\",\n        enName: \"Latgalian\",\n        pinyin: \"latejialaiyu\",\n        popularity: {\n            zh: 99,\n            en: 105\n        }\n    },\n    mau: {\n        zhName: \"毛里求斯克里奥尔语\",\n        enName: \"Mauritian Creole\",\n        pinyin: \"maoliqiusikeliaoeryu\",\n        popularity: {\n            zh: 118,\n            en: 122\n        }\n    },\n    mot: {\n        zhName: \"黑山语\",\n        enName: \"Montenegrin\",\n        pinyin: \"heishanyu\",\n        popularity: {\n            zh: 66,\n            en: 113\n        }\n    },\n    pot: {\n        zhName: \"巴西葡萄牙语\",\n        enName: \"Brazilian Portuguese\",\n        pinyin: \"baxiputaoyayu\",\n        popularity: {\n            zh: 28,\n            en: 144\n        }\n    },\n    ruy: {\n        zhName: \"卢森尼亚语\",\n        enName: \"Rusyn\",\n        pinyin: \"lusenniyayu\",\n        popularity: {\n            zh: 102,\n            en: 150\n        }\n    },\n    sec: {\n        zhName: \"塞尔维亚-克罗地亚语\",\n        enName: \"Serbo-Croatian\",\n        pinyin: \"saierweiya-keluodiyayu\",\n        popularity: {\n            zh: 144,\n            en: 168\n        }\n    },\n    sil: {\n        zhName: \"西里西亚语\",\n        enName: \"Silesian\",\n        pinyin: \"xilixiyayu\",\n        popularity: {\n            zh: 175,\n            en: 170\n        }\n    },\n    tua: {\n        zhName: \"突尼斯阿拉伯语\",\n        enName: \"Tunisian Arabic\",\n        pinyin: \"tunisialaboyu\",\n        popularity: {\n            zh: 156,\n            en: 184\n        }\n    },\n    ach: {\n        zhName: \"亚齐语\",\n        enName: \"Achinese\",\n        pinyin: \"yaqiyu\",\n        popularity: {\n            zh: 188,\n            en: 8\n        }\n    },\n    aka: {\n        zhName: \"阿肯语\",\n        enName: \"Akan\",\n        pinyin: \"akenyu\",\n        popularity: {\n            zh: 10,\n            en: 9\n        }\n    },\n    arg: {\n        zhName: \"阿拉贡语\",\n        enName: \"Aragonese\",\n        pinyin: \"alagongyu\",\n        popularity: {\n            zh: 11,\n            en: 12\n        }\n    },\n    aym: {\n        zhName: \"艾马拉语\",\n        enName: \"Aymara\",\n        pinyin: \"aimalayu\",\n        popularity: {\n            zh: 13,\n            en: 14\n        }\n    },\n    bal: {\n        zhName: \"俾路支语\",\n        enName: \"Baluchi\",\n        pinyin: \"biluzhiyu\",\n        popularity: {\n            zh: 34,\n            en: 22\n        }\n    },\n    bak: {\n        zhName: \"巴什基尔语\",\n        enName: \"Bashkir\",\n        pinyin: \"bashijieryu\",\n        popularity: {\n            zh: 27,\n            en: 23\n        }\n    },\n    bem: {\n        zhName: \"本巴语\",\n        enName: \"Bemba\",\n        pinyin: \"benbayu\",\n        popularity: {\n            zh: 32,\n            en: 24\n        }\n    },\n    ber: {\n        zhName: \"柏柏尔语\",\n        enName: \"Berber languages\",\n        pinyin: \"baibaieryu\",\n        popularity: {\n            zh: 29,\n            en: 25\n        }\n    },\n    bho: {\n        zhName: \"博杰普尔语\",\n        enName: \"Bhojpuri\",\n        pinyin: \"bojiepueryu\",\n        popularity: {\n            zh: 35,\n            en: 26\n        }\n    },\n    bli: {\n        zhName: \"比林语\",\n        enName: \"Blin\",\n        pinyin: \"bilinyu\",\n        popularity: {\n            zh: 33,\n            en: 27\n        }\n    },\n    bre: {\n        zhName: \"布列塔尼语\",\n        enName: \"Breton\",\n        pinyin: \"bulietaniyu\",\n        popularity: {\n            zh: 36,\n            en: 29\n        }\n    },\n    chr: {\n        zhName: \"切罗基语\",\n        enName: \"Cherokee\",\n        pinyin: \"qieluojiyu\",\n        popularity: {\n            zh: 131,\n            en: 40\n        }\n    },\n    nya: {\n        zhName: \"齐切瓦语\",\n        enName: \"Chichewa\",\n        pinyin: \"qiqiewayu\",\n        popularity: {\n            zh: 129,\n            en: 38\n        }\n    },\n    chv: {\n        zhName: \"楚瓦什语\",\n        enName: \"Chuvash\",\n        pinyin: \"chuwashiyu\",\n        popularity: {\n            zh: 38,\n            en: 41\n        }\n    },\n    cor: {\n        zhName: \"康瓦尔语\",\n        enName: \"Cornish\",\n        pinyin: \"kangwaeryu\",\n        popularity: {\n            zh: 86,\n            en: 42\n        }\n    },\n    cos: {\n        zhName: \"科西嘉语\",\n        enName: \"Corsican\",\n        pinyin: \"kexijiayu\",\n        popularity: {\n            zh: 79,\n            en: 37\n        }\n    },\n    cre: {\n        zhName: \"克里克语\",\n        enName: \"Creek\",\n        pinyin: \"kelikeyu\",\n        popularity: {\n            zh: 87,\n            en: 43\n        }\n    },\n    cri: {\n        zhName: \"克里米亚鞑靼语\",\n        enName: \"Crimean Tatar\",\n        pinyin: \"kelimiyadadayu\",\n        popularity: {\n            zh: 88,\n            en: 44\n        }\n    },\n    div: {\n        zhName: \"迪维希语\",\n        enName: \"Divehi\",\n        pinyin: \"diweixiyu\",\n        popularity: {\n            zh: 41,\n            en: 47\n        }\n    },\n    eno: {\n        zhName: \"古英语\",\n        enName: \"Old English\",\n        pinyin: \"guyingyu\",\n        popularity: {\n            zh: 62,\n            en: 134\n        }\n    },\n    frm: {\n        zhName: \"中古法语\",\n        enName: \"Middle French\",\n        pinyin: \"zhonggufayu\",\n        popularity: {\n            zh: 200,\n            en: 123\n        }\n    },\n    fri: {\n        zhName: \"弗留利语\",\n        enName: \"Friulian\",\n        pinyin: \"fuliuliyu\",\n        popularity: {\n            zh: 52,\n            en: 56\n        }\n    },\n    ful: {\n        zhName: \"富拉尼语\",\n        enName: \"Fulani\",\n        pinyin: \"fulaniyu\",\n        popularity: {\n            zh: 49,\n            en: 54\n        }\n    },\n    gla: {\n        zhName: \"盖尔语\",\n        enName: \"Gaelic\",\n        pinyin: \"gaieryu\",\n        popularity: {\n            zh: 59,\n            en: 63\n        }\n    },\n    lug: {\n        zhName: \"卢干达语\",\n        enName: \"Luganda\",\n        pinyin: \"lugandayu\",\n        popularity: {\n            zh: 101,\n            en: 110\n        }\n    },\n    gra: {\n        zhName: \"古希腊语\",\n        enName: \"Ancient Greek\",\n        pinyin: \"guxilayu\",\n        popularity: {\n            zh: 61,\n            en: 11\n        }\n    },\n    grn: {\n        zhName: \"瓜拉尼语\",\n        enName: \"Guarani\",\n        pinyin: \"gualaniyu\",\n        popularity: {\n            zh: 57,\n            en: 62\n        }\n    },\n    haw: {\n        zhName: \"夏威夷语\",\n        enName: \"Hawaiian\",\n        pinyin: \"xiaweiyiyu\",\n        popularity: {\n            zh: 171,\n            en: 67\n        }\n    },\n    hil: {\n        zhName: \"希利盖农语\",\n        enName: \"Hiligaynon\",\n        pinyin: \"xiligainongyu\",\n        popularity: {\n            zh: 176,\n            en: 71\n        }\n    },\n    ido: {\n        zhName: \"伊多语\",\n        enName: \"Ido\",\n        pinyin: \"yiduoyu\",\n        popularity: {\n            zh: 189,\n            en: 78\n        }\n    },\n    ina: {\n        zhName: \"因特语\",\n        enName: \"Interlingua \",\n        pinyin: \"yinteyu\",\n        popularity: {\n            zh: 191,\n            en: 80\n        }\n    },\n    iku: {\n        zhName: \"伊努克提图特语\",\n        enName: \"Inuktitut\",\n        pinyin: \"yinuketituteyu\",\n        popularity: {\n            zh: 190,\n            en: 81\n        }\n    },\n    jav: {\n        zhName: \"爪哇语\",\n        enName: \"Javanese\",\n        pinyin: \"zhaowayu\",\n        popularity: {\n            zh: 198,\n            en: 83\n        }\n    },\n    kab: {\n        zhName: \"卡拜尔语\",\n        enName: \"Kabyle\",\n        pinyin: \"kabaieryu\",\n        popularity: {\n            zh: 83,\n            en: 94\n        }\n    },\n    kal: {\n        zhName: \"格陵兰语\",\n        enName: \"Kalaallisut\",\n        pinyin: \"gelinglanyu\",\n        popularity: {\n            zh: 58,\n            en: 92\n        }\n    },\n    kau: {\n        zhName: \"卡努里语\",\n        enName: \"Kanuri\",\n        pinyin: \"kanuliyu\",\n        popularity: {\n            zh: 84,\n            en: 95\n        }\n    },\n    kas: {\n        zhName: \"克什米尔语\",\n        enName: \"Kashmiri\",\n        pinyin: \"keshimieryu\",\n        popularity: {\n            zh: 82,\n            en: 90\n        }\n    },\n    kah: {\n        zhName: \"卡舒比语\",\n        enName: \"Kashubian\",\n        pinyin: \"kashubiyu\",\n        popularity: {\n            zh: 85,\n            en: 96\n        }\n    },\n    kin: {\n        zhName: \"卢旺达语\",\n        enName: \"Kinyarwanda\",\n        pinyin: \"luwangdayu\",\n        popularity: {\n            zh: 103,\n            en: 97\n        }\n    },\n    kon: {\n        zhName: \"刚果语\",\n        enName: \"Kongo\",\n        pinyin: \"gangguoyu\",\n        popularity: {\n            zh: 56,\n            en: 91\n        }\n    },\n    kok: {\n        zhName: \"孔卡尼语\",\n        enName: \"Konkani\",\n        pinyin: \"kongkaniyu\",\n        popularity: {\n            zh: 81,\n            en: 89\n        }\n    },\n    lim: {\n        zhName: \"林堡语\",\n        enName: \"Limburgish\",\n        pinyin: \"linbaoyu\",\n        popularity: {\n            zh: 100,\n            en: 106\n        }\n    },\n    lin: {\n        zhName: \"林加拉语\",\n        enName: \"Lingala\",\n        pinyin: \"linjialayu\",\n        popularity: {\n            zh: 97,\n            en: 104\n        }\n    },\n    loj: {\n        zhName: \"逻辑语\",\n        enName: \"Lojban\",\n        pinyin: \"luojiyu\",\n        popularity: {\n            zh: 105,\n            en: 107\n        }\n    },\n    log: {\n        zhName: \"低地德语\",\n        enName: \"Low German\",\n        pinyin: \"didideyu\",\n        popularity: {\n            zh: 44,\n            en: 108\n        }\n    },\n    los: {\n        zhName: \"下索布语\",\n        enName: \"Lower Sorbian\",\n        pinyin: \"xiasuobuyu\",\n        popularity: {\n            zh: 177,\n            en: 109\n        }\n    },\n    mai: {\n        zhName: \"迈蒂利语\",\n        enName: \"Maithili\",\n        pinyin: \"maidiliyu\",\n        popularity: {\n            zh: 115,\n            en: 119\n        }\n    },\n    glv: {\n        zhName: \"曼克斯语\",\n        enName: \"Manx\",\n        pinyin: \"mankesiyu\",\n        popularity: {\n            zh: 117,\n            en: 121\n        }\n    },\n    mao: {\n        zhName: \"毛利语\",\n        enName: \"Maori\",\n        pinyin: \"maoliyu\",\n        popularity: {\n            zh: 113,\n            en: 117\n        }\n    },\n    mah: {\n        zhName: \"马绍尔语\",\n        enName: \"Marshallese\",\n        pinyin: \"mashaoeryu\",\n        popularity: {\n            zh: 116,\n            en: 120\n        }\n    },\n    nbl: {\n        zhName: \"南恩德贝莱语\",\n        enName: \"Southern Ndebele\",\n        pinyin: \"nanendebeilaiyu\",\n        popularity: {\n            zh: 123,\n            en: 163\n        }\n    },\n    nea: {\n        zhName: \"那不勒斯语\",\n        enName: \"Neapolitan\",\n        pinyin: \"nabulesiyu\",\n        popularity: {\n            zh: 124,\n            en: 127\n        }\n    },\n    nqo: {\n        zhName: \"西非书面语\",\n        enName: \"N'Ko\",\n        pinyin: \"xifeishumianyu\",\n        popularity: {\n            zh: 173,\n            en: 128\n        }\n    },\n    sme: {\n        zhName: \"北方萨米语\",\n        enName: \"Northern Sami\",\n        pinyin: \"beifangsamiyu\",\n        popularity: {\n            zh: 31,\n            en: 129\n        }\n    },\n    nor: {\n        zhName: \"挪威语\",\n        enName: \"Norwegian\",\n        pinyin: \"nuoweiyu\",\n        popularity: {\n            zh: 119,\n            en: 124\n        }\n    },\n    oji: {\n        zhName: \"奥杰布瓦语\",\n        enName: \"Ojibwa\",\n        pinyin: \"aojiebuwayu\",\n        popularity: {\n            zh: 14,\n            en: 133\n        }\n    },\n    ori: {\n        zhName: \"奥里亚语\",\n        enName: \"Oriya\",\n        pinyin: \"aoliyayu\",\n        popularity: {\n            zh: 8,\n            en: 131\n        }\n    },\n    orm: {\n        zhName: \"奥罗莫语\",\n        enName: \"Oromo\",\n        pinyin: \"aoluomoyu\",\n        popularity: {\n            zh: 16,\n            en: 135\n        }\n    },\n    oss: {\n        zhName: \"奥塞梯语\",\n        enName: \"Ossetian\",\n        pinyin: \"aosaitiyu\",\n        popularity: {\n            zh: 17,\n            en: 136\n        }\n    },\n    pam: {\n        zhName: \"邦板牙语\",\n        enName: \"Pampanga\",\n        pinyin: \"bangbanyayu\",\n        popularity: {\n            zh: 30,\n            en: 143\n        }\n    },\n    pap: {\n        zhName: \"帕皮阿门托语\",\n        enName: \"Papiamento\",\n        pinyin: \"papiamentuoyu\",\n        popularity: {\n            zh: 128,\n            en: 142\n        }\n    },\n    ped: {\n        zhName: \"北索托语\",\n        enName: \"Northern Sotho\",\n        pinyin: \"beisuotuoyu\",\n        popularity: {\n            zh: 25,\n            en: 126\n        }\n    },\n    que: {\n        zhName: \"克丘亚语\",\n        enName: \"Quechua\",\n        pinyin: \"keqiuyayu\",\n        popularity: {\n            zh: 90,\n            en: 145\n        }\n    },\n    roh: {\n        zhName: \"罗曼什语\",\n        enName: \"Romansh\",\n        pinyin: \"luomanshiyu\",\n        popularity: {\n            zh: 98,\n            en: 148\n        }\n    },\n    ro: {\n        zhName: \"罗姆语\",\n        enName: \"Romany\",\n        pinyin: \"luomuyu\",\n        popularity: {\n            zh: 104,\n            en: 149\n        }\n    },\n    sm: {\n        zhName: \"萨摩亚语\",\n        enName: \"Samoan\",\n        pinyin: \"samoyayu\",\n        popularity: {\n            zh: 140,\n            en: 161\n        }\n    },\n    san: {\n        zhName: \"梵语\",\n        enName: \"Sanskrit\",\n        pinyin: \"fanyu\",\n        popularity: {\n            zh: 51,\n            en: 164\n        }\n    },\n    sco: {\n        zhName: \"苏格兰语\",\n        enName: \"Scots\",\n        pinyin: \"sugelanyu\",\n        popularity: {\n            zh: 142,\n            en: 165\n        }\n    },\n    sha: {\n        zhName: \"掸语\",\n        enName: \"Shan\",\n        pinyin: \"shanyu\",\n        popularity: {\n            zh: 145,\n            en: 169\n        }\n    },\n    sna: {\n        zhName: \"修纳语\",\n        enName: \"Shona\",\n        pinyin: \"xiunayu\",\n        popularity: {\n            zh: 170,\n            en: 160\n        }\n    },\n    snd: {\n        zhName: \"信德语\",\n        enName: \"Sindhi\",\n        pinyin: \"xindeyu\",\n        popularity: {\n            zh: 169,\n            en: 157\n        }\n    },\n    sol: {\n        zhName: \"桑海语\",\n        enName: \"Songhai languages\",\n        pinyin: \"sanghaiyu\",\n        popularity: {\n            zh: 146,\n            en: 171\n        }\n    },\n    sot: {\n        zhName: \"南索托语\",\n        enName: \"Southern Sotho\",\n        pinyin: \"nansuotuoyu\",\n        popularity: {\n            zh: 122,\n            en: 162\n        }\n    },\n    syr: {\n        zhName: \"叙利亚语\",\n        enName: \"Syriac\",\n        pinyin: \"xuliyayu\",\n        popularity: {\n            zh: 172,\n            en: 166\n        }\n    },\n    tet: {\n        zhName: \"德顿语\",\n        enName: \"Tetum\",\n        pinyin: \"dedunyu\",\n        popularity: {\n            zh: 42,\n            en: 178\n        }\n    },\n    tir: {\n        zhName: \"提格利尼亚语\",\n        enName: \"Tigrinya\",\n        pinyin: \"tigeliniyayu\",\n        popularity: {\n            zh: 155,\n            en: 183\n        }\n    },\n    tso: {\n        zhName: \"聪加语\",\n        enName: \"Tsonga\",\n        pinyin: \"congjiayu\",\n        popularity: {\n            zh: 37,\n            en: 180\n        }\n    },\n    twi: {\n        zhName: \"契维语\",\n        enName: \"Twi\",\n        pinyin: \"qiweiyu\",\n        popularity: {\n            zh: 130,\n            en: 185\n        }\n    },\n    ups: {\n        zhName: \"高地索布语\",\n        enName: \"Upper Sorbian\",\n        pinyin: \"gaodisuobuyu\",\n        popularity: {\n            zh: 60,\n            en: 189\n        }\n    },\n    ven: {\n        zhName: \"文达语\",\n        enName: \"Venda\",\n        pinyin: \"wendayu\",\n        popularity: {\n            zh: 162,\n            en: 191\n        }\n    },\n    wln: {\n        zhName: \"瓦隆语\",\n        enName: \"Walloon\",\n        pinyin: \"walongyu\",\n        popularity: {\n            zh: 163,\n            en: 194\n        }\n    },\n    wel: {\n        zhName: \"威尔士语\",\n        enName: \"Welsh\",\n        pinyin: \"weiershiyu\",\n        popularity: {\n            zh: 160,\n            en: 192\n        }\n    },\n    fry: {\n        zhName: \"西弗里斯语\",\n        enName: \"Western Frisian\",\n        pinyin: \"xifulisiyu\",\n        popularity: {\n            zh: 174,\n            en: 195\n        }\n    },\n    wol: {\n        zhName: \"沃洛夫语\",\n        enName: \"Wolof\",\n        pinyin: \"woluofuyu\",\n        popularity: {\n            zh: 161,\n            en: 193\n        }\n    },\n    xho: {\n        zhName: \"科萨语\",\n        enName: \"Xhosa\",\n        pinyin: \"kesayu\",\n        popularity: {\n            zh: 78,\n            en: 196\n        }\n    },\n    yid: {\n        zhName: \"意第绪语\",\n        enName: \"Yiddish\",\n        pinyin: \"yidixuyu\",\n        popularity: {\n            zh: 187,\n            en: 198\n        }\n    },\n    yor: {\n        zhName: \"约鲁巴语\",\n        enName: \"Yoruba\",\n        pinyin: \"yuelubayu\",\n        popularity: {\n            zh: 185,\n            en: 197\n        }\n    },\n    zaz: {\n        zhName: \"扎扎其语\",\n        enName: \"Zaza\",\n        pinyin: \"zhazhaqiyu\",\n        popularity: {\n            zh: 199,\n            en: 200\n        }\n    },\n    zul: {\n        zhName: \"祖鲁语\",\n        enName: \"Zulu\",\n        pinyin: \"zuluyu\",\n        popularity: {\n            zh: 197,\n            en: 199\n        }\n    },\n    sun: {\n        zhName: \"巽他语\",\n        enName: \"BasaSunda\",\n        pinyin: \"xuntayu\",\n        popularity: {\n            zh: 172.5,\n            en: 29.5\n        }\n    },\n    hmn: {\n        zhName: \"苗语\",\n        enName: \"Hmong\",\n        pinyin: \"miaoyu\",\n        popularity: {\n            zh: 107.5,\n            en: 72.5\n        }\n    },\n    src: {\n        zhName: \"塞尔维亚语(西里尔文)\",\n        enName: \"Serb(Cyrillic)\",\n        pinyin: \"saierweiyayu\",\n        popularity: {\n            zh: 137.5,\n            en: 156.5\n        }\n    }\n}\n"
  },
  {
    "path": "tool/bing.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <style>\n        table {\n            box-sizing: border-box;\n            border-color: grey;\n            font-size: 14px;\n        }\n\n        table tr td {\n            min-width: 120px;\n        }\n    </style>\n</head>\n<body>\n<div style=\"display: none\">\n    <option value=\"af\">Afrikaans</option>\n    <option value=\"ar\">Arabic</option>\n    <option value=\"as\">Assamese</option>\n    <option value=\"bn\">Bangla</option>\n    <option value=\"bs\">Bosnian</option>\n    <option value=\"bg\">Bulgarian</option>\n    <option value=\"yue\">Cantonese (Traditional)</option>\n    <option value=\"ca\">Catalan</option>\n    <option value=\"zh-Hans\">Chinese Simplified</option>\n    <option value=\"zh-Hant\">Chinese Traditional</option>\n    <option value=\"hr\">Croatian</option>\n    <option value=\"cs\">Czech</option>\n    <option value=\"da\">Danish</option>\n    <option value=\"prs\">Dari</option>\n    <option value=\"nl\">Dutch</option>\n    <option value=\"en\">English</option>\n    <option value=\"et\">Estonian</option>\n    <option value=\"fj\">Fijian</option>\n    <option value=\"fil\">Filipino</option>\n    <option value=\"fi\">Finnish</option>\n    <option value=\"fr\">French</option>\n    <option value=\"fr-ca\">French (Canada)</option>\n    <option value=\"de\">German</option>\n    <option value=\"el\">Greek</option>\n    <option value=\"gu\">Gujarati</option>\n    <option value=\"ht\">Haitian Creole</option>\n    <option value=\"he\">Hebrew</option>\n    <option value=\"hi\">Hindi</option>\n    <option value=\"mww\">Hmong Daw</option>\n    <option value=\"hu\">Hungarian</option>\n    <option value=\"is\">Icelandic</option>\n    <option value=\"id\">Indonesian</option>\n    <option value=\"ga\">Irish</option>\n    <option value=\"it\">Italian</option>\n    <option value=\"ja\">Japanese</option>\n    <option value=\"kn\">Kannada</option>\n    <option value=\"kk\">Kazakh</option>\n    <option value=\"tlh-Latn\">Klingon (Latin)</option>\n    <option value=\"tlh-Piqd\">Klingon (pIqaD)</option>\n    <option value=\"ko\">Korean</option>\n    <option value=\"ku\">Kurdish (Central)</option>\n    <option value=\"kmr\">Kurdish (Northern)</option>\n    <option value=\"lv\">Latvian</option>\n    <option value=\"lt\">Lithuanian</option>\n    <option value=\"mg\">Malagasy</option>\n    <option value=\"ms\">Malay</option>\n    <option value=\"ml\">Malayalam</option>\n    <option value=\"mt\">Maltese</option>\n    <option value=\"mi\">Maori</option>\n    <option value=\"mr\">Marathi</option>\n    <option value=\"nb\">Norwegian</option>\n    <option value=\"or\">Odia</option>\n    <option value=\"ps\">Pashto</option>\n    <option value=\"fa\">Persian</option>\n    <option value=\"pl\">Polish</option>\n    <option value=\"pt\">Portuguese (Brazil)</option>\n    <option value=\"pt-pt\">Portuguese (Portugal)</option>\n    <option value=\"pa\">Punjabi</option>\n    <option value=\"otq\">Quer&#233;taro Otomi</option>\n    <option value=\"ro\">Romanian</option>\n    <option value=\"ru\">Russian</option>\n    <option value=\"sm\">Samoan</option>\n    <option value=\"sr-Cyrl\">Serbian (Cyrillic)</option>\n    <option value=\"sr-Latn\">Serbian (Latin)</option>\n    <option value=\"sk\">Slovak</option>\n    <option value=\"sl\">Slovenian</option>\n    <option value=\"es\">Spanish</option>\n    <option value=\"sw\">Swahili</option>\n    <option value=\"sv\">Swedish</option>\n    <option value=\"ty\">Tahitian</option>\n    <option value=\"ta\">Tamil</option>\n    <option value=\"te\">Telugu</option>\n    <option value=\"th\">Thai</option>\n    <option value=\"to\">Tongan</option>\n    <option value=\"tr\">Turkish</option>\n    <option value=\"uk\">Ukrainian</option>\n    <option value=\"ur\">Urdu</option>\n    <option value=\"vi\">Vietnamese</option>\n    <option value=\"cy\">Welsh</option>\n    <option value=\"yua\">Yucatec Maya</option>\n</div>\n<script src=\"bd.js\"></script>\n<script>\n    // bing language\n    let j = 0\n    let bingObj = {}\n    document.querySelectorAll('[value]').forEach(el => {\n        j++\n        let lang = el.getAttribute('value')\n        let text = el.innerText.trim()\n        // console.log(lang, text)\n        bingObj[lang] = text\n    })\n    console.log(JSON.stringify(bingObj))\n    console.log(j)\n\n    // baidu language list\n    let bdToObj = function (name) {\n        let i = 0\n        let o = {}\n        for (let k in bdList) {\n            i++\n            let n = bdList[k][name]\n            if (n) o[n] = k\n        }\n        console.log(JSON.stringify(o))\n        console.log(i)\n        return o\n    }\n    let bdObj = bdToObj('enName')\n\n    // 名字对应表\n    let nameMap = {\n        'Chinese Simplified': 'Chinese',\n        'Chinese Traditional': 'Traditional Chinese',\n        'French (Canada)': 'Canadian French',\n        'Portuguese (Brazil)': 'Brazilian Portuguese',\n        'Portuguese (Portugal)': 'Portuguese',\n        'Hmong Daw': 'Hmong',\n        'Serbian (Latin)': 'Serbian',\n    }\n\n    let err = 0, n\n    let a1 = [], a2 = [], a3 = [], ae = []\n    for (const [k, v] of Object.entries(bingObj)) {\n        if ((n = nameMap[v]) && bdObj[n]) {\n            a3.push({bdKey: bdObj[n], bingKey: k, name: v})\n        } else if (bdList[k]) {\n            // key 一样的情况\n            a1.push({key: k, bingName: v, bdName: bdList[k].enName})\n        } else if (bdObj[v]) {\n            // 中文名 一样的情况\n            a2.push({bdKey: bdObj[v], bingKey: k, name: v})\n        } else {\n            // 都没有的情况\n            err++\n            ae.push({key: k, name: v})\n        }\n    }\n    // console.log(JSON.stringify(a3))\n    console.log('err:', err)\n\n    let obj = {} // 最终的对应表\n    // key 一致的数据\n    document.write(`<table style=\"color:green\">`)\n    a1.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.bingName}</td><td>${v.bdName}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // name 一致的数据\n    document.write(`<table>`)\n    a2.forEach((v, k) => {\n        obj[v.bdKey] = v.bingKey\n        document.write(`<tr><td>${k + 1}</td><td>${v.bdKey}</td><td>${v.bingKey}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    document.write(`<table style=\"color:blue\">`)\n    a3.forEach((v, k) => {\n        obj[v.bdKey] = v.bingKey\n        document.write(`<tr><td>${k + 1}</td><td>${v.bdKey}</td><td>${v.bingKey}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // 都没有的情况\n    document.write(`<table style=\"color:red\">`)\n    ae.forEach((v, k) => {\n        // obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // document.write(`<hr>`)\n    console.log(JSON.stringify(obj))\n    // 总结：丢弃 13 种小众语言\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tool/google.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <style>\n        table {\n            box-sizing: border-box;\n            border-color: grey;\n            font-size: 14px;\n        }\n\n        table tr td {\n            min-width: 120px;\n        }\n    </style>\n</head>\n<body>\n<script src=\"bd.js\"></script>\n<script>\n    // google language\n    let googleList = [\n        {\n            code: 'auto',\n            name: '检测语言'\n        },\n        {\n            code: 'sq',\n            name: '阿尔巴尼亚语'\n        },\n        {\n            code: 'ar',\n            name: '阿拉伯语'\n        },\n        {\n            code: 'am',\n            name: '阿姆哈拉语'\n        },\n        {\n            code: 'az',\n            name: '阿塞拜疆语'\n        },\n        {\n            code: 'ga',\n            name: '爱尔兰语'\n        },\n        {\n            code: 'et',\n            name: '爱沙尼亚语'\n        },\n        {\n            code: 'or',\n            name: '奥利亚语'\n        },\n        {\n            code: 'eu',\n            name: '巴斯克语'\n        },\n        {\n            code: 'be',\n            name: '白俄罗斯语'\n        },\n        {\n            code: 'bg',\n            name: '保加利亚语'\n        },\n        {\n            code: 'is',\n            name: '冰岛语'\n        },\n        {\n            code: 'pl',\n            name: '波兰语'\n        },\n        {\n            code: 'bs',\n            name: '波斯尼亚语'\n        },\n        {\n            code: 'fa',\n            name: '波斯语'\n        },\n        {\n            code: 'af',\n            name: '布尔语(南非荷兰语)'\n        },\n        {\n            code: 'tt',\n            name: '鞑靼语'\n        },\n        {\n            code: 'da',\n            name: '丹麦语'\n        },\n        {\n            code: 'de',\n            name: '德语'\n        },\n        {\n            code: 'ru',\n            name: '俄语'\n        },\n        {\n            code: 'fr',\n            name: '法语'\n        },\n        {\n            code: 'tl',\n            name: '菲律宾语'\n        },\n        {\n            code: 'fi',\n            name: '芬兰语'\n        },\n        {\n            code: 'fy',\n            name: '弗里西语'\n        },\n        {\n            code: 'km',\n            name: '高棉语'\n        },\n        {\n            code: 'ka',\n            name: '格鲁吉亚语'\n        },\n        {\n            code: 'gu',\n            name: '古吉拉特语'\n        },\n        {\n            code: 'kk',\n            name: '哈萨克语'\n        },\n        {\n            code: 'ht',\n            name: '海地克里奥尔语'\n        },\n        {\n            code: 'ko',\n            name: '韩语'\n        },\n        {\n            code: 'ha',\n            name: '豪萨语'\n        },\n        {\n            code: 'nl',\n            name: '荷兰语'\n        },\n        {\n            code: 'ky',\n            name: '吉尔吉斯语'\n        },\n        {\n            code: 'gl',\n            name: '加利西亚语'\n        },\n        {\n            code: 'ca',\n            name: '加泰罗尼亚语'\n        },\n        {\n            code: 'cs',\n            name: '捷克语'\n        },\n        {\n            code: 'kn',\n            name: '卡纳达语'\n        },\n        {\n            code: 'co',\n            name: '科西嘉语'\n        },\n        {\n            code: 'hr',\n            name: '克罗地亚语'\n        },\n        {\n            code: 'ku',\n            name: '库尔德语'\n        },\n        {\n            code: 'la',\n            name: '拉丁语'\n        },\n        {\n            code: 'lv',\n            name: '拉脱维亚语'\n        },\n        {\n            code: 'lo',\n            name: '老挝语'\n        },\n        {\n            code: 'lt',\n            name: '立陶宛语'\n        },\n        {\n            code: 'lb',\n            name: '卢森堡语'\n        },\n        {\n            code: 'rw',\n            name: '卢旺达语'\n        },\n        {\n            code: 'ro',\n            name: '罗马尼亚语'\n        },\n        {\n            code: 'mg',\n            name: '马尔加什语'\n        },\n        {\n            code: 'mt',\n            name: '马耳他语'\n        },\n        {\n            code: 'mr',\n            name: '马拉地语'\n        },\n        {\n            code: 'ml',\n            name: '马拉雅拉姆语'\n        },\n        {\n            code: 'ms',\n            name: '马来语'\n        },\n        {\n            code: 'mk',\n            name: '马其顿语'\n        },\n        {\n            code: 'mi',\n            name: '毛利语'\n        },\n        {\n            code: 'mn',\n            name: '蒙古语'\n        },\n        {\n            code: 'bn',\n            name: '孟加拉语'\n        },\n        {\n            code: 'my',\n            name: '缅甸语'\n        },\n        {\n            code: 'hmn',\n            name: '苗语'\n        },\n        {\n            code: 'xh',\n            name: '南非科萨语'\n        },\n        {\n            code: 'zu',\n            name: '南非祖鲁语'\n        },\n        {\n            code: 'ne',\n            name: '尼泊尔语'\n        },\n        {\n            code: 'no',\n            name: '挪威语'\n        },\n        {\n            code: 'pa',\n            name: '旁遮普语'\n        },\n        {\n            code: 'pt',\n            name: '葡萄牙语'\n        },\n        {\n            code: 'ps',\n            name: '普什图语'\n        },\n        {\n            code: 'ny',\n            name: '齐切瓦语'\n        },\n        {\n            code: 'ja',\n            name: '日语'\n        },\n        {\n            code: 'sv',\n            name: '瑞典语'\n        },\n        {\n            code: 'sm',\n            name: '萨摩亚语'\n        },\n        {\n            code: 'sr',\n            name: '塞尔维亚语'\n        },\n        {\n            code: 'st',\n            name: '塞索托语'\n        },\n        {\n            code: 'si',\n            name: '僧伽罗语'\n        },\n        {\n            code: 'eo',\n            name: '世界语'\n        },\n        {\n            code: 'sk',\n            name: '斯洛伐克语'\n        },\n        {\n            code: 'sl',\n            name: '斯洛文尼亚语'\n        },\n        {\n            code: 'sw',\n            name: '斯瓦希里语'\n        },\n        {\n            code: 'gd',\n            name: '苏格兰盖尔语'\n        },\n        {\n            code: 'ceb',\n            name: '宿务语'\n        },\n        {\n            code: 'so',\n            name: '索马里语'\n        },\n        {\n            code: 'tg',\n            name: '塔吉克语'\n        },\n        {\n            code: 'te',\n            name: '泰卢固语'\n        },\n        {\n            code: 'ta',\n            name: '泰米尔语'\n        },\n        {\n            code: 'th',\n            name: '泰语'\n        },\n        {\n            code: 'tr',\n            name: '土耳其语'\n        },\n        {\n            code: 'tk',\n            name: '土库曼语'\n        },\n        {\n            code: 'cy',\n            name: '威尔士语'\n        },\n        {\n            code: 'ug',\n            name: '维吾尔语'\n        },\n        {\n            code: 'ur',\n            name: '乌尔都语'\n        },\n        {\n            code: 'uk',\n            name: '乌克兰语'\n        },\n        {\n            code: 'uz',\n            name: '乌兹别克语'\n        },\n        {\n            code: 'es',\n            name: '西班牙语'\n        },\n        {\n            code: 'iw',\n            name: '希伯来语'\n        },\n        {\n            code: 'el',\n            name: '希腊语'\n        },\n        {\n            code: 'haw',\n            name: '夏威夷语'\n        },\n        {\n            code: 'sd',\n            name: '信德语'\n        },\n        {\n            code: 'hu',\n            name: '匈牙利语'\n        },\n        {\n            code: 'sn',\n            name: '修纳语'\n        },\n        {\n            code: 'hy',\n            name: '亚美尼亚语'\n        },\n        {\n            code: 'ig',\n            name: '伊博语'\n        },\n        {\n            code: 'it',\n            name: '意大利语'\n        },\n        {\n            code: 'yi',\n            name: '意第绪语'\n        },\n        {\n            code: 'hi',\n            name: '印地语'\n        },\n        {\n            code: 'su',\n            name: '印尼巽他语'\n        },\n        {\n            code: 'id',\n            name: '印尼语'\n        },\n        {\n            code: 'jw',\n            name: '印尼爪哇语'\n        },\n        {\n            code: 'en',\n            name: '英语'\n        },\n        {\n            code: 'yo',\n            name: '约鲁巴语'\n        },\n        {\n            code: 'vi',\n            name: '越南语'\n        },\n        {\n            code: 'zh-CN',\n            name: '中文'\n        }\n    ]\n    let googleEnList = [\n        {\n            code: 'auto',\n            name: 'Detect language'\n        },\n        {\n            code: 'af',\n            name: 'Afrikaans'\n        },\n        {\n            code: 'sq',\n            name: 'Albanian'\n        },\n        {\n            code: 'am',\n            name: 'Amharic'\n        },\n        {\n            code: 'ar',\n            name: 'Arabic'\n        },\n        {\n            code: 'hy',\n            name: 'Armenian'\n        },\n        {\n            code: 'az',\n            name: 'Azerbaijani'\n        },\n        {\n            code: 'eu',\n            name: 'Basque'\n        },\n        {\n            code: 'be',\n            name: 'Belarusian'\n        },\n        {\n            code: 'bn',\n            name: 'Bengali'\n        },\n        {\n            code: 'bs',\n            name: 'Bosnian'\n        },\n        {\n            code: 'bg',\n            name: 'Bulgarian'\n        },\n        {\n            code: 'ca',\n            name: 'Catalan'\n        },\n        {\n            code: 'ceb',\n            name: 'Cebuano'\n        },\n        {\n            code: 'ny',\n            name: 'Chichewa'\n        },\n        {\n            code: 'zh-CN',\n            name: 'Chinese'\n        },\n        {\n            code: 'co',\n            name: 'Corsican'\n        },\n        {\n            code: 'hr',\n            name: 'Croatian'\n        },\n        {\n            code: 'cs',\n            name: 'Czech'\n        },\n        {\n            code: 'da',\n            name: 'Danish'\n        },\n        {\n            code: 'nl',\n            name: 'Dutch'\n        },\n        {\n            code: 'en',\n            name: 'English'\n        },\n        {\n            code: 'eo',\n            name: 'Esperanto'\n        },\n        {\n            code: 'et',\n            name: 'Estonian'\n        },\n        {\n            code: 'tl',\n            name: 'Filipino'\n        },\n        {\n            code: 'fi',\n            name: 'Finnish'\n        },\n        {\n            code: 'fr',\n            name: 'French'\n        },\n        {\n            code: 'fy',\n            name: 'Frisian'\n        },\n        {\n            code: 'gl',\n            name: 'Galician'\n        },\n        {\n            code: 'ka',\n            name: 'Georgian'\n        },\n        {\n            code: 'de',\n            name: 'German'\n        },\n        {\n            code: 'el',\n            name: 'Greek'\n        },\n        {\n            code: 'gu',\n            name: 'Gujarati'\n        },\n        {\n            code: 'ht',\n            name: 'Haitian Creole'\n        },\n        {\n            code: 'ha',\n            name: 'Hausa'\n        },\n        {\n            code: 'haw',\n            name: 'Hawaiian'\n        },\n        {\n            code: 'iw',\n            name: 'Hebrew'\n        },\n        {\n            code: 'hi',\n            name: 'Hindi'\n        },\n        {\n            code: 'hmn',\n            name: 'Hmong'\n        },\n        {\n            code: 'hu',\n            name: 'Hungarian'\n        },\n        {\n            code: 'is',\n            name: 'Icelandic'\n        },\n        {\n            code: 'ig',\n            name: 'Igbo'\n        },\n        {\n            code: 'id',\n            name: 'Indonesian'\n        },\n        {\n            code: 'ga',\n            name: 'Irish'\n        },\n        {\n            code: 'it',\n            name: 'Italian'\n        },\n        {\n            code: 'ja',\n            name: 'Japanese'\n        },\n        {\n            code: 'jw',\n            name: 'Javanese'\n        },\n        {\n            code: 'kn',\n            name: 'Kannada'\n        },\n        {\n            code: 'kk',\n            name: 'Kazakh'\n        },\n        {\n            code: 'km',\n            name: 'Khmer'\n        },\n        {\n            code: 'rw',\n            name: 'Kinyarwanda'\n        },\n        {\n            code: 'ko',\n            name: 'Korean'\n        },\n        {\n            code: 'ku',\n            name: 'Kurdish (Kurmanji)'\n        },\n        {\n            code: 'ky',\n            name: 'Kyrgyz'\n        },\n        {\n            code: 'lo',\n            name: 'Lao'\n        },\n        {\n            code: 'la',\n            name: 'Latin'\n        },\n        {\n            code: 'lv',\n            name: 'Latvian'\n        },\n        {\n            code: 'lt',\n            name: 'Lithuanian'\n        },\n        {\n            code: 'lb',\n            name: 'Luxembourgish'\n        },\n        {\n            code: 'mk',\n            name: 'Macedonian'\n        },\n        {\n            code: 'mg',\n            name: 'Malagasy'\n        },\n        {\n            code: 'ms',\n            name: 'Malay'\n        },\n        {\n            code: 'ml',\n            name: 'Malayalam'\n        },\n        {\n            code: 'mt',\n            name: 'Maltese'\n        },\n        {\n            code: 'mi',\n            name: 'Maori'\n        },\n        {\n            code: 'mr',\n            name: 'Marathi'\n        },\n        {\n            code: 'mn',\n            name: 'Mongolian'\n        },\n        {\n            code: 'my',\n            name: 'Myanmar (Burmese)'\n        },\n        {\n            code: 'ne',\n            name: 'Nepali'\n        },\n        {\n            code: 'no',\n            name: 'Norwegian'\n        },\n        {\n            code: 'or',\n            name: 'Odia (Oriya)'\n        },\n        {\n            code: 'ps',\n            name: 'Pashto'\n        },\n        {\n            code: 'fa',\n            name: 'Persian'\n        },\n        {\n            code: 'pl',\n            name: 'Polish'\n        },\n        {\n            code: 'pt',\n            name: 'Portuguese'\n        },\n        {\n            code: 'pa',\n            name: 'Punjabi'\n        },\n        {\n            code: 'ro',\n            name: 'Romanian'\n        },\n        {\n            code: 'ru',\n            name: 'Russian'\n        },\n        {\n            code: 'sm',\n            name: 'Samoan'\n        },\n        {\n            code: 'gd',\n            name: 'Scots Gaelic'\n        },\n        {\n            code: 'sr',\n            name: 'Serbian'\n        },\n        {\n            code: 'st',\n            name: 'Sesotho'\n        },\n        {\n            code: 'sn',\n            name: 'Shona'\n        },\n        {\n            code: 'sd',\n            name: 'Sindhi'\n        },\n        {\n            code: 'si',\n            name: 'Sinhala'\n        },\n        {\n            code: 'sk',\n            name: 'Slovak'\n        },\n        {\n            code: 'sl',\n            name: 'Slovenian'\n        },\n        {\n            code: 'so',\n            name: 'Somali'\n        },\n        {\n            code: 'es',\n            name: 'Spanish'\n        },\n        {\n            code: 'su',\n            name: 'Sundanese'\n        },\n        {\n            code: 'sw',\n            name: 'Swahili'\n        },\n        {\n            code: 'sv',\n            name: 'Swedish'\n        },\n        {\n            code: 'tg',\n            name: 'Tajik'\n        },\n        {\n            code: 'ta',\n            name: 'Tamil'\n        },\n        {\n            code: 'tt',\n            name: 'Tatar'\n        },\n        {\n            code: 'te',\n            name: 'Telugu'\n        },\n        {\n            code: 'th',\n            name: 'Thai'\n        },\n        {\n            code: 'tr',\n            name: 'Turkish'\n        },\n        {\n            code: 'tk',\n            name: 'Turkmen'\n        },\n        {\n            code: 'uk',\n            name: 'Ukrainian'\n        },\n        {\n            code: 'ur',\n            name: 'Urdu'\n        },\n        {\n            code: 'ug',\n            name: 'Uyghur'\n        },\n        {\n            code: 'uz',\n            name: 'Uzbek'\n        },\n        {\n            code: 'vi',\n            name: 'Vietnamese'\n        },\n        {\n            code: 'cy',\n            name: 'Welsh'\n        },\n        {\n            code: 'xh',\n            name: 'Xhosa'\n        },\n        {\n            code: 'yi',\n            name: 'Yiddish'\n        },\n        {\n            code: 'yo',\n            name: 'Yoruba'\n        },\n        {\n            code: 'zu',\n            name: 'Zulu'\n        }\n    ]\n    // console.log(googleList)\n    // console.log(googleEnList)\n\n    // google language list\n    let goToObj = function (arr) {\n        let o = {}\n        arr.forEach(v => {\n            o[v.code] = v.name\n        })\n        console.log(JSON.stringify(o))\n        console.log(arr.length)\n        return o\n    }\n    let goObj = goToObj(googleList)\n    let goEnObj = goToObj(googleEnList)\n\n    // baidu language list\n    let bdToObj = function (name) {\n        let i = 0\n        let o = {}\n        for (let k in bdList) {\n            i++\n            let n = bdList[k][name]\n            if (n) o[n] = k\n        }\n        console.log(JSON.stringify(o))\n        console.log(i)\n        return o\n    }\n    let bdObj = bdToObj('zhName')\n    let bdEnObj = bdToObj('enName')\n\n    let err = 0\n    let a1 = [], a2 = [], a3 = [], ae = []\n    for (let k in goObj) {\n        let v = goObj[k]\n        let enV = goEnObj[k]\n        if (bdList[k]) {\n            // baidu google key 一样的情况\n            a1.push({key: k, goName: v, goEnName: goEnObj[k], bdName: bdList[k].zhName, bdEnName: bdList[k].enName})\n        } else if (bdObj[v]) {\n            // baidu google 中文名 一样的情况\n            a2.push({bdKey: bdObj[v], goKey: k, name: v})\n        } else if (bdEnObj[enV]) {\n            // baidu google 英文名 一样的情况\n            a3.push({bdKey: bdEnObj[enV], goKey: k, name: enV})\n        } else {\n            // google 有，baidu 没有的情况\n            err++\n            ae.push({key: k, name: v})\n        }\n    }\n    // console.log(JSON.stringify(a3))\n    console.log('err:', err)\n\n    let obj = {} // 最终的对应表\n    // key 一致的数据\n    document.write(`<table style=\"color:green\">`)\n    a1.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.goName}</td><td>${v.bdName}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // name 一致的数据\n    document.write(`<table>`)\n    a2.forEach((v, k) => {\n        obj[v.bdKey] = v.goKey\n        document.write(`<tr><td>${k + 1}</td><td>${v.bdKey}</td><td>${v.goKey}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    document.write(`<table style=\"color:blue\">`)\n    a3.forEach((v, k) => {\n        obj[v.bdKey] = v.goKey\n        document.write(`<tr><td>${k + 1}</td><td>${v.bdKey}</td><td>${v.goKey}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // google 有，baidu 没有的情况\n    document.write(`<table style=\"color:red\">`)\n    ae.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // document.write(`<hr>`)\n    console.log(JSON.stringify(obj))\n    // 总结：人工整理数据，在百度中存在的数据\n    // fy 弗里西语 => fry 西弗里斯语\n    // gd 苏格兰盖尔语 => sco 苏格兰语\n    // su 印尼巽他语 => sun 巽他语\n    // 另外 4 种语言，百度没有\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tool/lang.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n</head>\n<body>\n<table class=\"table1\">\n    <caption class=\"visually-hidden\">语音转文本</caption>\n    <thead>\n    <tr>\n        <th><span data-ttu-id=\"52cf5-111\">语言</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-111\">Language</span></span></th>\n        <th><span data-ttu-id=\"52cf5-112\">区域设置 (BCP-47) </span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-112\">Locale (BCP-47)</span></span></th>\n        <th><span data-ttu-id=\"52cf5-113\">自定义</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-113\">Customizations</span></span></th>\n    </tr>\n    </thead>\n    <tbody>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-114\">阿拉伯语(巴林)，现代标准</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-114\">Arabic (Bahrain), modern standard</span></span></td>\n        <td><code>ar-BH</code></td>\n        <td><span data-ttu-id=\"52cf5-115\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-115\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-116\">阿拉伯语（埃及）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-116\">Arabic (Egypt)</span></span></td>\n        <td><code>ar-EG</code></td>\n        <td><span data-ttu-id=\"52cf5-117\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-117\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-118\">阿拉伯语（伊拉克）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-118\">Arabic (Iraq)</span></span></td>\n        <td><code>ar-IQ</code></td>\n        <td><span data-ttu-id=\"52cf5-119\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-119\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-120\">阿拉伯语（约旦）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-120\">Arabic (Jordan)</span></span></td>\n        <td><code>ar-JO</code></td>\n        <td><span data-ttu-id=\"52cf5-121\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-121\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-122\">阿拉伯语(科威特)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-122\">Arabic (Kuwait)</span></span></td>\n        <td><code>ar-KW</code></td>\n        <td><span data-ttu-id=\"52cf5-123\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-123\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-124\">阿拉伯语（黎巴嫩）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-124\">Arabic (Lebanon)</span></span></td>\n        <td><code>ar-LB</code></td>\n        <td><span data-ttu-id=\"52cf5-125\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-125\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-126\">阿拉伯语（阿曼）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-126\">Arabic (Oman)</span></span></td>\n        <td><code>ar-OM</code></td>\n        <td><span data-ttu-id=\"52cf5-127\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-127\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-128\">阿拉伯语（卡塔尔）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-128\">Arabic (Qatar)</span></span></td>\n        <td><code>ar-QA</code></td>\n        <td><span data-ttu-id=\"52cf5-129\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-129\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-130\">阿拉伯语（沙特阿拉伯）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-130\">Arabic (Saudi Arabia)</span></span></td>\n        <td><code>ar-SA</code></td>\n        <td><span data-ttu-id=\"52cf5-131\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-131\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-132\">阿拉伯语（叙利亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-132\">Arabic (Syria)</span></span></td>\n        <td><code>ar-SY</code></td>\n        <td><span data-ttu-id=\"52cf5-133\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-133\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-134\">阿拉伯语（阿拉伯联合酋长国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-134\">Arabic (United Arab Emirates)</span></span></td>\n        <td><code>ar-AE</code></td>\n        <td><span data-ttu-id=\"52cf5-135\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-135\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-136\">保加利亚语(保加利亚)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-136\">Bulgarian (Bulgaria)</span></span></td>\n        <td><code>bg-BG</code></td>\n        <td><span data-ttu-id=\"52cf5-137\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-137\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-138\">加泰罗尼亚语(西班牙)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-138\">Catalan (Spain)</span></span></td>\n        <td><code>ca-ES</code></td>\n        <td><span data-ttu-id=\"52cf5-139\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-139\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-140\">中文(粤语，繁体)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-140\">Chinese (Cantonese, Traditional)</span></span></td>\n        <td><code>zh-HK</code></td>\n        <td><span data-ttu-id=\"52cf5-141\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-141\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-142\">中文（普通话，简体）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-142\">Chinese (Mandarin, Simplified)</span></span></td>\n        <td><code>zh-CN</code></td>\n        <td><span data-ttu-id=\"52cf5-143\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-143\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-144\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-144\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-145\">中文(台湾普通话)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-145\">Chinese (Taiwanese Mandarin)</span></span></td>\n        <td><code>zh-TW</code></td>\n        <td><span data-ttu-id=\"52cf5-146\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-146\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-147\">克罗地亚语（克罗地亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-147\">Croatian (Croatia)</span></span></td>\n        <td><code>hr-HR</code></td>\n        <td><span data-ttu-id=\"52cf5-148\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-148\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-149\">捷克语（捷克共和国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-149\">Czech (Czech Republic)</span></span></td>\n        <td><code>cs-CZ</code></td>\n        <td><span data-ttu-id=\"52cf5-150\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-150\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-151\">丹麦语（丹麦）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-151\">Danish (Denmark)</span></span></td>\n        <td><code>da-DK</code></td>\n        <td><span data-ttu-id=\"52cf5-152\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-152\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-153\">荷兰语（荷兰）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-153\">Dutch (Netherlands)</span></span></td>\n        <td><code>nl-NL</code></td>\n        <td><span data-ttu-id=\"52cf5-154\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-154\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-155\">英语（澳大利亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-155\">English (Australia)</span></span></td>\n        <td><code>en-AU</code></td>\n        <td><span data-ttu-id=\"52cf5-156\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-156\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-157\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-157\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-158\">英语（加拿大）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-158\">English (Canada)</span></span></td>\n        <td><code>en-CA</code></td>\n        <td><span data-ttu-id=\"52cf5-159\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-159\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-160\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-160\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-161\">英语（香港）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-161\">English (Hong Kong)</span></span></td>\n        <td><code>en-HK</code></td>\n        <td><span data-ttu-id=\"52cf5-162\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-162\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-163\">英语（印度）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-163\">English (India)</span></span></td>\n        <td><code>en-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-164\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-164\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-165\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-165\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-166\">英语（爱尔兰）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-166\">English (Ireland)</span></span></td>\n        <td><code>en-IE</code></td>\n        <td><span data-ttu-id=\"52cf5-167\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-167\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-168\">英语（新西兰）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-168\">English (New Zealand)</span></span></td>\n        <td><code>en-NZ</code></td>\n        <td><span data-ttu-id=\"52cf5-169\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-169\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-170\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-170\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-171\">英语（菲律宾）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-171\">English (Philippines)</span></span></td>\n        <td><code>en-PH</code></td>\n        <td><span data-ttu-id=\"52cf5-172\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-172\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-173\">英语（新加坡）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-173\">English (Singapore)</span></span></td>\n        <td><code>en-SG</code></td>\n        <td><span data-ttu-id=\"52cf5-174\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-174\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-175\">英语（南非）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-175\">English (South Africa)</span></span></td>\n        <td><code>en-ZA</code></td>\n        <td><span data-ttu-id=\"52cf5-176\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-176\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-177\">英语(英国)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-177\">English (United Kingdom)</span></span></td>\n        <td><code>en-GB</code></td>\n        <td><span data-ttu-id=\"52cf5-178\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-178\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-179\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-179\">Language model</span></span><br><span\n                data-ttu-id=\"52cf5-180\">发音</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-180\">Pronunciation</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-181\">英语（美国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-181\">English (United States)</span></span></td>\n        <td><code>en-US</code></td>\n        <td><span data-ttu-id=\"52cf5-182\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-182\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-183\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-183\">Language model</span></span><br><span\n                data-ttu-id=\"52cf5-184\">发音</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-184\">Pronunciation</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-185\">爱沙尼亚语（爱沙尼亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-185\">Estonian(Estonia)</span></span></td>\n        <td><code>et-EE</code></td>\n        <td><span data-ttu-id=\"52cf5-186\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-186\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-187\">芬兰语（芬兰）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-187\">Finnish (Finland)</span></span></td>\n        <td><code>fi-FI</code></td>\n        <td><span data-ttu-id=\"52cf5-188\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-188\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-189\">法语（加拿大）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-189\">French (Canada)</span></span></td>\n        <td><code>fr-CA</code></td>\n        <td><span data-ttu-id=\"52cf5-190\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-190\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-191\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-191\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-192\">法语（法国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-192\">French (France)</span></span></td>\n        <td><code>fr-FR</code></td>\n        <td><span data-ttu-id=\"52cf5-193\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-193\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-194\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-194\">Language model</span></span><br><span\n                data-ttu-id=\"52cf5-195\">发音</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-195\">Pronunciation</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-196\">德语（德国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-196\">German (Germany)</span></span></td>\n        <td><code>de-DE</code></td>\n        <td><span data-ttu-id=\"52cf5-197\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-197\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-198\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-198\">Language model</span></span><br><span\n                data-ttu-id=\"52cf5-199\">发音</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-199\">Pronunciation</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-200\">希腊语(希腊)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-200\">Greek (Greece)</span></span></td>\n        <td><code>el-GR</code></td>\n        <td><span data-ttu-id=\"52cf5-201\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-201\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-202\">古吉拉特语(印度)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-202\">Gujarati (Indian)</span></span></td>\n        <td><code>gu-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-203\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-203\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-204\">印地语（印度）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-204\">Hindi (India)</span></span></td>\n        <td><code>hi-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-205\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-205\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-206\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-206\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-207\">匈牙利语（匈牙利）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-207\">Hungarian (Hungary)</span></span></td>\n        <td><code>hu-HU</code></td>\n        <td><span data-ttu-id=\"52cf5-208\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-208\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-209\">爱尔兰语（爱尔兰）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-209\">Irish(Ireland)</span></span></td>\n        <td><code>ga-IE</code></td>\n        <td><span data-ttu-id=\"52cf5-210\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-210\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-211\">意大利语（意大利）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-211\">Italian (Italy)</span></span></td>\n        <td><code>it-IT</code></td>\n        <td><span data-ttu-id=\"52cf5-212\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-212\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-213\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-213\">Language model</span></span><br><span\n                data-ttu-id=\"52cf5-214\">发音</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-214\">Pronunciation</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-215\">日语（日本）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-215\">Japanese (Japan)</span></span></td>\n        <td><code>ja-JP</code></td>\n        <td><span data-ttu-id=\"52cf5-216\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-216\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-217\">韩语(韩国)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-217\">Korean (Korea)</span></span></td>\n        <td><code>ko-KR</code></td>\n        <td><span data-ttu-id=\"52cf5-218\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-218\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-219\">拉脱维亚语（拉脱维亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-219\">Latvian (Latvia)</span></span></td>\n        <td><code>lv-LV</code></td>\n        <td><span data-ttu-id=\"52cf5-220\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-220\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-221\">立陶宛语（立陶宛）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-221\">Lithuanian (Lithuania)</span></span></td>\n        <td><code>lt-LT</code></td>\n        <td><span data-ttu-id=\"52cf5-222\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-222\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-223\">马耳他语（马耳他）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-223\">Maltese(Malta)</span></span></td>\n        <td><code>mt-MT</code></td>\n        <td><span data-ttu-id=\"52cf5-224\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-224\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-225\">马拉地语(印度)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-225\">Marathi (India)</span></span></td>\n        <td><code>mr-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-226\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-226\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-227\">挪威语（博克马尔语，挪威）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-227\">Norwegian (Bokmål, Norway)</span></span></td>\n        <td><code>nb-NO</code></td>\n        <td><span data-ttu-id=\"52cf5-228\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-228\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-229\">波兰语（波兰）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-229\">Polish (Poland)</span></span></td>\n        <td><code>pl-PL</code></td>\n        <td><span data-ttu-id=\"52cf5-230\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-230\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-231\">葡萄牙语（巴西）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-231\">Portuguese (Brazil)</span></span></td>\n        <td><code>pt-BR</code></td>\n        <td><span data-ttu-id=\"52cf5-232\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-232\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-233\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-233\">Language model</span></span><br><span\n                data-ttu-id=\"52cf5-234\">发音</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-234\">Pronunciation</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-235\">葡萄牙语(葡萄牙)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-235\">Portuguese (Portugal)</span></span></td>\n        <td><code>pt-PT</code></td>\n        <td><span data-ttu-id=\"52cf5-236\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-236\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-237\">罗马尼亚语（罗马尼亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-237\">Romanian (Romania)</span></span></td>\n        <td><code>ro-RO</code></td>\n        <td><span data-ttu-id=\"52cf5-238\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-238\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-239\">俄语（俄罗斯）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-239\">Russian (Russia)</span></span></td>\n        <td><code>ru-RU</code></td>\n        <td><span data-ttu-id=\"52cf5-240\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-240\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-241\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-241\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-242\">斯洛伐克语（斯洛伐克）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-242\">Slovak (Slovakia)</span></span></td>\n        <td><code>sk-SK</code></td>\n        <td><span data-ttu-id=\"52cf5-243\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-243\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-244\">斯洛文尼亚语（斯洛文尼亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-244\">Slovenian (Slovenia)</span></span></td>\n        <td><code>sl-SI</code></td>\n        <td><span data-ttu-id=\"52cf5-245\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-245\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-246\">西班牙语（阿根廷）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-246\">Spanish (Argentina)</span></span></td>\n        <td><code>es-AR</code></td>\n        <td><span data-ttu-id=\"52cf5-247\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-247\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-248\">西班牙语（玻利维亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-248\">Spanish (Bolivia)</span></span></td>\n        <td><code>es-BO</code></td>\n        <td><span data-ttu-id=\"52cf5-249\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-249\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-250\">西班牙语（智利）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-250\">Spanish (Chile)</span></span></td>\n        <td><code>es-CL</code></td>\n        <td><span data-ttu-id=\"52cf5-251\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-251\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-252\">西班牙语（哥伦比亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-252\">Spanish (Colombia)</span></span></td>\n        <td><code>es-CO</code></td>\n        <td><span data-ttu-id=\"52cf5-253\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-253\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-254\">西班牙语（哥斯达黎加）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-254\">Spanish (Costa Rica)</span></span></td>\n        <td><code>es-CR</code></td>\n        <td><span data-ttu-id=\"52cf5-255\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-255\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-256\">西班牙语（古巴）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-256\">Spanish (Cuba)</span></span></td>\n        <td><code>es-CU</code></td>\n        <td><span data-ttu-id=\"52cf5-257\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-257\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-258\">西班牙语（多米尼加共和国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-258\">Spanish (Dominican Republic)</span></span></td>\n        <td><code>es-DO</code></td>\n        <td><span data-ttu-id=\"52cf5-259\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-259\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-260\">西班牙语（厄瓜多尔）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-260\">Spanish (Ecuador)</span></span></td>\n        <td><code>es-EC</code></td>\n        <td><span data-ttu-id=\"52cf5-261\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-261\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-262\">西班牙语（萨尔瓦多）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-262\">Spanish (El Salvador)</span></span></td>\n        <td><code>es-SV</code></td>\n        <td><span data-ttu-id=\"52cf5-263\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-263\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-264\">西班牙语（危地马拉）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-264\">Spanish (Guatemala)</span></span></td>\n        <td><code>es-GT</code></td>\n        <td><span data-ttu-id=\"52cf5-265\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-265\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-266\">西班牙语（洪都拉斯）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-266\">Spanish (Honduras)</span></span></td>\n        <td><code>es-HN</code></td>\n        <td><span data-ttu-id=\"52cf5-267\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-267\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-268\">西班牙语（墨西哥）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-268\">Spanish (Mexico)</span></span></td>\n        <td><code>es-MX</code></td>\n        <td><span data-ttu-id=\"52cf5-269\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-269\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-270\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-270\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-271\">西班牙（尼加拉瓜）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-271\">Spanish (Nicaragua)</span></span></td>\n        <td><code>es-NI</code></td>\n        <td><span data-ttu-id=\"52cf5-272\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-272\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-273\">西班牙语（巴拿马）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-273\">Spanish (Panama)</span></span></td>\n        <td><code>es-PA</code></td>\n        <td><span data-ttu-id=\"52cf5-274\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-274\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-275\">西班牙语（巴拉圭）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-275\">Spanish (Paraguay)</span></span></td>\n        <td><code>es-PY</code></td>\n        <td><span data-ttu-id=\"52cf5-276\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-276\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-277\">西班牙语（秘鲁）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-277\">Spanish (Peru)</span></span></td>\n        <td><code>es-PE</code></td>\n        <td><span data-ttu-id=\"52cf5-278\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-278\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-279\">西班牙语（波多黎各）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-279\">Spanish (Puerto Rico)</span></span></td>\n        <td><code>es-PR</code></td>\n        <td><span data-ttu-id=\"52cf5-280\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-280\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-281\">西班牙语(西班牙)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-281\">Spanish (Spain)</span></span></td>\n        <td><code>es-ES</code></td>\n        <td><span data-ttu-id=\"52cf5-282\">声学模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-282\">Acoustic model</span></span><br><span data-ttu-id=\"52cf5-283\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-283\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-284\">西班牙语（乌拉圭）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-284\">Spanish (Uruguay)</span></span></td>\n        <td><code>es-UY</code></td>\n        <td><span data-ttu-id=\"52cf5-285\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-285\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-286\">西班牙语（美国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-286\">Spanish (USA)</span></span></td>\n        <td><code>es-US</code></td>\n        <td><span data-ttu-id=\"52cf5-287\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-287\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-288\">西班牙语（委内瑞拉）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-288\">Spanish (Venezuela)</span></span></td>\n        <td><code>es-VE</code></td>\n        <td><span data-ttu-id=\"52cf5-289\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-289\">Language Model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-290\">瑞典语（瑞典）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-290\">Swedish (Sweden)</span></span></td>\n        <td><code>sv-SE</code></td>\n        <td><span data-ttu-id=\"52cf5-291\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-291\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-292\">泰米尔语（印度）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-292\">Tamil (India)</span></span></td>\n        <td><code>ta-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-293\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-293\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-294\">泰卢固语（印度）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-294\">Telugu (India)</span></span></td>\n        <td><code>te-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-295\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-295\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-296\">泰语（泰国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-296\">Thai (Thailand)</span></span></td>\n        <td><code>th-TH</code></td>\n        <td><span data-ttu-id=\"52cf5-297\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-297\">Language model</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-298\">土耳其语（土耳其）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-298\">Turkish (Turkey)</span></span></td>\n        <td><code>tr-TR</code></td>\n        <td><span data-ttu-id=\"52cf5-299\">语言模型</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-299\">Language model</span></span></td>\n    </tr>\n    </tbody>\n</table>\n\n<table class=\"table2\">\n    <caption class=\"visually-hidden\">神经语音</caption>\n    <thead>\n    <tr>\n        <th><span data-ttu-id=\"52cf5-310\">语言</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-310\">Language</span></span></th>\n        <th><span data-ttu-id=\"52cf5-311\">Locale</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-311\">Locale</span></span></th>\n        <th><span data-ttu-id=\"52cf5-312\">性别</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-312\">Gender</span></span></th>\n        <th><span data-ttu-id=\"52cf5-313\">语音名称</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-313\">Voice name</span></span></th>\n        <th><span data-ttu-id=\"52cf5-314\">风格支持</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-314\">Style support</span></span></th>\n    </tr>\n    </thead>\n    <tbody>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-315\">阿拉伯语（埃及）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-315\">Arabic (Egypt)</span></span></td>\n        <td><code>ar-EG</code></td>\n        <td><span data-ttu-id=\"52cf5-316\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-316\">Female</span></span></td>\n        <td><code>ar-EG-SalmaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-317\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-317\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-318\">阿拉伯语（沙特阿拉伯）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-318\">Arabic (Saudi Arabia)</span></span></td>\n        <td><code>ar-SA</code></td>\n        <td><span data-ttu-id=\"52cf5-319\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-319\">Female</span></span></td>\n        <td><code>ar-SA-ZariyahNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-320\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-320\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-321\">保加利亚语（保加利亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-321\">Bulgarian (Bulgary)</span></span></td>\n        <td><code>bg-BG</code></td>\n        <td><span data-ttu-id=\"52cf5-322\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-322\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-323\"><code>bg-BG-KalinaNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-323\"><code>bg-BG-KalinaNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-324\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-324\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-325\">加泰罗尼亚语(西班牙)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-325\">Catalan (Spain)</span></span></td>\n        <td><code>ca-ES</code></td>\n        <td><span data-ttu-id=\"52cf5-326\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-326\">Female</span></span></td>\n        <td><code>ca-ES-AlbaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-327\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-327\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-328\">中文（粤语，繁体）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-328\">Chinese (Cantonese, Traditional)</span></span></td>\n        <td><code>zh-HK</code></td>\n        <td><span data-ttu-id=\"52cf5-329\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-329\">Female</span></span></td>\n        <td><code>zh-HK-HiuGaaiNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-330\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-330\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-331\">中文（普通话，简体）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-331\">Chinese (Mandarin, Simplified)</span></span></td>\n        <td><code>zh-CN</code></td>\n        <td><span data-ttu-id=\"52cf5-332\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-332\">Female</span></span></td>\n        <td><code>zh-CN-XiaoxiaoNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-333\">常规，<a href=\"speech-synthesis-markup#adjust-speaking-styles\" data-linktype=\"relative-path\">使用 SSML 可以使用</a>多种语音样式</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-333\">General, multiple voice styles available <a\n                href=\"speech-synthesis-markup#adjust-speaking-styles\" data-linktype=\"relative-path\">using SSML</a></span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-334\">中文（普通话，简体）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-334\">Chinese (Mandarin, Simplified)</span></span></td>\n        <td><code>zh-CN</code></td>\n        <td><span data-ttu-id=\"52cf5-335\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-335\">Female</span></span></td>\n        <td><code>zh-CN-XiaoyouNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-336\">儿童语音，针对讲故事进行了优化</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-336\">Kid voice, optimized for story narrating</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-337\">中文（普通话，简体）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-337\">Chinese (Mandarin, Simplified)</span></span></td>\n        <td><code>zh-CN</code></td>\n        <td><span data-ttu-id=\"52cf5-338\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-338\">Male</span></span></td>\n        <td><code>zh-CN-YunyangNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-339\">针对新闻阅读进行了优化，</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-339\">Optimized for news reading,</span></span><br> <span data-ttu-id=\"52cf5-340\"><a href=\"speech-synthesis-markup#adjust-speaking-styles\"\n                                                                                                                                                                                                   data-linktype=\"relative-path\">使用 SSML</a>提供多种语音样式</span><span class=\"sxs-lookup\"><span\n                data-stu-id=\"52cf5-340\">multiple voice styles available <a href=\"speech-synthesis-markup#adjust-speaking-styles\" data-linktype=\"relative-path\">using SSML</a></span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-341\">中文（普通话，简体）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-341\">Chinese (Mandarin, Simplified)</span></span></td>\n        <td><code>zh-CN</code></td>\n        <td><span data-ttu-id=\"52cf5-342\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-342\">Male</span></span></td>\n        <td><code>zh-CN-YunyeNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-343\">针对讲故事进行了优化</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-343\">Optimized for story narrating</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-344\">中文(台湾普通话)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-344\">Chinese (Taiwanese Mandarin)</span></span></td>\n        <td><code>zh-TW</code></td>\n        <td><span data-ttu-id=\"52cf5-345\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-345\">Female</span></span></td>\n        <td><code>zh-TW-HsiaoYuNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-346\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-346\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-347\">克罗地亚语(克罗地亚)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-347\">Croatian (Croatia)</span></span></td>\n        <td><code>hr-HR</code></td>\n        <td><span data-ttu-id=\"52cf5-348\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-348\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-349\"><code>hr-HR-GabrijelaNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-349\"><code>hr-HR-GabrijelaNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-350\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-350\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-351\">捷克语（捷克）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-351\">Czech (Czech)</span></span></td>\n        <td><code>cs-CZ</code></td>\n        <td><span data-ttu-id=\"52cf5-352\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-352\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-353\"><code>cs-CZ-VlastaNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-353\"><code>cs-CZ-VlastaNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-354\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-354\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-355\">丹麦语（丹麦）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-355\">Danish (Denmark)</span></span></td>\n        <td><code>da-DK</code></td>\n        <td><span data-ttu-id=\"52cf5-356\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-356\">Female</span></span></td>\n        <td><code>da-DK-ChristelNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-357\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-357\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-358\">荷兰语（荷兰）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-358\">Dutch (Netherlands)</span></span></td>\n        <td><code>nl-NL</code></td>\n        <td><span data-ttu-id=\"52cf5-359\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-359\">Female</span></span></td>\n        <td><code>nl-NL-ColetteNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-360\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-360\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-361\">英语（澳大利亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-361\">English (Australia)</span></span></td>\n        <td><code>en-AU</code></td>\n        <td><span data-ttu-id=\"52cf5-362\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-362\">Female</span></span></td>\n        <td><code>en-AU-NatashaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-363\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-363\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-364\">英语（澳大利亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-364\">English (Australia)</span></span></td>\n        <td><code>en-AU</code></td>\n        <td><span data-ttu-id=\"52cf5-365\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-365\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-366\"><code>en-AU-WilliamNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-366\"><code>en-AU-WilliamNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-367\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-367\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-368\">英语（加拿大）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-368\">English (Canada)</span></span></td>\n        <td><code>en-CA</code></td>\n        <td><span data-ttu-id=\"52cf5-369\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-369\">Female</span></span></td>\n        <td><code>en-CA-ClaraNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-370\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-370\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-371\">英语（印度）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-371\">English (India)</span></span></td>\n        <td><code>en-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-372\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-372\">Female</span></span></td>\n        <td><code>en-IN-NeerjaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-373\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-373\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-374\">英语（爱尔兰）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-374\">English (Ireland)</span></span></td>\n        <td><code>en-IE</code></td>\n        <td><span data-ttu-id=\"52cf5-375\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-375\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-376\"><code>en-IE-EmilyNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-376\"><code>en-IE-EmilyNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-377\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-377\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-378\">英语(英国)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-378\">English (United Kingdom)</span></span></td>\n        <td><code>en-GB</code></td>\n        <td><span data-ttu-id=\"52cf5-379\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-379\">Female</span></span></td>\n        <td><code>en-GB-LibbyNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-380\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-380\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-381\">英语(英国)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-381\">English (United Kingdom)</span></span></td>\n        <td><code>en-GB</code></td>\n        <td><span data-ttu-id=\"52cf5-382\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-382\">Female</span></span></td>\n        <td><code>en-GB-MiaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-383\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-383\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-384\">英语(英国)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-384\">English (United Kingdom)</span></span></td>\n        <td><code>en-GB</code></td>\n        <td><span data-ttu-id=\"52cf5-385\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-385\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-386\"><code>en-GB-RyanNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-386\"><code>en-GB-RyanNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-387\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-387\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-388\">英语（美国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-388\">English (United States)</span></span></td>\n        <td><code>en-US</code></td>\n        <td><span data-ttu-id=\"52cf5-389\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-389\">Female</span></span></td>\n        <td><code>en-US-AriaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-390\">常规，<a href=\"speech-synthesis-markup#adjust-speaking-styles\" data-linktype=\"relative-path\">使用 SSML 可以使用</a>多种语音样式</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-390\">General, multiple voice styles available <a\n                href=\"speech-synthesis-markup#adjust-speaking-styles\" data-linktype=\"relative-path\">using SSML</a></span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-391\">英语（美国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-391\">English (United States)</span></span></td>\n        <td><code>en-US</code></td>\n        <td><span data-ttu-id=\"52cf5-392\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-392\">Male</span></span></td>\n        <td><code>en-US-GuyNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-393\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-393\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-394\">英语（美国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-394\">English (United States)</span></span></td>\n        <td><code>en-US</code></td>\n        <td><span data-ttu-id=\"52cf5-395\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-395\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-396\"><code>en-US-JennyNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-396\"><code>en-US-JennyNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-397\">常规，<a href=\"speech-synthesis-markup#adjust-speaking-styles\" data-linktype=\"relative-path\">使用 SSML 可以使用</a>多种语音样式</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-397\">General, multiple voice styles available <a\n                href=\"speech-synthesis-markup#adjust-speaking-styles\" data-linktype=\"relative-path\">using SSML</a></span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-398\">芬兰语(芬兰)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-398\">Finnish (Finland)</span></span></td>\n        <td><code>fi-FI</code></td>\n        <td><span data-ttu-id=\"52cf5-399\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-399\">Female</span></span></td>\n        <td><code>fi-FI-NooraNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-400\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-400\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-401\">法语（加拿大）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-401\">French (Canada)</span></span></td>\n        <td><code>fr-CA</code></td>\n        <td><span data-ttu-id=\"52cf5-402\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-402\">Female</span></span></td>\n        <td><code>fr-CA-SylvieNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-403\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-403\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-404\">法语（加拿大）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-404\">French (Canada)</span></span></td>\n        <td><code>fr-CA</code></td>\n        <td><span data-ttu-id=\"52cf5-405\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-405\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-406\"><code>fr-CA-JeanNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-406\"><code>fr-CA-JeanNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-407\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-407\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-408\">法语（法国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-408\">French (France)</span></span></td>\n        <td><code>fr-FR</code></td>\n        <td><span data-ttu-id=\"52cf5-409\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-409\">Female</span></span></td>\n        <td><code>fr-FR-DeniseNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-410\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-410\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-411\">法语（法国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-411\">French (France)</span></span></td>\n        <td><code>fr-FR</code></td>\n        <td><span data-ttu-id=\"52cf5-412\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-412\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-413\"><code>fr-FR-HenriNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-413\"><code>fr-FR-HenriNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-414\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-414\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-415\">法语（瑞士）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-415\">French (Switzerland)</span></span></td>\n        <td><code>fr-CH</code></td>\n        <td><span data-ttu-id=\"52cf5-416\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-416\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-417\"><code>fr-CH-ArianeNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-417\"><code>fr-CH-ArianeNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-418\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-418\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-419\">德语（奥地利）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-419\">German (Austria)</span></span></td>\n        <td><code>de-AT</code></td>\n        <td><span data-ttu-id=\"52cf5-420\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-420\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-421\"><code>de-AT-IngridNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-421\"><code>de-AT-IngridNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-422\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-422\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-423\">德语（德国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-423\">German (Germany)</span></span></td>\n        <td><code>de-DE</code></td>\n        <td><span data-ttu-id=\"52cf5-424\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-424\">Female</span></span></td>\n        <td><code>de-DE-KatjaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-425\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-425\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-426\">德语（德国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-426\">German (Germany)</span></span></td>\n        <td><code>de-DE</code></td>\n        <td><span data-ttu-id=\"52cf5-427\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-427\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-428\"><code>de-DE-ConradNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-428\"><code>de-DE-ConradNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-429\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-429\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-430\">德语（瑞士）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-430\">German (Switzerland)</span></span></td>\n        <td><code>de-CH</code></td>\n        <td><span data-ttu-id=\"52cf5-431\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-431\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-432\"><code>de-CH-LeniNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-432\"><code>de-CH-LeniNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-433\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-433\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-434\">希腊语(希腊)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-434\">Greek (Greece)</span></span></td>\n        <td><code>el-GR</code></td>\n        <td><span data-ttu-id=\"52cf5-435\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-435\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-436\"><code>el-GR-AthinaNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-436\"><code>el-GR-AthinaNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-437\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-437\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-438\">希伯来语(以色列)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-438\">Hebrew (Israel)</span></span></td>\n        <td><code>he-IL</code></td>\n        <td><span data-ttu-id=\"52cf5-439\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-439\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-440\"><code>he-IL-HilaNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-440\"><code>he-IL-HilaNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-441\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-441\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-442\">印地语（印度）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-442\">Hindi (India)</span></span></td>\n        <td><code>hi-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-443\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-443\">Female</span></span></td>\n        <td><code>hi-IN-SwaraNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-444\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-444\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-445\">匈牙利语(匈牙利)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-445\">Hungarian (Hungary)</span></span></td>\n        <td><code>hu-HU</code></td>\n        <td><span data-ttu-id=\"52cf5-446\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-446\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-447\"><code>hu-HU-NoemiNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-447\"><code>hu-HU-NoemiNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-448\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-448\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-449\">印度尼西亚语(印度尼西亚)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-449\">Indonesian (Indonesia)</span></span></td>\n        <td><code>id-ID</code></td>\n        <td><span data-ttu-id=\"52cf5-450\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-450\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-451\"><code>id-ID-ArdiNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-451\"><code>id-ID-ArdiNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-452\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-452\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-453\">意大利语（意大利）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-453\">Italian (Italy)</span></span></td>\n        <td><code>it-IT</code></td>\n        <td><span data-ttu-id=\"52cf5-454\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-454\">Female</span></span></td>\n        <td><code>it-IT-ElsaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-455\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-455\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-456\">意大利语（意大利）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-456\">Italian (Italy)</span></span></td>\n        <td><code>it-IT</code></td>\n        <td><span data-ttu-id=\"52cf5-457\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-457\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-458\"><code>it-IT-IsabellaNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-458\"><code>it-IT-IsabellaNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-459\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-459\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-460\">意大利语（意大利）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-460\">Italian (Italy)</span></span></td>\n        <td><code>it-IT</code></td>\n        <td><span data-ttu-id=\"52cf5-461\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-461\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-462\"><code>it-IT-DiegoNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-462\"><code>it-IT-DiegoNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-463\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-463\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-464\">日语（日本）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-464\">Japanese (Japan)</span></span></td>\n        <td><code>ja-JP</code></td>\n        <td><span data-ttu-id=\"52cf5-465\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-465\">Female</span></span></td>\n        <td><code>ja-JP-NanamiNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-466\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-466\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-467\">日语（日本）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-467\">Japanese (Japan)</span></span></td>\n        <td><code>ja-JP</code></td>\n        <td><span data-ttu-id=\"52cf5-468\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-468\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-469\"><code>ja-JP-KeitaNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-469\"><code>ja-JP-KeitaNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-470\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-470\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-471\">韩语(韩国)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-471\">Korean (Korea)</span></span></td>\n        <td><code>ko-KR</code></td>\n        <td><span data-ttu-id=\"52cf5-472\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-472\">Female</span></span></td>\n        <td><code>ko-KR-SunHiNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-473\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-473\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-474\">韩语(韩国)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-474\">Korean (Korea)</span></span></td>\n        <td><code>ko-KR</code></td>\n        <td><span data-ttu-id=\"52cf5-475\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-475\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-476\"><code>ko-KR-InJoonNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-476\"><code>ko-KR-InJoonNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-477\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-477\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-478\">马来语（马来西亚）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-478\">Malay (Malaysia)</span></span></td>\n        <td><code>ms-MY</code></td>\n        <td><span data-ttu-id=\"52cf5-479\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-479\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-480\"><code>ms-MY-YasminNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-480\"><code>ms-MY-YasminNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-481\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-481\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-482\">挪威语（博克马尔语，挪威）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-482\">Norwegian (Bokmål, Norway)</span></span></td>\n        <td><code>nb-NO</code></td>\n        <td><span data-ttu-id=\"52cf5-483\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-483\">Female</span></span></td>\n        <td><code>nb-NO-IselinNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-484\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-484\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-485\">波兰语（波兰）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-485\">Polish (Poland)</span></span></td>\n        <td><code>pl-PL</code></td>\n        <td><span data-ttu-id=\"52cf5-486\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-486\">Female</span></span></td>\n        <td><code>pl-PL-ZofiaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-487\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-487\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-488\">葡萄牙语（巴西）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-488\">Portuguese (Brazil)</span></span></td>\n        <td><code>pt-BR</code></td>\n        <td><span data-ttu-id=\"52cf5-489\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-489\">Female</span></span></td>\n        <td><code>pt-BR-FranciscaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-490\">常规，<a href=\"speech-synthesis-markup#adjust-speaking-styles\" data-linktype=\"relative-path\">使用 SSML 可以使用</a>多种语音样式</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-490\">General, multiple voice styles available <a\n                href=\"speech-synthesis-markup#adjust-speaking-styles\" data-linktype=\"relative-path\">using SSML</a></span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-491\">葡萄牙语(巴西)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-491\">Portuguese (Brazil)</span></span></td>\n        <td><code>pt-BR</code></td>\n        <td><span data-ttu-id=\"52cf5-492\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-492\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-493\"><code>pt-BR-AntonioNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-493\"><code>pt-BR-AntonioNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-494\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-494\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-495\">葡萄牙语(葡萄牙)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-495\">Portuguese (Portugal)</span></span></td>\n        <td><code>pt-PT</code></td>\n        <td><span data-ttu-id=\"52cf5-496\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-496\">Female</span></span></td>\n        <td><code>pt-PT-FernandaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-497\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-497\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-498\">罗马尼亚语(罗马尼亚)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-498\">Romanian (Romania)</span></span></td>\n        <td><code>ro-RO</code></td>\n        <td><span data-ttu-id=\"52cf5-499\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-499\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-500\"><code>ro-RO-AlinaNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-500\"><code>ro-RO-AlinaNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-501\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-501\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-502\">俄语（俄罗斯）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-502\">Russian (Russia)</span></span></td>\n        <td><code>ru-RU</code></td>\n        <td><span data-ttu-id=\"52cf5-503\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-503\">Female</span></span></td>\n        <td><code>ru-RU-DariyaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-504\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-504\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-505\">斯洛伐克语(斯洛伐克)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-505\">Slovak (Slovakia)</span></span></td>\n        <td><code>sk-SK</code></td>\n        <td><span data-ttu-id=\"52cf5-506\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-506\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-507\"><code>sk-SK-ViktoriaNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-507\"><code>sk-SK-ViktoriaNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-508\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-508\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-509\">斯洛文尼亚语(斯洛文尼亚)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-509\">Slovenian (Slovenia)</span></span></td>\n        <td><code>sl-SI</code></td>\n        <td><span data-ttu-id=\"52cf5-510\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-510\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-511\"><code>sl-SI-PetraNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-511\"><code>sl-SI-PetraNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-512\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-512\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-513\">西班牙语（墨西哥）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-513\">Spanish (Mexico)</span></span></td>\n        <td><code>es-MX</code></td>\n        <td><span data-ttu-id=\"52cf5-514\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-514\">Female</span></span></td>\n        <td><code>es-MX-DaliaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-515\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-515\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-516\">西班牙语（墨西哥）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-516\">Spanish (Mexico)</span></span></td>\n        <td><code>es-MX</code></td>\n        <td><span data-ttu-id=\"52cf5-517\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-517\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-518\"><code>es-MX-JorgeNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-518\"><code>es-MX-JorgeNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-519\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-519\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-520\">西班牙语(西班牙)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-520\">Spanish (Spain)</span></span></td>\n        <td><code>es-ES</code></td>\n        <td><span data-ttu-id=\"52cf5-521\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-521\">Female</span></span></td>\n        <td><code>es-ES-ElviraNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-522\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-522\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-523\">西班牙语(西班牙)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-523\">Spanish (Spain)</span></span></td>\n        <td><code>es-ES</code></td>\n        <td><span data-ttu-id=\"52cf5-524\">男</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-524\">Male</span></span></td>\n        <td><span data-ttu-id=\"52cf5-525\"><code>es-ES-AlvaroNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-525\"><code>es-ES-AlvaroNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-526\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-526\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-527\">瑞典语（瑞典）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-527\">Swedish (Sweden)</span></span></td>\n        <td><code>sv-SE</code></td>\n        <td><span data-ttu-id=\"52cf5-528\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-528\">Female</span></span></td>\n        <td><code>sv-SE-HilleviNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-529\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-529\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-530\">泰米尔语（印度）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-530\">Tamil (India)</span></span></td>\n        <td><code>ta-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-531\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-531\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-532\"><code>ta-IN-PallaviNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-532\"><code>ta-IN-PallaviNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-533\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-533\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-534\">泰卢固语（印度）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-534\">Telugu (India)</span></span></td>\n        <td><code>te-IN</code></td>\n        <td><span data-ttu-id=\"52cf5-535\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-535\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-536\"><code>te-IN-ShrutiNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-536\"><code>te-IN-ShrutiNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-537\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-537\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-538\">泰语（泰国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-538\">Thai (Thailand)</span></span></td>\n        <td><code>th-TH</code></td>\n        <td><span data-ttu-id=\"52cf5-539\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-539\">Female</span></span></td>\n        <td><code>th-TH-AcharaNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-540\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-540\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-541\">泰语（泰国）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-541\">Thai (Thailand)</span></span></td>\n        <td><code>th-TH</code></td>\n        <td><span data-ttu-id=\"52cf5-542\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-542\">Female</span></span></td>\n        <td><span data-ttu-id=\"52cf5-543\"><code>th-TH-PremwadeeNeural</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-543\"><code>th-TH-PremwadeeNeural</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-544\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-544\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-545\">土耳其语（土耳其）</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-545\">Turkish (Turkey)</span></span></td>\n        <td><code>tr-TR</code></td>\n        <td><span data-ttu-id=\"52cf5-546\">Female</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-546\">Female</span></span></td>\n        <td><code>tr-TR-EmelNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-547\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-547\">General</span></span></td>\n    </tr>\n    <tr>\n        <td><span data-ttu-id=\"52cf5-548\">越南语(越南)</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-548\">Vietnamese (Vietnam)</span></span></td>\n        <td><span data-ttu-id=\"52cf5-549\"><code>vi-VN</code> <sup>新建</sup></span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-549\"><code>vi-VN</code> <sup>New</sup></span></span></td>\n        <td><span data-ttu-id=\"52cf5-550\">女</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-550\">Female</span></span></td>\n        <td><code>vi-VN-HoaiMyNeural</code></td>\n        <td><span data-ttu-id=\"52cf5-551\">常规</span><span class=\"sxs-lookup\"><span data-stu-id=\"52cf5-551\">General</span></span></td>\n    </tr>\n    </tbody>\n</table>\n\n<script>\n    let el = document.querySelector('.table1').querySelector('tbody').querySelectorAll('tr')\n    console.log(el.length)\n\n    let list = {}\n    el.forEach(e => {\n        // console.log(e)\n        let td = e.querySelectorAll('td')\n        let zhName = td[0].querySelector('span').innerText.replace(/（/g, '(').replace(/）/g, ')').replace(/ \\(/g, '(').trim()\n        let enName = td[0].querySelector('span[data-stu-id]').innerText.replace(/（/g, '(').replace(/）/g, ')').replace(/ \\(/g, '(').trim()\n        let k = td[1].querySelector('code').innerText.trim()\n        list[k] = {zhName: zhName, enName: enName}\n    })\n    console.log(list)\n    console.log(Object.keys(list).length)\n    console.log('======================')\n\n    let el2 = document.querySelector('.table2').querySelector('tbody').querySelectorAll('tr')\n    console.log(el2.length)\n\n    let arr = {}\n    el2.forEach(e => {\n        // console.log(e)\n        let td = e.querySelectorAll('td')\n        let zhName = td[0].querySelector('span').innerText.replace(/（/g, '(').replace(/）/g, ')').replace(/ \\(/g, '(').trim()\n        let enName = td[0].querySelector('span[data-stu-id]').innerText.replace(/（/g, '(').replace(/）/g, ')').replace(/ \\(/g, '(').trim()\n        let k = td[1].querySelector('code').innerText.trim()\n        arr[k] = {zhName: zhName, enName: enName}\n    })\n    console.log(arr)\n    console.log(Object.keys(arr).length)\n\n    for (let k in arr) {\n        if (!list[k]) {\n            console.log('err:', k)\n            list[k] = arr[k]\n        }\n    }\n    console.log('======================')\n\n    let langList = {\n        \"ar-SA\": \"阿拉伯语(沙特阿拉伯)\",\n        \"cs-CZ\": \"捷克语(捷克共和国)\",\n        \"da-DK\": \"丹麦语(丹麦)\",\n        \"de-DE\": \"德语(德国)\",\n        \"el-GR\": \"希腊语(希腊)\",\n        \"en\": \"英语\",\n        \"en-AU\": \"英语(澳大利亚)\",\n        \"en-GB\": \"英语(英国)\",\n        \"en-IE\": \"英语(爱尔兰)\",\n        \"en-IN\": \"英语(印度)\",\n        \"en-US\": \"英语(美国)\",\n        \"en-ZA\": \"英语(南非)\",\n        \"es-AR\": \"西班牙语(阿根廷)\",\n        \"es-ES\": \"西班牙语(西班牙)\",\n        \"es-MX\": \"西班牙语(墨西哥)\",\n        \"es-US\": \"西班牙语(美国)\",\n        \"fi-FI\": \"芬兰语(芬兰)\",\n        \"fr-CA\": \"法语(加拿大)\",\n        \"fr-FR\": \"法语(法国)\",\n        \"he-IL\": \"希伯来语(以色列)\",\n        \"hi-IN\": \"印地语(印度)\",\n        \"hu-HU\": \"匈牙利语(匈牙利)\",\n        \"id-ID\": \"印度尼西亚语(印度尼西亚)\",\n        \"it-IT\": \"意大利语(意大利)\",\n        \"ja-JP\": \"日语(日本)\",\n        \"ko-KR\": \"韩语(韩国)\",\n        \"nb-NO\": \"挪威语(博克马尔语，挪威)\",\n        \"nl-BE\": \"荷兰语(比利时)\",\n        \"nl-NL\": \"荷兰语(荷兰)\",\n        \"pl-PL\": \"波兰语(波兰)\",\n        \"pt-BR\": \"葡萄牙语(巴西)\",\n        \"pt-PT\": \"葡萄牙语(葡萄牙)\",\n        \"ro-RO\": \"罗马尼亚语(罗马尼亚)\",\n        \"ru-RU\": \"俄语(俄罗斯)\",\n        \"sk-SK\": \"斯洛伐克语(斯洛伐克)\",\n        \"sv-SE\": \"瑞典语(瑞典)\",\n        \"th-TH\": \"泰语(泰国)\",\n        \"tr-TR\": \"土耳其语(土耳其)\",\n        \"zh-CN\": \"中文(简体)\",\n        \"zh-HK\": \"中文(粤语)\",\n        \"zh-TW\": \"中文(繁体)\"\n    }\n\n    for (let k in langList) {\n        if (!list[k]) {\n            console.log('err:', k)\n            list[k] = {zhName: langList[k], enName: ''}\n        }\n    }\n    console.log('======================')\n\n    let kArr = Object.keys(list)\n    console.log(kArr)\n    kArr = kArr.sort()\n    console.log(kArr)\n\n    let lanList = {}\n    kArr.forEach(k => {\n        lanList[k] = list[k]\n    })\n    console.log('======================')\n\n    console.log(JSON.stringify(lanList))\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tool/qq.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <style>\n        table {\n            box-sizing: border-box;\n            border-color: grey;\n            font-size: 14px;\n        }\n\n        table tr td {\n            min-width: 120px;\n        }\n    </style>\n</head>\n<body>\n<script src=\"bd.js\"></script>\n<script>\n    // qq language\n    let qqList = {\n        auto: \"自动识别\",\n        zh: \"中文\",\n        en: \"英语\",\n        jp: \"日语\",\n        kr: \"韩语\",\n        fr: \"法语\",\n        es: \"西班牙语\",\n        it: \"意大利语\",\n        de: \"德语\",\n        tr: \"土耳其语\",\n        ru: \"俄语\",\n        pt: \"葡萄牙语\",\n        vi: \"越南语\",\n        id: \"印尼语\",\n        th: \"泰语\",\n        ms: \"马来西亚语\",\n        ar: \"阿拉伯语\",\n        hi: \"印地语\"\n    }\n    // console.log(qqList)\n\n    // baidu language list\n    let bdToObj = function (name) {\n        let i = 0\n        let o = {}\n        for (let k in bdList) {\n            i++\n            let n = bdList[k][name]\n            if (n) o[n] = k\n        }\n        console.log(JSON.stringify(o))\n        console.log(i)\n        return o\n    }\n    let bdObj = bdToObj('zhName')\n\n    let err = 0\n    let a1 = [], a2 = [], ae = []\n    for (let k in qqList) {\n        let v = qqList[k]\n        if (bdList[k]) {\n            // key 一样的情况\n            a1.push({key: k, qqName: v, bdName: bdList[k].zhName})\n        } else if (bdObj[v]) {\n            // 中文名 一样的情况\n            a2.push({bdKey: bdObj[v], qqKey: k, name: v})\n        } else {\n            // 都没有的情况\n            err++\n            ae.push({key: k, name: v})\n        }\n    }\n    // console.log(JSON.stringify(a3))\n    console.log('err:', err)\n\n    let obj = {} // 最终的对应表\n    // key 一致的数据\n    document.write(`<table style=\"color:green\">`)\n    a1.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.qqName}</td><td>${v.bdName}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // name 一致的数据\n    document.write(`<table>`)\n    a2.forEach((v, k) => {\n        obj[v.bdKey] = v.qqKey\n        document.write(`<tr><td>${k + 1}</td><td>${v.bdKey}</td><td>${v.qqKey}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // 都没有的情况\n    document.write(`<table style=\"color:red\">`)\n    ae.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // document.write(`<hr>`)\n    console.log(JSON.stringify(obj))\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tool/sogou.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <style>\n        table {\n            box-sizing: border-box;\n            border-color: grey;\n            font-size: 14px;\n        }\n\n        table tr td {\n            min-width: 120px;\n        }\n    </style>\n</head>\n<body>\n<script src=\"bd.js\"></script>\n<script>\n    // sogou language\n    let sogouList = {\n        \"auto\": {\n            \"lang\": \"auto\",\n            \"text\": \"自动检测\",\n            \"play\": !1\n        },\n        \"fromCY\": [{\n            \"lang\": \"zh-CHS\",\n            \"text\": \"中文\",\n            \"play\": !0\n        }, {\n            \"lang\": \"en\",\n            \"text\": \"英语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"ja\",\n            \"text\": \"日语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"ko\",\n            \"text\": \"韩语\",\n            \"play\": !0\n        }],\n        \"toCY\": [{\n            \"lang\": \"zh-CHS\",\n            \"text\": \"中文\",\n            \"play\": !0\n        }, {\n            \"lang\": \"en\",\n            \"text\": \"英语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"ja\",\n            \"text\": \"日语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"ko\",\n            \"text\": \"韩语\",\n            \"play\": !0\n        }],\n        \"LI\": {\n            \"A\": [{\n                \"lang\": \"ar\",\n                \"text\": \"阿拉伯语\",\n                \"play\": !0,\n                \"dir\": \"rtl\"\n            }, {\n                \"lang\": \"et\",\n                \"text\": \"爱沙尼亚语\",\n                \"play\": !1\n            }],\n            \"B\": [{\n                \"lang\": \"bg\",\n                \"text\": \"保加利亚语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"pl\",\n                \"text\": \"波兰语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"bs-Latn\",\n                \"text\": \"波斯尼亚语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"fa\",\n                \"text\": \"波斯语\",\n                \"play\": !1,\n                \"dir\": \"rtl\"\n            }, {\n                \"lang\": \"mww\",\n                \"text\": \"白苗文\",\n                \"play\": !1\n            }],\n            \"D\": [{\n                \"lang\": \"da\",\n                \"text\": \"丹麦语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"de\",\n                \"text\": \"德语\",\n                \"play\": !0\n            }],\n            \"E\": [{\n                \"lang\": \"ru\",\n                \"text\": \"俄语\",\n                \"play\": !0\n            }],\n            \"F\": [{\n                \"lang\": \"fr\",\n                \"text\": \"法语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"fi\",\n                \"text\": \"芬兰语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"fj\",\n                \"text\": \"斐济语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"fil\",\n                \"text\": \"菲律宾语\",\n                \"play\": !1\n            }],\n            \"H\": [{\n                \"lang\": \"ht\",\n                \"text\": \"海地克里奥尔语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"ko\",\n                \"text\": \"韩语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"nl\",\n                \"text\": \"荷兰语\",\n                \"play\": !0\n            }],\n            \"J\": [{\n                \"lang\": \"ca\",\n                \"text\": \"加泰隆语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"cs\",\n                \"text\": \"捷克语\",\n                \"play\": !0\n            }],\n            \"K\": [{\n                \"lang\": \"tlh\",\n                \"text\": \"克林贡语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"tlh-Qaak\",\n                \"text\": \"克林贡语(piqaD)\",\n                \"play\": !1,\n                \"font\": \"piqad\"\n            }, {\n                \"lang\": \"hr\",\n                \"text\": \"克罗地亚语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"otq\",\n                \"text\": \"克雷塔罗奥托米语\",\n                \"play\": !1\n            }],\n            \"L\": [{\n                \"lang\": \"ro\",\n                \"text\": \"罗马尼亚语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"lv\",\n                \"text\": \"拉脱维亚语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"lt\",\n                \"text\": \"立陶宛语\",\n                \"play\": !1\n            }],\n            \"M\": [{\n                \"lang\": \"ms\",\n                \"text\": \"马来语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"mt\",\n                \"text\": \"马耳他语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"mg\",\n                \"text\": \"马尔加什语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"bn\",\n                \"text\": \"孟加拉语\",\n                \"play\": !1\n            }],\n            \"N\": [{\n                \"lang\": \"af\",\n                \"text\": \"南非荷兰语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"no\",\n                \"text\": \"挪威语\",\n                \"play\": !0\n            }],\n            \"P\": [{\n                \"lang\": \"pt\",\n                \"text\": \"葡萄牙语\",\n                \"play\": !0\n            }],\n            \"R\": [{\n                \"lang\": \"ja\",\n                \"text\": \"日语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"sv\",\n                \"text\": \"瑞典语\",\n                \"play\": !0\n            }],\n            \"S\": [{\n                \"lang\": \"sl\",\n                \"text\": \"斯洛文尼亚语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"sr-Latn\",\n                \"text\": \"塞尔维亚语(拉丁文)\",\n                \"play\": !1\n            }, {\n                \"lang\": \"sr-Cyrl\",\n                \"text\": \"塞尔维亚语(西里尔文)\",\n                \"play\": !1\n            }, {\n                \"lang\": \"sk\",\n                \"text\": \"斯洛伐克语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"sw\",\n                \"text\": \"斯瓦希里语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"sm\",\n                \"text\": \"萨摩亚语\",\n                \"play\": !1\n            }],\n            \"T\": [{\n                \"lang\": \"th\",\n                \"text\": \"泰语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"tr\",\n                \"text\": \"土耳其语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"to\",\n                \"text\": \"汤加语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"ty\",\n                \"text\": \"塔希提语\",\n                \"play\": !1\n            }],\n            \"U\": [{\n                \"lang\": \"yua\",\n                \"text\": \"尤卡坦玛雅语\",\n                \"play\": !1\n            }],\n            \"V\": [{\n                \"lang\": \"cy\",\n                \"text\": \"威尔士语\",\n                \"play\": !1\n            }],\n            \"W\": [{\n                \"lang\": \"uk\",\n                \"text\": \"乌克兰语\",\n                \"play\": !1\n            }, {\n                \"lang\": \"ur\",\n                \"text\": \"乌尔都语\",\n                \"play\": !1,\n                \"dir\": \"rtl\"\n            }],\n            \"X\": [{\n                \"lang\": \"es\",\n                \"text\": \"西班牙语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"el\",\n                \"text\": \"希腊语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"hu\",\n                \"text\": \"匈牙利语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"he\",\n                \"text\": \"希伯来语\",\n                \"play\": !0,\n                \"dir\": \"rtl\"\n            }],\n            \"Y\": [{\n                \"lang\": \"en\",\n                \"text\": \"英语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"it\",\n                \"text\": \"意大利语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"hi\",\n                \"text\": \"印地语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"id\",\n                \"text\": \"印度尼西亚语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"vi\",\n                \"text\": \"越南语\",\n                \"play\": !0\n            }, {\n                \"lang\": \"yue\",\n                \"text\": \"粤语(繁体)\",\n                \"play\": !0\n            }],\n            \"Z\": [{\n                \"lang\": \"zh-CHS\",\n                \"text\": \"中文\",\n                \"play\": !0\n            }, {\n                \"lang\": \"zh-CHT\",\n                \"text\": \"中文繁体\",\n                \"play\": !0\n            }]\n        },\n        \"ALL\": [{\n            \"lang\": \"ar\",\n            \"text\": \"阿拉伯语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"et\",\n            \"text\": \"爱沙尼亚语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"bg\",\n            \"text\": \"保加利亚语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"pl\",\n            \"text\": \"波兰语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"bs-Latn\",\n            \"text\": \"波斯尼亚语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"fa\",\n            \"text\": \"波斯语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"mww\",\n            \"text\": \"白苗文\",\n            \"play\": !1\n        }, {\n            \"lang\": \"da\",\n            \"text\": \"丹麦语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"de\",\n            \"text\": \"德语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"ru\",\n            \"text\": \"俄语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"fr\",\n            \"text\": \"法语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"fi\",\n            \"text\": \"芬兰语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"fj\",\n            \"text\": \"斐济语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"fil\",\n            \"text\": \"菲律宾语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"ko\",\n            \"text\": \"韩语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"ht\",\n            \"text\": \"海地克里奥尔语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"nl\",\n            \"text\": \"荷兰语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"ca\",\n            \"text\": \"加泰隆语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"cs\",\n            \"text\": \"捷克语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"tlh\",\n            \"text\": \"克林贡语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"tlh-Qaak\",\n            \"text\": \"克林贡语(piqaD)\",\n            \"play\": !1,\n            \"font\": \"piqad\"\n        }, {\n            \"lang\": \"hr\",\n            \"text\": \"克罗地亚语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"otq\",\n            \"text\": \"克雷塔罗奥托米语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"ro\",\n            \"text\": \"罗马尼亚语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"lv\",\n            \"text\": \"拉脱维亚语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"lt\",\n            \"text\": \"立陶宛语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"ms\",\n            \"text\": \"马来语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"mt\",\n            \"text\": \"马耳他语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"mg\",\n            \"text\": \"马尔加什语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"bn\",\n            \"text\": \"孟加拉语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"af\",\n            \"text\": \"南非荷兰语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"no\",\n            \"text\": \"挪威语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"pt\",\n            \"text\": \"葡萄牙语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"ja\",\n            \"text\": \"日语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"sv\",\n            \"text\": \"瑞典语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"sl\",\n            \"text\": \"斯洛文尼亚语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"sr-Latn\",\n            \"text\": \"塞尔维亚语(拉丁文)\",\n            \"play\": !1\n        }, {\n            \"lang\": \"sr-Cyrl\",\n            \"text\": \"塞尔维亚语(西里尔文)\",\n            \"play\": !1\n        }, {\n            \"lang\": \"sk\",\n            \"text\": \"斯洛伐克语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"sw\",\n            \"text\": \"斯瓦希里语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"sm\",\n            \"text\": \"萨摩亚语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"th\",\n            \"text\": \"泰语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"tr\",\n            \"text\": \"土耳其语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"to\",\n            \"text\": \"汤加语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"ty\",\n            \"text\": \"塔希提语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"yua\",\n            \"text\": \"尤卡坦玛雅语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"cy\",\n            \"text\": \"威尔士语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"uk\",\n            \"text\": \"乌克兰语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"ur\",\n            \"text\": \"乌尔都语\",\n            \"play\": !1\n        }, {\n            \"lang\": \"es\",\n            \"text\": \"西班牙语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"el\",\n            \"text\": \"希腊语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"hu\",\n            \"text\": \"匈牙利语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"he\",\n            \"text\": \"希伯来语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"en\",\n            \"text\": \"英语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"it\",\n            \"text\": \"意大利语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"hi\",\n            \"text\": \"印地语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"id\",\n            \"text\": \"印度尼西亚语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"vi\",\n            \"text\": \"越南语\",\n            \"play\": !0\n        }, {\n            \"lang\": \"yue\",\n            \"text\": \"粤语(繁体)\",\n            \"play\": !0\n        }, {\n            \"lang\": \"zh-CHS\",\n            \"text\": \"中文\",\n            \"play\": !0\n        }, {\n            \"lang\": \"zh-CHT\",\n            \"text\": \"中文繁体\",\n            \"play\": !0\n        }],\n        \"Doc\": {\n            \"lang\": [{\n                \"type\": \"direction\",\n                \"text\": \"全部语言\",\n                \"value\": \"\",\n                \"uigs\": \"direction_all\"\n            }, {\n                \"type\": \"direction\",\n                \"text\": \"中 → 英\",\n                \"value\": \"zh2en\",\n                \"uigs\": \"direction_zh2en\"\n            }, {\n                \"type\": \"direction\",\n                \"text\": \"英 → 中\",\n                \"value\": \"en2zh\",\n                \"uigs\": \"direction_en2zh\"\n            }, {\n                \"type\": \"direction\",\n                \"text\": \"中 → 韩\",\n                \"value\": \"zh2ko\",\n                \"uigs\": \"direction_zh2en\"\n            }, {\n                \"type\": \"direction\",\n                \"text\": \"韩 → 中\",\n                \"value\": \"ko2zh\",\n                \"uigs\": \"direction_en2zh\"\n            }, {\n                \"type\": \"direction\",\n                \"text\": \"中 → 日\",\n                \"value\": \"zh2ja\",\n                \"uigs\": \"direction_zh2en\"\n            }, {\n                \"type\": \"direction\",\n                \"text\": \"日 → 中\",\n                \"value\": \"ja2zh\",\n                \"uigs\": \"direction_en2zh\"\n            }],\n            \"uploadTime\": [{\n                \"type\": \"uploadTime\",\n                \"text\": \"时间升序\",\n                \"value\": \"asc\",\n                \"uigs\": \"uploadTime_asc\"\n            }, {\n                \"type\": \"uploadTime\",\n                \"text\": \"时间降序\",\n                \"value\": \"desc\",\n                \"uigs\": \"uploadTime_desc\"\n            }]\n        }\n    }\n    // console.log(sogouList)\n    console.log(sogouList.ALL)\n\n    /*console.log(sogouList.LI)\n    let n = 0\n    for (let [k, v] of Object.entries(sogouList.LI)) {\n        console.log(k, v)\n        n += v.length\n    }\n    console.log('N:', n)*/\n\n    // baidu language list\n    let bdToObj = function (name) {\n        let i = 0\n        let o = {}\n        for (let k in bdList) {\n            i++\n            let n = bdList[k][name]\n            if (n) o[n] = k\n        }\n        console.log(JSON.stringify(o))\n        console.log(i)\n        return o\n    }\n    let bdObj = bdToObj('zhName')\n\n    // soList\n    let soList = {}\n    sogouList.ALL.forEach(v => {\n        soList[v.lang] = v.text\n    })\n\n    let err = 0\n    let a1 = [], a2 = [], ae = []\n    for (let k in soList) {\n        let v = soList[k]\n        if (bdList[k]) {\n            // key 一样的情况\n            a1.push({key: k, sogouName: v, bdName: bdList[k].zhName})\n        } else if (bdObj[v]) {\n            // 中文名 一样的情况\n            a2.push({bdKey: bdObj[v], sogouKey: k, name: v})\n        } else {\n            // 都没有的情况\n            err++\n            ae.push({key: k, name: v})\n        }\n    }\n    // console.log(JSON.stringify(a3))\n    console.log('err:', err)\n\n    let obj = {} // 最终的对应表\n    // key 一致的数据\n    document.write(`<table style=\"color:green\">`)\n    a1.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.sogouName}</td><td>${v.bdName}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // name 一致的数据\n    document.write(`<table>`)\n    a2.forEach((v, k) => {\n        obj[v.bdKey] = v.sogouKey\n        document.write(`<tr><td>${k + 1}</td><td>${v.bdKey}</td><td>${v.sogouKey}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // 都没有的情况\n    document.write(`<table style=\"color:red\">`)\n    ae.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // document.write(`<hr>`)\n    console.log(JSON.stringify(obj))\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "tool/youdao.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <style>\n        table {\n            box-sizing: border-box;\n            border-color: grey;\n            font-size: 14px;\n        }\n\n        table tr td {\n            min-width: 120px;\n        }\n    </style>\n</head>\n<body>\n<script src=\"bd.js\"></script>\n<script>\n    // youdao language\n    let ydList = {\n        \"中文\": \"zh-CHS\",\n        \"英语\": \"en\",\n        \"韩语\": \"ko\",\n        \"日语\": \"ja\",\n        \"法语\": \"fr\",\n        \"俄语\": \"ru\",\n        \"西班牙语\": \"es\",\n        \"葡萄牙语\": \"pt\",\n        \"印地语\": \"hi\",\n        \"阿拉伯语\": \"ar\",\n        \"丹麦语\": \"da\",\n        \"德语\": \"de\",\n        \"希腊语\": \"el\",\n        \"芬兰语\": \"fi\",\n        \"意大利语\": \"it\",\n        \"马来语\": \"ms\",\n        \"越南语\": \"vi\",\n        \"印尼语\": \"id\",\n        \"荷兰语\": \"nl\"\n    }\n    let ydObj = {}\n    for (const [key, value] of Object.entries(ydList)) {\n        // console.log(`${key}: ${value}`);\n        ydObj[value] = key\n    }\n    console.log(ydObj)\n\n    // baidu language list\n    let bdToObj = function (name) {\n        let i = 0\n        let o = {}\n        for (let k in bdList) {\n            i++\n            let n = bdList[k][name]\n            if (n) o[n] = k\n        }\n        console.log(JSON.stringify(o))\n        console.log(i)\n        return o\n    }\n    let bdObj = bdToObj('zhName')\n\n    let err = 0\n    let a1 = [], a2 = [], ae = []\n    for (const [k, v] of Object.entries(ydObj)) {\n        // console.log(k, v)\n        if (bdList[k]) {\n            // key 一样的情况\n            a1.push({key: k, ydName: v, bdName: bdList[k].zhName})\n        } else if (bdObj[v]) {\n            // 中文名 一样的情况\n            a2.push({bdKey: bdObj[v], ydKey: k, name: v})\n        } else {\n            // 都没有的情况\n            err++\n            ae.push({key: k, name: v})\n        }\n    }\n    // console.log(JSON.stringify(a3))\n    console.log('err:', err)\n\n    let obj = {} // 最终的对应表\n    // key 一致的数据\n    document.write(`<table style=\"color:green\">`)\n    a1.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.ydName}</td><td>${v.bdName}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // name 一致的数据\n    document.write(`<table>`)\n    a2.forEach((v, k) => {\n        obj[v.bdKey] = v.ydKey\n        document.write(`<tr><td>${k + 1}</td><td>${v.bdKey}</td><td>${v.ydKey}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // 都没有的情况\n    document.write(`<table style=\"color:red\">`)\n    ae.forEach((v, k) => {\n        obj[v.key] = v.key\n        document.write(`<tr><td>${k + 1}</td><td>${v.key}</td><td>${v.key}</td><td>${v.name}</td></tr>`)\n    })\n    document.write(`</table>`)\n\n    // document.write(`<hr>`)\n    console.log(JSON.stringify(obj))\n</script>\n</body>\n</html>\n"
  }
]