1
+ /*
2
+ * Copyright (C) 2017/2021 e-voyageurs technologies
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package ai.tock.translator.deepl
18
+
19
+ import ai.tock.shared.TockProxyAuthenticator
20
+ import ai.tock.shared.jackson.mapper
21
+ import ai.tock.shared.property
22
+ import ai.tock.shared.propertyOrNull
23
+ import com.fasterxml.jackson.module.kotlin.readValue
24
+ import java.io.IOException
25
+ import java.util.regex.Pattern
26
+ import okhttp3.FormBody
27
+ import okhttp3.OkHttpClient
28
+ import okhttp3.Request
29
+
30
+ internal data class TranslationResponse (
31
+ val translations : List <Translation >
32
+ )
33
+
34
+ internal data class Translation (
35
+ val text : String
36
+ )
37
+
38
+ const val TAG_HANDLING = " xml"
39
+
40
+ interface DeeplClient {
41
+ fun translate (
42
+ text : String ,
43
+ sourceLang : String ,
44
+ targetLang : String ,
45
+ preserveFormatting : Boolean ,
46
+ glossaryId : String?
47
+ ): String?
48
+ }
49
+
50
+ class OkHttpDeeplClient (
51
+ private val apiURL : String = property("tock_translator_deepl_api_url", "https://api.deepl.com/v2/translate"),
52
+ private val apiKey : String? = propertyOrNull("tock_translator_deepl_api_key"),
53
+ okHttpCustomizer : OkHttpClient .Builder .() -> Unit = {}
54
+ ) : DeeplClient {
55
+ private val client = OkHttpClient .Builder ()
56
+ .apply (TockProxyAuthenticator ::install)
57
+ .apply (okHttpCustomizer)
58
+ .build()
59
+
60
+ private fun replaceSpecificPlaceholders (text : String ): Pair <String , List <String >> {
61
+ // Store original placeholders for later restoration
62
+ val placeholderPattern = Pattern .compile(" \\ {:([^}]*)}" )
63
+ val matcher = placeholderPattern.matcher(text)
64
+
65
+ val placeholders = mutableListOf<String >()
66
+ while (matcher.find()) {
67
+ placeholders.add(matcher.group(1 ))
68
+ }
69
+
70
+ // Replace placeholders with '_PLACEHOLDER_'
71
+ val replacedText = matcher.replaceAll(" _PLACEHOLDER_" )
72
+
73
+ return Pair (replacedText, placeholders)
74
+ }
75
+
76
+ private fun revertSpecificPlaceholders (text : String , placeholders : List <String >): String {
77
+ var resultText = text
78
+ for (placeholder in placeholders) {
79
+ resultText = resultText.replaceFirst(" _PLACEHOLDER_" , " {:$placeholder }" )
80
+ }
81
+ return resultText
82
+ }
83
+
84
+ override fun translate (
85
+ text : String ,
86
+ sourceLang : String ,
87
+ targetLang : String ,
88
+ preserveFormatting : Boolean ,
89
+ glossaryId : String?
90
+ ): String? {
91
+ if (apiKey == null ) return text
92
+
93
+ val (textWithPlaceholders, originalPlaceholders) = replaceSpecificPlaceholders(text)
94
+
95
+ val formBuilder = FormBody .Builder ()
96
+
97
+ val requestBody = formBuilder
98
+ .add(" text" , textWithPlaceholders)
99
+ .add(" source_lang" , sourceLang)
100
+ .add(" target_lang" , targetLang)
101
+ .add(" preserve_formatting" , preserveFormatting.toString())
102
+ .add(" tag_handling" , TAG_HANDLING )
103
+ .build()
104
+
105
+ glossaryId?.let {
106
+ formBuilder.add(" glossary_id" , it)
107
+ }
108
+
109
+ val request = Request .Builder ()
110
+ .url(apiURL)
111
+ .addHeader(" Authorization" , " DeepL-Auth-Key $apiKey " )
112
+ .post(requestBody)
113
+ .build()
114
+
115
+ client.newCall(request).execute().use { response ->
116
+ if (! response.isSuccessful) throw IOException (" Unexpected code $response " )
117
+
118
+ val responseBody = response.body?.string()
119
+ val translationResponse = mapper.readValue<TranslationResponse >(responseBody!! )
120
+
121
+ val translatedText = translationResponse.translations.firstOrNull()?.text
122
+ return translatedText?.let { revertSpecificPlaceholders(it, originalPlaceholders) }
123
+ }
124
+ }
125
+ }
0 commit comments