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 +5 -0
- tarjamli/__main__.py +2 -0
- tarjamli/cli.py +26 -0
- tarjamli/client.py +95 -0
- tarjamli/languages.json +56 -0
- tarjamli/py.typed +0 -0
- tarjamli-0.1.0.dist-info/METADATA +105 -0
- tarjamli-0.1.0.dist-info/RECORD +12 -0
- tarjamli-0.1.0.dist-info/WHEEL +5 -0
- tarjamli-0.1.0.dist-info/entry_points.txt +2 -0
- tarjamli-0.1.0.dist-info/licenses/LICENSE +9 -0
- tarjamli-0.1.0.dist-info/top_level.txt +1 -0
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
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)
|
tarjamli/languages.json
ADDED
|
@@ -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,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
|