Initial commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
"""
|
||||
Утилиты для 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)}")
|
||||
Reference in New Issue
Block a user