#!/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 [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 ', '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);