@jsverse/transloco
Version:
The internationalization (i18n) library for Angular
175 lines • 7.28 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = exports.PIPE_IN_BINDING_REGEX = exports.PIPE_REGEX = void 0;
const p = require("node:path");
const ora = require("ora");
const replace_in_file_1 = require("replace-in-file");
const PIPE_CONTENT_REGEX = `\\s*([^}\\r\\n]*?\\|)\\s*(translate)[^\\r\\n]*?`;
exports.PIPE_REGEX = `{{${PIPE_CONTENT_REGEX}}}`;
exports.PIPE_IN_BINDING_REGEX = `\\]=('|")${PIPE_CONTENT_REGEX}\\1`;
// Example: `./src/ng2/**/*.html`;
function run(path) {
console.log('\x1b[4m%s\x1b[0m', '\nStarting migration script');
const dir = p.resolve(process.cwd());
path = p.join(dir, path, '/**/*');
const noSpecFiles = { ignore: `${path}spec.ts`, files: `${path}.ts` };
const [directive, pipe, pipeInBinding] = [
/(translate|\[translate(?:Params)?\])=("|')[^"']*\2/gm,
new RegExp(exports.PIPE_REGEX, 'gm'),
new RegExp(exports.PIPE_IN_BINDING_REGEX, 'gm'),
].map((regex) => ({
files: `${path}.html`,
from: regex,
to: (match) => match.replace(/translate/g, 'transloco'),
}));
const moduleMultiImport = {
files: `${path}.ts`,
from: /import\s*{((([^,}]*,)+\s*(TranslateModule)\s*(,[^}]*)*)|(([^,{}]*,)*\s*(TranslateModule)\s*,\s*[a-zA-Z0-9]+(,[^}]*)*))\s*}\s*from\s*('|").?ngx-translate(\/[^'"]+)?('|");?/g,
to: (match) => match
.replace('TranslateModule', '')
.replace(/,\s*,/, ',')
.replace(/{\s*,/, '{')
.replace(/,\s*}/, '}')
.concat(`\nimport { TranslocoModule } from '@jsverse/transloco';`),
};
const moduleSingleImport = {
files: `${path}.ts`,
from: /import\s*{\s*(TranslateModule),?\s*}\s*from\s*('|").?ngx-translate(\/[^'"]+)?('|");?/g,
to: `import { TranslocoModule } from '@jsverse/transloco';`,
};
const modules = {
files: `${path}.ts`,
from: /(?<![a-zA-Z])TranslateModule(?![^]*from)(\.(forRoot|forChild)\(({[^}]*})*[^)]*\))?/g,
to: 'TranslocoModule',
};
const serviceMultiImport = {
files: `${path}.ts`,
from: /import\s*{((([^,}]*,)+\s*(TranslateService)\s*(,[^}]*)*)|(([^,{}]*,)*\s*(TranslateService)\s*,\s*[a-zA-Z0-9]+(,[^}]*)*))\s*}\s*from\s*('|").?ngx-translate(\/[^'"]+)?('|");?/g,
to: (match) => match
.replace('TranslateService', '')
.replace(/,\s*,/, ',')
.replace(/{\s*,/, '{')
.replace(/,\s*}/, '}')
.concat(`\nimport { TranslocoService } from '@jsverse/transloco';`),
};
const [serviceSingleImport, pipeImport] = [
/import\s*{\s*(TranslateService),?\s*}\s*from\s*('|").?ngx-translate(\/[^'"]+)?('|");?/g,
/import\s*{\s*(TranslatePipe),?\s*}\s*from\s*('|")[^'"]+('|");?/g,
].map((regex) => ({
...noSpecFiles,
from: regex,
to: `import { TranslocoService } from '@jsverse/transloco';`,
}));
const constructorInjection = {
...noSpecFiles,
from: /(?:private|protected|public)\s+(.*?)\s*:\s*(?:TranslateService|TranslatePipe\s*(?:,|\)))/g,
to: (match) => match.replace(/TranslateService|TranslatePipe/g, 'TranslocoService'),
};
const serviceUsage = {
...noSpecFiles,
from: /(?=([^]+(?:private|protected|public)\s+([^,:()]+)\s*:\s*(?:TranslocoService\s*(?:,|\)))))\1[^]*/gm,
to: (match, _, serviceName) => {
const sanitizedName = serviceName
.split('')
.map((char) => (['$', '^'].includes(char) ? `\\${char}` : char))
.join('');
const functionsMap = {
instant: 'translate',
transform: 'translate',
get: 'selectTranslate',
stream: 'selectTranslate',
use: 'setActiveLang',
set: 'setTranslation',
};
const propsMap = {
currentLang: 'getActiveLang()',
onLangChange: 'langChanges$',
};
const serviceCallRgx = ({ map, func }) => new RegExp(`(?:(?:\\s*|this\\.)${sanitizedName})(?:\\s*\\t*\\r*\\n*)*\\.(?:\\s*\\t*\\r*\\n*)*(${getTarget(map)})[\\r\\t\\n\\s]*${func ? '\\(' : '(?!\\()'}`, 'g');
const getTarget = (t) => Object.keys(t).join('|');
return [
{ func: true, map: functionsMap },
{ func: false, map: propsMap },
].reduce((acc, curr) => {
return acc.replace(serviceCallRgx(curr), (str) => str.replace(new RegExp(getTarget(curr.map)), (func) => curr.map[func]));
}, match);
},
};
const specs = {
files: `${path}spec.ts`,
from: /TranslateService|TranslatePipe/g,
to: 'TranslocoService',
};
const htmlReplacements = [
{
matchers: [directive],
step: 'directives',
},
{
matchers: [pipe, pipeInBinding],
step: 'pipes',
},
];
const tsReplacements = [
{
matchers: [modules, moduleMultiImport, moduleSingleImport],
step: 'modules',
},
{
matchers: [serviceMultiImport, serviceSingleImport, pipeImport],
step: 'service imports',
},
{
matchers: [constructorInjection],
step: 'constructor injections',
},
{
matchers: [serviceUsage],
step: 'service usage',
},
{
matchers: [specs],
step: 'specs',
},
];
async function migrate(matchersArr, filesType) {
console.log(`\nMigrating ${filesType} files 📜`);
let spinner;
const isWindows = process.platform === 'win32';
for (let i = 0; i < matchersArr.length; i++) {
const { step, matchers } = matchersArr[i];
const msg = `Step ${i + 1}/${matchersArr.length}: Migrating ${step}`;
spinner = ora().start(msg);
const noFilesFound = [];
for (const matcher of matchers) {
try {
await (0, replace_in_file_1.replaceInFile)({
...matcher,
glob: {
windowsPathsNoEscape: isWindows,
},
});
}
catch (e) {
if (e.message.includes('No files match the pattern')) {
noFilesFound.push(e.message);
}
else {
throw e;
}
}
}
spinner.succeed(msg);
noFilesFound.forEach((pattern) => console.log('\x1b[33m%s\x1b[0m', `⚠️ ${pattern}`));
}
}
return migrate(htmlReplacements, 'HTML')
.then(() => migrate(tsReplacements, 'TS'))
.then(() => {
console.log('\n 🌵 Done! 🌵');
console.log('Welcome to a better translation experience 🌐');
console.log('\nFor more information about this script please visit 👉 https://jsverse.github.io/transloco/docs/migration/ngx\n');
});
}
exports.run = run;
//# sourceMappingURL=ngx-translate-migration.js.map