first commit
This commit is contained in:
commit
64e54819aa
1
__init__.py
Normal file
1
__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# This file is intentionally left blank.
|
||||
102
main.py
Normal file
102
main.py
Normal file
@ -0,0 +1,102 @@
|
||||
from openpyxl import load_workbook, Workbook
|
||||
import asyncio
|
||||
from googletrans import Translator
|
||||
import os
|
||||
from tqdm import tqdm
|
||||
import copy
|
||||
import re
|
||||
|
||||
async def translate_text(translator, text, target_language):
|
||||
try:
|
||||
translation = await translator.translate(text, dest=target_language)
|
||||
return translation.text
|
||||
except Exception as e:
|
||||
print(f"Error translating '{text}': {e}")
|
||||
return text # Return the original text if translation fails
|
||||
|
||||
def is_formula(text):
|
||||
"""Check if a cell value is a formula"""
|
||||
if isinstance(text, str):
|
||||
return text.startswith('=')
|
||||
return False
|
||||
|
||||
def copy_cell_format(source_cell, target_cell):
|
||||
"""Copy formatting from source cell to target cell without copying the problematic style index"""
|
||||
if source_cell.has_style:
|
||||
try:
|
||||
# Copy individual style attributes instead of the entire style object
|
||||
target_cell.font = copy.copy(source_cell.font)
|
||||
target_cell.border = copy.copy(source_cell.border)
|
||||
target_cell.fill = copy.copy(source_cell.fill)
|
||||
target_cell.number_format = source_cell.number_format
|
||||
target_cell.protection = copy.copy(source_cell.protection)
|
||||
target_cell.alignment = copy.copy(source_cell.alignment)
|
||||
# Copy any hyperlink
|
||||
if source_cell.hyperlink:
|
||||
target_cell.hyperlink = source_cell.hyperlink
|
||||
except Exception as e:
|
||||
print(f"Error copying format: {e}")
|
||||
|
||||
async def translate_excel(file_path: str, target_language: str):
|
||||
translator = Translator()
|
||||
workbook = load_workbook(file_path)
|
||||
translated_workbook = Workbook()
|
||||
|
||||
# Count total cells for progress bar
|
||||
total_cells = sum(
|
||||
sum(1 for _ in sheet.iter_rows())
|
||||
for sheet in workbook.worksheets
|
||||
)
|
||||
|
||||
progress_bar = tqdm(total=total_cells, desc="Translating cells")
|
||||
|
||||
for sheet_name in workbook.sheetnames:
|
||||
original_sheet = workbook[sheet_name]
|
||||
translated_sheet = translated_workbook.create_sheet(title=sheet_name)
|
||||
|
||||
# Copy sheet properties (column dimensions, etc.)
|
||||
for key, dimension in original_sheet.column_dimensions.items():
|
||||
if hasattr(dimension, 'width') and dimension.width:
|
||||
translated_sheet.column_dimensions[key].width = dimension.width
|
||||
|
||||
for key, dimension in original_sheet.row_dimensions.items():
|
||||
if hasattr(dimension, 'height') and dimension.height:
|
||||
translated_sheet.row_dimensions[key].height = dimension.height
|
||||
|
||||
# Copy merged cells
|
||||
for merged_cell_range in original_sheet.merged_cells:
|
||||
translated_sheet.merge_cells(str(merged_cell_range))
|
||||
|
||||
for row in original_sheet.iter_rows():
|
||||
for cell in row:
|
||||
progress_bar.update(1)
|
||||
# Create the cell at the same position in the new sheet
|
||||
if cell.value:
|
||||
if is_formula(cell.value):
|
||||
# Don't translate formulas
|
||||
translated_cell = translated_sheet.cell(row=cell.row, column=cell.column, value=cell.value)
|
||||
else:
|
||||
translated_text = await translate_text(translator, str(cell.value), target_language)
|
||||
translated_cell = translated_sheet.cell(row=cell.row, column=cell.column, value=translated_text)
|
||||
else:
|
||||
translated_cell = translated_sheet.cell(row=cell.row, column=cell.column)
|
||||
|
||||
# Copy formatting
|
||||
copy_cell_format(cell, translated_cell)
|
||||
|
||||
# Remove the default sheet created by Workbook
|
||||
if "Sheet" in translated_workbook.sheetnames:
|
||||
del translated_workbook["Sheet"]
|
||||
|
||||
translated_file_path = os.path.splitext(file_path)[0] + f"_translated_{target_language}.xlsx"
|
||||
translated_workbook.save(translated_file_path)
|
||||
progress_bar.close()
|
||||
print(f"Translated file saved as: {translated_file_path}")
|
||||
|
||||
async def main():
|
||||
input_file = r"F:\Dev\excel-translator\data\sample\test_sample.xlsx"
|
||||
language = "fr" # French
|
||||
await translate_excel(input_file, language)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
1
models/__init__.py
Normal file
1
models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# This file is intentionally left blank.
|
||||
13
models/excel_models.py
Normal file
13
models/excel_models.py
Normal file
@ -0,0 +1,13 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Dict, Any
|
||||
|
||||
class Cell(BaseModel):
|
||||
value: str
|
||||
format: Dict[str, Any]
|
||||
|
||||
class Sheet(BaseModel):
|
||||
name: str
|
||||
cells: List[List[Cell]]
|
||||
|
||||
class ExcelFile(BaseModel):
|
||||
sheets: List[Sheet]
|
||||
1
services/__init__.py
Normal file
1
services/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# This file is intentionally left blank.
|
||||
59
services/excel_service.py
Normal file
59
services/excel_service.py
Normal file
@ -0,0 +1,59 @@
|
||||
from openpyxl import load_workbook, Workbook
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
|
||||
class CellData(BaseModel):
|
||||
value: str
|
||||
font: dict
|
||||
fill: dict
|
||||
border: dict
|
||||
alignment: dict
|
||||
|
||||
class SheetData(BaseModel):
|
||||
name: str
|
||||
cells: List[List[CellData]]
|
||||
|
||||
def read_excel(file_path: str) -> List[SheetData]:
|
||||
workbook = load_workbook(file_path)
|
||||
sheets_data = []
|
||||
|
||||
for sheet in workbook.sheetnames:
|
||||
ws = workbook[sheet]
|
||||
cells_data = []
|
||||
|
||||
for row in ws.iter_rows():
|
||||
row_data = []
|
||||
for cell in row:
|
||||
cell_data = CellData(
|
||||
value=cell.value,
|
||||
font=cell.font.__dict__,
|
||||
fill=cell.fill.__dict__,
|
||||
border=cell.border.__dict__,
|
||||
alignment=cell.alignment.__dict__
|
||||
)
|
||||
row_data.append(cell_data)
|
||||
cells_data.append(row_data)
|
||||
|
||||
sheets_data.append(SheetData(name=sheet, cells=cells_data))
|
||||
|
||||
return sheets_data
|
||||
|
||||
def write_excel(file_path: str, sheets_data: List[SheetData]):
|
||||
workbook = Workbook()
|
||||
|
||||
for sheet_data in sheets_data:
|
||||
ws = workbook.create_sheet(title=sheet_data.name)
|
||||
|
||||
for row_index, row in enumerate(sheet_data.cells):
|
||||
for col_index, cell_data in enumerate(row):
|
||||
cell = ws.cell(row=row_index + 1, column=col_index + 1, value=cell_data.value)
|
||||
cell.font = cell_data.font
|
||||
cell.fill = cell_data.fill
|
||||
cell.border = cell_data.border
|
||||
cell.alignment = cell_data.alignment
|
||||
|
||||
# Remove the default sheet created by Workbook
|
||||
if "Sheet" in workbook.sheetnames:
|
||||
del workbook["Sheet"]
|
||||
|
||||
workbook.save(file_path)
|
||||
49
services/translator.py
Normal file
49
services/translator.py
Normal file
@ -0,0 +1,49 @@
|
||||
from typing import List
|
||||
import openpyxl
|
||||
from googletrans import Translator
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Cell(BaseModel):
|
||||
value: str
|
||||
row: int
|
||||
column: int
|
||||
|
||||
class Sheet(BaseModel):
|
||||
name: str
|
||||
cells: List[Cell]
|
||||
|
||||
class ExcelTranslator:
|
||||
def __init__(self, src_language: str, dest_language: str):
|
||||
self.translator = Translator()
|
||||
self.src_language = src_language
|
||||
self.dest_language = dest_language
|
||||
|
||||
def translate_text(self, text: str) -> str:
|
||||
translated = self.translator.translate(text, src=self.src_language, dest=self.dest_language)
|
||||
return translated.text
|
||||
|
||||
def translate_sheet(self, sheet: Sheet) -> Sheet:
|
||||
translated_cells = []
|
||||
for cell in sheet.cells:
|
||||
translated_value = self.translate_text(cell.value)
|
||||
translated_cells.append(Cell(value=translated_value, row=cell.row, column=cell.column))
|
||||
return Sheet(name=sheet.name, cells=translated_cells)
|
||||
|
||||
def translate_workbook(self, file_path: str) -> None:
|
||||
workbook = openpyxl.load_workbook(file_path)
|
||||
translated_workbook = openpyxl.Workbook()
|
||||
|
||||
for sheet_name in workbook.sheetnames:
|
||||
sheet = workbook[sheet_name]
|
||||
translated_sheet = translated_workbook.create_sheet(title=sheet_name)
|
||||
|
||||
for row in sheet.iter_rows():
|
||||
for cell in row:
|
||||
translated_value = self.translate_text(cell.value) if cell.value else ''
|
||||
translated_sheet[cell.coordinate].value = translated_value
|
||||
# Preserve cell formatting
|
||||
if cell.has_style:
|
||||
translated_sheet[cell.coordinate]._style = cell._style
|
||||
|
||||
translated_file_path = file_path.replace('.xlsx', '_translated.xlsx')
|
||||
translated_workbook.save(translated_file_path)
|
||||
1
utils/__init__.py
Normal file
1
utils/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# This file is intentionally left blank.
|
||||
31
utils/helpers.py
Normal file
31
utils/helpers.py
Normal file
@ -0,0 +1,31 @@
|
||||
from typing import Any, Dict
|
||||
import os
|
||||
import logging
|
||||
|
||||
def setup_logging() -> None:
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
def save_translated_file(file_path: str, data: Any) -> str:
|
||||
base, ext = os.path.splitext(file_path)
|
||||
translated_file_path = f"{base}_translated{ext}"
|
||||
|
||||
# Assuming data is a DataFrame or similar structure
|
||||
data.to_excel(translated_file_path, index=False)
|
||||
|
||||
logging.info(f"Translated file saved as: {translated_file_path}")
|
||||
return translated_file_path
|
||||
|
||||
def read_excel_file(file_path: str) -> Dict[str, Any]:
|
||||
import pandas as pd
|
||||
|
||||
logging.info(f"Reading Excel file: {file_path}")
|
||||
return pd.read_excel(file_path, sheet_name=None)
|
||||
|
||||
def translate_text(text: str, target_language: str) -> str:
|
||||
# Placeholder for translation logic
|
||||
logging.info(f"Translating text: {text} to {target_language}")
|
||||
return text # Replace with actual translation logic
|
||||
|
||||
def handle_file_not_found(file_path: str) -> None:
|
||||
logging.error(f"File not found: {file_path}")
|
||||
raise FileNotFoundError(f"The file {file_path} does not exist.")
|
||||
Loading…
x
Reference in New Issue
Block a user