File size: 11,240 Bytes
0e9ff78
 
 
6f8625d
0e9ff78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ae64ab
0e9ff78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
import os
import docx
from docx import Document
import google.generativeai as genai
import ast
import json
from docx.oxml import OxmlElement
from copy import deepcopy
import io
from pymongo import MongoClient
from gridfs import GridFS
from docx import Document
from deep_translator import GoogleTranslator

gemini_api = "AIzaSyAzKQgJcAufbpMFV8SVhhB_z057f8UgFWg"
target_language = 'vi'  
source_language = 'en'

def batch_translate(texts, source_lang = 'en', target_lang="fr"):
    """ Translates multiple text segments in a single API call. """
    if not texts:
        return texts  # Skip if empty

    prompt = f"""
            Translate the following JSON file from {source_lang} into {target_lang} while preserving names, links, symbols, and formatting:  
            {json.dumps([{"index": i, "text": t} for i, t in enumerate(texts)])}  

            - The original JSON file contains a Python array of objects, each with "index" and "text" keys.  
            - Ensure **one-to-one correspondence** — the output must have exactly as many items as the input.  
            - Do **not** merge, split, or omit strings. Each input object corresponds to exactly one output object.  
            - Return only valid JSON — a Python array of translated objects.  
            - If the original array is empty, return an empty array.  
            """  

    client = genai.Client(api_key=gemini_api)
    response = client.models.generate_content(
    model="gemini-2.0-flash", contents=prompt)

    translated_output = ast.literal_eval(response.text.strip().strip("json```").strip("```").strip())

    return [item["text"] for item in translated_output]

def merge_runs(runs):
    """ Merges adjacent runs with the same style. """
    merged_runs = []
    for run in runs:
        if merged_runs and isinstance(run, docx.text.run.Run) and isinstance(merged_runs[-1], docx.text.run.Run):
            if (
            merged_runs and 
            run.style == merged_runs[-1].style and 
            merged_runs[-1].bold == run.bold and
            merged_runs[-1].italic == run.italic and
            merged_runs[-1].underline == run.underline and 
            merged_runs[-1].font.size == run.font.size and
            merged_runs[-1].font.color.rgb == run.font.color.rgb and
            merged_runs[-1].font.name == run.font.name
):
                merged_runs[-1].text += run.text
        else:
                merged_runs.append(run)
    return merged_runs

NS_W = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"

def translate_paragraphs(doc, source_lang, target_lang):
    paragraphs = []
    for para in doc.paragraphs:
        for run in merge_runs(para.iter_inner_content()):
            if isinstance(run, docx.text.run.Run):
                paragraphs.append(run.text)
    # paragraphs = merge_runs(paragraphs)
    translated_paragraphs = []
    temp_batch = []
    words = 0
    for para in paragraphs:
        if len(para) + words > 5000:
            translated_paragraphs += batch_translate(temp_batch, source_lang, target_lang)
            temp_batch = []
            words = 0
        words += len(para)
        temp_batch.append(para)
    translated_paragraphs += batch_translate(temp_batch, source_lang, target_lang)
    # translated_paragraphs = batch_translate(paragraphs, target_lang)

    if len(translated_paragraphs) > 0:
        # Replace translated text back
        para_index = 0
        for para in doc.paragraphs:
            original_para = deepcopy(para)
            para.clear()  # Remove text while keeping paragraph properties            
            for run in merge_runs(original_para.iter_inner_content()):
                if isinstance(run, docx.text.run.Run):
                    translated_text = translated_paragraphs[para_index]
                    try:
                        translated_text = translated_text.encode('utf-8', 'ignore').decode('utf-8')  # Ignore invalid characters
                    except UnicodeEncodeError:
                        translated_text = translated_text.encode('utf-8', 'replace').decode('utf-8')  # Replace invalid characters
                    drawing = run._element.find(f".//{NS_W}drawing")
                    pict = run._element.find(".//{NS_W}pict")
                    
                    # Create a new run with translated text and copy the formatting
                    new_run = para.add_run(translated_text)
                    new_run.style = run.style

                    if drawing is not None:
                            new_run._element.append(drawing)
                    elif pict is not None:
                        new_run._element.append(pict)
                            
                    # Copy formatting from original run
                    new_run.bold = run.bold
                    new_run.italic = run.italic
                    new_run.underline = run.underline
                    new_run.font.size = run.font.size
                    new_run.font.color.rgb = run.font.color.rgb
                    new_run.font.name = run.font.name
                    para_index += 1
                elif isinstance(run, docx.text.hyperlink.Hyperlink):
                    parent = run._element
                    tag = parent.tag.split("}")[-1]
        
                    # Create a new hyperlink element with the correct namespace
                    new_hyperlink = OxmlElement(f"w:{tag}")
                    for attr in parent.attrib:
                        new_hyperlink.set(attr, parent.get(attr))
                    for child in parent:
                        new_hyperlink.append(child)
                    para._element.append(new_hyperlink)


def translate_tables(doc, source_lang, target_lang):
    table_texts = []
    run_mapping = {}
    

    for table in doc.tables:
        for row in table.rows:
            for cell in row.cells:
                for para in cell.paragraphs:
                    for run in merge_runs(para.iter_inner_content()):
                        if isinstance(run, docx.text.run.Run):
                            table_texts.append(run.text)                   

    translated_tables = []
    temp_batch = []
    words = 0
    for para in table_texts:
        if len(para) + words > 5000:
            translated_tables += batch_translate(temp_batch, source_lang, target_lang)
            temp_batch = []
            words = 0
        words += len(para)
        temp_batch.append(para)
    translated_tables += batch_translate(temp_batch, source_lang, target_lang)
    # translated_tables = batch_translate(table_texts, target_lang)

    if len(translated_tables) > 0:
        table_index = 0
        for table in doc.tables:
            for row in table.rows:
                for cell in row.cells:
                    for para in cell.paragraphs:
                        original_para = deepcopy(para)
                        para.clear()  # Remove text while keeping paragraph properties    
                        for run in merge_runs(original_para.iter_inner_content()):
                            if isinstance(run, docx.text.run.Run):
                                translated_text = translated_tables[table_index]
                                try:
                                    translated_text = translated_text.encode('utf-8', 'ignore').decode('utf-8')  # Ignore invalid characters
                                except UnicodeEncodeError:
                                    translated_text = translated_text.encode('utf-8', 'replace').decode('utf-8')  # Replace invalid characters
                                drawing = run._element.find(f".//{NS_W}drawing")
                                pict = run._element.find(".//{NS_W}pict")
                                
                                # Create a new run with translated text and copy the formatting
                                new_run = para.add_run(translated_text)
                                new_run.style = run.style

                                if drawing is not None:
                                        new_run._element.append(drawing)
                                elif pict is not None:
                                    new_run._element.append(pict)
                                        
                                # Copy formatting from original run
                                new_run.bold = run.bold
                                new_run.italic = run.italic
                                new_run.underline = run.underline
                                new_run.font.size = run.font.size
                                new_run.font.color.rgb = run.font.color.rgb
                                new_run.font.name = run.font.name
                                table_index += 1
                            elif isinstance(run, docx.text.hyperlink.Hyperlink):
                                parent = run._element
                                tag = parent.tag.split("}")[-1]
        
                                # Create a new hyperlink element with the correct namespace
                                new_hyperlink = OxmlElement(f"w:{tag}")
                                for attr in parent.attrib:
                                    new_hyperlink.set(attr, parent.get(attr))
                                for child in parent:
                                    new_hyperlink.append(child)
                                para._element.append(new_hyperlink)

def translate_header_footer(doc, source_lang, target_lang):
    head_foot = []
    for section in doc.sections:
        for header in section.header.paragraphs:
            for run in header.runs:
                head_foot.append(run.text) 
        for footer in section.footer.paragraphs:
            for run in footer.runs:
                head_foot.append(run.text)  
    translated_head_foot = batch_translate(head_foot, source_lang, target_lang)

    i = 0
    for section in doc.sections:
        for header in section.header.paragraphs:
            for run in header.runs:
                run.text = translated_head_foot[i]
                i += 1
        for footer in section.footer.paragraphs:
            for run in footer.runs:
                run.text = translated_head_foot[i]
                i += 1

def translate_docx(file_id, source_lang='en', target_lang='fr', db_name='word'):
    client = MongoClient('mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0')
    db = client[db_name]
    fs_input = GridFS(db, collection="root_file")
    fs_output = GridFS(db, collection="final_file")
    
    file_data = fs_input.get(file_id).read()
    input_doc = Document(io.BytesIO(file_data))
    
    translate_paragraphs(input_doc, source_lang, target_lang)
    translate_tables(input_doc, source_lang, target_lang)
    translate_header_footer(input_doc, source_lang, target_lang)
    
    output_stream = io.BytesIO()
    input_doc.save(output_stream)
    output_stream.seek(0)
    
    translated_file_id = fs_output.put(output_stream, filename=f"{target_lang}_translated.docx")
    print(f"Translation complete! Saved with file ID: {translated_file_id}")
    
    return translated_file_id