File size: 5,269 Bytes
04dc214
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
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
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
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
"""
Translation middleware for FastAPI
Handles language detection and response translation
"""

from fastapi import Request
from typing import Optional
import re

class TranslationMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        if scope["type"] == "http":
            request = Request(scope, receive)

            # Detect language from various sources
            lang = self.detect_language(request)

            # Add language to request state
            if "state" not in scope:
                scope["state"] = {}
            scope["state"]["lang"] = lang

        await self.app(scope, receive, send)

    def detect_language(self, request: Request) -> str:
        """
        Detect language from request
        Priority: 1. User preference (via API) 2. Query param 3. Header 4. Cookie 5. Default
        """
        # 1. Check for user preference in path (e.g., /api/i18n/translate?lang=ur)
        lang = request.query_params.get("lang")
        if lang:
            validated_lang = self.validate_language(lang)
            if validated_lang != "en":  # Only return if it's a valid non-default language
                return validated_lang

        # 2. Check for language in request path patterns that might indicate preferred language
        # This could come from frontend routing or user preferences stored in session

        # 3. Check Accept-Language header
        accept_language = request.headers.get("Accept-Language")
        if accept_language:
            # Parse Accept-Language header (e.g., "en-US,en;q=0.9,ur;q=0.8")
            languages = self.parse_accept_language(accept_language)
            if languages:
                for lang_code in languages:
                    validated_lang = self.validate_language(lang_code)
                    if validated_lang != "en":  # Prioritize non-English languages if user specified them
                        return validated_lang

        # 4. Check cookie (could be set by frontend based on user selection)
        lang = request.cookies.get("NEXT_LOCALE") or request.cookies.get("locale")
        if lang:
            validated_lang = self.validate_language(lang)
            if validated_lang != "en":
                return validated_lang

        # 5. Default to English
        return "en"

    def parse_accept_language(self, header: str) -> list:
        """Parse Accept-Language header and return sorted list of languages by priority"""
        languages = []
        # Split by comma and sort by quality value (q=x.x)
        items = []
        for item in header.split(","):
            parts = item.strip().split(";")
            lang = parts[0].strip()
            quality = 1.0  # Default quality

            # Extract quality value if present (e.g., "ur;q=0.8")
            for part in parts[1:]:
                if part.strip().startswith("q="):
                    try:
                        quality = float(part.strip()[2:])
                    except ValueError:
                        pass
            items.append((lang, quality))

        # Sort by quality (highest first)
        items.sort(key=lambda x: x[1], reverse=True)
        for lang, _ in items:
            # Extract language code (e.g., "en-US" -> "en")
            lang_code = lang.split("-")[0]
            if lang_code not in languages:  # Avoid duplicates
                languages.append(lang_code)

        return languages

    def validate_language(self, lang: str) -> str:
        """Validate and normalize language code"""
        # List of supported languages
        supported = ['en', 'ur', 'ur-PK', 'ar', 'es', 'fr', 'de', 'zh', 'hi', 'pt', 'ru', 'ja']

        if lang in supported:
            return lang

        # Handle common variations and aliases
        lang_aliases = {
            'urdu': 'ur',
            'roman-urdu': 'ur-PK',
            'roman_urdu': 'ur-PK',
            'ur-pk': 'ur-PK',
            'uk': 'ur',
            'pakistani': 'ur-PK',
            'english': 'en',
            'spanish': 'es',
            'french': 'fr',
            'german': 'de',
            'chinese': 'zh',
            'japanese': 'ja',
            'hindi': 'hi',
            'portuguese': 'pt',
            'russian': 'ru',
            'arabic': 'ar',
        }

        lang_lower = lang.lower().replace('_', '-').strip()

        if lang_lower in supported:
            return lang_lower

        if lang_lower in lang_aliases:
            return lang_aliases[lang_lower]

        # Handle locale variants (e.g., ur_PK -> ur-PK)
        if '_' in lang_lower:
            locale_variant = lang_lower.replace('_', '-')
            if locale_variant in supported:
                return locale_variant

        # Try to find partial match
        for supported_lang in supported:
            if lang_lower == supported_lang.lower():
                return supported_lang

        # Default to English
        return "en"

def get_request_language(request: Request) -> str:
    """Helper function to get language from request state"""
    if hasattr(request.state, 'lang'):
        return getattr(request.state, "lang", "en")
    # Fallback for cases where middleware didn't set the language
    return "en"