ai-localize-config 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/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # ai-localize-config
2
2
 
3
+ ## 2.0.3
4
+
5
+ ### Minor Changes
6
+
7
+ - **`keyStyle` Zod schema field** — `ConfigSchema` now validates `keyStyle: "path" | "screaming_snake"` (default `"path"`); controls whether locale keys are hierarchical dot-notation or UPPER_SNAKE_CASE derived from the text value
8
+ - **`staticKeys` Zod schema field** — `ConfigSchema` now validates `staticKeys: Record<string, string>` (default `{}`); entries are injected into every locale file at extract/full-migrate time
9
+
10
+ ## 2.0.2
11
+
12
+ ### Minor Changes
13
+
14
+ - **`CodemodConfigSchema` added** — Zod schema extended with `codemods` field (accessorStyle, translationFunction, namespace) and `localeStructure` field; both fields are now properly validated and surfaced in config loading
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies
19
+ - ai-localize-shared@2.0.2
20
+
21
+ ## 2.0.1
22
+
23
+ ### Patch Changes
24
+
25
+ - Fix config schema validation (aws optional/nullable, framework type), ignore className CSS values in AST scanner, add includePatterns config support for selective file scanning, add --extract-cdns flag to scan command to dump CDN URLs to a JSON file
26
+ - Updated dependencies
27
+ - ai-localize-shared@2.0.1
28
+
3
29
  ## 2.0.0
4
30
 
5
31
  ### Major Changes
package/dist/index.d.mts CHANGED
@@ -10,6 +10,7 @@ declare const LocalizationConfigSchema: z.ZodObject<{
10
10
  keyPrefix: z.ZodOptional<z.ZodString>;
11
11
  namespaces: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
12
12
  ignorePatterns: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
13
+ includePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
13
14
  incrementalCache: z.ZodDefault<z.ZodBoolean>;
14
15
  cacheDir: z.ZodDefault<z.ZodString>;
15
16
  aws: z.ZodNullable<z.ZodOptional<z.ZodObject<{
@@ -38,6 +39,91 @@ declare const LocalizationConfigSchema: z.ZodObject<{
38
39
  profile?: string | undefined;
39
40
  }>>>;
40
41
  plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
42
+ /** Codemod behaviour overrides — all fields are optional. */
43
+ codemods: z.ZodOptional<z.ZodObject<{
44
+ /**
45
+ * The npm package to import from.
46
+ * React default: "react-i18next"
47
+ * Vue default: "vue-i18n"
48
+ * Angular default: "@ngx-translate/core"
49
+ */
50
+ importPackage: z.ZodOptional<z.ZodString>;
51
+ /**
52
+ * The hook / function name to import and call.
53
+ * React default: "useTranslation"
54
+ * Vue default: "useI18n"
55
+ */
56
+ hookName: z.ZodOptional<z.ZodString>;
57
+ /**
58
+ * The translation function identifier returned by the hook (default: "t").
59
+ */
60
+ translationFunction: z.ZodOptional<z.ZodString>;
61
+ /**
62
+ * Namespace to pass as the first argument to the hook call.
63
+ * When omitted no namespace argument is injected.
64
+ */
65
+ namespace: z.ZodOptional<z.ZodString>;
66
+ /**
67
+ * How the translation accessor is written in generated code.
68
+ * "function" (default) — t('key')
69
+ * "bracket" — t['key']
70
+ */
71
+ accessorStyle: z.ZodOptional<z.ZodEnum<["function", "bracket"]>>;
72
+ }, "strip", z.ZodTypeAny, {
73
+ importPackage?: string | undefined;
74
+ hookName?: string | undefined;
75
+ translationFunction?: string | undefined;
76
+ namespace?: string | undefined;
77
+ accessorStyle?: "function" | "bracket" | undefined;
78
+ }, {
79
+ importPackage?: string | undefined;
80
+ hookName?: string | undefined;
81
+ translationFunction?: string | undefined;
82
+ namespace?: string | undefined;
83
+ accessorStyle?: "function" | "bracket" | undefined;
84
+ }>>;
85
+ /**
86
+ * Locale file layout.
87
+ * "nested" (default) — one file per language + namespace:
88
+ * locales/en/common.json, locales/fr/dashboard.json
89
+ * "flat" — one file per language, all keys merged:
90
+ * locales/en.json, locales/fr.json
91
+ */
92
+ localeStructure: z.ZodDefault<z.ZodEnum<["nested", "flat"]>>;
93
+ /**
94
+ * Controls the format of generated locale keys for hardcoded text found
95
+ * during scanning.
96
+ *
97
+ * "path" (default) — hierarchical dot-notation derived from file path + text:
98
+ * src/pages/settings/SettingsPage.tsx + "Save Changes"
99
+ * → "settings.settings_page.save_changes"
100
+ *
101
+ * "screaming_snake" — UPPER_SNAKE_CASE derived solely from the text value.
102
+ * The key equals the value converted to UPPER_SNAKE_CASE:
103
+ * "Save Changes" → key: "SAVE_CHANGES", value: "Save Changes"
104
+ * "Max Count" → key: "MAX_COUNT", value: "Max Count"
105
+ * This matches the pattern used by staticKeys and is ideal for
106
+ * projects that use constant-style i18n keys.
107
+ */
108
+ keyStyle: z.ZodDefault<z.ZodEnum<["path", "screaming_snake"]>>;
109
+ /**
110
+ * Static locale keys to inject into every generated locale file.
111
+ * These key/value pairs are merged with scanned keys and written to all
112
+ * languages (defaultLanguage + targetLanguages).
113
+ *
114
+ * Example:
115
+ * "staticKeys": {
116
+ * "MAX_COUNT": "Max Count",
117
+ * "ALLOWED": "Allowed",
118
+ * "DISABLED": "Disabled",
119
+ * "UNLIMITED": "Unlimited"
120
+ * }
121
+ *
122
+ * In nested mode these keys are placed in the default namespace file
123
+ * (translation.json / common.json). In flat mode they are merged at the
124
+ * top level of the single per-language file.
125
+ */
126
+ staticKeys: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
41
127
  }, "strip", z.ZodTypeAny, {
42
128
  framework: "react" | "react-cra" | "react-vite" | "react-nextjs" | "angular" | "angular-ngx" | "angular-i18n" | "vue" | "vue-i18n" | "jquery" | "vanilla-js" | "jsp" | "unknown";
43
129
  defaultLanguage: string;
@@ -48,8 +134,11 @@ declare const LocalizationConfigSchema: z.ZodObject<{
48
134
  incrementalCache: boolean;
49
135
  cacheDir: string;
50
136
  plugins: string[];
137
+ localeStructure: "flat" | "nested";
138
+ keyStyle: "path" | "screaming_snake";
51
139
  keyPrefix?: string | undefined;
52
140
  namespaces?: string[] | undefined;
141
+ includePatterns?: string[] | undefined;
53
142
  aws?: {
54
143
  region: string;
55
144
  bucket: string;
@@ -59,6 +148,14 @@ declare const LocalizationConfigSchema: z.ZodObject<{
59
148
  legacyCdnPattern?: string | undefined;
60
149
  profile?: string | undefined;
61
150
  } | null | undefined;
151
+ codemods?: {
152
+ importPackage?: string | undefined;
153
+ hookName?: string | undefined;
154
+ translationFunction?: string | undefined;
155
+ namespace?: string | undefined;
156
+ accessorStyle?: "function" | "bracket" | undefined;
157
+ } | undefined;
158
+ staticKeys?: Record<string, string> | undefined;
62
159
  }, {
63
160
  framework?: "react" | "react-cra" | "react-vite" | "react-nextjs" | "angular" | "angular-ngx" | "angular-i18n" | "vue" | "vue-i18n" | "jquery" | "vanilla-js" | "jsp" | "unknown" | undefined;
64
161
  defaultLanguage?: string | undefined;
@@ -68,6 +165,7 @@ declare const LocalizationConfigSchema: z.ZodObject<{
68
165
  keyPrefix?: string | undefined;
69
166
  namespaces?: string[] | undefined;
70
167
  ignorePatterns?: string[] | undefined;
168
+ includePatterns?: string[] | undefined;
71
169
  incrementalCache?: boolean | undefined;
72
170
  cacheDir?: string | undefined;
73
171
  aws?: {
@@ -80,6 +178,16 @@ declare const LocalizationConfigSchema: z.ZodObject<{
80
178
  profile?: string | undefined;
81
179
  } | null | undefined;
82
180
  plugins?: string[] | undefined;
181
+ codemods?: {
182
+ importPackage?: string | undefined;
183
+ hookName?: string | undefined;
184
+ translationFunction?: string | undefined;
185
+ namespace?: string | undefined;
186
+ accessorStyle?: "function" | "bracket" | undefined;
187
+ } | undefined;
188
+ localeStructure?: "flat" | "nested" | undefined;
189
+ keyStyle?: "path" | "screaming_snake" | undefined;
190
+ staticKeys?: Record<string, string> | undefined;
83
191
  }>;
84
192
  type LocalizationConfigInput = z.input<typeof LocalizationConfigSchema>;
85
193
  type LocalizationConfigOutput = z.output<typeof LocalizationConfigSchema>;
@@ -94,7 +202,11 @@ interface ConfigLoadResult {
94
202
  */
95
203
  declare function loadConfig(cwd?: string, overrides?: Partial<LocalizationConfig>): Promise<ConfigLoadResult>;
96
204
  /**
97
- * Writes a default config file to the project root.
205
+ * Writes a fully-populated default config file to the project root.
206
+ *
207
+ * Every supported configuration field is included so users can see all
208
+ * available options and edit them directly. Fields that are optional/unused
209
+ * are set to their default values or left as empty strings / empty arrays.
98
210
  */
99
211
  declare function writeDefaultConfig(cwd?: string, framework?: string): string;
100
212
  /** Validate an existing config file */
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ declare const LocalizationConfigSchema: z.ZodObject<{
10
10
  keyPrefix: z.ZodOptional<z.ZodString>;
11
11
  namespaces: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
12
12
  ignorePatterns: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
13
+ includePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
13
14
  incrementalCache: z.ZodDefault<z.ZodBoolean>;
14
15
  cacheDir: z.ZodDefault<z.ZodString>;
15
16
  aws: z.ZodNullable<z.ZodOptional<z.ZodObject<{
@@ -38,6 +39,91 @@ declare const LocalizationConfigSchema: z.ZodObject<{
38
39
  profile?: string | undefined;
39
40
  }>>>;
40
41
  plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
42
+ /** Codemod behaviour overrides — all fields are optional. */
43
+ codemods: z.ZodOptional<z.ZodObject<{
44
+ /**
45
+ * The npm package to import from.
46
+ * React default: "react-i18next"
47
+ * Vue default: "vue-i18n"
48
+ * Angular default: "@ngx-translate/core"
49
+ */
50
+ importPackage: z.ZodOptional<z.ZodString>;
51
+ /**
52
+ * The hook / function name to import and call.
53
+ * React default: "useTranslation"
54
+ * Vue default: "useI18n"
55
+ */
56
+ hookName: z.ZodOptional<z.ZodString>;
57
+ /**
58
+ * The translation function identifier returned by the hook (default: "t").
59
+ */
60
+ translationFunction: z.ZodOptional<z.ZodString>;
61
+ /**
62
+ * Namespace to pass as the first argument to the hook call.
63
+ * When omitted no namespace argument is injected.
64
+ */
65
+ namespace: z.ZodOptional<z.ZodString>;
66
+ /**
67
+ * How the translation accessor is written in generated code.
68
+ * "function" (default) — t('key')
69
+ * "bracket" — t['key']
70
+ */
71
+ accessorStyle: z.ZodOptional<z.ZodEnum<["function", "bracket"]>>;
72
+ }, "strip", z.ZodTypeAny, {
73
+ importPackage?: string | undefined;
74
+ hookName?: string | undefined;
75
+ translationFunction?: string | undefined;
76
+ namespace?: string | undefined;
77
+ accessorStyle?: "function" | "bracket" | undefined;
78
+ }, {
79
+ importPackage?: string | undefined;
80
+ hookName?: string | undefined;
81
+ translationFunction?: string | undefined;
82
+ namespace?: string | undefined;
83
+ accessorStyle?: "function" | "bracket" | undefined;
84
+ }>>;
85
+ /**
86
+ * Locale file layout.
87
+ * "nested" (default) — one file per language + namespace:
88
+ * locales/en/common.json, locales/fr/dashboard.json
89
+ * "flat" — one file per language, all keys merged:
90
+ * locales/en.json, locales/fr.json
91
+ */
92
+ localeStructure: z.ZodDefault<z.ZodEnum<["nested", "flat"]>>;
93
+ /**
94
+ * Controls the format of generated locale keys for hardcoded text found
95
+ * during scanning.
96
+ *
97
+ * "path" (default) — hierarchical dot-notation derived from file path + text:
98
+ * src/pages/settings/SettingsPage.tsx + "Save Changes"
99
+ * → "settings.settings_page.save_changes"
100
+ *
101
+ * "screaming_snake" — UPPER_SNAKE_CASE derived solely from the text value.
102
+ * The key equals the value converted to UPPER_SNAKE_CASE:
103
+ * "Save Changes" → key: "SAVE_CHANGES", value: "Save Changes"
104
+ * "Max Count" → key: "MAX_COUNT", value: "Max Count"
105
+ * This matches the pattern used by staticKeys and is ideal for
106
+ * projects that use constant-style i18n keys.
107
+ */
108
+ keyStyle: z.ZodDefault<z.ZodEnum<["path", "screaming_snake"]>>;
109
+ /**
110
+ * Static locale keys to inject into every generated locale file.
111
+ * These key/value pairs are merged with scanned keys and written to all
112
+ * languages (defaultLanguage + targetLanguages).
113
+ *
114
+ * Example:
115
+ * "staticKeys": {
116
+ * "MAX_COUNT": "Max Count",
117
+ * "ALLOWED": "Allowed",
118
+ * "DISABLED": "Disabled",
119
+ * "UNLIMITED": "Unlimited"
120
+ * }
121
+ *
122
+ * In nested mode these keys are placed in the default namespace file
123
+ * (translation.json / common.json). In flat mode they are merged at the
124
+ * top level of the single per-language file.
125
+ */
126
+ staticKeys: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
41
127
  }, "strip", z.ZodTypeAny, {
42
128
  framework: "react" | "react-cra" | "react-vite" | "react-nextjs" | "angular" | "angular-ngx" | "angular-i18n" | "vue" | "vue-i18n" | "jquery" | "vanilla-js" | "jsp" | "unknown";
43
129
  defaultLanguage: string;
@@ -48,8 +134,11 @@ declare const LocalizationConfigSchema: z.ZodObject<{
48
134
  incrementalCache: boolean;
49
135
  cacheDir: string;
50
136
  plugins: string[];
137
+ localeStructure: "flat" | "nested";
138
+ keyStyle: "path" | "screaming_snake";
51
139
  keyPrefix?: string | undefined;
52
140
  namespaces?: string[] | undefined;
141
+ includePatterns?: string[] | undefined;
53
142
  aws?: {
54
143
  region: string;
55
144
  bucket: string;
@@ -59,6 +148,14 @@ declare const LocalizationConfigSchema: z.ZodObject<{
59
148
  legacyCdnPattern?: string | undefined;
60
149
  profile?: string | undefined;
61
150
  } | null | undefined;
151
+ codemods?: {
152
+ importPackage?: string | undefined;
153
+ hookName?: string | undefined;
154
+ translationFunction?: string | undefined;
155
+ namespace?: string | undefined;
156
+ accessorStyle?: "function" | "bracket" | undefined;
157
+ } | undefined;
158
+ staticKeys?: Record<string, string> | undefined;
62
159
  }, {
63
160
  framework?: "react" | "react-cra" | "react-vite" | "react-nextjs" | "angular" | "angular-ngx" | "angular-i18n" | "vue" | "vue-i18n" | "jquery" | "vanilla-js" | "jsp" | "unknown" | undefined;
64
161
  defaultLanguage?: string | undefined;
@@ -68,6 +165,7 @@ declare const LocalizationConfigSchema: z.ZodObject<{
68
165
  keyPrefix?: string | undefined;
69
166
  namespaces?: string[] | undefined;
70
167
  ignorePatterns?: string[] | undefined;
168
+ includePatterns?: string[] | undefined;
71
169
  incrementalCache?: boolean | undefined;
72
170
  cacheDir?: string | undefined;
73
171
  aws?: {
@@ -80,6 +178,16 @@ declare const LocalizationConfigSchema: z.ZodObject<{
80
178
  profile?: string | undefined;
81
179
  } | null | undefined;
82
180
  plugins?: string[] | undefined;
181
+ codemods?: {
182
+ importPackage?: string | undefined;
183
+ hookName?: string | undefined;
184
+ translationFunction?: string | undefined;
185
+ namespace?: string | undefined;
186
+ accessorStyle?: "function" | "bracket" | undefined;
187
+ } | undefined;
188
+ localeStructure?: "flat" | "nested" | undefined;
189
+ keyStyle?: "path" | "screaming_snake" | undefined;
190
+ staticKeys?: Record<string, string> | undefined;
83
191
  }>;
84
192
  type LocalizationConfigInput = z.input<typeof LocalizationConfigSchema>;
85
193
  type LocalizationConfigOutput = z.output<typeof LocalizationConfigSchema>;
@@ -94,7 +202,11 @@ interface ConfigLoadResult {
94
202
  */
95
203
  declare function loadConfig(cwd?: string, overrides?: Partial<LocalizationConfig>): Promise<ConfigLoadResult>;
96
204
  /**
97
- * Writes a default config file to the project root.
205
+ * Writes a fully-populated default config file to the project root.
206
+ *
207
+ * Every supported configuration field is included so users can see all
208
+ * available options and edit them directly. Fields that are optional/unused
209
+ * are set to their default values or left as empty strings / empty arrays.
98
210
  */
99
211
  declare function writeDefaultConfig(cwd?: string, framework?: string): string;
100
212
  /** Validate an existing config file */
package/dist/index.js CHANGED
@@ -63,6 +63,36 @@ var FrameworkSchema = import_zod.z.enum([
63
63
  "jsp",
64
64
  "unknown"
65
65
  ]);
66
+ var CodemodConfigSchema = import_zod.z.object({
67
+ /**
68
+ * The npm package to import from.
69
+ * React default: "react-i18next"
70
+ * Vue default: "vue-i18n"
71
+ * Angular default: "@ngx-translate/core"
72
+ */
73
+ importPackage: import_zod.z.string().optional(),
74
+ /**
75
+ * The hook / function name to import and call.
76
+ * React default: "useTranslation"
77
+ * Vue default: "useI18n"
78
+ */
79
+ hookName: import_zod.z.string().optional(),
80
+ /**
81
+ * The translation function identifier returned by the hook (default: "t").
82
+ */
83
+ translationFunction: import_zod.z.string().optional(),
84
+ /**
85
+ * Namespace to pass as the first argument to the hook call.
86
+ * When omitted no namespace argument is injected.
87
+ */
88
+ namespace: import_zod.z.string().optional(),
89
+ /**
90
+ * How the translation accessor is written in generated code.
91
+ * "function" (default) — t('key')
92
+ * "bracket" — t['key']
93
+ */
94
+ accessorStyle: import_zod.z.enum(["function", "bracket"]).optional()
95
+ });
66
96
  var LocalizationConfigSchema = import_zod.z.object({
67
97
  framework: FrameworkSchema.default("unknown"),
68
98
  defaultLanguage: import_zod.z.string().default("en"),
@@ -72,10 +102,55 @@ var LocalizationConfigSchema = import_zod.z.object({
72
102
  keyPrefix: import_zod.z.string().optional(),
73
103
  namespaces: import_zod.z.array(import_zod.z.string()).optional(),
74
104
  ignorePatterns: import_zod.z.array(import_zod.z.string()).default(["node_modules", "dist", ".git", "coverage"]),
105
+ includePatterns: import_zod.z.array(import_zod.z.string()).optional(),
75
106
  incrementalCache: import_zod.z.boolean().default(true),
76
107
  cacheDir: import_zod.z.string().default(".ai-localize-cache"),
77
108
  aws: AwsConfigSchema.optional().nullable(),
78
- plugins: import_zod.z.array(import_zod.z.string()).default([])
109
+ plugins: import_zod.z.array(import_zod.z.string()).default([]),
110
+ /** Codemod behaviour overrides — all fields are optional. */
111
+ codemods: CodemodConfigSchema.optional(),
112
+ /**
113
+ * Locale file layout.
114
+ * "nested" (default) — one file per language + namespace:
115
+ * locales/en/common.json, locales/fr/dashboard.json
116
+ * "flat" — one file per language, all keys merged:
117
+ * locales/en.json, locales/fr.json
118
+ */
119
+ localeStructure: import_zod.z.enum(["nested", "flat"]).default("nested"),
120
+ /**
121
+ * Controls the format of generated locale keys for hardcoded text found
122
+ * during scanning.
123
+ *
124
+ * "path" (default) — hierarchical dot-notation derived from file path + text:
125
+ * src/pages/settings/SettingsPage.tsx + "Save Changes"
126
+ * → "settings.settings_page.save_changes"
127
+ *
128
+ * "screaming_snake" — UPPER_SNAKE_CASE derived solely from the text value.
129
+ * The key equals the value converted to UPPER_SNAKE_CASE:
130
+ * "Save Changes" → key: "SAVE_CHANGES", value: "Save Changes"
131
+ * "Max Count" → key: "MAX_COUNT", value: "Max Count"
132
+ * This matches the pattern used by staticKeys and is ideal for
133
+ * projects that use constant-style i18n keys.
134
+ */
135
+ keyStyle: import_zod.z.enum(["path", "screaming_snake"]).default("path"),
136
+ /**
137
+ * Static locale keys to inject into every generated locale file.
138
+ * These key/value pairs are merged with scanned keys and written to all
139
+ * languages (defaultLanguage + targetLanguages).
140
+ *
141
+ * Example:
142
+ * "staticKeys": {
143
+ * "MAX_COUNT": "Max Count",
144
+ * "ALLOWED": "Allowed",
145
+ * "DISABLED": "Disabled",
146
+ * "UNLIMITED": "Unlimited"
147
+ * }
148
+ *
149
+ * In nested mode these keys are placed in the default namespace file
150
+ * (translation.json / common.json). In flat mode they are merged at the
151
+ * top level of the single per-language file.
152
+ */
153
+ staticKeys: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional()
79
154
  });
80
155
 
81
156
  // src/loader.ts
@@ -136,22 +211,120 @@ function extractAwsFromEnv() {
136
211
  function writeDefaultConfig(cwd = process.cwd(), framework = "unknown") {
137
212
  const configPath = path.join(cwd, import_ai_localize_shared.CONFIG_FILE_NAME);
138
213
  const defaultConfig = {
214
+ // ── Framework ────────────────────────────────────────────────────────────
215
+ // Detected automatically by ai-localize init.
216
+ // Values: "react" | "react-cra" | "react-vite" | "react-nextjs"
217
+ // | "angular" | "angular-ngx" | "angular-i18n"
218
+ // | "vue" | "vue-i18n" | "jquery" | "vanilla-js" | "jsp" | "unknown"
139
219
  framework,
220
+ // ── Languages ────────────────────────────────────────────────────────────
221
+ // Source language — used as reference for missing-translation detection.
140
222
  defaultLanguage: "en",
223
+ // Additional languages to generate locale files for.
224
+ // All target files are seeded with the source value (no blank entries).
141
225
  targetLanguages: ["fr", "de"],
226
+ // ── Directories ──────────────────────────────────────────────────────────
227
+ // Root of your source code — scanning starts here.
228
+ // Keys are generated relative to this directory.
142
229
  sourceDir: "src",
230
+ // Where locale JSON files are written.
143
231
  localesDir: "locales",
232
+ // ── Locale file layout ───────────────────────────────────────────────────
233
+ // "nested" (default): one file per language + namespace
234
+ // locales/en/common.json, locales/en/dashboard.json
235
+ // "flat": one file per language, all keys merged
236
+ // locales/en.json (non-default namespace keys are prefixed: "dashboard.header.title")
237
+ localeStructure: "nested",
238
+ // ── Key style ────────────────────────────────────────────────────────────
239
+ // Controls the format of locale keys generated for scanned hardcoded text.
240
+ //
241
+ // "path" (default) — hierarchical dot-notation derived from file path + text:
242
+ // src/pages/settings/SettingsPage.tsx + "Save Changes"
243
+ // → "settings.settings_page.save_changes"
244
+ //
245
+ // "screaming_snake" — UPPER_SNAKE_CASE derived solely from the text value.
246
+ // The key equals the value converted to UPPER_SNAKE_CASE:
247
+ // "Save Changes" → key: "SAVE_CHANGES", value: "Save Changes"
248
+ // "Max Count" → key: "MAX_COUNT", value: "Max Count"
249
+ // This mirrors the staticKeys pattern and is ideal for projects that
250
+ // prefer constant-style i18n keys.
251
+ keyStyle: "path",
252
+ // ── Scan filtering ───────────────────────────────────────────────────────
253
+ // Directories/patterns to skip during scanning.
144
254
  ignorePatterns: ["node_modules", "dist", ".git", "coverage"],
255
+ // Optional: only scan files matching these glob patterns.
256
+ // If empty/omitted, all files under sourceDir are scanned.
257
+ // Example: ["src/components/**/*.tsx", "src/pages/**/*.tsx"]
258
+ includePatterns: [],
259
+ // ── Key generation ───────────────────────────────────────────────────────
260
+ // Optional prefix prepended to every generated key.
261
+ // Example: "myapp" → "myapp.components.button.save"
262
+ keyPrefix: "",
263
+ // Optional explicit list of namespace names.
264
+ // If omitted, namespace is derived from the first path segment after sourceDir.
265
+ namespaces: [],
266
+ // ── Static keys ──────────────────────────────────────────────────────────
267
+ // Key/value pairs to inject into every generated locale file regardless of
268
+ // what the scanner finds. Useful for enum labels, status codes, and other
269
+ // constant strings that are not hardcoded in source files.
270
+ //
271
+ // In nested mode these keys land in the default namespace file
272
+ // (translation.json). In flat mode they are merged at the top level.
273
+ //
274
+ // Example:
275
+ // "staticKeys": {
276
+ // "MAX_COUNT": "Max Count",
277
+ // "ALLOWED": "Allowed",
278
+ // "DISABLED": "Disabled",
279
+ // "UNLIMITED": "Unlimited"
280
+ // }
281
+ staticKeys: {},
282
+ // ── Incremental scanning ─────────────────────────────────────────────────
283
+ // Cache file hashes between runs — only re-scan changed files.
145
284
  incrementalCache: true,
146
285
  cacheDir: ".ai-localize-cache",
286
+ // ── Codemod behaviour ────────────────────────────────────────────────────
287
+ // Controls what the codemod injects when wrapping hardcoded strings.
288
+ //
289
+ // importPackage: npm package name OR project-relative path to the hook file.
290
+ // npm package: "react-i18next"
291
+ // local hook path: "src/hooks/useTranslation" (path relative to project root)
292
+ // The codemod computes the correct relative import path per file automatically.
293
+ //
294
+ // hookName: the hook function to import and call.
295
+ // Default: "useTranslation"
296
+ //
297
+ // translationFunction: the accessor destructured from the hook.
298
+ // Default: "t"
299
+ //
300
+ // namespace: passed as argument to the hook, e.g. useTranslation("common").
301
+ // Leave empty to omit the argument.
302
+ //
303
+ // accessorStyle:
304
+ // "function" (default) → t('key')
305
+ // "bracket" → t['key']
306
+ codemods: {
307
+ importPackage: "react-i18next",
308
+ hookName: "useTranslation",
309
+ translationFunction: "t",
310
+ namespace: "",
311
+ accessorStyle: "function"
312
+ },
313
+ // ── AWS / CloudFront (optional) ──────────────────────────────────────────
314
+ // Required only for CDN migration commands (migrate-cdn, upload-assets, replace-cdn).
315
+ // Leave all fields empty if not using CDN migration.
316
+ // Credentials can also be supplied via environment variables (see docs).
147
317
  aws: {
148
318
  region: "us-east-1",
149
319
  bucket: "",
150
320
  distributionId: "",
151
321
  cdnBaseUrl: "",
152
322
  legacyCdnPattern: "",
153
- assetsPrefix: "assets"
323
+ assetsPrefix: "assets",
324
+ profile: ""
154
325
  },
326
+ // ── Plugins ──────────────────────────────────────────────────────────────
327
+ // Paths to custom plugin modules. See docs/PLUGIN_SYSTEM.md for details.
155
328
  plugins: []
156
329
  };
157
330
  fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n", "utf-8");
package/dist/index.mjs CHANGED
@@ -24,6 +24,36 @@ var FrameworkSchema = z.enum([
24
24
  "jsp",
25
25
  "unknown"
26
26
  ]);
27
+ var CodemodConfigSchema = z.object({
28
+ /**
29
+ * The npm package to import from.
30
+ * React default: "react-i18next"
31
+ * Vue default: "vue-i18n"
32
+ * Angular default: "@ngx-translate/core"
33
+ */
34
+ importPackage: z.string().optional(),
35
+ /**
36
+ * The hook / function name to import and call.
37
+ * React default: "useTranslation"
38
+ * Vue default: "useI18n"
39
+ */
40
+ hookName: z.string().optional(),
41
+ /**
42
+ * The translation function identifier returned by the hook (default: "t").
43
+ */
44
+ translationFunction: z.string().optional(),
45
+ /**
46
+ * Namespace to pass as the first argument to the hook call.
47
+ * When omitted no namespace argument is injected.
48
+ */
49
+ namespace: z.string().optional(),
50
+ /**
51
+ * How the translation accessor is written in generated code.
52
+ * "function" (default) — t('key')
53
+ * "bracket" — t['key']
54
+ */
55
+ accessorStyle: z.enum(["function", "bracket"]).optional()
56
+ });
27
57
  var LocalizationConfigSchema = z.object({
28
58
  framework: FrameworkSchema.default("unknown"),
29
59
  defaultLanguage: z.string().default("en"),
@@ -33,10 +63,55 @@ var LocalizationConfigSchema = z.object({
33
63
  keyPrefix: z.string().optional(),
34
64
  namespaces: z.array(z.string()).optional(),
35
65
  ignorePatterns: z.array(z.string()).default(["node_modules", "dist", ".git", "coverage"]),
66
+ includePatterns: z.array(z.string()).optional(),
36
67
  incrementalCache: z.boolean().default(true),
37
68
  cacheDir: z.string().default(".ai-localize-cache"),
38
69
  aws: AwsConfigSchema.optional().nullable(),
39
- plugins: z.array(z.string()).default([])
70
+ plugins: z.array(z.string()).default([]),
71
+ /** Codemod behaviour overrides — all fields are optional. */
72
+ codemods: CodemodConfigSchema.optional(),
73
+ /**
74
+ * Locale file layout.
75
+ * "nested" (default) — one file per language + namespace:
76
+ * locales/en/common.json, locales/fr/dashboard.json
77
+ * "flat" — one file per language, all keys merged:
78
+ * locales/en.json, locales/fr.json
79
+ */
80
+ localeStructure: z.enum(["nested", "flat"]).default("nested"),
81
+ /**
82
+ * Controls the format of generated locale keys for hardcoded text found
83
+ * during scanning.
84
+ *
85
+ * "path" (default) — hierarchical dot-notation derived from file path + text:
86
+ * src/pages/settings/SettingsPage.tsx + "Save Changes"
87
+ * → "settings.settings_page.save_changes"
88
+ *
89
+ * "screaming_snake" — UPPER_SNAKE_CASE derived solely from the text value.
90
+ * The key equals the value converted to UPPER_SNAKE_CASE:
91
+ * "Save Changes" → key: "SAVE_CHANGES", value: "Save Changes"
92
+ * "Max Count" → key: "MAX_COUNT", value: "Max Count"
93
+ * This matches the pattern used by staticKeys and is ideal for
94
+ * projects that use constant-style i18n keys.
95
+ */
96
+ keyStyle: z.enum(["path", "screaming_snake"]).default("path"),
97
+ /**
98
+ * Static locale keys to inject into every generated locale file.
99
+ * These key/value pairs are merged with scanned keys and written to all
100
+ * languages (defaultLanguage + targetLanguages).
101
+ *
102
+ * Example:
103
+ * "staticKeys": {
104
+ * "MAX_COUNT": "Max Count",
105
+ * "ALLOWED": "Allowed",
106
+ * "DISABLED": "Disabled",
107
+ * "UNLIMITED": "Unlimited"
108
+ * }
109
+ *
110
+ * In nested mode these keys are placed in the default namespace file
111
+ * (translation.json / common.json). In flat mode they are merged at the
112
+ * top level of the single per-language file.
113
+ */
114
+ staticKeys: z.record(z.string(), z.string()).optional()
40
115
  });
41
116
 
42
117
  // src/loader.ts
@@ -97,22 +172,120 @@ function extractAwsFromEnv() {
97
172
  function writeDefaultConfig(cwd = process.cwd(), framework = "unknown") {
98
173
  const configPath = path.join(cwd, CONFIG_FILE_NAME);
99
174
  const defaultConfig = {
175
+ // ── Framework ────────────────────────────────────────────────────────────
176
+ // Detected automatically by ai-localize init.
177
+ // Values: "react" | "react-cra" | "react-vite" | "react-nextjs"
178
+ // | "angular" | "angular-ngx" | "angular-i18n"
179
+ // | "vue" | "vue-i18n" | "jquery" | "vanilla-js" | "jsp" | "unknown"
100
180
  framework,
181
+ // ── Languages ────────────────────────────────────────────────────────────
182
+ // Source language — used as reference for missing-translation detection.
101
183
  defaultLanguage: "en",
184
+ // Additional languages to generate locale files for.
185
+ // All target files are seeded with the source value (no blank entries).
102
186
  targetLanguages: ["fr", "de"],
187
+ // ── Directories ──────────────────────────────────────────────────────────
188
+ // Root of your source code — scanning starts here.
189
+ // Keys are generated relative to this directory.
103
190
  sourceDir: "src",
191
+ // Where locale JSON files are written.
104
192
  localesDir: "locales",
193
+ // ── Locale file layout ───────────────────────────────────────────────────
194
+ // "nested" (default): one file per language + namespace
195
+ // locales/en/common.json, locales/en/dashboard.json
196
+ // "flat": one file per language, all keys merged
197
+ // locales/en.json (non-default namespace keys are prefixed: "dashboard.header.title")
198
+ localeStructure: "nested",
199
+ // ── Key style ────────────────────────────────────────────────────────────
200
+ // Controls the format of locale keys generated for scanned hardcoded text.
201
+ //
202
+ // "path" (default) — hierarchical dot-notation derived from file path + text:
203
+ // src/pages/settings/SettingsPage.tsx + "Save Changes"
204
+ // → "settings.settings_page.save_changes"
205
+ //
206
+ // "screaming_snake" — UPPER_SNAKE_CASE derived solely from the text value.
207
+ // The key equals the value converted to UPPER_SNAKE_CASE:
208
+ // "Save Changes" → key: "SAVE_CHANGES", value: "Save Changes"
209
+ // "Max Count" → key: "MAX_COUNT", value: "Max Count"
210
+ // This mirrors the staticKeys pattern and is ideal for projects that
211
+ // prefer constant-style i18n keys.
212
+ keyStyle: "path",
213
+ // ── Scan filtering ───────────────────────────────────────────────────────
214
+ // Directories/patterns to skip during scanning.
105
215
  ignorePatterns: ["node_modules", "dist", ".git", "coverage"],
216
+ // Optional: only scan files matching these glob patterns.
217
+ // If empty/omitted, all files under sourceDir are scanned.
218
+ // Example: ["src/components/**/*.tsx", "src/pages/**/*.tsx"]
219
+ includePatterns: [],
220
+ // ── Key generation ───────────────────────────────────────────────────────
221
+ // Optional prefix prepended to every generated key.
222
+ // Example: "myapp" → "myapp.components.button.save"
223
+ keyPrefix: "",
224
+ // Optional explicit list of namespace names.
225
+ // If omitted, namespace is derived from the first path segment after sourceDir.
226
+ namespaces: [],
227
+ // ── Static keys ──────────────────────────────────────────────────────────
228
+ // Key/value pairs to inject into every generated locale file regardless of
229
+ // what the scanner finds. Useful for enum labels, status codes, and other
230
+ // constant strings that are not hardcoded in source files.
231
+ //
232
+ // In nested mode these keys land in the default namespace file
233
+ // (translation.json). In flat mode they are merged at the top level.
234
+ //
235
+ // Example:
236
+ // "staticKeys": {
237
+ // "MAX_COUNT": "Max Count",
238
+ // "ALLOWED": "Allowed",
239
+ // "DISABLED": "Disabled",
240
+ // "UNLIMITED": "Unlimited"
241
+ // }
242
+ staticKeys: {},
243
+ // ── Incremental scanning ─────────────────────────────────────────────────
244
+ // Cache file hashes between runs — only re-scan changed files.
106
245
  incrementalCache: true,
107
246
  cacheDir: ".ai-localize-cache",
247
+ // ── Codemod behaviour ────────────────────────────────────────────────────
248
+ // Controls what the codemod injects when wrapping hardcoded strings.
249
+ //
250
+ // importPackage: npm package name OR project-relative path to the hook file.
251
+ // npm package: "react-i18next"
252
+ // local hook path: "src/hooks/useTranslation" (path relative to project root)
253
+ // The codemod computes the correct relative import path per file automatically.
254
+ //
255
+ // hookName: the hook function to import and call.
256
+ // Default: "useTranslation"
257
+ //
258
+ // translationFunction: the accessor destructured from the hook.
259
+ // Default: "t"
260
+ //
261
+ // namespace: passed as argument to the hook, e.g. useTranslation("common").
262
+ // Leave empty to omit the argument.
263
+ //
264
+ // accessorStyle:
265
+ // "function" (default) → t('key')
266
+ // "bracket" → t['key']
267
+ codemods: {
268
+ importPackage: "react-i18next",
269
+ hookName: "useTranslation",
270
+ translationFunction: "t",
271
+ namespace: "",
272
+ accessorStyle: "function"
273
+ },
274
+ // ── AWS / CloudFront (optional) ──────────────────────────────────────────
275
+ // Required only for CDN migration commands (migrate-cdn, upload-assets, replace-cdn).
276
+ // Leave all fields empty if not using CDN migration.
277
+ // Credentials can also be supplied via environment variables (see docs).
108
278
  aws: {
109
279
  region: "us-east-1",
110
280
  bucket: "",
111
281
  distributionId: "",
112
282
  cdnBaseUrl: "",
113
283
  legacyCdnPattern: "",
114
- assetsPrefix: "assets"
284
+ assetsPrefix: "assets",
285
+ profile: ""
115
286
  },
287
+ // ── Plugins ──────────────────────────────────────────────────────────────
288
+ // Paths to custom plugin modules. See docs/PLUGIN_SYSTEM.md for details.
116
289
  plugins: []
117
290
  };
118
291
  fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + "\n", "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-localize-config",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "description": "Configuration loader and schema validator for ai-localize-core",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -16,7 +16,7 @@
16
16
  "zod": "^3.22.4",
17
17
  "cosmiconfig": "^9.0.0",
18
18
  "dotenv": "^16.4.1",
19
- "ai-localize-shared": "2.0.0"
19
+ "ai-localize-shared": "2.0.3"
20
20
  },
21
21
  "devDependencies": {
22
22
  "tsup": "^8.0.1",
package/src/loader.ts CHANGED
@@ -42,12 +42,12 @@ export async function loadConfig(
42
42
 
43
43
  try {
44
44
  const result = await explorer.search(cwd);
45
- if (result) {
45
+ if (result) {
46
46
  rawConfig = result.config as Record<string, unknown>;
47
47
  filePath = result.filepath;
48
48
  }
49
49
  } catch (err) {
50
- // Config not found - use defaults
50
+ // Config not found use defaults
51
51
  }
52
52
 
53
53
  // Merge env vars for AWS
@@ -69,12 +69,12 @@ export async function loadConfig(
69
69
  function extractAwsFromEnv(): Record<string, string> | null {
70
70
  const bucket = process.env.AI_LOCALIZE_S3_BUCKET || process.env.AWS_S3_BUCKET;
71
71
  const distributionId =
72
- process.env.AI_LOCALIZE_CF_DISTRIBUTION_ID || process.env.AWS_CF_DISTRIBUTION_ID;
72
+ process.env.AI_LOCALIZE_CF_DISTRIBUTION_ID || process.env.AWS_CF_DISTRIBUTION_ID;
73
73
 
74
74
  if (!bucket || !distributionId) return null;
75
75
 
76
76
  return {
77
- bucket,
77
+ bucket,
78
78
  distributionId,
79
79
  region: process.env.AWS_REGION || process.env.AI_LOCALIZE_AWS_REGION || 'us-east-1',
80
80
  cdnBaseUrl: process.env.AI_LOCALIZE_CDN_BASE_URL || '',
@@ -84,28 +84,146 @@ function extractAwsFromEnv(): Record<string, string> | null {
84
84
  }
85
85
 
86
86
  /**
87
- * Writes a default config file to the project root.
87
+ * Writes a fully-populated default config file to the project root.
88
+ *
89
+ * Every supported configuration field is included so users can see all
90
+ * available options and edit them directly. Fields that are optional/unused
91
+ * are set to their default values or left as empty strings / empty arrays.
88
92
  */
89
93
  export function writeDefaultConfig(cwd = process.cwd(), framework = 'unknown'): string {
90
94
  const configPath = path.join(cwd, CONFIG_FILE_NAME);
95
+
91
96
  const defaultConfig = {
97
+ // ── Framework ────────────────────────────────────────────────────────────
98
+ // Detected automatically by ai-localize init.
99
+ // Values: "react" | "react-cra" | "react-vite" | "react-nextjs"
100
+ // | "angular" | "angular-ngx" | "angular-i18n"
101
+ // | "vue" | "vue-i18n" | "jquery" | "vanilla-js" | "jsp" | "unknown"
92
102
  framework,
103
+
104
+ // ── Languages ────────────────────────────────────────────────────────────
105
+ // Source language — used as reference for missing-translation detection.
93
106
  defaultLanguage: 'en',
107
+
108
+ // Additional languages to generate locale files for.
109
+ // All target files are seeded with the source value (no blank entries).
94
110
  targetLanguages: ['fr', 'de'],
111
+
112
+ // ── Directories ──────────────────────────────────────────────────────────
113
+ // Root of your source code — scanning starts here.
114
+ // Keys are generated relative to this directory.
95
115
  sourceDir: 'src',
116
+
117
+ // Where locale JSON files are written.
96
118
  localesDir: 'locales',
119
+
120
+ // ── Locale file layout ───────────────────────────────────────────────────
121
+ // "nested" (default): one file per language + namespace
122
+ // locales/en/common.json, locales/en/dashboard.json
123
+ // "flat": one file per language, all keys merged
124
+ // locales/en.json (non-default namespace keys are prefixed: "dashboard.header.title")
125
+ localeStructure: 'nested',
126
+
127
+ // ── Key style ────────────────────────────────────────────────────────────
128
+ // Controls the format of locale keys generated for scanned hardcoded text.
129
+ //
130
+ // "path" (default) — hierarchical dot-notation derived from file path + text:
131
+ // src/pages/settings/SettingsPage.tsx + "Save Changes"
132
+ // → "settings.settings_page.save_changes"
133
+ //
134
+ // "screaming_snake" — UPPER_SNAKE_CASE derived solely from the text value.
135
+ // The key equals the value converted to UPPER_SNAKE_CASE:
136
+ // "Save Changes" → key: "SAVE_CHANGES", value: "Save Changes"
137
+ // "Max Count" → key: "MAX_COUNT", value: "Max Count"
138
+ // This mirrors the staticKeys pattern and is ideal for projects that
139
+ // prefer constant-style i18n keys.
140
+ keyStyle: 'path',
141
+
142
+ // ── Scan filtering ───────────────────────────────────────────────────────
143
+ // Directories/patterns to skip during scanning.
97
144
  ignorePatterns: ['node_modules', 'dist', '.git', 'coverage'],
145
+
146
+ // Optional: only scan files matching these glob patterns.
147
+ // If empty/omitted, all files under sourceDir are scanned.
148
+ // Example: ["src/components/**/*.tsx", "src/pages/**/*.tsx"]
149
+ includePatterns: [],
150
+
151
+ // ── Key generation ───────────────────────────────────────────────────────
152
+ // Optional prefix prepended to every generated key.
153
+ // Example: "myapp" → "myapp.components.button.save"
154
+ keyPrefix: '',
155
+
156
+ // Optional explicit list of namespace names.
157
+ // If omitted, namespace is derived from the first path segment after sourceDir.
158
+ namespaces: [],
159
+
160
+ // ── Static keys ──────────────────────────────────────────────────────────
161
+ // Key/value pairs to inject into every generated locale file regardless of
162
+ // what the scanner finds. Useful for enum labels, status codes, and other
163
+ // constant strings that are not hardcoded in source files.
164
+ //
165
+ // In nested mode these keys land in the default namespace file
166
+ // (translation.json). In flat mode they are merged at the top level.
167
+ //
168
+ // Example:
169
+ // "staticKeys": {
170
+ // "MAX_COUNT": "Max Count",
171
+ // "ALLOWED": "Allowed",
172
+ // "DISABLED": "Disabled",
173
+ // "UNLIMITED": "Unlimited"
174
+ // }
175
+ staticKeys: {},
176
+
177
+ // ── Incremental scanning ─────────────────────────────────────────────────
178
+ // Cache file hashes between runs — only re-scan changed files.
98
179
  incrementalCache: true,
99
180
  cacheDir: '.ai-localize-cache',
181
+
182
+ // ── Codemod behaviour ────────────────────────────────────────────────────
183
+ // Controls what the codemod injects when wrapping hardcoded strings.
184
+ //
185
+ // importPackage: npm package name OR project-relative path to the hook file.
186
+ // npm package: "react-i18next"
187
+ // local hook path: "src/hooks/useTranslation" (path relative to project root)
188
+ // The codemod computes the correct relative import path per file automatically.
189
+ //
190
+ // hookName: the hook function to import and call.
191
+ // Default: "useTranslation"
192
+ //
193
+ // translationFunction: the accessor destructured from the hook.
194
+ // Default: "t"
195
+ //
196
+ // namespace: passed as argument to the hook, e.g. useTranslation("common").
197
+ // Leave empty to omit the argument.
198
+ //
199
+ // accessorStyle:
200
+ // "function" (default) → t('key')
201
+ // "bracket" → t['key']
202
+ codemods: {
203
+ importPackage: 'react-i18next',
204
+ hookName: 'useTranslation',
205
+ translationFunction: 't',
206
+ namespace: '',
207
+ accessorStyle: 'function',
208
+ },
209
+
210
+ // ── AWS / CloudFront (optional) ──────────────────────────────────────────
211
+ // Required only for CDN migration commands (migrate-cdn, upload-assets, replace-cdn).
212
+ // Leave all fields empty if not using CDN migration.
213
+ // Credentials can also be supplied via environment variables (see docs).
100
214
  aws: {
101
- region: 'us-east-1',
102
- bucket: '',
215
+ region: 'us-east-1',
216
+ bucket: '',
103
217
  distributionId: '',
104
218
  cdnBaseUrl: '',
105
219
  legacyCdnPattern: '',
106
220
  assetsPrefix: 'assets',
221
+ profile: '',
107
222
  },
108
- plugins: [],
223
+
224
+ // ── Plugins ──────────────────────────────────────────────────────────────
225
+ // Paths to custom plugin modules. See docs/PLUGIN_SYSTEM.md for details.
226
+ plugins: [],
109
227
  };
110
228
 
111
229
  fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf-8');
@@ -119,19 +237,19 @@ export function validateConfig(configPath: string): {
119
237
  } {
120
238
  try {
121
239
  const raw = fs.readFileSync(configPath, 'utf-8');
122
- const parsed = JSON.parse(raw);
123
- LocalizationConfigSchema.parse(parsed);
240
+ const parsed = JSON.parse(raw);
241
+ LocalizationConfigSchema.parse(parsed);
124
242
  return { valid: true, errors: [] };
125
243
  } catch (err: unknown) {
126
244
  if (err && typeof err === 'object' && 'errors' in err) {
127
- const zodError = err as { errors: Array<{ path: unknown[]; message: string }> };
245
+ const zodError = err as { errors: Array<{ path: unknown[]; message: string }> };
128
246
  return {
129
247
  valid: false,
130
248
  errors: zodError.errors.map((e) => `${e.path.join('.')}: ${e.message}`),
131
249
  };
132
- }
250
+ }
133
251
  return {
134
- valid: false,
252
+ valid: false,
135
253
  errors: [err instanceof Error ? err.message : String(err)],
136
254
  };
137
255
  }
package/src/schema.ts CHANGED
@@ -12,7 +12,7 @@ const AwsConfigSchema = z.object({
12
12
 
13
13
  const FrameworkSchema = z.enum([
14
14
  'react',
15
- 'react-cra',
15
+ 'react-cra',
16
16
  'react-vite',
17
17
  'react-nextjs',
18
18
  'angular',
@@ -26,10 +26,45 @@ const FrameworkSchema = z.enum([
26
26
  'unknown',
27
27
  ]);
28
28
 
29
+ /**
30
+ * Optional codemod behaviour overrides.
31
+ * Controls which i18n library / hook / pipe the codemod injects.
32
+ */
33
+ const CodemodConfigSchema = z.object({
34
+ /**
35
+ * The npm package to import from.
36
+ * React default: "react-i18next"
37
+ * Vue default: "vue-i18n"
38
+ * Angular default: "@ngx-translate/core"
39
+ */
40
+ importPackage: z.string().optional(),
41
+ /**
42
+ * The hook / function name to import and call.
43
+ * React default: "useTranslation"
44
+ * Vue default: "useI18n"
45
+ */
46
+ hookName: z.string().optional(),
47
+ /**
48
+ * The translation function identifier returned by the hook (default: "t").
49
+ */
50
+ translationFunction: z.string().optional(),
51
+ /**
52
+ * Namespace to pass as the first argument to the hook call.
53
+ * When omitted no namespace argument is injected.
54
+ */
55
+ namespace: z.string().optional(),
56
+ /**
57
+ * How the translation accessor is written in generated code.
58
+ * "function" (default) — t('key')
59
+ * "bracket" — t['key']
60
+ */
61
+ accessorStyle: z.enum(['function', 'bracket']).optional(),
62
+ });
63
+
29
64
  export const LocalizationConfigSchema = z.object({
30
65
  framework: FrameworkSchema.default('unknown'),
31
66
  defaultLanguage: z.string().default('en'),
32
- targetLanguages: z.array(z.string()).default([]),
67
+ targetLanguages: z.array(z.string()).default([]),
33
68
  sourceDir: z.string().default('src'),
34
69
  localesDir: z.string().default('locales'),
35
70
  keyPrefix: z.string().optional(),
@@ -37,10 +72,55 @@ export const LocalizationConfigSchema = z.object({
37
72
  ignorePatterns: z
38
73
  .array(z.string())
39
74
  .default(['node_modules', 'dist', '.git', 'coverage']),
75
+ includePatterns: z.array(z.string()).optional(),
40
76
  incrementalCache: z.boolean().default(true),
41
77
  cacheDir: z.string().default('.ai-localize-cache'),
42
78
  aws: AwsConfigSchema.optional().nullable(),
43
79
  plugins: z.array(z.string()).default([]),
80
+ /** Codemod behaviour overrides — all fields are optional. */
81
+ codemods: CodemodConfigSchema.optional(),
82
+ /**
83
+ * Locale file layout.
84
+ * "nested" (default) — one file per language + namespace:
85
+ * locales/en/common.json, locales/fr/dashboard.json
86
+ * "flat" — one file per language, all keys merged:
87
+ * locales/en.json, locales/fr.json
88
+ */
89
+ localeStructure: z.enum(['nested', 'flat']).default('nested'),
90
+ /**
91
+ * Controls the format of generated locale keys for hardcoded text found
92
+ * during scanning.
93
+ *
94
+ * "path" (default) — hierarchical dot-notation derived from file path + text:
95
+ * src/pages/settings/SettingsPage.tsx + "Save Changes"
96
+ * → "settings.settings_page.save_changes"
97
+ *
98
+ * "screaming_snake" — UPPER_SNAKE_CASE derived solely from the text value.
99
+ * The key equals the value converted to UPPER_SNAKE_CASE:
100
+ * "Save Changes" → key: "SAVE_CHANGES", value: "Save Changes"
101
+ * "Max Count" → key: "MAX_COUNT", value: "Max Count"
102
+ * This matches the pattern used by staticKeys and is ideal for
103
+ * projects that use constant-style i18n keys.
104
+ */
105
+ keyStyle: z.enum(['path', 'screaming_snake']).default('path'),
106
+ /**
107
+ * Static locale keys to inject into every generated locale file.
108
+ * These key/value pairs are merged with scanned keys and written to all
109
+ * languages (defaultLanguage + targetLanguages).
110
+ *
111
+ * Example:
112
+ * "staticKeys": {
113
+ * "MAX_COUNT": "Max Count",
114
+ * "ALLOWED": "Allowed",
115
+ * "DISABLED": "Disabled",
116
+ * "UNLIMITED": "Unlimited"
117
+ * }
118
+ *
119
+ * In nested mode these keys are placed in the default namespace file
120
+ * (translation.json / common.json). In flat mode they are merged at the
121
+ * top level of the single per-language file.
122
+ */
123
+ staticKeys: z.record(z.string(), z.string()).optional(),
44
124
  });
45
125
 
46
126
  export type LocalizationConfigInput = z.input<typeof LocalizationConfigSchema>;