ai-localize-codemods 2.0.0 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,19 +1,34 @@
1
1
  // src/react-codemod.ts
2
2
  import * as fs from "fs";
3
+ import * as nodePath from "path";
3
4
  import * as parser from "@babel/parser";
4
5
  import traverse from "@babel/traverse";
5
6
  import generate from "@babel/generator";
6
7
  import * as t from "@babel/types";
7
- var USE_TRANSLATION_IMPORT = "react-i18next";
8
- var USE_TRANSLATION_HOOK = "useTranslation";
8
+ var DEFAULT_IMPORT_PACKAGE = "react-i18next";
9
+ var DEFAULT_HOOK_NAME = "useTranslation";
10
+ var DEFAULT_TRANSLATION_FN = "t";
11
+ var DEFAULT_ACCESSOR_STYLE = "function";
9
12
  var ReactCodemod = class {
10
13
  filePath;
11
14
  content;
12
15
  texts;
13
- constructor(filePath, content, texts) {
16
+ /** Resolved import specifier string (may differ per file for local paths) */
17
+ importSpecifier;
18
+ hookName;
19
+ translationFn;
20
+ namespace;
21
+ accessorStyle;
22
+ constructor(filePath, content, texts, codemodConfig, cwd = process.cwd()) {
14
23
  this.filePath = filePath;
15
24
  this.content = content;
16
25
  this.texts = texts;
26
+ this.hookName = codemodConfig?.hookName ?? DEFAULT_HOOK_NAME;
27
+ this.translationFn = codemodConfig?.translationFunction ?? DEFAULT_TRANSLATION_FN;
28
+ this.namespace = codemodConfig?.namespace;
29
+ this.accessorStyle = codemodConfig?.accessorStyle ?? DEFAULT_ACCESSOR_STYLE;
30
+ const pkg = codemodConfig?.importPackage ?? DEFAULT_IMPORT_PACKAGE;
31
+ this.importSpecifier = resolveImportSpecifier(pkg, filePath, cwd);
17
32
  }
18
33
  transform() {
19
34
  if (this.texts.length === 0) {
@@ -57,58 +72,66 @@ var ReactCodemod = class {
57
72
  textKeyMap.set(dt.text, dt.suggestedKey);
58
73
  }
59
74
  let replacedTexts = 0;
60
- let hasUseTranslationImport = false;
61
- let hasUseTranslationHook = false;
75
+ let hasImport = false;
76
+ let hasHook = false;
62
77
  let injectedImport = false;
63
78
  let injectedHook = false;
79
+ const translationFn = this.translationFn;
80
+ const hookName = this.hookName;
81
+ const importSpecifier2 = this.importSpecifier;
82
+ const accessorStyle = this.accessorStyle;
83
+ const buildTranslationExpr = (key) => {
84
+ if (accessorStyle === "bracket") {
85
+ return t.memberExpression(
86
+ t.identifier(translationFn),
87
+ t.stringLiteral(key),
88
+ true
89
+ /* computed */
90
+ );
91
+ }
92
+ return t.callExpression(t.identifier(translationFn), [t.stringLiteral(key)]);
93
+ };
64
94
  traverse(ast, {
65
- ImportDeclaration(nodePath) {
66
- if (nodePath.node.source.value === USE_TRANSLATION_IMPORT) {
67
- hasUseTranslationImport = true;
95
+ ImportDeclaration(nodePath2) {
96
+ if (nodePath2.node.source.value === importSpecifier2) {
97
+ hasImport = true;
68
98
  }
69
99
  },
70
- // Check if useTranslation hook already destructured
71
- VariableDeclarator(nodePath) {
72
- const id = nodePath.node.id;
100
+ VariableDeclarator(nodePath2) {
101
+ const id = nodePath2.node.id;
73
102
  if (t.isObjectPattern(id) && id.properties.some(
74
- (p) => t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === "t"
103
+ (p) => t.isObjectProperty(p) && t.isIdentifier(p.key) && p.key.name === translationFn
75
104
  )) {
76
- hasUseTranslationHook = true;
105
+ hasHook = true;
77
106
  }
78
107
  }
79
108
  });
80
109
  traverse(ast, {
81
- JSXText(nodePath) {
82
- const text = nodePath.node.value.trim();
110
+ JSXText(nodePath2) {
111
+ const text = nodePath2.node.value.trim();
83
112
  if (!text) return;
84
113
  const key = textKeyMap.get(text);
85
114
  if (!key) return;
86
- const tCall = t.jsxExpressionContainer(
87
- t.callExpression(t.identifier("t"), [t.stringLiteral(key)])
88
- );
89
- nodePath.replaceWith(tCall);
115
+ nodePath2.replaceWith(t.jsxExpressionContainer(buildTranslationExpr(key)));
90
116
  replacedTexts++;
91
117
  },
92
- StringLiteral(nodePath) {
93
- const text = nodePath.node.value;
118
+ StringLiteral(nodePath2) {
119
+ const text = nodePath2.node.value;
94
120
  const key = textKeyMap.get(text);
95
121
  if (!key) return;
96
- if (t.isImportDeclaration(nodePath.parent)) return;
97
- if (t.isObjectProperty(nodePath.parent) && nodePath.parent.key === nodePath.node) return;
98
- if (t.isJSXAttribute(nodePath.parent)) return;
99
- const tCall = t.callExpression(t.identifier("t"), [t.stringLiteral(key)]);
100
- nodePath.replaceWith(tCall);
122
+ if (t.isImportDeclaration(nodePath2.parent)) return;
123
+ if (t.isObjectProperty(nodePath2.parent) && nodePath2.parent.key === nodePath2.node) return;
124
+ if (t.isJSXAttribute(nodePath2.parent)) return;
125
+ nodePath2.replaceWith(buildTranslationExpr(key));
101
126
  replacedTexts++;
102
127
  },
103
- JSXAttribute(nodePath) {
104
- const valueNode = nodePath.node.value;
128
+ JSXAttribute(nodePath2) {
129
+ const valueNode = nodePath2.node.value;
105
130
  if (!t.isStringLiteral(valueNode)) return;
106
131
  const text = valueNode.value;
107
132
  const key = textKeyMap.get(text);
108
133
  if (!key) return;
109
- nodePath.node.value = t.jsxExpressionContainer(
110
- t.callExpression(t.identifier("t"), [t.stringLiteral(key)])
111
- );
134
+ nodePath2.node.value = t.jsxExpressionContainer(buildTranslationExpr(key));
112
135
  replacedTexts++;
113
136
  }
114
137
  });
@@ -123,25 +146,21 @@ var ReactCodemod = class {
123
146
  replacedTexts: 0
124
147
  };
125
148
  }
126
- if (!hasUseTranslationImport) {
149
+ if (!hasImport) {
127
150
  const importDecl = t.importDeclaration(
128
- [t.importSpecifier(t.identifier(USE_TRANSLATION_HOOK), t.identifier(USE_TRANSLATION_HOOK))],
129
- t.stringLiteral(USE_TRANSLATION_IMPORT)
151
+ [t.importSpecifier(t.identifier(hookName), t.identifier(hookName))],
152
+ t.stringLiteral(importSpecifier2)
130
153
  );
131
154
  ast.program.body.unshift(importDecl);
132
155
  injectedImport = true;
133
156
  }
134
- if (!hasUseTranslationHook) {
135
- this.injectUseTranslationHook(ast);
157
+ if (!hasHook) {
158
+ this.injectHook(ast);
136
159
  injectedHook = true;
137
160
  }
138
161
  const output = generate(
139
162
  ast,
140
- {
141
- retainLines: false,
142
- comments: true,
143
- compact: false
144
- },
163
+ { retainLines: false, comments: true, compact: false },
145
164
  this.content
146
165
  );
147
166
  return {
@@ -154,19 +173,22 @@ var ReactCodemod = class {
154
173
  replacedTexts
155
174
  };
156
175
  }
157
- injectUseTranslationHook(ast) {
176
+ injectHook(ast) {
177
+ const hookName = this.hookName;
178
+ const translationFn = this.translationFn;
179
+ const namespace = this.namespace;
158
180
  traverse(ast, {
159
- FunctionDeclaration(nodePath) {
160
- if (!isReactComponent(nodePath.node.id?.name)) return;
161
- injectHookIntoBody(nodePath.node.body);
162
- nodePath.stop();
181
+ FunctionDeclaration(nodePath2) {
182
+ if (!isReactComponent(nodePath2.node.id?.name)) return;
183
+ injectHookIntoBody(nodePath2.node.body, hookName, translationFn, namespace);
184
+ nodePath2.stop();
163
185
  },
164
- ArrowFunctionExpression(nodePath) {
165
- if (!isReactComponentParent(nodePath)) return;
166
- const body = nodePath.node.body;
186
+ ArrowFunctionExpression(nodePath2) {
187
+ if (!isReactComponentParent(nodePath2)) return;
188
+ const body = nodePath2.node.body;
167
189
  if (t.isBlockStatement(body)) {
168
- injectHookIntoBody(body);
169
- nodePath.stop();
190
+ injectHookIntoBody(body, hookName, translationFn, namespace);
191
+ nodePath2.stop();
170
192
  }
171
193
  }
172
194
  });
@@ -176,28 +198,76 @@ function isReactComponent(name) {
176
198
  if (!name) return false;
177
199
  return /^[A-Z]/.test(name);
178
200
  }
179
- function isReactComponentParent(nodePath) {
180
- const parent = nodePath.parent;
201
+ function isReactComponentParent(nodePath2) {
202
+ const parent = nodePath2.parent;
181
203
  if (t.isVariableDeclarator(parent)) {
182
204
  const id = parent.id;
183
205
  if (t.isIdentifier(id) && /^[A-Z]/.test(id.name)) return true;
184
206
  }
185
207
  return false;
186
208
  }
187
- function injectHookIntoBody(body) {
209
+ function injectHookIntoBody(body, hookName, translationFn, namespace) {
210
+ const hookArgs = namespace ? [t.stringLiteral(namespace)] : [];
188
211
  const hookDecl = t.variableDeclaration("const", [
189
212
  t.variableDeclarator(
190
213
  t.objectPattern([
191
- t.objectProperty(t.identifier("t"), t.identifier("t"), false, true)
214
+ t.objectProperty(t.identifier(translationFn), t.identifier(translationFn), false, true)
192
215
  ]),
193
- t.callExpression(t.identifier("useTranslation"), [])
216
+ t.callExpression(t.identifier(hookName), hookArgs)
194
217
  )
195
218
  ]);
196
219
  body.body.unshift(hookDecl);
197
220
  }
198
- function applyReactCodemod(filePath, texts, dryRun = false) {
221
+ function resolveImportSpecifier(importPackage, filePath, cwd) {
222
+ if (isNpmPackage(importPackage)) {
223
+ return importPackage;
224
+ }
225
+ let absoluteHookPath;
226
+ if (nodePath.isAbsolute(importPackage)) {
227
+ absoluteHookPath = importPackage;
228
+ } else {
229
+ absoluteHookPath = nodePath.resolve(cwd, importPackage);
230
+ }
231
+ const fileDir = nodePath.dirname(filePath);
232
+ let rel = nodePath.relative(fileDir, absoluteHookPath).replace(/\\/g, "/");
233
+ if (!rel.startsWith(".")) {
234
+ rel = `./${rel}`;
235
+ }
236
+ return rel;
237
+ }
238
+ function isNpmPackage(pkg) {
239
+ if (pkg.startsWith(".") || pkg.startsWith("/")) return false;
240
+ if (pkg.startsWith("@") && pkg.includes("/")) {
241
+ if (pkg.startsWith("@/")) return false;
242
+ return true;
243
+ }
244
+ if (!pkg.includes("/")) return true;
245
+ const firstSegment = pkg.split("/")[0].toLowerCase();
246
+ const sourceDirectories = /* @__PURE__ */ new Set([
247
+ "src",
248
+ "app",
249
+ "lib",
250
+ "hooks",
251
+ "utils",
252
+ "pages",
253
+ "components",
254
+ "features",
255
+ "shared",
256
+ "common",
257
+ "locales",
258
+ "i18n",
259
+ "services",
260
+ "store",
261
+ "helpers",
262
+ "core",
263
+ "modules"
264
+ ]);
265
+ if (sourceDirectories.has(firstSegment)) return false;
266
+ return true;
267
+ }
268
+ function applyReactCodemod(filePath, texts, dryRun = false, codemodConfig, cwd = process.cwd()) {
199
269
  const content = fs.readFileSync(filePath, "utf-8");
200
- const codemod = new ReactCodemod(filePath, content, texts);
270
+ const codemod = new ReactCodemod(filePath, content, texts, codemodConfig, cwd);
201
271
  const result = codemod.transform();
202
272
  if (result.changed && !dryRun) {
203
273
  fs.writeFileSync(filePath, result.transformedContent, "utf-8");
@@ -207,14 +277,17 @@ function applyReactCodemod(filePath, texts, dryRun = false) {
207
277
 
208
278
  // src/vue-codemod.ts
209
279
  import * as fs2 from "fs";
280
+ var DEFAULT_TRANSLATION_FN2 = "$t";
210
281
  var VueCodemod = class {
211
282
  filePath;
212
283
  content;
213
284
  texts;
214
- constructor(filePath, content, texts) {
285
+ translationFn;
286
+ constructor(filePath, content, texts, codemodConfig) {
215
287
  this.filePath = filePath;
216
288
  this.content = content;
217
289
  this.texts = texts;
290
+ this.translationFn = codemodConfig?.translationFunction ?? DEFAULT_TRANSLATION_FN2;
218
291
  }
219
292
  transform() {
220
293
  if (this.texts.length === 0) {
@@ -231,14 +304,15 @@ var VueCodemod = class {
231
304
  }
232
305
  let transformedContent = this.content;
233
306
  let replacedTexts = 0;
307
+ const fn = this.translationFn;
234
308
  if (templateMatch) {
235
309
  let template = templateMatch[1];
236
310
  for (const [text, key] of textKeyMap) {
237
311
  const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
238
312
  const re = new RegExp(`(>\\s*)${escaped}(\\s*<)`, "g");
239
- template = template.replace(re, `$1{{ $t('${key}') }}$2`);
313
+ template = template.replace(re, `$1{{ ${fn}('${key}') }}$2`);
240
314
  const attrRe = new RegExp(`((?:title|placeholder|alt|label)=)"(${escaped})"`, "g");
241
- template = template.replace(attrRe, `:$1"$t('${key}')"`);
315
+ template = template.replace(attrRe, `:$1"${fn}('${key}')"`);
242
316
  replacedTexts++;
243
317
  }
244
318
  transformedContent = transformedContent.replace(templateMatch[1], template);
@@ -269,9 +343,9 @@ var VueCodemod = class {
269
343
  };
270
344
  }
271
345
  };
272
- function applyVueCodemod(filePath, texts, dryRun = false) {
346
+ function applyVueCodemod(filePath, texts, dryRun = false, codemodConfig) {
273
347
  const content = fs2.readFileSync(filePath, "utf-8");
274
- const codemod = new VueCodemod(filePath, content, texts);
348
+ const codemod = new VueCodemod(filePath, content, texts, codemodConfig);
275
349
  const result = codemod.transform();
276
350
  if (result.changed && !dryRun) {
277
351
  fs2.writeFileSync(filePath, result.transformedContent, "utf-8");
@@ -281,14 +355,20 @@ function applyVueCodemod(filePath, texts, dryRun = false) {
281
355
 
282
356
  // src/angular-codemod.ts
283
357
  import * as fs3 from "fs";
358
+ var DEFAULT_TS_SERVICE = "this.translateService.instant";
359
+ var DEFAULT_TEMPLATE_PIPE = "translate";
284
360
  var AngularCodemod = class {
285
361
  filePath;
286
362
  content;
287
363
  texts;
288
- constructor(filePath, content, texts) {
364
+ templatePipe;
365
+ tsServiceCall;
366
+ constructor(filePath, content, texts, codemodConfig) {
289
367
  this.filePath = filePath;
290
368
  this.content = content;
291
369
  this.texts = texts;
370
+ this.templatePipe = codemodConfig?.translationFunction ?? DEFAULT_TEMPLATE_PIPE;
371
+ this.tsServiceCall = codemodConfig?.hookName ?? DEFAULT_TS_SERVICE;
292
372
  }
293
373
  transform() {
294
374
  if (this.texts.length === 0) return this.unchanged();
@@ -299,21 +379,23 @@ var AngularCodemod = class {
299
379
  let transformedContent = this.content;
300
380
  let replacedTexts = 0;
301
381
  const isTemplate = this.filePath.endsWith(".html");
382
+ const pipe = this.templatePipe;
383
+ const svc = this.tsServiceCall;
302
384
  for (const [text, key] of textKeyMap) {
303
385
  const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
304
386
  if (isTemplate) {
305
387
  const re = new RegExp(`(>\\s*)${escaped}(\\s*<)`, "g");
306
- transformedContent = transformedContent.replace(re, `$1{{ '${key}' | translate }}$2`);
388
+ transformedContent = transformedContent.replace(re, `$1{{ '${key}' | ${pipe} }}$2`);
307
389
  const attrRe = new RegExp(`\\[(?:placeholder|label|title|alt)\\]="'${escaped}'"`, "g");
308
390
  transformedContent = transformedContent.replace(
309
391
  attrRe,
310
- `[placeholder]="'${key}' | translate"`
392
+ `[placeholder]="'${key}' | ${pipe}"`
311
393
  );
312
394
  } else {
313
395
  const tsRe = new RegExp(`'${escaped}'`, "g");
314
396
  transformedContent = transformedContent.replace(
315
397
  tsRe,
316
- `this.translateService.instant('${key}')`
398
+ `${svc}('${key}')`
317
399
  );
318
400
  }
319
401
  replacedTexts++;
@@ -341,9 +423,9 @@ var AngularCodemod = class {
341
423
  };
342
424
  }
343
425
  };
344
- function applyAngularCodemod(filePath, texts, dryRun = false) {
426
+ function applyAngularCodemod(filePath, texts, dryRun = false, codemodConfig) {
345
427
  const content = fs3.readFileSync(filePath, "utf-8");
346
- const codemod = new AngularCodemod(filePath, content, texts);
428
+ const codemod = new AngularCodemod(filePath, content, texts, codemodConfig);
347
429
  const result = codemod.transform();
348
430
  if (result.changed && !dryRun) {
349
431
  fs3.writeFileSync(filePath, result.transformedContent, "utf-8");
@@ -414,8 +496,10 @@ async function batchReplaceCdnUrls(fileUrlMap, assets, dryRun = false) {
414
496
  import * as path2 from "path";
415
497
  var CodemodRunner = class {
416
498
  config;
417
- constructor(config) {
499
+ cwd;
500
+ constructor(config, cwd = process.cwd()) {
418
501
  this.config = config;
502
+ this.cwd = cwd;
419
503
  }
420
504
  async run(detectedTexts, options = {}) {
421
505
  const startTime = Date.now();
@@ -464,13 +548,14 @@ var CodemodRunner = class {
464
548
  }
465
549
  applyCodemod(filePath, texts, framework, dryRun) {
466
550
  const ext = path2.extname(filePath).toLowerCase();
551
+ const codemodConfig = this.config.codemods;
467
552
  if (ext === ".vue") {
468
- return applyVueCodemod(filePath, texts, dryRun);
553
+ return applyVueCodemod(filePath, texts, dryRun, codemodConfig);
469
554
  }
470
555
  if (framework === "angular" || framework === "angular-ngx" || framework === "angular-i18n") {
471
- return applyAngularCodemod(filePath, texts, dryRun);
556
+ return applyAngularCodemod(filePath, texts, dryRun, codemodConfig);
472
557
  }
473
- return applyReactCodemod(filePath, texts, dryRun);
558
+ return applyReactCodemod(filePath, texts, dryRun, codemodConfig, this.cwd);
474
559
  }
475
560
  };
476
561
  export {
@@ -482,5 +567,6 @@ export {
482
567
  applyAngularCodemod,
483
568
  applyReactCodemod,
484
569
  applyVueCodemod,
485
- batchReplaceCdnUrls
570
+ batchReplaceCdnUrls,
571
+ resolveImportSpecifier
486
572
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-localize-codemods",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "description": "AST-based codemods to inject i18n wrappers into frontend source code",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -19,7 +19,7 @@
19
19
  "@babel/generator": "^7.23.9",
20
20
  "recast": "^0.23.4",
21
21
  "jscodeshift": "^0.15.2",
22
- "ai-localize-shared": "2.0.0"
22
+ "ai-localize-shared": "2.0.3"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/babel__generator": "^7.6.8",
@@ -1,25 +1,39 @@
1
1
  import * as fs from 'fs';
2
2
 
3
- import type { DetectedText } from 'ai-localize-shared';
3
+ import type { DetectedText, CodemodConfig } from 'ai-localize-shared';
4
4
  import type { CodemodResult } from './react-codemod.js';
5
5
 
6
+ const DEFAULT_TS_SERVICE = 'this.translateService.instant';
7
+ const DEFAULT_TEMPLATE_PIPE = 'translate';
8
+
6
9
  /**
7
10
  * Angular ngx-translate codemod:
8
11
  * - In templates: wraps text with {{ 'key' | translate }}
9
12
  * - In TS: replaces string literals with this.translateService.instant('key')
13
+ *
14
+ * The pipe name used in templates can be overridden via
15
+ * `codemods.translationFunction` (default: "translate").
16
+ * The TS service call can be overridden via `codemods.hookName`
17
+ * (default: "this.translateService.instant").
10
18
  */
11
19
  export class AngularCodemod {
12
20
  private filePath: string;
13
21
  private content: string;
14
22
  private texts: DetectedText[];
23
+ private templatePipe: string;
24
+ private tsServiceCall: string;
15
25
 
16
- constructor(filePath: string, content: string, texts: DetectedText[]) {
17
- this.filePath = filePath;
18
- this.content = content;
26
+ constructor(filePath: string, content: string, texts: DetectedText[], codemodConfig?: CodemodConfig) {
27
+ this.filePath = filePath;
28
+ this.content = content;
19
29
  this.texts = texts;
30
+ // translationFunction maps to the Angular template pipe name
31
+ this.templatePipe = codemodConfig?.translationFunction ?? DEFAULT_TEMPLATE_PIPE;
32
+ // hookName maps to the TS service call expression
33
+ this.tsServiceCall = codemodConfig?.hookName ?? DEFAULT_TS_SERVICE;
20
34
  }
21
35
 
22
- transform(): CodemodResult {
36
+ transform(): CodemodResult {
23
37
  if (this.texts.length === 0) return this.unchanged();
24
38
 
25
39
  const textKeyMap = new Map<string, string>();
@@ -30,26 +44,28 @@ export class AngularCodemod {
30
44
  let transformedContent = this.content;
31
45
  let replacedTexts = 0;
32
46
  const isTemplate = this.filePath.endsWith('.html');
47
+ const pipe = this.templatePipe;
48
+ const svc = this.tsServiceCall;
33
49
 
34
- for (const [text, key] of textKeyMap) {
35
- const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
50
+ for (const [text, key] of textKeyMap) {
51
+ const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
36
52
  if (isTemplate) {
37
53
  // Template: >Some Text< => >{{ 'key' | translate }}<
38
- const re = new RegExp(`(>\\s*)${escaped}(\\s*<)`, 'g');
39
- transformedContent = transformedContent.replace(re, `$1{{ '${key}' | translate }}$2`);
54
+ const re = new RegExp(`(>\\s*)${escaped}(\\s*<)`, 'g');
55
+ transformedContent = transformedContent.replace(re, `$1{{ '${key}' | ${pipe} }}$2`);
40
56
  // attribute: [attr]="'Some Text'" => [attr]="'key' | translate"
41
57
  const attrRe = new RegExp(`\\[(?:placeholder|label|title|alt)\\]="'${escaped}'"`, 'g');
42
58
  transformedContent = transformedContent.replace(
43
- attrRe,
44
- `[placeholder]="'${key}' | translate"`
59
+ attrRe,
60
+ `[placeholder]="'${key}' | ${pipe}"`
45
61
  );
46
62
  } else {
47
- // TS file: 'Some Text' => this.translateService.instant('key')
48
- const tsRe = new RegExp(`'${escaped}'`, 'g');
63
+ // TS file: 'Some Text' => this.translateService.instant('key')
64
+ const tsRe = new RegExp(`'${escaped}'`, 'g');
49
65
  transformedContent = transformedContent.replace(
50
66
  tsRe,
51
- `this.translateService.instant('${key}')`
52
- );
67
+ `${svc}('${key}')`
68
+ );
53
69
  }
54
70
  replacedTexts++;
55
71
  }
@@ -58,12 +74,12 @@ export class AngularCodemod {
58
74
 
59
75
  return {
60
76
  filePath: this.filePath,
61
- originalContent: this.content,
77
+ originalContent: this.content,
62
78
  transformedContent,
63
79
  changed: true,
64
80
  injectedImport: false,
65
81
  injectedHook: false,
66
- replacedTexts,
82
+ replacedTexts,
67
83
  };
68
84
  }
69
85
 
@@ -71,11 +87,11 @@ export class AngularCodemod {
71
87
  return {
72
88
  filePath: this.filePath,
73
89
  originalContent: this.content,
74
- transformedContent: this.content,
75
- changed: false,
90
+ transformedContent: this.content,
91
+ changed: false,
76
92
  injectedImport: false,
77
93
  injectedHook: false,
78
- replacedTexts: 0,
94
+ replacedTexts: 0,
79
95
  };
80
96
  }
81
97
  }
@@ -83,10 +99,11 @@ export class AngularCodemod {
83
99
  export function applyAngularCodemod(
84
100
  filePath: string,
85
101
  texts: DetectedText[],
86
- dryRun = false
102
+ dryRun = false,
103
+ codemodConfig?: CodemodConfig
87
104
  ): CodemodResult {
88
105
  const content = fs.readFileSync(filePath, 'utf-8');
89
- const codemod = new AngularCodemod(filePath, content, texts);
106
+ const codemod = new AngularCodemod(filePath, content, texts, codemodConfig);
90
107
  const result = codemod.transform();
91
108
  if (result.changed && !dryRun) {
92
109
  fs.writeFileSync(filePath, result.transformedContent, 'utf-8');