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[]) => { 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); } } } }