eoboldina/localization-manager
0
1#!/usr/bin/env python32"""3Полнофункциональный менеджер локализации для HuggingFace Spaces4Включает все возможности оригинального приложения, но без проблемных зависимостей5"""6 7import gradio as gr8import json9import tempfile10import shutil11import os12import zipfile13import io14from datetime import datetime15from typing import Dict, Set, Tuple, List16from file_manager import FileManager17from neural_translator import get_neural_translator, initialize_neural_translator18 19def safe_get_path(file_input):20 """Безопасное получение пути к файлу"""21 if file_input is None:22 return None23 if isinstance(file_input, str):24 return file_input25 if hasattr(file_input, 'name'):26 return file_input.name27 return str(file_input)28 29def auto_translate_template(template_file, target_language):30 """31 Автоматический перевод шаблона с помощью нейросетей32 """33 if not template_file:34 return "❌ Загрузите шаблон для перевода.", None35 36 # Получаем нейросетевой переводчик37 translator = get_neural_translator()38 if not translator or not translator.is_available():39 return "❌ Нейросетевой переводчик недоступен. Попробуйте позже.", None40 41 try:42 template_path = safe_get_path(template_file)43 if not template_path:44 return "❌ Не удалось получить путь к файлу", None45 46 # Создаем временную директорию47 temp_dir = tempfile.mkdtemp()48 49 try:50 # Определяем тип файла и создаем file manager51 file_manager = FileManager()52 file_type = file_manager._detect_file_type(template_path)53 54 # Загружаем данные из шаблона55 template_data = file_manager.load_file(template_path)56 if not template_data:57 return "❌ Не удалось загрузить данные из шаблона", None58 59 # Определяем целевой язык код60 lang_mapping = {61 "английский (en)": "en",62 "узбекский (uz)": "uz",63 "французский (fr)": "fr",64 "немецкий (de)": "de",65 "испанский (es)": "es",66 "итальянский (it)": "it"67 }68 69 target_lang_code = lang_mapping.get(target_language, "en")70 71 # Переводим все ключи72 translated_data = {}73 total_keys = len(template_data)74 75 for i, (key, russian_text) in enumerate(template_data.items()):76 if russian_text and russian_text.strip():77 # Определяем домен и контекст78 domain = translator.get_domain_from_key(key)79 context_type = translator.get_context_type_from_key(key)80 81 # Переводим текст82 if target_lang_code in ["en", "uz"]:83 # Используем нейросетевые модели84 translated_text = translator.translate_text(85 russian_text, target_lang_code, domain, context_type86 )87 else:88 # Для других языков оставляем исходный текст89 translated_text = f"[TO_TRANSLATE_{target_lang_code.upper()}] {russian_text}"90 91 translated_data[key] = translated_text92 else:93 translated_data[key] = russian_text94 95 # Сохраняем переведенный файл96 output_filename = f"auto_translated_{target_lang_code}_{os.path.basename(template_path)}"97 output_path = os.path.join(temp_dir, output_filename)98 99 file_manager.save_file(output_path, translated_data, file_type)100 101 # Формируем отчет102 neural_translations = sum(1 for v in translated_data.values() 103 if v and not v.startswith("[TO_TRANSLATE_"))104 105 report = f"""✅ Автоматический перевод завершен!106 107📊 **Статистика:**108- Всего ключей: {total_keys}109- Переведено нейросетью: {neural_translations}110- Целевой язык: {target_language}111 112🤖 **Использованные модели:**113- RU→EN: mBART (для английского)114- RU→UZ: NLLB (для узбекского)115 116📝 **Рекомендации:**117- Проверьте качество переводов118- Отредактируйте при необходимости119- Для других языков ({target_lang_code}) требуется ручной перевод120 121🎯 **Готово к использованию!**"""122 123 return report, output_path124 125 finally:126 # Очищаем временные файлы (кроме результата)127 pass128 129 except Exception as e:130 return f"❌ Ошибка при автоматическом переводе: {str(e)}", None131 132 133 134def process_localizations(old_file, new_file, target_files):135 """Полная обработка локализаций"""136 137 # Проверяем входные данные138 if not old_file:139 return "❌ Загрузите старую русскую локаль.", None140 if not new_file:141 return "❌ Загрузите новую русскую локаль.", None142 if not target_files:143 return "❌ Загрузите целевые файлы для обновления.", None144 145 try:146 # Получаем пути к файлам147 old_path = safe_get_path(old_file)148 new_path = safe_get_path(new_file)149 150 if not old_path or not new_path:151 return "❌ Ошибка получения путей к основным файлам", None152 153 # Обрабатываем целевые файлы154 target_paths = []155 if isinstance(target_files, list):156 for tf in target_files:157 tp = safe_get_path(tf)158 if tp and os.path.exists(tp):159 target_paths.append(tp)160 else:161 tp = safe_get_path(target_files)162 if tp and os.path.exists(tp):163 target_paths.append(tp)164 165 if not target_paths:166 return "❌ Целевые файлы не найдены или недоступны", None167 168 # Проверяем существование основных файлов169 if not os.path.exists(old_path):170 return f"❌ Старая локаль не найдена: {os.path.basename(old_path)}", None171 if not os.path.exists(new_path):172 return f"❌ Новая локаль не найдена: {os.path.basename(new_path)}", None173 174 # Создаем файловый менеджер и определяем тип файлов175 file_manager = FileManager()176 177 try:178 old_type = file_manager.detect_file_type(old_path)179 new_type = file_manager.detect_file_type(new_path)180 181 if old_type != new_type:182 return f"❌ Типы файлов не совпадают: {old_type} vs {new_type}", None183 184 # Проверяем типы целевых файлов185 for target_path in target_paths:186 target_type = file_manager.detect_file_type(target_path)187 if target_type != old_type:188 return f"❌ Тип файла {os.path.basename(target_path)} ({target_type}) не совпадает с основным ({old_type})", None189 190 # Загружаем файлы191 old_data, old_order = file_manager.load_file(old_path)192 new_data, new_order = file_manager.load_file(new_path)193 194 except Exception as e:195 return f"❌ {str(e)}", None196 197 # Сравниваем локализации198 changes, added_keys, updated_keys = file_manager.compare_files(old_path, new_path)199 200 if not changes:201 result_msg = "Между старой и новой локалью изменений не обнаружено.\n\n"202 result_msg += f"общая информация\n"203 result_msg += f"тип файлов: {old_type.upper()}\n"204 result_msg += f"обработано файлов: {len(target_paths)}\n"205 result_msg += f"изменений: 0\n\n"206 result_msg += f"Все локали уже актуальны."207 return result_msg, None208 209 # Создаем временную директорию210 temp_dir = tempfile.mkdtemp()211 212 try:213 processed_files = []214 total_changes = 0215 errors = []216 217 # Обрабатываем каждый целевой файл218 for i, target_path in enumerate(target_paths):219 try:220 # Применяем изменения с использованием файлового менеджера221 updated_data, file_changes = file_manager.apply_changes_to_target(222 target_path, changes, added_keys, updated_keys, new_data, new_order223 )224 225 if file_changes > 0:226 # Сохраняем обновленный файл227 updated_filename = f"updated_{os.path.basename(target_path)}"228 updated_path = os.path.join(temp_dir, updated_filename)229 file_manager.save_file(updated_path, updated_data, new_order)230 processed_files.append(updated_path)231 total_changes += file_changes232 233 except Exception as e:234 errors.append(f"Ошибка обработки {os.path.basename(target_path)}: {str(e)}")235 continue236 237 if not processed_files:238 error_msg = "❌ Не удалось обработать ни одного файла"239 if errors:240 error_msg += f"\nОшибки:\n" + "\n".join(errors)241 return error_msg, None242 243 # Создаем шаблон для переводчиков244 template_created = False245 if added_keys:246 # Формируем шаблон в порядке ключей новой локали247 template = {}248 for key in new_order:249 if key in added_keys:250 template[key] = new_data[key]251 252 # Определяем расширение файла для шаблона253 template_extension = ".properties" if old_type == "properties" else ".json"254 template_filename = f"translation_template{template_extension}"255 template_path = os.path.join(temp_dir, template_filename)256 257 try:258 file_manager.save_file(template_path, template, new_order)259 processed_files.append(template_path)260 template_created = True261 except:262 pass263 264 # Создаем ZIP архив265 zip_path = os.path.join(temp_dir, "localization_results.zip")266 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:267 for file_path in processed_files:268 zip_file.write(file_path, os.path.basename(file_path))269 270 # Формируем детальное сообщение результата со статистикой271 result_msg = "Обработка завершена.\n\n"272 273 # Общая информация274 result_msg += f"общая информация\n"275 result_msg += f"тип файлов: {old_type.upper()}\n"276 result_msg += f"обработано файлов: {len(target_paths)}\n"277 result_msg += f"обновлено: {len(processed_files) - (1 if template_created else 0)}\n"278 if errors:279 result_msg += f"ошибок при обработке: {len(errors)}\n"280 result_msg += f"шаблон создан: {'да' if template_created else 'нет'}\n\n"281 282 # Детальная статистика изменений283 result_msg += f"статистика изменений\n"284 result_msg += f"всего изменений: {len(added_keys) + len(updated_keys)}\n"285 result_msg += f"новых ключей: {len(added_keys)}\n"286 result_msg += f"изменённых ключей: {len(updated_keys)}\n"287 result_msg += f"удалённых ключей: 0\n\n"288 289 # Детализация новых ключей290 if added_keys:291 result_msg += f"новые ключи ({len(added_keys)}):\n"292 for key in sorted(list(added_keys))[:10]: # Показываем первые 10293 result_msg += f"{key}\n"294 if len(added_keys) > 10:295 result_msg += f"... и ещё {len(added_keys) - 10}\n"296 result_msg += "\n"297 298 # Детализация измененных ключей299 if updated_keys:300 result_msg += f"изменённые ключи ({len(updated_keys)}):\n"301 for key in sorted(list(updated_keys))[:10]: # Показываем первые 10302 result_msg += f"{key}\n"303 if len(updated_keys) > 10:304 result_msg += f"... и ещё {len(updated_keys) - 10}\n"305 result_msg += "\n"306 307 # Содержимое архива308 result_msg += f"содержимое архива:\n"309 for file_path in processed_files:310 filename = os.path.basename(file_path)311 if filename == "translation_template.json":312 result_msg += f"{filename} (шаблон)\n"313 else:314 result_msg += f"{filename} (обновлённая локаль)\n"315 316 if errors:317 result_msg += f"\nпредупреждения:\n"318 for error in errors:319 result_msg += f"{error}\n"320 321 return result_msg, zip_path322 323 finally:324 # Временная папка будет очищена автоматически при завершении сессии325 pass326 327 except Exception as e:328 return f"❌ Критическая ошибка: {str(e)}", None329 330def apply_translations(template_file, target_file):331 """Применяет переводы из шаблона к целевому файлу"""332 333 if not template_file or not target_file:334 return "❌ Загрузите оба файла: шаблон переводов и целевой файл.", None335 336 try:337 template_path = safe_get_path(template_file)338 target_path = safe_get_path(target_file)339 340 if not template_path or not target_path:341 return "❌ Ошибка получения путей к файлам", None342 343 if not os.path.exists(template_path):344 return f"❌ Файл шаблона не найден: {os.path.basename(template_path)}", None345 if not os.path.exists(target_path):346 return f"❌ Целевой файл не найден: {os.path.basename(target_path)}", None347 348 # Создаем файловый менеджер и загружаем данные349 file_manager = FileManager()350 351 try:352 template_type = file_manager.detect_file_type(template_path)353 target_type = file_manager.detect_file_type(target_path)354 355 if template_type != target_type:356 return f"❌ Типы файлов не совпадают: шаблон ({template_type}) vs целевой ({target_type})", None357 358 template_data, _ = file_manager.load_file(template_path)359 target_data, target_order = file_manager.load_file(target_path)360 361 except Exception as e:362 return f"❌ {str(e)}", None363 364 # Применяем переводы365 updated_data = target_data.copy()366 applied_count = 0367 368 for key, value in template_data.items():369 if key in updated_data:370 updated_data[key] = value371 applied_count += 1372 373 if applied_count == 0:374 return "ℹ️ Не найдено ключей для применения из шаблона", None375 376 # Сохраняем результат377 temp_dir = tempfile.mkdtemp()378 result_filename = f"translated_{os.path.basename(target_path)}"379 result_path = os.path.join(temp_dir, result_filename)380 381 try:382 file_manager.save_file(result_path, updated_data, target_order)383 except Exception as e:384 return f"❌ {str(e)}", None385 386 result_msg = f"Переводы применены.\n\n"387 result_msg += f"Тип файла: {target_type.upper()}\n"388 result_msg += f"Применено переводов: {applied_count}\n"389 result_msg += f"Результат сохранен как: {result_filename}"390 391 return result_msg, result_path392 393 except Exception as e:394 return f"❌ Критическая ошибка: {str(e)}", None395 396def create_full_interface():397 """Создает полнофункциональный интерфейс"""398 399 with gr.Blocks(400 title="Localization Manager Pro", 401 theme=gr.themes.Soft(),402 css="""403 .gradio-container {404 max-width: 1400px !important;405 }406 407 /* Кастомные стили для файловых компонентов */408 .custom-file-upload {409 border: 2px dashed #7c3aed !important;410 border-radius: 12px !important;411 background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%) !important;412 padding: 20px !important;413 text-align: center !important;414 transition: all 0.3s ease !important;415 min-height: 100px !important;416 }417 418 .custom-file-upload:hover {419 border-color: #5b21b6 !important;420 background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%) !important;421 transform: translateY(-2px) !important;422 box-shadow: 0 4px 12px rgba(124, 58, 237, 0.15) !important;423 }424 425 .custom-file-upload .file-preview {426 font-size: 14px !important;427 color: #374151 !important;428 margin-top: 8px !important;429 }430 431 .custom-file-upload .file-preview::before {432 content: "📄 ";433 color: #7c3aed;434 font-size: 16px;435 }436 437 /* Стили для лейблов */438 .custom-label {439 font-size: 16px !important;440 font-weight: 600 !important;441 color: #1f2937 !important;442 margin-bottom: 8px !important;443 display: block !important;444 }445 446 .custom-label .highlight {447 color: #7c3aed !important;448 }449 450 .custom-label .subtitle {451 font-size: 12px !important;452 color: #6b7280 !important;453 font-weight: 400 !important;454 margin-top: 4px !important;455 }456 457 /* Компактные результаты */458 .compact-result {459 min-height: 60px !important;460 max-height: 80px !important;461 border: 1px solid #e5e7eb !important;462 border-radius: 8px !important;463 }464 465 /* Улучшенные кнопки */466 .primary-button {467 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;468 border: none !important;469 border-radius: 12px !important;470 color: white !important;471 font-weight: 600 !important;472 padding: 16px 32px !important;473 font-size: 16px !important;474 transition: all 0.3s ease !important;475 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important;476 }477 478 .primary-button:hover {479 background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%) !important;480 transform: translateY(-2px) !important;481 box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3) !important;482 }483 484 /* Улучшенные текстовые поля */485 .custom-textbox {486 border: 1px solid #e5e7eb !important;487 border-radius: 8px !important;488 background: #fafafa !important;489 }490 491 /* Общие улучшения */492 .tab-nav {493 margin-bottom: 30px !important;494 }495 496 .main-title {497 font-size: 32px !important;498 font-weight: 700 !important;499 color: #1f2937 !important;500 margin-bottom: 8px !important;501 }502 503 .subtitle {504 font-size: 16px !important;505 color: #6b7280 !important;506 margin-bottom: 15px !important;507 }508 """509 ) as interface:510 511 gr.Markdown("# Localization Manager", elem_classes=["main-title"])512 gr.Markdown("инструмент для управления переводами и локализацией", elem_classes=["subtitle"])513 514 with gr.Tab("автоматическая обработка"):515 with gr.Row():516 with gr.Column():517 gr.Markdown("### загрузка файлов", elem_classes=["custom-label"])518 519 old_ru_file = gr.File(520 label="старая русская локаль",521 file_types=[".json", ".properties"],522 type="filepath",523 elem_classes=["custom-file-upload"],524 height=100525 )526 new_ru_file = gr.File(527 label="новая русская локаль", 528 file_types=[".json", ".properties"],529 type="filepath",530 elem_classes=["custom-file-upload"],531 height=100532 )533 target_files = gr.File(534 label="целевые локали для обновления",535 file_count="multiple",536 file_types=[".json", ".properties"],537 type="filepath",538 elem_classes=["custom-file-upload"],539 height=100540 )541 542 process_btn = gr.Button(543 "обработать локали", 544 variant="primary",545 size="lg",546 elem_classes=["primary-button"]547 )548 549 with gr.Column():550 gr.Markdown("### результаты", elem_classes=["custom-label"])551 552 process_result = gr.Textbox(553 label="статистика",554 lines=20,555 interactive=False,556 show_copy_button=True,557 elem_classes=["custom-textbox"]558 )559 process_output = gr.File(560 label="скачать архив",561 interactive=False,562 elem_classes=["compact-result"],563 height=70564 )565 566 process_btn.click(567 fn=process_localizations,568 inputs=[old_ru_file, new_ru_file, target_files],569 outputs=[process_result, process_output]570 )571 572 with gr.Tab("применение переводов"):573 with gr.Row():574 with gr.Column():575 gr.Markdown("### загрузка файлов", elem_classes=["custom-label"])576 577 template_file = gr.File(578 label="шаблон с переводами (JSON или Properties)",579 file_types=[".json", ".properties"],580 type="filepath",581 elem_classes=["custom-file-upload"],582 height=100583 )584 apply_target_file = gr.File(585 label="целевая локаль (JSON или Properties)",586 file_types=[".json", ".properties"],587 type="filepath",588 elem_classes=["custom-file-upload"],589 height=100590 )591 592 apply_btn = gr.Button(593 "применить переводы", 594 variant="primary",595 size="lg",596 elem_classes=["primary-button"]597 )598 599 with gr.Column():600 gr.Markdown("### результаты", elem_classes=["custom-label"])601 602 apply_result = gr.Textbox(603 label="статистика",604 lines=10,605 interactive=False,606 show_copy_button=True,607 elem_classes=["custom-textbox"]608 )609 apply_output = gr.File(610 label="скачать обновлённый файл",611 interactive=False,612 elem_classes=["compact-result"],613 height=70614 )615 616 apply_btn.click(617 fn=apply_translations,618 inputs=[template_file, apply_target_file],619 outputs=[apply_result, apply_output]620 )621 622 with gr.Tab("нейросетевой перевод"):623 gr.Markdown("""624 ## автоматический перевод с помощью нейросетей625 626 Автоматический перевод шаблонов с помощью обученных нейросетевых моделей.627 628 **Поддерживаемые языки:**629 - **Английский** - с помощью mBART модели630 - **Узбекский** - с помощью NLLB модели631 - **Другие языки** - будут помечены для ручного перевода632 """)633 634 with gr.Row():635 with gr.Column():636 gr.Markdown("### загрузка шаблона", elem_classes=["custom-label"])637 638 neural_template_file = gr.File(639 label="шаблон для перевода (JSON/Properties)",640 file_types=[".json", ".properties"],641 type="filepath",642 elem_classes=["custom-file-upload"],643 height=100644 )645 646 neural_target_language = gr.Dropdown(647 label="целевой язык",648 choices=[649 "английский (en)",650 "узбекский (uz)", 651 "французский (fr)",652 "немецкий (de)",653 "испанский (es)",654 "итальянский (it)"655 ],656 value="английский (en)",657 elem_classes=["custom-textbox"]658 )659 660 neural_translate_btn = gr.Button(661 "перевести автоматически", 662 variant="primary",663 elem_classes=["primary-button"]664 )665 666 with gr.Column():667 gr.Markdown("### результат", elem_classes=["custom-label"])668 669 neural_result = gr.Textbox(670 label="отчет о переводе",671 lines=15,672 interactive=False,673 show_copy_button=True,674 elem_classes=["custom-textbox"]675 )676 neural_output = gr.File(677 label="скачать переведенный файл",678 interactive=False,679 elem_classes=["compact-result"],680 height=70681 )682 683 # Подключаем обработчик684 neural_translate_btn.click(685 fn=auto_translate_template,686 inputs=[neural_template_file, neural_target_language],687 outputs=[neural_result, neural_output]688 )689 690 with gr.Tab("справка"):691 gr.Markdown("""692 ## руководство пользователя693 694 ### автоматическая обработка695 696 **шаг 1:** загрузите файлы697 - **старая русская локаль** - предыдущая версия ru.json или messages.properties698 - **новая русская локаль** - новая версия с изменениями (тот же формат)699 - **целевые локали** - файлы других языков (en.json, fr.json, messages-en.properties, и т.д.)700 701 **шаг 2:** нажмите "обработать локали"702 703 **результат:** ZIP архив с:704 - обновлёнными локалями705 - шаблоном для переводчиков (только новые ключи)706 707 ### применение переводов708 709 **шаг 1:** отправьте `translation_template.json` или `.properties` переводчикам710 711 **шаг 2:** после получения переведенного шаблона:712 - загрузите заполненный шаблон713 - загрузите целевую локаль714 - нажмите "применить переводы"715 716 **результат:** обновлённый файл с новыми переводами717 718 ### 🤖 нейросетевой перевод719 720 Автоматический перевод с помощью обученных нейросетевых моделей:721 722 **шаг 1:** загрузите шаблон для перевода (JSON или Properties)723 724 **шаг 2:** выберите целевой язык:725 - **Английский** - автоматический перевод с помощью mBART726 - **Узбекский** - автоматический перевод с помощью NLLB727 - **Другие языки** - разметка для ручного перевода728 729 **шаг 3:** нажмите "перевести автоматически"730 731 **результат:** полностью переведенный файл готовый к использованию!732 733 **преимущества:**734 - Мгновенный перевод всех ключей735 - Контекстуальный перевод для HR-доменов736 - Автоматическое определение типа контекста737 - Подробная статистика переводов738 739 ### поддерживаемые форматы740 741 #### JSON (фронтенд)742 - **формат файлов:** .json743 - **кодировка:** UTF-8744 - **структура:** плоский объект ключ-значение745 746 ```json747 {748 "key1": "Значение 1",749 "key2": "Значение 2"750 }751 ```752 753 #### Properties (бэкенд)754 - **формат файлов:** .properties755 - **кодировка:** UTF-8756 - **структура:** ключ=значение757 758 ```properties759 key1=Значение 1760 key2=Значение 2761 ```762 763 **Важно:** Все файлы в одной операции должны быть одного формата.764 """)765 766 return interface767 768if __name__ == "__main__":769 # Инициализируем нейросетевой переводчик при запуске770 print("Инициализируем нейросетевые модели...")771 initialize_neural_translator()772 773 interface = create_full_interface()774 interface.launch() 775 