Skip to content

Commit

Permalink
resolve theopenconversationkit#1606 [Translator] : init Deepl integra…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
charles_moulhaud committed Jun 14, 2024
1 parent 4acd3bb commit 405e49c
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 101 deletions.
2 changes: 1 addition & 1 deletion translator/deepl-translate/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<version>23.9.3-SNAPSHOT</version>
</parent>

<artifactId>tock-translator-deepl</artifactId>
<artifactId>tock-deepl-translate</artifactId>
<name>Tock Deepl Translator</name>
<description>Deepl translator implementation</description>

Expand Down
54 changes: 9 additions & 45 deletions translator/deepl-translate/src/main/kotlin/DeeplClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@
* limitations under the License.
*/

package ai.tock.translator.deepl
package ai.tock
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import mu.KotlinLogging
import java.io.IOException
import java.util.regex.Pattern

data class TranslationResponse(
val translations: List<Translation>
Expand All @@ -34,58 +32,24 @@ data class Translation(
)

val DEEPL_URL_API = "https://api.deepl.com/v2/translate"
val TAG_HANDLING = "xml"

class DeeplClient(private val apiKey: String) {
private val client = OkHttpClient()
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
private val jsonAdapter = moshi.adapter(TranslationResponse::class.java)

private val logger = KotlinLogging.logger {}
fun translate(text: String, sourceLang: String,targetLang: String): String? {
// TODO Pass placeholders options in parameters
val textWithTags = text.replace("{", "<x>").replace("}", "</x>")

private fun replaceSpecificPlaceholders(text: String): Pair<String, List<String>> {
// Store original placeholders for later restoration
val placeholderPattern = Pattern.compile("\\{:([^}]*)}")
val matcher = placeholderPattern.matcher(text)

val placeholders = mutableListOf<String>()
while (matcher.find()) {
placeholders.add(matcher.group(1))
}

// Replace placeholders with '_PLACEHOLDER_'
val replacedText = matcher.replaceAll("_PLACEHOLDER_")

return Pair(replacedText, placeholders)
}

private fun revertSpecificPlaceholders(text: String, placeholders: List<String>): String {
var resultText = text
for (placeholder in placeholders) {
resultText = resultText.replaceFirst("_PLACEHOLDER_", "{:$placeholder}")
}
return resultText
}

fun translate(text: String, sourceLang: String,targetLang: String,preserveFormatting: Boolean,glossaryId:String?): String? {
val (textWithPlaceholders, originalPlaceholders) = replaceSpecificPlaceholders(text)

val requestBody = buildString {
append("text=$textWithPlaceholders")
append("&source_lang=$sourceLang")
append("&target_lang=$targetLang")
append("&preserve_formatting=$preserveFormatting")
append("&tag_handling=$TAG_HANDLING")

if (glossaryId != "default") {
append("&glossary=$glossaryId")
}
}
val requestBody = """
text=$textWithTags&source_lang=$sourceLang&target_lang=$targetLang&tag_handling=xml&non_translatable_tags=x
""".trimIndent().toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull())

val request = Request.Builder()
.url(DEEPL_URL_API)
.addHeader("Authorization", "DeepL-Auth-Key $apiKey")
.post(requestBody.trimIndent().toRequestBody("application/x-www-form-urlencoded".toMediaTypeOrNull()))
.post(requestBody)
.build()

client.newCall(request).execute().use { response ->
Expand All @@ -95,7 +59,7 @@ class DeeplClient(private val apiKey: String) {
val translationResponse = jsonAdapter.fromJson(responseBody!!)

val translatedText = translationResponse?.translations?.firstOrNull()?.text
return translatedText?.let { revertSpecificPlaceholders(it,originalPlaceholders) }
return translatedText?.replace("<x>", "{")?.replace("</x>", "}")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,20 @@
* limitations under the License.
*/

package ai.tock.translator.deepl
package ai.tock

import ai.tock.shared.property
import ai.tock.translator.TranslatorEngine
import mu.KotlinLogging
import org.apache.commons.text.StringEscapeUtils
import java.util.Locale

internal object DeeplTranslatorEngine : TranslatorEngine {

private val logger = KotlinLogging.logger {}

private val deeplClient = DeeplClient(property ("tock_translator_deepl_api_key", "default"))
private val glossaryId = property ("tock_translator_deepl_glossaryId", "default")
private val deeplClient = DeeplClient(property ("tock_deepl_api_key", "default"))
override val supportAdminTranslation: Boolean = true

override fun translate(text: String, source: Locale, target: Locale): String {
logger.debug("Try to translate text using deepl")
val translatedText = deeplClient.translate(text, source.language, target.language,true,glossaryId)
val translatedText = deeplClient.translate(text, source.language, target.language)
return StringEscapeUtils.unescapeHtml4(translatedText)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import ai.tock.translator.deepl.DeeplTranslatorEngine
import org.junit.jupiter.api.Disabled
import ai.tock.DeeplTranslatorEngine
import org.junit.jupiter.api.Test
import java.util.Locale
import kotlin.test.assertEquals
Expand All @@ -20,57 +19,16 @@ import kotlin.test.assertEquals
* limitations under the License.
*/

/**
* All these tests are disabled because it uses Deepl pro api that can be expensive
*/
class DeeplTranslateIntegrationTest {
@Test
@Disabled
fun simpleTest() {
val result = DeeplTranslatorEngine.translate(
"Bonjour, je voudrais me rendre à New-York Mardi prochain",
Locale.FRENCH,
Locale.ENGLISH
)
assertEquals("Hello, I would like to go to New York next Tuesday.", result)
}

@Test
@Disabled
fun testWithEmoticonAndAntislash() {
val result = DeeplTranslatorEngine.translate("Bonjour, je suis l'Agent virtuel SNCF Voyageurs! \uD83E\uDD16\n" +
"Je vous informe sur l'état du trafic en temps réel.\n" +
"Dites-moi par exemple \"Mon train 6111 est-il à l'heure ?\", \"Aller à Saint-Lazare\", \"Prochains départs Gare de Lyon\" ...",
Locale.FRENCH,
Locale.ENGLISH
)

assertEquals("Hello, I'm the SNCF Voyageurs Virtual Agent! \uD83E\uDD16\n" +
"I inform you about traffic conditions in real time.\n" +
"Tell me for example \"Is my train 6111 on time?\", \"Going to Saint-Lazare\", \"Next departures Gare de Lyon\" ...",
result
)
val result = DeeplTranslatorEngine.translate("Bonjour, je voudrais me rendre à New-York Mardi prochain", Locale.FRENCH, Locale.ENGLISH)
assertEquals("Hello, I would like to go to New York next Tuesday.",result)
}

@Test
@Disabled
fun testWithParameters() {
val result = DeeplTranslatorEngine.translate(
"Bonjour, je voudrais me rendre à {:city} {:date}",
Locale.FRENCH,
Locale.GERMAN
)
assertEquals("Hallo, ich möchte nach {:city} {:date} reisen", result)
}

@Test
@Disabled
fun testWithHTML() {
val result = DeeplTranslatorEngine.translate(
"Bonjour, je voudrais me rendre à Paris <br><br/> demain soir",
Locale.FRENCH,
Locale.GERMAN
)
assertEquals("Hallo, ich möchte morgen Abend nach Paris <br><br/> fahren", result)
val result = DeeplTranslatorEngine.translate("Bonjour, je voudrais me rendre à {:city} {:date}", Locale.FRENCH, Locale.GERMAN)
assertEquals("Hallo, ich möchte nach {:city} {:date} reisen",result)
}
}

0 comments on commit 405e49c

Please sign in to comment.