From 531e56d3f691dbd4a3f35390ba46df16c9e190fc Mon Sep 17 00:00:00 2001 From: David Turnbull Date: Thu, 18 Sep 2025 12:13:26 +1000 Subject: [PATCH 1/8] docs: revise Python SDK docs --- src/lingodotdev/__init__.py | 7 +- src/lingodotdev/engine.py | 459 ++++++++++++++++++++++++++---------- 2 files changed, 337 insertions(+), 129 deletions(-) diff --git a/src/lingodotdev/__init__.py b/src/lingodotdev/__init__.py index ce0d641..e1c61d3 100644 --- a/src/lingodotdev/__init__.py +++ b/src/lingodotdev/__init__.py @@ -1,8 +1,9 @@ """ -Lingo.dev Python SDK +Public entry point for the Lingo.dev Python SDK. -A powerful localization engine that supports various content types including -plain text, objects, chat sequences, and HTML documents. +The package exposes :class:`~lingodotdev.engine.LingoDotDevEngine`, the +asynchronous client used to access the Lingo.dev localization API. Refer to the +engine module for detailed usage guidance. """ __version__ = "1.3.0" diff --git a/src/lingodotdev/engine.py b/src/lingodotdev/engine.py index 293bead..f16d5fd 100644 --- a/src/lingodotdev/engine.py +++ b/src/lingodotdev/engine.py @@ -1,5 +1,8 @@ """ -LingoDotDevEngine implementation for Python SDK - Async version with httpx +Async client implementation for the Lingo.dev localization service. + +This module houses :class:`LingoDotDevEngine` alongside supporting data models +used to validate configuration and localization parameters. """ # mypy: disable-error-code=unreachable @@ -14,7 +17,17 @@ class EngineConfig(BaseModel): - """Configuration for the LingoDotDevEngine""" + """Stores and validates runtime configuration for :class:`LingoDotDevEngine`. + + Attributes: + api_key: Secret token used to authenticate with the Lingo.dev API. + api_url: Base endpoint for the localization engine. Defaults to + ``https://engine.lingo.dev``. + batch_size: Maximum number of top-level entries to send in a single + localization request (between 1 and 250 inclusive). + ideal_batch_item_size: Target word count per request before payloads are + split into multiple batches (between 1 and 2500 inclusive). + """ api_key: str api_url: str = "https://engine.lingo.dev" @@ -30,7 +43,20 @@ def validate_api_url(cls, v: str) -> str: class LocalizationParams(BaseModel): - """Parameters for localization requests""" + """Request parameters accepted by localization operations. + + These values are serialized directly into API requests after validation. + + Attributes: + source_locale: Optional BCP 47 language code representing the source + language. When omitted the API attempts automatic detection. + target_locale: Required BCP 47 language code for the desired translation + target. + fast: Optional flag that enables the service's low-latency translation + mode at the cost of some quality safeguards. + reference: Optional nested mapping of existing translations that + provides additional context to the engine. + """ source_locale: Optional[str] = None target_locale: str @@ -39,33 +65,48 @@ class LocalizationParams(BaseModel): class LingoDotDevEngine: - """ - LingoDotDevEngine class for interacting with the LingoDotDev API - A powerful localization engine that supports various content types including - plain text, objects, chat sequences, and HTML documents. + """Asynchronous client for the Lingo.dev localization API. + + The engine manages an :class:`httpx.AsyncClient`, handles chunking and + batching of content, and exposes helper coroutines for translating strings, + structured objects, and chat transcripts. Instances can be reused or + managed via an async context manager:: + + async with LingoDotDevEngine({"api_key": "..."}) as engine: + await engine.localize_text("Hello", {"target_locale": "es"}) + + All localization methods are ``async`` and must be awaited. """ def __init__(self, config: Dict[str, Any]): - """ - Create a new LingoDotDevEngine instance + """Instantiate the engine with configuration data. Args: - config: Configuration options for the Engine + config: Mapping of values understood by + :class:`EngineConfig`. At minimum an ``api_key`` entry must + be supplied. + + Raises: + ValueError: If the supplied configuration fails validation. """ self.config = EngineConfig(**config) self._client: Optional[httpx.AsyncClient] = None async def __aenter__(self): - """Async context manager entry""" + """Open the HTTP session when entering an async context. + + Returns: + LingoDotDevEngine: The active engine instance. + """ await self._ensure_client() return self async def __aexit__(self, exc_type, exc_val, exc_tb): - """Async context manager exit""" + """Release resources acquired during the async context.""" await self.close() async def _ensure_client(self): - """Ensure the httpx client is initialized""" + """Create an :class:`httpx.AsyncClient` if one is not already available.""" if self._client is None or self._client.is_closed: self._client = httpx.AsyncClient( headers={ @@ -76,7 +117,7 @@ async def _ensure_client(self): ) async def close(self): - """Close the httpx client""" + """Close the HTTP client if it has been created.""" if self._client and not self._client.is_closed: await self._client.aclose() @@ -89,17 +130,28 @@ async def _localize_raw( ] = None, concurrent: bool = False, ) -> Dict[str, str]: - """ - Localize content using the Lingo.dev API + """Submit a localization request for the provided payload. + + The payload is split into chunks based on the configured limits and the + resulting pieces are localized sequentially or concurrently. Sequential + mode enables progress reporting while concurrent mode maximizes + throughput. Args: - payload: The content to be localized - params: Localization parameters - progress_callback: Optional callback function to report progress (0-100) - concurrent: Whether to process chunks concurrently (faster but no progress tracking) + payload: Mapping or structured content to be localized. + params: Pre-validated localization parameters. + progress_callback: Optional callable invoked after each chunk is + localized. Receives the percentage completed, the source chunk, + and the localized chunk. + concurrent: When ``True`` and no ``progress_callback`` is supplied, + chunks are processed concurrently. Returns: - Localized content + Dictionary containing the merged localized chunks. + + Raises: + RuntimeError: If the API responds with an error. + ValueError: If the API rejects the payload. """ await self._ensure_client() chunked_payload = self._extract_payload_chunks(payload) @@ -154,18 +206,24 @@ async def _localize_chunk( workflow_id: str, fast: bool, ) -> Dict[str, str]: - """ - Localize a single chunk of content + """Translate a single payload chunk through the ``/i18n`` endpoint. Args: - 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 + source_locale: Optional source locale used for the request. + target_locale: Target locale requested from the API. + payload: Dictionary containing the chunk under the ``data`` key and + optional ``reference`` metadata. + workflow_id: Identifier shared across chunks that belong to the + same localization workflow. + fast: Whether to request the service's fast translation mode. Returns: - Localized chunk + A dictionary representing the localized chunk returned by the API. + + Raises: + RuntimeError: If the API responds with an error status or signals a + streaming error. + ValueError: If the API reports an invalid request (HTTP 400). """ await self._ensure_client() assert self._client is not None # Type guard for mypy @@ -208,14 +266,17 @@ async def _localize_chunk( raise RuntimeError(f"Request failed: {str(e)}") def _extract_payload_chunks(self, payload: Dict[str, Any]) -> List[Dict[str, Any]]: - """ - Extract payload chunks based on the ideal chunk size + """Split a payload into smaller dictionaries based on configured limits. + + The method iterates through the payload in insertion order, grouping + keys until the number of words or items would exceed the configured + thresholds. Each chunk is suitable for sending directly to the API. Args: - payload: The payload to be chunked + payload: Mapping to be divided into localization chunks. Returns: - An array of payload chunks + List of dictionaries representing individual request chunks. """ result = [] current_chunk = {} @@ -240,14 +301,13 @@ def _extract_payload_chunks(self, payload: Dict[str, Any]) -> List[Dict[str, Any return result def _count_words_in_record(self, payload: Any) -> int: - """ - Count words in a record or array + """Recursively count whitespace-delimited words within a payload. Args: - payload: The payload to count words in + payload: String, mapping, list, or other primitive values to count. Returns: - The total number of words + Total number of words discovered within string values. """ if isinstance(payload, list): return sum(self._count_words_in_record(item) for item in payload) @@ -267,20 +327,47 @@ async def localize_object( ] = None, concurrent: bool = False, ) -> Dict[str, Any]: - """ - Localize a typical Python dictionary + """Localize every string value contained in a mapping. Args: - obj: The object to be localized (strings will be extracted and translated) - params: Localization parameters: - - source_locale: The source language code (e.g., 'en') - - target_locale: The target language code (e.g., 'es') - - fast: Optional boolean to enable fast mode - progress_callback: Optional callback function to report progress (0-100) - concurrent: Whether to process chunks concurrently (faster but no progress tracking) + obj: Mapping whose string leaves should be translated. + params: Dictionary of options accepted by + :class:`LocalizationParams`. + progress_callback: Optional callable invoked with progress updates + (0-100) alongside the source and localized chunks. If provided, + leave ``concurrent`` as ``False`` (the default) because progress + updates are unavailable in concurrent mode. + concurrent: When ``True`` the payload chunks are processed + concurrently and no progress updates are emitted. Returns: - A new object with the same structure but localized string values + A dictionary mirroring ``obj`` with localized string values. + + Raises: + RuntimeError: If the API responds with an error. + ValueError: If the API rejects the request. + + Examples: + .. code-block:: python + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + localized = await engine.localize_object( + {"title": "Hello"}, + {"target_locale": "es"}, + concurrent=True, + ) + # localized -> {"title": "Hola"} (example output) + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + def on_progress(percent, *_): + print(f"Progress: {percent}%") + + localized = await engine.localize_object( + {"welcome": "Hello", "farewell": "Goodbye"}, + {"source_locale": "en", "target_locale": "fr"}, + progress_callback=on_progress, + ) + # localized -> {"welcome": "Bonjour", "farewell": "Au revoir"} (example output) """ localization_params = LocalizationParams(**params) return await self._localize_raw( @@ -293,19 +380,40 @@ async def localize_text( params: Dict[str, Any], progress_callback: Optional[Callable[[int], None]] = None, ) -> str: - """ - Localize a single text string + """Localize a single text string. Args: - text: The text string to be localized - params: Localization parameters: - - source_locale: The source language code (e.g., 'en') - - target_locale: The target language code (e.g., 'es') - - fast: Optional boolean to enable fast mode - progress_callback: Optional callback function to report progress (0-100) + text: The text to translate. + params: Dictionary of options accepted by + :class:`LocalizationParams`. + progress_callback: Optional callable receiving the percentage + complete (0-100). Returns: - The localized text string + The localized text string. + + Raises: + RuntimeError: If the API responds with an error. + ValueError: If the API rejects the request. + + Examples: + .. code-block:: python + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + greeting = await engine.localize_text( + "Hello", {"target_locale": "de"} + ) + # greeting -> "Hallo" (example output) + + progress_updates = [] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + farewell = await engine.localize_text( + "Goodbye", + {"source_locale": "en", "target_locale": "it"}, + progress_callback=progress_updates.append, + ) + # farewell -> "Arrivederci" (example output) """ localization_params = LocalizationParams(**params) @@ -322,18 +430,41 @@ def wrapped_progress_callback( return response.get("text", "") async def batch_localize_text(self, text: str, params: Dict[str, Any]) -> List[str]: - """ - Localize a text string to multiple target locales + """Localize a single text string into multiple target locales. Args: - text: The text string to be localized - params: Localization parameters: - - source_locale: The source language code (e.g., 'en') - - target_locales: A list of target language codes (e.g., ['es', 'fr']) - - fast: Optional boolean to enable fast mode + text: The text string to translate. + params: Dictionary of options accepted by + :class:`LocalizationParams` plus a ``target_locales`` list. Returns: - A list of localized text strings + List of localized strings ordered to match ``target_locales``. + + Raises: + ValueError: If ``target_locales`` is missing or the API rejects a + request. + RuntimeError: If the API responds with an error. + + Examples: + .. code-block:: python + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Welcome", + {"target_locales": ["es", "fr"]}, + ) + # variants -> ["Bienvenido", "Bienvenue"] (example output) + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Checkout", + { + "source_locale": "en", + "target_locales": ["pt-BR", "it"], + "fast": True, + }, + ) + # variants -> ["Finalizar compra", "Pagamento"] (example output) """ if "target_locales" not in params: raise ValueError("target_locales is required") @@ -365,19 +496,48 @@ async def localize_chat( params: Dict[str, Any], progress_callback: Optional[Callable[[int], None]] = None, ) -> List[Dict[str, str]]: - """ - Localize a chat sequence while preserving speaker names + """Localize a chat transcript while preserving speaker metadata. Args: - chat: Array of chat messages, each with 'name' and 'text' properties - params: Localization parameters: - - source_locale: The source language code (e.g., 'en') - - target_locale: The target language code (e.g., 'es') - - fast: Optional boolean to enable fast mode - progress_callback: Optional callback function to report progress (0-100) + chat: Sequence of chat messages. Each item must include ``name`` and + ``text`` keys. + params: Dictionary of options accepted by + :class:`LocalizationParams`. + progress_callback: Optional callable receiving percentage updates + (0-100) while the transcript is localized. Returns: - Array of localized chat messages with preserved structure + List of localized chat messages in the same order as ``chat``. If + the API omits chat data an empty list is returned. + + Raises: + ValueError: If any message in ``chat`` omits the required keys. + RuntimeError: If the API responds with an error. + + Examples: + .. code-block:: python + + chat = [ + {"name": "Alice", "text": "Hello"}, + {"name": "Bob", "text": "Goodbye"}, + ] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, + {"target_locale": "es"}, + ) + # translated -> [{"name": "Alice", "text": "Hola"}, ...] (example output) + + updates = [] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, + {"source_locale": "en", "target_locale": "de"}, + progress_callback=updates.append, + ) + # translated -> [{"name": "Alice", "text": "Hallo"}, ...] (example output) """ # Validate chat format for message in chat: @@ -406,14 +566,18 @@ def wrapped_progress_callback( return [] async def recognize_locale(self, text: str) -> str: - """ - Detect the language of a given text + """Detect the language of the supplied text via the ``/recognize`` endpoint. Args: - text: The text to analyze + text: Non-empty string to analyse. Returns: - A locale code (e.g., 'en', 'es', 'fr') + Locale code reported by the API (for example ``"en"``) or an empty + string when the service cannot determine a locale. + + Raises: + ValueError: If ``text`` is empty or only whitespace. + RuntimeError: If the request fails or the API reports an error. """ if not text or not text.strip(): raise ValueError("Text cannot be empty") @@ -442,11 +606,15 @@ async def recognize_locale(self, text: str) -> str: raise RuntimeError(f"Request failed: {str(e)}") async def whoami(self) -> Optional[Dict[str, str]]: - """ - Get information about the current API key + """Retrieve account metadata associated with the current API key. Returns: - Dictionary with 'email' and 'id' keys, or None if not authenticated + Dictionary containing ``email`` and ``id`` keys when available, or + ``None`` if the key is unauthenticated or a recoverable network + error occurs. + + Raises: + RuntimeError: If the service reports a server-side error. """ await self._ensure_client() assert self._client is not None # Type guard for mypy @@ -477,15 +645,19 @@ async def whoami(self) -> Optional[Dict[str, str]]: async def batch_localize_objects( self, objects: List[Dict[str, Any]], params: Dict[str, Any] ) -> List[Dict[str, Any]]: - """ - Localize multiple objects concurrently + """Localize multiple mapping objects concurrently. Args: - objects: List of objects to localize - params: Localization parameters + objects: List of objects whose string values should be translated. + params: Dictionary of options accepted by + :class:`LocalizationParams`, shared across all objects. Returns: - List of localized objects + List of localized objects preserving the order of ``objects``. + + Raises: + RuntimeError: If the API responds with an error. + ValueError: If the API rejects a request. """ tasks = [] for obj in objects: @@ -504,35 +676,50 @@ async def quick_translate( api_url: str = "https://engine.lingo.dev", fast: bool = True, ) -> Any: - """ - Quick one-off translation without manual context management. - Automatically handles the async context manager. + """Translate content without managing an engine instance manually. + + The helper opens an :class:`LingoDotDevEngine` using the supplied + configuration, performs the translation, and automatically closes the + underlying HTTP client. Args: - content: Text string or dict to 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) - api_url: API endpoint URL - fast: Enable fast mode for quicker translations + content: Text string or mapping to translate. The returned value + matches the type of ``content``. + api_key: Lingo.dev API key to authenticate the request. + target_locale: Target language code for the translation. + source_locale: Optional source language code. When omitted the API + may attempt to detect it. + api_url: Lingo.dev engine base URL. Defaults to + ``"https://engine.lingo.dev"``. + fast: Whether to enable the service's fast translation mode. Returns: - Translated content (same type as input) - - Example: - # Translate text - result = await LingoDotDevEngine.quick_translate( - "Hello world", - "your-api-key", - "es" - ) + Translated content with the same type as ``content``. - # Translate object - result = await LingoDotDevEngine.quick_translate( - {"greeting": "Hello", "farewell": "Goodbye"}, - "your-api-key", - "es" - ) + Raises: + ValueError: If ``content`` is not a string or dictionary, or if the + service rejects the request. + RuntimeError: If the API indicates a failure or the request cannot + be completed. + + Examples: + .. code-block:: python + + greeting = await LingoDotDevEngine.quick_translate( + "Hello world", + api_key="api-key", + target_locale="es", + ) + # greeting -> "Hola mundo" (example output) + + landing_page = await LingoDotDevEngine.quick_translate( + {"headline": "Hello", "cta": "Buy now"}, + api_key="api-key", + target_locale="de", + source_locale="en", + fast=False, + ) + # landing_page -> {"headline": "Hallo", "cta": "Jetzt kaufen"} (example output) """ config = { "api_key": api_key, @@ -563,28 +750,48 @@ async def quick_batch_translate( api_url: str = "https://engine.lingo.dev", fast: bool = True, ) -> List[Any]: - """ - Quick batch translation to multiple target locales. - Automatically handles the async context manager. + """Translate content into multiple locales without manual setup. Args: - content: Text string or dict to 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) - api_url: API endpoint URL - fast: Enable fast mode for quicker translations + content: Text string or mapping to translate for each locale. + api_key: Lingo.dev API key to authenticate requests. + target_locales: List of locale codes. Results maintain this order. + source_locale: Optional source language code. When omitted the API + may attempt to detect it. + api_url: Lingo.dev engine base URL. Defaults to + ``"https://engine.lingo.dev"``. + fast: Whether to enable the service's fast translation mode. Returns: - List of translated content (one for each target locale) + List of translated content, one entry per ``target_locales`` item. - Example: - results = await LingoDotDevEngine.quick_batch_translate( - "Hello world", - "your-api-key", - ["es", "fr", "de"] - ) - # Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"] + Raises: + ValueError: If ``content`` is not a string or dictionary, or if a + request is rejected by the API. + RuntimeError: If the API indicates a failure or the request cannot + be completed. + + Examples: + .. code-block:: python + + variants = await LingoDotDevEngine.quick_batch_translate( + "Hello world", + api_key="api-key", + target_locales=["es", "fr"], + ) + # variants -> ["Hola mundo", "Bonjour le monde"] (example output) + + localized_objects = await LingoDotDevEngine.quick_batch_translate( + {"success": "Saved", "error": "Failed"}, + api_key="api-key", + target_locales=["pt-BR", "it"], + source_locale="en", + fast=False, + ) + # localized_objects -> [ + # {"success": "Salvo", "error": "Falhou"}, + # {"success": "Salvato", "error": "Non riuscito"}, + # ] (example output) """ config = { "api_key": api_key, From bbab3f3d63dec25e329536a02215b671b206744f Mon Sep 17 00:00:00 2001 From: David Turnbull Date: Fri, 19 Sep 2025 10:41:39 +1000 Subject: [PATCH 2/8] feat: docs generation --- .github/workflows/update-docs.yml | 43 +++ docs/api.md | 583 ++++++++++++++++++++++++++++++ pydoc-markdown.yml | 21 ++ pyproject.toml | 3 +- 4 files changed, 649 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/update-docs.yml create mode 100644 docs/api.md create mode 100644 pydoc-markdown.yml diff --git a/.github/workflows/update-docs.yml b/.github/workflows/update-docs.yml new file mode 100644 index 0000000..d4a5ceb --- /dev/null +++ b/.github/workflows/update-docs.yml @@ -0,0 +1,43 @@ +name: Regenerate API Docs + +on: + push: + branches: + - main + paths: + - 'src/**' + - 'pyproject.toml' + workflow_dispatch: + +permissions: + contents: write + +jobs: + build-docs: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install .[dev] + + - name: Regenerate Markdown API docs + run: | + mkdir -p docs + pydoc-markdown + + - name: Commit updated docs + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore(docs): update generated API docs" + file_pattern: docs/*.md diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..a4d525b --- /dev/null +++ b/docs/api.md @@ -0,0 +1,583 @@ +# Table of Contents + +* [lingodotdev.engine](#lingodotdev.engine) + * [EngineConfig](#lingodotdev.engine.EngineConfig) + * [LocalizationParams](#lingodotdev.engine.LocalizationParams) + * [LingoDotDevEngine](#lingodotdev.engine.LingoDotDevEngine) + * [\_\_init\_\_](#lingodotdev.engine.LingoDotDevEngine.__init__) + * [\_\_aenter\_\_](#lingodotdev.engine.LingoDotDevEngine.__aenter__) + * [\_\_aexit\_\_](#lingodotdev.engine.LingoDotDevEngine.__aexit__) + * [close](#lingodotdev.engine.LingoDotDevEngine.close) + * [localize\_object](#lingodotdev.engine.LingoDotDevEngine.localize_object) + * [localize\_text](#lingodotdev.engine.LingoDotDevEngine.localize_text) + * [batch\_localize\_text](#lingodotdev.engine.LingoDotDevEngine.batch_localize_text) + * [localize\_chat](#lingodotdev.engine.LingoDotDevEngine.localize_chat) + * [recognize\_locale](#lingodotdev.engine.LingoDotDevEngine.recognize_locale) + * [whoami](#lingodotdev.engine.LingoDotDevEngine.whoami) + * [batch\_localize\_objects](#lingodotdev.engine.LingoDotDevEngine.batch_localize_objects) + * [quick\_translate](#lingodotdev.engine.LingoDotDevEngine.quick_translate) + * [quick\_batch\_translate](#lingodotdev.engine.LingoDotDevEngine.quick_batch_translate) + + + +# lingodotdev.engine + +Async client implementation for the Lingo.dev localization service. + +This module houses :class:`LingoDotDevEngine` alongside supporting data models +used to validate configuration and localization parameters. + + + +## EngineConfig Objects + +```python +class EngineConfig(BaseModel) +``` + +Stores and validates runtime configuration for :class:`LingoDotDevEngine`. + +**Attributes**: + +- `api_key` - Secret token used to authenticate with the Lingo.dev API. +- `api_url` - Base endpoint for the localization engine. Defaults to + ``https://engine.lingo.dev``. +- `batch_size` - Maximum number of top-level entries to send in a single + localization request (between 1 and 250 inclusive). +- `ideal_batch_item_size` - Target word count per request before payloads are + split into multiple batches (between 1 and 2500 inclusive). + + + +## LocalizationParams Objects + +```python +class LocalizationParams(BaseModel) +``` + +Request parameters accepted by localization operations. + +These values are serialized directly into API requests after validation. + +**Attributes**: + +- `source_locale` - Optional BCP 47 language code representing the source + language. When omitted the API attempts automatic detection. +- `target_locale` - Required BCP 47 language code for the desired translation + target. +- `fast` - Optional flag that enables the service's low-latency translation + mode at the cost of some quality safeguards. +- `reference` - Optional nested mapping of existing translations that + provides additional context to the engine. + + + +## LingoDotDevEngine Objects + +```python +class LingoDotDevEngine() +``` + +Asynchronous client for the Lingo.dev localization API. + +The engine manages an :class:`httpx.AsyncClient`, handles chunking and +batching of content, and exposes helper coroutines for translating strings, +structured objects, and chat transcripts. Instances can be reused or +managed via an async context manager:: + + async with LingoDotDevEngine({"api_key": "..."}) as engine: + await engine.localize_text("Hello", {"target_locale": "es"}) + +All localization methods are ``async`` and must be awaited. + + + +### \_\_init\_\_ + +```python +def __init__(config: Dict[str, Any]) +``` + +Instantiate the engine with configuration data. + +**Arguments**: + +- `config` - Mapping of values understood by + :class:`EngineConfig`. At minimum an ``api_key`` entry must + be supplied. + + +**Raises**: + +- `ValueError` - If the supplied configuration fails validation. + + + +### \_\_aenter\_\_ + +```python +async def __aenter__() +``` + +Open the HTTP session when entering an async context. + +**Returns**: + +- `LingoDotDevEngine` - The active engine instance. + + + +### \_\_aexit\_\_ + +```python +async def __aexit__(exc_type, exc_val, exc_tb) +``` + +Release resources acquired during the async context. + + + +### close + +```python +async def close() +``` + +Close the HTTP client if it has been created. + + + +### localize\_object + +```python +async def localize_object(obj: Dict[str, Any], + params: Dict[str, Any], + progress_callback: Optional[ + Callable[[int, Dict[str, str], Dict[str, str]], + None]] = None, + concurrent: bool = False) -> Dict[str, Any] +``` + +Localize every string value contained in a mapping. + +**Arguments**: + +- `obj` - Mapping whose string leaves should be translated. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams`. +- `progress_callback` - Optional callable invoked with progress updates + (0-100) alongside the source and localized chunks. If provided, + leave ``concurrent`` as ``False`` (the default) because progress + updates are unavailable in concurrent mode. +- `concurrent` - When ``True`` the payload chunks are processed + concurrently and no progress updates are emitted. + + +**Returns**: + + A dictionary mirroring ``obj`` with localized string values. + + +**Raises**: + +- `RuntimeError` - If the API responds with an error. +- `ValueError` - If the API rejects the request. + + +**Examples**: + + .. code-block:: python + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + localized = await engine.localize_object( +- `{"title"` - "Hello"}, +- `{"target_locale"` - "es"}, + concurrent=True, + ) + # localized -> {"title": "Hola"} (example output) + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + def on_progress(percent, *_): +- `print(f"Progress` - {percent}%") + + localized = await engine.localize_object( +- `{"welcome"` - "Hello", "farewell": "Goodbye"}, +- `{"source_locale"` - "en", "target_locale": "fr"}, + progress_callback=on_progress, + ) + # localized -> {"welcome": "Bonjour", "farewell": "Au revoir"} (example output) + + + +### localize\_text + +```python +async def localize_text( + text: str, + params: Dict[str, Any], + progress_callback: Optional[Callable[[int], None]] = None) -> str +``` + +Localize a single text string. + +**Arguments**: + +- `text` - The text to translate. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams`. +- `progress_callback` - Optional callable receiving the percentage + complete (0-100). + + +**Returns**: + + The localized text string. + + +**Raises**: + +- `RuntimeError` - If the API responds with an error. +- `ValueError` - If the API rejects the request. + + +**Examples**: + + .. code-block:: python + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + greeting = await engine.localize_text( + "Hello", {"target_locale": "de"} + ) + # greeting -> "Hallo" (example output) + + progress_updates = [] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + farewell = await engine.localize_text( + "Goodbye", +- `{"source_locale"` - "en", "target_locale": "it"}, + progress_callback=progress_updates.append, + ) + # farewell -> "Arrivederci" (example output) + + + +### batch\_localize\_text + +```python +async def batch_localize_text(text: str, params: Dict[str, Any]) -> List[str] +``` + +Localize a single text string into multiple target locales. + +**Arguments**: + +- `text` - The text string to translate. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams` plus a ``target_locales`` list. + + +**Returns**: + + List of localized strings ordered to match ``target_locales``. + + +**Raises**: + +- `ValueError` - If ``target_locales`` is missing or the API rejects a + request. +- `RuntimeError` - If the API responds with an error. + + +**Examples**: + + .. code-block:: python + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Welcome", +- `{"target_locales"` - ["es", "fr"]}, + ) + # variants -> ["Bienvenido", "Bienvenue"] (example output) + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Checkout", + { +- `"source_locale"` - "en", +- `"target_locales"` - ["pt-BR", "it"], +- `"fast"` - True, + }, + ) + # variants -> ["Finalizar compra", "Pagamento"] (example output) + + + +### localize\_chat + +```python +async def localize_chat( + chat: List[Dict[str, str]], + params: Dict[str, Any], + progress_callback: Optional[Callable[[int], None]] = None +) -> List[Dict[str, str]] +``` + +Localize a chat transcript while preserving speaker metadata. + +**Arguments**: + +- `chat` - Sequence of chat messages. Each item must include ``name`` and + ``text`` keys. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams`. +- `progress_callback` - Optional callable receiving percentage updates + (0-100) while the transcript is localized. + + +**Returns**: + + List of localized chat messages in the same order as ``chat``. If + the API omits chat data an empty list is returned. + + +**Raises**: + +- `ValueError` - If any message in ``chat`` omits the required keys. +- `RuntimeError` - If the API responds with an error. + + +**Examples**: + + .. code-block:: python + + chat = [ +- `{"name"` - "Alice", "text": "Hello"}, +- `{"name"` - "Bob", "text": "Goodbye"}, + ] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, +- `{"target_locale"` - "es"}, + ) + # translated -> [{"name": "Alice", "text": "Hola"}, ...] (example output) + + updates = [] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, +- `{"source_locale"` - "en", "target_locale": "de"}, + progress_callback=updates.append, + ) + # translated -> [{"name": "Alice", "text": "Hallo"}, ...] (example output) + + + +### recognize\_locale + +```python +async def recognize_locale(text: str) -> str +``` + +Detect the language of the supplied text via the ``/recognize`` endpoint. + +**Arguments**: + +- `text` - Non-empty string to analyse. + + +**Returns**: + + Locale code reported by the API (for example ``"en"``) or an empty + string when the service cannot determine a locale. + + +**Raises**: + +- `ValueError` - If ``text`` is empty or only whitespace. +- `RuntimeError` - If the request fails or the API reports an error. + + + +### whoami + +```python +async def whoami() -> Optional[Dict[str, str]] +``` + +Retrieve account metadata associated with the current API key. + +**Returns**: + + Dictionary containing ``email`` and ``id`` keys when available, or + ``None`` if the key is unauthenticated or a recoverable network + error occurs. + + +**Raises**: + +- `RuntimeError` - If the service reports a server-side error. + + + +### batch\_localize\_objects + +```python +async def batch_localize_objects( + objects: List[Dict[str, Any]], + params: Dict[str, Any]) -> List[Dict[str, Any]] +``` + +Localize multiple mapping objects concurrently. + +**Arguments**: + +- `objects` - List of objects whose string values should be translated. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams`, shared across all objects. + + +**Returns**: + + List of localized objects preserving the order of ``objects``. + + +**Raises**: + +- `RuntimeError` - If the API responds with an error. +- `ValueError` - If the API rejects a request. + + + +### quick\_translate + +```python +@classmethod +async def quick_translate(cls, + content: Any, + api_key: str, + target_locale: str, + source_locale: Optional[str] = None, + api_url: str = "https://engine.lingo.dev", + fast: bool = True) -> Any +``` + +Translate content without managing an engine instance manually. + +The helper opens an :class:`LingoDotDevEngine` using the supplied +configuration, performs the translation, and automatically closes the +underlying HTTP client. + +**Arguments**: + +- `content` - Text string or mapping to translate. The returned value + matches the type of ``content``. +- `api_key` - Lingo.dev API key to authenticate the request. +- `target_locale` - Target language code for the translation. +- `source_locale` - Optional source language code. When omitted the API + may attempt to detect it. +- `api_url` - Lingo.dev engine base URL. Defaults to + ``"https://engine.lingo.dev"``. +- `fast` - Whether to enable the service's fast translation mode. + + +**Returns**: + + Translated content with the same type as ``content``. + + +**Raises**: + +- `ValueError` - If ``content`` is not a string or dictionary, or if the + service rejects the request. +- `RuntimeError` - If the API indicates a failure or the request cannot + be completed. + + +**Examples**: + + .. code-block:: python + + greeting = await LingoDotDevEngine.quick_translate( + "Hello world", + api_key="api-key", + target_locale="es", + ) + # greeting -> "Hola mundo" (example output) + + landing_page = await LingoDotDevEngine.quick_translate( +- `{"headline"` - "Hello", "cta": "Buy now"}, + api_key="api-key", + target_locale="de", + source_locale="en", + fast=False, + ) + # landing_page -> {"headline": "Hallo", "cta": "Jetzt kaufen"} (example output) + + + +### quick\_batch\_translate + +```python +@classmethod +async def quick_batch_translate(cls, + content: Any, + api_key: str, + target_locales: List[str], + source_locale: Optional[str] = None, + api_url: str = "https://engine.lingo.dev", + fast: bool = True) -> List[Any] +``` + +Translate content into multiple locales without manual setup. + +**Arguments**: + +- `content` - Text string or mapping to translate for each locale. +- `api_key` - Lingo.dev API key to authenticate requests. +- `target_locales` - List of locale codes. Results maintain this order. +- `source_locale` - Optional source language code. When omitted the API + may attempt to detect it. +- `api_url` - Lingo.dev engine base URL. Defaults to + ``"https://engine.lingo.dev"``. +- `fast` - Whether to enable the service's fast translation mode. + + +**Returns**: + + List of translated content, one entry per ``target_locales`` item. + + +**Raises**: + +- `ValueError` - If ``content`` is not a string or dictionary, or if a + request is rejected by the API. +- `RuntimeError` - If the API indicates a failure or the request cannot + be completed. + + +**Examples**: + + .. code-block:: python + + variants = await LingoDotDevEngine.quick_batch_translate( + "Hello world", + api_key="api-key", + target_locales=["es", "fr"], + ) + # variants -> ["Hola mundo", "Bonjour le monde"] (example output) + + localized_objects = await LingoDotDevEngine.quick_batch_translate( +- `{"success"` - "Saved", "error": "Failed"}, + api_key="api-key", + target_locales=["pt-BR", "it"], + source_locale="en", + fast=False, + ) + # localized_objects -> [ + # {"success": "Salvo", "error": "Falhou"}, + # {"success": "Salvato", "error": "Non riuscito"}, + # ] (example output) + diff --git a/pydoc-markdown.yml b/pydoc-markdown.yml new file mode 100644 index 0000000..f042ad0 --- /dev/null +++ b/pydoc-markdown.yml @@ -0,0 +1,21 @@ +loaders: + - type: python + search_path: + - src + packages: + - lingodotdev +processors: + - type: filter + skip_empty_modules: true + - type: smart + - type: crossref +renderer: + type: markdown + filename: docs/api.md + render_toc: true + header_level_by_type: + Module: 1 + Class: 2 + Method: 3 + Function: 3 + Variable: 3 diff --git a/pyproject.toml b/pyproject.toml index 0f3eb26..ce007ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ dev = [ "flake8>=6.0.0", "mypy>=1.0.0", "python-semantic-release>=8.0.0", + "pydoc-markdown>=4.8", ] [project.urls] @@ -129,4 +130,4 @@ changelog_file = "CHANGELOG.md" [tool.semantic_release.branches.main] match = "main" -prerelease = false \ No newline at end of file +prerelease = false From 1920ba1755f29fc41d47a1ee286a0246347ae0d0 Mon Sep 17 00:00:00 2001 From: David Turnbull Date: Fri, 19 Sep 2025 10:41:55 +1000 Subject: [PATCH 3/8] chore: remove --- docs/api.md | 583 ---------------------------------------------------- 1 file changed, 583 deletions(-) delete mode 100644 docs/api.md diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index a4d525b..0000000 --- a/docs/api.md +++ /dev/null @@ -1,583 +0,0 @@ -# Table of Contents - -* [lingodotdev.engine](#lingodotdev.engine) - * [EngineConfig](#lingodotdev.engine.EngineConfig) - * [LocalizationParams](#lingodotdev.engine.LocalizationParams) - * [LingoDotDevEngine](#lingodotdev.engine.LingoDotDevEngine) - * [\_\_init\_\_](#lingodotdev.engine.LingoDotDevEngine.__init__) - * [\_\_aenter\_\_](#lingodotdev.engine.LingoDotDevEngine.__aenter__) - * [\_\_aexit\_\_](#lingodotdev.engine.LingoDotDevEngine.__aexit__) - * [close](#lingodotdev.engine.LingoDotDevEngine.close) - * [localize\_object](#lingodotdev.engine.LingoDotDevEngine.localize_object) - * [localize\_text](#lingodotdev.engine.LingoDotDevEngine.localize_text) - * [batch\_localize\_text](#lingodotdev.engine.LingoDotDevEngine.batch_localize_text) - * [localize\_chat](#lingodotdev.engine.LingoDotDevEngine.localize_chat) - * [recognize\_locale](#lingodotdev.engine.LingoDotDevEngine.recognize_locale) - * [whoami](#lingodotdev.engine.LingoDotDevEngine.whoami) - * [batch\_localize\_objects](#lingodotdev.engine.LingoDotDevEngine.batch_localize_objects) - * [quick\_translate](#lingodotdev.engine.LingoDotDevEngine.quick_translate) - * [quick\_batch\_translate](#lingodotdev.engine.LingoDotDevEngine.quick_batch_translate) - - - -# lingodotdev.engine - -Async client implementation for the Lingo.dev localization service. - -This module houses :class:`LingoDotDevEngine` alongside supporting data models -used to validate configuration and localization parameters. - - - -## EngineConfig Objects - -```python -class EngineConfig(BaseModel) -``` - -Stores and validates runtime configuration for :class:`LingoDotDevEngine`. - -**Attributes**: - -- `api_key` - Secret token used to authenticate with the Lingo.dev API. -- `api_url` - Base endpoint for the localization engine. Defaults to - ``https://engine.lingo.dev``. -- `batch_size` - Maximum number of top-level entries to send in a single - localization request (between 1 and 250 inclusive). -- `ideal_batch_item_size` - Target word count per request before payloads are - split into multiple batches (between 1 and 2500 inclusive). - - - -## LocalizationParams Objects - -```python -class LocalizationParams(BaseModel) -``` - -Request parameters accepted by localization operations. - -These values are serialized directly into API requests after validation. - -**Attributes**: - -- `source_locale` - Optional BCP 47 language code representing the source - language. When omitted the API attempts automatic detection. -- `target_locale` - Required BCP 47 language code for the desired translation - target. -- `fast` - Optional flag that enables the service's low-latency translation - mode at the cost of some quality safeguards. -- `reference` - Optional nested mapping of existing translations that - provides additional context to the engine. - - - -## LingoDotDevEngine Objects - -```python -class LingoDotDevEngine() -``` - -Asynchronous client for the Lingo.dev localization API. - -The engine manages an :class:`httpx.AsyncClient`, handles chunking and -batching of content, and exposes helper coroutines for translating strings, -structured objects, and chat transcripts. Instances can be reused or -managed via an async context manager:: - - async with LingoDotDevEngine({"api_key": "..."}) as engine: - await engine.localize_text("Hello", {"target_locale": "es"}) - -All localization methods are ``async`` and must be awaited. - - - -### \_\_init\_\_ - -```python -def __init__(config: Dict[str, Any]) -``` - -Instantiate the engine with configuration data. - -**Arguments**: - -- `config` - Mapping of values understood by - :class:`EngineConfig`. At minimum an ``api_key`` entry must - be supplied. - - -**Raises**: - -- `ValueError` - If the supplied configuration fails validation. - - - -### \_\_aenter\_\_ - -```python -async def __aenter__() -``` - -Open the HTTP session when entering an async context. - -**Returns**: - -- `LingoDotDevEngine` - The active engine instance. - - - -### \_\_aexit\_\_ - -```python -async def __aexit__(exc_type, exc_val, exc_tb) -``` - -Release resources acquired during the async context. - - - -### close - -```python -async def close() -``` - -Close the HTTP client if it has been created. - - - -### localize\_object - -```python -async def localize_object(obj: Dict[str, Any], - params: Dict[str, Any], - progress_callback: Optional[ - Callable[[int, Dict[str, str], Dict[str, str]], - None]] = None, - concurrent: bool = False) -> Dict[str, Any] -``` - -Localize every string value contained in a mapping. - -**Arguments**: - -- `obj` - Mapping whose string leaves should be translated. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams`. -- `progress_callback` - Optional callable invoked with progress updates - (0-100) alongside the source and localized chunks. If provided, - leave ``concurrent`` as ``False`` (the default) because progress - updates are unavailable in concurrent mode. -- `concurrent` - When ``True`` the payload chunks are processed - concurrently and no progress updates are emitted. - - -**Returns**: - - A dictionary mirroring ``obj`` with localized string values. - - -**Raises**: - -- `RuntimeError` - If the API responds with an error. -- `ValueError` - If the API rejects the request. - - -**Examples**: - - .. code-block:: python - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - localized = await engine.localize_object( -- `{"title"` - "Hello"}, -- `{"target_locale"` - "es"}, - concurrent=True, - ) - # localized -> {"title": "Hola"} (example output) - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - def on_progress(percent, *_): -- `print(f"Progress` - {percent}%") - - localized = await engine.localize_object( -- `{"welcome"` - "Hello", "farewell": "Goodbye"}, -- `{"source_locale"` - "en", "target_locale": "fr"}, - progress_callback=on_progress, - ) - # localized -> {"welcome": "Bonjour", "farewell": "Au revoir"} (example output) - - - -### localize\_text - -```python -async def localize_text( - text: str, - params: Dict[str, Any], - progress_callback: Optional[Callable[[int], None]] = None) -> str -``` - -Localize a single text string. - -**Arguments**: - -- `text` - The text to translate. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams`. -- `progress_callback` - Optional callable receiving the percentage - complete (0-100). - - -**Returns**: - - The localized text string. - - -**Raises**: - -- `RuntimeError` - If the API responds with an error. -- `ValueError` - If the API rejects the request. - - -**Examples**: - - .. code-block:: python - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - greeting = await engine.localize_text( - "Hello", {"target_locale": "de"} - ) - # greeting -> "Hallo" (example output) - - progress_updates = [] - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - farewell = await engine.localize_text( - "Goodbye", -- `{"source_locale"` - "en", "target_locale": "it"}, - progress_callback=progress_updates.append, - ) - # farewell -> "Arrivederci" (example output) - - - -### batch\_localize\_text - -```python -async def batch_localize_text(text: str, params: Dict[str, Any]) -> List[str] -``` - -Localize a single text string into multiple target locales. - -**Arguments**: - -- `text` - The text string to translate. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams` plus a ``target_locales`` list. - - -**Returns**: - - List of localized strings ordered to match ``target_locales``. - - -**Raises**: - -- `ValueError` - If ``target_locales`` is missing or the API rejects a - request. -- `RuntimeError` - If the API responds with an error. - - -**Examples**: - - .. code-block:: python - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - variants = await engine.batch_localize_text( - "Welcome", -- `{"target_locales"` - ["es", "fr"]}, - ) - # variants -> ["Bienvenido", "Bienvenue"] (example output) - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - variants = await engine.batch_localize_text( - "Checkout", - { -- `"source_locale"` - "en", -- `"target_locales"` - ["pt-BR", "it"], -- `"fast"` - True, - }, - ) - # variants -> ["Finalizar compra", "Pagamento"] (example output) - - - -### localize\_chat - -```python -async def localize_chat( - chat: List[Dict[str, str]], - params: Dict[str, Any], - progress_callback: Optional[Callable[[int], None]] = None -) -> List[Dict[str, str]] -``` - -Localize a chat transcript while preserving speaker metadata. - -**Arguments**: - -- `chat` - Sequence of chat messages. Each item must include ``name`` and - ``text`` keys. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams`. -- `progress_callback` - Optional callable receiving percentage updates - (0-100) while the transcript is localized. - - -**Returns**: - - List of localized chat messages in the same order as ``chat``. If - the API omits chat data an empty list is returned. - - -**Raises**: - -- `ValueError` - If any message in ``chat`` omits the required keys. -- `RuntimeError` - If the API responds with an error. - - -**Examples**: - - .. code-block:: python - - chat = [ -- `{"name"` - "Alice", "text": "Hello"}, -- `{"name"` - "Bob", "text": "Goodbye"}, - ] - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - translated = await engine.localize_chat( - chat, -- `{"target_locale"` - "es"}, - ) - # translated -> [{"name": "Alice", "text": "Hola"}, ...] (example output) - - updates = [] - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - translated = await engine.localize_chat( - chat, -- `{"source_locale"` - "en", "target_locale": "de"}, - progress_callback=updates.append, - ) - # translated -> [{"name": "Alice", "text": "Hallo"}, ...] (example output) - - - -### recognize\_locale - -```python -async def recognize_locale(text: str) -> str -``` - -Detect the language of the supplied text via the ``/recognize`` endpoint. - -**Arguments**: - -- `text` - Non-empty string to analyse. - - -**Returns**: - - Locale code reported by the API (for example ``"en"``) or an empty - string when the service cannot determine a locale. - - -**Raises**: - -- `ValueError` - If ``text`` is empty or only whitespace. -- `RuntimeError` - If the request fails or the API reports an error. - - - -### whoami - -```python -async def whoami() -> Optional[Dict[str, str]] -``` - -Retrieve account metadata associated with the current API key. - -**Returns**: - - Dictionary containing ``email`` and ``id`` keys when available, or - ``None`` if the key is unauthenticated or a recoverable network - error occurs. - - -**Raises**: - -- `RuntimeError` - If the service reports a server-side error. - - - -### batch\_localize\_objects - -```python -async def batch_localize_objects( - objects: List[Dict[str, Any]], - params: Dict[str, Any]) -> List[Dict[str, Any]] -``` - -Localize multiple mapping objects concurrently. - -**Arguments**: - -- `objects` - List of objects whose string values should be translated. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams`, shared across all objects. - - -**Returns**: - - List of localized objects preserving the order of ``objects``. - - -**Raises**: - -- `RuntimeError` - If the API responds with an error. -- `ValueError` - If the API rejects a request. - - - -### quick\_translate - -```python -@classmethod -async def quick_translate(cls, - content: Any, - api_key: str, - target_locale: str, - source_locale: Optional[str] = None, - api_url: str = "https://engine.lingo.dev", - fast: bool = True) -> Any -``` - -Translate content without managing an engine instance manually. - -The helper opens an :class:`LingoDotDevEngine` using the supplied -configuration, performs the translation, and automatically closes the -underlying HTTP client. - -**Arguments**: - -- `content` - Text string or mapping to translate. The returned value - matches the type of ``content``. -- `api_key` - Lingo.dev API key to authenticate the request. -- `target_locale` - Target language code for the translation. -- `source_locale` - Optional source language code. When omitted the API - may attempt to detect it. -- `api_url` - Lingo.dev engine base URL. Defaults to - ``"https://engine.lingo.dev"``. -- `fast` - Whether to enable the service's fast translation mode. - - -**Returns**: - - Translated content with the same type as ``content``. - - -**Raises**: - -- `ValueError` - If ``content`` is not a string or dictionary, or if the - service rejects the request. -- `RuntimeError` - If the API indicates a failure or the request cannot - be completed. - - -**Examples**: - - .. code-block:: python - - greeting = await LingoDotDevEngine.quick_translate( - "Hello world", - api_key="api-key", - target_locale="es", - ) - # greeting -> "Hola mundo" (example output) - - landing_page = await LingoDotDevEngine.quick_translate( -- `{"headline"` - "Hello", "cta": "Buy now"}, - api_key="api-key", - target_locale="de", - source_locale="en", - fast=False, - ) - # landing_page -> {"headline": "Hallo", "cta": "Jetzt kaufen"} (example output) - - - -### quick\_batch\_translate - -```python -@classmethod -async def quick_batch_translate(cls, - content: Any, - api_key: str, - target_locales: List[str], - source_locale: Optional[str] = None, - api_url: str = "https://engine.lingo.dev", - fast: bool = True) -> List[Any] -``` - -Translate content into multiple locales without manual setup. - -**Arguments**: - -- `content` - Text string or mapping to translate for each locale. -- `api_key` - Lingo.dev API key to authenticate requests. -- `target_locales` - List of locale codes. Results maintain this order. -- `source_locale` - Optional source language code. When omitted the API - may attempt to detect it. -- `api_url` - Lingo.dev engine base URL. Defaults to - ``"https://engine.lingo.dev"``. -- `fast` - Whether to enable the service's fast translation mode. - - -**Returns**: - - List of translated content, one entry per ``target_locales`` item. - - -**Raises**: - -- `ValueError` - If ``content`` is not a string or dictionary, or if a - request is rejected by the API. -- `RuntimeError` - If the API indicates a failure or the request cannot - be completed. - - -**Examples**: - - .. code-block:: python - - variants = await LingoDotDevEngine.quick_batch_translate( - "Hello world", - api_key="api-key", - target_locales=["es", "fr"], - ) - # variants -> ["Hola mundo", "Bonjour le monde"] (example output) - - localized_objects = await LingoDotDevEngine.quick_batch_translate( -- `{"success"` - "Saved", "error": "Failed"}, - api_key="api-key", - target_locales=["pt-BR", "it"], - source_locale="en", - fast=False, - ) - # localized_objects -> [ - # {"success": "Salvo", "error": "Falhou"}, - # {"success": "Salvato", "error": "Non riuscito"}, - # ] (example output) - From a6cda3700df4944fffb5fb811114616f72aa60ae Mon Sep 17 00:00:00 2001 From: David Turnbull Date: Fri, 19 Sep 2025 10:49:48 +1000 Subject: [PATCH 4/8] cohre: deps --- .github/workflows/pr.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- pyproject.toml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 4a271c5..664d4bb 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - name: Checkout code @@ -67,4 +67,4 @@ jobs: file: ./coverage.xml flags: unittests name: codecov-umbrella - fail_ci_if_error: false \ No newline at end of file + fail_ci_if_error: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c92e30a..2c87c6f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - name: Checkout code @@ -118,4 +118,4 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - semantic-release publish \ No newline at end of file + semantic-release publish diff --git a/pyproject.toml b/pyproject.toml index ce007ff..a462756 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "lingodotdev" version = "1.3.0" description = "Lingo.dev Python SDK" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "Apache-2.0" } authors = [ { name = "Lingo.dev Team", email = "hi@lingo.dev" }, @@ -20,11 +20,11 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Localization", "Topic :: Text Processing :: Linguistic", From 8859c62a9921b118bc39e4999aec653c8c01a1e7 Mon Sep 17 00:00:00 2001 From: David Turnbull Date: Fri, 19 Sep 2025 10:50:56 +1000 Subject: [PATCH 5/8] docs: regenerate --- .github/workflows/update-docs.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/update-docs.yml b/.github/workflows/update-docs.yml index d4a5ceb..6c8c2f6 100644 --- a/.github/workflows/update-docs.yml +++ b/.github/workflows/update-docs.yml @@ -2,11 +2,10 @@ name: Regenerate API Docs on: push: - branches: - - main paths: - 'src/**' - 'pyproject.toml' + - 'pydoc-markdown.yml' workflow_dispatch: permissions: From ac3335a903c02dfcac6f3b63084e8400bb17fa13 Mon Sep 17 00:00:00 2001 From: David Turnbull Date: Fri, 19 Sep 2025 10:57:51 +1000 Subject: [PATCH 6/8] fix: tests --- docs/api.md | 583 ++++++++++++++++++++++++++++++++++++++ src/lingodotdev/engine.py | 14 + 2 files changed, 597 insertions(+) create mode 100644 docs/api.md diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..a4d525b --- /dev/null +++ b/docs/api.md @@ -0,0 +1,583 @@ +# Table of Contents + +* [lingodotdev.engine](#lingodotdev.engine) + * [EngineConfig](#lingodotdev.engine.EngineConfig) + * [LocalizationParams](#lingodotdev.engine.LocalizationParams) + * [LingoDotDevEngine](#lingodotdev.engine.LingoDotDevEngine) + * [\_\_init\_\_](#lingodotdev.engine.LingoDotDevEngine.__init__) + * [\_\_aenter\_\_](#lingodotdev.engine.LingoDotDevEngine.__aenter__) + * [\_\_aexit\_\_](#lingodotdev.engine.LingoDotDevEngine.__aexit__) + * [close](#lingodotdev.engine.LingoDotDevEngine.close) + * [localize\_object](#lingodotdev.engine.LingoDotDevEngine.localize_object) + * [localize\_text](#lingodotdev.engine.LingoDotDevEngine.localize_text) + * [batch\_localize\_text](#lingodotdev.engine.LingoDotDevEngine.batch_localize_text) + * [localize\_chat](#lingodotdev.engine.LingoDotDevEngine.localize_chat) + * [recognize\_locale](#lingodotdev.engine.LingoDotDevEngine.recognize_locale) + * [whoami](#lingodotdev.engine.LingoDotDevEngine.whoami) + * [batch\_localize\_objects](#lingodotdev.engine.LingoDotDevEngine.batch_localize_objects) + * [quick\_translate](#lingodotdev.engine.LingoDotDevEngine.quick_translate) + * [quick\_batch\_translate](#lingodotdev.engine.LingoDotDevEngine.quick_batch_translate) + + + +# lingodotdev.engine + +Async client implementation for the Lingo.dev localization service. + +This module houses :class:`LingoDotDevEngine` alongside supporting data models +used to validate configuration and localization parameters. + + + +## EngineConfig Objects + +```python +class EngineConfig(BaseModel) +``` + +Stores and validates runtime configuration for :class:`LingoDotDevEngine`. + +**Attributes**: + +- `api_key` - Secret token used to authenticate with the Lingo.dev API. +- `api_url` - Base endpoint for the localization engine. Defaults to + ``https://engine.lingo.dev``. +- `batch_size` - Maximum number of top-level entries to send in a single + localization request (between 1 and 250 inclusive). +- `ideal_batch_item_size` - Target word count per request before payloads are + split into multiple batches (between 1 and 2500 inclusive). + + + +## LocalizationParams Objects + +```python +class LocalizationParams(BaseModel) +``` + +Request parameters accepted by localization operations. + +These values are serialized directly into API requests after validation. + +**Attributes**: + +- `source_locale` - Optional BCP 47 language code representing the source + language. When omitted the API attempts automatic detection. +- `target_locale` - Required BCP 47 language code for the desired translation + target. +- `fast` - Optional flag that enables the service's low-latency translation + mode at the cost of some quality safeguards. +- `reference` - Optional nested mapping of existing translations that + provides additional context to the engine. + + + +## LingoDotDevEngine Objects + +```python +class LingoDotDevEngine() +``` + +Asynchronous client for the Lingo.dev localization API. + +The engine manages an :class:`httpx.AsyncClient`, handles chunking and +batching of content, and exposes helper coroutines for translating strings, +structured objects, and chat transcripts. Instances can be reused or +managed via an async context manager:: + + async with LingoDotDevEngine({"api_key": "..."}) as engine: + await engine.localize_text("Hello", {"target_locale": "es"}) + +All localization methods are ``async`` and must be awaited. + + + +### \_\_init\_\_ + +```python +def __init__(config: Dict[str, Any]) +``` + +Instantiate the engine with configuration data. + +**Arguments**: + +- `config` - Mapping of values understood by + :class:`EngineConfig`. At minimum an ``api_key`` entry must + be supplied. + + +**Raises**: + +- `ValueError` - If the supplied configuration fails validation. + + + +### \_\_aenter\_\_ + +```python +async def __aenter__() +``` + +Open the HTTP session when entering an async context. + +**Returns**: + +- `LingoDotDevEngine` - The active engine instance. + + + +### \_\_aexit\_\_ + +```python +async def __aexit__(exc_type, exc_val, exc_tb) +``` + +Release resources acquired during the async context. + + + +### close + +```python +async def close() +``` + +Close the HTTP client if it has been created. + + + +### localize\_object + +```python +async def localize_object(obj: Dict[str, Any], + params: Dict[str, Any], + progress_callback: Optional[ + Callable[[int, Dict[str, str], Dict[str, str]], + None]] = None, + concurrent: bool = False) -> Dict[str, Any] +``` + +Localize every string value contained in a mapping. + +**Arguments**: + +- `obj` - Mapping whose string leaves should be translated. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams`. +- `progress_callback` - Optional callable invoked with progress updates + (0-100) alongside the source and localized chunks. If provided, + leave ``concurrent`` as ``False`` (the default) because progress + updates are unavailable in concurrent mode. +- `concurrent` - When ``True`` the payload chunks are processed + concurrently and no progress updates are emitted. + + +**Returns**: + + A dictionary mirroring ``obj`` with localized string values. + + +**Raises**: + +- `RuntimeError` - If the API responds with an error. +- `ValueError` - If the API rejects the request. + + +**Examples**: + + .. code-block:: python + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + localized = await engine.localize_object( +- `{"title"` - "Hello"}, +- `{"target_locale"` - "es"}, + concurrent=True, + ) + # localized -> {"title": "Hola"} (example output) + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + def on_progress(percent, *_): +- `print(f"Progress` - {percent}%") + + localized = await engine.localize_object( +- `{"welcome"` - "Hello", "farewell": "Goodbye"}, +- `{"source_locale"` - "en", "target_locale": "fr"}, + progress_callback=on_progress, + ) + # localized -> {"welcome": "Bonjour", "farewell": "Au revoir"} (example output) + + + +### localize\_text + +```python +async def localize_text( + text: str, + params: Dict[str, Any], + progress_callback: Optional[Callable[[int], None]] = None) -> str +``` + +Localize a single text string. + +**Arguments**: + +- `text` - The text to translate. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams`. +- `progress_callback` - Optional callable receiving the percentage + complete (0-100). + + +**Returns**: + + The localized text string. + + +**Raises**: + +- `RuntimeError` - If the API responds with an error. +- `ValueError` - If the API rejects the request. + + +**Examples**: + + .. code-block:: python + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + greeting = await engine.localize_text( + "Hello", {"target_locale": "de"} + ) + # greeting -> "Hallo" (example output) + + progress_updates = [] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + farewell = await engine.localize_text( + "Goodbye", +- `{"source_locale"` - "en", "target_locale": "it"}, + progress_callback=progress_updates.append, + ) + # farewell -> "Arrivederci" (example output) + + + +### batch\_localize\_text + +```python +async def batch_localize_text(text: str, params: Dict[str, Any]) -> List[str] +``` + +Localize a single text string into multiple target locales. + +**Arguments**: + +- `text` - The text string to translate. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams` plus a ``target_locales`` list. + + +**Returns**: + + List of localized strings ordered to match ``target_locales``. + + +**Raises**: + +- `ValueError` - If ``target_locales`` is missing or the API rejects a + request. +- `RuntimeError` - If the API responds with an error. + + +**Examples**: + + .. code-block:: python + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Welcome", +- `{"target_locales"` - ["es", "fr"]}, + ) + # variants -> ["Bienvenido", "Bienvenue"] (example output) + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Checkout", + { +- `"source_locale"` - "en", +- `"target_locales"` - ["pt-BR", "it"], +- `"fast"` - True, + }, + ) + # variants -> ["Finalizar compra", "Pagamento"] (example output) + + + +### localize\_chat + +```python +async def localize_chat( + chat: List[Dict[str, str]], + params: Dict[str, Any], + progress_callback: Optional[Callable[[int], None]] = None +) -> List[Dict[str, str]] +``` + +Localize a chat transcript while preserving speaker metadata. + +**Arguments**: + +- `chat` - Sequence of chat messages. Each item must include ``name`` and + ``text`` keys. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams`. +- `progress_callback` - Optional callable receiving percentage updates + (0-100) while the transcript is localized. + + +**Returns**: + + List of localized chat messages in the same order as ``chat``. If + the API omits chat data an empty list is returned. + + +**Raises**: + +- `ValueError` - If any message in ``chat`` omits the required keys. +- `RuntimeError` - If the API responds with an error. + + +**Examples**: + + .. code-block:: python + + chat = [ +- `{"name"` - "Alice", "text": "Hello"}, +- `{"name"` - "Bob", "text": "Goodbye"}, + ] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, +- `{"target_locale"` - "es"}, + ) + # translated -> [{"name": "Alice", "text": "Hola"}, ...] (example output) + + updates = [] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, +- `{"source_locale"` - "en", "target_locale": "de"}, + progress_callback=updates.append, + ) + # translated -> [{"name": "Alice", "text": "Hallo"}, ...] (example output) + + + +### recognize\_locale + +```python +async def recognize_locale(text: str) -> str +``` + +Detect the language of the supplied text via the ``/recognize`` endpoint. + +**Arguments**: + +- `text` - Non-empty string to analyse. + + +**Returns**: + + Locale code reported by the API (for example ``"en"``) or an empty + string when the service cannot determine a locale. + + +**Raises**: + +- `ValueError` - If ``text`` is empty or only whitespace. +- `RuntimeError` - If the request fails or the API reports an error. + + + +### whoami + +```python +async def whoami() -> Optional[Dict[str, str]] +``` + +Retrieve account metadata associated with the current API key. + +**Returns**: + + Dictionary containing ``email`` and ``id`` keys when available, or + ``None`` if the key is unauthenticated or a recoverable network + error occurs. + + +**Raises**: + +- `RuntimeError` - If the service reports a server-side error. + + + +### batch\_localize\_objects + +```python +async def batch_localize_objects( + objects: List[Dict[str, Any]], + params: Dict[str, Any]) -> List[Dict[str, Any]] +``` + +Localize multiple mapping objects concurrently. + +**Arguments**: + +- `objects` - List of objects whose string values should be translated. +- `params` - Dictionary of options accepted by + :class:`LocalizationParams`, shared across all objects. + + +**Returns**: + + List of localized objects preserving the order of ``objects``. + + +**Raises**: + +- `RuntimeError` - If the API responds with an error. +- `ValueError` - If the API rejects a request. + + + +### quick\_translate + +```python +@classmethod +async def quick_translate(cls, + content: Any, + api_key: str, + target_locale: str, + source_locale: Optional[str] = None, + api_url: str = "https://engine.lingo.dev", + fast: bool = True) -> Any +``` + +Translate content without managing an engine instance manually. + +The helper opens an :class:`LingoDotDevEngine` using the supplied +configuration, performs the translation, and automatically closes the +underlying HTTP client. + +**Arguments**: + +- `content` - Text string or mapping to translate. The returned value + matches the type of ``content``. +- `api_key` - Lingo.dev API key to authenticate the request. +- `target_locale` - Target language code for the translation. +- `source_locale` - Optional source language code. When omitted the API + may attempt to detect it. +- `api_url` - Lingo.dev engine base URL. Defaults to + ``"https://engine.lingo.dev"``. +- `fast` - Whether to enable the service's fast translation mode. + + +**Returns**: + + Translated content with the same type as ``content``. + + +**Raises**: + +- `ValueError` - If ``content`` is not a string or dictionary, or if the + service rejects the request. +- `RuntimeError` - If the API indicates a failure or the request cannot + be completed. + + +**Examples**: + + .. code-block:: python + + greeting = await LingoDotDevEngine.quick_translate( + "Hello world", + api_key="api-key", + target_locale="es", + ) + # greeting -> "Hola mundo" (example output) + + landing_page = await LingoDotDevEngine.quick_translate( +- `{"headline"` - "Hello", "cta": "Buy now"}, + api_key="api-key", + target_locale="de", + source_locale="en", + fast=False, + ) + # landing_page -> {"headline": "Hallo", "cta": "Jetzt kaufen"} (example output) + + + +### quick\_batch\_translate + +```python +@classmethod +async def quick_batch_translate(cls, + content: Any, + api_key: str, + target_locales: List[str], + source_locale: Optional[str] = None, + api_url: str = "https://engine.lingo.dev", + fast: bool = True) -> List[Any] +``` + +Translate content into multiple locales without manual setup. + +**Arguments**: + +- `content` - Text string or mapping to translate for each locale. +- `api_key` - Lingo.dev API key to authenticate requests. +- `target_locales` - List of locale codes. Results maintain this order. +- `source_locale` - Optional source language code. When omitted the API + may attempt to detect it. +- `api_url` - Lingo.dev engine base URL. Defaults to + ``"https://engine.lingo.dev"``. +- `fast` - Whether to enable the service's fast translation mode. + + +**Returns**: + + List of translated content, one entry per ``target_locales`` item. + + +**Raises**: + +- `ValueError` - If ``content`` is not a string or dictionary, or if a + request is rejected by the API. +- `RuntimeError` - If the API indicates a failure or the request cannot + be completed. + + +**Examples**: + + .. code-block:: python + + variants = await LingoDotDevEngine.quick_batch_translate( + "Hello world", + api_key="api-key", + target_locales=["es", "fr"], + ) + # variants -> ["Hola mundo", "Bonjour le monde"] (example output) + + localized_objects = await LingoDotDevEngine.quick_batch_translate( +- `{"success"` - "Saved", "error": "Failed"}, + api_key="api-key", + target_locales=["pt-BR", "it"], + source_locale="en", + fast=False, + ) + # localized_objects -> [ + # {"success": "Salvo", "error": "Falhou"}, + # {"success": "Salvato", "error": "Non riuscito"}, + # ] (example output) + diff --git a/src/lingodotdev/engine.py b/src/lingodotdev/engine.py index f16d5fd..86e6d14 100644 --- a/src/lingodotdev/engine.py +++ b/src/lingodotdev/engine.py @@ -8,6 +8,7 @@ # mypy: disable-error-code=unreachable import asyncio +import re from typing import Any, Callable, Dict, List, Optional from urllib.parse import urljoin @@ -15,6 +16,8 @@ from nanoid import generate from pydantic import BaseModel, Field, validator +_BCP47_TAG_RE = re.compile(r"^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$") + class EngineConfig(BaseModel): """Stores and validates runtime configuration for :class:`LingoDotDevEngine`. @@ -63,6 +66,17 @@ class LocalizationParams(BaseModel): fast: Optional[bool] = None reference: Optional[Dict[str, Dict[str, Any]]] = None + @validator("source_locale", "target_locale") + @classmethod + def validate_locale(cls, v: Optional[str]) -> Optional[str]: + if v is None: + return v + if not _BCP47_TAG_RE.fullmatch(v): + raise ValueError( + "Locale values must be valid BCP 47 language tags (example: 'en', 'en-US')." + ) + return v + class LingoDotDevEngine: """Asynchronous client for the Lingo.dev localization API. From ddcae0ee0352f57e76900e0d36855ffb9d4c773a Mon Sep 17 00:00:00 2001 From: David Turnbull Date: Fri, 19 Sep 2025 11:24:28 +1000 Subject: [PATCH 7/8] docs: update --- src/lingodotdev/__init__.py | 12 +- src/lingodotdev/engine.py | 406 +++++++++++++++++++----------------- 2 files changed, 220 insertions(+), 198 deletions(-) diff --git a/src/lingodotdev/__init__.py b/src/lingodotdev/__init__.py index e1c61d3..9f677ae 100644 --- a/src/lingodotdev/__init__.py +++ b/src/lingodotdev/__init__.py @@ -1,9 +1,11 @@ -""" -Public entry point for the Lingo.dev Python SDK. +"""Public entry point for the Lingo.dev Python SDK. + +The package exposes LingoDotDevEngine, the asynchronous client used to access +the Lingo.dev localization API. Refer to the engine module for detailed usage +guidance. -The package exposes :class:`~lingodotdev.engine.LingoDotDevEngine`, the -asynchronous client used to access the Lingo.dev localization API. Refer to the -engine module for detailed usage guidance. + async with LingoDotDevEngine({"api_key": "..."}) as engine: + result = await engine.localize_text("Hello", {"target_locale": "es"}) """ __version__ = "1.3.0" diff --git a/src/lingodotdev/engine.py b/src/lingodotdev/engine.py index 86e6d14..efcc8f2 100644 --- a/src/lingodotdev/engine.py +++ b/src/lingodotdev/engine.py @@ -1,8 +1,11 @@ -""" -Async client implementation for the Lingo.dev localization service. +"""Async client implementation for the Lingo.dev localization service. + +This module provides LingoDotDevEngine and supporting data models for +configuration and localization parameter validation. -This module houses :class:`LingoDotDevEngine` alongside supporting data models -used to validate configuration and localization parameters. + config = {"api_key": "your-api-key"} + async with LingoDotDevEngine(config) as engine: + result = await engine.localize_text("Hello", {"target_locale": "es"}) """ # mypy: disable-error-code=unreachable @@ -20,12 +23,15 @@ class EngineConfig(BaseModel): - """Stores and validates runtime configuration for :class:`LingoDotDevEngine`. + """Runtime configuration for LingoDotDevEngine. + + Stores and validates configuration parameters required to interact with the + Lingo.dev API. Attributes: api_key: Secret token used to authenticate with the Lingo.dev API. api_url: Base endpoint for the localization engine. Defaults to - ``https://engine.lingo.dev``. + 'https://engine.lingo.dev'. batch_size: Maximum number of top-level entries to send in a single localization request (between 1 and 250 inclusive). ideal_batch_item_size: Target word count per request before payloads are @@ -40,21 +46,32 @@ class EngineConfig(BaseModel): @validator("api_url") @classmethod def validate_api_url(cls, v: str) -> str: + """Validates that the API URL is a valid HTTP/HTTPS URL. + + Args: + v: The URL string to validate. + + Returns: + The validated URL string. + + Raises: + ValueError: If the URL doesn't start with http:// or https://. + """ if not v.startswith(("http://", "https://")): raise ValueError("API URL must be a valid HTTP/HTTPS URL") return v class LocalizationParams(BaseModel): - """Request parameters accepted by localization operations. + """Request parameters for localization operations. These values are serialized directly into API requests after validation. Attributes: source_locale: Optional BCP 47 language code representing the source - language. When omitted the API attempts automatic detection. - target_locale: Required BCP 47 language code for the desired translation - target. + language. When omitted, the API attempts automatic detection. + target_locale: Required BCP 47 language code for the desired + translation target. fast: Optional flag that enables the service's low-latency translation mode at the cost of some quality safeguards. reference: Optional nested mapping of existing translations that @@ -69,6 +86,17 @@ class LocalizationParams(BaseModel): @validator("source_locale", "target_locale") @classmethod def validate_locale(cls, v: Optional[str]) -> Optional[str]: + """Validates that locale codes conform to BCP 47 standards. + + Args: + v: The locale string to validate, or None. + + Returns: + The validated locale string or None. + + Raises: + ValueError: If the locale is not a valid BCP 47 language tag. + """ if v is None: return v if not _BCP47_TAG_RE.fullmatch(v): @@ -81,24 +109,19 @@ def validate_locale(cls, v: Optional[str]) -> Optional[str]: class LingoDotDevEngine: """Asynchronous client for the Lingo.dev localization API. - The engine manages an :class:`httpx.AsyncClient`, handles chunking and - batching of content, and exposes helper coroutines for translating strings, - structured objects, and chat transcripts. Instances can be reused or - managed via an async context manager:: - - async with LingoDotDevEngine({"api_key": "..."}) as engine: - await engine.localize_text("Hello", {"target_locale": "es"}) + The engine manages an httpx.AsyncClient, handles chunking and batching of + content, and exposes helper coroutines for translating strings, structured + objects, and chat transcripts. - All localization methods are ``async`` and must be awaited. + All localization methods are async and must be awaited. """ def __init__(self, config: Dict[str, Any]): - """Instantiate the engine with configuration data. + """Instantiates the engine with configuration data. Args: - config: Mapping of values understood by - :class:`EngineConfig`. At minimum an ``api_key`` entry must - be supplied. + config: Mapping of values understood by `EngineConfig`. At minimum + an `api_key` entry must be supplied. Raises: ValueError: If the supplied configuration fails validation. @@ -107,20 +130,20 @@ def __init__(self, config: Dict[str, Any]): self._client: Optional[httpx.AsyncClient] = None async def __aenter__(self): - """Open the HTTP session when entering an async context. + """Opens the HTTP session when entering an async context. Returns: - LingoDotDevEngine: The active engine instance. + `LingoDotDevEngine`: The active engine instance. """ await self._ensure_client() return self async def __aexit__(self, exc_type, exc_val, exc_tb): - """Release resources acquired during the async context.""" + """Releases resources acquired during the async context.""" await self.close() async def _ensure_client(self): - """Create an :class:`httpx.AsyncClient` if one is not already available.""" + """Creates an `httpx.AsyncClient` if one is not already available.""" if self._client is None or self._client.is_closed: self._client = httpx.AsyncClient( headers={ @@ -131,7 +154,7 @@ async def _ensure_client(self): ) async def close(self): - """Close the HTTP client if it has been created.""" + """Closes the HTTP client if it has been created.""" if self._client and not self._client.is_closed: await self._client.aclose() @@ -144,7 +167,7 @@ async def _localize_raw( ] = None, concurrent: bool = False, ) -> Dict[str, str]: - """Submit a localization request for the provided payload. + """Submits a localization request for the provided payload. The payload is split into chunks based on the configured limits and the resulting pieces are localized sequentially or concurrently. Sequential @@ -157,7 +180,7 @@ async def _localize_raw( progress_callback: Optional callable invoked after each chunk is localized. Receives the percentage completed, the source chunk, and the localized chunk. - concurrent: When ``True`` and no ``progress_callback`` is supplied, + concurrent: When `True` and no `progress_callback` is supplied, chunks are processed concurrently. Returns: @@ -220,19 +243,19 @@ async def _localize_chunk( workflow_id: str, fast: bool, ) -> Dict[str, str]: - """Translate a single payload chunk through the ``/i18n`` endpoint. + """Translates a single payload chunk through the `/i18n` endpoint. Args: source_locale: Optional source locale used for the request. target_locale: Target locale requested from the API. - payload: Dictionary containing the chunk under the ``data`` key and - optional ``reference`` metadata. + payload: Dictionary containing the chunk under the `data` key and + optional `reference` metadata. workflow_id: Identifier shared across chunks that belong to the same localization workflow. fast: Whether to request the service's fast translation mode. Returns: - A dictionary representing the localized chunk returned by the API. + Localized chunk returned by the API. Raises: RuntimeError: If the API responds with an error status or signals a @@ -280,7 +303,7 @@ async def _localize_chunk( raise RuntimeError(f"Request failed: {str(e)}") def _extract_payload_chunks(self, payload: Dict[str, Any]) -> List[Dict[str, Any]]: - """Split a payload into smaller dictionaries based on configured limits. + """Splits a payload into smaller dictionaries based on configured limits. The method iterates through the payload in insertion order, grouping keys until the number of words or items would exceed the configured @@ -290,7 +313,7 @@ def _extract_payload_chunks(self, payload: Dict[str, Any]) -> List[Dict[str, Any payload: Mapping to be divided into localization chunks. Returns: - List of dictionaries representing individual request chunks. + Individual request chunks ready for the API. """ result = [] current_chunk = {} @@ -315,7 +338,7 @@ def _extract_payload_chunks(self, payload: Dict[str, Any]) -> List[Dict[str, Any return result def _count_words_in_record(self, payload: Any) -> int: - """Recursively count whitespace-delimited words within a payload. + """Recursively counts whitespace-delimited words within a payload. Args: payload: String, mapping, list, or other primitive values to count. @@ -341,47 +364,46 @@ async def localize_object( ] = None, concurrent: bool = False, ) -> Dict[str, Any]: - """Localize every string value contained in a mapping. + """Localizes every string value contained in a mapping. Args: obj: Mapping whose string leaves should be translated. - params: Dictionary of options accepted by - :class:`LocalizationParams`. + params: Dictionary of options accepted by `LocalizationParams`. progress_callback: Optional callable invoked with progress updates - (0-100) alongside the source and localized chunks. If provided, - leave ``concurrent`` as ``False`` (the default) because progress + (0-100) alongside the source and localized chunks. Do not set + `concurrent` when providing this callback because progress updates are unavailable in concurrent mode. - concurrent: When ``True`` the payload chunks are processed + concurrent: When `True` the payload chunks are processed concurrently and no progress updates are emitted. Returns: - A dictionary mirroring ``obj`` with localized string values. + A dictionary mirroring `obj` with localized string values. Raises: RuntimeError: If the API responds with an error. ValueError: If the API rejects the request. Examples: - .. code-block:: python - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - localized = await engine.localize_object( - {"title": "Hello"}, - {"target_locale": "es"}, - concurrent=True, - ) - # localized -> {"title": "Hola"} (example output) + ```python + async with LingoDotDevEngine({"api_key": "token"}) as engine: + localized = await engine.localize_object( + {"title": "Hello"}, + {"target_locale": "es"}, + concurrent=True, + ) + # localized -> {"title": "Hola"} (example output) - async with LingoDotDevEngine({"api_key": "token"}) as engine: - def on_progress(percent, *_): - print(f"Progress: {percent}%") + async with LingoDotDevEngine({"api_key": "token"}) as engine: + def on_progress(percent, *_): + print(f"Progress: {percent}%") - localized = await engine.localize_object( - {"welcome": "Hello", "farewell": "Goodbye"}, - {"source_locale": "en", "target_locale": "fr"}, - progress_callback=on_progress, - ) - # localized -> {"welcome": "Bonjour", "farewell": "Au revoir"} (example output) + localized = await engine.localize_object( + {"welcome": "Hello", "farewell": "Goodbye"}, + {"source_locale": "en", "target_locale": "fr"}, + progress_callback=on_progress, + ) + # localized -> {"welcome": "Bonjour", "farewell": "Au revoir"} (example output) + ``` """ localization_params = LocalizationParams(**params) return await self._localize_raw( @@ -394,12 +416,11 @@ async def localize_text( params: Dict[str, Any], progress_callback: Optional[Callable[[int], None]] = None, ) -> str: - """Localize a single text string. + """Localizes a single text string. Args: text: The text to translate. - params: Dictionary of options accepted by - :class:`LocalizationParams`. + params: Dictionary of options accepted by `LocalizationParams`. progress_callback: Optional callable receiving the percentage complete (0-100). @@ -411,23 +432,23 @@ async def localize_text( ValueError: If the API rejects the request. Examples: - .. code-block:: python - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - greeting = await engine.localize_text( - "Hello", {"target_locale": "de"} - ) - # greeting -> "Hallo" (example output) + ```python + async with LingoDotDevEngine({"api_key": "token"}) as engine: + greeting = await engine.localize_text( + "Hello", {"target_locale": "de"} + ) + # greeting -> "Hallo" (example output) - progress_updates = [] + progress_updates = [] - async with LingoDotDevEngine({"api_key": "token"}) as engine: - farewell = await engine.localize_text( - "Goodbye", - {"source_locale": "en", "target_locale": "it"}, - progress_callback=progress_updates.append, - ) - # farewell -> "Arrivederci" (example output) + async with LingoDotDevEngine({"api_key": "token"}) as engine: + farewell = await engine.localize_text( + "Goodbye", + {"source_locale": "en", "target_locale": "it"}, + progress_callback=progress_updates.append, + ) + # farewell -> "Arrivederci" (example output) + ``` """ localization_params = LocalizationParams(**params) @@ -444,41 +465,41 @@ def wrapped_progress_callback( return response.get("text", "") async def batch_localize_text(self, text: str, params: Dict[str, Any]) -> List[str]: - """Localize a single text string into multiple target locales. + """Localizes a single text string into multiple target locales. Args: text: The text string to translate. - params: Dictionary of options accepted by - :class:`LocalizationParams` plus a ``target_locales`` list. + params: Dictionary of options accepted by `LocalizationParams` plus + a `target_locales` list. Returns: - List of localized strings ordered to match ``target_locales``. + Localized strings ordered to match `target_locales`. Raises: - ValueError: If ``target_locales`` is missing or the API rejects a + ValueError: If `target_locales` is missing or the API rejects a request. RuntimeError: If the API responds with an error. Examples: - .. code-block:: python - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - variants = await engine.batch_localize_text( - "Welcome", - {"target_locales": ["es", "fr"]}, - ) - # variants -> ["Bienvenido", "Bienvenue"] (example output) - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - variants = await engine.batch_localize_text( - "Checkout", - { - "source_locale": "en", - "target_locales": ["pt-BR", "it"], - "fast": True, - }, - ) - # variants -> ["Finalizar compra", "Pagamento"] (example output) + ```python + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Welcome", + {"target_locales": ["es", "fr"]}, + ) + # variants -> ["Bienvenido", "Bienvenue"] (example output) + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Checkout", + { + "source_locale": "en", + "target_locales": ["pt-BR", "it"], + "fast": True, + }, + ) + # variants -> ["Finalizar compra", "Pagamento"] (example output) + ``` """ if "target_locales" not in params: raise ValueError("target_locales is required") @@ -510,48 +531,47 @@ async def localize_chat( params: Dict[str, Any], progress_callback: Optional[Callable[[int], None]] = None, ) -> List[Dict[str, str]]: - """Localize a chat transcript while preserving speaker metadata. + """Localizes a chat transcript while preserving speaker metadata. Args: - chat: Sequence of chat messages. Each item must include ``name`` and - ``text`` keys. - params: Dictionary of options accepted by - :class:`LocalizationParams`. + chat: Sequence of chat messages. Each item must include `name` and + `text` keys. + params: Dictionary of options accepted by `LocalizationParams`. progress_callback: Optional callable receiving percentage updates (0-100) while the transcript is localized. Returns: - List of localized chat messages in the same order as ``chat``. If - the API omits chat data an empty list is returned. + Localized chat messages in the same order as `chat`. If the API + omits chat data an empty list is returned. Raises: - ValueError: If any message in ``chat`` omits the required keys. + ValueError: If any message in `chat` omits the required keys. RuntimeError: If the API responds with an error. Examples: - .. code-block:: python - - chat = [ - {"name": "Alice", "text": "Hello"}, - {"name": "Bob", "text": "Goodbye"}, - ] - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - translated = await engine.localize_chat( - chat, - {"target_locale": "es"}, - ) - # translated -> [{"name": "Alice", "text": "Hola"}, ...] (example output) + ```python + chat = [ + {"name": "Alice", "text": "Hello"}, + {"name": "Bob", "text": "Goodbye"}, + ] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, + {"target_locale": "es"}, + ) + # translated -> [{"name": "Alice", "text": "Hola"}, ...] (example output) - updates = [] + updates = [] - async with LingoDotDevEngine({"api_key": "token"}) as engine: - translated = await engine.localize_chat( - chat, - {"source_locale": "en", "target_locale": "de"}, - progress_callback=updates.append, - ) - # translated -> [{"name": "Alice", "text": "Hallo"}, ...] (example output) + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, + {"source_locale": "en", "target_locale": "de"}, + progress_callback=updates.append, + ) + # translated -> [{"name": "Alice", "text": "Hallo"}, ...] (example output) + ``` """ # Validate chat format for message in chat: @@ -580,17 +600,17 @@ def wrapped_progress_callback( return [] async def recognize_locale(self, text: str) -> str: - """Detect the language of the supplied text via the ``/recognize`` endpoint. + """Detects the language of the supplied text. Args: text: Non-empty string to analyse. Returns: - Locale code reported by the API (for example ``"en"``) or an empty - string when the service cannot determine a locale. + Locale code reported by the API (for example `"en"`) or an + empty string when the service cannot determine a locale. Raises: - ValueError: If ``text`` is empty or only whitespace. + ValueError: If `text` is empty or only whitespace. RuntimeError: If the request fails or the API reports an error. """ if not text or not text.strip(): @@ -620,12 +640,12 @@ async def recognize_locale(self, text: str) -> str: raise RuntimeError(f"Request failed: {str(e)}") async def whoami(self) -> Optional[Dict[str, str]]: - """Retrieve account metadata associated with the current API key. + """Retrieves account metadata associated with the current API key. Returns: - Dictionary containing ``email`` and ``id`` keys when available, or - ``None`` if the key is unauthenticated or a recoverable network - error occurs. + Dictionary containing `email` and `id` keys when available, or + `None` if the key is unauthenticated or a recoverable network error + occurs. Raises: RuntimeError: If the service reports a server-side error. @@ -659,15 +679,15 @@ async def whoami(self) -> Optional[Dict[str, str]]: async def batch_localize_objects( self, objects: List[Dict[str, Any]], params: Dict[str, Any] ) -> List[Dict[str, Any]]: - """Localize multiple mapping objects concurrently. + """Localizes multiple mapping objects concurrently. Args: objects: List of objects whose string values should be translated. - params: Dictionary of options accepted by - :class:`LocalizationParams`, shared across all objects. + params: Dictionary of options accepted by `LocalizationParams`, + shared across all objects. Returns: - List of localized objects preserving the order of ``objects``. + Localized objects preserving the order of `objects`. Raises: RuntimeError: If the API responds with an error. @@ -690,50 +710,50 @@ async def quick_translate( api_url: str = "https://engine.lingo.dev", fast: bool = True, ) -> Any: - """Translate content without managing an engine instance manually. + """Translates content without managing an engine instance manually. - The helper opens an :class:`LingoDotDevEngine` using the supplied - configuration, performs the translation, and automatically closes the - underlying HTTP client. + The helper opens a `LingoDotDevEngine` using the supplied configuration, + performs the translation, and automatically closes the underlying HTTP + client. Args: content: Text string or mapping to translate. The returned value - matches the type of ``content``. + matches the type of `content`. api_key: Lingo.dev API key to authenticate the request. target_locale: Target language code for the translation. source_locale: Optional source language code. When omitted the API may attempt to detect it. api_url: Lingo.dev engine base URL. Defaults to - ``"https://engine.lingo.dev"``. + `"https://engine.lingo.dev"`. fast: Whether to enable the service's fast translation mode. Returns: - Translated content with the same type as ``content``. + Translated content with the same type as `content`. Raises: - ValueError: If ``content`` is not a string or dictionary, or if the + ValueError: If `content` is not a string or dictionary, or if the service rejects the request. RuntimeError: If the API indicates a failure or the request cannot be completed. Examples: - .. code-block:: python - - greeting = await LingoDotDevEngine.quick_translate( - "Hello world", - api_key="api-key", - target_locale="es", - ) - # greeting -> "Hola mundo" (example output) - - landing_page = await LingoDotDevEngine.quick_translate( - {"headline": "Hello", "cta": "Buy now"}, - api_key="api-key", - target_locale="de", - source_locale="en", - fast=False, - ) - # landing_page -> {"headline": "Hallo", "cta": "Jetzt kaufen"} (example output) + ```python + greeting = await LingoDotDevEngine.quick_translate( + "Hello world", + api_key="api-key", + target_locale="es", + ) + # greeting -> "Hola mundo" (example output) + + landing_page = await LingoDotDevEngine.quick_translate( + {"headline": "Hello", "cta": "Buy now"}, + api_key="api-key", + target_locale="de", + source_locale="en", + fast=False, + ) + # landing_page -> {"headline": "Hallo", "cta": "Jetzt kaufen"} (example output) + ``` """ config = { "api_key": api_key, @@ -764,7 +784,7 @@ async def quick_batch_translate( api_url: str = "https://engine.lingo.dev", fast: bool = True, ) -> List[Any]: - """Translate content into multiple locales without manual setup. + """Translates content into multiple locales without manual setup. Args: content: Text string or mapping to translate for each locale. @@ -773,39 +793,39 @@ async def quick_batch_translate( source_locale: Optional source language code. When omitted the API may attempt to detect it. api_url: Lingo.dev engine base URL. Defaults to - ``"https://engine.lingo.dev"``. + `"https://engine.lingo.dev"`. fast: Whether to enable the service's fast translation mode. Returns: - List of translated content, one entry per ``target_locales`` item. + Translated content, one entry per `target_locales` item. Raises: - ValueError: If ``content`` is not a string or dictionary, or if a + ValueError: If `content` is not a string or dictionary, or if a request is rejected by the API. RuntimeError: If the API indicates a failure or the request cannot be completed. Examples: - .. code-block:: python - - variants = await LingoDotDevEngine.quick_batch_translate( - "Hello world", - api_key="api-key", - target_locales=["es", "fr"], - ) - # variants -> ["Hola mundo", "Bonjour le monde"] (example output) - - localized_objects = await LingoDotDevEngine.quick_batch_translate( - {"success": "Saved", "error": "Failed"}, - api_key="api-key", - target_locales=["pt-BR", "it"], - source_locale="en", - fast=False, - ) - # localized_objects -> [ - # {"success": "Salvo", "error": "Falhou"}, - # {"success": "Salvato", "error": "Non riuscito"}, - # ] (example output) + ```python + variants = await LingoDotDevEngine.quick_batch_translate( + "Hello world", + api_key="api-key", + target_locales=["es", "fr"], + ) + # variants -> ["Hola mundo", "Bonjour le monde"] (example output) + + localized_objects = await LingoDotDevEngine.quick_batch_translate( + {"success": "Saved", "error": "Failed"}, + api_key="api-key", + target_locales=["pt-BR", "it"], + source_locale="en", + fast=False, + ) + # localized_objects -> [ + # {"success": "Salvo", "error": "Falhou"}, + # {"success": "Salvato", "error": "Non riuscito"}, + # ] (example output) + ``` """ config = { "api_key": api_key, From a68a275e122327ced10aa14957567e46b2f65411 Mon Sep 17 00:00:00 2001 From: davidturnbull <4712958+davidturnbull@users.noreply.github.com> Date: Fri, 19 Sep 2025 01:25:02 +0000 Subject: [PATCH 8/8] chore(docs): update generated API docs --- docs/api.md | 427 +++++++++++++++++++++++++++++----------------------- 1 file changed, 240 insertions(+), 187 deletions(-) diff --git a/docs/api.md b/docs/api.md index a4d525b..0448e5a 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2,7 +2,9 @@ * [lingodotdev.engine](#lingodotdev.engine) * [EngineConfig](#lingodotdev.engine.EngineConfig) + * [validate\_api\_url](#lingodotdev.engine.EngineConfig.validate_api_url) * [LocalizationParams](#lingodotdev.engine.LocalizationParams) + * [validate\_locale](#lingodotdev.engine.LocalizationParams.validate_locale) * [LingoDotDevEngine](#lingodotdev.engine.LingoDotDevEngine) * [\_\_init\_\_](#lingodotdev.engine.LingoDotDevEngine.__init__) * [\_\_aenter\_\_](#lingodotdev.engine.LingoDotDevEngine.__aenter__) @@ -24,8 +26,12 @@ Async client implementation for the Lingo.dev localization service. -This module houses :class:`LingoDotDevEngine` alongside supporting data models -used to validate configuration and localization parameters. +This module provides LingoDotDevEngine and supporting data models for +configuration and localization parameter validation. + + config = {"api_key": "your-api-key"} + async with LingoDotDevEngine(config) as engine: + result = await engine.localize_text("Hello", {"target_locale": "es"}) @@ -35,18 +41,47 @@ used to validate configuration and localization parameters. class EngineConfig(BaseModel) ``` -Stores and validates runtime configuration for :class:`LingoDotDevEngine`. +Runtime configuration for LingoDotDevEngine. + +Stores and validates configuration parameters required to interact with the +Lingo.dev API. **Attributes**: - `api_key` - Secret token used to authenticate with the Lingo.dev API. - `api_url` - Base endpoint for the localization engine. Defaults to - ``https://engine.lingo.dev``. + 'https://engine.lingo.dev'. - `batch_size` - Maximum number of top-level entries to send in a single localization request (between 1 and 250 inclusive). - `ideal_batch_item_size` - Target word count per request before payloads are split into multiple batches (between 1 and 2500 inclusive). + + +### validate\_api\_url + +```python +@validator("api_url") +@classmethod +def validate_api_url(cls, v: str) -> str +``` + +Validates that the API URL is a valid HTTP/HTTPS URL. + +**Arguments**: + +- `v` - The URL string to validate. + + +**Returns**: + + The validated URL string. + + +**Raises**: + +- `ValueError` - If the URL doesn't start with http:// or https://. + ## LocalizationParams Objects @@ -55,21 +90,47 @@ Stores and validates runtime configuration for :class:`LingoDotDevEngine`. class LocalizationParams(BaseModel) ``` -Request parameters accepted by localization operations. +Request parameters for localization operations. These values are serialized directly into API requests after validation. **Attributes**: - `source_locale` - Optional BCP 47 language code representing the source - language. When omitted the API attempts automatic detection. -- `target_locale` - Required BCP 47 language code for the desired translation - target. + language. When omitted, the API attempts automatic detection. +- `target_locale` - Required BCP 47 language code for the desired + translation target. - `fast` - Optional flag that enables the service's low-latency translation mode at the cost of some quality safeguards. - `reference` - Optional nested mapping of existing translations that provides additional context to the engine. + + +### validate\_locale + +```python +@validator("source_locale", "target_locale") +@classmethod +def validate_locale(cls, v: Optional[str]) -> Optional[str] +``` + +Validates that locale codes conform to BCP 47 standards. + +**Arguments**: + +- `v` - The locale string to validate, or None. + + +**Returns**: + + The validated locale string or None. + + +**Raises**: + +- `ValueError` - If the locale is not a valid BCP 47 language tag. + ## LingoDotDevEngine Objects @@ -80,15 +141,11 @@ class LingoDotDevEngine() Asynchronous client for the Lingo.dev localization API. -The engine manages an :class:`httpx.AsyncClient`, handles chunking and -batching of content, and exposes helper coroutines for translating strings, -structured objects, and chat transcripts. Instances can be reused or -managed via an async context manager:: +The engine manages an httpx.AsyncClient, handles chunking and batching of +content, and exposes helper coroutines for translating strings, structured +objects, and chat transcripts. - async with LingoDotDevEngine({"api_key": "..."}) as engine: - await engine.localize_text("Hello", {"target_locale": "es"}) - -All localization methods are ``async`` and must be awaited. +All localization methods are async and must be awaited. @@ -98,13 +155,12 @@ All localization methods are ``async`` and must be awaited. def __init__(config: Dict[str, Any]) ``` -Instantiate the engine with configuration data. +Instantiates the engine with configuration data. **Arguments**: -- `config` - Mapping of values understood by - :class:`EngineConfig`. At minimum an ``api_key`` entry must - be supplied. +- `config` - Mapping of values understood by `EngineConfig`. At minimum + an `api_key` entry must be supplied. **Raises**: @@ -119,11 +175,11 @@ Instantiate the engine with configuration data. async def __aenter__() ``` -Open the HTTP session when entering an async context. +Opens the HTTP session when entering an async context. **Returns**: -- `LingoDotDevEngine` - The active engine instance. +- ``LingoDotDevEngine`` - The active engine instance. @@ -133,7 +189,7 @@ Open the HTTP session when entering an async context. async def __aexit__(exc_type, exc_val, exc_tb) ``` -Release resources acquired during the async context. +Releases resources acquired during the async context. @@ -143,7 +199,7 @@ Release resources acquired during the async context. async def close() ``` -Close the HTTP client if it has been created. +Closes the HTTP client if it has been created. @@ -158,24 +214,23 @@ async def localize_object(obj: Dict[str, Any], concurrent: bool = False) -> Dict[str, Any] ``` -Localize every string value contained in a mapping. +Localizes every string value contained in a mapping. **Arguments**: - `obj` - Mapping whose string leaves should be translated. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams`. +- `params` - Dictionary of options accepted by `LocalizationParams`. - `progress_callback` - Optional callable invoked with progress updates - (0-100) alongside the source and localized chunks. If provided, - leave ``concurrent`` as ``False`` (the default) because progress + (0-100) alongside the source and localized chunks. Do not set + `concurrent` when providing this callback because progress updates are unavailable in concurrent mode. -- `concurrent` - When ``True`` the payload chunks are processed +- `concurrent` - When `True` the payload chunks are processed concurrently and no progress updates are emitted. **Returns**: - A dictionary mirroring ``obj`` with localized string values. + A dictionary mirroring `obj` with localized string values. **Raises**: @@ -186,26 +241,26 @@ Localize every string value contained in a mapping. **Examples**: - .. code-block:: python - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - localized = await engine.localize_object( -- `{"title"` - "Hello"}, -- `{"target_locale"` - "es"}, - concurrent=True, - ) - # localized -> {"title": "Hola"} (example output) - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - def on_progress(percent, *_): -- `print(f"Progress` - {percent}%") - - localized = await engine.localize_object( -- `{"welcome"` - "Hello", "farewell": "Goodbye"}, -- `{"source_locale"` - "en", "target_locale": "fr"}, - progress_callback=on_progress, - ) - # localized -> {"welcome": "Bonjour", "farewell": "Au revoir"} (example output) + ```python + async with LingoDotDevEngine({"api_key": "token"}) as engine: + localized = await engine.localize_object( + {"title": "Hello"}, + {"target_locale": "es"}, + concurrent=True, + ) + # localized -> {"title": "Hola"} (example output) + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + def on_progress(percent, *_): + print(f"Progress: {percent}%") + + localized = await engine.localize_object( + {"welcome": "Hello", "farewell": "Goodbye"}, + {"source_locale": "en", "target_locale": "fr"}, + progress_callback=on_progress, + ) + # localized -> {"welcome": "Bonjour", "farewell": "Au revoir"} (example output) + ``` @@ -218,13 +273,12 @@ async def localize_text( progress_callback: Optional[Callable[[int], None]] = None) -> str ``` -Localize a single text string. +Localizes a single text string. **Arguments**: - `text` - The text to translate. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams`. +- `params` - Dictionary of options accepted by `LocalizationParams`. - `progress_callback` - Optional callable receiving the percentage complete (0-100). @@ -242,23 +296,23 @@ Localize a single text string. **Examples**: - .. code-block:: python - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - greeting = await engine.localize_text( - "Hello", {"target_locale": "de"} - ) - # greeting -> "Hallo" (example output) - - progress_updates = [] - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - farewell = await engine.localize_text( - "Goodbye", -- `{"source_locale"` - "en", "target_locale": "it"}, - progress_callback=progress_updates.append, - ) - # farewell -> "Arrivederci" (example output) + ```python + async with LingoDotDevEngine({"api_key": "token"}) as engine: + greeting = await engine.localize_text( + "Hello", {"target_locale": "de"} + ) + # greeting -> "Hallo" (example output) + + progress_updates = [] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + farewell = await engine.localize_text( + "Goodbye", + {"source_locale": "en", "target_locale": "it"}, + progress_callback=progress_updates.append, + ) + # farewell -> "Arrivederci" (example output) + ``` @@ -268,48 +322,48 @@ Localize a single text string. async def batch_localize_text(text: str, params: Dict[str, Any]) -> List[str] ``` -Localize a single text string into multiple target locales. +Localizes a single text string into multiple target locales. **Arguments**: - `text` - The text string to translate. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams` plus a ``target_locales`` list. +- `params` - Dictionary of options accepted by `LocalizationParams` plus + a `target_locales` list. **Returns**: - List of localized strings ordered to match ``target_locales``. + Localized strings ordered to match `target_locales`. **Raises**: -- `ValueError` - If ``target_locales`` is missing or the API rejects a +- `ValueError` - If `target_locales` is missing or the API rejects a request. - `RuntimeError` - If the API responds with an error. **Examples**: - .. code-block:: python - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - variants = await engine.batch_localize_text( - "Welcome", -- `{"target_locales"` - ["es", "fr"]}, - ) - # variants -> ["Bienvenido", "Bienvenue"] (example output) - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - variants = await engine.batch_localize_text( - "Checkout", - { -- `"source_locale"` - "en", -- `"target_locales"` - ["pt-BR", "it"], -- `"fast"` - True, - }, - ) - # variants -> ["Finalizar compra", "Pagamento"] (example output) + ```python + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Welcome", + {"target_locales": ["es", "fr"]}, + ) + # variants -> ["Bienvenido", "Bienvenue"] (example output) + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + variants = await engine.batch_localize_text( + "Checkout", + { + "source_locale": "en", + "target_locales": ["pt-BR", "it"], + "fast": True, + }, + ) + # variants -> ["Finalizar compra", "Pagamento"] (example output) + ``` @@ -323,55 +377,54 @@ async def localize_chat( ) -> List[Dict[str, str]] ``` -Localize a chat transcript while preserving speaker metadata. +Localizes a chat transcript while preserving speaker metadata. **Arguments**: -- `chat` - Sequence of chat messages. Each item must include ``name`` and - ``text`` keys. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams`. +- `chat` - Sequence of chat messages. Each item must include `name` and + `text` keys. +- `params` - Dictionary of options accepted by `LocalizationParams`. - `progress_callback` - Optional callable receiving percentage updates (0-100) while the transcript is localized. **Returns**: - List of localized chat messages in the same order as ``chat``. If - the API omits chat data an empty list is returned. + Localized chat messages in the same order as `chat`. If the API + omits chat data an empty list is returned. **Raises**: -- `ValueError` - If any message in ``chat`` omits the required keys. +- `ValueError` - If any message in `chat` omits the required keys. - `RuntimeError` - If the API responds with an error. **Examples**: - .. code-block:: python - - chat = [ -- `{"name"` - "Alice", "text": "Hello"}, -- `{"name"` - "Bob", "text": "Goodbye"}, - ] - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - translated = await engine.localize_chat( - chat, -- `{"target_locale"` - "es"}, - ) - # translated -> [{"name": "Alice", "text": "Hola"}, ...] (example output) - - updates = [] - - async with LingoDotDevEngine({"api_key": "token"}) as engine: - translated = await engine.localize_chat( - chat, -- `{"source_locale"` - "en", "target_locale": "de"}, - progress_callback=updates.append, - ) - # translated -> [{"name": "Alice", "text": "Hallo"}, ...] (example output) + ```python + chat = [ + {"name": "Alice", "text": "Hello"}, + {"name": "Bob", "text": "Goodbye"}, + ] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, + {"target_locale": "es"}, + ) + # translated -> [{"name": "Alice", "text": "Hola"}, ...] (example output) + + updates = [] + + async with LingoDotDevEngine({"api_key": "token"}) as engine: + translated = await engine.localize_chat( + chat, + {"source_locale": "en", "target_locale": "de"}, + progress_callback=updates.append, + ) + # translated -> [{"name": "Alice", "text": "Hallo"}, ...] (example output) + ``` @@ -381,7 +434,7 @@ Localize a chat transcript while preserving speaker metadata. async def recognize_locale(text: str) -> str ``` -Detect the language of the supplied text via the ``/recognize`` endpoint. +Detects the language of the supplied text. **Arguments**: @@ -390,13 +443,13 @@ Detect the language of the supplied text via the ``/recognize`` endpoint. **Returns**: - Locale code reported by the API (for example ``"en"``) or an empty - string when the service cannot determine a locale. + Locale code reported by the API (for example `"en"`) or an + empty string when the service cannot determine a locale. **Raises**: -- `ValueError` - If ``text`` is empty or only whitespace. +- `ValueError` - If `text` is empty or only whitespace. - `RuntimeError` - If the request fails or the API reports an error. @@ -407,13 +460,13 @@ Detect the language of the supplied text via the ``/recognize`` endpoint. async def whoami() -> Optional[Dict[str, str]] ``` -Retrieve account metadata associated with the current API key. +Retrieves account metadata associated with the current API key. **Returns**: - Dictionary containing ``email`` and ``id`` keys when available, or - ``None`` if the key is unauthenticated or a recoverable network - error occurs. + Dictionary containing `email` and `id` keys when available, or + `None` if the key is unauthenticated or a recoverable network error + occurs. **Raises**: @@ -430,18 +483,18 @@ async def batch_localize_objects( params: Dict[str, Any]) -> List[Dict[str, Any]] ``` -Localize multiple mapping objects concurrently. +Localizes multiple mapping objects concurrently. **Arguments**: - `objects` - List of objects whose string values should be translated. -- `params` - Dictionary of options accepted by - :class:`LocalizationParams`, shared across all objects. +- `params` - Dictionary of options accepted by `LocalizationParams`, + shared across all objects. **Returns**: - List of localized objects preserving the order of ``objects``. + Localized objects preserving the order of `objects`. **Raises**: @@ -464,33 +517,33 @@ async def quick_translate(cls, fast: bool = True) -> Any ``` -Translate content without managing an engine instance manually. +Translates content without managing an engine instance manually. -The helper opens an :class:`LingoDotDevEngine` using the supplied -configuration, performs the translation, and automatically closes the -underlying HTTP client. +The helper opens a `LingoDotDevEngine` using the supplied configuration, +performs the translation, and automatically closes the underlying HTTP +client. **Arguments**: - `content` - Text string or mapping to translate. The returned value - matches the type of ``content``. + matches the type of `content`. - `api_key` - Lingo.dev API key to authenticate the request. - `target_locale` - Target language code for the translation. - `source_locale` - Optional source language code. When omitted the API may attempt to detect it. - `api_url` - Lingo.dev engine base URL. Defaults to - ``"https://engine.lingo.dev"``. + `"https://engine.lingo.dev"`. - `fast` - Whether to enable the service's fast translation mode. **Returns**: - Translated content with the same type as ``content``. + Translated content with the same type as `content`. **Raises**: -- `ValueError` - If ``content`` is not a string or dictionary, or if the +- `ValueError` - If `content` is not a string or dictionary, or if the service rejects the request. - `RuntimeError` - If the API indicates a failure or the request cannot be completed. @@ -498,23 +551,23 @@ underlying HTTP client. **Examples**: - .. code-block:: python - - greeting = await LingoDotDevEngine.quick_translate( - "Hello world", - api_key="api-key", - target_locale="es", - ) - # greeting -> "Hola mundo" (example output) - - landing_page = await LingoDotDevEngine.quick_translate( -- `{"headline"` - "Hello", "cta": "Buy now"}, - api_key="api-key", - target_locale="de", - source_locale="en", - fast=False, - ) - # landing_page -> {"headline": "Hallo", "cta": "Jetzt kaufen"} (example output) + ```python + greeting = await LingoDotDevEngine.quick_translate( + "Hello world", + api_key="api-key", + target_locale="es", + ) + # greeting -> "Hola mundo" (example output) + + landing_page = await LingoDotDevEngine.quick_translate( + {"headline": "Hello", "cta": "Buy now"}, + api_key="api-key", + target_locale="de", + source_locale="en", + fast=False, + ) + # landing_page -> {"headline": "Hallo", "cta": "Jetzt kaufen"} (example output) + ``` @@ -531,7 +584,7 @@ async def quick_batch_translate(cls, fast: bool = True) -> List[Any] ``` -Translate content into multiple locales without manual setup. +Translates content into multiple locales without manual setup. **Arguments**: @@ -541,18 +594,18 @@ Translate content into multiple locales without manual setup. - `source_locale` - Optional source language code. When omitted the API may attempt to detect it. - `api_url` - Lingo.dev engine base URL. Defaults to - ``"https://engine.lingo.dev"``. + `"https://engine.lingo.dev"`. - `fast` - Whether to enable the service's fast translation mode. **Returns**: - List of translated content, one entry per ``target_locales`` item. + Translated content, one entry per `target_locales` item. **Raises**: -- `ValueError` - If ``content`` is not a string or dictionary, or if a +- `ValueError` - If `content` is not a string or dictionary, or if a request is rejected by the API. - `RuntimeError` - If the API indicates a failure or the request cannot be completed. @@ -560,24 +613,24 @@ Translate content into multiple locales without manual setup. **Examples**: - .. code-block:: python - - variants = await LingoDotDevEngine.quick_batch_translate( - "Hello world", - api_key="api-key", - target_locales=["es", "fr"], - ) - # variants -> ["Hola mundo", "Bonjour le monde"] (example output) - - localized_objects = await LingoDotDevEngine.quick_batch_translate( -- `{"success"` - "Saved", "error": "Failed"}, - api_key="api-key", - target_locales=["pt-BR", "it"], - source_locale="en", - fast=False, - ) - # localized_objects -> [ - # {"success": "Salvo", "error": "Falhou"}, - # {"success": "Salvato", "error": "Non riuscito"}, - # ] (example output) + ```python + variants = await LingoDotDevEngine.quick_batch_translate( + "Hello world", + api_key="api-key", + target_locales=["es", "fr"], + ) + # variants -> ["Hola mundo", "Bonjour le monde"] (example output) + + localized_objects = await LingoDotDevEngine.quick_batch_translate( + {"success": "Saved", "error": "Failed"}, + api_key="api-key", + target_locales=["pt-BR", "it"], + source_locale="en", + fast=False, + ) + # localized_objects -> [ + # {"success": "Salvo", "error": "Falhou"}, + # {"success": "Salvato", "error": "Non riuscito"}, + # ] (example output) + ```