first commit

This commit is contained in:
sepehr 2025-04-06 21:39:00 +02:00
commit 64e54819aa
9 changed files with 258 additions and 0 deletions

1
__init__.py Normal file
View File

@ -0,0 +1 @@
# This file is intentionally left blank.

102
main.py Normal file
View 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
View File

@ -0,0 +1 @@
# This file is intentionally left blank.

13
models/excel_models.py Normal file
View 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
View File

@ -0,0 +1 @@
# This file is intentionally left blank.

59
services/excel_service.py Normal file
View 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
View 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
View File

@ -0,0 +1 @@
# This file is intentionally left blank.

31
utils/helpers.py Normal file
View 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.")