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.
@@ -1,60 +1,68 @@
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_TRANSLATION_FN = '$t';
7
+
6
8
  /**
7
- * Vue i18n codemod: wraps hardcoded text with $t() calls in Vue SFCs.
8
- * Handles both Options API and Composition API.
9
+ * Vue i18n codemod: wraps hardcoded text with $t() / t() calls in Vue SFCs.
10
+ * Handles both Options API (global $t) and Composition API (useI18n / useTranslation).
11
+ *
12
+ * The translation function name can be overridden via the `codemods.translationFunction`
13
+ * config field (default: "$t").
9
14
  */
10
15
  export class VueCodemod {
11
16
  private filePath: string;
12
17
  private content: string;
13
18
  private texts: DetectedText[];
19
+ private translationFn: string;
14
20
 
15
- constructor(filePath: string, content: string, texts: DetectedText[]) {
21
+ constructor(filePath: string, content: string, texts: DetectedText[], codemodConfig?: CodemodConfig) {
16
22
  this.filePath = filePath;
17
23
  this.content = content;
18
- this.texts = texts;
24
+ this.texts = texts;
25
+ this.translationFn = codemodConfig?.translationFunction ?? DEFAULT_TRANSLATION_FN;
19
26
  }
20
27
 
21
28
  transform(): CodemodResult {
22
29
  if (this.texts.length === 0) {
23
- return this.unchanged();
24
- }
30
+ return this.unchanged();
31
+ }
25
32
 
26
33
  // Parse <template> and <script> sections separately
27
34
  const templateMatch = this.content.match(/<template[^>]*>([\s\S]*?)<\/template>/);
28
35
  const scriptMatch = this.content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
29
36
 
30
- if (!templateMatch && !scriptMatch) {
37
+ if (!templateMatch && !scriptMatch) {
31
38
  return this.unchanged();
32
39
  }
33
40
 
34
41
  const textKeyMap = new Map<string, string>();
35
42
  for (const dt of this.texts) {
36
- textKeyMap.set(dt.text, dt.suggestedKey);
37
- }
43
+ textKeyMap.set(dt.text, dt.suggestedKey);
44
+ }
38
45
 
39
46
  let transformedContent = this.content;
40
47
  let replacedTexts = 0;
48
+ const fn = this.translationFn;
41
49
 
42
- // Replace in template: >text< => >{{ $t('key') }}<
50
+ // Replace in template: >text< => >{{ $t('key') }}<
43
51
  if (templateMatch) {
44
52
  let template = templateMatch[1];
45
- for (const [text, key] of textKeyMap) {
46
- const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
47
- const re = new RegExp(`(>\\s*)${escaped}(\\s*<)`, 'g');
48
- template = template.replace(re, `$1{{ $t('${key}') }}$2`);
49
- // Also replace in attribute values: title="text" => :title="$t('key')"
50
- const attrRe = new RegExp(`((?:title|placeholder|alt|label)=)"(${escaped})"`, 'g');
51
- template = template.replace(attrRe, `:$1"$t('${key}')"`);
52
- replacedTexts++;
53
+ for (const [text, key] of textKeyMap) {
54
+ const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
55
+ const re = new RegExp(`(>\\s*)${escaped}(\\s*<)`, 'g');
56
+ template = template.replace(re, `$1{{ ${fn}('${key}') }}$2`);
57
+ // Also replace in attribute values: title="text" => :title="$t('key')"
58
+ const attrRe = new RegExp(`((?:title|placeholder|alt|label)=)"(${escaped})"`, 'g');
59
+ template = template.replace(attrRe, `:$1"${fn}('${key}')"`);
60
+ replacedTexts++;
53
61
  }
54
62
  transformedContent = transformedContent.replace(templateMatch[1], template);
55
63
  }
56
64
 
57
- if (replacedTexts === 0) {
65
+ if (replacedTexts === 0) {
58
66
  return this.unchanged();
59
67
  }
60
68
 
@@ -65,16 +73,16 @@ export class VueCodemod {
65
73
  changed: true,
66
74
  injectedImport: false, // vue-i18n is globally injected
67
75
  injectedHook: false,
68
- replacedTexts,
76
+ replacedTexts,
69
77
  };
70
78
  }
71
79
 
72
80
  private unchanged(): CodemodResult {
73
81
  return {
74
- filePath: this.filePath,
75
- originalContent: this.content,
82
+ filePath: this.filePath,
83
+ originalContent: this.content,
76
84
  transformedContent: this.content,
77
- changed: false,
85
+ changed: false,
78
86
  injectedImport: false,
79
87
  injectedHook: false,
80
88
  replacedTexts: 0,
@@ -85,10 +93,11 @@ export class VueCodemod {
85
93
  export function applyVueCodemod(
86
94
  filePath: string,
87
95
  texts: DetectedText[],
88
- dryRun = false
96
+ dryRun = false,
97
+ codemodConfig?: CodemodConfig
89
98
  ): CodemodResult {
90
99
  const content = fs.readFileSync(filePath, 'utf-8');
91
- const codemod = new VueCodemod(filePath, content, texts);
100
+ const codemod = new VueCodemod(filePath, content, texts, codemodConfig);
92
101
  const result = codemod.transform();
93
102
  if (result.changed && !dryRun) {
94
103
  fs.writeFileSync(filePath, result.transformedContent, 'utf-8');