@umituz/react-native-localization 3.3.0 → 3.4.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-localization",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Generic localization system for React Native apps with i18n support",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -13,7 +13,10 @@
13
13
  "prepublishOnly": "node scripts/prepublish.js",
14
14
  "version:patch": "npm version patch -m 'chore: release v%s'",
15
15
  "version:minor": "npm version minor -m 'chore: release v%s'",
16
- "version:major": "npm version major -m 'chore: release v%s'"
16
+ "version:major": "npm version major -m 'chore: release v%s'",
17
+ "i18n:setup": "node scripts/setup-languages.js",
18
+ "i18n:sync": "node scripts/sync-translations.js",
19
+ "i18n:translate": "node scripts/translate-missing.js"
17
20
  },
18
21
  "keywords": [
19
22
  "react-native",
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+
4
+ /**
5
+ * Sync Translations Script
6
+ *
7
+ * Synchronizes translation keys from en-US.ts to all other language files.
8
+ * - Adds missing keys (with English values as placeholders)
9
+ * - Removes extra keys not in en-US
10
+ * - Maintains existing translations
11
+ *
12
+ * Usage: node scripts/sync-translations.js [locales-dir]
13
+ * Default: src/domains/localization/infrastructure/locales
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+
19
+ /**
20
+ * Parse TypeScript translation file and extract the object
21
+ */
22
+ function parseTypeScriptFile(filePath) {
23
+ const content = fs.readFileSync(filePath, 'utf8');
24
+
25
+ // Extract the object from "export default { ... };"
26
+ const match = content.match(/export\s+default\s+(\{[\s\S]*\});?\s*$/);
27
+ if (!match) {
28
+ throw new Error(`Could not parse TypeScript file: ${filePath}`);
29
+ }
30
+
31
+ const objectStr = match[1].replace(/;$/, '');
32
+
33
+ try {
34
+ // eslint-disable-next-line no-eval
35
+ return eval(`(${objectStr})`);
36
+ } catch (error) {
37
+ throw new Error(`Failed to parse object in ${filePath}: ${error.message}`);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Generate TypeScript file content from object
43
+ */
44
+ function generateTypeScriptContent(obj, langCode) {
45
+ const langName = getLangDisplayName(langCode);
46
+
47
+ function stringifyValue(value, indent = 2) {
48
+ if (typeof value === 'string') {
49
+ const escaped = value
50
+ .replace(/\\/g, '\\\\')
51
+ .replace(/"/g, '\\"')
52
+ .replace(/\n/g, '\\n');
53
+ return `"${escaped}"`;
54
+ }
55
+
56
+ if (Array.isArray(value)) {
57
+ if (value.length === 0) return '[]';
58
+ const items = value.map(v => stringifyValue(v, indent + 2));
59
+ return `[${items.join(', ')}]`;
60
+ }
61
+
62
+ if (typeof value === 'object' && value !== null) {
63
+ const spaces = ' '.repeat(indent);
64
+ const innerSpaces = ' '.repeat(indent + 2);
65
+ const entries = Object.entries(value)
66
+ .map(([k, v]) => {
67
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : `"${k}"`;
68
+ return `${innerSpaces}${key}: ${stringifyValue(v, indent + 2)}`;
69
+ })
70
+ .join(',\n');
71
+ return `{\n${entries},\n${spaces}}`;
72
+ }
73
+
74
+ return String(value);
75
+ }
76
+
77
+ const objString = stringifyValue(obj, 0);
78
+
79
+ return `/**
80
+ * ${langName} Translations
81
+ * Auto-synced from en-US.ts
82
+ */
83
+
84
+ export default ${objString};
85
+ `;
86
+ }
87
+
88
+ /**
89
+ * Get display name for language code
90
+ */
91
+ function getLangDisplayName(code) {
92
+ const names = {
93
+ 'ar-SA': 'Arabic (Saudi Arabia)',
94
+ 'bg-BG': 'Bulgarian',
95
+ 'cs-CZ': 'Czech',
96
+ 'da-DK': 'Danish',
97
+ 'de-DE': 'German',
98
+ 'el-GR': 'Greek',
99
+ 'en-AU': 'English (Australia)',
100
+ 'en-CA': 'English (Canada)',
101
+ 'en-GB': 'English (UK)',
102
+ 'en-US': 'English (US)',
103
+ 'es-ES': 'Spanish (Spain)',
104
+ 'es-MX': 'Spanish (Mexico)',
105
+ 'fi-FI': 'Finnish',
106
+ 'fr-CA': 'French (Canada)',
107
+ 'fr-FR': 'French (France)',
108
+ 'hi-IN': 'Hindi',
109
+ 'hr-HR': 'Croatian',
110
+ 'hu-HU': 'Hungarian',
111
+ 'id-ID': 'Indonesian',
112
+ 'it-IT': 'Italian',
113
+ 'ja-JP': 'Japanese',
114
+ 'ko-KR': 'Korean',
115
+ 'ms-MY': 'Malay',
116
+ 'nl-NL': 'Dutch',
117
+ 'no-NO': 'Norwegian',
118
+ 'pl-PL': 'Polish',
119
+ 'pt-BR': 'Portuguese (Brazil)',
120
+ 'pt-PT': 'Portuguese (Portugal)',
121
+ 'ro-RO': 'Romanian',
122
+ 'ru-RU': 'Russian',
123
+ 'sk-SK': 'Slovak',
124
+ 'sv-SE': 'Swedish',
125
+ 'th-TH': 'Thai',
126
+ 'tl-PH': 'Tagalog',
127
+ 'tr-TR': 'Turkish',
128
+ 'uk-UA': 'Ukrainian',
129
+ 'vi-VN': 'Vietnamese',
130
+ 'zh-CN': 'Chinese (Simplified)',
131
+ 'zh-TW': 'Chinese (Traditional)',
132
+ };
133
+ return names[code] || code;
134
+ }
135
+
136
+ /**
137
+ * Add missing keys from en-US to target
138
+ */
139
+ function addMissingKeys(enObj, targetObj, stats = { added: 0 }) {
140
+ for (const key in enObj) {
141
+ if (!Object.prototype.hasOwnProperty.call(targetObj, key)) {
142
+ targetObj[key] = enObj[key];
143
+ stats.added++;
144
+ } else if (
145
+ typeof enObj[key] === 'object' &&
146
+ enObj[key] !== null &&
147
+ !Array.isArray(enObj[key])
148
+ ) {
149
+ if (!targetObj[key] || typeof targetObj[key] !== 'object') {
150
+ targetObj[key] = {};
151
+ }
152
+ addMissingKeys(enObj[key], targetObj[key], stats);
153
+ }
154
+ }
155
+ return stats;
156
+ }
157
+
158
+ /**
159
+ * Remove extra keys not in en-US
160
+ */
161
+ function removeExtraKeys(enObj, targetObj, stats = { removed: 0 }) {
162
+ for (const key in targetObj) {
163
+ if (!Object.prototype.hasOwnProperty.call(enObj, key)) {
164
+ delete targetObj[key];
165
+ stats.removed++;
166
+ } else if (
167
+ typeof enObj[key] === 'object' &&
168
+ enObj[key] !== null &&
169
+ !Array.isArray(enObj[key]) &&
170
+ typeof targetObj[key] === 'object' &&
171
+ targetObj[key] !== null &&
172
+ !Array.isArray(targetObj[key])
173
+ ) {
174
+ removeExtraKeys(enObj[key], targetObj[key], stats);
175
+ }
176
+ }
177
+ return stats;
178
+ }
179
+
180
+ /**
181
+ * Sync a single language file
182
+ */
183
+ function syncLanguageFile(enUSPath, targetPath, langCode) {
184
+ const enUS = parseTypeScriptFile(enUSPath);
185
+ let target;
186
+
187
+ try {
188
+ target = parseTypeScriptFile(targetPath);
189
+ } catch {
190
+ target = {};
191
+ }
192
+
193
+ const addStats = { added: 0 };
194
+ const removeStats = { removed: 0 };
195
+
196
+ addMissingKeys(enUS, target, addStats);
197
+ removeExtraKeys(enUS, target, removeStats);
198
+
199
+ const changed = addStats.added > 0 || removeStats.removed > 0;
200
+
201
+ if (changed) {
202
+ const content = generateTypeScriptContent(target, langCode);
203
+ fs.writeFileSync(targetPath, content);
204
+ }
205
+
206
+ return {
207
+ added: addStats.added,
208
+ removed: removeStats.removed,
209
+ changed,
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Main function
215
+ */
216
+ function main() {
217
+ const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
218
+ const localesDir = path.resolve(process.cwd(), targetDir);
219
+
220
+ console.log('šŸš€ Starting translation synchronization...\n');
221
+ console.log(`šŸ“‚ Locales directory: ${localesDir}\n`);
222
+
223
+ if (!fs.existsSync(localesDir)) {
224
+ console.error(`āŒ Locales directory not found: ${localesDir}`);
225
+ process.exit(1);
226
+ }
227
+
228
+ const enUSPath = path.join(localesDir, 'en-US.ts');
229
+ if (!fs.existsSync(enUSPath)) {
230
+ console.error(`āŒ Base file not found: ${enUSPath}`);
231
+ process.exit(1);
232
+ }
233
+
234
+ // Find all language files
235
+ const files = fs.readdirSync(localesDir)
236
+ .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
237
+ .sort();
238
+
239
+ console.log(`šŸ“Š Languages to sync: ${files.length}\n`);
240
+
241
+ let totalAdded = 0;
242
+ let totalRemoved = 0;
243
+ let totalChanged = 0;
244
+
245
+ for (const file of files) {
246
+ const langCode = file.replace('.ts', '');
247
+ const targetPath = path.join(localesDir, file);
248
+
249
+ console.log(`šŸŒ Syncing ${langCode}...`);
250
+
251
+ const result = syncLanguageFile(enUSPath, targetPath, langCode);
252
+
253
+ if (result.changed) {
254
+ console.log(` āœļø +${result.added} keys, -${result.removed} keys`);
255
+ totalAdded += result.added;
256
+ totalRemoved += result.removed;
257
+ totalChanged++;
258
+ } else {
259
+ console.log(` āœ… Already synchronized`);
260
+ }
261
+ }
262
+
263
+ console.log(`\nšŸ“Š Summary:`);
264
+ console.log(` Languages processed: ${files.length}`);
265
+ console.log(` Files changed: ${totalChanged}`);
266
+ console.log(` Keys added: ${totalAdded}`);
267
+ console.log(` Keys removed: ${totalRemoved}`);
268
+
269
+ if (totalChanged > 0) {
270
+ console.log(`\nāœ… Synchronization completed!`);
271
+ console.log(` Next: Run 'node scripts/translate-missing.js' to translate new keys`);
272
+ } else {
273
+ console.log(`\nāœ… All languages were already synchronized!`);
274
+ }
275
+ }
276
+
277
+ main();
@@ -1,6 +1,396 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+
4
+ /**
5
+ * Translate Missing Script
6
+ *
7
+ * Translates missing strings from en-US.ts to all other language files
8
+ * using Google Translate free API.
9
+ *
10
+ * Usage: node scripts/translate-missing.js [locales-dir]
11
+ * Default: src/domains/localization/infrastructure/locales
12
+ */
13
+
1
14
  const fs = require('fs');
2
15
  const path = require('path');
16
+ const https = require('https');
17
+
18
+ // Language codes mapping for Google Translate API
19
+ const LANGUAGE_MAP = {
20
+ 'ar-SA': 'ar', // Arabic
21
+ 'bg-BG': 'bg', // Bulgarian
22
+ 'cs-CZ': 'cs', // Czech
23
+ 'da-DK': 'da', // Danish
24
+ 'de-DE': 'de', // German
25
+ 'el-GR': 'el', // Greek
26
+ 'en-AU': 'en', // English (skip)
27
+ 'en-CA': 'en', // English (skip)
28
+ 'en-GB': 'en', // English (skip)
29
+ 'es-ES': 'es', // Spanish
30
+ 'es-MX': 'es', // Spanish
31
+ 'fi-FI': 'fi', // Finnish
32
+ 'fr-CA': 'fr', // French
33
+ 'fr-FR': 'fr', // French
34
+ 'hi-IN': 'hi', // Hindi
35
+ 'hr-HR': 'hr', // Croatian
36
+ 'hu-HU': 'hu', // Hungarian
37
+ 'id-ID': 'id', // Indonesian
38
+ 'it-IT': 'it', // Italian
39
+ 'ja-JP': 'ja', // Japanese
40
+ 'ko-KR': 'ko', // Korean
41
+ 'ms-MY': 'ms', // Malay
42
+ 'nl-NL': 'nl', // Dutch
43
+ 'no-NO': 'no', // Norwegian
44
+ 'pl-PL': 'pl', // Polish
45
+ 'pt-BR': 'pt', // Portuguese
46
+ 'pt-PT': 'pt', // Portuguese
47
+ 'ro-RO': 'ro', // Romanian
48
+ 'ru-RU': 'ru', // Russian
49
+ 'sk-SK': 'sk', // Slovak
50
+ 'sv-SE': 'sv', // Swedish
51
+ 'th-TH': 'th', // Thai
52
+ 'tl-PH': 'tl', // Tagalog
53
+ 'tr-TR': 'tr', // Turkish
54
+ 'uk-UA': 'uk', // Ukrainian
55
+ 'vi-VN': 'vi', // Vietnamese
56
+ 'zh-CN': 'zh-CN', // Chinese Simplified
57
+ 'zh-TW': 'zh-TW', // Chinese Traditional
58
+ };
59
+
60
+ // Common English words that don't need translation
61
+ const SKIP_WORDS = new Set([
62
+ 'OK',
63
+ 'Email',
64
+ 'Google',
65
+ 'Apple',
66
+ 'Facebook',
67
+ 'Premium',
68
+ 'Pro',
69
+ 'Plus',
70
+ 'BPM',
71
+ ]);
72
+
73
+ /**
74
+ * Parse TypeScript translation file and extract the object
75
+ */
76
+ function parseTypeScriptFile(filePath) {
77
+ const content = fs.readFileSync(filePath, 'utf8');
78
+
79
+ // Extract the object from "export default { ... };"
80
+ const match = content.match(/export\s+default\s+(\{[\s\S]*\});?\s*$/);
81
+ if (!match) {
82
+ throw new Error(`Could not parse TypeScript file: ${filePath}`);
83
+ }
84
+
85
+ // Use eval to parse the object (safe since we control the files)
86
+ // Remove trailing semicolon if present
87
+ const objectStr = match[1].replace(/;$/, '');
88
+
89
+ try {
90
+ // eslint-disable-next-line no-eval
91
+ return eval(`(${objectStr})`);
92
+ } catch (error) {
93
+ throw new Error(`Failed to parse object in ${filePath}: ${error.message}`);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Generate TypeScript file content from object
99
+ */
100
+ function generateTypeScriptContent(obj, langCode) {
101
+ const langName = getLangDisplayName(langCode);
102
+
103
+ function stringifyValue(value, indent = 2) {
104
+ if (typeof value === 'string') {
105
+ // Escape quotes and handle multiline
106
+ const escaped = value
107
+ .replace(/\\/g, '\\\\')
108
+ .replace(/"/g, '\\"')
109
+ .replace(/\n/g, '\\n');
110
+ return `"${escaped}"`;
111
+ }
112
+
113
+ if (Array.isArray(value)) {
114
+ if (value.length === 0) return '[]';
115
+ const items = value.map(v => stringifyValue(v, indent + 2));
116
+ return `[${items.join(', ')}]`;
117
+ }
118
+
119
+ if (typeof value === 'object' && value !== null) {
120
+ const spaces = ' '.repeat(indent);
121
+ const innerSpaces = ' '.repeat(indent + 2);
122
+ const entries = Object.entries(value)
123
+ .map(([k, v]) => {
124
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : `"${k}"`;
125
+ return `${innerSpaces}${key}: ${stringifyValue(v, indent + 2)}`;
126
+ })
127
+ .join(',\n');
128
+ return `{\n${entries},\n${spaces}}`;
129
+ }
130
+
131
+ return String(value);
132
+ }
133
+
134
+ const objString = stringifyValue(obj, 0);
135
+
136
+ return `/**
137
+ * ${langName} Translations
138
+ * Auto-translated from en-US.ts
139
+ */
140
+
141
+ export default ${objString};
142
+ `;
143
+ }
144
+
145
+ /**
146
+ * Get display name for language code
147
+ */
148
+ function getLangDisplayName(code) {
149
+ const names = {
150
+ 'ar-SA': 'Arabic (Saudi Arabia)',
151
+ 'bg-BG': 'Bulgarian',
152
+ 'cs-CZ': 'Czech',
153
+ 'da-DK': 'Danish',
154
+ 'de-DE': 'German',
155
+ 'el-GR': 'Greek',
156
+ 'en-AU': 'English (Australia)',
157
+ 'en-CA': 'English (Canada)',
158
+ 'en-GB': 'English (UK)',
159
+ 'en-US': 'English (US)',
160
+ 'es-ES': 'Spanish (Spain)',
161
+ 'es-MX': 'Spanish (Mexico)',
162
+ 'fi-FI': 'Finnish',
163
+ 'fr-CA': 'French (Canada)',
164
+ 'fr-FR': 'French (France)',
165
+ 'hi-IN': 'Hindi',
166
+ 'hr-HR': 'Croatian',
167
+ 'hu-HU': 'Hungarian',
168
+ 'id-ID': 'Indonesian',
169
+ 'it-IT': 'Italian',
170
+ 'ja-JP': 'Japanese',
171
+ 'ko-KR': 'Korean',
172
+ 'ms-MY': 'Malay',
173
+ 'nl-NL': 'Dutch',
174
+ 'no-NO': 'Norwegian',
175
+ 'pl-PL': 'Polish',
176
+ 'pt-BR': 'Portuguese (Brazil)',
177
+ 'pt-PT': 'Portuguese (Portugal)',
178
+ 'ro-RO': 'Romanian',
179
+ 'ru-RU': 'Russian',
180
+ 'sk-SK': 'Slovak',
181
+ 'sv-SE': 'Swedish',
182
+ 'th-TH': 'Thai',
183
+ 'tl-PH': 'Tagalog',
184
+ 'tr-TR': 'Turkish',
185
+ 'uk-UA': 'Ukrainian',
186
+ 'vi-VN': 'Vietnamese',
187
+ 'zh-CN': 'Chinese (Simplified)',
188
+ 'zh-TW': 'Chinese (Traditional)',
189
+ };
190
+ return names[code] || code;
191
+ }
192
+
193
+ /**
194
+ * Simple Google Translate API call using free endpoint
195
+ */
196
+ async function translateText(text, targetLang) {
197
+ return new Promise((resolve) => {
198
+ if (SKIP_WORDS.has(text)) {
199
+ resolve(text);
200
+ return;
201
+ }
202
+
203
+ const encodedText = encodeURIComponent(text);
204
+ const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodedText}`;
205
+
206
+ https
207
+ .get(url, res => {
208
+ let data = '';
209
+ res.on('data', chunk => {
210
+ data += chunk;
211
+ });
212
+ res.on('end', () => {
213
+ try {
214
+ const parsed = JSON.parse(data);
215
+ const translated = parsed[0]
216
+ .map(item => item[0])
217
+ .join('')
218
+ .trim();
219
+ resolve(translated || text);
220
+ } catch (error) {
221
+ console.warn(` āš ļø Translation failed for "${text.substring(0, 30)}...": ${error.message}`);
222
+ resolve(text);
223
+ }
224
+ });
225
+ })
226
+ .on('error', err => {
227
+ console.warn(` āš ļø Network error: ${err.message}`);
228
+ resolve(text);
229
+ });
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Add delay between API calls to avoid rate limiting
235
+ */
236
+ function delay(ms) {
237
+ return new Promise(resolve => setTimeout(resolve, ms));
238
+ }
239
+
240
+ /**
241
+ * Check if a value needs translation (is still in English)
242
+ */
243
+ function needsTranslation(value, enValue) {
244
+ if (typeof value !== 'string') return false;
245
+ if (value === enValue) return true;
246
+ if (SKIP_WORDS.has(value)) return false;
247
+ return false;
248
+ }
249
+
250
+ /**
251
+ * Recursively translate object values
252
+ */
253
+ async function translateObject(enObj, targetObj, targetLang, path = '', stats = { count: 0 }) {
254
+ for (const key in enObj) {
255
+ const currentPath = path ? `${path}.${key}` : key;
256
+ const enValue = enObj[key];
257
+ const targetValue = targetObj[key];
258
+
259
+ if (Array.isArray(enValue)) {
260
+ // Handle arrays
261
+ if (!Array.isArray(targetValue)) {
262
+ targetObj[key] = [];
263
+ }
264
+ for (let i = 0; i < enValue.length; i++) {
265
+ if (typeof enValue[i] === 'string') {
266
+ if (needsTranslation(targetObj[key][i], enValue[i])) {
267
+ console.log(` šŸ”„ ${currentPath}[${i}]: "${enValue[i].substring(0, 40)}..."`);
268
+ targetObj[key][i] = await translateText(enValue[i], targetLang);
269
+ stats.count++;
270
+ await delay(200);
271
+ }
272
+ }
273
+ }
274
+ } else if (typeof enValue === 'object' && enValue !== null) {
275
+ // Handle nested objects
276
+ if (!targetObj[key] || typeof targetObj[key] !== 'object') {
277
+ targetObj[key] = {};
278
+ }
279
+ await translateObject(enValue, targetObj[key], targetLang, currentPath, stats);
280
+ } else if (typeof enValue === 'string') {
281
+ // Handle string values
282
+ if (needsTranslation(targetValue, enValue)) {
283
+ // Skip placeholders like {{count}}
284
+ if (enValue.includes('{{') && enValue.includes('}}')) {
285
+ // Translate but preserve placeholders
286
+ console.log(` šŸ”„ ${currentPath}: "${enValue.substring(0, 40)}${enValue.length > 40 ? '...' : ''}"`);
287
+ targetObj[key] = await translateText(enValue, targetLang);
288
+ stats.count++;
289
+ await delay(200);
290
+ } else {
291
+ console.log(` šŸ”„ ${currentPath}: "${enValue.substring(0, 40)}${enValue.length > 40 ? '...' : ''}"`);
292
+ targetObj[key] = await translateText(enValue, targetLang);
293
+ stats.count++;
294
+ await delay(200);
295
+ }
296
+ }
297
+ }
298
+ }
299
+
300
+ return stats.count;
301
+ }
302
+
303
+ /**
304
+ * Translate a single language file
305
+ */
306
+ async function translateLanguageFile(enUSPath, targetPath, langCode) {
307
+ const targetLang = LANGUAGE_MAP[langCode];
308
+
309
+ if (!targetLang) {
310
+ console.log(` āš ļø No language mapping for ${langCode}, skipping`);
311
+ return 0;
312
+ }
313
+
314
+ // Skip English variants
315
+ if (targetLang === 'en') {
316
+ console.log(` ā­ļø Skipping English variant: ${langCode}`);
317
+ return 0;
318
+ }
319
+
320
+ const enUS = parseTypeScriptFile(enUSPath);
321
+ let target;
322
+
323
+ try {
324
+ target = parseTypeScriptFile(targetPath);
325
+ } catch {
326
+ // If target file doesn't exist or is invalid, start fresh
327
+ target = {};
328
+ }
329
+
330
+ const stats = { count: 0 };
331
+ await translateObject(enUS, target, targetLang, '', stats);
332
+
333
+ if (stats.count > 0) {
334
+ const content = generateTypeScriptContent(target, langCode);
335
+ fs.writeFileSync(targetPath, content);
336
+ }
337
+
338
+ return stats.count;
339
+ }
340
+
341
+ /**
342
+ * Main function
343
+ */
344
+ async function main() {
345
+ const targetDir = process.argv[2] || 'src/domains/localization/infrastructure/locales';
346
+ const localesDir = path.resolve(process.cwd(), targetDir);
347
+
348
+ console.log('šŸš€ Starting automatic translation...\n');
349
+ console.log(`šŸ“‚ Locales directory: ${localesDir}\n`);
350
+
351
+ if (!fs.existsSync(localesDir)) {
352
+ console.error(`āŒ Locales directory not found: ${localesDir}`);
353
+ process.exit(1);
354
+ }
355
+
356
+ const enUSPath = path.join(localesDir, 'en-US.ts');
357
+ if (!fs.existsSync(enUSPath)) {
358
+ console.error(`āŒ Base file not found: ${enUSPath}`);
359
+ process.exit(1);
360
+ }
361
+
362
+ // Find all language files
363
+ const files = fs.readdirSync(localesDir)
364
+ .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== 'en-US.ts')
365
+ .sort();
366
+
367
+ console.log(`šŸ“Š Languages to translate: ${files.length}`);
368
+ console.log('⚔ Running with 200ms delay between API calls\n');
369
+
370
+ let totalTranslated = 0;
371
+
372
+ for (const file of files) {
373
+ const langCode = file.replace('.ts', '');
374
+ const targetPath = path.join(localesDir, file);
375
+
376
+ console.log(`\nšŸŒ Translating ${langCode}...`);
377
+
378
+ const count = await translateLanguageFile(enUSPath, targetPath, langCode);
379
+ totalTranslated += count;
380
+
381
+ if (count > 0) {
382
+ console.log(` āœ… Translated ${count} strings`);
383
+ } else {
384
+ console.log(` āœ“ Already complete`);
385
+ }
386
+ }
387
+
388
+ console.log(`\nāœ… Translation completed!`);
389
+ console.log(` Total strings translated: ${totalTranslated}`);
390
+ console.log(`\nšŸ“ Next: Run 'node scripts/setup-languages.js' to update index.ts`);
391
+ }
3
392
 
4
- // Placeholder for translation script
5
- console.log('šŸŒ [Localization] Translate Missing Script');
6
- console.log('Not implemented yet. This script would check for missing keys against en-US and auto-translate them.');
393
+ main().catch(error => {
394
+ console.error('āŒ Translation failed:', error);
395
+ process.exit(1);
396
+ });