227 lines
8.8 KiB
TypeScript
227 lines
8.8 KiB
TypeScript
|
import * as fs from 'fs';
|
||
|
import {
|
||
|
Project,
|
||
|
VariableStatement,
|
||
|
SyntaxKind,
|
||
|
Node,
|
||
|
Statement,
|
||
|
ts,
|
||
|
Identifier,
|
||
|
SourceFile,
|
||
|
} from 'ts-morph';
|
||
|
import {
|
||
|
LOCALE_FOLDER_PATH,
|
||
|
TRANSLATOR_CLASS_NAME,
|
||
|
USE_INTL_NAME,
|
||
|
trimQuotes,
|
||
|
} from '../consts';
|
||
|
import { checkForms, AvailableLocales } from '../../src/localization/Translator';
|
||
|
|
||
|
const project = new Project({
|
||
|
tsConfigFilePath: './tsconfig.json',
|
||
|
});
|
||
|
|
||
|
let lang = 'ru';
|
||
|
let option = '';
|
||
|
|
||
|
if (process.argv.length > 2) {
|
||
|
lang = process.argv[2];
|
||
|
option = process.argv[3];
|
||
|
}
|
||
|
|
||
|
const usedTranslations: string[] = [];
|
||
|
const usedPluralTranslations: string[] = [];
|
||
|
|
||
|
const problemFiles: string[] = [];
|
||
|
const sourceFiles = project.getSourceFiles();
|
||
|
const sourceFilesWithIntl = sourceFiles.filter((sf) => {
|
||
|
return !!sf.getImportDeclarations().find((id) => {
|
||
|
return !!id.getNamedImports().find((ni) => ni.getName() === USE_INTL_NAME)
|
||
|
})
|
||
|
});
|
||
|
const getFileUsedIntl = (statements: Statement<ts.Statement>[]) => {
|
||
|
statements.forEach((s) => {
|
||
|
if (s instanceof VariableStatement) {
|
||
|
s.forEachDescendant((node) => {
|
||
|
let intVariableDeclaration: Identifier = null;
|
||
|
switch (node.getKind()) {
|
||
|
case SyntaxKind.VariableDeclaration:
|
||
|
if (node.getSymbol()) {
|
||
|
const name = node.getSymbol().getName();
|
||
|
const callExp = node.getChildren().find((n) => n.getKind() === SyntaxKind.CallExpression);
|
||
|
if (callExp) {
|
||
|
const callExpIden = callExp.getChildren().find(n => n.getKind() === SyntaxKind.Identifier);
|
||
|
if (callExpIden && callExpIden.getSymbol().getName() === USE_INTL_NAME) {
|
||
|
intVariableDeclaration = node as Identifier;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
if (intVariableDeclaration) {
|
||
|
intVariableDeclaration.findReferencesAsNodes().forEach((fr) => {
|
||
|
if (fr instanceof Node) {
|
||
|
const parent = fr.getParentIfKind(SyntaxKind.PropertyAccessExpression);
|
||
|
if (parent && (parent.getName() === 'getMessage' || parent.getName() === 'getPlural')) {
|
||
|
const syntaxList = parent.getNextSiblings().find((n) => n.getKind() === SyntaxKind.SyntaxList);
|
||
|
if (syntaxList) {
|
||
|
const id = syntaxList.getChildren()[0];
|
||
|
if (id && id.getKind() !== SyntaxKind.StringLiteral) {
|
||
|
problemFiles.push(fr.getSourceFile().getFilePath());
|
||
|
}
|
||
|
if (id) {
|
||
|
usedTranslations.push(trimQuotes(id.getText()));
|
||
|
if (parent.getName() === 'getPlural') {
|
||
|
usedPluralTranslations.push(trimQuotes(id.getText()));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
const getFileUsedTranslations = (file: SourceFile) => {
|
||
|
const namedImport = file.getImportDeclarations().find((id) => !!id.getNamedImports().find((ni) => ni.getName() === TRANSLATOR_CLASS_NAME));
|
||
|
if (namedImport) {
|
||
|
const identifier = namedImport.getImportClause().getNamedImports().find((iden) => iden.getName() === TRANSLATOR_CLASS_NAME);
|
||
|
const translateReferences = identifier.getNodeProperty('name').findReferencesAsNodes();
|
||
|
if (translateReferences.length > 0) {
|
||
|
translateReferences.forEach((identifierNode) => {
|
||
|
if (identifierNode.getParentIfKind(SyntaxKind.TypeReference)) {
|
||
|
const translatorVariable = identifierNode.getParent().getPreviousSibling().getPreviousSiblingIfKind(SyntaxKind.Identifier);
|
||
|
if (translatorVariable) {
|
||
|
translatorVariable.findReferencesAsNodes().forEach((node) => {
|
||
|
const parent = node.getParentIfKind(SyntaxKind.PropertyAccessExpression);
|
||
|
if (parent && (parent.getName() === 'getMessage' || parent.getName() === 'getPlural')) {
|
||
|
|
||
|
const syntaxList = parent.getNextSiblings().find((n) => n.getKind() === SyntaxKind.SyntaxList);
|
||
|
if (syntaxList) {
|
||
|
const id = syntaxList.getChildren()[0];
|
||
|
if (id && id.getKind() !== SyntaxKind.StringLiteral) {
|
||
|
problemFiles.push(parent.getSourceFile().getFilePath());
|
||
|
}
|
||
|
if (id) {
|
||
|
usedTranslations.push(trimQuotes(id.getText()));
|
||
|
if (parent.getName() === 'getPlural') {
|
||
|
usedPluralTranslations.push(trimQuotes(id.getText()));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
sourceFilesWithIntl.forEach((file) => {
|
||
|
getFileUsedIntl(file.getStatements());
|
||
|
})
|
||
|
|
||
|
const sourceFilesWithTranslator = project.getSourceFiles().filter((sf) => {
|
||
|
return !!sf.getImportDeclarations().find((id) => {
|
||
|
return !!id.getNamedImports().find((ni) => ni.getName() === TRANSLATOR_CLASS_NAME)
|
||
|
})
|
||
|
});
|
||
|
sourceFilesWithTranslator.forEach((file) => {
|
||
|
getFileUsedTranslations(file);
|
||
|
})
|
||
|
const filteredUsedTranslations = Array.from(new Set(usedTranslations));
|
||
|
const filteredUsedPluralTranslations = Array.from(new Set(usedPluralTranslations));
|
||
|
|
||
|
if (problemFiles.length) {
|
||
|
console.warn(`\n============== Files where translation id provided not as string ==============\n`);
|
||
|
console.log(problemFiles.join('\n'));
|
||
|
process.exit(255);
|
||
|
}
|
||
|
|
||
|
const allFiles = fs.readdirSync(LOCALE_FOLDER_PATH);
|
||
|
// Use ru or needed language
|
||
|
const translationFile = allFiles.find((file) => file.includes(`${lang}.json`));
|
||
|
|
||
|
if (!translationFile) {
|
||
|
console.error('File not found');
|
||
|
process.exit(255);
|
||
|
}
|
||
|
|
||
|
const translationsObject = JSON.parse(fs.readFileSync(`./src/lib/intl/__locales/${translationFile}`, { flag: 'r+' }) as unknown as string);
|
||
|
const translations = {
|
||
|
locale: translationFile,
|
||
|
messages: Object.keys(translationsObject),
|
||
|
};
|
||
|
|
||
|
const someMessagesNotFound: string[] = [];
|
||
|
const notUsed: string[] = [];
|
||
|
const notFound: string[] = [];
|
||
|
const checkLocaleMessages = (locale: string, messages: string[]) => {
|
||
|
filteredUsedTranslations.forEach(f => {
|
||
|
if (!messages.includes(f)) {
|
||
|
notFound.push(f);
|
||
|
}
|
||
|
});
|
||
|
messages.forEach(t => {
|
||
|
if (!filteredUsedTranslations.includes(t)) {
|
||
|
notUsed.push(t);
|
||
|
}
|
||
|
});
|
||
|
if (notFound.length > 0) {
|
||
|
someMessagesNotFound.push(locale);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const render = (data: string[], title: string) => {
|
||
|
console.log(`============ ${title} ============`);
|
||
|
console.table(data);
|
||
|
console.log(`============ ${title} ============`);
|
||
|
}
|
||
|
|
||
|
checkLocaleMessages(translations.locale, translations.messages);
|
||
|
|
||
|
const checkPluralForm = () => {
|
||
|
const pluralFormWrong: string[] = [];
|
||
|
filteredUsedPluralTranslations.forEach((id) => {
|
||
|
const message = translationsObject[id];
|
||
|
if (!checkForms(message, lang as AvailableLocales, id)) {
|
||
|
pluralFormWrong.push(id)
|
||
|
}
|
||
|
});
|
||
|
return pluralFormWrong;
|
||
|
}
|
||
|
|
||
|
const plural = checkPluralForm();
|
||
|
if (!option && (someMessagesNotFound.length || plural.length > 0 )) {
|
||
|
someMessagesNotFound.forEach(locale => console.error(`\nSome translatins for ${locale} was not found!\n`));
|
||
|
plural.forEach(id => console.error(`\nTranslation with id: "${id}" - have wrong number of plural forms!\n`));
|
||
|
process.exit(255);
|
||
|
}
|
||
|
if (option) {
|
||
|
switch (option) {
|
||
|
case '--show-missing': {
|
||
|
render(notFound, 'NotFound')
|
||
|
break;
|
||
|
}
|
||
|
case '--show-unused': {
|
||
|
render(notUsed, 'notUsed')
|
||
|
break;
|
||
|
}
|
||
|
case '--check-plurals': {
|
||
|
render(plural, 'Wrong Plural Form')
|
||
|
}
|
||
|
default: {
|
||
|
if (someMessagesNotFound.length) {
|
||
|
someMessagesNotFound.forEach(locale => console.error(`\nSome translatins for ${locale} was not found!\n\n`));
|
||
|
process.exit(255);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|