Add translation strings for download dialogs and notification

This commit is contained in:
Nicholas Wallace
2026-07-20 20:01:02 -07:00
parent 6282fd1f95
commit 58fbfca645
6 changed files with 145 additions and 18 deletions
@@ -53,6 +53,18 @@ class AbsDownloader : Plugin() {
super.handleOnDestroy()
}
@PluginMethod
fun setDownloadNotificationStrings(call: PluginCall) {
DownloadServiceHost.setNotificationStrings(
mainActivity,
call.getString("preparing") ?: "Preparing downloads",
call.getString("downloadingFile") ?: "Downloading {0}",
call.getString("waitingForStorage") ?: "Waiting for available storage",
call.getString("downloads") ?: "Downloads",
call.getString("cancel") ?: "Cancel")
call.resolve()
}
/** Replays restored queue items when the frontend subscribes to download events. */
@PluginMethod(returnType = PluginMethod.RETURN_NONE)
override fun addListener(call: PluginCall) {
@@ -91,7 +103,7 @@ class AbsDownloader : Plugin() {
if (localFolder == null && localFolderId.startsWith("internal-")) {
Log.d(tag, "Creating new App Storage internal LocalFolder $localFolderId")
localFolder = LocalFolder(localFolderId, "Internal App Storage", "", "", "", "", "internal", libraryItem.mediaType)
localFolder = LocalFolder(localFolderId, "Internal App Storage", "", "", "", "internal", libraryItem.mediaType)
DeviceManager.dbManager.saveLocalFolder(localFolder)
}
@@ -1,6 +1,7 @@
package com.audiobookshelf.app.plugins
import android.app.AlertDialog
import android.content.Context
import android.net.Uri
import android.os.Build
import android.util.Log
@@ -68,6 +69,19 @@ class AbsFileSystem : Plugin() {
}
}
@PluginMethod
fun setFolderPickerStrings(call: PluginCall) {
mainActivity.getSharedPreferences(FOLDER_PICKER_PREFERENCES, Context.MODE_PRIVATE)
.edit()
.putString(KEY_WRITE_ACCESS_REQUIRED, call.getString("writeAccessRequired"))
.putString(KEY_ALLOW, call.getString("allow"))
.putString(KEY_CANCEL, call.getString("cancel"))
.putString(KEY_ACCESS_DENIED, call.getString("accessDenied"))
.putString(KEY_PERMISSION_DENIED, call.getString("permissionDenied"))
.apply()
call.resolve()
}
@PluginMethod
fun selectFolder(call: PluginCall) {
val mediaType = call.data.getString("mediaType", "book").toString()
@@ -114,17 +128,14 @@ class AbsFileSystem : Plugin() {
if (requestCode == REQUEST_CODE_SELECT_FOLDER) {
val builder: AlertDialog.Builder = AlertDialog.Builder(mainActivity)
builder.setMessage(
"You have no write access to this storage, thus selecting this folder is useless." +
"\nWould you like to grant access to this folder?"
)
builder.setNegativeButton("Dont Allow") { _, _ ->
builder.setMessage(folderPickerString(KEY_WRITE_ACCESS_REQUIRED, DEFAULT_WRITE_ACCESS_REQUIRED))
builder.setNegativeButton(folderPickerString(KEY_CANCEL, DEFAULT_CANCEL)) { _, _ ->
run {
jsobj.put("error", "User Canceled, Access Denied")
jsobj.put("error", folderPickerString(KEY_ACCESS_DENIED, DEFAULT_ACCESS_DENIED))
call.resolve(jsobj)
}
}
builder.setPositiveButton("Allow.") { _, _ ->
builder.setPositiveButton(folderPickerString(KEY_ALLOW, DEFAULT_ALLOW)) { _, _ ->
mainActivity.storageHelper.requestStorageAccess(
REQUEST_CODE_SDCARD_ACCESS,
initialPath = FileFullPath(mainActivity, storageId, "")
@@ -133,7 +144,7 @@ class AbsFileSystem : Plugin() {
builder.show()
} else {
Log.d(TAG, "STORAGE ACCESS DENIED $requestCode")
jsobj.put("error", "Access Denied")
jsobj.put("error", folderPickerString(KEY_ACCESS_DENIED, DEFAULT_ACCESS_DENIED))
call.resolve(jsobj)
}
}
@@ -141,7 +152,7 @@ class AbsFileSystem : Plugin() {
override fun onStoragePermissionDenied(requestCode: Int) {
Log.d(TAG, "STORAGE PERMISSION DENIED $requestCode")
val jsobj = JSObject()
jsobj.put("error", "Permission Denied")
jsobj.put("error", folderPickerString(KEY_PERMISSION_DENIED, DEFAULT_PERMISSION_DENIED))
call.resolve(jsobj)
}
}
@@ -295,4 +306,23 @@ class AbsFileSystem : Plugin() {
call.resolve(JSObject("{\"success\":false}"))
}
}
private fun folderPickerString(key: String, defaultValue: String): String =
mainActivity.getSharedPreferences(FOLDER_PICKER_PREFERENCES, Context.MODE_PRIVATE)
.getString(key, defaultValue) ?: defaultValue
private companion object {
const val FOLDER_PICKER_PREFERENCES = "folder_picker"
const val KEY_WRITE_ACCESS_REQUIRED = "write_access_required"
const val KEY_ALLOW = "allow"
const val KEY_CANCEL = "cancel"
const val KEY_ACCESS_DENIED = "access_denied"
const val KEY_PERMISSION_DENIED = "permission_denied"
const val DEFAULT_WRITE_ACCESS_REQUIRED =
"You do not have write access to this folder. Would you like to grant access?"
const val DEFAULT_ALLOW = "Allow"
const val DEFAULT_CANCEL = "Cancel"
const val DEFAULT_ACCESS_DENIED = "Access denied"
const val DEFAULT_PERMISSION_DENIED = "Permission denied"
}
}
@@ -17,7 +17,7 @@ class DownloadService : Service() {
override fun onCreate() {
super.onCreate()
createChannel()
startForeground(NOTIFICATION_ID, notification("Preparing downloads"))
startForeground(NOTIFICATION_ID, notification(DownloadServiceHost.notificationStrings(this).preparing))
DownloadServiceHost.attachService(this)
}
@@ -37,7 +37,10 @@ class DownloadService : Service() {
override fun onBind(intent: Intent?): IBinder? = null
fun onPartUpdate(part: DownloadItemPart) {
val text = if (part.waitingForSpace) "Waiting for available storage" else "Downloading ${part.filename}"
val strings = DownloadServiceHost.notificationStrings(this)
val text =
if (part.waitingForSpace) strings.waitingForStorage
else strings.downloadingFile.replace("{0}", part.filename)
val progress = part.progress.coerceIn(0L, 100L).toInt()
val notification = notification(text, progress, part.fileSize > 0L)
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(NOTIFICATION_ID, notification)
@@ -55,18 +58,22 @@ class DownloadService : Service() {
this, 1, Intent(this, DownloadService::class.java).setAction(ACTION_CANCEL), pendingIntentFlags())
return NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.icon)
.setContentTitle("Audiobookshelf downloads")
.setContentTitle(DownloadServiceHost.notificationStrings(this).downloads)
.setContentText(text)
.setOnlyAlertOnce(true)
.setOngoing(true)
.setProgress(100, progress, !determinate)
.addAction(0, "Cancel", cancelIntent)
.addAction(0, DownloadServiceHost.notificationStrings(this).cancel, cancelIntent)
.build()
}
private fun createChannel() {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(NotificationChannel(CHANNEL_ID, "Downloads", NotificationManager.IMPORTANCE_LOW))
manager.createNotificationChannel(
NotificationChannel(
CHANNEL_ID,
DownloadServiceHost.notificationStrings(this).downloads,
NotificationManager.IMPORTANCE_LOW))
}
private fun pendingIntentFlags(): Int = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
@@ -11,6 +11,14 @@ import java.util.Collections
/** Shared process owner used by the foreground service and the Capacitor bridge. */
object DownloadServiceHost {
data class NotificationStrings(
val preparing: String,
val downloadingFile: String,
val waitingForStorage: String,
val downloads: String,
val cancel: String
)
private var manager: DownloadItemManager? = null
private var bridgeEmitter: DownloadItemManager.DownloadEventEmitter = NoopEmitter
private var service: DownloadService? = null
@@ -58,6 +66,36 @@ object DownloadServiceHost {
@Synchronized
fun cancelAll(context: Context) { ensure(context).cancelAll() }
fun setNotificationStrings(
context: Context,
preparing: String,
downloadingFile: String,
waitingForStorage: String,
downloads: String,
cancel: String
) {
context.getSharedPreferences(NOTIFICATION_PREFERENCES, Context.MODE_PRIVATE)
.edit()
.putString(KEY_PREPARING, preparing)
.putString(KEY_DOWNLOADING_FILE, downloadingFile)
.putString(KEY_WAITING_FOR_STORAGE, waitingForStorage)
.putString(KEY_DOWNLOADS, downloads)
.putString(KEY_CANCEL, cancel)
.apply()
}
fun notificationStrings(context: Context): NotificationStrings {
val preferences = context.getSharedPreferences(NOTIFICATION_PREFERENCES, Context.MODE_PRIVATE)
return NotificationStrings(
preferences.getString(KEY_PREPARING, DEFAULT_PREPARING) ?: DEFAULT_PREPARING,
preferences.getString(KEY_DOWNLOADING_FILE, DEFAULT_DOWNLOADING_FILE)
?: DEFAULT_DOWNLOADING_FILE,
preferences.getString(KEY_WAITING_FOR_STORAGE, DEFAULT_WAITING_FOR_STORAGE)
?: DEFAULT_WAITING_FOR_STORAGE,
preferences.getString(KEY_DOWNLOADS, DEFAULT_DOWNLOADS) ?: DEFAULT_DOWNLOADS,
preferences.getString(KEY_CANCEL, DEFAULT_CANCEL) ?: DEFAULT_CANCEL)
}
@Synchronized
fun attachService(downloadService: DownloadService) {
service = downloadService
@@ -94,4 +132,16 @@ object DownloadServiceHost {
override fun onDownloadItemComplete(jsobj: JSObject) = Unit
override fun onQueueChanged(hasWork: Boolean) = Unit
}
private const val NOTIFICATION_PREFERENCES = "download_notifications"
private const val KEY_PREPARING = "preparing"
private const val KEY_DOWNLOADING_FILE = "downloading_file"
private const val KEY_WAITING_FOR_STORAGE = "waiting_for_storage"
private const val KEY_DOWNLOADS = "downloads"
private const val KEY_CANCEL = "cancel"
private const val DEFAULT_PREPARING = "Preparing downloads"
private const val DEFAULT_DOWNLOADING_FILE = "Downloading {0}"
private const val DEFAULT_WAITING_FOR_STORAGE = "Waiting for available storage"
private const val DEFAULT_DOWNLOADS = "Downloads"
private const val DEFAULT_CANCEL = "Cancel"
}