Skip to content

Commit

Permalink
Fix/Improvement: Translator feature (#2693)
Browse files Browse the repository at this point in the history
Co-authored-by: hannibal2 <[email protected]>
  • Loading branch information
Obsidianninja11 and hannibal002 authored Oct 11, 2024
1 parent 8a0105f commit 92291e6
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 74 deletions.
12 changes: 8 additions & 4 deletions src/main/java/at/hannibal2/skyhanni/config/commands/Commands.kt
Original file line number Diff line number Diff line change
Expand Up @@ -252,12 +252,12 @@ object Commands {
) { FarmingWeightDisplay.lookUpCommand(it) }
registerCommand(
"shcopytranslation",
"Copy the English translation of a message in another language to the clipboard.\n" + "Uses a 2 letter language code that can be found at the end of a translation message.",
) { Translator.fromEnglish(it) }
"Copy the translation of a message in another language to your clipboard.\n" + "Uses a language code that can be found at the end of a translation message.",
) { Translator.fromNativeLanguage(it) }
registerCommand(
"shtranslate",
"Translate a message in another language to English.",
) { Translator.toEnglish(it) }
"Translate a message in another language to your language.",
) { Translator.toNativeLanguage(it) }
registerCommand(
"shmouselock",
"Lock/Unlock the mouse so it will no longer rotate the player (for farming)",
Expand Down Expand Up @@ -619,6 +619,10 @@ object Commands {
"shresetmineshaftpitystats",
"Resets the mineshaft pity display stats",
) { MineshaftPityDisplay.fullResetCounter() }
registerCommand(
"shtranslateadvanced",
"Translates a message in an inputted language to another inputted language.",
) { Translator.translateAdvancedCommand(it) }
}

private fun internalCommands() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import at.hannibal2.skyhanni.config.FeatureToggle;
import at.hannibal2.skyhanni.features.chat.translation.TranslatableLanguage;
import at.hannibal2.skyhanni.utils.OSUtils;
import com.google.gson.annotations.Expose;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorButton;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDropdown;
import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorText;
import io.github.notenoughupdates.moulconfig.annotations.ConfigOption;
Expand All @@ -14,24 +16,34 @@ public class TranslatorConfig {
@Expose
@ConfigOption(
name = "Translate On Click",
desc = "Click on a message to translate it to English.\n" +
desc = "Click on a message to translate it to your language.\n" +
"Use §e/shcopytranslation§7 to translate from English.\n" +
"§cTranslation is not guaranteed to be 100% accurate."
)
@ConfigEditorBoolean
@FeatureToggle
public boolean translateOnClick = false;

@ConfigOption(name = "Language Name", desc = "The name of the language selected below. Note that languages marked as unknown might still be supported.")
@ConfigOption(name = "Your Language", desc = "The language that messages should be translated to.")
@Expose
@ConfigEditorDropdown
public Property<TranslatableLanguage> languageName = Property.of(TranslatableLanguage.ENGLISH);

@Expose
@ConfigOption(
name = "Language Code",
desc = "Enter a language code here to translate on chat click into another language. " +
"E.g. `es` for spanish or 'de' for german. Empty for english.")
desc = "If your language doesn't show in the dropdown, enter your language code here. " +
"E.g. 'es' for Spanish or 'de' for German. Empty will use English."
)
@ConfigEditorText
public Property<String> languageCode = Property.of("en");

@ConfigOption(
name = "List of Language Codes",
desc = "A list of Google Translate's suppored language codes."
)
@ConfigEditorButton(buttonText = "Open")
public Runnable langCodesURL = () -> OSUtils.openBrowser(
"https://cloud.google.com/translate/docs/languages#try-it-for-yourself"
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ enum class TranslatableLanguage(private val englishName: String, private val nat
TAGALOG("Tagalog", "Tagalog", "tl"), // Major language in the Philippines
PUNJABI("Punjabi", "ਪੰਜਾਬੀ", "pa"), // Significant in India and Pakistan

// 5. need better name
UNKNOWN("Unknown Language", "", ""),
// 5. Other Language
UNKNOWN("Other", "", ""),
;

// Limit to 20 characters so that the text is not too small in the config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ object Translator {
if (text.isEmpty()) {
config.languageName.set(TranslatableLanguage.ENGLISH)
} else {
for (language in TranslatableLanguage.values()) {
for (language in TranslatableLanguage.entries) {
if (language.languageCode.equals(text, ignoreCase = true)) {
config.languageName.set(language)
return@onToggle
Expand Down Expand Up @@ -107,102 +107,107 @@ object Translator {
* ]
* ],
* null,
* '"target language as a two-letter code following ISO 639-1"',
* '"target language as a (usually) two-letter code following ISO 639-1"',
* ]
*/

private fun getJSONResponse(urlString: String) =
APIUtils.getJSONResponseAsElement(urlString, false, "Google Translate API")
private fun getJSONResponse(urlString: String) = APIUtils.getJSONResponseAsElement(urlString, false, "Google Translate API")

private fun getTranslationToEnglish(message: String): String {
val url =
"https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=" +
URLEncoder.encode(message, "UTF-8")
fun getTranslation(
message: String,
targetLanguage: String,
sourceLanguage: String = "auto",
): Array<String>? {
// TODO add &dj=1 to use named json
val url = "https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&sl=$sourceLanguage&tl=$targetLanguage&q=" +
URLEncoder.encode(message, "UTF-8")

var messageToSend = ""
val layer1 = getJSONResponse(url).asJsonArray
if (layer1.size() <= 2) return "Error!"
val fullResponse = getJSONResponse(url).asJsonArray
if (fullResponse.size() < 3) return null

val language = layer1[2].toString()
if (language == "\"en\"") return "Unable to translate!"
if (language.length != 4) return "Error!"
val language = fullResponse[2].toString() // the detected language the message is in
val sentences = fullResponse[0] as? JsonArray ?: return null

val layer2 = try {
layer1[0] as JsonArray
} catch (_: Exception) {
return "Error!"
}

for (layer3 in layer2) {
val arrayLayer3 = layer3 as? JsonArray ?: continue
val sentence = arrayLayer3[0].toString()
for (rawSentence in sentences) {
val arrayPhrase = rawSentence as? JsonArray ?: continue
val sentence = arrayPhrase[0].toString()
val sentenceWithoutQuotes = sentence.substring(1, sentence.length - 1)
messageToSend = "$messageToSend$sentenceWithoutQuotes"
}
messageToSend = "$messageToSend §7(Language: $language)"
messageToSend = URLDecoder.decode(messageToSend, "UTF-8").replace("\\", "") // Not sure if this is actually needed
return arrayOf(messageToSend, language)
}

return URLDecoder.decode(messageToSend, "UTF-8").replace("\\", "")
@Deprecated("Use toNativeLanguage() instead", ReplaceWith("Translator.toNativeLanguage(args)"))
fun toEnglish(args: Array<String>) {
toNativeLanguage(args)
}

private fun getTranslationFromEnglish(message: String, lang: String): String {
val url =
"https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=$lang&dt=t&q=" +
URLEncoder.encode(message, "UTF-8")

val layer1 = getJSONResponse(url).asJsonArray
if (layer1.size() < 1) return "Error!"
val layer2 = layer1[0] as? JsonArray

val firstSentence = (layer2?.get(0) as? JsonArray)?.get(0).toString()
var messageToSend = firstSentence.substring(0, firstSentence.length - 1)
if (layer2 != null) {
for (sentenceIndex in 1..<layer2.size()) {
val sentence = (layer2.get(sentenceIndex) as JsonArray).get(0).toString()
val sentenceWithoutQuotes = sentence.substring(1, sentence.length - 1)
messageToSend = "$messageToSend$sentenceWithoutQuotes"
}
} // The first translated sentence only has 1 extra char at the end, but sentences after it need 1 at the front and 1 at the end removed in the substring
messageToSend = messageToSend.substring(1, messageToSend.length)
return URLDecoder.decode(messageToSend, "UTF-8").replace("\\", "")
@Deprecated("Use fromNativeLanguage() instead", ReplaceWith("Translator.fromNativeLanguage(args)"))
fun fromEnglish(args: Array<String>) {
fromNativeLanguage(args)
}

fun toEnglish(args: Array<String>) {
fun toNativeLanguage(args: Array<String>) {
val message = args.joinToString(" ").removeColor()

coroutineScope.launch {
var lang = config.languageCode.get()
val translation = if (lang.isEmpty()) {
getTranslationToEnglish(message)
} else {
getTranslationFromEnglish(message, lang)
}
if (message == translation) {
ChatUtils.userError("Translation is the same as the original message!")
return@launch
}
val translation = getTranslation(message, nativeLanguage())
val translatedMessage = translation?.get(0) ?: "Error!"
val detectedLanguage = translation?.get(1) ?: "Error!"

if (translation == "Unable to translate!") {
ChatUtils.userError("Unable to translate message :( (is it in English?)")
if (message == translatedMessage) {
ChatUtils.userError("Translation is the same as the original message!")
return@launch
}
ChatUtils.chat("Found translation: §f$translation")
ChatUtils.clickableChat(
"Found translation: §f$translatedMessage",
onClick = { OSUtils.copyToClipboard(translatedMessage) },
"§eClick to copy!\n§eOriginal message: §f$message §7(Language: $detectedLanguage)",
)
}
}

fun fromEnglish(args: Array<String>) {
if (args.size < 2 || args[0].length != 2) { // args[0] is the language code
ChatUtils.userError("Usage: /shcopytranslation <two letter language code (at the end of a translation)> <message>")
fun fromNativeLanguage(args: Array<String>) {
if (args.size < 2) {
ChatUtils.userError("Usage: /shcopytranslation <language code (found at the end of a translation)> <message>")
return
}
val language = args[0]
val message = args.drop(1).joinToString(" ")

coroutineScope.launch {
val translation = getTranslationFromEnglish(message, language)
ChatUtils.chat("Copied translation to clipboard: §f$translation")
OSUtils.copyToClipboard(translation)
val translation = getTranslation(message, language, nativeLanguage())?.get(0) ?: "Error!"
ChatUtils.clickableChat(
"Copied §f$language §etranslation to clipboard: §f$translation",
onClick = { OSUtils.copyToClipboard(translation) },
"§eClick to copy!\n§eOriginal message: §f$message",
)
}
}

fun translateAdvancedCommand(args: Array<String>) {
if (args.size < 3) {
ChatUtils.userError("Usage: /shtranslateadvanced <source lang code> <target lang code> <message>")
return
}
val sourceLanguage = args[0]
val targetLanguage = args[1]
val message = args.drop(2).joinToString(" ")

val translation = getTranslation(message, sourceLanguage, targetLanguage)
val translatedMessage = translation?.get(0) ?: "Error!"
val detectedLanguage = if (sourceLanguage == "auto") " ${translation?.get(1) ?: "Error!"}" else ""

ChatUtils.clickableChat(
"Found translation from sl: $sourceLanguage: §f$translatedMessage §7(tl: $targetLanguage)",
onClick = { OSUtils.copyToClipboard(translatedMessage) },
"§eClick to copy!\n§eOriginal message: §f$message §7(sl: $sourceLanguage$detectedLanguage)",
)
}

fun nativeLanguage(): String = config.languageCode.get().ifEmpty { "en" }

fun isEnabled() = config.translateOnClick
}

0 comments on commit 92291e6

Please sign in to comment.