mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-01 00:18:30 +02:00
Adds automatic translation to feed and chat.
This commit is contained in:
parent
d168a6c861
commit
229f15ee7f
@ -85,7 +85,7 @@ dependencies {
|
||||
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.11'
|
||||
|
||||
// Json Serialization
|
||||
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.14.1'
|
||||
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.14.2'
|
||||
|
||||
// link preview
|
||||
implementation 'tw.com.oneup.www:Baha-UrlPreview:1.0.1'
|
||||
@ -111,12 +111,21 @@ dependencies {
|
||||
implementation "com.google.accompanist:accompanist-pager-indicators:$accompanist_version"
|
||||
|
||||
// For QR generation
|
||||
implementation "com.google.zxing:core:3.5.0"
|
||||
implementation 'com.google.zxing:core:3.5.1'
|
||||
implementation "androidx.camera:camera-camera2:1.2.1"
|
||||
implementation 'androidx.camera:camera-lifecycle:1.2.1'
|
||||
implementation 'androidx.camera:camera-view:1.2.1'
|
||||
|
||||
// For QR Scanning
|
||||
implementation 'com.google.mlkit:vision-common:17.3.0'
|
||||
implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0'
|
||||
implementation 'com.google.mlkit:barcode-scanning:17.0.3'
|
||||
|
||||
// Use this dependency to use the dynamically downloaded model in Google Play Services
|
||||
implementation 'com.google.android.gms:play-services-mlkit-language-id:17.0.0'
|
||||
|
||||
// Use this dependency to use the translate text
|
||||
implementation 'com.google.mlkit:translate:17.0.1'
|
||||
implementation 'com.google.mlkit:language-id-common:16.1.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
|
@ -1,30 +1,45 @@
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.util.LruCache
|
||||
import android.util.Patterns
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.navigation.NavController
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
import com.google.android.gms.tasks.Task
|
||||
import com.google.android.gms.tasks.Tasks
|
||||
import com.google.mlkit.nl.languageid.LanguageIdentification
|
||||
import com.google.mlkit.nl.translate.TranslateLanguage
|
||||
import com.google.mlkit.nl.translate.Translation
|
||||
import com.google.mlkit.nl.translate.Translator
|
||||
import com.google.mlkit.nl.translate.TranslatorOptions
|
||||
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.toByteArray
|
||||
import com.vitorpamplona.amethyst.model.toNote
|
||||
import com.vitorpamplona.amethyst.service.Nip19
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URISyntaxException
|
||||
import java.net.URL
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
import nostr.postr.toNpub
|
||||
|
||||
@ -50,10 +65,25 @@ fun isValidURL(url: String?): Boolean {
|
||||
|
||||
@Composable
|
||||
fun RichTextViewer(content: String, tags: List<List<String>>?, navController: NavController) {
|
||||
Column(modifier = Modifier.padding(top = 5.dp)) {
|
||||
val translatedTextState = remember {
|
||||
mutableStateOf(ResultOrError(content, null, null, null))
|
||||
}
|
||||
|
||||
var showOriginal by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
LanguageTranslatorService.autoTranslate(content).addOnCompleteListener { task ->
|
||||
if (task.isSuccessful) {
|
||||
translatedTextState.value = task.result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val text = if (showOriginal) content else translatedTextState.value.result
|
||||
|
||||
Column(modifier = Modifier.padding(top = 5.dp)) {
|
||||
// FlowRow doesn't work well with paragraphs. So we need to split them
|
||||
content.split('\n').forEach { paragraph ->
|
||||
text?.split('\n')?.forEach { paragraph ->
|
||||
|
||||
FlowRow() {
|
||||
paragraph.split(' ').forEach { word: String ->
|
||||
@ -88,7 +118,34 @@ fun RichTextViewer(content: String, tags: List<List<String>>?, navController: Na
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val target = translatedTextState.value.targetLang
|
||||
val source = translatedTextState.value.sourceLang
|
||||
|
||||
if (source != null && target != null) {
|
||||
if (source != target) {
|
||||
Row() {
|
||||
Text(
|
||||
text = "Auto-translated from ",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
)
|
||||
ClickableText(
|
||||
text = AnnotatedString("${Locale(source).displayName}"),
|
||||
onClick = { showOriginal = true },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f))
|
||||
)
|
||||
Text(
|
||||
text = " to ",
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
)
|
||||
ClickableText(
|
||||
text = AnnotatedString("${Locale(target).displayName}"),
|
||||
onClick = { showOriginal = false },
|
||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,3 +222,90 @@ fun TagLink(word: String, tags: List<List<String>>, navController: NavController
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ResultOrError(
|
||||
var result: String?,
|
||||
var sourceLang: String?,
|
||||
var targetLang: String?,
|
||||
var error: Exception?
|
||||
)
|
||||
|
||||
object LanguageTranslatorService {
|
||||
private val languageIdentification = LanguageIdentification.getClient()
|
||||
|
||||
private val languagesSpokenByTheUser = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration()).toLanguageTags()
|
||||
private val usersPreferredLanguage = Locale.getDefault().language
|
||||
|
||||
init {
|
||||
println("LanguagesAAA: ${languagesSpokenByTheUser}")
|
||||
}
|
||||
|
||||
private val translators =
|
||||
object : LruCache<TranslatorOptions, Translator>(10) {
|
||||
override fun create(options: TranslatorOptions): Translator {
|
||||
return Translation.getClient(options)
|
||||
}
|
||||
|
||||
override fun entryRemoved(
|
||||
evicted: Boolean,
|
||||
key: TranslatorOptions,
|
||||
oldValue: Translator,
|
||||
newValue: Translator?
|
||||
) {
|
||||
oldValue.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun identifyLanguage(text: String): Task<String> {
|
||||
return languageIdentification.identifyLanguage(text)
|
||||
}
|
||||
|
||||
fun translate(text: String, source: String, target: String): Task<ResultOrError> {
|
||||
val sourceLangCode = TranslateLanguage.fromLanguageTag(source)
|
||||
val targetLangCode = TranslateLanguage.fromLanguageTag(target)
|
||||
if (sourceLangCode == null || targetLangCode == null) {
|
||||
return Tasks.forCanceled()
|
||||
}
|
||||
|
||||
val options = TranslatorOptions.Builder()
|
||||
.setSourceLanguage(sourceLangCode)
|
||||
.setTargetLanguage(targetLangCode)
|
||||
.build()
|
||||
|
||||
val translator = translators[options]
|
||||
|
||||
return translator.downloadModelIfNeeded().onSuccessTask {
|
||||
|
||||
val tasks = mutableListOf<Task<String>>()
|
||||
for (paragraph in text.split("\n")) {
|
||||
tasks.add(translator.translate(paragraph))
|
||||
}
|
||||
|
||||
Tasks.whenAll(tasks).continueWith {
|
||||
val results: MutableList<String> = ArrayList()
|
||||
for (task in tasks) {
|
||||
results.add(task.result)
|
||||
}
|
||||
ResultOrError(results.joinToString("\n"), source, target, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun autoTranslate(text: String, target: String): Task<ResultOrError> {
|
||||
return identifyLanguage(text).onSuccessTask {
|
||||
if (it == target) {
|
||||
Tasks.forCanceled()
|
||||
} else if (it != "und" && !languagesSpokenByTheUser.contains(it)) {
|
||||
translate(text, it, target)
|
||||
} else {
|
||||
Tasks.forCanceled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun autoTranslate(text: String): Task<ResultOrError> {
|
||||
return autoTranslate(text, usersPreferredLanguage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user