From eb662fab5ad62f2ad9185213b5fbad9754803df4 Mon Sep 17 00:00:00 2001 From: Tykayn Date: Thu, 4 Sep 2025 00:14:55 +0200 Subject: [PATCH] auto traduction ollama --- public/logo-osm.png | Bin 0 -> 12811 bytes .../__pycache__/wiki_compare.cpython-313.pyc | Bin 0 -> 46198 bytes wiki_compare/wiki_compare.py | 85 ++++- wiki_compare/wiki_translate.py | 329 ++++++++++++++++++ 4 files changed, 407 insertions(+), 7 deletions(-) create mode 100644 public/logo-osm.png create mode 100644 wiki_compare/__pycache__/wiki_compare.cpython-313.pyc create mode 100644 wiki_compare/wiki_translate.py diff --git a/public/logo-osm.png b/public/logo-osm.png new file mode 100644 index 0000000000000000000000000000000000000000..4388167c0614dab34e408a571e794b1aef9a3336 GIT binary patch literal 12811 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 literal 0 HcmV?d00001 diff --git a/wiki_compare/__pycache__/wiki_compare.cpython-313.pyc b/wiki_compare/__pycache__/wiki_compare.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc9871438d74f7e568796bb619c0698dd0091ac4 GIT binary patch literal 46198 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≠VQ&6h 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 literal 0 HcmV?d00001 diff --git a/wiki_compare/wiki_compare.py b/wiki_compare/wiki_compare.py index dbf0806..60156f9 100755 --- a/wiki_compare/wiki_compare.py +++ b/wiki_compare/wiki_compare.py @@ -57,7 +57,7 @@ WIKI_PAGES_CSV = "wiki_pages.csv" OUTDATED_PAGES_FILE = "outdated_pages.json" STALENESS_HISTOGRAM_FILE = "staleness_histogram.png" # Number of wiki pages to examine -NUM_WIKI_PAGES = 50 +NUM_WIKI_PAGES = 2 # HTML cache folder HTML_CACHE_DIR = "html_cache" @@ -66,6 +66,12 @@ try: nltk.data.find('tokenizers/punkt') except LookupError: nltk.download('punkt') + +# Also download punkt_tab resource which is needed for sent_tokenize +try: + nltk.data.find('tokenizers/punkt_tab') +except LookupError: + nltk.download('punkt_tab') # Create HTML cache directory if it doesn't exist Path(HTML_CACHE_DIR).mkdir(exist_ok=True) @@ -124,6 +130,29 @@ def fetch_top_keys(limit=NUM_WIKI_PAGES): logger.error(f"Error fetching data from TagInfo API: {e}") return [] +def load_json_data(filename): + """ + Load data from a JSON file + + Args: + filename (str): Name of the file + + Returns: + dict: Data loaded from the file or empty dict if file doesn't exist + """ + try: + if os.path.exists(filename): + with open(filename, 'r', encoding='utf-8') as f: + data = json.load(f) + logger.info(f"Data loaded from {filename}") + return data + else: + logger.info(f"File {filename} doesn't exist, returning empty dict") + return {} + except (IOError, json.JSONDecodeError) as e: + logger.error(f"Error loading data from {filename}: {e}") + return {} + def save_to_json(data, filename): """ Save data to a JSON file @@ -138,6 +167,52 @@ def save_to_json(data, filename): logger.info(f"Data saved to {filename}") except IOError as e: logger.error(f"Error saving data to {filename}: {e}") + +def save_with_history(data, filename): + """ + Save data to a JSON file while preserving history + + This function loads existing data from the file (if it exists), + adds the new data to the history, and saves the updated data back to the file. + + Args: + data: New data to save + filename (str): Name of the file + """ + try: + # Load existing data + existing_data = load_json_data(filename) + + # Create a timestamp for the current data + current_timestamp = datetime.now().isoformat() + + # Initialize history if it doesn't exist + if 'history' not in existing_data: + existing_data['history'] = {} + + # Add current regular_pages and specific_pages to history + history_entry = { + 'regular_pages': data.get('regular_pages', []), + 'specific_pages': data.get('specific_pages', []) + } + + # Add the entry to history with timestamp as key + existing_data['history'][current_timestamp] = history_entry + + # Update the current data + existing_data['regular_pages'] = data.get('regular_pages', []) + existing_data['specific_pages'] = data.get('specific_pages', []) + existing_data['last_updated'] = current_timestamp + + # Save the updated data + with open(filename, 'w', encoding='utf-8') as f: + json.dump(existing_data, f, indent=2, ensure_ascii=False) + + logger.info(f"Data with history saved to {filename}") + except (IOError, json.JSONDecodeError) as e: + logger.error(f"Error saving data with history to {filename}: {e}") + # Fallback to regular save if there's an error + save_to_json(data, filename) def check_grammar_with_grammalecte(text): """ @@ -604,10 +679,6 @@ def fetch_wiki_page(key, language='en', is_specific_page=False): 'grammar_suggestions': grammar_suggestions, 'html_content': html_content } - - except requests.exceptions.RequestException as e: - logger.error(f"Error fetching wiki page for {'page' if is_specific_page else 'key'} '{key}' in {language}: {e}") - return None def generate_staleness_histogram(wiki_pages): """ @@ -1183,8 +1254,8 @@ def main(): "last_updated": datetime.now().isoformat() } - # Save pages that need updating to JSON - save_to_json(output_data, OUTDATED_PAGES_FILE) + # Save pages that need updating to JSON with history + save_with_history(output_data, OUTDATED_PAGES_FILE) # Print the top pages needing updates print(f"\n===== TOP {min(NUM_WIKI_PAGES, len(pages_to_update))} WIKI PAGES NEEDING UPDATES =====") diff --git a/wiki_compare/wiki_translate.py b/wiki_compare/wiki_translate.py new file mode 100644 index 0000000..2b5d8fe --- /dev/null +++ b/wiki_compare/wiki_translate.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +wiki_translate.py + +This script translates wiki pages that don't have translations using the Ollama server +with the mistral:7b model. It saves the translations in a JSON file that is ignored by +.gitignore. + +Usage: + python wiki_translate.py [key] + + If a key is provided, only that page will be translated. + If no key is provided, all pages missing translations will be processed. + +Output: + - translations.json: JSON file containing the translations +""" + +import json +import os +import sys +import logging +import requests +from pathlib import Path +from datetime import datetime +from bs4 import BeautifulSoup + +# Import functions from wiki_compare.py +from wiki_compare import ( + fetch_wiki_page, + load_json_data, + save_to_json, + save_with_history, + SPECIFIC_PAGES, + logger +) + +# Constants +TRANSLATIONS_FILE = "translations.json" +OLLAMA_API_URL = "http://localhost:11434/api/generate" +OLLAMA_MODEL = "mistral:7b" + +def extract_main_content(html_content): + """ + Extract the main content from a wiki page HTML + + Args: + html_content (str): HTML content of the wiki page + + Returns: + str: Main content text + """ + soup = BeautifulSoup(html_content, 'html.parser') + + # Find the main content div + content = soup.select_one('#mw-content-text') + if not content: + logger.warning("Could not find main content div") + return "" + + # Remove script and style elements + for script in content.select('script, style'): + script.extract() + + # Remove navigation elements + for nav in content.select('.languages, .mw-editsection, #toc, .toc'): + nav.extract() + + # Get text + clean_text = content.get_text(separator='\n', strip=True) + + return clean_text + +def translate_text(text, model=OLLAMA_MODEL): + """ + Translate text using Ollama API + + Args: + text (str): Text to translate + model (str): Ollama model to use + + Returns: + str: Translated text + """ + logger.info(f"Translating text using Ollama model {model}") + + # Prepare the prompt + prompt = f"""Translate the following English text to French. +Maintain the original formatting as much as possible. +Keep technical terms intact when appropriate. +Preserve mediawiki formatting if present. + +English text: +{text} + +French translation:""" + + # Prepare the request + data = { + "model": model, + "prompt": prompt, + "stream": False + } + + try: + response = requests.post(OLLAMA_API_URL, json=data) + response.raise_for_status() + result = response.json() + + # Extract the translated text + translated_text = result.get('response', '') + + logger.info(f"Translation successful, received {len(translated_text)} characters") + return translated_text + + except requests.exceptions.RequestException as e: + logger.error(f"Error translating text: {e}") + return "" + +def translate_wiki_page(key): + """ + Translate a wiki page + + Args: + key (str): Key or page title + + Returns: + dict: Translation information + """ + logger.info(f"Translating wiki page for key: {key}") + + # Check if the key is a specific page + is_specific_page = key in SPECIFIC_PAGES or key.startswith('http') or key.startswith('FR:') + + # Fetch the English page + en_page = fetch_wiki_page(key, 'en', is_specific_page=is_specific_page) + if not en_page: + logger.warning(f"English page for key '{key}' not found") + return None + + # Check if French page already exists + fr_page = fetch_wiki_page(key, 'fr', is_specific_page=is_specific_page) + if fr_page: + logger.info(f"French page for key '{key}' already exists") + return None + + # Extract the main content from the English page + html_content = en_page.get('html_content', '') + if not html_content: + logger.warning(f"No HTML content found for key '{key}'") + return None + + main_content = extract_main_content(html_content) + if not main_content: + logger.warning(f"No main content extracted for key '{key}'") + return None + + # Translate the main content + translated_content = translate_text(main_content) + if not translated_content: + logger.warning(f"Translation failed for key '{key}'") + return None + + # Create translation information + translation_info = { + 'key': key, + 'en_page': { + 'url': en_page.get('url', ''), + 'last_modified': en_page.get('last_modified', ''), + 'word_count': en_page.get('word_count', 0) + }, + 'translated_content': translated_content, + 'translated_at': datetime.now().isoformat(), + 'model': OLLAMA_MODEL, + 'is_specific_page': is_specific_page + } + + logger.info(f"Translation completed for key '{key}'") + return translation_info + +def save_translation(translation_info): + """ + Save translation to the translations file + + Args: + translation_info (dict): Translation information + + Returns: + bool: True if successful, False otherwise + """ + if not translation_info: + return False + + # Load existing translations + translations = load_json_data(TRANSLATIONS_FILE) + + # Initialize if empty + if not translations: + translations = { + 'translations': {}, + 'last_updated': datetime.now().isoformat() + } + + # Add or update translation + key = translation_info['key'] + translations['translations'][key] = translation_info + translations['last_updated'] = datetime.now().isoformat() + + # Save translations + save_to_json(translations, TRANSLATIONS_FILE) + + logger.info(f"Translation saved for key '{key}'") + return True + +def update_translation(key): + """ + Update a translation for a specific key + + Args: + key (str): Key or page title + + Returns: + bool: True if successful, False otherwise + """ + logger.info(f"Updating translation for key: {key}") + + # Translate the page + translation_info = translate_wiki_page(key) + + # Save the translation + if translation_info: + return save_translation(translation_info) + + return False + +def get_missing_translations(): + """ + Get a list of pages missing translations + + Returns: + list: List of keys for pages missing translations + """ + from wiki_compare import fetch_top_keys, NUM_WIKI_PAGES + + missing_translations = [] + + # Process top keys + top_keys = fetch_top_keys(NUM_WIKI_PAGES) + for key_info in top_keys: + key = key_info['key'] + + # Fetch English page + en_page = fetch_wiki_page(key, 'en') + if not en_page: + continue + + # Check if French page exists + fr_page = fetch_wiki_page(key, 'fr') + if not fr_page: + missing_translations.append(key) + + # Process specific pages + for page in SPECIFIC_PAGES: + # Skip pages with FR: prefix + if page.startswith('FR:'): + continue + + # For full URLs, extract the key + if page.startswith('http'): + page_title = page.split('/')[-1] + # Skip if it's a French page + if 'FR:' in page_title: + continue + key = page_title + else: + key = page + + # Fetch English page + en_page = fetch_wiki_page(key, 'en', is_specific_page=True) + if not en_page: + continue + + # Check if French page exists + fr_page = fetch_wiki_page(key, 'fr', is_specific_page=True) + if not fr_page: + missing_translations.append(key) + + return missing_translations + +def get_available_translations(): + """ + Get a list of available translations + + Returns: + dict: Dictionary of available translations + """ + translations = load_json_data(TRANSLATIONS_FILE) + if not translations: + return {} + + return translations.get('translations', {}) + +def main(): + """ + Main function to execute the script + """ + logger.info("Starting wiki_translate.py") + + # Check if a specific key was provided + if len(sys.argv) > 1: + key = sys.argv[1] + logger.info(f"Translating specific key: {key}") + update_translation(key) + else: + # Get missing translations + missing_translations = get_missing_translations() + logger.info(f"Found {len(missing_translations)} pages missing translations") + + # Translate each missing page + for key in missing_translations: + logger.info(f"Processing key: {key}") + update_translation(key) + + logger.info("Translation process completed") + +if __name__ == "__main__": + main() \ No newline at end of file