@@ -78,11 +78,6 @@ export class TranslateCommand {
7878 this . config = config ;
7979 }
8080
81- /**
82- * Validate language codes
83- * Throws error if any language code is invalid
84- * Fix for Issue #3: Validate language codes upfront to provide clear error messages
85- */
8681 private validateLanguageCodes ( langCodes : string [ ] ) : void {
8782 for ( const lang of langCodes ) {
8883 if ( ! VALID_LANGUAGES . has ( lang ) ) {
@@ -110,74 +105,48 @@ export class TranslateCommand {
110105 return glossary . glossary_id ;
111106 }
112107
113- /**
114- * Translate text, file, or directory
115- * Fix for Issue #8: Cache stats to avoid redundant filesystem calls
116- * Fix for Issue #6: Reject symlinks for security (prevent directory traversal attacks)
117- */
118108 async translate ( textOrPath : string , options : TranslateOptions ) : Promise < string > {
119- // Check if input is a file/directory with a single stat() call
120- // Cache the stats result to avoid duplicate syscalls later
121109 let stats : fs . Stats | null = null ;
122110 try {
123- // First check if the path is a symlink using lstatSync (doesn't follow symlinks)
124- // This prevents directory traversal attacks and accessing sensitive files
125111 const lstat = fs . lstatSync ( textOrPath ) ;
126112 if ( lstat . isSymbolicLink ( ) ) {
127113 throw new Error ( `Symlinks are not supported for security reasons: ${ textOrPath } ` ) ;
128114 }
129115
130- // Now safe to use statSync (follows symlinks if any, but we already checked)
131116 stats = fs . statSync ( textOrPath ) ;
132117 if ( stats . isDirectory ( ) ) {
133118 return this . translateDirectory ( textOrPath , options ) ;
134119 }
135120 } catch ( error ) {
136- // Re-throw symlink errors
137121 if ( error instanceof Error && error . message . includes ( 'Symlinks are not supported' ) ) {
138122 throw error ;
139123 }
140- // Not a file/directory, treat as text
141124 }
142125
143- // Check if input is a file path (passing cached stats to avoid re-stating)
144126 if ( this . isFilePath ( textOrPath , stats ) ) {
145127 return this . translateFile ( textOrPath , options , stats ) ;
146128 }
147129
148130 return this . translateText ( textOrPath , options ) ;
149131 }
150132
151- /**
152- * Check if input is a file path
153- * Handles cross-platform paths (Windows backslashes and Unix forward slashes)
154- * Excludes URLs from being treated as file paths
155- * Fix for Issue #8: Accept cached stats to avoid redundant filesystem calls
156- */
157133 private isFilePath ( input : string , cachedStats ?: fs . Stats | null ) : boolean {
158- // If we have cached stats and it's a file, return true immediately
159134 if ( cachedStats && cachedStats . isFile ( ) ) {
160135 return true ;
161136 }
162137
163- // Check if file exists (only if we don't have cached stats)
164138 if ( ! cachedStats && fs . existsSync ( input ) ) {
165139 return true ;
166140 }
167141
168- // Don't treat URLs as file paths
169- // Check for common URL protocols: http://, https://, ftp://, file://
170142 if ( / ^ [ a - z A - Z ] [ a - z A - Z 0 - 9 + . - ] * : \/ \/ / . test ( input ) ) {
171143 return false ;
172144 }
173145
174- // Check for path separators (cross-platform)
175- // Windows: backslash (\), Unix: forward slash (/)
176146 const hasPathSep = input . includes ( path . sep ) ||
177147 input . includes ( '/' ) ||
178148 input . includes ( '\\' ) ;
179149
180- // Must have path separator AND supported extension to be considered a file path
181150 return hasPathSep && this . fileTranslationService . isSupportedFile ( input ) ;
182151 }
183152
@@ -229,20 +198,13 @@ export class TranslateCommand {
229198 }
230199 }
231200
232- /**
233- * Translate file
234- * Fix for Issue #8: Accept cached stats to avoid redundant filesystem calls
235- */
236201 private async translateFile ( filePath : string , options : TranslateOptions , cachedStats ?: fs . Stats | null ) : Promise < string > {
237202 if ( ! options . output ) {
238203 throw new Error ( 'Output file path is required for file translation. Use --output <path>' ) ;
239204 }
240205
241- // Check if translating to multiple languages (must be done BEFORE text file optimization)
242206 if ( options . to . includes ( ',' ) ) {
243207 const targetLangs = options . to . split ( ',' ) . map ( lang => lang . trim ( ) ) ;
244-
245- // Validate all language codes (Issue #3)
246208 this . validateLanguageCodes ( targetLangs ) ;
247209
248210 // Now safe to cast as Language[]
@@ -274,10 +236,7 @@ export class TranslateCommand {
274236 results . map ( r => ` [${ r . targetLang } ] ${ r . outputPath } ` ) . join ( '\n' ) ;
275237 }
276238
277- // Smart routing for text-based files
278- // Use text API (cached) for small text files, document API for large files or binaries
279239 if ( this . isTextBasedFile ( filePath ) ) {
280- // Get file size from cached stats or stat the file (Fix for Issue #8)
281240 let fileSize : number | null ;
282241 if ( cachedStats ) {
283242 fileSize = cachedStats . size ;
@@ -308,7 +267,6 @@ export class TranslateCommand {
308267 return this . translateDocument ( filePath , options ) ;
309268 }
310269
311- // Validate single language code for file translation (Issue #3)
312270 this . validateLanguageCodes ( [ options . to ] ) ;
313271
314272 // Single language translation using file translation service
@@ -359,7 +317,6 @@ export class TranslateCommand {
359317 return this . translateToMultiple ( text , options ) ;
360318 }
361319
362- // Validate single language code (Issue #3)
363320 this . validateLanguageCodes ( [ options . to ] ) ;
364321
365322 // Build translation options
@@ -475,8 +432,6 @@ export class TranslateCommand {
475432 */
476433 private async translateToMultiple ( text : string , options : TranslateOptions ) : Promise < string > {
477434 const targetLangs = options . to . split ( ',' ) . map ( lang => lang . trim ( ) ) ;
478-
479- // Validate all language codes (Issue #3)
480435 this . validateLanguageCodes ( targetLangs ) ;
481436
482437 // Now safe to cast as Language[]
@@ -539,7 +494,6 @@ export class TranslateCommand {
539494 throw new Error ( 'Output directory is required for batch translation. Use --output <dir>' ) ;
540495 }
541496
542- // Validate language code (Issue #3)
543497 this . validateLanguageCodes ( [ options . to ] ) ;
544498
545499 // Build translation options
@@ -625,12 +579,7 @@ export class TranslateCommand {
625579 }
626580 }
627581
628- /**
629- * Translate text-based file using text API (with caching)
630- * Used for small .txt, .md, .html, .srt, .xlf files
631- */
632582 private async translateTextFile ( filePath : string , options : TranslateOptions ) : Promise < string > {
633- // Validate language code (Issue #3)
634583 this . validateLanguageCodes ( [ options . to ] ) ;
635584
636585 // Read file content
@@ -727,11 +676,7 @@ export class TranslateCommand {
727676 } ) ;
728677 }
729678
730- /**
731- * Translate binary document (PDF, DOCX, etc.)
732- */
733679 private async translateDocument ( filePath : string , options : TranslateOptions ) : Promise < string > {
734- // Validate language code (Issue #3)
735680 this . validateLanguageCodes ( [ options . to ] ) ;
736681
737682 const outputPath = options . output ! ;
0 commit comments