@awesome-fe/translate
Version:
Translation utils
324 lines • 13.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.markdown = void 0;
const unified = require("unified");
const custom_parser_plugin_1 = require("./remark-plugins/custom-parser-plugin");
const custom_compiler_plugin_1 = require("./remark-plugins/custom-compiler-plugin");
const mast_to_hast_handlers_1 = require("./remark-plugins/mast-to-hast-handlers");
const hast_to_mast_handlers_1 = require("./remark-plugins/hast-to-mast-handlers");
const remarkParse = require("remark-parse");
const remarkStringify = require("remark-stringify");
const rehypeParse = require("rehype-parse");
const rehypeStringify = require("rehype-stringify");
const remarkRehype = require("remark-rehype");
const rehypeRemark = require("rehype-remark");
const frontmatter = require("remark-frontmatter");
const stringWidth = require("string-width");
const common_1 = require("../common");
const js_yaml_1 = require("js-yaml");
const lodash_1 = require("lodash");
var markdown;
(function (markdown_1) {
function isLiteral(node) {
return [
'html',
'code',
'yaml',
'text',
'inlineCode',
].includes(node.type);
}
markdown_1.isLiteral = isLiteral;
function isParent(node) {
return [
'root',
'paragraph',
'heading',
'blockquote',
'list',
'listItem',
'table',
'tableRow',
'tableCell',
'emphasis',
'strong',
'delete',
'footnote',
].includes(node.type);
}
markdown_1.isParent = isParent;
function isTranslatableUnit(node) {
return node.type === 'paragraph' || node.type === 'heading';
}
markdown_1.isTranslatableUnit = isTranslatableUnit;
function isLinkReference(node) {
return node.type === 'linkReference';
}
markdown_1.isLinkReference = isLinkReference;
function isLink(node) {
return node.type === 'link';
}
markdown_1.isLink = isLink;
function isTableRow(node) {
return node.type === 'tableRow';
}
markdown_1.isTableRow = isTableRow;
function isTableCell(node) {
return node.type === 'tableCell';
}
markdown_1.isTableCell = isTableCell;
function isTable(node) {
return node.type === 'table';
}
markdown_1.isTable = isTable;
function isTableFamily(node) {
return isTable(node) || isTableRow(node) || isTableCell(node);
}
markdown_1.isTableFamily = isTableFamily;
function isYaml(node) {
return node.type === 'yaml';
}
markdown_1.isYaml = isYaml;
function isListItem(node) {
return node.type === 'listItem';
}
markdown_1.isListItem = isListItem;
function parse(markdown) {
return unified().use(remarkParse)
.use(frontmatter)
.use(custom_parser_plugin_1.customParser)
.parse(markdown);
}
markdown_1.parse = parse;
function stringify(tree) {
if (!tree) {
return '';
}
tree.children = tree.children ?? [];
return unified().use(remarkStringify, stringifyOptions)
.use(frontmatter)
.use(custom_compiler_plugin_1.customCompiler)
.stringify(tree)
.replace(/ /g, ' ')
.replace(/[ \t]+$/g, '')
// 无法完美处理 list-item-visitor 出现两个空行或者少一个空行的问题,因此只好在这里做后期替换
.replace(/\n\n\n+/g, '\n\n')
.trim();
}
markdown_1.stringify = stringify;
function toHtml(textOrAst) {
let text;
if (typeof textOrAst === 'string') {
text = textOrAst;
}
else {
text = stringify(textOrAst).replace(/^<p>([\s\S]*?)<\/p>$/gi, '$1');
}
return unified().use(remarkParse)
.use(frontmatter)
.use(custom_parser_plugin_1.customParser)
.use(remarkRehype, { handlers: mast_to_hast_handlers_1.mastToHastHandlers })
.use(rehypeStringify, { closeSelfClosing: true })
.processSync(text).contents.toString();
}
markdown_1.toHtml = toHtml;
function fromHtml(html) {
return parse(mdFromHtml(html));
}
markdown_1.fromHtml = fromHtml;
function mdToHtml(md) {
return toHtml(md);
}
markdown_1.mdToHtml = mdToHtml;
function mdFromHtml(html) {
if (!html) {
return html;
}
return unified().use(rehypeParse)
.use(rehypeRemark, { handlers: hast_to_mast_handlers_1.hastToMastHandlers })
.use(remarkStringify, stringifyOptions)
.use(custom_compiler_plugin_1.customCompiler)
.processSync(html).contents.toString().trim();
}
markdown_1.mdFromHtml = mdFromHtml;
function normalize(text) {
return stringify(parse(text));
}
markdown_1.normalize = normalize;
function nodeContainsChinese(node) {
if (!node) {
return false;
}
if (isLiteral(node)) {
return (0, common_1.containsChinese)(node.value);
}
else if (isLink(node)) {
return (0, common_1.containsChinese)(node.title) || node.children.some(it => nodeContainsChinese(it));
}
else if (isLinkReference(node)) {
return (0, common_1.containsChinese)(node.label) || node.children.some(it => nodeContainsChinese(it));
}
else if (['htmlRaw', 'anchor'].includes(node.type)) {
return false;
}
else if (isParent(node)) {
return node.children.some(it => nodeContainsChinese(it));
}
else {
return (0, common_1.containsChinese)(contentOf(node));
}
}
markdown_1.nodeContainsChinese = nodeContainsChinese;
const stringifyOptions = {
emphasis: '*',
listItemIndent: 1,
incrementListMarker: false,
stringLength: stringWidth,
paddedTable: true,
fences: true,
entities: false,
};
function visit(node, parent, visitor) {
if (isYaml(node)) {
return handleFrontMatter(node, visitor);
}
else if (isTable(node)) {
return handleTable(node, visitor);
}
else if (isTranslatableUnit(node) && !nodeContainsChinese(node)) {
return handleParagraphAndHeadings(node, parent, visitor);
}
else if (isParent(node)) {
return Promise.all(node.children.map(it => visit(it, node, visitor))).then(() => node);
}
}
markdown_1.visit = visit;
function handleFrontMatter(yaml, visitor) {
const frontMatter = (0, js_yaml_1.safeLoad)(yaml.value) || {};
const entries = Object.entries(frontMatter);
const tasks = entries.map(([key, value]) => {
if (key.endsWith('$$origin')) {
// 忽略保存的原文
return;
}
if ((0, common_1.containsChinese)(value)) {
return visitor(frontMatter[`${key}$$origin`], value).then((result) => {
if (result && (0, common_1.containsChinese)(result) && result !== value) {
frontMatter[key] = result;
}
});
}
else {
return visitor(value, undefined).then((result) => {
if (result && (0, common_1.containsChinese)(result)) {
frontMatter[key] = result;
frontMatter[`${key}$$origin`] = value;
}
});
}
});
return Promise.all(tasks.filter(it => !!it)).then(() => {
yaml.value = (0, js_yaml_1.safeDump)(frontMatter);
return yaml;
});
}
function handleTable(table, visitor) {
const tasks = [];
// 倒序循环,以免插入的新节点影响循环本身
for (let rowIndex = table.children.length - 1; rowIndex >= 0; --rowIndex) {
const originalRow = table.children[rowIndex];
const translationRow = table.children[rowIndex + 1];
// 原文和译文按行进行对照,只需要处理原文行,译文行是被动处理的
if (!nodeContainsChinese(originalRow)) {
// 有译文行时要提取译文行,没有译文行时要插入译文行
if (nodeContainsChinese(translationRow)) {
console.assert(originalRow.children.length === translationRow.children.length);
for (let colIndex = 0; colIndex < originalRow.children.length; colIndex++) {
const originalCell = originalRow.children[colIndex];
const translationCell = translationRow.children[colIndex];
const originalText = stringify(originalCell);
const translationText = stringify(translationCell);
tasks.push(visitor(originalText, translationText).then((result) => {
if (result && (0, common_1.containsChinese)(result) && result !== translationText) {
translationCell.children = parseCellContent(result);
}
}));
}
}
else {
let translationRow;
for (let colIndex = 0; colIndex < originalRow.children.length; colIndex++) {
const originalCell = originalRow.children[colIndex];
const originalText = stringify(originalCell);
tasks.push(visitor(originalText, undefined).then((result) => {
if (result && (0, common_1.containsChinese)(result)) {
if (!translationRow) {
translationRow = (0, lodash_1.cloneDeep)(originalRow);
table.children.splice(rowIndex + 1, 0, translationRow);
}
const translationCell = translationRow.children[colIndex];
translationCell.children = parseCellContent(result);
}
}));
}
}
}
}
return Promise.all(tasks).then(() => table);
function parseCellContent(result) {
const paragraph = parse(result).children[0];
return paragraph.children;
}
}
function handleParagraphAndHeadings(originalNode, parent, visitor) {
// 我们要处理的所有节点都必须是 Parent,因为它至少也会包含一个 text 子节点
const index = parent.children.indexOf(originalNode);
const translationNode = parent.children[index + 1];
const originalText = contentOf(originalNode);
if (translationNode && originalNode.type === translationNode.type && nodeContainsChinese(translationNode)) {
const translationText = contentOf(translationNode);
return visitor(originalText, translationText).then((result) => {
if (!result || !(0, common_1.containsChinese)(result) || result === translationText) {
return translationNode;
}
applyTranslation(translationNode, result);
return translationNode;
});
}
else {
return visitor(originalText, undefined).then((result) => {
if (!result || !(0, common_1.containsChinese)(result)) {
return originalNode;
}
const translationNode = createAndInsertTranslation(parent, originalNode);
applyTranslation(translationNode, result);
return translationNode;
});
}
function applyTranslation(translationNode, result) {
if (isListItem(parent)) {
// 如果是 listItem 被翻译了,则需要扩展成阔表模式,以便容纳两个段落
parent.spread = true;
}
const translation = parse(result).children[0];
Object.assign(translationNode, translation, { type: originalNode.type });
}
function createAndInsertTranslation(parent, original) {
const node = (0, lodash_1.cloneDeep)(original);
const index = parent.children.indexOf(original);
parent.children.splice(index + 1, 0, node);
return node;
}
}
function contentOf(node) {
if (!node) {
return;
}
const cloned = (0, lodash_1.cloneDeep)(node);
if (cloned.type !== 'paragraph' && cloned.type !== 'heading') {
cloned.type = 'paragraph';
}
return stringify(cloned);
}
})(markdown = exports.markdown || (exports.markdown = {}));
//# sourceMappingURL=markdown.js.map