Athena1621 commited on
Commit
3db3dcf
·
1 Parent(s): 1b3c855

Fix translation service language validation and PyTorch loading issues

Browse files
Files changed (3) hide show
  1. .gitignore +2 -2
  2. backend/models.py +212 -0
  3. backend/translation_service.py +440 -0
.gitignore CHANGED
@@ -7,8 +7,8 @@ __pycache__/
7
 
8
  # Local Development Files
9
  local-development/
10
- backend/
11
- frontend/
12
  IndicTrans2/
13
  models/
14
  data/
 
7
 
8
  # Local Development Files
9
  local-development/
10
+ # backend/ - Allow backend to be tracked
11
+ # frontend/ - Allow frontend to be tracked
12
  IndicTrans2/
13
  models/
14
  data/
backend/models.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pydantic models for API request/response schemas
3
+ """
4
+
5
+ from pydantic import BaseModel, Field
6
+ from typing import Optional, List
7
+ from datetime import datetime
8
+
9
+ class LanguageDetectionRequest(BaseModel):
10
+ """Request model for language detection"""
11
+ text: str = Field(..., description="Text to detect language for", min_length=1)
12
+
13
+ class Config:
14
+ schema_extra = {
15
+ "example": {
16
+ "text": "यह एक अच्छी किताब है।"
17
+ }
18
+ }
19
+
20
+ class LanguageDetectionResponse(BaseModel):
21
+ """Response model for language detection"""
22
+ language: str = Field(..., description="Detected language code (e.g., 'hi', 'en')")
23
+ confidence: float = Field(..., description="Confidence score between 0 and 1")
24
+ language_name: str = Field(..., description="Human-readable language name")
25
+
26
+ class Config:
27
+ schema_extra = {
28
+ "example": {
29
+ "language": "hi",
30
+ "confidence": 0.95,
31
+ "language_name": "Hindi"
32
+ }
33
+ }
34
+
35
+ class TranslationRequest(BaseModel):
36
+ """Request model for translation"""
37
+ text: str = Field(..., description="Text to translate", min_length=1)
38
+ target_language: str = Field(..., description="Target language code")
39
+ source_language: Optional[str] = Field(None, description="Source language code (auto-detect if not provided)")
40
+
41
+ class Config:
42
+ schema_extra = {
43
+ "example": {
44
+ "text": "यह एक अच्छी किताब है।",
45
+ "target_language": "en",
46
+ "source_language": "hi"
47
+ }
48
+ }
49
+
50
+ class TranslationResponse(BaseModel):
51
+ """Response model for translation"""
52
+ translated_text: str = Field(..., description="Translated text")
53
+ source_language: str = Field(..., description="Source language code")
54
+ target_language: str = Field(..., description="Target language code")
55
+ confidence: float = Field(..., description="Translation confidence score")
56
+ translation_id: int = Field(..., description="Unique translation ID for future reference")
57
+
58
+ class Config:
59
+ schema_extra = {
60
+ "example": {
61
+ "translated_text": "This is a good book.",
62
+ "source_language": "hi",
63
+ "target_language": "en",
64
+ "confidence": 0.92,
65
+ "translation_id": 12345
66
+ }
67
+ }
68
+
69
+ class CorrectionRequest(BaseModel):
70
+ """Request model for submitting translation corrections"""
71
+ translation_id: int = Field(..., description="ID of the translation to correct")
72
+ corrected_text: str = Field(..., description="Manually corrected translation", min_length=1)
73
+ feedback: Optional[str] = Field(None, description="Optional feedback about the correction")
74
+
75
+ class Config:
76
+ schema_extra = {
77
+ "example": {
78
+ "translation_id": 12345,
79
+ "corrected_text": "This is an excellent book.",
80
+ "feedback": "The word 'अच्छी' should be translated as 'excellent' not 'good' in this context"
81
+ }
82
+ }
83
+
84
+ class CorrectionResponse(BaseModel):
85
+ """Response model for correction submission"""
86
+ correction_id: int = Field(..., description="Unique correction ID")
87
+ message: str = Field(..., description="Success message")
88
+ status: str = Field(..., description="Status of the correction submission")
89
+
90
+ class Config:
91
+ schema_extra = {
92
+ "example": {
93
+ "correction_id": 67890,
94
+ "message": "Correction submitted successfully",
95
+ "status": "success"
96
+ }
97
+ }
98
+
99
+ class TranslationHistory(BaseModel):
100
+ """Model for translation history records"""
101
+ id: int = Field(..., description="Translation ID")
102
+ original_text: str = Field(..., description="Original text")
103
+ translated_text: str = Field(..., description="Machine-translated text")
104
+ source_language: str = Field(..., description="Source language code")
105
+ target_language: str = Field(..., description="Target language code")
106
+ model_confidence: float = Field(..., description="Model confidence score")
107
+ created_at: datetime = Field(..., description="Timestamp when translation was created")
108
+ corrected_text: Optional[str] = Field(None, description="Manual correction if available")
109
+ correction_feedback: Optional[str] = Field(None, description="Feedback for the correction")
110
+
111
+ class Config:
112
+ schema_extra = {
113
+ "example": {
114
+ "id": 12345,
115
+ "original_text": "यह एक अच्छी किताब है।",
116
+ "translated_text": "This is a good book.",
117
+ "source_language": "hi",
118
+ "target_language": "en",
119
+ "model_confidence": 0.92,
120
+ "created_at": "2025-01-25T10:30:00Z",
121
+ "corrected_text": "This is an excellent book.",
122
+ "correction_feedback": "Context-specific improvement"
123
+ }
124
+ }
125
+
126
+ class BatchTranslationRequest(BaseModel):
127
+ """Request model for batch translation"""
128
+ texts: List[str] = Field(..., description="List of texts to translate", min_items=1)
129
+ target_language: str = Field(..., description="Target language code")
130
+ source_language: Optional[str] = Field(None, description="Source language code (auto-detect if not provided)")
131
+
132
+ class Config:
133
+ schema_extra = {
134
+ "example": {
135
+ "texts": [
136
+ "यह एक अच्छी किताब है।",
137
+ "मुझे यह पसंद है।",
138
+ "कितना पैसा लगेगा?"
139
+ ],
140
+ "target_language": "en",
141
+ "source_language": "hi"
142
+ }
143
+ }
144
+
145
+ class ProductCatalogItem(BaseModel):
146
+ """Model for e-commerce product catalog items"""
147
+ title: str = Field(..., description="Product title", min_length=1)
148
+ description: str = Field(..., description="Product description", min_length=1)
149
+ category: Optional[str] = Field(None, description="Product category")
150
+ price: Optional[str] = Field(None, description="Product price")
151
+ seller_id: Optional[str] = Field(None, description="Seller identifier")
152
+
153
+ class Config:
154
+ schema_extra = {
155
+ "example": {
156
+ "title": "शुद्ध कपास की साड़ी",
157
+ "description": "यह एक सुंदर पारंपरिक साड़ी है जो शुद्ध कपास से बनी है। विशेष अवसरों के लिए आदर्श।",
158
+ "category": "वस्त्र",
159
+ "price": "₹2500",
160
+ "seller_id": "seller_123"
161
+ }
162
+ }
163
+
164
+ class TranslatedProductCatalogItem(BaseModel):
165
+ """Model for translated product catalog items"""
166
+ original_item: ProductCatalogItem
167
+ translated_title: str
168
+ translated_description: str
169
+ translated_category: Optional[str] = None
170
+ source_language: str
171
+ target_language: str
172
+ translation_ids: dict = Field(..., description="Map of field names to translation IDs")
173
+
174
+ class Config:
175
+ schema_extra = {
176
+ "example": {
177
+ "original_item": {
178
+ "title": "शुद्ध कपास की साड़ी",
179
+ "description": "यह एक सुंदर पारंपरिक साड़ी है।",
180
+ "category": "वस्त्र"
181
+ },
182
+ "translated_title": "Pure Cotton Saree",
183
+ "translated_description": "This is a beautiful traditional saree.",
184
+ "translated_category": "Clothing",
185
+ "source_language": "hi",
186
+ "target_language": "en",
187
+ "translation_ids": {
188
+ "title": 12345,
189
+ "description": 12346,
190
+ "category": 12347
191
+ }
192
+ }
193
+ }
194
+
195
+ # Supported language mappings for the translation service
196
+ SUPPORTED_LANGUAGES = {
197
+ "en": "English",
198
+ "hi": "Hindi",
199
+ "bn": "Bengali",
200
+ "gu": "Gujarati",
201
+ "kn": "Kannada",
202
+ "ml": "Malayalam",
203
+ "mr": "Marathi",
204
+ "or": "Odia",
205
+ "pa": "Punjabi",
206
+ "ta": "Tamil",
207
+ "te": "Telugu",
208
+ "ur": "Urdu",
209
+ "as": "Assamese",
210
+ "ne": "Nepali",
211
+ "sa": "Sanskrit"
212
+ }
backend/translation_service.py ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Translation service using IndicTrans2 by AI4Bharat
3
+ Handles language detection and translation between Indian languages
4
+ """
5
+
6
+ import asyncio
7
+ import logging
8
+ from typing import Dict, List, Optional, Any
9
+ import torch
10
+ try:
11
+ import fasttext
12
+ FASTTEXT_AVAILABLE = True
13
+ except ImportError:
14
+ FASTTEXT_AVAILABLE = False
15
+ fasttext = None
16
+ import os
17
+ import requests
18
+ from dotenv import load_dotenv
19
+ from models import SUPPORTED_LANGUAGES
20
+
21
+ # Load environment variables
22
+ load_dotenv()
23
+
24
+ # Load environment variables early
25
+ load_dotenv()
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # --- Model Configuration ---
30
+ FASTTEXT_MODEL_URL = "https://dl.fbaipublicfiles.com/fasttext/supervised-models/lid.176.bin"
31
+ FASTTEXT_MODEL_PATH = os.path.join(os.path.dirname(__file__), "lid.176.bin")
32
+
33
+
34
+ class TranslationService:
35
+ """Service for handling language detection and translation using IndicTrans2"""
36
+
37
+ def __init__(self):
38
+ self.en_indic_model = None
39
+ self.en_indic_tokenizer = None
40
+ self.indic_en_model = None
41
+ self.indic_en_tokenizer = None
42
+ self.language_detector = None
43
+ self.device = "cuda" if torch.cuda.is_available() and os.getenv("DEVICE", "cuda") == "cuda" else "cpu"
44
+ self.model_dir = os.getenv("MODEL_PATH", "models/indictrans2")
45
+ self.model_loaded = False
46
+ self.model_type = os.getenv("MODEL_TYPE", "mock") # Read here instead
47
+
48
+ # Try to import transformers when needed
49
+ self.transformers_available = False
50
+ try:
51
+ import transformers
52
+ self.transformers_available = True
53
+ except ImportError:
54
+ logger.warning("Transformers not available, will use mock mode")
55
+
56
+ # Language code mappings for IndicTrans2 (ISO to Flores codes)
57
+ self.lang_code_map = {
58
+ "en": "eng_Latn",
59
+ "hi": "hin_Deva",
60
+ "bn": "ben_Beng",
61
+ "gu": "guj_Gujr",
62
+ "kn": "kan_Knda",
63
+ "ml": "mal_Mlym",
64
+ "mr": "mar_Deva",
65
+ "or": "ory_Orya",
66
+ "pa": "pan_Guru",
67
+ "ta": "tam_Taml",
68
+ "te": "tel_Telu",
69
+ "ur": "urd_Arab",
70
+ "as": "asm_Beng",
71
+ "ne": "npi_Deva",
72
+ "sa": "san_Deva"
73
+ }
74
+
75
+ # Language name to code mapping
76
+ self.lang_name_to_code = {
77
+ "English": "en",
78
+ "Hindi": "hi",
79
+ "Bengali": "bn",
80
+ "Gujarati": "gu",
81
+ "Kannada": "kn",
82
+ "Malayalam": "ml",
83
+ "Marathi": "mr",
84
+ "Odia": "or",
85
+ "Punjabi": "pa",
86
+ "Tamil": "ta",
87
+ "Telugu": "te",
88
+ "Urdu": "ur",
89
+ "Assamese": "as",
90
+ "Nepali": "ne",
91
+ "Sanskrit": "sa"
92
+ }
93
+
94
+ # Reverse mapping for response
95
+ self.reverse_lang_map = {v: k for k, v in self.lang_code_map.items()}
96
+
97
+ async def load_models(self):
98
+ """Load IndicTrans2 model and language detector based on MODEL_TYPE"""
99
+ if self.model_loaded:
100
+ return
101
+
102
+ logger.info(f"Starting model loading process (Mode: {self.model_type}, Device: {self.device})...")
103
+
104
+ if self.model_type == "indictrans2" and self.transformers_available:
105
+ try:
106
+ await self._load_language_detector()
107
+ await self._load_indictrans2_model()
108
+ self.model_loaded = True
109
+ logger.info("✅ Real IndicTrans2 models loaded successfully!")
110
+ except Exception as e:
111
+ logger.error(f"❌ Failed to load real models: {str(e)}")
112
+ logger.warning("Falling back to mock implementation.")
113
+ self._use_mock_implementation()
114
+ else:
115
+ self._use_mock_implementation()
116
+
117
+ def _use_mock_implementation(self):
118
+ """Sets up the service to use mock implementations."""
119
+ logger.info("Using mock implementation for development.")
120
+ self.language_detector = "mock"
121
+ self.en_indic_model = "mock"
122
+ self.en_indic_tokenizer = "mock"
123
+ self.indic_en_model = "mock"
124
+ self.indic_en_tokenizer = "mock"
125
+ self.model_loaded = True
126
+
127
+ async def _download_fasttext_model(self):
128
+ """Downloads the FastText model if it doesn't exist."""
129
+ if not os.path.exists(FASTTEXT_MODEL_PATH):
130
+ logger.info(f"Downloading FastText language detection model from {FASTTEXT_MODEL_URL}...")
131
+ try:
132
+ response = requests.get(FASTTEXT_MODEL_URL, stream=True)
133
+ response.raise_for_status()
134
+ with open(FASTTEXT_MODEL_PATH, 'wb') as f:
135
+ for chunk in response.iter_content(chunk_size=8192):
136
+ f.write(chunk)
137
+ logger.info(f"✅ FastText model downloaded to {FASTTEXT_MODEL_PATH}")
138
+ except Exception as e:
139
+ logger.error(f"❌ Failed to download FastText model: {e}")
140
+ raise
141
+
142
+ async def _load_language_detector(self):
143
+ """Load FastText language detection model"""
144
+ if not FASTTEXT_AVAILABLE:
145
+ logger.warning("FastText not available, falling back to rule-based detection")
146
+ self.language_detector = "rule_based"
147
+ return
148
+
149
+ await self._download_fasttext_model()
150
+ try:
151
+ logger.info("Loading FastText language detection model...")
152
+ self.language_detector = fasttext.load_model(FASTTEXT_MODEL_PATH)
153
+ logger.info("✅ FastText model loaded.")
154
+ except Exception as e:
155
+ logger.error(f"❌ Failed to load FastText model: {str(e)}")
156
+ logger.warning("Falling back to rule-based detection")
157
+ self.language_detector = "rule_based"
158
+
159
+ async def _load_indictrans2_model(self):
160
+ """Load IndicTrans2 translation models using Hugging Face transformers"""
161
+ try:
162
+ # Import transformers here to avoid import-time errors
163
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
164
+ import warnings
165
+ warnings.filterwarnings("ignore", category=UserWarning)
166
+
167
+ logger.info(f"Loading IndicTrans2 models from: {self.model_dir}...")
168
+
169
+ # Use Hugging Face model hub directly instead of local files
170
+ logger.info("Loading EN→Indic model from Hugging Face...")
171
+ self.en_indic_tokenizer = AutoTokenizer.from_pretrained(
172
+ "ai4bharat/indictrans2-en-indic-1B",
173
+ trust_remote_code=True
174
+ )
175
+ self.en_indic_model = AutoModelForSeq2SeqLM.from_pretrained(
176
+ "ai4bharat/indictrans2-en-indic-1B",
177
+ trust_remote_code=True,
178
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32
179
+ )
180
+ self.en_indic_model.to(self.device)
181
+ self.en_indic_model.eval()
182
+
183
+ logger.info("Loading Indic→EN model from Hugging Face...")
184
+ self.indic_en_tokenizer = AutoTokenizer.from_pretrained(
185
+ "ai4bharat/indictrans2-indic-en-1B",
186
+ trust_remote_code=True
187
+ )
188
+ self.indic_en_model = AutoModelForSeq2SeqLM.from_pretrained(
189
+ "ai4bharat/indictrans2-indic-en-1B",
190
+ trust_remote_code=True,
191
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32
192
+ )
193
+ self.indic_en_model.to(self.device)
194
+ self.indic_en_model.eval()
195
+
196
+ logger.info("✅ IndicTrans2 models loaded successfully.")
197
+ except Exception as e:
198
+ logger.error(f"❌ Failed to load IndicTrans2 models: {str(e)}")
199
+ logger.error("Make sure you have:")
200
+ logger.error("1. Downloaded the IndicTrans2 model files")
201
+ logger.error("2. Set the correct MODEL_PATH in .env")
202
+ logger.error("3. Installed all required dependencies")
203
+ raise
204
+
205
+ async def detect_language(self, text: str) -> Dict[str, Any]:
206
+ """
207
+ Detect language of input text
208
+ """
209
+ await self.load_models()
210
+
211
+ if self.model_type == "mock" or not FASTTEXT_AVAILABLE or self.language_detector == "rule_based":
212
+ detected_lang = self._rule_based_language_detection(text)
213
+ return {
214
+ "language": detected_lang,
215
+ "confidence": 0.85,
216
+ "language_name": SUPPORTED_LANGUAGES.get(detected_lang, detected_lang)
217
+ }
218
+
219
+ try:
220
+ # Use FastText for language detection
221
+ predictions = self.language_detector.predict(text.replace('\n', ' '), k=1)
222
+ detected_lang_code = predictions[0][0].replace('__label__', '')
223
+ confidence = float(predictions[1][0])
224
+
225
+ # Map to our supported languages
226
+ lang_mapping = {
227
+ 'hi': 'hi', 'bn': 'bn', 'gu': 'gu', 'kn': 'kn', 'ml': 'ml',
228
+ 'mr': 'mr', 'or': 'or', 'pa': 'pa', 'ta': 'ta', 'te': 'te',
229
+ 'ur': 'ur', 'as': 'as', 'ne': 'ne', 'sa': 'sa', 'en': 'en'
230
+ }
231
+
232
+ detected_lang = lang_mapping.get(detected_lang_code, 'en')
233
+
234
+ return {
235
+ "language": detected_lang,
236
+ "confidence": confidence,
237
+ "language_name": SUPPORTED_LANGUAGES.get(detected_lang, detected_lang)
238
+ }
239
+
240
+ except Exception as e:
241
+ logger.error(f"Language detection failed: {str(e)}")
242
+ # Fallback to rule-based detection
243
+ detected_lang = self._rule_based_language_detection(text)
244
+ return {
245
+ "language": detected_lang,
246
+ "confidence": 0.50,
247
+ "language_name": SUPPORTED_LANGUAGES.get(detected_lang, detected_lang)
248
+ }
249
+
250
+ def _rule_based_language_detection(self, text: str) -> str:
251
+ """Simple rule-based language detection as fallback"""
252
+ text_lower = text.lower()
253
+
254
+ # Check for English indicators
255
+ english_words = ['the', 'and', 'is', 'in', 'to', 'of', 'for', 'with', 'on', 'at']
256
+ if any(word in text_lower for word in english_words):
257
+ return 'en'
258
+
259
+ # Check for Hindi indicators (Devanagari script)
260
+ if any('\u0900' <= char <= '\u097F' for char in text):
261
+ return 'hi'
262
+
263
+ # Check for Bengali indicators
264
+ if any('\u0980' <= char <= '\u09FF' for char in text):
265
+ return 'bn'
266
+
267
+ # Check for Tamil indicators
268
+ if any('\u0B80' <= char <= '\u0BFF' for char in text):
269
+ return 'ta'
270
+
271
+ # Check for Telugu indicators
272
+ if any('\u0C00' <= char <= '\u0C7F' for char in text):
273
+ return 'te'
274
+
275
+ # Default to English
276
+ return 'en'
277
+
278
+ async def translate(self, text: str, source_lang: str, target_lang: str) -> Dict[str, Any]:
279
+ """
280
+ Translate text from source language to target language using IndicTrans2
281
+ """
282
+ await self.load_models()
283
+
284
+ if self.model_type == "mock" or self.en_indic_model == "mock":
285
+ return self._mock_translate(text, source_lang, target_lang)
286
+
287
+ try:
288
+ # Validate language codes first
289
+ valid_codes = set(self.lang_code_map.keys()) | set(self.lang_name_to_code.keys())
290
+
291
+ if source_lang not in valid_codes:
292
+ logger.error(f"Invalid source language: {source_lang}")
293
+ return self._mock_translate(text, source_lang, target_lang)
294
+
295
+ if target_lang not in valid_codes:
296
+ logger.error(f"Invalid target language: {target_lang}")
297
+ return self._mock_translate(text, source_lang, target_lang)
298
+
299
+ # Convert language names to codes if needed
300
+ src_lang_code = self.lang_name_to_code.get(source_lang, source_lang)
301
+ tgt_lang_code = self.lang_name_to_code.get(target_lang, target_lang)
302
+
303
+ # Validate converted codes
304
+ if src_lang_code not in self.lang_code_map:
305
+ logger.error(f"Invalid source language code after conversion: {src_lang_code}")
306
+ return self._mock_translate(text, source_lang, target_lang)
307
+
308
+ if tgt_lang_code not in self.lang_code_map:
309
+ logger.error(f"Invalid target language code after conversion: {tgt_lang_code}")
310
+ return self._mock_translate(text, source_lang, target_lang)
311
+
312
+ logger.info(f"Converting {source_lang} -> {src_lang_code}, {target_lang} -> {tgt_lang_code}")
313
+
314
+ # Map language codes to IndicTrans2 format
315
+ src_code = self.lang_code_map.get(src_lang_code, src_lang_code)
316
+ tgt_code = self.lang_code_map.get(tgt_lang_code, tgt_lang_code)
317
+
318
+ logger.info(f"Using IndicTrans2 codes: {src_code} -> {tgt_code}")
319
+
320
+ # Choose the right model and tokenizer based on direction
321
+ if src_lang_code == "en" and tgt_lang_code != "en":
322
+ # English to Indic
323
+ model = self.en_indic_model
324
+ tokenizer = self.en_indic_tokenizer
325
+ input_text = f"{src_code} {tgt_code} {text}"
326
+ elif src_lang_code != "en" and tgt_lang_code == "en":
327
+ # Indic to English
328
+ model = self.indic_en_model
329
+ tokenizer = self.indic_en_tokenizer
330
+ input_text = f"{src_code} {tgt_code} {text}"
331
+ else:
332
+ # For Indic to Indic, use English as pivot (not ideal but works)
333
+ if src_lang_code != "en":
334
+ # First translate to English
335
+ intermediate_result = await self.translate(text, src_lang_code, "en")
336
+ intermediate_text = intermediate_result["translated_text"]
337
+ # Then translate from English to target
338
+ return await self.translate(intermediate_text, "en", tgt_lang_code)
339
+ else:
340
+ # Same language, return as is
341
+ return {
342
+ "translated_text": text,
343
+ "source_language": source_lang,
344
+ "target_language": target_lang,
345
+ "model": "IndicTrans2 (No translation needed)",
346
+ "confidence": 1.0
347
+ }
348
+
349
+ # Tokenize and translate
350
+ inputs = tokenizer(input_text, return_tensors="pt", padding=True, truncation=True, max_length=512)
351
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
352
+
353
+ with torch.no_grad():
354
+ outputs = model.generate(**inputs, max_length=512, num_beams=5, do_sample=False)
355
+
356
+ translated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
357
+
358
+ return {
359
+ "translated_text": translated_text,
360
+ "source_language": source_lang,
361
+ "target_language": target_lang,
362
+ "model": "IndicTrans2",
363
+ "confidence": 0.92
364
+ }
365
+
366
+ except Exception as e:
367
+ logger.error(f"Translation failed: {str(e)}")
368
+ # Fallback to mock translation
369
+ return self._mock_translate(text, source_lang, target_lang)
370
+
371
+ def _mock_translate(self, text: str, source_lang: str, target_lang: str) -> Dict[str, Any]:
372
+ """Mock translation for development and fallback"""
373
+ mock_translations = {
374
+ ("en", "hi"): "नमस्ते, यह एक परीक्षण अनुवाद है।",
375
+ ("hi", "en"): "Hello, this is a test translation.",
376
+ ("en", "bn"): "হ্যালো, এটি একটি পরীক্ষা অনুবাদ।",
377
+ ("bn", "en"): "Hello, this is a test translation.",
378
+ ("en", "ta"): "வணக்கம், இது ஒரு சோதனை மொழிபெயர்ப்பு.",
379
+ ("ta", "en"): "Hello, this is a test translation."
380
+ }
381
+
382
+ translated_text = mock_translations.get(
383
+ (source_lang, target_lang),
384
+ f"[MOCK] Translated from {source_lang} to {target_lang}: {text}"
385
+ )
386
+
387
+ return {
388
+ "translated_text": translated_text,
389
+ "source_language": source_lang,
390
+ "target_language": target_lang,
391
+ "model": "Mock (Development)",
392
+ "confidence": 0.75
393
+ }
394
+
395
+ async def batch_translate(self, texts: List[str], source_lang: str, target_lang: str) -> List[Dict[str, Any]]:
396
+ """
397
+ Translate multiple texts in batch for efficiency
398
+ """
399
+ await self.load_models()
400
+
401
+ if self.model_type == "mock" or self.en_indic_model == "mock":
402
+ return [self._mock_translate(text, source_lang, target_lang) for text in texts]
403
+
404
+ try:
405
+ results = []
406
+ for text in texts:
407
+ result = await self.translate(text, source_lang, target_lang)
408
+ result["original_text"] = text
409
+ results.append(result)
410
+
411
+ return results
412
+
413
+ except Exception as e:
414
+ logger.error(f"Batch translation failed: {str(e)}")
415
+ # Fallback to individual mock translations
416
+ return [self._mock_translate(text, source_lang, target_lang) for text in texts]
417
+
418
+ def get_supported_languages(self) -> Dict[str, str]:
419
+ """Get supported languages mapping"""
420
+ return SUPPORTED_LANGUAGES
421
+
422
+ def get_language_codes(self) -> List[str]:
423
+ """Get list of supported language codes"""
424
+ return list(self.lang_code_map.keys())
425
+
426
+ def validate_language_code(self, lang_code: str) -> bool:
427
+ """Validate if a language code is supported"""
428
+ valid_codes = set(self.lang_code_map.keys()) | set(self.lang_name_to_code.keys())
429
+ return lang_code in valid_codes
430
+
431
+ def is_translation_supported(self, source_lang: str, target_lang: str) -> bool:
432
+ """Check if translation between two languages is supported"""
433
+ return source_lang in SUPPORTED_LANGUAGES and target_lang in SUPPORTED_LANGUAGES
434
+
435
+ # Global service instance
436
+ translation_service = TranslationService()
437
+
438
+ async def get_translation_service() -> TranslationService:
439
+ """Dependency injection for FastAPI"""
440
+ return translation_service