Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,6 @@ A powerful async-first localization engine that supports various content types i
- 🔀 **Concurrent processing** for dramatically faster bulk translations
- 🎯 **Multiple content types**: text, objects, chat messages, and more
- 🌐 **Auto-detection** of source languages
- ⚡ **Fast mode** for quick translations
- 🔧 **Flexible configuration** with progress callbacks
- 📦 **Context manager** support for proper resource management

Expand DownExpand Up@@ -39,6 +38,7 @@ async def main():
result = await LingoDotDevEngine.quick_translate(
"Hello, world!",
api_key="your-api-key",
engine_id="your-engine-id",
target_locale="es"
)
print(result) # "¡Hola, mundo!"
Expand All@@ -55,9 +55,9 @@ from lingodotdev import LingoDotDevEngine
async def main():
config = {
"api_key": "your-api-key",
"api_url": "https://engine.lingo.dev" # Optional, defaults to this
"engine_id": "your-engine-id", # Optional
}

async with LingoDotDevEngine(config) as engine:
# Translate text
text_result = await engine.localize_text(
Expand DownExpand Up@@ -89,6 +89,7 @@ async def batch_example():
results = await LingoDotDevEngine.quick_batch_translate(
"Welcome to our application",
api_key="your-api-key",
engine_id="your-engine-id",
target_locales=["es", "fr", "de", "it"]
)
# Results: ["Bienvenido...", "Bienvenue...", "Willkommen...", "Benvenuto..."]
Expand All@@ -103,7 +104,7 @@ async def progress_example():

large_content = {f"item_{i}": f"Content {i}" for i in range(1000)}

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_object(
large_content,
{"target_locale": "es"},
Expand All@@ -122,7 +123,7 @@ async def chat_example():
{"name": "Charlie", "text": "Great to see you all!"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
translated_chat = await engine.localize_chat(
chat_messages,
{"source_locale": "en", "target_locale": "es"}
Expand All@@ -140,7 +141,7 @@ async def concurrent_objects_example():
{"success": "Account created", "next": "Continue to dashboard"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
results = await engine.batch_localize_objects(
objects,
{"target_locale": "fr"}
Expand All@@ -152,7 +153,7 @@ async def concurrent_objects_example():

```python
async def detection_example():
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
detected = await engine.recognize_locale("Bonjour le monde")
print(detected) # "fr"
```
Expand All@@ -162,7 +163,8 @@ async def detection_example():
```python
config = {
"api_key": "your-api-key", # Required: Your API key
"api_url": "https://engine.lingo.dev", # Optional: API endpoint
"engine_id": "your-engine-id", # Optional: Your engine ID
"api_url": "https://api.lingo.dev", # Optional: API endpoint
"batch_size": 25, # Optional: Items per batch (1-250)
"ideal_batch_item_size": 250 # Optional: Target words per batch (1-2500)
}
Expand All@@ -173,7 +175,6 @@ config = {
### Translation Parameters
- **source_locale**: Source language code (auto-detected if None)
- **target_locale**: Target language code (required)
- **fast**: Enable fast mode for quicker translations
- **reference**: Reference translations for context
- **concurrent**: Process chunks concurrently (faster, but no progress callbacks)

Expand All@@ -186,7 +187,7 @@ config = {
```python
async def error_handling_example():
try:
async with LingoDotDevEngine({"api_key": "invalid-key"}) as engine:
async with LingoDotDevEngine({"api_key": "invalid-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_text("Hello", {"target_locale": "es"})
except ValueError as e:
print(f"Invalid request: {e}")
Expand Down
63 changes: 42 additions & 21 deletions src/lingodotdev/engine.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@
import asyncio
import json
from typing import Any, Callable, Dict, List, Optional
from urllib.parse import urljoin

import httpx
from nanoid import generate
Expand All@@ -18,16 +17,27 @@ class EngineConfig(BaseModel):
"""Configuration for the LingoDotDevEngine"""

api_key: str
api_url: str = "https://engine.lingo.dev"
engine_id: Optional[str] = None
api_url: str = "https://api.lingo.dev"
batch_size: int = Field(default=25, ge=1, le=250)
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)

@validator("engine_id", pre=True, always=True)
@classmethod
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
stripped = v.strip()
if not stripped:
return None
return stripped

@validator("api_url")
@classmethod
def validate_api_url(cls, v: str) -> str:
if not v.startswith(("http://", "https://")):
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
return v
return v.rstrip("/")


class LocalizationParams(BaseModel):
Expand DownExpand Up@@ -55,6 +65,7 @@ def __init__(self, config: Dict[str, Any]):
"""
self.config = EngineConfig(**config)
self._client: Optional[httpx.AsyncClient] = None
self._session_id: str = generate()

async def __aenter__(self):
"""Async context manager entry"""
Expand All@@ -71,7 +82,7 @@ async def _ensure_client(self):
self._client = httpx.AsyncClient(
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.config.api_key}",
"X-API-Key": self.config.api_key,
},
timeout=60.0,
)
Expand DownExpand Up@@ -134,7 +145,6 @@ async def _localize_raw(
"""
await self._ensure_client()
chunked_payload = self._extract_payload_chunks(payload)
workflow_id = generate()

if concurrent and not progress_callback:
# Process chunks concurrently for better performance
Expand All@@ -144,7 +154,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)
tasks.append(task)
Expand All@@ -160,7 +169,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)

Expand All@@ -182,7 +190,6 @@ async def _localize_chunk(
source_locale: Optional[str],
target_locale: str,
payload: Dict[str, Any],
workflow_id: str,
fast: bool,
) -> Dict[str, str]:
"""
Expand All@@ -192,22 +199,24 @@ async def _localize_chunk(
source_locale: Source locale
target_locale: Target locale
payload: Payload containing the chunk to be localized
workflow_id: Workflow ID for tracking
fast: Whether to use fast mode

Returns:
Localized chunk
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/i18n")

request_data = {
"params": {"workflowId": workflow_id, "fast": fast},
"locale": {"source": source_locale, "target": target_locale},
url = f"{self.config.api_url}/process/localize"
request_data: Dict[str, Any] = {
"params": {"fast": fast},
"sourceLocale": source_locale,
"targetLocale": target_locale,
"data": payload["data"],
"sessionId": self._session_id,
}

if self.config.engine_id:
request_data["engineId"] = self.config.engine_id
if payload.get("reference"):
request_data["reference"] = payload["reference"]

Expand DownExpand Up@@ -455,7 +464,8 @@ async def recognize_locale(self, text: str) -> str:

await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/recognize")

url = f"{self.config.api_url}/process/recognize"

try:
response = await self._client.post(url, json={"text": text})
Expand DownExpand Up@@ -487,10 +497,11 @@ async def whoami(self) -> Optional[Dict[str, str]]:
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/whoami")

url = f"{self.config.api_url}/users/me"

try:
response = await self._client.post(url)
response = await self._client.get(url)

if response.is_success:
payload = self._safe_parse_json(response)
Expand DownExpand Up@@ -539,7 +550,8 @@ async def quick_translate(
api_key: str,
target_locale: str,
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> Any:
"""
Expand All@@ -551,6 +563,7 @@ async def quick_translate(
api_key: Your Lingo.dev API key
target_locale: Target language code (e.g., 'es', 'fr')
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -562,20 +575,24 @@ async def quick_translate(
result = await LingoDotDevEngine.quick_translate(
"Hello world",
"your-api-key",
"your-engine-id",
"es"
)

# Translate object
result = await LingoDotDevEngine.quick_translate(
{"greeting": "Hello", "farewell": "Goodbye"},
"your-api-key",
"your-engine-id",
"es"
)
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
params = {
Expand All@@ -598,7 +615,8 @@ async def quick_batch_translate(
api_key: str,
target_locales: List[str],
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> List[Any]:
"""
Expand All@@ -610,6 +628,7 @@ async def quick_batch_translate(
api_key: Your Lingo.dev API key
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -624,10 +643,12 @@ async def quick_batch_translate(
)
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
if isinstance(content, str):
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,6 @@ A powerful async-first localization engine that supports various content types i
- 🔀 **Concurrent processing** for dramatically faster bulk translations
- 🎯 **Multiple content types**: text, objects, chat messages, and more
- 🌐 **Auto-detection** of source languages
- ⚡ **Fast mode** for quick translations
- 🔧 **Flexible configuration** with progress callbacks
- 📦 **Context manager** support for proper resource management

Expand DownExpand Up@@ -39,6 +38,7 @@ async def main():
result = await LingoDotDevEngine.quick_translate(
"Hello, world!",
api_key="your-api-key",
engine_id="your-engine-id",
target_locale="es"
)
print(result) # "¡Hola, mundo!"
Expand All@@ -55,9 +55,9 @@ from lingodotdev import LingoDotDevEngine
async def main():
config = {
"api_key": "your-api-key",
"api_url": "https://engine.lingo.dev" # Optional, defaults to this
"engine_id": "your-engine-id", # Optional
}

async with LingoDotDevEngine(config) as engine:
# Translate text
text_result = await engine.localize_text(
Expand DownExpand Up@@ -89,6 +89,7 @@ async def batch_example():
results = await LingoDotDevEngine.quick_batch_translate(
"Welcome to our application",
api_key="your-api-key",
engine_id="your-engine-id",
target_locales=["es", "fr", "de", "it"]
)
# Results: ["Bienvenido...", "Bienvenue...", "Willkommen...", "Benvenuto..."]
Expand All@@ -103,7 +104,7 @@ async def progress_example():

large_content = {f"item_{i}": f"Content {i}" for i in range(1000)}

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_object(
large_content,
{"target_locale": "es"},
Expand All@@ -122,7 +123,7 @@ async def chat_example():
{"name": "Charlie", "text": "Great to see you all!"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
translated_chat = await engine.localize_chat(
chat_messages,
{"source_locale": "en", "target_locale": "es"}
Expand All@@ -140,7 +141,7 @@ async def concurrent_objects_example():
{"success": "Account created", "next": "Continue to dashboard"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
results = await engine.batch_localize_objects(
objects,
{"target_locale": "fr"}
Expand All@@ -152,7 +153,7 @@ async def concurrent_objects_example():

```python
async def detection_example():
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
detected = await engine.recognize_locale("Bonjour le monde")
print(detected) # "fr"
```
Expand All@@ -162,7 +163,8 @@ async def detection_example():
```python
config = {
"api_key": "your-api-key", # Required: Your API key
"api_url": "https://engine.lingo.dev", # Optional: API endpoint
"engine_id": "your-engine-id", # Optional: Your engine ID
"api_url": "https://api.lingo.dev", # Optional: API endpoint
"batch_size": 25, # Optional: Items per batch (1-250)
"ideal_batch_item_size": 250 # Optional: Target words per batch (1-2500)
}
Expand All@@ -173,7 +175,6 @@ config = {
### Translation Parameters
- **source_locale**: Source language code (auto-detected if None)
- **target_locale**: Target language code (required)
- **fast**: Enable fast mode for quicker translations
- **reference**: Reference translations for context
- **concurrent**: Process chunks concurrently (faster, but no progress callbacks)

Expand All@@ -186,7 +187,7 @@ config = {
```python
async def error_handling_example():
try:
async with LingoDotDevEngine({"api_key": "invalid-key"}) as engine:
async with LingoDotDevEngine({"api_key": "invalid-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_text("Hello", {"target_locale": "es"})
except ValueError as e:
print(f"Invalid request: {e}")
Expand Down
63 changes: 42 additions & 21 deletions src/lingodotdev/engine.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@
import asyncio
import json
from typing import Any, Callable, Dict, List, Optional
from urllib.parse import urljoin

import httpx
from nanoid import generate
Expand All@@ -18,16 +17,27 @@ class EngineConfig(BaseModel):
"""Configuration for the LingoDotDevEngine"""

api_key: str
api_url: str = "https://engine.lingo.dev"
engine_id: Optional[str] = None
api_url: str = "https://api.lingo.dev"
batch_size: int = Field(default=25, ge=1, le=250)
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)

@validator("engine_id", pre=True, always=True)
@classmethod
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
stripped = v.strip()
if not stripped:
return None
return stripped

@validator("api_url")
@classmethod
def validate_api_url(cls, v: str) -> str:
if not v.startswith(("http://", "https://")):
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
return v
return v.rstrip("/")


class LocalizationParams(BaseModel):
Expand DownExpand Up@@ -55,6 +65,7 @@ def __init__(self, config: Dict[str, Any]):
"""
self.config = EngineConfig(**config)
self._client: Optional[httpx.AsyncClient] = None
self._session_id: str = generate()

async def __aenter__(self):
"""Async context manager entry"""
Expand All@@ -71,7 +82,7 @@ async def _ensure_client(self):
self._client = httpx.AsyncClient(
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.config.api_key}",
"X-API-Key": self.config.api_key,
},
timeout=60.0,
)
Expand DownExpand Up@@ -134,7 +145,6 @@ async def _localize_raw(
"""
await self._ensure_client()
chunked_payload = self._extract_payload_chunks(payload)
workflow_id = generate()

if concurrent and not progress_callback:
# Process chunks concurrently for better performance
Expand All@@ -144,7 +154,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)
tasks.append(task)
Expand All@@ -160,7 +169,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)

Expand All@@ -182,7 +190,6 @@ async def _localize_chunk(
source_locale: Optional[str],
target_locale: str,
payload: Dict[str, Any],
workflow_id: str,
fast: bool,
) -> Dict[str, str]:
"""
Expand All@@ -192,22 +199,24 @@ async def _localize_chunk(
source_locale: Source locale
target_locale: Target locale
payload: Payload containing the chunk to be localized
workflow_id: Workflow ID for tracking
fast: Whether to use fast mode

Returns:
Localized chunk
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/i18n")

request_data = {
"params": {"workflowId": workflow_id, "fast": fast},
"locale": {"source": source_locale, "target": target_locale},
url = f"{self.config.api_url}/process/localize"
request_data: Dict[str, Any] = {
"params": {"fast": fast},
"sourceLocale": source_locale,
"targetLocale": target_locale,
"data": payload["data"],
"sessionId": self._session_id,
}

if self.config.engine_id:
request_data["engineId"] = self.config.engine_id
if payload.get("reference"):
request_data["reference"] = payload["reference"]

Expand DownExpand Up@@ -455,7 +464,8 @@ async def recognize_locale(self, text: str) -> str:

await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/recognize")

url = f"{self.config.api_url}/process/recognize"

try:
response = await self._client.post(url, json={"text": text})
Expand DownExpand Up@@ -487,10 +497,11 @@ async def whoami(self) -> Optional[Dict[str, str]]:
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/whoami")

url = f"{self.config.api_url}/users/me"

try:
response = await self._client.post(url)
response = await self._client.get(url)

if response.is_success:
payload = self._safe_parse_json(response)
Expand DownExpand Up@@ -539,7 +550,8 @@ async def quick_translate(
api_key: str,
target_locale: str,
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> Any:
"""
Expand All@@ -551,6 +563,7 @@ async def quick_translate(
api_key: Your Lingo.dev API key
target_locale: Target language code (e.g., 'es', 'fr')
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -562,20 +575,24 @@ async def quick_translate(
result = await LingoDotDevEngine.quick_translate(
"Hello world",
"your-api-key",
"your-engine-id",
"es"
)

# Translate object
result = await LingoDotDevEngine.quick_translate(
{"greeting": "Hello", "farewell": "Goodbye"},
"your-api-key",
"your-engine-id",
"es"
)
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
params = {
Expand All@@ -598,7 +615,8 @@ async def quick_batch_translate(
api_key: str,
target_locales: List[str],
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> List[Any]:
"""
Expand All@@ -610,6 +628,7 @@ async def quick_batch_translate(
api_key: Your Lingo.dev API key
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -624,10 +643,12 @@ async def quick_batch_translate(
)
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
if isinstance(content, str):
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,6 @@ A powerful async-first localization engine that supports various content types i
- 🔀 **Concurrent processing** for dramatically faster bulk translations
- 🎯 **Multiple content types**: text, objects, chat messages, and more
- 🌐 **Auto-detection** of source languages
- ⚡ **Fast mode** for quick translations
- 🔧 **Flexible configuration** with progress callbacks
- 📦 **Context manager** support for proper resource management

Expand DownExpand Up@@ -39,6 +38,7 @@ async def main():
result = await LingoDotDevEngine.quick_translate(
"Hello, world!",
api_key="your-api-key",
engine_id="your-engine-id",
target_locale="es"
)
print(result) # "¡Hola, mundo!"
Expand All@@ -55,9 +55,9 @@ from lingodotdev import LingoDotDevEngine
async def main():
config = {
"api_key": "your-api-key",
"api_url": "https://engine.lingo.dev" # Optional, defaults to this
"engine_id": "your-engine-id", # Optional
}

async with LingoDotDevEngine(config) as engine:
# Translate text
text_result = await engine.localize_text(
Expand DownExpand Up@@ -89,6 +89,7 @@ async def batch_example():
results = await LingoDotDevEngine.quick_batch_translate(
"Welcome to our application",
api_key="your-api-key",
engine_id="your-engine-id",
target_locales=["es", "fr", "de", "it"]
)
# Results: ["Bienvenido...", "Bienvenue...", "Willkommen...", "Benvenuto..."]
Expand All@@ -103,7 +104,7 @@ async def progress_example():

large_content = {f"item_{i}": f"Content {i}" for i in range(1000)}

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_object(
large_content,
{"target_locale": "es"},
Expand All@@ -122,7 +123,7 @@ async def chat_example():
{"name": "Charlie", "text": "Great to see you all!"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
translated_chat = await engine.localize_chat(
chat_messages,
{"source_locale": "en", "target_locale": "es"}
Expand All@@ -140,7 +141,7 @@ async def concurrent_objects_example():
{"success": "Account created", "next": "Continue to dashboard"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
results = await engine.batch_localize_objects(
objects,
{"target_locale": "fr"}
Expand All@@ -152,7 +153,7 @@ async def concurrent_objects_example():

```python
async def detection_example():
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
detected = await engine.recognize_locale("Bonjour le monde")
print(detected) # "fr"
```
Expand All@@ -162,7 +163,8 @@ async def detection_example():
```python
config = {
"api_key": "your-api-key", # Required: Your API key
"api_url": "https://engine.lingo.dev", # Optional: API endpoint
"engine_id": "your-engine-id", # Optional: Your engine ID
"api_url": "https://api.lingo.dev", # Optional: API endpoint
"batch_size": 25, # Optional: Items per batch (1-250)
"ideal_batch_item_size": 250 # Optional: Target words per batch (1-2500)
}
Expand All@@ -173,7 +175,6 @@ config = {
### Translation Parameters
- **source_locale**: Source language code (auto-detected if None)
- **target_locale**: Target language code (required)
- **fast**: Enable fast mode for quicker translations
- **reference**: Reference translations for context
- **concurrent**: Process chunks concurrently (faster, but no progress callbacks)

Expand All@@ -186,7 +187,7 @@ config = {
```python
async def error_handling_example():
try:
async with LingoDotDevEngine({"api_key": "invalid-key"}) as engine:
async with LingoDotDevEngine({"api_key": "invalid-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_text("Hello", {"target_locale": "es"})
except ValueError as e:
print(f"Invalid request: {e}")
Expand Down
63 changes: 42 additions & 21 deletions src/lingodotdev/engine.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@
import asyncio
import json
from typing import Any, Callable, Dict, List, Optional
from urllib.parse import urljoin

import httpx
from nanoid import generate
Expand All@@ -18,16 +17,27 @@ class EngineConfig(BaseModel):
"""Configuration for the LingoDotDevEngine"""

api_key: str
api_url: str = "https://engine.lingo.dev"
engine_id: Optional[str] = None
api_url: str = "https://api.lingo.dev"
batch_size: int = Field(default=25, ge=1, le=250)
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)

@validator("engine_id", pre=True, always=True)
@classmethod
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
stripped = v.strip()
if not stripped:
return None
return stripped

@validator("api_url")
@classmethod
def validate_api_url(cls, v: str) -> str:
if not v.startswith(("http://", "https://")):
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
return v
return v.rstrip("/")


class LocalizationParams(BaseModel):
Expand DownExpand Up@@ -55,6 +65,7 @@ def __init__(self, config: Dict[str, Any]):
"""
self.config = EngineConfig(**config)
self._client: Optional[httpx.AsyncClient] = None
self._session_id: str = generate()

async def __aenter__(self):
"""Async context manager entry"""
Expand All@@ -71,7 +82,7 @@ async def _ensure_client(self):
self._client = httpx.AsyncClient(
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.config.api_key}",
"X-API-Key": self.config.api_key,
},
timeout=60.0,
)
Expand DownExpand Up@@ -134,7 +145,6 @@ async def _localize_raw(
"""
await self._ensure_client()
chunked_payload = self._extract_payload_chunks(payload)
workflow_id = generate()

if concurrent and not progress_callback:
# Process chunks concurrently for better performance
Expand All@@ -144,7 +154,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)
tasks.append(task)
Expand All@@ -160,7 +169,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)

Expand All@@ -182,7 +190,6 @@ async def _localize_chunk(
source_locale: Optional[str],
target_locale: str,
payload: Dict[str, Any],
workflow_id: str,
fast: bool,
) -> Dict[str, str]:
"""
Expand All@@ -192,22 +199,24 @@ async def _localize_chunk(
source_locale: Source locale
target_locale: Target locale
payload: Payload containing the chunk to be localized
workflow_id: Workflow ID for tracking
fast: Whether to use fast mode

Returns:
Localized chunk
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/i18n")

request_data = {
"params": {"workflowId": workflow_id, "fast": fast},
"locale": {"source": source_locale, "target": target_locale},
url = f"{self.config.api_url}/process/localize"
request_data: Dict[str, Any] = {
"params": {"fast": fast},
"sourceLocale": source_locale,
"targetLocale": target_locale,
"data": payload["data"],
"sessionId": self._session_id,
}

if self.config.engine_id:
request_data["engineId"] = self.config.engine_id
if payload.get("reference"):
request_data["reference"] = payload["reference"]

Expand DownExpand Up@@ -455,7 +464,8 @@ async def recognize_locale(self, text: str) -> str:

await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/recognize")

url = f"{self.config.api_url}/process/recognize"

try:
response = await self._client.post(url, json={"text": text})
Expand DownExpand Up@@ -487,10 +497,11 @@ async def whoami(self) -> Optional[Dict[str, str]]:
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/whoami")

url = f"{self.config.api_url}/users/me"

try:
response = await self._client.post(url)
response = await self._client.get(url)

if response.is_success:
payload = self._safe_parse_json(response)
Expand DownExpand Up@@ -539,7 +550,8 @@ async def quick_translate(
api_key: str,
target_locale: str,
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> Any:
"""
Expand All@@ -551,6 +563,7 @@ async def quick_translate(
api_key: Your Lingo.dev API key
target_locale: Target language code (e.g., 'es', 'fr')
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -562,20 +575,24 @@ async def quick_translate(
result = await LingoDotDevEngine.quick_translate(
"Hello world",
"your-api-key",
"your-engine-id",
"es"
)

# Translate object
result = await LingoDotDevEngine.quick_translate(
{"greeting": "Hello", "farewell": "Goodbye"},
"your-api-key",
"your-engine-id",
"es"
)
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
params = {
Expand All@@ -598,7 +615,8 @@ async def quick_batch_translate(
api_key: str,
target_locales: List[str],
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> List[Any]:
"""
Expand All@@ -610,6 +628,7 @@ async def quick_batch_translate(
api_key: Your Lingo.dev API key
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -624,10 +643,12 @@ async def quick_batch_translate(
)
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
if isinstance(content, str):
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,6 @@ A powerful async-first localization engine that supports various content types i
- 🔀 **Concurrent processing** for dramatically faster bulk translations
- 🎯 **Multiple content types**: text, objects, chat messages, and more
- 🌐 **Auto-detection** of source languages
- ⚡ **Fast mode** for quick translations
- 🔧 **Flexible configuration** with progress callbacks
- 📦 **Context manager** support for proper resource management

Expand DownExpand Up@@ -39,6 +38,7 @@ async def main():
result = await LingoDotDevEngine.quick_translate(
"Hello, world!",
api_key="your-api-key",
engine_id="your-engine-id",
target_locale="es"
)
print(result) # "¡Hola, mundo!"
Expand All@@ -55,9 +55,9 @@ from lingodotdev import LingoDotDevEngine
async def main():
config = {
"api_key": "your-api-key",
"api_url": "https://engine.lingo.dev" # Optional, defaults to this
"engine_id": "your-engine-id", # Optional
}

async with LingoDotDevEngine(config) as engine:
# Translate text
text_result = await engine.localize_text(
Expand DownExpand Up@@ -89,6 +89,7 @@ async def batch_example():
results = await LingoDotDevEngine.quick_batch_translate(
"Welcome to our application",
api_key="your-api-key",
engine_id="your-engine-id",
target_locales=["es", "fr", "de", "it"]
)
# Results: ["Bienvenido...", "Bienvenue...", "Willkommen...", "Benvenuto..."]
Expand All@@ -103,7 +104,7 @@ async def progress_example():

large_content = {f"item_{i}": f"Content {i}" for i in range(1000)}

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_object(
large_content,
{"target_locale": "es"},
Expand All@@ -122,7 +123,7 @@ async def chat_example():
{"name": "Charlie", "text": "Great to see you all!"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
translated_chat = await engine.localize_chat(
chat_messages,
{"source_locale": "en", "target_locale": "es"}
Expand All@@ -140,7 +141,7 @@ async def concurrent_objects_example():
{"success": "Account created", "next": "Continue to dashboard"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
results = await engine.batch_localize_objects(
objects,
{"target_locale": "fr"}
Expand All@@ -152,7 +153,7 @@ async def concurrent_objects_example():

```python
async def detection_example():
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
detected = await engine.recognize_locale("Bonjour le monde")
print(detected) # "fr"
```
Expand All@@ -162,7 +163,8 @@ async def detection_example():
```python
config = {
"api_key": "your-api-key", # Required: Your API key
"api_url": "https://engine.lingo.dev", # Optional: API endpoint
"engine_id": "your-engine-id", # Optional: Your engine ID
"api_url": "https://api.lingo.dev", # Optional: API endpoint
"batch_size": 25, # Optional: Items per batch (1-250)
"ideal_batch_item_size": 250 # Optional: Target words per batch (1-2500)
}
Expand All@@ -173,7 +175,6 @@ config = {
### Translation Parameters
- **source_locale**: Source language code (auto-detected if None)
- **target_locale**: Target language code (required)
- **fast**: Enable fast mode for quicker translations
- **reference**: Reference translations for context
- **concurrent**: Process chunks concurrently (faster, but no progress callbacks)

Expand All@@ -186,7 +187,7 @@ config = {
```python
async def error_handling_example():
try:
async with LingoDotDevEngine({"api_key": "invalid-key"}) as engine:
async with LingoDotDevEngine({"api_key": "invalid-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_text("Hello", {"target_locale": "es"})
except ValueError as e:
print(f"Invalid request: {e}")
Expand Down
63 changes: 42 additions & 21 deletions src/lingodotdev/engine.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@
import asyncio
import json
from typing import Any, Callable, Dict, List, Optional
from urllib.parse import urljoin

import httpx
from nanoid import generate
Expand All@@ -18,16 +17,27 @@ class EngineConfig(BaseModel):
"""Configuration for the LingoDotDevEngine"""

api_key: str
api_url: str = "https://engine.lingo.dev"
engine_id: Optional[str] = None
api_url: str = "https://api.lingo.dev"
batch_size: int = Field(default=25, ge=1, le=250)
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)

@validator("engine_id", pre=True, always=True)
@classmethod
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
stripped = v.strip()
if not stripped:
return None
return stripped

@validator("api_url")
@classmethod
def validate_api_url(cls, v: str) -> str:
if not v.startswith(("http://", "https://")):
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
return v
return v.rstrip("/")


class LocalizationParams(BaseModel):
Expand DownExpand Up@@ -55,6 +65,7 @@ def __init__(self, config: Dict[str, Any]):
"""
self.config = EngineConfig(**config)
self._client: Optional[httpx.AsyncClient] = None
self._session_id: str = generate()

async def __aenter__(self):
"""Async context manager entry"""
Expand All@@ -71,7 +82,7 @@ async def _ensure_client(self):
self._client = httpx.AsyncClient(
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.config.api_key}",
"X-API-Key": self.config.api_key,
},
timeout=60.0,
)
Expand DownExpand Up@@ -134,7 +145,6 @@ async def _localize_raw(
"""
await self._ensure_client()
chunked_payload = self._extract_payload_chunks(payload)
workflow_id = generate()

if concurrent and not progress_callback:
# Process chunks concurrently for better performance
Expand All@@ -144,7 +154,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)
tasks.append(task)
Expand All@@ -160,7 +169,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)

Expand All@@ -182,7 +190,6 @@ async def _localize_chunk(
source_locale: Optional[str],
target_locale: str,
payload: Dict[str, Any],
workflow_id: str,
fast: bool,
) -> Dict[str, str]:
"""
Expand All@@ -192,22 +199,24 @@ async def _localize_chunk(
source_locale: Source locale
target_locale: Target locale
payload: Payload containing the chunk to be localized
workflow_id: Workflow ID for tracking
fast: Whether to use fast mode

Returns:
Localized chunk
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/i18n")

request_data = {
"params": {"workflowId": workflow_id, "fast": fast},
"locale": {"source": source_locale, "target": target_locale},
url = f"{self.config.api_url}/process/localize"
request_data: Dict[str, Any] = {
"params": {"fast": fast},
"sourceLocale": source_locale,
"targetLocale": target_locale,
"data": payload["data"],
"sessionId": self._session_id,
}

if self.config.engine_id:
request_data["engineId"] = self.config.engine_id
if payload.get("reference"):
request_data["reference"] = payload["reference"]

Expand DownExpand Up@@ -455,7 +464,8 @@ async def recognize_locale(self, text: str) -> str:

await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/recognize")

url = f"{self.config.api_url}/process/recognize"

try:
response = await self._client.post(url, json={"text": text})
Expand DownExpand Up@@ -487,10 +497,11 @@ async def whoami(self) -> Optional[Dict[str, str]]:
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/whoami")

url = f"{self.config.api_url}/users/me"

try:
response = await self._client.post(url)
response = await self._client.get(url)

if response.is_success:
payload = self._safe_parse_json(response)
Expand DownExpand Up@@ -539,7 +550,8 @@ async def quick_translate(
api_key: str,
target_locale: str,
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> Any:
"""
Expand All@@ -551,6 +563,7 @@ async def quick_translate(
api_key: Your Lingo.dev API key
target_locale: Target language code (e.g., 'es', 'fr')
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -562,20 +575,24 @@ async def quick_translate(
result = await LingoDotDevEngine.quick_translate(
"Hello world",
"your-api-key",
"your-engine-id",
"es"
)

# Translate object
result = await LingoDotDevEngine.quick_translate(
{"greeting": "Hello", "farewell": "Goodbye"},
"your-api-key",
"your-engine-id",
"es"
)
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
params = {
Expand All@@ -598,7 +615,8 @@ async def quick_batch_translate(
api_key: str,
target_locales: List[str],
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> List[Any]:
"""
Expand All@@ -610,6 +628,7 @@ async def quick_batch_translate(
api_key: Your Lingo.dev API key
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -624,10 +643,12 @@ async def quick_batch_translate(
)
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
if isinstance(content, str):
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,6 @@ A powerful async-first localization engine that supports various content types i
- 🔀 **Concurrent processing** for dramatically faster bulk translations
- 🎯 **Multiple content types**: text, objects, chat messages, and more
- 🌐 **Auto-detection** of source languages
- ⚡ **Fast mode** for quick translations
- 🔧 **Flexible configuration** with progress callbacks
- 📦 **Context manager** support for proper resource management

Expand DownExpand Up@@ -39,6 +38,7 @@ async def main():
result = await LingoDotDevEngine.quick_translate(
"Hello, world!",
api_key="your-api-key",
engine_id="your-engine-id",
target_locale="es"
)
print(result) # "¡Hola, mundo!"
Expand All@@ -55,9 +55,9 @@ from lingodotdev import LingoDotDevEngine
async def main():
config = {
"api_key": "your-api-key",
"api_url": "https://engine.lingo.dev" # Optional, defaults to this
"engine_id": "your-engine-id", # Optional
}

async with LingoDotDevEngine(config) as engine:
# Translate text
text_result = await engine.localize_text(
Expand DownExpand Up@@ -89,6 +89,7 @@ async def batch_example():
results = await LingoDotDevEngine.quick_batch_translate(
"Welcome to our application",
api_key="your-api-key",
engine_id="your-engine-id",
target_locales=["es", "fr", "de", "it"]
)
# Results: ["Bienvenido...", "Bienvenue...", "Willkommen...", "Benvenuto..."]
Expand All@@ -103,7 +104,7 @@ async def progress_example():

large_content = {f"item_{i}": f"Content {i}" for i in range(1000)}

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_object(
large_content,
{"target_locale": "es"},
Expand All@@ -122,7 +123,7 @@ async def chat_example():
{"name": "Charlie", "text": "Great to see you all!"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
translated_chat = await engine.localize_chat(
chat_messages,
{"source_locale": "en", "target_locale": "es"}
Expand All@@ -140,7 +141,7 @@ async def concurrent_objects_example():
{"success": "Account created", "next": "Continue to dashboard"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
results = await engine.batch_localize_objects(
objects,
{"target_locale": "fr"}
Expand All@@ -152,7 +153,7 @@ async def concurrent_objects_example():

```python
async def detection_example():
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
detected = await engine.recognize_locale("Bonjour le monde")
print(detected) # "fr"
```
Expand All@@ -162,7 +163,8 @@ async def detection_example():
```python
config = {
"api_key": "your-api-key", # Required: Your API key
"api_url": "https://engine.lingo.dev", # Optional: API endpoint
"engine_id": "your-engine-id", # Optional: Your engine ID
"api_url": "https://api.lingo.dev", # Optional: API endpoint
"batch_size": 25, # Optional: Items per batch (1-250)
"ideal_batch_item_size": 250 # Optional: Target words per batch (1-2500)
}
Expand All@@ -173,7 +175,6 @@ config = {
### Translation Parameters
- **source_locale**: Source language code (auto-detected if None)
- **target_locale**: Target language code (required)
- **fast**: Enable fast mode for quicker translations
- **reference**: Reference translations for context
- **concurrent**: Process chunks concurrently (faster, but no progress callbacks)

Expand All@@ -186,7 +187,7 @@ config = {
```python
async def error_handling_example():
try:
async with LingoDotDevEngine({"api_key": "invalid-key"}) as engine:
async with LingoDotDevEngine({"api_key": "invalid-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_text("Hello", {"target_locale": "es"})
except ValueError as e:
print(f"Invalid request: {e}")
Expand Down
63 changes: 42 additions & 21 deletions src/lingodotdev/engine.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@
import asyncio
import json
from typing import Any, Callable, Dict, List, Optional
from urllib.parse import urljoin

import httpx
from nanoid import generate
Expand All@@ -18,16 +17,27 @@ class EngineConfig(BaseModel):
"""Configuration for the LingoDotDevEngine"""

api_key: str
api_url: str = "https://engine.lingo.dev"
engine_id: Optional[str] = None
api_url: str = "https://api.lingo.dev"
batch_size: int = Field(default=25, ge=1, le=250)
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)

@validator("engine_id", pre=True, always=True)
@classmethod
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
stripped = v.strip()
if not stripped:
return None
return stripped

@validator("api_url")
@classmethod
def validate_api_url(cls, v: str) -> str:
if not v.startswith(("http://", "https://")):
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
return v
return v.rstrip("/")


class LocalizationParams(BaseModel):
Expand DownExpand Up@@ -55,6 +65,7 @@ def __init__(self, config: Dict[str, Any]):
"""
self.config = EngineConfig(**config)
self._client: Optional[httpx.AsyncClient] = None
self._session_id: str = generate()

async def __aenter__(self):
"""Async context manager entry"""
Expand All@@ -71,7 +82,7 @@ async def _ensure_client(self):
self._client = httpx.AsyncClient(
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.config.api_key}",
"X-API-Key": self.config.api_key,
},
timeout=60.0,
)
Expand DownExpand Up@@ -134,7 +145,6 @@ async def _localize_raw(
"""
await self._ensure_client()
chunked_payload = self._extract_payload_chunks(payload)
workflow_id = generate()

if concurrent and not progress_callback:
# Process chunks concurrently for better performance
Expand All@@ -144,7 +154,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)
tasks.append(task)
Expand All@@ -160,7 +169,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)

Expand All@@ -182,7 +190,6 @@ async def _localize_chunk(
source_locale: Optional[str],
target_locale: str,
payload: Dict[str, Any],
workflow_id: str,
fast: bool,
) -> Dict[str, str]:
"""
Expand All@@ -192,22 +199,24 @@ async def _localize_chunk(
source_locale: Source locale
target_locale: Target locale
payload: Payload containing the chunk to be localized
workflow_id: Workflow ID for tracking
fast: Whether to use fast mode

Returns:
Localized chunk
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/i18n")

request_data = {
"params": {"workflowId": workflow_id, "fast": fast},
"locale": {"source": source_locale, "target": target_locale},
url = f"{self.config.api_url}/process/localize"
request_data: Dict[str, Any] = {
"params": {"fast": fast},
"sourceLocale": source_locale,
"targetLocale": target_locale,
"data": payload["data"],
"sessionId": self._session_id,
}

if self.config.engine_id:
request_data["engineId"] = self.config.engine_id
if payload.get("reference"):
request_data["reference"] = payload["reference"]

Expand DownExpand Up@@ -455,7 +464,8 @@ async def recognize_locale(self, text: str) -> str:

await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/recognize")

url = f"{self.config.api_url}/process/recognize"

try:
response = await self._client.post(url, json={"text": text})
Expand DownExpand Up@@ -487,10 +497,11 @@ async def whoami(self) -> Optional[Dict[str, str]]:
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/whoami")

url = f"{self.config.api_url}/users/me"

try:
response = await self._client.post(url)
response = await self._client.get(url)

if response.is_success:
payload = self._safe_parse_json(response)
Expand DownExpand Up@@ -539,7 +550,8 @@ async def quick_translate(
api_key: str,
target_locale: str,
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> Any:
"""
Expand All@@ -551,6 +563,7 @@ async def quick_translate(
api_key: Your Lingo.dev API key
target_locale: Target language code (e.g., 'es', 'fr')
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -562,20 +575,24 @@ async def quick_translate(
result = await LingoDotDevEngine.quick_translate(
"Hello world",
"your-api-key",
"your-engine-id",
"es"
)

# Translate object
result = await LingoDotDevEngine.quick_translate(
{"greeting": "Hello", "farewell": "Goodbye"},
"your-api-key",
"your-engine-id",
"es"
)
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
params = {
Expand All@@ -598,7 +615,8 @@ async def quick_batch_translate(
api_key: str,
target_locales: List[str],
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> List[Any]:
"""
Expand All@@ -610,6 +628,7 @@ async def quick_batch_translate(
api_key: Your Lingo.dev API key
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -624,10 +643,12 @@ async def quick_batch_translate(
)
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
if isinstance(content, str):
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,6 @@ A powerful async-first localization engine that supports various content types i
- 🔀 **Concurrent processing** for dramatically faster bulk translations
- 🎯 **Multiple content types**: text, objects, chat messages, and more
- 🌐 **Auto-detection** of source languages
- ⚡ **Fast mode** for quick translations
- 🔧 **Flexible configuration** with progress callbacks
- 📦 **Context manager** support for proper resource management

Expand DownExpand Up@@ -39,6 +38,7 @@ async def main():
result = await LingoDotDevEngine.quick_translate(
"Hello, world!",
api_key="your-api-key",
engine_id="your-engine-id",
target_locale="es"
)
print(result) # "¡Hola, mundo!"
Expand All@@ -55,9 +55,9 @@ from lingodotdev import LingoDotDevEngine
async def main():
config = {
"api_key": "your-api-key",
"api_url": "https://engine.lingo.dev" # Optional, defaults to this
"engine_id": "your-engine-id", # Optional
}

async with LingoDotDevEngine(config) as engine:
# Translate text
text_result = await engine.localize_text(
Expand DownExpand Up@@ -89,6 +89,7 @@ async def batch_example():
results = await LingoDotDevEngine.quick_batch_translate(
"Welcome to our application",
api_key="your-api-key",
engine_id="your-engine-id",
target_locales=["es", "fr", "de", "it"]
)
# Results: ["Bienvenido...", "Bienvenue...", "Willkommen...", "Benvenuto..."]
Expand All@@ -103,7 +104,7 @@ async def progress_example():

large_content = {f"item_{i}": f"Content {i}" for i in range(1000)}

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_object(
large_content,
{"target_locale": "es"},
Expand All@@ -122,7 +123,7 @@ async def chat_example():
{"name": "Charlie", "text": "Great to see you all!"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
translated_chat = await engine.localize_chat(
chat_messages,
{"source_locale": "en", "target_locale": "es"}
Expand All@@ -140,7 +141,7 @@ async def concurrent_objects_example():
{"success": "Account created", "next": "Continue to dashboard"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
results = await engine.batch_localize_objects(
objects,
{"target_locale": "fr"}
Expand All@@ -152,7 +153,7 @@ async def concurrent_objects_example():

```python
async def detection_example():
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
detected = await engine.recognize_locale("Bonjour le monde")
print(detected) # "fr"
```
Expand All@@ -162,7 +163,8 @@ async def detection_example():
```python
config = {
"api_key": "your-api-key", # Required: Your API key
"api_url": "https://engine.lingo.dev", # Optional: API endpoint
"engine_id": "your-engine-id", # Optional: Your engine ID
"api_url": "https://api.lingo.dev", # Optional: API endpoint
"batch_size": 25, # Optional: Items per batch (1-250)
"ideal_batch_item_size": 250 # Optional: Target words per batch (1-2500)
}
Expand All@@ -173,7 +175,6 @@ config = {
### Translation Parameters
- **source_locale**: Source language code (auto-detected if None)
- **target_locale**: Target language code (required)
- **fast**: Enable fast mode for quicker translations
- **reference**: Reference translations for context
- **concurrent**: Process chunks concurrently (faster, but no progress callbacks)

Expand All@@ -186,7 +187,7 @@ config = {
```python
async def error_handling_example():
try:
async with LingoDotDevEngine({"api_key": "invalid-key"}) as engine:
async with LingoDotDevEngine({"api_key": "invalid-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_text("Hello", {"target_locale": "es"})
except ValueError as e:
print(f"Invalid request: {e}")
Expand Down
63 changes: 42 additions & 21 deletions src/lingodotdev/engine.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@
import asyncio
import json
from typing import Any, Callable, Dict, List, Optional
from urllib.parse import urljoin

import httpx
from nanoid import generate
Expand All@@ -18,16 +17,27 @@ class EngineConfig(BaseModel):
"""Configuration for the LingoDotDevEngine"""

api_key: str
api_url: str = "https://engine.lingo.dev"
engine_id: Optional[str] = None
api_url: str = "https://api.lingo.dev"
batch_size: int = Field(default=25, ge=1, le=250)
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)

@validator("engine_id", pre=True, always=True)
@classmethod
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
stripped = v.strip()
if not stripped:
return None
return stripped

@validator("api_url")
@classmethod
def validate_api_url(cls, v: str) -> str:
if not v.startswith(("http://", "https://")):
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
return v
return v.rstrip("/")


class LocalizationParams(BaseModel):
Expand DownExpand Up@@ -55,6 +65,7 @@ def __init__(self, config: Dict[str, Any]):
"""
self.config = EngineConfig(**config)
self._client: Optional[httpx.AsyncClient] = None
self._session_id: str = generate()

async def __aenter__(self):
"""Async context manager entry"""
Expand All@@ -71,7 +82,7 @@ async def _ensure_client(self):
self._client = httpx.AsyncClient(
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.config.api_key}",
"X-API-Key": self.config.api_key,
},
timeout=60.0,
)
Expand DownExpand Up@@ -134,7 +145,6 @@ async def _localize_raw(
"""
await self._ensure_client()
chunked_payload = self._extract_payload_chunks(payload)
workflow_id = generate()

if concurrent and not progress_callback:
# Process chunks concurrently for better performance
Expand All@@ -144,7 +154,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)
tasks.append(task)
Expand All@@ -160,7 +169,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)

Expand All@@ -182,7 +190,6 @@ async def _localize_chunk(
source_locale: Optional[str],
target_locale: str,
payload: Dict[str, Any],
workflow_id: str,
fast: bool,
) -> Dict[str, str]:
"""
Expand All@@ -192,22 +199,24 @@ async def _localize_chunk(
source_locale: Source locale
target_locale: Target locale
payload: Payload containing the chunk to be localized
workflow_id: Workflow ID for tracking
fast: Whether to use fast mode

Returns:
Localized chunk
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/i18n")

request_data = {
"params": {"workflowId": workflow_id, "fast": fast},
"locale": {"source": source_locale, "target": target_locale},
url = f"{self.config.api_url}/process/localize"
request_data: Dict[str, Any] = {
"params": {"fast": fast},
"sourceLocale": source_locale,
"targetLocale": target_locale,
"data": payload["data"],
"sessionId": self._session_id,
}

if self.config.engine_id:
request_data["engineId"] = self.config.engine_id
if payload.get("reference"):
request_data["reference"] = payload["reference"]

Expand DownExpand Up@@ -455,7 +464,8 @@ async def recognize_locale(self, text: str) -> str:

await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/recognize")

url = f"{self.config.api_url}/process/recognize"

try:
response = await self._client.post(url, json={"text": text})
Expand DownExpand Up@@ -487,10 +497,11 @@ async def whoami(self) -> Optional[Dict[str, str]]:
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/whoami")

url = f"{self.config.api_url}/users/me"

try:
response = await self._client.post(url)
response = await self._client.get(url)

if response.is_success:
payload = self._safe_parse_json(response)
Expand DownExpand Up@@ -539,7 +550,8 @@ async def quick_translate(
api_key: str,
target_locale: str,
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> Any:
"""
Expand All@@ -551,6 +563,7 @@ async def quick_translate(
api_key: Your Lingo.dev API key
target_locale: Target language code (e.g., 'es', 'fr')
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -562,20 +575,24 @@ async def quick_translate(
result = await LingoDotDevEngine.quick_translate(
"Hello world",
"your-api-key",
"your-engine-id",
"es"
)

# Translate object
result = await LingoDotDevEngine.quick_translate(
{"greeting": "Hello", "farewell": "Goodbye"},
"your-api-key",
"your-engine-id",
"es"
)
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
params = {
Expand All@@ -598,7 +615,8 @@ async def quick_batch_translate(
api_key: str,
target_locales: List[str],
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> List[Any]:
"""
Expand All@@ -610,6 +628,7 @@ async def quick_batch_translate(
api_key: Your Lingo.dev API key
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -624,10 +643,12 @@ async def quick_batch_translate(
)
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
if isinstance(content, str):
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,6 @@ A powerful async-first localization engine that supports various content types i
- 🔀 **Concurrent processing** for dramatically faster bulk translations
- 🎯 **Multiple content types**: text, objects, chat messages, and more
- 🌐 **Auto-detection** of source languages
- ⚡ **Fast mode** for quick translations
- 🔧 **Flexible configuration** with progress callbacks
- 📦 **Context manager** support for proper resource management

Expand DownExpand Up@@ -39,6 +38,7 @@ async def main():
result = await LingoDotDevEngine.quick_translate(
"Hello, world!",
api_key="your-api-key",
engine_id="your-engine-id",
target_locale="es"
)
print(result) # "¡Hola, mundo!"
Expand All@@ -55,9 +55,9 @@ from lingodotdev import LingoDotDevEngine
async def main():
config = {
"api_key": "your-api-key",
"api_url": "https://engine.lingo.dev" # Optional, defaults to this
"engine_id": "your-engine-id", # Optional
}

async with LingoDotDevEngine(config) as engine:
# Translate text
text_result = await engine.localize_text(
Expand DownExpand Up@@ -89,6 +89,7 @@ async def batch_example():
results = await LingoDotDevEngine.quick_batch_translate(
"Welcome to our application",
api_key="your-api-key",
engine_id="your-engine-id",
target_locales=["es", "fr", "de", "it"]
)
# Results: ["Bienvenido...", "Bienvenue...", "Willkommen...", "Benvenuto..."]
Expand All@@ -103,7 +104,7 @@ async def progress_example():

large_content = {f"item_{i}": f"Content {i}" for i in range(1000)}

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_object(
large_content,
{"target_locale": "es"},
Expand All@@ -122,7 +123,7 @@ async def chat_example():
{"name": "Charlie", "text": "Great to see you all!"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
translated_chat = await engine.localize_chat(
chat_messages,
{"source_locale": "en", "target_locale": "es"}
Expand All@@ -140,7 +141,7 @@ async def concurrent_objects_example():
{"success": "Account created", "next": "Continue to dashboard"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
results = await engine.batch_localize_objects(
objects,
{"target_locale": "fr"}
Expand All@@ -152,7 +153,7 @@ async def concurrent_objects_example():

```python
async def detection_example():
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
detected = await engine.recognize_locale("Bonjour le monde")
print(detected) # "fr"
```
Expand All@@ -162,7 +163,8 @@ async def detection_example():
```python
config = {
"api_key": "your-api-key", # Required: Your API key
"api_url": "https://engine.lingo.dev", # Optional: API endpoint
"engine_id": "your-engine-id", # Optional: Your engine ID
"api_url": "https://api.lingo.dev", # Optional: API endpoint
"batch_size": 25, # Optional: Items per batch (1-250)
"ideal_batch_item_size": 250 # Optional: Target words per batch (1-2500)
}
Expand All@@ -173,7 +175,6 @@ config = {
### Translation Parameters
- **source_locale**: Source language code (auto-detected if None)
- **target_locale**: Target language code (required)
- **fast**: Enable fast mode for quicker translations
- **reference**: Reference translations for context
- **concurrent**: Process chunks concurrently (faster, but no progress callbacks)

Expand All@@ -186,7 +187,7 @@ config = {
```python
async def error_handling_example():
try:
async with LingoDotDevEngine({"api_key": "invalid-key"}) as engine:
async with LingoDotDevEngine({"api_key": "invalid-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_text("Hello", {"target_locale": "es"})
except ValueError as e:
print(f"Invalid request: {e}")
Expand Down
63 changes: 42 additions & 21 deletions src/lingodotdev/engine.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@
import asyncio
import json
from typing import Any, Callable, Dict, List, Optional
from urllib.parse import urljoin

import httpx
from nanoid import generate
Expand All@@ -18,16 +17,27 @@ class EngineConfig(BaseModel):
"""Configuration for the LingoDotDevEngine"""

api_key: str
api_url: str = "https://engine.lingo.dev"
engine_id: Optional[str] = None
api_url: str = "https://api.lingo.dev"
batch_size: int = Field(default=25, ge=1, le=250)
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)

@validator("engine_id", pre=True, always=True)
@classmethod
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
stripped = v.strip()
if not stripped:
return None
return stripped

@validator("api_url")
@classmethod
def validate_api_url(cls, v: str) -> str:
if not v.startswith(("http://", "https://")):
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
return v
return v.rstrip("/")


class LocalizationParams(BaseModel):
Expand DownExpand Up@@ -55,6 +65,7 @@ def __init__(self, config: Dict[str, Any]):
"""
self.config = EngineConfig(**config)
self._client: Optional[httpx.AsyncClient] = None
self._session_id: str = generate()

async def __aenter__(self):
"""Async context manager entry"""
Expand All@@ -71,7 +82,7 @@ async def _ensure_client(self):
self._client = httpx.AsyncClient(
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.config.api_key}",
"X-API-Key": self.config.api_key,
},
timeout=60.0,
)
Expand DownExpand Up@@ -134,7 +145,6 @@ async def _localize_raw(
"""
await self._ensure_client()
chunked_payload = self._extract_payload_chunks(payload)
workflow_id = generate()

if concurrent and not progress_callback:
# Process chunks concurrently for better performance
Expand All@@ -144,7 +154,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)
tasks.append(task)
Expand All@@ -160,7 +169,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)

Expand All@@ -182,7 +190,6 @@ async def _localize_chunk(
source_locale: Optional[str],
target_locale: str,
payload: Dict[str, Any],
workflow_id: str,
fast: bool,
) -> Dict[str, str]:
"""
Expand All@@ -192,22 +199,24 @@ async def _localize_chunk(
source_locale: Source locale
target_locale: Target locale
payload: Payload containing the chunk to be localized
workflow_id: Workflow ID for tracking
fast: Whether to use fast mode

Returns:
Localized chunk
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/i18n")

request_data = {
"params": {"workflowId": workflow_id, "fast": fast},
"locale": {"source": source_locale, "target": target_locale},
url = f"{self.config.api_url}/process/localize"
request_data: Dict[str, Any] = {
"params": {"fast": fast},
"sourceLocale": source_locale,
"targetLocale": target_locale,
"data": payload["data"],
"sessionId": self._session_id,
}

if self.config.engine_id:
request_data["engineId"] = self.config.engine_id
if payload.get("reference"):
request_data["reference"] = payload["reference"]

Expand DownExpand Up@@ -455,7 +464,8 @@ async def recognize_locale(self, text: str) -> str:

await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/recognize")

url = f"{self.config.api_url}/process/recognize"

try:
response = await self._client.post(url, json={"text": text})
Expand DownExpand Up@@ -487,10 +497,11 @@ async def whoami(self) -> Optional[Dict[str, str]]:
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/whoami")

url = f"{self.config.api_url}/users/me"

try:
response = await self._client.post(url)
response = await self._client.get(url)

if response.is_success:
payload = self._safe_parse_json(response)
Expand DownExpand Up@@ -539,7 +550,8 @@ async def quick_translate(
api_key: str,
target_locale: str,
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> Any:
"""
Expand All@@ -551,6 +563,7 @@ async def quick_translate(
api_key: Your Lingo.dev API key
target_locale: Target language code (e.g., 'es', 'fr')
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -562,20 +575,24 @@ async def quick_translate(
result = await LingoDotDevEngine.quick_translate(
"Hello world",
"your-api-key",
"your-engine-id",
"es"
)

# Translate object
result = await LingoDotDevEngine.quick_translate(
{"greeting": "Hello", "farewell": "Goodbye"},
"your-api-key",
"your-engine-id",
"es"
)
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
params = {
Expand All@@ -598,7 +615,8 @@ async def quick_batch_translate(
api_key: str,
target_locales: List[str],
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> List[Any]:
"""
Expand All@@ -610,6 +628,7 @@ async def quick_batch_translate(
api_key: Your Lingo.dev API key
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -624,10 +643,12 @@ async def quick_batch_translate(
)
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
if isinstance(content, str):
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,7 +8,6 @@ A powerful async-first localization engine that supports various content types i
- 🔀 **Concurrent processing** for dramatically faster bulk translations
- 🎯 **Multiple content types**: text, objects, chat messages, and more
- 🌐 **Auto-detection** of source languages
- ⚡ **Fast mode** for quick translations
- 🔧 **Flexible configuration** with progress callbacks
- 📦 **Context manager** support for proper resource management

Expand DownExpand Up@@ -39,6 +38,7 @@ async def main():
result = await LingoDotDevEngine.quick_translate(
"Hello, world!",
api_key="your-api-key",
engine_id="your-engine-id",
target_locale="es"
)
print(result) # "¡Hola, mundo!"
Expand All@@ -55,9 +55,9 @@ from lingodotdev import LingoDotDevEngine
async def main():
config = {
"api_key": "your-api-key",
"api_url": "https://engine.lingo.dev" # Optional, defaults to this
"engine_id": "your-engine-id", # Optional
}

async with LingoDotDevEngine(config) as engine:
# Translate text
text_result = await engine.localize_text(
Expand DownExpand Up@@ -89,6 +89,7 @@ async def batch_example():
results = await LingoDotDevEngine.quick_batch_translate(
"Welcome to our application",
api_key="your-api-key",
engine_id="your-engine-id",
target_locales=["es", "fr", "de", "it"]
)
# Results: ["Bienvenido...", "Bienvenue...", "Willkommen...", "Benvenuto..."]
Expand All@@ -103,7 +104,7 @@ async def progress_example():

large_content = {f"item_{i}": f"Content {i}" for i in range(1000)}

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_object(
large_content,
{"target_locale": "es"},
Expand All@@ -122,7 +123,7 @@ async def chat_example():
{"name": "Charlie", "text": "Great to see you all!"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
translated_chat = await engine.localize_chat(
chat_messages,
{"source_locale": "en", "target_locale": "es"}
Expand All@@ -140,7 +141,7 @@ async def concurrent_objects_example():
{"success": "Account created", "next": "Continue to dashboard"}
]

async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
results = await engine.batch_localize_objects(
objects,
{"target_locale": "fr"}
Expand All@@ -152,7 +153,7 @@ async def concurrent_objects_example():

```python
async def detection_example():
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
detected = await engine.recognize_locale("Bonjour le monde")
print(detected) # "fr"
```
Expand All@@ -162,7 +163,8 @@ async def detection_example():
```python
config = {
"api_key": "your-api-key", # Required: Your API key
"api_url": "https://engine.lingo.dev", # Optional: API endpoint
"engine_id": "your-engine-id", # Optional: Your engine ID
"api_url": "https://api.lingo.dev", # Optional: API endpoint
"batch_size": 25, # Optional: Items per batch (1-250)
"ideal_batch_item_size": 250 # Optional: Target words per batch (1-2500)
}
Expand All@@ -173,7 +175,6 @@ config = {
### Translation Parameters
- **source_locale**: Source language code (auto-detected if None)
- **target_locale**: Target language code (required)
- **fast**: Enable fast mode for quicker translations
- **reference**: Reference translations for context
- **concurrent**: Process chunks concurrently (faster, but no progress callbacks)

Expand All@@ -186,7 +187,7 @@ config = {
```python
async def error_handling_example():
try:
async with LingoDotDevEngine({"api_key": "invalid-key"}) as engine:
async with LingoDotDevEngine({"api_key": "invalid-key", "engine_id": "your-engine-id"}) as engine:
result = await engine.localize_text("Hello", {"target_locale": "es"})
except ValueError as e:
print(f"Invalid request: {e}")
Expand Down
63 changes: 42 additions & 21 deletions src/lingodotdev/engine.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,6 @@
import asyncio
import json
from typing import Any, Callable, Dict, List, Optional
from urllib.parse import urljoin

import httpx
from nanoid import generate
Expand All@@ -18,16 +17,27 @@ class EngineConfig(BaseModel):
"""Configuration for the LingoDotDevEngine"""

api_key: str
api_url: str = "https://engine.lingo.dev"
engine_id: Optional[str] = None
api_url: str = "https://api.lingo.dev"
batch_size: int = Field(default=25, ge=1, le=250)
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)

@validator("engine_id", pre=True, always=True)
@classmethod
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
stripped = v.strip()
if not stripped:
return None
return stripped

@validator("api_url")
@classmethod
def validate_api_url(cls, v: str) -> str:
if not v.startswith(("http://", "https://")):
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
return v
return v.rstrip("/")


class LocalizationParams(BaseModel):
Expand DownExpand Up@@ -55,6 +65,7 @@ def __init__(self, config: Dict[str, Any]):
"""
self.config = EngineConfig(**config)
self._client: Optional[httpx.AsyncClient] = None
self._session_id: str = generate()

async def __aenter__(self):
"""Async context manager entry"""
Expand All@@ -71,7 +82,7 @@ async def _ensure_client(self):
self._client = httpx.AsyncClient(
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.config.api_key}",
"X-API-Key": self.config.api_key,
},
timeout=60.0,
)
Expand DownExpand Up@@ -134,7 +145,6 @@ async def _localize_raw(
"""
await self._ensure_client()
chunked_payload = self._extract_payload_chunks(payload)
workflow_id = generate()

if concurrent and not progress_callback:
# Process chunks concurrently for better performance
Expand All@@ -144,7 +154,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)
tasks.append(task)
Expand All@@ -160,7 +169,6 @@ async def _localize_raw(
params.source_locale,
params.target_locale,
{"data": chunk, "reference": params.reference},
workflow_id,
params.fast or False,
)

Expand All@@ -182,7 +190,6 @@ async def _localize_chunk(
source_locale: Optional[str],
target_locale: str,
payload: Dict[str, Any],
workflow_id: str,
fast: bool,
) -> Dict[str, str]:
"""
Expand All@@ -192,22 +199,24 @@ async def _localize_chunk(
source_locale: Source locale
target_locale: Target locale
payload: Payload containing the chunk to be localized
workflow_id: Workflow ID for tracking
fast: Whether to use fast mode

Returns:
Localized chunk
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/i18n")

request_data = {
"params": {"workflowId": workflow_id, "fast": fast},
"locale": {"source": source_locale, "target": target_locale},
url = f"{self.config.api_url}/process/localize"
request_data: Dict[str, Any] = {
"params": {"fast": fast},
"sourceLocale": source_locale,
"targetLocale": target_locale,
"data": payload["data"],
"sessionId": self._session_id,
}

if self.config.engine_id:
request_data["engineId"] = self.config.engine_id
if payload.get("reference"):
request_data["reference"] = payload["reference"]

Expand DownExpand Up@@ -455,7 +464,8 @@ async def recognize_locale(self, text: str) -> str:

await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/recognize")

url = f"{self.config.api_url}/process/recognize"

try:
response = await self._client.post(url, json={"text": text})
Expand DownExpand Up@@ -487,10 +497,11 @@ async def whoami(self) -> Optional[Dict[str, str]]:
"""
await self._ensure_client()
assert self._client is not None # Type guard for mypy
url = urljoin(self.config.api_url, "/whoami")

url = f"{self.config.api_url}/users/me"

try:
response = await self._client.post(url)
response = await self._client.get(url)

if response.is_success:
payload = self._safe_parse_json(response)
Expand DownExpand Up@@ -539,7 +550,8 @@ async def quick_translate(
api_key: str,
target_locale: str,
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> Any:
"""
Expand All@@ -551,6 +563,7 @@ async def quick_translate(
api_key: Your Lingo.dev API key
target_locale: Target language code (e.g., 'es', 'fr')
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -562,20 +575,24 @@ async def quick_translate(
result = await LingoDotDevEngine.quick_translate(
"Hello world",
"your-api-key",
"your-engine-id",
"es"
)

# Translate object
result = await LingoDotDevEngine.quick_translate(
{"greeting": "Hello", "farewell": "Goodbye"},
"your-api-key",
"your-engine-id",
"es"
)
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
params = {
Expand All@@ -598,7 +615,8 @@ async def quick_batch_translate(
api_key: str,
target_locales: List[str],
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
engine_id: Optional[str] = None,
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> List[Any]:
"""
Expand All@@ -610,6 +628,7 @@ async def quick_batch_translate(
api_key: Your Lingo.dev API key
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
source_locale: Source language code (optional, auto-detected if None)
engine_id: Optional engine ID for the API
api_url: API endpoint URL
fast: Enable fast mode for quicker translations

Expand All@@ -624,10 +643,12 @@ async def quick_batch_translate(
)
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
"""
config = {
config: Dict[str, Any] = {
"api_key": api_key,
"api_url": api_url,
}
if engine_id:
config["engine_id"] = engine_id

async with cls(config) as engine:
if isinstance(content, str):
Expand Down
Loading