299 lines
11 KiB
Python
299 lines
11 KiB
Python
|
|
"""
|
||
|
|
Утилиты для Ollama Translator GUI
|
||
|
|
Дополнительные функции: история, batch обработка, статистика
|
||
|
|
"""
|
||
|
|
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
from datetime import datetime
|
||
|
|
from pathlib import Path
|
||
|
|
import hashlib
|
||
|
|
|
||
|
|
|
||
|
|
class TranslationHistory:
|
||
|
|
"""Управление историей переводов"""
|
||
|
|
|
||
|
|
def __init__(self, history_file="translation_history.json"):
|
||
|
|
self.history_file = history_file
|
||
|
|
self.history = self.load_history()
|
||
|
|
|
||
|
|
def load_history(self):
|
||
|
|
"""Загрузить историю из файла"""
|
||
|
|
if os.path.exists(self.history_file):
|
||
|
|
try:
|
||
|
|
with open(self.history_file, 'r', encoding='utf-8') as f:
|
||
|
|
return json.load(f)
|
||
|
|
except:
|
||
|
|
return []
|
||
|
|
return []
|
||
|
|
|
||
|
|
def save_history(self):
|
||
|
|
"""Сохранить историю в файл"""
|
||
|
|
try:
|
||
|
|
with open(self.history_file, 'w', encoding='utf-8') as f:
|
||
|
|
json.dump(self.history, f, ensure_ascii=False, indent=2)
|
||
|
|
except Exception as e:
|
||
|
|
print(f"Ошибка сохранения истории: {e}")
|
||
|
|
|
||
|
|
def add_translation(self, source_text, translated_text, source_lang="auto", target_lang="ru"):
|
||
|
|
"""Добавить перевод в историю"""
|
||
|
|
entry = {
|
||
|
|
"id": hashlib.md5(source_text.encode()).hexdigest()[:8],
|
||
|
|
"timestamp": datetime.now().isoformat(),
|
||
|
|
"source_text": source_text[:500], # Ограничение для экономии места
|
||
|
|
"translated_text": translated_text[:500],
|
||
|
|
"source_lang": source_lang,
|
||
|
|
"target_lang": target_lang,
|
||
|
|
"char_count": len(source_text),
|
||
|
|
"word_count": len(source_text.split())
|
||
|
|
}
|
||
|
|
|
||
|
|
self.history.insert(0, entry) # Добавить в начало
|
||
|
|
|
||
|
|
# Ограничить размер истории (последние 100 записей)
|
||
|
|
if len(self.history) > 100:
|
||
|
|
self.history = self.history[:100]
|
||
|
|
|
||
|
|
self.save_history()
|
||
|
|
|
||
|
|
def get_recent(self, limit=10):
|
||
|
|
"""Получить последние переводы"""
|
||
|
|
return self.history[:limit]
|
||
|
|
|
||
|
|
def search(self, query):
|
||
|
|
"""Поиск в истории"""
|
||
|
|
results = []
|
||
|
|
query_lower = query.lower()
|
||
|
|
for entry in self.history:
|
||
|
|
if (query_lower in entry['source_text'].lower() or
|
||
|
|
query_lower in entry['translated_text'].lower()):
|
||
|
|
results.append(entry)
|
||
|
|
return results
|
||
|
|
|
||
|
|
def clear_history(self):
|
||
|
|
"""Очистить историю"""
|
||
|
|
self.history = []
|
||
|
|
self.save_history()
|
||
|
|
|
||
|
|
def get_statistics(self):
|
||
|
|
"""Получить статистику"""
|
||
|
|
if not self.history:
|
||
|
|
return {
|
||
|
|
"total_translations": 0,
|
||
|
|
"total_chars": 0,
|
||
|
|
"total_words": 0,
|
||
|
|
"avg_chars": 0,
|
||
|
|
"avg_words": 0
|
||
|
|
}
|
||
|
|
|
||
|
|
total_chars = sum(entry.get('char_count', 0) for entry in self.history)
|
||
|
|
total_words = sum(entry.get('word_count', 0) for entry in self.history)
|
||
|
|
|
||
|
|
return {
|
||
|
|
"total_translations": len(self.history),
|
||
|
|
"total_chars": total_chars,
|
||
|
|
"total_words": total_words,
|
||
|
|
"avg_chars": total_chars // len(self.history) if self.history else 0,
|
||
|
|
"avg_words": total_words // len(self.history) if self.history else 0,
|
||
|
|
"first_translation": self.history[-1]['timestamp'] if self.history else None,
|
||
|
|
"last_translation": self.history[0]['timestamp'] if self.history else None
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class BatchProcessor:
|
||
|
|
"""Batch обработка файлов"""
|
||
|
|
|
||
|
|
def __init__(self, translate_func):
|
||
|
|
self.translate_func = translate_func
|
||
|
|
self.results = []
|
||
|
|
|
||
|
|
def process_files(self, file_paths, output_dir=None):
|
||
|
|
"""Обработать несколько файлов"""
|
||
|
|
self.results = []
|
||
|
|
|
||
|
|
for file_path in file_paths:
|
||
|
|
try:
|
||
|
|
# Читаем файл
|
||
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||
|
|
content = f.read()
|
||
|
|
|
||
|
|
# Переводим
|
||
|
|
translation = self.translate_func(content)
|
||
|
|
|
||
|
|
# Определяем путь для сохранения
|
||
|
|
if output_dir:
|
||
|
|
output_path = Path(output_dir) / f"{Path(file_path).stem}_ru{Path(file_path).suffix}"
|
||
|
|
else:
|
||
|
|
output_path = Path(file_path).parent / f"{Path(file_path).stem}_ru{Path(file_path).suffix}"
|
||
|
|
|
||
|
|
# Сохраняем
|
||
|
|
with open(output_path, 'w', encoding='utf-8') as f:
|
||
|
|
f.write(translation)
|
||
|
|
|
||
|
|
self.results.append({
|
||
|
|
"file": file_path,
|
||
|
|
"status": "success",
|
||
|
|
"output": str(output_path),
|
||
|
|
"chars": len(content)
|
||
|
|
})
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
self.results.append({
|
||
|
|
"file": file_path,
|
||
|
|
"status": "error",
|
||
|
|
"error": str(e)
|
||
|
|
})
|
||
|
|
|
||
|
|
return self.results
|
||
|
|
|
||
|
|
def get_summary(self):
|
||
|
|
"""Получить сводку по обработке"""
|
||
|
|
success = sum(1 for r in self.results if r['status'] == 'success')
|
||
|
|
failed = sum(1 for r in self.results if r['status'] == 'error')
|
||
|
|
total_chars = sum(r.get('chars', 0) for r in self.results if r['status'] == 'success')
|
||
|
|
|
||
|
|
return {
|
||
|
|
"total": len(self.results),
|
||
|
|
"success": success,
|
||
|
|
"failed": failed,
|
||
|
|
"total_chars": total_chars
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
class FileFormatHandler:
|
||
|
|
"""Обработка различных форматов файлов"""
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def read_file(file_path):
|
||
|
|
"""Прочитать файл с автоопределением кодировки"""
|
||
|
|
encodings = ['utf-8', 'cp1251', 'latin-1', 'utf-16']
|
||
|
|
|
||
|
|
for encoding in encodings:
|
||
|
|
try:
|
||
|
|
with open(file_path, 'r', encoding=encoding) as f:
|
||
|
|
return f.read()
|
||
|
|
except UnicodeDecodeError:
|
||
|
|
continue
|
||
|
|
|
||
|
|
raise ValueError(f"Не удалось определить кодировку файла: {file_path}")
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def save_file(file_path, content, encoding='utf-8'):
|
||
|
|
"""Сохранить файл"""
|
||
|
|
with open(file_path, 'w', encoding=encoding) as f:
|
||
|
|
f.write(content)
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def get_supported_formats():
|
||
|
|
"""Получить список поддерживаемых форматов"""
|
||
|
|
return [
|
||
|
|
("Текстовые файлы", "*.txt"),
|
||
|
|
("Markdown файлы", "*.md"),
|
||
|
|
("Python файлы", "*.py"),
|
||
|
|
("JavaScript файлы", "*.js"),
|
||
|
|
("HTML файлы", "*.html"),
|
||
|
|
("CSS файлы", "*.css"),
|
||
|
|
("JSON файлы", "*.json"),
|
||
|
|
("XML файлы", "*.xml"),
|
||
|
|
("Все файлы", "*.*")
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
class Settings:
|
||
|
|
"""Управление настройками приложения"""
|
||
|
|
|
||
|
|
def __init__(self, settings_file="settings.json"):
|
||
|
|
self.settings_file = settings_file
|
||
|
|
self.default_settings = {
|
||
|
|
"theme": "dark",
|
||
|
|
"font_size": 13,
|
||
|
|
"auto_save": False,
|
||
|
|
"save_history": True,
|
||
|
|
"max_history": 100,
|
||
|
|
"timeout": 120,
|
||
|
|
"model": "translator",
|
||
|
|
"window_width": 1000,
|
||
|
|
"window_height": 700
|
||
|
|
}
|
||
|
|
self.settings = self.load_settings()
|
||
|
|
|
||
|
|
def load_settings(self):
|
||
|
|
"""Загрузить настройки"""
|
||
|
|
if os.path.exists(self.settings_file):
|
||
|
|
try:
|
||
|
|
with open(self.settings_file, 'r', encoding='utf-8') as f:
|
||
|
|
loaded = json.load(f)
|
||
|
|
# Объединить с дефолтными настройками
|
||
|
|
return {**self.default_settings, **loaded}
|
||
|
|
except:
|
||
|
|
return self.default_settings.copy()
|
||
|
|
return self.default_settings.copy()
|
||
|
|
|
||
|
|
def save_settings(self):
|
||
|
|
"""Сохранить настройки"""
|
||
|
|
try:
|
||
|
|
with open(self.settings_file, 'w', encoding='utf-8') as f:
|
||
|
|
json.dump(self.settings, f, indent=2)
|
||
|
|
except Exception as e:
|
||
|
|
print(f"Ошибка сохранения настроек: {e}")
|
||
|
|
|
||
|
|
def get(self, key, default=None):
|
||
|
|
"""Получить значение настройки"""
|
||
|
|
return self.settings.get(key, default)
|
||
|
|
|
||
|
|
def set(self, key, value):
|
||
|
|
"""Установить значение настройки"""
|
||
|
|
self.settings[key] = value
|
||
|
|
self.save_settings()
|
||
|
|
|
||
|
|
def reset(self):
|
||
|
|
"""Сбросить настройки к дефолтным"""
|
||
|
|
self.settings = self.default_settings.copy()
|
||
|
|
self.save_settings()
|
||
|
|
|
||
|
|
|
||
|
|
def format_timestamp(iso_timestamp):
|
||
|
|
"""Форматировать timestamp для отображения"""
|
||
|
|
try:
|
||
|
|
dt = datetime.fromisoformat(iso_timestamp)
|
||
|
|
return dt.strftime("%d.%m.%Y %H:%M")
|
||
|
|
except:
|
||
|
|
return iso_timestamp
|
||
|
|
|
||
|
|
|
||
|
|
def format_file_size(size_bytes):
|
||
|
|
"""Форматировать размер файла"""
|
||
|
|
for unit in ['Б', 'КБ', 'МБ', 'ГБ']:
|
||
|
|
if size_bytes < 1024.0:
|
||
|
|
return f"{size_bytes:.1f} {unit}"
|
||
|
|
size_bytes /= 1024.0
|
||
|
|
return f"{size_bytes:.1f} ТБ"
|
||
|
|
|
||
|
|
|
||
|
|
def truncate_text(text, max_length=100):
|
||
|
|
"""Обрезать текст с многоточием"""
|
||
|
|
if len(text) <= max_length:
|
||
|
|
return text
|
||
|
|
return text[:max_length-3] + "..."
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
# Тестирование утилит
|
||
|
|
print("=== Тест TranslationHistory ===")
|
||
|
|
history = TranslationHistory("test_history.json")
|
||
|
|
history.add_translation("Hello world", "Привет мир")
|
||
|
|
history.add_translation("Good morning", "Доброе утро")
|
||
|
|
print(f"История: {len(history.history)} записей")
|
||
|
|
print(f"Статистика: {history.get_statistics()}")
|
||
|
|
|
||
|
|
print("\n=== Тест Settings ===")
|
||
|
|
settings = Settings("test_settings.json")
|
||
|
|
print(f"Тема: {settings.get('theme')}")
|
||
|
|
settings.set('theme', 'light')
|
||
|
|
print(f"Новая тема: {settings.get('theme')}")
|
||
|
|
|
||
|
|
print("\n=== Тест форматирования ===")
|
||
|
|
print(f"Timestamp: {format_timestamp(datetime.now().isoformat())}")
|
||
|
|
print(f"Размер: {format_file_size(1024*1024*5.5)}")
|
||
|
|
print(f"Текст: {truncate_text('A' * 150, 50)}")
|