Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OpenAI translation support #2887

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ class SuggestResultModel(
"TOLGEE": {
"output": "This was translated by Tolgee Translator",
"contextDescription": "This is an example in swagger"
}
},
"OPENAI": {
"output": "This was translated by OpenAI",
"contextDescription": null
}
}
""",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ class TranslationSuggestionControllerMtTest : ProjectAuthControllerTest("/v2/pro
node("AZURE").isEqualTo("Translated with Azure Cognitive")
node("BAIDU").isEqualTo("Translated with Baidu")
node("TOLGEE").isEqualTo("Translated with Tolgee Translator")
node("OPENAI").isEqualTo("Translated with OpenAI")
}

mtCreditBucketService.getCreditBalances(
Expand All @@ -275,6 +276,7 @@ class TranslationSuggestionControllerMtTest : ProjectAuthControllerTest("/v2/pro
node("AZURE").isAbsent()
node("BAIDU").isAbsent()
node("TOLGEE").isEqualTo("Translated with Tolgee Translator")
node("OPENAI").isEqualTo("Translated with OpenAI")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package io.tolgee.component.machineTranslation.providers

import com.fasterxml.jackson.annotation.JsonProperty
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import io.tolgee.configuration.tolgee.machineTranslation.OpenaiMachineTranslationProperties
import org.springframework.beans.factory.config.ConfigurableBeanFactory
import org.springframework.context.annotation.Scope
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.client.RestTemplate

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
class OpenaiApiService(
private val openaiMachineTranslationProperties: OpenaiMachineTranslationProperties,
private val restTemplate: RestTemplate,
) {
fun translate(
text: String,
sourceTag: String,
targetTag: String,
): String? {
var headers = HttpHeaders()
headers.contentType = MediaType.APPLICATION_JSON
headers.add("Authorization", "Bearer ${openaiMachineTranslationProperties.apiKey}")

var prompt = openaiMachineTranslationProperties.prompt
prompt = prompt.replace("{source}", sourceTag)
prompt = prompt.replace("{target}", targetTag)
prompt = prompt.replace("{text}", text)

val requestBody = JsonObject()
requestBody.addProperty("model", openaiMachineTranslationProperties.model)
requestBody.add(
"messages",
JsonArray().apply {
add(
JsonObject().apply {
addProperty("role", "user")
addProperty("content", prompt)
},
)
},
)

val response =
restTemplate.postForEntity<OpenaiCompletionResponse>(
openaiMachineTranslationProperties.apiEndpoint,
HttpEntity(requestBody.toString(), headers),
OpenaiCompletionResponse::class.java,
)

return response.body?.choices?.first()?.message?.content
?: throw RuntimeException(response.toString())
}

companion object {
class OpenaiCompletionResponse {
@JsonProperty("choices")
var choices: List<OpenaiChoice>? = null
}

class OpenaiChoice {
@JsonProperty("message")
var message: OpenaiMessage? = null
}

class OpenaiMessage {
@JsonProperty("content")
var content: String? = null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package io.tolgee.component.machineTranslation.providers

import io.tolgee.component.machineTranslation.MtValueProvider
import io.tolgee.configuration.tolgee.machineTranslation.OpenaiMachineTranslationProperties
import org.springframework.beans.factory.config.ConfigurableBeanFactory
import org.springframework.context.annotation.Scope
import org.springframework.stereotype.Component

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
class OpenaiTranslationProvider(
private val openaiMachineTranslationProperties: OpenaiMachineTranslationProperties,
private val openaiApiService: OpenaiApiService,
) : AbstractMtValueProvider() {
override val isEnabled: Boolean
get() = !openaiMachineTranslationProperties.apiKey.isNullOrEmpty()

override fun translateViaProvider(params: ProviderTranslateParams): MtValueProvider.MtResult {
val result =
openaiApiService.translate(
params.text,
params.sourceLanguageTag,
params.targetLanguageTag,
)
return MtValueProvider.MtResult(result, params.text.length * 100)
}

override val formalitySupportingLanguages =
arrayOf(
"de",
"es",
"fr",
"it",
"ja",
"nl",
"pl",
"pt-pt",
"pt-br",
"ru",
)

override val supportedLanguages =
arrayOf(
"yue",
"yue-hans",
"yue-hans-cn",
"kor",
"ko",
"ko-kr",
"th",
"th-th",
"pt",
"pt-pt",
"pt-br",
"el",
"el-gr",
"bul",
"bg",
"bg-bg",
"fin",
"fi",
"fi-fi",
"slo",
"sk",
"sk-sk",
"cht",
"zh-hant",
"zh-hant-hk",
"zh-hant-mo",
"zh-hant-tw",
"zh-tw",
"zh",
"zh-hans",
"zh-hans-cn",
"zh-hans-sg",
"zh-hans-hk",
"zh-hans-mo",
"wyw",
"fra",
"fr",
"fr-fr",
"ara",
"ar",
"de",
"de-de",
"nl",
"nl",
"nl-nl",
"est",
"et",
"et-ee",
"cs",
"cs-cz",
"swe",
"sl",
"sl-si",
"sv",
"sv-se",
"vie",
"vi",
"vi-vn",
"en",
"en-us",
"en-gb",
"jp",
"ja",
"ja-jp",
"spa",
"es",
"es-es",
"ru",
"ru-ru",
"it",
"it-it",
"pl",
"pl-pl",
"dan",
"da",
"da-dk",
"rom",
"ro",
"ro-ro",
"hu",
"hu-hu",
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ open class MachineTranslationProperties(
var azure: AzureCognitiveTranslationProperties = AzureCognitiveTranslationProperties(),
var baidu: BaiduMachineTranslationProperties = BaiduMachineTranslationProperties(),
var tolgee: TolgeeMachineTranslationProperties = TolgeeMachineTranslationProperties(),
var openai: OpenaiMachineTranslationProperties = OpenaiMachineTranslationProperties(),
@DocProperty(
description =
"Amount of machine translations users of the Free tier can request per month. " +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.tolgee.configuration.tolgee.machineTranslation

import io.tolgee.configuration.annotations.DocProperty
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "tolgee.machine-translation.openai")
@DocProperty(
description = "OpenAI machine translation properties",
displayName = "OpenAI Translate",
)
open class OpenaiMachineTranslationProperties(
@DocProperty(description = "Whether OpenAI-powered machine translation is enabled.")
override var defaultEnabled: Boolean = true,
@DocProperty(description = "Whether to use OpenAI Translate as a primary translation engine.")
override var defaultPrimary: Boolean = false,
@DocProperty(description = "OpenAI API key")
var apiKey: String? = null,
@DocProperty(description = "OpenAI model to use for translation")
var model: String = "gpt-4o-mini",
@DocProperty(description = "OpenAI API endpoint")
var apiEndpoint: String = "https://api.openai.com/v1/chat/completions",
@DocProperty(
description = "Translation prompt. Should contain {source}, {target} and {text} placeholders.",
)
var prompt: String =
"Translate the following text from {source} to {target}: \"{text}\". " +
"Do not include any other information in the response.",
) : MachineTranslationServiceProperties
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import io.tolgee.component.machineTranslation.providers.AzureCognitiveTranslatio
import io.tolgee.component.machineTranslation.providers.BaiduTranslationProvider
import io.tolgee.component.machineTranslation.providers.DeeplTranslationProvider
import io.tolgee.component.machineTranslation.providers.GoogleTranslationProvider
import io.tolgee.component.machineTranslation.providers.OpenaiTranslationProvider
import io.tolgee.component.machineTranslation.providers.tolgee.TolgeeTranslationProvider
import io.tolgee.configuration.tolgee.machineTranslation.AwsMachineTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.AzureCognitiveTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.BaiduMachineTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.DeeplMachineTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.GoogleMachineTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.MachineTranslationServiceProperties
import io.tolgee.configuration.tolgee.machineTranslation.OpenaiMachineTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.TolgeeMachineTranslationProperties

enum class MtServiceType(
Expand Down Expand Up @@ -54,4 +56,9 @@ enum class MtServiceType(
order = -1,
supportsPlurals = true,
),
OPENAI(
propertyClass = OpenaiMachineTranslationProperties::class.java,
providerClass = OpenaiTranslationProvider::class.java,
order = 6,
),
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ class SuggestionTestData : BaseTestData() {
MtServiceType.AZURE,
MtServiceType.BAIDU,
MtServiceType.TOLGEE,
MtServiceType.OPENAI,
)
this.primaryService = MtServiceType.AWS
}
Expand All @@ -187,6 +188,7 @@ class SuggestionTestData : BaseTestData() {
MtServiceType.DEEPL,
MtServiceType.AZURE,
MtServiceType.BAIDU,
MtServiceType.OPENAI,
)
this.primaryService = MtServiceType.GOOGLE
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.tolgee.configuration.tolgee.machineTranslation.BaiduMachineTranslation
import io.tolgee.configuration.tolgee.machineTranslation.DeeplMachineTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.GoogleMachineTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.MachineTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.OpenaiMachineTranslationProperties
import io.tolgee.configuration.tolgee.machineTranslation.TolgeeMachineTranslationProperties
import io.tolgee.constants.MtServiceType
import io.tolgee.development.DbPopulatorReal
Expand Down Expand Up @@ -166,6 +167,9 @@ abstract class AbstractSpringTest : AbstractTransactionalTest() {
@Autowired
lateinit var tolgeeMachineTranslationProperties: TolgeeMachineTranslationProperties

@Autowired
lateinit var openaiMachineTranslationProperties: OpenaiMachineTranslationProperties

@Autowired
lateinit var internalProperties: InternalProperties

Expand Down Expand Up @@ -256,6 +260,7 @@ abstract class AbstractSpringTest : AbstractTransactionalTest() {
tolgeeMachineTranslationProperties.url = "http://localhost:8081"
tolgeeMachineTranslationProperties.defaultEnabled = enabledServices.contains(MtServiceType.TOLGEE)
internalProperties.fakeMtProviders = false
openaiMachineTranslationProperties.apiKey = "dummy"
}

fun <T> executeInNewTransaction(fn: (ts: TransactionStatus) -> T): T {
Expand Down
11 changes: 11 additions & 0 deletions webapp/public/images/providers/openai-icon-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions webapp/public/images/providers/openai-icon-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading