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

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5from __future__ import annotations 

6 

7import json 

8import re 

9 

10from django import forms 

11from django.core.exceptions import ValidationError 

12from django.utils.translation import gettext, gettext_lazy, pgettext_lazy 

13 

14from weblate.utils.forms import WeblateServiceURLField 

15 

16from .types import SourceLanguageChoices 

17 

18 

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 ) 

28 

29 def __init__(self, machinery, *args, **kwargs) -> None: 

30 self.machinery = machinery 

31 super().__init__(*args, **kwargs) 

32 

33 def serialize_form(self): 

34 return self.cleaned_data 

35 

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() 

45 

46 

47class KeyMachineryForm(BaseMachineryForm): 

48 key = forms.CharField( 

49 label=pgettext_lazy("Automatic suggestion service configuration", "API key") 

50 ) 

51 

52 

53class URLMachineryForm(BaseMachineryForm): 

54 url = WeblateServiceURLField( 

55 label=pgettext_lazy("Automatic suggestion service configuration", "API URL"), 

56 ) 

57 

58 

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 ) 

68 

69 

70class KeyURLMachineryForm(KeyMachineryForm, URLMachineryForm): 

71 pass 

72 

73 

74class LibreTranslateMachineryForm(KeyURLMachineryForm): 

75 key = forms.CharField( 

76 label=pgettext_lazy("Automatic suggestion service configuration", "API key"), 

77 required=False, 

78 ) 

79 

80 

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 ) 

96 

97 

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 ) 

133 

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() 

144 

145 

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 ) 

194 

195 

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 ) 

244 

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"] 

253 

254 

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 ) 

265 

266 

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 ) 

277 

278 

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 ) 

293 

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"] 

309 

310 

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 ) 

352 

353 

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 ) 

377 

378 

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 ) 

425 

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() 

439 

440 

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 )