RemarkJS制作的HTML/Markdown幻灯片单文件多语言方案咨询
单文件双语Remark幻灯片实现方案
以下方案均无需维护双份文件,兼容原生RemarkJS逻辑,比简单的|||行分割容错率更高、维护成本更低:
方案1:基于Remark原生类注释实现(优先推荐)
RemarkJS本身支持通过<!-- .class: 标识 -->的注释语法给内容块打标记,完全不需要自定义特殊分割符,也不会和正文内容冲突。
首先在#source文本域里按如下格式写Markdown内容,不同语言块前加对应语言标记即可:
<textarea id="source"> class: middle, center <!-- .lang: en --> # First slide My presentation about XYZ <!-- .lang: fr --> # Première diapositive Ma présentation à propos de XYZ --- <!-- .lang: en --> ## Second slide Hello! <!-- .lang: fr --> ## Seconde diapositive Bonjour! </textarea>
在初始化Remark之前加一段简单的过滤逻辑,只保留目标语言的内容块即可:
// 可从URL参数、localStorage读取用户选择的语言,这里默认英文 const TARGET_LANG = 'en'; const sourceNode = document.getElementById('source'); const rawMarkdown = sourceNode.value; const processedMarkdown = rawMarkdown.split('\n---\n').map(slideContent => { // 保留幻灯片本身的布局、样式配置(比如开头的class配置) const slideConfig = slideContent.split('<!-- .lang:')[0]; // 匹配当前幻灯片下所有带语言标记的内容块 const langBlockReg = /<!-- \.lang: (en|fr) -->\n([\s\S]*?)(?=(<!-- \.lang:|$))/g; const targetContent = [...slideContent.matchAll(langBlockReg)] .filter(matchRes => matchRes[1] === TARGET_LANG) .map(matchRes => matchRes[2].trim()) .join('\n\n'); return slideConfig + targetContent; }).join('\n---\n'); sourceNode.value = processedMarkdown; // 内容处理完成后再初始化幻灯片 remark.create();
这个方案的优势:
- 完全兼容Remark原生语法,不会和Markdown排版、幻灯片分页/布局标记冲突
- 后续新增语言只需要新增对应lang标记块,无需修改解析逻辑
- 搭配语言切换按钮可以实现无刷新切语言,切换时重新解析内容后调用幻灯片实例的reload方法即可,无需跳转页面
方案2:成对注释包裹方案(适合内容长度不对等的场景)
如果不同语言的内容长度差异大,不想逐块对齐拆分,可以用成对的HTML注释包裹对应语言的内容,容错率更高:
Markdown内容写法:
<!-- en:start --> # First slide My presentation about XYZ This is a longer paragraph that only exists in English version for explanation <!-- en:end --> <!-- fr:start --> # Première diapositive Ma présentation à propos de XYZ <!-- fr:end --> --- <!-- en:start --> ## Second slide Hello! <!-- en:end --> <!-- fr:start --> ## Seconde diapositive Bonjour! Voici un contenu plus long en français qui n'a pas d'équivalent anglais <!-- fr:end -->
解析逻辑更简单,两次正则替换就能完成:
const TARGET_LANG = 'en'; const sourceNode = document.getElementById('source'); let content = sourceNode.value; // 先删除所有非目标语言的内容块 ['en', 'fr'].filter(lang => lang !== TARGET_LANG).forEach(lang => { const blockReg = new RegExp(`<!-- ${lang}:start -->[\\s\\S]*?<!-- ${lang}:end -->`, 'g'); content = content.replace(blockReg, ''); }); // 清除目标语言的注释标记 content = content.replace(/<!-- (en|fr):start -->\s*/g, '').replace(/<!-- (en|fr):end -->\s*/g, ''); sourceNode.value = content; remark.create();
这个方案不需要对齐不同语言的内容结构,哪怕某一版内容多几行、少几个段落,都不会影响解析结果。
方案3:自定义宏标记方案(适合逐行对照翻译的场景)
如果你习惯写内容的时候左右对照翻译,也可以替换掉容易和正文冲突的|||分隔符,用几乎不会在正文出现的宏标记做包裹,避免误解析:
# {{en}}First slide{{/en}}{{fr}}Première diapositive{{/fr}} {{en}}My presentation about XYZ{{/en}}{{fr}}Ma présentation à propos de XYZ{{/fr}} --- ## {{en}}Second slide{{/en}}{{fr}}Seconde diapositive{{/fr}} {{en}}Hello!{{/en}}{{fr}}Bonjour!{{/fr}}
解析时直接正则删除非目标语言的包裹内容即可,比逐行按|||分割的逻辑更稳定,不会因为某一行漏写分隔符导致整页内容错乱。
避坑提示
- 不要用短符号、常用标点当语言分隔符,很容易和正文里的代码、公式、特殊标点冲突,导致解析错误
- 可以把用户选择的语言存在localStorage里,下次打开自动沿用上次的语言设置
- 做语言切换按钮时不需要刷新页面,重新替换文本域内容后调用幻灯片的reload方法就能完成切换
内容的提问来源于stack exchange,提问作者Basj
相关产品推荐
相关产品推荐

