Coverage for app/venv/lib/python3.14/site-packages/weblate/machinery/apertium.py: 45%
45 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 07:15 +0000
1# Copyright © Michal Čihař <michal@weblate.org>
2#
3# SPDX-License-Identifier: GPL-3.0-or-later
4from __future__ import annotations
6from itertools import chain
7from typing import TYPE_CHECKING
9from django.core.exceptions import ValidationError
10from django.utils.translation import gettext
12from .base import (
13 ResponseStatusMachineTranslation,
14)
15from .forms import URLMachineryForm
17if TYPE_CHECKING: 17 ↛ 18line 17 didn't jump to line 18 because the condition on line 17 was never true
18 from .base import (
19 DownloadTranslations,
20 )
22LANGUAGE_MAP = {
23 "ca": "cat",
24 "cy": "cym",
25 "eo": "epo",
26 "gl": "glg",
27 "bs": "hbs_BS",
28 "es": "spa",
29 "en": "eng",
30 "en_US": "eng",
31 "en_UK": "eng",
32 "nl": "nld",
33 "de": "deu",
34 "fr": "fra",
35 "sl": "slv",
36 "sr": "hbs",
37 "nb_NO": "nob",
38 "nn": "nno",
39 "se": "sme", # codespell:ignore sme
40 "oc": "oci",
41 "pt": "por",
42 "co": "cos",
43 "fi": "fin",
44 "ia": "ina",
45 "ro": "ron",
46 "cs": "ces",
47 "sk": "slk",
48 "ru": "rus",
49 "av": "ava",
50 "is": "isl",
51 "pl": "pol",
52 "kk": "kaz",
53 "tt": "tat",
54 "be": "bel",
55 "uk": "ukr",
56 "gn": "grn",
57 "mt": "mlt",
58 "it": "ita",
59 "zh_Hant": "zho",
60 "br": "bre", # codespell:ignore
61 "qu": "qve",
62 "an": "arg",
63 "mr": "mar",
64 "af": "afr",
65 "fa": "pes",
66 "el": "ell",
67 "lv": "lvs",
68 "as": "asm",
69 "hi": "hin",
70 "te": "tel", # codespell:ignore te
71 "hy": "hye",
72 "th": "tha", # codespell:ignore tha
73 "mk": "mkd",
74 "la": "lat",
75 "ga": "gle",
76 "sw": "swa",
77 "hu": "hun",
78 "ml": "mal",
79}
82class ApertiumAPYTranslation(ResponseStatusMachineTranslation):
83 """Apertium machine translation support."""
85 name = "Apertium APy"
86 max_score = 88
87 settings_form = URLMachineryForm
88 request_timeout = 20
90 @property
91 def all_langs(self):
92 """Return all language codes known to service."""
93 return set(chain.from_iterable(self.supported_languages))
95 def map_language_code(self, code):
96 """Convert language to service specific code."""
97 code = super().map_language_code(code).replace("-", "_")
98 # Force download of supported languages
99 if code not in self.all_langs and code in LANGUAGE_MAP:
100 return LANGUAGE_MAP[code]
101 return code
103 def validate_settings(self) -> None:
104 try:
105 languages = self.download_languages()
106 except Exception as error:
107 raise ValidationError(
108 gettext("Could not fetch supported languages: %s") % error
109 ) from error
110 try:
111 source_language, target_language = languages[0]
112 except IndexError as error:
113 raise ValidationError(
114 gettext("No supported languages found: %s") % error
115 ) from error
116 try:
117 self.download_multiple_translations(
118 source_language, target_language, [("test", None)], None, 75
119 )
120 except Exception as error:
121 raise ValidationError(
122 gettext("Could not fetch translation: %s") % error
123 ) from error
125 def download_languages(self):
126 """Download list of supported languages from a service."""
127 data = self.request("get", self.get_api_url("listPairs")).json()
128 return [
129 (item["sourceLanguage"], item["targetLanguage"])
130 for item in data["responseData"]
131 ]
133 def is_supported(self, source_language, target_language):
134 """Check whether given language combination is supported."""
135 return (source_language, target_language) in self.supported_languages
137 def download_translations(
138 self,
139 source_language,
140 target_language,
141 text: str,
142 unit,
143 user,
144 threshold: int = 75,
145 ) -> DownloadTranslations:
146 """Download list of possible translations from Apertium."""
147 args = {
148 "langpair": f"{source_language}|{target_language}",
149 "q": text,
150 "markUnknown": "no",
151 }
152 response = self.request(
153 "get", self.get_api_url("translate"), params=args
154 ).json()
156 yield {
157 "text": response["responseData"]["translatedText"],
158 "quality": self.max_score,
159 "service": self.name,
160 "source": text,
161 }