[{"data":1,"prerenderedAt":2453},["ShallowReactive",2],{"post-zh-moodle-opencc-traditional-chinese":3,"surround-zh-blog\u002Fzh\u002Fblog\u002Fmoodle-opencc-traditional-chinese.md":2446},{"id":4,"title":5,"body":6,"canonical":283,"category":2429,"date":2430,"description":2431,"extension":2432,"image":2433,"meta":2434,"navigation":1070,"path":2435,"seo":2436,"slug":2437,"status":2438,"stem":2439,"tags":2440,"translationSlug":2437,"__hash__":2445},"blog\u002Fzh\u002Fblog\u002Fmoodle-opencc-traditional-chinese.md","不改数据库，用 OpenCC 给 Moodle 加上全站繁体中文转换（含 RequireJS 避坑）",{"type":7,"value":8,"toc":2405},"minimark",[9,14,18,21,24,58,76,79,83,86,91,97,100,111,122,148,177,187,191,194,258,260,266,276,286,294,301,500,530,538,559,562,568,585,597,616,619,660,693,902,905,907,911,915,938,942,970,2185,2210,2214,2217,2345,2352,2354,2358,2368,2401],[10,11,13],"h2",{"id":12},"_1-起因与结论先行","1. 起因与结论先行",[15,16,17],"p",{},"如果你使用 Moodle 做了一套简体中文 AI 课程，但是又不想忽略掉繁体中文的用户，同时也不想再单独维护一套繁体中文的课程，可以试一下这个办法。",[15,19,20],{},"需求很明确：不能改动现有的简体数据库，不能把课程和题库复制成两套维护，也尽量不要去重新编译 Docker 容器或折腾 PHP 后端插件，但要让切换到繁体中文的学员看到全繁体的界面、课件正文与测验题目。",[15,22,23],{},"今天我把整套链路在 Moodle 线上环境跑通了。最终验证可行的方案分为两层：",[25,26,27,40],"ol",{},[28,29,30,34,35,39],"li",{},[31,32,33],"strong",{},"系统界面层","：直接在 Moodle 后台安装官方的 ",[36,37,38],"code",{},"zh_tw","（正体中文）语言包，接管导航栏、按钮和系统提示。",[28,41,42,45,46,49,50,53,54,57],{},[31,43,44],{},"课程与题库内容层","：通过 Moodle 自带的「额外 HTML（Additional HTML）」功能，在 ",[36,47,48],{},"\u003C\u002Fbody>"," 闭合前注入一段基于 ",[36,51,52],{},"opencc-js","（",[36,55,56],{},"cn -> twp","）的前端转换脚本，实时把页面 DOM 中的简体正文和地域词汇（比如「文件 → 檔案」「视频 → 影片」）转为台湾繁体。",[15,59,60,61,64,65,67,68,71,72,75],{},"原本以为只需要往后台贴一行 ",[36,62,63],{},"\u003Cscript>"," 引入 ",[36,66,52],{}," 就能收工，结果在实际页面里连续踩了 ",[31,69,70],{},"Moodle 内置 RequireJS 劫持 UMD 模块","和",[31,73,74],{},"语言标识属性判断失灵","两个坑。下面把原理、排错过程和可直接复制的完整代码整理出来。",[77,78],"hr",{},[10,80,82],{"id":81},"_2-核心认知为什么直觉做法会卡住","2. 核心认知：为什么直觉做法会卡住？",[15,84,85],{},"在动手写代码前，有三个关于 Moodle 的机制边界必须先厘清：",[87,88,90],"h3",{"id":89},"_21-官方语言包不管数据库里的课程正文","2.1 官方语言包不管数据库里的课程正文",[15,92,93,94,96],{},"Moodle 官方提供的 ",[36,95,38],{}," 语言包非常完善，但它只负责替换 Moodle 源码里的语言字符串（比如把页面顶部的“我的主页”变成“我的主頁”）。老师录入在数据库里的课程标题、章节导读、页面资源和测验题干，语言包完全不会触碰。",[15,98,99],{},"如果去写后端 PHP Filter 插件，不仅 Moodle 官方插件库没有现成的 OpenCC 插件，还得往服务器容器里编译 C++ 依赖和 PHP 扩展，每次请求都要消耗服务器 CPU 做字典匹配；相比之下，把转换放到浏览器端执行，服务器零负载，随时删掉代码就能回滚。",[87,101,103,104,107,108],{"id":102},"_22-判断简繁环境别信-html-lang要看-bodyclasslist","2.2 判断简繁环境：别信 ",[36,105,106],{},"\u003Chtml lang>","，要看 ",[36,109,110],{},"body.classList",[15,112,113,114,117,118,121],{},"直觉上，前端脚本判断当前页面是不是繁体中文，通常会去读 ",[36,115,116],{},"document.documentElement.lang","（也就是 ",[36,119,120],{},"\u003Chtml lang=\"...\">","）。",[15,123,124,125,128,129,132,133,135,136,139,140,143,144,147],{},"但在 Moodle 里实测发现，无论是访问 ",[36,126,127],{},"?lang=zh_cn"," 还是 ",[36,130,131],{},"?lang=zh_tw","，",[36,134,116],{}," 返回的值全都是 ",[36,137,138],{},"\"zh\"","，根本区分不开简繁。Moodle 真正稳定暴露当前语言包的位置在 ",[36,141,142],{},"\u003Cbody>"," 的 ",[36,145,146],{},"class"," 列表里：",[149,150,151,161,169],"ul",{},[28,152,153,154,157,158],{},"简体中文环境：",[36,155,156],{},"document.body.className"," 包含 ",[36,159,160],{},"lang-zh_cn",[28,162,163,164,157,166],{},"台湾繁体环境：",[36,165,156],{},[36,167,168],{},"lang-zh_tw",[28,170,171,172,157,174],{},"香港繁体环境：",[36,173,156],{},[36,175,176],{},"lang-zh_hk",[15,178,179,180,183,184,186],{},"因此，脚本开头只要检查 ",[36,181,182],{},"document.body.classList.contains('lang-zh_tw')"," 或 ",[36,185,176],{},"，就能确保简体学员访问时脚本直接休眠，不加载任何转换字典。",[87,188,190],{"id":189},"_23-前端把题目转成繁体会影响测验自动判分吗","2.3 前端把题目转成繁体，会影响测验自动判分吗？",[15,192,193],{},"这取决于 Moodle 题型的提交机制：",[149,195,196,224,237],{},[28,197,198,213,214,217,218,217,221,121],{},[31,199,200,201,204,205,208,209,212],{},"单选\u002F多选题（",[36,202,203],{},"multichoice","）、判断题（",[36,206,207],{},"truefalse","）、配对连线题（",[36,210,211],{},"matching","）","：完全不受影响。前端只改了文字展示，学员交卷时传给后端的是选项 ID（如 ",[36,215,216],{},"1",", ",[36,219,220],{},"2",[36,222,223],{},"3",[28,225,226,232,233,236],{},[31,227,228,229,212],{},"选词填空题（",[36,230,231],{},"gapselect","：虽然名字叫“填空”，但查看 Moodle 题库 XML 源码就会发现，它在网页上渲染的是 ",[36,234,235],{},"\u003Cselect>"," 下拉菜单（让学员从预设词里下拉挑选），交卷同样提交选项序号，判分零影响。",[28,238,239,253,254,257],{},[31,240,241,242,245,246,249,250,212],{},"简答题（",[36,243,244],{},"shortanswer","）与内嵌 ",[36,247,248],{},"SHORTANSWER"," 的完形填空（",[36,251,252],{},"cloze","：这类题需要学员在文本框里敲字。如果后台标准答案只有简体，学员敲入繁体字直接交卷就会被判错。针对这类题，可以在表单触发 ",[36,255,256],{},"submit"," 时加一道拦截，把输入框里的繁体字自动转回简体再发给服务器。",[77,259],{},[10,261,263,264],{"id":262},"_3-踩坑记录被-requirejs-劫持的-opencc-js","3. 踩坑记录：被 RequireJS 劫持的 ",[36,265,52],{},[15,267,268,269,271,272,275],{},"明确了思路后，我第一版代码直接用动态 ",[36,270,63],{}," 标签去加载 CDN 上的 ",[36,273,274],{},"opencc-js@1.0.5\u002Fdist\u002Fumd\u002Ffull.js","，结果控制台当场报错：",[277,278,284],"pre",{"className":279,"code":281,"language":282,"meta":283},[280],"language-text","Uncaught ReferenceError: OpenCC is not defined\n","text","",[36,285,281],{"__ignoreMap":283},[87,287,289,290,293],{"id":288},"第一轮排查为什么-windowopencc-没挂载上","第一轮排查：为什么 ",[36,291,292],{},"window.OpenCC"," 没挂载上？",[15,295,296,297,300],{},"拉取 ",[36,298,299],{},"opencc-js\u002Fdist\u002Fumd\u002Ffull.js"," 的头部源码一看，它是一个标准的 UMD 包装器：",[277,302,306],{"className":303,"code":304,"language":305,"meta":283,"style":283},"language-javascript shiki shiki-themes github-dark","(function (global, factory) {\n  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n  typeof define === 'function' && define.amd ? define(['exports'], factory) :\n  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.OpenCC = {}));\n})(this, (function (exports) { 'use strict'; ... }));\n","javascript",[36,307,308,336,386,422,467],{"__ignoreMap":283},[309,310,313,317,321,324,328,330,333],"span",{"class":311,"line":312},"line",1,[309,314,316],{"class":315},"s95oV","(",[309,318,320],{"class":319},"snl16","function",[309,322,323],{"class":315}," (",[309,325,327],{"class":326},"s9osk","global",[309,329,217],{"class":315},[309,331,332],{"class":326},"factory",[309,334,335],{"class":315},") {\n",[309,337,339,342,346,349,353,356,359,362,365,368,371,375,377,380,383],{"class":311,"line":338},2,[309,340,341],{"class":319},"  typeof",[309,343,345],{"class":344},"sDLfK"," exports",[309,347,348],{"class":319}," ===",[309,350,352],{"class":351},"sU2Wk"," 'object'",[309,354,355],{"class":319}," &&",[309,357,358],{"class":319}," typeof",[309,360,361],{"class":344}," module",[309,363,364],{"class":319}," !==",[309,366,367],{"class":351}," 'undefined'",[309,369,370],{"class":319}," ?",[309,372,374],{"class":373},"svObZ"," factory",[309,376,316],{"class":315},[309,378,379],{"class":344},"exports",[309,381,382],{"class":315},") ",[309,384,385],{"class":319},":\n",[309,387,389,391,394,397,400,402,405,408,411,414,417,420],{"class":311,"line":388},3,[309,390,341],{"class":319},[309,392,393],{"class":315}," define ",[309,395,396],{"class":319},"===",[309,398,399],{"class":351}," 'function'",[309,401,355],{"class":319},[309,403,404],{"class":315}," define.amd ",[309,406,407],{"class":319},"?",[309,409,410],{"class":373}," define",[309,412,413],{"class":315},"([",[309,415,416],{"class":351},"'exports'",[309,418,419],{"class":315},"], factory) ",[309,421,385],{"class":319},[309,423,425,428,431,433,436,439,441,443,445,448,451,454,457,459,462,464],{"class":311,"line":424},4,[309,426,427],{"class":315},"  (global ",[309,429,430],{"class":319},"=",[309,432,358],{"class":319},[309,434,435],{"class":315}," globalThis ",[309,437,438],{"class":319},"!==",[309,440,367],{"class":351},[309,442,370],{"class":319},[309,444,435],{"class":315},[309,446,447],{"class":319},":",[309,449,450],{"class":315}," global ",[309,452,453],{"class":319},"||",[309,455,456],{"class":315}," self, ",[309,458,332],{"class":373},[309,460,461],{"class":315},"(global.OpenCC ",[309,463,430],{"class":319},[309,465,466],{"class":315}," {}));\n",[309,468,470,473,476,479,481,483,485,488,491,494,497],{"class":311,"line":469},5,[309,471,472],{"class":315},"})(",[309,474,475],{"class":344},"this",[309,477,478],{"class":315},", (",[309,480,320],{"class":319},[309,482,323],{"class":315},[309,484,379],{"class":326},[309,486,487],{"class":315},") { ",[309,489,490],{"class":351},"'use strict'",[309,492,493],{"class":315},"; ",[309,495,496],{"class":319},"...",[309,498,499],{"class":315}," }));\n",[15,501,502,503,506,507,510,511,514,515,518,519,522,523,526,527,529],{},"再去 Moodle 页面控制台里一查 ",[36,504,505],{},"typeof window.define","，结果是 ",[36,508,509],{},"\"function\"","，且 ",[36,512,513],{},"window.define.amd"," 为真——",[31,516,517],{},"Moodle 底层全局挂载了 RequireJS","。当 UMD 脚本发现页面里存在 ",[36,520,521],{},"define.amd"," 时，就会走第二分支调用 ",[36,524,525],{},"define(['exports'], factory)"," 注册成 AMD 匿名模块，根本不会走第三分支去给 ",[36,528,292],{}," 赋值。",[87,531,533,534,537],{"id":532},"第二轮踩坑临时置空-windowdefine-为什么在真实页面翻车","第二轮踩坑：临时置空 ",[36,535,536],{},"window.define"," 为什么在真实页面翻车？",[15,539,540,541,543,544,546,547,550,551,554,555,558],{},"遇到 RequireJS 劫持，最容易想到的绕法是在插入 ",[36,542,63],{}," 前先把 ",[36,545,536],{}," 设为 ",[36,548,549],{},"undefined","，等 ",[36,552,553],{},"script.onload"," 触发后再把原来的 ",[36,556,557],{},"define"," 还原回去。",[15,560,561],{},"我在控制台单步测试时，这个办法确实跑通了。但当我把这段代码填进 Moodle 后台保存、刷新正式页面时，页面上的课程标题依然是简体，打开控制台一看，爆出了更严重的错误：",[277,563,566],{"className":564,"code":565,"language":282,"meta":283},[280],"Uncaught TypeError: Cannot read properties of undefined (reading 'Converter')\nUncaught Error: Mismatched anonymous define() module: function (exports) { 'use strict'; ...\n",[36,567,565],{"__ignoreMap":283},[15,569,570,571,574,575,577,578,580,581,584],{},"原因在于：",[31,572,573],{},"Moodle 页面初始化期间，RequireJS 自身正在异步并发加载大量系统组件","。在 ",[36,576,63],{}," 异步下载的几百毫秒窗口期内，如果强行把全局的 ",[36,579,536],{}," 置空或者时机稍有交错，要么打崩 Moodle 自己的模块加载，要么在还原瞬间被 RequireJS 捕获到未配对的匿名 ",[36,582,583],{},"define()"," 调用。",[87,586,588,589,592,593,596],{"id":587},"最终解法fetch-new-function-显式走-commonjs-分支","最终解法：",[36,590,591],{},"fetch"," + ",[36,594,595],{},"new Function"," 显式走 CommonJS 分支",[15,598,599,600,602,603,605,606,608,609,612,613,615],{},"要想既不碰全局的 ",[36,601,536],{},"，又能把 ",[36,604,52],{}," 干净地导出来，最稳妥的路子是绕过 ",[36,607,63],{}," 标签，直接用 ",[36,610,611],{},"fetch()"," 把脚本源码当纯文本拉下来，再用 ",[36,614,595],{}," 构造一个局部的闭包作用域来执行它。",[15,617,618],{},"注意看上面 UMD 头部的第一行：",[277,620,622],{"className":303,"code":621,"language":305,"meta":283,"style":283},"typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : ...\n",[36,623,624],{"__ignoreMap":283},[309,625,626,629,631,633,635,637,639,641,643,645,647,649,651,653,655,657],{"class":311,"line":312},[309,627,628],{"class":319},"typeof",[309,630,345],{"class":344},[309,632,348],{"class":319},[309,634,352],{"class":351},[309,636,355],{"class":319},[309,638,358],{"class":319},[309,640,361],{"class":344},[309,642,364],{"class":319},[309,644,367],{"class":351},[309,646,370],{"class":319},[309,648,374],{"class":373},[309,650,316],{"class":315},[309,652,379],{"class":344},[309,654,382],{"class":315},[309,656,447],{"class":319},[309,658,659],{"class":319}," ...\n",[15,661,662,663,665,666,669,670,672,673,675,676,679,680,682,683,686,687,689,690,692],{},"它同时检查了 ",[36,664,379],{}," 和 ",[36,667,668],{},"module"," 两个变量（如果只传 ",[36,671,379],{}," 不传 ",[36,674,668],{},"，依然会落入后面的分支导致 ",[36,677,678],{},"exports.Converter is not a function","）。所以我们只需要给 ",[36,681,595],{}," 声明 ",[36,684,685],{},"'module', 'exports', 'define'"," 三个形参，把 ",[36,688,557],{}," 显式传为 ",[36,691,549],{},"，就能 100% 强制它走第一行的 CommonJS 分支：",[277,694,696],{"className":303,"code":695,"language":305,"meta":283,"style":283},"function loadOpenCC() {\n    if (window._OpenCC) return Promise.resolve(window._OpenCC);\n    return fetch('https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fopencc-js@1.0.5\u002Fdist\u002Fumd\u002Ffull.js')\n        .then(function(res) { return res.text(); })\n        .then(function(code) {\n            var module = { exports: {} };\n            var fn = new Function('module', 'exports', 'define', code);\n            fn(module, module.exports, undefined);\n            window._OpenCC = module.exports;\n            return window._OpenCC;\n        });\n}\n",[36,697,698,708,731,747,776,792,806,839,864,881,890,896],{"__ignoreMap":283},[309,699,700,702,705],{"class":311,"line":312},[309,701,320],{"class":319},[309,703,704],{"class":373}," loadOpenCC",[309,706,707],{"class":315},"() {\n",[309,709,710,713,716,719,722,725,728],{"class":311,"line":338},[309,711,712],{"class":319},"    if",[309,714,715],{"class":315}," (window._OpenCC) ",[309,717,718],{"class":319},"return",[309,720,721],{"class":344}," Promise",[309,723,724],{"class":315},".",[309,726,727],{"class":373},"resolve",[309,729,730],{"class":315},"(window._OpenCC);\n",[309,732,733,736,739,741,744],{"class":311,"line":388},[309,734,735],{"class":319},"    return",[309,737,738],{"class":373}," fetch",[309,740,316],{"class":315},[309,742,743],{"class":351},"'https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fopencc-js@1.0.5\u002Fdist\u002Fumd\u002Ffull.js'",[309,745,746],{"class":315},")\n",[309,748,749,752,755,757,759,761,764,766,768,771,773],{"class":311,"line":424},[309,750,751],{"class":315},"        .",[309,753,754],{"class":373},"then",[309,756,316],{"class":315},[309,758,320],{"class":319},[309,760,316],{"class":315},[309,762,763],{"class":326},"res",[309,765,487],{"class":315},[309,767,718],{"class":319},[309,769,770],{"class":315}," res.",[309,772,282],{"class":373},[309,774,775],{"class":315},"(); })\n",[309,777,778,780,782,784,786,788,790],{"class":311,"line":469},[309,779,751],{"class":315},[309,781,754],{"class":373},[309,783,316],{"class":315},[309,785,320],{"class":319},[309,787,316],{"class":315},[309,789,36],{"class":326},[309,791,335],{"class":315},[309,793,795,798,801,803],{"class":311,"line":794},6,[309,796,797],{"class":319},"            var",[309,799,800],{"class":315}," module ",[309,802,430],{"class":319},[309,804,805],{"class":315}," { exports: {} };\n",[309,807,809,811,814,816,819,822,824,827,829,831,833,836],{"class":311,"line":808},7,[309,810,797],{"class":319},[309,812,813],{"class":315}," fn ",[309,815,430],{"class":319},[309,817,818],{"class":319}," new",[309,820,821],{"class":373}," Function",[309,823,316],{"class":315},[309,825,826],{"class":351},"'module'",[309,828,217],{"class":315},[309,830,416],{"class":351},[309,832,217],{"class":315},[309,834,835],{"class":351},"'define'",[309,837,838],{"class":315},", code);\n",[309,840,842,845,847,849,851,853,855,857,859,861],{"class":311,"line":841},8,[309,843,844],{"class":373},"            fn",[309,846,316],{"class":315},[309,848,668],{"class":344},[309,850,217],{"class":315},[309,852,668],{"class":344},[309,854,724],{"class":315},[309,856,379],{"class":344},[309,858,217],{"class":315},[309,860,549],{"class":344},[309,862,863],{"class":315},");\n",[309,865,867,870,872,874,876,878],{"class":311,"line":866},9,[309,868,869],{"class":315},"            window._OpenCC ",[309,871,430],{"class":319},[309,873,361],{"class":344},[309,875,724],{"class":315},[309,877,379],{"class":344},[309,879,880],{"class":315},";\n",[309,882,884,887],{"class":311,"line":883},10,[309,885,886],{"class":319},"            return",[309,888,889],{"class":315}," window._OpenCC;\n",[309,891,893],{"class":311,"line":892},11,[309,894,895],{"class":315},"        });\n",[309,897,899],{"class":311,"line":898},12,[309,900,901],{"class":315},"}\n",[15,903,904],{},"换上这段加载逻辑后，再次刷新 Moodle 页面，控制台 RequireJS 报错彻底清零，转换器稳定就绪。",[77,906],{},[10,908,910],{"id":909},"_4-完整复现步骤","4. 完整复现步骤",[87,912,914],{"id":913},"step-1在-moodle-后台安装繁体语言包","Step 1：在 Moodle 后台安装繁体语言包",[25,916,917,924,935],{},[28,918,919,920,923],{},"用管理员账号登录 Moodle，进入 ",[31,921,922],{},"网站管理（Site administration）→ 语言（Language）→ 语言包（Language packs）","。",[28,925,926,927,930,931,934],{},"在右侧可用列表中找到 ",[31,928,929],{},"「繁體中文 (zh_tw)」","（如果需要兼顾香港学员，可一并安装 ",[36,932,933],{},"zh_hk","），点击安装。",[28,936,937],{},"安装完成后，网站顶栏会出现语言切换菜单。",[87,939,941],{"id":940},"step-2在额外-html注入转换脚本","Step 2：在「额外 HTML」注入转换脚本",[25,943,944,954,964],{},[28,945,946,947,950,951],{},"进入 ",[31,948,949],{},"网站管理（Site administration）→ 外观（Appearance）→ 额外 HTML（Additional HTML）","，或者直接访问后台路径：\n",[36,952,953],{},"\u002Fadmin\u002Fsettings.php?section=additionalhtml",[28,955,956,957,960,961,121],{},"向下滚动找到第三个输入框：",[31,958,959],{},"「在 BODY 闭合标签前（Before BODY is closed）」","（对应表单字段 ",[36,962,963],{},"#id_s__additionalhtmlfooter",[28,965,966,969],{},[31,967,968],{},"注意保留该输入框内原有的代码","（例如很多站点在这里放了 Google Analytics \u002F Tag Manager 统计代码，不要直接覆盖），在原有代码下方追加以下完整脚本：",[277,971,975],{"className":972,"code":973,"language":974,"meta":283,"style":283},"language-html shiki shiki-themes github-dark","\u003C!-- OpenCC 繁简智能转换（Moodle RequireJS 隔离版） -->\n\u003Cscript>\n(function() {\n    \u002F\u002F 1. 仅当用户处于繁体语言环境 (zh_tw \u002F zh_hk) 时才执行\n    var isTraditional = document.body.classList.contains('lang-zh_tw') || \n                        document.body.classList.contains('lang-zh_hk');\n    if (!isTraditional) return;\n\n    \u002F\u002F 2. 使用 fetch + new Function 隔离加载，避免触发 Moodle RequireJS 的 define 冲突\n    function loadOpenCC() {\n        if (window._OpenCC) return Promise.resolve(window._OpenCC);\n        return fetch('https:\u002F\u002Fcdn.jsdelivr.net\u002Fnpm\u002Fopencc-js@1.0.5\u002Fdist\u002Fumd\u002Ffull.js')\n            .then(function(res) { return res.text(); })\n            .then(function(code) {\n                var module = { exports: {} };\n                var fn = new Function('module', 'exports', 'define', code);\n                fn(module, module.exports, undefined);\n                window._OpenCC = module.exports;\n                return window._OpenCC;\n            });\n    }\n\n    loadOpenCC().then(function(OpenCC) {\n        \u002F\u002F cn -> twp：简体转台湾正体（含台湾地域习惯用词转换）\n        var converter = OpenCC.Converter({ from: 'cn', to: 'twp' });\n        var reverseConverter = OpenCC.Converter({ from: 'tw', to: 'cn' });\n\n        \u002F\u002F 3. 遍历并转换文本节点，跳过代码与输入控件\n        function convertElement(node) {\n            if (!node) return;\n            var walker = document.createTreeWalker(\n                node,\n                NodeFilter.SHOW_TEXT,\n                {\n                    acceptNode: function(textNode) {\n                        var p = textNode.parentNode;\n                        if (!p) return NodeFilter.FILTER_REJECT;\n                        var tag = p.tagName;\n                        if (['SCRIPT', 'STYLE', 'CODE', 'PRE', 'TEXTAREA'].indexOf(tag) !== -1) {\n                            return NodeFilter.FILTER_REJECT;\n                        }\n                        if (tag === 'INPUT' || p.isContentEditable) {\n                            return NodeFilter.FILTER_REJECT;\n                        }\n                        return NodeFilter.FILTER_ACCEPT;\n                    }\n                }\n            );\n            var curr;\n            while ((curr = walker.nextNode())) {\n                curr.nodeValue = converter(curr.nodeValue);\n            }\n        }\n\n        convertElement(document.body);\n        document.title = converter(document.title);\n        document.querySelectorAll('input[placeholder]').forEach(function(el) {\n            el.placeholder = converter(el.placeholder);\n        });\n\n        \u002F\u002F 4. 监听 Moodle 动态加载内容（折叠章节、侧边抽屉、AJAX 弹窗）\n        var debounceTimer = null;\n        var pendingNodes = [];\n        var observer = new MutationObserver(function(mutations) {\n            mutations.forEach(function(m) {\n                m.addedNodes.forEach(function(n) {\n                    if (n.nodeType === Node.ELEMENT_NODE) {\n                        pendingNodes.push(n);\n                    }\n                });\n            });\n            clearTimeout(debounceTimer);\n            debounceTimer = setTimeout(function() {\n                while (pendingNodes.length > 0) {\n                    convertElement(pendingNodes.shift());\n                }\n            }, 60);\n        });\n        observer.observe(document.body, { childList: true, subtree: true });\n\n        \u002F\u002F 5. 表单提交前将输入框内的繁体转回简体\n        document.addEventListener('submit', function(e) {\n            e.target.querySelectorAll('input[type=\"text\"], textarea').forEach(function(input) {\n                if (input.value) {\n                    input.value = reverseConverter(input.value);\n                }\n            });\n        }, true);\n    }).catch(function(err) {\n        console.error('[OpenCC] Load error:', err);\n    });\n})();\n\u003C\u002Fscript>\n","html",[36,976,977,983,995,1003,1008,1036,1050,1066,1072,1077,1086,1103,1116,1142,1159,1171,1198,1222,1238,1246,1252,1258,1263,1285,1291,1323,1348,1353,1359,1375,1392,1411,1417,1429,1435,1453,1467,1490,1503,1552,1564,1570,1589,1600,1605,1618,1624,1630,1636,1644,1664,1678,1684,1690,1695,1704,1717,1748,1761,1766,1771,1777,1792,1805,1831,1850,1869,1888,1900,1905,1911,1916,1925,1942,1962,1977,1982,1993,1998,2020,2025,2031,2055,2083,2092,2106,2111,2116,2126,2146,2163,2169,2175],{"__ignoreMap":283},[309,978,979],{"class":311,"line":312},[309,980,982],{"class":981},"sAwPA","\u003C!-- OpenCC 繁简智能转换（Moodle RequireJS 隔离版） -->\n",[309,984,985,988,992],{"class":311,"line":338},[309,986,987],{"class":315},"\u003C",[309,989,991],{"class":990},"s4JwU","script",[309,993,994],{"class":315},">\n",[309,996,997,999,1001],{"class":311,"line":388},[309,998,316],{"class":315},[309,1000,320],{"class":319},[309,1002,707],{"class":315},[309,1004,1005],{"class":311,"line":424},[309,1006,1007],{"class":981},"    \u002F\u002F 1. 仅当用户处于繁体语言环境 (zh_tw \u002F zh_hk) 时才执行\n",[309,1009,1010,1013,1016,1018,1021,1024,1026,1029,1031,1033],{"class":311,"line":469},[309,1011,1012],{"class":319},"    var",[309,1014,1015],{"class":315}," isTraditional ",[309,1017,430],{"class":319},[309,1019,1020],{"class":315}," document.body.classList.",[309,1022,1023],{"class":373},"contains",[309,1025,316],{"class":315},[309,1027,1028],{"class":351},"'lang-zh_tw'",[309,1030,382],{"class":315},[309,1032,453],{"class":319},[309,1034,1035],{"class":315}," \n",[309,1037,1038,1041,1043,1045,1048],{"class":311,"line":794},[309,1039,1040],{"class":315},"                        document.body.classList.",[309,1042,1023],{"class":373},[309,1044,316],{"class":315},[309,1046,1047],{"class":351},"'lang-zh_hk'",[309,1049,863],{"class":315},[309,1051,1052,1054,1056,1059,1062,1064],{"class":311,"line":808},[309,1053,712],{"class":319},[309,1055,323],{"class":315},[309,1057,1058],{"class":319},"!",[309,1060,1061],{"class":315},"isTraditional) ",[309,1063,718],{"class":319},[309,1065,880],{"class":315},[309,1067,1068],{"class":311,"line":841},[309,1069,1071],{"emptyLinePlaceholder":1070},true,"\n",[309,1073,1074],{"class":311,"line":866},[309,1075,1076],{"class":981},"    \u002F\u002F 2. 使用 fetch + new Function 隔离加载，避免触发 Moodle RequireJS 的 define 冲突\n",[309,1078,1079,1082,1084],{"class":311,"line":883},[309,1080,1081],{"class":319},"    function",[309,1083,704],{"class":373},[309,1085,707],{"class":315},[309,1087,1088,1091,1093,1095,1097,1099,1101],{"class":311,"line":892},[309,1089,1090],{"class":319},"        if",[309,1092,715],{"class":315},[309,1094,718],{"class":319},[309,1096,721],{"class":344},[309,1098,724],{"class":315},[309,1100,727],{"class":373},[309,1102,730],{"class":315},[309,1104,1105,1108,1110,1112,1114],{"class":311,"line":898},[309,1106,1107],{"class":319},"        return",[309,1109,738],{"class":373},[309,1111,316],{"class":315},[309,1113,743],{"class":351},[309,1115,746],{"class":315},[309,1117,1119,1122,1124,1126,1128,1130,1132,1134,1136,1138,1140],{"class":311,"line":1118},13,[309,1120,1121],{"class":315},"            .",[309,1123,754],{"class":373},[309,1125,316],{"class":315},[309,1127,320],{"class":319},[309,1129,316],{"class":315},[309,1131,763],{"class":326},[309,1133,487],{"class":315},[309,1135,718],{"class":319},[309,1137,770],{"class":315},[309,1139,282],{"class":373},[309,1141,775],{"class":315},[309,1143,1145,1147,1149,1151,1153,1155,1157],{"class":311,"line":1144},14,[309,1146,1121],{"class":315},[309,1148,754],{"class":373},[309,1150,316],{"class":315},[309,1152,320],{"class":319},[309,1154,316],{"class":315},[309,1156,36],{"class":326},[309,1158,335],{"class":315},[309,1160,1162,1165,1167,1169],{"class":311,"line":1161},15,[309,1163,1164],{"class":319},"                var",[309,1166,800],{"class":315},[309,1168,430],{"class":319},[309,1170,805],{"class":315},[309,1172,1174,1176,1178,1180,1182,1184,1186,1188,1190,1192,1194,1196],{"class":311,"line":1173},16,[309,1175,1164],{"class":319},[309,1177,813],{"class":315},[309,1179,430],{"class":319},[309,1181,818],{"class":319},[309,1183,821],{"class":373},[309,1185,316],{"class":315},[309,1187,826],{"class":351},[309,1189,217],{"class":315},[309,1191,416],{"class":351},[309,1193,217],{"class":315},[309,1195,835],{"class":351},[309,1197,838],{"class":315},[309,1199,1201,1204,1206,1208,1210,1212,1214,1216,1218,1220],{"class":311,"line":1200},17,[309,1202,1203],{"class":373},"                fn",[309,1205,316],{"class":315},[309,1207,668],{"class":344},[309,1209,217],{"class":315},[309,1211,668],{"class":344},[309,1213,724],{"class":315},[309,1215,379],{"class":344},[309,1217,217],{"class":315},[309,1219,549],{"class":344},[309,1221,863],{"class":315},[309,1223,1225,1228,1230,1232,1234,1236],{"class":311,"line":1224},18,[309,1226,1227],{"class":315},"                window._OpenCC ",[309,1229,430],{"class":319},[309,1231,361],{"class":344},[309,1233,724],{"class":315},[309,1235,379],{"class":344},[309,1237,880],{"class":315},[309,1239,1241,1244],{"class":311,"line":1240},19,[309,1242,1243],{"class":319},"                return",[309,1245,889],{"class":315},[309,1247,1249],{"class":311,"line":1248},20,[309,1250,1251],{"class":315},"            });\n",[309,1253,1255],{"class":311,"line":1254},21,[309,1256,1257],{"class":315},"    }\n",[309,1259,1261],{"class":311,"line":1260},22,[309,1262,1071],{"emptyLinePlaceholder":1070},[309,1264,1266,1269,1272,1274,1276,1278,1280,1283],{"class":311,"line":1265},23,[309,1267,1268],{"class":373},"    loadOpenCC",[309,1270,1271],{"class":315},"().",[309,1273,754],{"class":373},[309,1275,316],{"class":315},[309,1277,320],{"class":319},[309,1279,316],{"class":315},[309,1281,1282],{"class":326},"OpenCC",[309,1284,335],{"class":315},[309,1286,1288],{"class":311,"line":1287},24,[309,1289,1290],{"class":981},"        \u002F\u002F cn -> twp：简体转台湾正体（含台湾地域习惯用词转换）\n",[309,1292,1294,1297,1300,1302,1305,1308,1311,1314,1317,1320],{"class":311,"line":1293},25,[309,1295,1296],{"class":319},"        var",[309,1298,1299],{"class":315}," converter ",[309,1301,430],{"class":319},[309,1303,1304],{"class":315}," OpenCC.",[309,1306,1307],{"class":373},"Converter",[309,1309,1310],{"class":315},"({ from: ",[309,1312,1313],{"class":351},"'cn'",[309,1315,1316],{"class":315},", to: ",[309,1318,1319],{"class":351},"'twp'",[309,1321,1322],{"class":315}," });\n",[309,1324,1326,1328,1331,1333,1335,1337,1339,1342,1344,1346],{"class":311,"line":1325},26,[309,1327,1296],{"class":319},[309,1329,1330],{"class":315}," reverseConverter ",[309,1332,430],{"class":319},[309,1334,1304],{"class":315},[309,1336,1307],{"class":373},[309,1338,1310],{"class":315},[309,1340,1341],{"class":351},"'tw'",[309,1343,1316],{"class":315},[309,1345,1313],{"class":351},[309,1347,1322],{"class":315},[309,1349,1351],{"class":311,"line":1350},27,[309,1352,1071],{"emptyLinePlaceholder":1070},[309,1354,1356],{"class":311,"line":1355},28,[309,1357,1358],{"class":981},"        \u002F\u002F 3. 遍历并转换文本节点，跳过代码与输入控件\n",[309,1360,1362,1365,1368,1370,1373],{"class":311,"line":1361},29,[309,1363,1364],{"class":319},"        function",[309,1366,1367],{"class":373}," convertElement",[309,1369,316],{"class":315},[309,1371,1372],{"class":326},"node",[309,1374,335],{"class":315},[309,1376,1378,1381,1383,1385,1388,1390],{"class":311,"line":1377},30,[309,1379,1380],{"class":319},"            if",[309,1382,323],{"class":315},[309,1384,1058],{"class":319},[309,1386,1387],{"class":315},"node) ",[309,1389,718],{"class":319},[309,1391,880],{"class":315},[309,1393,1395,1397,1400,1402,1405,1408],{"class":311,"line":1394},31,[309,1396,797],{"class":319},[309,1398,1399],{"class":315}," walker ",[309,1401,430],{"class":319},[309,1403,1404],{"class":315}," document.",[309,1406,1407],{"class":373},"createTreeWalker",[309,1409,1410],{"class":315},"(\n",[309,1412,1414],{"class":311,"line":1413},32,[309,1415,1416],{"class":315},"                node,\n",[309,1418,1420,1423,1426],{"class":311,"line":1419},33,[309,1421,1422],{"class":315},"                NodeFilter.",[309,1424,1425],{"class":344},"SHOW_TEXT",[309,1427,1428],{"class":315},",\n",[309,1430,1432],{"class":311,"line":1431},34,[309,1433,1434],{"class":315},"                {\n",[309,1436,1438,1441,1444,1446,1448,1451],{"class":311,"line":1437},35,[309,1439,1440],{"class":373},"                    acceptNode",[309,1442,1443],{"class":315},": ",[309,1445,320],{"class":319},[309,1447,316],{"class":315},[309,1449,1450],{"class":326},"textNode",[309,1452,335],{"class":315},[309,1454,1456,1459,1462,1464],{"class":311,"line":1455},36,[309,1457,1458],{"class":319},"                        var",[309,1460,1461],{"class":315}," p ",[309,1463,430],{"class":319},[309,1465,1466],{"class":315}," textNode.parentNode;\n",[309,1468,1470,1473,1475,1477,1480,1482,1485,1488],{"class":311,"line":1469},37,[309,1471,1472],{"class":319},"                        if",[309,1474,323],{"class":315},[309,1476,1058],{"class":319},[309,1478,1479],{"class":315},"p) ",[309,1481,718],{"class":319},[309,1483,1484],{"class":315}," NodeFilter.",[309,1486,1487],{"class":344},"FILTER_REJECT",[309,1489,880],{"class":315},[309,1491,1493,1495,1498,1500],{"class":311,"line":1492},38,[309,1494,1458],{"class":319},[309,1496,1497],{"class":315}," tag ",[309,1499,430],{"class":319},[309,1501,1502],{"class":315}," p.tagName;\n",[309,1504,1506,1508,1511,1514,1516,1519,1521,1524,1526,1529,1531,1534,1537,1540,1543,1545,1548,1550],{"class":311,"line":1505},39,[309,1507,1472],{"class":319},[309,1509,1510],{"class":315}," ([",[309,1512,1513],{"class":351},"'SCRIPT'",[309,1515,217],{"class":315},[309,1517,1518],{"class":351},"'STYLE'",[309,1520,217],{"class":315},[309,1522,1523],{"class":351},"'CODE'",[309,1525,217],{"class":315},[309,1527,1528],{"class":351},"'PRE'",[309,1530,217],{"class":315},[309,1532,1533],{"class":351},"'TEXTAREA'",[309,1535,1536],{"class":315},"].",[309,1538,1539],{"class":373},"indexOf",[309,1541,1542],{"class":315},"(tag) ",[309,1544,438],{"class":319},[309,1546,1547],{"class":319}," -",[309,1549,216],{"class":344},[309,1551,335],{"class":315},[309,1553,1555,1558,1560,1562],{"class":311,"line":1554},40,[309,1556,1557],{"class":319},"                            return",[309,1559,1484],{"class":315},[309,1561,1487],{"class":344},[309,1563,880],{"class":315},[309,1565,1567],{"class":311,"line":1566},41,[309,1568,1569],{"class":315},"                        }\n",[309,1571,1573,1575,1578,1580,1583,1586],{"class":311,"line":1572},42,[309,1574,1472],{"class":319},[309,1576,1577],{"class":315}," (tag ",[309,1579,396],{"class":319},[309,1581,1582],{"class":351}," 'INPUT'",[309,1584,1585],{"class":319}," ||",[309,1587,1588],{"class":315}," p.isContentEditable) {\n",[309,1590,1592,1594,1596,1598],{"class":311,"line":1591},43,[309,1593,1557],{"class":319},[309,1595,1484],{"class":315},[309,1597,1487],{"class":344},[309,1599,880],{"class":315},[309,1601,1603],{"class":311,"line":1602},44,[309,1604,1569],{"class":315},[309,1606,1608,1611,1613,1616],{"class":311,"line":1607},45,[309,1609,1610],{"class":319},"                        return",[309,1612,1484],{"class":315},[309,1614,1615],{"class":344},"FILTER_ACCEPT",[309,1617,880],{"class":315},[309,1619,1621],{"class":311,"line":1620},46,[309,1622,1623],{"class":315},"                    }\n",[309,1625,1627],{"class":311,"line":1626},47,[309,1628,1629],{"class":315},"                }\n",[309,1631,1633],{"class":311,"line":1632},48,[309,1634,1635],{"class":315},"            );\n",[309,1637,1639,1641],{"class":311,"line":1638},49,[309,1640,797],{"class":319},[309,1642,1643],{"class":315}," curr;\n",[309,1645,1647,1650,1653,1655,1658,1661],{"class":311,"line":1646},50,[309,1648,1649],{"class":319},"            while",[309,1651,1652],{"class":315}," ((curr ",[309,1654,430],{"class":319},[309,1656,1657],{"class":315}," walker.",[309,1659,1660],{"class":373},"nextNode",[309,1662,1663],{"class":315},"())) {\n",[309,1665,1667,1670,1672,1675],{"class":311,"line":1666},51,[309,1668,1669],{"class":315},"                curr.nodeValue ",[309,1671,430],{"class":319},[309,1673,1674],{"class":373}," converter",[309,1676,1677],{"class":315},"(curr.nodeValue);\n",[309,1679,1681],{"class":311,"line":1680},52,[309,1682,1683],{"class":315},"            }\n",[309,1685,1687],{"class":311,"line":1686},53,[309,1688,1689],{"class":315},"        }\n",[309,1691,1693],{"class":311,"line":1692},54,[309,1694,1071],{"emptyLinePlaceholder":1070},[309,1696,1698,1701],{"class":311,"line":1697},55,[309,1699,1700],{"class":373},"        convertElement",[309,1702,1703],{"class":315},"(document.body);\n",[309,1705,1707,1710,1712,1714],{"class":311,"line":1706},56,[309,1708,1709],{"class":315},"        document.title ",[309,1711,430],{"class":319},[309,1713,1674],{"class":373},[309,1715,1716],{"class":315},"(document.title);\n",[309,1718,1720,1723,1726,1728,1731,1734,1737,1739,1741,1743,1746],{"class":311,"line":1719},57,[309,1721,1722],{"class":315},"        document.",[309,1724,1725],{"class":373},"querySelectorAll",[309,1727,316],{"class":315},[309,1729,1730],{"class":351},"'input[placeholder]'",[309,1732,1733],{"class":315},").",[309,1735,1736],{"class":373},"forEach",[309,1738,316],{"class":315},[309,1740,320],{"class":319},[309,1742,316],{"class":315},[309,1744,1745],{"class":326},"el",[309,1747,335],{"class":315},[309,1749,1751,1754,1756,1758],{"class":311,"line":1750},58,[309,1752,1753],{"class":315},"            el.placeholder ",[309,1755,430],{"class":319},[309,1757,1674],{"class":373},[309,1759,1760],{"class":315},"(el.placeholder);\n",[309,1762,1764],{"class":311,"line":1763},59,[309,1765,895],{"class":315},[309,1767,1769],{"class":311,"line":1768},60,[309,1770,1071],{"emptyLinePlaceholder":1070},[309,1772,1774],{"class":311,"line":1773},61,[309,1775,1776],{"class":981},"        \u002F\u002F 4. 监听 Moodle 动态加载内容（折叠章节、侧边抽屉、AJAX 弹窗）\n",[309,1778,1780,1782,1785,1787,1790],{"class":311,"line":1779},62,[309,1781,1296],{"class":319},[309,1783,1784],{"class":315}," debounceTimer ",[309,1786,430],{"class":319},[309,1788,1789],{"class":344}," null",[309,1791,880],{"class":315},[309,1793,1795,1797,1800,1802],{"class":311,"line":1794},63,[309,1796,1296],{"class":319},[309,1798,1799],{"class":315}," pendingNodes ",[309,1801,430],{"class":319},[309,1803,1804],{"class":315}," [];\n",[309,1806,1808,1810,1813,1815,1817,1820,1822,1824,1826,1829],{"class":311,"line":1807},64,[309,1809,1296],{"class":319},[309,1811,1812],{"class":315}," observer ",[309,1814,430],{"class":319},[309,1816,818],{"class":319},[309,1818,1819],{"class":373}," MutationObserver",[309,1821,316],{"class":315},[309,1823,320],{"class":319},[309,1825,316],{"class":315},[309,1827,1828],{"class":326},"mutations",[309,1830,335],{"class":315},[309,1832,1834,1837,1839,1841,1843,1845,1848],{"class":311,"line":1833},65,[309,1835,1836],{"class":315},"            mutations.",[309,1838,1736],{"class":373},[309,1840,316],{"class":315},[309,1842,320],{"class":319},[309,1844,316],{"class":315},[309,1846,1847],{"class":326},"m",[309,1849,335],{"class":315},[309,1851,1853,1856,1858,1860,1862,1864,1867],{"class":311,"line":1852},66,[309,1854,1855],{"class":315},"                m.addedNodes.",[309,1857,1736],{"class":373},[309,1859,316],{"class":315},[309,1861,320],{"class":319},[309,1863,316],{"class":315},[309,1865,1866],{"class":326},"n",[309,1868,335],{"class":315},[309,1870,1872,1875,1878,1880,1883,1886],{"class":311,"line":1871},67,[309,1873,1874],{"class":319},"                    if",[309,1876,1877],{"class":315}," (n.nodeType ",[309,1879,396],{"class":319},[309,1881,1882],{"class":315}," Node.",[309,1884,1885],{"class":344},"ELEMENT_NODE",[309,1887,335],{"class":315},[309,1889,1891,1894,1897],{"class":311,"line":1890},68,[309,1892,1893],{"class":315},"                        pendingNodes.",[309,1895,1896],{"class":373},"push",[309,1898,1899],{"class":315},"(n);\n",[309,1901,1903],{"class":311,"line":1902},69,[309,1904,1623],{"class":315},[309,1906,1908],{"class":311,"line":1907},70,[309,1909,1910],{"class":315},"                });\n",[309,1912,1914],{"class":311,"line":1913},71,[309,1915,1251],{"class":315},[309,1917,1919,1922],{"class":311,"line":1918},72,[309,1920,1921],{"class":373},"            clearTimeout",[309,1923,1924],{"class":315},"(debounceTimer);\n",[309,1926,1928,1931,1933,1936,1938,1940],{"class":311,"line":1927},73,[309,1929,1930],{"class":315},"            debounceTimer ",[309,1932,430],{"class":319},[309,1934,1935],{"class":373}," setTimeout",[309,1937,316],{"class":315},[309,1939,320],{"class":319},[309,1941,707],{"class":315},[309,1943,1945,1948,1951,1954,1957,1960],{"class":311,"line":1944},74,[309,1946,1947],{"class":319},"                while",[309,1949,1950],{"class":315}," (pendingNodes.",[309,1952,1953],{"class":344},"length",[309,1955,1956],{"class":319}," >",[309,1958,1959],{"class":344}," 0",[309,1961,335],{"class":315},[309,1963,1965,1968,1971,1974],{"class":311,"line":1964},75,[309,1966,1967],{"class":373},"                    convertElement",[309,1969,1970],{"class":315},"(pendingNodes.",[309,1972,1973],{"class":373},"shift",[309,1975,1976],{"class":315},"());\n",[309,1978,1980],{"class":311,"line":1979},76,[309,1981,1629],{"class":315},[309,1983,1985,1988,1991],{"class":311,"line":1984},77,[309,1986,1987],{"class":315},"            }, ",[309,1989,1990],{"class":344},"60",[309,1992,863],{"class":315},[309,1994,1996],{"class":311,"line":1995},78,[309,1997,895],{"class":315},[309,1999,2001,2004,2007,2010,2013,2016,2018],{"class":311,"line":2000},79,[309,2002,2003],{"class":315},"        observer.",[309,2005,2006],{"class":373},"observe",[309,2008,2009],{"class":315},"(document.body, { childList: ",[309,2011,2012],{"class":344},"true",[309,2014,2015],{"class":315},", subtree: ",[309,2017,2012],{"class":344},[309,2019,1322],{"class":315},[309,2021,2023],{"class":311,"line":2022},80,[309,2024,1071],{"emptyLinePlaceholder":1070},[309,2026,2028],{"class":311,"line":2027},81,[309,2029,2030],{"class":981},"        \u002F\u002F 5. 表单提交前将输入框内的繁体转回简体\n",[309,2032,2034,2036,2039,2041,2044,2046,2048,2050,2053],{"class":311,"line":2033},82,[309,2035,1722],{"class":315},[309,2037,2038],{"class":373},"addEventListener",[309,2040,316],{"class":315},[309,2042,2043],{"class":351},"'submit'",[309,2045,217],{"class":315},[309,2047,320],{"class":319},[309,2049,316],{"class":315},[309,2051,2052],{"class":326},"e",[309,2054,335],{"class":315},[309,2056,2058,2061,2063,2065,2068,2070,2072,2074,2076,2078,2081],{"class":311,"line":2057},83,[309,2059,2060],{"class":315},"            e.target.",[309,2062,1725],{"class":373},[309,2064,316],{"class":315},[309,2066,2067],{"class":351},"'input[type=\"text\"], textarea'",[309,2069,1733],{"class":315},[309,2071,1736],{"class":373},[309,2073,316],{"class":315},[309,2075,320],{"class":319},[309,2077,316],{"class":315},[309,2079,2080],{"class":326},"input",[309,2082,335],{"class":315},[309,2084,2086,2089],{"class":311,"line":2085},84,[309,2087,2088],{"class":319},"                if",[309,2090,2091],{"class":315}," (input.value) {\n",[309,2093,2095,2098,2100,2103],{"class":311,"line":2094},85,[309,2096,2097],{"class":315},"                    input.value ",[309,2099,430],{"class":319},[309,2101,2102],{"class":373}," reverseConverter",[309,2104,2105],{"class":315},"(input.value);\n",[309,2107,2109],{"class":311,"line":2108},86,[309,2110,1629],{"class":315},[309,2112,2114],{"class":311,"line":2113},87,[309,2115,1251],{"class":315},[309,2117,2119,2122,2124],{"class":311,"line":2118},88,[309,2120,2121],{"class":315},"        }, ",[309,2123,2012],{"class":344},[309,2125,863],{"class":315},[309,2127,2129,2132,2135,2137,2139,2141,2144],{"class":311,"line":2128},89,[309,2130,2131],{"class":315},"    }).",[309,2133,2134],{"class":373},"catch",[309,2136,316],{"class":315},[309,2138,320],{"class":319},[309,2140,316],{"class":315},[309,2142,2143],{"class":326},"err",[309,2145,335],{"class":315},[309,2147,2149,2152,2155,2157,2160],{"class":311,"line":2148},90,[309,2150,2151],{"class":315},"        console.",[309,2153,2154],{"class":373},"error",[309,2156,316],{"class":315},[309,2158,2159],{"class":351},"'[OpenCC] Load error:'",[309,2161,2162],{"class":315},", err);\n",[309,2164,2166],{"class":311,"line":2165},91,[309,2167,2168],{"class":315},"    });\n",[309,2170,2172],{"class":311,"line":2171},92,[309,2173,2174],{"class":315},"})();\n",[309,2176,2178,2181,2183],{"class":311,"line":2177},93,[309,2179,2180],{"class":315},"\u003C\u002F",[309,2182,991],{"class":990},[309,2184,994],{"class":315},[25,2186,2187],{"start":424},[28,2188,2189,2190],{},"点击页面底部的「保存更改（Save changes）」。\n",[149,2191,2192],{},[28,2193,2194,2198,2199,2202,2203,2206,2207,923],{},[2195,2196,2197],"em",{},"顺带提一个小坑","：如果你是用浏览器自动化工具帮你在后台点保存，千万别直接写 ",[36,2200,2201],{},"document.querySelector('button[type=\"submit\"]').click()","，因为 Moodle 顶栏导航条里的全局搜索框按钮也是 ",[36,2204,2205],{},"button[type=\"submit\"]","，直接点会跳去全站搜索页；务必限定选择器为 ",[36,2208,2209],{},"form#adminsettings button[type=\"submit\"]",[87,2211,2213],{"id":2212},"step-3切换双语验证效果","Step 3：切换双语验证效果",[15,2215,2216],{},"保存后打开任意一门课程，在右上角语言菜单切换简繁进行对比：",[2218,2219,2220,2241],"table",{},[2221,2222,2223],"thead",{},[2224,2225,2226,2231,2236],"tr",{},[2227,2228,2230],"th",{"align":2229},"left","检查项",[2227,2232,2233,2234,212],{"align":2229},"简体环境（",[36,2235,127],{},[2227,2237,2238,2239,212],{"align":2229},"繁体环境（",[36,2240,131],{},[2242,2243,2244,2260,2278,2302,2327],"tbody",{},[2224,2245,2246,2252,2255],{},[2247,2248,2249],"td",{"align":2229},[31,2250,2251],{},"课程与章节标题",[2247,2253,2254],{"align":2229},"第一单元：为什么要学习……",[2247,2256,2257],{"align":2229},[31,2258,2259],{},"第一單元：為什麼要學習……",[2224,2261,2262,2267,2273],{},[2247,2263,2264],{"align":2229},[31,2265,2266],{},"资源类型标签",[2247,2268,2269,2270,212],{"align":2229},"课程指南（",[31,2271,2272],{},"文件",[2247,2274,2275],{"align":2229},[31,2276,2277],{},"課程指南（檔案）",[2224,2279,2280,2285,2294],{},[2247,2281,2282],{"align":2229},[31,2283,2284],{},"视听活动名称",[2247,2286,2287,2290,2291],{"align":2229},[31,2288,2289],{},"视频"," \u002F ",[31,2292,2293],{},"音频",[2247,2295,2296,2290,2299],{"align":2229},[31,2297,2298],{},"影片",[31,2300,2301],{},"音訊",[2224,2303,2304,2309,2319],{},[2247,2305,2306],{"align":2229},[31,2307,2308],{},"常用术语",[2247,2310,2311,2312,2315,2316],{"align":2229},"伙伴",[31,2313,2314],{},"项目"," \u002F 量身定",[31,2317,2318],{},"制",[2247,2320,2321,2315,2324],{"align":2229},[31,2322,2323],{},"夥伴專案",[31,2325,2326],{},"製",[2224,2328,2329,2334,2340],{},[2247,2330,2331],{"align":2229},[31,2332,2333],{},"控制台报错",[2247,2335,2336,2339],{"align":2229},[36,2337,2338],{},"0","（脚本提前退出，不发请求）",[2247,2341,2342,2344],{"align":2229},[36,2343,2338],{},"（无 RequireJS 冲突报错）",[15,2346,2347,2348,2351],{},"得益于 ",[36,2349,2350],{},"from: 'cn', to: 'twp'"," 地域词典模式，不仅字形完成了转换，「文件\u002F檔案」「视频\u002F影片」「音频\u002F音訊」这类两岸日常用词差异也一并自动对齐。",[77,2353],{},[10,2355,2357],{"id":2356},"_5-小结与适用边界","5. 小结与适用边界",[15,2359,2360,2361,2364,2365,2367],{},"这套「官方语言包 + ",[36,2362,2363],{},"Additional HTML"," 注入 ",[36,2366,52],{},"」的做法，适合绝大多数以简体中文为主、需要低成本兼顾港澳台学员阅读体验的 Moodle 站点：",[149,2369,2370,2376],{},[28,2371,2372,2375],{},[31,2373,2374],{},"何时最好用","：全站课程以图文网页、视频外链、客观测验题（单选、多选、判断、配对、下拉选词填空）为主，希望零数据库改动、5 分钟内上线生效。",[28,2377,2378,2381,2382],{},[31,2379,2380],{},"何时不要硬套","：\n",[25,2383,2384,2387],{},[28,2385,2386],{},"如果课件文字压在图片或扫描版 PDF 里，前端 DOM 替换无法生效。",[28,2388,2389,2390,2392,2393,143,2395,2397,2398,2400],{},"如果你的测验里包含大量需要手敲文字的填空题（",[36,2391,244],{}," 或内嵌 ",[36,2394,248],{},[36,2396,252],{},"），虽然上面脚本第 5 步加入了捕获阶段 ",[36,2399,256],{}," 事件自动繁转简，但本次我只验证了页面渲染，还没在真实考试交卷和 Moodle AJAX 自动保存草稿的链路中做过端到端测验，正式开考前建议先拿测试账号跑通一次交卷判分，或者直接在题库标准答案里补上繁体同义词。",[2402,2403,2404],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}",{"title":283,"searchDepth":338,"depth":338,"links":2406},[2407,2408,2414,2423,2428],{"id":12,"depth":338,"text":13},{"id":81,"depth":338,"text":82,"children":2409},[2410,2411,2413],{"id":89,"depth":388,"text":90},{"id":102,"depth":388,"text":2412},"2.2 判断简繁环境：别信 \u003Chtml lang>，要看 body.classList",{"id":189,"depth":388,"text":190},{"id":262,"depth":338,"text":2415,"children":2416},"3. 踩坑记录：被 RequireJS 劫持的 opencc-js",[2417,2419,2421],{"id":288,"depth":388,"text":2418},"第一轮排查：为什么 window.OpenCC 没挂载上？",{"id":532,"depth":388,"text":2420},"第二轮踩坑：临时置空 window.define 为什么在真实页面翻车？",{"id":587,"depth":388,"text":2422},"最终解法：fetch + new Function 显式走 CommonJS 分支",{"id":909,"depth":338,"text":910,"children":2424},[2425,2426,2427],{"id":913,"depth":388,"text":914},{"id":940,"depth":388,"text":941},{"id":2212,"depth":388,"text":2213},{"id":2356,"depth":338,"text":2357},"coder","2026-11-09","如果你使用 Moodle 做了一套简体中文 AI 课程，既不想忽略掉繁体中文用户，又不想再单独维护一套繁体中文课程，可以通过官方语言包加 OpenCC 前端注入实现免改库全站繁简转换。","md",null,{},"\u002Fzh\u002Fblog\u002Fmoodle-opencc-traditional-chinese",{"title":5,"description":2431},"moodle-opencc-traditional-chinese","publish","zh\u002Fblog\u002Fmoodle-opencc-traditional-chinese",[2441,1282,2442,2443,2444],"Moodle","RequireJS","Frontend","i18n","pFRLN1OvZjz5v0kI500a1wR_Gv39NyMcmGrqrZzw1Ko",[2433,2447],{"title":2448,"path":2449,"stem":2450,"slug":2451,"date":2452,"children":-1},"Mac 排错实录：为什么一个叫「自动代理发现」的开关，会让你的 Tailscale 频频超时？","\u002Fzh\u002Fblog\u002Fmacos-auto-proxy-discovery-tailscale","zh\u002Fblog\u002Fmacos-auto-proxy-discovery-tailscale","macos-auto-proxy-discovery-tailscale","2026-11-08",1794217078105]