@umituz/web-localization 1.1.8 → 1.1.9

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.
@@ -14,6 +14,7 @@ export interface TranslationResponse {
14
14
  readonly targetLanguage: string;
15
15
  readonly success: boolean;
16
16
  readonly error?: string;
17
+ readonly cached?: boolean;
17
18
  }
18
19
  export interface TranslationItem {
19
20
  readonly key: string;
@@ -4,7 +4,6 @@ import type { TranslationRequest, TranslationResponse, TranslationStats } from "
4
4
  */
5
5
  export interface TranslationServiceConfig {
6
6
  minDelay?: number;
7
- maxRetries?: number;
8
7
  timeout?: number;
9
8
  apiKey?: string;
10
9
  }
@@ -16,5 +15,5 @@ export interface ITranslationService {
16
15
  isInitialized(): boolean;
17
16
  translate(request: TranslationRequest): Promise<TranslationResponse>;
18
17
  translateBatch(requests: TranslationRequest[]): Promise<TranslationStats>;
19
- translateObject(sourceObject: Record<string, unknown>, targetObject: Record<string, unknown>, targetLanguage: string, path?: string, stats?: TranslationStats, onTranslate?: (key: string, from: string, to: string) => void): Promise<void>;
18
+ translateObject(sourceObject: Record<string, unknown>, targetObject: Record<string, unknown>, targetLanguage: string, path?: string, stats?: TranslationStats, onTranslate?: (key: string, from: string, to: string) => void, force?: boolean): Promise<void>;
20
19
  }
@@ -8,3 +8,5 @@ export declare const DEFAULT_TIMEOUT = 10000;
8
8
  export declare const DEFAULT_LOCALES_DIR = "src/locales";
9
9
  export declare const DEFAULT_SOURCE_DIR = "src";
10
10
  export declare const DEFAULT_BASE_LANGUAGE = "en-US";
11
+ export declare const TRANSLATION_BATCH_SIZE = 50;
12
+ export declare const TRANSLATION_CONCURRENCY_LIMIT = 10;
@@ -8,3 +8,6 @@ export const DEFAULT_TIMEOUT = 10000;
8
8
  export const DEFAULT_LOCALES_DIR = "src/locales";
9
9
  export const DEFAULT_SOURCE_DIR = "src";
10
10
  export const DEFAULT_BASE_LANGUAGE = "en-US";
11
+ // Translation batch settings
12
+ export const TRANSLATION_BATCH_SIZE = 50;
13
+ export const TRANSLATION_CONCURRENCY_LIMIT = 10;
@@ -3,6 +3,7 @@ export interface SyncOptions {
3
3
  sourceDir?: string;
4
4
  baseLang?: string;
5
5
  force?: boolean;
6
+ concurrency?: number;
6
7
  }
7
8
  export declare class CLIService {
8
9
  sync(options?: SyncOptions): Promise<void>;
@@ -3,7 +3,63 @@ import path from "path";
3
3
  import chalk from "chalk";
4
4
  import { googleTranslateService } from "./google-translate.service.js";
5
5
  import { parseTypeScriptFile, generateTypeScriptContent } from "../utils/file.util.js";
6
- import { DEFAULT_LOCALES_DIR, DEFAULT_BASE_LANGUAGE } from "../constants/index.js";
6
+ import { DEFAULT_LOCALES_DIR, DEFAULT_BASE_LANGUAGE, TRANSLATION_CONCURRENCY_LIMIT, } from "../constants/index.js";
7
+ /**
8
+ * Semaphore for controlling concurrent operations
9
+ */
10
+ class Semaphore {
11
+ permits;
12
+ queue = [];
13
+ constructor(permits) {
14
+ this.permits = permits;
15
+ }
16
+ async acquire() {
17
+ if (this.permits > 0) {
18
+ this.permits--;
19
+ return;
20
+ }
21
+ return new Promise((resolve) => {
22
+ this.queue.push(resolve);
23
+ });
24
+ }
25
+ release() {
26
+ this.permits++;
27
+ const next = this.queue.shift();
28
+ if (next) {
29
+ this.permits--;
30
+ next();
31
+ }
32
+ }
33
+ async run(fn) {
34
+ await this.acquire();
35
+ try {
36
+ return await fn();
37
+ }
38
+ finally {
39
+ this.release();
40
+ }
41
+ }
42
+ }
43
+ // Extracted outside loop for better performance
44
+ const syncObject = (source, target) => {
45
+ const result = { ...target };
46
+ for (const key in source) {
47
+ if (typeof source[key] === "object" && source[key] !== null) {
48
+ result[key] = syncObject(source[key], target[key] || {});
49
+ }
50
+ else if (target[key] === undefined) {
51
+ // Let empty string indicate untranslated state
52
+ result[key] = typeof source[key] === "string" ? "" : source[key];
53
+ }
54
+ }
55
+ // Remove extra keys
56
+ for (const key in target) {
57
+ if (source[key] === undefined) {
58
+ delete result[key];
59
+ }
60
+ }
61
+ return result;
62
+ };
7
63
  export class CLIService {
8
64
  async sync(options = {}) {
9
65
  const localesDir = path.resolve(process.cwd(), options.localesDir || DEFAULT_LOCALES_DIR);
@@ -18,38 +74,27 @@ export class CLIService {
18
74
  return;
19
75
  }
20
76
  const baseData = parseTypeScriptFile(baseLangPath);
77
+ // Pre-compile regex for better performance
78
+ const localeFileRegex = /^[a-z]{2}(-[A-Z]{2})?\.ts$/;
21
79
  const files = fs.readdirSync(localesDir)
22
- .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== `${baseLang}.ts`)
80
+ .filter(f => localeFileRegex.test(f) && f !== `${baseLang}.ts`)
23
81
  .sort();
24
82
  console.log(chalk.blue(`📊 Found ${files.length} languages to sync with ${baseLang}.\n`));
25
- for (const file of files) {
26
- const targetPath = path.join(localesDir, file);
27
- const targetData = parseTypeScriptFile(targetPath);
28
- const langCode = file.replace(".ts", "");
29
- // Deep merge with base data structure
30
- const syncObject = (source, target) => {
31
- const result = { ...target };
32
- for (const key in source) {
33
- if (typeof source[key] === "object" && source[key] !== null) {
34
- result[key] = syncObject(source[key], target[key] || {});
35
- }
36
- else if (target[key] === undefined) {
37
- // Let empty string indicate untranslated state
38
- result[key] = typeof source[key] === "string" ? "" : source[key];
39
- }
40
- }
41
- // Remove extra keys
42
- for (const key in target) {
43
- if (source[key] === undefined) {
44
- delete result[key];
45
- }
46
- }
47
- return result;
48
- };
49
- const syncedData = syncObject(baseData, targetData);
50
- fs.writeFileSync(targetPath, generateTypeScriptContent(syncedData, langCode));
51
- console.log(chalk.green(` 🌍 ${langCode}: Synced structure.`));
52
- }
83
+ // Process files in parallel with controlled concurrency
84
+ const concurrency = options.concurrency || TRANSLATION_CONCURRENCY_LIMIT;
85
+ const semaphore = new Semaphore(concurrency);
86
+ const syncPromises = files.map(async (file) => {
87
+ return semaphore.run(async () => {
88
+ const targetPath = path.join(localesDir, file);
89
+ const targetData = parseTypeScriptFile(targetPath);
90
+ const langCode = path.basename(file, ".ts");
91
+ const syncedData = syncObject(baseData, targetData);
92
+ fs.writeFileSync(targetPath, generateTypeScriptContent(syncedData, langCode));
93
+ // Non-blocking progress update
94
+ process.stdout.write(chalk.green(` 🌍 ${langCode}: Synced structure.\n`));
95
+ });
96
+ });
97
+ await Promise.all(syncPromises);
53
98
  console.log(chalk.bold.green("\n✅ Synchronization completed!"));
54
99
  }
55
100
  async translate(options = {}) {
@@ -62,35 +107,82 @@ export class CLIService {
62
107
  }
63
108
  googleTranslateService.initialize({});
64
109
  const baseData = parseTypeScriptFile(baseLangPath);
110
+ // Pre-compile regex for better performance
111
+ const localeFileRegex = /^[a-z]{2}(-[A-Z]{2})?\.ts$/;
65
112
  const files = fs.readdirSync(localesDir)
66
- .filter(f => f.match(/^[a-z]{2}-[A-Z]{2}\.ts$/) && f !== `${baseLang}.ts`)
113
+ .filter(f => localeFileRegex.test(f) && f !== `${baseLang}.ts`)
67
114
  .sort();
68
115
  console.log(chalk.blue.bold(`🚀 Starting automatic translation for ${files.length} languages...\n`));
69
- for (const file of files) {
70
- const targetPath = path.join(localesDir, file);
71
- const targetData = parseTypeScriptFile(targetPath);
72
- const langCode = file.replace(".ts", "");
73
- const stats = {
74
- totalCount: 0,
75
- successCount: 0,
76
- failureCount: 0,
77
- skippedCount: 0,
78
- translatedKeys: []
79
- };
80
- console.log(chalk.yellow(`🌍 Translating ${langCode}...`));
81
- await googleTranslateService.translateObject(baseData, targetData, langCode.split("-")[0], // ISO 639-1
82
- "", stats, (key, from, to) => {
83
- process.stdout.write(chalk.gray(` • ${key}: ${from.substring(0, 15)}... → ${to.substring(0, 15)}...\r`));
84
- }, options.force);
85
- if (stats.successCount > 0) {
86
- fs.writeFileSync(targetPath, generateTypeScriptContent(targetData, langCode));
87
- console.log(chalk.green(` ✅ Successfully translated ${stats.successCount} keys.`));
88
- }
89
- else {
90
- console.log(chalk.gray(" ✨ Already up to date."));
91
- }
92
- }
116
+ // Process languages in parallel with controlled concurrency
117
+ const concurrency = options.concurrency || TRANSLATION_CONCURRENCY_LIMIT;
118
+ const semaphore = new Semaphore(concurrency);
119
+ // Shared statistics tracking
120
+ const totalStats = {
121
+ totalLanguages: files.length,
122
+ completedLanguages: 0,
123
+ totalSuccess: 0,
124
+ totalFailure: 0,
125
+ };
126
+ const translatePromises = files.map(async (file) => {
127
+ return semaphore.run(async () => {
128
+ const targetPath = path.join(localesDir, file);
129
+ const targetData = parseTypeScriptFile(targetPath);
130
+ const langCode = path.basename(file, ".ts");
131
+ const stats = {
132
+ totalCount: 0,
133
+ successCount: 0,
134
+ failureCount: 0,
135
+ skippedCount: 0,
136
+ translatedKeys: []
137
+ };
138
+ // Non-blocking progress update with language name
139
+ const langName = langCode.padEnd(6);
140
+ process.stdout.write(chalk.yellow(`🌍 Translating ${langName}...\n`));
141
+ try {
142
+ // Extract ISO 639-1 language code (e.g., "en" from "en-US")
143
+ const targetLang = langCode.includes("-") ? langCode.split("-")[0] : langCode;
144
+ await googleTranslateService.translateObject(baseData, targetData, targetLang, "", stats, (_key, _from, _to) => {
145
+ // Non-blocking per-key progress (only in verbose mode or for debugging)
146
+ // Commented out to reduce console spam
147
+ // process.stdout.write(chalk.gray(` • ${key}: ${from.substring(0, 15)}... → ${to.substring(0, 15)}...\r`));
148
+ }, options.force);
149
+ // Write translated content
150
+ if (stats.successCount > 0) {
151
+ fs.writeFileSync(targetPath, generateTypeScriptContent(targetData, langCode));
152
+ totalStats.totalSuccess += stats.successCount;
153
+ process.stdout.write(chalk.green(` ✅ ${langName} Successfully translated ${stats.successCount} keys.\n`));
154
+ }
155
+ else if (stats.failureCount > 0) {
156
+ totalStats.totalFailure += stats.failureCount;
157
+ process.stdout.write(chalk.red(` ❌ ${langName} Failed to translate ${stats.failureCount} keys.\n`));
158
+ }
159
+ else {
160
+ process.stdout.write(chalk.gray(` ✨ ${langName} Already up to date.\n`));
161
+ }
162
+ }
163
+ catch (error) {
164
+ totalStats.totalFailure += stats.failureCount;
165
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
166
+ process.stdout.write(chalk.red(` ❌ ${langName} Error: ${errorMsg}\n`));
167
+ }
168
+ finally {
169
+ totalStats.completedLanguages++;
170
+ }
171
+ });
172
+ });
173
+ await Promise.all(translatePromises);
174
+ // Final summary
93
175
  console.log(chalk.bold.green("\n✅ All translations completed!"));
176
+ console.log(chalk.gray(` 📊 Processed ${totalStats.completedLanguages}/${totalStats.totalLanguages} languages`));
177
+ if (totalStats.totalSuccess > 0) {
178
+ console.log(chalk.green(` ✅ Total keys translated: ${totalStats.totalSuccess}`));
179
+ }
180
+ if (totalStats.totalFailure > 0) {
181
+ console.log(chalk.red(` ❌ Total keys failed: ${totalStats.totalFailure}`));
182
+ }
183
+ // Display cache statistics
184
+ const cacheStats = googleTranslateService.getCacheStats();
185
+ console.log(chalk.gray(` 💾 Cache hit rate: ${cacheStats.size}/${cacheStats.maxSize}`));
94
186
  }
95
187
  }
96
188
  export const cliService = new CLIService();
@@ -1,19 +1,61 @@
1
1
  /**
2
- * Google Translate Service
3
- * @description Main translation service using Google Translate API
2
+ * Google Translate Service with Performance Optimizations
3
+ * @description Main translation service using Google Translate API with caching and pooling
4
4
  */
5
5
  import type { TranslationRequest, TranslationResponse, TranslationStats } from "../../domain/entities/translation.entity.js";
6
6
  import type { ITranslationService, TranslationServiceConfig } from "../../domain/interfaces/translation-service.interface.js";
7
7
  declare class GoogleTranslateService implements ITranslationService {
8
8
  private config;
9
- private rateLimiter;
9
+ private _rateLimiter;
10
+ private translationCache;
11
+ private readonly maxCacheSize;
12
+ private readonly cacheTTL;
13
+ private readonly mapPool;
14
+ private activeRequests;
15
+ private readonly maxConcurrentRequests;
10
16
  initialize(config: TranslationServiceConfig): void;
11
17
  isInitialized(): boolean;
18
+ private get rateLimiter();
12
19
  private ensureInitialized;
20
+ /**
21
+ * Generate cache key for translation
22
+ */
23
+ private getCacheKey;
24
+ /**
25
+ * Get translation from cache
26
+ */
27
+ private getFromCache;
28
+ /**
29
+ * Store translation in cache with automatic eviction
30
+ */
31
+ private storeInCache;
32
+ /**
33
+ * Clear translation cache
34
+ */
35
+ clearCache(): void;
36
+ /**
37
+ * Get cache statistics
38
+ */
39
+ getCacheStats(): {
40
+ size: number;
41
+ maxSize: number;
42
+ };
13
43
  translate(request: TranslationRequest): Promise<TranslationResponse>;
14
44
  translateBatch(requests: TranslationRequest[]): Promise<TranslationStats>;
15
45
  translateObject(sourceObject: Record<string, unknown>, targetObject: Record<string, unknown>, targetLanguage: string, path?: string, stats?: TranslationStats, onTranslate?: (key: string, from: string, to: string) => void, force?: boolean): Promise<void>;
16
46
  private callTranslateAPI;
47
+ /**
48
+ * Optimized sleep function
49
+ */
50
+ private sleep;
51
+ /**
52
+ * Get number of active requests
53
+ */
54
+ getActiveRequestCount(): number;
55
+ /**
56
+ * Clean up resources
57
+ */
58
+ dispose(): void;
17
59
  }
18
60
  export declare const googleTranslateService: GoogleTranslateService;
19
61
  export { GoogleTranslateService };