wuchale
Version:
Protobuf-like i18n from plain code
183 lines (182 loc) • 7.29 kB
JavaScript
// Shared logic between adapters for handling nested / mixed elements within elements / fragments
import { IndexTracker, Message } from "../adapters.js";
import { commentPrefix, nonWhitespaceText } from "./index.js";
export class MixedVisitor {
constructor(props) {
Object.assign(this, props);
}
separatelyVisitChildren = (props) => {
let hasTextChild = false;
let hasNonTextChild = false;
let heurStr = '';
let hasCommentDirectives = false;
for (const child of props.children) {
if (this.isText(child)) {
const strContent = this.getTextContent(child);
if (!strContent.trim()) {
continue;
}
hasTextChild = true;
heurStr += strContent;
}
else if (this.isComment(child)) {
if (this.getCommentData(child).trim().startsWith(commentPrefix)) {
hasCommentDirectives = true;
}
}
else if (!this.leaveInPlace(child)) {
hasNonTextChild = true;
heurStr += `#`;
}
}
heurStr = heurStr.trimEnd();
const msg = new Message(heurStr, this.fullHeuristicDetails({
scope: props.scope,
element: props.element,
attribute: props.attribute,
}));
const heurMsgType = this.checkHeuristic(msg);
if (heurMsgType) {
let hasCompoundText = hasTextChild && hasNonTextChild;
if (props.inCompoundText || hasCompoundText && !hasCommentDirectives) {
return [false, hasTextChild, hasCompoundText, heurMsgType, []];
}
}
// can't be extracted as one; visit each separately if markup
const msgs = [];
if (props.scope === 'markup') {
for (const child of props.children) {
msgs.push(...this.visitFunc(child, props.inCompoundText));
}
}
return [true, false, false, heurMsgType || 'message', msgs];
};
visit = (props) => {
if (props.children.length === 0) {
return [];
}
const [visitedSeparately, hasTextChild, hasCompoundText, heurMsgType, separateTxts] = this.separatelyVisitChildren(props);
if (visitedSeparately) {
return separateTxts;
}
let msgStr = '';
let iArg = 0;
let iTag = 0;
const lastChildEnd = this.getRange(props.children.slice(-1)[0]).end;
const childrenNestedRanges = [];
let hasTextDescendants = false;
const msgs = [];
const comments = [];
for (const child of props.children) {
if (this.isComment(child)) {
continue;
}
const chRange = this.getRange(child);
if (this.isText(child)) {
const [startWh, trimmed, endWh] = nonWhitespaceText(this.getTextContent(child));
const msgInfo = new Message(trimmed, this.fullHeuristicDetails({ scope: props.scope }), props.commentDirectives.context);
if (startWh && !msgStr.endsWith(' ')) {
msgStr += ' ';
}
if (!trimmed) { // whitespace
continue;
}
msgStr += msgInfo.msgStr;
if (endWh) {
msgStr += ' ';
}
this.mstr.remove(chRange.start, chRange.end);
continue;
}
if (this.isExpression(child)) {
msgs.push(...this.visitExpressionTag(child));
if (!hasCompoundText) {
continue;
}
const placeholder = `{${iArg}}`;
msgStr += placeholder;
comments.push(`placeholder ${placeholder}: ${this.mstr.original.slice(chRange.start + 1, chRange.end - 1)}`);
let moveStart = chRange.start;
if (iArg > 0) {
this.mstr.update(chRange.start, chRange.start + 1, ', ');
}
else {
moveStart++;
this.mstr.remove(chRange.start, chRange.start + 1);
}
this.mstr.move(moveStart, chRange.end - 1, lastChildEnd);
this.mstr.remove(chRange.end - 1, chRange.end);
iArg++;
continue;
}
if (this.leaveInPlace(child)) {
msgs.push(...this.visitFunc(child, this.canHaveChildren(child)));
continue;
}
// elements, components and other things as well
const canHaveChildren = this.canHaveChildren(child);
const childMsgs = this.visitFunc(child, canHaveChildren);
let nestedNeedsCtx = false;
let chTxt = '';
for (const msgInfo of childMsgs) {
if (canHaveChildren && msgInfo.details.scope === props.scope) {
chTxt += msgInfo.msgStr[0];
hasTextDescendants = true;
nestedNeedsCtx = true;
}
else { // attributes, blocks
msgs.push(msgInfo);
}
}
childrenNestedRanges.push([chRange.start, chRange.end, nestedNeedsCtx]);
if (canHaveChildren && chTxt) {
chTxt = `<${iTag}>${chTxt}</${iTag}>`;
}
else {
// childless elements and everything else
chTxt = `<${iTag}/>`;
}
iTag++;
msgStr += chTxt;
}
msgStr = msgStr.trim();
if (!msgStr) {
return msgs;
}
const msgInfo = new Message(msgStr, this.fullHeuristicDetails({ scope: props.scope }), props.commentDirectives.context);
msgInfo.type = heurMsgType;
msgInfo.comments = comments;
if (hasTextChild || hasTextDescendants) {
msgs.push(msgInfo);
}
else {
return msgs;
}
if ((props.useComponent ?? true) && props.scope === 'markup' && iArg > 0 || childrenNestedRanges.length > 0) {
this.wrapNested(msgInfo, iArg > 0, childrenNestedRanges, lastChildEnd);
}
else {
// no need for component use
let begin = '{';
let end = ')}';
if (props.inCompoundText) {
begin += `${this.vars().rtTransCtx}(${this.vars().nestCtx}`;
}
else {
begin += `${this.vars().rtTrans}(${this.index.get(msgInfo.toKey())}`;
}
if (iArg > 0) {
begin += ', [';
end = ']' + end;
}
if (props.scope === 'attribute' && `'"`.includes(this.mstr.original[lastChildEnd])) {
const firstChild = props.children[0];
this.mstr.remove(firstChild.start - 1, firstChild.start);
this.mstr.remove(lastChildEnd, lastChildEnd + 1);
}
this.mstr.appendLeft(lastChildEnd, begin);
this.mstr.appendRight(lastChildEnd, end);
}
return msgs;
};
}