1- import { TranslationService , TranslationJob } from "./translation-service" ;
2- import { TranslationResult , TranslationTargetType } from "../types/minecraft" ;
3- import { invoke } from "@tauri-apps/api/core" ;
4- import { ErrorLogger } from "../utils/error-logger" ;
5- import { TRANSLATION_DEFAULTS } from "../constants/defaults" ;
6- import { backupService , type CreateBackupOptions } from "./backup-service" ;
1+ import { TranslationService , TranslationJob } from "./translation-service" ;
2+ import { TranslationResult , TranslationTargetType } from "../types/minecraft" ;
73
8- /**
9- * Shared translation runner for all tabs.
10- * Processes jobs chunk-by-chunk, checks for cancellation, and reports progress/results.
11- */
124export interface RunTranslationJobsOptions < T extends TranslationJob = TranslationJob > {
135 jobs : T [ ] ;
146 translationService : TranslationService ;
@@ -57,32 +49,13 @@ export async function runTranslationJobs<T extends TranslationJob = TranslationJ
5749
5850 for ( let i = 0 ; i < jobs . length ; i ++ ) {
5951 const job = jobs [ i ] ;
60-
61- // Set session information for comprehensive logging
62- job . totalFiles = jobs . length ;
63- job . currentFileIndex = i + 1 ;
64-
6552 if ( onJobStart ) onJobStart ( job , i ) ;
6653 if ( setCurrentJobId ) setCurrentJobId ( job . id ) ;
6754
6855 // Start the translation job chunk-by-chunk, checking for interruption
6956 job . status = "processing" ;
7057 job . startTime = Date . now ( ) ;
7158
72- // Log initial file progress
73- await logFileProgress (
74- job . currentFileName || `File ${ i + 1 } ` ,
75- i + 1 ,
76- jobs . length ,
77- 0 ,
78- job . chunks . length ,
79- 0 ,
80- getTotalKeysInJob ( job )
81- ) ;
82-
83- let failedChunksCount = 0 ;
84- const maxFailedChunks = Math . ceil ( job . chunks . length * TRANSLATION_DEFAULTS . maxFailedChunksRatio ) ;
85-
8659 for ( let chunkIndex = 0 ; chunkIndex < job . chunks . length ; chunkIndex ++ ) {
8760 // Check for cancellation
8861 if ( translationService . isJobInterrupted ( job . id ) ) {
@@ -92,91 +65,23 @@ export async function runTranslationJobs<T extends TranslationJob = TranslationJ
9265 break ;
9366 }
9467 const chunk = job . chunks [ chunkIndex ] ;
95-
96- // Skip already failed chunks
97- if ( chunk . status === "failed" ) {
98- continue ;
99- }
100-
10168 chunk . status = "processing" ;
10269 try {
10370 const translatedContent = await translationService . translateChunk (
10471 chunk . content ,
10572 job . targetLanguage ,
10673 job . id
10774 ) ;
108-
109- // Check if translation actually produced content
110- if ( ! translatedContent || Object . keys ( translatedContent ) . length === 0 ) {
111- // Handle empty content as a failure without throwing
112- console . log ( `[TranslationRunner] Translation returned empty content for chunk ${ chunkIndex } - possible API key issue` ) ;
113- chunk . status = "failed" ;
114- chunk . error = "Translation returned empty content - possible API key issue" ;
115- chunk . translatedContent = { } ;
116- failedChunksCount ++ ;
117-
118- // Log the failure
119- const errorMessage = "API key is not configured or translation failed" ;
120- if ( errorMessage . includes ( "API key" ) ) {
121- console . log ( `[TranslationRunner] API key error detected, stopping job immediately` ) ;
122- job . status = "failed" ;
123- job . error = errorMessage ;
124- break ;
125- }
126- } else {
127- chunk . translatedContent = translatedContent ;
128- chunk . status = "completed" ;
129- }
75+ chunk . translatedContent = translatedContent ;
76+ chunk . status = "completed" ;
13077 } catch ( error ) {
13178 chunk . status = "failed" ;
13279 chunk . error = error instanceof Error ? error . message : String ( error ) ;
133- failedChunksCount ++ ;
134-
135- // Log the failure
136- console . log ( `[TranslationRunner] Chunk ${ chunkIndex } failed:` , error ) ;
137-
138- // If it's an API key error, stop the entire job immediately
139- const errorMessage = error instanceof Error ? error . message : String ( error ) ;
140- if ( errorMessage . includes ( "API key is not configured" ) ||
141- errorMessage . includes ( "API key is not set" ) ||
142- errorMessage . includes ( "Incorrect API key provided" ) ) {
143- console . log ( `[TranslationRunner] API key error detected, stopping job immediately` ) ;
144- job . status = "failed" ;
145- job . error = errorMessage ;
146- break ;
147- }
148-
149- // If too many chunks fail, stop processing this job
150- if ( failedChunksCount > maxFailedChunks && maxFailedChunks > 0 ) {
151- console . log ( `[TranslationRunner] Too many failed chunks (${ failedChunksCount } /${ job . chunks . length } ), stopping job` ) ;
152- job . status = "failed" ;
153- job . error = `Too many chunks failed (${ failedChunksCount } /${ job . chunks . length } )` ;
154- break ;
155- }
15680 }
157-
81+
15882 // Increment chunk-level progress once per chunk (only if chunk tracking is used)
159- if ( incrementCompletedChunks ) {
160- console . log ( `[TranslationRunner] Incrementing completed chunks for job ${ job . id } , chunk ${ chunkIndex + 1 } /${ job . chunks . length } ` ) ;
161- incrementCompletedChunks ( ) ;
162- }
83+ if ( incrementCompletedChunks ) incrementCompletedChunks ( ) ;
16384 if ( onJobChunkComplete ) onJobChunkComplete ( job , chunkIndex ) ;
164-
165- // Log updated file progress after chunk completion
166- const completedChunks = job . chunks . filter ( c => c . status === "completed" ) . length ;
167- const completedKeys = job . chunks
168- . filter ( c => c . status === "completed" )
169- . reduce ( ( sum , c ) => sum + Object . keys ( c . content ) . length , 0 ) ;
170-
171- await logFileProgress (
172- job . currentFileName || `File ${ i + 1 } ` ,
173- i + 1 ,
174- jobs . length ,
175- completedChunks ,
176- job . chunks . length ,
177- completedKeys ,
178- getTotalKeysInJob ( job )
179- ) ;
18085 }
18186
18287 // If interrupted, stop processing further jobs
@@ -188,154 +93,40 @@ export async function runTranslationJobs<T extends TranslationJob = TranslationJ
18893 // Mark job as complete
18994 job . status = job . chunks . every ( ( c : import ( "./translation-service" ) . TranslationChunk ) => c . status === "completed" ) ? "completed" : "failed" ;
19095 job . endTime = Date . now ( ) ;
191-
192- // Log performance metrics for this job
193- const jobDuration = job . endTime - job . startTime ;
194- const jobTotalKeys = getTotalKeysInJob ( job ) ;
195- const keysPerSecond = jobTotalKeys / ( jobDuration / 1000 ) ;
196-
197- await logPerformanceMetrics (
198- `Job Translation` ,
199- jobDuration ,
200- undefined ,
201- `${ jobTotalKeys } keys, ${ keysPerSecond . toFixed ( 2 ) } keys/sec, ${ job . chunks . length } chunks`
202- ) ;
203-
20496 if ( onJobComplete ) onJobComplete ( job , i ) ;
20597
20698 // Write output and report result
20799 const outputPath = getOutputPath ( job ) ;
208100 const content = getResultContent ( job ) ;
209101 let writeSuccess = true ;
210-
102+
211103 try {
212- // Create backup before writing output (if enabled and session ID provided)
213- if ( enableBackup && sessionId ) {
214- try {
215- // Determine source name for backup
216- const sourceName = job . currentFileName || job . id || `${ type } _${ i + 1 } ` ;
217-
218- // Calculate file size estimate
219- const contentSize = JSON . stringify ( content ) . length ;
220-
221- // Create backup options
222- const backupOptions : CreateBackupOptions = {
223- type,
224- sourceName,
225- targetLanguage,
226- sessionId,
227- filePaths : [ outputPath ] , // Path where the output will be written
228- statistics : {
229- totalKeys : getTotalKeysInJob ( job ) ,
230- successfulTranslations : job . chunks . filter ( c => c . status === "completed" ) . length ,
231- fileSize : contentSize
232- }
233- } ;
234-
235- // Create the backup
236- const backupInfo = await backupService . createBackup ( backupOptions ) ;
237- console . log ( `[TranslationRunner] Backup created for ${ sourceName } : ${ backupInfo . metadata . id } ` ) ;
238- } catch ( backupError ) {
239- // Log backup error but don't fail the translation
240- console . warn ( `[TranslationRunner] Failed to create backup for job ${ job . id } :` , backupError ) ;
241- await ErrorLogger . logError ( `Backup creation failed for job ${ job . id } ` , backupError , type ) ;
242- }
243- }
244-
245104 await writeOutput ( job , outputPath , content ) ;
246105 } catch ( error ) {
247- await ErrorLogger . logError ( `Failed to write output for job ${ job . id } ` , error , type ) ;
106+ console . error ( `Failed to write output for job ${ job . id } : ` , error ) ;
248107 writeSuccess = false ;
249108 }
250109
251110 if ( onResult ) {
252- // Check if content is actually valid (not empty)
253- const hasValidContent = content && Object . keys ( content ) . length > 0 ;
254-
255111 onResult ( {
256112 type,
257113 id : type === "mod" ? ( job . currentFileName || job . id ) : job . id ,
258114 displayName : job . currentFileName || job . id ,
259115 targetLanguage,
260116 content,
261117 outputPath,
262- success : job . status === "completed" && writeSuccess && hasValidContent
118+ success : writeSuccess && job . status === "completed" ,
119+ sessionId,
120+ enableBackup
263121 } ) ;
264122 }
265123
266- // Increment mod-level progress when entire job is complete (only if mod tracking is used)
267- if ( incrementCompletedMods ) {
268- console . log ( `[TranslationRunner] Incrementing completed mods for job ${ job . id } ` ) ;
269- incrementCompletedMods ( ) ;
270- }
271- }
272- if ( setCurrentJobId ) setCurrentJobId ( null ) ;
273- }
274-
275- /**
276- * Log file progress with detailed status
277- * @param fileName Name of the file
278- * @param fileIndex Current file index (1-based)
279- * @param totalFiles Total number of files
280- * @param chunksCompleted Chunks completed for this file
281- * @param totalChunks Total chunks for this file
282- * @param keysCompleted Keys completed for this file
283- * @param totalKeys Total keys for this file
284- */
285- async function logFileProgress (
286- fileName : string ,
287- fileIndex : number ,
288- totalFiles : number ,
289- chunksCompleted : number ,
290- totalChunks : number ,
291- keysCompleted : number ,
292- totalKeys : number
293- ) : Promise < void > {
294- try {
295- await invoke ( 'log_file_progress' , {
296- fileName : fileName ,
297- fileIndex : fileIndex ,
298- totalFiles : totalFiles ,
299- chunksCompleted : chunksCompleted ,
300- totalChunks : totalChunks ,
301- keysCompleted : keysCompleted ,
302- totalKeys : totalKeys
303- } ) ;
304- } catch ( error ) {
305- await ErrorLogger . logError ( 'logFileProgress' , error , 'TRANSLATION_PROGRESS' ) ;
124+ // Increment mod-level progress if applicable
125+ if ( incrementCompletedMods ) incrementCompletedMods ( ) ;
126+
127+ // Increment whole progress
128+ if ( incrementWholeProgress ) incrementWholeProgress ( ) ;
306129 }
307- }
308-
309- /**
310- * Get total number of keys in a translation job
311- * @param job Translation job
312- * @returns Total number of keys
313- */
314- function getTotalKeysInJob ( job : TranslationJob ) : number {
315- return job . chunks . reduce ( ( sum , chunk ) => sum + Object . keys ( chunk . content ) . length , 0 ) ;
316- }
317130
318- /**
319- * Log performance metrics for debugging
320- * @param operation Operation name
321- * @param durationMs Duration in milliseconds
322- * @param memoryUsageMb Optional memory usage in MB
323- * @param additionalInfo Optional additional information
324- */
325- async function logPerformanceMetrics (
326- operation : string ,
327- durationMs : number ,
328- memoryUsageMb ?: number ,
329- additionalInfo ?: string
330- ) : Promise < void > {
331- try {
332- await invoke ( 'log_performance_metrics' , {
333- operation,
334- durationMs : durationMs ,
335- memoryUsageMb : memoryUsageMb ,
336- additionalInfo : additionalInfo
337- } ) ;
338- } catch ( error ) {
339- await ErrorLogger . logError ( 'logPerformanceMetrics' , error , 'PERFORMANCE' ) ;
340- }
341- }
131+ if ( setCurrentJobId ) setCurrentJobId ( null ) ;
132+ }
0 commit comments