Upload 48 files
Browse files- .gitattributes +7 -34
- .gitignore +2 -0
- Dockerfile +20 -0
- README.md +246 -10
- api_log.txt +20 -0
- app.py +213 -0
- baseline_analysis.py +55 -0
- baseline_translate.py +51 -0
- data/processed/nepali.en +3 -0
- data/processed/nepali.ne +3 -0
- data/processed/sinhala.en +3 -0
- data/processed/sinhala.si +0 -0
- data/test_sets/test.en +3 -0
- data/test_sets/test.ne +3 -0
- data/test_sets/test.si +500 -0
- debug_load.py +26 -0
- fast_api.py +214 -0
- frontend/WhatsApp Image 2025-10-07 at 12.52.12.jpeg +3 -0
- frontend/backup/index.html +23 -0
- frontend/backup/script.js +42 -0
- frontend/backup/styles.css +54 -0
- frontend/index.html +101 -0
- frontend/public/android-chrome-192x192.png +3 -0
- frontend/public/android-chrome-512x512.png +3 -0
- frontend/public/apple-touch-icon.png +3 -0
- frontend/public/favicon-16x16.png +3 -0
- frontend/public/favicon-32x32.png +3 -0
- frontend/public/favicon.ico +3 -0
- frontend/public/site.webmanifest +1 -0
- frontend/script.js +337 -0
- frontend/site.webmanifest +1 -0
- frontend/styles.css +512 -0
- interactive_translate.py +74 -0
- requirements.txt +92 -0
- scripts/clean_text_data.py +62 -0
- scripts/create_sinhala_test_set.py +37 -0
- scripts/create_test_set.py +44 -0
- scripts/download_model.py +36 -0
- scripts/fetch_parallel_data.py +81 -0
- scripts/scrape_bbc_nepali.py +80 -0
- src/__init__.py +0 -0
- src/__pycache__/evaluate.cpython-313.pyc +0 -0
- src/evaluate_sinhala.py +58 -0
- src/evaluation.py +64 -0
- src/train.py +109 -0
- src/train_nepali.py +95 -0
- src/translate.py +52 -0
- test_analysis.py +84 -0
    	
        .gitattributes
    CHANGED
    
    | @@ -1,35 +1,8 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
             
            *.bin filter=lfs diff=lfs merge=lfs -text
         | 
| 4 | 
            -
            *. | 
| 5 | 
            -
            *. | 
| 6 | 
            -
            *. | 
| 7 | 
            -
            *. | 
| 8 | 
            -
            *. | 
| 9 | 
            -
            *.joblib filter=lfs diff=lfs merge=lfs -text
         | 
| 10 | 
            -
            *.lfs.* filter=lfs diff=lfs merge=lfs -text
         | 
| 11 | 
            -
            *.mlmodel filter=lfs diff=lfs merge=lfs -text
         | 
| 12 | 
            -
            *.model filter=lfs diff=lfs merge=lfs -text
         | 
| 13 | 
            -
            *.msgpack filter=lfs diff=lfs merge=lfs -text
         | 
| 14 | 
            -
            *.npy filter=lfs diff=lfs merge=lfs -text
         | 
| 15 | 
            -
            *.npz filter=lfs diff=lfs merge=lfs -text
         | 
| 16 | 
            -
            *.onnx filter=lfs diff=lfs merge=lfs -text
         | 
| 17 | 
            -
            *.ot filter=lfs diff=lfs merge=lfs -text
         | 
| 18 | 
            -
            *.parquet filter=lfs diff=lfs merge=lfs -text
         | 
| 19 | 
            -
            *.pb filter=lfs diff=lfs merge=lfs -text
         | 
| 20 | 
            -
            *.pickle filter=lfs diff=lfs merge=lfs -text
         | 
| 21 | 
            -
            *.pkl filter=lfs diff=lfs merge=lfs -text
         | 
| 22 | 
            -
            *.pt filter=lfs diff=lfs merge=lfs -text
         | 
| 23 | 
            -
            *.pth filter=lfs diff=lfs merge=lfs -text
         | 
| 24 | 
            -
            *.rar filter=lfs diff=lfs merge=lfs -text
         | 
| 25 | 
            -
            *.safetensors filter=lfs diff=lfs merge=lfs -text
         | 
| 26 | 
            -
            saved_model/**/* filter=lfs diff=lfs merge=lfs -text
         | 
| 27 | 
            -
            *.tar.* filter=lfs diff=lfs merge=lfs -text
         | 
| 28 | 
            -
            *.tar filter=lfs diff=lfs merge=lfs -text
         | 
| 29 | 
            -
            *.tflite filter=lfs diff=lfs merge=lfs -text
         | 
| 30 | 
            -
            *.tgz filter=lfs diff=lfs merge=lfs -text
         | 
| 31 | 
            -
            *.wasm filter=lfs diff=lfs merge=lfs -text
         | 
| 32 | 
            -
            *.xz filter=lfs diff=lfs merge=lfs -text
         | 
| 33 | 
            -
            *.zip filter=lfs diff=lfs merge=lfs -text
         | 
| 34 | 
            -
            *.zst filter=lfs diff=lfs merge=lfs -text
         | 
| 35 | 
            -
            *tfevents* filter=lfs diff=lfs merge=lfs -text
         | 
|  | |
| 1 | 
            +
            data/processed/nepali.en filter=lfs diff=lfs merge=lfs -text
         | 
| 2 | 
            +
            data/processed/nepali.ne filter=lfs diff=lfs merge=lfs -text
         | 
| 3 | 
             
            *.bin filter=lfs diff=lfs merge=lfs -text
         | 
| 4 | 
            +
            *.png filter=lfs diff=lfs merge=lfs -text
         | 
| 5 | 
            +
            *.jpeg filter=lfs diff=lfs merge=lfs -text
         | 
| 6 | 
            +
            *.ico filter=lfs diff=lfs merge=lfs -text
         | 
| 7 | 
            +
            *.en filter=lfs diff=lfs merge=lfs -text
         | 
| 8 | 
            +
            *.ne filter=lfs diff=lfs merge=lfs -text
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
    	
        .gitignore
    ADDED
    
    | @@ -0,0 +1,2 @@ | |
|  | |
|  | 
|  | |
| 1 | 
            +
            .venv/  
         | 
| 2 | 
            +
            models/  
         | 
    	
        Dockerfile
    ADDED
    
    | @@ -0,0 +1,20 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # Use an official Python runtime as a parent image
         | 
| 2 | 
            +
            FROM python:3.10-slim
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            # Set the working directory in the container
         | 
| 5 | 
            +
            WORKDIR /code
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            # Copy the requirements file into the container at /code
         | 
| 8 | 
            +
            COPY ./requirements.txt /code/requirements.txt
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            # Install any needed packages specified in requirements.txt
         | 
| 11 | 
            +
            RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            # Copy the rest of the application's code
         | 
| 14 | 
            +
            COPY . /code/
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            # Expose the port the app runs on
         | 
| 17 | 
            +
            EXPOSE 7860
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            # Command to run the application
         | 
| 20 | 
            +
            CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
         | 
    	
        README.md
    CHANGED
    
    | @@ -1,10 +1,246 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # Saksi Translation: Nepali-English Machine Translation
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This project provides a machine translation solution to translate text from Nepali and Sinhala to English. It leverages the power of the NLLB (No Language Left Behind) model from Meta AI, which is fine-tuned on a custom dataset for improved performance. The project includes a complete workflow from data acquisition to model deployment, featuring a REST API for easy integration.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Table of Contents
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            - [Features](#features)
         | 
| 8 | 
            +
            - [Workflow](#workflow)
         | 
| 9 | 
            +
            - [Tech Stack](#tech-stack)
         | 
| 10 | 
            +
            - [Model Details](#model-details)
         | 
| 11 | 
            +
            - [API Endpoints](#api-endpoints)
         | 
| 12 | 
            +
            - [Getting Started](#getting-started)
         | 
| 13 | 
            +
            - [Usage](#usage)
         | 
| 14 | 
            +
            - [Project Structure](#project-structure)
         | 
| 15 | 
            +
            - [Future Improvements](#future-improvements)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ## Features
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            -   **High-Quality Translation:** Utilizes a fine-tuned NLLB model for accurate translations.
         | 
| 20 | 
            +
            -   **Support for Multiple Languages:** Currently supports Nepali and Sinhala to English translation.
         | 
| 21 | 
            +
            -   **REST API:** Exposes the translation model through a high-performance FastAPI application.
         | 
| 22 | 
            +
            -   **Interactive Frontend:** A simple and intuitive web interface for easy translation.
         | 
| 23 | 
            +
            -   **Batch Translation:** Supports translating multiple texts in a single request.
         | 
| 24 | 
            +
            -   **PDF Translation:** Supports translating text directly from PDF files.
         | 
| 25 | 
            +
            -   **Scalable and Reproducible:** Built with a modular structure and uses MLflow for experiment tracking.
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ## Workflow
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            The project follows a standard machine learning workflow for building and deploying a translation model:
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            1.  **Data Acquisition:** The process begins with collecting parallel text data (Nepali/Sinhala and English). The `scripts/fetch_parallel_data.py` script is used to download data from various online sources. The quality and quantity of this data are crucial for the model's performance.
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            2.  **Data Cleaning and Preprocessing:** Raw data from the web is often noisy and requires cleaning. The `scripts/clean_text_data.py` script performs several preprocessing steps:
         | 
| 34 | 
            +
                *   **HTML Tag Removal:** Strips out HTML tags and other web artifacts.
         | 
| 35 | 
            +
                *   **Unicode Normalization:** Normalizes Unicode characters to ensure consistency.
         | 
| 36 | 
            +
                *   **Sentence Filtering:** Removes sentences that are too long or too short, which can negatively impact training.
         | 
| 37 | 
            +
                *   **Corpus Alignment:** Ensures a one-to-one correspondence between source and target sentences.
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            3.  **Model Finetuning:** The core of the project is fine-tuning a pre-trained NLLB model on our custom parallel dataset. The `src/train.py` script, which leverages the Hugging Face `Trainer` API, handles this process. This script manages the entire training loop, including:
         | 
| 40 | 
            +
                *   Loading the pre-trained NLLB model and tokenizer.
         | 
| 41 | 
            +
                *   Creating a PyTorch Dataset from the preprocessed data.
         | 
| 42 | 
            +
                *   Configuring training arguments like learning rate, batch size, and number of epochs.
         | 
| 43 | 
            +
                *   Executing the training loop and saving the fine-tuned model checkpoints.
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            4.  **Model Evaluation:** After training, the model's performance is evaluated using the `src/evaluation.py` script. This script calculates the **BLEU (Bilingual Evaluation Understudy)** score, a widely accepted metric for machine translation quality. It works by comparing the model's translations of a test set with a set of high-quality reference translations.
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            5.  **Inference and Deployment:** Once the model is trained and evaluated, it's ready for use.
         | 
| 48 | 
            +
                *   `interactive_translate.py`: A command-line script for quick, interactive translation tests.
         | 
| 49 | 
            +
                *   `fast_api.py`: A production-ready REST API built with FastAPI that serves the translation model. This allows other applications to easily consume the translation service.
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            ## Tech Stack
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            The technologies used in this project were chosen to create a robust, efficient, and maintainable machine translation pipeline:
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            -   **Python:** The primary language for the project, offering a rich ecosystem of libraries and frameworks for machine learning.
         | 
| 56 | 
            +
            -   **PyTorch:** A flexible and powerful deep learning framework that provides fine-grained control over the model training process.
         | 
| 57 | 
            +
            -   **Hugging Face Transformers:** The backbone of the project, providing easy access to pre-trained models like NLLB and a standardized interface for training and inference.
         | 
| 58 | 
            +
            -   **Hugging Face Datasets:** Simplifies the process of loading and preprocessing large datasets, with efficient data loading and manipulation capabilities.
         | 
| 59 | 
            +
            -   **FastAPI:** A modern, high-performance web framework for building APIs with Python. It's used to serve the translation model as a REST API.
         | 
| 60 | 
            +
            -   **Uvicorn:** A lightning-fast ASGI server, used to run the FastAPI application.
         | 
| 61 | 
            +
            -   **MLflow:** Used for experiment tracking to ensure reproducibility. It logs training parameters, metrics, and model artifacts, which is crucial for managing machine learning projects.
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            ## Model Details
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            -   **Base Model:** The project uses the `facebook/nllb-200-distilled-600M` model, a distilled version of the NLLB-200 model. This model is designed to be efficient while still providing high-quality translations for a large number of languages.
         | 
| 66 | 
            +
            -   **Fine-tuning:** The base model is fine-tuned on a custom dataset of Nepali-English and Sinhala-English parallel text to improve its performance on these specific language pairs.
         | 
| 67 | 
            +
            -   **Tokenizer:** The `NllbTokenizer` is used for tokenizing the text. It's a sentence-piece based tokenizer that is specifically designed for the NLLB model.
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            ## API Endpoints
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            The FastAPI application provides the following endpoints:
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            -   **`GET /`**: Returns the frontend HTML page.
         | 
| 74 | 
            +
            -   **`GET /languages`**: Returns a list of supported languages.
         | 
| 75 | 
            +
            -   **`POST /translate`**: Translates a single text.
         | 
| 76 | 
            +
                -   **Request Body:**
         | 
| 77 | 
            +
                    ```json
         | 
| 78 | 
            +
                    {
         | 
| 79 | 
            +
                      "text": "string",
         | 
| 80 | 
            +
                      "source_language": "string"
         | 
| 81 | 
            +
                    }
         | 
| 82 | 
            +
                    ```
         | 
| 83 | 
            +
                -   **Response Body:**
         | 
| 84 | 
            +
                    ```json
         | 
| 85 | 
            +
                    {
         | 
| 86 | 
            +
                      "original_text": "string",
         | 
| 87 | 
            +
                      "translated_text": "string",
         | 
| 88 | 
            +
                      "source_language": "string"
         | 
| 89 | 
            +
                    }
         | 
| 90 | 
            +
                    ```
         | 
| 91 | 
            +
            -   **`POST /batch-translate`**: Translates a batch of texts.
         | 
| 92 | 
            +
                -   **Request Body:**
         | 
| 93 | 
            +
                    ```json
         | 
| 94 | 
            +
                    {
         | 
| 95 | 
            +
                      "texts": [
         | 
| 96 | 
            +
                        "string"
         | 
| 97 | 
            +
                      ],
         | 
| 98 | 
            +
                      "source_language": "string"
         | 
| 99 | 
            +
                    }
         | 
| 100 | 
            +
                    ```
         | 
| 101 | 
            +
                -   **Response Body:**
         | 
| 102 | 
            +
                    ```json
         | 
| 103 | 
            +
                    {
         | 
| 104 | 
            +
                      "original_texts": [
         | 
| 105 | 
            +
                        "string"
         | 
| 106 | 
            +
                      ],
         | 
| 107 | 
            +
                      "translated_texts": [
         | 
| 108 | 
            +
                        "string"
         | 
| 109 | 
            +
                      ],
         | 
| 110 | 
            +
                      "source_language": "string"
         | 
| 111 | 
            +
                    }
         | 
| 112 | 
            +
                    ```
         | 
| 113 | 
            +
            -   **`POST /translate-pdf`**: Translates a PDF file.
         | 
| 114 | 
            +
                -   **Request:** `source_language: str`, `file: UploadFile`
         | 
| 115 | 
            +
                -   **Response Body:**
         | 
| 116 | 
            +
                    ```json
         | 
| 117 | 
            +
                    {
         | 
| 118 | 
            +
                      "filename": "string",
         | 
| 119 | 
            +
                      "translated_text": "string",
         | 
| 120 | 
            +
                      "source_language": "string"
         | 
| 121 | 
            +
                    }
         | 
| 122 | 
            +
                    ```
         | 
| 123 | 
            +
             | 
| 124 | 
            +
            ## Getting Started
         | 
| 125 | 
            +
             | 
| 126 | 
            +
            ### Prerequisites
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            -   **Python 3.10 or higher:** Ensure you have a recent version of Python installed.
         | 
| 129 | 
            +
            -   **Git and Git LFS:** Git is required to clone the repository, and Git LFS is required to handle large model files.
         | 
| 130 | 
            +
            -   **(Optional) NVIDIA GPU with CUDA:** A GPU is highly recommended for training the model.
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            ### Installation
         | 
| 133 | 
            +
             | 
| 134 | 
            +
            1.  **Clone the repository:**
         | 
| 135 | 
            +
                ```bash
         | 
| 136 | 
            +
                git clone <repository-url>
         | 
| 137 | 
            +
                cd saksi_translation
         | 
| 138 | 
            +
                ```
         | 
| 139 | 
            +
             | 
| 140 | 
            +
            2.  **Create and activate a virtual environment:**
         | 
| 141 | 
            +
                ```bash
         | 
| 142 | 
            +
                python -m venv .venv
         | 
| 143 | 
            +
                # On Windows
         | 
| 144 | 
            +
                .venv\Scripts\activate
         | 
| 145 | 
            +
                # On macOS/Linux
         | 
| 146 | 
            +
                source .venv/bin/activate
         | 
| 147 | 
            +
                ```
         | 
| 148 | 
            +
             | 
| 149 | 
            +
            3.  **Install dependencies:**
         | 
| 150 | 
            +
                ```bash
         | 
| 151 | 
            +
                pip install -r requirements.txt
         | 
| 152 | 
            +
                ```
         | 
| 153 | 
            +
             | 
| 154 | 
            +
            ## Usage
         | 
| 155 | 
            +
             | 
| 156 | 
            +
            ### Data Preparation
         | 
| 157 | 
            +
             | 
| 158 | 
            +
            -   **Fetch Parallel Data:**
         | 
| 159 | 
            +
                ```bash
         | 
| 160 | 
            +
                python scripts/fetch_parallel_data.py --output_dir data/raw
         | 
| 161 | 
            +
                ```
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            -   **Clean Text Data:**
         | 
| 164 | 
            +
                ```bash
         | 
| 165 | 
            +
                python scripts/clean_text_data.py --input_dir data/raw --output_dir data/processed
         | 
| 166 | 
            +
                ```
         | 
| 167 | 
            +
             | 
| 168 | 
            +
            ### Training
         | 
| 169 | 
            +
             | 
| 170 | 
            +
            -   **Start Training:**
         | 
| 171 | 
            +
                ```bash
         | 
| 172 | 
            +
                python src/train.py \
         | 
| 173 | 
            +
                    --model_name "facebook/nllb-200-distilled-600M" \
         | 
| 174 | 
            +
                    --dataset_path "data/processed" \
         | 
| 175 | 
            +
                    --output_dir "models/nllb-finetuned-nepali-en" \
         | 
| 176 | 
            +
                    --learning_rate 2e-5 \
         | 
| 177 | 
            +
                    --per_device_train_batch_size 8 \
         | 
| 178 | 
            +
                    --num_train_epochs 3
         | 
| 179 | 
            +
                ```
         | 
| 180 | 
            +
             | 
| 181 | 
            +
            ### Evaluation
         | 
| 182 | 
            +
             | 
| 183 | 
            +
            -   **Evaluate the Model:**
         | 
| 184 | 
            +
                ```bash
         | 
| 185 | 
            +
                python src/evaluate.py \
         | 
| 186 | 
            +
                    --model_path "models/nllb-finetuned-nepali-en" \
         | 
| 187 | 
            +
                    --test_data_path "data/test_sets/test.en" \
         | 
| 188 | 
            +
                    --reference_data_path "data/test_sets/test.ne"
         | 
| 189 | 
            +
                ```
         | 
| 190 | 
            +
             | 
| 191 | 
            +
            ### Interactive Translation
         | 
| 192 | 
            +
             | 
| 193 | 
            +
            -   **Run the interactive script:**
         | 
| 194 | 
            +
                ```bash
         | 
| 195 | 
            +
                python interactive_translate.py
         | 
| 196 | 
            +
                ```
         | 
| 197 | 
            +
             | 
| 198 | 
            +
            ### API
         | 
| 199 | 
            +
             | 
| 200 | 
            +
            -   **Run the API:**
         | 
| 201 | 
            +
                ```bash
         | 
| 202 | 
            +
                uvicorn fast_api:app --reload
         | 
| 203 | 
            +
                ```
         | 
| 204 | 
            +
                Open your browser and navigate to `http://127.0.0.1:8000` to use the web interface.
         | 
| 205 | 
            +
             | 
| 206 | 
            +
            ## Project Structure
         | 
| 207 | 
            +
             | 
| 208 | 
            +
            ```
         | 
| 209 | 
            +
            saksi_translation/
         | 
| 210 | 
            +
            ├── .gitignore
         | 
| 211 | 
            +
            ├── fast_api.py             # FastAPI application
         | 
| 212 | 
            +
            ├── interactive_translate.py  # Interactive translation script
         | 
| 213 | 
            +
            ├── README.md               # Project documentation
         | 
| 214 | 
            +
            ├── requirements.txt        # Python dependencies
         | 
| 215 | 
            +
            ├── test_translation.py     # Script for testing the translation model
         | 
| 216 | 
            +
            ├── frontend/
         | 
| 217 | 
            +
            │   ├── index.html          # Frontend HTML
         | 
| 218 | 
            +
            │   ├── script.js           # Frontend JavaScript
         | 
| 219 | 
            +
            │   └── styles.css          # Frontend CSS
         | 
| 220 | 
            +
            ├── data/
         | 
| 221 | 
            +
            │   ├── processed/          # Processed data for training
         | 
| 222 | 
            +
            │   ├── raw/                # Raw data downloaded from the web
         | 
| 223 | 
            +
            │   └── test_sets/          # Test sets for evaluation
         | 
| 224 | 
            +
            ├── mlruns/                 # MLflow experiment tracking data
         | 
| 225 | 
            +
            ├── models/
         | 
| 226 | 
            +
            │   └── nllb-finetuned-nepali-en/ # Fine-tuned model
         | 
| 227 | 
            +
            ├── notebooks/              # Jupyter notebooks for experimentation
         | 
| 228 | 
            +
            ├── scripts/
         | 
| 229 | 
            +
            │   ├── clean_text_data.py
         | 
| 230 | 
            +
            │   ├── create_test_set.py
         | 
| 231 | 
            +
            │   ├── download_model.py
         | 
| 232 | 
            +
            │   ├── fetch_parallel_data.py
         | 
| 233 | 
            +
            │   └── scrape_bbc_nepali.py
         | 
| 234 | 
            +
            └── src/
         | 
| 235 | 
            +
                ├── __init__.py
         | 
| 236 | 
            +
                ├── evaluation.py       # Script for evaluating the model
         | 
| 237 | 
            +
                ├── train.py            # Script for training the model
         | 
| 238 | 
            +
                └── translate.py        # Script for translating text
         | 
| 239 | 
            +
            ```
         | 
| 240 | 
            +
             | 
| 241 | 
            +
            ## Future Improvements
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            -   **Support for more languages:** The project can be extended to support more languages by adding more parallel data and fine-tuning the model on it.
         | 
| 244 | 
            +
            -   **Improved Model:** The model can be improved by using a larger version of the NLLB model or by fine-tuning it on a larger and cleaner dataset.
         | 
| 245 | 
            +
            -   **Advanced Frontend:** The frontend can be improved by adding features like translation history, user accounts, and more advanced styling.
         | 
| 246 | 
            +
            -   **Containerization:** The application can be containerized using Docker for easier deployment and scaling.
         | 
    	
        api_log.txt
    ADDED
    
    | @@ -0,0 +1,20 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            Loading models on CPU...
         | 
| 2 | 
            +
            Traceback (most recent call last):
         | 
| 3 | 
            +
              File "D:\SIH\saksi_translation\api.py", line 14, in <module>
         | 
| 4 | 
            +
                "nepali": AutoModelForSeq2SeqLM.from_pretrained("models/nllb-finetuned-nepali-en").to(DEVICE),
         | 
| 5 | 
            +
                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         | 
| 6 | 
            +
              File "C:\Users\dynos\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0\LocalCache\local-packages\Python313\site-packages\transformers\models\auto\auto_factory.py", line 549, in from_pretrained
         | 
| 7 | 
            +
                config, kwargs = AutoConfig.from_pretrained(
         | 
| 8 | 
            +
                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~^
         | 
| 9 | 
            +
                    pretrained_model_name_or_path,
         | 
| 10 | 
            +
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
         | 
| 11 | 
            +
                ...<4 lines>...
         | 
| 12 | 
            +
                    **kwargs,
         | 
| 13 | 
            +
                    ^^^^^^^^^
         | 
| 14 | 
            +
                )
         | 
| 15 | 
            +
                ^
         | 
| 16 | 
            +
              File "C:\Users\dynos\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0\LocalCache\local-packages\Python313\site-packages\transformers\models\auto\configuration_auto.py", line 1329, in from_pretrained
         | 
| 17 | 
            +
                raise ValueError(
         | 
| 18 | 
            +
                ...<3 lines>...
         | 
| 19 | 
            +
                )
         | 
| 20 | 
            +
            ValueError: Unrecognized model in models/nllb-finetuned-nepali-en. Should have a `model_type` key in its config.json, or contain one of the following strings in its name: aimv2, aimv2_vision_model, albert, align, altclip, apertus, arcee, aria, aria_text, audio-spectrogram-transformer, autoformer, aya_vision, bamba, bark, bart, beit, bert, bert-generation, big_bird, bigbird_pegasus, biogpt, bit, bitnet, blenderbot, blenderbot-small, blip, blip-2, blip_2_qformer, bloom, bridgetower, bros, camembert, canine, chameleon, chinese_clip, chinese_clip_vision_model, clap, clip, clip_text_model, clip_vision_model, clipseg, clvp, code_llama, codegen, cohere, cohere2, cohere2_vision, colpali, colqwen2, conditional_detr, convbert, convnext, convnextv2, cpmant, csm, ctrl, cvt, d_fine, dab-detr, dac, data2vec-audio, data2vec-text, data2vec-vision, dbrx, deberta, deberta-v2, decision_transformer, deepseek_v2, deepseek_v3, deepseek_vl, deepseek_vl_hybrid, deformable_detr, deit, depth_anything, depth_pro, deta, detr, dia, diffllama, dinat, dinov2, dinov2_with_registers, dinov3_convnext, dinov3_vit, distilbert, doge, donut-swin, dots1, dpr, dpt, efficientformer, efficientloftr, efficientnet, electra, emu3, encodec, encoder-decoder, eomt, ernie, ernie4_5, ernie4_5_moe, ernie_m, esm, evolla, exaone4, falcon, falcon_h1, falcon_mamba, fastspeech2_conformer, fastspeech2_conformer_with_hifigan, flaubert, flava, florence2, fnet, focalnet, fsmt, funnel, fuyu, gemma, gemma2, gemma3, gemma3_text, gemma3n, gemma3n_audio, gemma3n_text, gemma3n_vision, git, glm, glm4, glm4_moe, glm4v, glm4v_moe, glm4v_moe_text, glm4v_text, glpn, got_ocr2, gpt-sw3, gpt2, gpt_bigcode, gpt_neo, gpt_neox, gpt_neox_japanese, gpt_oss, gptj, gptsan-japanese, granite, granite_speech, granitemoe, granitemoehybrid, granitemoeshared, granitevision, graphormer, grounding-dino, groupvit, helium, hgnet_v2, hiera, hubert, hunyuan_v1_dense, hunyuan_v1_moe, ibert, idefics, idefics2, idefics3, idefics3_vision, ijepa, imagegpt, informer, instructblip, instructblipvideo, internvl, internvl_vision, jamba, janus, jetmoe, jukebox, kosmos-2, kosmos-2.5, kyutai_speech_to_text, layoutlm, layoutlmv2, layoutlmv3, led, levit, lfm2, lightglue, lilt, llama, llama4, llama4_text, llava, llava_next, llava_next_video, llava_onevision, longformer, longt5, luke, lxmert, m2m_100, mamba, mamba2, marian, markuplm, mask2former, maskformer, maskformer-swin, mbart, mctct, mega, megatron-bert, metaclip_2, mgp-str, mimi, minimax, mistral, mistral3, mixtral, mlcd, mllama, mm-grounding-dino, mobilebert, mobilenet_v1, mobilenet_v2, mobilevit, mobilevitv2, modernbert, modernbert-decoder, moonshine, moshi, mpnet, mpt, mra, mt5, musicgen, musicgen_melody, mvp, nat, nemotron, nezha, nllb-moe, nougat, nystromformer, olmo, olmo2, olmoe, omdet-turbo, oneformer, open-llama, openai-gpt, opt, ovis2, owlv2, owlvit, paligemma, patchtsmixer, patchtst, pegasus, pegasus_x, perceiver, perception_encoder, perception_lm, persimmon, phi, phi3, phi4_multimodal, phimoe, pix2struct, pixtral, plbart, poolformer, pop2piano, prompt_depth_anything, prophetnet, pvt, pvt_v2, qdqbert, qwen2, qwen2_5_omni, qwen2_5_vl, qwen2_5_vl_text, qwen2_audio, qwen2_audio_encoder, qwen2_moe, qwen2_vl, qwen2_vl_text, qwen3, qwen3_moe, rag, realm, recurrent_gemma, reformer, regnet, rembert, resnet, retribert, roberta, roberta-prelayernorm, roc_bert, roformer, rt_detr, rt_detr_resnet, rt_detr_v2, rwkv, sam, sam2, sam2_hiera_det_model, sam2_video, sam2_vision_model, sam_hq, sam_hq_vision_model, sam_vision_model, seamless_m4t, seamless_m4t_v2, seed_oss, segformer, seggpt, sew, sew-d, shieldgemma2, siglip, siglip2, siglip_vision_model, smollm3, smolvlm, smolvlm_vision, speech-encoder-decoder, speech_to_text, speech_to_text_2, speecht5, splinter, squeezebert, stablelm, starcoder2, superglue, superpoint, swiftformer, swin, swin2sr, swinv2, switch_transformers, t5, t5gemma, table-transformer, tapas, textnet, time_series_transformer, timesfm, timesformer, timm_backbone, timm_wrapper, trajectory_transformer, transfo-xl, trocr, tvlt, tvp, udop, umt5, unispeech, unispeech-sat, univnet, upernet, van, video_llava, videomae, vilt, vipllava, vision-encoder-decoder, vision-text-dual-encoder, visual_bert, vit, vit_hybrid, vit_mae, vit_msn, vitdet, vitmatte, vitpose, vitpose_backbone, vits, vivit, vjepa2, voxtral, voxtral_encoder, wav2vec2, wav2vec2-bert, wav2vec2-conformer, wavlm, whisper, xclip, xcodec, xglm, xlm, xlm-prophetnet, xlm-roberta, xlm-roberta-xl, xlnet, xlstm, xmod, yolos, yoso, zamba, zamba2, zoedepth
         | 
    	
        app.py
    ADDED
    
    | @@ -0,0 +1,213 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            A FastAPI application for serving the translation model, inspired by interactive_translate.py.
         | 
| 3 | 
            +
            """
         | 
| 4 | 
            +
            import torch
         | 
| 5 | 
            +
            from transformers import M2M100ForConditionalGeneration, NllbTokenizer
         | 
| 6 | 
            +
            from fastapi import FastAPI, HTTPException, UploadFile, File
         | 
| 7 | 
            +
            from fastapi.staticfiles import StaticFiles
         | 
| 8 | 
            +
            from fastapi.responses import FileResponse
         | 
| 9 | 
            +
            from pydantic import BaseModel
         | 
| 10 | 
            +
            import logging
         | 
| 11 | 
            +
            from typing import List
         | 
| 12 | 
            +
            import fitz  # PyMuPDF
         | 
| 13 | 
            +
            import shutil
         | 
| 14 | 
            +
            import os
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            # --- 1. App Configuration ---
         | 
| 17 | 
            +
            logging.basicConfig(level=logging.INFO)
         | 
| 18 | 
            +
            logger = logging.getLogger(__name__)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            app = FastAPI(
         | 
| 21 | 
            +
                title="Saksi Translation API",
         | 
| 22 | 
            +
                description="A simple API for translating text and PDFs to English.",
         | 
| 23 | 
            +
                version="2.0",
         | 
| 24 | 
            +
            )
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            app.mount("/frontend", StaticFiles(directory="frontend"), name="frontend")
         | 
| 27 | 
            +
             | 
| 28 | 
            +
             | 
| 29 | 
            +
            # --- 2. Global Variables ---
         | 
| 30 | 
            +
            DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
         | 
| 31 | 
            +
            SUPPORTED_LANGUAGES = {
         | 
| 32 | 
            +
                "nepali": "nep_Npan",
         | 
| 33 | 
            +
                "sinhala": "sin_Sinh",
         | 
| 34 | 
            +
            }
         | 
| 35 | 
            +
            MODEL_PATH = "facebook/nllb-200-distilled-600M"
         | 
| 36 | 
            +
            model = None
         | 
| 37 | 
            +
            tokenizer = None
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            # --- 3. Pydantic Models ---
         | 
| 40 | 
            +
            class TranslationRequest(BaseModel):
         | 
| 41 | 
            +
                text: str
         | 
| 42 | 
            +
                source_language: str
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            class TranslationResponse(BaseModel):
         | 
| 45 | 
            +
                original_text: str
         | 
| 46 | 
            +
                translated_text: str
         | 
| 47 | 
            +
                source_language: str
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            class BatchTranslationRequest(BaseModel):
         | 
| 50 | 
            +
                texts: List[str]
         | 
| 51 | 
            +
                source_language: str
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            class BatchTranslationResponse(BaseModel):
         | 
| 54 | 
            +
                original_texts: List[str]
         | 
| 55 | 
            +
                translated_texts: List[str]
         | 
| 56 | 
            +
                source_language: str
         | 
| 57 | 
            +
                
         | 
| 58 | 
            +
            class PdfTranslationResponse(BaseModel):
         | 
| 59 | 
            +
                filename: str
         | 
| 60 | 
            +
                translated_text: str
         | 
| 61 | 
            +
                source_language: str
         | 
| 62 | 
            +
             | 
| 63 | 
            +
             | 
| 64 | 
            +
            # --- 4. Helper Functions ---
         | 
| 65 | 
            +
            def load_model_and_tokenizer(model_path):
         | 
| 66 | 
            +
                """Loads the model and tokenizer from the given path."""
         | 
| 67 | 
            +
                global model, tokenizer
         | 
| 68 | 
            +
                logger.info(f"Loading model on {DEVICE.upper()}...")
         | 
| 69 | 
            +
                try:
         | 
| 70 | 
            +
                    model = M2M100ForConditionalGeneration.from_pretrained(model_path).to(DEVICE)
         | 
| 71 | 
            +
                    tokenizer = NllbTokenizer.from_pretrained(model_path)
         | 
| 72 | 
            +
                    logger.info("Model and tokenizer loaded successfully!")
         | 
| 73 | 
            +
                except Exception as e:
         | 
| 74 | 
            +
                    logger.error(f"Error loading model: {e}")
         | 
| 75 | 
            +
                    # In a real app, you might want to exit or handle this more gracefully
         | 
| 76 | 
            +
                    raise
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            def translate_text(text: str, src_lang: str) -> str:
         | 
| 79 | 
            +
                """
         | 
| 80 | 
            +
                Translates a single string of text to English.
         | 
| 81 | 
            +
                """
         | 
| 82 | 
            +
                if src_lang not in SUPPORTED_LANGUAGES:
         | 
| 83 | 
            +
                    raise ValueError(f"Language '{src_lang}' not supported.")
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                tokenizer.src_lang = SUPPORTED_LANGUAGES[src_lang]
         | 
| 86 | 
            +
                inputs = tokenizer(text, return_tensors="pt").to(DEVICE)
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                generated_tokens = model.generate(
         | 
| 89 | 
            +
                    **inputs,
         | 
| 90 | 
            +
                    forced_bos_token_id=tokenizer.convert_tokens_to_ids("eng_Latn"),
         | 
| 91 | 
            +
                    max_length=128,
         | 
| 92 | 
            +
                )
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                return tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            def batch_translate_text(texts: List[str], src_lang: str) -> List[str]:
         | 
| 97 | 
            +
                """
         | 
| 98 | 
            +
                Translates a batch of texts to English.
         | 
| 99 | 
            +
                """
         | 
| 100 | 
            +
                if src_lang not in SUPPORTED_LANGUAGES:
         | 
| 101 | 
            +
                    raise ValueError(f"Language '{src_lang}' not supported.")
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                tokenizer.src_lang = SUPPORTED_LANGUAGES[src_lang]
         | 
| 104 | 
            +
                # We use padding=True to handle batches of different lengths
         | 
| 105 | 
            +
                inputs = tokenizer(texts, return_tensors="pt", padding=True, truncation=True, max_length=512).to(DEVICE)
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                generated_tokens = model.generate(
         | 
| 108 | 
            +
                    **inputs,
         | 
| 109 | 
            +
                    forced_bos_token_id=tokenizer.convert_tokens_to_ids("eng_Latn"),
         | 
| 110 | 
            +
                    max_length=512, # Allow for longer generated sequences in batches
         | 
| 111 | 
            +
                )
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                return tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            # --- 5. API Events ---
         | 
| 116 | 
            +
            @app.on_event("startup")
         | 
| 117 | 
            +
            async def startup_event():
         | 
| 118 | 
            +
                """Load the model at startup."""
         | 
| 119 | 
            +
                load_model_and_tokenizer(MODEL_PATH)
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            # --- 6. API Endpoints ---
         | 
| 122 | 
            +
            @app.get("/")
         | 
| 123 | 
            +
            async def root():
         | 
| 124 | 
            +
                """Returns the frontend."""
         | 
| 125 | 
            +
                return FileResponse('frontend/index.html')
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            @app.get("/languages")
         | 
| 128 | 
            +
            def get_supported_languages():
         | 
| 129 | 
            +
                """Returns a list of supported languages."""
         | 
| 130 | 
            +
                return {"supported_languages": list(SUPPORTED_LANGUAGES.keys())}
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            @app.post("/translate", response_model=TranslationResponse)
         | 
| 133 | 
            +
            async def translate(request: TranslationRequest):
         | 
| 134 | 
            +
                """Translates a single text from a source language to English."""
         | 
| 135 | 
            +
                try:
         | 
| 136 | 
            +
                    translated_text = translate_text(request.text, request.source_language)
         | 
| 137 | 
            +
                    return TranslationResponse(
         | 
| 138 | 
            +
                        original_text=request.text,
         | 
| 139 | 
            +
                        translated_text=translated_text,
         | 
| 140 | 
            +
                        source_language=request.source_language,
         | 
| 141 | 
            +
                    )
         | 
| 142 | 
            +
                except ValueError as e:
         | 
| 143 | 
            +
                    raise HTTPException(status_code=400, detail=str(e))
         | 
| 144 | 
            +
                except Exception as e:
         | 
| 145 | 
            +
                    raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {e}")
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            @app.post("/batch-translate", response_model=BatchTranslationResponse)
         | 
| 148 | 
            +
            async def batch_translate(request: BatchTranslationRequest):
         | 
| 149 | 
            +
                """Translates a batch of texts from a source language to English."""
         | 
| 150 | 
            +
                try:
         | 
| 151 | 
            +
                    translated_texts = batch_translate_text(request.texts, request.source_language)
         | 
| 152 | 
            +
                    return BatchTranslationResponse(
         | 
| 153 | 
            +
                        original_texts=request.texts,
         | 
| 154 | 
            +
                        translated_texts=translated_texts,
         | 
| 155 | 
            +
                        source_language=request.source_language,
         | 
| 156 | 
            +
                    )
         | 
| 157 | 
            +
                except ValueError as e:
         | 
| 158 | 
            +
                    raise HTTPException(status_code=400, detail=str(e))
         | 
| 159 | 
            +
                except Exception as e:
         | 
| 160 | 
            +
                    raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {e}")
         | 
| 161 | 
            +
             | 
| 162 | 
            +
            @app.post("/translate-pdf", response_model=PdfTranslationResponse)
         | 
| 163 | 
            +
            async def translate_pdf(source_language: str, file: UploadFile = File(...)):
         | 
| 164 | 
            +
                """Translates a PDF file from a source language to English."""
         | 
| 165 | 
            +
                if file.content_type != "application/pdf":
         | 
| 166 | 
            +
                    raise HTTPException(status_code=400, detail="Invalid file type. Please upload a PDF.")
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                # Save the uploaded file temporarily
         | 
| 169 | 
            +
                temp_pdf_path = f"temp_{file.filename}"
         | 
| 170 | 
            +
                with open(temp_pdf_path, "wb") as buffer:
         | 
| 171 | 
            +
                    shutil.copyfileobj(file.file, buffer)
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                try:
         | 
| 174 | 
            +
                    # Extract text from the PDF
         | 
| 175 | 
            +
                    doc = fitz.open(temp_pdf_path)
         | 
| 176 | 
            +
                    extracted_text = ""
         | 
| 177 | 
            +
                    for page in doc:
         | 
| 178 | 
            +
                        extracted_text += page.get_text()
         | 
| 179 | 
            +
                    doc.close()
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                    if not extracted_text.strip():
         | 
| 182 | 
            +
                        raise HTTPException(status_code=400, detail="Could not extract any text from the PDF.")
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                    # Split text into chunks (e.g., by paragraph) to handle large texts
         | 
| 185 | 
            +
                    text_chunks = [p.strip() for p in extracted_text.split('\n') if p.strip()]
         | 
| 186 | 
            +
                    
         | 
| 187 | 
            +
                    # Translate the chunks in batches
         | 
| 188 | 
            +
                    translated_chunks = batch_translate_text(text_chunks, source_language)
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                    # Join the translated chunks back together
         | 
| 191 | 
            +
                    final_translation = "\n".join(translated_chunks)
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                    return PdfTranslationResponse(
         | 
| 194 | 
            +
                        filename=file.filename,
         | 
| 195 | 
            +
                        translated_text=final_translation,
         | 
| 196 | 
            +
                        source_language=source_language,
         | 
| 197 | 
            +
                    )
         | 
| 198 | 
            +
                except Exception as e:
         | 
| 199 | 
            +
                    logger.error(f"Error processing PDF: {e}")
         | 
| 200 | 
            +
                    raise HTTPException(status_code=500, detail=f"An error occurred while processing the PDF: {e}")
         | 
| 201 | 
            +
                finally:
         | 
| 202 | 
            +
                    # Clean up the temporary file
         | 
| 203 | 
            +
                    if os.path.exists(temp_pdf_path):
         | 
| 204 | 
            +
                        os.remove(temp_pdf_path)
         | 
| 205 | 
            +
             | 
| 206 | 
            +
             | 
| 207 | 
            +
            # --- 7. Example Usage (for running with uvicorn) ---
         | 
| 208 | 
            +
            # To run this API, use the following command in your terminal:
         | 
| 209 | 
            +
            # uvicorn fast_api:app --reload
         | 
| 210 | 
            +
             | 
| 211 | 
            +
            if __name__ == "__main__":
         | 
| 212 | 
            +
                import uvicorn
         | 
| 213 | 
            +
                uvicorn.run(app, host="0.0.0.0", port=8000)
         | 
    	
        baseline_analysis.py
    ADDED
    
    | @@ -0,0 +1,55 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # baseline_analysis.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
         | 
| 4 | 
            +
            import torch
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # Define the model we want to use. We'll use a distilled (smaller, faster)
         | 
| 7 | 
            +
            # version of NLLB-200 for this quick test.
         | 
| 8 | 
            +
            model_name = "facebook/nllb-200-distilled-600M"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            # Load the pre-trained tokenizer and model from Hugging Face.
         | 
| 11 | 
            +
            # This might take a minute to download the first time.
         | 
| 12 | 
            +
            print(f"Loading model: {model_name}")
         | 
| 13 | 
            +
            tokenizer = AutoTokenizer.from_pretrained(model_name)
         | 
| 14 | 
            +
            model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
         | 
| 15 | 
            +
            print("Model loaded successfully!")
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            # Sentences we want to translate.
         | 
| 18 | 
            +
            sinhala_sentences = [
         | 
| 19 | 
            +
                "ඩෝසන් මිස් දුරකථනයෙන් ඩෝසන් මිස් කවුද සර්",
         | 
| 20 | 
            +
                "කවුද ඩෝසන් නැතුව ඉන්නේ ඔව් සර්",
         | 
| 21 | 
            +
                "ඔබ එය උත්සාහ කරන්න සර්",
         | 
| 22 | 
            +
                "කොහොමද වැඩේ හරිද ඔව් සර්ට ස්තුතියි",
         | 
| 23 | 
            +
                "ඔව්, හරි, ස්තුතියි රත්තරං"
         | 
| 24 | 
            +
            ]
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            print("\n--- Starting Translation ---")
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            # Loop through each sentence and translate it.
         | 
| 29 | 
            +
            for sentence in sinhala_sentences:
         | 
| 30 | 
            +
                
         | 
| 31 | 
            +
                # 1. Prepare the input for the model
         | 
| 32 | 
            +
                # We need to tell the tokenizer what the source language is.
         | 
| 33 | 
            +
                tokenizer.src_lang = "sin_Sinh"
         | 
| 34 | 
            +
                
         | 
| 35 | 
            +
                # Convert the text into a format the model understands (input IDs).
         | 
| 36 | 
            +
                inputs = tokenizer(sentence, return_tensors="pt")
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                # 2. Generate the translation
         | 
| 39 | 
            +
                # We force the model to output English by setting the target language ID.
         | 
| 40 | 
            +
                target_lang = "eng_Latn"
         | 
| 41 | 
            +
                translated_tokens = model.generate(
         | 
| 42 | 
            +
                    **inputs,
         | 
| 43 | 
            +
                    forced_bos_token_id=tokenizer.vocab[target_lang],
         | 
| 44 | 
            +
                    max_length=50 # Set a max length for the output
         | 
| 45 | 
            +
                )
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                # 3. Decode the output
         | 
| 48 | 
            +
                # Convert the model's output tokens back into readable text.
         | 
| 49 | 
            +
                translation = tokenizer.batch_decode(translated_tokens, skip_special_tokens=True)[0]
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                # 4. Display the results
         | 
| 52 | 
            +
                print(f"\nOriginal (si): {sentence}")
         | 
| 53 | 
            +
                print(f"Translation (en): {translation}")
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            print("\n--- Translation Complete ---")
         | 
    	
        baseline_translate.py
    ADDED
    
    | @@ -0,0 +1,51 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # baseline_translate.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
         | 
| 4 | 
            +
            import torch
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # Define the model we want to use. We'll use a distilled (smaller, faster)
         | 
| 7 | 
            +
            # version of NLLB-200 for this quick test.
         | 
| 8 | 
            +
            model_name = "facebook/nllb-200-distilled-600M"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            # Load the pre-trained tokenizer and model from Hugging Face.
         | 
| 11 | 
            +
            # This might take a minute to download the first time.
         | 
| 12 | 
            +
            print(f"Loading model: {model_name}")
         | 
| 13 | 
            +
            tokenizer = AutoTokenizer.from_pretrained(model_name)
         | 
| 14 | 
            +
            model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
         | 
| 15 | 
            +
            print("Model loaded successfully!")
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            # Sentences we want to translate.
         | 
| 18 | 
            +
            sentences_to_translate = {
         | 
| 19 | 
            +
                "nep_Npan": "नेपालको राजधानी काठमाडौं हो।",  # Nepali: "The capital of Nepal is Kathmandu."
         | 
| 20 | 
            +
                "sin_Sinh": "ශ්රී ලංකාවේ අගනුවර කොළඹ වේ."   # Sinhala: "The capital of Sri Lanka is Colombo."
         | 
| 21 | 
            +
            }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            print("\n--- Starting Translation ---")
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            # Loop through each sentence and translate it.
         | 
| 26 | 
            +
            for lang_code, text in sentences_to_translate.items():
         | 
| 27 | 
            +
                
         | 
| 28 | 
            +
                # 1. Prepare the input for the model
         | 
| 29 | 
            +
                # We need to tell the tokenizer what the source language is.
         | 
| 30 | 
            +
                tokenizer.src_lang = lang_code
         | 
| 31 | 
            +
                
         | 
| 32 | 
            +
                # Convert the text into a format the model understands (input IDs).
         | 
| 33 | 
            +
                inputs = tokenizer(text, return_tensors="pt")
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                # 2. Generate the translation
         | 
| 36 | 
            +
                # We force the model to output English by setting the target language ID.
         | 
| 37 | 
            +
                translated_tokens = model.generate(
         | 
| 38 | 
            +
                    **inputs,
         | 
| 39 | 
            +
                    forced_bos_token_id=tokenizer.lang_code_to_id["eng_Latn"],
         | 
| 40 | 
            +
                    max_length=50 # Set a max length for the output
         | 
| 41 | 
            +
                )
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                # 3. Decode the output
         | 
| 44 | 
            +
                # Convert the model's output tokens back into readable text.
         | 
| 45 | 
            +
                translation = tokenizer.batch_decode(translated_tokens, skip_special_tokens=True)[0]
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                # 4. Display the results
         | 
| 48 | 
            +
                print(f"\nOriginal ({lang_code}): {text}")
         | 
| 49 | 
            +
                print(f"Translation (eng_Latn): {translation}")
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            print("\n--- Translation Complete ---")
         | 
    	
        data/processed/nepali.en
    ADDED
    
    | @@ -0,0 +1,3 @@ | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            version https://git-lfs.github.com/spec/v1
         | 
| 2 | 
            +
            oid sha256:ada69aaf3da595194e62d42f18645cc0531a90756c3afd3ede86360564ba6676
         | 
| 3 | 
            +
            size 11287409
         | 
    	
        data/processed/nepali.ne
    ADDED
    
    | @@ -0,0 +1,3 @@ | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            version https://git-lfs.github.com/spec/v1
         | 
| 2 | 
            +
            oid sha256:7379a3e194663f8ae7ca094d215e959ceee862c21148aba7d41865e114ebc157
         | 
| 3 | 
            +
            size 31223019
         | 
    	
        data/processed/sinhala.en
    ADDED
    
    | @@ -0,0 +1,3 @@ | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            version https://git-lfs.github.com/spec/v1
         | 
| 2 | 
            +
            oid sha256:7fa08214fe2741e627e353cc8ec66d63bac33b2765d2299bc4d9fbdb3fb727c6
         | 
| 3 | 
            +
            size 2528297
         | 
    	
        data/processed/sinhala.si
    ADDED
    
    | The diff for this file is too large to render. 
		See raw diff | 
|  | 
    	
        data/test_sets/test.en
    ADDED
    
    | @@ -0,0 +1,3 @@ | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            version https://git-lfs.github.com/spec/v1
         | 
| 2 | 
            +
            oid sha256:5361734250c39c0844f5588c2e8a6ca21ec2c923ab09e797fc7a92d66a078996
         | 
| 3 | 
            +
            size 35684
         | 
    	
        data/test_sets/test.ne
    ADDED
    
    | @@ -0,0 +1,3 @@ | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            version https://git-lfs.github.com/spec/v1
         | 
| 2 | 
            +
            oid sha256:4b76bc4d9604a6c6fd10dea4ddcd615ddfb0c51ab1cb0977bb64a42dddbbf3c1
         | 
| 3 | 
            +
            size 220134
         | 
    	
        data/test_sets/test.si
    ADDED
    
    | @@ -0,0 +1,500 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            නැත පුහුණු මනෝ මේජර් නැත
         | 
| 2 | 
            +
            මනෝ ප්රධාන ඉංග්රීසි ලිට්
         | 
| 3 | 
            +
            ඉංග්රීසි ලිට් ජීස් මෑන් එත් ඔයා ඒකට දක්ෂයි
         | 
| 4 | 
            +
            ඔයා කොහෙද යන්නේ මට ප්රශ්න මිලියනයක් තියෙනවා බ්රහස්පතින්දා ඔබව හමුවෙමු
         | 
| 5 | 
            +
            මට ගෞරවය ලැබීමට පෙර මම කිසි විටෙකත් කිසිවෙකු මෙහි පහතට ගෙන නැත
         | 
| 6 | 
            +
            skip මට ගැටලුවක් ඇති අතර මට උපදෙස් කිහිපයක් අවශ්යයි ඔබට මගේ උපදෙස් බොහෝ දුරට අවශ්යයි
         | 
| 7 | 
            +
            හරිම භයානකයි නේද ඔබ සම්පූර්ණයෙන්ම පිළිකුල් සහගතද ඔබ බරක් විය හැකි දූරදර්ශී කෙනෙක්
         | 
| 8 | 
            +
            skip you is a visionary that can be a බරක් විය හැකි මෙය ටිකක් විකෘති බවක් නොපෙනේ
         | 
| 9 | 
            +
            මෙය ටිකක් විකෘති බවක් නොපෙනේ, ලෝකයේ බොහෝ හුදකලා මිනිසුන් සිටින බව කවුරුන් හෝ කවදා හෝ මෙය තේරුම් ගනීවි
         | 
| 10 | 
            +
            ලෝකේ ගොඩක් පාළු මිනිස්සු ඉන්නවා කවුරුහරි දවසක මේක තේරුම් ගනීවි ඒක මම වෙන්නෑ මම ඒක අතහරිනවා
         | 
| 11 | 
            +
            පසුගිය පැය 48 තුළ ඒ සියල්ල ඔබගේ වරදකි, මම කුමක් කරන්නේද යන්න පිළිබඳ උනන්දුව සම්පූර්ණයෙන්ම නැති වී ඇත
         | 
| 12 | 
            +
            මම හිතන්නේ ඒක තේරුමක් තියෙනවා ඔව් ඔව් ඒක කරනවා
         | 
| 13 | 
            +
            සමහර විට මම ඒ ගැන නොදන්නවා වෙන්න පුළුවන් ඔබේ දීර්ඝ පුහුණුව එම ප්රදේශයේ ආයතනයක තිබුණා යැයි මම සිතමි
         | 
| 14 | 
            +
            ඔහ් ඔව් හරිම අමාරුයි නමුත් මම හිතන්නේ ඔබ ළඟ සිටි පරීක්ෂකවරයා මම දන්නවා ඇති
         | 
| 15 | 
            +
            ඔබ ළඟ සිටි පරීක්ෂකවරයා මම ඔහුව ග්රීන්ස්බර්ග්හි වොලෙස් ෆ්රෑන්ක්ලින් දන්නවා ඇති
         | 
| 16 | 
            +
            එය භයානක දෙයක් වූයේ එල්ලෙන ග්ලයිඩින් නීත්යානුකූල ක්රීඩාවක් ලෙස සලකන්නේ මන්දැයි මම නොදනිමි
         | 
| 17 | 
            +
            සැහැල්ලුවෙන් මම ඔහුව ලබා ගත්තා කිසිම මාරාන්තික ආයුධයකට මේ සත්වයා මරන්න බැහැ ඔහු මේ ලෝකයේ නොවේ
         | 
| 18 | 
            +
            කිසිම මාරාන්තික ආයුධයකට මේ සත්වයා මරා දැමිය නොහැක ඔහු මේ ලෝකයේ නොවේ අපි කතා කරන්නේ එම සත්වයා ගැනද ඇවිදින මළ සිරුර ඇත්තෙන්ම විශාල මුඛය ඇත්තෙන්ම නරක හුස්ම
         | 
| 19 | 
            +
            චාරිත්රය ඉටු කිරීමට ඔහු ඇයව හාමුනප්රා වෙත රැගෙන යන්නේ කොතැනටදැයි ඔබ දන්නවා
         | 
| 20 | 
            +
            කවුද මේ හාමුදුරුවො ඉහොටෙප්ගෙ පූජකයො
         | 
| 21 | 
            +
            මම කවදාවත් පූජකයෙක් මැරුවේ නැහැ ඔවුන් නපුරු ශාපයකට පෙර ඔවුන් කමක් නැහැ
         | 
| 22 | 
            +
            මුන් නපුරුයි ශාප උනාට කමක් නෑ එහෙනම්
         | 
| 23 | 
            +
            සහ අන්ක් සු නම්මුන්ගේ සිරුර නැවත පණ ගැන්වීමේ චාරිත්රය කුමක්ද?
         | 
| 24 | 
            +
            අන්ක් සු නම්මුන්ගේ සිරුර නැවත පණ ගැන්වීමේ චාරිත්රය සහ එය කරන්නේ කෙසේද?
         | 
| 25 | 
            +
            සහ මළවුන්ගේ පොත කියවීමෙන් කෙනෙකු එය කරන්නේ කෙසේද?
         | 
| 26 | 
            +
            ඇත්ත වශයෙන්ම, මළවුන්ගේ පොත කියවීමෙන්
         | 
| 27 | 
            +
            ඔව් ඔව් ඇත්ත වශයෙන්ම සහ පසුව ඔබේ සහෝදරිය මරා දැමීම
         | 
| 28 | 
            +
            ඊට පස්සේ ඔයාගේ නංගිව මරනවා මට සමාවෙන්න
         | 
| 29 | 
            +
            එයා අපිට කලින් ආවොත් පරක්කු වැඩියි ඔයා කිව්වද මගේ නංගිව මරන්න කියලා
         | 
| 30 | 
            +
            පුද්ගලිකව මම යටත් වීමට කැමැත්තෙමි ඇයි අපට යටත් විය නොහැක්කේ කටවහගෙන ඔබේ බැන්ඩෝලය ලබා දීමටය
         | 
| 31 | 
            +
            දැන් යන්න මට කාන්තාරයේ විශාල පොල්ලක් සොයාගන්න
         | 
| 32 | 
            +
            මොකක්ද එතකොට මංකොල්ලකෑම් කප්පම් පැහැරගැනීම් ඉහත කී කිසිවකට ස්තූතියි
         | 
| 33 | 
            +
            ඉහත කිසිම දෙයකට ස්තූතියි ඔබට එසේ නම් ඔබ මෙහි කරන්නේ කුමක්ද?
         | 
| 34 | 
            +
            මගේ ඉතා හොඳ මිත්රයා මොනතරම් පුදුමයක්ද මන්ද එය මගේ කුඩා මිතුරා බෙනී නොවේ නම් මම ඔබව මරා දැමිය යුතුයි
         | 
| 35 | 
            +
            කෙල්ල මගේ ජීවිතය බේරුවා කියලා හිතුවේ මට එයාව කරදරෙන් බේරගන්න පුළුවන් අඩුම දේ ඒකයි ඔයාට හැම වෙලාවෙම මොලේට වඩා බෝල තිබුණා
         | 
| 36 | 
            +
            ඕ කොනෙල් මම ඔයාව මරන්න යන්නේ මේක හුරුපුරුදුයි කියලා
         | 
| 37 | 
            +
            හේ ඕ'කොනල් මට පෙනෙන්නේ මට සියලුම අශ්වයන් ලැබුණා වගේ හේයි බෙනී මට පෙනෙන්නේ ඔබ ගඟේ වැරදි පැත්තේ සිටින ආකාරයටය
         | 
| 38 | 
            +
            දහයෙන් එකකට හෝ ඔබේ අසමතුලිතතාවය එතරම් හොඳ නැත, මට වඩා නරක විය
         | 
| 39 | 
            +
            කොහේ හරි යනවා ඔයාව හොයනවා, මට මගේ යාළුවෙක් එක්ක ඉන්න ඕන වුණා
         | 
| 40 | 
            +
            ඇයි යාළුවනේ ඔයා ඔච්චර රණ්ඩු වෙන්න කැමති
         | 
| 41 | 
            +
            ඇයි ඔයා මේ තරම් රණ්ඩු කරන්න කැමති මම ඒක කරන නිසා මම හොඳ පෙනුමක් ඇති නිසා
         | 
| 42 | 
            +
            බෙනී යා පුංචි දුගඳයි ඔබ කොහිද ඔබ වෙතට රිංගුවේ මා හැර දමා ඔබ මාව කුණු වීමට කාන්තාරයේ දමා ගියා
         | 
| 43 | 
            +
            ඔයා මාව දාලා ගියා ඔයා මාව කාන්තාරයට දාලා කුණු වෙන්න ඔහ් ඔව් සමාවෙන්න ඒකට ඉතින් කවුද මේ මිනිහා
         | 
| 44 | 
            +
            ඔහ් ඔව් සමාවෙන්න ඉතින් කවුද මේ මිනිහා ඔසිරිස්හි ප්රධාන පූජක ඉම්හොටෙප් කුමාරයා
         | 
| 45 | 
            +
            මේ ඔසිරිස්හි ප්රධාන පූජක ඉම්හොටෙප් කුමාරයා, ඔහ්, ඔබට කොහොමද?
         | 
| 46 | 
            +
            කුමාරයා වෙනත් මිනිසුන්ගේ ස්පර්ශයට කැමති නැත මෝඩ පෙරදිග මිථ්යා විශ්වාසයක් මම බයයි ඔව් හොඳයි අපි හැමෝටම අද අපේ පුංචි ප්රශ්න ඇති නේද
         | 
| 47 | 
            +
            ඔව් හොඳයි අපි හැමෝටම අද අපේ පොඩි ප්රශ්න ආවා නේද අපි එයා ආවෙ මිස්ටර්ට කොහොම හරි පිලිස්සුම් තුවාල වලට උදව් කරන්න නේද මට වගකීමක් දැනෙනවා
         | 
| 48 | 
            +
            ඔහු පැමිණ ඇත්තේ මිස්ටර්ට පිළිස්සීමට උදව් කිරීමටය
         | 
| 49 | 
            +
            ඔබට කිසි දිනක කිසිඳු කම්මැලිකමක් නොතිබූ බව මට නොකියන්න, මට සමහරක් සොරකම් කළ හැක්කේ කොතැනදැයි ඔබ දන්නවාද?
         | 
| 50 | 
            +
            කෝ ඔයාගේ අලුත් යාළුවා මොන යාළුවාද ඔයා මගේ එකම යාළුවා
         | 
| 51 | 
            +
            ඔබ දකින දෙයින් ආරක්ෂිතයි
         | 
| 52 | 
            +
            ඔබ බොරු සොයන්නේ කුමක් දැයි ඔබට පෙනෙනු ඇත, මම ඔබේ බෙල්ල කපා දමමි
         | 
| 53 | 
            +
            ඔහුට පොත අවශ්ය වන්නේ ඔහුගේ මිය ගිය පෙම්වතිය නැවත පණ ගැන්වීම ගැ�� යමක් සඳහා ඔහුට පොත සහ ඔබේ සහෝදරිය අවශ්යයි
         | 
| 54 | 
            +
            හරියට අර මෝසෙස් මිනිහා අර පාරාවෝ මිනිහාට කරපු දේ ඒක දාන්න එක විදිහක්
         | 
| 55 | 
            +
            මට මතක ඇති එකම දෙය නම් ඔහු වැලි පිපිරුමක් බවට පත් වීමයි, එවිට මට කිසිවක් මතක නැත
         | 
| 56 | 
            +
            ඒවගේම බඩේ බඩවැලේ තියෙන සෙවල දේවල් කොහොමද කියන්නේ
         | 
| 57 | 
            +
            බඩවැල් ඔව් ඒවා
         | 
| 58 | 
            +
            මට සමාවෙන්න රැම්සෙස් සිරියාව විනාශ කළ විට එය හදිසි අනතුරක් විය එය හදිසි අනතුරකි ඔබ ව්යසනයක් ඇයි මම ඔබ ඉවසන්නේ
         | 
| 59 | 
            +
            එහි ඇති කාටූචය බලන්න එය සෙටිගේ නිල රාජකීය මුද්රාව වන අතර මට එය විශ්වාසයි සමහර විට
         | 
| 60 | 
            +
            මිස් carnavon මහත්තයෝ එයා මොකද මෙතන කරන්නේ
         | 
| 61 | 
            +
            ඔහු මෙහි කරන්නේ කුමක්ද යන්න ඔබට සැබවින්ම දැන ගැනීමට අවශ්යද නැතහොත් ඔබ අපට වෙඩි තැබීමට කැමතිද?
         | 
| 62 | 
            +
            ඔබ සිතන්නේ මෙය මෙම සත්වයා නැවැත්වීමට අහිංසක මිනිසුන් මරා දැමීම සාධාරණීකරණය කරන බවයි
         | 
| 63 | 
            +
            මම හිතන්නේ මළවුන්ගේ කළු පොතට මිනිසුන්ව නැවත පණ ගැන්වීමට හැකි නම්, සමහර විට ජීවත්ව සිටින අයගේ රන් පොත ඔවුන් නැවත පාතාලයට ගෙන යා හැකි බව
         | 
| 64 | 
            +
            එවිට ජීවතුන්ගේ රන් පොතට ඔවුන්ව හරියටම පාතාලයට ආපසු හැරවිය හැක
         | 
| 65 | 
            +
            ඇය අන් සියල්ලන් මෙන් ඇය කාන්තාරයේ මිය යනු ඇත, ඇය ඕනෑවට වඩා දැක නැත, ඇය බොහෝ දේ දනී
         | 
| 66 | 
            +
            යතුර ඇය සතුව නැතිවූ යතුර ඔව් කිසිවකු මෙතරම් සමීපව සිට නැති තරම් අප ඇයව නැවැත්විය යුතුය නැතිනම් එය අප සැමගේ අවසානය වනු ඇත
         | 
| 67 | 
            +
            ඔව් කිසිවකු මෙතරම් සමීප වී නැති තරම් අපි ඇයව නැවැත්විය යුතුය, එසේත් නැතිනම් එය අප සියල්ලන්ගේම අවසානය වනු ඇත, එවිට අපි ඇයව මරා දමමු අපි ඇයව සහ ඇය සමඟ සිටින සියල්ලන්ම මරා දමමු
         | 
| 68 | 
            +
            එවිට අපි ඇයව මරා දමා ඇය සහ ඇය සමඟ සිටින සියල්ලන් මරා දමා සිතියම පුළුස්සා යතුර ලබා ගනිමු
         | 
| 69 | 
            +
            සිතියම පුළුස්සා යතුර ලබා ගැනීම සිදු කරනු ඇත, නමුත් ඔවුන් හෙට පිටත් වන ඇමරිකානු ගවේෂණය ගැන කුමක් කිව හැකිද
         | 
| 70 | 
            +
            මම මෙතන ටිකක් විශ්වාස කරන්න කැමතියි ඔබ එය විශ්වාස කරන්නේ නැහැ
         | 
| 71 | 
            +
            ඉතින් අපි රනින් හදපු පොත හොයාගෙන ඒක ඇතුලේ තියෙන පූජනීය මන්ත්ර කියෙව්වොත් ඔයා කියනවා
         | 
| 72 | 
            +
            සහ එහි අඩංගු පූජනීය මන්ත්ර කියවා බලන්න, එය මේ පුද්ගලයා නැවත අපායට යවනු ඇතැයි ඔබ සිතනවා
         | 
| 73 | 
            +
            ඔබ සිතන්නේ එය මේ පුද්ගලයාව නිවැරදි ලෙස අපායට යවනු ඇති බවත් එවිටය
         | 
| 74 | 
            +
            අනුබිස් ප්රතිමාවට නැගෙනහිරින් නුදුරින් පිහිටා ඇති එය අපට නැවත එහි යා යුතු බව මට කියන්න එපා
         | 
| 75 | 
            +
            මට කියන්න එපා අපිට සත්වයා මරන්න ඕන නම් අපි ආපහු එතනට යන්න ඕන
         | 
| 76 | 
            +
            ඒක ඔප්පු වෙනව බලන්න පරණ සෙටිගෙ වාසනාව එයාලට මේ වැලි යට ඉන්න ඕනෙ මේ වගේ මේක ආරක්ෂා කරන්න ඔයා දන්නව එතන නිධානයක් තියෙන්න ඕනෙ කියල
         | 
| 77 | 
            +
            මම ඔබව පිත්තල කෙලකට වෙළඳාම් නොකරමි ඔව් එය පිරිසිදු රත්රන් වලින් සෑදිය යුතුයි
         | 
| 78 | 
            +
            සූර්යයා කළු ජලය ලේ බවට හැරේ
         | 
| 79 | 
            +
            තොපි මොනාද ඌට කලේ
         | 
| 80 | 
            +
            අපායත් එක්ක මම කොහේවත් යන්නේ නැහැ අපි මෙතන ආරක්ෂිතයි ඔව් මම මේ බලකොටුවෙන් පිටව යන්නේ නැහැ
         | 
| 81 | 
            +
            මේකත් එක්ක මම පල්ලෙහාට යන්නේ මට බොන්න බොන්න ඔයාට මොනවා හරි ඕන ඔව් මට බර්බන් වීදුරුවක් අරන් දෙන්න බෝර්බන් ෂොට් එකක් සහ බර්බන් චේසර් එකක් දෙන්න
         | 
| 82 | 
            +
            ඔහු හිරේ සිටින්නේ කුමක්දැයි මම නොදනිමි, ඔබ එන බව මට ආරංචි වූ විට මම එය ඔහුගෙන්ම ඇසුවෙමි
         | 
| 83 | 
            +
            මම නොදන්න නිසා ඔයා එන බව ඇහුණාම මම එයාගෙන් මගෙන් ඇහුවා මොකද කිව්වේ කියලා
         | 
| 84 | 
            +
            සහ එයා මොනවද කිව්වේ එයා හොඳ වෙලාවක් බලාගෙන ඉන්නවා කිව්වා කියලා
         | 
| 85 | 
            +
            එයාව එල්ලන්න ගෙනියන්නේ කොහෙද කියලා
         | 
| 86 | 
            +
            කාන්තාවන්ට අවසර නැත මම ඉංග්රීසි කාන්තාවක්
         | 
| 87 | 
            +
            ඔහුගේ ජීවිතය බේරා ගැනීමට මම ඔබට පවුම් සියයක් දෙන්නම් ඔහු එල්ලී සිටිනු දැකීමට පවුම් සියයක් ගෙවන්නෙමි
         | 
| 88 | 
            +
            ඔහු පවුම් දෙසීයක් එල්ලනවා දැකීම සඳහා මම පවුම් සියයක් ගෙවන්නෙමි
         | 
| 89 | 
            +
            පවුම් දෙසීයක් ඉදිරියට යයි
         | 
| 90 | 
            +
            පවුම් තුන්සියයක් ඉදිරියට යන්න
         | 
| 91 | 
            +
            ඔයා බොරු කියනවා මම කවදාවත් කරන්නේ නැහැ
         | 
| 92 | 
            +
            ඔබ කියන්නේ මේ ජරා දේවත්වයෙන් තොර ඌරු පුත්රයා මළවුන්ගේ නගරය සොයන්නේ කොතැනදැයි දන්නා බවත් ඇත්ත වශයෙන්ම ඔව් ඔබ ඔහුව කපා දැමුවොත් අපි ඔබට සියයට දහයක් දෙන්නම්
         | 
| 93 | 
            +
            ඔව්, ඔබ ඔහුව කපා දැමුවහොත් අපි ඔබට සියයට දහයක් පනහක් දෙන්නෙමු
         | 
| 94 | 
            +
            ඔබ මෙහි කරන සියල්ලන්ටම දීප්තිමත් සුබ උදෑසනක්
         | 
| 95 | 
            +
            ඔබ මෙහි කරන්නේ කුමක්ද, මම මගේ ආයෝජනය ආරක්ෂා කිරීමට පැමිණියෙමි, ඔබට බොහෝම ස්තූතියි
         | 
| 96 | 
            +
            ඔබට තේරෙනවාද අප සිටින්නේ වසර හාර දහසකට වැඩි කාලයක් තුළ කිසිවකු ඇතුළු නොවූ කාමරයක් තුළ සිටින බව මට නිධානයක් නොපෙනේ
         | 
| 97 | 
            +
            ඔබ කොහොමද දන්නේ පෙට්ටිය හාමුනාප්ට්රාට අදාළ බව මොකද මම ඒක හොයාගත්ත තැන මම එතන හිටියා
         | 
| 98 | 
            +
            ඔයා ඇත්තටම hamunaptra එකේ හිටියේ මම ඔයාගේ මල්ලිව ලෑස්ති කළා
         | 
| 99 | 
            +
            මම ඔයාගේ මල්ලිලාව හොඳට ලෑස්ති කළා මම මගේ මල්ලිව දන්නවා
         | 
| 100 | 
            +
            ඔව් මම එතන හිටියා ඔයා දිවුරනවා
         | 
| 101 | 
            +
            ඔයා හැමදාම දිවුරනවා
         | 
| 102 | 
            +
            හැම දවසකම නෑ මම කිව්වේ
         | 
| 103 | 
            +
            නෑ මම කිව්වේ ඔයා අදහස් කරන දේ මම දන්නවා මම එතන හිටියා හරි සෙටි ගේ මළවුන්ගේ නගරය තියන්න
         | 
| 104 | 
            +
            ඔබ අදහස් කරන්නේ කුමක්දැයි මම දනිමි.
         | 
| 105 | 
            +
            ඔබ සොයාගත්තේ කුමක්ද ඔබ දුටුවේ මම වැලි සොයාගත්තා මම මරණය දුටුවා
         | 
| 106 | 
            +
            ���බට දැන ගැනීමට අවශ්ය නිශ්චිත ස්ථානය එහි යන්නේ කෙසේදැයි මට කියන්න
         | 
| 107 | 
            +
            ඔහුට සියයට විසිපහක්වත් වැඩි දුරක් වත් දෙන්න
         | 
| 108 | 
            +
            සමාවෙන්න ඔයාව බය කරන්න හිතුවේ නෑ මිස්ටර් ඕ කොනෙල් මාව බය කරන එකම දේ ඔයාගේ පුරුදු
         | 
| 109 | 
            +
            මිස්ටර් ඕ කොනෙල් මාව බය කරන එකම දෙය නම් මම ඔබව සිපගත් නිසා ඔබේ හැසිරීම තවමත් තරහයි
         | 
| 110 | 
            +
            තාම තරහයි මම ඔයාව සිපගත්තා කියලා ඔයා ඒකට හාදුවක් කිව්වොත්
         | 
| 111 | 
            +
            මට මොකක් හරි මගහැරුණාද අපි අන්තිම වතාවට සටනට යනවාද මම එම ස්ථානයේ සිටි මා සමඟ සිටි සියලුම දෙනා මිය ගියා
         | 
| 112 | 
            +
            ඔබ ඔබේ ඉතිහාසය දන්නවා මම මගේ වස්තුව දන්නවා
         | 
| 113 | 
            +
            විවේක ගන්න මම සිතියමයි, ඒ සියල්ල මෙහි තිබේ, එය සැනසිලිදායකයි
         | 
| 114 | 
            +
            ඔබට හොඳින් පීනන්න පුළුවන්ද, අවස්ථාවක් අවශ්ය නම් මට පීනන්න පුළුවන්
         | 
| 115 | 
            +
            ඇත්ත වශයෙන්ම මට පීනන්න පුළුවන් අවස්ථාවක් අවශ්ය නම් මාව විශ්වාස කරන්න
         | 
| 116 | 
            +
            අපි බොහෝ දුරට එහි සිටින බව ඔබට විශ්වාසයි
         | 
| 117 | 
            +
            අපට මාර්ගය පෙන්වීමට යන දේ සඳහා
         | 
| 118 | 
            +
            ඒ දේ මාව උද්දීපනය කරන දේවල් වගේම ඔයාව උද්යෝගිමත් කරනවා
         | 
| 119 | 
            +
            පැරණි ඊජිප්තු උපක්රම සඳහා එම දර්පණ මොනවාදැයි ඔබ දකිනු ඇත
         | 
| 120 | 
            +
            දෙවියනේ එය කුමක් සඳහා සූදානම් කිරීමේ කාමරයක් සූදානම් කිරීමකි
         | 
| 121 | 
            +
            මරණින් මතු ජීවිතයට ඇතුල් වීම සඳහා සූදානම් වීම
         | 
| 122 | 
            +
            ඔව්, එය ඔබට නැවත ජීවිතය ගෙන ඒමට ඔබ දෙදෙනා පාසල් සිසුන් කිහිප දෙනෙකුට වඩා නරක ය
         | 
| 123 | 
            +
            අපොයි දෙවියනේ එය සරොෆගස් එකක් මෙන් පෙනේ ඇයි ඔවුන් යමෙකු සිවිලිමේ වළලන්නේ
         | 
| 124 | 
            +
            මෙහි යම් ආකාරයක අගුලක් ඇති බව ඔබ පවසන පරිදි මේවා ග්රැනයිට් වලින් සාදා ඇති අතර ඒවා කොබෝල්ට් ලයිනිං සහිත වානේ අභ්යන්තර ගල් කැට සහිත ග්රැනයිට් වලින් සාදා ඇත
         | 
| 125 | 
            +
            ඔහු කතා කළේ කවුරුන් කුමක් ගැනද කතා කළේද යන්නයි
         | 
| 126 | 
            +
            ඔයාලා දෙන්නා ශාප විශ්වාස කරන්නේ නැහැ
         | 
| 127 | 
            +
            ඔබ ශාප විශ්වාස කරන්නේ නැහැ හහ් නැහැ මම විශ්වාස කරනවා මට එය දැකිය හැකි නම් සහ මට එය ස්පර්ශ කළ හැකි නම් එය සැබෑ ය, එය මා විශ්වාස කරන දෙයයි
         | 
| 128 | 
            +
            ඔයාගෙ අයියට ඔයාව නැතුව පාලුයි වගේ නෙමෙයි මට ඔයාව අලුත් බ්රවු එකක් කියල තේරෙන්නේ නෑ මම දන්නවා ඔයා කල්පනා කරනව මම වගේ තැනක් මේ වගේ කෙල්ලෙක් අතරේ මොනවා කරයිද කියලා
         | 
| 129 | 
            +
            මම දන්නවා ඔයා කල්පනා කරනව මම වගේ තැනක් මේ වගේ කෙල්ලෙක් අතරේ මේ වගේ දෙයක් කරන්නේ කියලා
         | 
| 130 | 
            +
            මම ඔයාව සිපගන්න යනවා මහත්මයා, ඔයා එහෙම නෑ
         | 
| 131 | 
            +
            නැහැ ඔබ නොවේ මම නොවේ
         | 
| 132 | 
            +
            ඔබ මට රික් ලෙස කතා කරන්නේ නම් මිස මම නොවේ
         | 
| 133 | 
            +
            ඔයා මට රික් කියලා කතා කරන්නේ නැත්නම් ඇයි මම එහෙම කරන්නේ
         | 
| 134 | 
            +
            ඇයි මම එහෙම කරන්නේ ඒක මගේ නම නිසා
         | 
| 135 | 
            +
            නැහැ ඇයි මම ඔව් ඔව් ඔයා මට කිව්වා ඒක ඔයාට ලැබුන හොඳම වෙලාව කියලා
         | 
| 136 | 
            +
            අනේ දෙවියනේ මම පුංචි කාලේ ඉඳන්ම මේ ගැන හීන මැව්වා ඔයා මැරුණ කොල්ලෝ ගැන සිහින දකිනවා
         | 
| 137 | 
            +
            ඔහු එසේ විය යුතුද නැහැ මම කවදාවත් මේ වගේ මමියක් දැකලා නැහැ ඔහු තවමත් ඉන්නවා
         | 
| 138 | 
            +
            ඔයා කියන්නේ කවුරුහරි අපේ මිනිහාට මේ දේවල් විසි කරලා එයාව හෙමින් හෙමින් පණපිටින් කෑවා කියලා
         | 
| 139 | 
            +
            එය පොතක් පමණක් වන බැවින් ඔබ සෙල්ලම් කරමින් සිටිය යුතු බව ඔබට විශ්වාසයි
         | 
| 140 | 
            +
            එය අමතක කරන්න අපි දොරෙන් බැස ශාලාවෙන් බැස ගියෙමු, නැත අපි නැත
         | 
| 141 | 
            +
            අවුරුදු හාර දහසක් පැරණි ඇවිදින කතා කරන මළ සිරුරක් සමඟ හමුවීමක් ඇති වීම කෙනෙකුට පරිවර්තනය කිරීමට නැඹුරු වේ එය අමතක කර අපි ශාලාවෙන් බැස දොරෙන් බැස ගියෙමු
         | 
| 142 | 
            +
            නැහැ අපි ඔහුව අවදි කළේ නැහැ, අපි ඔහුව නැවැත්වීමට උත්සාහ කළ යුතුයි අපි ඔබ එම පොත කියවා නැති දේ අපි ඔබට කියෙව්වේ ඒ දේ සමඟ සෙල්ලම් කරන්න එපා කියායි
         | 
| 143 | 
            +
            අපි ඔබ එම පොත කියවා නැති දේ අපි ඔබට කියෙව්වේ ඒ දේ සමඟ සෙල්ලම් කරන්න එපා යැයි මම ඔබට කීවෙමි එවිට මම පොත කියවා මම ඔහුව අවදි කරවා මම ඔහුව නවත්වන්නට අදහස් කරමි
         | 
| 144 | 
            +
            එවිට අපට අමරණීය අය සොයා ගැනීමට සිදුවනු ඇත
         | 
| 145 | 
            +
            ඔව් ඉතින් මගේ ප්රශ්නය හැමෝගෙම ප්රශ්නයක්
         | 
| 146 | 
            +
            එයයි මම ඔබට කොන්ත්රාත්තුවක් වන්නේ ඔබට මා සමඟ ටැග් කළ හැකිය, නැතහොත් ඔබට මෙහි නැවතී මිස්ටර් මැග්ගොට් සමඟ සෙල්ලම් කළ හැකිය
         | 
| 147 | 
            +
            ඔබට මා සමඟ ටැග් කළ හැකිය, නැතහොත් ඔබට මෙහි නැවතී මම නැවතී සිටින මිස්ටර් මැග්ගොට් සමඟ සෙල්ලම් කළ හැකිය
         | 
| 148 | 
            +
            ඔහු මෙහි සිටින බව මම ඔහුව දුටුව දෙය මෙහි ඇති සත්වයා ඔබට විශ්වාසයි
         | 
| 149 | 
            +
            ඔයා මට ඔයාගේ කෙල්ල කිව්වෙ මොකක්ද අනේ ඔව් ඒක ඔයා දන්නවද කතාවක් කියලා
         | 
| 150 | 
            +
            ඔහ්, ඔව්, එය ඔබ දන්නවාද, මම සිතන්නේ ඔබ ඊර්ෂ්යා කළ කතාවක් කියාය
         | 
| 151 | 
            +
            මම හිතන්නේ ඔයා ඊර්ෂ්යා කළා ඔයා මට විහිළු කළා ඒ මිනිහගේ මූණ දැක්කද
         | 
| 152 | 
            +
            එව්ලින් ඒක කරන්න එපා මට වෙන කරන්න දෙයක් නෑ
         | 
| 153 | 
            +
            ධෛර්යය තියෙනවා නෝනා ඔව් මම දන්නවා මම ඒවා තියාගන්න කැමතියි
         | 
| 154 | 
            +
            ඔබ බේබද්දා මෝඩ මී අවජාතකයා කරුණාකර මට මුල් දෙයක් අමතන්න
         | 
| 155 | 
            +
            ඔබට දැන් මළවුන්ට ගෞරවයක් නැද්ද, මම ඔවුන් සමඟ එක්වීමට කැමැත්තෙමි
         | 
| 156 | 
            +
            ප්රශ්න දෙකක් සෙටි පළමුවැන්නා සහ ඔහු ධනවත්ද ඔහු පැරණි රාජධානියේ අවසාන පාරාවෝ විය
         | 
| 157 | 
            +
            ඔහු පැරණි රාජධානියේ අවසාන පාරාවෝ ඔවුන්ගෙන් ධනවත්ම පාරාවෝ යැයි කියනු ලැබේ, හොඳයි, එය හොඳයි, මම ඔහු වැනි කෙනෙකුට ඉතා කැමතියි
         | 
| 158 | 
            +
            ඔබ මට කිව්වා ඔබට එය තීබ්ස් හි කැණීමකදී හමු වූ බව මම වැරදියට තේරුම් ගත්තා
         | 
| 159 | 
            +
            ඔයා මට බොරු කිව්වා කියලා මම වැරදියි
         | 
| 160 | 
            +
            ඔයා මට බොරු කිව්වා මම හැමෝටම බොරු කියනවා ඔයාව විශේෂ වෙන්නේ මොකක්ද කියලා
         | 
| 161 | 
            +
            මම හැමෝටම බොරු කියනවා ඔයාව විශේෂ වෙන්නේ මම ඔයාගේ නංගි
         | 
| 162 | 
            +
            මම ඔබේ සහෝදරිය වන අතර එය ඔබව වඩාත් රැවටිලිකාර කරයි
         | 
| 163 | 
            +
            එය ඔබව වඩාත් රැවටිලිකාර කරයි, ඔබ එය ප්රාදේශීය කැස්බාවේ බේබද්දෙකුගෙන් සොරකම් කළේය
         | 
| 164 | 
            +
            ඔබ එය සොරකම් කළේ ප්රාදේශීය කැස්බාවේ බේබද්දෙකුගෙන් ඔහුගේ සාක්කුව සැබවින්ම තෝරා ගත්හ
         | 
| 165 | 
            +
            නමුත් ඔහු දූෂිත සාපරාධී මාර්ගයකි
         | 
| 166 | 
            +
            ඔබ ඇත්තටම සිතන්නේ ඔහු නිසැකවම පෙනී සිටිනු ඇතැයි මම දනිමි ඔහු කව්බෝයි කෙනෙකු විය හැකි නමුත් ඔහුගේ වචනය ඔහුගේ වචනයයි
         | 
| 167 | 
            +
            සැකයකින් තොරව මම දන්නවා ඔහු කව්බෝයි කෙනෙක් විය හැකි නමුත් ඔහුගේ වචනය ඔහුගේ වචනය පෞද්ගලිකව මම හිතන්නේ ඔහු අපිරිසිදු රළු සහ සම්පූර්ණ පාදඩයෙක් මම ඔහුට ටිකක් කැමති නැහැ
         | 
| 168 | 
            +
            අහෝ සමාව අයදිනවා නමුත් ඔබ එම ඔට්ටුව ජයග්රහණය කිරීමට දිවා රෑ නොබලා අපව පැදවූ සියල්ල පසුපස අපි යා යුතු නොවේද
         | 
| 169 | 
            +
            ඔවුන් තවමත් මෙය කරන විට ඔබ මිය ගිය බව එය මමිකරණය ලෙස හැඳින්වේ
         | 
| 170 | 
            +
            ඔබ ඔහුව මරා දැමුවේ කුමක්දැයි ඔබ සිතන්නේ ඔහු කනවා ඔබ කවදා හෝ දැක තිබේද?
         | 
| 171 | 
            +
            මට විශ්වාස කරන්න බෑ මම ඔයාලා දෙන්නට මාව බීලා එන්න දුන්නා කියලා මට බනින්න එපා මට එතන හිටියා කියලාවත් මතක නැහැ
         | 
| 172 | 
            +
            මට දොස් කියන්න එපා, මට එහි හොඳින් සිටි බවක්වත් මතක නැත, මම ඔබට ස්තූති නොකරමි
         | 
| 173 | 
            +
            මට එය හමු විය, මම එය වසා දමා මාව මෙතැනින් ඉවත් කළෙමි
         | 
| 174 | 
            +
            මම කරන්නේ කුමක්ද සහ මම කරන්නේ කුමක්ද? කවරයේ ඇති සෙල්ලිපිය කියවන්න
         | 
| 175 | 
            +
            ummm hootash im hootash im දැන් මොකක්ද මේ අන්තිම සංකේතය මොකක්ද මෙහි පෙනෙන්නේ කෙසේද?
         | 
| 176 | 
            +
            අහ් අහ්මෙනොෆස් ඔව් මට පේනවා
         | 
| 177 | 
            +
            ඇත්තටම ඇය මගේ සහෝදරියයි ඔව් හොඳයි මට විශ්වාසයි ඇය සම්පූර්ණ පාඩුවක් නොවන බව
         | 
| 178 | 
            +
            හේයි මම දන්නේ නැහැ ඔබ හොඳින් දකිනවා
         | 
| 179 | 
            +
            වාඩි වෙන්න o කොනෙල් වාඩි වෙන්න අපිට තවත් හොඳ ක්රීඩකයෙක් යොදාගන්න පුළුවන් මම මගේ ජීවිතය සමඟ සූදු කෙළින්නේ මගේ මුදල් සමඟ නොවේ
         | 
| 180 | 
            +
            මට විශ්වාස කරන්න බෑ මේ බෑග් වල මිල අපිට නොමිලයේ තිබුනා නම් අපිට කරන්න තිබුනේ ඔයාගේ සහෝදරියට දෙන්න විතරයි
         | 
| 181 | 
            +
            අපට ඔවුන්ව නොමිලේ ලබා ගත හැකි විය, අපට කළ යුතුව තිබුණේ ඔබේ සොහොයුරිය ඔවුන්ට ලබා දීමයි, ඔව්, එය මහත් පෙළඹවීමක් නොවේද
         | 
| 182 | 
            +
            ඔව්, එය දරුණු ලෙස පෙළඹවීමක් නොවේ
         | 
| 183 | 
            +
            ඒ දේ මට බඩගිනි වෙන්න දෙනවා ඒ දේ මගේ ජීවිතය බේරුවා
         | 
| 184 | 
            +
            මගේ මකුළු ��ැල් කොටස වෙත ඔබව සාදරයෙන් පිළිගනිමු, එය මෙහි ඉහළ අහසට ගඳ ගසයි
         | 
| 185 | 
            +
            කිසිවෙක් ගත්තේ නැත, අපි හොරෙන් ගොස් ඔවුන් යටින් එම පොත සොරකම් කරමු
         | 
| 186 | 
            +
            අපි ඔවුන් යටින් එම පොත සොරකම් කරන්නෙමු, එවිට ඔබට රහස් මැදිරිය සොයාගත හැකි බව ඔබට විශ්වාසයි
         | 
| 187 | 
            +
            මෙහි සිටින කවුරුන් හෝ යතුරක් නොමැතිව කිසිදු ළමයෙකු පිටතට නොගිය බව සහතිකයි, මේ දෙය බිඳ දැමීමට අපට මාසයක් ගතවනු ඇත
         | 
| 188 | 
            +
            දැඩි විවේකයක් ඔව් මම කඳුළු සලනවා දැන් අපි බලමු කවුද ඇතුලේ ඉන්නේ කියලා
         | 
| 189 | 
            +
            කෝ මගේ තුවක්කුව මොකක්ද ඔයා එයාට වෙඩි තියන්න හදන්නේ
         | 
| 190 | 
            +
            ඔව්, ඔහු අවදි කිරීමට තීරණය කළහොත් ඔබ ඔහුට වෙඩි තියන්නට යන්නේ කුමක්ද?
         | 
| 191 | 
            +
            ඔබ නොවේද අපි මී ගොබ්බයන් නොවේ ඔවුන් නරක සුවඳක් සහ නරක රසයක් ඇත නමුත් කාන්තාරය ලබා දීමට ඇති හොඳම දේ එයයි
         | 
| 192 | 
            +
            ඔවුන් රෝපණය කරන විට ඔහු නිසැකවම ජනප්රිය මිතුරෙකු නොවීය
         | 
| 193 | 
            +
            ඔබ දැක්කද පළගැටියන් කෝටි ගණන් පළගැටියන් සිටින බව, එය වසංගතවලින් එකක් හරි තණකොළ වසංගතය
         | 
| 194 | 
            +
            කවුද මෙතන ඉන්නේ පූජකයා මමිය
         | 
| 195 | 
            +
            එය තරමක් වේදනාකාරී බව ඔබ දන්නවා මම ඔබව මුණගැසුණු දා සිට මගේ වාසනාව විකාරයක් විය
         | 
| 196 | 
            +
            ඔයා දන්නවනේ මම ඔයාව මුණගැහුණු දා ඉඳන් මගේ වාසනාව තමයි ජරාවට තියෙන්නේ ඔව් මම දන්නවා මම මිනිස්සුන්ට එහෙම කරනවා කියලා
         | 
| 197 | 
            +
            අපරාදේ, දෙන්නෙක් බැහැලා, යන්න දෙන්නෙක් විතරයි, එතකොට එයා එව්වා පස්සෙන් එනවා
         | 
| 198 | 
            +
            විශ්වාස කරන්න නංගි ඒක තමයි අපේ යාලුවාට ආයෙත් පණ දුන්නේ, දැන් එයා ඒක පාවිච්චි කරලා එයාගේ පෙම්වතියව ගේන්න යනවා
         | 
| 199 | 
            +
            අපි ඇයව නැවත ලබා ගත යුතුයි, මම ඔබ සමඟ සිටිමි, වයසක, කිසිවෙකු මගේ සහෝදරියට එසේ ස්පර්ශ කර එයින් ගැලවී යන්නේ නැත
         | 
| 200 | 
            +
            හරි දැන් මේ හොරස් මිනිහා මොන මගුලකටද පේන්නේ ඌ උකුසු කන් ඇති උකුස්සෙක් වගේ මූණක් තියෙන ලොකු මිනිහෙක් වගේ
         | 
| 201 | 
            +
            ජොනතන් මොනවා හරි කරන්න ඒක මරන්න ඔයාට විහිළු කරන්න වෙනවා
         | 
| 202 | 
            +
            මම දැනගෙන හිටියා ඔයා එනවා කියලා මම ඒ ස්කයි ලයිට් ඔයා වෙනුවෙන් විවෘත කළා කියලා මම දන්නවා ඔයා කියලා
         | 
| 203 | 
            +
            මම දන්නවා ඔයා දන්නවා මම දන්නවා ඔයා දන්නවා කියලා
         | 
| 204 | 
            +
            මම දන්නවා ඔයා දන්නවා කියලා මම දන්නවා ඔයා දන්නවා මම දන්නවා කියලා
         | 
| 205 | 
            +
            මම දන්නවා ඔබ දන්නවා මම දන්නවා නමුත් ඔබ දන්නවාද මම දන්නවා ඔබ දන්නවා මම දන්නවා කියලා
         | 
| 206 | 
            +
            නමුත් ඔබ දන්නවාද මම දන්නවා ඔබ දන්නවා මම අනිවාර්යයෙන්ම දන්නවා කියලා
         | 
| 207 | 
            +
            ඔබ මේ නගරයෙන් සිදු කිරීමට සැලසුම් කරන දරුණු පළිගැනීම මම දනිමි
         | 
| 208 | 
            +
            එක් කුඩා දෙයක් හැර එය කුමක්ද
         | 
| 209 | 
            +
            සහ මම එම කැට්වෝක් හරහා නගරයේ මුළු විදුලි සැපයුමම උණුසුම් කර ඇත්තේ කුමක්ද?
         | 
| 210 | 
            +
            මම එම කැට්වෝක් හරහා නගරයේ මුළු විදුලි සැපයුමම උණුසුම් කර ඇති බව
         | 
| 211 | 
            +
            අපි සැලසුම් කළ ආකාරයටම සියල්ල සිදුවෙමින් පවතී, ඔබ තවමත් අපගේ විවාහ ගිවිස ගැනීම ප්රකාශ කර නැත
         | 
| 212 | 
            +
            ඔබ අපේ විවාහ ගිවිස ගැනීම ප්රකාශ කර නැතත් එය මගේ මනස ගිලිහී යන්නට ඇත
         | 
| 213 | 
            +
            එය මගේ මනස ලිස්සා යන්නට ඇත ඔබේ මනස ඉතා ලිස්සන සුළුය
         | 
| 214 | 
            +
            ඔබ දඩයම් කිරීමට යන්නේ කොහේද?
         | 
| 215 | 
            +
            අඳුරේ ඔබ තනිව ඔබ ගැන මවාපෑමක් කරමින් සිටින්නේ කුමක්ද?
         | 
| 216 | 
            +
            මම හිතුවේ ඔයා අන්තිම වෙනස් කිරීමක් කළා කියලා
         | 
| 217 | 
            +
            අපේ අමුත්තන් බලාගෙන ඉන්නවා මම ක්ෂණයකින් පහළට එනවා
         | 
| 218 | 
            +
            ඔයාලා දෙන්නා ටයිමිං සයිකෝටික් අවජාතකයෙක්, ඔයාට වැරදි අදහසක් තියෙනවා
         | 
| 219 | 
            +
            පන්දු යවන්නා මට ඔහුව මතකයි මම කුඩා කාලයේ සිට ඔහුව මරා දැමුවේ මීට වසර ගණනාවකට පෙර මම ඔහුගේ දියණියයි
         | 
| 220 | 
            +
            ඉතින් කවුද එයාව මැරුවේ ඩිස්කෝ කොල්ලෝ
         | 
| 221 | 
            +
            අපේ තාත්තාට මේ යාළුවා හිටියා එයා නව නිපැයුම් කරුවෙක් කියලා අපි මේක හොඳටම හිතුවේ නැහැ
         | 
| 222 | 
            +
            මම ඔහුව අවසන් වරට දුටුවේ එදාය
         | 
| 223 | 
            +
            එතකොට මට අට වසරේ
         | 
| 224 | 
            +
            ගොඩක් අඩුවෙන් එලියට යනවා එතකොට කපිතාන් විස්මිතයි
         | 
| 225 | 
            +
            අපිට ඇත්තටම කෝපි ටිකක් සහ සැන්විචස් ටිකක් පාවිච්චි කරන්න පුළුවන්
         | 
| 226 | 
            +
            එන්න බබා ඒක කරන්න ලොකු පුතේ
         | 
| 227 | 
            +
            atta girl atta කොල්ලා
         | 
| 228 | 
            +
            හේයි මම පිරිමියෙක් වගේ හොඳයි අපිට අපිව අභිරහස් මිනිස්සු කියලා කියන්න බෑ
         | 
| 229 | 
            +
            ඔබ කවුද මම පන්දු යවන්නා
         | 
| 230 | 
            +
            ඔබට හෝස්ට් බක්හෝල්ට්ස් කෙසේ වෙතත් ඔහු හුරතල් වුවද ගණන් කළ නොහැක
         | 
| 231 | 
            +
            ඔහු හුරතල් වුවත් ඔවුන් සියල්ලන්ටම අපට නොලැබුණු එක දෙයක් තිබුණි
         | 
| 232 | 
            +
            කොහොමද ම්ලේච්ඡ හය, අදෘශ්යමාන හය
         | 
| 233 | 
            +
            ඒත් එයා ඔයාගේ අම්මා ඔයාට කියන්න ඕනේ මට බෑ කියලා
         | 
| 234 | 
            +
            මම ඇගේ එකම පුත්රයා වන අතර ඇය නිතරම මා වෛද්ය නීතිය ගැන බලාපොරොත්තු තබාගෙන සිටියත් ඔබ සුපිරි වීරයෙකි
         | 
| 235 | 
            +
            නමුත් ඔබ සුපිරි වීරයෙක් වන අතර ඇයට තේරෙන්නේ නැත
         | 
| 236 | 
            +
            මම දන්නවා මගේ පෙම්වතියන් මම වෙස්මුහුණ පැළඳීමෙන් පසු මාව ඉවත දැමූ බව ඔවුන් සිතුවා මට එය නැති වී යයි නමුත් ඇත්ත වශයෙන්ම ඔබ එය සොයා ගත්තා
         | 
| 237 | 
            +
            හිස්වැස්ම ඇයට තේරෙන්නේ නැතැයි මම දනිමි මගේ පෙම්වතියන් සියල්ලෝම මා වෙස්මුහුණ පැළඳීමෙන් පසු මාව විසි කළ බව ඔවුන් සිතූ මට එය නැති වී යයි
         | 
| 238 | 
            +
            පරක්කුයි මමත් ගෙදර යනවා
         | 
| 239 | 
            +
            මමත් එන්න ජූනියර් එය පාසල් රාත්රියකි
         | 
| 240 | 
            +
            කිසිවෙකු අපව විශ්වාස නොකරනු ඇත, ඔවුන් සිතන්නේ අපි නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම ���ිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නිකම්ම නික්මෙයි
         | 
| 241 | 
            +
            මට හොදයි වගේ අපි ඒක කරමු
         | 
| 242 | 
            +
            නමුත් එම ස්ථානය ඉතා විශාල වන අතර මෙම මනෝවිද්යාව කොහිදැයි හෝ එහි පෙනුම කෙබඳුදැයි අපි නොදනිමු
         | 
| 243 | 
            +
            හේයි මම ඔයාට බියර් එකක් අරන් දෙන්නද මම හිතුවා ඔයා කවදාවත් අහන්නේ නැහැ කියලා
         | 
| 244 | 
            +
            එය අපිරිසිදු දුසිම් භාගයකට වඩා අපූරු හතරකට වඩා දෙකක් වැඩිය
         | 
| 245 | 
            +
            ඔහු අදහස් කළේ කුමක්දැයි මම නොදනිමි
         | 
| 246 | 
            +
            ඔබේ අබ කන්න, අපි අපව හඳුන්වන්නේ කමක් නැත, අපි කවුදැයි අපි දනිමු
         | 
| 247 | 
            +
            ඒ මම එලිසබෙත් එලිසබෙත් පුංචි එලිසබෙත් ඇයි ඔයා මෙතරම් මැදි වයසේ ඉන්නේ
         | 
| 248 | 
            +
            කොහොමද ඔයාගේ තාත්තා මැරිලා
         | 
| 249 | 
            +
            එයා මැරිලා, ඒක හරි, එයාලා එයාව පොඩි මිනිහෙක් මැව්වා
         | 
| 250 | 
            +
            doc මේ මගේ මිතුරන් අපි සුපිරි වීරයන් වන අතර අපට ඔබේ උදව් අවශ්යයි මම එක්සත් ක්රමයට ලබා දෙන අතර මට එවැනි ආවරණයක් දැනේ
         | 
| 251 | 
            +
            එයින් ඉවත් වන්න, ඔබ වෙතට යන්න
         | 
| 252 | 
            +
            ඔහු කිසිවිටෙක එය ධනාත්මකව සිතන්නේ නැත
         | 
| 253 | 
            +
            මට තේරුණා කැප්ටන් ගැන මොකක්ද කියලා
         | 
| 254 | 
            +
            සුපිරි වීරයන් මම ඔවුන්ව මරා දැමිය යුතුයි
         | 
| 255 | 
            +
            මම ඔවුන්ව මරන්නද ඇයි කරදර කරන්නේ
         | 
| 256 | 
            +
            ඒ ළමයාට දක්ෂතා තියෙනවා, මම ඒක පොහොට්ටුවට දාන්නම්
         | 
| 257 | 
            +
            ඔයා කැප්ටන් පුදුම කැප්ටන් එක්ක මොනවද කලේ
         | 
| 258 | 
            +
            උබට පිස්සු වගේ උන් හැමදාම කියන්නේ ලොකු උන්ට ගෙඩි කියල
         | 
| 259 | 
            +
            ඔවුන් සෑම විටම ශ්රේෂ්ඨයන්ව ගෙඩි ලෙස හඳුන්වන අතර ගෙඩි සෑම විටම තමන් ශ්රේෂ්ඨයන් ලෙස හඳුන්වයි
         | 
| 260 | 
            +
            ඒ වගේම ගෙඩි හැමවිටම තමන් ශ්රේෂ්ඨ යැයි කියාගන්නවා ඔබ මා සමඟ සිටිනවාද මට විරුද්ධව සිටිනවාද කියා
         | 
| 261 | 
            +
            ඔබ මා සමඟ සිටිනවාද නැත්නම් මට විරුද්ධව සිටිනවාද?
         | 
| 262 | 
            +
            ඉතා නරක ප්ලග් ඔහුට එරෙහිව
         | 
| 263 | 
            +
            මම ඔබ මිය ගියේ කුමන කණ්ඩායමටද යන්න මට මතක් කිරීම ගැන ස්තූතියි
         | 
| 264 | 
            +
            ඔබ මිය ගොස් ඇත
         | 
| 265 | 
            +
            සහ ආලෝකය frankenstein නිවී යයි
         | 
| 266 | 
            +
            මට අනුමාන කිරීමට ඉඩ දෙන්න උණ්ඩ ඔබට රිදවන්නේ නැත ඒවා රිදෙනවා නමුත් ඒවා මාව නවත්වන්නේ නැත
         | 
| 267 | 
            +
            ඔබ නිළියක් පමණක් වේටර්වරියක්
         | 
| 268 | 
            +
            ඔබ ඔබව අවතක්සේරු කරන වේටර්වරියක් පමණි
         | 
| 269 | 
            +
            ඔබ එය කියවන බව ඇසීමට මම කැමතියි දිවා භෝජන සංග්රහයෙන් පසු ඔබට රැඳී සිටිය හැකි බව මම විශ්වාස කරමි
         | 
| 270 | 
            +
            ඇත්තෙන්ම මම හිතන්නේ නියමයි
         | 
| 271 | 
            +
            මම හිතුවේ ඔයාට මගේ නහය කුඩු කරන්න ඕන කියලා ම���ව රවට්ටලා කියලා
         | 
| 272 | 
            +
            ඇයි මට සංචාරය ලබා දෙන්නේ කෙසේද?
         | 
| 273 | 
            +
            මෙහෙ එන්න මම එහෙම කෙල්ලෙක් නෙවෙයි
         | 
| 274 | 
            +
            මම එහෙම කෙල්ලෙක් නෙවෙයි එහෙනම් ඇයි ඔයා මෙතන ඉන්නේ
         | 
| 275 | 
            +
            එසේනම් කුතුහලයෙන් ඔබ මෙහි සිටින්නේ ඇයි?
         | 
| 276 | 
            +
            කුතුහලය නිසා බළලා මතකයි
         | 
| 277 | 
            +
            ඔබ ඔත්තුකරුවෙකු වීම මට වඩා හොඳය
         | 
| 278 | 
            +
            ඈත් වෙලා ඉන්න නැත්තම් ඔයාට මට මොනවද කරන්න පුලුවන්
         | 
| 279 | 
            +
            මම හිතන්නේ ඔයා නිතරම තරහෙන් ඉන්නේ මම වැඩ ඇරිලා ඔයා කාර්යබහුල වෙලා ඉන්නකොට විතරයි
         | 
| 280 | 
            +
            නැත්නම් අද රෑ කතා කරන්න එපා
         | 
| 281 | 
            +
            හැමදාම රෑට අද රෑ තනියම
         | 
| 282 | 
            +
            ප්රසිද්ධ වෙන්න මට කමක් නෑ කවුද අකමැති කියලා
         | 
| 283 | 
            +
            ඔබේ සැබෑ නම වේටර්වරියක් වීම වරදක් නොවේ
         | 
| 284 | 
            +
            මමත් මේ මෝඩ පැරණි නගරයට ආදරෙයි එය ඝෝෂාකාරීයි එය ගඳයි එය කඩා වැටෙනවා එය නිවසයි
         | 
| 285 | 
            +
            මම චිකාගෝ හෝ නිව් යෝර්ක් යාමට පිටත්ව යාමට සිතුවෙමි, නමුත් අපට නොලැබෙන ඔවුන් සතුව ඇත්තේ කුමක්ද
         | 
| 286 | 
            +
            අපි ශූරයන් නැවත පැමිනීමට යන්නේ නැති බවත් එය සිදු වූ විට මට මෙහි සිටීමට අවශ්ය බවත් ඔවුන් සතුව ඇත්තේ කුමක්ද
         | 
| 287 | 
            +
            ශූරයා ආපසු පැන්නේය, එය මා කරන විට මට මෙහි සිටීමට අවශ්යය
         | 
| 288 | 
            +
            මටත් ඔයා දැන් ගොඩක් තරහා නෑ වගේ
         | 
| 289 | 
            +
            මට අවශ්ය සුපිරි වීරයෙක් වීමටයි, මම අදහස් කරන්නේ නයිට් රෝයි යන්නයි
         | 
| 290 | 
            +
            ඒ මම මොනිකා ඔයා කොහෙද
         | 
| 291 | 
            +
            ෆ්රැන්කන්ස්ටයින් මධ්යස්ථානයේදී ඔබට එතැනින් ඉවත් විය නොහැකිද?
         | 
| 292 | 
            +
            ඔතනින් යන්න එපා මම ඇතුලට යනවා
         | 
| 293 | 
            +
            මම ඇතුලට යනවා ඔයා මොනවද කතා කරන්නේ
         | 
| 294 | 
            +
            ඔබ මරා දැමුවහොත්, අවම වශයෙන් මම නිවැරදි උත්සාහයෙන් මිය යනු ඇත
         | 
| 295 | 
            +
            ඉතින් ඔයා කොහොමද දන්නේ ඔයා කවදාවත් අදෘශ්යමාන වෙලා කියලා මම දන්නවා
         | 
| 296 | 
            +
            එන්න යාලුවනේ අපි නරක යහපතට හෝ අයහපතට එරෙහිව සටන් කරනවා මොකක්ද වෙනස
         | 
| 297 | 
            +
            ඔබ sphinx වන අතර ඔබ මෝඩයෙකි
         | 
| 298 | 
            +
            ප්රඥාවන්ත සර්පයා පහර දීමට පෙර දඟර ගසන අතර කඳක් ගඳ ගසයි
         | 
| 299 | 
            +
            ඔබ එම වෙස්මුහුණ ගලවන්නේ කවදාද යන්න ඔබ ඕනෑවට වඩා බොනවා
         | 
| 300 | 
            +
            මම මිතුරන් අතර සිටින බව මට විශ්වාස වූ විට ඔබ එම වෙස් මුහුණ ගලවන්නේ කවදාද?
         | 
| 301 | 
            +
            ඔබේ කෝපය ඉතා විශාල බලයක් නමුත් එය ඔබේ හදවතට ඔබව අන්ධ කරයි මගේ හදවත බොහෝ කලකට පෙර මිය ගියේය
         | 
| 302 | 
            +
            මගේ හදවත මිය ගොස් බොහෝ කලකට පෙර එය මිය ගොස් නැත එය සැඟවී ඇත
         | 
| 303 | 
            +
            එයාට තනියම ඉන්න දෙන්න එයා එයාගේ අම්මා ඔයාගේ නෙවෙයි අපිට නිවාඩුවක් තිබ්බේ එච්චරයි
         | 
| 304 | 
            +
            අපිට විවේක රාත්රියක් තිබුණා, ඉතින් අපි කවද්ද රාත්රිය ගතකරන්නේ
         | 
| 305 | 
            +
            ඔබ කතා කරන්නේ ප්රසිද්ධ සුපිරි වීරයන්ට අපට නොලැබුණු දේ ගැනයි
         | 
| 306 | 
            +
            ඔව් ඔව් ඒකත් කරන එක තමයි හරි දේ
         | 
| 307 | 
            +
            මම මෙයට කැමතියි මම කියන්නේ අපි වචනය යවා අප දන්නා නො���ැලපෙන සියලුම සුපිරි වීරයන් කැඳවන්නෙමු
         | 
| 308 | 
            +
            ඔබ ඔහුව දැක තිබේද ඔහු අදෘශ්යමාන නම් මම ඔහුව දකින්නේ කෙසේද?
         | 
| 309 | 
            +
            ඔහු නොපෙනෙන හොඳ කරුණක් නම් මම ඔහුව දකින්නේ කෙසේද?
         | 
| 310 | 
            +
            මම විශ්වාස කළ විශාල වෙනසක් ඇත, දැන් මට එතරම් විශ්වාස නැත
         | 
| 311 | 
            +
            ඔයා දන්නවද ඒ මිනිස්සු ඇත්තටම මාව අවුස්සන්න පටන් අරන් නමුත් තවම ඉන්නේ අපි හය දෙනෙක් විතරයි
         | 
| 312 | 
            +
            ඒත් තවම ඉන්නේ අපි හය දෙනයි ඉතින් මොකද
         | 
| 313 | 
            +
            ඉතින් සුපර්මෑන්ට තව මොනවද ලැබුනේ ඔහු සුපිරි මිනිසා බව ඔහුට වැටහෙනවා
         | 
| 314 | 
            +
            මෙම ස්ථානය බලකොටුවක් මෙන් ඉදිකර ඇත්තේ එය නිසාය
         | 
| 315 | 
            +
            පිස්සු හැදෙනවා, නමුත් මට එය දැනෙන්නේ නැහැ
         | 
| 316 | 
            +
            ඔයාගේ ස්පයිඩර්මෑන් පෙස් ඩිස්පෙන්සර් හරි ඔයා දිනනවා මට තරහ ගියා මට හොඳටම කේන්ති ගියා
         | 
| 317 | 
            +
            නමුත් ඇය තවමත් ඔබ එනවද නැද්ද කියා කතා කරයි
         | 
| 318 | 
            +
            ඔයා එනවද නැද්ද මම එලවන්නම්
         | 
| 319 | 
            +
            මම රිය පැදවීමට අවස්ථාවක් නොලැබේ
         | 
| 320 | 
            +
            මාව ආවරණය කරන්න
         | 
| 321 | 
            +
            ඔහු උපක්රමයක් අතපසු කරන්නේ නැත, ඔහු මොන ජරාවක්ද සහ ඔහු ඇත්තටම කවුදැයි කිසිවෙකු නොදනී
         | 
| 322 | 
            +
            ඔහුගේ ලාන්ස් දඩයම කණ්නාඩි ගලවයි, එය නොපැහැදිලි සමානකමක් ඇත
         | 
| 323 | 
            +
            නොපැහැදිලි සමානකමක් ඇත නොපැහැදිලි සමානකමක් එය එකම පුද්ගලයා ය
         | 
| 324 | 
            +
            ඔහු කැසනෝවා ෆ්රැන්කන්ස්ටයින් පරාජය කරන තෙක් කිසිවෙකු ඔහු ගැන අසා නැති නමුත් ඔහු දෙස බලා අප දෙස බලන තෙක් අපට විවේකයක් අවශ්යයි
         | 
| 325 | 
            +
            ඔවුන් නිතරම මේ දේවල් පුරවන්නේ ඇයි ඔබට ඒවා ඉරීමකින් තොරව පිටතට ඇද ගත නොහැක මට අද රාත්රියේ තවත් දෙබලක අහිමි විය ඇය සැක කරයි මම එය දනිමි
         | 
| 326 | 
            +
            අද රාත්රියේ මට තවත් දෙබලක් අහිමි විය ඇය සැක කරයි මම එය දනිමි ඇයි ඔබ ඇයට නොකියන්නේ
         | 
| 327 | 
            +
            ඉතින් ඇයි ඔයා එයාට කියන්න එපා මට බෑ කියලා
         | 
| 328 | 
            +
            මට බැහැ ඇයි බැරි කියලා
         | 
| 329 | 
            +
            ඇයි නැත්තේ මට බැරි නිසා ඇයට තේරෙන්නේ නැහැ
         | 
| 330 | 
            +
            ඉතින් ඔබ කුකුළා කවුද කුකුළා
         | 
| 331 | 
            +
            සමහර විට ඔබ මගේම කන කපා දැමීම වැනි වඩාත් ආදර ප්රවේශයක් උත්සාහ කළ යුතුය
         | 
| 332 | 
            +
            හරියට මගේම කන හෝ මල් කපා ගන්නවා වගේ
         | 
| 333 | 
            +
            නැත්නම් මල් හෙට හමුවෙමු
         | 
| 334 | 
            +
            ඔහු ඇතුලට යන බව මම දුටු අතර ඔහු පිටතට නොපැමිණි නමුත් ඒ එකම පුද්ගලයා බව අපි නිසැකවම නොදනිමු
         | 
| 335 | 
            +
            අපි ගිහින් බලමු
         | 
| 336 | 
            +
            සමහර විට ඇය හරිද ඔබ බැරෑරුම්ද මෙය අප බලා සිටි විවේකයයි
         | 
| 337 | 
            +
            සමහර විට ගමනාගමනය ඇති විය හැකි අතර අපි විහිළුවට ලක් කළෙමු, අපි ජීවත් වන්නේ ෆැන්ටසියක බව කිසිවෙකු පෙන්වන්නේ නැත
         | 
| 338 | 
            +
            රෝයි මතක තබා ගන්න, ඒ සියල්ල ඔබගේ බලය තුළ ඇති බව ඔබේ මාර්ගයට ඇති එකම දෙය ඔබ වහන්ස
         | 
| 339 | 
            +
            ඉතින් ඔහු තවමත් සොරකම් නොකළ කලාව කොහෙද?
         | 
| 340 | 
            +
            මොකක්ද ඒක එන්නෙ
         | 
| 341 | 
            +
            ඔහු ��ම්පූර්ණයෙන්ම සාමාන්ය පුද්ගලයෙකු බවට පත් වී ඇත සාමාන්ය දෙය සාමාන්ය දෙයක් තිබේද එය එසේ නම් අප එය දැන ගන්නේ කෙසේද?
         | 
| 342 | 
            +
            මකා දමන්නන් තුරන් කරන්නන්
         | 
| 343 | 
            +
            ගිනි බලය ඕනෑම කෙනෙකුට ඕනෑම මුදලක් වැය වේ
         | 
| 344 | 
            +
            ඔව් ඔබි වන් හේ එයා ගියා
         | 
| 345 | 
            +
            ඔයාලා ඇඳුම් සාදයකට යනවා අපි සුපිරි වීරයෝ
         | 
| 346 | 
            +
            අපි ඇත්තටම කැප්ටන් වගේ සුපිරි වීරයෝ වගේ
         | 
| 347 | 
            +
            ඔබ තවමත් ප්රසිද්ධ නැද්ද?
         | 
| 348 | 
            +
            තාම නෑ ඉතින් ඔයා අරගල කරන සුපිරි වීරයෝ වගේ
         | 
| 349 | 
            +
            එබැවින් ඔබ අරගල කරන සුපිරි වීරයන් වැනි ය
         | 
| 350 | 
            +
            සාමාන්යයෙන් සුපිරි බලවතෙක් යනු ඉන්ද්රජාලික දායාදයක් හෝ ඔහුගේ නඩුවේදී විශිෂ්ට දක්ෂතාවයක් වන අතර එය සම්පූර්ණයෙන්ම චිත්තවේගීය වේ, එබැවින් මට ඔබට ලබා ගත හැක්කේ කුමක්ද?
         | 
| 351 | 
            +
            ඉතින් මධ්යම දුර්ලභ අමුවෙන් මම ඔබට බර්ගර් ලබා ගත හැක්කේ කුමක්ද?
         | 
| 352 | 
            +
            ජෙෆ්රි ඔහ් හායි අම්මා
         | 
| 353 | 
            +
            අනේ අම්මේ ඔයා රිදී ලාච්චුවේ මොනවද කරන්නේ
         | 
| 354 | 
            +
            ඔබ රිදී ලාච්චුවේ රූපවාහිනී මාර්ගෝපදේශකය සොයමින් සිටින්නේ කුමක්ද?
         | 
| 355 | 
            +
            එය රූපවාහිනියේ ඇත්ත වශයෙන්ම මම එතරම් මෝඩයෙක්, ස්තූතියි මමී
         | 
| 356 | 
            +
            ජෙෆ්රි ඔයා හොරා අම්මේ ඔයා හිතන දේ නෙවෙයි
         | 
| 357 | 
            +
            අම්මේ ඔයා හිතන දේ නෙවෙයි ඇයි ඔයා ඔය මෝඩ ඇඳුම ඇඳගෙන ඉන්නේ
         | 
| 358 | 
            +
            මම සුපිරි වීරයෙක් නිසා ඇයි ඔයා ඔය මෝඩ ඇඳුම ඇඳගෙන ඉන්නේ
         | 
| 359 | 
            +
            නමුත් රිදී භාණ්ඩ මම නපුරට එරෙහිව සටන් කිරීමට භාවිතා කරමි
         | 
| 360 | 
            +
            නපුරු ජෙෆ්රි සමඟ සටන් කිරීමට මම එය භාවිතා කරමි, මෙය විශිෂ්ටයි
         | 
| 361 | 
            +
            මම නිතරම දැනගෙන හිටියා ඔයා විශේෂ කෙනෙක් කියලා
         | 
| 362 | 
            +
            ඔයා පොඩි කාලේ ඉඳන්ම මාත් එක්ක ආවා
         | 
| 363 | 
            +
            අපි ලෙවකන අතර ඔහුට පැටවුන් ලැබේ
         | 
| 364 | 
            +
            ඇය අනිවාර්යයෙන්ම ඔබට කැමතියි
         | 
| 365 | 
            +
            අනිවාර්යයෙන්ම ඇයගෙන් විමසන්න
         | 
| 366 | 
            +
            ඔවුන් කවුද මේ නරකයි
         | 
| 367 | 
            +
            කවුද ඒ ඩිස්කෝ කොල්ලෝ
         | 
| 368 | 
            +
            අපි අපේ ඔළුවට උඩින් යනවා ඇති මේක සුපර්මෑන් ගේ වැඩක් වගේ
         | 
| 369 | 
            +
            මෙය සුපර්මෑන් හෝ බැට්මෑන් සඳහා රැකියාවක් ලෙස පෙනේ
         | 
| 370 | 
            +
            හෝ බැට්මෑන් හෝ දෙකම
         | 
| 371 | 
            +
            කට වහගෙන මෝහිකයෙක් වෙන්න
         | 
| 372 | 
            +
            මට මේ ඥාති සහෝදරයා ලැබිය යුතුද ඔහු සැබෑ ඩූෆස් නමුත් ඔහු කියා සිටින්නේ ඔහුට අදෘශ්යමාන විය හැකි බවයි
         | 
| 373 | 
            +
            ඒ වගේම sphinx the who ඉන්නවා
         | 
| 374 | 
            +
            ඔහු ජනප්රිය වෙස්මුහුණු පැළඳ මෙක්සිකානු අපරාධ සටන් කරන සුපිරි මල්ලවපොර ක්රීඩකයෙකු සහ මැචෙට් හි ප්රධානියෙකි
         | 
| 375 | 
            +
            සහ සමාජ ජීවිතය ඔව් නමුත් අපි ඔවුන් වෙත යන්නේ කෙසේද
         | 
| 376 | 
            +
            ඔහු තවමත් මෙහි ජීවත් වන බව ඔබට විශ්වාසද ඔහු තවමත් ජීවතුන් අතර සිටින බව ඔබට විශ්වාසද?
         | 
| 377 | 
            +
            නමුත් ඩොක්ටර් කොහෙද මැෂින් තුවක්කු බසූකා
         | 
| 378 | 
            +
            ඔහු කැසනෝවා විනාශ කර වංචාකරුවන්ට බඩු අසුරා යැවූ අතර එ���ැන් පටන් මෙය ජීවත් වීමට ඉතා හොඳ තැනක් විය
         | 
| 379 | 
            +
            සමහර විට අපි එම ස්ථානය පරීක්ෂා කිරීමට කාලය පැමිණ ඇත, නමුත් අපි ඇතුල් වන්නේ කෙසේද?
         | 
| 380 | 
            +
            එය ඉතා ආසන්න නමුත් අපට එහි සිදුවන්නේ කුමක්දැයි සොයා බැලිය යුතුය
         | 
| 381 | 
            +
            අපි සියදිවි නසාගැනීම් වලින් එකකට විස්සක් ඉක්මවා ගියෙමු
         | 
| 382 | 
            +
            එය ඔහු සමඟ හෝ නැතිව අපට යා යුතු කාලයයි
         | 
| 383 | 
            +
            ගෑරුප්පු අප සමඟ වේවා අපි ඔසවන්නෙමු
         | 
| 384 | 
            +
            මම එහි යන්නේ කොතැනටද?
         | 
| 385 | 
            +
            මම ඔබේ මහිමය නම් කොන්දේසි ස්ථාපිත කර නොමැති සාකච්ඡා මූලධර්මය පිළිගැනීමට මම තීරණය කළෙමි, මම විරුද්ධ මතයක් ඉදිරිපත් කිරීමට කැමැත්තෙමි
         | 
| 386 | 
            +
            මම ඔබේ මහිමය නම්, ජෙනරාල් කුටුසොව්ට විරුද්ධ මතයක් ඉදිරිපත් කිරීමට කැමැත්තෙමි, ඔබ කැමති දෙයක් පැවසීමට නිදහස් වන්න
         | 
| 387 | 
            +
            මම ඔබේ සහතිකය පිළිගන්නවා නමුත් මම බයයි ඔබේ අදහස මට නොපෙනේ ඔබේ මහිමය මට සමාවෙන්න මම එය කිරීමට සූදානම්ව සිටිමි
         | 
| 388 | 
            +
            මට සමාවෙන්න, ඔබේ මහිමය මම එය කිරීමට සූදානම්, ඔව් ඉදිරියට යන්න
         | 
| 389 | 
            +
            ඔබ ඇලෙක්සැන්ඩර් හමුවීමට ලැබීම ඇත්තෙන්ම මොනතරම් සතුටක්ද සහ මොනතරම් ප්රසන්න අදහසක්ද?
         | 
| 390 | 
            +
            සහ ඔබ අනුමත කරන්නේ මොනතරම් රසවත් අදහසක්ද?
         | 
| 391 | 
            +
            අහ් ඔබ අනුමත කරනවා මම හිතන්නේ එය සම්පූර්ණයෙන්ම ආකර්ශනීයයි
         | 
| 392 | 
            +
            මම හිතන්නේ එය අතිශයින්ම ආකර්ශනීයයි ඔබ එයට කැමති වීම ගැන මම සතුටු වෙමි
         | 
| 393 | 
            +
            ඔබට කුමන අදහසක් යෝජනා කළත් ඔබ එයට කැමති වීම ගැන මම සතුටු වෙමි
         | 
| 394 | 
            +
            අනේ මගේ ආදරණීය නැපෝලියන් සමහර වෙලාවට මට හිතෙනවා මම ඇත්තටම අධිරාජ්යයෙක් නෙවෙයි කියලා ඔයා අදහස් කරන්නේ ඔයා වගේ
         | 
| 395 | 
            +
            ඔබ අදහස් කරන්නේ මම යුද්ධය ගැන කිසිවක් නොදන්නා අතර මම තවමත් මගේ ජෙනරාල්වරුන් මත සම්පූර්ණයෙන්ම රඳා සිටිමි
         | 
| 396 | 
            +
            ඔව් කවුද කතා කළේ සර්
         | 
| 397 | 
            +
            ඔව් කපිතාන්, ඔබට කියන්නට අවශ්ය ඕනෑම දෙයක් තිබේද, මම පුරවැසි බරාස් කරමි
         | 
| 398 | 
            +
            ඔව්, සියලු ගෞරවයෙන් මම පුරවැසි බරාස් කරුණාකර කරමි
         | 
| 399 | 
            +
            කරුණාකර මට සිතියමට එන්න පුළුවන්
         | 
| 400 | 
            +
            ආහ් මගේ හිතවත් මිත්රයා ඇතුලට එන්න කරුණාකරලා වාඩි වෙන්න මට සමාවෙන්න මම රංග ශාලාවේ සිටි අතර මම නැවත මගේ හෝටලයට එනතුරු ඔබගේ සටහන මට ලැබුනේ නැහැ
         | 
| 401 | 
            +
            මට සමාවෙන්න මම රඟහලේ සිටි අතර මම නැවත මගේ හෝටලයට පැමිණෙන තෙක් ඔබේ සටහන මට නොලැබුනේය
         | 
| 402 | 
            +
            ආවට ස්තුතියි ඔබ බීමක් ගැන සැලකිලිමත් වෙනවාද, ස්තුතියි
         | 
| 403 | 
            +
            අපගේ නවතම දුෂ්කරතා ගැන මම ඔබට පැවසිය යුතු නැත, මම කිව යුතු දේ ඉතා බරපතල ය
         | 
| 404 | 
            +
            සම්පූර්ණයෙන්ම අවංක වීමට ඔබේ මනසේ ඇත්තේ කුමක්ද, මට නොපැහැදිලි අදහසක් නොමැත
         | 
| 405 | 
            +
            සම්පුර්ණයෙන්ම අවංකව කිවහොත්, ඔබ බැරෑරුම්ද යන නොපැහැදිලි අදහස මට නැත
         | 
| 406 | 
            +
            ඔබ බැරෑරුම්ද, ආරක්ෂාවක් කළ හැකිද යන්නවත් මම නොදනිමි
         | 
| 407 | 
            +
            ආරක්ෂාවක් කළ හැකිදැයි මම නොදනිමි, ඔබ සතුව ඇති බලවේග මොනවාද?
         | 
| 408 | 
            +
            භටයන් 5000 ක් පමණ ඔබ සතුව ඇති බලවේග මොනවාද?
         | 
| 409 | 
            +
            5000 ක පමණ භට පිරිසක් අශ්වාරෝහක
         | 
| 410 | 
            +
            අශ්වාරෝහක 21 වන මකරුන් දෙසිය තුන්සියයක් පමණ සෙබළුන්
         | 
| 411 | 
            +
            21 වන මකරුන් දෙසිය හෝ තුන්සීයක් පමණ භට පිරිස් ඕනෑම කාලතුවක්කුවකි
         | 
| 412 | 
            +
            ඕනෑම කාලතුවක්කුවක් මෙහි නැත
         | 
| 413 | 
            +
            ඔවුන් සිටින තැන මෙහි කිසිවෙක් නැත
         | 
| 414 | 
            +
            ඔවුන් හොඳින් සිටින්නේ කොහිදැයි මම විශ්වාස කරමි අවම වශයෙන් තුවක්කු 30ක්වත් සැබ්ලෝන්වල තිබේ
         | 
| 415 | 
            +
            හොඳයි, මම විශ්වාස කරනවා සැබ්ලෝන්වල අවම වශයෙන් තුවක්කු 30 ක්වත් ඇති බව ඔබට ඒවා දිවා කාලයේදී මෙහි තබා ගත හැකිය
         | 
| 416 | 
            +
            ඔබට ඔවුන්ව අලුයම වන විට මෙහි රැගෙන යා හැකිය, මිනිසුන් 40,000කට විරුද්ධ වීමට මෙය ප්රමාණවත් වේ
         | 
| 417 | 
            +
            මිනිසුන් 40,000කට විරුද්ධ වීමට මෙය ප්රමාණවත්ද ඔව්
         | 
| 418 | 
            +
            නිසි ලෙස සකස් කර ඇත ඔව් මේවා 8 සිට 1 දක්වා සමානුපාතික වේ
         | 
| 419 | 
            +
            ඔබ සෑම ප්රායෝගික අර්ථයකින්ම මට විධානය පැවරීමට යෝජනා කරනවාද ඔව් නමුත් නිල වශයෙන් මට විධානය රඳවා ගැනීමට සිදුවේ
         | 
| 420 | 
            +
            ඔබ මට විධානය මාරු කිරීමට යෝජනා කරන්නේ මා වෙනුවෙන් මෙය හැසිරවීමට ඔබ සූදානම්ද?
         | 
| 421 | 
            +
            සෑම ප්රායෝගික අර්ථයකින්ම ඔව් නමුත් නිල වශයෙන් ඇත්ත වශයෙන්ම මට සාධාරණ ලෙස විධානය රඳවා ගැනීමට සිදුවේ
         | 
| 422 | 
            +
            මම අපහාස කළේ නැහැ ඔබ අවදානමට ලක්ව ඇති දේ තේරුම් ගන්න
         | 
| 423 | 
            +
            මගේ වෘත්තියේ විප්ලවය අපේ ජීවිතයට තර්ජනයක් වන දේ ඔබට වැටහෙනවා
         | 
| 424 | 
            +
            හොඳයි බෙලියර්ඩ් මොකක්ද ඔයා මෙතන කරන්නේ මොකක්ද සතුරා ඔවුන් පැරිස් සර්ගේ ගේට්ටුව ළඟ
         | 
| 425 | 
            +
            ඔවුන් සිටින්නේ පැරිස් සර්ගේ ගේට්ටුවේ සහ හමුදාව කොහෙද
         | 
| 426 | 
            +
            කෝ හමුදාව මේ පාරේ ඉන්නේ සර් මගේ පස්සෙන්
         | 
| 427 | 
            +
            මේ පාරේ තමයි සර් මගේ පස්සෙන් ඇවිත් පැරිස් ආරක්ෂා කරන්නේ
         | 
| 428 | 
            +
            පැරිස් යටත් වී ඇත, මම එය විශ්වාස නොකරමි, අවාසනාවන්ත ලෙස එය ඇත්ත සර්
         | 
| 429 | 
            +
            අවාසනාවට එය ඇත්ත සර් නමුත් මගේ බිරිඳ සහ පුතා කෝ ඔවුන්ට මොකද වුණේ මාමොන්ට් කොහෙද මෝටියර්
         | 
| 430 | 
            +
            මගේ බිරිඳ සහ පුතා පැරිසියෙන් එව්වේ කවුද මම දන්නේ නැහැ සර්
         | 
| 431 | 
            +
            මම දන්නේ නැහැ සර් සහ ජෝසප් කොහෙද කියලා
         | 
| 432 | 
            +
            ජෝසප් කොහෙද ඉන්නේ කියලා මම දන්නේ නැහැ ජෝසප් කුමාරයාට මොනවා වෙලාද කියලා
         | 
| 433 | 
            +
            නමුත් ඇයට හොඳම වෛද්යවරුන් සිටියාද ඇයව බේරාගැනීමට කිසිඳු අවස්ථාවක් නොතිබුනේද මම දන්නේ නැහැ සර් ඇයට සාර්ගේ පෞද්ගලික වෛද්යවරයා සිටියා
         | 
| 434 | 
            +
            කවුද ඉන්නේ bertrand sir
         | 
| 435 | 
            +
            බර්ට්රන්ඩ් සර් මට ද��න් ජෝසෆින් ගැන වඩාත්ම පැහැදිලි සිහිනයක් තිබුණා
         | 
| 436 | 
            +
            මම දැන් ජෝසෆින් ගැන වඩාත් පැහැදිලි සිහිනයක් දුටුවෙමි ඔව් සර්
         | 
| 437 | 
            +
            ජෙනරල් බොනපාට් පැයකින් ආපහු එනවා
         | 
| 438 | 
            +
            ඔබ මගේ සොහොයුරා ජෝසප් බොනපාට් සහ මගේ සහායක මේජර් ජූනොට් සමඟ හඳුනන බව මම විශ්වාස කරමි ඔව් සර් පැරිසියේ සිට පැමිණි ගමනේදී ඔවුන් හමුවීමේ ගෞරවය මට හිමි විය
         | 
| 439 | 
            +
            කපිතාන් චාල්ස් මම විශ්වාස කරනවා ඔබ සාමාන්ය ලිපිකරුගේ සහායකයෙකු බව මම විශ්වාස කරමි ඔව් සර් මම
         | 
| 440 | 
            +
            ඔව් සර් මම තමයි මැඩම් බොනපාට් ගේ පුහුණුකරු කැටුව ගිය පරිවාර හමුදාවට අණ දීමට ඔබට පැවරුවේ ඔහු ය
         | 
| 441 | 
            +
            මැඩම් බොනපාට්ගේ පුහුණුකරු ඔව් සර් සමඟ පැමිණි පරිවාර හමුදාවට අණ දීමට ඔබට පැවරුවේ ඔහුද
         | 
| 442 | 
            +
            ඒ ගමන හැම අතින්ම සාමාන්ය දෙයක්. ඔව් සර්
         | 
| 443 | 
            +
            ඔව් සර් ගමනේදී යම් ආකාරයක දුෂ්කරතා ඇති වුණාද?
         | 
| 444 | 
            +
            ගමන අතරතුර කිසියම් ආකාරයක දුෂ්කරතා ඇති වූයේද සර් කිසිවෙක් නැත
         | 
| 445 | 
            +
            ස්තුතියි සර් මම කරන්නම් ඔයාට යන්න පුළුවන් කැප්ටන් චාල්ස්
         | 
| 446 | 
            +
            ඔව් සර් කරුණාකර ෂැම්පේන් වීදුරුවක්
         | 
| 447 | 
            +
            ෂැම්පේන් වීදුරුවක් කරුණාකර ඔව් සර් ජෙනරාල් බොනපාට්ගෙන් ඇසීම ගැන ඔබ මට සමාව දෙනු ඇතැයි මම බලාපොරොත්තු වෙමි නමුත් ඔබ කෝර්සිකානු ද
         | 
| 448 | 
            +
            ඔව් සර් ජෙනරල් බොනපාට්ගෙන් ඇසීම ගැන ඔබ මට සමාව දෙනු ඇතැයි මම බලාපොරොත්තු වෙමි නමුත් ඔබ කෝර්සිකානු ද ඔව් මම
         | 
| 449 | 
            +
            ඔව් මම හිතුවා ඉතින් මම ඔයාගෙ නම දැක්කා ඔයාව ප්රකාශ කලාම මමත් corsican කියලා මගේ නම Arena
         | 
| 450 | 
            +
            මම හිතුවා ඔයාව ප්රකාශ කරනකොටම මම ඔයාගේ නම දැක්කා මමත් කෝර්සිකන් කියලා මගේ නම අරීනා ඔහ් ඔයා කොහෙන්ද එන්නේ
         | 
| 451 | 
            +
            ඔහ් ඔයා කොහෙන්ද බැස්ටියා සහ ඔයා
         | 
| 452 | 
            +
            bastia සහ ඔබ ajaccio
         | 
| 453 | 
            +
            ajaccio ඔබ මෑතකදී නැවත පැමිණ තිබේද?
         | 
| 454 | 
            +
            මම අවුරුදු තුනක් තිස්සේ එහි නොසිටියෙමි, මම වසර දහයකින් ආපසු පැමිණ නැත, ඔබේ පවුල තවමත් එහි සිටී
         | 
| 455 | 
            +
            මම අවුරුදු දහයකින් ආපහු ආවේ නැහැ ඔයාගේ පවුල තවමත් එතන නැහැ ඔවුන් දැන් ලස්සනට ජීවත් වෙනවා
         | 
| 456 | 
            +
            නැහැ ඔවුන් දැන් ලස්සන නගරයක ජීවත් වෙනවා, මේ ඔබ මෙහි පැමිණි පළමු අවස්ථාවයි
         | 
| 457 | 
            +
            එය ලස්සන නගරයක් මෙය ඔබගේ පළමු අවස්ථාව මෙය නොවේද ඇත්ත වශයෙන්ම එය එසේය
         | 
| 458 | 
            +
            ඔව්, ඇත්ත වශයෙන්ම, පුරවැසි බාරාස් මිතුරන් බොහෝ දෙනෙක් ඔබ නොදනිති
         | 
| 459 | 
            +
            ඔබ බොහෝ පුරවැසි බැරාස් මිතුරන්ව දන්නේ නැහැ නේද?
         | 
| 460 | 
            +
            අහ්හ්හ් නෑ මම හිතුවා මුළු රෑම මම ඔයාව දැක්කේ නැහැ කියලා
         | 
| 461 | 
            +
            ආයුබෝවන් picart ah Didier ඔබ ජීවතුන් අතර
         | 
| 462 | 
            +
            අහ් ඩිඩියර් ඔබ ජීවතුන් අතර ඇයි ඔබ බල්ලා රැගෙන යන්නේ
         | 
| 463 | 
            +
            ඇයි ඔබ බල්ලා උසුලාගෙන යන්නේ ඔහුගේ දෙපා කැටි වී ඔහුට ඇවිදීමට නොහැකිය
         | 
| 464 | 
            +
            ඔහුගේ දෙපා ශීත වී ඇති අතර ඔබ ඔහුව අනුභව කරන විට ඔහුට ඇවිදීමට නොහැකිය
         | 
| 465 | 
            +
            ඔබ ඔහුව කන විට මට මගේ දෙවියනේ ටිකක් ලැබේවා ඔබ මවුටන් අපේ රෙජිමේන්තු බල්ලා අඳුරන්නේ නැද්ද මම කොසැක් කන්න කැමතියි
         | 
| 466 | 
            +
            දෙයියනේ වෙලාව හතරයි
         | 
| 467 | 
            +
            පැය හතරයි දෙයියනේ මොන ගින්නක්ද
         | 
| 468 | 
            +
            කවද්ද පටන් ගත්තෙ මුල්ම වාර්තා දහයට විතර ආවා
         | 
| 469 | 
            +
            දහයට විතර මුල්ම වාර්තා ආවා ඔයා ඇයි මාව ඇහැරෙව්වේ නැත්තේ
         | 
| 470 | 
            +
            ඔබ මාව අවදි නොකළේ ඇයි? මුලදී එය සාමාන්ය ගින්නකට වඩා වැඩි දෙයක් ලෙස පෙනෙන්නට නොතිබුණි
         | 
| 471 | 
            +
            මුලදී එය සාමාන්ය ගින්නකට වඩා වැඩි යමක් පෙනුනේ එය මෙතරම් ඉක්මනින් පැතිර ගියේ කෙසේද යන්නයි
         | 
| 472 | 
            +
            එය මෙතරම් ඉක්මනින් පැතිර ගියේ කෙසේද යන්න ගිනි අවුලුවන අයගේ වැඩකි
         | 
| 473 | 
            +
            ඕනෑම කොල්ලයකට ජීවිතයෙන් පිළිතුරු දෙන බව මම මෝටියර්ට කීවෙමි එය ගිනිදැල් කරුවන්ගේ වැඩක්
         | 
| 474 | 
            +
            මම මෝර්ටියර්ට කීවේ රුසියානුවන් විසින් ආරම්භ කර ඇති ඕනෑම කොල්ලකෑමක් සඳහා ඔහු සිය ජීවිතයෙන් පිළිතුරු දෙන බවයි
         | 
| 475 | 
            +
            අපේ භටයින්ට මේ සඳහා කිසිදු කොටසක් නොමැත, එය රුසියානුවන් විසින් ආරම්භ කරන ලද්දකි, මම එය විශ්වාස නොකරමි
         | 
| 476 | 
            +
            good skys ambassador මොකද වෙලා තියෙන්නේ සුබ සන්ධ්යාවක් මගේ ආදරණීය ඩුරොක් මම බයයි මම දඩයම් කරන්න ගිහින් මට තරමක් නරක වැටීමක් ඇති වුනා
         | 
| 477 | 
            +
            ආහ් සුභ සන්ධ්යාවක් මගේ ආදරණීය ඩුරොක් මම දඩයම් කිරීමට ගොස් ඇති අතර මට තරමක් නරක වැටීමක් සිදුවී ඇතැයි මම බිය වෙමි, ඇත්ත වශයෙන්ම ඔබට තානාපතිවරයෙක් සිටීද ඔබ වෛද්යවරයකු වෙත යවා තිබේද
         | 
| 478 | 
            +
            අධිරාජ්යයා ඔබේ ආදිපාදවරිය වන මාරි ලුයිස් සමඟ විවාහ වීමට තීරණය කර ඇත
         | 
| 479 | 
            +
            සුබ උදෑසනක් පුරවැසියා de beauharnais සුබ උදෑසනක් සර් ඔබ ජෙනරාල් බොනපාට් ද?
         | 
| 480 | 
            +
            සුභ උදෑසනක් සර් ඔබ ජෙනරාල් බොනපාට් ද මම පුරවැසියෙක් ඔබේ මව ජෝසෆින් ද බියුහාර්නායිස් මැඩම්
         | 
| 481 | 
            +
            මම පුරවැසියෙක් ඔබේ මව ජෝසෆින් ද බියුහාර්නයිස් මැඩම් ඔව් සර් ඔබ ඇයව හඳුනනවාද
         | 
| 482 | 
            +
            ඔව් මහත්මයා ඔබ ඇයව හඳුනනවාද මට ඇයව මුණගැසුණා ඔබ මා සමඟ කරන ව්යාපාරය කුමක්ද?
         | 
| 483 | 
            +
            ඔහු ඔබව මා වෙත එව්වේ ඔබ හැර අන් කිසිවකුට එම නියෝගය අවලංගු කිරීමට බලයක් නොමැති බවයි
         | 
| 484 | 
            +
            ඔබ පැමිණි බව ඔබේ මව දන්නවා ඔබ හැර නියෝගය අවලංගු කිරීමට කිසිවෙකුට බලයක් නොමැති බව ඔහු පැවසීය
         | 
| 485 | 
            +
            අම්මා දන්නවද සර් ඔයා ආවා කියලා
         | 
| 486 | 
            +
            නෑ සර් හොඳයි එහෙනම් ඔයාට ගොඩක් මුලපිරීමක් තියෙනවා මගේ තරුණ මිත්රයා
         | 
| 487 | 
            +
            එසේනම් ඔබට බොහෝ මුලපිරීමක් තිබේ මගේ තරුණ මිතුරා මගේ පියාගේ කඩුව යනු මා සතු වෙනත් ඕනෑම සන්තකයකට වඩා වැඩි යමක් අදහස් කරයි
         | 
| 488 | 
            +
            අහෝ මගේ ආදරණීය ෆ්රැන්සිස්, අවසානයේ ඔබව හමුවීම කොතරම් සැබෑ සතුටක්ද, අපගේ හමුවීම බොහෝ කලකට පසු නැපෝලියන් වෙතැයි මම බිය වෙමි
         | 
| 489 | 
            +
            ඔබ එය ඉතා විශිෂ්ට ලෙස භාවිතා කර ඇති බව මම සිතමි, ඔබ එය අත්හැරීමට අකමැති වනු ඇත, අපි ගින්නට සමීප වෙමු
         | 
| 490 | 
            +
            අපි ගින්න ළඟට යමුද ඔව් විශිෂ්ට අදහසක්
         | 
| 491 | 
            +
            ඇලෙක්සැන්ඩර් ළඟදීම අපිත් එක්ක එකතු වෙයිද කියලා මට ගොඩක් සැකයි
         | 
| 492 | 
            +
            මගේ ආදරණීය ෆ්රැන්සිස් ඔබ ඉතා අපහසුතාවයට පත් වී ඇති බව පෙනේ, මම ටිකක් යැයි බිය වෙමි
         | 
| 493 | 
            +
            මම බයයි මම ටිකක් ඔයා බ්රැන්ඩි වලට කැමති වෙයිද කියලා
         | 
| 494 | 
            +
            ඔබ බ්රැන්ඩි ටිකක් කැමතිද ස්තුතියි
         | 
| 495 | 
            +
            ස්තූතියි මම ගින්න ගොඩනඟා ගන්නෙමි
         | 
| 496 | 
            +
            ස්තූතියි නැපෝලියන් ෆ්රැන්සිස් ඔබ උණුසුම් ශීත යට ඇඳුම් අඳින්නේදැයි මම අසමි
         | 
| 497 | 
            +
            සුභ සන්ධ්යාවක් සර් සුබ සන්ධ්යාවක් මේඩ්මොයිසෙල්
         | 
| 498 | 
            +
            කාලගුණය භයානකයි නේද සර් ඔව් එය මේ ශීත ඍතුවේ අප ගත කළ නරකම රාත්රියක් විය යුතුය
         | 
| 499 | 
            +
            ඔව්, එය මේ ශීත ඍතුවේ අප ගත කළ නරකම රාත්රියක් විය යුතුය, ඔව් එය විය යුතුය
         | 
| 500 | 
            +
            ඔබ මේ ආකාරයට දොරෙන් පිටත සිට ඇටකටු වලට සිසිල් විය යුතුය ඔව් මම සර්
         | 
    	
        debug_load.py
    ADDED
    
    | @@ -0,0 +1,26 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # debug_load.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import torch
         | 
| 4 | 
            +
            from transformers import AutoTokenizer, M2M100ForConditionalGeneration
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # --- Configuration ---
         | 
| 7 | 
            +
            DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
         | 
| 8 | 
            +
            nepali_model_path = r"D:\SIH\saksi_translation\models\nllb-finetuned-nepali-en"
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            # --- Tokenizer Loading ---
         | 
| 11 | 
            +
            print("Loading Nepali tokenizer...")
         | 
| 12 | 
            +
            try:
         | 
| 13 | 
            +
                nepali_tokenizer = AutoTokenizer.from_pretrained(nepali_model_path)
         | 
| 14 | 
            +
                print("Nepali tokenizer loaded successfully.")
         | 
| 15 | 
            +
                print(nepali_tokenizer)
         | 
| 16 | 
            +
            except Exception as e:
         | 
| 17 | 
            +
                print(f"Error loading Nepali tokenizer: {e}")
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            # --- Model Loading ---
         | 
| 20 | 
            +
            print("\nLoading Nepali model...")
         | 
| 21 | 
            +
            try:
         | 
| 22 | 
            +
                nepali_model = M2M100ForConditionalGeneration.from_pretrained(nepali_model_path).to(DEVICE)
         | 
| 23 | 
            +
                print("Nepali model loaded successfully.")
         | 
| 24 | 
            +
                print(nepali_model)
         | 
| 25 | 
            +
            except Exception as e:
         | 
| 26 | 
            +
                print(f"Error loading Nepali model: {e}")
         | 
    	
        fast_api.py
    ADDED
    
    | @@ -0,0 +1,214 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            A FastAPI application for serving the translation model, inspired by interactive_translate.py.
         | 
| 3 | 
            +
            """
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            import torch
         | 
| 6 | 
            +
            from transformers import M2M100ForConditionalGeneration, NllbTokenizer
         | 
| 7 | 
            +
            from fastapi import FastAPI, HTTPException, UploadFile, File
         | 
| 8 | 
            +
            from fastapi.staticfiles import StaticFiles
         | 
| 9 | 
            +
            from fastapi.responses import FileResponse
         | 
| 10 | 
            +
            from pydantic import BaseModel
         | 
| 11 | 
            +
            import logging
         | 
| 12 | 
            +
            from typing import List
         | 
| 13 | 
            +
            import fitz  # PyMuPDF
         | 
| 14 | 
            +
            import shutil
         | 
| 15 | 
            +
            import os
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            # --- 1. App Configuration ---
         | 
| 18 | 
            +
            logging.basicConfig(level=logging.INFO)
         | 
| 19 | 
            +
            logger = logging.getLogger(__name__)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            app = FastAPI(
         | 
| 22 | 
            +
                title="Saksi Translation API",
         | 
| 23 | 
            +
                description="A simple API for translating text and PDFs to English.",
         | 
| 24 | 
            +
                version="2.0",
         | 
| 25 | 
            +
            )
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            app.mount("/frontend", StaticFiles(directory="frontend"), name="frontend")
         | 
| 28 | 
            +
             | 
| 29 | 
            +
             | 
| 30 | 
            +
            # --- 2. Global Variables ---
         | 
| 31 | 
            +
            DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
         | 
| 32 | 
            +
            SUPPORTED_LANGUAGES = {
         | 
| 33 | 
            +
                "nepali": "nep_Npan",
         | 
| 34 | 
            +
                "sinhala": "sin_Sinh",
         | 
| 35 | 
            +
            }
         | 
| 36 | 
            +
            MODEL_PATH = "models/nllb-finetuned-nepali-en"
         | 
| 37 | 
            +
            model = None
         | 
| 38 | 
            +
            tokenizer = None
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            # --- 3. Pydantic Models ---
         | 
| 41 | 
            +
            class TranslationRequest(BaseModel):
         | 
| 42 | 
            +
                text: str
         | 
| 43 | 
            +
                source_language: str
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            class TranslationResponse(BaseModel):
         | 
| 46 | 
            +
                original_text: str
         | 
| 47 | 
            +
                translated_text: str
         | 
| 48 | 
            +
                source_language: str
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            class BatchTranslationRequest(BaseModel):
         | 
| 51 | 
            +
                texts: List[str]
         | 
| 52 | 
            +
                source_language: str
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            class BatchTranslationResponse(BaseModel):
         | 
| 55 | 
            +
                original_texts: List[str]
         | 
| 56 | 
            +
                translated_texts: List[str]
         | 
| 57 | 
            +
                source_language: str
         | 
| 58 | 
            +
                
         | 
| 59 | 
            +
            class PdfTranslationResponse(BaseModel):
         | 
| 60 | 
            +
                filename: str
         | 
| 61 | 
            +
                translated_text: str
         | 
| 62 | 
            +
                source_language: str
         | 
| 63 | 
            +
             | 
| 64 | 
            +
             | 
| 65 | 
            +
            # --- 4. Helper Functions ---
         | 
| 66 | 
            +
            def load_model_and_tokenizer(model_path):
         | 
| 67 | 
            +
                """Loads the model and tokenizer from the given path."""
         | 
| 68 | 
            +
                global model, tokenizer
         | 
| 69 | 
            +
                logger.info(f"Loading model on {DEVICE.upper()}...")
         | 
| 70 | 
            +
                try:
         | 
| 71 | 
            +
                    model = M2M100ForConditionalGeneration.from_pretrained(model_path).to(DEVICE)
         | 
| 72 | 
            +
                    tokenizer = NllbTokenizer.from_pretrained(model_path)
         | 
| 73 | 
            +
                    logger.info("Model and tokenizer loaded successfully!")
         | 
| 74 | 
            +
                except Exception as e:
         | 
| 75 | 
            +
                    logger.error(f"Error loading model: {e}")
         | 
| 76 | 
            +
                    # In a real app, you might want to exit or handle this more gracefully
         | 
| 77 | 
            +
                    raise
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            def translate_text(text: str, src_lang: str) -> str:
         | 
| 80 | 
            +
                """
         | 
| 81 | 
            +
                Translates a single string of text to English.
         | 
| 82 | 
            +
                """
         | 
| 83 | 
            +
                if src_lang not in SUPPORTED_LANGUAGES:
         | 
| 84 | 
            +
                    raise ValueError(f"Language '{src_lang}' not supported.")
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                tokenizer.src_lang = SUPPORTED_LANGUAGES[src_lang]
         | 
| 87 | 
            +
                inputs = tokenizer(text, return_tensors="pt").to(DEVICE)
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                generated_tokens = model.generate(
         | 
| 90 | 
            +
                    **inputs,
         | 
| 91 | 
            +
                    forced_bos_token_id=tokenizer.convert_tokens_to_ids("eng_Latn"),
         | 
| 92 | 
            +
                    max_length=128,
         | 
| 93 | 
            +
                )
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                return tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            def batch_translate_text(texts: List[str], src_lang: str) -> List[str]:
         | 
| 98 | 
            +
                """
         | 
| 99 | 
            +
                Translates a batch of texts to English.
         | 
| 100 | 
            +
                """
         | 
| 101 | 
            +
                if src_lang not in SUPPORTED_LANGUAGES:
         | 
| 102 | 
            +
                    raise ValueError(f"Language '{src_lang}' not supported.")
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                tokenizer.src_lang = SUPPORTED_LANGUAGES[src_lang]
         | 
| 105 | 
            +
                # We use padding=True to handle batches of different lengths
         | 
| 106 | 
            +
                inputs = tokenizer(texts, return_tensors="pt", padding=True, truncation=True, max_length=512).to(DEVICE)
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                generated_tokens = model.generate(
         | 
| 109 | 
            +
                    **inputs,
         | 
| 110 | 
            +
                    forced_bos_token_id=tokenizer.convert_tokens_to_ids("eng_Latn"),
         | 
| 111 | 
            +
                    max_length=512, # Allow for longer generated sequences in batches
         | 
| 112 | 
            +
                )
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                return tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            # --- 5. API Events ---
         | 
| 117 | 
            +
            @app.on_event("startup")
         | 
| 118 | 
            +
            async def startup_event():
         | 
| 119 | 
            +
                """Load the model at startup."""
         | 
| 120 | 
            +
                load_model_and_tokenizer(MODEL_PATH)
         | 
| 121 | 
            +
             | 
| 122 | 
            +
            # --- 6. API Endpoints ---
         | 
| 123 | 
            +
            @app.get("/")
         | 
| 124 | 
            +
            async def root():
         | 
| 125 | 
            +
                """Returns the frontend."""
         | 
| 126 | 
            +
                return FileResponse('frontend/index.html')
         | 
| 127 | 
            +
             | 
| 128 | 
            +
            @app.get("/languages")
         | 
| 129 | 
            +
            def get_supported_languages():
         | 
| 130 | 
            +
                """Returns a list of supported languages."""
         | 
| 131 | 
            +
                return {"supported_languages": list(SUPPORTED_LANGUAGES.keys())}
         | 
| 132 | 
            +
             | 
| 133 | 
            +
            @app.post("/translate", response_model=TranslationResponse)
         | 
| 134 | 
            +
            async def translate(request: TranslationRequest):
         | 
| 135 | 
            +
                """Translates a single text from a source language to English."""
         | 
| 136 | 
            +
                try:
         | 
| 137 | 
            +
                    translated_text = translate_text(request.text, request.source_language)
         | 
| 138 | 
            +
                    return TranslationResponse(
         | 
| 139 | 
            +
                        original_text=request.text,
         | 
| 140 | 
            +
                        translated_text=translated_text,
         | 
| 141 | 
            +
                        source_language=request.source_language,
         | 
| 142 | 
            +
                    )
         | 
| 143 | 
            +
                except ValueError as e:
         | 
| 144 | 
            +
                    raise HTTPException(status_code=400, detail=str(e))
         | 
| 145 | 
            +
                except Exception as e:
         | 
| 146 | 
            +
                    raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {e}")
         | 
| 147 | 
            +
             | 
| 148 | 
            +
            @app.post("/batch-translate", response_model=BatchTranslationResponse)
         | 
| 149 | 
            +
            async def batch_translate(request: BatchTranslationRequest):
         | 
| 150 | 
            +
                """Translates a batch of texts from a source language to English."""
         | 
| 151 | 
            +
                try:
         | 
| 152 | 
            +
                    translated_texts = batch_translate_text(request.texts, request.source_language)
         | 
| 153 | 
            +
                    return BatchTranslationResponse(
         | 
| 154 | 
            +
                        original_texts=request.texts,
         | 
| 155 | 
            +
                        translated_texts=translated_texts,
         | 
| 156 | 
            +
                        source_language=request.source_language,
         | 
| 157 | 
            +
                    )
         | 
| 158 | 
            +
                except ValueError as e:
         | 
| 159 | 
            +
                    raise HTTPException(status_code=400, detail=str(e))
         | 
| 160 | 
            +
                except Exception as e:
         | 
| 161 | 
            +
                    raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {e}")
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            @app.post("/translate-pdf", response_model=PdfTranslationResponse)
         | 
| 164 | 
            +
            async def translate_pdf(source_language: str, file: UploadFile = File(...)):
         | 
| 165 | 
            +
                """Translates a PDF file from a source language to English."""
         | 
| 166 | 
            +
                if file.content_type != "application/pdf":
         | 
| 167 | 
            +
                    raise HTTPException(status_code=400, detail="Invalid file type. Please upload a PDF.")
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                # Save the uploaded file temporarily
         | 
| 170 | 
            +
                temp_pdf_path = f"temp_{file.filename}"
         | 
| 171 | 
            +
                with open(temp_pdf_path, "wb") as buffer:
         | 
| 172 | 
            +
                    shutil.copyfileobj(file.file, buffer)
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                try:
         | 
| 175 | 
            +
                    # Extract text from the PDF
         | 
| 176 | 
            +
                    doc = fitz.open(temp_pdf_path)
         | 
| 177 | 
            +
                    extracted_text = ""
         | 
| 178 | 
            +
                    for page in doc:
         | 
| 179 | 
            +
                        extracted_text += page.get_text()
         | 
| 180 | 
            +
                    doc.close()
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                    if not extracted_text.strip():
         | 
| 183 | 
            +
                        raise HTTPException(status_code=400, detail="Could not extract any text from the PDF.")
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                    # Split text into chunks (e.g., by paragraph) to handle large texts
         | 
| 186 | 
            +
                    text_chunks = [p.strip() for p in extracted_text.split('\n') if p.strip()]
         | 
| 187 | 
            +
                    
         | 
| 188 | 
            +
                    # Translate the chunks in batches
         | 
| 189 | 
            +
                    translated_chunks = batch_translate_text(text_chunks, source_language)
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                    # Join the translated chunks back together
         | 
| 192 | 
            +
                    final_translation = "\n".join(translated_chunks)
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                    return PdfTranslationResponse(
         | 
| 195 | 
            +
                        filename=file.filename,
         | 
| 196 | 
            +
                        translated_text=final_translation,
         | 
| 197 | 
            +
                        source_language=source_language,
         | 
| 198 | 
            +
                    )
         | 
| 199 | 
            +
                except Exception as e:
         | 
| 200 | 
            +
                    logger.error(f"Error processing PDF: {e}")
         | 
| 201 | 
            +
                    raise HTTPException(status_code=500, detail=f"An error occurred while processing the PDF: {e}")
         | 
| 202 | 
            +
                finally:
         | 
| 203 | 
            +
                    # Clean up the temporary file
         | 
| 204 | 
            +
                    if os.path.exists(temp_pdf_path):
         | 
| 205 | 
            +
                        os.remove(temp_pdf_path)
         | 
| 206 | 
            +
             | 
| 207 | 
            +
             | 
| 208 | 
            +
            # --- 7. Example Usage (for running with uvicorn) ---
         | 
| 209 | 
            +
            # To run this API, use the following command in your terminal:
         | 
| 210 | 
            +
            # uvicorn fast_api:app --reload
         | 
| 211 | 
            +
             | 
| 212 | 
            +
            if __name__ == "__main__":
         | 
| 213 | 
            +
                import uvicorn
         | 
| 214 | 
            +
                uvicorn.run(app, host="0.0.0.0", port=8000)
         | 
    	
        frontend/WhatsApp Image 2025-10-07 at 12.52.12.jpeg
    ADDED
    
    |   | 
| Git LFS Details
 | 
    	
        frontend/backup/index.html
    ADDED
    
    | @@ -0,0 +1,23 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            <!DOCTYPE html>
         | 
| 2 | 
            +
            <html lang="en">
         | 
| 3 | 
            +
            <head>
         | 
| 4 | 
            +
                <meta charset="UTF-8">
         | 
| 5 | 
            +
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
         | 
| 6 | 
            +
                <title>Saksi Translation</title>
         | 
| 7 | 
            +
                <link rel="stylesheet" href="/frontend/styles.css">
         | 
| 8 | 
            +
                <script src="/frontend/script.js" defer></script>
         | 
| 9 | 
            +
            </head>
         | 
| 10 | 
            +
            <body>
         | 
| 11 | 
            +
                <div class="container">
         | 
| 12 | 
            +
                    <h1>Saksi Translation</h1>
         | 
| 13 | 
            +
                    <textarea id="text-to-translate" rows="5" placeholder="Enter text to translate..."></textarea>
         | 
| 14 | 
            +
                    <select id="source-language">
         | 
| 15 | 
            +
                        <option value="nepali">Nepali</option>
         | 
| 16 | 
            +
                        <option value="sinhala">Sinhala</option>
         | 
| 17 | 
            +
                    </select>
         | 
| 18 | 
            +
                    <button id="translate-button">Translate</button>
         | 
| 19 | 
            +
                    <h2>Translated Text:</h2>
         | 
| 20 | 
            +
                    <div id="output"></div>
         | 
| 21 | 
            +
                </div>
         | 
| 22 | 
            +
            </body>
         | 
| 23 | 
            +
            </html>
         | 
    	
        frontend/backup/script.js
    ADDED
    
    | @@ -0,0 +1,42 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            document.addEventListener('DOMContentLoaded', () => {
         | 
| 2 | 
            +
                const translateButton = document.getElementById('translate-button');
         | 
| 3 | 
            +
                const textToTranslate = document.getElementById('text-to-translate');
         | 
| 4 | 
            +
                const sourceLanguage = document.getElementById('source-language');
         | 
| 5 | 
            +
                const outputDiv = document.getElementById('output');
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                translateButton.addEventListener('click', async () => {
         | 
| 8 | 
            +
                    const text = textToTranslate.value;
         | 
| 9 | 
            +
                    const lang = sourceLanguage.value;
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    outputDiv.innerText = "Translating...";
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    if (!text.trim()) {
         | 
| 14 | 
            +
                        outputDiv.innerText = "Please enter some text to translate.";
         | 
| 15 | 
            +
                        return;
         | 
| 16 | 
            +
                    }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    try {
         | 
| 19 | 
            +
                        const response = await fetch('/translate', {
         | 
| 20 | 
            +
                            method: 'POST',
         | 
| 21 | 
            +
                            headers: {
         | 
| 22 | 
            +
                                'Content-Type': 'application/json',
         | 
| 23 | 
            +
                                'accept': 'application/json'
         | 
| 24 | 
            +
                            },
         | 
| 25 | 
            +
                            body: JSON.stringify({
         | 
| 26 | 
            +
                                text: text,
         | 
| 27 | 
            +
                                source_language: lang
         | 
| 28 | 
            +
                            })
         | 
| 29 | 
            +
                        });
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                        if (!response.ok) {
         | 
| 32 | 
            +
                            const errorData = await response.json();
         | 
| 33 | 
            +
                            throw new Error(errorData.detail || 'An error occurred');
         | 
| 34 | 
            +
                        }
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                        const data = await response.json();
         | 
| 37 | 
            +
                        outputDiv.innerText = data.translated_text;
         | 
| 38 | 
            +
                    } catch (error) {
         | 
| 39 | 
            +
                        outputDiv.innerText = `Error: ${error.message}`;
         | 
| 40 | 
            +
                    }
         | 
| 41 | 
            +
                });
         | 
| 42 | 
            +
            });
         | 
    	
        frontend/backup/styles.css
    ADDED
    
    | @@ -0,0 +1,54 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            body {
         | 
| 2 | 
            +
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
         | 
| 3 | 
            +
                background-color: #f0f2f5;
         | 
| 4 | 
            +
                display: flex;
         | 
| 5 | 
            +
                justify-content: center;
         | 
| 6 | 
            +
                align-items: center;
         | 
| 7 | 
            +
                height: 100vh;
         | 
| 8 | 
            +
                margin: 0;
         | 
| 9 | 
            +
            }
         | 
| 10 | 
            +
            .container {
         | 
| 11 | 
            +
                background-color: #fff;
         | 
| 12 | 
            +
                padding: 2rem;
         | 
| 13 | 
            +
                border-radius: 8px;
         | 
| 14 | 
            +
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
         | 
| 15 | 
            +
                width: 100%;
         | 
| 16 | 
            +
                max-width: 500px;
         | 
| 17 | 
            +
            }
         | 
| 18 | 
            +
            h1 {
         | 
| 19 | 
            +
                text-align: center;
         | 
| 20 | 
            +
                color: #1c1e21;
         | 
| 21 | 
            +
            }
         | 
| 22 | 
            +
            textarea {
         | 
| 23 | 
            +
                width: 100%;
         | 
| 24 | 
            +
                padding: 0.5rem;
         | 
| 25 | 
            +
                border: 1px solid #dddfe2;
         | 
| 26 | 
            +
                border-radius: 6px;
         | 
| 27 | 
            +
                margin-bottom: 1rem;
         | 
| 28 | 
            +
                font-size: 1rem;
         | 
| 29 | 
            +
                resize: vertical;
         | 
| 30 | 
            +
            }
         | 
| 31 | 
            +
            select, button {
         | 
| 32 | 
            +
                width: 100%;
         | 
| 33 | 
            +
                padding: 0.75rem;
         | 
| 34 | 
            +
                border-radius: 6px;
         | 
| 35 | 
            +
                border: 1px solid #dddfe2;
         | 
| 36 | 
            +
                font-size: 1rem;
         | 
| 37 | 
            +
                margin-bottom: 1rem;
         | 
| 38 | 
            +
            }
         | 
| 39 | 
            +
            button {
         | 
| 40 | 
            +
                background-color: #1877f2;
         | 
| 41 | 
            +
                color: #fff;
         | 
| 42 | 
            +
                border: none;
         | 
| 43 | 
            +
                cursor: pointer;
         | 
| 44 | 
            +
            }
         | 
| 45 | 
            +
            button:hover {
         | 
| 46 | 
            +
                background-color: #166fe5;
         | 
| 47 | 
            +
            }
         | 
| 48 | 
            +
            #output {
         | 
| 49 | 
            +
                margin-top: 1rem;
         | 
| 50 | 
            +
                padding: 1rem;
         | 
| 51 | 
            +
                background-color: #f0f2f5;
         | 
| 52 | 
            +
                border-radius: 6px;
         | 
| 53 | 
            +
                min-height: 50px;
         | 
| 54 | 
            +
            }
         | 
    	
        frontend/index.html
    ADDED
    
    | @@ -0,0 +1,101 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            <!DOCTYPE html>
         | 
| 2 | 
            +
            <html lang="en" data-theme="light">
         | 
| 3 | 
            +
            <head>
         | 
| 4 | 
            +
                <meta charset="UTF-8">
         | 
| 5 | 
            +
                <meta name="viewport" content="width=device-width, initial-scale=1.0">
         | 
| 6 | 
            +
                <title>Anuvaad AI</title>
         | 
| 7 | 
            +
                <meta name="description" content="Anuvaad AI - Nepali and Sinhala to English Machine Translation">
         | 
| 8 | 
            +
                <link rel="stylesheet" href="/frontend/styles.css">
         | 
| 9 | 
            +
                <script src="/frontend/script.js" defer></script>
         | 
| 10 | 
            +
                <!-- Preload brand logo for faster first paint -->
         | 
| 11 | 
            +
                <link rel="preload" as="image" href="/frontend/public/android-chrome-192x192.png">
         | 
| 12 | 
            +
                <link rel="icon" type="image/png" sizes="32x32" href="/frontend/public/favicon-32x32.png">
         | 
| 13 | 
            +
                <link rel="icon" type="image/png" sizes="16x16" href="/frontend/public/favicon-16x16.png">
         | 
| 14 | 
            +
                <link rel="apple-touch-icon" sizes="180x180" href="/frontend/public/apple-touch-icon.png">
         | 
| 15 | 
            +
                <link rel="manifest" href="/frontend/public/site.webmanifest">
         | 
| 16 | 
            +
                <!-- Optional larger icons -->
         | 
| 17 | 
            +
                <link rel="icon" type="image/png" sizes="192x192" href="/frontend/public/android-chrome-192x192.png">
         | 
| 18 | 
            +
                <link rel="icon" type="image/png" sizes="512x512" href="/frontend/public/android-chrome-512x512.png">
         | 
| 19 | 
            +
            </head>
         | 
| 20 | 
            +
            <body>
         | 
| 21 | 
            +
                <header class="header">
         | 
| 22 | 
            +
                    <div class="brand">
         | 
| 23 | 
            +
                        <a href="/" class="brand-link" aria-label="Go to home">
         | 
| 24 | 
            +
                            <img src="/frontend/public/android-chrome-192x192.png" alt="Anuvaad AI logo" class="brand-logo" width="44" height="44" decoding="async" fetchpriority="high">
         | 
| 25 | 
            +
                            <span class="brand-text">Anuvaad.ai</span>
         | 
| 26 | 
            +
                        </a>
         | 
| 27 | 
            +
                    </div>
         | 
| 28 | 
            +
                    <div class="tagline">Nepali and Sinhala to English translation</div>
         | 
| 29 | 
            +
                    <div class="header-actions">
         | 
| 30 | 
            +
                        <!-- Theme selector in header -->
         | 
| 31 | 
            +
                        <label for="theme-select" class="sr-only">Theme</label>
         | 
| 32 | 
            +
                        <select id="theme-select" class="theme-select" aria-label="Theme">
         | 
| 33 | 
            +
                          <option value="gradient">Gradient (Default)</option>
         | 
| 34 | 
            +
                          <option value="light">Light</option>
         | 
| 35 | 
            +
                          <option value="dark">Dark</option>
         | 
| 36 | 
            +
                          <option value="ocean">Ocean</option>
         | 
| 37 | 
            +
                          <option value="sunset">Sunset</option>
         | 
| 38 | 
            +
                          <option value="forest">Forest</option>
         | 
| 39 | 
            +
                          <option value="rose">Rose</option>
         | 
| 40 | 
            +
                          <option value="slate">Slate</option>
         | 
| 41 | 
            +
                        </select>
         | 
| 42 | 
            +
                        <button id="theme-toggle" class="ghost" aria-label="Toggle theme">Dark mode</button>
         | 
| 43 | 
            +
                        <!-- Header status indicator -->
         | 
| 44 | 
            +
                    </div>
         | 
| 45 | 
            +
                </header>
         | 
| 46 | 
            +
                <main class="container">
         | 
| 47 | 
            +
                    <div class="grid">
         | 
| 48 | 
            +
                        <section class="panel">
         | 
| 49 | 
            +
                            <label for="text-to-translate" class="label">Enter text to translate</label>
         | 
| 50 | 
            +
                            <textarea id="text-to-translate" rows="10" placeholder="Type or paste text here. For batch translation, enter one sentence per line."></textarea>
         | 
| 51 | 
            +
                            <div class="controls">
         | 
| 52 | 
            +
                                <div class="control">
         | 
| 53 | 
            +
                                    <label for="source-language" class="label">Source language</label>
         | 
| 54 | 
            +
                                    <select id="source-language"></select>
         | 
| 55 | 
            +
                                    <small id="lang-detect-hint" class="hint" aria-live="polite"></small>
         | 
| 56 | 
            +
                                </div>
         | 
| 57 | 
            +
                                <div class="control toggle">
         | 
| 58 | 
            +
                                    <label class="checkbox-label">
         | 
| 59 | 
            +
                                        <input type="checkbox" id="batch-toggle" />
         | 
| 60 | 
            +
                                        Batch mode
         | 
| 61 | 
            +
                                    </label>
         | 
| 62 | 
            +
                                    <small class="hint">Translate multiple lines at once</small>
         | 
| 63 | 
            +
                                </div>
         | 
| 64 | 
            +
                                <!-- Borrowed words / names correction toggle -->
         | 
| 65 | 
            +
                                <div class="control toggle">
         | 
| 66 | 
            +
                                    <label class="checkbox-label">
         | 
| 67 | 
            +
                                        <input type="checkbox" id="borrowed-toggle" checked />
         | 
| 68 | 
            +
                                        Fix borrowed words and names
         | 
| 69 | 
            +
                                    </label>
         | 
| 70 | 
            +
                                    <small class="hint">Transliterate and correct English-like names (e.g., Coco Beach)</small>
         | 
| 71 | 
            +
                                </div>
         | 
| 72 | 
            +
                                <!-- Dataset processing (no data display to users) -->
         | 
| 73 | 
            +
                                <div class="control">
         | 
| 74 | 
            +
                                    <label for="process-data-button" class="label">Dataset processing</label>
         | 
| 75 | 
            +
                                    <button id="process-data-button" class="ghost" aria-label="Process dataset">Process data</button>
         | 
| 76 | 
            +
                                    <small id="process-data-status" class="hint" aria-live="polite"></small>
         | 
| 77 | 
            +
                                </div>
         | 
| 78 | 
            +
                            </div>
         | 
| 79 | 
            +
                            <div class="actions">
         | 
| 80 | 
            +
                                <button id="translate-button" class="primary">Translate</button>
         | 
| 81 | 
            +
                                <button id="clear-button" class="ghost" aria-label="Clear input">Clear</button>
         | 
| 82 | 
            +
                            </div>
         | 
| 83 | 
            +
                        </section>
         | 
| 84 | 
            +
                        <section class="panel">
         | 
| 85 | 
            +
                            <div class="panel-header">
         | 
| 86 | 
            +
                                <h2>Translated Text</h2>
         | 
| 87 | 
            +
                                <div class="panel-actions">
         | 
| 88 | 
            +
                                    <button id="copy-button" class="ghost" aria-label="Copy output">Copy</button>
         | 
| 89 | 
            +
                                    <button id="download-button" class="ghost" aria-label="Download output">Download</button>
         | 
| 90 | 
            +
                                    <button id="share-button" class="ghost" aria-label="Share link">Share</button>
         | 
| 91 | 
            +
                                </div>
         | 
| 92 | 
            +
                            </div>
         | 
| 93 | 
            +
                            <div id="output" class="output" role="status" aria-live="polite"></div>
         | 
| 94 | 
            +
                        </section>
         | 
| 95 | 
            +
                    </div>
         | 
| 96 | 
            +
                </main>
         | 
| 97 | 
            +
                <footer class="footer">
         | 
| 98 | 
            +
                    <span>Powered by NLLB and FastAPI</span>
         | 
| 99 | 
            +
                </footer>
         | 
| 100 | 
            +
            </body>
         | 
| 101 | 
            +
            </html>
         | 
    	
        frontend/public/android-chrome-192x192.png
    ADDED
    
    |   | 
| Git LFS Details
 | 
    	
        frontend/public/android-chrome-512x512.png
    ADDED
    
    |   | 
| Git LFS Details
 | 
    	
        frontend/public/apple-touch-icon.png
    ADDED
    
    |   | 
| Git LFS Details
 | 
    	
        frontend/public/favicon-16x16.png
    ADDED
    
    |   | 
| Git LFS Details
 | 
    	
        frontend/public/favicon-32x32.png
    ADDED
    
    |   | 
| Git LFS Details
 | 
    	
        frontend/public/favicon.ico
    ADDED
    
    |  | 
| Git LFS Details
 | 
    	
        frontend/public/site.webmanifest
    ADDED
    
    | @@ -0,0 +1 @@ | |
|  | 
|  | |
| 1 | 
            +
            {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
         | 
    	
        frontend/script.js
    ADDED
    
    | @@ -0,0 +1,337 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            document.addEventListener('DOMContentLoaded', async () => {
         | 
| 2 | 
            +
                const translateButton = document.getElementById('translate-button');
         | 
| 3 | 
            +
                const clearButton = document.getElementById('clear-button');
         | 
| 4 | 
            +
                const copyButton = document.getElementById('copy-button');
         | 
| 5 | 
            +
                const downloadButton = document.getElementById('download-button');
         | 
| 6 | 
            +
                const shareButton = document.getElementById('share-button');
         | 
| 7 | 
            +
                const textToTranslate = document.getElementById('text-to-translate');
         | 
| 8 | 
            +
                const sourceLanguage = document.getElementById('source-language');
         | 
| 9 | 
            +
                const outputDiv = document.getElementById('output');
         | 
| 10 | 
            +
                const batchToggle = document.getElementById('batch-toggle');
         | 
| 11 | 
            +
                const langDetectHint = document.getElementById('lang-detect-hint');
         | 
| 12 | 
            +
                const themeToggle = document.getElementById('theme-toggle');
         | 
| 13 | 
            +
                const processDataButton = document.getElementById('process-data-button');
         | 
| 14 | 
            +
                const processDataStatus = document.getElementById('process-data-status');
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                // Debounce timer for detection
         | 
| 17 | 
            +
                let detectTimer = null;
         | 
| 18 | 
            +
                const detectDelay = 250;
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                // Populate languages dynamically
         | 
| 21 | 
            +
                try {
         | 
| 22 | 
            +
                    const langRes = await fetch('/languages');
         | 
| 23 | 
            +
                    const langData = await langRes.json();
         | 
| 24 | 
            +
                    const langs = (langData && langData.supported_languages) || ['nepali', 'sinhala'];
         | 
| 25 | 
            +
                    sourceLanguage.innerHTML = '';
         | 
| 26 | 
            +
                    langs.forEach(l => {
         | 
| 27 | 
            +
                        const opt = document.createElement('option');
         | 
| 28 | 
            +
                        opt.value = l;
         | 
| 29 | 
            +
                        opt.textContent = l.charAt(0).toUpperCase() + l.slice(1);
         | 
| 30 | 
            +
                        sourceLanguage.appendChild(opt);
         | 
| 31 | 
            +
                    });
         | 
| 32 | 
            +
                } catch (e) {
         | 
| 33 | 
            +
                    // Fallback
         | 
| 34 | 
            +
                    sourceLanguage.innerHTML = '<option value="nepali">Nepali</option><option value="sinhala">Sinhala</option>';
         | 
| 35 | 
            +
                }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                // Theme toggle
         | 
| 38 | 
            +
                // Ensure default gradient theme on first load unless user saved preference
         | 
| 39 | 
            +
                (function() {
         | 
| 40 | 
            +
                  const savedTheme = localStorage.getItem('theme');
         | 
| 41 | 
            +
                  if (!savedTheme) {
         | 
| 42 | 
            +
                    document.documentElement.setAttribute('data-theme', 'gradient');
         | 
| 43 | 
            +
                  }
         | 
| 44 | 
            +
                })();
         | 
| 45 | 
            +
                
         | 
| 46 | 
            +
                themeToggle.addEventListener('click', () => {
         | 
| 47 | 
            +
                    const html = document.documentElement;
         | 
| 48 | 
            +
                    const isDark = html.getAttribute('data-theme') === 'dark';
         | 
| 49 | 
            +
                    html.setAttribute('data-theme', isDark ? 'light' : 'dark');
         | 
| 50 | 
            +
                    themeToggle.textContent = isDark ? 'Light mode' : 'Dark mode';
         | 
| 51 | 
            +
                    localStorage.setItem('anuvaad_theme', isDark ? 'light' : 'dark');
         | 
| 52 | 
            +
                });
         | 
| 53 | 
            +
                const savedTheme = localStorage.getItem('anuvaad_theme');
         | 
| 54 | 
            +
                if (savedTheme) {
         | 
| 55 | 
            +
                    document.documentElement.setAttribute('data-theme', savedTheme);
         | 
| 56 | 
            +
                    themeToggle.textContent = savedTheme === 'dark' ? 'Dark mode' : 'Light mode';
         | 
| 57 | 
            +
                }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                function setLoading(isLoading) {
         | 
| 60 | 
            +
                    translateButton.disabled = isLoading;
         | 
| 61 | 
            +
                    translateButton.textContent = isLoading ? 'Translating…' : 'Translate';
         | 
| 62 | 
            +
                    outputDiv.setAttribute('aria-busy', String(isLoading));
         | 
| 63 | 
            +
                }
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                // Basic language auto-detect by script characters (debounced)
         | 
| 66 | 
            +
                textToTranslate.addEventListener('input', () => {
         | 
| 67 | 
            +
                    clearTimeout(detectTimer);
         | 
| 68 | 
            +
                    detectTimer = setTimeout(async () => {
         | 
| 69 | 
            +
                        const sample = (textToTranslate.value || '').slice(0, 200);
         | 
| 70 | 
            +
                        let detected = '';
         | 
| 71 | 
            +
                        // Backend-assisted detection for robustness
         | 
| 72 | 
            +
                        try {
         | 
| 73 | 
            +
                            const res = await fetch('/detect', {
         | 
| 74 | 
            +
                                method: 'POST',
         | 
| 75 | 
            +
                                headers: { 'Content-Type': 'application/json', 'accept': 'application/json' },
         | 
| 76 | 
            +
                                body: JSON.stringify({ text: sample })
         | 
| 77 | 
            +
                            });
         | 
| 78 | 
            +
                            if (res.ok) {
         | 
| 79 | 
            +
                                const data = await res.json();
         | 
| 80 | 
            +
                                detected = data.detected_language || '';
         | 
| 81 | 
            +
                            }
         | 
| 82 | 
            +
                        } catch (e) {
         | 
| 83 | 
            +
                            // ignore detection errors, fallback to script-based
         | 
| 84 | 
            +
                        }
         | 
| 85 | 
            +
                        if (!detected) {
         | 
| 86 | 
            +
                            const hasDevanagari = /[\u0900-\u097F]/.test(sample);
         | 
| 87 | 
            +
                            const hasSinhala = /[\u0D80-\u0DFF]/.test(sample);
         | 
| 88 | 
            +
                            if (hasDevanagari) detected = 'nepali';
         | 
| 89 | 
            +
                            else if (hasSinhala) detected = 'sinhala';
         | 
| 90 | 
            +
                        }
         | 
| 91 | 
            +
                        if (detected) {
         | 
| 92 | 
            +
                            sourceLanguage.value = detected;
         | 
| 93 | 
            +
                            langDetectHint.textContent = `Detected: ${detected}`;
         | 
| 94 | 
            +
                        } else {
         | 
| 95 | 
            +
                            langDetectHint.textContent = '';
         | 
| 96 | 
            +
                        }
         | 
| 97 | 
            +
                    }, detectDelay);
         | 
| 98 | 
            +
                });
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                translateButton.addEventListener('click', async () => {
         | 
| 101 | 
            +
                    const text = (textToTranslate.value || '').trim();
         | 
| 102 | 
            +
                    const lang = sourceLanguage.value;
         | 
| 103 | 
            +
                    const isBatch = batchToggle && batchToggle.checked;
         | 
| 104 | 
            +
                    const borrowedFixEl = document.getElementById('borrowed-toggle');
         | 
| 105 | 
            +
                    const borrowedFix = borrowedFixEl ? borrowedFixEl.checked : true;
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                    outputDiv.innerHTML = '';
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    if (!text) {
         | 
| 110 | 
            +
                        outputDiv.innerText = 'Please enter some text to translate.';
         | 
| 111 | 
            +
                        return;
         | 
| 112 | 
            +
                    }
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    setLoading(true);
         | 
| 115 | 
            +
                    try {
         | 
| 116 | 
            +
                        let response;
         | 
| 117 | 
            +
                        if (isBatch) {
         | 
| 118 | 
            +
                            const texts = text.split('\n').map(t => t.trim()).filter(Boolean);
         | 
| 119 | 
            +
                            if (texts.length === 0) {
         | 
| 120 | 
            +
                                outputDiv.innerText = 'Please provide at least one non-empty line for batch translation.';
         | 
| 121 | 
            +
                                setLoading(false);
         | 
| 122 | 
            +
                                return;
         | 
| 123 | 
            +
                            }
         | 
| 124 | 
            +
                            response = await fetch('/batch-translate', {
         | 
| 125 | 
            +
                                method: 'POST',
         | 
| 126 | 
            +
                                headers: {
         | 
| 127 | 
            +
                                    'Content-Type': 'application/json',
         | 
| 128 | 
            +
                                    'accept': 'application/json'
         | 
| 129 | 
            +
                                },
         | 
| 130 | 
            +
                                body: JSON.stringify({ texts, source_language: lang, borrowed_fix: borrowedFix })
         | 
| 131 | 
            +
                            });
         | 
| 132 | 
            +
                        } else {
         | 
| 133 | 
            +
                            response = await fetch('/translate', {
         | 
| 134 | 
            +
                                method: 'POST',
         | 
| 135 | 
            +
                                headers: {
         | 
| 136 | 
            +
                                    'Content-Type': 'application/json',
         | 
| 137 | 
            +
                                    'accept': 'application/json'
         | 
| 138 | 
            +
                                },
         | 
| 139 | 
            +
                                body: JSON.stringify({ text, source_language: lang, borrowed_fix: borrowedFix })
         | 
| 140 | 
            +
                            });
         | 
| 141 | 
            +
                        }
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                        if (!response.ok) {
         | 
| 144 | 
            +
                            const errorData = await response.json().catch(() => ({}));
         | 
| 145 | 
            +
                            throw new Error(errorData.detail || 'An error occurred while translating.');
         | 
| 146 | 
            +
                        }
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                        const data = await response.json();
         | 
| 149 | 
            +
                        if (isBatch) {
         | 
| 150 | 
            +
                            const results = data.translated_texts || [];
         | 
| 151 | 
            +
                            const table = document.createElement('table');
         | 
| 152 | 
            +
                            table.className = 'result-table';
         | 
| 153 | 
            +
                            const thead = document.createElement('thead');
         | 
| 154 | 
            +
                            thead.innerHTML = '<tr><th>#</th><th>Source</th><th>Translation</th></tr>';
         | 
| 155 | 
            +
                            table.appendChild(thead);
         | 
| 156 | 
            +
                            const tbody = document.createElement('tbody');
         | 
| 157 | 
            +
                            const sources = text.split('\n').map(t => t.trim()).filter(Boolean);
         | 
| 158 | 
            +
                            results.forEach((t, idx) => {
         | 
| 159 | 
            +
                                const tr = document.createElement('tr');
         | 
| 160 | 
            +
                                const tdIdx = document.createElement('td'); tdIdx.textContent = String(idx + 1);
         | 
| 161 | 
            +
                                const tdSrc = document.createElement('td'); tdSrc.textContent = sources[idx] || '';
         | 
| 162 | 
            +
                                const tdDst = document.createElement('td'); tdDst.textContent = t;
         | 
| 163 | 
            +
                                tr.appendChild(tdIdx); tr.appendChild(tdSrc); tr.appendChild(tdDst);
         | 
| 164 | 
            +
                                tbody.appendChild(tr);
         | 
| 165 | 
            +
                            });
         | 
| 166 | 
            +
                            table.appendChild(tbody);
         | 
| 167 | 
            +
                            outputDiv.appendChild(table);
         | 
| 168 | 
            +
                            downloadButton.dataset.csv = toCSV(sources, results);
         | 
| 169 | 
            +
                        } else {
         | 
| 170 | 
            +
                            outputDiv.innerText = data.translated_text || data.translation || '';
         | 
| 171 | 
            +
                            downloadButton.dataset.csv = toCSV([text], [outputDiv.innerText]);
         | 
| 172 | 
            +
                        }
         | 
| 173 | 
            +
                    } catch (error) {
         | 
| 174 | 
            +
                        outputDiv.innerText = `Error: ${error.message}`;
         | 
| 175 | 
            +
                    } finally {
         | 
| 176 | 
            +
                        setLoading(false);
         | 
| 177 | 
            +
                    }
         | 
| 178 | 
            +
                });
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                // Allow Ctrl+Enter to trigger translation
         | 
| 181 | 
            +
                textToTranslate.addEventListener('keydown', (e) => {
         | 
| 182 | 
            +
                    if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
         | 
| 183 | 
            +
                        translateButton.click();
         | 
| 184 | 
            +
                    }
         | 
| 185 | 
            +
                });
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                // Clear button
         | 
| 188 | 
            +
                clearButton.addEventListener('click', () => {
         | 
| 189 | 
            +
                    textToTranslate.value = '';
         | 
| 190 | 
            +
                    outputDiv.innerHTML = '';
         | 
| 191 | 
            +
                    downloadButton.removeAttribute('data-csv');
         | 
| 192 | 
            +
                });
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                // Hide dataset processing controls from users (keep in DOM, but not visible)
         | 
| 195 | 
            +
                if (processDataButton) {
         | 
| 196 | 
            +
                    const datasetControl = processDataButton.closest('.control');
         | 
| 197 | 
            +
                    if (datasetControl) datasetControl.hidden = true;
         | 
| 198 | 
            +
                    const datasetLabel = document.querySelector('label[for="process-data-button"]');
         | 
| 199 | 
            +
                    if (datasetLabel) datasetLabel.hidden = true;
         | 
| 200 | 
            +
                    if (processDataStatus) processDataStatus.hidden = true;
         | 
| 201 | 
            +
                }
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                // Hide borrowed words/names UI text while preserving functionality
         | 
| 204 | 
            +
                const borrowedFixEl = document.getElementById('borrowed-toggle');
         | 
| 205 | 
            +
                if (borrowedFixEl) {
         | 
| 206 | 
            +
                    const borrowedControl = borrowedFixEl.closest('.control');
         | 
| 207 | 
            +
                    // Keep the control present only during translation flow but hidden from display
         | 
| 208 | 
            +
                    if (borrowedControl) borrowedControl.hidden = true;
         | 
| 209 | 
            +
                    const borrowedLabel = borrowedFixEl.closest('label');
         | 
| 210 | 
            +
                    if (borrowedLabel) {
         | 
| 211 | 
            +
                        borrowedLabel.hidden = true;
         | 
| 212 | 
            +
                        // Remove any visible text nodes to avoid displaying borrowed words/names text
         | 
| 213 | 
            +
                        borrowedLabel.childNodes.forEach(node => {
         | 
| 214 | 
            +
                            if (node.nodeType === Node.TEXT_NODE) {
         | 
| 215 | 
            +
                                node.textContent = '';
         | 
| 216 | 
            +
                            }
         | 
| 217 | 
            +
                        });
         | 
| 218 | 
            +
                    }
         | 
| 219 | 
            +
                    const borrowedHint = borrowedControl ? borrowedControl.querySelector('small.hint') : null;
         | 
| 220 | 
            +
                    if (borrowedHint) {
         | 
| 221 | 
            +
                        borrowedHint.hidden = true;
         | 
| 222 | 
            +
                        borrowedHint.textContent = '';
         | 
| 223 | 
            +
                    }
         | 
| 224 | 
            +
                    // Remove the input element itself to ensure it never appears on screen
         | 
| 225 | 
            +
                    borrowedFixEl.remove();
         | 
| 226 | 
            +
                }
         | 
| 227 | 
            +
             | 
| 228 | 
            +
                // Helper to trigger dataset processing without user interaction
         | 
| 229 | 
            +
                async function triggerProcessData() {
         | 
| 230 | 
            +
                    if (!processDataStatus) return;
         | 
| 231 | 
            +
                    try {
         | 
| 232 | 
            +
                        const res = await fetch('/process-data', { method: 'POST' });
         | 
| 233 | 
            +
                        if (!res.ok) {
         | 
| 234 | 
            +
                            const err = await res.json().catch(() => ({}));
         | 
| 235 | 
            +
                            throw new Error(err.detail || 'Failed to process dataset');
         | 
| 236 | 
            +
                        }
         | 
| 237 | 
            +
                        const data = await res.json();
         | 
| 238 | 
            +
                        // Update hidden status for diagnostics; users won't see it
         | 
| 239 | 
            +
                        processDataStatus.textContent = `Processed: ${data.processed_files} files, ${data.total_lines} lines`;
         | 
| 240 | 
            +
                    } catch (e) {
         | 
| 241 | 
            +
                        processDataStatus.textContent = `Error: ${e.message}`;
         | 
| 242 | 
            +
                    }
         | 
| 243 | 
            +
                }
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                // Automatically process dataset on page load (runs once)
         | 
| 246 | 
            +
                triggerProcessData();
         | 
| 247 | 
            +
             | 
| 248 | 
            +
                // Dataset processing trigger (kept inside DOMContentLoaded for scope safety)
         | 
| 249 | 
            +
                if (processDataButton) {
         | 
| 250 | 
            +
                    processDataButton.addEventListener('click', async () => {
         | 
| 251 | 
            +
                        // Even if clicked (hidden), keep behavior consistent
         | 
| 252 | 
            +
                        processDataStatus.textContent = 'Processing dataset…';
         | 
| 253 | 
            +
                        try {
         | 
| 254 | 
            +
                            const res = await fetch('/process-data', { method: 'POST' });
         | 
| 255 | 
            +
                            if (!res.ok) {
         | 
| 256 | 
            +
                                const err = await res.json().catch(() => ({}));
         | 
| 257 | 
            +
                                throw new Error(err.detail || 'Failed to process dataset');
         | 
| 258 | 
            +
                            }
         | 
| 259 | 
            +
                            const data = await res.json();
         | 
| 260 | 
            +
                            processDataStatus.textContent = `Processed: ${data.processed_files} files, ${data.total_lines} lines`;
         | 
| 261 | 
            +
                        } catch (e) {
         | 
| 262 | 
            +
                            processDataStatus.textContent = `Error: ${e.message}`;
         | 
| 263 | 
            +
                        }
         | 
| 264 | 
            +
                    });
         | 
| 265 | 
            +
                }
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                // Copy button
         | 
| 268 | 
            +
                copyButton.addEventListener('click', async () => {
         | 
| 269 | 
            +
                    const text = outputDiv.innerText || '';
         | 
| 270 | 
            +
                    if (!text) return;
         | 
| 271 | 
            +
                    try {
         | 
| 272 | 
            +
                        await navigator.clipboard.writeText(text);
         | 
| 273 | 
            +
                    } catch (e) {
         | 
| 274 | 
            +
                        // Fallback for older browsers
         | 
| 275 | 
            +
                        const ta = document.createElement('textarea');
         | 
| 276 | 
            +
                        ta.value = text;
         | 
| 277 | 
            +
                        document.body.appendChild(ta);
         | 
| 278 | 
            +
                        ta.select();
         | 
| 279 | 
            +
                        document.execCommand('copy');
         | 
| 280 | 
            +
                        document.body.removeChild(ta);
         | 
| 281 | 
            +
                    }
         | 
| 282 | 
            +
                });
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                // Download CSV
         | 
| 285 | 
            +
                downloadButton.addEventListener('click', () => {
         | 
| 286 | 
            +
                    const csv = downloadButton.dataset.csv;
         | 
| 287 | 
            +
                    if (!csv) return;
         | 
| 288 | 
            +
                    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
         | 
| 289 | 
            +
                    const url = URL.createObjectURL(blob);
         | 
| 290 | 
            +
                    const a = document.createElement('a');
         | 
| 291 | 
            +
                    a.href = url;
         | 
| 292 | 
            +
                    a.download = 'translations.csv';
         | 
| 293 | 
            +
                    a.click();
         | 
| 294 | 
            +
                    URL.revokeObjectURL(url);
         | 
| 295 | 
            +
                });
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                // Share result
         | 
| 298 | 
            +
                shareButton.addEventListener('click', async () => {
         | 
| 299 | 
            +
                    const text = outputDiv.innerText || '';
         | 
| 300 | 
            +
                    if (!text) return;
         | 
| 301 | 
            +
                    try {
         | 
| 302 | 
            +
                        await navigator.share({ text });
         | 
| 303 | 
            +
                    } catch (e) {
         | 
| 304 | 
            +
                        // Ignore if not supported
         | 
| 305 | 
            +
                    }
         | 
| 306 | 
            +
                });
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                function toCSV(sources, results) {
         | 
| 309 | 
            +
                    const rows = sources.map((s, i) => [s, results[i] || '']);
         | 
| 310 | 
            +
                    const csvRows = rows.map(r => r.map(v => '"' + String(v).replaceAll('"', '""') + '"').join(','));
         | 
| 311 | 
            +
                    return 'source,translation\n' + csvRows.join('\n');
         | 
| 312 | 
            +
                }
         | 
| 313 | 
            +
             | 
| 314 | 
            +
                // Theme select
         | 
| 315 | 
            +
                const themeSelect = document.getElementById('theme-select');
         | 
| 316 | 
            +
                if (themeSelect) {
         | 
| 317 | 
            +
                  const saved = localStorage.getItem('theme');
         | 
| 318 | 
            +
                  const initial = saved || 'gradient';
         | 
| 319 | 
            +
                  document.documentElement.setAttribute('data-theme', initial);
         | 
| 320 | 
            +
                  themeSelect.value = initial;
         | 
| 321 | 
            +
                  themeSelect.addEventListener('change', (e) => {
         | 
| 322 | 
            +
                    const v = e.target.value;
         | 
| 323 | 
            +
                    document.documentElement.setAttribute('data-theme', v);
         | 
| 324 | 
            +
                    localStorage.setItem('theme', v);
         | 
| 325 | 
            +
                  });
         | 
| 326 | 
            +
                }
         | 
| 327 | 
            +
             | 
| 328 | 
            +
                const themeToggleEl = document.getElementById('theme-toggle');
         | 
| 329 | 
            +
                if (themeToggleEl) {
         | 
| 330 | 
            +
                  themeToggleEl.addEventListener('click', () => {
         | 
| 331 | 
            +
                    const html = document.documentElement;
         | 
| 332 | 
            +
                    const isDark = html.getAttribute('data-theme') === 'dark';
         | 
| 333 | 
            +
                    html.setAttribute('data-theme', isDark ? 'light' : 'dark');
         | 
| 334 | 
            +
                    localStorage.setItem('theme', isDark ? 'light' : 'dark');
         | 
| 335 | 
            +
                  });
         | 
| 336 | 
            +
                }
         | 
| 337 | 
            +
            });
         | 
    	
        frontend/site.webmanifest
    ADDED
    
    | @@ -0,0 +1 @@ | |
|  | 
|  | |
| 1 | 
            +
            {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
         | 
    	
        frontend/styles.css
    ADDED
    
    | @@ -0,0 +1,512 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            /* Base variables as fallback; specific themes override below */
         | 
| 2 | 
            +
            :root {
         | 
| 3 | 
            +
              --bg: #f8fafc;
         | 
| 4 | 
            +
              --bg-soft: #f1f5f9;
         | 
| 5 | 
            +
              --card: #ffffff;
         | 
| 6 | 
            +
              --text: #0f172a;
         | 
| 7 | 
            +
              --muted: #64748b;
         | 
| 8 | 
            +
              --primary: #3b82f6;
         | 
| 9 | 
            +
              --primary-hover: #2563eb;
         | 
| 10 | 
            +
              --ghost: #e2e8f0;
         | 
| 11 | 
            +
              --ghost-hover: #cbd5e1;
         | 
| 12 | 
            +
            }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            /* Default gradient theme */
         | 
| 15 | 
            +
            html[data-theme="gradient"] {
         | 
| 16 | 
            +
              --bg: #f8fafc;           /* soft off-white */
         | 
| 17 | 
            +
              --bg-soft: #f1f5f9;      /* misty grey */
         | 
| 18 | 
            +
              --card: #ffffff;         /* pure card surface */
         | 
| 19 | 
            +
              --text: #0f172a;         /* rich charcoal */
         | 
| 20 | 
            +
              --muted: #64748b;        /* calm slate */
         | 
| 21 | 
            +
              --primary: #3b82f6;      /* vibrant blue */
         | 
| 22 | 
            +
              --primary-hover: #2563eb;/* deeper blue */
         | 
| 23 | 
            +
              --ghost: #e2e8f0;        /* airy grey */
         | 
| 24 | 
            +
              --ghost-hover: #cbd5e1;  /* gentle hover */
         | 
| 25 | 
            +
            }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            /* Light theme (flat look) */
         | 
| 28 | 
            +
            html[data-theme="light"] {
         | 
| 29 | 
            +
              --bg: #ffffff;
         | 
| 30 | 
            +
              --bg-soft: #ffffff;
         | 
| 31 | 
            +
              --card: #ffffff;
         | 
| 32 | 
            +
              --text: #0f172a;
         | 
| 33 | 
            +
              --muted: #64748b;
         | 
| 34 | 
            +
              --primary: #3b82f6;
         | 
| 35 | 
            +
              --primary-hover: #2563eb;
         | 
| 36 | 
            +
              --ghost: #eef2f7;
         | 
| 37 | 
            +
              --ghost-hover: #e4e9f2;
         | 
| 38 | 
            +
            }
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            /* Dark theme */
         | 
| 41 | 
            +
            html[data-theme="dark"] {
         | 
| 42 | 
            +
              --bg: #0f172a;           /* midnight navy */
         | 
| 43 | 
            +
              --bg-soft: #1e293b;      /* subtle charcoal */
         | 
| 44 | 
            +
              --card: #1e293b;         /* sleek card */
         | 
| 45 | 
            +
              --text: #f1f5f9;         /* crisp white */
         | 
| 46 | 
            +
              --muted: #94a3b8;        /* muted silver */
         | 
| 47 | 
            +
              --ghost: #334155;        /* muted slate */
         | 
| 48 | 
            +
              --ghost-hover: #475569;  /* soft hover */
         | 
| 49 | 
            +
            }
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            /* Ocean theme */
         | 
| 52 | 
            +
            html[data-theme="ocean"] {
         | 
| 53 | 
            +
              --bg: #e0f2fe;           /* sky tint */
         | 
| 54 | 
            +
              --bg-soft: #bae6fd;      /* light ocean */
         | 
| 55 | 
            +
              --card: #ffffff;
         | 
| 56 | 
            +
              --text: #0f172a;
         | 
| 57 | 
            +
              --muted: #0ea5e9;
         | 
| 58 | 
            +
              --primary: #06b6d4;      /* cyan */
         | 
| 59 | 
            +
              --primary-hover: #0891b2;/* deep cyan */
         | 
| 60 | 
            +
              --ghost: #e0f2fe;
         | 
| 61 | 
            +
              --ghost-hover: #bae6fd;
         | 
| 62 | 
            +
            }
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            /* Sunset theme */
         | 
| 65 | 
            +
            html[data-theme="sunset"] {
         | 
| 66 | 
            +
              --bg: #fff7ed;           /* peach */
         | 
| 67 | 
            +
              --bg-soft: #fde68a;      /* amber */
         | 
| 68 | 
            +
              --card: #ffffff;
         | 
| 69 | 
            +
              --text: #0f172a;
         | 
| 70 | 
            +
              --muted: #ea580c;        /* warm orange */
         | 
| 71 | 
            +
              --primary: #f97316;      /* orange */
         | 
| 72 | 
            +
              --primary-hover: #ea580c;/* deeper orange */
         | 
| 73 | 
            +
              --ghost: #fff1e6;
         | 
| 74 | 
            +
              --ghost-hover: #ffe4c7;
         | 
| 75 | 
            +
            }
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            /* Forest theme */
         | 
| 78 | 
            +
            html[data-theme="forest"] {
         | 
| 79 | 
            +
              --bg: #dcfce7;           /* mint */
         | 
| 80 | 
            +
              --bg-soft: #a7f3d0;      /* light green */
         | 
| 81 | 
            +
              --card: #ffffff;
         | 
| 82 | 
            +
              --text: #0f172a;
         | 
| 83 | 
            +
              --muted: #16a34a;        /* deep green */
         | 
| 84 | 
            +
              --primary: #22c55e;      /* green */
         | 
| 85 | 
            +
              --primary-hover: #16a34a;/* deeper green */
         | 
| 86 | 
            +
              --ghost: #e7ffe9;
         | 
| 87 | 
            +
              --ghost-hover: #d1fadf;
         | 
| 88 | 
            +
            }
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            /* Rose theme */
         | 
| 91 | 
            +
            html[data-theme="rose"] {
         | 
| 92 | 
            +
              --bg: #ffe4e6;           /* blush */
         | 
| 93 | 
            +
              --bg-soft: #fecdd3;      /* soft rose */
         | 
| 94 | 
            +
              --card: #ffffff;
         | 
| 95 | 
            +
              --text: #0f172a;
         | 
| 96 | 
            +
              --muted: #e11d48;        /* rose */
         | 
| 97 | 
            +
              --primary: #f43f5e;      /* vibrant rose */
         | 
| 98 | 
            +
              --primary-hover: #e11d48;/* deep rose */
         | 
| 99 | 
            +
              --ghost: #fff1f2;
         | 
| 100 | 
            +
              --ghost-hover: #ffe4e6;
         | 
| 101 | 
            +
            }
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            /* Slate theme (neutral) */
         | 
| 104 | 
            +
            html[data-theme="slate"] {
         | 
| 105 | 
            +
              --bg: #f1f5f9;           /* light slate */
         | 
| 106 | 
            +
              --bg-soft: #e2e8f0;      /* soft slate */
         | 
| 107 | 
            +
              --card: #ffffff;
         | 
| 108 | 
            +
              --text: #0f172a;
         | 
| 109 | 
            +
              --muted: #64748b;        /* slate */
         | 
| 110 | 
            +
              --primary: #64748b;      /* neutral accent */
         | 
| 111 | 
            +
              --primary-hover: #475569;/* deeper slate */
         | 
| 112 | 
            +
              --ghost: #eceff4;
         | 
| 113 | 
            +
              --ghost-hover: #e1e6ee;
         | 
| 114 | 
            +
            }
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            /* Header theme selector styling */
         | 
| 117 | 
            +
            .theme-select {
         | 
| 118 | 
            +
              padding: 0.5rem 0.75rem;
         | 
| 119 | 
            +
              border-radius: 10px;
         | 
| 120 | 
            +
              border: 1px solid var(--ghost-hover);
         | 
| 121 | 
            +
              background: var(--ghost);
         | 
| 122 | 
            +
              color: var(--text);
         | 
| 123 | 
            +
            }
         | 
| 124 | 
            +
             | 
| 125 | 
            +
            :root {
         | 
| 126 | 
            +
                /* Fresh, vibrant palette */
         | 
| 127 | 
            +
                --bg: #0b1020;           /* deep navy */
         | 
| 128 | 
            +
                --bg-soft: #10172a;      /* softer navy */
         | 
| 129 | 
            +
                --card: #ffffff;         /* cards on light theme */
         | 
| 130 | 
            +
                --text: #0e1a2b;         /* dark text on light surfaces */
         | 
| 131 | 
            +
                --muted: #64748b;        /* slate */
         | 
| 132 | 
            +
                --primary: #7c3aed;      /* purple */
         | 
| 133 | 
            +
                --primary-hover: #6d28d9;/* darker purple */
         | 
| 134 | 
            +
                --ghost: #f1f5f9;        /* light slate */
         | 
| 135 | 
            +
                --ghost-hover: #e2e8f0;  /* hover slate */
         | 
| 136 | 
            +
            }
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            html[data-theme="dark"] {
         | 
| 139 | 
            +
                --bg: #0b1020;           /* deep navy */
         | 
| 140 | 
            +
                --bg-soft: #10172a;      /* softer navy */
         | 
| 141 | 
            +
                --card: #0b1220;         /* dark cards */
         | 
| 142 | 
            +
                --text: #e2e8f0;         /* light text */
         | 
| 143 | 
            +
                --muted: #94a3b8;        /* slate-muted */
         | 
| 144 | 
            +
                --ghost: #0f172a;        /* ghost dark */
         | 
| 145 | 
            +
                --ghost-hover: #1f2937;  /* ghost hover dark */
         | 
| 146 | 
            +
            }
         | 
| 147 | 
            +
             | 
| 148 | 
            +
            .header-actions {
         | 
| 149 | 
            +
                margin-top: 0.5rem;
         | 
| 150 | 
            +
            }
         | 
| 151 | 
            +
             | 
| 152 | 
            +
            /* Utility: visually hidden (for sr-only labels) */
         | 
| 153 | 
            +
            .sr-only {
         | 
| 154 | 
            +
              position: absolute;
         | 
| 155 | 
            +
              width: 1px;
         | 
| 156 | 
            +
              height: 1px;
         | 
| 157 | 
            +
              padding: 0;
         | 
| 158 | 
            +
              margin: -1px;
         | 
| 159 | 
            +
              overflow: hidden;
         | 
| 160 | 
            +
              clip: rect(0, 0, 0, 0);
         | 
| 161 | 
            +
              white-space: nowrap;
         | 
| 162 | 
            +
              border: 0;
         | 
| 163 | 
            +
            }
         | 
| 164 | 
            +
             | 
| 165 | 
            +
            /* Adjust body gradient to respect theme */
         | 
| 166 | 
            +
            body {
         | 
| 167 | 
            +
              background: linear-gradient(135deg, var(--bg) 0%, var(--bg-soft) 100%);
         | 
| 168 | 
            +
            }
         | 
| 169 | 
            +
             | 
| 170 | 
            +
            .header {
         | 
| 171 | 
            +
                width: 100%;
         | 
| 172 | 
            +
                max-width: 1100px;
         | 
| 173 | 
            +
                padding: 2rem 1rem 0.5rem 1rem;
         | 
| 174 | 
            +
                margin: 0 auto;
         | 
| 175 | 
            +
                color: #fff;
         | 
| 176 | 
            +
                display: flex;
         | 
| 177 | 
            +
                flex-wrap: wrap;
         | 
| 178 | 
            +
                align-items: center;
         | 
| 179 | 
            +
                justify-content: center;
         | 
| 180 | 
            +
                text-align: center;
         | 
| 181 | 
            +
                gap: 0.75rem 1rem; /* row/column gap for wrap */
         | 
| 182 | 
            +
            }
         | 
| 183 | 
            +
            /* Arrange brand, tagline, and actions for better UX */
         | 
| 184 | 
            +
            .brand { order: 0; }
         | 
| 185 | 
            +
            .tagline { order: 0; text-align: center; margin: 0.5rem auto; }
         | 
| 186 | 
            +
            .header-actions { order: 0; margin: 0.5rem auto; }
         | 
| 187 | 
            +
             | 
| 188 | 
            +
            @media (min-width: 768px) {
         | 
| 189 | 
            +
              .header {
         | 
| 190 | 
            +
                align-items: center;
         | 
| 191 | 
            +
                justify-content: center;
         | 
| 192 | 
            +
                text-align: center;
         | 
| 193 | 
            +
              }
         | 
| 194 | 
            +
              .header-actions { 
         | 
| 195 | 
            +
                margin: 0 auto; 
         | 
| 196 | 
            +
                order: 0; 
         | 
| 197 | 
            +
                justify-content: center;
         | 
| 198 | 
            +
                align-items: center;
         | 
| 199 | 
            +
              }
         | 
| 200 | 
            +
            }
         | 
| 201 | 
            +
            /* Prevent overflow of actions on small screens */
         | 
| 202 | 
            +
            .header-actions {
         | 
| 203 | 
            +
              display: flex;
         | 
| 204 | 
            +
              flex-wrap: wrap;
         | 
| 205 | 
            +
              gap: 0.5rem;
         | 
| 206 | 
            +
            }
         | 
| 207 | 
            +
             | 
| 208 | 
            +
            /* Improve status pill semantics and visibility */
         | 
| 209 | 
            +
            .status {
         | 
| 210 | 
            +
                padding: 0.35rem 0.6rem;
         | 
| 211 | 
            +
                border-radius: 999px;
         | 
| 212 | 
            +
                font-size: 0.85rem;
         | 
| 213 | 
            +
                background-color: var(--ghost);
         | 
| 214 | 
            +
                color: var(--text);
         | 
| 215 | 
            +
            }
         | 
| 216 | 
            +
            .brand {
         | 
| 217 | 
            +
                display: flex;
         | 
| 218 | 
            +
                align-items: center;
         | 
| 219 | 
            +
                gap: 0.75rem;
         | 
| 220 | 
            +
                font-size: 2.25rem; /* more prominent */
         | 
| 221 | 
            +
                font-weight: 800;
         | 
| 222 | 
            +
            }
         | 
| 223 | 
            +
             | 
| 224 | 
            +
            .brand-link {
         | 
| 225 | 
            +
                display: inline-flex;
         | 
| 226 | 
            +
                align-items: center;
         | 
| 227 | 
            +
                gap: 0.75rem;
         | 
| 228 | 
            +
                text-decoration: none;
         | 
| 229 | 
            +
                color: inherit;
         | 
| 230 | 
            +
            }
         | 
| 231 | 
            +
             | 
| 232 | 
            +
            .brand-text {
         | 
| 233 | 
            +
                letter-spacing: 0.15px; /* slightly tighter for script fonts */
         | 
| 234 | 
            +
                color: var(--text); /* adapt to theme for proper contrast */
         | 
| 235 | 
            +
                text-shadow: none;
         | 
| 236 | 
            +
                font-family: cursive; /* handwriting-style via generic cursive fallback */
         | 
| 237 | 
            +
            }
         | 
| 238 | 
            +
             | 
| 239 | 
            +
            html[data-theme="dark"] .brand-text {
         | 
| 240 | 
            +
                text-shadow: 0 1px 2px rgba(0,0,0,0.25);
         | 
| 241 | 
            +
            }
         | 
| 242 | 
            +
             | 
| 243 | 
            +
            .brand-logo {
         | 
| 244 | 
            +
                width: 44px;
         | 
| 245 | 
            +
                height: 44px;
         | 
| 246 | 
            +
                border-radius: 10px;
         | 
| 247 | 
            +
                box-shadow: 0 6px 16px rgba(0,0,0,0.18);
         | 
| 248 | 
            +
            }
         | 
| 249 | 
            +
             | 
| 250 | 
            +
            @media (max-width: 640px) {
         | 
| 251 | 
            +
              .brand {
         | 
| 252 | 
            +
                font-size: 1.8rem;
         | 
| 253 | 
            +
              }
         | 
| 254 | 
            +
              .brand-logo {
         | 
| 255 | 
            +
                width: 36px;
         | 
| 256 | 
            +
                height: 36px;
         | 
| 257 | 
            +
              }
         | 
| 258 | 
            +
            }
         | 
| 259 | 
            +
            .tagline {
         | 
| 260 | 
            +
                flex: 1 1 100%; /* occupy a full row under the brand for perfect placement */
         | 
| 261 | 
            +
                font-size: 1.05rem;
         | 
| 262 | 
            +
                line-height: 1.5;
         | 
| 263 | 
            +
                letter-spacing: 0.2px;
         | 
| 264 | 
            +
                color: var(--muted); /* theme-aware secondary text for better visibility */
         | 
| 265 | 
            +
                margin-top: 0.25rem;
         | 
| 266 | 
            +
                text-shadow: none;
         | 
| 267 | 
            +
            }
         | 
| 268 | 
            +
             | 
| 269 | 
            +
            html[data-theme="dark"] .tagline {
         | 
| 270 | 
            +
                color: var(--muted);
         | 
| 271 | 
            +
                text-shadow: 0 1px 1.5px rgba(0,0,0,0.25); /* subtle lift on dark background */
         | 
| 272 | 
            +
            }
         | 
| 273 | 
            +
             | 
| 274 | 
            +
            .container {
         | 
| 275 | 
            +
                background-color: var(--card);
         | 
| 276 | 
            +
                padding: 1.5rem;
         | 
| 277 | 
            +
                border-radius: 14px;
         | 
| 278 | 
            +
                box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
         | 
| 279 | 
            +
                width: 100%;
         | 
| 280 | 
            +
                max-width: 1100px;
         | 
| 281 | 
            +
                margin: 1rem auto;
         | 
| 282 | 
            +
            }
         | 
| 283 | 
            +
             | 
| 284 | 
            +
            .grid {
         | 
| 285 | 
            +
                display: grid;
         | 
| 286 | 
            +
                grid-template-columns: 1fr 1fr;
         | 
| 287 | 
            +
                gap: 1.25rem;
         | 
| 288 | 
            +
                align-items: stretch; /* ensure panels have equal height for similar dimensions */
         | 
| 289 | 
            +
                justify-items: stretch;
         | 
| 290 | 
            +
            }
         | 
| 291 | 
            +
             | 
| 292 | 
            +
            .grid > .panel {
         | 
| 293 | 
            +
                height: 100%;
         | 
| 294 | 
            +
            }
         | 
| 295 | 
            +
             | 
| 296 | 
            +
            .panel {
         | 
| 297 | 
            +
                background: #fff;
         | 
| 298 | 
            +
                border: 1px solid #e5e7eb;
         | 
| 299 | 
            +
                border-radius: 12px;
         | 
| 300 | 
            +
                padding: 1rem;
         | 
| 301 | 
            +
                display: flex;              /* ensure inner elements are placed correctly */
         | 
| 302 | 
            +
                flex-direction: column;     /* stack content in order */
         | 
| 303 | 
            +
                gap: 0.75rem;               /* balanced spacing between children */
         | 
| 304 | 
            +
            }
         | 
| 305 | 
            +
             | 
| 306 | 
            +
            .panel > * {                    /* normalize child spacing */
         | 
| 307 | 
            +
                margin: 0;
         | 
| 308 | 
            +
            }
         | 
| 309 | 
            +
             | 
| 310 | 
            +
            /* keep header layout consistent within the panel */
         | 
| 311 | 
            +
            .panel-header {
         | 
| 312 | 
            +
                display: flex;
         | 
| 313 | 
            +
                align-items: center;
         | 
| 314 | 
            +
                justify-content: space-between;
         | 
| 315 | 
            +
                margin-bottom: 0.5rem;
         | 
| 316 | 
            +
            }
         | 
| 317 | 
            +
             | 
| 318 | 
            +
            .panel-actions {
         | 
| 319 | 
            +
                display: flex;
         | 
| 320 | 
            +
                gap: 0.5rem;
         | 
| 321 | 
            +
            }
         | 
| 322 | 
            +
             | 
| 323 | 
            +
            .label {
         | 
| 324 | 
            +
                display: block;
         | 
| 325 | 
            +
                font-size: 0.9rem;
         | 
| 326 | 
            +
                color: var(--muted);
         | 
| 327 | 
            +
                margin-bottom: 0.5rem;
         | 
| 328 | 
            +
            }
         | 
| 329 | 
            +
             | 
| 330 | 
            +
            /* Ensure consistent sizing and prevent overflow misalignment */
         | 
| 331 | 
            +
            *, *::before, *::after {
         | 
| 332 | 
            +
                box-sizing: border-box;
         | 
| 333 | 
            +
            }
         | 
| 334 | 
            +
             | 
| 335 | 
            +
            /* Prevent panels and form controls from exceeding their containers */
         | 
| 336 | 
            +
            .panel, textarea, select {
         | 
| 337 | 
            +
                max-width: 100%;
         | 
| 338 | 
            +
            }
         | 
| 339 | 
            +
             | 
| 340 | 
            +
            /* Ensure textarea aligns properly within its panel */
         | 
| 341 | 
            +
            textarea {
         | 
| 342 | 
            +
                display: block;
         | 
| 343 | 
            +
            }
         | 
| 344 | 
            +
             | 
| 345 | 
            +
            /* Hide any accidental overflow from internal content */
         | 
| 346 | 
            +
            .panel {
         | 
| 347 | 
            +
                overflow: hidden;
         | 
| 348 | 
            +
            }
         | 
| 349 | 
            +
             | 
| 350 | 
            +
            textarea {
         | 
| 351 | 
            +
                width: 100%;
         | 
| 352 | 
            +
                padding: 0.75rem 1rem;
         | 
| 353 | 
            +
                border: 1px solid #e5e7eb;
         | 
| 354 | 
            +
                border-radius: 10px; /* restore rounded corners */
         | 
| 355 | 
            +
                margin-bottom: 1rem;
         | 
| 356 | 
            +
                font-size: 1rem;
         | 
| 357 | 
            +
                resize: vertical;
         | 
| 358 | 
            +
            }
         | 
| 359 | 
            +
             | 
| 360 | 
            +
            .controls {
         | 
| 361 | 
            +
                display: block;
         | 
| 362 | 
            +
            }
         | 
| 363 | 
            +
             | 
| 364 | 
            +
            .controls .control { /* full-width block and natural spacing */
         | 
| 365 | 
            +
                width: 100%;
         | 
| 366 | 
            +
                margin-bottom: 1rem;
         | 
| 367 | 
            +
            }
         | 
| 368 | 
            +
            .control select {
         | 
| 369 | 
            +
                width: 100%;
         | 
| 370 | 
            +
                padding: 0.65rem 0.75rem;
         | 
| 371 | 
            +
                border-radius: 10px;
         | 
| 372 | 
            +
                border: 1px solid #e5e7eb;
         | 
| 373 | 
            +
                font-size: 1rem;
         | 
| 374 | 
            +
                background-color: #fff;
         | 
| 375 | 
            +
            }
         | 
| 376 | 
            +
             | 
| 377 | 
            +
            .toggle {
         | 
| 378 | 
            +
                display: flex;
         | 
| 379 | 
            +
                flex-direction: column;
         | 
| 380 | 
            +
                align-items: flex-start;
         | 
| 381 | 
            +
            }
         | 
| 382 | 
            +
             | 
| 383 | 
            +
            .checkbox-label {
         | 
| 384 | 
            +
                display: inline-flex;
         | 
| 385 | 
            +
                align-items: center;
         | 
| 386 | 
            +
                gap: 0.5rem;
         | 
| 387 | 
            +
                user-select: none;
         | 
| 388 | 
            +
            }
         | 
| 389 | 
            +
             | 
| 390 | 
            +
            .hint {
         | 
| 391 | 
            +
                color: var(--muted);
         | 
| 392 | 
            +
            }
         | 
| 393 | 
            +
             | 
| 394 | 
            +
            .actions {
         | 
| 395 | 
            +
                display: flex;
         | 
| 396 | 
            +
                gap: 0.5rem;
         | 
| 397 | 
            +
            }
         | 
| 398 | 
            +
             | 
| 399 | 
            +
            button.primary {
         | 
| 400 | 
            +
                padding: 0.9rem 1rem;
         | 
| 401 | 
            +
                border-radius: 10px;
         | 
| 402 | 
            +
                border: none;
         | 
| 403 | 
            +
                font-size: 1rem;
         | 
| 404 | 
            +
                background-color: var(--primary);
         | 
| 405 | 
            +
                color: #fff;
         | 
| 406 | 
            +
                cursor: pointer;
         | 
| 407 | 
            +
            }
         | 
| 408 | 
            +
             | 
| 409 | 
            +
            button.primary:hover {
         | 
| 410 | 
            +
                background-color: var(--primary-hover);
         | 
| 411 | 
            +
            }
         | 
| 412 | 
            +
             | 
| 413 | 
            +
            button.primary:disabled {
         | 
| 414 | 
            +
                opacity: 0.7;
         | 
| 415 | 
            +
                cursor: not-allowed;
         | 
| 416 | 
            +
            }
         | 
| 417 | 
            +
             | 
| 418 | 
            +
            button.ghost {
         | 
| 419 | 
            +
                padding: 0.9rem 1rem;
         | 
| 420 | 
            +
                border-radius: 10px;
         | 
| 421 | 
            +
                border: 1px solid #e5e7eb;
         | 
| 422 | 
            +
                font-size: 1rem;
         | 
| 423 | 
            +
                background-color: var(--ghost);
         | 
| 424 | 
            +
                color: var(--text);
         | 
| 425 | 
            +
                cursor: pointer;
         | 
| 426 | 
            +
            }
         | 
| 427 | 
            +
             | 
| 428 | 
            +
            button.ghost:hover {
         | 
| 429 | 
            +
                background-color: var(--ghost-hover);
         | 
| 430 | 
            +
            }
         | 
| 431 | 
            +
             | 
| 432 | 
            +
            .output {
         | 
| 433 | 
            +
                padding: 1rem;
         | 
| 434 | 
            +
                background-color: #f9fafb;
         | 
| 435 | 
            +
                border-radius: 10px;
         | 
| 436 | 
            +
                min-height: 120px;
         | 
| 437 | 
            +
                border: 1px solid #e5e7eb;
         | 
| 438 | 
            +
            }
         | 
| 439 | 
            +
             | 
| 440 | 
            +
            .result-list {
         | 
| 441 | 
            +
                margin: 0;
         | 
| 442 | 
            +
                padding-left: 1.25rem;
         | 
| 443 | 
            +
            }
         | 
| 444 | 
            +
             | 
| 445 | 
            +
            .footer {
         | 
| 446 | 
            +
                width: 100%;
         | 
| 447 | 
            +
                max-width: 1100px;
         | 
| 448 | 
            +
                text-align: right;
         | 
| 449 | 
            +
                color: #cbd5e1;
         | 
| 450 | 
            +
                padding: 0.5rem 1rem 1.5rem 1rem;
         | 
| 451 | 
            +
            }
         | 
| 452 | 
            +
             | 
| 453 | 
            +
            @media (max-width: 900px) {
         | 
| 454 | 
            +
                .grid {
         | 
| 455 | 
            +
                    grid-template-columns: 1fr;
         | 
| 456 | 
            +
                }
         | 
| 457 | 
            +
                .actions {
         | 
| 458 | 
            +
                    flex-wrap: wrap;
         | 
| 459 | 
            +
                }
         | 
| 460 | 
            +
            }
         | 
| 461 | 
            +
             | 
| 462 | 
            +
            /* Table styles for batch alignment */
         | 
| 463 | 
            +
            .result-table {
         | 
| 464 | 
            +
                width: 100%;
         | 
| 465 | 
            +
                border-collapse: collapse;
         | 
| 466 | 
            +
                background: #fff;
         | 
| 467 | 
            +
            }
         | 
| 468 | 
            +
            .result-table th, .result-table td {
         | 
| 469 | 
            +
                border: 1px solid #e5e7eb;
         | 
| 470 | 
            +
                padding: 0.5rem 0.6rem;
         | 
| 471 | 
            +
                vertical-align: top;
         | 
| 472 | 
            +
            }
         | 
| 473 | 
            +
            .result-table th {
         | 
| 474 | 
            +
                background: #f3f4f6;
         | 
| 475 | 
            +
                text-align: left;
         | 
| 476 | 
            +
            }
         | 
| 477 | 
            +
            .result-table tr:nth-child(even) td {
         | 
| 478 | 
            +
                background: #fafafa;
         | 
| 479 | 
            +
            }
         | 
| 480 | 
            +
             | 
| 481 | 
            +
            /* Smooth theme transitions */
         | 
| 482 | 
            +
            html, body, .container, .panel, textarea, select, button, .output {
         | 
| 483 | 
            +
                transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
         | 
| 484 | 
            +
            }
         | 
| 485 | 
            +
             | 
| 486 | 
            +
            /* Elevated panel hover for subtle depth */
         | 
| 487 | 
            +
            .panel:hover {
         | 
| 488 | 
            +
                box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
         | 
| 489 | 
            +
            }
         | 
| 490 | 
            +
             | 
| 491 | 
            +
            /* Improved focus visibility and accessibility */
         | 
| 492 | 
            +
            textarea:focus-visible, select:focus-visible, button:focus-visible {
         | 
| 493 | 
            +
                outline: 3px solid rgba(37, 99, 235, 0.35);
         | 
| 494 | 
            +
                outline-offset: 2px;
         | 
| 495 | 
            +
                border-color: var(--primary);
         | 
| 496 | 
            +
                box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
         | 
| 497 | 
            +
            }
         | 
| 498 | 
            +
             | 
| 499 | 
            +
            /* Harmonize output surface with theme variables */
         | 
| 500 | 
            +
            .output {
         | 
| 501 | 
            +
                background-color: var(--ghost);
         | 
| 502 | 
            +
                color: var(--text);
         | 
| 503 | 
            +
            }
         | 
| 504 | 
            +
             | 
| 505 | 
            +
            /* Button hover and active subtle animations */
         | 
| 506 | 
            +
            button.primary:hover, button.ghost:hover {
         | 
| 507 | 
            +
                transform: translateY(-1px);
         | 
| 508 | 
            +
            }
         | 
| 509 | 
            +
            button.primary:active, button.ghost:active {
         | 
| 510 | 
            +
                transform: translateY(0);
         | 
| 511 | 
            +
            }
         | 
| 512 | 
            +
             | 
    	
        interactive_translate.py
    ADDED
    
    | @@ -0,0 +1,74 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            An interactive script to translate text to English using a fine-tuned NLLB model.
         | 
| 3 | 
            +
            """
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            import torch
         | 
| 6 | 
            +
            from transformers import M2M100ForConditionalGeneration, NllbTokenizer
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            # --- 1. Configuration ---
         | 
| 9 | 
            +
            DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
         | 
| 10 | 
            +
            SUPPORTED_LANGUAGES = {
         | 
| 11 | 
            +
                "nepali": "nep_Npan",
         | 
| 12 | 
            +
                "sinhala": "sin_Sinh",
         | 
| 13 | 
            +
            }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            # --- 2. Load Model and Tokenizer ---
         | 
| 16 | 
            +
            def load_model_and_tokenizer(model_path):
         | 
| 17 | 
            +
                """Loads the model and tokenizer from the given path."""
         | 
| 18 | 
            +
                print(f"Loading model on {DEVICE.upper()}...")
         | 
| 19 | 
            +
                try:
         | 
| 20 | 
            +
                    model = M2M100ForConditionalGeneration.from_pretrained(model_path).to(DEVICE)
         | 
| 21 | 
            +
                    tokenizer = NllbTokenizer.from_pretrained(model_path)
         | 
| 22 | 
            +
                    print("Model and tokenizer loaded successfully!")
         | 
| 23 | 
            +
                    return model, tokenizer
         | 
| 24 | 
            +
                except Exception as e:
         | 
| 25 | 
            +
                    print(f"Error loading model: {e}")
         | 
| 26 | 
            +
                    return None, None
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            # --- 3. Translation Function ---
         | 
| 29 | 
            +
            def translate_text(model, tokenizer, text: str, src_lang: str) -> str:
         | 
| 30 | 
            +
                """
         | 
| 31 | 
            +
                Translates a single string of text to English.
         | 
| 32 | 
            +
                """
         | 
| 33 | 
            +
                if src_lang not in SUPPORTED_LANGUAGES:
         | 
| 34 | 
            +
                    return f"Language '{src_lang}' not supported."
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                tokenizer.src_lang = SUPPORTED_LANGUAGES[src_lang]
         | 
| 37 | 
            +
                inputs = tokenizer(text, return_tensors="pt").to(DEVICE)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                generated_tokens = model.generate(
         | 
| 40 | 
            +
                    **inputs,
         | 
| 41 | 
            +
                    forced_bos_token_id=tokenizer.convert_tokens_to_ids("eng_Latn"),
         | 
| 42 | 
            +
                    max_length=128,
         | 
| 43 | 
            +
                )
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                return tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            # --- 4. Interactive Translation Loop ---
         | 
| 48 | 
            +
            if __name__ == "__main__":
         | 
| 49 | 
            +
                # Select model path based on language
         | 
| 50 | 
            +
                lang_choice = input(f"Choose a language ({list(SUPPORTED_LANGUAGES.keys())}): ").lower()
         | 
| 51 | 
            +
                if lang_choice not in SUPPORTED_LANGUAGES:
         | 
| 52 | 
            +
                    print("Invalid language choice.")
         | 
| 53 | 
            +
                    exit()
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                # For now, we assume a single model path. This can be extended.
         | 
| 56 | 
            +
                model_path = "models/nllb-finetuned-nepali-en"
         | 
| 57 | 
            +
                model, tokenizer = load_model_and_tokenizer(model_path)
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                if model and tokenizer:
         | 
| 60 | 
            +
                    print(f"\n--- Interactive Translation ({lang_choice.capitalize()}) ---")
         | 
| 61 | 
            +
                    print(f"Enter a {lang_choice} sentence to translate to English.")
         | 
| 62 | 
            +
                    print("Type 'exit' to quit.\n")
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    while True:
         | 
| 65 | 
            +
                        text_to_translate = input(f"{lang_choice.capitalize()}: ")
         | 
| 66 | 
            +
                        if text_to_translate.lower() == "exit":
         | 
| 67 | 
            +
                            break
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                        if not text_to_translate.strip():
         | 
| 70 | 
            +
                            print("Please enter some text to translate.")
         | 
| 71 | 
            +
                            continue
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                        english_translation = translate_text(model, tokenizer, text_to_translate, lang_choice)
         | 
| 74 | 
            +
                        print(f"English: {english_translation}\n")
         | 
    	
        requirements.txt
    ADDED
    
    | @@ -0,0 +1,92 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            accelerate==1.10.1
         | 
| 2 | 
            +
            aiohappyeyeballs==2.6.1
         | 
| 3 | 
            +
            aiohttp==3.12.15
         | 
| 4 | 
            +
            aiosignal==1.4.0
         | 
| 5 | 
            +
            annotated-types==0.7.0
         | 
| 6 | 
            +
            anyio==4.11.0
         | 
| 7 | 
            +
            attrs==25.3.0
         | 
| 8 | 
            +
            beautifulsoup4==4.14.2
         | 
| 9 | 
            +
            certifi==2025.10.5
         | 
| 10 | 
            +
            charset-normalizer==3.4.3
         | 
| 11 | 
            +
            click==8.3.0
         | 
| 12 | 
            +
            colorama==0.4.6
         | 
| 13 | 
            +
            datasets==4.1.1
         | 
| 14 | 
            +
            dill==0.4.0
         | 
| 15 | 
            +
            dnspython==2.8.0
         | 
| 16 | 
            +
            email-validator==2.3.0
         | 
| 17 | 
            +
            evaluate==0.4.6
         | 
| 18 | 
            +
            fastapi==0.118.0
         | 
| 19 | 
            +
            fastapi-cli==0.0.13
         | 
| 20 | 
            +
            fastapi-cloud-cli==0.3.0
         | 
| 21 | 
            +
            filelock==3.19.1
         | 
| 22 | 
            +
            frozenlist==1.7.0
         | 
| 23 | 
            +
            fsspec==2025.9.0
         | 
| 24 | 
            +
            h11==0.16.0
         | 
| 25 | 
            +
            httpcore==1.0.9
         | 
| 26 | 
            +
            httptools==0.6.4
         | 
| 27 | 
            +
            httpx==0.28.1
         | 
| 28 | 
            +
            huggingface-hub==0.35.3
         | 
| 29 | 
            +
            idna==3.10
         | 
| 30 | 
            +
            itsdangerous==2.2.0
         | 
| 31 | 
            +
            Jinja2==3.1.6
         | 
| 32 | 
            +
            langdetect==1.0.9
         | 
| 33 | 
            +
            lxml==6.0.2
         | 
| 34 | 
            +
            markdown-it-py==4.0.0
         | 
| 35 | 
            +
            MarkupSafe==3.0.3
         | 
| 36 | 
            +
            mdurl==0.1.2
         | 
| 37 | 
            +
            mpmath==1.3.0
         | 
| 38 | 
            +
            multidict==6.6.4
         | 
| 39 | 
            +
            multiprocess==0.70.16
         | 
| 40 | 
            +
            networkx==3.5
         | 
| 41 | 
            +
            numpy==2.3.3
         | 
| 42 | 
            +
            orjson==3.11.3
         | 
| 43 | 
            +
            packaging==25.0
         | 
| 44 | 
            +
            pandas==2.3.3
         | 
| 45 | 
            +
            portalocker==3.2.0
         | 
| 46 | 
            +
            propcache==0.4.0
         | 
| 47 | 
            +
            protobuf==6.32.1
         | 
| 48 | 
            +
            psutil==7.1.0
         | 
| 49 | 
            +
            pyarrow==21.0.0
         | 
| 50 | 
            +
            pydantic==2.11.10
         | 
| 51 | 
            +
            pydantic-extra-types==2.10.5
         | 
| 52 | 
            +
            pydantic-settings==2.11.0
         | 
| 53 | 
            +
            pydantic_core==2.33.2
         | 
| 54 | 
            +
            Pygments==2.19.2
         | 
| 55 | 
            +
            PyMuPDF==1.26.4
         | 
| 56 | 
            +
            python-dateutil==2.9.0.post0
         | 
| 57 | 
            +
            python-dotenv==1.1.1
         | 
| 58 | 
            +
            python-multipart==0.0.20
         | 
| 59 | 
            +
            pytz==2025.2
         | 
| 60 | 
            +
            PyYAML==6.0.3
         | 
| 61 | 
            +
            regex==2025.9.18
         | 
| 62 | 
            +
            requests==2.32.5
         | 
| 63 | 
            +
            rich==14.1.0
         | 
| 64 | 
            +
            rich-toolkit==0.15.1
         | 
| 65 | 
            +
            rignore==0.7.0
         | 
| 66 | 
            +
            sacrebleu==2.5.1
         | 
| 67 | 
            +
            safetensors==0.6.2
         | 
| 68 | 
            +
            sentencepiece==0.2.1
         | 
| 69 | 
            +
            sentry-sdk==2.39.0
         | 
| 70 | 
            +
            setuptools==80.9.0
         | 
| 71 | 
            +
            shellingham==1.5.4
         | 
| 72 | 
            +
            six==1.17.0
         | 
| 73 | 
            +
            sniffio==1.3.1
         | 
| 74 | 
            +
            soupsieve==2.8
         | 
| 75 | 
            +
            starlette==0.48.0
         | 
| 76 | 
            +
            sympy==1.14.0
         | 
| 77 | 
            +
            tabulate==0.9.0
         | 
| 78 | 
            +
            tokenizers==0.22.1
         | 
| 79 | 
            +
            torch==2.8.0
         | 
| 80 | 
            +
            tqdm==4.67.1
         | 
| 81 | 
            +
            transformers==4.57.0
         | 
| 82 | 
            +
            typer==0.19.2
         | 
| 83 | 
            +
            typing-inspection==0.4.2
         | 
| 84 | 
            +
            typing_extensions==4.15.0
         | 
| 85 | 
            +
            tzdata==2025.2
         | 
| 86 | 
            +
            ujson==5.11.0
         | 
| 87 | 
            +
            urllib3==2.5.0
         | 
| 88 | 
            +
            uvicorn==0.37.0
         | 
| 89 | 
            +
            watchfiles==1.1.0
         | 
| 90 | 
            +
            websockets==15.0.1
         | 
| 91 | 
            +
            xxhash==3.6.0
         | 
| 92 | 
            +
            yarl==1.20.1
         | 
    	
        scripts/clean_text_data.py
    ADDED
    
    | @@ -0,0 +1,62 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # scripts/clean_text_data.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import os
         | 
| 4 | 
            +
            import datetime
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            def clean_data():
         | 
| 7 | 
            +
                """
         | 
| 8 | 
            +
                Reads a raw text file, cleans it, and saves it to the processed data folder.
         | 
| 9 | 
            +
                """
         | 
| 10 | 
            +
                # --- Configuration ---
         | 
| 11 | 
            +
                # Construct the filename based on today's date, matching the scraper's output
         | 
| 12 | 
            +
                current_date = datetime.datetime.now().strftime("%Y-%m-%d")
         | 
| 13 | 
            +
                raw_filename = f"bbc_nepali_articles_{current_date}.txt"
         | 
| 14 | 
            +
                cleaned_filename = f"bbc_nepali_articles_{current_date}_cleaned.txt"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # Define the paths using our project structure
         | 
| 17 | 
            +
                raw_file_path = os.path.join("data", "raw", raw_filename)
         | 
| 18 | 
            +
                processed_file_path = os.path.join("data", "processed", cleaned_filename)
         | 
| 19 | 
            +
                
         | 
| 20 | 
            +
                # Simple rule: we'll discard any line that has fewer than this many words.
         | 
| 21 | 
            +
                MIN_WORDS_PER_LINE = 5 
         | 
| 22 | 
            +
                # --- End Configuration ---
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                print("--- Starting data cleaning process ---")
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                # Check if the raw file exists before we start
         | 
| 27 | 
            +
                if not os.path.exists(raw_file_path):
         | 
| 28 | 
            +
                    print(f"Error: Raw data file not found at '{raw_file_path}'")
         | 
| 29 | 
            +
                    print("Please run the scraping script first.")
         | 
| 30 | 
            +
                    return
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                print(f"Reading raw data from: {raw_file_path}")
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                # Read all lines from the raw file
         | 
| 35 | 
            +
                with open(raw_file_path, "r", encoding="utf-8") as f:
         | 
| 36 | 
            +
                    lines = f.readlines()
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                cleaned_lines = []
         | 
| 39 | 
            +
                for line in lines:
         | 
| 40 | 
            +
                    # 1. Strip leading/trailing whitespace from the line
         | 
| 41 | 
            +
                    text = line.strip()
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    # 2. Apply our cleaning rules
         | 
| 44 | 
            +
                    # We keep the line only if it's not empty AND has enough words
         | 
| 45 | 
            +
                    if text and len(text.split()) >= MIN_WORDS_PER_LINE:
         | 
| 46 | 
            +
                        cleaned_lines.append(text)
         | 
| 47 | 
            +
                
         | 
| 48 | 
            +
                # 3. Save the cleaned lines to the new file
         | 
| 49 | 
            +
                print(f"Saving cleaned data to: {processed_file_path}")
         | 
| 50 | 
            +
                os.makedirs(os.path.dirname(processed_file_path), exist_ok=True)
         | 
| 51 | 
            +
                with open(processed_file_path, "w", encoding="utf-8") as f:
         | 
| 52 | 
            +
                    f.write("\n".join(cleaned_lines))
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                # Print a summary report
         | 
| 55 | 
            +
                print("\n--- Cleaning Summary ---")
         | 
| 56 | 
            +
                print(f"Total lines read: {len(lines)}")
         | 
| 57 | 
            +
                print(f"Lines after cleaning: {len(cleaned_lines)}")
         | 
| 58 | 
            +
                print(f"Lines discarded: {len(lines) - len(cleaned_lines)}")
         | 
| 59 | 
            +
                print("------------------------")
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            if __name__ == "__main__":
         | 
| 62 | 
            +
                clean_data()
         | 
    	
        scripts/create_sinhala_test_set.py
    ADDED
    
    | @@ -0,0 +1,37 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # scripts/create_sinhala_test_set.py
         | 
| 2 | 
            +
            import os
         | 
| 3 | 
            +
            from datasets import load_dataset
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # --- Configuration ---
         | 
| 6 | 
            +
            DATA_DIR = "data/processed"
         | 
| 7 | 
            +
            TEST_DIR = "data/test_sets"
         | 
| 8 | 
            +
            DATASET_NAME = "Programmer-RD-AI/sinhala-english-singlish-translation"
         | 
| 9 | 
            +
            NUM_TEST_LINES = 500
         | 
| 10 | 
            +
            # ---
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            print("--- Creating a held-back test set for Sinhalese ---")
         | 
| 13 | 
            +
            os.makedirs(TEST_DIR, exist_ok=True)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            # Load the dataset from Hugging Face
         | 
| 16 | 
            +
            dataset = load_dataset(DATASET_NAME, split='train')
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            # Split the dataset
         | 
| 19 | 
            +
            train_dataset = dataset.select(range(len(dataset) - NUM_TEST_LINES))
         | 
| 20 | 
            +
            test_dataset = dataset.select(range(len(dataset) - NUM_TEST_LINES, len(dataset)))
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            # Write the new training files
         | 
| 23 | 
            +
            with open(os.path.join(DATA_DIR, "sinhala.si"), "w", encoding="utf-8") as f_source, \
         | 
| 24 | 
            +
                 open(os.path.join(DATA_DIR, "sinhala.en"), "w", encoding="utf-8") as f_target:
         | 
| 25 | 
            +
                for example in train_dataset:
         | 
| 26 | 
            +
                    f_source.write(example['Sinhala'] + "\n")
         | 
| 27 | 
            +
                    f_target.write(example['English'] + "\n")
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            # Write the new test files
         | 
| 30 | 
            +
            with open(os.path.join(TEST_DIR, "test.si"), "w", encoding="utf-8") as f_source, \
         | 
| 31 | 
            +
                 open(os.path.join(TEST_DIR, "test.en"), "w", encoding="utf-8") as f_target:
         | 
| 32 | 
            +
                for example in test_dataset:
         | 
| 33 | 
            +
                    f_source.write(example['Sinhala'] + "\n")
         | 
| 34 | 
            +
                    f_target.write(example['English'] + "\n")
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            print(f"Successfully created a test set with {NUM_TEST_LINES} lines for Sinhalese.")
         | 
| 37 | 
            +
            print(f"The original training files in '{DATA_DIR}' have been updated.")
         | 
    	
        scripts/create_test_set.py
    ADDED
    
    | @@ -0,0 +1,44 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # scripts/create_test_set.py
         | 
| 2 | 
            +
            import os
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            # --- Configuration ---
         | 
| 5 | 
            +
            DATA_DIR = "data/processed"
         | 
| 6 | 
            +
            TEST_DIR = "data/test_sets"
         | 
| 7 | 
            +
            SOURCE_FILE = os.path.join(DATA_DIR, "nepali.ne")
         | 
| 8 | 
            +
            TARGET_FILE = os.path.join(DATA_DIR, "nepali.en")
         | 
| 9 | 
            +
            NUM_TEST_LINES = 500
         | 
| 10 | 
            +
            # ---
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            print("--- Creating a held-back test set for Nepali ---")
         | 
| 13 | 
            +
            os.makedirs(TEST_DIR, exist_ok=True)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            # Read all lines from the original files
         | 
| 16 | 
            +
            with open(SOURCE_FILE, "r", encoding="utf-8") as f:
         | 
| 17 | 
            +
                source_lines = f.readlines()
         | 
| 18 | 
            +
            with open(TARGET_FILE, "r", encoding="utf-8") as f:
         | 
| 19 | 
            +
                target_lines = f.readlines()
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            # Ensure the files have the same number of lines
         | 
| 22 | 
            +
            assert len(source_lines) == len(target_lines), "Source and target files have different lengths!"
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            # Split the data
         | 
| 25 | 
            +
            train_source_lines = source_lines[:-NUM_TEST_LINES]
         | 
| 26 | 
            +
            test_source_lines = source_lines[-NUM_TEST_LINES:]
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            train_target_lines = target_lines[:-NUM_TEST_LINES]
         | 
| 29 | 
            +
            test_target_lines = target_lines[-NUM_TEST_LINES:]
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            # Write the new, smaller training files (overwriting the old ones)
         | 
| 32 | 
            +
            with open(SOURCE_FILE, "w", encoding="utf-8") as f:
         | 
| 33 | 
            +
                f.writelines(train_source_lines)
         | 
| 34 | 
            +
            with open(TARGET_FILE, "w", encoding="utf-8") as f:
         | 
| 35 | 
            +
                f.writelines(train_target_lines)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            # Write the new test files
         | 
| 38 | 
            +
            with open(os.path.join(TEST_DIR, "test.ne"), "w", encoding="utf-8") as f:
         | 
| 39 | 
            +
                f.writelines(test_source_lines)
         | 
| 40 | 
            +
            with open(os.path.join(TEST_DIR, "test.en"), "w", encoding="utf-8") as f:
         | 
| 41 | 
            +
                f.writelines(test_target_lines)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            print(f"Successfully created a test set with {NUM_TEST_LINES} lines for Nepali.")
         | 
| 44 | 
            +
            print(f"The original training files in '{DATA_DIR}' have been updated.")
         | 
    	
        scripts/download_model.py
    ADDED
    
    | @@ -0,0 +1,36 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
             | 
| 2 | 
            +
            import os
         | 
| 3 | 
            +
            from huggingface_hub import snapshot_download
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            def download_model():
         | 
| 6 | 
            +
                """
         | 
| 7 | 
            +
                Downloads the NLLB model from Hugging Face Hub.
         | 
| 8 | 
            +
                """
         | 
| 9 | 
            +
                # --- Configuration ---
         | 
| 10 | 
            +
                # Note: The original script referred to 'nllb-finetuned-nepali-en', which is not a public model.
         | 
| 11 | 
            +
                # We are downloading the base model 'facebook/nllb-200-distilled-600M' instead.
         | 
| 12 | 
            +
                # You may need to fine-tune this model on your own dataset to get the desired performance.
         | 
| 13 | 
            +
                model_name = "facebook/nllb-200-distilled-600M"
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                # --- Path setup ---
         | 
| 16 | 
            +
                # Construct the path to save the model, relative to this script's location.
         | 
| 17 | 
            +
                script_dir = os.path.dirname(os.path.abspath(__file__))
         | 
| 18 | 
            +
                # We want to save it in saksi_translation/models/nllb-finetuned-nepali-en
         | 
| 19 | 
            +
                target_dir = os.path.abspath(os.path.join(script_dir, '..', 'models', 'nllb-finetuned-nepali-en'))
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                print(f"Downloading model: {model_name}")
         | 
| 22 | 
            +
                print(f"Saving to: {target_dir}")
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                # --- Download ---
         | 
| 25 | 
            +
                try:
         | 
| 26 | 
            +
                    if not os.path.exists(target_dir):
         | 
| 27 | 
            +
                        os.makedirs(target_dir)
         | 
| 28 | 
            +
                        
         | 
| 29 | 
            +
                    snapshot_download(repo_id=model_name, local_dir=target_dir, local_dir_use_symlinks=False)
         | 
| 30 | 
            +
                    print("Model downloaded successfully.")
         | 
| 31 | 
            +
                    
         | 
| 32 | 
            +
                except Exception as e:
         | 
| 33 | 
            +
                    print(f"An error occurred during download: {e}")
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            if __name__ == "__main__":
         | 
| 36 | 
            +
                download_model()
         | 
    	
        scripts/fetch_parallel_data.py
    ADDED
    
    | @@ -0,0 +1,81 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # scripts/fetch_parallel_data.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            from datasets import load_dataset
         | 
| 4 | 
            +
            import os
         | 
| 5 | 
            +
             | 
| 6 | 
            +
             | 
| 7 | 
            +
            def fetch_and_save_parallel_data(lang_pair, dataset_name, output_name):
         | 
| 8 | 
            +
                """
         | 
| 9 | 
            +
                Downloads a parallel dataset and saves it into two
         | 
| 10 | 
            +
                separate text files (one for each language).
         | 
| 11 | 
            +
                
         | 
| 12 | 
            +
                Args:
         | 
| 13 | 
            +
                    lang_pair (str): Language pair, e.g., "en-ne" for English-Nepali.
         | 
| 14 | 
            +
                    dataset_name (str): The name of the dataset on Hugging Face Hub.
         | 
| 15 | 
            +
                    output_name (str): The name to use for the output files.
         | 
| 16 | 
            +
                """
         | 
| 17 | 
            +
                source_lang, target_lang = lang_pair.split("-")
         | 
| 18 | 
            +
                output_dir = "data/processed"
         | 
| 19 | 
            +
                os.makedirs(output_dir, exist_ok=True)
         | 
| 20 | 
            +
                
         | 
| 21 | 
            +
                source_filepath = os.path.join(output_dir, f"{output_name}.{source_lang}")
         | 
| 22 | 
            +
                target_filepath = os.path.join(output_dir, f"{output_name}.{target_lang}")
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                print(f"--- Starting download for {lang_pair} from {dataset_name} ---")
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                try:
         | 
| 27 | 
            +
                    # Load the dataset from Hugging Face
         | 
| 28 | 
            +
                    if dataset_name == "Programmer-RD-AI/sinhala-english-singlish-translation":
         | 
| 29 | 
            +
                        dataset = load_dataset(dataset_name, split='train')
         | 
| 30 | 
            +
                    else:
         | 
| 31 | 
            +
                        dataset = load_dataset(dataset_name, lang_pair, split='train')
         | 
| 32 | 
            +
                    print(f"Dataset loaded successfully. Total pairs: {len(dataset)}")
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    print(f"Processing and saving files...")
         | 
| 35 | 
            +
                    with open(source_filepath, "w", encoding="utf-8") as f_source, \
         | 
| 36 | 
            +
                         open(target_filepath, "w", encoding="utf-8") as f_target:
         | 
| 37 | 
            +
                        
         | 
| 38 | 
            +
                        for example in dataset:
         | 
| 39 | 
            +
                            if dataset_name == "Programmer-RD-AI/sinhala-english-singlish-translation":
         | 
| 40 | 
            +
                                source_sentence = example['Sinhala']
         | 
| 41 | 
            +
                                target_sentence = example['English']
         | 
| 42 | 
            +
                            else:
         | 
| 43 | 
            +
                                source_sentence = example['translation'][source_lang]
         | 
| 44 | 
            +
                                target_sentence = example['translation'][target_lang]
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                            if source_sentence and target_sentence:
         | 
| 47 | 
            +
                                f_source.write(source_sentence.strip() + "\n")
         | 
| 48 | 
            +
                                f_target.write(target_sentence.strip() + "\n")
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    print(f"Successfully saved data for {lang_pair}")
         | 
| 51 | 
            +
                except Exception as e:
         | 
| 52 | 
            +
                    print(f"An error occurred for {lang_pair}: {e}")
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            if __name__ == "__main__":
         | 
| 55 | 
            +
                # --- Fetch Nepali Data ---
         | 
| 56 | 
            +
                print("Fetching Nepali data...")
         | 
| 57 | 
            +
                fetch_and_save_parallel_data(lang_pair="en-ne", dataset_name="Helsinki-NLP/opus-100", output_name="nepali")
         | 
| 58 | 
            +
                
         | 
| 59 | 
            +
                # --- Fetch Sinhalese Data ---
         | 
| 60 | 
            +
                print("\nFetching Sinhalese data...")
         | 
| 61 | 
            +
                fetch_and_save_parallel_data(lang_pair="si-en", dataset_name="Programmer-RD-AI/sinhala-english-singlish-translation", output_name="sinhala")
         | 
| 62 | 
            +
                
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                # --- Fetch Sinhalese Idioms Data ---
         | 
| 65 | 
            +
                print("\nFetching Sinhalese idioms data...")
         | 
| 66 | 
            +
                output_dir = "data/processed"
         | 
| 67 | 
            +
                try:
         | 
| 68 | 
            +
                    idioms_dataset = load_dataset("Venuraa/English-Sinhala-Idioms-Parallel-Translations", split='train')
         | 
| 69 | 
            +
                    print(f"Idioms dataset loaded successfully. Total pairs: {len(idioms_dataset)}")
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    with open(os.path.join(output_dir, "sinhala.si"), "a", encoding="utf-8") as f_source, \
         | 
| 72 | 
            +
                         open(os.path.join(output_dir, "sinhala.en"), "a", encoding="utf-8") as f_target:
         | 
| 73 | 
            +
                        for example in idioms_dataset:
         | 
| 74 | 
            +
                            parts = example['text'].split('\n')
         | 
| 75 | 
            +
                            if len(parts) == 2:
         | 
| 76 | 
            +
                                f_target.write(parts[0] + "\n")
         | 
| 77 | 
            +
                                f_source.write(parts[1] + "\n")
         | 
| 78 | 
            +
                    print("Successfully appended idioms data.")
         | 
| 79 | 
            +
                except Exception as e:
         | 
| 80 | 
            +
                    print(f"An error occurred while fetching idioms data: {e}")
         | 
| 81 | 
            +
                print("\nAll data fetching complete.")
         | 
    	
        scripts/scrape_bbc_nepali.py
    ADDED
    
    | @@ -0,0 +1,80 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # scripts/scrape_bbc_nepali.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import requests
         | 
| 4 | 
            +
            from bs4 import BeautifulSoup
         | 
| 5 | 
            +
            import datetime
         | 
| 6 | 
            +
            import os
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            def scrape_bbc_nepali():
         | 
| 9 | 
            +
                """
         | 
| 10 | 
            +
                Scrapes news articles from the BBC Nepali homepage and saves them to a file.
         | 
| 11 | 
            +
                """
         | 
| 12 | 
            +
                # The base URL for BBC Nepali news
         | 
| 13 | 
            +
                BASE_URL = "https://www.bbc.com"
         | 
| 14 | 
            +
                START_URL = f"{BASE_URL}/nepali"
         | 
| 15 | 
            +
                
         | 
| 16 | 
            +
                # Get the current date to create a unique filename
         | 
| 17 | 
            +
                current_date = datetime.datetime.now().strftime("%Y-%m-%d")
         | 
| 18 | 
            +
                output_filename = f"bbc_nepali_articles_{current_date}.txt"
         | 
| 19 | 
            +
                
         | 
| 20 | 
            +
                # Ensure the output directory exists
         | 
| 21 | 
            +
                output_dir = "data/raw"
         | 
| 22 | 
            +
                os.makedirs(output_dir, exist_ok=True)
         | 
| 23 | 
            +
                output_path = os.path.join(output_dir, output_filename)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                print(f"Starting scrape of {START_URL}")
         | 
| 26 | 
            +
                print(f"Saving data to: {output_path}")
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                try:
         | 
| 29 | 
            +
                    # 1. Fetch the main homepage
         | 
| 30 | 
            +
                    main_page = requests.get(START_URL)
         | 
| 31 | 
            +
                    main_page.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx) 
         | 
| 32 | 
            +
                    
         | 
| 33 | 
            +
                    main_soup = BeautifulSoup(main_page.content, "html.parser")
         | 
| 34 | 
            +
                    
         | 
| 35 | 
            +
                    # 2. Find all links that likely lead to articles
         | 
| 36 | 
            +
                    # This is a bit of trial and error; we look for <a> tags with hrefs
         | 
| 37 | 
            +
                    # that match the pattern of BBC articles.
         | 
| 38 | 
            +
                    article_links = set() # Use a set to avoid duplicate links
         | 
| 39 | 
            +
                    for a_tag in main_soup.find_all("a", href=True):
         | 
| 40 | 
            +
                        href = a_tag['href']
         | 
| 41 | 
            +
                        # We filter for links that look like internal news articles
         | 
| 42 | 
            +
                        if href.startswith("/nepali/articles/"):
         | 
| 43 | 
            +
                            full_url = f"{BASE_URL}{href}"
         | 
| 44 | 
            +
                            article_links.add(full_url)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    print(f"Found {len(article_links)} unique article links.")
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    # 3. Visit each article and extract its text
         | 
| 49 | 
            +
                    all_article_text = []
         | 
| 50 | 
            +
                    for i, link in enumerate(article_links):
         | 
| 51 | 
            +
                        try:
         | 
| 52 | 
            +
                            print(f"  Scraping ({i+1}/{len(article_links)}): {link}")
         | 
| 53 | 
            +
                            article_page = requests.get(link)
         | 
| 54 | 
            +
                            article_page.raise_for_status()
         | 
| 55 | 
            +
                            
         | 
| 56 | 
            +
                            article_soup = BeautifulSoup(article_page.content, "html.parser")
         | 
| 57 | 
            +
                            
         | 
| 58 | 
            +
                            # Find all paragraph tags (<p>) which usually contain the article text
         | 
| 59 | 
            +
                            paragraphs = article_soup.find_all("p")
         | 
| 60 | 
            +
                            
         | 
| 61 | 
            +
                            article_text = "\n".join([p.get_text() for p in paragraphs])
         | 
| 62 | 
            +
                            all_article_text.append(article_text)
         | 
| 63 | 
            +
                            
         | 
| 64 | 
            +
                        except requests.exceptions.RequestException as e:
         | 
| 65 | 
            +
                            print(f"    Could not fetch article {link}: {e}")
         | 
| 66 | 
            +
                        except Exception as e:
         | 
| 67 | 
            +
                            print(f"    An error occurred while processing {link}: {e}")
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    # 4. Save the collected text to a file
         | 
| 70 | 
            +
                    with open(output_path, "w", encoding="utf-8") as f:
         | 
| 71 | 
            +
                        # Separate articles with a clear delimiter
         | 
| 72 | 
            +
                        f.write("\n\n--- NEW ARTICLE ---\n\n".join(all_article_text))
         | 
| 73 | 
            +
                        
         | 
| 74 | 
            +
                    print(f"\nScraping complete. All text saved to {output_path}")
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                except requests.exceptions.RequestException as e:
         | 
| 77 | 
            +
                    print(f"Failed to fetch the main page {START_URL}: {e}")
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            if __name__ == "__main__":
         | 
| 80 | 
            +
                scrape_bbc_nepali()
         | 
    	
        src/__init__.py
    ADDED
    
    | 
            File without changes
         | 
    	
        src/__pycache__/evaluate.cpython-313.pyc
    ADDED
    
    | Binary file (3.88 kB). View file | 
|  | 
    	
        src/evaluate_sinhala.py
    ADDED
    
    | @@ -0,0 +1,58 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # src/evaluate_sinhala.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import torch
         | 
| 4 | 
            +
            import evaluate # The new, preferred Hugging Face library for metrics
         | 
| 5 | 
            +
            from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
         | 
| 6 | 
            +
            from tqdm import tqdm # A library to create smart progress bars
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            def evaluate_model():
         | 
| 9 | 
            +
                """
         | 
| 10 | 
            +
                Loads a fine-tuned model and evaluates its performance on the test set using the BLEU score.
         | 
| 11 | 
            +
                """
         | 
| 12 | 
            +
                # --- 1. Configuration ---
         | 
| 13 | 
            +
                MODEL_PATH = "thilina/mt5-sinhalese-english"
         | 
| 14 | 
            +
                TEST_DIR = "data/test_sets"
         | 
| 15 | 
            +
                SOURCE_LANG_FILE = f"{TEST_DIR}/test.si"
         | 
| 16 | 
            +
                TARGET_LANG_FILE = f"{TEST_DIR}/test.en"
         | 
| 17 | 
            +
                DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
         | 
| 18 | 
            +
                
         | 
| 19 | 
            +
                # --- 2. Load Model, Tokenizer, and Metric ---
         | 
| 20 | 
            +
                print("Loading model, tokenizer, and evaluation metric...")
         | 
| 21 | 
            +
                tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
         | 
| 22 | 
            +
                model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_PATH).to(DEVICE)
         | 
| 23 | 
            +
                bleu_metric = evaluate.load("sacrebleu")
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                # --- 3. Load Test Data ---
         | 
| 26 | 
            +
                with open(SOURCE_LANG_FILE, "r", encoding="utf-8") as f:
         | 
| 27 | 
            +
                    source_sentences = [line.strip() for line in f.readlines()]
         | 
| 28 | 
            +
                with open(TARGET_LANG_FILE, "r", encoding="utf-8") as f:
         | 
| 29 | 
            +
                    # The BLEU metric expects references to be a list of lists
         | 
| 30 | 
            +
                    reference_translations = [[line.strip()] for line in f.readlines()]
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                # --- 4. Generate Predictions ---
         | 
| 33 | 
            +
                print(f"Generating translations for {len(source_sentences)} test sentences...")
         | 
| 34 | 
            +
                predictions = []
         | 
| 35 | 
            +
                for sentence in tqdm(source_sentences):
         | 
| 36 | 
            +
                    inputs = tokenizer(sentence, return_tensors="pt").to(DEVICE)
         | 
| 37 | 
            +
                    
         | 
| 38 | 
            +
                    generated_tokens = model.generate(
         | 
| 39 | 
            +
                        **inputs,
         | 
| 40 | 
            +
                        max_length=128
         | 
| 41 | 
            +
                    )
         | 
| 42 | 
            +
                    
         | 
| 43 | 
            +
                    translation = tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
         | 
| 44 | 
            +
                    predictions.append(translation)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # --- 5. Compute BLEU Score ---
         | 
| 47 | 
            +
                print("Calculating BLEU score...")
         | 
| 48 | 
            +
                results = bleu_metric.compute(predictions=predictions, references=reference_translations)
         | 
| 49 | 
            +
                
         | 
| 50 | 
            +
                # The result is a dictionary. The 'score' key holds the main BLEU score.
         | 
| 51 | 
            +
                bleu_score = results["score"]
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                print("\n--- Evaluation Complete ---")
         | 
| 54 | 
            +
                print(f"BLEU Score: {bleu_score:.2f}")
         | 
| 55 | 
            +
                print("---------------------------")
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            if __name__ == "__main__":
         | 
| 58 | 
            +
                evaluate_model()
         | 
    	
        src/evaluation.py
    ADDED
    
    | @@ -0,0 +1,64 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # src/evaluate.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import torch
         | 
| 4 | 
            +
            import evaluate # The new, preferred Hugging Face library for metrics
         | 
| 5 | 
            +
            from transformers import AutoModelForSeq2SeqLM, AutoTokenizer
         | 
| 6 | 
            +
            from tqdm import tqdm # A library to create smart progress bars
         | 
| 7 | 
            +
            import argparse
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            def evaluate_model():
         | 
| 10 | 
            +
                """
         | 
| 11 | 
            +
                Loads a fine-tuned model and evaluates its performance on the test set using the BLEU score.
         | 
| 12 | 
            +
                """
         | 
| 13 | 
            +
                parser = argparse.ArgumentParser(description="Evaluate a translation model.")
         | 
| 14 | 
            +
                parser.add_argument("--model_path", type=str, required=True, help="Path to the fine-tuned model directory")
         | 
| 15 | 
            +
                parser.add_argument("--source_lang_file", type=str, required=True, help="Path to the source language test file")
         | 
| 16 | 
            +
                parser.add_argument("--target_lang_file", type=str, required=True, help="Path to the target language test file")
         | 
| 17 | 
            +
                parser.add_argument("--source_lang_tokenizer", type=str, required=True, help="Source language code for tokenizer (e.g., 'nep_Npan')")
         | 
| 18 | 
            +
                args = parser.parse_args()
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                # --- 1. Configuration ---
         | 
| 21 | 
            +
                DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
         | 
| 22 | 
            +
                
         | 
| 23 | 
            +
                # --- 2. Load Model, Tokenizer, and Metric ---
         | 
| 24 | 
            +
                print("Loading model, tokenizer, and evaluation metric...")
         | 
| 25 | 
            +
                tokenizer = AutoTokenizer.from_pretrained(args.model_path)
         | 
| 26 | 
            +
                model = AutoModelForSeq2SeqLM.from_pretrained(args.model_path).to(DEVICE)
         | 
| 27 | 
            +
                bleu_metric = evaluate.load("sacrebleu")
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                # --- 3. Load Test Data ---
         | 
| 30 | 
            +
                with open(args.source_lang_file, "r", encoding="utf-8") as f:
         | 
| 31 | 
            +
                    source_sentences = [line.strip() for line in f.readlines()]
         | 
| 32 | 
            +
                with open(args.target_lang_file, "r", encoding="utf-8") as f:
         | 
| 33 | 
            +
                    # The BLEU metric expects references to be a list of lists
         | 
| 34 | 
            +
                    reference_translations = [[line.strip()] for line in f.readlines()]
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                # --- 4. Generate Predictions ---
         | 
| 37 | 
            +
                print(f"Generating translations for {len(source_sentences)} test sentences...")
         | 
| 38 | 
            +
                predictions = []
         | 
| 39 | 
            +
                for sentence in tqdm(source_sentences):
         | 
| 40 | 
            +
                    tokenizer.src_lang = args.source_lang_tokenizer
         | 
| 41 | 
            +
                    inputs = tokenizer(sentence, return_tensors="pt").to(DEVICE)
         | 
| 42 | 
            +
                    
         | 
| 43 | 
            +
                    generated_tokens = model.generate(
         | 
| 44 | 
            +
                        **inputs,
         | 
| 45 | 
            +
                        forced_bos_token_id=tokenizer.vocab["eng_Latn"],
         | 
| 46 | 
            +
                        max_length=128
         | 
| 47 | 
            +
                    )
         | 
| 48 | 
            +
                    
         | 
| 49 | 
            +
                    translation = tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
         | 
| 50 | 
            +
                    predictions.append(translation)
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                # --- 5. Compute BLEU Score ---
         | 
| 53 | 
            +
                print("Calculating BLEU score...")
         | 
| 54 | 
            +
                results = bleu_metric.compute(predictions=predictions, references=reference_translations)
         | 
| 55 | 
            +
                
         | 
| 56 | 
            +
                # The result is a dictionary. The 'score' key holds the main BLEU score.
         | 
| 57 | 
            +
                bleu_score = results["score"]
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                print("\n--- Evaluation Complete ---")
         | 
| 60 | 
            +
                print(f"BLEU Score: {bleu_score:.2f}")
         | 
| 61 | 
            +
                print("---------------------------")
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            if __name__ == "__main__":
         | 
| 64 | 
            +
                evaluate_model()
         | 
    	
        src/train.py
    ADDED
    
    | @@ -0,0 +1,109 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # src/train.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import os
         | 
| 4 | 
            +
            import argparse
         | 
| 5 | 
            +
            from datasets import Dataset
         | 
| 6 | 
            +
            from transformers import (
         | 
| 7 | 
            +
                AutoModelForSeq2SeqLM,
         | 
| 8 | 
            +
                AutoTokenizer,
         | 
| 9 | 
            +
                DataCollatorForSeq2Seq,
         | 
| 10 | 
            +
                Seq2SeqTrainingArguments,
         | 
| 11 | 
            +
                Seq2SeqTrainer,
         | 
| 12 | 
            +
            )
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            def train_model():
         | 
| 15 | 
            +
                """
         | 
| 16 | 
            +
                Fine-tunes a pre-trained NLLB model on a parallel dataset.
         | 
| 17 | 
            +
                """
         | 
| 18 | 
            +
                parser = argparse.ArgumentParser(description="Fine-tune a translation model.")
         | 
| 19 | 
            +
                parser.add_argument("--model_checkpoint", type=str, default="facebook/nllb-200-distilled-600M")
         | 
| 20 | 
            +
                parser.add_argument("--source_lang", type=str, required=True, help="Source language code (e.g., 'ne')")
         | 
| 21 | 
            +
                parser.add_argument("--target_lang", type=str, default="en")
         | 
| 22 | 
            +
                parser.add_argument("--source_lang_tokenizer", type=str, required=True, help="Source language code for tokenizer (e.g., 'nep_Npan')")
         | 
| 23 | 
            +
                parser.add_argument("--train_file_source", type=str, required=True, help="Path to the source language training file")
         | 
| 24 | 
            +
                parser.add_argument("--train_file_target", type=str, required=True, help="Path to the target language training file")
         | 
| 25 | 
            +
                parser.add_argument("--output_dir", type=str, required=True, help="Directory to save the fine-tuned model")
         | 
| 26 | 
            +
                parser.add_argument("--epochs", type=int, default=3)
         | 
| 27 | 
            +
                parser.add_argument("--batch_size", type=int, default=8)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                args = parser.parse_args()
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                # --- 1. Configuration ---
         | 
| 32 | 
            +
                MODEL_CHECKPOINT = args.model_checkpoint
         | 
| 33 | 
            +
                SOURCE_LANG = args.source_lang
         | 
| 34 | 
            +
                TARGET_LANG = args.target_lang
         | 
| 35 | 
            +
                MODEL_OUTPUT_DIR = args.output_dir
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                # --- 2. Load Tokenizer and Model ---
         | 
| 38 | 
            +
                print("Loading tokenizer and model...")
         | 
| 39 | 
            +
                tokenizer = AutoTokenizer.from_pretrained(
         | 
| 40 | 
            +
                    MODEL_CHECKPOINT, src_lang=args.source_lang_tokenizer, tgt_lang="eng_Latn"
         | 
| 41 | 
            +
                )
         | 
| 42 | 
            +
                model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_CHECKPOINT)
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                # --- 3. Load and Preprocess Data (Memory-Efficiently) ---
         | 
| 45 | 
            +
                print("Loading and preprocessing data...")
         | 
| 46 | 
            +
                
         | 
| 47 | 
            +
                def generate_examples():
         | 
| 48 | 
            +
                    with open(args.train_file_source, "r", encoding="utf-8") as f_src, \
         | 
| 49 | 
            +
                         open(args.train_file_target, "r", encoding="utf-8") as f_tgt:
         | 
| 50 | 
            +
                        for src_line, tgt_line in zip(f_src, f_tgt):
         | 
| 51 | 
            +
                            yield {"translation": {SOURCE_LANG: src_line.strip(), TARGET_LANG: tgt_line.strip()}}
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                dataset = Dataset.from_generator(generate_examples)
         | 
| 54 | 
            +
                
         | 
| 55 | 
            +
                split_datasets = dataset.train_test_split(train_size=0.95, seed=42)
         | 
| 56 | 
            +
                split_datasets["validation"] = split_datasets.pop("test")
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def preprocess_function(examples):
         | 
| 59 | 
            +
                    inputs = [ex[SOURCE_LANG] for ex in examples["translation"]]
         | 
| 60 | 
            +
                    targets = [ex[TARGET_LANG] for ex in examples["translation"]]
         | 
| 61 | 
            +
                    
         | 
| 62 | 
            +
                    model_inputs = tokenizer(inputs, text_target=targets, max_length=128, truncation=True)
         | 
| 63 | 
            +
                    return model_inputs
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                tokenized_datasets = split_datasets.map(
         | 
| 66 | 
            +
                    preprocess_function,
         | 
| 67 | 
            +
                    batched=True,
         | 
| 68 | 
            +
                    remove_columns=split_datasets["train"].column_names,
         | 
| 69 | 
            +
                )
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                # --- 4. Set Up Training Arguments ---
         | 
| 72 | 
            +
                print("Setting up training arguments...")
         | 
| 73 | 
            +
                training_args = Seq2SeqTrainingArguments(
         | 
| 74 | 
            +
                    output_dir=MODEL_OUTPUT_DIR,
         | 
| 75 | 
            +
                    eval_strategy="epoch",
         | 
| 76 | 
            +
                    learning_rate=2e-5,
         | 
| 77 | 
            +
                    per_device_train_batch_size=args.batch_size,
         | 
| 78 | 
            +
                    per_device_eval_batch_size=args.batch_size,
         | 
| 79 | 
            +
                    weight_decay=0.01,
         | 
| 80 | 
            +
                    save_total_limit=3,
         | 
| 81 | 
            +
                    num_train_epochs=args.epochs,
         | 
| 82 | 
            +
                    predict_with_generate=True,
         | 
| 83 | 
            +
                    fp16=False, # Set to True if you have a compatible GPU
         | 
| 84 | 
            +
                )
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                # --- 5. Create the Trainer ---
         | 
| 87 | 
            +
                data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model)
         | 
| 88 | 
            +
                
         | 
| 89 | 
            +
                trainer = Seq2SeqTrainer(
         | 
| 90 | 
            +
                    model=model,
         | 
| 91 | 
            +
                    args=training_args,
         | 
| 92 | 
            +
                    train_dataset=tokenized_datasets["train"],
         | 
| 93 | 
            +
                    eval_dataset=tokenized_datasets["validation"],
         | 
| 94 | 
            +
                    tokenizer=tokenizer,
         | 
| 95 | 
            +
                    data_collator=data_collator,
         | 
| 96 | 
            +
                )
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                # --- 6. Start Training ---
         | 
| 99 | 
            +
                print("\n--- Starting model fine-tuning ---")
         | 
| 100 | 
            +
                trainer.train()
         | 
| 101 | 
            +
                print("--- Training complete ---")
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                # --- 7. Save the Final Model ---
         | 
| 104 | 
            +
                print(f"Saving final model to {MODEL_OUTPUT_DIR}")
         | 
| 105 | 
            +
                trainer.save_model()
         | 
| 106 | 
            +
                print("Model saved successfully!")
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            if __name__ == "__main__":
         | 
| 109 | 
            +
                train_model()
         | 
    	
        src/train_nepali.py
    ADDED
    
    | @@ -0,0 +1,95 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # src/train_nepali.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            import os
         | 
| 4 | 
            +
            from datasets import load_dataset, DatasetDict, concatenate_datasets
         | 
| 5 | 
            +
            from transformers import (
         | 
| 6 | 
            +
                AutoModelForSeq2SeqLM,
         | 
| 7 | 
            +
                AutoTokenizer,
         | 
| 8 | 
            +
                DataCollatorForSeq2Seq,
         | 
| 9 | 
            +
                Seq2SeqTrainingArguments,
         | 
| 10 | 
            +
                Seq2SeqTrainer,
         | 
| 11 | 
            +
            )
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            def train_nepali_model():
         | 
| 14 | 
            +
                """
         | 
| 15 | 
            +
                Fine-tunes a pre-trained NLLB model on the Nepali parallel dataset.
         | 
| 16 | 
            +
                """
         | 
| 17 | 
            +
                # --- 1. Configuration ---
         | 
| 18 | 
            +
                MODEL_CHECKPOINT = "facebook/nllb-200-distilled-600M"
         | 
| 19 | 
            +
                DATA_DIR = "data/processed"
         | 
| 20 | 
            +
                MODEL_OUTPUT_DIR = "D:\\SIH\\models\\nllb-finetuned-nepali-en"
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # --- 2. Load Tokenizer and Model ---
         | 
| 23 | 
            +
                print("Loading tokenizer and model...")
         | 
| 24 | 
            +
                tokenizer = AutoTokenizer.from_pretrained(
         | 
| 25 | 
            +
                    MODEL_CHECKPOINT, src_lang="nep_Npan", tgt_lang="eng_Latn"
         | 
| 26 | 
            +
                )
         | 
| 27 | 
            +
                model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_CHECKPOINT)
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                # --- 3. Load and Preprocess Data ---
         | 
| 30 | 
            +
                print("Loading and preprocessing data...")
         | 
| 31 | 
            +
                nepali_dataset = load_dataset("text", data_files=os.path.join(DATA_DIR, "nepali.ne"))["train"]
         | 
| 32 | 
            +
                english_dataset = load_dataset("text", data_files=os.path.join(DATA_DIR, "nepali.en"))["train"]
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                # rename the 'text' column to 'ne' and 'en'
         | 
| 35 | 
            +
                nepali_dataset = nepali_dataset.rename_column("text", "ne")
         | 
| 36 | 
            +
                english_dataset = english_dataset.rename_column("text", "en")
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                # combine the datasets
         | 
| 39 | 
            +
                raw_datasets = concatenate_datasets([nepali_dataset, english_dataset], axis=1)
         | 
| 40 | 
            +
                
         | 
| 41 | 
            +
                split_datasets = raw_datasets.train_test_split(train_size=0.95, seed=42)
         | 
| 42 | 
            +
                split_datasets["validation"] = split_datasets.pop("test")
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def preprocess_function(examples):
         | 
| 45 | 
            +
                    inputs = examples["ne"]
         | 
| 46 | 
            +
                    targets = examples["en"]
         | 
| 47 | 
            +
                    
         | 
| 48 | 
            +
                    model_inputs = tokenizer(inputs, text_target=targets, max_length=128, truncation=True)
         | 
| 49 | 
            +
                    return model_inputs
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                tokenized_datasets = split_datasets.map(
         | 
| 52 | 
            +
                    preprocess_function,
         | 
| 53 | 
            +
                    batched=True,
         | 
| 54 | 
            +
                    remove_columns=split_datasets["train"].column_names,
         | 
| 55 | 
            +
                )
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                # --- 4. Set Up Training Arguments ---
         | 
| 58 | 
            +
                print("Setting up training arguments...")
         | 
| 59 | 
            +
                training_args = Seq2SeqTrainingArguments(
         | 
| 60 | 
            +
                    output_dir=MODEL_OUTPUT_DIR,
         | 
| 61 | 
            +
                    eval_strategy="epoch",
         | 
| 62 | 
            +
                    learning_rate=2e-5,
         | 
| 63 | 
            +
                    per_device_train_batch_size=8,
         | 
| 64 | 
            +
                    per_device_eval_batch_size=8,
         | 
| 65 | 
            +
                    weight_decay=0.01,
         | 
| 66 | 
            +
                    save_total_limit=3,
         | 
| 67 | 
            +
                    num_train_epochs=3, # Reduced for faster training, can be increased
         | 
| 68 | 
            +
                    predict_with_generate=True,
         | 
| 69 | 
            +
                    fp16=False, # Set to True if you have a compatible GPU
         | 
| 70 | 
            +
                )
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                # --- 5. Create the Trainer ---
         | 
| 73 | 
            +
                data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model)
         | 
| 74 | 
            +
                
         | 
| 75 | 
            +
                trainer = Seq2SeqTrainer(
         | 
| 76 | 
            +
                    model=model,
         | 
| 77 | 
            +
                    args=training_args,
         | 
| 78 | 
            +
                    train_dataset=tokenized_datasets["train"],
         | 
| 79 | 
            +
                    eval_dataset=tokenized_datasets["validation"],
         | 
| 80 | 
            +
                    tokenizer=tokenizer,
         | 
| 81 | 
            +
                    data_collator=data_collator,
         | 
| 82 | 
            +
                )
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                # --- 6. Start Training ---
         | 
| 85 | 
            +
                print(f"\n--- Starting model fine-tuning for Nepali-English ---")
         | 
| 86 | 
            +
                trainer.train()
         | 
| 87 | 
            +
                print("--- Training complete ---")
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                # --- 7. Save the Final Model ---
         | 
| 90 | 
            +
                print(f"Saving final model to {MODEL_OUTPUT_DIR}")
         | 
| 91 | 
            +
                trainer.save_model()
         | 
| 92 | 
            +
                print("Model saved successfully!")
         | 
| 93 | 
            +
             | 
| 94 | 
            +
            if __name__ == "__main__":
         | 
| 95 | 
            +
                train_nepali_model()
         | 
    	
        src/translate.py
    ADDED
    
    | @@ -0,0 +1,52 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            # src/translate.py
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # src/translate.py
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            import torch
         | 
| 6 | 
            +
            from transformers import MBartForConditionalGeneration, NllbTokenizer
         | 
| 7 | 
            +
            import argparse
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # --- 1. Configuration ---
         | 
| 10 | 
            +
            DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            # --- 2. Load Models and Tokenizers ---
         | 
| 13 | 
            +
            print(f"Loading models on {DEVICE.upper()}...")
         | 
| 14 | 
            +
            models = {
         | 
| 15 | 
            +
                "nepali": MBartForConditionalGeneration.from_pretrained("models/nllb-finetuned-nepali-en").to(DEVICE)
         | 
| 16 | 
            +
            }
         | 
| 17 | 
            +
            tokenizers = {
         | 
| 18 | 
            +
                "nepali": NllbTokenizer.from_pretrained("models/nllb-finetuned-nepali-en")
         | 
| 19 | 
            +
            }
         | 
| 20 | 
            +
            print("All models loaded successfully!")
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            def translate_text(text_to_translate: str, source_language: str) -> str:
         | 
| 23 | 
            +
                """
         | 
| 24 | 
            +
                Translates a single string of text to English using our fine-tuned models.
         | 
| 25 | 
            +
                """
         | 
| 26 | 
            +
                model = models[source_language]
         | 
| 27 | 
            +
                tokenizer = tokenizers[source_language]
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                tokenizer.src_lang = "nep_Npan"
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                inputs = tokenizer(text_to_translate, return_tensors="pt").to(DEVICE)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                generated_tokens = model.generate(
         | 
| 34 | 
            +
                    **inputs,
         | 
| 35 | 
            +
                    forced_bos_token_id=tokenizer.convert_tokens_to_ids("eng_Latn"),
         | 
| 36 | 
            +
                    max_length=128
         | 
| 37 | 
            +
                )
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                translation = tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
         | 
| 40 | 
            +
                return translation
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            # --- 3. Example Usage ---
         | 
| 43 | 
            +
            if __name__ == "__main__":
         | 
| 44 | 
            +
                parser = argparse.ArgumentParser(description="Translate text using a fine-tuned model.")
         | 
| 45 | 
            +
                parser.add_argument("--text", type=str, required=True, help="Text to translate.")
         | 
| 46 | 
            +
                parser.add_argument("--lang", type=str, required=True, choices=["nepali"], help="Source language: 'nepali'.")
         | 
| 47 | 
            +
                args = parser.parse_args()
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                translated_sentence = translate_text(args.text, args.lang)
         | 
| 50 | 
            +
                
         | 
| 51 | 
            +
                print(f"\nOriginal ({args.lang}): {args.text}")
         | 
| 52 | 
            +
                print(f"Translated (en): {translated_sentence}")
         | 
    	
        test_analysis.py
    ADDED
    
    | @@ -0,0 +1,84 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            import os
         | 
| 2 | 
            +
            import sys
         | 
| 3 | 
            +
            import codecs
         | 
| 4 | 
            +
            import torch
         | 
| 5 | 
            +
            from transformers import M2M100ForConditionalGeneration, NllbTokenizerFast
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            def translate_text(text, model, tokenizer, src_lang, target_lang="eng_Latn"):
         | 
| 8 | 
            +
                """
         | 
| 9 | 
            +
                Translates a single text string.
         | 
| 10 | 
            +
                """
         | 
| 11 | 
            +
                try:
         | 
| 12 | 
            +
                    tokenizer.src_lang = src_lang
         | 
| 13 | 
            +
                    inputs = tokenizer(text, return_tensors="pt")
         | 
| 14 | 
            +
                    generated_tokens = model.generate(
         | 
| 15 | 
            +
                        **inputs,
         | 
| 16 | 
            +
                        forced_bos_token_id=tokenizer.vocab[target_lang],
         | 
| 17 | 
            +
                        max_length=512
         | 
| 18 | 
            +
                    )
         | 
| 19 | 
            +
                    translated_text = tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
         | 
| 20 | 
            +
                    return translated_text
         | 
| 21 | 
            +
                except Exception as e:
         | 
| 22 | 
            +
                    return f"An error occurred during translation: {e}"
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            def main():
         | 
| 25 | 
            +
                """
         | 
| 26 | 
            +
                Main function to load the model and run a test translation.
         | 
| 27 | 
            +
                """
         | 
| 28 | 
            +
                # Reconfigure stdout to handle UTF-8 encoding
         | 
| 29 | 
            +
                sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                # --- Configuration ---
         | 
| 32 | 
            +
                script_dir = os.path.dirname(os.path.abspath(__file__))
         | 
| 33 | 
            +
                nepali_model_path = os.path.join(script_dir, "models", "nllb-finetuned-nepali-en")
         | 
| 34 | 
            +
                
         | 
| 35 | 
            +
                # --- Model Loading ---
         | 
| 36 | 
            +
                print("Loading Nepali model and tokenizer...")
         | 
| 37 | 
            +
                try:
         | 
| 38 | 
            +
                    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
         | 
| 39 | 
            +
                    nepali_model = M2M100ForConditionalGeneration.from_pretrained(nepali_model_path).to(device)
         | 
| 40 | 
            +
                    nepali_tokenizer = NllbTokenizerFast.from_pretrained(nepali_model_path)
         | 
| 41 | 
            +
                    print("Nepali model and tokenizer loaded successfully.")
         | 
| 42 | 
            +
                except Exception as e:
         | 
| 43 | 
            +
                    print(f"Error loading Nepali model or tokenizer: {e}")
         | 
| 44 | 
            +
                    return
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # --- Nepali Translation ---
         | 
| 47 | 
            +
                nepali_sentences = [
         | 
| 48 | 
            +
                    "जडान बिन्दु थप्नुहोस्",
         | 
| 49 | 
            +
                    "स्टिकी नोट आयात पूरा भयो",
         | 
| 50 | 
            +
                    "मोनोस्पेस १२",
         | 
| 51 | 
            +
                    "पानी जेट पम्पमा दुईवटा भित्रिने र एउटा बाहिरिने पाइप हुन्छन् र एक भित्र अर्को सिद्धान्त अनुरूप दुईवटा पाइप हुन्छन् । पानीको प्रविष्टिमा एउटा पानी जेटले केही ठूलो पाइपमा पूरा चापले टुटीबाट बाहिर फाल्दछ । यस्तो तरिकाले पानी जेटले वायू वा तरललाई दोस्रो प्रविष्टिबाट टाढा पुर्याउदछ । ड्रिफ्टिङ तरलमा ऋणात्मक चापको कारणले यस्तो हुन्छ । त्यसैले यो हाइड्रोडायनमिक विरोधाभाषको एउटा अनुप्रयोग हो । यसले ड्रिफ्टिङ तरल नजिकका वस्तु टाढा फाल्नुको साटोमा सोस्ने कुरा बताउदछ ।",
         | 
| 52 | 
            +
                    "वस्तुको परिवर्तन बचत गर्नुहोस् ।"
         | 
| 53 | 
            +
                    "तिमीलाई कस्तो छ" ,
         | 
| 54 | 
            +
                    "तिमी को हौ",
         | 
| 55 | 
            +
                    "कति बज्यो"
         | 
| 56 | 
            +
                ]
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                print("\n--- Nepali to English Translation Analysis ---")
         | 
| 59 | 
            +
                for sentence in nepali_sentences:
         | 
| 60 | 
            +
                    print(f"\nOriginal (ne): {sentence}")
         | 
| 61 | 
            +
                    translated_text = translate_text(sentence, nepali_model, nepali_tokenizer, src_lang="nep_Npan")
         | 
| 62 | 
            +
                    print(f"Translated (en): {translated_text}")
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                # --- Sinhala Translation ---
         | 
| 65 | 
            +
                # NOTE: No fine-tuned model for sinhala was found. Using the baseline model for now.
         | 
| 66 | 
            +
                print("\n\n--- Sinhala to English Translation Analysis ---")
         | 
| 67 | 
            +
                
         | 
| 68 | 
            +
                sinhala_sentences = [
         | 
| 69 | 
            +
                    "ඩෝසන්මිස් දුරකථනයෙන් ඩෝසන්මිස් කවුද සර්",
         | 
| 70 | 
            +
                    "කවුද ඩෝසන් නැතුව ඉන්නේ ඔව් සර්",
         | 
| 71 | 
            +
                    "ඔබ එය උත්සාහ කරන්න සර්",
         | 
| 72 | 
            +
                    "කොහොමද වැඩේ හරිද ඔව් සර්ට ස්තුතියි",
         | 
| 73 | 
            +
                    "ඔව්, හරි, ස්තුතියි රත්තරං",
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                ]
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                for sentence in sinhala_sentences:
         | 
| 78 | 
            +
                    print(f"\nOriginal (si): {sentence}")
         | 
| 79 | 
            +
                    translated_text = translate_text(sentence, nepali_model, nepali_tokenizer, src_lang="sin_Sinh")
         | 
| 80 | 
            +
                    print(f"Translated (en): {translated_text}")
         | 
| 81 | 
            +
             | 
| 82 | 
            +
             | 
| 83 | 
            +
            if __name__ == "__main__":
         | 
| 84 | 
            +
                main()
         |