Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,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 +56,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",
}

async with LingoDotDevEngine(config) as engine:
# Translate text
text_result = await engine.localize_text(
Expand Down Expand Up @@ -89,6 +90,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 +105,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 +124,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 +142,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 +154,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 +164,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", # Required: 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 @@ -186,7 +189,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 Expand Up @@ -221,8 +224,8 @@ The async version is a drop-in replacement with these changes:
- `whoami()` - Get API account info

### Convenience Methods
- `quick_translate(content, api_key, target_locale, ...)` - One-off translation
- `quick_batch_translate(content, api_key, target_locales, ...)` - Batch translation
- `quick_translate(content, api_key, engine_id, target_locale, ...)` - One-off translation
- `quick_batch_translate(content, api_key, engine_id, target_locales, ...)` - Batch translation
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

## 📄 License

Expand Down
57 changes: 35 additions & 22 deletions src/lingodotdev/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
LingoDotDevEngine implementation for Python SDK - Async version with httpx
"""

# mypy: disable-error-code=unreachable

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 +15,24 @@ class EngineConfig(BaseModel):
"""Configuration for the LingoDotDevEngine"""

api_key: str
api_url: str = "https://engine.lingo.dev"
engine_id: str
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("api_url")
@validator("engine_id", pre=True, always=True)
@classmethod
def validate_engine_id(cls, v: str) -> str:
if not v.strip():
raise ValueError("engine_id is required and cannot be empty")
return v.strip()

@validator("api_url", pre=True, always=True)
@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("/")
Comment thread
coderabbitai[bot] marked this conversation as resolved.


class LocalizationParams(BaseModel):
Expand Down Expand Up @@ -55,6 +60,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 +77,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 Down Expand Up @@ -134,7 +140,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 +149,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 +164,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 +185,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 +194,22 @@ 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/{self.config.engine_id}/localize"
request_data: Dict[str, Any] = {
"params": {"fast": fast},
"sourceLocale": source_locale,
"targetLocale": target_locale,
"data": payload["data"],
"sessionId": self._session_id,
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if payload.get("reference"):
request_data["reference"] = payload["reference"]

Expand Down Expand Up @@ -455,7 +457,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 Down Expand Up @@ -487,10 +490,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 Down Expand Up @@ -537,9 +541,10 @@ async def quick_translate(
cls,
content: Any,
api_key: str,
engine_id: str,
target_locale: str,
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> Any:
"""
Expand All @@ -549,6 +554,7 @@ async def quick_translate(
Args:
content: Text string or dict to translate
api_key: Your Lingo.dev API key
engine_id: Engine ID for the API
target_locale: Target language code (e.g., 'es', 'fr')
source_locale: Source language code (optional, auto-detected if None)
api_url: API endpoint URL
Expand All @@ -562,18 +568,21 @@ 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 = {
"api_key": api_key,
"engine_id": engine_id,
"api_url": api_url,
}

Expand All @@ -596,9 +605,10 @@ async def quick_batch_translate(
cls,
content: Any,
api_key: str,
engine_id: str,
target_locales: List[str],
source_locale: Optional[str] = None,
api_url: str = "https://engine.lingo.dev",
api_url: str = "https://api.lingo.dev",
fast: bool = True,
) -> List[Any]:
"""
Expand All @@ -608,6 +618,7 @@ async def quick_batch_translate(
Args:
content: Text string or dict to translate
api_key: Your Lingo.dev API key
engine_id: Engine ID for the API
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
source_locale: Source language code (optional, auto-detected if None)
api_url: API endpoint URL
Expand All @@ -620,12 +631,14 @@ async def quick_batch_translate(
results = await LingoDotDevEngine.quick_batch_translate(
"Hello world",
"your-api-key",
"your-engine-id",
["es", "fr", "de"]
)
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
"""
config = {
"api_key": api_key,
"engine_id": engine_id,
"api_url": api_url,
}

Expand Down
Loading
Loading