chore(translation): new strings

This commit is contained in:
rhunk 2024-04-06 15:34:18 +02:00
parent b610825a22
commit 1d0456e8a0
13 changed files with 238 additions and 154 deletions

View File

@ -78,6 +78,8 @@ class Routes(
lateinit var routeInfo: RouteInfo
lateinit var routes: Routes
val translation by lazy { context.translation.getCategory("manager.sections.${routeInfo.key.substringBefore("/")}")}
private fun replaceArguments(id: String, args: Map<String, String>) = args.takeIf { it.isNotEmpty() }?.let {
args.entries.fold(id) { acc, (key, value) ->
acc.replace("{$key}", value)

View File

@ -126,7 +126,7 @@ class LoggerHistoryRoot : Routes.Route() {
LaunchedEffect(Unit, message) {
runCatching {
decodeMessage(message) { senderId, contentType, messageReader, attachments ->
val senderUsername = senderId?.let { context.modDatabase.getFriendInfo(it)?.mutableUsername } ?: "unknown sender"
val senderUsername = senderId?.let { context.modDatabase.getFriendInfo(it)?.mutableUsername } ?: translation["unknown_sender"]
@Composable
fun ContentHeader() {
@ -134,7 +134,7 @@ class LoggerHistoryRoot : Routes.Route() {
}
if (contentType == ContentType.CHAT) {
val content = messageReader.getString(2, 1) ?: "[empty chat message]"
val content = messageReader.getString(2, 1) ?: "[${translation["empty_message"]}]"
contentView = {
Column {
Text(content, modifier = Modifier
@ -166,7 +166,7 @@ class LoggerHistoryRoot : Routes.Route() {
downloadAttachment(message.timestamp, attachment)
}.onFailure {
context.log.error("Failed to download attachment", it)
context.shortToast("Failed to download attachment")
context.shortToast(translation["download_attachment_failed_toast"])
}
}
}) {
@ -175,7 +175,7 @@ class LoggerHistoryRoot : Routes.Route() {
contentDescription = "Download",
modifier = Modifier.padding(end = 4.dp)
)
Text("Attachment ${index + 1}")
Text(translation.format("chat_attachment", "index" to (index + 1).toString()))
}
}
}
@ -186,7 +186,7 @@ class LoggerHistoryRoot : Routes.Route() {
}.onFailure {
context.log.error("Failed to parse message", it)
contentView = {
Text("[Failed to parse message]")
Text("[${translation["message_parse_failed"]}]")
}
}
}
@ -212,8 +212,10 @@ class LoggerHistoryRoot : Routes.Route() {
) {
fun formatConversationId(conversationId: String?): String? {
if (conversationId == null) return null
return context.modDatabase.getGroupInfo(conversationId)?.name?.let { "Group $it" } ?: context.modDatabase.findFriend(conversationId)?.let {
"Friend " + (it.displayName?.let { name -> "$name (${it.mutableUsername})" } ?: it.mutableUsername)
return context.modDatabase.getGroupInfo(conversationId)?.name?.let {
translation.format("list_group_format", "name" to it)
} ?: context.modDatabase.findFriend(conversationId)?.let {
translation.format("list_friend_format", "name" to (it.displayName?.let { name -> "$name (${it.mutableUsername})" } ?: it.mutableUsername))
} ?: conversationId
}
@ -257,7 +259,7 @@ class LoggerHistoryRoot : Routes.Route() {
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(2.dp),
) {
Text("Reverse order")
Text(translation["reverse_order_checkbox"])
Checkbox(checked = reverseOrder, onCheckedChange = {
reverseOrder = it
})
@ -275,7 +277,7 @@ class LoggerHistoryRoot : Routes.Route() {
item {
if (selectedConversation != null) {
if (hasReachedEnd) {
Text("No more messages", modifier = Modifier
Text(translation["no_more_messages"], modifier = Modifier
.padding(8.dp)
.fillMaxWidth(), textAlign = TextAlign.Center)
} else {

View File

@ -99,7 +99,7 @@ class TasksRoot : Routes.Route() {
}
runCatching {
context.shortToast("Merging ${filesToMerge.size} files")
context.shortToast(translation.format("merge_files_toast", "count" to filesToMerge.size.toString()))
FFMpegProcessor.newFFMpegProcessor(context, pendingTask).execute(
FFMpegProcessor.Request(FFMpegProcessor.Action.MERGE_MEDIA, filesToMerge.map { it.absolutePath }, mergedFile)
)
@ -177,15 +177,15 @@ class TasksRoot : Routes.Route() {
onDismissRequest = { showConfirmDialog = false },
title = {
if (taskSelection.isNotEmpty()) {
Text("Remove ${taskSelection.size} tasks?")
Text(translation.format("remove_selected_tasks_confirm", "count" to taskSelection.size.toString()))
} else {
Text("Remove all tasks?")
Text(translation["remove_all_tasks_confirm"])
}
},
text = {
Column {
if (taskSelection.isNotEmpty()) {
Text("Are you sure you want to remove selected tasks?")
Text(translation["remove_selected_tasks_title"])
Row (
modifier = Modifier.padding(top = 10.dp).fillMaxWidth().clickable {
alsoDeleteFiles = !alsoDeleteFiles
@ -196,10 +196,10 @@ class TasksRoot : Routes.Route() {
Checkbox(checked = alsoDeleteFiles, onCheckedChange = {
alsoDeleteFiles = it
})
Text("Also delete files")
Text(translation["delete_files_option"])
}
} else {
Text("Are you sure you want to remove all tasks?")
Text(translation["remove_all_tasks_title"])
}
}
},
@ -233,7 +233,7 @@ class TasksRoot : Routes.Route() {
}
}
) {
Text("Yes")
Text(context.translation["button.positive"])
}
},
dismissButton = {
@ -242,7 +242,7 @@ class TasksRoot : Routes.Route() {
showConfirmDialog = false
}
) {
Text("No")
Text(context.translation["button.negative"])
}
}
)
@ -429,7 +429,7 @@ class TasksRoot : Routes.Route() {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
context.translation["manager.sections.tasks.no_tasks"].let {
translation["no_tasks"].let {
Icon(Icons.Filled.CheckCircle, contentDescription = it, tint = MaterialTheme.colorScheme.primary)
Text(it, style = MaterialTheme.typography.bodyLarge)
}

View File

@ -17,7 +17,6 @@ import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -30,11 +29,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.Lifecycle
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -53,7 +54,6 @@ class FeaturesRoot : Routes.Route() {
}
private var activityLauncherHelper: ActivityLauncherHelper? = null
private lateinit var rememberScaffoldState: BottomSheetScaffoldState
private val allContainers by lazy {
val containers = mutableMapOf<String, PropertyPair<*>>()
@ -444,48 +444,62 @@ class FeaturesRoot : Routes.Route() {
var showResetConfirmationDialog by remember { mutableStateOf(false) }
if (showResetConfirmationDialog) {
Dialog(onDismissRequest = { showResetConfirmationDialog = false }) {
alertDialogs.ConfirmDialog(
title = "Reset config",
message = "Are you sure you want to reset the config?",
onConfirm = {
AlertDialog(
title = { Text(text = context.translation["manager.dialogs.reset_config.title"]) },
text = { Text(text = context.translation["manager.dialogs.reset_config.content"]) },
onDismissRequest = { showResetConfirmationDialog = false },
confirmButton = {
Button(
onClick = {
context.config.reset()
context.shortToast("Config successfully reset!")
},
onDismiss = { showResetConfirmationDialog = false }
)
context.shortToast(context.translation["manager.dialogs.reset_config.success_toast"])
showResetConfirmationDialog = false
}
) {
Text(text = context.translation["button.positive"])
}
},
dismissButton = {
Button(
onClick = {
showResetConfirmationDialog = false
}
) {
Text(text = context.translation["button.negative"])
}
}
)
}
val actions = remember {
mapOf(
"Export" to {
translation["export_option"] to {
activityLauncher {
saveFile("config.json", "application/json") { uri ->
context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use {
context.config.writeConfig()
context.config.exportToString().byteInputStream().copyTo(it)
context.shortToast("Config exported successfully!")
context.shortToast(translation["config_export_success_toast"])
}
}
}
},
"Import" to {
translation["import_option"] to {
activityLauncher {
openFile("application/json") { uri ->
context.androidContext.contentResolver.openInputStream(Uri.parse(uri))?.use {
runCatching {
context.config.loadFromString(it.readBytes().toString(Charsets.UTF_8))
}.onFailure {
context.longToast("Failed to import config ${it.message}")
context.longToast(translation.format("config_import_failure_toast", "error" to it.message.toString()))
return@use
}
context.shortToast("Config successfully loaded!")
context.shortToast(translation["config_import_success_toast"])
}
}
}
},
"Reset" to { showResetConfirmationDialog = true }
translation["reset_option"] to { showResetConfirmationDialog = true }
)
}
@ -519,9 +533,7 @@ class FeaturesRoot : Routes.Route() {
private fun PropertiesView(
properties: List<PropertyPair<*>>
) {
rememberScaffoldState = rememberBottomSheetScaffoldState()
Scaffold(
snackbarHost = { SnackbarHost(rememberScaffoldState.snackbarHostState) },
modifier = Modifier.fillMaxSize(),
content = { innerPadding ->
LazyColumn(
@ -541,23 +553,23 @@ class FeaturesRoot : Routes.Route() {
}
override val floatingActionButton: @Composable () -> Unit = {
val scope = rememberCoroutineScope()
FloatingActionButton(
onClick = {
fun saveConfig() {
context.coroutineScope.launch(Dispatchers.IO) {
context.config.writeConfig()
scope.launch {
rememberScaffoldState.snackbarHostState.showSnackbar("Saved")
context.log.verbose("saved config!")
}
}
OnLifecycleEvent { _, event ->
if (event == Lifecycle.Event.ON_PAUSE || event == Lifecycle.Event.ON_STOP) {
saveConfig()
}
}
DisposableEffect(Unit) {
onDispose {
saveConfig()
}
},
modifier = Modifier.padding(10.dp),
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary,
shape = RoundedCornerShape(16.dp),
) {
Icon(
imageVector = Icons.Rounded.Save,
contentDescription = null
)
}
}
@ -566,10 +578,8 @@ class FeaturesRoot : Routes.Route() {
private fun Container(
configContainer: ConfigContainer
) {
val properties = remember {
PropertiesView(remember {
configContainer.properties.map { PropertyPair(it.key, it.value) }
}
PropertiesView(properties)
})
}
}

View File

@ -68,28 +68,27 @@ class HomeLogs : Routes.Route() {
navigate()
showDropDown = false
}, text = {
Text(
text = context.translation["manager.sections.home.logs.clear_logs_button"]
)
Text(translation["clear_logs_button"])
})
DropdownMenuItem(onClick = {
activityLauncherHelper.saveFile("snapenhance-logs-${System.currentTimeMillis()}.zip", "application/zip") { uri ->
context.coroutineScope.launch {
context.shortToast(translation["saving_logs_toast"])
context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use {
runCatching {
context.log.exportLogsToZip(it)
context.longToast("Saved logs to $uri")
context.longToast(translation["saved_logs_success_toast"])
}.onFailure {
context.longToast("Failed to save logs to $uri!")
context.longToast(translation["saved_logs_failure_toast"])
context.log.error("Failed to save logs to $uri!", it)
}
}
}
}
showDropDown = false
}, text = {
Text(
text = context.translation["manager.sections.home.logs.export_logs_button"]
)
Text(translation["export_logs_button"])
})
}
}
@ -141,7 +140,7 @@ class HomeLogs : Routes.Route() {
item {
if (lineCount == 0 && logReader != null) {
Text(
text = "No logs found!",
text = translation["no_logs_hint"],
modifier = Modifier.padding(16.dp),
fontSize = 12.sp,
fontWeight = FontWeight.Light

View File

@ -198,9 +198,9 @@ class HomeRoot : Routes.Route() {
}
)
}
Spacer(modifier = Modifier.height(20.dp))
if (latestUpdate != null) {
Spacer(modifier = Modifier.height(20.dp))
OutlinedCard(
modifier = Modifier
.padding(all = cardMargin)
@ -218,13 +218,13 @@ class HomeRoot : Routes.Route() {
) {
Column {
Text(
text = "SnapEnhance Update",
text = translation["update_title"],
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
)
Text(
fontSize = 12.sp,
text = "Version ${latestUpdate?.versionName} is available!",
text = translation.format("update_content", "version" to (latestUpdate?.versionName ?: "unknown")),
lineHeight = 20.sp
)
}
@ -235,7 +235,7 @@ class HomeRoot : Routes.Route() {
}
)
}, modifier = Modifier.height(40.dp)) {
Text(text = "Download")
Text(text = translation["update_button"])
}
}
}

View File

@ -84,7 +84,7 @@ class HomeSettings : Routes.Route() {
if (requireConfirmation && confirmationDialog) {
Dialog(onDismissRequest = { confirmationDialog = false }) {
dialogs.ConfirmDialog(title = "Are you sure?", onConfirm = {
dialogs.ConfirmDialog(title = context.translation["manager.dialogs.action_confirm.title"], onConfirm = {
action()
confirmationDialog = false
}, onDismiss = {
@ -148,7 +148,7 @@ class HomeSettings : Routes.Route() {
.fillMaxSize()
.verticalScroll(ScrollState(0))
) {
RowTitle(title = "Actions")
RowTitle(title = translation["actions_title"])
EnumAction.entries.forEach { enumAction ->
RowAction(key = enumAction.key) {
launchActionIntent(enumAction)
@ -160,7 +160,7 @@ class HomeSettings : Routes.Route() {
RowAction(key = "change_language") {
context.checkForRequirements(Requirements.LANGUAGE)
}
RowTitle(title = "Message Logger")
RowTitle(title = translation["message_logger_title"])
ShiftedRow {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
@ -184,8 +184,11 @@ class HomeSettings : Routes.Route() {
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(text = "$storedMessagesCount messages")
Text(text = "$storedStoriesCount stories")
Text(
translation.format("message_logger_summary",
"messageCount" to storedMessagesCount.toString(),
"storyCount" to storedStoriesCount.toString()
), maxLines = 2)
}
Button(onClick = {
runCatching {
@ -201,7 +204,7 @@ class HomeSettings : Routes.Route() {
context.longToast("Failed to export database! ${it.localizedMessage}")
}
}) {
Text(text = "Export")
Text(text = translation["export_button"])
}
Button(onClick = {
runCatching {
@ -212,10 +215,10 @@ class HomeSettings : Routes.Route() {
context.log.error("Failed to clear messages", it)
context.longToast("Failed to clear messages! ${it.localizedMessage}")
}.onSuccess {
context.shortToast("Done!")
context.shortToast(translation["success_toast"])
}
}) {
Text(text = "Clear")
Text(text = translation["clear_button"])
}
}
OutlinedButton(
@ -226,12 +229,12 @@ class HomeSettings : Routes.Route() {
routes.loggerHistory.navigate()
}
) {
Text(text = "View Message History")
Text(translation["view_logger_history_button"])
}
}
}
RowTitle(title = "Debug")
RowTitle(title = translation["debug_title"])
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
@ -275,10 +278,10 @@ class HomeSettings : Routes.Route() {
context.log.error("Failed to clear file", it)
context.longToast("Failed to clear file! ${it.localizedMessage}")
}.onSuccess {
context.shortToast("Done!")
context.shortToast(translation["success_toast"])
}
}) {
Text(text = "Clear File")
Text(translation["clear_button"])
}
}
ShiftedRow {

View File

@ -148,7 +148,7 @@ class LoggedStories : Routes.Route() {
}
}
}) {
Text(text = "Open")
Text(text = context.translation["button.open"])
}
Button(onClick = {
@ -160,7 +160,7 @@ class LoggedStories : Routes.Route() {
)
)
}) {
Text(text = "Download")
Text(text = context.translation["button.download"])
}
if (remember {
@ -180,7 +180,7 @@ class LoggedStories : Routes.Route() {
)
)
}) {
Text(text = "Save from cache")
Text(text = translation["save_from_cache_button"])
}
}
}
@ -190,7 +190,7 @@ class LoggedStories : Routes.Route() {
}
if (stories.isEmpty()) {
Text(text = "No stories found", Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
Text(text = translation["no_stories"], Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
}
LazyVerticalGrid(
@ -212,7 +212,7 @@ class LoggedStories : Routes.Route() {
verticalArrangement = Arrangement.Center,
) {
if (hasFailed) {
Text(text = "Failed to load", Modifier.padding(8.dp), fontSize = 10.sp)
Text(text = translation["story_failed_to_load"], Modifier.padding(8.dp), fontSize = 10.sp)
} else {
Image(
painter = rememberAsyncImagePainter(

View File

@ -29,7 +29,6 @@ import kotlin.io.encoding.ExperimentalEncodingApi
class ManageScope: Routes.Route() {
private val dialogs by lazy { AlertDialogs(context.translation) }
private val translation by lazy { context.translation.getCategory("manager.sections.social") }
private fun deleteScope(scope: SocialScope, id: String, coroutineScope: CoroutineScope) {
when (scope) {
@ -56,7 +55,7 @@ class ManageScope: Routes.Route() {
deleteConfirmDialog = false
}) {
remember { AlertDialogs(context.translation) }.ConfirmDialog(
title = "Are you sure you want to delete this ${scope.key.lowercase()}?",
title = translation.format("delete_scope_confirm_dialog_title", "scope" to context.translation["scopes.${scope.key}"]),
onDismiss = { deleteConfirmDialog = false },
onConfirm = {
deleteScope(scope, id, coroutineScope); deleteConfirmDialog = false
@ -94,7 +93,6 @@ class ManageScope: Routes.Route() {
SectionTitle(translation["rules_title"])
ContentCard {
//manager anti features etc
MessagingRuleType.entries.forEach { ruleType ->
var ruleEnabled by remember {
mutableStateOf(rules.any { it.key == ruleType.key })
@ -110,14 +108,17 @@ class ManageScope: Routes.Route() {
text = if (ruleType.listMode && ruleState != null) {
context.translation["rules.properties.${ruleType.key}.options.${ruleState.key}"]
} else context.translation["rules.properties.${ruleType.key}.name"],
modifier = Modifier.weight(1f).padding(start = 5.dp, end = 5.dp)
modifier = Modifier
.weight(1f)
.padding(start = 5.dp, end = 5.dp)
)
Switch(checked = ruleEnabled,
enabled = if (ruleType.listMode) ruleState != null else true,
onCheckedChange = {
context.modDatabase.setRule(id, ruleType.key, it)
ruleEnabled = it
})
}
)
}
}
}
@ -155,8 +156,7 @@ class ManageScope: Routes.Route() {
)
}
//need to display all units?
private fun computeStreakETA(timestamp: Long): String {
private fun computeStreakETA(timestamp: Long): String? {
val now = System.currentTimeMillis()
val stringBuilder = StringBuilder()
val diff = timestamp - now
@ -180,7 +180,7 @@ class ManageScope: Routes.Route() {
stringBuilder.append("$seconds seconds ")
return stringBuilder.toString()
}
return "Expired"
return null
}
@OptIn(ExperimentalEncodingApi::class)
@ -234,7 +234,7 @@ class ManageScope: Routes.Route() {
put("id", id)
}
}) {
Text("Show Logged Stories")
Text(translation["logged_stories_button"])
}
}
@ -259,10 +259,11 @@ class ManageScope: Routes.Route() {
), maxLines = 1
)
Text(
text = translation.format(
text = computeStreakETA(streaks.expirationTimestamp)?.let { translation.format(
"streaks_expiration_text",
"eta" to computeStreakETA(streaks.expirationTimestamp)
), maxLines = 1
"eta" to it
) } ?: translation["streaks_expiration_text_expired"],
maxLines = 1
)
}
Row(
@ -282,7 +283,6 @@ class ManageScope: Routes.Route() {
}
}
Spacer(modifier = Modifier.height(16.dp))
// e2ee section
if (context.config.root.experimental.e2eEncryption.globalState == true) {
SectionTitle(translation["e2ee_title"])
@ -362,7 +362,6 @@ class ManageScope: Routes.Route() {
return
}
Column(
modifier = Modifier
.padding(10.dp)

View File

@ -47,7 +47,6 @@ class MessagingPreview: Routes.Route() {
private lateinit var messagingBridge: MessagingBridge
private lateinit var previewScrollState: LazyListState
private val myUserId by lazy { messagingBridge.myUserId }
private val contentTypeTranslation by lazy { context.translation.getCategory("content_type") }
private var messages = mutableStateListOf<Message>()
@ -117,7 +116,7 @@ class MessagingPreview: Routes.Route() {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(5.dp)
) {
Text("Choose content types to process")
Text(context.translation["manager.dialogs.messaging_action.title"])
Spacer(modifier = Modifier.height(5.dp))
availableTypes.forEach { contentType ->
Row(
@ -135,7 +134,7 @@ class MessagingPreview: Routes.Route() {
enabled = !selectAllState,
onCheckedChange = { toggleContentType(contentType) }
)
Text(text = contentType.toString())
Text(text = contentTypeTranslation[contentType.name])
}
}
Row(
@ -148,7 +147,7 @@ class MessagingPreview: Routes.Route() {
Switch(checked = selectAllState, onCheckedChange = {
selectAllState = it
})
Text(text = "Select all")
Text(text = context.translation["manager.dialogs.messaging_action.select_all_button"])
}
Row(
modifier = Modifier
@ -156,13 +155,13 @@ class MessagingPreview: Routes.Route() {
horizontalArrangement = Arrangement.SpaceEvenly,
) {
Button(onClick = { onDismiss() }) {
Text("Cancel")
Text(context.translation["button.cancel"])
}
Button(onClick = {
onChoose(if (selectAllState) ContentType.entries.toTypedArray()
else selectedTypes.toTypedArray())
}) {
Text("Continue")
Text(context.translation["button.ok"])
}
}
}
@ -286,28 +285,28 @@ class MessagingPreview: Routes.Route() {
shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(50.dp))
) {
DropdownMenu(
expanded = taskSelectionDropdown, onDismissRequest = { taskSelectionDropdown = false }
expanded = taskSelectionDropdown && messages.isNotEmpty(), onDismissRequest = { taskSelectionDropdown = false }
) {
val hasSelection = selectedMessages.isNotEmpty()
ActionButton(text = if (hasSelection) "Save selection" else "Save all", icon = Icons.Rounded.BookmarkAdded) {
ActionButton(text = translation[if (hasSelection) "save_selection_option" else "save_all_option"], icon = Icons.Rounded.BookmarkAdded) {
launchMessagingTask(MessagingTaskType.SAVE)
if (hasSelection) runCurrentTask()
else selectConstraintsDialog = true
}
ActionButton(text = if (hasSelection) "Unsave selection" else "Unsave all", icon = Icons.Rounded.BookmarkBorder) {
ActionButton(text = translation[if (hasSelection) "unsave_selection_option" else "unsave_all_option"], icon = Icons.Rounded.BookmarkBorder) {
launchMessagingTask(MessagingTaskType.UNSAVE)
if (hasSelection) runCurrentTask()
else selectConstraintsDialog = true
}
ActionButton(text = if (hasSelection) "Mark selected Snap as seen" else "Mark all Snaps as seen", icon = Icons.Rounded.RemoveRedEye) {
ActionButton(text = translation[if (hasSelection) "mark_selection_as_seen_option" else "mark_all_as_seen_option"], icon = Icons.Rounded.RemoveRedEye) {
launchMessagingTask(MessagingTaskType.READ, listOf(
MessagingConstraints.NO_USER_ID(myUserId),
MessagingConstraints.NO_USER_ID(messagingBridge.myUserId),
MessagingConstraints.CONTENT_TYPE(arrayOf(ContentType.SNAP))
))
runCurrentTask()
}
ActionButton(text = if (hasSelection) "Delete selected" else "Delete all", icon = Icons.Rounded.DeleteForever) {
launchMessagingTask(MessagingTaskType.DELETE, listOf(MessagingConstraints.USER_ID(myUserId))) { message ->
ActionButton(text = translation[if (hasSelection) "delete_selection_option" else "delete_all_option"], icon = Icons.Rounded.DeleteForever) {
launchMessagingTask(MessagingTaskType.DELETE, listOf(MessagingConstraints.USER_ID(messagingBridge.myUserId))) { message ->
coroutineScope.launch {
message.contentType = ContentType.STATUS.id
}
@ -377,7 +376,7 @@ class MessagingPreview: Routes.Route() {
.padding(40.dp),
horizontalArrangement = Arrangement.Center
) {
Text("No messages")
Text(translation["no_message_hint"])
}
}
Spacer(modifier = Modifier.height(20.dp))
@ -428,12 +427,7 @@ class MessagingPreview: Routes.Route() {
conversationId!!,
20,
lastMessageId
)?.reversed()
if (queriedMessages == null) {
context.shortToast("Failed to fetch messages")
return@cs
}
)?.reversed() ?: throw IllegalStateException("Failed to fetch messages. Bridge returned null")
withContext(Dispatchers.Main) {
messages.addAll(queriedMessages)
@ -441,7 +435,7 @@ class MessagingPreview: Routes.Route() {
}
}.onFailure {
context.log.error("Failed to fetch messages", it)
context.shortToast("Failed to fetch messages: ${it.message}")
context.shortToast(translation["message_fetch_failed"])
}
}
}
@ -451,11 +445,7 @@ class MessagingPreview: Routes.Route() {
runCatching {
messagingBridge = context.bridgeService!!.messagingBridge!!
conversationId = if (scope == SocialScope.FRIEND) messagingBridge.getOneToOneConversationId(scopeId) else scopeId
if (conversationId == null) {
context.longToast("Failed to fetch conversation id")
return
}
conversationId = (if (scope == SocialScope.FRIEND) messagingBridge.getOneToOneConversationId(scopeId) else scopeId) ?: throw IllegalStateException("Failed to get conversation id")
if (runCatching { !messagingBridge.isSessionStarted }.getOrDefault(true)) {
context.androidContext.packageManager.getLaunchIntentForPackage(
Constants.SNAPCHAT_PACKAGE_NAME
@ -474,7 +464,7 @@ class MessagingPreview: Routes.Route() {
}
fetchNewMessages()
}.onFailure {
context.longToast("Failed to initialize messaging bridge")
context.longToast(translation["bridge_init_failed"])
context.log.error("Failed to initialize messaging bridge", it)
}
}
@ -511,7 +501,7 @@ class MessagingPreview: Routes.Route() {
.fillMaxSize()
) {
if (hasBridgeError) {
Text("Failed to connect to Snapchat through bridge service")
Text(translation["bridge_connection_failed"])
}
if (!isBridgeConnected && !hasBridgeError) {

View File

@ -164,8 +164,8 @@ class SocialRoot : Routes.Route() {
else MaterialTheme.colorScheme.primary
)
Text(
text = context.translation.format(
"manager.sections.social.streaks_expiration_short",
text = translation.format(
"streaks_expiration_short",
"hours" to (((streaks.expirationTimestamp - System.currentTimeMillis()) / 3600000).toInt().takeIf { it > 0 } ?: 0)
.toString()
),

View File

@ -44,9 +44,9 @@ class AlertDialogs(
@Composable
fun DefaultDialogCard(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
Card(
shape = MaterialTheme.shapes.medium,
shape = MaterialTheme.shapes.large,
modifier = Modifier
.padding(10.dp, 5.dp, 10.dp, 10.dp)
.padding(16.dp)
.then(modifier),
) {
Column(

View File

@ -21,6 +21,11 @@
}
},
"scopes": {
"friend": "Friend",
"group": "Group"
},
"manager": {
"routes": {
"tasks": "Tasks",
@ -37,27 +42,92 @@
},
"sections": {
"home": {
"logs": {
"update_title": "SnapEnhance Update",
"update_content": "Version {version} is available!",
"update_button": "Download"
},
"home_logs": {
"no_logs_hint": "No logs available",
"clear_logs_button": "Clear Logs",
"export_logs_button": "Export Logs"
}
"export_logs_button": "Export Logs",
"saving_logs_toast": "Saving logs, this may take a while ...",
"saved_logs_success_toast": "Logs saved successfully",
"saved_logs_failure_toast": "Failed to save logs"
},
"home_settings": {
"actions_title": "Actions",
"message_logger_title": "Message Logger",
"debug_title": "Debug",
"success_toast": "Done!",
"message_logger_summary": "{messageCount} messages\n{storyCount} stories",
"export_button": "Export",
"clear_button": "Clear",
"view_logger_history_button": "View Logger History"
},
"tasks": {
"no_tasks": "No tasks"
"no_tasks": "No tasks",
"merge_files_toast": "Merging {count} files",
"remove_selected_tasks_title": "Are you sure you want to remove selected tasks?",
"remove_all_tasks_title": "Are you sure you want to remove all tasks?",
"delete_files_option": "Also delete files",
"remove_selected_tasks_confirm": "Remove {count} tasks?",
"remove_all_tasks_confirm": "Remove all tasks?"
},
"features": {
"disabled": "Disabled"
"disabled": "Disabled",
"export_option": "Export",
"import_option": "Import",
"reset_option": "Reset",
"config_export_success_toast": "Config exported successfully",
"config_import_success_toast": "Config imported successfully",
"config_import_failure_toast": "Failed to import config {error}",
"saved_config_snackbar": "Config saved"
},
"social": {
"streaks_expiration_short": "{hours}h"
},
"manage_scope": {
"logged_stories_button": "Show Logged Stories",
"e2ee_title": "End-to-End Encryption",
"rules_title": "Rules",
"participants_text": "{count} participants",
"not_found": "Not found",
"streaks_title": "Streaks",
"streaks_length_text": "Length: {length}",
"streaks_expiration_short": "{hours}h",
"streaks_expiration_text": "Expires in {eta}",
"reminder_button": "Set Reminder"
"streaks_expiration_text_expired": "Expired",
"reminder_button": "Set Reminder",
"delete_scope_confirm_dialog_title": "Are you sure you want to delete a {scope}?"
},
"logged_stories": {
"story_failed_to_load": "Failed to load",
"no_stories": "No stories found",
"save_from_cache_button": "Save from Cache"
},
"messaging_preview": {
"bridge_connection_failed": "Failed to connect to Snapchat through bridge service",
"bridge_init_failed": "Failed to initialize messaging bridge",
"message_fetch_failed": "Failed to fetch messages",
"no_message_hint": "No message",
"save_selection_option": "Save Selection",
"save_all_option": "Save All",
"unsave_selection_option": "Unsave Selection",
"unsave_all_option": "Unsave All",
"mark_selection_as_seen_option": "Mark selected Snap as seen",
"mark_all_as_seen_option": "Mark all Snaps as seen",
"delete_selection_option": "Delete Selection",
"delete_all_option": "Delete All"
},
"logger_history": {
"list_friend_format": "Friend {name}",
"list_group_format": "Group {name}",
"no_more_messages": "No more messages",
"reverse_order_checkbox": "Reverse Order",
"chat_attachment": "Attachment {index}",
"empty_message": "Empty Chat Message",
"message_parse_failed": "Failed to parse message",
"unknown_sender": "Unknown Sender",
"download_attachment_failed_toast": "Failed to download attachment"
}
},
"dialogs": {
@ -71,6 +141,15 @@
"scripting_warning": {
"title": "Warning",
"content": "SnapEnhance includes a scripting tool, allowing the execution of user-defined code on your device. Use extreme caution and only install modules from known, reliable sources. Unauthorized or unverified modules may pose security risks to your system."
},
"reset_config": {
"title": "Reset config",
"content": "Are you sure you want to reset the config?",
"success_toast": "Config reset successfully"
},
"messaging_action": {
"title": "Choose content types to process",
"select_all_button": "Select All"
}
}
},