1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 12:55:55 +00:00
FrankerFaceZ/bin/static_i18n.js
SirStendec 8ac1b2ce91 4.15.4
* Added: Setting to hide the mass gift sub banner at the top of chat.
* Changed: Messages for redeeming Channel Points now have custom rendering with less padding and background colors to properly highlight them.
* Fixed: Moderation profiles not always applying when navigating between channels.
* Fixed: Settings to disable auto-play not working.
* Fixed: Remove debug logging when importing a profile.
2019-11-11 14:38:49 -05:00

153 lines
3.3 KiB
JavaScript

const transformSync = require('@babel/core').transformSync;
const type = require('@babel/types');
const traverse = require('@babel/traverse').default;
const fs = require('fs');
const glob = require('glob');
function matchesPattern(member, match, allowPartial = false) {
if ( ! type.isMemberExpression(member) )
return false;
const parts = Array.isArray(match) ? match : match.split('.');
const nodes = [];
let node;
for(node = member; type.isMemberExpression(node); node = node.object)
nodes.push(node.property);
nodes.push(node);
if ( nodes.length < parts.length )
return false;
if ( ! allowPartial && nodes.length > parts.length )
return false;
for(let i = 0, j = nodes.length - 1; i < parts.length; i++, j--) {
const node = nodes[j];
let value;
if ( type.isIdentifier(node) )
value = node.name;
else if ( type.isStringLiteral(node) )
value = node.value;
else if ( type.isThisExpression(node) )
value = 'this';
else
return false;
if ( parts[i] !== value )
return false;
}
return true;
}
const babelOptions = {
ast: true,
parserOpts: JSON.parse(fs.readFileSync('.babelrc', 'utf8'))
};
babelOptions.parserOpts.plugins = [
'jsx',
'dynamicImport',
'optionalChaining',
'objectRestSpread'
]
function getString(node) {
if ( type.isStringLiteral(node) )
return node.value;
if ( type.isTemplateLiteral(node) && (! node.expressions || ! node.expressions.length) && node.quasis && node.quasis.length === 1 )
return node.quasis[0].value.cooked;
return null;
}
function extractFromCode(code) {
const { ast } = transformSync(code, babelOptions);
const matches = [];
traverse(ast, {
CallExpression(path) {
const callee = path.get('callee');
if ( ! callee )
return;
if ( !( matchesPattern(callee.node, 'this.i18n.t') ||
matchesPattern(callee.node, 'i18n.t') ||
matchesPattern(callee.node, 't.i18n.t') ||
matchesPattern(callee.node, 'this.i18n.tList') ||
matchesPattern(callee.node, 'i18n.tList') ||
matchesPattern(callee.node, 't.i18n.tList') ||
matchesPattern(callee.node, 'this.t') ||
matchesPattern(callee.node, 'this.tList') ))
return;
const key = getString(path.get('arguments.0').node);
if ( ! key )
return;
matches.push({
key,
loc: path.node.loc.start,
phrase: getString(path.get('arguments.1').node)
});
}
})
return matches;
}
function extractFromFiles(files) {
const results = [];
if ( ! Array.isArray(files) )
files = [files];
const scannable = new Set;
for(const thing of files) {
for(const file of glob.sync(thing, {}))
scannable.add(file);
}
for(const file of scannable) {
const code = fs.readFileSync(file, 'utf8');
const matches = extractFromCode(code);
for(const match of matches) {
match.source = `${file}:${match.loc.line}:${match.loc.column}`;
delete match.loc;
results.push(match);
}
}
return results;
}
const bits = extractFromFiles([
'src/**/*.js',
'src/**/*.jsx'
]);
const seen = new Set;
const out = [];
for(const entry of bits) {
if ( seen.has(entry.key) )
continue;
seen.add(entry.key);
if ( entry.key && entry.phrase )
out.push({
key: entry.key,
phrase: entry.phrase,
calls: [
`/${entry.source}`
]
});
}
fs.writeFileSync('extracted.json', JSON.stringify(out, null, '\t'));
console.log(`Extracted ${out.length} strings.`);