310 lines
12 KiB
JavaScript
Executable File
310 lines
12 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
var sade = require('sade');
|
|
var glob = require('tiny-glob');
|
|
var compiler = require('svelte/compiler');
|
|
var estreeWalker = require('estree-walker');
|
|
|
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
|
|
function _interopNamespace(e) {
|
|
if (e && e.__esModule) return e;
|
|
var n = Object.create(null);
|
|
if (e) {
|
|
Object.keys(e).forEach(function (k) {
|
|
if (k !== 'default') {
|
|
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
Object.defineProperty(n, k, d.get ? d : {
|
|
enumerable: true,
|
|
get: function () {
|
|
return e[k];
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
n['default'] = e;
|
|
return Object.freeze(n);
|
|
}
|
|
|
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
var sade__default = /*#__PURE__*/_interopDefaultLegacy(sade);
|
|
var glob__default = /*#__PURE__*/_interopDefaultLegacy(glob);
|
|
|
|
/*! *****************************************************************************
|
|
Copyright (c) Microsoft Corporation.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
PERFORMANCE OF THIS SOFTWARE.
|
|
***************************************************************************** */
|
|
|
|
function __values(o) {
|
|
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
if (m) return m.call(o);
|
|
if (o && typeof o.length === "number") return {
|
|
next: function () {
|
|
if (o && i >= o.length) o = void 0;
|
|
return { value: o && o[i++], done: !o };
|
|
}
|
|
};
|
|
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
}
|
|
|
|
function __asyncValues(o) {
|
|
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
var m = o[Symbol.asyncIterator], i;
|
|
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
}
|
|
|
|
/* eslint-disable no-multi-assign */
|
|
/* eslint-disable no-return-assign */
|
|
const isNumberString = (n) => !Number.isNaN(parseInt(n, 10));
|
|
function deepSet(obj, path, value) {
|
|
const parts = path.replace(/\[(\w+)\]/gi, '.$1').split('.');
|
|
return parts.reduce((ref, part, i) => {
|
|
if (part in ref)
|
|
return (ref = ref[part]);
|
|
if (i < parts.length - 1) {
|
|
if (isNumberString(parts[i + 1])) {
|
|
return (ref = ref[part] = []);
|
|
}
|
|
return (ref = ref[part] = {});
|
|
}
|
|
return (ref[part] = value);
|
|
}, obj);
|
|
}
|
|
|
|
function getObjFromExpression(exprNode) {
|
|
return exprNode.properties.reduce((acc, prop) => {
|
|
// we only want primitives
|
|
if (prop.value.type === 'Literal' &&
|
|
prop.value.value !== Object(prop.value.value)) {
|
|
const key = prop.key.name;
|
|
acc[key] = prop.value.value;
|
|
}
|
|
return acc;
|
|
}, {});
|
|
}
|
|
|
|
function delve(obj, fullKey) {
|
|
if (fullKey in obj) {
|
|
return obj[fullKey];
|
|
}
|
|
const keys = fullKey.split('.');
|
|
let result = obj;
|
|
for (let p = 0; p < keys.length; p++) {
|
|
if (typeof result === 'object') {
|
|
if (p > 0) {
|
|
const partialKey = keys.slice(p, keys.length).join('.');
|
|
if (partialKey in result) {
|
|
result = result[partialKey];
|
|
break;
|
|
}
|
|
}
|
|
result = result[keys[p]];
|
|
}
|
|
else {
|
|
result = undefined;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const LIB_NAME = 'svelte-i18n';
|
|
const DEFINE_MESSAGES_METHOD_NAME = 'defineMessages';
|
|
const FORMAT_METHOD_NAMES = new Set(['format', '_', 't']);
|
|
function isFormatCall(node, imports) {
|
|
if (node.type !== 'CallExpression')
|
|
return false;
|
|
let identifier;
|
|
if (node.callee.type === 'Identifier') {
|
|
identifier = node.callee;
|
|
}
|
|
if (!identifier || identifier.type !== 'Identifier') {
|
|
return false;
|
|
}
|
|
const methodName = identifier.name.slice(1);
|
|
return imports.has(methodName);
|
|
}
|
|
function isMessagesDefinitionCall(node, methodName) {
|
|
if (node.type !== 'CallExpression')
|
|
return false;
|
|
return (node.callee &&
|
|
node.callee.type === 'Identifier' &&
|
|
node.callee.name === methodName);
|
|
}
|
|
function getLibImportDeclarations(ast) {
|
|
return (ast.instance
|
|
? ast.instance.content.body.filter((node) => node.type === 'ImportDeclaration' && node.source.value === LIB_NAME)
|
|
: []);
|
|
}
|
|
function getDefineMessagesSpecifier(decl) {
|
|
return decl.specifiers.find((spec) => 'imported' in spec && spec.imported.name === DEFINE_MESSAGES_METHOD_NAME);
|
|
}
|
|
function getFormatSpecifiers(decl) {
|
|
return decl.specifiers.filter((spec) => 'imported' in spec && FORMAT_METHOD_NAMES.has(spec.imported.name));
|
|
}
|
|
function collectFormatCalls(ast) {
|
|
const importDecls = getLibImportDeclarations(ast);
|
|
if (importDecls.length === 0)
|
|
return [];
|
|
const imports = new Set(importDecls.flatMap((decl) => getFormatSpecifiers(decl).map((n) => n.local.name)));
|
|
if (imports.size === 0)
|
|
return [];
|
|
const calls = [];
|
|
function enter(node) {
|
|
if (isFormatCall(node, imports)) {
|
|
calls.push(node);
|
|
this.skip();
|
|
}
|
|
}
|
|
estreeWalker.walk(ast.instance, { enter });
|
|
estreeWalker.walk(ast.html, { enter });
|
|
return calls;
|
|
}
|
|
function collectMessageDefinitions(ast) {
|
|
const definitions = [];
|
|
const defineImportDecl = getLibImportDeclarations(ast).find(getDefineMessagesSpecifier);
|
|
if (defineImportDecl == null)
|
|
return [];
|
|
const defineMethodName = getDefineMessagesSpecifier(defineImportDecl).local
|
|
.name;
|
|
estreeWalker.walk(ast.instance, {
|
|
enter(node) {
|
|
if (isMessagesDefinitionCall(node, defineMethodName) === false)
|
|
return;
|
|
const [arg] = node.arguments;
|
|
if (arg.type === 'ObjectExpression') {
|
|
definitions.push(arg);
|
|
this.skip();
|
|
}
|
|
},
|
|
});
|
|
return definitions.flatMap((definitionDict) => definitionDict.properties.map((propNode) => {
|
|
if (propNode.type !== 'Property') {
|
|
throw new Error(`Found invalid '${propNode.type}' at L${propNode.loc.start.line}:${propNode.loc.start.column}`);
|
|
}
|
|
return propNode.value;
|
|
}));
|
|
}
|
|
function collectMessages(markup) {
|
|
const ast = compiler.parse(markup);
|
|
const calls = collectFormatCalls(ast);
|
|
const definitions = collectMessageDefinitions(ast);
|
|
return [
|
|
...definitions.map((definition) => getObjFromExpression(definition)),
|
|
...calls.map((call) => {
|
|
const [pathNode, options] = call.arguments;
|
|
let messageObj;
|
|
if (pathNode.type === 'ObjectExpression') {
|
|
// _({ ...opts })
|
|
messageObj = getObjFromExpression(pathNode);
|
|
}
|
|
else {
|
|
const node = pathNode;
|
|
const id = node.value;
|
|
if (options && options.type === 'ObjectExpression') {
|
|
// _(id, { ...opts })
|
|
messageObj = getObjFromExpression(options);
|
|
messageObj.id = id;
|
|
}
|
|
else {
|
|
// _(id)
|
|
messageObj = { id };
|
|
}
|
|
}
|
|
if ((messageObj === null || messageObj === void 0 ? void 0 : messageObj.id) == null)
|
|
return null;
|
|
return messageObj;
|
|
}),
|
|
].filter(Boolean);
|
|
}
|
|
function extractMessages(markup, { accumulator = {}, shallow = false, overwrite = false } = {}) {
|
|
collectMessages(markup).forEach((messageObj) => {
|
|
let defaultValue = messageObj.default;
|
|
if (typeof defaultValue === 'undefined') {
|
|
defaultValue = '';
|
|
}
|
|
if (shallow) {
|
|
if (overwrite === false && messageObj.id in accumulator) {
|
|
return;
|
|
}
|
|
accumulator[messageObj.id] = defaultValue;
|
|
}
|
|
else {
|
|
if (overwrite === false &&
|
|
typeof delve(accumulator, messageObj.id) !== 'undefined') {
|
|
return;
|
|
}
|
|
deepSet(accumulator, messageObj.id, defaultValue);
|
|
}
|
|
});
|
|
return accumulator;
|
|
}
|
|
|
|
const { readFile, writeFile, mkdir, access } = fs__default['default'].promises;
|
|
const fileExists = (path) => access(path)
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
const program = sade__default['default']('svelte-i18n');
|
|
program
|
|
.command('extract <glob> [output]')
|
|
.describe('extract all message definitions from files to a json')
|
|
.option('-s, --shallow', 'extract to a shallow dictionary (ids with dots interpreted as strings, not paths)', false)
|
|
.option('--overwrite', 'overwrite the content of the output file instead of just appending new properties', false)
|
|
.option('-c, --config <dir>', 'path to the "svelte.config.js" file', process.cwd())
|
|
.action(async (globStr, output, { shallow, overwrite, config }) => {
|
|
var e_1, _a;
|
|
const filesToExtract = (await glob__default['default'](globStr)).filter((file) => file.match(/\.html|svelte$/i));
|
|
const svelteConfig = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(path.resolve(config, 'svelte.config.js'))); }).catch(() => null);
|
|
let accumulator = {};
|
|
if (output != null && overwrite === false && (await fileExists(output))) {
|
|
accumulator = await readFile(output)
|
|
.then((file) => JSON.parse(file.toString()))
|
|
.catch((e) => {
|
|
console.warn(e);
|
|
accumulator = {};
|
|
});
|
|
}
|
|
try {
|
|
for (var filesToExtract_1 = __asyncValues(filesToExtract), filesToExtract_1_1; filesToExtract_1_1 = await filesToExtract_1.next(), !filesToExtract_1_1.done;) {
|
|
const filePath = filesToExtract_1_1.value;
|
|
const buffer = await readFile(filePath);
|
|
let content = buffer.toString();
|
|
if (svelteConfig === null || svelteConfig === void 0 ? void 0 : svelteConfig.preprocess) {
|
|
const processed = await compiler.preprocess(content, svelteConfig.preprocess, {
|
|
filename: filePath,
|
|
});
|
|
content = processed.code;
|
|
}
|
|
extractMessages(content, { filePath, accumulator, shallow });
|
|
}
|
|
}
|
|
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
finally {
|
|
try {
|
|
if (filesToExtract_1_1 && !filesToExtract_1_1.done && (_a = filesToExtract_1.return)) await _a.call(filesToExtract_1);
|
|
}
|
|
finally { if (e_1) throw e_1.error; }
|
|
}
|
|
const jsonDictionary = JSON.stringify(accumulator, null, ' ');
|
|
if (output == null)
|
|
return console.log(jsonDictionary);
|
|
await mkdir(path.dirname(output), { recursive: true });
|
|
await writeFile(output, jsonDictionary);
|
|
});
|
|
program.parse(process.argv);
|