@umituz/web-localization 1.1.5 → 1.1.7

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/web-localization",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Google Translate integrated localization package for web applications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -16,6 +16,7 @@ export interface TranslationResponse {
16
16
  readonly targetLanguage: string;
17
17
  readonly success: boolean;
18
18
  readonly error?: string;
19
+ readonly cached?: boolean;
19
20
  }
20
21
 
21
22
  export interface TranslationItem {
@@ -9,7 +9,6 @@ import type {
9
9
  */
10
10
  export interface TranslationServiceConfig {
11
11
  minDelay?: number;
12
- maxRetries?: number;
13
12
  timeout?: number;
14
13
  apiKey?: string; // Optional if using public API
15
14
  }
@@ -12,6 +12,6 @@ export const DEFAULT_LOCALES_DIR = "src/locales";
12
12
  export const DEFAULT_SOURCE_DIR = "src";
13
13
  export const DEFAULT_BASE_LANGUAGE = "en-US";
14
14
 
15
- // Rate limiter defaults
16
- export const RATE_LIMIT_DEFAULT_DELAY = 100;
17
- export const RATE_LIMIT_MIN_DELAY = 0;
15
+ // Translation batch settings
16
+ export const TRANSLATION_BATCH_SIZE = 50;
17
+ export const TRANSLATION_CONCURRENCY_LIMIT = 10;
@@ -6,7 +6,8 @@ import { googleTranslateService } from "./google-translate.service.js";
6
6
  import { parseTypeScriptFile, generateTypeScriptContent } from "../utils/file.util.js";
7
7
  import {
8
8
  DEFAULT_LOCALES_DIR,
9
- DEFAULT_BASE_LANGUAGE
9
+ DEFAULT_BASE_LANGUAGE,
10
+ TRANSLATION_CONCURRENCY_LIMIT,
10
11
  } from "../constants/index.js";
11
12
 
12
13
  export interface SyncOptions {
@@ -14,6 +15,48 @@ export interface SyncOptions {
14
15
  sourceDir?: string;
15
16
  baseLang?: string;
16
17
  force?: boolean;
18
+ concurrency?: number;
19
+ }
20
+
21
+ /**
22
+ * Semaphore for controlling concurrent operations
23
+ */
24
+ class Semaphore {
25
+ private permits: number;
26
+ private queue: Array<() => void> = [];
27
+
28
+ constructor(permits: number) {
29
+ this.permits = permits;
30
+ }
31
+
32
+ async acquire(): Promise<void> {
33
+ if (this.permits > 0) {
34
+ this.permits--;
35
+ return;
36
+ }
37
+
38
+ return new Promise<void>((resolve) => {
39
+ this.queue.push(resolve);
40
+ });
41
+ }
42
+
43
+ release(): void {
44
+ this.permits++;
45
+ const next = this.queue.shift();
46
+ if (next) {
47
+ this.permits--;
48
+ next();
49
+ }
50
+ }
51
+
52
+ async run<T>(fn: () => Promise<T>): Promise<T> {
53
+ await this.acquire();
54
+ try {
55
+ return await fn();
56
+ } finally {
57
+ this.release();
58
+ }
59
+ }
17
60
  }
18
61
 
19
62
  // Extracted outside loop for better performance
@@ -59,21 +102,34 @@ export class CLIService {
59
102
  }
60
103
 
61
104
  const baseData = parseTypeScriptFile(baseLangPath);
105
+
106
+ // Pre-compile regex for better performance
107
+ const localeFileRegex = /^[a-z]{2}(-[A-Z]{2})?\.ts$/;
62
108
  const files = fs.readdirSync(localesDir)
63
- .filter(f => f.match(/^[a-z]{2}(-[A-Z]{2})?\.ts$/) && f !== `${baseLang}.ts`)
109
+ .filter(f => localeFileRegex.test(f) && f !== `${baseLang}.ts`)
64
110
  .sort();
65
111
 
66
112
  console.log(chalk.blue(`šŸ“Š Found ${files.length} languages to sync with ${baseLang}.\n`));
67
113
 
68
- for (const file of files) {
69
- const targetPath = path.join(localesDir, file);
70
- const targetData = parseTypeScriptFile(targetPath);
71
- const langCode = path.basename(file, ".ts");
114
+ // Process files in parallel with controlled concurrency
115
+ const concurrency = options.concurrency || TRANSLATION_CONCURRENCY_LIMIT;
116
+ const semaphore = new Semaphore(concurrency);
72
117
 
73
- const syncedData = syncObject(baseData, targetData);
74
- fs.writeFileSync(targetPath, generateTypeScriptContent(syncedData, langCode));
75
- console.log(chalk.green(` šŸŒ ${langCode}: Synced structure.`));
76
- }
118
+ const syncPromises = files.map(async (file) => {
119
+ return semaphore.run(async () => {
120
+ const targetPath = path.join(localesDir, file);
121
+ const targetData = parseTypeScriptFile(targetPath);
122
+ const langCode = path.basename(file, ".ts");
123
+
124
+ const syncedData = syncObject(baseData, targetData);
125
+ fs.writeFileSync(targetPath, generateTypeScriptContent(syncedData, langCode));
126
+
127
+ // Non-blocking progress update
128
+ process.stdout.write(chalk.green(` šŸŒ ${langCode}: Synced structure.\n`));
129
+ });
130
+ });
131
+
132
+ await Promise.all(syncPromises);
77
133
 
78
134
  console.log(chalk.bold.green("\nāœ… Synchronization completed!"));
79
135
  }
@@ -90,51 +146,99 @@ export class CLIService {
90
146
 
91
147
  googleTranslateService.initialize({});
92
148
  const baseData = parseTypeScriptFile(baseLangPath);
149
+
150
+ // Pre-compile regex for better performance
151
+ const localeFileRegex = /^[a-z]{2}(-[A-Z]{2})?\.ts$/;
93
152
  const files = fs.readdirSync(localesDir)
94
- .filter(f => f.match(/^[a-z]{2}(-[A-Z]{2})?\.ts$/) && f !== `${baseLang}.ts`)
153
+ .filter(f => localeFileRegex.test(f) && f !== `${baseLang}.ts`)
95
154
  .sort();
96
155
 
97
156
  console.log(chalk.blue.bold(`šŸš€ Starting automatic translation for ${files.length} languages...\n`));
98
157
 
99
- for (const file of files) {
100
- const targetPath = path.join(localesDir, file);
101
- const targetData = parseTypeScriptFile(targetPath);
102
- const langCode = path.basename(file, ".ts");
103
-
104
- const stats: TranslationStats = {
105
- totalCount: 0,
106
- successCount: 0,
107
- failureCount: 0,
108
- skippedCount: 0,
109
- translatedKeys: []
110
- };
111
-
112
- console.log(chalk.yellow(`šŸŒ Translating ${langCode}...`));
113
-
114
- // Extract ISO 639-1 language code (e.g., "en" from "en-US")
115
- const targetLang = langCode.includes("-") ? langCode.split("-")[0] : langCode;
116
-
117
- await googleTranslateService.translateObject(
118
- baseData,
119
- targetData,
120
- targetLang,
121
- "",
122
- stats,
123
- (key, from, to) => {
124
- process.stdout.write(chalk.gray(` • ${key}: ${from.substring(0, 15)}... → ${to.substring(0, 15)}...\r`));
125
- },
126
- options.force
127
- );
128
-
129
- if (stats.successCount > 0) {
130
- fs.writeFileSync(targetPath, generateTypeScriptContent(targetData, langCode));
131
- console.log(chalk.green(` āœ… Successfully translated ${stats.successCount} keys.`));
132
- } else {
133
- console.log(chalk.gray(" ✨ Already up to date."));
134
- }
158
+ // Process languages in parallel with controlled concurrency
159
+ const concurrency = options.concurrency || TRANSLATION_CONCURRENCY_LIMIT;
160
+ const semaphore = new Semaphore(concurrency);
161
+
162
+ // Shared statistics tracking
163
+ const totalStats = {
164
+ totalLanguages: files.length,
165
+ completedLanguages: 0,
166
+ totalSuccess: 0,
167
+ totalFailure: 0,
168
+ };
169
+
170
+ const translatePromises = files.map(async (file) => {
171
+ return semaphore.run(async () => {
172
+ const targetPath = path.join(localesDir, file);
173
+ const targetData = parseTypeScriptFile(targetPath);
174
+ const langCode = path.basename(file, ".ts");
175
+
176
+ const stats: TranslationStats = {
177
+ totalCount: 0,
178
+ successCount: 0,
179
+ failureCount: 0,
180
+ skippedCount: 0,
181
+ translatedKeys: []
182
+ };
183
+
184
+ // Non-blocking progress update with language name
185
+ const langName = langCode.padEnd(6);
186
+ process.stdout.write(chalk.yellow(`šŸŒ Translating ${langName}...\n`));
187
+
188
+ try {
189
+ // Extract ISO 639-1 language code (e.g., "en" from "en-US")
190
+ const targetLang = langCode.includes("-") ? langCode.split("-")[0] : langCode;
191
+
192
+ await googleTranslateService.translateObject(
193
+ baseData,
194
+ targetData,
195
+ targetLang,
196
+ "",
197
+ stats,
198
+ (_key, _from, _to) => {
199
+ // Non-blocking per-key progress (only in verbose mode or for debugging)
200
+ // Commented out to reduce console spam
201
+ // process.stdout.write(chalk.gray(` • ${key}: ${from.substring(0, 15)}... → ${to.substring(0, 15)}...\r`));
202
+ },
203
+ options.force
204
+ );
205
+
206
+ // Write translated content
207
+ if (stats.successCount > 0) {
208
+ fs.writeFileSync(targetPath, generateTypeScriptContent(targetData, langCode));
209
+ totalStats.totalSuccess += stats.successCount;
210
+ process.stdout.write(chalk.green(` āœ… ${langName} Successfully translated ${stats.successCount} keys.\n`));
211
+ } else if (stats.failureCount > 0) {
212
+ totalStats.totalFailure += stats.failureCount;
213
+ process.stdout.write(chalk.red(` āŒ ${langName} Failed to translate ${stats.failureCount} keys.\n`));
214
+ } else {
215
+ process.stdout.write(chalk.gray(` ✨ ${langName} Already up to date.\n`));
216
+ }
217
+ } catch (error) {
218
+ totalStats.totalFailure += stats.failureCount;
219
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
220
+ process.stdout.write(chalk.red(` āŒ ${langName} Error: ${errorMsg}\n`));
221
+ } finally {
222
+ totalStats.completedLanguages++;
223
+ }
224
+ });
225
+ });
226
+
227
+ await Promise.all(translatePromises);
228
+
229
+ // Final summary
230
+ console.log(chalk.bold.green("\nāœ… All translations completed!"));
231
+ console.log(chalk.gray(` šŸ“Š Processed ${totalStats.completedLanguages}/${totalStats.totalLanguages} languages`));
232
+ if (totalStats.totalSuccess > 0) {
233
+ console.log(chalk.green(` āœ… Total keys translated: ${totalStats.totalSuccess}`));
234
+ }
235
+ if (totalStats.totalFailure > 0) {
236
+ console.log(chalk.red(` āŒ Total keys failed: ${totalStats.totalFailure}`));
135
237
  }
136
238
 
137
- console.log(chalk.bold.green("\nāœ… All translations completed!"));
239
+ // Display cache statistics
240
+ const cacheStats = googleTranslateService.getCacheStats();
241
+ console.log(chalk.gray(` šŸ’¾ Cache hit rate: ${cacheStats.size}/${cacheStats.maxSize}`));
138
242
  }
139
243
  }
140
244