tarjamli 0.1.0__py3-none-any.whl

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.
tarjamli/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Tarjamli translation client for Sawalni API."""
2
+ from .client import (Tarjamli, Translation, TranslationError, AuthenticationError,
3
+ RateLimitError, IncompleteTranslationError)
4
+ __all__ = ["Tarjamli", "Translation", "TranslationError", "AuthenticationError",
5
+ "RateLimitError", "IncompleteTranslationError"]
tarjamli/__main__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .cli import main
2
+ main()
tarjamli/cli.py ADDED
@@ -0,0 +1,26 @@
1
+ import argparse
2
+ import json
3
+ import sys
4
+ from . import Tarjamli, TranslationError
5
+
6
+ def main():
7
+ parser = argparse.ArgumentParser(prog="tarjamli")
8
+ commands = parser.add_subparsers(dest="command", required=True)
9
+ commands.add_parser("languages", help="List language and script codes")
10
+ command = commands.add_parser("translate", help="Translate text; use --text - to read stdin")
11
+ command.add_argument("--text", required=True)
12
+ command.add_argument("--source", required=True)
13
+ command.add_argument("--target", required=True)
14
+ command.add_argument("--instructions", default="")
15
+ command.add_argument("--json", action="store_true", help="Print the full API response")
16
+ args = parser.parse_args()
17
+ if args.command == "languages":
18
+ print(json.dumps(Tarjamli.languages(), ensure_ascii=False, indent=2))
19
+ return
20
+ try:
21
+ text = sys.stdin.read() if args.text == "-" else args.text
22
+ result = Tarjamli().translate(text, source=args.source, target=args.target, instructions=args.instructions)
23
+ print(json.dumps(result.raw, ensure_ascii=False, indent=2) if args.json else result.text)
24
+ except (TranslationError, ValueError) as error:
25
+ print(str(error), file=sys.stderr)
26
+ raise SystemExit(1)
tarjamli/client.py ADDED
@@ -0,0 +1,95 @@
1
+ """Small standard-library client for Tarjamli Mini v4. Python 3.10+."""
2
+ import json
3
+ import os
4
+ import urllib.error
5
+ import urllib.request
6
+ from importlib.resources import files
7
+
8
+ ENDPOINT = "https://api.sawalni.com/v1/language/translate"
9
+ LANGUAGES = json.loads(files("tarjamli").joinpath("languages.json").read_text())
10
+
11
+ class TranslationError(RuntimeError):
12
+ """A request failed or returned an unusable translation."""
13
+
14
+ class AuthenticationError(TranslationError):
15
+ pass
16
+
17
+ class RateLimitError(TranslationError):
18
+ pass
19
+
20
+ class IncompleteTranslationError(TranslationError):
21
+ pass
22
+
23
+ def make_request(text, source, target, instructions="", *, model="tarjamli-mini-v4"):
24
+ if model not in ("tarjamli-mini-v4",):
25
+ raise ValueError("Unsupported model")
26
+ if not isinstance(text, str) or not text.strip():
27
+ raise ValueError("text must be nonempty")
28
+ if len(text.encode("utf-8")) > 262144:
29
+ raise ValueError("text exceeds 262144 UTF-8 bytes")
30
+ if source not in LANGUAGES or target not in LANGUAGES:
31
+ raise ValueError("Use explicit supported source and target codes from languages.json")
32
+ if not isinstance(instructions, str) or len(instructions.encode("utf-8")) > 32768:
33
+ raise ValueError("instructions must be a string of at most 32768 UTF-8 bytes")
34
+ payload = dict(model=model, text=text, source=source, target=target)
35
+ if instructions.strip():
36
+ payload["instructions"] = instructions
37
+ return payload
38
+
39
+ def translate(text, source, target, instructions="", *, api_key=None, timeout=180, model="tarjamli-mini-v4"):
40
+ payload = make_request(text, source, target, instructions, model=model)
41
+ key = api_key or os.environ.get("SAWALNI_API_KEY")
42
+ if not key:
43
+ raise AuthenticationError("Set SAWALNI_API_KEY in your shell or pass api_key")
44
+ request = urllib.request.Request(ENDPOINT, data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
45
+ headers={"Authorization": "Bearer " + key, "Content-Type": "application/json", "User-Agent": "Sawalni-Tarjamli-Starter/1.0"})
46
+ try:
47
+ with urllib.request.urlopen(request, timeout=timeout) as response:
48
+ body = json.load(response)
49
+ except urllib.error.HTTPError as error:
50
+ error_type = AuthenticationError if error.code in (401, 403) else RateLimitError if error.code == 429 else TranslationError
51
+ raise error_type(f"Translation HTTP {error.code}; check access, input and API status. No automatic retry.") from None
52
+ except (urllib.error.URLError, TimeoutError):
53
+ raise TranslationError("Translation connection failed or timed out. Check before retrying; the request may have run.") from None
54
+ except (ValueError, UnicodeError):
55
+ raise TranslationError("API returned invalid JSON") from None
56
+ data = body.get("data") if isinstance(body, dict) else None
57
+ if not isinstance(data, dict) or not isinstance(data.get("translated"), str) or not data["translated"].strip():
58
+ raise TranslationError("API returned no translation")
59
+ if data.get("complete") is not True:
60
+ raise IncompleteTranslationError("Translation is incomplete; shorten the input before trying again")
61
+ if data.get("source") != source or data.get("target") != target or data.get("model") != payload["model"]:
62
+ raise TranslationError("Unexpected response model or language identity")
63
+ return body
64
+
65
+
66
+ from dataclasses import dataclass
67
+ from typing import Any
68
+
69
+ @dataclass(frozen=True)
70
+ class Translation:
71
+ """Translation text and the full API response, including usage when provided."""
72
+ text: str
73
+ source: str
74
+ target: str
75
+ model: str
76
+ raw: dict[str, Any]
77
+
78
+ class Tarjamli:
79
+ """Synchronous client. Requests are never retried automatically."""
80
+ def __init__(self, api_key: str | None = None, *, timeout: float = 180) -> None:
81
+ if timeout <= 0:
82
+ raise ValueError("timeout must be positive")
83
+ self._api_key = api_key
84
+ self.timeout = timeout
85
+
86
+ @staticmethod
87
+ def languages() -> dict[str, str]:
88
+ """Return a copy of the supported language-code mapping."""
89
+ return dict(LANGUAGES)
90
+
91
+ def translate(self, text: str, *, source: str, target: str, instructions: str = "", model: str = "tarjamli-mini-v4") -> Translation:
92
+ body = translate(text, source, target, instructions, api_key=self._api_key,
93
+ timeout=self.timeout, model=model)
94
+ data = body["data"]
95
+ return Translation(data["translated"], data["source"], data["target"], data["model"], body)
@@ -0,0 +1,56 @@
1
+ {
2
+ "ary_Arab": "Moroccan Arabic",
3
+ "ary_Latn": "Moroccan Arabic written in Latin script",
4
+ "shi_Latn": "Tachelhit written in Latin script",
5
+ "zgh_Tfng": "Standard Moroccan Tamazight written in Tifinagh script",
6
+ "amh_Ethi": "Amharic",
7
+ "arb_Arab": "Modern Standard Arabic",
8
+ "ben_Beng": "Bengali",
9
+ "ces_Latn": "Czech",
10
+ "cmn_Hans": "Simplified Chinese",
11
+ "dan_Latn": "Danish",
12
+ "deu_Latn": "German",
13
+ "ell_Grek": "Greek",
14
+ "eng_Latn": "English",
15
+ "fin_Latn": "Finnish",
16
+ "fra_Latn": "French",
17
+ "guj_Gujr": "Gujarati",
18
+ "hau_Latn": "Hausa",
19
+ "heb_Hebr": "Hebrew",
20
+ "hin_Deva": "Hindi",
21
+ "hun_Latn": "Hungarian",
22
+ "ind_Latn": "Indonesian",
23
+ "ita_Latn": "Italian",
24
+ "jpn_Jpan": "Japanese",
25
+ "kan_Knda": "Kannada",
26
+ "khm_Khmr": "Khmer",
27
+ "kor_Hang": "Korean",
28
+ "lao_Laoo": "Lao",
29
+ "mal_Mlym": "Malayalam",
30
+ "mar_Deva": "Marathi",
31
+ "mya_Mymr": "Burmese",
32
+ "nld_Latn": "Dutch",
33
+ "nob_Latn": "Norwegian Bokmål",
34
+ "npi_Deva": "Nepali",
35
+ "pan_Guru": "Punjabi written in Gurmukhi",
36
+ "pes_Arab": "Persian",
37
+ "pol_Latn": "Polish",
38
+ "por_Latn": "Portuguese",
39
+ "ron_Latn": "Romanian",
40
+ "rus_Cyrl": "Russian",
41
+ "sin_Sinh": "Sinhala",
42
+ "som_Latn": "Somali",
43
+ "spa_Latn": "Spanish",
44
+ "swe_Latn": "Swedish",
45
+ "swh_Latn": "Swahili",
46
+ "tam_Taml": "Tamil",
47
+ "tel_Telu": "Telugu",
48
+ "tha_Thai": "Thai",
49
+ "tur_Latn": "Turkish",
50
+ "ukr_Cyrl": "Ukrainian",
51
+ "urd_Arab": "Urdu",
52
+ "vie_Latn": "Vietnamese",
53
+ "yor_Latn": "Yoruba",
54
+ "zsm_Latn": "Malay",
55
+ "zul_Latn": "Zulu"
56
+ }
tarjamli/py.typed ADDED
File without changes
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: tarjamli
3
+ Version: 0.1.0
4
+ Summary: Python client for Tarjamli translation through Sawalni API
5
+ Author-email: Omar Kamali <omar@omneitylabs.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://api.sawalni.com
8
+ Project-URL: Issues, https://github.com/omneity-labs/starter-translation/issues
9
+ Project-URL: Examples, https://github.com/omneity-labs/starter-translation
10
+ Keywords: translation,darija,amazigh,tarjamli,sawalni
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Text Processing :: Linguistic
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ # tarjamli
21
+
22
+ A small Python client for Tarjamli translation through the [Sawalni API](https://api.sawalni.com). Translate text, convert Darija between Arabic and Latin scripts, and give the translation context about tone, gender or address forms.
23
+
24
+ Python 3.10+. No runtime dependencies.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ pip install tarjamli
30
+ ```
31
+
32
+ Set your Sawalni API key:
33
+
34
+ ```sh
35
+ export SAWALNI_API_KEY="your-key"
36
+ ```
37
+
38
+ ## Translate
39
+
40
+ ```python
41
+ from tarjamli import Tarjamli
42
+
43
+ client = Tarjamli()
44
+ result = client.translate(
45
+ "واش تقدر تصيفط ليا الدوسي؟",
46
+ source="ary_Arab",
47
+ target="fra_Latn",
48
+ instructions="Address one person formally using vous.",
49
+ )
50
+ print(result.text)
51
+ ```
52
+
53
+ Pass your own text. Remove `instructions` for ordinary translation, or change it to supply context. The API field is `instructions`; tone and gender are not separate parameters.
54
+
55
+ ## Convert Darija scripts
56
+
57
+ ```python
58
+ result = client.translate(
59
+ "سلام، كيف داير؟",
60
+ source="ary_Arab",
61
+ target="ary_Latn",
62
+ )
63
+ print(result.text)
64
+ ```
65
+
66
+ Reverse the source and target for Latin → Arabic. Use `client.languages()` to list supported language and script codes, including `shi_Latn` for Tachelhit and `zgh_Tfng` for Tamazight in Tifinagh.
67
+
68
+ ## CLI
69
+
70
+ ```sh
71
+ tarjamli translate --text 'واش تقدر تصيفط ليا الدوسي؟' --source ary_Arab --target fra_Latn --instructions 'Address one person formally using vous.'
72
+ tarjamli languages
73
+ ```
74
+
75
+ Use `--text -` to read standard input. Add `--json` for the full API response.
76
+
77
+ ## Configuration and results
78
+
79
+ `Tarjamli(api_key=None, timeout=180)` reads `SAWALNI_API_KEY` when no key is passed. Keep keys in server-side code or your terminal environment. The package does not load `.env` files.
80
+
81
+ `translate(text, *, source, target, instructions="", model="tarjamli-mini-v4")` returns a `Translation` with `text`, `source`, `target`, `model`, and `raw` (the full response, including usage when supplied). This release supports the checked Mini model only.
82
+
83
+ Each call sends one request to `POST https://api.sawalni.com/v1/language/translate`. Live requests use your API balance. There are no automatic retries.
84
+
85
+ ## Errors
86
+
87
+ ```python
88
+ from tarjamli import AuthenticationError, RateLimitError, TranslationError
89
+
90
+ try:
91
+ result = client.translate("Hello", source="eng_Latn", target="ary_Arab")
92
+ except AuthenticationError:
93
+ # Check your key and model access.
94
+ raise
95
+ except RateLimitError:
96
+ # Decide when to retry in your application.
97
+ raise
98
+ except TranslationError:
99
+ # Includes incomplete responses, connection errors and unexpected identities.
100
+ raise
101
+ ```
102
+
103
+ Invalid local input raises `ValueError`. Incomplete output raises `IncompleteTranslationError`, a subclass of `TranslationError`. The client checks the returned model and language codes before returning a result.
104
+
105
+ [More examples](https://github.com/omneity-labs/starter-translation) · [API documentation](https://api.sawalni.com/docs) · [MIT license](LICENSE).
@@ -0,0 +1,12 @@
1
+ tarjamli/__init__.py,sha256=a0UHJwDGgscod5B4EHzf2gxFxBSBB8hZF1-Y60mmYJU,339
2
+ tarjamli/__main__.py,sha256=z3OQolCKD8rUd3pKYp_FISq2VctFhFl1mV-DVnaoNOM,29
3
+ tarjamli/cli.py,sha256=IlSaA59_Uws8UXbs0gDVqwfBWhV0x3ZFSFVJAWDUf70,1263
4
+ tarjamli/client.py,sha256=EdJFQtI6xH5Vji03Mee_Z73xBd9eT2dL6HQZSLtfaEs,4531
5
+ tarjamli/languages.json,sha256=3CKr5vJN7wf195yrba5Ijh9trCh4wZbVqJI7Qa75Yp4,1501
6
+ tarjamli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ tarjamli-0.1.0.dist-info/licenses/LICENSE,sha256=nRQxaiQTOi3f1G6BfpYfKOMwtIj7mM6gREvd8qyjIqY,1069
8
+ tarjamli-0.1.0.dist-info/METADATA,sha256=0D6E-jeUJtYqZfDcEfJSVjR_ph4jIVJDeXxelsuYdHI,3729
9
+ tarjamli-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ tarjamli-0.1.0.dist-info/entry_points.txt,sha256=ghwleMXtELYNk5Mw3hbB3OTYN8_dqofTR73FIQoIuMU,47
11
+ tarjamli-0.1.0.dist-info/top_level.txt,sha256=ZvcCOuJWN_nn8ygf5F_p-QDd0-9r6bwf_au9_2WGFZU,9
12
+ tarjamli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tarjamli = tarjamli.cli:main
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Omneity Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ tarjamli