implement automated translation and caching for vocabulary pack names and descriptions in ExplorePacksScreen using LibreTranslate.
This commit is contained in:
@@ -55,7 +55,9 @@ class TranslationService(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun libreTranslate(text: String, source: String?, target: String, alternatives: Int = 1): Result<String> = withContext(Dispatchers.IO) {
|
// Public method to directly use LibreTranslate (bypasses AI)
|
||||||
|
suspend fun libreTranslate(text: String, source: String?, target: String, alternatives: Int = 1): Result<String> = withContext(Dispatchers.IO) {
|
||||||
|
Log.d("libreTranslate: $text, $source, $target")
|
||||||
try {
|
try {
|
||||||
val json = org.json.JSONObject().apply {
|
val json = org.json.JSONObject().apply {
|
||||||
put("q", text)
|
put("q", text)
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ import eu.gaudian.translator.viewmodel.ImportState
|
|||||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||||
import eu.gaudian.translator.viewmodel.PackDownloadState
|
import eu.gaudian.translator.viewmodel.PackDownloadState
|
||||||
import eu.gaudian.translator.viewmodel.PackUiState
|
import eu.gaudian.translator.viewmodel.PackUiState
|
||||||
|
import eu.gaudian.translator.viewmodel.TranslationViewModel
|
||||||
import eu.gaudian.translator.viewmodel.VocabPacksViewModel
|
import eu.gaudian.translator.viewmodel.VocabPacksViewModel
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
@@ -125,19 +126,168 @@ enum class PackFilter {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
private val gradientPalette = listOf(
|
private val gradientPalette = listOf(
|
||||||
listOf(Color(0xFF1565C0), Color(0xFF42A5F5)),
|
// Original Gradients
|
||||||
listOf(Color(0xFF00695C), Color(0xFF26A69A)),
|
listOf(Color(0xFF1565C0), Color(0xFF42A5F5)), // Blue
|
||||||
listOf(Color(0xFF6A1B9A), Color(0xFFAB47BC)),
|
listOf(Color(0xFF00695C), Color(0xFF26A69A)), // Teal
|
||||||
listOf(Color(0xFFE65100), Color(0xFFFFA726)),
|
listOf(Color(0xFF6A1B9A), Color(0xFFAB47BC)), // Purple
|
||||||
listOf(Color(0xFF212121), Color(0xFF546E7A)),
|
listOf(Color(0xFFE65100), Color(0xFFFFA726)), // Orange
|
||||||
listOf(Color(0xFFC62828), Color(0xFFEF9A9A)),
|
listOf(Color(0xFF212121), Color(0xFF546E7A)), // Dark Grey to Blue Grey
|
||||||
listOf(Color(0xFF1B5E20), Color(0xFF66BB6A)),
|
listOf(Color(0xFFC62828), Color(0xFFEF9A9A)), // Red
|
||||||
listOf(Color(0xFF0D47A1), Color(0xFF90CAF9)),
|
listOf(Color(0xFF1B5E20), Color(0xFF66BB6A)), // Green
|
||||||
|
listOf(Color(0xFF0D47A1), Color(0xFF90CAF9)), // Deep Blue
|
||||||
|
|
||||||
|
// New Monochromatic / Material Shades
|
||||||
|
listOf(Color(0xFFAD1457), Color(0xFFF06292)), // Pink
|
||||||
|
listOf(Color(0xFF283593), Color(0xFF7986CB)), // Indigo
|
||||||
|
listOf(Color(0xFF00838F), Color(0xFF4DD0E1)), // Cyan
|
||||||
|
listOf(Color(0xFFFF8F00), Color(0xFFFFD54F)), // Amber
|
||||||
|
listOf(Color(0xFFD84315), Color(0xFFFF8A65)), // Deep Orange
|
||||||
|
listOf(Color(0xFF4E342E), Color(0xFFA1887F)), // Brown
|
||||||
|
listOf(Color(0xFF4527A0), Color(0xFF9575CD)), // Deep Purple
|
||||||
|
listOf(Color(0xFF9E9D24), Color(0xFFDCE775)), // Lime
|
||||||
|
listOf(Color(0xFF37474F), Color(0xFF90A4AE)), // Cool Grey
|
||||||
|
listOf(Color(0xFFF57F17), Color(0xFFFFF176)), // Yellow
|
||||||
|
|
||||||
|
// New Multi-Hue / Vibrant Gradients
|
||||||
|
listOf(Color(0xFF1A237E), Color(0xFF880E4F)), // Deep Blue to Deep Pink (Midnight)
|
||||||
|
listOf(Color(0xFFE65100), Color(0xFFE91E63)), // Dark Orange to Pink (Sunset)
|
||||||
|
listOf(Color(0xFF0277BD), Color(0xFF00897B)), // Light Blue to Teal (Ocean)
|
||||||
|
listOf(Color(0xFF303F9F), Color(0xFF7B1FA2)), // Indigo to Purple (Galaxy)
|
||||||
|
listOf(Color(0xFFBF360C), Color(0xFFFFCA28)), // Deep Red to Amber (Fire)
|
||||||
|
listOf(Color(0xFF004D40), Color(0xFF64FFDA)), // Dark Teal to Mint (Aqua)
|
||||||
|
listOf(Color(0xFF4A148C), Color(0xFFF50057)), // Dark Purple to Neon Pink (Cyberpunk)
|
||||||
|
listOf(Color(0xFF1B5E20), Color(0xFFC0CA33)), // Dark Green to Lime (Forest)
|
||||||
|
listOf(Color(0xFF827717), Color(0xFFFF9800)), // Olive to Orange (Autumn)
|
||||||
|
listOf(Color(0xFF01579B), Color(0xFF00E5FF)), // Navy to Neon Cyan (Electric Blue)
|
||||||
|
|
||||||
|
// Pastel / Soft Gradients
|
||||||
|
listOf(Color(0xFF80DEEA), Color(0xFFE0F7FA)), // Soft Cyan
|
||||||
|
listOf(Color(0xFFF48FB1), Color(0xFFFCE4EC)), // Soft Pink
|
||||||
|
listOf(Color(0xFFCE93D8), Color(0xFFF3E5F5)), // Soft Purple
|
||||||
|
listOf(Color(0xFFA5D6A7), Color(0xFFE8F5E9)), // Soft Green
|
||||||
|
|
||||||
|
listOf(Color(0xFF81D4FA), Color(0xFFE1F5FE)), // Light Sky Blue
|
||||||
|
listOf(Color(0xFFB39DDB), Color(0xFFEDE7F6)), // Soft Lavender
|
||||||
|
listOf(Color(0xFFFFCC80), Color(0xFFFFF3E0)), // Peach / Warm Sand
|
||||||
|
listOf(Color(0xFFA5D6A7), Color(0xFFF1F8E9)), // Pale Mint
|
||||||
|
listOf(Color(0xFFFFF59D), Color(0xFFFFFDE7)), // Soft Lemon
|
||||||
|
listOf(Color(0xFFFFAB91), Color(0xFFFBE9E7)), // Pale Coral
|
||||||
|
listOf(Color(0xFFCE93D8), Color(0xFFF3E5F5)), // Light Orchid
|
||||||
|
listOf(Color(0xFFBCAAA4), Color(0xFFEFEBE9)), // Light Taupe / Oat
|
||||||
|
listOf(Color(0xFF90CAF9), Color(0xFFE3F2FD)), // Baby Blue
|
||||||
|
listOf(Color(0xFFF48FB1), Color(0xFFFCE4EC)), // Rosewater
|
||||||
|
|
||||||
|
// Soft Multi-Hue (Two-tone Pastels)
|
||||||
|
listOf(Color(0xFFE1BEE7), Color(0xFFBBDEFB)), // Light Purple to Light Blue (Cotton Candy)
|
||||||
|
listOf(Color(0xFFFFF9C4), Color(0xFFFFCCBC)), // Pale Yellow to Pale Peach (Morning Light)
|
||||||
|
listOf(Color(0xFFB2EBF2), Color(0xFFC8E6C9)), // Pale Cyan to Pale Green (Seafoam)
|
||||||
|
listOf(Color(0xFFFFD54F), Color(0xFFFF8A65)), // Warm Sun to Soft Coral (Soft Sunset)
|
||||||
|
listOf(Color(0xFFD1C4E9), Color(0xFFF8BBD0)), // Periwinkle to Blush Pink (Twilight)
|
||||||
|
listOf(Color(0xFFC5E1A5), Color(0xFFFFF59D)), // Spring Green to Pale Yellow (Meadow)
|
||||||
|
listOf(Color(0xFF80CBC4), Color(0xFF81D4FA)), // Soft Teal to Light Blue (Glacier)
|
||||||
|
listOf(Color(0xFFF8BBD0), Color(0xFFFFE0B2)), // Soft Pink to Cream (Sorbet)
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun gradientForId(id: String): List<Color> =
|
private fun gradientForId(id: String): List<Color> =
|
||||||
gradientPalette[abs(id.hashCode()) % gradientPalette.size]
|
gradientPalette[abs(id.hashCode()) % gradientPalette.size]
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Translation cache - shared between PackCard and PackPreviewDialog, cleared on screen exit
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private class TranslationCache {
|
||||||
|
// Cache key: pack ID, value: Pair(translatedName, translatedDescription)
|
||||||
|
private val cache = mutableMapOf<String, Pair<String, String>>()
|
||||||
|
|
||||||
|
fun get(packId: String): Pair<String, String>? = cache[packId]
|
||||||
|
|
||||||
|
fun put(packId: String, translated: Pair<String, String>) {
|
||||||
|
cache[packId] = translated
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
cache.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun rememberTranslationCache(): TranslationCache {
|
||||||
|
val cache = remember { TranslationCache() }
|
||||||
|
|
||||||
|
// Clear cache when leaving the screen
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
cache.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Translation helper - translates pack name and description from English to device's locale using LibreTranslate
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun rememberTranslatedPackInfo(
|
||||||
|
info: eu.gaudian.translator.model.communication.files_download.VocabCollectionInfo,
|
||||||
|
translationViewModel: TranslationViewModel,
|
||||||
|
cache: TranslationCache
|
||||||
|
): Pair<String, String> {
|
||||||
|
// Check cache first
|
||||||
|
val cached = cache.get(info.id)
|
||||||
|
if (cached != null) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
var translatedName by remember(info.id) { mutableStateOf(info.name) }
|
||||||
|
var translatedDescription by remember(info.id) { mutableStateOf(info.description) }
|
||||||
|
|
||||||
|
// Get device locale using Locale.getDefault() which is more reliable
|
||||||
|
val deviceLocale = remember { java.util.Locale.getDefault().language }
|
||||||
|
|
||||||
|
// Launch translation when language or content changes
|
||||||
|
LaunchedEffect(info.name, info.description, deviceLocale) {
|
||||||
|
try {
|
||||||
|
// Always translate from English to device locale
|
||||||
|
val targetCode = deviceLocale
|
||||||
|
|
||||||
|
var finalName = info.name
|
||||||
|
var finalDescription = info.description
|
||||||
|
|
||||||
|
// Translate name if not empty using LibreTranslate directly
|
||||||
|
if (info.name.isNotBlank()) {
|
||||||
|
val nameResult = translationViewModel.translateWithLibreTranslate(info.name, targetCode, "en")
|
||||||
|
if (nameResult.isSuccess) {
|
||||||
|
finalName = nameResult.getOrNull() ?: info.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate description if not empty using LibreTranslate directly
|
||||||
|
if (info.description.isNotBlank()) {
|
||||||
|
val descResult = translationViewModel.translateWithLibreTranslate(info.description, targetCode, "en")
|
||||||
|
if (descResult.isSuccess) {
|
||||||
|
finalDescription = descResult.getOrNull() ?: info.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
translatedName = finalName
|
||||||
|
translatedDescription = finalDescription
|
||||||
|
|
||||||
|
// Store in cache
|
||||||
|
cache.put(info.id, Pair(finalName, finalDescription))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Translation failed for pack ${info.id}: ${e.message}")
|
||||||
|
// Keep original text on failure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(translatedName, translatedDescription)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Screen
|
// Screen
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -154,6 +304,7 @@ fun ExplorePacksScreen(
|
|||||||
val vocabPacksViewModel: VocabPacksViewModel = hiltViewModel()
|
val vocabPacksViewModel: VocabPacksViewModel = hiltViewModel()
|
||||||
val exportImportViewModel: ExportImportViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val exportImportViewModel: ExportImportViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
val languageViewModel: LanguageViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
|
val translationViewModel: TranslationViewModel = hiltViewModel(viewModelStoreOwner = activity)
|
||||||
|
|
||||||
val packs by vocabPacksViewModel.packs.collectAsState()
|
val packs by vocabPacksViewModel.packs.collectAsState()
|
||||||
val isLoadingManifest by vocabPacksViewModel.isLoadingManifest.collectAsState()
|
val isLoadingManifest by vocabPacksViewModel.isLoadingManifest.collectAsState()
|
||||||
@@ -184,6 +335,9 @@ fun ExplorePacksScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Translation cache - shared between PackCard and PackPreviewDialog
|
||||||
|
val translationCache = rememberTranslationCache()
|
||||||
|
|
||||||
// Auto-open conflict dialog once a queued download finishes
|
// Auto-open conflict dialog once a queued download finishes
|
||||||
LaunchedEffect(packs, pendingImportPackId) {
|
LaunchedEffect(packs, pendingImportPackId) {
|
||||||
val id = pendingImportPackId ?: return@LaunchedEffect
|
val id = pendingImportPackId ?: return@LaunchedEffect
|
||||||
@@ -228,10 +382,10 @@ fun ExplorePacksScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtered + sorted pack list
|
// Filtered + sorted pack list - also search through translated names
|
||||||
val filteredPacks = remember(
|
val filteredPacks = remember(
|
||||||
packs, selectedFilter, searchQuery,
|
packs, selectedFilter, searchQuery,
|
||||||
selectedSourceLanguage, selectedTargetLanguage
|
selectedSourceLanguage, selectedTargetLanguage, translationCache
|
||||||
) {
|
) {
|
||||||
val srcId = selectedSourceLanguage?.nameResId
|
val srcId = selectedSourceLanguage?.nameResId
|
||||||
val tgtId = selectedTargetLanguage?.nameResId
|
val tgtId = selectedTargetLanguage?.nameResId
|
||||||
@@ -248,9 +402,16 @@ fun ExplorePacksScreen(
|
|||||||
(tgtId == null || ids.contains(tgtId))
|
(tgtId == null || ids.contains(tgtId))
|
||||||
}
|
}
|
||||||
|
|
||||||
val matchSearch = searchQuery.isBlank() ||
|
// Search in both original English and translated names
|
||||||
|
val translated = translationCache.get(info.id)
|
||||||
|
val matchSearch = if (searchQuery.isBlank()) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
info.name.contains(searchQuery, ignoreCase = true) ||
|
info.name.contains(searchQuery, ignoreCase = true) ||
|
||||||
info.category.contains(searchQuery, ignoreCase = true)
|
info.category.contains(searchQuery, ignoreCase = true) ||
|
||||||
|
(translated?.first?.contains(searchQuery, ignoreCase = true) == true) ||
|
||||||
|
(translated?.second?.contains(searchQuery, ignoreCase = true) == true)
|
||||||
|
}
|
||||||
|
|
||||||
val matchFilter = when (val code = selectedFilter.cefrCode) {
|
val matchFilter = when (val code = selectedFilter.cefrCode) {
|
||||||
null -> true // All or Newest – handled by sort below
|
null -> true // All or Newest – handled by sort below
|
||||||
@@ -450,6 +611,9 @@ fun ExplorePacksScreen(
|
|||||||
items(filteredPacks, key = { it.info.id }) { packState ->
|
items(filteredPacks, key = { it.info.id }) { packState ->
|
||||||
PackCard(
|
PackCard(
|
||||||
packState = packState,
|
packState = packState,
|
||||||
|
languageViewModel = languageViewModel,
|
||||||
|
translationViewModel = translationViewModel,
|
||||||
|
translationCache = translationCache,
|
||||||
onCardClick = {
|
onCardClick = {
|
||||||
previewPack = packState
|
previewPack = packState
|
||||||
when (packState.downloadState) {
|
when (packState.downloadState) {
|
||||||
@@ -509,6 +673,9 @@ fun ExplorePacksScreen(
|
|||||||
if (preview != null) {
|
if (preview != null) {
|
||||||
PackPreviewDialog(
|
PackPreviewDialog(
|
||||||
packState = preview,
|
packState = preview,
|
||||||
|
languageViewModel = languageViewModel,
|
||||||
|
translationViewModel = translationViewModel,
|
||||||
|
translationCache = translationCache,
|
||||||
onDismiss = { previewPack = null },
|
onDismiss = { previewPack = null },
|
||||||
onGetClick = {
|
onGetClick = {
|
||||||
pendingImportPackId = preview.info.id
|
pendingImportPackId = preview.info.id
|
||||||
@@ -665,12 +832,15 @@ private fun PackConflictStrategyOption(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun PackCard(
|
private fun PackCard(
|
||||||
packState: PackUiState,
|
packState: PackUiState,
|
||||||
|
languageViewModel: LanguageViewModel,
|
||||||
|
translationViewModel: TranslationViewModel,
|
||||||
|
translationCache: TranslationCache,
|
||||||
onCardClick: () -> Unit,
|
onCardClick: () -> Unit,
|
||||||
onGetClick: () -> Unit,
|
onGetClick: () -> Unit,
|
||||||
onAddToLibraryClick: () -> Unit,
|
onAddToLibraryClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
languageViewModel: LanguageViewModel = hiltViewModel(),
|
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val info = packState.info
|
val info = packState.info
|
||||||
val gradient = gradientForId(info.id)
|
val gradient = gradientForId(info.id)
|
||||||
|
|
||||||
@@ -683,6 +853,9 @@ private fun PackCard(
|
|||||||
.joinToString(" ⇆ ")
|
.joinToString(" ⇆ ")
|
||||||
.ifEmpty { info.category }
|
.ifEmpty { info.category }
|
||||||
|
|
||||||
|
// Get translated name and description
|
||||||
|
val (translatedName, translatedDescription) = rememberTranslatedPackInfo(info, translationViewModel, translationCache)
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -788,7 +961,7 @@ private fun PackCard(
|
|||||||
// ── Pack info ─────────────────────────────────────────────────────
|
// ── Pack info ─────────────────────────────────────────────────────
|
||||||
Column(modifier = Modifier.padding(10.dp)) {
|
Column(modifier = Modifier.padding(10.dp)) {
|
||||||
Text(
|
Text(
|
||||||
text = info.name,
|
text = translatedName,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
maxLines = 2
|
maxLines = 2
|
||||||
@@ -893,6 +1066,9 @@ private fun PackCard(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun PackPreviewDialog(
|
private fun PackPreviewDialog(
|
||||||
packState: PackUiState,
|
packState: PackUiState,
|
||||||
|
languageViewModel: LanguageViewModel,
|
||||||
|
translationViewModel: TranslationViewModel,
|
||||||
|
translationCache: TranslationCache,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onGetClick: () -> Unit,
|
onGetClick: () -> Unit,
|
||||||
onAddToLibraryClick: () -> Unit,
|
onAddToLibraryClick: () -> Unit,
|
||||||
@@ -902,9 +1078,12 @@ private fun PackPreviewDialog(
|
|||||||
val gradient = gradientForId(info.id)
|
val gradient = gradientForId(info.id)
|
||||||
var selectedItem by remember { mutableStateOf<VocabularyItem?>(null) }
|
var selectedItem by remember { mutableStateOf<VocabularyItem?>(null) }
|
||||||
|
|
||||||
|
// Get translated name and description
|
||||||
|
val (translatedName, translatedDescription) = rememberTranslatedPackInfo(info, translationViewModel, translationCache)
|
||||||
|
|
||||||
AppDialog(
|
AppDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text(info.name, fontWeight = FontWeight.Bold) },
|
title = { Text(translatedName, fontWeight = FontWeight.Bold) },
|
||||||
) {
|
) {
|
||||||
// ── Gradient banner ───────────────────────────────────────────
|
// ── Gradient banner ───────────────────────────────────────────
|
||||||
Box(
|
Box(
|
||||||
@@ -932,18 +1111,18 @@ private fun PackPreviewDialog(
|
|||||||
Column(modifier = Modifier.align(Alignment.BottomStart).padding(12.dp)) {
|
Column(modifier = Modifier.align(Alignment.BottomStart).padding(12.dp)) {
|
||||||
Text(info.emoji, fontSize = 32.sp)
|
Text(info.emoji, fontSize = 32.sp)
|
||||||
Text(
|
Text(
|
||||||
"${info.category} · ${stringResource(R.string.text_d_cards, info.itemCount)}",
|
"$translatedName · ${stringResource(R.string.text_d_cards, info.itemCount)}",
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
color = Color.White.copy(alpha = 0.85f)
|
color = Color.White.copy(alpha = 0.85f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Description ───────────────────────────────────────────────
|
// ── Description ───────────────────────────────────────────────
|
||||||
if (info.description.isNotBlank()) {
|
if (translatedDescription.isNotBlank()) {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
text = info.description,
|
text = translatedDescription,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -177,6 +177,17 @@ class TranslationViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Direct LibreTranslate without AI - for translating pack names/descriptions
|
||||||
|
suspend fun translateWithLibreTranslate(text: String, targetLanguageCode: String, sourceLanguageCode: String?): Result<String> {
|
||||||
|
// If source and target are the same, return the original text without calling the API
|
||||||
|
val sourceCode = sourceLanguageCode?.lowercase() ?: "en"
|
||||||
|
val targetCode = targetLanguageCode.lowercase()
|
||||||
|
if (sourceCode == targetCode) {
|
||||||
|
return Result.success(text)
|
||||||
|
}
|
||||||
|
return translationService.libreTranslate(text, sourceLanguageCode, targetLanguageCode, 1)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getMultipleTranslations(sentence: String, contextPhrase: String? = null): Result<List<String>> {
|
suspend fun getMultipleTranslations(sentence: String, contextPhrase: String? = null): Result<List<String>> {
|
||||||
return translationService.getMultipleSynonyms(sentence, contextPhrase)
|
return translationService.getMultipleSynonyms(sentence, contextPhrase)
|
||||||
.also { result ->
|
.also { result ->
|
||||||
|
|||||||
Reference in New Issue
Block a user