next-translate-scanner
Version:
Scan next-translate code for translations and update json files.
162 lines (161 loc) • 7.25 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const javascript_lexer_1 = __importDefault(require("./javascript-lexer"));
const ts = __importStar(require("typescript"));
const logger_1 = __importDefault(require("../utils/logger"));
const spread_options_to_entry_1 = __importDefault(require("../utils/spread-options-to-entry"));
const extract_namespace_from_key_1 = __importDefault(require("../utils/extract-namespace-from-key"));
class JsxLexer extends javascript_lexer_1.default {
constructor(options = {}) {
super(options);
}
extract(content, filename = '__default.jsx') {
const keys = [];
const parseCommentNode = this.createCommentNodeParser();
const parseTree = (node) => {
let entry = null;
parseCommentNode(keys, node, content);
switch (node.kind) {
case ts.SyntaxKind.CallExpression:
entry = this.expressionExtractor(node);
break;
case ts.SyntaxKind.TaggedTemplateExpression:
entry = this.taggedTemplateExpressionExtractor(node);
break;
case ts.SyntaxKind.JsxElement:
case ts.SyntaxKind.JsxSelfClosingElement:
entry = this.jsxExtractor(node);
break;
}
if (entry) {
keys.push(entry);
}
node.forEachChild(parseTree);
};
const sourceFile = ts.createSourceFile(filename, content, ts.ScriptTarget.Latest);
parseTree(sourceFile);
return this.setNamespaces(keys);
}
jsxExtractor(node) {
const tagNode = node.openingElement || node;
const getPropValue = (node, tagName) => {
const attribute = node.attributes.properties.find((attr) => attr.name.text === tagName);
if (!attribute) {
return null;
}
return attribute.initializer.expression ? attribute.initializer.expression.text : attribute.initializer.text;
};
const getKey = (node) => getPropValue(node, 'i18nKey');
if (tagNode.tagName.text === 'Trans') {
const entry = {};
entry.key = getKey(tagNode);
const defaultValue = getPropValue(tagNode, 'defaultTrans');
if (defaultValue && defaultValue !== '') {
entry.default = defaultValue;
}
const ns = getPropValue(tagNode, 'ns');
if (ns) {
entry.ns = ns;
}
for (const property of tagNode.attributes.properties) {
if (property.initializer) {
if (property.name.text === 'values') {
try {
(0, spread_options_to_entry_1.default)(entry, property.initializer.expression, (p) => `{${p.name.text}}`);
}
catch (e) {
logger_1.default.warning(e.message);
}
}
}
}
(0, extract_namespace_from_key_1.default)(entry, this.nsSeparator);
return entry.key ? entry : null;
}
return null;
}
parseChildren(children, sourceText) {
return children?.map((child) => {
if (child.kind === ts.SyntaxKind.JsxText) {
return {
type: 'text',
content: child.text.replace(/(^(\n|\r)\s*)|((\n|\r)\s*$)/g, '').replace(/(\n|\r)\s*/g, ' ')
};
}
else if (child.kind === ts.SyntaxKind.JsxElement || child.kind === ts.SyntaxKind.JsxSelfClosingElement) {
const element = child.openingElement || child;
const name = element.tagName.escapedText;
const isBasic = !element.attributes.properties.length;
return {
type: 'tag',
children: this.parseChildren(child.children, sourceText),
name,
isBasic,
selfClosing: child.kind === ts.SyntaxKind.JsxSelfClosingElement
};
}
else if (child.kind === ts.SyntaxKind.JsxExpression) {
// strip empty expressions
if (!child.expression) {
return {
type: 'text',
content: ''
};
}
else if (child.expression.kind === ts.SyntaxKind.StringLiteral) {
return {
type: 'text',
content: child.expression.text
};
}
// strip properties from ObjectExpressions
// annoying (and who knows how many other exceptions we'll need to write) but necessary
else if (child.expression.kind === ts.SyntaxKind.ObjectLiteralExpression) {
let nonFormatProperties = child.expression.properties.filter((prop) => prop.name.text !== 'format');
if (nonFormatProperties.length > 1) {
logger_1.default.warning(`The passed in object contained more than one variable - the object should look like {{ value, format }} where format is optional.`);
return {
type: 'text',
content: ''
};
}
return {
type: 'js',
content: `{{${nonFormatProperties[0].name.text}}}`
};
}
// slice on the expression so that we ignore comments around it
return {
type: 'js',
content: `{${sourceText.slice(child.expression.pos, child.expression.end)}}`
};
}
else {
throw new Error('Unknown ast element when parsing jsx: ' + child.kind);
}
}).filter((child) => child.type !== 'text' || child.content);
}
}
exports.default = JsxLexer;