@awesome-fe/translate
Version:
Translation utils
331 lines • 13.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.markdown = void 0;
var unified = require("unified");
var custom_parser_plugin_1 = require("./remark-plugins/custom-parser-plugin");
var custom_compiler_plugin_1 = require("./remark-plugins/custom-compiler-plugin");
var mast_to_hast_handlers_1 = require("./remark-plugins/mast-to-hast-handlers");
var hast_to_mast_handlers_1 = require("./remark-plugins/hast-to-mast-handlers");
var remarkParse = require("remark-parse");
var remarkStringify = require("remark-stringify");
var rehypeParse = require("rehype-parse");
var rehypeStringify = require("rehype-stringify");
var remarkRehype = require("remark-rehype");
var rehypeRemark = require("rehype-remark");
var frontmatter = require("remark-frontmatter");
var stringWidth = require("string-width");
var common_1 = require("../common");
var js_yaml_1 = require("js-yaml");
var 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) {
var _a;
tree.children = (_a = tree.children) !== null && _a !== void 0 ? _a : [];
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) {
var 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);
}
else if (isLinkReference(node)) {
return (0, common_1.containsChinese)(node.label);
}
else if (['htmlRaw', 'anchor'].includes(node.type)) {
return false;
}
else if (isParent(node)) {
return node.children.some(function (it) { return nodeContainsChinese(it); });
}
else {
console.warn('nodeContainsChinese: unknown node type:', node.type);
return (0, common_1.containsChinese)(contentOf(node));
}
}
markdown_1.nodeContainsChinese = nodeContainsChinese;
var 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(function (it) { return visit(it, node, visitor); })).then(function () { return node; });
}
}
markdown_1.visit = visit;
function handleFrontMatter(yaml, visitor) {
var frontMatter = (0, js_yaml_1.safeLoad)(yaml.value) || {};
var entries = Object.entries(frontMatter);
var tasks = entries.map(function (_a) {
var key = _a[0], value = _a[1];
if (key.endsWith('$$origin')) {
// 忽略保存的原文
return;
}
if ((0, common_1.containsChinese)(value)) {
return visitor(frontMatter["".concat(key, "$$origin")], value).then(function (result) {
if (result && (0, common_1.containsChinese)(result) && result !== value) {
frontMatter[key] = result;
}
});
}
else {
return visitor(value, undefined).then(function (result) {
if (result && (0, common_1.containsChinese)(result)) {
frontMatter[key] = result;
frontMatter["".concat(key, "$$origin")] = value;
}
});
}
});
return Promise.all(tasks.filter(function (it) { return !!it; })).then(function () {
yaml.value = (0, js_yaml_1.safeDump)(frontMatter);
return yaml;
});
}
function handleTable(table, visitor) {
var tasks = [];
var _loop_1 = function (rowIndex) {
var originalRow = table.children[rowIndex];
var translationRow = table.children[rowIndex + 1];
// 原文和译文按行进行对照,只需要处理原文行,译文行是被动处理的
if (!nodeContainsChinese(originalRow)) {
// 有译文行时要提取译文行,没有译文行时要插入译文行
if (nodeContainsChinese(translationRow)) {
console.assert(originalRow.children.length === translationRow.children.length);
var _loop_2 = function (colIndex) {
var originalCell = originalRow.children[colIndex];
var translationCell = translationRow.children[colIndex];
var originalText = stringify(originalCell);
var translationText = stringify(translationCell);
tasks.push(visitor(originalText, translationText).then(function (result) {
if (result && (0, common_1.containsChinese)(result) && result !== translationText) {
translationCell.children = parseCellContent(result);
}
}));
};
for (var colIndex = 0; colIndex < originalRow.children.length; colIndex++) {
_loop_2(colIndex);
}
}
else {
var translationRow_1;
var _loop_3 = function (colIndex) {
var originalCell = originalRow.children[colIndex];
var originalText = stringify(originalCell);
tasks.push(visitor(originalText, undefined).then(function (result) {
if (result && (0, common_1.containsChinese)(result)) {
if (!translationRow_1) {
translationRow_1 = (0, lodash_1.cloneDeep)(originalRow);
table.children.splice(rowIndex + 1, 0, translationRow_1);
}
var translationCell = translationRow_1.children[colIndex];
translationCell.children = parseCellContent(result);
}
}));
};
for (var colIndex = 0; colIndex < originalRow.children.length; colIndex++) {
_loop_3(colIndex);
}
}
}
};
// 倒序循环,以免插入的新节点影响循环本身
for (var rowIndex = table.children.length - 1; rowIndex >= 0; --rowIndex) {
_loop_1(rowIndex);
}
return Promise.all(tasks).then(function () { return table; });
function parseCellContent(result) {
var paragraph = parse(result).children[0];
return paragraph.children;
}
}
function handleParagraphAndHeadings(originalNode, parent, visitor) {
// 我们要处理的所有节点都必须是 Parent,因为它至少也会包含一个 text 子节点
var index = parent.children.indexOf(originalNode);
var translationNode = parent.children[index + 1];
var originalText = contentOf(originalNode);
if (translationNode && originalNode.type === translationNode.type && nodeContainsChinese(translationNode)) {
var translationText_1 = contentOf(translationNode);
return visitor(originalText, translationText_1).then(function (result) {
if (!result || !(0, common_1.containsChinese)(result) || result === translationText_1) {
return translationNode;
}
applyTranslation(translationNode, result);
return translationNode;
});
}
else {
return visitor(originalText, undefined).then(function (result) {
if (!result || !(0, common_1.containsChinese)(result)) {
return originalNode;
}
var translationNode = createAndInsertTranslation(parent, originalNode);
applyTranslation(translationNode, result);
return translationNode;
});
}
function applyTranslation(translationNode, result) {
if (isListItem(parent)) {
// 如果是 listItem 被翻译了,则需要扩展成阔表模式,以便容纳两个段落
parent.spread = true;
}
var translation = parse(result).children[0];
Object.assign(translationNode, translation, { type: originalNode.type });
}
function createAndInsertTranslation(parent, original) {
var node = (0, lodash_1.cloneDeep)(original);
var index = parent.children.indexOf(original);
parent.children.splice(index + 1, 0, node);
return node;
}
}
function contentOf(node) {
if (!node) {
return;
}
var cloned = (0, lodash_1.cloneDeep)(node);
cloned.type = 'paragraph';
return stringify(cloned);
}
})(markdown = exports.markdown || (exports.markdown = {}));
//# sourceMappingURL=markdown.js.map