Skip to content

Instantly share code, notes, and snippets.

@emiloberg
Created April 14, 2021 20:59
Show Gist options
  • Save emiloberg/5b44885b2aeb90604f6b6f87c8eada8f to your computer and use it in GitHub Desktop.
Save emiloberg/5b44885b2aeb90604f6b6f87c8eada8f to your computer and use it in GitHub Desktop.
Extract translations from Typescript
/*
* Finds and prints all usage of t()
* Use to extract translations
* Save as file and run:
* ts-node extract-translations.ts
*
* Change ROOT_PATH in this file if needed.
*/
import { readFileSync, readdirSync, statSync } from 'fs';
import { join as joinPath, parse as parsePath } from 'path';
import {
createSourceFile,
ScriptTarget,
Node,
SyntaxKind,
CallExpression,
StringLiteral,
PropertyAccessExpression,
Identifier,
TemplateExpression,
ScriptKind,
} from 'typescript';
const ROOT_PATH = joinPath(__dirname, './src');
const getAllFiles = (dirPath: string): string[] => {
const out: string[] = [];
const traverseDir = (dir: string): void => {
for (const file of readdirSync(dir)) {
const absPath = joinPath(dir, file);
if (statSync(absPath).isDirectory()) {
traverseDir(absPath);
} else {
const ext = parsePath(absPath).ext;
if (ext === '.ts' || ext === '.tsx') {
out.push(absPath);
}
}
}
};
traverseDir(dirPath);
return out;
};
const isType = <T>(obj: any, kind: SyntaxKind): obj is T => obj.kind === kind;
const getTranslationKeys = (paths: string[]): { keys: Set<string>; badKeys: Set<string> } => {
const foundKeys = new Set<string>();
const foundBadKeys = new Set<string>();
const traverseTree = (node: Node, path: string) => {
if (isType<CallExpression>(node, SyntaxKind.CallExpression)) {
if (
(isType<PropertyAccessExpression>(node.expression, SyntaxKind.PropertyAccessExpression) &&
node.expression.name.escapedText === 't') ||
(isType<Identifier>(node.expression, SyntaxKind.Identifier) && node.expression.escapedText === 't')
) {
const arg = node.arguments[0];
if (isType<StringLiteral>(arg, SyntaxKind.StringLiteral)) {
foundKeys.add(arg.text);
} else if (isType<TemplateExpression>(arg, SyntaxKind.TemplateExpression)) {
foundBadKeys.add(`Template key ${arg.getFullText()} found in ${path}`);
} else if (isType<Identifier>(arg, SyntaxKind.Identifier)) {
foundBadKeys.add(`Variable '${arg.getText()}' found in ${path}`);
} else if (isType<PropertyAccessExpression>(arg, SyntaxKind.PropertyAccessExpression)) {
foundBadKeys.add(`Property access '${arg.getText()}' found in ${path}`);
} else {
foundBadKeys.add(`Unhandled argument of type ${SyntaxKind[arg.kind]} in file ${path}`);
}
}
}
node.forEachChild((childNode) => traverseTree(childNode, path));
};
for (const path of paths) {
const fileStr = readFileSync(path, 'utf-8');
const extension = path.split('.')[path.split('.').length - 1];
let scriptKind;
if (extension === 'tsx') {
scriptKind = ScriptKind.TSX;
} else if (extension === 'ts') {
scriptKind = ScriptKind.TS;
}
const ast = createSourceFile('apa.boll', fileStr, ScriptTarget.ES2015, true, scriptKind);
traverseTree(ast, path);
}
return {
keys: foundKeys,
badKeys: foundBadKeys,
};
};
console.log();
console.log();
console.log(`Looking for keys in all files in ${ROOT_PATH}`);
console.log();
console.log();
const filePaths = getAllFiles(ROOT_PATH);
const { keys, badKeys } = getTranslationKeys(filePaths);
for (const key of keys) {
console.log(key);
}
console.log();
console.log();
for (const key of badKeys) {
console.warn(key);
}
console.log();
console.log();
console.log(`Found ${keys.size} keys`);
console.log(`Found ${badKeys.size} BAD keys`);
console.log();
console.log();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment