Coverage for app/venv/lib/python3.14/site-packages/weblate/machinery/forms.py: 57%
112 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
5from __future__ import annotations
7import json
8import re
10from django import forms
11from django.core.exceptions import ValidationError
12from django.utils.translation import gettext, gettext_lazy, pgettext_lazy
14from weblate.utils.forms import WeblateServiceURLField
16from .types import SourceLanguageChoices
19class BaseMachineryForm(forms.Form):
20 source_language = forms.ChoiceField(
21 label=pgettext_lazy(
22 "Automatic suggestion service configuration", "Source language selection"
23 ),
24 choices=SourceLanguageChoices,
25 initial=SourceLanguageChoices.AUTO,
26 required=False,
27 )
29 def __init__(self, machinery, *args, **kwargs) -> None:
30 self.machinery = machinery
31 super().__init__(*args, **kwargs)
33 def serialize_form(self):
34 return self.cleaned_data
36 def clean(self) -> None:
37 settings = self.serialize_form()
38 for field, data in self.fields.items():
39 if not data.required:
40 continue
41 if field not in settings:
42 return
43 machinery = self.machinery(settings)
44 machinery.validate_settings()
47class KeyMachineryForm(BaseMachineryForm):
48 key = forms.CharField(
49 label=pgettext_lazy("Automatic suggestion service configuration", "API key")
50 )
53class URLMachineryForm(BaseMachineryForm):
54 url = WeblateServiceURLField(
55 label=pgettext_lazy("Automatic suggestion service configuration", "API URL"),
56 )
59class KeySecretMachineryForm(KeyMachineryForm):
60 key = forms.CharField(
61 label=pgettext_lazy("Automatic suggestion service configuration", "Client ID")
62 )
63 secret = forms.CharField(
64 label=pgettext_lazy(
65 "Automatic suggestion service configuration", "Client secret"
66 )
67 )
70class KeyURLMachineryForm(KeyMachineryForm, URLMachineryForm):
71 pass
74class LibreTranslateMachineryForm(KeyURLMachineryForm):
75 key = forms.CharField(
76 label=pgettext_lazy("Automatic suggestion service configuration", "API key"),
77 required=False,
78 )
81class MyMemoryMachineryForm(BaseMachineryForm):
82 email = forms.EmailField(
83 label=pgettext_lazy(
84 "Automatic suggestion service configuration", "Contact e-mail"
85 ),
86 required=False,
87 )
88 username = forms.CharField(
89 label=pgettext_lazy("Automatic suggestion service configuration", "Username"),
90 required=False,
91 )
92 key = forms.CharField(
93 label=pgettext_lazy("Automatic suggestion service configuration", "API key"),
94 required=False,
95 )
98class SAPMachineryForm(URLMachineryForm):
99 key = forms.CharField(
100 label=pgettext_lazy("Automatic suggestion service configuration", "API key"),
101 required=False,
102 )
103 username = forms.CharField(
104 label=pgettext_lazy(
105 "Automatic suggestion service configuration", "SAP username"
106 ),
107 required=False,
108 )
109 password = forms.CharField(
110 label=pgettext_lazy(
111 "Automatic suggestion service configuration", "SAP password"
112 ),
113 widget=forms.PasswordInput,
114 required=False,
115 )
116 enable_mt = forms.BooleanField(
117 label=pgettext_lazy(
118 "Automatic suggestion service configuration", "Enable machine translation"
119 ),
120 required=False,
121 initial=True,
122 )
123 domain = forms.CharField(
124 label=pgettext_lazy(
125 "Automatic suggestion service configuration", "Translation domain"
126 ),
127 help_text=gettext_lazy(
128 "The ID of a translation domain, for example, BC. If you do not specify "
129 "a domain, the method searches for translations in all available domains."
130 ),
131 required=False,
132 )
134 def clean(self) -> None:
135 has_sandbox = bool(self.cleaned_data.get("key"))
136 has_production = self.cleaned_data.get("username") and self.cleaned_data.get(
137 "password"
138 )
139 if not has_sandbox and not has_production:
140 raise ValidationError(
141 gettext("Please provide either key or username and password.")
142 )
143 super().clean()
146class MicrosoftMachineryForm(KeyMachineryForm):
147 base_url = forms.ChoiceField(
148 label=pgettext_lazy(
149 "Automatic suggestion service configuration", "Application base URL"
150 ),
151 initial="api.cognitive.microsofttranslator.com",
152 choices=(
153 ("api.cognitive.microsofttranslator.com", "Global (non-regional)"),
154 ("api-apc.cognitive.microsofttranslator.com", "Asia Pacific"),
155 ("api-eur.cognitive.microsofttranslator.com", "Europe"),
156 (
157 "api-nam.cognitive.microsofttranslator.com", # codespell:ignore
158 "North America",
159 ),
160 ("api.translator.azure.cn", "China"),
161 ("api.cognitive.microsofttranslator.us", "Azure US Government cloud"),
162 ),
163 )
164 endpoint_url = forms.ChoiceField(
165 label=pgettext_lazy(
166 "Automatic suggestion service configuration", "Authentication service URL"
167 ),
168 initial="api.cognitive.microsoft.com",
169 choices=(
170 ("api.cognitive.microsoft.com", "Global"),
171 ("api.cognitive.azure.cn", "China"),
172 ("api.cognitive.microsoft.us", "Azure US Government cloud"),
173 ),
174 help_text=gettext_lazy(
175 "Regional or multi-service can be specified using region field below."
176 ),
177 )
178 region = forms.CharField(
179 label=pgettext_lazy(
180 "Automatic suggestion service configuration",
181 "Authentication service region",
182 ),
183 required=False,
184 )
185 category = forms.CharField(
186 label=pgettext_lazy("Automatic suggestion service configuration", "Category"),
187 help_text=pgettext_lazy(
188 "Automatic suggestion service configuration",
189 "Specify a customized system category ID to use it instead of general one.",
190 ),
191 initial="general",
192 required=False,
193 )
196class GoogleV3MachineryForm(BaseMachineryForm):
197 credentials = forms.CharField(
198 label=pgettext_lazy(
199 "Automatic suggestion service configuration",
200 "Google Translate service account info",
201 ),
202 widget=forms.Textarea,
203 help_text=pgettext_lazy(
204 "Google Cloud Translation configuration",
205 "Enter a JSON key for the service account.",
206 ),
207 )
208 project = forms.CharField(
209 label=pgettext_lazy(
210 "Automatic suggestion service configuration", "Google Translate project"
211 ),
212 help_text=pgettext_lazy(
213 "Google Cloud Translation configuration",
214 "Enter the numeric or alphanumeric ID of your Google Cloud project.",
215 ),
216 )
217 location = forms.CharField(
218 label=pgettext_lazy(
219 "Automatic suggestion service configuration", "Google Translate location"
220 ),
221 initial="global",
222 help_text=pgettext_lazy(
223 "Google Cloud Translation configuration",
224 "Choose a Google Cloud Translation region that is used for the Google Cloud project or is closest to you.",
225 ),
226 widget=forms.Select(
227 choices=(
228 ("global ", pgettext_lazy("Google Cloud region", "Global")),
229 ("europe-west1 ", pgettext_lazy("Google Cloud region", "Europe")),
230 ("us-west1", pgettext_lazy("Google Cloud region", "US")),
231 )
232 ),
233 )
234 bucket_name = forms.CharField(
235 label=pgettext_lazy(
236 "Automatic suggestion service configuration", "Google Storage Bucket name"
237 ),
238 help_text=pgettext_lazy(
239 "Google Cloud Translation configuration",
240 "Enter the name of the Google Cloud Storage bucket that is used to store the Glossary files.",
241 ),
242 required=False,
243 )
245 def clean_credentials(self):
246 try:
247 json.loads(self.cleaned_data["credentials"])
248 except json.JSONDecodeError as error:
249 raise ValidationError(
250 gettext("Could not parse JSON: %s") % error
251 ) from error
252 return self.cleaned_data["credentials"]
255class AWSMachineryForm(KeySecretMachineryForm):
256 key = forms.CharField(
257 label=pgettext_lazy("AWS Translate configuration", "Access key ID")
258 )
259 secret = forms.CharField(
260 label=pgettext_lazy("AWS Translate configuration", "API secret key")
261 )
262 region = forms.CharField(
263 label=pgettext_lazy("AWS Translate configuration", "Region name")
264 )
267class AlibabaMachineryForm(KeySecretMachineryForm):
268 key = forms.CharField(
269 label=pgettext_lazy("Alibaba Translate configuration", "Access key ID")
270 )
271 secret = forms.CharField(
272 label=pgettext_lazy("Alibaba Translate configuration", "Access key secret")
273 )
274 region = forms.CharField(
275 label=pgettext_lazy("Alibaba Translate configuration", "Region ID")
276 )
279class ModernMTMachineryForm(KeyURLMachineryForm):
280 url = WeblateServiceURLField(
281 label=pgettext_lazy("Automatic suggestion service configuration", "API URL"),
282 initial="https://api.modernmt.com/",
283 )
284 context_vector = forms.CharField(
285 label=pgettext_lazy(
286 "Automatic suggestion service configuration", "Context vector"
287 ),
288 help_text=gettext_lazy(
289 "Comma-separated list of memory IDs:weight. e.g: 1234:0.123,4567:0.456"
290 ),
291 required=False,
292 )
294 def clean_context_vector(self):
295 """Validate context_vector field."""
296 pattern = r"^\d+:\d(?:\.\d{1,3})?$"
297 context_vector = self.cleaned_data["context_vector"]
298 if context_vector:
299 for couple in context_vector.split(","):
300 if not re.match(pattern, couple):
301 raise ValidationError(
302 gettext("The context vector is not valid: %s") % couple
303 )
304 if not 0 < (weight := float(couple.split(":")[1])) < 1:
305 raise ValidationError(
306 gettext("The weight value must be between 0 and 1: %s") % weight
307 )
308 return self.cleaned_data["context_vector"]
311class DeepLMachineryForm(KeyURLMachineryForm):
312 url = WeblateServiceURLField(
313 label=pgettext_lazy("Automatic suggestion service configuration", "API URL"),
314 initial="https://api.deepl.com/v2/",
315 )
316 formality = forms.CharField(
317 label=pgettext_lazy("Automatic suggestion service configuration", "Formality"),
318 help_text=gettext_lazy(
319 "Uses the specified formality if language is not specified as (in)formal"
320 ),
321 widget=forms.Select(
322 choices=(
323 ("default ", "Default"),
324 ("prefer_more", "Formal"),
325 ("prefer_less", "Informal"),
326 )
327 ),
328 initial="default",
329 required=False,
330 )
331 context = forms.CharField(
332 label=pgettext_lazy(
333 "Automatic suggestion service configuration",
334 "Translation context",
335 ),
336 widget=forms.Textarea,
337 help_text=gettext_lazy(
338 "Describe the context of the translation to improve the accuracy of the translation."
339 ),
340 required=False,
341 )
342 next_gen = forms.BooleanField(
343 label=pgettext_lazy(
344 "Automatic suggestion service configuration",
345 "Use next-gen model",
346 ),
347 help_text=gettext_lazy(
348 "Prefer next-gen LLM over classic machine translation model."
349 ),
350 required=False,
351 )
354class BaseOpenAIMachineryForm(KeyMachineryForm):
355 persona = forms.CharField(
356 label=pgettext_lazy(
357 "Automatic suggestion service configuration",
358 "Translator persona",
359 ),
360 widget=forms.Textarea,
361 help_text=gettext_lazy(
362 "Describe the persona of translator to improve the accuracy of the translation. For example: “You are a squirrel breeder.”"
363 ),
364 required=False,
365 )
366 style = forms.CharField(
367 label=pgettext_lazy(
368 "Automatic suggestion service configuration",
369 "Translator style",
370 ),
371 widget=forms.Textarea,
372 help_text=gettext_lazy(
373 "Describe the style of translation. For example: “Use informal language.”"
374 ),
375 required=False,
376 )
379class OpenAIMachineryForm(BaseOpenAIMachineryForm):
380 # Ordering choices here defines priority for automatic selection
381 MODEL_CHOICES = (
382 ("auto", pgettext_lazy("OpenAI model selection", "Automatic selection")),
383 ("gpt-5-nano", "GPT-5-nano"),
384 ("gpt-5-mini", "GPT-5-mini"),
385 ("gpt-5", "GPT-5"),
386 ("gpt-4.1-nano", "GPT-4.1-nano"),
387 ("gpt-4.1-mini", "GPT-4.1-mini"),
388 ("gpt-4.1", "GPT-4.1"),
389 ("gpt-4o-mini", "GPT-4o mini"),
390 ("gpt-4o", "GPT-4o"),
391 ("o3-mini", "o3-mini"),
392 ("o3", "o3"),
393 ("o1-mini", "o1-mini"),
394 ("o1", "o1"),
395 ("o1-pro", "o1-pro"),
396 ("custom", pgettext_lazy("OpenAI model selection", "Custom model")),
397 )
398 base_url = WeblateServiceURLField(
399 label=pgettext_lazy(
400 "Automatic suggestion service configuration",
401 "OpenAI API base URL",
402 ),
403 widget=forms.TextInput,
404 help_text=gettext_lazy(
405 "Base URL of the OpenAI API, if it differs from the OpenAI default URL"
406 ),
407 required=False,
408 )
409 model = forms.ChoiceField(
410 label=pgettext_lazy(
411 "Automatic suggestion service configuration",
412 "OpenAI model",
413 ),
414 initial="auto",
415 choices=MODEL_CHOICES,
416 )
417 custom_model = forms.CharField(
418 label=pgettext_lazy(
419 "OpenAI model selection",
420 "Custom model name",
421 ),
422 help_text=gettext_lazy("Only needed when model is set to 'Custom model'"),
423 required=False,
424 )
426 def clean(self) -> None:
427 """Validate `custom_model, model` fields."""
428 has_custom_model = bool(self.cleaned_data.get("custom_model"))
429 model = self.cleaned_data.get("model")
430 if model == "custom" and not has_custom_model:
431 raise ValidationError(
432 {"custom_model": gettext("Missing custom model name.")}
433 )
434 if model != "custom" and has_custom_model:
435 raise ValidationError(
436 {"model": gettext("Choose custom model here to enable it.")}
437 )
438 super().clean()
441class AzureOpenAIMachineryForm(BaseOpenAIMachineryForm):
442 azure_endpoint = WeblateServiceURLField(
443 label=pgettext_lazy(
444 "Automatic suggestion service configuration",
445 "Azure OpenAI endpoint URL",
446 ),
447 widget=forms.TextInput,
448 help_text=gettext_lazy(
449 "Endpoint URL of the instance, e.g: https://my-instance.openai.azure.com."
450 ),
451 )
452 deployment = forms.CharField(
453 label=pgettext_lazy(
454 "Automatic suggestion service configuration",
455 "Azure OpenAI deployment",
456 ),
457 widget=forms.TextInput,
458 help_text=gettext_lazy("The model's unique deployment name."),
459 )