lingva-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,57 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # Dependencies
4
+ node_modules
5
+ .pnp
6
+ .pnp.js
7
+
8
+ # Local env files
9
+ .env*
10
+ !.env*.example
11
+ .npmrc.private
12
+
13
+ # Testing
14
+ coverage
15
+ .lingva/local-remote/
16
+
17
+ # Turbo
18
+ .turbo
19
+
20
+ # Vercel
21
+ .vercel
22
+
23
+ # Build Outputs
24
+ .angular/
25
+ .next/
26
+ .nuxt/
27
+ .output/
28
+ .svelte-kit/
29
+ .build/
30
+ .dart_tool/
31
+ .gradle/
32
+ out/
33
+ build
34
+ dist
35
+
36
+ # Python
37
+ __pycache__/
38
+ .pytest_cache/
39
+ *.pyc
40
+ .venv/
41
+ venv/
42
+
43
+ # Native SDK package-manager state
44
+ .flutter-plugins
45
+ .flutter-plugins-dependencies
46
+ sdks/flutter/pubspec.lock
47
+
48
+
49
+ # Debug
50
+ npm-debug.log*
51
+
52
+ # Misc
53
+ .DS_Store
54
+ *.pem
55
+ .env*.local
56
+
57
+ .pnpm-store/
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.5
2
+ Name: lingva-sdk
3
+ Version: 0.1.0
4
+ Summary: Python client for the Lingva localization platform
5
+ License: SEE LICENSE IN ../../LICENSE.md
6
+ Requires-Python: >=3.11
7
+ Provides-Extra: dev
8
+ Requires-Dist: httpx>=0.27; extra == 'dev'
9
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
10
+ Requires-Dist: pytest>=8; extra == 'dev'
11
+ Provides-Extra: http
12
+ Requires-Dist: httpx>=0.27; extra == 'http'
13
+ Description-Content-Type: text/markdown
14
+
15
+ # lingva — Python SDK
16
+
17
+ Python client for the [Lingva](https://lingva.dev) localization platform.
18
+
19
+ **Requires Python 3.11+.**
20
+
21
+ > This is a preview package. It follows the same conformance contract as the Swift, Kotlin, and Flutter SDKs but is not yet published to PyPI.
22
+
23
+ ---
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ # From the monorepo root (editable install)
29
+ pip install -e sdks/python
30
+
31
+ # With async HTTP support (httpx)
32
+ pip install -e "sdks/python[http]"
33
+ ```
34
+
35
+ Once published:
36
+
37
+ ```bash
38
+ pip install lingva-sdk
39
+ pip install "lingva-sdk[http]" # adds httpx for async delivery
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Quickstart
45
+
46
+ ### Load from a local bundle file
47
+
48
+ ```python
49
+ from lingva import LingvaClient
50
+
51
+ client = LingvaClient.from_file(".lingva/translations/en.bundle.json")
52
+
53
+ print(client.t("actions.continue")) # Continue
54
+ print(client.t("welcome.title", name="Alice")) # Welcome, Alice.
55
+ ```
56
+
57
+ ### Load from CDN (async)
58
+
59
+ ```python
60
+ import asyncio
61
+ from lingva import LingvaClient, LingvaDeliveryTarget
62
+
63
+ target = LingvaDeliveryTarget(
64
+ base_url="https://api.lingva.dev",
65
+ project_id="my-project",
66
+ environment="production",
67
+ )
68
+
69
+ async def main():
70
+ client = await LingvaClient.from_url(target, locale="fr", fallback_locale="en")
71
+ print(client.t("welcome.title", name="Alice"))
72
+
73
+ asyncio.run(main())
74
+ ```
75
+
76
+ ### Load from CDN (synchronous)
77
+
78
+ ```python
79
+ from lingva import LingvaClient, LingvaDeliveryTarget
80
+
81
+ target = LingvaDeliveryTarget(
82
+ base_url="https://api.lingva.dev",
83
+ project_id="my-project",
84
+ environment="production",
85
+ )
86
+
87
+ client = LingvaClient.from_url_sync(target, locale="fr", fallback_locale="en")
88
+ print(client.t("welcome.title", name="Alice"))
89
+ ```
90
+
91
+ ---
92
+
93
+ ## API
94
+
95
+ ### `LingvaClient`
96
+
97
+ | Method / Property | Description |
98
+ |---|---|
99
+ | `LingvaClient.from_bundle(bundle)` | Create from a parsed dict |
100
+ | `LingvaClient.from_file(path, *, fallback_path=None)` | Load from filesystem |
101
+ | `LingvaClient.from_url_sync(target, locale, ...)` | Synchronous HTTP fetch |
102
+ | `await LingvaClient.from_url(target, locale, ...)` | Async HTTP fetch (requires `lingva[http]`) |
103
+ | `.t(key, **variables)` | Translate a key with optional `{varname}` substitution |
104
+ | `.has(key)` | Check if a key exists |
105
+ | `.locale` | Active locale string |
106
+ | `.fallback_locale` | Fallback locale string or `None` |
107
+ | `.bundle()` | Raw bundle dict (for last-valid caching) |
108
+
109
+ ### `LingvaDeliveryTarget`
110
+
111
+ ```python
112
+ LingvaDeliveryTarget(
113
+ base_url="https://api.lingva.dev",
114
+ project_id="my-project",
115
+ environment="production", # default
116
+ uri_template="...", # optional custom path template
117
+ )
118
+ target.resolve("fr")
119
+ # -> "https://api.lingva.dev/bundles/my-project/production/latest/fr.bundle.json"
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Bundle schema
125
+
126
+ The SDK reads schemaVersion 1 bundles. Variables use `{varname}` syntax. Unknown variables pass through unchanged.
127
+
128
+ ```json
129
+ {
130
+ "schemaVersion": 1,
131
+ "locale": "en",
132
+ "fallbackLocale": "en",
133
+ "fallbackKeys": [],
134
+ "messages": {
135
+ "actions.continue": "Continue",
136
+ "welcome.title": "Welcome, {name}."
137
+ }
138
+ }
139
+ ```
@@ -0,0 +1,125 @@
1
+ # lingva — Python SDK
2
+
3
+ Python client for the [Lingva](https://lingva.dev) localization platform.
4
+
5
+ **Requires Python 3.11+.**
6
+
7
+ > This is a preview package. It follows the same conformance contract as the Swift, Kotlin, and Flutter SDKs but is not yet published to PyPI.
8
+
9
+ ---
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ # From the monorepo root (editable install)
15
+ pip install -e sdks/python
16
+
17
+ # With async HTTP support (httpx)
18
+ pip install -e "sdks/python[http]"
19
+ ```
20
+
21
+ Once published:
22
+
23
+ ```bash
24
+ pip install lingva-sdk
25
+ pip install "lingva-sdk[http]" # adds httpx for async delivery
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Quickstart
31
+
32
+ ### Load from a local bundle file
33
+
34
+ ```python
35
+ from lingva import LingvaClient
36
+
37
+ client = LingvaClient.from_file(".lingva/translations/en.bundle.json")
38
+
39
+ print(client.t("actions.continue")) # Continue
40
+ print(client.t("welcome.title", name="Alice")) # Welcome, Alice.
41
+ ```
42
+
43
+ ### Load from CDN (async)
44
+
45
+ ```python
46
+ import asyncio
47
+ from lingva import LingvaClient, LingvaDeliveryTarget
48
+
49
+ target = LingvaDeliveryTarget(
50
+ base_url="https://api.lingva.dev",
51
+ project_id="my-project",
52
+ environment="production",
53
+ )
54
+
55
+ async def main():
56
+ client = await LingvaClient.from_url(target, locale="fr", fallback_locale="en")
57
+ print(client.t("welcome.title", name="Alice"))
58
+
59
+ asyncio.run(main())
60
+ ```
61
+
62
+ ### Load from CDN (synchronous)
63
+
64
+ ```python
65
+ from lingva import LingvaClient, LingvaDeliveryTarget
66
+
67
+ target = LingvaDeliveryTarget(
68
+ base_url="https://api.lingva.dev",
69
+ project_id="my-project",
70
+ environment="production",
71
+ )
72
+
73
+ client = LingvaClient.from_url_sync(target, locale="fr", fallback_locale="en")
74
+ print(client.t("welcome.title", name="Alice"))
75
+ ```
76
+
77
+ ---
78
+
79
+ ## API
80
+
81
+ ### `LingvaClient`
82
+
83
+ | Method / Property | Description |
84
+ |---|---|
85
+ | `LingvaClient.from_bundle(bundle)` | Create from a parsed dict |
86
+ | `LingvaClient.from_file(path, *, fallback_path=None)` | Load from filesystem |
87
+ | `LingvaClient.from_url_sync(target, locale, ...)` | Synchronous HTTP fetch |
88
+ | `await LingvaClient.from_url(target, locale, ...)` | Async HTTP fetch (requires `lingva[http]`) |
89
+ | `.t(key, **variables)` | Translate a key with optional `{varname}` substitution |
90
+ | `.has(key)` | Check if a key exists |
91
+ | `.locale` | Active locale string |
92
+ | `.fallback_locale` | Fallback locale string or `None` |
93
+ | `.bundle()` | Raw bundle dict (for last-valid caching) |
94
+
95
+ ### `LingvaDeliveryTarget`
96
+
97
+ ```python
98
+ LingvaDeliveryTarget(
99
+ base_url="https://api.lingva.dev",
100
+ project_id="my-project",
101
+ environment="production", # default
102
+ uri_template="...", # optional custom path template
103
+ )
104
+ target.resolve("fr")
105
+ # -> "https://api.lingva.dev/bundles/my-project/production/latest/fr.bundle.json"
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Bundle schema
111
+
112
+ The SDK reads schemaVersion 1 bundles. Variables use `{varname}` syntax. Unknown variables pass through unchanged.
113
+
114
+ ```json
115
+ {
116
+ "schemaVersion": 1,
117
+ "locale": "en",
118
+ "fallbackLocale": "en",
119
+ "fallbackKeys": [],
120
+ "messages": {
121
+ "actions.continue": "Continue",
122
+ "welcome.title": "Welcome, {name}."
123
+ }
124
+ }
125
+ ```
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "lingva-sdk"
7
+ version = "0.1.0"
8
+ description = "Python client for the Lingva localization platform"
9
+ readme = "README.md"
10
+ license = { text = "SEE LICENSE IN ../../LICENSE.md" }
11
+ requires-python = ">=3.11"
12
+ dependencies = []
13
+
14
+ [project.optional-dependencies]
15
+ http = ["httpx>=0.27"]
16
+ dev = ["pytest>=8", "pytest-asyncio>=0.24", "httpx>=0.27"]
17
+
18
+ [tool.hatch.build.targets.wheel]
19
+ packages = ["src/lingva"]
20
+
21
+ [tool.pytest.ini_options]
22
+ asyncio_mode = "auto"
23
+ testpaths = ["tests"]
@@ -0,0 +1,14 @@
1
+ from lingva._client import LingvaClient, LingvaSchemaError
2
+ from lingva._delivery import (
3
+ DEFAULT_REMOTE_BUNDLE_PATH_TEMPLATE,
4
+ LingvaDeliveryTarget,
5
+ load_bundle_from_file,
6
+ )
7
+
8
+ __all__ = [
9
+ "DEFAULT_REMOTE_BUNDLE_PATH_TEMPLATE",
10
+ "LingvaClient",
11
+ "LingvaDeliveryTarget",
12
+ "LingvaSchemaError",
13
+ "load_bundle_from_file",
14
+ ]
@@ -0,0 +1,153 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from lingva._delivery import (
8
+ LingvaDeliveryTarget,
9
+ load_bundle_from_file,
10
+ load_bundle_from_url_async,
11
+ load_bundle_from_url_sync,
12
+ )
13
+
14
+ _SCHEMA_VERSION = 1
15
+ _PLACEHOLDER_RE = re.compile(r"\{(\w+)\}")
16
+
17
+
18
+ def _interpolate(template: str, variables: dict[str, str]) -> str:
19
+ """Replace {varname} tokens; unknown tokens pass through unchanged."""
20
+ def replace(m: re.Match[str]) -> str:
21
+ return variables.get(m.group(1), m.group(0))
22
+ return _PLACEHOLDER_RE.sub(replace, template)
23
+
24
+
25
+ class LingvaSchemaError(ValueError):
26
+ """Raised when a bundle fails schema validation."""
27
+
28
+
29
+ class LingvaClient:
30
+ """
31
+ Reads a Lingva locale bundle and provides key lookup with fallback and
32
+ variable interpolation.
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ bundle: dict[str, Any],
38
+ fallback_bundle: dict[str, Any] | None = None,
39
+ ) -> None:
40
+ if bundle.get("schemaVersion") != _SCHEMA_VERSION:
41
+ raise LingvaSchemaError(
42
+ f"Unsupported bundle schemaVersion: {bundle.get('schemaVersion')!r}"
43
+ )
44
+ self._bundle = bundle
45
+ self._messages: dict[str, str] = bundle.get("messages") or {}
46
+ self._fallback_messages: dict[str, str] = (
47
+ fallback_bundle.get("messages") or {} if fallback_bundle else {}
48
+ )
49
+ self._fallback_keys: set[str] = set(bundle.get("fallbackKeys") or [])
50
+ self._locale: str = bundle.get("locale") or bundle.get("defaultLocale") or "en"
51
+ self._fallback_locale: str | None = bundle.get("fallbackLocale")
52
+ self._last_valid: dict[str, Any] = bundle
53
+
54
+ # ------------------------------------------------------------------
55
+ # Factory methods
56
+ # ------------------------------------------------------------------
57
+
58
+ @classmethod
59
+ def from_bundle(cls, bundle: dict[str, Any]) -> "LingvaClient":
60
+ """Create a client from an already-parsed bundle dict."""
61
+ return cls(bundle)
62
+
63
+ @classmethod
64
+ def from_file(cls, path: str | Path, *, fallback_path: str | Path | None = None) -> "LingvaClient":
65
+ """Load a bundle from the local filesystem."""
66
+ bundle = load_bundle_from_file(path)
67
+ fallback = load_bundle_from_file(fallback_path) if fallback_path else None
68
+ return cls(bundle, fallback)
69
+
70
+ @classmethod
71
+ def from_url_sync(
72
+ cls,
73
+ target: LingvaDeliveryTarget,
74
+ locale: str,
75
+ *,
76
+ fallback_locale: str | None = None,
77
+ last_valid: dict[str, Any] | None = None,
78
+ ) -> "LingvaClient":
79
+ """Fetch a bundle synchronously; return a client over `last_valid` on error."""
80
+ try:
81
+ bundle = load_bundle_from_url_sync(target.resolve(locale))
82
+ except Exception:
83
+ if last_valid is not None:
84
+ return cls(last_valid)
85
+ raise
86
+ fallback = None
87
+ if fallback_locale and fallback_locale != locale:
88
+ try:
89
+ fallback = load_bundle_from_url_sync(target.resolve(fallback_locale))
90
+ except Exception:
91
+ pass
92
+ return cls(bundle, fallback)
93
+
94
+ @classmethod
95
+ async def from_url(
96
+ cls,
97
+ target: LingvaDeliveryTarget,
98
+ locale: str,
99
+ *,
100
+ fallback_locale: str | None = None,
101
+ last_valid: dict[str, Any] | None = None,
102
+ ) -> "LingvaClient":
103
+ """Fetch a bundle asynchronously; return a client over `last_valid` on error."""
104
+ try:
105
+ bundle = await load_bundle_from_url_async(target.resolve(locale))
106
+ except Exception:
107
+ if last_valid is not None:
108
+ return cls(last_valid)
109
+ raise
110
+ fallback = None
111
+ if fallback_locale and fallback_locale != locale:
112
+ try:
113
+ fallback = await load_bundle_from_url_async(target.resolve(fallback_locale))
114
+ except Exception:
115
+ pass
116
+ return cls(bundle, fallback)
117
+
118
+ # ------------------------------------------------------------------
119
+ # Public API
120
+ # ------------------------------------------------------------------
121
+
122
+ @property
123
+ def locale(self) -> str:
124
+ return self._locale
125
+
126
+ @property
127
+ def fallback_locale(self) -> str | None:
128
+ return self._fallback_locale
129
+
130
+ def has(self, key: str) -> bool:
131
+ """Return True if the key exists in the primary or fallback messages."""
132
+ return key in self._messages or key in self._fallback_messages
133
+
134
+ def t(self, key: str, **variables: str) -> str:
135
+ """
136
+ Translate `key` with optional variable substitution.
137
+
138
+ Falls back to `fallback_bundle` when the key is missing from the
139
+ primary bundle or is listed in `fallbackKeys`.
140
+ """
141
+ if key in self._fallback_keys or key not in self._messages:
142
+ template = self._fallback_messages.get(key) or self._messages.get(key)
143
+ else:
144
+ template = self._messages.get(key)
145
+
146
+ if template is None:
147
+ return key
148
+
149
+ return _interpolate(template, variables) if variables else template
150
+
151
+ def bundle(self) -> dict[str, Any]:
152
+ """Return the raw bundle dict (useful for last-valid caching)."""
153
+ return self._bundle
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import urllib.request
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ DEFAULT_REMOTE_BUNDLE_PATH_TEMPLATE = "/bundles/{projectId}/{environment}/latest/{locale}.bundle.json"
10
+
11
+ _PLACEHOLDERS = {"projectId", "environment", "locale"}
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class LingvaDeliveryTarget:
16
+ """Resolves a Lingva CDN delivery URL for a given locale."""
17
+ base_url: str
18
+ uri_template: str = DEFAULT_REMOTE_BUNDLE_PATH_TEMPLATE
19
+ project_id: str = ""
20
+ environment: str = "production"
21
+
22
+ def resolve(self, locale: str) -> str:
23
+ path = (
24
+ self.uri_template
25
+ .replace("{projectId}", self.project_id)
26
+ .replace("{environment}", self.environment)
27
+ .replace("{locale}", locale)
28
+ )
29
+ return self.base_url.rstrip("/") + path
30
+
31
+
32
+ def load_bundle_from_file(path: str | Path) -> dict[str, Any]:
33
+ """Read and parse a locale bundle JSON file."""
34
+ return json.loads(Path(path).read_text(encoding="utf-8"))
35
+
36
+
37
+ def load_bundle_from_url_sync(url: str) -> dict[str, Any]:
38
+ """Fetch a locale bundle over HTTP (synchronous, no extra deps)."""
39
+ with urllib.request.urlopen(url) as resp: # noqa: S310
40
+ return json.loads(resp.read().decode("utf-8"))
41
+
42
+
43
+ async def load_bundle_from_url_async(url: str) -> dict[str, Any]:
44
+ """Fetch a locale bundle over HTTP using httpx (async)."""
45
+ try:
46
+ import httpx
47
+ except ImportError as exc:
48
+ raise ImportError(
49
+ "Install 'lingva[http]' (httpx) for async HTTP delivery."
50
+ ) from exc
51
+ async with httpx.AsyncClient() as client:
52
+ response = await client.get(url)
53
+ response.raise_for_status()
54
+ return response.json()
@@ -0,0 +1,78 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+
6
+ from lingva import LingvaClient, LingvaSchemaError
7
+
8
+ FIXTURE_PATH = Path(__file__).parent.parent.parent / "fixtures" / "en.bundle.json"
9
+
10
+
11
+ @pytest.fixture()
12
+ def fixture_bundle() -> dict:
13
+ return json.loads(FIXTURE_PATH.read_text())
14
+
15
+
16
+ def test_loads_fixture_bundle(fixture_bundle):
17
+ client = LingvaClient.from_bundle(fixture_bundle)
18
+ assert client.locale == "en"
19
+
20
+
21
+ def test_translates_simple_key(fixture_bundle):
22
+ client = LingvaClient.from_bundle(fixture_bundle)
23
+ assert client.t("actions.continue") == "Continue"
24
+
25
+
26
+ def test_interpolates_variable(fixture_bundle):
27
+ client = LingvaClient.from_bundle(fixture_bundle)
28
+ assert client.t("welcome.title", name="Alice") == "Welcome, Alice."
29
+
30
+
31
+ def test_has_returns_true_for_known_key(fixture_bundle):
32
+ client = LingvaClient.from_bundle(fixture_bundle)
33
+ assert client.has("actions.continue") is True
34
+
35
+
36
+ def test_has_returns_false_for_unknown_key(fixture_bundle):
37
+ client = LingvaClient.from_bundle(fixture_bundle)
38
+ assert client.has("nonexistent.key") is False
39
+
40
+
41
+ def test_returns_key_for_missing_translation(fixture_bundle):
42
+ client = LingvaClient.from_bundle(fixture_bundle)
43
+ assert client.t("nonexistent.key") == "nonexistent.key"
44
+
45
+
46
+ def test_rejects_wrong_schema_version():
47
+ with pytest.raises(LingvaSchemaError):
48
+ LingvaClient.from_bundle({"schemaVersion": 99, "messages": {}})
49
+
50
+
51
+ def test_fallback_bundle_used_for_missing_key():
52
+ primary = {"schemaVersion": 1, "locale": "fr", "fallbackLocale": "en", "messages": {}, "fallbackKeys": []}
53
+ fallback = {"schemaVersion": 1, "locale": "en", "messages": {"greeting": "Hello"}, "fallbackKeys": []}
54
+ client = LingvaClient(primary, fallback)
55
+ assert client.t("greeting") == "Hello"
56
+
57
+
58
+ def test_fallback_keys_list_forces_fallback():
59
+ primary = {
60
+ "schemaVersion": 1, "locale": "fr", "fallbackLocale": "en",
61
+ "messages": {"greeting": "Bonjour"},
62
+ "fallbackKeys": ["greeting"],
63
+ }
64
+ fallback = {"schemaVersion": 1, "locale": "en", "messages": {"greeting": "Hello"}, "fallbackKeys": []}
65
+ client = LingvaClient(primary, fallback)
66
+ assert client.t("greeting") == "Hello"
67
+
68
+
69
+ def test_from_file_loads_fixture():
70
+ client = LingvaClient.from_file(FIXTURE_PATH)
71
+ assert client.t("actions.continue") == "Continue"
72
+
73
+
74
+ def test_unknown_variable_passes_through(fixture_bundle):
75
+ client = LingvaClient.from_bundle(fixture_bundle)
76
+ # {name} is known but {extra} is not — {extra} should remain
77
+ result = client.t("welcome.title", name="Bob")
78
+ assert result == "Welcome, Bob."
@@ -0,0 +1,35 @@
1
+ from lingva._delivery import DEFAULT_REMOTE_BUNDLE_PATH_TEMPLATE, LingvaDeliveryTarget
2
+
3
+
4
+ def test_default_template_constant():
5
+ assert DEFAULT_REMOTE_BUNDLE_PATH_TEMPLATE == "/bundles/{projectId}/{environment}/latest/{locale}.bundle.json"
6
+
7
+
8
+ def test_resolve_builds_correct_url():
9
+ target = LingvaDeliveryTarget(
10
+ base_url="https://api.lingva.dev",
11
+ project_id="my-project",
12
+ environment="production",
13
+ )
14
+ url = target.resolve("fr")
15
+ assert url == "https://api.lingva.dev/bundles/my-project/production/latest/fr.bundle.json"
16
+
17
+
18
+ def test_resolve_strips_trailing_slash():
19
+ target = LingvaDeliveryTarget(
20
+ base_url="https://api.lingva.dev/",
21
+ project_id="p1",
22
+ environment="dev",
23
+ )
24
+ url = target.resolve("en")
25
+ assert url == "https://api.lingva.dev/bundles/p1/dev/latest/en.bundle.json"
26
+
27
+
28
+ def test_custom_uri_template():
29
+ target = LingvaDeliveryTarget(
30
+ base_url="https://cdn.example.com",
31
+ uri_template="/v2/{locale}/{projectId}.json",
32
+ project_id="abc",
33
+ )
34
+ url = target.resolve("de")
35
+ assert url == "https://cdn.example.com/v2/de/abc.json"