Skip to content

Commit 4f02d8a

Browse files
authored
fix(translation-workflow): add case managment for deletions and update prompts (#301)
1 parent b554611 commit 4f02d8a

1 file changed

Lines changed: 98 additions & 48 deletions

File tree

.github/scripts/translation-agent/translation-sync-agent.py

Lines changed: 98 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -250,18 +250,25 @@ def apply_translation_to_file(self, target_file: str, original_content: str, tra
250250

251251
# If target file doesn't exist, create it with translated content
252252
if not target_path.exists():
253+
if not translated_content:
254+
# Pure deletion with no target file: nothing to remove or create
255+
print(f"⚠️ Target file does not exist and no content to add: {target_file} - skipping")
256+
return
253257
with open(target_path, 'w', encoding='utf-8') as f:
254258
f.write(translated_content)
255259
print(f"Created new file: {target_file}")
256260
return
257-
261+
258262
# Read current target file content
259263
with open(target_path, 'r', encoding='utf-8') as f:
260264
current_content = f.read()
261-
262-
if not translated_content:
265+
266+
# Nothing to insert and nothing removed in the source: no work to do.
267+
# For a pure deletion translated_content is empty but the diff still
268+
# carries removed lines, so we must proceed to positioning.
269+
if not translated_content and not self._diff_has_deletions(diff_content):
263270
return
264-
271+
265272
# Use AI to intelligently position the translated content
266273
updated_content = self._apply_ai_positioning(
267274
current_content, translated_content, original_content, diff_content, target_file
@@ -278,16 +285,34 @@ def apply_translation_to_file(self, target_file: str, original_content: str, tra
278285

279286
def _apply_ai_positioning(self, current_target_content: str, translated_content: str, original_source_content: str, diff_content: str, target_file: str) -> Optional[str]:
280287
"""Use AI to intelligently position translated content in the target file"""
281-
282-
prompt = f"""You are an expert documentation editor. Your task is to intelligently merge new translated content into an existing documentation file.
288+
289+
if translated_content.strip():
290+
new_content_section = f"""- New translated content to be inserted:
291+
```
292+
{translated_content}
293+
```"""
294+
else:
295+
new_content_section = (
296+
"- New translated content to be inserted: (NONE — this change ONLY "
297+
"REMOVES content).\n"
298+
" IMPORTANT: This is a deletion-only change. You must return the "
299+
"current target file EXACTLY as it is, changing NOTHING except the "
300+
"removal of the parts that correspond to the lines deleted in the "
301+
"git diff. Do NOT translate, re-translate, rephrase, reformat, "
302+
"reorder or otherwise touch any other part of the file. Every line "
303+
"that is not the one being removed must remain byte-for-byte "
304+
"identical to the current target file content shown above."
305+
)
306+
307+
prompt = f"""You are an expert documentation editor. Your task is to intelligently merge the changes shown in a git diff into an existing translated documentation file.
283308
284309
CONTEXT:
285-
- Original source file (the file that was modified):
310+
- Original source file (the file that was modified):
286311
```
287312
{original_source_content}
288313
```
289314
290-
- Current target file content (where the translation should be inserted):
315+
- Current target file content (where the change should be applied):
291316
```
292317
{current_target_content}
293318
```
@@ -297,23 +322,22 @@ def _apply_ai_positioning(self, current_target_content: str, translated_content:
297322
{diff_content}
298323
```
299324
300-
- New translated content to be inserted:
301-
```
302-
{translated_content}
303-
```
325+
{new_content_section}
304326
305327
TASK:
306-
Analyze the changes shown in the git diff and intelligently insert the translated content into the appropriate position in the current target file.
328+
Analyze the changes shown in the git diff and apply the equivalent change to the current target file. Changes may ADD, MODIFY, or REMOVE content.
307329
308330
RULES:
309-
1. **Understand the context**: Look at where the changes were made in the source file
331+
1. **Understand the context**: Look at where the changes were made in the source file (lines starting with '+' were added, lines starting with '-' were removed)
310332
2. **Find the equivalent position**: Locate the corresponding section in the target file
311-
3. **Insert appropriately**:
333+
3. **Insert appropriately**:
312334
- If it's a NEW section/content: Insert it in the same relative position
313335
- If it's a MODIFICATION: Replace the existing content with the new translation
314336
- If it's an ADDITION to existing section: Add it in the correct place within that section
315-
4. **Preserve structure**: Maintain the overall document structure and hierarchy
316-
5. **Keep formatting**: Preserve all markdown formatting, spacing, and line breaks
337+
4. **Avoid duplication (CRITICAL)**: Before inserting anything, check whether that content — or its equivalent already-translated version — is ALREADY present in the current target file. If it is, do NOT add it again: leave that part of the file unchanged. The result must be IDEMPOTENT — if the change was already applied in a previous run, re-applying it must produce no further changes. Never create a second copy of a sentence, list item, paragraph or section that already exists in the target file.
338+
5. **Handle deletions**: If the git diff shows removed lines (starting with '-' with no '+' counterpart), locate the sentence, paragraph or list item in the target file that corresponds to the removed source text and REMOVE only that part. Do NOT translate or re-insert the removed text. Everything else in the file must stay untouched — do NOT translate, re-translate, rephrase or reformat any surrounding content; leave it exactly as it currently is.
339+
6. **Preserve structure**: Maintain the overall document structure and hierarchy. Do not add, remove or reword any content other than what the git diff indicates. Any content not affected by the diff must remain identical to the current target file, character for character.
340+
7. **Keep formatting**: Preserve all markdown formatting, spacing, and line breaks
317341
318342
OUTPUT FORMAT:
319343
Return the COMPLETE updated target file content with the translated content properly positioned.
@@ -362,25 +386,26 @@ def _apply_ai_positioning(self, current_target_content: str, translated_content:
362386
print(f"❌ Error with AI positioning: {e}")
363387
return None
364388

365-
def _is_completely_new_file(self, diff_content: str) -> bool:
366-
"""Check if the diff represents a completely new file"""
367-
lines = diff_content.split('\n')
368-
369-
# Look for "new file mode" indicator
370-
for line in lines:
371-
if line.startswith('new file mode'):
372-
return True
373-
# Also check if all non-header lines are additions (start with +)
374-
if line.startswith('@@'):
375-
# After finding diff header, check if most lines are additions
376-
break
377-
378-
# Count additions vs modifications
379-
additions = sum(1 for line in lines if line.startswith('+') and not line.startswith('+++'))
380-
modifications = sum(1 for line in lines if line.startswith('-') and not line.startswith('---'))
381-
382-
# If there are only additions and no deletions, it's likely a new file
383-
return additions > 0 and modifications == 0
389+
def _is_file_deletion(self, diff_content: str) -> bool:
390+
"""Check if the diff represents a full file deletion"""
391+
return any(
392+
line.startswith('deleted file mode')
393+
for line in diff_content.split('\n')
394+
)
395+
396+
def _diff_has_additions(self, diff_content: str) -> bool:
397+
"""Check if the diff contains any added lines (excluding the +++ header)"""
398+
return any(
399+
line.startswith('+') and not line.startswith('+++')
400+
for line in diff_content.split('\n')
401+
)
402+
403+
def _diff_has_deletions(self, diff_content: str) -> bool:
404+
"""Check if the diff contains any removed lines (excluding the --- header)"""
405+
return any(
406+
line.startswith('-') and not line.startswith('---')
407+
for line in diff_content.split('\n')
408+
)
384409

385410
def translate_entire_file(self, file_path: str, source_content: str, source_lang: str, target_lang: str) -> Optional[str]:
386411
"""Translate an entire file content for new files"""
@@ -465,26 +490,51 @@ def sync_translation(self, source_file: str, target_file: str, source_lang: str,
465490

466491
print(f"Processing changes in {source_file}")
467492
print(f"Diff content preview: {diff_content[:200]}...")
468-
469-
# Determine if this is a completely new file
470-
is_new_file = self._is_completely_new_file(diff_content)
471-
472-
if is_new_file and not target_exists:
473-
print(f"🆕 Detected new file: {source_file}")
474-
# For new files, translate the entire content
493+
494+
# Handle full file deletion: the source file was removed entirely, so the
495+
# corresponding translated file must be deleted too (not emptied/merged).
496+
# This must be checked before the pure-deletion branch, since a deleted
497+
# file also contains only removed lines.
498+
if self._is_file_deletion(diff_content):
499+
print(f"🗑️ Detected full file deletion: {source_file}")
500+
target_path = Path(target_file)
501+
if target_path.exists():
502+
target_path.unlink()
503+
print(f"🗑️ Deleted translated file: {target_file}")
504+
else:
505+
print(f"ℹ️ Translated file does not exist, nothing to delete: {target_file}")
506+
return
507+
508+
# Determine the kind of change we're dealing with
509+
has_additions = self._diff_has_additions(diff_content)
510+
has_deletions = self._diff_has_deletions(diff_content)
511+
is_pure_deletion = has_deletions and not has_additions
512+
513+
if not target_exists:
514+
print(f"🆕 No existing translation for {source_file} - translating entire file")
475515
source_content = self.get_file_content(source_file)
516+
if not source_content:
517+
print(f"Source file is empty or missing, nothing to translate: {source_file}")
518+
return
476519
translated_content = self.translate_entire_file(source_file, source_content, source_lang, target_lang)
520+
if not translated_content:
521+
print(f"Could not generate translation for {source_file}")
522+
return
523+
elif is_pure_deletion:
524+
print(f"🗑️ Detected pure deletion: {source_file}")
525+
# no new content to translate/insert, just deletion
526+
# skip the translation call entirely to avoid mistakenly translating the removed lines as content to insert
527+
translated_content = ""
477528
else:
478529
print(f"📝 Detected file modification: {source_file}")
479530
# For modifications, use the existing diff-based approach
480531
translated_content = self.analyze_changes_with_ai(
481532
source_file, diff_content, source_lang, target_lang
482533
)
483-
484-
if not translated_content:
485-
print(f"Could not generate translation for {source_file}")
486-
return
487-
534+
if not translated_content:
535+
print(f"Could not generate translation for {source_file}")
536+
return
537+
488538
print(f"Generated translation: {translated_content[:200]}...")
489539

490540
# Get original content for context

0 commit comments

Comments
 (0)