Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9440782027 | ||
|
|
f19ec05780 | ||
|
|
d95b75e317 | ||
|
|
a6dec31461 | ||
|
|
b12427daa6 | ||
|
|
892686b213 | ||
|
|
81e71cd7a2 | ||
|
|
38a5e3637d | ||
|
|
eb16e769fe | ||
|
|
9417f0dd3d | ||
|
|
68d82f7a3a | ||
|
|
b8fa041dcb | ||
|
|
1df07dc58d | ||
|
|
e63358f776 | ||
|
|
9518189898 | ||
|
|
70a9cab6f2 | ||
|
|
495ec80ebd | ||
|
|
34454fee10 | ||
|
|
fbac4be4a5 | ||
|
|
a150e6109e | ||
|
|
f392075836 | ||
|
|
5d9dd394ea | ||
|
|
162c189d4f | ||
|
|
01902f8a79 | ||
|
|
399a01a937 | ||
|
|
dcaa19071f | ||
|
|
a6280805e6 | ||
|
|
3d8843109a | ||
|
|
16eaccc8e3 | ||
|
|
9df996a507 | ||
|
|
fa0b9ed6ab | ||
|
|
9b8536a581 | ||
|
|
9158f4f8f1 | ||
|
|
072f19e157 | ||
|
|
44198fa17a | ||
|
|
83ccb398c5 | ||
|
|
824e335dec | ||
|
|
e28e9d71ae | ||
|
|
46acc76f07 | ||
|
|
356fa4eb7c | ||
|
|
09a6c631cb | ||
|
|
cf19816e06 | ||
|
|
2c4b9f5a50 | ||
|
|
9e29d45fc3 | ||
|
|
b4e350a308 | ||
|
|
8274072b6a | ||
|
|
1ddd146558 | ||
|
|
2d76fc6e8c | ||
|
|
e19e541a70 | ||
|
|
3a56c82501 | ||
|
|
2f0e385ca2 | ||
|
|
9241a4729c | ||
|
|
ea331bd4ef | ||
|
|
c2b598d98e | ||
|
|
2fde05412c | ||
|
|
d8436d3273 | ||
|
|
e662dae396 | ||
|
|
e74c5d8609 | ||
|
|
3f05a8408d | ||
|
|
61f0ba4643 | ||
|
|
b773ec7690 | ||
|
|
df294730a0 | ||
|
|
ee1cd38fa2 | ||
|
|
a012328f4c | ||
|
|
1df9656191 | ||
|
|
fde2d0887e | ||
|
|
356e439715 | ||
|
|
00ee7b662e | ||
|
|
b222b2a5e5 | ||
|
|
15b212734e | ||
|
|
631ba52ffb | ||
|
|
a4b58a893d | ||
|
|
47a3fd9462 | ||
|
|
abcd56f0c5 | ||
|
|
b498b5eafa | ||
|
|
be114e7789 | ||
|
|
8c44142e70 | ||
|
|
5a655f77d2 | ||
|
|
c825f791d0 | ||
|
|
83a9d8a162 | ||
|
|
acfa2e2183 | ||
|
|
591933f3d8 | ||
|
|
982f94d955 | ||
|
|
95bf28d7c6 | ||
|
|
48a299de45 | ||
|
|
719e517dda | ||
|
|
361c55c5ac | ||
|
|
b157fff229 | ||
|
|
e23e6417ee | ||
|
|
ad77209832 | ||
|
|
4ceb427d9c | ||
|
|
16a26130f2 | ||
|
|
e7773c9e88 | ||
|
|
1b38b92177 | ||
|
|
ef180f08b0 | ||
|
|
941e823374 | ||
|
|
6de45a3b88 | ||
|
|
ceb9537b73 | ||
|
|
976bf73388 | ||
|
|
6c7f2d96f3 | ||
|
|
ae2fc1fcfb | ||
|
|
3aad7e9e07 | ||
|
|
e43f1e75c8 | ||
|
|
bc5505e12d | ||
|
|
19a2703ea4 | ||
|
|
8a8f7d4f27 | ||
|
|
e350b4970f | ||
|
|
4be28bc579 | ||
|
|
13855a8682 | ||
|
|
4be1598eca | ||
|
|
7aebcd92c3 | ||
|
|
bd8668f0bf | ||
|
|
c6a7c6fec2 | ||
|
|
e367fa8a86 | ||
|
|
ecdbabfa17 | ||
|
|
796189ad48 | ||
|
|
76d1f2b29b | ||
|
|
73599815ba | ||
|
|
66372e9743 | ||
|
|
0d7f92129b | ||
|
|
f74a1ceb47 | ||
|
|
9da49d6116 | ||
|
|
7eeb9095e8 | ||
|
|
58281bb5ce | ||
|
|
f24dd3b289 | ||
|
|
51f2e5e7ec | ||
|
|
a937800ef0 | ||
|
|
c34d14edb5 | ||
|
|
8722e91c0f | ||
|
|
8038a8ac66 | ||
|
|
69a54c5fa9 | ||
|
|
aa508887f3 |
@@ -6,3 +6,7 @@ indent_size = 2
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.swift]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
@@ -72,10 +72,10 @@ body:
|
||||
description: Please ensure your app is up to date. *If you are using a 3rd-party app, please reach out to them directly.*
|
||||
multiple: true
|
||||
options:
|
||||
- 'Android App - 0.10.1'
|
||||
- 'iOS App - 0.10.1'
|
||||
- 'Android App - 0.10.0'
|
||||
- 'iOS App - 0.10.0'
|
||||
- 'Android App - 0.9.81'
|
||||
- 'iOS App - 0.9.81'
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
||||
@@ -43,10 +43,10 @@ body:
|
||||
description: Please ensure your app is up to date. *If you are using a 3rd-party app, please reach out to them directly.*
|
||||
multiple: true
|
||||
options:
|
||||
- 'Android App - 0.10.1'
|
||||
- 'iOS App - 0.10.1'
|
||||
- 'Android App - 0.10.0'
|
||||
- 'iOS App - 0.10.0'
|
||||
- 'Android App - 0.9.81'
|
||||
- 'iOS App - 0.9.81'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -36,8 +36,8 @@ android {
|
||||
applicationId "com.audiobookshelf.app"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 113
|
||||
versionName "0.10.0-beta"
|
||||
versionCode 115
|
||||
versionName "0.11.0-beta"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
manifestPlaceholders = [
|
||||
"appAuthRedirectScheme": "com.audiobookshelf.app"
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/media_player_widget_info" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.samsung.android.appwidget.provider"
|
||||
android:resource="@xml/samsung_cover_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<!-- Used by Android Auto -->
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
||||
val tag = "MediaManager"
|
||||
@@ -175,21 +176,20 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
||||
* Load personalized shelves from server for all libraries.
|
||||
* [cb] resolves when all libraries are processed
|
||||
*/
|
||||
fun populatePersonalizedDataForAllLibraries(cb: () -> Unit ) {
|
||||
serverLibraries.forEach {
|
||||
libraryPersonalizationsDone++
|
||||
Log.d(tag, "Loading personalization for library ${it.name} - ${it.id} - ${it.mediaType}")
|
||||
populatePersonalizedDataForLibrary(it.id) {
|
||||
Log.d(tag, "Loaded personalization for library ${it.name} - ${it.id} - ${it.mediaType}")
|
||||
libraryPersonalizationsDone--
|
||||
fun populatePersonalizedDataForAllLibraries(cb: () -> Unit) {
|
||||
val remaining = AtomicInteger(serverLibraries.size)
|
||||
|
||||
serverLibraries.forEach { lib ->
|
||||
Log.d(tag, "Loading personalization for library ${lib.name}")
|
||||
populatePersonalizedDataForLibrary(lib.id) {
|
||||
Log.d(tag, "Loaded personalization for library ${lib.name}")
|
||||
if (remaining.decrementAndGet() == 0) {
|
||||
Log.d(tag, "Finished loading all library personalization data")
|
||||
allLibraryPersonalizationsDone = true
|
||||
cb()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (libraryPersonalizationsDone > 0) { }
|
||||
|
||||
Log.d(tag, "Finished loading all library personalization data")
|
||||
allLibraryPersonalizationsDone = true
|
||||
cb()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,7 +76,10 @@ class CastPlayer(var castContext: CastContext) : BasePlayer() {
|
||||
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
||||
COMMAND_SET_MEDIA_ITEMS_METADATA,
|
||||
COMMAND_CHANGE_MEDIA_ITEMS,
|
||||
COMMAND_GET_TRACKS)
|
||||
COMMAND_GET_TRACKS,
|
||||
COMMAND_GET_DEVICE_VOLUME,
|
||||
COMMAND_SET_DEVICE_VOLUME,
|
||||
COMMAND_ADJUST_DEVICE_VOLUME)
|
||||
.build()
|
||||
|
||||
var currentPlaybackState = Player.STATE_IDLE
|
||||
@@ -861,27 +864,48 @@ class CastPlayer(var castContext: CastContext) : BasePlayer() {
|
||||
}
|
||||
|
||||
override fun getDeviceVolume(): Int {
|
||||
return 0
|
||||
val session = castContext.sessionManager.currentCastSession
|
||||
return if (session != null && !session.isMute) {
|
||||
(session.volume * 100).toInt()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
override fun isDeviceMuted(): Boolean {
|
||||
return false
|
||||
val session = castContext.sessionManager.currentCastSession
|
||||
return session?.isMute ?: false
|
||||
}
|
||||
|
||||
override fun setDeviceVolume(volume: Int) {
|
||||
|
||||
val session = castContext.sessionManager.currentCastSession
|
||||
try {
|
||||
val clamped = volume.coerceIn(0, 100)
|
||||
session?.volume = clamped / 100.0
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Failed to set cast device volume", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun increaseDeviceVolume() {
|
||||
|
||||
val currentVolume = getDeviceVolume()
|
||||
val newVolume = (currentVolume + 1).coerceAtMost(100)
|
||||
setDeviceVolume(newVolume)
|
||||
}
|
||||
|
||||
override fun decreaseDeviceVolume() {
|
||||
|
||||
val currentVolume = getDeviceVolume()
|
||||
val newVolume = (currentVolume - 1).coerceAtLeast(0)
|
||||
setDeviceVolume(newVolume)
|
||||
}
|
||||
|
||||
override fun setDeviceMuted(muted: Boolean) {
|
||||
|
||||
val session = castContext.sessionManager.currentCastSession
|
||||
try {
|
||||
session?.isMute = muted
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Failed to set cast device mute state", e)
|
||||
}
|
||||
}
|
||||
|
||||
inner class StatusListener() : RemoteMediaClient.Callback(), SessionManagerListener<CastSession>, RemoteMediaClient.ProgressListener {
|
||||
|
||||
@@ -18,6 +18,8 @@ import android.support.v4.media.MediaDescriptionCompat
|
||||
import android.support.v4.media.MediaMetadataCompat
|
||||
import android.support.v4.media.session.MediaControllerCompat
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import androidx.media.VolumeProviderCompat
|
||||
import android.media.AudioManager
|
||||
import android.support.v4.media.session.PlaybackStateCompat
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
@@ -94,6 +96,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
private lateinit var mediaSessionConnector: MediaSessionConnector
|
||||
private lateinit var playerNotificationManager: PlayerNotificationManager
|
||||
lateinit var mediaSession: MediaSessionCompat
|
||||
private var remoteVolumeProvider: VolumeProviderCompat? = null
|
||||
private lateinit var transportControls: MediaControllerCompat.TransportControls
|
||||
|
||||
lateinit var mediaManager: MediaManager
|
||||
@@ -705,11 +708,13 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
Log.d(tag, "switchToPlayer: Using Cast Player " + castPlayer?.deviceInfo)
|
||||
mediaSessionConnector.setPlayer(castPlayer)
|
||||
playerNotificationManager.setPlayer(castPlayer)
|
||||
setMediaSessionToCastVolume()
|
||||
castPlayer as CastPlayer
|
||||
} else {
|
||||
Log.d(tag, "switchToPlayer: Using ExoPlayer")
|
||||
mediaSessionConnector.setPlayer(mPlayer)
|
||||
playerNotificationManager.setPlayer(mPlayer)
|
||||
setMediaSessionToLocalVolume()
|
||||
mPlayer
|
||||
}
|
||||
|
||||
@@ -726,6 +731,44 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMediaSessionToCastVolume() {
|
||||
val currentVol = try { castPlayer?.getDeviceVolume() ?: 0 } catch (e: Exception) { 0 }
|
||||
val provider = object : VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 100, currentVol) {
|
||||
override fun onSetVolumeTo(volume: Int) {
|
||||
val clamped = volume.coerceIn(0, 100)
|
||||
try {
|
||||
castPlayer?.setDeviceVolume(clamped)
|
||||
val actual = castPlayer?.getDeviceVolume() ?: clamped
|
||||
setCurrentVolume(actual)
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
override fun onAdjustVolume(direction: Int) {
|
||||
val current = try { castPlayer?.getDeviceVolume() ?: currentVolume } catch (_: Exception) { currentVolume }
|
||||
val step = if (direction > 0) 1 else if (direction < 0) -1 else 0
|
||||
if (step == 0) return
|
||||
var target = (current + step).coerceIn(0, 100)
|
||||
try {
|
||||
castPlayer?.setDeviceVolume(target)
|
||||
var actual = castPlayer?.getDeviceVolume() ?: current
|
||||
// If device ignored tiny step, try nudging up to 3 steps total.
|
||||
if (actual == current) {
|
||||
target = (current + step * 3).coerceIn(0, 100)
|
||||
castPlayer?.setDeviceVolume(target)
|
||||
actual = castPlayer?.getDeviceVolume() ?: current
|
||||
}
|
||||
setCurrentVolume(actual)
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
remoteVolumeProvider = provider
|
||||
mediaSession.setPlaybackToRemote(provider)
|
||||
}
|
||||
|
||||
private fun setMediaSessionToLocalVolume() {
|
||||
mediaSession.setPlaybackToLocal(AudioManager.STREAM_MUSIC)
|
||||
remoteVolumeProvider = null
|
||||
}
|
||||
|
||||
fun getCurrentTrackStartOffsetMs(): Long {
|
||||
return if (currentPlayer.mediaItemCount > 1) {
|
||||
val windowIndex = currentPlayer.currentMediaItemIndex
|
||||
@@ -1087,6 +1130,26 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
private val DOWNLOADS_ROOT = "__DOWNLOADS__"
|
||||
private val CONTINUE_ROOT = "__CONTINUE__"
|
||||
private lateinit var browseTree: BrowseTree
|
||||
private val browseTreeInitListeners = mutableListOf<() -> Unit>()
|
||||
|
||||
private fun waitForBrowseTree(cb: () -> Unit)
|
||||
{
|
||||
if (this::browseTree.isInitialized)
|
||||
{
|
||||
cb()
|
||||
}
|
||||
else
|
||||
{
|
||||
browseTreeInitListeners += cb
|
||||
}
|
||||
}
|
||||
|
||||
private fun onBrowseTreeInitialized()
|
||||
{
|
||||
// Called after browseTree is assigned for the first time
|
||||
browseTreeInitListeners.forEach { it.invoke() }
|
||||
browseTreeInitListeners.clear()
|
||||
}
|
||||
|
||||
// Only allowing android auto or similar to access media browser service
|
||||
// normal loading of audiobooks is handled in webview (not natively)
|
||||
@@ -1257,6 +1320,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
mediaManager.serverLibraries,
|
||||
mediaManager.allLibraryPersonalizationsDone
|
||||
)
|
||||
onBrowseTreeInitialized()
|
||||
val children =
|
||||
browseTree[parentMediaId]?.map { item ->
|
||||
Log.d(tag, "Found top menu item: ${item.description.title}")
|
||||
@@ -1291,6 +1355,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
mediaManager.serverLibraries,
|
||||
mediaManager.allLibraryPersonalizationsDone
|
||||
)
|
||||
onBrowseTreeInitialized()
|
||||
val children =
|
||||
browseTree[parentMediaId]?.map { item ->
|
||||
Log.d(tag, "Found top menu item: ${item.description.title}")
|
||||
@@ -1303,22 +1368,40 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||
AbsLogger.info(tag, "onLoadChildren: Android auto data loaded")
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
} else if (parentMediaId == LIBRARIES_ROOT || parentMediaId == RECENTLY_ROOT) {
|
||||
} else if (parentMediaId == LIBRARIES_ROOT || parentMediaId == RECENTLY_ROOT)
|
||||
{
|
||||
Log.d(tag, "First load done: $firstLoadDone")
|
||||
if (!firstLoadDone) {
|
||||
if (!firstLoadDone)
|
||||
{
|
||||
result.sendResult(null)
|
||||
return
|
||||
}
|
||||
// Wait until top-menu is initialized
|
||||
while (!this::browseTree.isInitialized) {}
|
||||
val children =
|
||||
browseTree[parentMediaId]?.map { item ->
|
||||
Log.d(tag, "[MENU: $parentMediaId] Showing list item ${item.description.title}")
|
||||
MediaBrowserCompat.MediaItem(
|
||||
item.description,
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
)
|
||||
}
|
||||
|
||||
if (!this::browseTree.isInitialized)
|
||||
{
|
||||
// ✅ good: detach and wait for init
|
||||
result.detach()
|
||||
waitForBrowseTree {
|
||||
val children = browseTree[parentMediaId]?.map { item ->
|
||||
Log.d(tag, "[MENU: $parentMediaId] Showing list item ${item.description.title}")
|
||||
MediaBrowserCompat.MediaItem(
|
||||
item.description,
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Already initialized: just return
|
||||
val children = browseTree[parentMediaId]?.map { item ->
|
||||
Log.d(tag, "[MENU: $parentMediaId] Showing list item ${item.description.title}")
|
||||
MediaBrowserCompat.MediaItem(
|
||||
item.description,
|
||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||
)
|
||||
}
|
||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||
} else if (mediaManager.getIsLibrary(parentMediaId)) { // Load library items for library
|
||||
Log.d(tag, "Loading items for library $parentMediaId")
|
||||
|
||||
@@ -407,6 +407,9 @@ class ApiHandler(var ctx:Context) {
|
||||
if (checkAbsDatabaseNotifyListenersInitted()) {
|
||||
val tokenJsObject = JSObject()
|
||||
tokenJsObject.put("error", "Token refresh failed")
|
||||
if (serverConnectionConfigId.isNotEmpty()) {
|
||||
tokenJsObject.put("serverConnectionConfigId", serverConnectionConfigId)
|
||||
}
|
||||
absDatabaseNotifyListeners("onTokenRefreshFailure", tokenJsObject)
|
||||
} else {
|
||||
// Can happen if Webview is never run
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
android:targetCellWidth="4"
|
||||
android:targetCellHeight="1"
|
||||
android:updatePeriodMillis="86400000"
|
||||
android:widgetCategory="home_screen" />
|
||||
android:widgetCategory="home_screen|keyguard" />
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<samsung-appwidget-provider
|
||||
display="sub_screen"
|
||||
privacyWidget="false" />
|
||||
@@ -4,14 +4,14 @@
|
||||
<nuxt-link v-show="!showBack" to="/" class="mr-3">
|
||||
<img src="/Logo.png" class="h-10 w-10" />
|
||||
</nuxt-link>
|
||||
<a v-if="showBack" @click="back" class="rounded-full h-10 w-10 flex items-center justify-center mr-2 cursor-pointer">
|
||||
<a v-if="showBack" @click="back" aria-label="Back" class="rounded-full h-10 w-10 flex items-center justify-center mr-2 cursor-pointer">
|
||||
<span class="material-symbols text-3xl text-fg">arrow_back</span>
|
||||
</a>
|
||||
<div v-if="user && currentLibrary">
|
||||
<div class="pl-1.5 pr-2.5 py-2 bg-bg bg-opacity-30 rounded-md flex items-center" @click="clickShowLibraryModal">
|
||||
<button type="button" aria-label="Show library modal" class="pl-1.5 pr-2.5 py-2 bg-bg bg-opacity-30 rounded-md flex items-center" @click="clickShowLibraryModal">
|
||||
<ui-library-icon :icon="currentLibraryIcon" :size="4" font-size="base" />
|
||||
<p class="text-sm leading-4 ml-2 mt-0.5 max-w-24 truncate">{{ currentLibraryName }}</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<widgets-connection-indicator />
|
||||
@@ -21,19 +21,19 @@
|
||||
<widgets-download-progress-indicator />
|
||||
|
||||
<!-- Must be connected to a server to cast, only supports media items on server -->
|
||||
<div v-show="isCastAvailable && user" class="mx-2 cursor-pointer flex items-center" @click="castClick">
|
||||
<button type="button" aria-label="Cast" v-show="isCastAvailable && user" class="mx-2 cursor-pointer flex items-center" @click="castClick">
|
||||
<span class="material-symbols text-2xl leading-none">
|
||||
{{ isCasting ? 'cast_connected' : 'cast' }}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<nuxt-link v-if="user" class="mx-1.5 flex items-center h-10" to="/search">
|
||||
<nuxt-link v-if="user" class="mx-1.5 flex items-center h-10" to="/search" aria-label="Search">
|
||||
<span class="material-symbols text-2xl leading-none">search</span>
|
||||
</nuxt-link>
|
||||
|
||||
<div class="h-7 mx-1.5">
|
||||
<span class="material-symbols" style="font-size: 1.75rem" @click="clickShowSideDrawer">menu</span>
|
||||
</div>
|
||||
<button type="button" aria-label="Toggle side drawer" class="h-7 mx-1.5" @click="clickShowSideDrawer">
|
||||
<span class="material-symbols" style="font-size: 1.75rem">menu</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -581,8 +581,12 @@ export default {
|
||||
}
|
||||
|
||||
const ptWidth = Math.round(percentDone * this.trackWidth)
|
||||
this.$refs.playedTrack.style.width = ptWidth + 'px'
|
||||
this.$refs.bufferedTrack.style.width = Math.round(bufferedPercent * this.trackWidth) + 'px'
|
||||
if (this.$refs.playedTrack) {
|
||||
this.$refs.playedTrack.style.width = ptWidth + 'px'
|
||||
}
|
||||
if (this.$refs.bufferedTrack) {
|
||||
this.$refs.bufferedTrack.style.width = Math.round(bufferedPercent * this.trackWidth) + 'px'
|
||||
}
|
||||
|
||||
if (this.$refs.trackCursor) {
|
||||
this.$refs.trackCursor.style.left = ptWidth - 14 + 'px'
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
|
||||
<div class="w-full overflow-y-auto">
|
||||
<template v-for="item in navItems">
|
||||
<button v-if="item.action" :key="item.text" class="w-full hover:bg-bg/60 flex items-center py-3 px-6 text-fg-muted" @click="clickAction(item.action)">
|
||||
<button v-if="item.action" :key="item.text" :tabindex="show ? 0 : -1" class="w-full hover:bg-bg/60 flex items-center py-3 px-6 text-fg-muted" @click="clickAction(item.action)">
|
||||
<span class="material-symbols fill text-lg">{{ item.icon }}</span>
|
||||
<p class="pl-4">{{ item.text }}</p>
|
||||
</button>
|
||||
<nuxt-link v-else :to="item.to" :key="item.text" class="w-full hover:bg-bg/60 flex items-center py-3 px-6 text-fg" :class="currentRoutePath.startsWith(item.to) ? 'bg-bg-hover/50' : 'text-fg-muted'">
|
||||
<nuxt-link v-else :to="item.to" :key="item.text" :tabindex="show ? 0 : -1" class="w-full hover:bg-bg/60 flex items-center py-3 px-6 text-fg" :class="currentRoutePath.startsWith(item.to) ? 'bg-bg-hover/50' : 'text-fg-muted'">
|
||||
<span class="material-symbols fill text-lg">{{ item.icon }}</span>
|
||||
<p class="pl-4">{{ item.text }}</p>
|
||||
</nuxt-link>
|
||||
@@ -134,14 +134,12 @@ export default {
|
||||
to: '/settings'
|
||||
})
|
||||
|
||||
if (this.$platform !== 'ios') {
|
||||
items.push({
|
||||
icon: 'bug_report',
|
||||
iconOutlined: true,
|
||||
text: this.$strings.ButtonLogs,
|
||||
to: '/logs'
|
||||
})
|
||||
}
|
||||
items.push({
|
||||
icon: 'bug_report',
|
||||
iconOutlined: true,
|
||||
text: this.$strings.ButtonLogs,
|
||||
to: '/logs'
|
||||
})
|
||||
|
||||
if (this.serverConnectionConfig) {
|
||||
items.push({
|
||||
@@ -185,11 +183,18 @@ export default {
|
||||
await this.$hapticsImpact()
|
||||
await this.logout()
|
||||
|
||||
// Redirect to home page
|
||||
if (this.$route.name !== 'bookshelf') {
|
||||
this.$router.replace('/bookshelf')
|
||||
} else {
|
||||
location.reload()
|
||||
}
|
||||
|
||||
// If player is open and not playing locally, then close the player
|
||||
if (this.$store.getters['getIsPlayerOpen']) {
|
||||
this.$eventBus.$emit('close-stream')
|
||||
}
|
||||
|
||||
// Close side drawer
|
||||
this.show = false
|
||||
},
|
||||
touchstart(e) {
|
||||
this.touchEvent = new TouchEvent(e)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="card" :id="`book-card-${index}`" :style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }" class="rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @click="clickCard">
|
||||
<div ref="card" tabindex="0" :id="`book-card-${index}`" :style="{ minWidth: width + 'px', maxWidth: width + 'px', height: height + 'px' }" class="rounded-sm z-10 bg-primary cursor-pointer box-shadow-book" @click="clickCard">
|
||||
<!-- When cover image does not fill -->
|
||||
<div v-show="showCoverBg" class="absolute top-0 left-0 w-full h-full overflow-hidden rounded-sm bg-primary">
|
||||
<div class="absolute cover-bg" ref="coverBg" />
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
<!-- list of server connection configs -->
|
||||
<template v-if="!showForm">
|
||||
<div v-for="config in serverConnectionConfigs" :key="config.id" class="border-b border-fg/10 py-4">
|
||||
<div class="flex items-center my-1 relative" @click="connectToServer(config)">
|
||||
<span class="material-symbols text-xl text-fg-muted">dns</span>
|
||||
<p class="pl-3 pr-6 text-base text-fg">{{ config.name }}</p>
|
||||
|
||||
<div class="absolute top-0 right-0 h-full px-4 flex items-center" @click.stop="editServerConfig(config)">
|
||||
<div class="flex items-center my-1 relative space-x-2" @click="connectToServer(config)">
|
||||
<div class="grow inline-flex items-center overflow-hidden">
|
||||
<p class="text-base text-fg truncate">{{ config.name }}</p>
|
||||
</div>
|
||||
<div class="h-full w-6 flex items-center" @click.stop="editServerConfig(config)">
|
||||
<span class="material-symbols text-2xl text-fg-muted">more_vert</span>
|
||||
</div>
|
||||
<div class="h-full w-6 flex items-center" @click.stop="removeServerConfigClick(config)">
|
||||
<span class="material-symbols fill text-1.5xl text-fg-muted">delete</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- warning message if server connection config is using an old user id -->
|
||||
<div v-if="!checkIdUuid(config.userId)" class="flex flex-nowrap justify-between items-center space-x-4 pt-4">
|
||||
@@ -30,7 +33,7 @@
|
||||
<!-- form to add a new server connection config -->
|
||||
<div v-else class="w-full">
|
||||
<!-- server address input -->
|
||||
<form v-if="!showAuth" @submit.prevent="submit" novalidate class="w-full">
|
||||
<form v-if="!showAuth" @submit.prevent="submit(false)" novalidate class="w-full">
|
||||
<div v-if="serverConnectionConfigs.length" class="flex items-center mb-4" @click="showServerList">
|
||||
<span class="material-symbols text-fg-muted">arrow_back</span>
|
||||
</div>
|
||||
@@ -57,7 +60,7 @@
|
||||
<ui-text-input v-model="password" type="password" :disabled="processing" :placeholder="$strings.LabelPassword" class="w-full mb-2 text-lg" />
|
||||
|
||||
<div class="flex items-center pt-2">
|
||||
<ui-icon-btn v-if="serverConfig.id" small bg-color="error" icon="delete" type="button" @click="removeServerConfigClick" />
|
||||
<ui-icon-btn v-if="serverConfig.id" bg-color="error" icon="delete" type="button" @click="removeServerConfigClick(serverConfig)" />
|
||||
<div class="flex-grow" />
|
||||
<ui-btn :disabled="processing || !networkConnected" type="submit" class="mt-1 h-10">{{ networkConnected ? $strings.ButtonSubmit : $strings.MessageNoNetworkConnection }}</ui-btn>
|
||||
</div>
|
||||
@@ -76,9 +79,6 @@
|
||||
|
||||
<div :class="processing ? 'opacity-100' : 'opacity-0 pointer-events-none'" class="fixed w-full h-full top-0 left-0 bg-black/75 flex items-center justify-center z-30 transition-opacity duration-500">
|
||||
<div>
|
||||
<div class="absolute top-0 left-0 w-full p-6 flex items-center flex-col justify-center z-0 short:hidden">
|
||||
<img src="/Logo.png" class="h-20 w-20 mb-2" />
|
||||
</div>
|
||||
<svg class="animate-spin w-16 h-16" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
||||
</svg>
|
||||
@@ -412,7 +412,7 @@ export default {
|
||||
throw new Error('Authentication failed with the provided token.')
|
||||
}
|
||||
|
||||
const duplicateConfig = this.serverConnectionConfigs.find((scc) => scc.address === this.serverConfig.address && scc.username === payload.user.username)
|
||||
const duplicateConfig = this.serverConnectionConfigs.find((scc) => scc.address === this.serverConfig.address && scc.username === payload.user.username && scc.id !== this.serverConfig.id)
|
||||
if (duplicateConfig) {
|
||||
throw new Error('Config already exists for this address and username.')
|
||||
}
|
||||
@@ -471,11 +471,15 @@ export default {
|
||||
// Will NOT include access token and refresh token
|
||||
this.setUserAndConnection(payload)
|
||||
} else {
|
||||
this.showAuth = true
|
||||
let error = this.error
|
||||
if (await this.submit(true)) {
|
||||
this.showForm = true
|
||||
this.error = error
|
||||
}
|
||||
}
|
||||
},
|
||||
async removeServerConfigClick() {
|
||||
if (!this.serverConfig.id) return
|
||||
async removeServerConfigClick(serverConfig) {
|
||||
if (!serverConfig.id) return
|
||||
await this.$hapticsImpact()
|
||||
|
||||
const { value } = await Dialog.confirm({
|
||||
@@ -484,9 +488,9 @@ export default {
|
||||
})
|
||||
if (value) {
|
||||
this.processing = true
|
||||
await this.$db.removeServerConnectionConfig(this.serverConfig.id)
|
||||
await this.$db.removeServerConnectionConfig(serverConfig.id)
|
||||
const updatedDeviceData = { ...this.deviceData }
|
||||
updatedDeviceData.serverConnectionConfigs = this.deviceData.serverConnectionConfigs.filter((scc) => scc.id != this.serverConfig.id)
|
||||
updatedDeviceData.serverConnectionConfigs = this.deviceData.serverConnectionConfigs.filter((scc) => scc.id != serverConfig.id)
|
||||
this.$store.commit('setDeviceData', updatedDeviceData)
|
||||
|
||||
this.serverConfig = {
|
||||
@@ -498,14 +502,17 @@ export default {
|
||||
this.processing = false
|
||||
this.showAuth = false
|
||||
this.showForm = !this.serverConnectionConfigs.length
|
||||
this.error = null
|
||||
}
|
||||
},
|
||||
editServerConfig(serverConfig) {
|
||||
async editServerConfig(serverConfig) {
|
||||
this.serverConfig = {
|
||||
...serverConfig
|
||||
}
|
||||
this.showForm = true
|
||||
this.showAuth = true
|
||||
|
||||
if (await this.submit(true)) {
|
||||
this.showForm = true
|
||||
}
|
||||
},
|
||||
async newServerConfigClick() {
|
||||
await this.$hapticsImpact()
|
||||
@@ -651,9 +658,8 @@ export default {
|
||||
return false
|
||||
})
|
||||
},
|
||||
async submit() {
|
||||
if (!this.networkConnected) return
|
||||
if (!this.serverConfig.address) return
|
||||
async submit(preventAutoLogin = false) {
|
||||
if (!this.networkConnected || !this.serverConfig.address) return false
|
||||
|
||||
const initialAddress = this.serverConfig.address
|
||||
// Did the user specify a protocol?
|
||||
@@ -666,6 +672,7 @@ export default {
|
||||
this.authMethods = []
|
||||
|
||||
try {
|
||||
console.log('[ServerConnectForm] submit tryServerUrl: ' + this.serverConfig.address)
|
||||
// Try the server URL. If it fails and the protocol was not provided, try with http instead of https
|
||||
const statusData = await this.tryServerUrl(this.serverConfig.address, !protocolProvided)
|
||||
if (this.validateLoginFormResponse(statusData, this.serverConfig.address, protocolProvided)) {
|
||||
@@ -674,11 +681,12 @@ export default {
|
||||
this.oauth.buttonText = statusData.data.authFormData?.authOpenIDButtonText || 'Login with OpenID'
|
||||
this.serverConfig.version = statusData.data.serverVersion
|
||||
|
||||
if (statusData.data.authFormData?.authOpenIDAutoLaunch) {
|
||||
if (statusData.data.authFormData?.authOpenIDAutoLaunch && !preventAutoLogin) {
|
||||
this.clickLoginWithOpenId()
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
console.log('[ServerConnectForm] submit validateLoginFormResponse failed: ' + this.serverConfig.address)
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -852,7 +860,7 @@ export default {
|
||||
} else {
|
||||
// Detect if the connection config is using the old token. If so, force re-login
|
||||
if (this.serverConfig.token === user.token || user.isOldToken) {
|
||||
this.setForceReloginForNewAuth()
|
||||
this.setForceRelogin('oldAuthToken')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -926,20 +934,29 @@ export default {
|
||||
this.processing = false
|
||||
return authRes
|
||||
},
|
||||
async setForceReloginForNewAuth() {
|
||||
async setForceRelogin(error) {
|
||||
console.log('[ServerConnectForm] setForceRelogin', error, this.serverConfig.address)
|
||||
// This calls /status on the server and sets the auth methods
|
||||
const result = await this.submit()
|
||||
const result = await this.submit(true)
|
||||
if (result) {
|
||||
this.error = this.$strings.MessageOldServerAuthReLoginRequired
|
||||
this.showForm = true
|
||||
|
||||
if (error === 'oldAuthToken') {
|
||||
this.error = this.$strings.MessageOldServerAuthReLoginRequired
|
||||
} else if (error === 'refreshTokenFailed') {
|
||||
this.error = this.$strings.MessageFailedToRefreshToken
|
||||
}
|
||||
}
|
||||
},
|
||||
init() {
|
||||
// Handle force re-login for servers using new JWT auth but still using an old token in the server config
|
||||
if (this.$route.query.error === 'oldAuthToken' && this.$route.query.serverConnectionConfigId) {
|
||||
if (this.$route.query.serverConnectionConfigId) {
|
||||
// Handle force re-login for servers using new JWT auth but still using an old token OR refresh token failed
|
||||
this.serverConfig = this.serverConnectionConfigs.find((scc) => scc.id === this.$route.query.serverConnectionConfigId)
|
||||
if (this.serverConfig) {
|
||||
this.setForceReloginForNewAuth()
|
||||
this.setForceRelogin(this.$route.query.error)
|
||||
return
|
||||
} else {
|
||||
console.error('[ServerConnectForm] init with serverConnectionConfigId but no serverConfig found', this.$route.query.serverConnectionConfigId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<modals-modal v-model="show" :width="400" height="100%">
|
||||
<modals-modal v-model="show" :width="400" max-width="95%" height="100%">
|
||||
<template #outer>
|
||||
<div v-if="currentChapter" class="absolute top-10 left-4 z-40 pt-1" style="max-width: 80%">
|
||||
<p class="text-white text-lg truncate">{{ chapters.length }} {{ $strings.LabelChapters }}</p>
|
||||
@@ -8,19 +8,31 @@
|
||||
|
||||
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
|
||||
<div ref="container" class="w-full overflow-x-hidden overflow-y-auto bg-secondary rounded-lg border border-fg/20" style="max-height: 75%" @click.stop>
|
||||
<div class="sticky top-0 z-10 bg-secondary grid grid-cols-[1fr_auto_auto] gap-2 px-3 py-2 border-b border-fg/10 transition-shadow" :class="{ 'shadow-md': isScrolled }">
|
||||
<div>
|
||||
<p class="text-fg-muted text-sm">{{ $strings.LabelChapters }}</p>
|
||||
</div>
|
||||
<div class="text-right" style="min-width: 60px">
|
||||
<p class="text-fg-muted text-sm" style="letter-spacing: -0.5px">{{ $strings.LabelStart }}</p>
|
||||
</div>
|
||||
<div class="text-right" style="min-width: 60px">
|
||||
<p class="text-fg-muted text-sm" style="letter-spacing: -0.5px">{{ $strings.LabelDuration }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||
<template v-for="chapter in chapters">
|
||||
<li :key="chapter.id" :id="`chapter-row-${chapter.id}`" class="text-fg select-none relative py-4 cursor-pointer" :class="currentChapterId === chapter.id ? 'bg-primary bg-opacity-80' : ''" role="option" @click="clickedOption(chapter)">
|
||||
<div class="relative flex items-center pl-3 pr-20">
|
||||
<p class="font-normal block truncate text-sm text-fg/80">{{ chapter.title }}</p>
|
||||
<div class="absolute top-0 right-3 -mt-0.5">
|
||||
<span class="font-mono text-fg-muted leading-3 text-sm" style="letter-spacing: -0.5px">{{ $secondsToTimestamp(chapter.start / _playbackRate) }}</span>
|
||||
</div>
|
||||
<li v-for="chapter in chapters" :key="chapter.id" :id="`chapter-row-${chapter.id}`" class="text-fg select-none relative cursor-pointer" :class="currentChapterId === chapter.id ? 'bg-primary bg-opacity-80' : ''" role="option" @click="clickedOption(chapter)">
|
||||
<div class="grid grid-cols-[1fr_auto_auto] gap-2 px-3 py-3 items-start">
|
||||
<p class="font-normal line-clamp-2 text-sm text-fg/80">{{ chapter.title }}</p>
|
||||
<div class="text-right" style="min-width: 60px">
|
||||
<span class="font-mono text-fg-muted text-sm" style="letter-spacing: -0.5px">{{ $secondsToTimestamp(chapter.start / _playbackRate) }}</span>
|
||||
</div>
|
||||
<div class="text-right" style="min-width: 60px">
|
||||
<span class="font-mono text-fg-muted text-sm" style="letter-spacing: -0.5px">{{ $secondsToTimestamp(Math.max(0, chapter.end - chapter.start) / _playbackRate) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="chapter.id === currentChapterId" class="w-0.5 h-full absolute top-0 left-0 bg-yellow-400" />
|
||||
</li>
|
||||
</template>
|
||||
<div v-show="chapter.id === currentChapterId" class="w-0.5 h-full absolute top-0 left-0 bg-yellow-400" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,7 +54,9 @@ export default {
|
||||
playbackRate: Number
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
isScrolled: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
@@ -87,8 +101,25 @@ export default {
|
||||
container.scrollTo({ top: offsetTop - containerHeight / 2 })
|
||||
}
|
||||
}
|
||||
},
|
||||
handleScroll() {
|
||||
const container = this.$refs.container
|
||||
if (container) {
|
||||
this.isScrolled = container.scrollTop > 0
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
mounted() {
|
||||
const container = this.$refs.container
|
||||
if (container) {
|
||||
container.addEventListener('scroll', this.handleScroll)
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
const container = this.$refs.container
|
||||
if (container) {
|
||||
container.removeEventListener('scroll', this.handleScroll)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="wrapper" class="modal modal-bg w-screen fixed bottom-0 left-0 flex items-center justify-center z-50" :class="threeQuartersScreen ? 'h-[75vh] min-h-[400px]' : 'h-screen'" @click.stop @touchstart.stop @touchend.stop>
|
||||
<div ref="wrapper" class="modal modal-bg w-screen fixed bottom-0 left-0 flex items-center justify-center z-50" :class="threeQuartersScreen ? 'h-[75vh] min-h-[400px] short:min-h-0 short:h-screen' : 'h-screen'" @click.stop @touchstart.stop @touchend.stop>
|
||||
<div ref="content" class="relative text-fg h-full w-full bg-bg">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -74,4 +74,4 @@ export default {
|
||||
this.$eventBus.$off('close-modal', this.closeModalEvt)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
<div class="w-full relative">
|
||||
<h1 class="text-lg mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</h1>
|
||||
|
||||
<ui-text-input v-model="currentFeed.feedUrl" class="text-sm" readonly />
|
||||
<ui-text-input v-model="fullFeedUrl" class="text-sm" readonly />
|
||||
|
||||
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base" :class="linkCopied ? 'text-success' : 'text-fg-muted'" @click="copyToClipboard(currentFeed.feedUrl)">{{ linkCopied ? 'check' : 'content_copy' }}</span>
|
||||
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base" :class="linkCopied ? 'text-success' : 'text-fg-muted'" @click="copyToClipboard(fullFeedUrl)">{{ linkCopied ? 'check' : 'content_copy' }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="currentFeed.meta" class="mt-5">
|
||||
@@ -119,6 +119,10 @@ export default {
|
||||
demoFeedUrl() {
|
||||
return `${this.serverAddress}/feed/${this.newFeedSlug}`
|
||||
},
|
||||
fullFeedUrl() {
|
||||
if (!this.currentFeed) return ''
|
||||
return `${this.serverAddress}${this.currentFeed.feedUrl}`
|
||||
},
|
||||
isHttp() {
|
||||
return !!this.serverAddress?.startsWith('http://')
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export default {
|
||||
isRefreshingUI: false,
|
||||
ereaderSettings: {
|
||||
theme: 'dark',
|
||||
font: 'serif',
|
||||
fontScale: 100,
|
||||
lineSpacing: 115,
|
||||
textStroke: 0
|
||||
@@ -134,6 +135,7 @@ export default {
|
||||
|
||||
const fontScale = settings.fontScale || 100
|
||||
this.rendition.themes.fontSize(`${fontScale}%`)
|
||||
this.rendition.themes.font(settings.font)
|
||||
this.rendition.spread(settings.spread || 'auto')
|
||||
},
|
||||
goToChapter(href) {
|
||||
|
||||
@@ -173,16 +173,18 @@ export default {
|
||||
try {
|
||||
console.log('[PdfReader] Handling refresh failure - logging out user')
|
||||
|
||||
const serverConnectionConfigId = this.$store.getters['user/getServerConnectionConfigId']
|
||||
|
||||
// Clear store
|
||||
await this.$store.dispatch('user/logout')
|
||||
|
||||
if (this.$store.getters['user/getServerConnectionConfigId']) {
|
||||
if (serverConnectionConfigId) {
|
||||
// Clear refresh token for server connection config
|
||||
await this.$db.clearRefreshToken(this.$store.getters['user/getServerConnectionConfigId'])
|
||||
await this.$db.clearRefreshToken(serverConnectionConfigId)
|
||||
}
|
||||
|
||||
if (window.location.pathname !== '/connect') {
|
||||
window.location.href = '/connect'
|
||||
window.location.href = '/connect?error=refreshTokenFailed&serverConnectionConfigId=' + serverConnectionConfigId
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[PdfReader] Failed to handle refresh failure:', error)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-if="show" :data-theme="ereaderTheme" class="group fixed top-0 left-0 right-0 layout-wrapper w-full z-40 pt-8 data-[theme=black]:bg-black data-[theme=black]:text-white data-[theme=dark]:bg-[#232323] data-[theme=dark]:text-white data-[theme=light]:bg-white data-[theme=light]:text-black" :class="{ 'reader-player-open': isPlayerOpen }">
|
||||
<!-- toolbar -->
|
||||
<div class="h-32 pt-10 w-full px-2 fixed top-0 left-0 z-30 transition-transform bg-bg text-fg" :class="showingToolbar ? 'translate-y-0' : '-translate-y-32'" :style="{ boxShadow: showingToolbar ? '0px 8px 8px #11111155' : '' }" @touchstart.stop @mousedown.stop @touchend.stop @mouseup.stop>
|
||||
<div class="w-full px-2 fixed top-0 left-0 z-30 transition-transform bg-bg text-fg" :class="`${showingToolbar ? 'translate-y-0' : '-translate-y-36'} ${isIos ? 'pt-14 h-36' : 'pt-10 h-32'}`" :style="{ boxShadow: showingToolbar ? '0px 8px 8px #11111155' : '' }" @touchstart.stop @mousedown.stop @touchend.stop @mouseup.stop>
|
||||
<div class="flex items-center mb-2">
|
||||
<button type="button" class="inline-flex mx-2" @click.stop="show = false">
|
||||
<span class="material-symbols text-3xl text-fg">chevron_left</span>
|
||||
@@ -62,7 +62,7 @@
|
||||
<span class="material-symbols">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="w-full overflow-y-auto overflow-x-hidden h-full max-h-[calc(75vh-85px)]">
|
||||
<div class="w-full overflow-y-auto overflow-x-hidden h-[calc(75vh-85px)] min-h-[320px] short:min-h-0 short:h-[calc(100vh-85px)]">
|
||||
<div class="w-full h-full px-4">
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-32">
|
||||
@@ -70,6 +70,12 @@
|
||||
</div>
|
||||
<ui-toggle-btns v-model="ereaderSettings.theme" name="theme" :items="themeItems" @input="settingsUpdated" />
|
||||
</div>
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-32">
|
||||
<p class="text-sm">{{ $strings.LabelFontFamily }}</p>
|
||||
</div>
|
||||
<ui-toggle-btns v-model="ereaderSettings.font" name="font" :items="fontItems" @input="settingsUpdated" />
|
||||
</div>
|
||||
<div class="flex items-center mb-6">
|
||||
<div class="w-32">
|
||||
<p class="text-sm">{{ $strings.LabelFontScale }}</p>
|
||||
@@ -141,6 +147,7 @@ export default {
|
||||
isInittingWatchVolume: false,
|
||||
ereaderSettings: {
|
||||
theme: 'dark',
|
||||
font: 'serif',
|
||||
fontScale: 100,
|
||||
lineSpacing: 115,
|
||||
spread: 'auto',
|
||||
@@ -180,6 +187,9 @@ export default {
|
||||
this.$store.commit('setShowReader', val)
|
||||
}
|
||||
},
|
||||
isIos() {
|
||||
return this.$platform === 'ios'
|
||||
},
|
||||
title() {
|
||||
return this.mediaMetadata.title || 'No Title'
|
||||
},
|
||||
@@ -252,6 +262,18 @@ export default {
|
||||
}
|
||||
]
|
||||
},
|
||||
fontItems() {
|
||||
return [
|
||||
{
|
||||
text: this.$strings.LabelFontFamilySans,
|
||||
value: 'sans-serif'
|
||||
},
|
||||
{
|
||||
text: this.$strings.LabelFontFamilySerif,
|
||||
value: 'serif'
|
||||
}
|
||||
]
|
||||
},
|
||||
readerComponentName() {
|
||||
if (this.ebookType === 'epub') return 'readers-epub-reader'
|
||||
else if (this.ebookType === 'mobi') return 'readers-mobi-reader'
|
||||
|
||||
@@ -183,10 +183,16 @@ export default {
|
||||
daysInARow() {
|
||||
var count = 0
|
||||
while (true) {
|
||||
var _date = this.$addDaysToToday(count * -1)
|
||||
var datestr = this.$formatJsDate(_date, 'yyyy-MM-dd')
|
||||
const _date = this.$addDaysToToday(count * -1 - 1)
|
||||
const datestr = this.$formatJsDate(_date, 'yyyy-MM-dd')
|
||||
|
||||
if (!this.listeningStatsDays[datestr] || this.listeningStatsDays[datestr] === 0) {
|
||||
// don't require listening today to count towards days in a row, but do count it if already listened today
|
||||
const today = this.$formatJsDate(new Date(), 'yyyy-MM-dd')
|
||||
if (this.listeningStatsDays[today]) {
|
||||
count++
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
count++
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<tr>
|
||||
<th class="text-left">{{ $strings.LabelTitle }}</th>
|
||||
<th class="text-center w-16">{{ $strings.LabelStart }}</th>
|
||||
<th class="text-center w-16">{{ $strings.LabelDuration }}</th>
|
||||
</tr>
|
||||
<tr v-for="chapter in chapters" :key="chapter.id">
|
||||
<td>
|
||||
@@ -23,6 +24,9 @@
|
||||
<td class="font-mono text-center underline w-16" @click.stop="goToTimestamp(chapter.start)">
|
||||
{{ $secondsToTimestamp(chapter.start) }}
|
||||
</td>
|
||||
<td class="font-mono text-center">
|
||||
{{ $secondsToTimestamp(Math.max(0, chapter.end - chapter.start)) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</transition>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<p>Zur Verwendung mit Ihrem Audiobookshelf-Server. Audiobookshelf ist ein kostenloser Open-Source-Hörbuch- und Podcast-Manager.</p><p>Verbinden Sie sich mit Ihrem selbst gehosteten Audiobookshelf-Server und streamen Sie Ihre Hörbücher und Podcasts direkt auf Ihr Gerät.</p>
|
||||
@@ -0,0 +1 @@
|
||||
Hörbuch- und Podcast-Player für Ihren selbst gehosteten Audiobookshelf-Server
|
||||
@@ -0,0 +1 @@
|
||||
<p>For use with your audiobookshelf server. Audiobookshelf is a free and open source audiobook and podcast manager.</p><p>Connect to your self-hosted audiobookshelf server and stream your audiobooks and podcasts directly to your device.</p>
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 45 KiB |
@@ -0,0 +1 @@
|
||||
Audiobook and podcast player for your self-hosted Audiobookshelf server
|
||||
@@ -36,6 +36,7 @@
|
||||
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
|
||||
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
|
||||
65643F4DAA661FB0B247247E /* Pods_Audiobookshelf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F9A7CCC479333E44DC314BE /* Pods_Audiobookshelf.framework */; };
|
||||
6F2516272E68DA3000F40541 /* LogEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2516262E68DA3000F40541 /* LogEntry.swift */; };
|
||||
E9D5504628AC1A3900C746DD /* LibraryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D5504528AC1A3900C746DD /* LibraryItem.swift */; };
|
||||
E9D5504828AC1A7A00C746DD /* MediaType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D5504728AC1A7A00C746DD /* MediaType.swift */; };
|
||||
E9D5504A28AC1AA600C746DD /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9D5504928AC1AA600C746DD /* Metadata.swift */; };
|
||||
@@ -111,6 +112,7 @@
|
||||
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
|
||||
6F2516262E68DA3000F40541 /* LogEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogEntry.swift; sourceTree = "<group>"; };
|
||||
8F9A7CCC479333E44DC314BE /* Pods_Audiobookshelf.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Audiobookshelf.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BE96D57E131924D520D57057 /* Pods-Audiobookshelf.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Audiobookshelf.debug.xcconfig"; path = "Target Support Files/Pods-Audiobookshelf/Pods-Audiobookshelf.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
D2F7F575384A63F1C47DE984 /* Pods-Audiobookshelf.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Audiobookshelf.release.xcconfig"; path = "Target Support Files/Pods-Audiobookshelf/Pods-Audiobookshelf.release.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -231,6 +233,7 @@
|
||||
E9D5506128AC1CC900C746DD /* PlayerState.swift */,
|
||||
4DF74911287105C600AC7814 /* DeviceSettings.swift */,
|
||||
E9D5507428AEF93100C746DD /* PlayerSettings.swift */,
|
||||
6F2516262E68DA3000F40541 /* LogEntry.swift */,
|
||||
E9D5506328AC1D3F00C746DD /* server */,
|
||||
E9D5506428AC1D5800C746DD /* local */,
|
||||
E9D5506D28AC1E7400C746DD /* download */,
|
||||
@@ -533,6 +536,7 @@
|
||||
3ABF580928059BAE005DFBE5 /* PlaybackSession.swift in Sources */,
|
||||
E9D5506628AC1D7300C746DD /* LocalLibraryItem.swift in Sources */,
|
||||
E9D5504628AC1A3900C746DD /* LibraryItem.swift in Sources */,
|
||||
6F2516272E68DA3000F40541 /* LogEntry.swift in Sources */,
|
||||
3ABF618F2804325C0070250E /* PlayerHandler.swift in Sources */,
|
||||
3AD4FCED28044E6C006DB301 /* Store.swift in Sources */,
|
||||
4D66B958282EEA14008272D4 /* AbsFileSystem.swift in Sources */,
|
||||
@@ -736,12 +740,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 39;
|
||||
CURRENT_PROJECT_VERSION = 41;
|
||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 0.10.0;
|
||||
MARKETING_VERSION = 0.11.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.dev;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -760,12 +764,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 39;
|
||||
CURRENT_PROJECT_VERSION = 41;
|
||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||
INFOPLIST_FILE = App/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 0.10.0;
|
||||
MARKETING_VERSION = 0.11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||
|
||||
@@ -5,8 +5,6 @@ import RealmSwift
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
private let logger = AppLogger(category: "AppDelegate")
|
||||
|
||||
lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
|
||||
var backgroundCompletionHandler: (() -> Void)?
|
||||
|
||||
@@ -17,13 +15,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
schemaVersion: 20,
|
||||
migrationBlock: { [weak self] migration, oldSchemaVersion in
|
||||
if (oldSchemaVersion < 1) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)")
|
||||
AbsLogger.info(message: "Realm schema version was \(oldSchemaVersion)")
|
||||
migration.enumerateObjects(ofType: DeviceSettings.className()) { oldObject, newObject in
|
||||
newObject?["enableAltView"] = false
|
||||
}
|
||||
}
|
||||
if (oldSchemaVersion < 4) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Reindexing server configs")
|
||||
AbsLogger.info(message: "Realm schema version was \(oldSchemaVersion)... Reindexing server configs")
|
||||
var indexCounter = 1
|
||||
migration.enumerateObjects(ofType: ServerConnectionConfig.className()) { oldObject, newObject in
|
||||
newObject?["index"] = indexCounter
|
||||
@@ -31,44 +29,44 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
}
|
||||
if (oldSchemaVersion < 5) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding lockOrientation setting")
|
||||
AbsLogger.info(message: "Realm schema version was \(oldSchemaVersion)... Adding lockOrientation setting")
|
||||
migration.enumerateObjects(ofType: DeviceSettings.className()) { oldObject, newObject in
|
||||
newObject?["lockOrientation"] = "NONE"
|
||||
}
|
||||
}
|
||||
if (oldSchemaVersion < 6) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding hapticFeedback setting")
|
||||
AbsLogger.info(message: "Realm schema version was \(oldSchemaVersion)... Adding hapticFeedback setting")
|
||||
migration.enumerateObjects(ofType: DeviceSettings.className()) { oldObject, newObject in
|
||||
newObject?["hapticFeedback"] = "LIGHT"
|
||||
}
|
||||
}
|
||||
if (oldSchemaVersion < 15) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding languageCode setting")
|
||||
AbsLogger.info(message: "Realm schema version was \(oldSchemaVersion)... Adding languageCode setting")
|
||||
migration.enumerateObjects(ofType: DeviceSettings.className()) { oldObject, newObject in
|
||||
newObject?["languageCode"] = "en-us"
|
||||
}
|
||||
}
|
||||
if (oldSchemaVersion < 16) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding chapterTrack setting")
|
||||
AbsLogger.info(message: "Realm schema version was \(oldSchemaVersion)... Adding chapterTrack setting")
|
||||
migration.enumerateObjects(ofType: PlayerSettings.className()) { oldObject, newObject in
|
||||
newObject?["chapterTrack"] = false
|
||||
}
|
||||
}
|
||||
if (oldSchemaVersion < 17) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding downloadUsingCellular and streamingUsingCellular settings")
|
||||
AbsLogger.info(message: "Realm schema version was \(oldSchemaVersion)... Adding downloadUsingCellular and streamingUsingCellular settings")
|
||||
migration.enumerateObjects(ofType: PlayerSettings.className()) { oldObject, newObject in
|
||||
newObject?["downloadUsingCellular"] = "ALWAYS"
|
||||
newObject?["streamingUsingCellular"] = "ALWAYS"
|
||||
}
|
||||
}
|
||||
if (oldSchemaVersion < 18) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding disableSleepTimerFadeOut settings")
|
||||
AbsLogger.info(message: "Realm schema version was \(oldSchemaVersion)... Adding disableSleepTimerFadeOut settings")
|
||||
migration.enumerateObjects(ofType: PlayerSettings.className()) { oldObject, newObject in
|
||||
newObject?["disableSleepTimerFadeOut"] = false
|
||||
}
|
||||
}
|
||||
if (oldSchemaVersion < 20) {
|
||||
self?.logger.log("Realm schema version was \(oldSchemaVersion)... Adding version to ServerConnectionConfigs")
|
||||
AbsLogger.info(message: "Realm schema version was \(oldSchemaVersion)... Adding version to ServerConnectionConfigs")
|
||||
migration.enumerateObjects(ofType: ServerConnectionConfig.className()) { oldObject, newObject in
|
||||
newObject?["version"] = ""
|
||||
}
|
||||
@@ -88,22 +86,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
logger.log("Audiobookself is now in the background")
|
||||
AbsLogger.info(message: "Audiobookself is now in the background")
|
||||
}
|
||||
|
||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||
logger.log("Audiobookself is now in the foreground")
|
||||
AbsLogger.info(message: "Audiobookself is now in the foreground")
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
logger.log("Audiobookself is now active")
|
||||
AbsLogger.info(message: "Audiobookself is now active")
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
logger.log("Audiobookself is terminating")
|
||||
AbsLogger.info(message: "Audiobookself is terminating")
|
||||
}
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||
|
||||
@@ -33,8 +33,6 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
CAPPluginMethod(name: "getSleepTimerTime", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "setSleepTimer", returnType: CAPPluginReturnPromise)
|
||||
]
|
||||
|
||||
private let logger = AppLogger(category: "AbsAudioPlayer")
|
||||
|
||||
private var initialPlayWhenReady = false
|
||||
private var monitor: NWPathMonitor?
|
||||
@@ -82,7 +80,7 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
let startTimeOverride = call.getDouble("startTime")
|
||||
|
||||
if libraryItemId == nil {
|
||||
logger.error("provide library item id")
|
||||
AbsLogger.error(message: "No id provided")
|
||||
return call.resolve()
|
||||
}
|
||||
|
||||
@@ -93,7 +91,7 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
let item = Database.shared.getLocalLibraryItem(localLibraryItemId: libraryItemId!)
|
||||
let episode = item?.getPodcastEpisode(episodeId: episodeId)
|
||||
guard let playbackSession = item?.getPlaybackSession(episode: episode) else {
|
||||
logger.error("Failed to get local playback session")
|
||||
AbsLogger.error(message: "Failed to get local playback session")
|
||||
return call.resolve([:])
|
||||
}
|
||||
|
||||
@@ -105,7 +103,7 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
try self.startPlaybackSession(playbackSession, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||
call.resolve(try playbackSession.asDictionary())
|
||||
} catch(let exception) {
|
||||
logger.error("Failed to start session")
|
||||
AbsLogger.error(message: "Failed to start session for local item: \(exception)")
|
||||
debugPrint(exception)
|
||||
call.resolve([:])
|
||||
}
|
||||
@@ -119,7 +117,7 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
try self?.startPlaybackSession(session, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||
call.resolve(try session.asDictionary())
|
||||
} catch(let exception) {
|
||||
self?.logger.error("Failed to start session")
|
||||
AbsLogger.error(message: "Failed to start streaming session")
|
||||
debugPrint(exception)
|
||||
call.resolve([:])
|
||||
}
|
||||
@@ -127,19 +125,22 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops playback and closes session.
|
||||
@objc func closePlayback(_ call: CAPPluginCall) {
|
||||
logger.log("Close playback")
|
||||
AbsLogger.info(message: "Close playback")
|
||||
|
||||
PlayerHandler.stopPlayback()
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
|
||||
@objc func getCurrentTime(_ call: CAPPluginCall) {
|
||||
call.resolve([
|
||||
"value": PlayerHandler.getCurrentTime() ?? 0,
|
||||
"bufferedTime": PlayerHandler.getCurrentTime() ?? 0,
|
||||
])
|
||||
}
|
||||
|
||||
@objc func setPlaybackSpeed(_ call: CAPPluginCall) {
|
||||
let playbackRate = call.getFloat("value", 1.0)
|
||||
let settings = PlayerSettings.main()
|
||||
@@ -152,7 +153,7 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
|
||||
@objc func setChapterTrack(_ call: CAPPluginCall) {
|
||||
let chapterTrack = call.getBool("enabled", true)
|
||||
logger.log(String(chapterTrack))
|
||||
AbsLogger.info(message: String(chapterTrack))
|
||||
let settings = PlayerSettings.main()
|
||||
try? settings.update {
|
||||
settings.chapterTrack = chapterTrack
|
||||
@@ -225,7 +226,7 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
|
||||
let seconds = time / 1000
|
||||
|
||||
logger.log("chapter time: \(isChapterTime)")
|
||||
AbsLogger.info(message: "chapter time: \(isChapterTime)")
|
||||
if isChapterTime {
|
||||
PlayerHandler.setChapterSleepTime(stopAt: seconds)
|
||||
return call.resolve([ "success": true ])
|
||||
@@ -262,7 +263,7 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
guard let localMediaProgressId = PlayerHandler.getPlaybackSession()?.localMediaProgressId else { return }
|
||||
guard let localMediaProgress = Database.shared.getLocalMediaProgress(localMediaProgressId: localMediaProgressId) else { return }
|
||||
guard let progressUpdate = try? localMediaProgress.asDictionary() else { return }
|
||||
logger.log("Sending local progress back to the UI")
|
||||
AbsLogger.info(message: "Sending local progress back to the UI")
|
||||
self.notifyListeners("onLocalMediaProgressUpdate", data: progressUpdate)
|
||||
}
|
||||
|
||||
@@ -272,7 +273,7 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
let playWhenReady = PlayerHandler.getPlayWhenReady()
|
||||
let libraryItemId = session?.libraryItemId ?? ""
|
||||
let episodeId = session?.episodeId ?? nil
|
||||
logger.log("Forcing Transcode")
|
||||
AbsLogger.info(message: "Forcing Transcode")
|
||||
|
||||
// If direct playing then fallback to transcode
|
||||
ApiClient.startPlaybackSession(libraryItemId: libraryItemId, episodeId: episodeId, forceTranscode: true) { [weak self] session in
|
||||
@@ -283,7 +284,7 @@ public class AbsAudioPlayer: CAPPlugin, CAPBridgedPlugin {
|
||||
self.sendPlaybackSession(session: try session.asDictionary())
|
||||
self.sendMetadata()
|
||||
} catch(let exception) {
|
||||
self?.logger.error("Failed to start transcoded session")
|
||||
AbsLogger.error(message: "Failed to start transcoded session")
|
||||
debugPrint(exception)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,7 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
CAPPluginMethod(name: "updateDeviceSettings", returnType: CAPPluginReturnPromise),
|
||||
CAPPluginMethod(name: "updateLocalEbookProgress", returnType: CAPPluginReturnPromise)
|
||||
]
|
||||
|
||||
private let logger = AppLogger(category: "AbsDatabase")
|
||||
|
||||
private let secureStorage = SecureStorage()
|
||||
|
||||
// Used to notify the webview frontend that the token has been refreshed
|
||||
@@ -73,14 +72,14 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
|
||||
let name = "\(address) (\(username))"
|
||||
|
||||
if id == nil {
|
||||
id = "\(address)@\(username)".toBase64()
|
||||
}
|
||||
|
||||
if (refreshToken != "") {
|
||||
// Store refresh token securely if provided
|
||||
let hasRefreshToken = secureStorage.storeRefreshToken(serverConnectionConfigId: id ?? "", refreshToken: refreshToken)
|
||||
logger.log("Refresh token secured = \(hasRefreshToken)")
|
||||
}
|
||||
|
||||
if id == nil {
|
||||
id = "\(address)@\(username)".toBase64()
|
||||
AbsLogger.info(message: "Refresh token secured = \(hasRefreshToken)")
|
||||
}
|
||||
|
||||
let config = ServerConnectionConfig()
|
||||
@@ -148,15 +147,16 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
let items = Database.shared.getLocalLibraryItems()
|
||||
call.resolve([ "value": try items.asDictionaryArray()])
|
||||
} catch(let exception) {
|
||||
logger.error("error while readling local library items")
|
||||
AbsLogger.error(message: "error reading local library items \(exception)")
|
||||
debugPrint(exception)
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func getLocalLibraryItem(_ call: CAPPluginCall) {
|
||||
let id = call.getString("id") ?? ""
|
||||
do {
|
||||
let item = Database.shared.getLocalLibraryItem(localLibraryItemId: call.getString("id") ?? "")
|
||||
let item = Database.shared.getLocalLibraryItem(localLibraryItemId: id)
|
||||
switch item {
|
||||
case .some(let foundItem):
|
||||
call.resolve(try foundItem.asDictionary())
|
||||
@@ -164,7 +164,7 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
call.resolve()
|
||||
}
|
||||
} catch(let exception) {
|
||||
logger.error("error while readling local library items")
|
||||
AbsLogger.error(message: "error reading local library item[\(id)] \(exception)")
|
||||
debugPrint(exception)
|
||||
call.resolve()
|
||||
}
|
||||
@@ -180,8 +180,7 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
call.resolve()
|
||||
}
|
||||
} catch(let exception) {
|
||||
logger.error("error while readling local library items")
|
||||
debugPrint(exception)
|
||||
AbsLogger.error(message: "error while readling local library items: \(exception)", error: exception)
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
@@ -194,8 +193,7 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
do {
|
||||
call.resolve([ "value": try Database.shared.getAllLocalMediaProgress().asDictionaryArray() ])
|
||||
} catch {
|
||||
logger.error("Error while loading local media progress")
|
||||
debugPrint(error)
|
||||
AbsLogger.error(message: "Error while loading local media progress", error: error)
|
||||
call.resolve(["value": []])
|
||||
}
|
||||
}
|
||||
@@ -212,7 +210,7 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
|
||||
@objc func syncLocalSessionsWithServer(_ call: CAPPluginCall) {
|
||||
let isFirstSync = call.getBool("isFirstSync", false)
|
||||
logger.log("syncLocalSessionsWithServer: Starting (First sync: \(isFirstSync))")
|
||||
AbsLogger.info(message: "Starting syncLocalSessionsWithServer isFirstSync=\(isFirstSync)")
|
||||
guard Store.serverConfig != nil else {
|
||||
call.reject("syncLocalSessionsWithServer not connected to server")
|
||||
return call.resolve()
|
||||
@@ -244,12 +242,13 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
return
|
||||
}
|
||||
|
||||
logger.log("syncServerMediaProgressWithLocalMediaProgress: Saving local media progress")
|
||||
AbsLogger.info(message: "Saving local media progress \(serverMediaProgress)")
|
||||
try localMediaProgress.updateFromServerMediaProgress(serverMediaProgress)
|
||||
|
||||
call.resolve(try localMediaProgress.asDictionary())
|
||||
} catch {
|
||||
call.reject("Failed to sync media progress")
|
||||
AbsLogger.error(message: "Failed to sync: \(error)")
|
||||
debugPrint(error)
|
||||
}
|
||||
}
|
||||
@@ -264,7 +263,7 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
localMediaProgressId += "-\(localEpisodeId ?? "")"
|
||||
}
|
||||
|
||||
logger.log("updateLocalMediaProgressFinished \(localMediaProgressId) | Is Finished: \(isFinished)")
|
||||
AbsLogger.info(message: "\(localMediaProgressId): isFinished=\(isFinished)")
|
||||
|
||||
do {
|
||||
let localMediaProgress = try LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localMediaProgressId, localLibraryItemId: localLibraryItemId, localEpisodeId: localEpisodeId)
|
||||
@@ -337,7 +336,7 @@ public class AbsDatabase: CAPPlugin, CAPBridgedPlugin {
|
||||
let ebookLocation = call.getString("ebookLocation", "")
|
||||
let ebookProgress = call.getDouble("ebookProgress", 0.0)
|
||||
|
||||
logger.log("updateLocalEbookProgress \(localLibraryItemId ?? "Unknown") | ebookLocation: \(ebookLocation) | ebookProgress: \(ebookProgress)")
|
||||
AbsLogger.info(message: "\(localLibraryItemId ?? "Unknown"): ebookLocation=\(ebookLocation) ebookProgress=\(ebookProgress)")
|
||||
|
||||
do {
|
||||
let localMediaProgress = try LocalMediaProgress.fetchOrCreateLocalMediaProgress(localMediaProgressId: localLibraryItemId, localLibraryItemId: localLibraryItemId, localEpisodeId: nil)
|
||||
|
||||
@@ -19,8 +19,6 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
|
||||
static private let downloadsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
|
||||
private let logger = AppLogger(category: "AbsDownloader")
|
||||
|
||||
private lazy var session: URLSession = {
|
||||
let config = URLSessionConfiguration.background(withIdentifier: "AbsDownloader")
|
||||
let queue = OperationQueue()
|
||||
@@ -104,7 +102,7 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
private func handleDownloadTaskUpdate(downloadTask: URLSessionTask, progressHandler: DownloadProgressHandler) {
|
||||
do {
|
||||
guard let downloadItemPartId = downloadTask.taskDescription else { throw LibraryItemDownloadError.noTaskDescription }
|
||||
logger.log("Received download update for \(downloadItemPartId)")
|
||||
AbsLogger.info(message: "Received download update for \(downloadItemPartId)")
|
||||
|
||||
// Find the download item
|
||||
let downloadItem = Database.shared.getDownloadItem(downloadItemPartId: downloadItemPartId)
|
||||
@@ -119,7 +117,7 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
try progressHandler(downloadItem, part)
|
||||
try? self.notifyListeners("onDownloadItemPartUpdate", data: part.asDictionary())
|
||||
} catch {
|
||||
logger.error("Error while processing progress")
|
||||
AbsLogger.error(message: "Error while processing progress")
|
||||
debugPrint(error)
|
||||
}
|
||||
|
||||
@@ -130,7 +128,7 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
}
|
||||
self.notifyDownloadProgress()
|
||||
} catch {
|
||||
logger.error("DownloadItemError")
|
||||
AbsLogger.error(message: "DownloadItemError")
|
||||
debugPrint(error)
|
||||
}
|
||||
}
|
||||
@@ -138,18 +136,18 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
// We want to handle updating the UI in the background and throttled so we don't overload the UI with progress updates
|
||||
private func notifyDownloadProgress() {
|
||||
if self.monitoringProgressTimer?.isValid ?? false {
|
||||
logger.log("Already monitoring progress, no need to start timer again")
|
||||
AbsLogger.info(message: "Already monitoring progress, no need to start timer again")
|
||||
} else {
|
||||
DispatchQueue.runOnMainQueue {
|
||||
self.monitoringProgressTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true, block: { [unowned self] t in
|
||||
self.logger.log("Starting monitoring download progress...")
|
||||
AbsLogger.info(message: "Starting monitoring download progress...")
|
||||
|
||||
// Fetch active downloads in a thread-safe way
|
||||
func fetchActiveDownloads() -> [String: DownloadItem]? {
|
||||
self.progressStatusQueue.sync {
|
||||
let activeDownloads = self.downloadItemProgress
|
||||
if activeDownloads.isEmpty {
|
||||
logger.log("Finishing monitoring download progress...")
|
||||
AbsLogger.info(message: "Finishing monitoring download progress...")
|
||||
t.invalidate()
|
||||
}
|
||||
return activeDownloads
|
||||
@@ -184,7 +182,7 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
|
||||
if ( downloadItem.didDownloadSuccessfully() ) {
|
||||
ApiClient.getLibraryItemWithProgress(libraryItemId: downloadItem.libraryItemId!, episodeId: downloadItem.episodeId) { [weak self] libraryItem in
|
||||
guard let libraryItem = libraryItem else { self?.logger.error("LibraryItem not found"); return }
|
||||
guard let libraryItem = libraryItem else { AbsLogger.error(message: "LibraryItem not found"); return }
|
||||
let localDirectory = libraryItem.id
|
||||
var coverFile: String?
|
||||
|
||||
@@ -231,12 +229,12 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
var episodeId = call.getString("episodeId")
|
||||
if ( episodeId == "null" ) { episodeId = nil }
|
||||
|
||||
logger.log("Download library item \(libraryItemId ?? "N/A") / episode \(episodeId ?? "N/A")")
|
||||
AbsLogger.info(message: "Download library item \(libraryItemId ?? "N/A") / episode \(episodeId ?? "N/A")")
|
||||
guard let libraryItemId = libraryItemId else { return call.resolve(["error": "libraryItemId not specified"]) }
|
||||
|
||||
ApiClient.getLibraryItemWithProgress(libraryItemId: libraryItemId, episodeId: episodeId) { [weak self] libraryItem in
|
||||
if let libraryItem = libraryItem {
|
||||
self?.logger.log("Got library item from server \(libraryItem.id)")
|
||||
AbsLogger.info(message: "Got library item from server \(libraryItem.id)")
|
||||
do {
|
||||
if let episodeId = episodeId {
|
||||
// Download a podcast episode
|
||||
@@ -317,7 +315,7 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
}
|
||||
|
||||
private func startLibraryItemTrackDownload(downloadItemId: String, item: LibraryItem, position: Int, track: AudioTrack, episode: PodcastEpisode?) throws -> DownloadItemPartTask {
|
||||
logger.log("TRACK \(track.contentUrl!)")
|
||||
AbsLogger.info(message: "TRACK \(track.contentUrl!)")
|
||||
|
||||
// If we don't name metadata, then we can't proceed
|
||||
guard let filename = track.metadata?.filename else {
|
||||
@@ -393,10 +391,10 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
|
||||
private func createLibraryItemFileDirectory(item: LibraryItem) throws -> String {
|
||||
let itemDirectory = item.id
|
||||
logger.log("ITEM DIR \(itemDirectory)")
|
||||
AbsLogger.info(message: "ITEM DIR \(itemDirectory)")
|
||||
|
||||
guard AbsDownloader.itemDownloadFolder(path: itemDirectory) != nil else {
|
||||
logger.error("Failed to CREATE LI DIRECTORY \(itemDirectory)")
|
||||
AbsLogger.error(message: "Failed to CREATE LI DIRECTORY \(itemDirectory)")
|
||||
throw LibraryItemDownloadError.failedDirectory
|
||||
}
|
||||
|
||||
@@ -418,7 +416,7 @@ public class AbsDownloader: CAPPlugin, CAPBridgedPlugin, URLSessionDownloadDeleg
|
||||
|
||||
return itemFolder
|
||||
} catch {
|
||||
AppLogger().error("Failed to CREATE LI DIRECTORY \(error)")
|
||||
AbsLogger.error(message: "Failed to CREATE LI DIRECTORY \(error)", error: error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,10 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
CAPPluginMethod(name: "deleteTrackFromItem", returnType: CAPPluginReturnPromise)
|
||||
]
|
||||
|
||||
private let logger = AppLogger(category: "AbsFileSystem")
|
||||
|
||||
@objc func selectFolder(_ call: CAPPluginCall) {
|
||||
let mediaType = call.getString("mediaType")
|
||||
|
||||
logger.log("Select Folder for media type \(mediaType ?? "UNSET")")
|
||||
AbsLogger.info(message: "Select Folder for media type \(mediaType ?? "UNSET")")
|
||||
|
||||
call.unavailable("Not available on iOS")
|
||||
}
|
||||
@@ -36,7 +34,7 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
@objc func checkFolderPermission(_ call: CAPPluginCall) {
|
||||
let folderUrl = call.getString("folderUrl")
|
||||
|
||||
logger.log("checkFolderPermission for folder \(folderUrl ?? "UNSET")")
|
||||
AbsLogger.info(message: "checkFolderPermission for folder \(folderUrl ?? "UNSET")")
|
||||
|
||||
call.unavailable("Not available on iOS")
|
||||
}
|
||||
@@ -45,7 +43,7 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
let folderId = call.getString("folderId")
|
||||
let forceAudioProbe = call.getBool("forceAudioProbe", false)
|
||||
|
||||
logger.log("scanFolder \(folderId ?? "UNSET") | Force Probe = \(forceAudioProbe)")
|
||||
AbsLogger.info(message: "scanFolder \(folderId ?? "UNSET") | Force Probe = \(forceAudioProbe)")
|
||||
|
||||
call.unavailable("Not available on iOS")
|
||||
}
|
||||
@@ -53,7 +51,7 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
@objc func removeFolder(_ call: CAPPluginCall) {
|
||||
let folderId = call.getString("folderId")
|
||||
|
||||
logger.log("removeFolder \(folderId ?? "UNSET")")
|
||||
AbsLogger.info(message: "removeFolder \(folderId ?? "UNSET")")
|
||||
|
||||
call.unavailable("Not available on iOS")
|
||||
}
|
||||
@@ -61,7 +59,7 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
@objc func removeLocalLibraryItem(_ call: CAPPluginCall) {
|
||||
let localLibraryItemId = call.getString("localLibraryItemId")
|
||||
|
||||
logger.log("removeLocalLibraryItem \(localLibraryItemId ?? "UNSET")")
|
||||
AbsLogger.info(message: "removeLocalLibraryItem \(localLibraryItemId ?? "UNSET")")
|
||||
|
||||
call.unavailable("Not available on iOS")
|
||||
}
|
||||
@@ -70,7 +68,7 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
let localLibraryItemId = call.getString("localLibraryItemId")
|
||||
let forceAudioProbe = call.getBool("forceAudioProbe", false)
|
||||
|
||||
logger.log("scanLocalLibraryItem \(localLibraryItemId ?? "UNSET") | Force Probe = \(forceAudioProbe)")
|
||||
AbsLogger.info(message: "scanLocalLibraryItem \(localLibraryItemId ?? "UNSET") | Force Probe = \(forceAudioProbe)")
|
||||
|
||||
call.unavailable("Not available on iOS")
|
||||
}
|
||||
@@ -79,7 +77,7 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
let localLibraryItemId = call.getString("id")
|
||||
let contentUrl = call.getString("contentUrl")
|
||||
|
||||
logger.log("deleteItem \(localLibraryItemId ?? "UNSET") url \(contentUrl ?? "UNSET")")
|
||||
AbsLogger.info(message: "deleteItem \(localLibraryItemId ?? "UNSET") url \(contentUrl ?? "UNSET")")
|
||||
|
||||
var success = false
|
||||
do {
|
||||
@@ -89,7 +87,7 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
success = true
|
||||
}
|
||||
} catch {
|
||||
logger.error("Failed to delete \(error)")
|
||||
AbsLogger.error(message: "Failed to delete \(error)")
|
||||
success = false
|
||||
}
|
||||
|
||||
@@ -100,7 +98,7 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
let localLibraryItemId = call.getString("id")
|
||||
let trackLocalFileId = call.getString("trackLocalFileId")
|
||||
|
||||
logger.log("deleteTrackFromItem \(localLibraryItemId ?? "UNSET") track file \(trackLocalFileId ?? "UNSET")")
|
||||
AbsLogger.info(message: "deleteTrackFromItem \(localLibraryItemId ?? "UNSET") track file \(trackLocalFileId ?? "UNSET")")
|
||||
|
||||
var success = false
|
||||
if let localLibraryItemId = localLibraryItemId, let trackLocalFileId = trackLocalFileId, let item = Database.shared.getLocalLibraryItem(localLibraryItemId: localLibraryItemId) {
|
||||
@@ -120,12 +118,12 @@ public class AbsFileSystem: CAPPlugin, CAPBridgedPlugin {
|
||||
success = true
|
||||
}
|
||||
} catch {
|
||||
logger.error("Failed to delete \(error)")
|
||||
AbsLogger.error(message: "Failed to delete \(error)")
|
||||
success = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
logger.error("Failed to delete \(error)")
|
||||
AbsLogger.error(message: "Failed to delete \(error)")
|
||||
success = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
import Foundation
|
||||
import Capacitor
|
||||
|
||||
enum AbsLogLevel {
|
||||
case info
|
||||
case warn
|
||||
case error
|
||||
}
|
||||
|
||||
func fileName(file: String = #file) -> String {
|
||||
return (file as NSString).lastPathComponent
|
||||
}
|
||||
|
||||
@objc(AbsLogger)
|
||||
public class AbsLogger: CAPPlugin, CAPBridgedPlugin {
|
||||
public var identifier = "AbsLoggerPlugin"
|
||||
@@ -19,29 +29,96 @@ public class AbsLogger: CAPPlugin, CAPBridgedPlugin {
|
||||
CAPPluginMethod(name: "clearLogs", returnType: CAPPluginReturnPromise)
|
||||
]
|
||||
|
||||
private let logger = AppLogger(category: "AbsLogger")
|
||||
private static let shared: AbsLogger = {
|
||||
AbsLogger()
|
||||
}()
|
||||
|
||||
public static func info(_ tag: String = #file, message: String) {
|
||||
try? shared.info(tag: fileName(file: tag), message: message)
|
||||
}
|
||||
|
||||
public static func error(_ tag: String = #file, message: String, error: Error? = nil) {
|
||||
try? shared.error(tag: fileName(file: tag), message: message)
|
||||
}
|
||||
|
||||
private let loggers: Dictionary<String, AppLogger> = [:]
|
||||
|
||||
private func _logger(_ tag: String) -> AppLogger {
|
||||
return loggers[tag, default: AppLogger(category: tag)]
|
||||
}
|
||||
|
||||
private func saveLogEntry(_ level: AbsLogLevel, tag: String, message: String) throws {
|
||||
let entry = LogEntry()
|
||||
entry.tag = tag
|
||||
entry.message = message
|
||||
entry.level = "\(level)"
|
||||
entry.timestamp = Int(Date().timeIntervalSince1970 * 1000)
|
||||
try Database.shared.saveLog(entry)
|
||||
|
||||
self.notifyListeners("onLog", data: [
|
||||
"id": entry.id,
|
||||
"tag": entry.tag,
|
||||
"message": entry.message,
|
||||
"level": entry.level,
|
||||
"timestamp": entry.timestamp
|
||||
])
|
||||
}
|
||||
|
||||
public func info(tag: String = "\(#file)", message: String) throws {
|
||||
_logger(tag).log("[\(tag)] \(message)")
|
||||
try saveLogEntry(.info, tag: tag, message: message)
|
||||
}
|
||||
|
||||
public func error(tag: String = "\(#file)", message: String, error: Error? = nil) throws {
|
||||
_logger(tag).error("[\(tag)] \(message)")
|
||||
if let error = error { _logger(tag).error(error) }
|
||||
try saveLogEntry(.error, tag: tag, message: message)
|
||||
}
|
||||
|
||||
@objc func info(_ call: CAPPluginCall) {
|
||||
let message = call.getString("message") ?? ""
|
||||
let tag = call.getString("tag") ?? ""
|
||||
|
||||
logger.log("[\(tag)] \(message)")
|
||||
call.resolve()
|
||||
|
||||
do {
|
||||
try info(tag: tag, message: message)
|
||||
call.resolve()
|
||||
} catch {
|
||||
call.reject("Failed to log \(message)", "101", error)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@objc func error(_ call: CAPPluginCall) {
|
||||
let message = call.getString("message") ?? ""
|
||||
let tag = call.getString("tag") ?? ""
|
||||
|
||||
logger.error("[\(tag)] \(message)")
|
||||
call.resolve()
|
||||
|
||||
do {
|
||||
try error(tag: tag, message: message)
|
||||
call.resolve()
|
||||
} catch {
|
||||
call.reject("Failed to log \(message)", "101", error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc func getAllLogs(_ call: CAPPluginCall) {
|
||||
call.unimplemented("Not implemented on iOS")
|
||||
do {
|
||||
let logs = Database.shared.getAllLogs()
|
||||
call.resolve([ "value": try logs.asDictionaryArray()])
|
||||
} catch {
|
||||
call.reject("Failed to get logs", "100", error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc func clearLogs(_ call: CAPPluginCall) {
|
||||
call.unimplemented("Not implemented on iOS")
|
||||
do {
|
||||
try Database.shared.clearLogs()
|
||||
call.resolve()
|
||||
} catch {
|
||||
call.reject("Failed to clear logs", "100", error)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// LogEntry.swift
|
||||
// Audiobookshelf
|
||||
//
|
||||
// Created by Christopher Jensen-Reimann on 9/3/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RealmSwift
|
||||
|
||||
class LogEntry: Object, Codable {
|
||||
@Persisted(primaryKey: true) var id: String = UUID().uuidString
|
||||
@Persisted var tag: String = ""
|
||||
@Persisted var level: String = ""
|
||||
@Persisted var message: String = ""
|
||||
@Persisted var timestamp: Int = 0
|
||||
}
|
||||
@@ -26,7 +26,6 @@ enum PlayerStatus: Int {
|
||||
|
||||
class AudioPlayer: NSObject {
|
||||
internal let queue = DispatchQueue(label: "ABSAudioPlayerQueue")
|
||||
internal let logger = AppLogger(category: "AudioPlayer")
|
||||
|
||||
private var status: PlayerStatus
|
||||
internal var rateManager: AudioPlayerRateManager
|
||||
@@ -81,7 +80,7 @@ class AudioPlayer: NSObject {
|
||||
|
||||
let playbackSession = self.getPlaybackSession()
|
||||
guard let playbackSession = playbackSession else {
|
||||
logger.error("Failed to fetch playback session. Player will not initialize")
|
||||
AbsLogger.error(message:"Failed to fetch playback session. Player will not initialize")
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
|
||||
return
|
||||
}
|
||||
@@ -115,10 +114,10 @@ class AudioPlayer: NSObject {
|
||||
}
|
||||
|
||||
self.currentTrackIndex = getItemIndexForTime(time: playbackSession.currentTime)
|
||||
logger.log("Starting track index \(self.currentTrackIndex) for start time \(playbackSession.currentTime)")
|
||||
AbsLogger.info(message:"Starting track index \(self.currentTrackIndex) for start time \(playbackSession.currentTime)")
|
||||
|
||||
let playerItems = self.allPlayerItems[self.currentTrackIndex..<self.allPlayerItems.count]
|
||||
logger.log("Setting player items \(playerItems.count)")
|
||||
AbsLogger.info(message:"Setting player items \(playerItems.count)")
|
||||
|
||||
for item in Array(playerItems) {
|
||||
self.audioPlayer.insert(item, after:self.audioPlayer.items().last)
|
||||
@@ -128,7 +127,7 @@ class AudioPlayer: NSObject {
|
||||
setupQueueObserver()
|
||||
setupQueueItemStatusObserver()
|
||||
|
||||
logger.log("Audioplayer ready")
|
||||
AbsLogger.info(message:"Audioplayer ready")
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -146,8 +145,7 @@ class AudioPlayer: NSObject {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(false)
|
||||
} catch {
|
||||
logger.error("Failed to set AVAudioSession inactive")
|
||||
logger.error(error)
|
||||
AbsLogger.error(message: "Failed to set AVAudioSession inactive", error: error)
|
||||
}
|
||||
|
||||
self.removeAudioSessionNotifications()
|
||||
@@ -253,14 +251,14 @@ class AudioPlayer: NSObject {
|
||||
self.audioPlayer.currentItem.map { item in
|
||||
self.currentTrackIndex = self.allPlayerItems.firstIndex(of:item) ?? 0
|
||||
if (self.currentTrackIndex != prevTrackIndex) {
|
||||
self.logger.log("New Current track index \(self.currentTrackIndex)")
|
||||
AbsLogger.info(message:"New Current track index \(self.currentTrackIndex)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setupQueueItemStatusObserver() {
|
||||
logger.log("queueStatusObserver: Setting up")
|
||||
AbsLogger.info(message:"queueStatusObserver: Setting up")
|
||||
|
||||
// Listen for player item updates
|
||||
self.queueItemStatusObserver?.invalidate()
|
||||
@@ -275,13 +273,13 @@ class AudioPlayer: NSObject {
|
||||
}
|
||||
|
||||
private func handleQueueItemStatus(playerItem: AVPlayerItem) {
|
||||
logger.log("queueStatusObserver: Current item status changed")
|
||||
AbsLogger.info(message:"queueStatusObserver: Current item status changed")
|
||||
guard let playbackSession = self.getPlaybackSession() else {
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
|
||||
return
|
||||
}
|
||||
if (playerItem.status == .readyToPlay) {
|
||||
logger.log("queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
|
||||
AbsLogger.info(message:"queueStatusObserver: Current Item Ready to play. PlayWhenReady: \(self.playWhenReady)")
|
||||
|
||||
// Seek the player before initializing, so a currentTime of 0 does not appear in MediaProgress / session
|
||||
let firstReady = self.status == .uninitialized
|
||||
@@ -300,7 +298,7 @@ class AudioPlayer: NSObject {
|
||||
self.status = .paused
|
||||
}
|
||||
} else if (playerItem.status == .failed) {
|
||||
logger.error("queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")")
|
||||
AbsLogger.error(message:"queueStatusObserver: FAILED \(playerItem.error?.localizedDescription ?? "")")
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.failed.rawValue), object: nil)
|
||||
}
|
||||
}
|
||||
@@ -332,7 +330,7 @@ class AudioPlayer: NSObject {
|
||||
}
|
||||
|
||||
private func resumePlayback() {
|
||||
logger.log("PLAY: Resuming playback")
|
||||
AbsLogger.info(message:"PLAY: Resuming playback")
|
||||
|
||||
self.markAudioSessionAs(active: true)
|
||||
DispatchQueue.runOnMainQueue {
|
||||
@@ -351,7 +349,7 @@ class AudioPlayer: NSObject {
|
||||
public func pause() {
|
||||
guard self.isInitialized() else { return }
|
||||
|
||||
logger.log("PAUSE: Pausing playback")
|
||||
AbsLogger.info(message:"PAUSE: Pausing playback")
|
||||
DispatchQueue.runOnMainQueue {
|
||||
self.audioPlayer.pause()
|
||||
}
|
||||
@@ -371,7 +369,7 @@ class AudioPlayer: NSObject {
|
||||
public func startFadeOut() {
|
||||
guard self.isInitialized() else { return }
|
||||
guard let currentTime = self.getCurrentTime() else { return }
|
||||
logger.log("fadeOut: Fading out playback")
|
||||
AbsLogger.info(message:"fadeOut: Fading out playback")
|
||||
|
||||
// Define fade parameters.
|
||||
let fadeDuration: Float = 60.0 // total fade duration in seconds
|
||||
@@ -409,7 +407,7 @@ class AudioPlayer: NSObject {
|
||||
// Ensure volume is exactly zero and end fade.
|
||||
self.audioPlayer.volume = targetVolume
|
||||
t.invalidate()
|
||||
self.logger.log("Fadeout: Fade complete, pausing playback")
|
||||
AbsLogger.info(message:"Fadeout: Fade complete, pausing playback")
|
||||
self.pause()
|
||||
self.audioPlayer.volume = initialVolume
|
||||
self.seek(currentTime, from: "fadeOut")
|
||||
@@ -419,12 +417,12 @@ class AudioPlayer: NSObject {
|
||||
}
|
||||
|
||||
public func seek(_ to: Double, from: String) {
|
||||
logger.log("SEEK: Seek to \(to) from \(from)")
|
||||
AbsLogger.info(message:"SEEK: Seek to \(to) from \(from)")
|
||||
|
||||
guard let playbackSession = self.getPlaybackSession() else { return }
|
||||
|
||||
let indexOfSeek = getItemIndexForTime(time: to)
|
||||
logger.log("SEEK: Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)")
|
||||
AbsLogger.info(message:"SEEK: Seek to index \(indexOfSeek) | Current index \(self.currentTrackIndex)")
|
||||
|
||||
if self.audioPlayer.currentItem == nil {
|
||||
self.currentTrackIndex = indexOfSeek
|
||||
@@ -488,16 +486,16 @@ class AudioPlayer: NSObject {
|
||||
let currentTrack = playbackSession.audioTracks[self.currentTrackIndex]
|
||||
let ctso = currentTrack.startOffset ?? 0.0
|
||||
let trackEnd = ctso + currentTrack.duration
|
||||
logger.log("SEEK: Seeking in current item \(to) (track START = \(ctso) END = \(trackEnd))")
|
||||
AbsLogger.info(message:"SEEK: Seeking in current item \(to) (track START = \(ctso) END = \(trackEnd))")
|
||||
|
||||
let boundedTime = min(max(to, ctso), trackEnd)
|
||||
let seekTime = boundedTime - ctso
|
||||
|
||||
DispatchQueue.runOnMainQueue {
|
||||
self.audioPlayer.seek(to: CMTime(seconds: seekTime, preferredTimescale: 1000)) { [weak self] completed in
|
||||
self?.logger.log("SEEK: Completion handler called")
|
||||
AbsLogger.info(message:"SEEK: Completion handler called")
|
||||
guard completed else {
|
||||
self?.logger.log("SEEK: WARNING: seeking not completed (to \(seekTime)")
|
||||
AbsLogger.info(message:"SEEK: WARNING: seeking not completed (to \(seekTime)")
|
||||
return
|
||||
}
|
||||
guard let self = self else { return }
|
||||
@@ -574,7 +572,7 @@ class AudioPlayer: NSObject {
|
||||
} else if (playbackSession.playMethod == PlayMethod.local.rawValue) {
|
||||
guard let localFile = track.getLocalFile() else {
|
||||
// Worst case we can stream the file
|
||||
logger.log("Unable to play local file. Resulting to streaming \(track.localFileId ?? "Unknown")")
|
||||
AbsLogger.info(message:"Unable to play local file. Resulting to streaming \(track.localFileId ?? "Unknown")")
|
||||
let urlstr = "\(Store.serverConfig!.address)/api/items/\(itemId)/file/\(ino)?token=\(Store.serverConfig!.token)"
|
||||
let url = URL(string: urlstr)!
|
||||
return AVURLAsset(url: url)
|
||||
@@ -594,8 +592,7 @@ class AudioPlayer: NSObject {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .spokenAudio)
|
||||
} catch {
|
||||
logger.error("Failed to set AVAudioSession category")
|
||||
logger.error(error)
|
||||
AbsLogger.error(message: "Failed to set AVAudioSession category", error: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,7 +600,7 @@ class AudioPlayer: NSObject {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(active)
|
||||
} catch {
|
||||
logger.error("Failed to set audio session as active=\(active)")
|
||||
AbsLogger.error(message:"Failed to set audio session as active=\(active)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,7 +617,7 @@ class AudioPlayer: NSObject {
|
||||
let reasonValue = userInfo[AVAudioSessionInterruptionReasonKey] as? UInt ?? 0
|
||||
let reason = AVAudioSession.InterruptionReason(rawValue: reasonValue)
|
||||
if (reason == .appWasSuspended) {
|
||||
logger.log("AVAudioSession was suspended")
|
||||
AbsLogger.info(message:"AVAudioSession was suspended")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -812,14 +809,14 @@ class AudioPlayer: NSObject {
|
||||
if context == &playerContext {
|
||||
if keyPath == #keyPath(AVPlayer.currentItem) {
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
||||
logger.log("WARNING: Item ended")
|
||||
AbsLogger.info(message:"WARNING: Item ended")
|
||||
|
||||
if audioPlayer.currentItem == nil {
|
||||
// if the queue is rebuilding, we expect the current item may be nil
|
||||
if self.isRebuildingQueue {
|
||||
return
|
||||
}
|
||||
logger.log("Player ended or next item is nil, marking ended")
|
||||
AbsLogger.info(message:"Player ended or next item is nil, marking ended")
|
||||
self.markAudioSessionAs(active: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ extension AudioPlayer {
|
||||
}
|
||||
|
||||
public func setSleepTimer(secondsUntilSleep: Double) {
|
||||
logger.log("SLEEP TIMER: Sleeping in \(secondsUntilSleep) seconds")
|
||||
AbsLogger.info(message: "SLEEP TIMER: Sleeping in \(secondsUntilSleep) seconds")
|
||||
self.removeSleepTimer()
|
||||
self.sleepTimeRemaining = secondsUntilSleep
|
||||
|
||||
@@ -53,7 +53,7 @@ extension AudioPlayer {
|
||||
guard let currentTime = self.getCurrentTime() else { return }
|
||||
guard stopAt >= currentTime else { return }
|
||||
|
||||
logger.log("SLEEP TIMER: Scheduling for chapter end \(stopAt)")
|
||||
AbsLogger.info(message: "SLEEP TIMER: Scheduling for chapter end \(stopAt)")
|
||||
|
||||
// Schedule the observation time
|
||||
self.sleepTimeChapterStopAt = stopAt
|
||||
@@ -138,7 +138,7 @@ extension AudioPlayer {
|
||||
}
|
||||
|
||||
private func handleSleepEnd() {
|
||||
logger.log("SLEEP TIMER: Pausing audio")
|
||||
AbsLogger.info(message: "SLEEP TIMER: Pausing audio")
|
||||
self.pause()
|
||||
self.removeSleepTimer()
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@ import AVFoundation
|
||||
|
||||
@available(iOS 16.0, *)
|
||||
class DefaultedAudioPlayerRateManager: NSObject, AudioPlayerRateManager {
|
||||
internal let logger = AppLogger(category: "DefaultedAudioPlayerRateManager")
|
||||
|
||||
|
||||
internal var audioPlayer: AVPlayer
|
||||
|
||||
// MARK: - AudioPlayerRateManager
|
||||
|
||||
@@ -9,7 +9,6 @@ import Foundation
|
||||
import AVFoundation
|
||||
|
||||
class LegacyAudioPlayerRateManager: NSObject, AudioPlayerRateManager {
|
||||
internal let logger = AppLogger(category: "AudioPlayer")
|
||||
|
||||
internal var audioPlayer: AVPlayer
|
||||
|
||||
@@ -54,7 +53,7 @@ class LegacyAudioPlayerRateManager: NSObject, AudioPlayerRateManager {
|
||||
let playbackSpeedChanged = rate > 0.0 && rate != self.defaultRate && !(observed && rate == 1)
|
||||
|
||||
if self.audioPlayer.rate != rate {
|
||||
logger.log("setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
|
||||
AbsLogger.info(message: "setPlaybakRate rate changed from \(self.audioPlayer.rate) to \(rate)")
|
||||
DispatchQueue.runOnMainQueue {
|
||||
self.audioPlayer.rate = rate
|
||||
}
|
||||
@@ -73,7 +72,7 @@ class LegacyAudioPlayerRateManager: NSObject, AudioPlayerRateManager {
|
||||
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
if context == &managerContext {
|
||||
if keyPath == #keyPath(AVPlayer.rate) {
|
||||
logger.log("playerContext observer player rate")
|
||||
AbsLogger.info(message: "playerContext observer player rate")
|
||||
self.handlePlaybackRateChange(change?[.newKey] as? Float ?? 1.0, observed: true)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -14,8 +14,6 @@ class PlayerProgress {
|
||||
|
||||
private static var TIME_BETWEEN_SESSION_SYNC_IN_SECONDS = 15.0
|
||||
|
||||
private let logger = AppLogger(category: "PlayerProgress")
|
||||
|
||||
private init() {}
|
||||
|
||||
|
||||
@@ -30,8 +28,7 @@ class PlayerProgress {
|
||||
try await updateServerSessionFromLocalSession(session, rateLimitSync: !isStopping)
|
||||
}
|
||||
} catch {
|
||||
logger.error("Failed to syncFromPlayer")
|
||||
logger.error(error)
|
||||
AbsLogger.error(message: "Failed to syncFromPlayer \(error)", error: error)
|
||||
}
|
||||
await UIApplication.shared.endBackgroundTask(backgroundToken)
|
||||
}
|
||||
@@ -76,7 +73,7 @@ class PlayerProgress {
|
||||
|
||||
try localMediaProgress.updateFromPlaybackSession(session)
|
||||
|
||||
logger.log("Local progress saved to the database")
|
||||
AbsLogger.info(message:"Local progress saved to the database")
|
||||
|
||||
// Send the local progress back to front-end
|
||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.localProgress.rawValue), object: nil)
|
||||
@@ -126,7 +123,7 @@ class PlayerProgress {
|
||||
session = session.freeze()
|
||||
|
||||
guard safeToSync else { return }
|
||||
logger.log("Sending sessionId(\(session.id)) to server with currentTime(\(session.currentTime))")
|
||||
AbsLogger.info(message:"Sending sessionId(\(session.id)) to server with currentTime(\(session.currentTime))")
|
||||
|
||||
var success = false
|
||||
if session.isLocal {
|
||||
@@ -155,25 +152,25 @@ class PlayerProgress {
|
||||
|
||||
// TODO: Unused for now
|
||||
private func updateLocalSessionFromServerMediaProgress() async throws {
|
||||
logger.log("updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
|
||||
AbsLogger.info(message:"updateLocalSessionFromServerMediaProgress: Checking if local media progress was updated on server")
|
||||
guard let session = try Realm(queue: nil).objects(PlaybackSession.self).last(where: {
|
||||
$0.isActiveSession == true && $0.serverConnectionConfigId == Store.serverConfig?.id
|
||||
})?.freeze() else {
|
||||
logger.log("updateLocalSessionFromServerMediaProgress: Failed to get session")
|
||||
AbsLogger.info(message:"updateLocalSessionFromServerMediaProgress: Failed to get session")
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch the current progress
|
||||
let progress = await ApiClient.getMediaProgress(libraryItemId: session.libraryItemId!, episodeId: session.episodeId)
|
||||
guard let progress = progress else {
|
||||
logger.log("updateLocalSessionFromServerMediaProgress: No progress object")
|
||||
AbsLogger.info(message:"updateLocalSessionFromServerMediaProgress: No progress object")
|
||||
return
|
||||
}
|
||||
|
||||
// Determine which session is newer
|
||||
let serverLastUpdate = progress.lastUpdate
|
||||
guard let localLastUpdate = session.updatedAt else {
|
||||
logger.log("updateLocalSessionFromServerMediaProgress: No local session updatedAt")
|
||||
AbsLogger.info(message:"updateLocalSessionFromServerMediaProgress: No local session updatedAt")
|
||||
return
|
||||
}
|
||||
let serverCurrentTime = progress.currentTime
|
||||
@@ -184,16 +181,16 @@ class PlayerProgress {
|
||||
|
||||
// Update the session, if needed
|
||||
if serverIsNewerThanLocal && currentTimeIsDifferent {
|
||||
logger.log("updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
|
||||
AbsLogger.info(message:"updateLocalSessionFromServerMediaProgress: Server has newer time than local serverLastUpdate=\(serverLastUpdate) localLastUpdate=\(localLastUpdate)")
|
||||
guard let session = session.thaw() else { return }
|
||||
try session.update {
|
||||
session.currentTime = serverCurrentTime
|
||||
session.updatedAt = serverLastUpdate
|
||||
}
|
||||
logger.log("updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
|
||||
AbsLogger.info(message:"updateLocalSessionFromServerMediaProgress: Updated session currentTime newCurrentTime=\(serverCurrentTime) previousCurrentTime=\(localCurrentTime)")
|
||||
PlayerHandler.seek(amount: session.currentTime)
|
||||
} else {
|
||||
logger.log("updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
|
||||
AbsLogger.info(message:"updateLocalSessionFromServerMediaProgress: Local session does not need updating; local has latest progress")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import Foundation
|
||||
import Alamofire
|
||||
|
||||
class ApiClient {
|
||||
private static let logger = AppLogger(category: "ApiClient")
|
||||
private static let secureStorage = SecureStorage()
|
||||
|
||||
public static func getData(from url: URL, completion: @escaping (UIImage?) -> Void) {
|
||||
@@ -22,7 +21,7 @@ class ApiClient {
|
||||
|
||||
public static func postResource<T: Decodable>(endpoint: String, parameters: [String: Any], decodable: T.Type = T.self, callback: ((_ param: T) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
logger.error("Server config not set")
|
||||
AbsLogger.error(message: "Server config not set")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -35,7 +34,7 @@ class ApiClient {
|
||||
case .success(let obj):
|
||||
callback?(obj)
|
||||
case .failure(let error):
|
||||
logger.error("api request to \(endpoint) failed")
|
||||
AbsLogger.error(message: "api request to \(endpoint) failed")
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
@@ -43,7 +42,7 @@ class ApiClient {
|
||||
|
||||
public static func postResource<T: Encodable, U: Decodable>(endpoint: String, parameters: T, decodable: U.Type = U.self, callback: ((_ param: U) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
logger.error("Server config not set")
|
||||
AbsLogger.error(message: "Server config not set")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -56,7 +55,7 @@ class ApiClient {
|
||||
case .success(let obj):
|
||||
callback?(obj)
|
||||
case .failure(let error):
|
||||
logger.error("api request to \(endpoint) failed")
|
||||
AbsLogger.error(message: "api request to \(endpoint) failed")
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
@@ -72,7 +71,7 @@ class ApiClient {
|
||||
|
||||
public static func postResource<T:Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
logger.error("Server config not set")
|
||||
AbsLogger.error(message: "Server config not set")
|
||||
callback?(false)
|
||||
return
|
||||
}
|
||||
@@ -86,7 +85,7 @@ class ApiClient {
|
||||
case .success(_):
|
||||
callback?(true)
|
||||
case .failure(let error):
|
||||
logger.error("api request to \(endpoint) failed")
|
||||
AbsLogger.error(message: "api request to \(endpoint) failed")
|
||||
print(error)
|
||||
|
||||
callback?(false)
|
||||
@@ -96,7 +95,7 @@ class ApiClient {
|
||||
|
||||
public static func patchResource<T: Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
logger.error("Server config not set")
|
||||
AbsLogger.error(message: "Server config not set")
|
||||
callback?(false)
|
||||
return
|
||||
}
|
||||
@@ -110,7 +109,7 @@ class ApiClient {
|
||||
case .success(_):
|
||||
callback?(true)
|
||||
case .failure(let error):
|
||||
logger.error("api request to \(endpoint) failed")
|
||||
AbsLogger.error(message: "api request to \(endpoint) failed")
|
||||
print(error)
|
||||
callback?(false)
|
||||
}
|
||||
@@ -127,7 +126,7 @@ class ApiClient {
|
||||
|
||||
public static func getResource<T: Decodable>(endpoint: String, decodable: T.Type = T.self, callback: ((_ param: T?) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
logger.error("Server config not set")
|
||||
AbsLogger.error(message: "Server config not set")
|
||||
callback?(nil)
|
||||
return
|
||||
}
|
||||
@@ -141,7 +140,7 @@ class ApiClient {
|
||||
case .success(let obj):
|
||||
callback?(obj)
|
||||
case .failure(let error):
|
||||
logger.error("api request to \(endpoint) failed")
|
||||
AbsLogger.error(message: "api request to \(endpoint) failed")
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
@@ -160,22 +159,22 @@ class ApiClient {
|
||||
*/
|
||||
private static func handleTokenRefresh<T: Decodable>(originalRequest: DataRequest, endpoint: String, method: HTTPMethod, parameters: Any?, decodable: T.Type, callback: ((_ param: T?) -> Void)?) {
|
||||
guard let serverConfig = Store.serverConfig else {
|
||||
logger.error("handleTokenRefresh: No server config available")
|
||||
AbsLogger.error(message: "handleTokenRefresh: No server config available")
|
||||
callback?(nil)
|
||||
return
|
||||
}
|
||||
|
||||
logger.log("handleTokenRefresh: Attempting to refresh auth tokens for server \(serverConfig.name)")
|
||||
AbsLogger.info(message: "handleTokenRefresh: Attempting to refresh auth tokens for server \(serverConfig.name)")
|
||||
|
||||
// Get refresh token from secure storage
|
||||
guard let refreshToken = secureStorage.getRefreshToken(serverConnectionConfigId: serverConfig.id) else {
|
||||
logger.error("handleTokenRefresh: No refresh token available for server \(serverConfig.name)")
|
||||
AbsLogger.error(message: "handleTokenRefresh: No refresh token available for server \(serverConfig.name)")
|
||||
handleRefreshFailure()
|
||||
callback?(nil)
|
||||
return
|
||||
}
|
||||
|
||||
logger.log("handleTokenRefresh: Retrieved refresh token, attempting to refresh access token")
|
||||
AbsLogger.info(message: "handleTokenRefresh: Retrieved refresh token, attempting to refresh access token")
|
||||
|
||||
// Create refresh token request
|
||||
let refreshHeaders: HTTPHeaders = [
|
||||
@@ -190,23 +189,23 @@ class ApiClient {
|
||||
case .success(let refreshResponse):
|
||||
guard let user = refreshResponse.user,
|
||||
!user.accessToken.isEmpty else {
|
||||
logger.error("handleTokenRefresh: No access token in refresh response for server \(serverConfig.name)")
|
||||
AbsLogger.error(message: "handleTokenRefresh: No access token in refresh response for server \(serverConfig.name)")
|
||||
handleRefreshFailure()
|
||||
callback?(nil)
|
||||
return
|
||||
}
|
||||
|
||||
logger.log("handleTokenRefresh: Successfully obtained new access token")
|
||||
AbsLogger.info(message: "handleTokenRefresh: Successfully obtained new access token")
|
||||
|
||||
// Update tokens in secure storage and store
|
||||
updateTokens(newAccessToken: user.accessToken, newRefreshToken: user.refreshToken ?? refreshToken, serverConnectionConfigId: serverConfig.id)
|
||||
|
||||
// Retry the original request with the new access token
|
||||
logger.log("handleTokenRefresh: Retrying original request with new token")
|
||||
AbsLogger.info(message: "handleTokenRefresh: Retrying original request with new token")
|
||||
retryOriginalRequest(endpoint: endpoint, method: method, parameters: parameters, decodable: decodable, newAccessToken: user.accessToken, callback: callback)
|
||||
|
||||
case .failure(let error):
|
||||
logger.error("handleTokenRefresh: Refresh request failed for server \(serverConfig.name): \(error)")
|
||||
AbsLogger.error(message: "handleTokenRefresh: Refresh request failed for server \(serverConfig.name): \(error)")
|
||||
handleRefreshFailure()
|
||||
callback?(nil)
|
||||
}
|
||||
@@ -220,14 +219,14 @@ class ApiClient {
|
||||
// Update the refresh token in secure storage if it's new
|
||||
if newRefreshToken != secureStorage.getRefreshToken(serverConnectionConfigId: serverConnectionConfigId) {
|
||||
let hasStored = secureStorage.storeRefreshToken(serverConnectionConfigId: serverConnectionConfigId, refreshToken: newRefreshToken)
|
||||
logger.log("updateTokens: Updated refresh token in secure storage. Stored=\(hasStored)")
|
||||
AbsLogger.info(message: "updateTokens: Updated refresh token in secure storage. Stored=\(hasStored)")
|
||||
}
|
||||
|
||||
// Update access token on server connection config
|
||||
Database.shared.updateServerConnectionConfigToken(newToken: newAccessToken)
|
||||
logger.log("updateTokens: Updated access token in server connection config")
|
||||
AbsLogger.info(message: "updateTokens: Updated access token in server connection config")
|
||||
|
||||
logger.log("updateTokens: Successfully refreshed auth tokens for server \(Store.serverConfig?.name ?? "unknown")")
|
||||
AbsLogger.info(message: "updateTokens: Successfully refreshed auth tokens for server \(Store.serverConfig?.name ?? "unknown")")
|
||||
|
||||
// Notify webview frontend about token refresh
|
||||
if let callback = AbsDatabase.tokenRefreshCallback {
|
||||
@@ -241,7 +240,7 @@ class ApiClient {
|
||||
*/
|
||||
private static func retryOriginalRequest<T: Decodable>(endpoint: String, method: HTTPMethod, parameters: Any?, decodable: T.Type, newAccessToken: String, callback: ((_ param: T?) -> Void)?) {
|
||||
guard let serverConfig = Store.serverConfig else {
|
||||
logger.error("retryOriginalRequest: No server config available")
|
||||
AbsLogger.error(message: "retryOriginalRequest: No server config available")
|
||||
callback?(nil)
|
||||
return
|
||||
}
|
||||
@@ -270,7 +269,7 @@ class ApiClient {
|
||||
retryRequest = AF.request("\(serverConfig.address)/\(endpoint)", method: .patch, headers: headers)
|
||||
}
|
||||
default:
|
||||
logger.error("retryOriginalRequest: Unsupported method \(method)")
|
||||
AbsLogger.error(message: "retryOriginalRequest: Unsupported method \(method)")
|
||||
callback?(nil)
|
||||
return
|
||||
}
|
||||
@@ -282,7 +281,7 @@ class ApiClient {
|
||||
if let data = response.data, !data.isEmpty {
|
||||
// If it is a string return nil (e.g. express returns OK for 200 status codes)
|
||||
if let responseString = String(data: data, encoding: .utf8) {
|
||||
logger.log("retryOriginalRequest: Got string response '\(responseString)'")
|
||||
AbsLogger.info(message: "retryOriginalRequest: Got string response '\(responseString)'")
|
||||
callback?(nil)
|
||||
return
|
||||
}
|
||||
@@ -292,16 +291,16 @@ class ApiClient {
|
||||
let decodedObject = try JSONDecoder().decode(decodable, from: data)
|
||||
callback?(decodedObject)
|
||||
} catch {
|
||||
logger.error("retryOriginalRequest: JSON decode failed: \(error)")
|
||||
AbsLogger.error(message: "retryOriginalRequest: JSON decode failed: \(error)", error: error)
|
||||
callback?(nil)
|
||||
}
|
||||
} else {
|
||||
// Empty response
|
||||
logger.log("retryOriginalRequest: Empty response with success status \(statusCode)")
|
||||
AbsLogger.info(message: "retryOriginalRequest: Empty response with success status \(statusCode)")
|
||||
callback?(nil)
|
||||
}
|
||||
} else {
|
||||
logger.error("retryOriginalRequest: Request failed with status \(response.response?.statusCode ?? 0)")
|
||||
AbsLogger.error(message: "retryOriginalRequest: Request failed with status \(response.response?.statusCode ?? 0)")
|
||||
callback?(nil)
|
||||
}
|
||||
}
|
||||
@@ -312,7 +311,7 @@ class ApiClient {
|
||||
* This will clear the current server connection and notify webview
|
||||
*/
|
||||
private static func handleRefreshFailure() {
|
||||
logger.log("handleRefreshFailure: Token refresh failed, clearing session")
|
||||
AbsLogger.info(message: "handleRefreshFailure: Token refresh failed, clearing session")
|
||||
|
||||
// Clear the current server connection
|
||||
Store.serverConfig = nil
|
||||
@@ -332,7 +331,7 @@ class ApiClient {
|
||||
|
||||
public static func getResourceWithTokenRefresh<T: Decodable>(endpoint: String, decodable: T.Type = T.self, callback: ((_ param: T?) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
logger.error("Server config not set")
|
||||
AbsLogger.error(message: "Server config not set")
|
||||
callback?(nil)
|
||||
return
|
||||
}
|
||||
@@ -345,14 +344,14 @@ class ApiClient {
|
||||
|
||||
request.responseDecodable(of: decodable) { response in
|
||||
if let statusCode = response.response?.statusCode, statusCode == 401 {
|
||||
logger.log("getResourceWithTokenRefresh: 401 Unauthorized for request to \(endpoint) - attempting token refresh")
|
||||
AbsLogger.info(message: "getResourceWithTokenRefresh: 401 Unauthorized for request to \(endpoint) - attempting token refresh")
|
||||
handleTokenRefresh(originalRequest: request, endpoint: endpoint, method: .get, parameters: nil, decodable: decodable, callback: callback)
|
||||
} else {
|
||||
switch response.result {
|
||||
case .success(let obj):
|
||||
callback?(obj)
|
||||
case .failure(let error):
|
||||
logger.error("api request to \(endpoint) failed")
|
||||
AbsLogger.error(message: "api request to \(endpoint) failed")
|
||||
print(error)
|
||||
callback?(nil)
|
||||
}
|
||||
@@ -362,7 +361,7 @@ class ApiClient {
|
||||
|
||||
public static func postResourceWithTokenRefresh<T: Encodable, U: Decodable>(endpoint: String, parameters: T, decodable: U.Type = U.self, callback: ((_ param: U?) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
logger.error("Server config not set")
|
||||
AbsLogger.error(message: "Server config not set")
|
||||
callback?(nil)
|
||||
return
|
||||
}
|
||||
@@ -375,14 +374,14 @@ class ApiClient {
|
||||
|
||||
request.responseDecodable(of: decodable) { response in
|
||||
if let statusCode = response.response?.statusCode, statusCode == 401 {
|
||||
logger.log("postResourceWithTokenRefresh: 401 Unauthorized for request to \(endpoint) - attempting token refresh")
|
||||
AbsLogger.info(message: "postResourceWithTokenRefresh: 401 Unauthorized for request to \(endpoint) - attempting token refresh")
|
||||
handleTokenRefresh(originalRequest: request, endpoint: endpoint, method: .post, parameters: parameters, decodable: decodable, callback: callback)
|
||||
} else {
|
||||
switch response.result {
|
||||
case .success(let obj):
|
||||
callback?(obj)
|
||||
case .failure(let error):
|
||||
logger.error("api request to \(endpoint) failed")
|
||||
AbsLogger.error(message: "api request to \(endpoint) failed")
|
||||
print(error)
|
||||
callback?(nil)
|
||||
}
|
||||
@@ -395,7 +394,7 @@ class ApiClient {
|
||||
*/
|
||||
public static func postResourceWithTokenRefresh<T: Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
logger.error("Server config not set")
|
||||
AbsLogger.error(message: "Server config not set")
|
||||
callback?(false)
|
||||
return
|
||||
}
|
||||
@@ -408,7 +407,7 @@ class ApiClient {
|
||||
|
||||
request.response { response in
|
||||
if let statusCode = response.response?.statusCode, statusCode == 401 {
|
||||
logger.log("postResourceWithTokenRefresh: 401 Unauthorized for request to \(endpoint) - attempting token refresh")
|
||||
AbsLogger.info(message: "postResourceWithTokenRefresh: 401 Unauthorized for request to \(endpoint) - attempting token refresh")
|
||||
handleTokenRefresh(originalRequest: request, endpoint: endpoint, method: .post, parameters: parameters, decodable: EmptyResponse.self) { result in
|
||||
callback?(result != nil)
|
||||
}
|
||||
@@ -417,7 +416,7 @@ class ApiClient {
|
||||
case .success(_):
|
||||
callback?(true)
|
||||
case .failure(let error):
|
||||
logger.error("api request to \(endpoint) failed")
|
||||
AbsLogger.error(message: "api request to \(endpoint) failed")
|
||||
print(error)
|
||||
callback?(false)
|
||||
}
|
||||
@@ -427,7 +426,7 @@ class ApiClient {
|
||||
|
||||
public static func patchResourceWithTokenRefresh<T: Encodable>(endpoint: String, parameters: T, callback: ((_ success: Bool) -> Void)?) {
|
||||
if (Store.serverConfig == nil) {
|
||||
logger.error("Server config not set")
|
||||
AbsLogger.error(message: "Server config not set")
|
||||
callback?(false)
|
||||
return
|
||||
}
|
||||
@@ -440,7 +439,7 @@ class ApiClient {
|
||||
|
||||
request.response { response in
|
||||
if let statusCode = response.response?.statusCode, statusCode == 401 {
|
||||
logger.log("patchResourceWithTokenRefresh: 401 Unauthorized for request to \(endpoint) - attempting token refresh")
|
||||
AbsLogger.info(message: "patchResourceWithTokenRefresh: 401 Unauthorized for request to \(endpoint) - attempting token refresh")
|
||||
handleTokenRefresh(originalRequest: request, endpoint: endpoint, method: .patch, parameters: parameters, decodable: EmptyResponse.self) { result in
|
||||
callback?(result != nil)
|
||||
}
|
||||
@@ -449,7 +448,7 @@ class ApiClient {
|
||||
case .success(_):
|
||||
callback?(true)
|
||||
case .failure(let error):
|
||||
logger.error("api request to \(endpoint) failed")
|
||||
AbsLogger.error(message: "api request to \(endpoint) failed")
|
||||
print(error)
|
||||
callback?(false)
|
||||
}
|
||||
@@ -489,7 +488,7 @@ class ApiClient {
|
||||
// Use the new token refresh-enabled method
|
||||
postResourceWithTokenRefresh(endpoint: endpoint, parameters: parameters, decodable: PlaybackSession.self) { session in
|
||||
guard let session = session else {
|
||||
logger.error("startPlaybackSession: Failed to create playback session")
|
||||
AbsLogger.error(message: "startPlaybackSession: Failed to create playback session")
|
||||
callback(PlaybackSession()) // Return empty session on failure
|
||||
return
|
||||
}
|
||||
@@ -533,14 +532,14 @@ class ApiClient {
|
||||
let localMediaProgressList = Database.shared.getAllLocalMediaProgress().filter {
|
||||
$0.serverConnectionConfigId == Store.serverConfig?.id
|
||||
}.map { $0.freeze() }
|
||||
logger.log("syncLocalSessionsWithServer: Found \(localMediaProgressList.count) local media progress for server")
|
||||
AbsLogger.info(message: "syncLocalSessionsWithServer: Found \(localMediaProgressList.count) local media progress for server")
|
||||
|
||||
if (localMediaProgressList.isEmpty) {
|
||||
logger.log("syncLocalSessionsWithServer: No local progress to sync")
|
||||
AbsLogger.info(message: "syncLocalSessionsWithServer: No local progress to sync")
|
||||
} else {
|
||||
let currentUser = await ApiClient.getCurrentUser()
|
||||
guard let currentUser = currentUser else {
|
||||
logger.log("syncLocalSessionsWithServer: No User")
|
||||
AbsLogger.info(message: "syncLocalSessionsWithServer: No User")
|
||||
return
|
||||
}
|
||||
try currentUser.mediaProgress.forEach { mediaProgress in
|
||||
@@ -552,12 +551,12 @@ class ApiClient {
|
||||
}
|
||||
}
|
||||
if (localMediaProgress != nil && mediaProgress.lastUpdate > localMediaProgress!.lastUpdate) {
|
||||
logger.log("syncLocalSessionsWithServer: Updating local media progress \(localMediaProgress!.id) with server media progress")
|
||||
AbsLogger.info(message: "syncLocalSessionsWithServer: Updating local media progress \(localMediaProgress!.id) with server media progress")
|
||||
if let localMediaProgress = localMediaProgress?.thaw() {
|
||||
try localMediaProgress.updateFromServerMediaProgress(mediaProgress)
|
||||
}
|
||||
} else if (localMediaProgress != nil) {
|
||||
logger.log("syncLocalSessionsWithServer: Local progress for \(localMediaProgress!.id) is more recent then server progress")
|
||||
AbsLogger.info(message: "syncLocalSessionsWithServer: Local progress for \(localMediaProgress!.id) is more recent then server progress")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -566,13 +565,13 @@ class ApiClient {
|
||||
let playbackSessions = Database.shared.getAllPlaybackSessions().filter {
|
||||
$0.serverConnectionConfigId == Store.serverConfig?.id
|
||||
}.map { $0.freeze() }
|
||||
logger.log("syncLocalSessionsWithServer: Found \(playbackSessions.count) playback sessions for server (first sync: \(isFirstSync))")
|
||||
AbsLogger.info(message: "syncLocalSessionsWithServer: Found \(playbackSessions.count) playback sessions for server (first sync: \(isFirstSync))")
|
||||
if (!playbackSessions.isEmpty) {
|
||||
let success = await ApiClient.reportAllLocalPlaybackSessions(playbackSessions)
|
||||
if (success) {
|
||||
// Remove sessions from db
|
||||
try playbackSessions.forEach { session in
|
||||
logger.log("syncLocalSessionsWithServer: Handling \(session.displayTitle ?? "") (\(session.id)) \(session.isActiveSession)")
|
||||
AbsLogger.info(message: "syncLocalSessionsWithServer: Handling \(session.displayTitle ?? "") (\(session.id)) \(session.isActiveSession)")
|
||||
// On first sync then remove all sessions
|
||||
if (!session.isActiveSession || isFirstSync) {
|
||||
if let session = session.thaw() {
|
||||
@@ -589,7 +588,7 @@ class ApiClient {
|
||||
}
|
||||
|
||||
public static func updateMediaProgress<T:Encodable>(libraryItemId: String, episodeId: String?, payload: T, callback: @escaping () -> Void) {
|
||||
logger.log("updateMediaProgress \(libraryItemId) \(episodeId ?? "NIL") \(payload)")
|
||||
AbsLogger.info(message: "updateMediaProgress \(libraryItemId) \(episodeId ?? "NIL") \(payload)")
|
||||
let endpoint = episodeId?.isEmpty ?? true ? "api/me/progress/\(libraryItemId)" : "api/me/progress/\(libraryItemId)/\(episodeId ?? "")"
|
||||
patchResourceWithTokenRefresh(endpoint: endpoint, parameters: payload) { _ in
|
||||
callback()
|
||||
@@ -597,7 +596,7 @@ class ApiClient {
|
||||
}
|
||||
|
||||
public static func getMediaProgress(libraryItemId: String, episodeId: String?) async -> MediaProgress? {
|
||||
logger.log("getMediaProgress \(libraryItemId) \(episodeId ?? "NIL")")
|
||||
AbsLogger.info(message: "getMediaProgress \(libraryItemId) \(episodeId ?? "NIL")")
|
||||
let endpoint = episodeId?.isEmpty ?? true ? "api/me/progress/\(libraryItemId)" : "api/me/progress/\(libraryItemId)/\(episodeId ?? "")"
|
||||
return await withCheckedContinuation { continuation in
|
||||
getResourceWithTokenRefresh(endpoint: endpoint, decodable: MediaProgress.self) { result in
|
||||
@@ -607,7 +606,7 @@ class ApiClient {
|
||||
}
|
||||
|
||||
public static func getCurrentUser() async -> User? {
|
||||
logger.log("getCurrentUser")
|
||||
AbsLogger.info(message: "getCurrentUser")
|
||||
return await withCheckedContinuation { continuation in
|
||||
getResourceWithTokenRefresh(endpoint: "api/me", decodable: User.self) { result in
|
||||
continuation.resume(returning: result)
|
||||
|
||||
@@ -12,10 +12,14 @@ class Database {
|
||||
public static var shared = {
|
||||
return Database()
|
||||
}()
|
||||
|
||||
private let logger = AppLogger(category: "Database")
|
||||
|
||||
private init() {}
|
||||
private init() {
|
||||
do {
|
||||
try cleanExpiredLogs()
|
||||
} catch {
|
||||
debugPrint(error)
|
||||
}
|
||||
}
|
||||
|
||||
public func setServerConnectionConfig(config: ServerConnectionConfig) {
|
||||
let config = config
|
||||
@@ -33,7 +37,7 @@ class Database {
|
||||
existing.token = config.token
|
||||
}
|
||||
} catch {
|
||||
logger.error("failed to update server config")
|
||||
AbsLogger.error("setServerConn", message: "failed to update server config")
|
||||
debugPrint(error)
|
||||
}
|
||||
|
||||
@@ -54,7 +58,7 @@ class Database {
|
||||
realm.add(config)
|
||||
}
|
||||
} catch(let exception) {
|
||||
logger.error("failed to save server config")
|
||||
AbsLogger.error(message: "failed to save server config")
|
||||
debugPrint(exception)
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ class Database {
|
||||
}
|
||||
}
|
||||
} catch(let exception) {
|
||||
logger.error("failed to delete server config")
|
||||
AbsLogger.error(message: "failed to delete server config")
|
||||
debugPrint(exception)
|
||||
}
|
||||
}
|
||||
@@ -117,7 +121,7 @@ class Database {
|
||||
}
|
||||
}
|
||||
} catch(let exception) {
|
||||
logger.error("failed to save server config active index")
|
||||
AbsLogger.error(message: "failed to save server config active index")
|
||||
debugPrint(exception)
|
||||
}
|
||||
}
|
||||
@@ -137,7 +141,7 @@ class Database {
|
||||
realm.add(deviceSettings)
|
||||
}
|
||||
} catch {
|
||||
logger.error("failed to save device settings")
|
||||
AbsLogger.error(message: "failed to save device settings")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,4 +279,54 @@ class Database {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func saveLog(_ log: LogEntry) throws {
|
||||
let realm = try Realm()
|
||||
return try realm.write { realm.add(log) }
|
||||
}
|
||||
|
||||
public func getAllLogs() -> [LogEntry] {
|
||||
do {
|
||||
let realm = try Realm()
|
||||
return realm.objects(LogEntry.self).toArray()
|
||||
} catch {
|
||||
debugPrint(error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public func clearLogs() throws {
|
||||
do {
|
||||
let realm = try! Realm()
|
||||
try realm.write {
|
||||
realm.objects(LogEntry.self).forEach { log in
|
||||
realm.delete(log)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
AbsLogger.error(message: "\(error)", error: error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
private func cleanExpiredLogs() throws {
|
||||
let realm = try Realm()
|
||||
let numberOfHoursToKeep = 48
|
||||
let keepLogCutoff = Date().addingTimeInterval(TimeInterval(-1 * numberOfHoursToKeep * 3600))
|
||||
|
||||
let allLogs = getAllLogs()
|
||||
var logsRemoved = 0
|
||||
try? realm.write {
|
||||
allLogs.forEach { log in
|
||||
if log.timestamp < Int(keepLogCutoff.timeIntervalSince1970) {
|
||||
realm.delete(log)
|
||||
logsRemoved += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if logsRemoved > 0 {
|
||||
AbsLogger.info(message: "cleanLogs: Removed \(logsRemoved) logs older than \(numberOfHoursToKeep) hours")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,7 @@ extension CAPPluginCall {
|
||||
let json = try JSONSerialization.data(withJSONObject: value)
|
||||
return try JSONDecoder().decode(type, from: json)
|
||||
} catch {
|
||||
AppLogger().error("Failed to get json for \(key)")
|
||||
debugPrint(error)
|
||||
AbsLogger.error(message: "Failed to get json for \(key)", error: error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -106,3 +105,80 @@ extension URL {
|
||||
return attributes?[.creationDate] as? Date
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - RealmSwift helpers
|
||||
// From https://github.com/realm/realm-swift/issues/5859#issuecomment-589026869
|
||||
|
||||
extension Results {
|
||||
func toArray<T: Object>() -> [T] {
|
||||
var array = [T]()
|
||||
|
||||
for i in 0 ..< self.count {
|
||||
if let result = self[i] as? T {
|
||||
array.append(result.detached())
|
||||
}
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
}
|
||||
|
||||
extension List {
|
||||
func toArray<T: Object>() -> [T] {
|
||||
var array = [T]()
|
||||
|
||||
for i in 0 ..< self.count {
|
||||
if let result = self[i] as? T {
|
||||
array.append(result.detached())
|
||||
}
|
||||
}
|
||||
|
||||
return array
|
||||
}
|
||||
}
|
||||
|
||||
protocol DetachableObject: AnyObject {
|
||||
func detached() -> Self
|
||||
}
|
||||
|
||||
extension Object: DetachableObject {
|
||||
func detached() -> Self {
|
||||
let detached = type(of: self).init()
|
||||
for property in objectSchema.properties {
|
||||
guard let value = value(forKey: property.name) else { continue }
|
||||
if property.isArray == true {
|
||||
//Realm List property support
|
||||
let detachable = value as? DetachableObject
|
||||
detached.setValue(detachable?.detached(), forKey: property.name)
|
||||
} else if property.type == .object {
|
||||
//Realm Object property support
|
||||
let detachable = value as? DetachableObject
|
||||
detached.setValue(detachable?.detached(), forKey: property.name)
|
||||
} else {
|
||||
detached.setValue(value, forKey: property.name)
|
||||
}
|
||||
}
|
||||
return detached
|
||||
}
|
||||
}
|
||||
|
||||
extension List: DetachableObject {
|
||||
func detached() -> List<Element> {
|
||||
let result = List<Element>()
|
||||
|
||||
forEach {
|
||||
if let detachable = $0 as? DetachableObject {
|
||||
let detached = detachable.detached() as! Element
|
||||
result.append(detached)
|
||||
} else {
|
||||
result.append($0) //Primtives are pass by value; don't need to recreate
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func toArray() -> [Element] {
|
||||
return Array(self.detached())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "audiobookshelf-app",
|
||||
"version": "0.10.0-beta",
|
||||
"version": "0.11.0-beta",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audiobookshelf-app",
|
||||
"version": "0.10.0-beta",
|
||||
"version": "0.11.0-beta",
|
||||
"dependencies": {
|
||||
"@capacitor-community/keep-awake": "^7.0.0",
|
||||
"@capacitor-community/volume-buttons": "^7.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "audiobookshelf-app",
|
||||
"version": "0.10.0-beta",
|
||||
"version": "0.11.0-beta",
|
||||
"author": "advplyr",
|
||||
"scripts": {
|
||||
"dev": "nuxt --hostname 0.0.0.0 --port 1337",
|
||||
|
||||
@@ -74,6 +74,12 @@ export default {
|
||||
console.log(`[categories] networkConnected false so fetching categories`)
|
||||
this.fetchCategories()
|
||||
}
|
||||
},
|
||||
user(newVal, oldVal) {
|
||||
if ((newVal && !oldVal) || (!newVal && oldVal)) {
|
||||
console.log(`[categories] user changed so fetching categories`)
|
||||
this.fetchCategories()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -37,9 +37,9 @@ export default {
|
||||
computed: {},
|
||||
methods: {
|
||||
async init() {
|
||||
await this.$store.dispatch('setupNetworkListener')
|
||||
this.deviceData = await this.$db.getDeviceData()
|
||||
this.$store.commit('setDeviceData', this.deviceData)
|
||||
await this.$store.dispatch('setupNetworkListener')
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="flex-grow"></div>
|
||||
<div class="text-xs text-gray-400">{{ log.tag }}</div>
|
||||
</div>
|
||||
<div class="text-xs">{{ maskServerAddress ? log.maskedMessage : log.message }}</div>
|
||||
<div class="text-xs break-words">{{ maskServerAddress ? log.maskedMessage : log.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function ({ $axios, store, $db }) {
|
||||
}
|
||||
|
||||
if (window.location.pathname !== '/connect') {
|
||||
window.location.href = '/connect'
|
||||
window.location.href = '/connect?error=refreshTokenFailed&serverConnectionConfigId=' + serverConnectionConfigId
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[axios] Failed to handle refresh failure:', error)
|
||||
|
||||
@@ -138,7 +138,7 @@ export default ({ app, store }, inject) => {
|
||||
// Clear store and redirect to login page
|
||||
await store.dispatch('user/logout')
|
||||
if (window.location.pathname !== '/connect') {
|
||||
window.location.href = '/connect'
|
||||
window.location.href = '/connect?error=refreshTokenFailed&serverConnectionConfigId=' + data.serverConnectionConfigId || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ export default function ({ store, $db, $socket }, inject) {
|
||||
|
||||
// Redirect to login page
|
||||
if (window.location.pathname !== '/connect') {
|
||||
window.location.href = '/connect'
|
||||
window.location.href = '/connect?error=refreshTokenFailed&serverConnectionConfigId=' + serverConnectionConfigId
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[nativeHttp] Failed to handle refresh failure:', error)
|
||||
|
||||
@@ -144,6 +144,12 @@ npx cap open android
|
||||
|
||||
Start coding!
|
||||
|
||||
After making changes to the JS layer you need to rebuild the nuxt pages and sync them to the native shells:
|
||||
|
||||
```shell
|
||||
npm run sync
|
||||
```
|
||||
|
||||
### Mac Environment Setup for iOS
|
||||
|
||||
Required Software:
|
||||
@@ -213,3 +219,9 @@ npx cap open ios
|
||||
<br>
|
||||
|
||||
Start coding!
|
||||
|
||||
After making changes to the JS layer you need to rebuild the nuxt pages and sync them to the native shells:
|
||||
|
||||
```shell
|
||||
npm run sync
|
||||
```
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"ButtonCancelTimer": "إلغاء المؤقت",
|
||||
"ButtonClearFilter": "تصفية الفرز",
|
||||
"ButtonClearLogs": "مسح السجل",
|
||||
"ButtonCloseFeed": "إغلاق",
|
||||
"ButtonCloseFeed": "إغلاق الموجز",
|
||||
"ButtonCollections": "المجموعات",
|
||||
"ButtonConnect": "اتصل",
|
||||
"ButtonConnectToServer": "اتصال بالخادم",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"ButtonCancel": "Скасаваць",
|
||||
"ButtonCancelTimer": "Скасаваць таймер",
|
||||
"ButtonClearFilter": "Ачысціць фільтр",
|
||||
"ButtonClearLogs": "Ачысціць журналы",
|
||||
"ButtonCloseFeed": "Закрыць стужку",
|
||||
"ButtonCollections": "Калекцыі",
|
||||
"ButtonConnect": "Падлучыцца",
|
||||
@@ -26,7 +27,9 @@
|
||||
"ButtonLatest": "Апошняе",
|
||||
"ButtonLibrary": "Бібліятэка",
|
||||
"ButtonLocalMedia": "Лакальныя медыя",
|
||||
"ButtonLogs": "Журналы",
|
||||
"ButtonManageLocalFiles": "Кіраванне лакальнымі файламі",
|
||||
"ButtonMaskServerAddress": "Схаваць адрас сервера",
|
||||
"ButtonNewFolder": "Новая тэчка",
|
||||
"ButtonNextEpisode": "Наступны эпізод",
|
||||
"ButtonOk": "Добра",
|
||||
@@ -50,6 +53,7 @@
|
||||
"ButtonStream": "Трансляцыя",
|
||||
"ButtonSubmit": "Адправіць",
|
||||
"ButtonSwitchServerUser": "Змяніць Сервер/Карыстальніка",
|
||||
"ButtonUnmaskServerAddress": "Паказаць адрас сервера",
|
||||
"ButtonUserStats": "Статыстыка карыстальніка",
|
||||
"ButtonYes": "Так",
|
||||
"HeaderAccount": "Уліковы запіс",
|
||||
@@ -78,7 +82,7 @@
|
||||
"HeaderProgressSyncFailed": "Не ўдалося сінхранізаваць прагрэс",
|
||||
"HeaderRSSFeed": "RSS-стужка",
|
||||
"HeaderRSSFeedGeneral": "Падрабязнасці RSS",
|
||||
"HeaderRSSFeedIsOpen": "RSS-стужка адкрыта",
|
||||
"HeaderRSSFeedIsOpen": "RSS-стужка адкрытая",
|
||||
"HeaderSelectDownloadLocation": "Абраць месцазнаходжання для спампоўкі",
|
||||
"HeaderSettings": "Налады",
|
||||
"HeaderSleepTimer": "Таймер сну",
|
||||
@@ -142,6 +146,7 @@
|
||||
"LabelEndOfChapter": "Канец раздзела",
|
||||
"LabelEndTime": "Час заканчэння",
|
||||
"LabelEpisode": "Эпізод",
|
||||
"LabelExplicit": "Відверты",
|
||||
"LabelFeedURL": "URL стужкі",
|
||||
"LabelFile": "Файл",
|
||||
"LabelFileBirthtime": "Час стварэння файла",
|
||||
@@ -216,6 +221,7 @@
|
||||
"LabelPublishedDate": "Апублікавана {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Карыстальніцкая электронная пошта ўладальніка",
|
||||
"LabelRSSFeedCustomOwnerName": "Карыстальніцкае імя ўладальніка",
|
||||
"LabelRSSFeedOpen": "RSS-стужка адкрыта",
|
||||
"LabelRSSFeedPreventIndexing": "Прадухіліць індэксацыю",
|
||||
"LabelRSSFeedSlug": "Ідэнтыфікатар RSS-стужкі",
|
||||
"LabelRandomly": "Выпадкова",
|
||||
@@ -237,6 +243,8 @@
|
||||
"LabelShowAll": "Паказаць усё",
|
||||
"LabelSize": "Памер",
|
||||
"LabelSleepTimer": "Таймер сну",
|
||||
"LabelSleepTimerAlmostDoneChime": "Праіграйце звонак, калі амаль скончана",
|
||||
"LabelSleepTimerAlmostDoneChimeHelp": "Памякчыць звонак, калі на таймеры сну застанецца 30 секунд",
|
||||
"LabelStart": "Пачаць",
|
||||
"LabelStartTime": "Час пачатку",
|
||||
"LabelStatsBestDay": "Лепшы дзень",
|
||||
@@ -252,6 +260,7 @@
|
||||
"LabelTag": "Метка",
|
||||
"LabelTags": "Меткі",
|
||||
"LabelTheme": "Тэма",
|
||||
"LabelThemeBlack": "Чорны",
|
||||
"LabelThemeDark": "Цёмная",
|
||||
"LabelThemeLight": "Светлая",
|
||||
"LabelTimeRemaining": "Засталося {0}",
|
||||
@@ -289,6 +298,7 @@
|
||||
"MessageDownloading": "Спампоўванне...",
|
||||
"MessageDownloadingEpisode": "Спампоўка эпізоду",
|
||||
"MessageEpisodesQueuedForDownload": "{0} эпізод(аў) у чарзе для спампоўкі",
|
||||
"MessageFailedToRefreshToken": "Не ўдалося абнавіць токен, патрабуецца паўторны ўваход",
|
||||
"MessageFeedURLWillBe": "URL стужкі будзе {0}",
|
||||
"MessageFetching": "Атрыманне...",
|
||||
"MessageFollowTheProjectOnGithub": "Сачыце за праектам на Github",
|
||||
@@ -309,12 +319,16 @@
|
||||
"MessageNoItems": "Няма элементаў",
|
||||
"MessageNoItemsFound": "Элементы не знойдзены",
|
||||
"MessageNoListeningSessions": "Няма сеансаў праслухоўвання",
|
||||
"MessageNoLogs": "Няма журналаў",
|
||||
"MessageNoMediaFolders": "Няма тэчак з медыя",
|
||||
"MessageNoNetworkConnection": "Няма падлучэння да сеткі",
|
||||
"MessageNoPodcastsFound": "Падкасты не знойдзены",
|
||||
"MessageNoSeries": "Няма серый",
|
||||
"MessageNoUpdatesWereNecessary": "Абнаўленні не патрабаваліся",
|
||||
"MessageNoUserPlaylists": "У вас няма спісаў прайгравання",
|
||||
"MessageOldServerAuthReLoginRequired": "Аўтэнтыфікацыя была палепшана для бяспекі ў серверы v2.26.0. Усім карыстальнікам патрабуецца паўторна ўвайсці ў сістэму.",
|
||||
"MessageOldServerAuthWarning": "Сервер выкарыстоўвае састарэлую аўтэнтыфікацыю",
|
||||
"MessageOldServerAuthWarningHelp": "Гэты сервер працуе пад версіяй, старэйшай за 2.26.0. У версіі 2.26.0 была дададзена больш бяспечная сістэма аўтэнтыфікацыі. Настойліва рэкамендуецца абнавіць сервер да апошняй версіі. Калі вы ўжо абнавілі сервер, увайдзіце зноў, каб выкарыстоўваць новую аўтэнтыфікацыю.",
|
||||
"MessageOldServerConnectionWarning": "Канфігурацыя падлучэння да сервера выкарыстоўвае стары ідэнтыфікатар карыстальніка. Калі ласка, выдаліце і зноў дадайце гэта падлучэнне да сервера.",
|
||||
"MessageOldServerConnectionWarningHelp": "Вы першапачаткова наладзілі падключэнне да гэтага сервера да міграцыі базы даных у версіі 2.3.0, якая выйшла ў чэрвені 2023 года. У будучым абнаўленні сервера магчымасць уваходу праз гэтае старое падключэнне будзе выдалена. Калі ласка, выдаліце існуючае падключэнне да сервера і падключыцеся зноў (выкарыстоўваючы той жа адрас сервера і ўліковыя даныя). Калі на гэтай прыладзе ёсць спампаваныя медыяфайлы, іх трэба будзе спампаваць зноў для сінхранізацыі з серверам.",
|
||||
"MessagePodcastSearchField": "Увядзіце пошукавы запыт або URL RSS-стужкі",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"ButtonCancel": "Отказ",
|
||||
"ButtonCancelTimer": "Отмени Таймер",
|
||||
"ButtonClearFilter": "Изчисти филтър",
|
||||
"ButtonClearLogs": "Изчисти логове",
|
||||
"ButtonCloseFeed": "Затвори стената",
|
||||
"ButtonCollections": "Колекции",
|
||||
"ButtonConnect": "Свържи се",
|
||||
@@ -26,7 +27,9 @@
|
||||
"ButtonLatest": "Последни",
|
||||
"ButtonLibrary": "Библиотека",
|
||||
"ButtonLocalMedia": "Локална медия",
|
||||
"ButtonLogs": "Логове",
|
||||
"ButtonManageLocalFiles": "Управление на локални файлове",
|
||||
"ButtonMaskServerAddress": "Маскирай адреса на сървърът",
|
||||
"ButtonNewFolder": "Създай нова папка",
|
||||
"ButtonNextEpisode": "Следващ епизод",
|
||||
"ButtonOk": "Приемам",
|
||||
@@ -50,6 +53,7 @@
|
||||
"ButtonStream": "Стрийм",
|
||||
"ButtonSubmit": "Изпрати",
|
||||
"ButtonSwitchServerUser": "Смени сървър/потребител",
|
||||
"ButtonUnmaskServerAddress": "Демаскирай адреса на съвърът",
|
||||
"ButtonUserStats": "Потребителски статистики",
|
||||
"ButtonYes": "Да",
|
||||
"HeaderAccount": "Профил",
|
||||
@@ -111,7 +115,7 @@
|
||||
"LabelBooks": "Книги",
|
||||
"LabelChapterTrack": "Трак на глава",
|
||||
"LabelChapters": "Глави",
|
||||
"LabelClosePlayer": "Затвори",
|
||||
"LabelClosePlayer": "Затвори плейъра",
|
||||
"LabelCollapseSeries": "Скрий сериите",
|
||||
"LabelComplete": "Приключено",
|
||||
"LabelContinueBooks": "Продължи книги",
|
||||
@@ -142,6 +146,7 @@
|
||||
"LabelEndOfChapter": "Край на глава",
|
||||
"LabelEndTime": "Оставащо време",
|
||||
"LabelEpisode": "Епизод",
|
||||
"LabelExplicit": "Експлицитно",
|
||||
"LabelFeedURL": "URL на емисия",
|
||||
"LabelFile": "Файл",
|
||||
"LabelFileBirthtime": "Дата на създаване на файла",
|
||||
@@ -216,6 +221,7 @@
|
||||
"LabelPublishedDate": "Публикувани {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Персонализиран имейл на собственика",
|
||||
"LabelRSSFeedCustomOwnerName": "Персонализирано име на собственика",
|
||||
"LabelRSSFeedOpen": "RSS Feed е отворен",
|
||||
"LabelRSSFeedPreventIndexing": "Предотвратете индексиране",
|
||||
"LabelRSSFeedSlug": "идентификатор на RSS емисия",
|
||||
"LabelRandomly": "Случайно",
|
||||
@@ -237,6 +243,8 @@
|
||||
"LabelShowAll": "Покажи всички",
|
||||
"LabelSize": "Размер",
|
||||
"LabelSleepTimer": "Таймер за изключване",
|
||||
"LabelSleepTimerAlmostDoneChime": "Възпроизведи звуков сигнал малко преди да приключи",
|
||||
"LabelSleepTimerAlmostDoneChimeHelp": "Възпроизведи звуков сигнал, 30 секунди преди изтичане на таймера за заспиване",
|
||||
"LabelStart": "Старт",
|
||||
"LabelStartTime": "Време на стартиране",
|
||||
"LabelStatsBestDay": "Най-добър ден",
|
||||
@@ -252,6 +260,7 @@
|
||||
"LabelTag": "Таг",
|
||||
"LabelTags": "Тагове",
|
||||
"LabelTheme": "Тема",
|
||||
"LabelThemeBlack": "Черен",
|
||||
"LabelThemeDark": "Тъмна",
|
||||
"LabelThemeLight": "Светла",
|
||||
"LabelTimeRemaining": "{0} оставащи",
|
||||
@@ -289,6 +298,7 @@
|
||||
"MessageDownloading": "Сваляне...",
|
||||
"MessageDownloadingEpisode": "Сваля епизод",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Епизод(и) са сложени за сваляне",
|
||||
"MessageFailedToRefreshToken": "Неуспешно опресняване на токена, налага се да се впишете отново",
|
||||
"MessageFeedURLWillBe": "Адресът на емисията ще бъде {0}",
|
||||
"MessageFetching": "Извличане...",
|
||||
"MessageFollowTheProjectOnGithub": "Последвайте проекта в Github",
|
||||
@@ -296,6 +306,7 @@
|
||||
"MessageItemMissing": "Елементът липсва и трябва да бъде поправен на сървъра. Обикновено един елемент се маркира като липсващ, защото пътищата към файловете не са достъпни.",
|
||||
"MessageLoading": "Зарежда...",
|
||||
"MessageLoadingServerData": "Зарежда данни от сървъра...",
|
||||
"MessageLocalFolderDescription": "'Вътрешното хранилище на приложението' е достъпно само през това приложение. Това приложение поддържа само медийни файлове, изтеглени директно през него. Споделените папки за съхранение могат да се използват, за да се позволи на други приложения да имат достъп до медийните файлове, изтеглени чрез това приложение.",
|
||||
"MessageMarkAsFinished": "Маркирай като завършено",
|
||||
"MessageMediaLinkedToADifferentServer": "Медията е свързана със сървър на Audiobookshelf на различен адрес ({0}). Напредъкът ще бъде синхронизиран, когато се свържете с този сървърен адрес.",
|
||||
"MessageMediaLinkedToADifferentUser": "Медията е свързана с този сървър, но е изтеглена от друг потребител. Напредъкът ще бъде синхронизиран само с потребителя, който я е изтеглил.",
|
||||
@@ -308,12 +319,16 @@
|
||||
"MessageNoItems": "Няма елементи",
|
||||
"MessageNoItemsFound": "Няма намерени елементи",
|
||||
"MessageNoListeningSessions": "Няма сесии за слушане",
|
||||
"MessageNoLogs": "Няма логове",
|
||||
"MessageNoMediaFolders": "Няма медии папки",
|
||||
"MessageNoNetworkConnection": "Няма връзка с интернет",
|
||||
"MessageNoPodcastsFound": "Няма намерени подкасти",
|
||||
"MessageNoSeries": "Няма серии",
|
||||
"MessageNoUpdatesWereNecessary": "Няма нужда от обновяване",
|
||||
"MessageNoUserPlaylists": "Нямате създадени плейлисти",
|
||||
"MessageOldServerAuthReLoginRequired": "За осигуряването на по-добра сигурност, автентикация беше подобрена във v.2.26.0. Всеки потребител ще трябва да се автентикира наново.",
|
||||
"MessageOldServerAuthWarning": "Сървърът използва остаряла форма на автентикация",
|
||||
"MessageOldServerAuthWarningHelp": "Този сървър е на версия, която е по-стара от v2.26.0. Във версия v2.26.0 беше добавена по-сигурна автентикация. Силно е препоръчително да обновите сървъра до последната версия. Аковече сте обновили сървъра, влезде наново, за да ползвате новата автентикация.",
|
||||
"MessageOldServerConnectionWarning": "Конфигурацията за връзка със сървъра използва старо потребителско ID. Моля, изтрийте и добавете отново тази връзка със сървъра.",
|
||||
"MessageOldServerConnectionWarningHelp": "Първоначално сте настроили връзката към този сървър преди миграцията на базата данни във версия 2.3.0, пусната през юни 2023 г. Бъдеща актуализация на сървъра ще премахне възможността за влизане с тази стара връзка. Моля, изтрийте съществуващата връзка със сървъра и се свържете отново (използвайки същия сървърен адрес и идентификационни данни). Ако имате изтеглена медия на това устройство, тя ще трябва да бъде изтеглена отново, за да се синхронизира със сървъра.",
|
||||
"MessagePodcastSearchField": "Въведи какво да търся или RSS емисия адрес",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"ButtonPlaylists": "Seznamy skladeb",
|
||||
"ButtonRead": "Číst",
|
||||
"ButtonReadLess": "Číst méně",
|
||||
"ButtonReadMore": "Číst další",
|
||||
"ButtonReadMore": "Číst více",
|
||||
"ButtonRemove": "Odstranit",
|
||||
"ButtonRemoveFromServer": "Odstranit ze serveru",
|
||||
"ButtonSave": "Uložit",
|
||||
@@ -82,7 +82,7 @@
|
||||
"HeaderProgressSyncFailed": "Chyba při synchronizaci pokroku",
|
||||
"HeaderRSSFeed": "RSS kanál",
|
||||
"HeaderRSSFeedGeneral": "Detaily RSS",
|
||||
"HeaderRSSFeedIsOpen": "Kanál RSS je otevřen",
|
||||
"HeaderRSSFeedIsOpen": "Informační kanál RSS je otevřený",
|
||||
"HeaderSelectDownloadLocation": "Vyberte umístění pro stažení",
|
||||
"HeaderSettings": "Nastavení",
|
||||
"HeaderSleepTimer": "Časovač spánku",
|
||||
@@ -146,7 +146,7 @@
|
||||
"LabelEndOfChapter": "Konec kapitoly",
|
||||
"LabelEndTime": "Do",
|
||||
"LabelEpisode": "Epizoda",
|
||||
"LabelExplicit": "Explicitně",
|
||||
"LabelExplicit": "Explicitní",
|
||||
"LabelFeedURL": "URL kanálu",
|
||||
"LabelFile": "Soubor",
|
||||
"LabelFileBirthtime": "Čas vytvoření souboru",
|
||||
@@ -160,8 +160,8 @@
|
||||
"LabelGenres": "Žánry",
|
||||
"LabelHapticFeedback": "Haptická odezva",
|
||||
"LabelHasEbook": "Má e-knihu",
|
||||
"LabelHasSupplementaryEbook": "Má doplňkovou e-knihu",
|
||||
"LabelHeavy": "Těžké",
|
||||
"LabelHasSupplementaryEbook": "Obsahuje doplňkovou e-knihu",
|
||||
"LabelHeavy": "Silná",
|
||||
"LabelHigh": "Vysoké",
|
||||
"LabelHost": "Hostitel",
|
||||
"LabelInProgress": "Probíhá",
|
||||
@@ -174,7 +174,7 @@
|
||||
"LabelLayout": "Rozvržení",
|
||||
"LabelLayoutAuto": "Automatické",
|
||||
"LabelLayoutSinglePage": "Jedna stránka",
|
||||
"LabelLight": "Světlé",
|
||||
"LabelLight": "Slabá",
|
||||
"LabelLineSpacing": "Řádkování",
|
||||
"LabelListenAgain": "Poslouchat znovu",
|
||||
"LabelLocalBooks": "Místní knihy",
|
||||
@@ -204,7 +204,7 @@
|
||||
"LabelNumEpisodes": "{0} epizod",
|
||||
"LabelNumEpisodesIncomplete": "{0} epizod, {1} nekompletní",
|
||||
"LabelNumberOfEpisodes": "Počet epizod",
|
||||
"LabelOff": "Vypnout",
|
||||
"LabelOff": "Vypnutá",
|
||||
"LabelOn": "Zapnuto",
|
||||
"LabelPassword": "Heslo",
|
||||
"LabelPath": "Cesta",
|
||||
@@ -298,6 +298,7 @@
|
||||
"MessageDownloading": "Stahuje se...",
|
||||
"MessageDownloadingEpisode": "Stahuji epizodu",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Epizody zařazené do fronty ke stažení",
|
||||
"MessageFailedToRefreshToken": "Nepovedlo se obnovit token, je nutné se znovu přihlásit",
|
||||
"MessageFeedURLWillBe": "URL zdroje bude {0}",
|
||||
"MessageFetching": "Načítání...",
|
||||
"MessageFollowTheProjectOnGithub": "Sledujte projekt na GitHubu",
|
||||
@@ -325,13 +326,16 @@
|
||||
"MessageNoSeries": "Žádné série",
|
||||
"MessageNoUpdatesWereNecessary": "Nebyly nutné žádné aktualizace",
|
||||
"MessageNoUserPlaylists": "Nemáte žádné seznamy skladeb",
|
||||
"MessageOldServerAuthReLoginRequired": "V rámci aktualizace serveru v2.26.0 bylo vylepšeno zabezpečení autentizace. Všichni uživatelé se musí znovu přihlásit.",
|
||||
"MessageOldServerAuthWarning": "Server používá zastaralou autentizaci",
|
||||
"MessageOldServerAuthWarningHelp": "Server běží na verzi starší než v2.26.0. Bezpečnější způsob autentizace byl přidán ve verzi v2.26.0. Důrazně se doporučuje aktualizovat server na nejnovější verzi. Pokud jste již server aktualizovali, znovu se přihlašte pro použití nové autentizace.",
|
||||
"MessageOldServerConnectionWarning": "Konfigurace připojení k serveru používá staré ID uživatele. Odstraňte a znovu přidejte toto připojení k serveru.",
|
||||
"MessageOldServerConnectionWarningHelp": "Připojení k tomuto serveru jste původně nastavili před migrací databáze ve verzi 2.3.0 vydané v červnu 2023. Budoucí aktualizace serveru odstraní možnost přihlášení pomocí tohoto starého připojení. Odstraňte prosím stávající připojení k serveru a připojte se znovu (pomocí stejné adresy serveru a přihlašovacích údajů). Pokud máte v tomto zařízení stažená média, bude nutné je pro synchronizaci se serverem stáhnout znovu.",
|
||||
"MessagePodcastSearchField": "Zadejte hledaný pojem pro RSS feed URL",
|
||||
"MessageProgressSyncFailed": "Poslední pokus o nahlášení průběhu poslechu na server se nezdařil. Během přehrávání médií se budou požadavky na synchronizaci pokroku nadále pokoušet každých 15 až 60 sekund.",
|
||||
"MessageReportBugsAndContribute": "Nahlašte chyby, vyžádejte si funkce a přispěte na",
|
||||
"MessageSeriesAlreadyDownloaded": "Všechny knihy v této sérii jste již stáhli.",
|
||||
"MessageSeriesDownloadConfirm": "Stáhnout chybějící {0} knihu(y) s {1} souborem(y), celkem {2} do složky {3}?",
|
||||
"MessageSeriesDownloadConfirm": "Stáhnout {0} chybějících knih s {1} souborem/soubory, celkem {2}, do složky {3}?",
|
||||
"MessageSeriesDownloadConfirmIos": "Stáhnout chybějící {0} knihu(y) s {1} souborem(y), celkem {2}?",
|
||||
"MessageSocketConnectedOverMeteredCellular": "Socket připojen přes měřenou mobilní síť",
|
||||
"MessageSocketConnectedOverMeteredWifi": "Socket připojen přes měřenou Wi-Fi",
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
"LabelEndOfChapter": "Slutningen af kapitel",
|
||||
"LabelEndTime": "Sluttidspunkt",
|
||||
"LabelEpisode": "Afsnit",
|
||||
"LabelExplicit": "Eksplisit",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFile": "Fil",
|
||||
"LabelFileBirthtime": "Oprettelsestidspunkt for fil",
|
||||
@@ -324,6 +325,9 @@
|
||||
"MessageNoSeries": "Ingen serier",
|
||||
"MessageNoUpdatesWereNecessary": "Ingen opdateringer var nødvendige",
|
||||
"MessageNoUserPlaylists": "Du har ingen afspilningslister",
|
||||
"MessageOldServerAuthReLoginRequired": "Autentificering er blevet forbedret af sikkerhedsmæssige årsager i server v2.26.0. Alle brugere skal logge ind igen.",
|
||||
"MessageOldServerAuthWarning": "Serveren bruger forældet autentificering",
|
||||
"MessageOldServerAuthWarningHelp": "Denne server kører en version ældre end v2.26.0. Et mere sikkert autentificeringssystem blev tilføjet i v2.26.0. Det anbefales kraftigt at opdatere serveren til den nyeste version. Hvis du allerede har opdateret serveren, skal du logge ind igen for at bruge den nye autentificering.",
|
||||
"MessageOldServerConnectionWarning": "Server forbindelseskonfiguration anvender et gammelt bruger ID. Fjern venligst og gentilføj denne server forbindelse.",
|
||||
"MessageOldServerConnectionWarningHelp": "Du har oprindeligt sat forbindelsen til denne server op før databasemigreringen i 2.3.0, udgivet i juni 2023. En senere serveropdatering vil fjerne muligheden for at logge ind med genne gamle metode. Sørg venligst for at slette den eksisterende serverforbindelse og opret forbindelse igen (ved anvendelse af den samme server adresse og login oplysninger). Hvis du har downloaded noget medie på denne enhed vil dette være nødsaget til at bive downloaded igen efter serversynkronisering.",
|
||||
"MessagePodcastSearchField": "Indtast søgeterm eller RSS URL",
|
||||
@@ -343,8 +347,8 @@
|
||||
"ToastBookmarkRemoveFailed": "Mislykkedes fjernelse af bogmærke",
|
||||
"ToastBookmarkUpdateFailed": "Mislykkedes opdatering af bogmærke",
|
||||
"ToastDownloadNotAllowedOnCellular": "Det er ikke tillad at downloade over mobildata",
|
||||
"ToastItemMarkedAsFinishedFailed": "Mislykkedes markering som afsluttet",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Mislykkedes markering som ikke afsluttet",
|
||||
"ToastItemMarkedAsFinishedFailed": "Markering som afsluttet mislykkedes",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Markering som ikke afsluttet mislykkedes",
|
||||
"ToastPlaylistCreateFailed": "Mislykkedes oprettelse af afspilningsliste",
|
||||
"ToastPodcastCreateFailed": "Mislykkedes oprettelse af podcast",
|
||||
"ToastPodcastCreateSuccess": "Podcast oprettet med succes",
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
"ButtonConnect": "Verbinden",
|
||||
"ButtonConnectToServer": "Mit Server verbinden",
|
||||
"ButtonCreate": "Erstellen",
|
||||
"ButtonCreateBookmark": "Erstelle Lesezeichen",
|
||||
"ButtonCreateNewPlaylist": "Erstelle neue Wiedergabeliste",
|
||||
"ButtonCreateBookmark": "Lesezeichen erstellen",
|
||||
"ButtonCreateNewPlaylist": "Neue Wiedergabeliste erstellen",
|
||||
"ButtonDelete": "Löschen",
|
||||
"ButtonDeleteLocalEpisode": "Lösche lokale Episode",
|
||||
"ButtonDeleteLocalFile": "Lösche lokale Datei",
|
||||
"ButtonDeleteLocalEpisode": "Lokale Episode löschen",
|
||||
"ButtonDeleteLocalFile": "Lokale Datei löschen",
|
||||
"ButtonDeleteLocalItem": "Lokales Element löschen",
|
||||
"ButtonDisableAutoTimer": "Automatischen Timer deaktivieren",
|
||||
"ButtonDisconnect": "Trennen",
|
||||
@@ -32,7 +32,7 @@
|
||||
"ButtonMaskServerAddress": "Server Adresse verstecken",
|
||||
"ButtonNewFolder": "Neuer Ordner",
|
||||
"ButtonNextEpisode": "Nächste Episode",
|
||||
"ButtonOk": "Ok",
|
||||
"ButtonOk": "OK",
|
||||
"ButtonOpenFeed": "Feed öffnen",
|
||||
"ButtonOverride": "Überschreiben",
|
||||
"ButtonPause": "Pausieren",
|
||||
@@ -40,18 +40,18 @@
|
||||
"ButtonPlayEpisode": "Episode abspielen",
|
||||
"ButtonPlaylists": "Wiedergabelisten",
|
||||
"ButtonRead": "Lesen",
|
||||
"ButtonReadLess": "weniger Anzeigen",
|
||||
"ButtonReadLess": "Weniger Anzeigen",
|
||||
"ButtonReadMore": "Mehr Anzeigen",
|
||||
"ButtonRemove": "Entfernen",
|
||||
"ButtonRemoveFromServer": "Vom Server entfernen",
|
||||
"ButtonSave": "Speichern",
|
||||
"ButtonSaveOrder": "Reihenfolge speichern",
|
||||
"ButtonSearch": "Suchen",
|
||||
"ButtonSendEbookToDevice": "E-Buch an Gerät senden",
|
||||
"ButtonSendEbookToDevice": "E-Book an Gerät senden",
|
||||
"ButtonSeries": "Serien",
|
||||
"ButtonSetTimer": "Setze Timer",
|
||||
"ButtonStream": "Streamen",
|
||||
"ButtonSubmit": "Ok",
|
||||
"ButtonSubmit": "Absenden",
|
||||
"ButtonSwitchServerUser": "Server/Benutzer wechseln",
|
||||
"ButtonUnmaskServerAddress": "Server Adresse anzeigen",
|
||||
"ButtonUserStats": "Nutzerstatistiken",
|
||||
@@ -67,13 +67,13 @@
|
||||
"HeaderDataSettings": "Daten-Einstellungen",
|
||||
"HeaderDetails": "Details",
|
||||
"HeaderDownloads": "Downloads",
|
||||
"HeaderEbookFiles": "E-Buch-Dateien",
|
||||
"HeaderEbookFiles": "E-Book-Dateien",
|
||||
"HeaderEpisodes": "Episoden",
|
||||
"HeaderEreaderSettings": "Einstellungen zum Lesen",
|
||||
"HeaderEreaderSettings": "E-Reader-Einstellungen",
|
||||
"HeaderLatestEpisodes": "Neueste Episoden",
|
||||
"HeaderLibraries": "Bibliotheken",
|
||||
"HeaderLocalFolders": "Lokale Ordner",
|
||||
"HeaderLocalLibraryItems": "Elemente der lokalen Bibliothek",
|
||||
"HeaderLocalLibraryItems": "Objekte in der lokalen Bibliothek",
|
||||
"HeaderNewPlaylist": "Neue Wiedergabeliste",
|
||||
"HeaderOpenRSSFeed": "RSS-Feed öffnen",
|
||||
"HeaderPlaybackSettings": "Wiedergabe-Einstellungen",
|
||||
@@ -221,7 +221,7 @@
|
||||
"LabelPublishedDate": "Veröffentlicht {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Benutzerdefinierte Eigentümer-E-Mail",
|
||||
"LabelRSSFeedCustomOwnerName": "Benutzerdefinierter Name des Eigentümers",
|
||||
"LabelRSSFeedOpen": "RSS Feed offen",
|
||||
"LabelRSSFeedOpen": "RSS-Feed offen",
|
||||
"LabelRSSFeedPreventIndexing": "Indizierung verhindern",
|
||||
"LabelRSSFeedSlug": "RSS-Feed-Schlagwort",
|
||||
"LabelRandomly": "Zufällig",
|
||||
@@ -298,6 +298,7 @@
|
||||
"MessageDownloading": "Wird heruntergeladen …",
|
||||
"MessageDownloadingEpisode": "Episode herunterladen",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episode(n) in der Warteschlange zum Herunterladen",
|
||||
"MessageFailedToRefreshToken": "Aktualisierung des Tokens fehlgeschlagen, erneute Anmeldung erforderlich",
|
||||
"MessageFeedURLWillBe": "Feed-URL wird {0} sein",
|
||||
"MessageFetching": "Wird abgerufen …",
|
||||
"MessageFollowTheProjectOnGithub": "Folge dem Projekt auf GitHub",
|
||||
@@ -327,14 +328,15 @@
|
||||
"MessageNoUserPlaylists": "Keine Wiedergabelisten vorhanden",
|
||||
"MessageOldServerAuthReLoginRequired": "Für die verbesserung der Sicherheit wurde die Authentifizierung in der Version v2.26.0 verbessert. Alle Nutzer müssen sich neu einloggen.",
|
||||
"MessageOldServerAuthWarning": "Der Server nutzt eine nicht mehr aktuelle Authentifizierung",
|
||||
"MessageOldServerAuthWarningHelp": "Die Version dieses Servers ist älter als v2.26.0. Ein sichereres Authentifizierungssystem wurde in v2.26.0 hinzugefügt. Es wird dringend empfohlen, den Server auf die neuste Version zu aktualisieren. Solltest du den Server bereits aktualisiert haben, melde dich neu an, um das neue Authentifizierungssystem zu verwenden.",
|
||||
"MessageOldServerConnectionWarning": "Diese Serververbindung nutzt eine alte Nutzer-ID (user ID). Bitte entferne diese Serververbindung und füge sie wieder neu hinzu.",
|
||||
"MessageOldServerConnectionWarningHelp": "Du hast diese Serververbindung vor der Datenbankmigration in 2.3.0, veröffentlicht im Juni 2023, eingerichtet. Ein zukünftiges Serverupdate wird die Möglichkeit entfernen, sich mit dieser Verbindung anzumelden. Bitte lösche die existierende Serververbindung und melde dich neu an (mit der gleichen Serveradresse und Zugangsdaten). Wenn du irgendwelche Medien auf dieses Gerät heruntergeladen hast, musst du sie anschließend erneut herunterladen, um den Status zu synchronisieren.",
|
||||
"MessagePodcastSearchField": "Suchbegriff oder RSS-Feed URL eingeben",
|
||||
"MessageProgressSyncFailed": "Der letzte Versuch den aktuellen Hörfortschritt an den Server zu melden ist fehlgeschlagen. Die Anfragen den Fortschritt zu synchronisieren wird alle 15 Sekunden bis 1 Minute versucht, während Medien abgespielt werden.",
|
||||
"MessageReportBugsAndContribute": "Fehler melden, Funktionen anfordern und mitwirken",
|
||||
"MessageSeriesAlreadyDownloaded": "Du hast bereits alle Bücher in dieser Serie heruntergeladen.",
|
||||
"MessageSeriesDownloadConfirm": "Lade {0} Bücher mit {1} Daten, zusammen {2}, in den Ordner {3} herunter?",
|
||||
"MessageSeriesDownloadConfirmIos": "Lade {0} Bücher mit {1} Daten, zusammen {2}, herunter?",
|
||||
"MessageSeriesDownloadConfirm": "Fehlende(s) {0} Buch/Bücher mit {1} Datei(en), insgesamt {2}, in den Ordner {3} herunterladen?",
|
||||
"MessageSeriesDownloadConfirmIos": "Fehlende(s) {0} Buch/Bücher mit {1} Datei(en), insgesamt {2}, herunterladen?",
|
||||
"MessageSocketConnectedOverMeteredCellular": "Socket verbunden über getaktetes Mobilfunknetz",
|
||||
"MessageSocketConnectedOverMeteredWifi": "Socket verbunden über getaktetes WLAN",
|
||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket verbunden über ungetaktetes Mobilfunknetz",
|
||||
|
||||
@@ -155,6 +155,9 @@
|
||||
"LabelFinished": "Finished",
|
||||
"LabelFolder": "Folder",
|
||||
"LabelFontBoldness": "Font Boldness",
|
||||
"LabelFontFamily": "Font family",
|
||||
"LabelFontFamilySans": "Sans",
|
||||
"LabelFontFamilySerif": "Serif",
|
||||
"LabelFontScale": "Font scale",
|
||||
"LabelGenre": "Genre",
|
||||
"LabelGenres": "Genres",
|
||||
@@ -298,6 +301,7 @@
|
||||
"MessageDownloading": "Downloading...",
|
||||
"MessageDownloadingEpisode": "Downloading episode",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download",
|
||||
"MessageFailedToRefreshToken": "Failed to refresh token, re-login required",
|
||||
"MessageFeedURLWillBe": "Feed URL will be {0}",
|
||||
"MessageFetching": "Fetching...",
|
||||
"MessageFollowTheProjectOnGithub": "Follow the project on Github",
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
"LabelEndOfChapter": "Fin del capítulo",
|
||||
"LabelEndTime": "Hora de finalización",
|
||||
"LabelEpisode": "Episodio",
|
||||
"LabelExplicit": "Explícito",
|
||||
"LabelFeedURL": "URL del suministro",
|
||||
"LabelFile": "Archivo",
|
||||
"LabelFileBirthtime": "Archivo creado en",
|
||||
|
||||
@@ -1 +1,179 @@
|
||||
{}
|
||||
{
|
||||
"ButtonAdd": "Lisa",
|
||||
"ButtonAddNewServer": "Lisa uus server",
|
||||
"ButtonAuthors": "Autorid",
|
||||
"ButtonBack": "Tagasi",
|
||||
"ButtonCancel": "Katkesta",
|
||||
"ButtonCancelTimer": "Katkesta taimer",
|
||||
"ButtonClearFilter": "Tühista filter",
|
||||
"ButtonCollections": "Kollektsioonid",
|
||||
"ButtonConnect": "Ühenda",
|
||||
"ButtonConnectToServer": "Ühenda serveriga",
|
||||
"ButtonCreate": "Loo uus",
|
||||
"ButtonCreateBookmark": "Loo uus järjehoidja",
|
||||
"ButtonCreateNewPlaylist": "Loo uus playlist",
|
||||
"ButtonDelete": "Kustuta",
|
||||
"ButtonDeleteLocalEpisode": "Kustuta lokaalne episood",
|
||||
"ButtonDeleteLocalFile": "Kustuta lokaalne fail",
|
||||
"ButtonDisableAutoTimer": "Lülita välja auto-taimer",
|
||||
"ButtonDisconnect": "Ühenda lahti",
|
||||
"ButtonHistory": "Ajalugu",
|
||||
"ButtonIssues": "Probleemid",
|
||||
"ButtonLatest": "Viimased",
|
||||
"ButtonLibrary": "Raamatukogu",
|
||||
"ButtonLocalMedia": "Lokaalne meedia",
|
||||
"ButtonNewFolder": "Uus kaust",
|
||||
"ButtonNextEpisode": "Järgmine episood",
|
||||
"ButtonOk": "Ok",
|
||||
"ButtonPause": "Paus",
|
||||
"ButtonPlay": "Play",
|
||||
"ButtonPlaylists": "Playlist",
|
||||
"ButtonRead": "Loe",
|
||||
"ButtonReadLess": "Loe vähem",
|
||||
"ButtonReadMore": "Loe rohkem",
|
||||
"ButtonRemove": "Kustuta",
|
||||
"ButtonRemoveFromServer": "Kustuta serverist",
|
||||
"ButtonSave": "Salvesta",
|
||||
"ButtonSaveOrder": "Salvesta järjekord",
|
||||
"ButtonSearch": "Otsi",
|
||||
"ButtonSendEbookToDevice": "Saada e-raamat seadmele",
|
||||
"ButtonSetTimer": "Säti taimer",
|
||||
"ButtonSubmit": "Esita",
|
||||
"ButtonUserStats": "Kasutaja statistika",
|
||||
"ButtonYes": "Jah",
|
||||
"HeaderAccount": "Konto",
|
||||
"HeaderAndroidAutoSettings": "Android Auto sätted",
|
||||
"HeaderChapters": "Peatükid",
|
||||
"HeaderConnectionStatus": "Ühenduse staatus",
|
||||
"HeaderDataSettings": "Andmete sätted",
|
||||
"HeaderDetails": "Detailid",
|
||||
"HeaderDownloads": "Allatõmmatud",
|
||||
"HeaderEbookFiles": "E-raamatu failid",
|
||||
"HeaderEpisodes": "Episoodid",
|
||||
"HeaderEreaderSettings": "E-lugeja sätted",
|
||||
"HeaderLatestEpisodes": "Viimased episoodid",
|
||||
"HeaderLibraries": "Raamatukogud",
|
||||
"HeaderLocalFolders": "Lokaalsed failid",
|
||||
"HeaderLocalLibraryItems": "Lokaalse raamatukogu esemed",
|
||||
"HeaderNewPlaylist": "Uus playlist",
|
||||
"HeaderPlaylist": "Playlist",
|
||||
"HeaderPlaylistItems": "Playlisti esemed",
|
||||
"HeaderProgressSyncFailed": "Progressi sühnkronisatsioon ebaõnnestus",
|
||||
"HeaderSelectDownloadLocation": "Vali allalaadimise kaust",
|
||||
"HeaderSettings": "Seaded",
|
||||
"HeaderSleepTimer": "Unetaimer",
|
||||
"HeaderSleepTimerSettings": "Unetaimeri seaded",
|
||||
"HeaderStatsRecentSessions": "Hiljutised sessioonid",
|
||||
"HeaderTableOfContents": "Sisukord",
|
||||
"HeaderUserInterfaceSettings": "Kasutajaliidese seaded",
|
||||
"HeaderYourStats": "Sinu statistika",
|
||||
"LabelAddToPlaylist": "Lisa playlisti",
|
||||
"LabelAddedAt": "Lisatud",
|
||||
"LabelAddedDate": "Lisatud {0}",
|
||||
"LabelAll": "Kõik",
|
||||
"LabelAllowSeekingOnMediaControls": "Luba positsiooniotsing meediateavituste juhtelementidel",
|
||||
"LabelAlways": "Alati",
|
||||
"LabelAndroidAutoBrowseLimitForGrouping": "Tähestikuline väljavõtmise limiit",
|
||||
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Ära kasuta tähestikulises järjekorras järjestamist juhul kui kuvatavaid esemeid on vähem kui see arv",
|
||||
"LabelAskConfirmation": "Küsi kinnitust",
|
||||
"LabelAuthor": "Autor",
|
||||
"LabelAuthorFirstLast": "Autor (eesnimi perekonnanimi)",
|
||||
"LabelAuthorLastFirst": "Autor (perekonnanimi, eesnimi)",
|
||||
"LabelAuthors": "Autorid",
|
||||
"LabelAutoDownloadEpisodes": "Episoodide automaatne allalaadimine",
|
||||
"LabelAutoRewindTime": "Keri aeg automaatselt tagasi",
|
||||
"LabelAutoSleepTimer": "Automaatne unetaimer",
|
||||
"LabelBooks": "Raamatud",
|
||||
"LabelChapterTrack": "Peatüki heliriba",
|
||||
"LabelChapters": "Peatükid",
|
||||
"LabelComplete": "Valmis",
|
||||
"LabelContinueBooks": "Jätka raamatuid",
|
||||
"LabelContinueEpisodes": "Jätka episoode",
|
||||
"LabelContinueListening": "Jätka kuulamist",
|
||||
"LabelContinueReading": "Jätka lugemist",
|
||||
"LabelDescription": "Kirjeldus",
|
||||
"LabelDownload": "Lae alla",
|
||||
"LabelDownloadUsingCellular": "Lae alla kasutades andmesidet",
|
||||
"LabelDownloaded": "Allalaaditud",
|
||||
"LabelDuration": "Kestvus",
|
||||
"LabelEbook": "E-raamat",
|
||||
"LabelEbooks": "E-raamatud",
|
||||
"LabelEnable": "Luba",
|
||||
"LabelEnd": "Lõpp",
|
||||
"LabelEndOfChapter": "Peatükki lõpp",
|
||||
"LabelEpisode": "Episood",
|
||||
"LabelFile": "Fail",
|
||||
"LabelFilename": "Faili nimi",
|
||||
"LabelFinished": "Lõpetatud",
|
||||
"LabelFolder": "Kaust",
|
||||
"LabelFontBoldness": "Fondi paksus",
|
||||
"LabelFontScale": "Fondi suurus",
|
||||
"LabelGenre": "Žanr",
|
||||
"LabelGenres": "Žanrid",
|
||||
"LabelHapticFeedback": "Haptiline tagasiside",
|
||||
"LabelHasEbook": "E-raamat olemas",
|
||||
"LabelIncomplete": "Lõpetamata",
|
||||
"LabelKeepScreenAwake": "Hoia ekraan ärkvel",
|
||||
"LabelLanguage": "Keel",
|
||||
"LabelLayoutAuto": "Auto",
|
||||
"LabelLight": "Hele",
|
||||
"LabelListenAgain": "Kuula uuesti",
|
||||
"LabelLocalBooks": "Lokaalsed raamatud",
|
||||
"LabelLocalPodcasts": "Lokaalsed podcastid",
|
||||
"LabelLow": "Madal",
|
||||
"LabelMediaType": "Meedia tüüp",
|
||||
"LabelMedium": "Keskmine",
|
||||
"LabelMissing": "Puudub",
|
||||
"LabelMore": "Rohkem",
|
||||
"LabelMoreInfo": "Rohkem infot",
|
||||
"LabelName": "Nimi",
|
||||
"LabelNavigateWithVolumeMirrored": "Peegeldatud",
|
||||
"LabelNavigateWithVolumeWhilePlayingDisabled": "Väljalülitatud",
|
||||
"LabelNavigateWithVolumeWhilePlayingEnabled": "Sisselülitatud",
|
||||
"LabelNever": "Mitte kunagi",
|
||||
"LabelNewestAuthors": "Uuemad autorid",
|
||||
"LabelNewestEpisodes": "Uuemad episoodid",
|
||||
"LabelNo": "Ei",
|
||||
"LabelNotFinished": "Lõpetamata",
|
||||
"LabelNotStarted": "Pole alustatud",
|
||||
"LabelNumEpisodes": "{0} episoode",
|
||||
"LabelNumEpisodesIncomplete": "{0} episoodi, {1} lõpetamata",
|
||||
"LabelNumberOfEpisodes": "# episoode",
|
||||
"LabelOff": "Väljalülitatud",
|
||||
"LabelOn": "Sisselülitatud",
|
||||
"LabelPassword": "Parool",
|
||||
"LabelPlaybackDirect": "Otse",
|
||||
"LabelPlaybackLocal": "Lokaalne",
|
||||
"LabelPlaybackTranscode": "Transkodeeri",
|
||||
"LabelPodcast": "Podcast",
|
||||
"LabelPodcasts": "Podcastid",
|
||||
"LabelProgress": "Progress",
|
||||
"LabelPubDate": "Publitseerimise kuupäev",
|
||||
"LabelPublishYear": "Publitseerimise aasta",
|
||||
"LabelPublishedDate": "Publitseeritud {0}",
|
||||
"LabelRandomly": "Juhuslikus järjekorras",
|
||||
"LabelRead": "Loetud läbi",
|
||||
"LabelReadAgain": "Loe uuesti",
|
||||
"LabelRecentlyAdded": "Hiljuti lisatud",
|
||||
"LabelRemoveFromPlaylist": "Eemalda playlistist",
|
||||
"LabelSeason": "Hooaeg",
|
||||
"LabelSelectADevice": "Vali seade",
|
||||
"LabelServerAddress": "Serveri aadres",
|
||||
"LabelShowAll": "Näita kõik",
|
||||
"LabelSize": "Suurus",
|
||||
"LabelSleepTimer": "Unetaimer",
|
||||
"LabelStart": "Alusta",
|
||||
"LabelStatsBestDay": "Parim päev",
|
||||
"LabelStatsDays": "Päevi",
|
||||
"LabelStatsInARow": "järjest",
|
||||
"LabelStatsItemsFinished": "Lõpetatud raamatud",
|
||||
"LabelStatsMinutes": "minuteid",
|
||||
"LabelStatsWeekListening": "Nädala keskmine",
|
||||
"LabelThemeBlack": "Must",
|
||||
"LabelThemeDark": "Pime",
|
||||
"LabelThemeLight": "Hele",
|
||||
"LabelTitle": "Pealkiri",
|
||||
"LabelTotalSize": "Kogusuurus",
|
||||
"LabelType": "Tüüp",
|
||||
"LabelUnknown": "Teadmata"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"ButtonAdd": "افزودن",
|
||||
"ButtonAddNewServer": "افزودن سرور جدید",
|
||||
"ButtonAuthors": "ناشر",
|
||||
"ButtonBack": "بازگشت",
|
||||
"ButtonCancel": "انصراف",
|
||||
"ButtonCancelTimer": "لغو تایمر",
|
||||
"ButtonClearFilter": "حذف صافی",
|
||||
"ButtonClearLogs": "حذف گزارشها",
|
||||
"ButtonCloseFeed": "بستن فید",
|
||||
"ButtonCollections": "مجموعه ها",
|
||||
"ButtonConnect": "اتصال",
|
||||
"ButtonConnectToServer": "اتصال به سرور",
|
||||
"ButtonCreate": "ساختن",
|
||||
"ButtonCreateBookmark": "ساخت نشانک",
|
||||
"ButtonCreateNewPlaylist": "ساخت لیست پخش جدید",
|
||||
"ButtonDelete": "حذف",
|
||||
"ButtonDeleteLocalEpisode": "حذف اپیزود های محلی",
|
||||
"ButtonDeleteLocalFile": "حذف فایل های محلی",
|
||||
"ButtonDeleteLocalItem": "حذف آیتم های محلی",
|
||||
"ButtonDisableAutoTimer": "غیر فعال کردن تایم خودکار",
|
||||
"ButtonDisconnect": "قطع کردن",
|
||||
"ButtonGoToWebClient": "رفتن به نسخه تحت وب سمت کاربر",
|
||||
"ButtonHistory": "تاریخچه",
|
||||
"ButtonHome": "خانه",
|
||||
"ButtonIssues": "مشکلات",
|
||||
"ButtonLatest": "جدیدترین",
|
||||
"ButtonLibrary": "کتابخانه",
|
||||
"ButtonLocalMedia": "رسانههای محلی",
|
||||
"ButtonLogs": "گزارشها",
|
||||
"ButtonManageLocalFiles": "مدیریت فایل های محلی",
|
||||
"ButtonMaskServerAddress": "پنهان کردن آدرس سرور",
|
||||
"ButtonNewFolder": "پوشه جدید",
|
||||
"ButtonNextEpisode": "اپیزود بعدی",
|
||||
"ButtonOk": "تایید",
|
||||
"ButtonOpenFeed": "باز کردن فید",
|
||||
"ButtonOverride": "جایگزینی",
|
||||
"ButtonPause": "توقف",
|
||||
"ButtonPlay": "پخش",
|
||||
"ButtonPlayEpisode": "پخش اپیزود",
|
||||
"ButtonPlaylists": "لیست پخش",
|
||||
"ButtonRead": "خواندن",
|
||||
"ButtonReadLess": "خواندن کمتر",
|
||||
"ButtonReadMore": "خواندن بیشتر",
|
||||
"ButtonRemove": "حذف",
|
||||
"ButtonRemoveFromServer": "حذف از سرور",
|
||||
"ButtonSave": "ذخیره",
|
||||
"ButtonSaveOrder": "ذخیره ترتیب",
|
||||
"ButtonSearch": "جستجو",
|
||||
"ButtonSendEbookToDevice": "ارسال کتاب الکترونیکی به دستگاه",
|
||||
"ButtonSeries": "مجموعه",
|
||||
"ButtonSetTimer": "تنظیم تایمر",
|
||||
"ButtonStream": "پخش آنلاین",
|
||||
"ButtonSubmit": "ثبت",
|
||||
"ButtonSwitchServerUser": "تعویض سرور/کاربر",
|
||||
"ButtonUnmaskServerAddress": "نمایش آدرس سرور"
|
||||
}
|
||||
@@ -27,7 +27,9 @@
|
||||
"ButtonLatest": "Viimeisimmät",
|
||||
"ButtonLibrary": "Kirjasto",
|
||||
"ButtonLocalMedia": "Paikallinen media",
|
||||
"ButtonLogs": "Lokit",
|
||||
"ButtonManageLocalFiles": "Hallitse paikallsiia tiedostoja",
|
||||
"ButtonMaskServerAddress": "Piilota palvelimen osoite",
|
||||
"ButtonNewFolder": "Uusi kansio",
|
||||
"ButtonNextEpisode": "Seuraava jakso",
|
||||
"ButtonOk": "Hyvä on",
|
||||
@@ -51,6 +53,7 @@
|
||||
"ButtonStream": "Suoratoisto",
|
||||
"ButtonSubmit": "Lähetä",
|
||||
"ButtonSwitchServerUser": "Vaihda palvelinta/käyttäjää",
|
||||
"ButtonUnmaskServerAddress": "Näytä palvelimen osoite",
|
||||
"ButtonUserStats": "Käyttäjän tilastot",
|
||||
"ButtonYes": "Kyllä",
|
||||
"HeaderAccount": "Tili",
|
||||
@@ -143,6 +146,7 @@
|
||||
"LabelEndOfChapter": "Luvun loppu",
|
||||
"LabelEndTime": "Päättymisaika",
|
||||
"LabelEpisode": "Jakso",
|
||||
"LabelExplicit": "Sopimaton",
|
||||
"LabelFeedURL": "Syötteen URL",
|
||||
"LabelFile": "Tiedosto",
|
||||
"LabelFileBirthtime": "Tiedoston syntymäaika",
|
||||
@@ -217,13 +221,14 @@
|
||||
"LabelPublishedDate": "Julkaistu {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Mukautettu omistajan sähköposti",
|
||||
"LabelRSSFeedCustomOwnerName": "Mukautettu omistajan nimi",
|
||||
"LabelRSSFeedOpen": "RSS Syöte Avoin",
|
||||
"LabelRSSFeedPreventIndexing": "Estä indeksointi",
|
||||
"LabelRSSFeedSlug": "RSS-syöte Slug",
|
||||
"LabelRandomly": "Satunnaisesti",
|
||||
"LabelRead": "Lue",
|
||||
"LabelReadAgain": "Lue uudelleen",
|
||||
"LabelRecentSeries": "Viimeisimmät sarjat",
|
||||
"LabelRecentlyAdded": "Viimeeksi lisätyt",
|
||||
"LabelRecentlyAdded": "Viimeksi lisätyt",
|
||||
"LabelRemoveFromPlaylist": "Poista soittolistalta",
|
||||
"LabelScaleElapsedTimeBySpeed": "Skaalaa kulunut aika nopeuden mukaan",
|
||||
"LabelSeason": "Kausi",
|
||||
@@ -238,6 +243,8 @@
|
||||
"LabelShowAll": "Näytä kaikki",
|
||||
"LabelSize": "Koko",
|
||||
"LabelSleepTimer": "Uniajastin",
|
||||
"LabelSleepTimerAlmostDoneChime": "Soita merkkiääni, kun on ajastin on lähellä loppua",
|
||||
"LabelSleepTimerAlmostDoneChimeHelp": "Soita merkkiääni, kun uniajastimessa on 30 sekuntia jäljellä",
|
||||
"LabelStart": "Aloita",
|
||||
"LabelStartTime": "Aloitusaika",
|
||||
"LabelStatsBestDay": "Paras päivä",
|
||||
@@ -291,6 +298,7 @@
|
||||
"MessageDownloading": "Ladataan...",
|
||||
"MessageDownloadingEpisode": "Ladataan jaksoa",
|
||||
"MessageEpisodesQueuedForDownload": "{0} jaksoa on latausjonossa",
|
||||
"MessageFailedToRefreshToken": "Tunnuksen uusiminen epäonnistui, kirjaudu uudelleen sisään",
|
||||
"MessageFeedURLWillBe": "Syötteen URL tulee olemaan {0}",
|
||||
"MessageFetching": "Haetaan...",
|
||||
"MessageFollowTheProjectOnGithub": "Seuraa projektia Githubissa",
|
||||
@@ -311,12 +319,16 @@
|
||||
"MessageNoItems": "Ei kohteita",
|
||||
"MessageNoItemsFound": "Kohteita ei löytynyt",
|
||||
"MessageNoListeningSessions": "Ei kuunteluistuntoja",
|
||||
"MessageNoLogs": "Ei lokeja",
|
||||
"MessageNoMediaFolders": "Ei mediakansioita",
|
||||
"MessageNoNetworkConnection": "Ei verkkoyhteyttä",
|
||||
"MessageNoPodcastsFound": "Podcasteja ei löytynyt",
|
||||
"MessageNoSeries": "Ei sarjoja",
|
||||
"MessageNoUpdatesWereNecessary": "Päivityksiä ei tarvittu",
|
||||
"MessageNoUserPlaylists": "Sinulla ei ole soittolistoja",
|
||||
"MessageOldServerAuthReLoginRequired": "Todennuksen tietoturvaa on parannettu palvelimen versiossa 2.26.0. Kaikkien käyttäjien on kirjauduttava uudelleen sisään.",
|
||||
"MessageOldServerAuthWarning": "Palvelun käyttää vanhentunutta todennusta",
|
||||
"MessageOldServerAuthWarningHelp": "Palvelimessa on vanhempi versio kuin v.2.26.0. Turvallisempi todennus lisättiin versioon v.2.26.0. On suositeltavaa päivittää palvelin uusimpaan versioon. Mikäli olet jo päivittänyt, kirjaudu uudelleen sisään ja käytä uutta todennusta sisäänkirjautumisessa.",
|
||||
"MessageOldServerConnectionWarning": "Palvelinyhteysmääritys käyttää vanhaa käyttäjätunnusta. Poista tämä palvelinyhteys ja lisää se uudelleen.",
|
||||
"MessageOldServerConnectionWarningHelp": "Määritit alun perin yhteyden tähän palvelimeen ennen tietokannan siirtoa versiossa 2.3.0, joka julkaistiin kesäkuussa 2023. Tuleva palvelinpäivitys poistaa mahdollisuuden kirjautua sisään tällä vanhalla yhteydellä. Poista olemassa oleva palvelinyhteys ja muodosta yhteys uudelleen (käyttäen samaa palvelinosoitetta ja tunnistetietoja). Jos sinulla on ladattua mediaa tällä laitteella, media on ladattava uudelleen synkronoitavaksi palvelimen kanssa.",
|
||||
"MessagePodcastSearchField": "Syötä hakutermi tai RSS-syötteen URL-osoite",
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
"LabelEndOfChapter": "Fin du chapitre",
|
||||
"LabelEndTime": "Heure de fin",
|
||||
"LabelEpisode": "Épisode",
|
||||
"LabelExplicit": "Contenu explicite",
|
||||
"LabelFeedURL": "URL du flux",
|
||||
"LabelFile": "Fichier",
|
||||
"LabelFileBirthtime": "Création du fichier",
|
||||
@@ -220,6 +221,7 @@
|
||||
"LabelPublishedDate": "Publié le {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Courriel personnalisée du propriétaire",
|
||||
"LabelRSSFeedCustomOwnerName": "Nom propriétaire personnalisé",
|
||||
"LabelRSSFeedOpen": "Flux RSS ouvert",
|
||||
"LabelRSSFeedPreventIndexing": "Empêcher l’indexation",
|
||||
"LabelRSSFeedSlug": "Identifiant d’URL du flux RSS",
|
||||
"LabelRandomly": "Au hasard",
|
||||
@@ -296,6 +298,7 @@
|
||||
"MessageDownloading": "Téléchargement…",
|
||||
"MessageDownloadingEpisode": "Téléchargement de l’épisode",
|
||||
"MessageEpisodesQueuedForDownload": "{0} épisode(s) mis en file pour téléchargement",
|
||||
"MessageFailedToRefreshToken": "Échec de l'actualisation du jeton, reconnexion requise",
|
||||
"MessageFeedURLWillBe": "l’URL du flux sera {0}",
|
||||
"MessageFetching": "Récupération…",
|
||||
"MessageFollowTheProjectOnGithub": "Suivre le projet sur GitHub",
|
||||
@@ -323,6 +326,9 @@
|
||||
"MessageNoSeries": "Aucune série",
|
||||
"MessageNoUpdatesWereNecessary": "Aucune mise à jour n’était nécessaire",
|
||||
"MessageNoUserPlaylists": "Vous n’avez aucune liste de lecture",
|
||||
"MessageOldServerAuthReLoginRequired": "L’authentification a été renforcée pour des raisons de sécurité dans la version 2.26.0 du serveur. Tous les utilisateurs doivent se reconnecter.",
|
||||
"MessageOldServerAuthWarning": "Le serveur utilise un système d’authentification obsolète",
|
||||
"MessageOldServerAuthWarningHelp": "Ce serveur utilise une version antérieure à la v2.26.0. Un système d’authentification plus sécurisé a été ajouté dans la version 2.26.0. Il est fortement recommandé de mettre à jour le serveur vers la dernière version. Si vous avez déjà effectué la mise à jour, reconnectez-vous pour utiliser le nouveau système d’authentification.",
|
||||
"MessageOldServerConnectionWarning": "La configuration de connexion du serveur utilise un ancien identifiant utilisateur. Veuillez supprimer et ajouter cette connexion serveur.",
|
||||
"MessageOldServerConnectionWarningHelp": "Vous avez initialement créé la connexion à ce serveur avant la migration de la base de données dans 2.3.0, publié en juin 2023. Une mise à jour future du serveur supprimera la possibilité de signer avec cette ancienne connexion. Veuillez supprimer la connexion existante du serveur et se connecter à nouveau (en utilisant la même adresse de serveur et les mêmes identifiants). Si vous avez des médias téléchargés sur cet appareil, les médias devront être téléchargés à nouveau pour synchroniser avec le serveur.",
|
||||
"MessagePodcastSearchField": "Saisir un terme de recherche ou l'URL d'un flux RSS",
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
"LabelEndOfChapter": "סוף הפרק",
|
||||
"LabelEndTime": "זמן סיום",
|
||||
"LabelEpisode": "פרק",
|
||||
"LabelExplicit": "מפורש",
|
||||
"LabelFeedURL": "כתובת ערוץ",
|
||||
"LabelFile": "קובץ",
|
||||
"LabelFileBirthtime": "זמן יצירת הקובץ",
|
||||
|
||||
@@ -6,16 +6,34 @@
|
||||
"ButtonCancel": "रद्द करें",
|
||||
"ButtonCancelTimer": "टाइमर रद्द करें",
|
||||
"ButtonClearFilter": "लागू फ़िल्टर साफ़ करें",
|
||||
"ButtonClearLogs": "अभिलेख साफ़ करें",
|
||||
"ButtonCloseFeed": "फ़ीड बंद करें",
|
||||
"ButtonCollections": "संग्रह",
|
||||
"ButtonConnect": "जोड़ना",
|
||||
"ButtonConnectToServer": "सर्वर से संबद्ध स्थापित करें",
|
||||
"ButtonCreate": "बनाएं",
|
||||
"ButtonCreateBookmark": "बुकमार्क बनाएं",
|
||||
"ButtonCreateNewPlaylist": "प्लेलिस्ट बनाएं",
|
||||
"ButtonDelete": "हटाएं",
|
||||
"ButtonDeleteLocalEpisode": "स्थानीय प्रकरण हटाएँ",
|
||||
"ButtonDeleteLocalFile": "स्थानीय फ़ाइल हटाएँ",
|
||||
"ButtonDeleteLocalItem": "स्थानीय वस्तु हटाएँ",
|
||||
"ButtonDisableAutoTimer": "स्वचालित घड़ी अक्षम करें",
|
||||
"ButtonDisconnect": "सर्वर संबद्ध तोड़े",
|
||||
"ButtonGoToWebClient": "वेब क्लाइंट पर जाएँ",
|
||||
"ButtonHistory": "इतिहास",
|
||||
"ButtonHome": "घर",
|
||||
"ButtonIssues": "समस्याएं",
|
||||
"ButtonLatest": "नवीनतम",
|
||||
"ButtonLibrary": "पुस्तकालय",
|
||||
"ButtonLogs": "अभिलेख",
|
||||
"ButtonManageLocalFiles": "स्थानीय फ़ाइल प्रबंधित करें",
|
||||
"ButtonMaskServerAddress": "सर्वर पता नकाबपोश करें",
|
||||
"ButtonNewFolder": "नया फ़ोल्डर",
|
||||
"ButtonNextEpisode": "अगला एपिसोड",
|
||||
"ButtonOk": "ठीक है",
|
||||
"ButtonOpenFeed": "फ़ीड खोलें",
|
||||
"ButtonOverride": "अवहेलना करें",
|
||||
"ButtonPlay": "चलाएँ",
|
||||
"ButtonPlaylists": "प्लेलिस्ट्स",
|
||||
"ButtonRead": "पढ़ लिया",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"LabelAlways": "Uvijek",
|
||||
"LabelAndroidAutoBrowseLimitForGrouping": "Ograničenje za abecednu kliznu traku",
|
||||
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Nemoj koristiti abecednu kliznu traku kada popis ima manje od ovoliko stavki",
|
||||
"LabelAndroidAutoBrowseSeriesSequenceOrder": "Redoslijed knjiga serija",
|
||||
"LabelAndroidAutoBrowseSeriesSequenceOrder": "Redoslijed knjiga serijala",
|
||||
"LabelAskConfirmation": "Zatraži potvrdu",
|
||||
"LabelAuthor": "Autor",
|
||||
"LabelAuthorFirstLast": "Autor (Ime Prezime)",
|
||||
@@ -110,14 +110,14 @@
|
||||
"LabelAutoRewindTime": "Automatsko vraćanje unatrag",
|
||||
"LabelAutoSleepTimer": "Automatski timer za spavanje",
|
||||
"LabelAutoSleepTimerAutoRewind": "Automatsko vraćanje unatrag timera za spavanje",
|
||||
"LabelAutoSleepTimerAutoRewindHelp": "Nakon što se aktivira timer za spavanje, stavka koju ste slušali automatski će se vratiti na zadani položaj.",
|
||||
"LabelAutoSleepTimerAutoRewindHelp": "Kada automatsk timer za spavanje završi, ponovna reprodukcija stavke će se automatski vratiti na zadani položaj.",
|
||||
"LabelAutoSleepTimerHelp": "Automatski timer za spavanje uključit će se u zadano vrijeme prilikom izvođenja medija.",
|
||||
"LabelBooks": "Knjige",
|
||||
"LabelChapterTrack": "Zapis poglavlja",
|
||||
"LabelChapters": "Poglavlja",
|
||||
"LabelClosePlayer": "Zatvori reproduktor",
|
||||
"LabelCollapseSeries": "Serijale prikaži sažeto",
|
||||
"LabelComplete": "Dovršeno",
|
||||
"LabelCollapseSeries": "Sažmi serijal",
|
||||
"LabelComplete": "Potpuno",
|
||||
"LabelContinueBooks": "Nastavi knjige",
|
||||
"LabelContinueEpisodes": "Nastavi nastavke",
|
||||
"LabelContinueListening": "Nastavi slušati",
|
||||
@@ -151,8 +151,8 @@
|
||||
"LabelFile": "Datoteka",
|
||||
"LabelFileBirthtime": "Datoteka stvorena",
|
||||
"LabelFileModified": "Datoteka izmijenjena",
|
||||
"LabelFilename": "Naziv datoteke",
|
||||
"LabelFinished": "Dovršeno",
|
||||
"LabelFilename": "Ime datoteke",
|
||||
"LabelFinished": "Završeno",
|
||||
"LabelFolder": "Mapa",
|
||||
"LabelFontBoldness": "Debljina slova",
|
||||
"LabelFontScale": "Veličina slova",
|
||||
@@ -199,7 +199,7 @@
|
||||
"LabelNewestAuthors": "Najnoviji autori",
|
||||
"LabelNewestEpisodes": "Najnoviji nastavci",
|
||||
"LabelNo": "Ne",
|
||||
"LabelNotFinished": "Nije dovršeno",
|
||||
"LabelNotFinished": "Nezavršeno",
|
||||
"LabelNotStarted": "Nije započeto",
|
||||
"LabelNumEpisodes": "{0} nastavaka",
|
||||
"LabelNumEpisodesIncomplete": "{0} nastavaka, {1} nedovršeno",
|
||||
@@ -227,7 +227,7 @@
|
||||
"LabelRandomly": "Nasumično",
|
||||
"LabelRead": "Čitaj",
|
||||
"LabelReadAgain": "Ponovno čitaj",
|
||||
"LabelRecentSeries": "Najnoviji serijali",
|
||||
"LabelRecentSeries": "Nedavni serijali",
|
||||
"LabelRecentlyAdded": "Nedavno dodano",
|
||||
"LabelRemoveFromPlaylist": "Ukloni s popisa za izvođenje",
|
||||
"LabelScaleElapsedTimeBySpeed": "Prilagodi preostalo vrijeme brzini izvođenja",
|
||||
@@ -243,7 +243,7 @@
|
||||
"LabelShowAll": "Prikaži sve",
|
||||
"LabelSize": "Veličina",
|
||||
"LabelSleepTimer": "Timer za spavanje",
|
||||
"LabelSleepTimerAlmostDoneChime": "Zvučno upozorenje prije isteka",
|
||||
"LabelSleepTimerAlmostDoneChime": "Odsviraj zvuk upozorenja neposredno prije isteka",
|
||||
"LabelSleepTimerAlmostDoneChimeHelp": "Reproduciraj zvučno upozorenje 30 sekundi prije isteka timera za spavanje",
|
||||
"LabelStart": "Početak",
|
||||
"LabelStartTime": "Vrijeme pokretanja",
|
||||
@@ -252,7 +252,7 @@
|
||||
"LabelStatsDays": "Dani",
|
||||
"LabelStatsDaysListened": "Dana slušano",
|
||||
"LabelStatsInARow": "uzastopno",
|
||||
"LabelStatsItemsFinished": "Dovršenih stavki",
|
||||
"LabelStatsItemsFinished": "Završene stavke",
|
||||
"LabelStatsMinutes": "minute",
|
||||
"LabelStatsMinutesListening": "Minuta odslušano",
|
||||
"LabelStatsWeekListening": "Tjedno slušanje",
|
||||
@@ -290,7 +290,7 @@
|
||||
"MessageConfirmDisableAutoTimer": "Sigurno želite onemogućiti automatski timer do kraja dana? Timer će se ponovno aktivirati po isteku trenutnog automatskog timera ili ako ponovno pokrenete aplikaciju.",
|
||||
"MessageConfirmDiscardProgress": "Sigurno želite resetirati napredak?",
|
||||
"MessageConfirmDownloadUsingCellular": "Spremate se pokrenuti preuzimanje koristeći mobilne podatke. Ovo može uzrokovati dodatne troškove. Želite li nastaviti?",
|
||||
"MessageConfirmMarkAsFinished": "Sigurno želite označiti ovu stavku dovršenom?",
|
||||
"MessageConfirmMarkAsFinished": "Sigurno želite označiti ovu stavku kao završenu?",
|
||||
"MessageConfirmRemoveBookmark": "Sigurno želite ukloniti knjižnu oznaku?",
|
||||
"MessageConfirmStreamingUsingCellular": "Pokrenut ćete strujanje korištenjem mobilnih podataka. Ovo može uzrokovati dodatne troškove. Želite li nastaviti?",
|
||||
"MessageDiscardProgress": "Zanemari napredak",
|
||||
@@ -298,15 +298,16 @@
|
||||
"MessageDownloading": "Preuzimanje...",
|
||||
"MessageDownloadingEpisode": "Preuzimam nastavak",
|
||||
"MessageEpisodesQueuedForDownload": "{0} nastavak(a) u redu za preuzimanje",
|
||||
"MessageFailedToRefreshToken": "Osvježavanje tokena nije uspjelo, potrebna je ponovna prijava",
|
||||
"MessageFeedURLWillBe": "URL izvora bit će {0}",
|
||||
"MessageFetching": "Dohvaćam...",
|
||||
"MessageFetching": "Dohvaćanje...",
|
||||
"MessageFollowTheProjectOnGithub": "Pratite projekt na GitHubu",
|
||||
"MessageItemDownloadCompleteFailedToCreate": "Stavka je uspješno preuzeta, ali izrada knjižničke stavke nije uspjela",
|
||||
"MessageItemMissing": "Stavka nedostaje i treba je popraviti na poslužitelju. Ovo se u pravilu događa ako datotekama nije moguće pristupiti.",
|
||||
"MessageLoading": "Učitavam...",
|
||||
"MessageLoadingServerData": "Učitavam podatke poslužitelja...",
|
||||
"MessageLoading": "Učitavanje...",
|
||||
"MessageLoadingServerData": "Učitavanje podataka poslužitelja...",
|
||||
"MessageLocalFolderDescription": "„Interna pohrana aplikacije” je dostupna samo s ovom aplikacijom. Ova aplikacija podržava samo medije koji su preuzeti izravno putem aplikacije. Dijeljene mape pohrane mogu se koristiti kako bi se ostalim aplikacijama omogućio pristup medijima koji su preuzeti pomoću ove aplikacije.",
|
||||
"MessageMarkAsFinished": "Označi kao dovršeno",
|
||||
"MessageMarkAsFinished": "Označi kao završeno",
|
||||
"MessageMediaLinkedToADifferentServer": "Medijski zapis vezan je s Audiobookshelf poslužiteljem na drugoj adresi ({0}). Napredak će se sinkronizirati samo dok ste povezani s poslužiteljem na toj adresi.",
|
||||
"MessageMediaLinkedToADifferentUser": "Medijski zapis povezan je s ovim poslužiteljem, ali preuzeo ga je drugi korisnik. Napredak će se sinkronizirati isključivo korisniku koji ga je preuzeo.",
|
||||
"MessageMediaLinkedToServer": "Povezani s poslužiteljem {0}",
|
||||
@@ -332,10 +333,10 @@
|
||||
"MessageOldServerConnectionWarningHelp": "Vezu s ovim poslužiteljem postavili ste prije migracije baze podataka u inačici 2.3.0 objavljenoj u lipnju 2023. Nadolazeće ažuriranje poslužitelja onemogućit će prijavu korištenjem ove stare veze. Molimo izbrišite postojeću vezu s poslužiteljem i ponovno se povežite (korištenjem iste adrese poslužitelja i korisničkih podataka). Ukoliko imate preuzetih medijskih datoteka na ovom uređaju, morat ćete ih iznova preuzeti da bi se sinkronizirali s poslužiteljem.",
|
||||
"MessagePodcastSearchField": "Upišite izraz za pretraživanje ili URL RSS izvora",
|
||||
"MessageProgressSyncFailed": "Zadnji pokušaj slanja napretka slušanja poslužitelju nije uspio. Nastavit ćemo s pokušajima slanja napretka svakih 15 do 60 sekundi dok god traje izvođenje medijske datoteke.",
|
||||
"MessageReportBugsAndContribute": "Prijavite pogreške, zatražite značajke i doprinesite na",
|
||||
"MessageSeriesAlreadyDownloaded": "Već ste preuzeli sve knjige iz ovog serijala.",
|
||||
"MessageSeriesDownloadConfirm": "Želite li preuzeti {0} knjiga koje nedostaju s {1} datoteka, ukupno {2} u mapu {3}?",
|
||||
"MessageSeriesDownloadConfirmIos": "Preuzeti {0} knjiga koje nedostaju s {1} datoteka, ukupno {2}?",
|
||||
"MessageReportBugsAndContribute": "Prijavite pogreške, zatražite funkcije i doprinesite na",
|
||||
"MessageSeriesAlreadyDownloaded": "Već ste preuzeli sve knjige ovog serijala.",
|
||||
"MessageSeriesDownloadConfirm": "Želite li preuzeti nedostajuće {0} knjige sa {1} datotekom/a, ukupno {2} u mapu {3}?",
|
||||
"MessageSeriesDownloadConfirmIos": "Želite li preuzeti nedostajuće {0} knjige sa {1} datotekoam/a, ukupno {2}?",
|
||||
"MessageSocketConnectedOverMeteredCellular": "Socket je povezan preko mobilnih podataka",
|
||||
"MessageSocketConnectedOverMeteredWifi": "Socket je povezan preko mobilne mreže s praćenjem potrošnje",
|
||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket je povezan preko mobilne mreže kod koje se ne prati potrošnja",
|
||||
@@ -347,12 +348,12 @@
|
||||
"ToastBookmarkRemoveFailed": "Brisanje knjižne bilješke nije uspjelo",
|
||||
"ToastBookmarkUpdateFailed": "Ažuriranje knjižne oznake nije uspjelo",
|
||||
"ToastDownloadNotAllowedOnCellular": "Preuzimanje korištenjem mobilnih podataka onemogućeno je",
|
||||
"ToastItemMarkedAsFinishedFailed": "Označavanje kao Dovršeno nije uspjelo",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Označavanje kao Nije dovršeno nije uspjelo",
|
||||
"ToastPlaylistCreateFailed": "Popis za izvođenje nije izrađen",
|
||||
"ToastPodcastCreateFailed": "Podcast nije izrađen",
|
||||
"ToastItemMarkedAsFinishedFailed": "Označavanje kao „Završeno” nije uspjelo",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Označavanje kao „Nezavršeno” nije uspjelo",
|
||||
"ToastPlaylistCreateFailed": "Izrada popisa za izvođenje nije uspjela",
|
||||
"ToastPodcastCreateFailed": "Stvaranje podcasta nije uspjelo",
|
||||
"ToastPodcastCreateSuccess": "Podcast uspješno izrađen",
|
||||
"ToastRSSFeedCloseFailed": "RSS izvor nije uspješno zatvoren",
|
||||
"ToastRSSFeedCloseFailed": "Zatvaranje RSS izvora nije uspjelo",
|
||||
"ToastRSSFeedCloseSuccess": "RSS izvor zatvoren",
|
||||
"ToastStreamingNotAllowedOnCellular": "Strujanje korištenjem mobilnih podataka onemogućeno je"
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
"LabelGenres": "Műfajok",
|
||||
"LabelHapticFeedback": "Tapintás visszajelzés",
|
||||
"LabelHasEbook": "Van e-könyv",
|
||||
"LabelHasSupplementaryEbook": "Van kiegészítő e-könyv",
|
||||
"LabelHasSupplementaryEbook": "Van kiegészítő e-könyve",
|
||||
"LabelHeavy": "Nehéz",
|
||||
"LabelHigh": "Magas",
|
||||
"LabelHost": "Kiszolgáló",
|
||||
@@ -298,6 +298,7 @@
|
||||
"MessageDownloading": "Letöltés...",
|
||||
"MessageDownloadingEpisode": "Epizód letöltése",
|
||||
"MessageEpisodesQueuedForDownload": "{0} epizód letöltésre vár",
|
||||
"MessageFailedToRefreshToken": "A token frissítése nem sikerült, ismételt bejelentkezés szükséges",
|
||||
"MessageFeedURLWillBe": "A hírcsatorna URL-je lesz: {0}",
|
||||
"MessageFetching": "Lekérdezés...",
|
||||
"MessageFollowTheProjectOnGithub": "Kövesse a projektet a GitHubon",
|
||||
@@ -325,6 +326,9 @@
|
||||
"MessageNoSeries": "Nincsenek sorozatok",
|
||||
"MessageNoUpdatesWereNecessary": "Nem volt szükséges frissítés",
|
||||
"MessageNoUserPlaylists": "Nincsenek lejátszási listái",
|
||||
"MessageOldServerAuthReLoginRequired": "A szerver v2.26.0 verziójában a biztonság érdekében továbbfejlesztettük a hitelesítést. Minden felhasználónak újra be kell jelentkeznie.",
|
||||
"MessageOldServerAuthWarning": "A szerver elavult hitelesítést használ",
|
||||
"MessageOldServerAuthWarningHelp": "Ez a szerver a v2.26.0-nál régebbi verziót futtat. A v2.26.0-ban egy biztonságosabb hitelesítési rendszer került bevezetésre. Erősen ajánlott a szervert a legújabb verzióra frissíteni. Ha már frissítette a szervert, jelentkezzen be újra, hogy az új hitelesítést használhassa.",
|
||||
"MessageOldServerConnectionWarning": "A kiszolgáló kapcsolat beállításai egy régi felhasználói azonosítót használnak. Kérjük, törölje és adja hozzá újra ezt a szerverkapcsolatot.",
|
||||
"MessageOldServerConnectionWarningHelp": "Eredetileg a 2023 júniusában megjelent 2.3.0-s verziójú adatbázis-migráció előtt állította be a kapcsolatot ehhez a kiszolgálóhoz. Egy jövőbeli kiszolgálófrissítéssel megszűnik a lehetőség, hogy ezzel a régi kapcsolattal jelentkezzen be. Kérjük, törölje a meglévő szerverkapcsolatot, és csatlakozzon újra (ugyanazzal a szervercímmel és hitelesítő adatokkal). Ha ezen az eszközön bármilyen letöltött média van, a szerverrel való szinkronizáláshoz a médiát újra le kell tölteni.",
|
||||
"MessagePodcastSearchField": "Adja meg a keresési kifejezést vagy az RSS hírcsatorna URL-címét",
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
"LabelEndOfChapter": "Fine Capitolo",
|
||||
"LabelEndTime": "Tempo alla fine",
|
||||
"LabelEpisode": "Episodio",
|
||||
"LabelExplicit": "Esplicito",
|
||||
"LabelFeedURL": "URL del flusso",
|
||||
"LabelFile": "File",
|
||||
"LabelFileBirthtime": "Data di creazione",
|
||||
@@ -297,6 +298,7 @@
|
||||
"MessageDownloading": "Scaricamento…",
|
||||
"MessageDownloadingEpisode": "Scaricamento dell’episodio in corso",
|
||||
"MessageEpisodesQueuedForDownload": "{0} episodio(i) in coda per lo scaricamento",
|
||||
"MessageFailedToRefreshToken": "Rinnovo del Token fallito, riautenticazione richiesta",
|
||||
"MessageFeedURLWillBe": "l’URL del flusso sarà {0}",
|
||||
"MessageFetching": "Recupero info…",
|
||||
"MessageFollowTheProjectOnGithub": "Segui il progetto su GitHub",
|
||||
@@ -324,6 +326,9 @@
|
||||
"MessageNoSeries": "Nessuna serie",
|
||||
"MessageNoUpdatesWereNecessary": "Nessun aggiornamento necessario",
|
||||
"MessageNoUserPlaylists": "Non hai nessuna Playlist",
|
||||
"MessageOldServerAuthReLoginRequired": "L'autenticazione è stata migliorata per la sicurezza nel server v2.26.0. Tutti gli utenti sono tenuti a re-login.",
|
||||
"MessageOldServerAuthWarning": "Server utilizza l'autenticazione obsoleta",
|
||||
"MessageOldServerAuthWarningHelp": "Questo server esegue una versione precedente a v2.26.0. Un sistema di autenticazione più sicuro è stato aggiunto in v2.26.0. Si raccomanda vivamente di aggiornare il server alla versione più recente. Se hai già aggiornato il server, accedi di nuovo per utilizzare la nuova autenticazione.",
|
||||
"MessageOldServerConnectionWarning": "La configurazione della connessione al server utilizza un vecchio ID utente. Elimina e aggiungi nuovamente questa connessione al server.",
|
||||
"MessageOldServerConnectionWarningHelp": "Originariamente hai impostato la connessione a questo server prima della migrazione del database in 2.3.0, rilasciata a giugno 2023. Un futuro aggiornamento del server rimuoverà la possibilità di accedere con questa vecchia connessione. Elimina la connessione al server esistente e riconnettiti (utilizzando lo stesso indirizzo server e le stesse credenziali). Se hai scaricato dei contenuti multimediali su questo dispositivo, i contenuti multimediali dovranno essere scaricati di nuovo per la sincronizzazione con il server.",
|
||||
"MessagePodcastSearchField": "Inserisci il termine di ricerca o l'URL del feed RSS",
|
||||
|
||||
@@ -1,14 +1,164 @@
|
||||
{
|
||||
"ButtonAdd": "追加",
|
||||
"ButtonAddNewServer": "サーバーを追加",
|
||||
"ButtonAuthors": "作者",
|
||||
"ButtonBack": "戻る",
|
||||
"ButtonCancel": "キャンセル",
|
||||
"ButtonCancelTimer": "キャンセルタイマー",
|
||||
"ButtonCancelTimer": "タイマーをキャンセル",
|
||||
"ButtonClearFilter": "絞り込みを解除",
|
||||
"ButtonClearLogs": "ログを削除",
|
||||
"ButtonCloseFeed": "フィードを閉じる",
|
||||
"ButtonCollections": "コレクション",
|
||||
"ButtonConnect": "接続",
|
||||
"ButtonConnectToServer": "サーバーに接続",
|
||||
"ButtonCreate": "作成",
|
||||
"ButtonCreateBookmark": "ブックマークを作成",
|
||||
"ButtonCreateNewPlaylist": "新しいプレイリストを作成",
|
||||
"ButtonDelete": "削除",
|
||||
"ButtonDeleteLocalEpisode": "ローカルエピソードを削除",
|
||||
"ButtonDeleteLocalFile": "ローカルファイルを削除",
|
||||
"ButtonDeleteLocalItem": "ローカルアイテムを削除",
|
||||
"ButtonDisableAutoTimer": "自動タイマーを無効",
|
||||
"ButtonDisconnect": "切断",
|
||||
"ButtonGoToWebClient": "ウェブクライアントに遷移",
|
||||
"ButtonHistory": "履歴",
|
||||
"ButtonHome": "ホーム",
|
||||
"ButtonIssues": "問題",
|
||||
"ButtonLatest": "最新",
|
||||
"ButtonLibrary": "ライブラリー",
|
||||
"ButtonLocalMedia": "ローカルメディア",
|
||||
"ButtonLogs": "ログ",
|
||||
"ButtonManageLocalFiles": "ローカルファイルの管理",
|
||||
"ButtonMaskServerAddress": "サーバーアドレスを隠す",
|
||||
"ButtonNewFolder": "新しいフォルダー",
|
||||
"ButtonNextEpisode": "次のエピソード",
|
||||
"ButtonOk": "はい",
|
||||
"ButtonPlay": "プレイ",
|
||||
"ButtonRead": "野村",
|
||||
"ButtonOpenFeed": "フィードを開く",
|
||||
"ButtonOverride": "上書きする",
|
||||
"ButtonPause": "一時停止",
|
||||
"ButtonPlay": "再生",
|
||||
"ButtonPlayEpisode": "エピソードを再生",
|
||||
"ButtonPlaylists": "プレイリスト",
|
||||
"ButtonRead": "読む",
|
||||
"ButtonReadLess": "閉じる",
|
||||
"ButtonReadMore": "もっと見る",
|
||||
"ButtonRemove": "削除",
|
||||
"ButtonRemoveFromServer": "サーバーから削除",
|
||||
"ButtonSave": "保存",
|
||||
"ButtonSaveOrder": "並び替えを保存",
|
||||
"ButtonSearch": "検索",
|
||||
"ButtonSeries": "シリーズ",
|
||||
"ButtonSetTimer": "タイマーを設定",
|
||||
"ButtonSubmit": "送信",
|
||||
"ButtonSwitchServerUser": "サーバー/ユーザーの切り替え",
|
||||
"ButtonUnmaskServerAddress": "サーバーアドレスを表示",
|
||||
"ButtonUserStats": "ユーザーの記録",
|
||||
"ButtonYes": "はい",
|
||||
"HeaderAccount": "アカウント",
|
||||
"HeaderAdvanced": "上級者向け",
|
||||
"HeaderAndroidAutoSettings": "Android Autoの設定",
|
||||
"HeaderAudioTracks": "オーディオトラック",
|
||||
"HeaderChapters": "チャプター",
|
||||
"HeaderCollection": "コレクション",
|
||||
"HeaderCollectionItems": "コレクションの項目",
|
||||
"HeaderConnectionStatus": "接続状況",
|
||||
"HeaderDataSettings": "データ設定",
|
||||
"HeaderDetails": "詳細",
|
||||
"HeaderDownloads": "ダウンロード",
|
||||
"HeaderEbookFiles": "電子書籍ファイル",
|
||||
"HeaderEpisodes": "エピソード",
|
||||
"HeaderEreaderSettings": "電子書籍リーダーの設定",
|
||||
"HeaderLatestEpisodes": "最新のエピソード",
|
||||
"HeaderLibraries": "ライブラリー",
|
||||
"HeaderLocalFolders": "ローカルフォルダー",
|
||||
"HeaderLocalLibraryItems": "ローカルライブラリーの項目",
|
||||
"HeaderNewPlaylist": "新しいプレイリスト",
|
||||
"HeaderOpenRSSFeed": "RSS Feedを開く",
|
||||
"HeaderPlaylist": "プレイリスト",
|
||||
"HeaderPlaylistItems": "プレイリストアイテム",
|
||||
"HeaderProgressSyncFailed": "進行中の同期失敗",
|
||||
"HeaderRSSFeed": "RSS Feed",
|
||||
"HeaderRSSFeedGeneral": "RSS 詳細",
|
||||
"HeaderSelectDownloadLocation": "保存場所の選択",
|
||||
"HeaderSettings": "設定",
|
||||
"HeaderSleepTimer": "スリープタイマー",
|
||||
"HeaderSleepTimerSettings": "スリープタイマー設定",
|
||||
"HeaderStatsMinutesListeningChart": "過去7日間の視聴時間(分)",
|
||||
"HeaderUserInterfaceSettings": "UI設定",
|
||||
"LabelAddToPlaylist": "プレイリストの追加",
|
||||
"LabelAllowSeekingOnMediaControls": "メディア通知コントロールでの位置情報の取得を許可",
|
||||
"LabelAskConfirmation": "確認を求める",
|
||||
"LabelAuthor": "著者",
|
||||
"LabelAuthorFirstLast": "著者(名 氏)",
|
||||
"LabelAuthorLastFirst": "著者(氏 名)",
|
||||
"LabelAuthors": "著者",
|
||||
"LabelAutoDownloadEpisodes": "エピソードの自動ダウンロード",
|
||||
"LabelAutoSleepTimer": "自動スリープタイマー",
|
||||
"LabelBooks": "ほん",
|
||||
"LabelChapterTrack": "チャプタートラック",
|
||||
"LabelChapters": "チャプター",
|
||||
"LabelClosePlayer": "プレイヤーを閉じる",
|
||||
"LabelComplete": "完了",
|
||||
"LabelContinueListening": "続きから聞く",
|
||||
"LabelDescription": "説明",
|
||||
"LabelDisableAudioFadeOut": "オーディオフェードアウトを無効",
|
||||
"LabelDisableAudioFadeOutHelp": "スリープタイマーの残り時間が1分を切ると、音量が徐々に小さくなります。フェードアウトしないようにするには、この設定を有効にしてください。",
|
||||
"LabelDisableAutoRewind": "自動巻き戻しを無効",
|
||||
"LabelDisableShakeToResetHelp": "タイマー作動中、またはタイマー終了後2分以内に端末を振ると、スリープタイマーがリセットされます。この設定を有効にすると、振ってリセットする機能を無効にできます。",
|
||||
"LabelDisableVibrateOnResetHelp": "スリープタイマーがリセットされると、デバイスが振動します。この設定を有効にすると、スリープタイマーがリセットされた際に振動しなくなります。",
|
||||
"LabelDownload": "ダウンロード",
|
||||
"LabelDownloadUsingCellular": "モバイル通信を使用してダウンロード",
|
||||
"LabelDownloaded": "ダウンロード完了",
|
||||
"LabelDuration": "長さ",
|
||||
"LabelEbook": "Eブック",
|
||||
"LabelEbooks": "Eブック",
|
||||
"LabelEnable": "有効",
|
||||
"LabelEnableMp3IndexSeeking": "mp3インデックス検索を有効化",
|
||||
"LabelEnableMp3IndexSeekingHelp": "この設定は、正しくシークできないMP3ファイルがある場合にのみ有効にしてください。シークの不正確さは、可変ビットレート(VBR)のMP3ファイルが原因である可能性が高いです。この設定はインデックスシークを強制し、ファイルの読み取り時に時間-バイトマッピングを構築します。大きなMP3ファイルの場合、ファイルの終わり付近をシークする際に遅延が生じる場合があります。",
|
||||
"LabelEndOfChapter": "チャプターの最後",
|
||||
"LabelEndTime": "終了時間",
|
||||
"LabelEpisode": "エピソード",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFile": "ファイル",
|
||||
"LabelFilename": "ファイル名",
|
||||
"LabelFinished": "完了",
|
||||
"LabelFolder": "フォルダ",
|
||||
"LabelFontBoldness": "フォントの太さ",
|
||||
"LabelFontScale": "フォントサイズ",
|
||||
"LabelGenre": "ジャンル",
|
||||
"LabelGenres": "ジャンル",
|
||||
"LabelHost": "ホスト",
|
||||
"LabelInProgress": "進行中",
|
||||
"LabelInternalAppStorage": "アプリ内ストレージ",
|
||||
"LabelKeepScreenAwake": "画面をスリープにしない",
|
||||
"LabelLanguage": "言語",
|
||||
"LabelLayout": "レイアウト",
|
||||
"LabelLayoutAuto": "自動",
|
||||
"LabelLayoutSinglePage": "単ページ",
|
||||
"LabelLight": "ライト",
|
||||
"LabelLineSpacing": "行間",
|
||||
"LabelListenAgain": "再度視聴",
|
||||
"LabelLocalBooks": "ローカルブック",
|
||||
"LabelLocalPodcasts": "ローカルポッドキャスト",
|
||||
"LabelLockPlayer": "プレイヤーのロック",
|
||||
"LabelMediaType": "メディアの種類",
|
||||
"LabelMoreInfo": "追加情報",
|
||||
"LabelName": "名",
|
||||
"LabelNarrator": "ナレーター",
|
||||
"LabelNarrators": "ナレーター",
|
||||
"LabelNavigateWithVolume": "音量キーで操作",
|
||||
"LabelNavigateWithVolumeWhilePlaying": "再生中に音量キーで操作を許可",
|
||||
"LabelNavigateWithVolumeWhilePlayingDisabled": "オフ",
|
||||
"LabelNavigateWithVolumeWhilePlayingEnabled": "オン",
|
||||
"LabelNewestAuthors": "最新の著者",
|
||||
"LabelNewestEpisodes": "最新エピソード",
|
||||
"LabelNumEpisodes": "{0} エピソード",
|
||||
"LabelNumEpisodesIncomplete": "{0} エピソード, {1} 未完了",
|
||||
"LabelPassword": "パスワード",
|
||||
"LabelPodcast": "ポッドキャスト"
|
||||
"LabelPath": "パス",
|
||||
"LabelPlaybackLocal": "ローカル",
|
||||
"LabelPodcast": "ポッドキャスト",
|
||||
"LabelPodcasts": "ポッドキャスト",
|
||||
"LabelPreventIndexing": "フィードがiTunesおよびGoogleのポッドキャストディレクトリにインデックス登録されるのを防ぎます",
|
||||
"LabelPublishYear": "公開年"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -137,7 +137,7 @@
|
||||
"LabelDownloadUsingCellular": "Download met mobiele data",
|
||||
"LabelDownloaded": "Gedownload",
|
||||
"LabelDuration": "Duur",
|
||||
"LabelEbook": "Ebook",
|
||||
"LabelEbook": "E-boek",
|
||||
"LabelEbooks": "Eboeken",
|
||||
"LabelEnable": "Inschakelen",
|
||||
"LabelEnableMp3IndexSeeking": "MP3-index zoeken inschakelen",
|
||||
@@ -146,6 +146,7 @@
|
||||
"LabelEndOfChapter": "Einde van het Hoofdstuk",
|
||||
"LabelEndTime": "Eindtijd",
|
||||
"LabelEpisode": "Aflevering",
|
||||
"LabelExplicit": "Expliciet",
|
||||
"LabelFeedURL": "Feed URL",
|
||||
"LabelFile": "Bestand",
|
||||
"LabelFileBirthtime": "Aanmaaktijd bestand",
|
||||
@@ -153,10 +154,10 @@
|
||||
"LabelFilename": "Bestandsnaam",
|
||||
"LabelFinished": "Voltooid",
|
||||
"LabelFolder": "Map",
|
||||
"LabelFontBoldness": "Font Boldness",
|
||||
"LabelFontBoldness": "Lettertype Dikte",
|
||||
"LabelFontScale": "Lettertype schaal",
|
||||
"LabelGenre": "Genre",
|
||||
"LabelGenres": "Genres",
|
||||
"LabelGenres": "Categorieën",
|
||||
"LabelHapticFeedback": "Haptische feedback",
|
||||
"LabelHasEbook": "Heeft Ebook",
|
||||
"LabelHasSupplementaryEbook": "Heeft aanvullend Ebook",
|
||||
@@ -220,7 +221,7 @@
|
||||
"LabelPublishedDate": "{0} gepubliceerd",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Aangepast e-mailadres eigenaar",
|
||||
"LabelRSSFeedCustomOwnerName": "Aangepaste naam eigenaar",
|
||||
"LabelRSSFeedOpen": "RSS Feed Open",
|
||||
"LabelRSSFeedOpen": "RSS Feed open",
|
||||
"LabelRSSFeedPreventIndexing": "Voorkom indexering",
|
||||
"LabelRSSFeedSlug": "RSS-feed slug",
|
||||
"LabelRandomly": "Willekeurig",
|
||||
@@ -297,6 +298,7 @@
|
||||
"MessageDownloading": "Downloaden...",
|
||||
"MessageDownloadingEpisode": "Aflevering aan het dowloaden",
|
||||
"MessageEpisodesQueuedForDownload": "{0} aflevering(en) in de rij om te downloaden",
|
||||
"MessageFailedToRefreshToken": "Token vernieuwen mislukt, opnieuw inloggen vereist",
|
||||
"MessageFeedURLWillBe": "Feed URL zal {0} zijn",
|
||||
"MessageFetching": "Aan het ophalen...",
|
||||
"MessageFollowTheProjectOnGithub": "Volg dit project op Github",
|
||||
@@ -324,6 +326,9 @@
|
||||
"MessageNoSeries": "Geen series",
|
||||
"MessageNoUpdatesWereNecessary": "Geen bijwerkingen waren noodzakelijk",
|
||||
"MessageNoUserPlaylists": "Je hebt geen afspeellijsten",
|
||||
"MessageOldServerAuthReLoginRequired": "Authenticatie is verbeterd om de veiligheid te verhogen in server versie v2.26.0. Alle gebruikers moeten opnieuw inloggen.",
|
||||
"MessageOldServerAuthWarning": "De server gebruikt een verouderde authenticatie versie",
|
||||
"MessageOldServerAuthWarningHelp": "Deze server draait een versie ouder dan v2.26.0. Een veiliger authenticatie systeem is geïntroduceerd in v2.26.0. Het wordt met klem aangeraden om de server te updaten naar de laatste versie. Mocht de server al geüpdatet zijn, log dan opnieuw in om het nieuwe authenticatie mechanisme te gebruiken.",
|
||||
"MessageOldServerConnectionWarning": "Server connectie configuratie gebruikt een oud user ID. Gelieve deze server connectie te verwijderen en opnieuw toe te voegen.",
|
||||
"MessageOldServerConnectionWarningHelp": "U hebt de verbinding met deze server oorspronkelijk ingesteld vóór de databasemigratie in versie 2.3.0, uitgebracht in juni 2023. Een toekomstige serverupdate zal de mogelijkheid om in te loggen met deze oude verbinding verwijderen. Verwijder de bestaande serververbinding en maak opnieuw verbinding (met hetzelfde serveradres en dezelfde inloggegevens). Als u gedownloade media op dit apparaat hebt, moet u deze opnieuw downloaden om te synchroniseren met de server.",
|
||||
"MessagePodcastSearchField": "Zoekterm of RSS feed URL invullen",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"ButtonCreateBookmark": "Opprett bokmerke",
|
||||
"ButtonCreateNewPlaylist": "Opprett ny spilleliste",
|
||||
"ButtonDelete": "Slett",
|
||||
"ButtonDeleteLocalEpisode": "Slett episode på enheten",
|
||||
"ButtonDeleteLocalEpisode": "Slett nedlastet episode",
|
||||
"ButtonDeleteLocalFile": "Slett fil på enheten",
|
||||
"ButtonDeleteLocalItem": "Slett element på enheten",
|
||||
"ButtonDisableAutoTimer": "Skru av automatisk tidsur",
|
||||
@@ -29,7 +29,7 @@
|
||||
"ButtonLocalMedia": "Lokal media",
|
||||
"ButtonLogs": "Logger",
|
||||
"ButtonManageLocalFiles": "Administrer Lokale Filer",
|
||||
"ButtonMaskServerAddress": "Skjul serveradresse",
|
||||
"ButtonMaskServerAddress": "Skjul tjeneradresse",
|
||||
"ButtonNewFolder": "Ny mappe",
|
||||
"ButtonNextEpisode": "Neste episode",
|
||||
"ButtonOk": "Ok",
|
||||
@@ -53,7 +53,7 @@
|
||||
"ButtonStream": "Strøm",
|
||||
"ButtonSubmit": "Send inn",
|
||||
"ButtonSwitchServerUser": "Bytt tjener/bruker",
|
||||
"ButtonUnmaskServerAddress": "Vis serveradresse",
|
||||
"ButtonUnmaskServerAddress": "Vis tjeneradresse",
|
||||
"ButtonUserStats": "Brukerstatistikk",
|
||||
"ButtonYes": "Ja",
|
||||
"HeaderAccount": "Konto",
|
||||
@@ -73,19 +73,20 @@
|
||||
"HeaderLatestEpisodes": "Siste episoder",
|
||||
"HeaderLibraries": "Biblioteker",
|
||||
"HeaderLocalFolders": "Lokale Mapper",
|
||||
"HeaderLocalLibraryItems": "Elementer i bibliotek på enheten",
|
||||
"HeaderLocalLibraryItems": "Nedlastede elementer",
|
||||
"HeaderNewPlaylist": "Ny spilleliste",
|
||||
"HeaderOpenRSSFeed": "Åpne RSS Feed",
|
||||
"HeaderPlaybackSettings": "Avspillingsinnstillinger",
|
||||
"HeaderPlaylist": "Spilleliste",
|
||||
"HeaderPlaylistItems": "Spillelisteelement",
|
||||
"HeaderProgressSyncFailed": "Fremskritts-synkronisering feilet",
|
||||
"HeaderRSSFeed": "RSS-informasjonskanal",
|
||||
"HeaderRSSFeedGeneral": "RSS Detailer",
|
||||
"HeaderRSSFeedIsOpen": "RSS Feed er åpen",
|
||||
"HeaderSelectDownloadLocation": "Velg Nedlastningssted",
|
||||
"HeaderSettings": "Innstillinger",
|
||||
"HeaderSleepTimer": "Sove timer",
|
||||
"HeaderSleepTimerSettings": "Innstillinger for Søvntidtaker",
|
||||
"HeaderSleepTimerSettings": "Innstillinger for søvntidtaker",
|
||||
"HeaderStatsMinutesListeningChart": "Minutter lyttet (siste 7 dagene)",
|
||||
"HeaderStatsRecentSessions": "Siste sesjoner",
|
||||
"HeaderTableOfContents": "Innholdsfortegnelse",
|
||||
@@ -93,9 +94,13 @@
|
||||
"HeaderYourStats": "Din statistikk",
|
||||
"LabelAddToPlaylist": "Legg til i spilleliste",
|
||||
"LabelAddedAt": "Lagt Til",
|
||||
"LabelAddedDate": "La til {0}",
|
||||
"LabelAll": "Alle",
|
||||
"LabelAllowSeekingOnMediaControls": "Tillat posisjonsblafring i mediamerknadskontroller",
|
||||
"LabelAlways": "Alltid",
|
||||
"LabelAndroidAutoBrowseLimitForGrouping": "Alfabetisk nedtreksmeny-grense",
|
||||
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Ikke bruk alfabetisk nedtrekksmeny når det er færre enn dette antallet elementer",
|
||||
"LabelAndroidAutoBrowseSeriesSequenceOrder": "Rekkefølge på bokserie",
|
||||
"LabelAskConfirmation": "Spør om bekreftelse",
|
||||
"LabelAuthor": "Forfatter",
|
||||
"LabelAuthorFirstLast": "Forfatter (Fornavn Etternavn)",
|
||||
@@ -105,7 +110,7 @@
|
||||
"LabelAutoRewindTime": "Automatisk tilbakespolingstid",
|
||||
"LabelAutoSleepTimer": "Automatisk søvntidsur",
|
||||
"LabelAutoSleepTimerAutoRewind": "Automatisk tilbakespoling for automatisk søvntidsur",
|
||||
"LabelAutoSleepTimerAutoRewindHelp": "Når automatisk søvntidsur fullføres vil avspilling av elementet automatisk spole tilbake posisjonen din.",
|
||||
"LabelAutoSleepTimerAutoRewindHelp": "Når automatisk søvntidsur fullføres og du gjenopptar avspillingen vil posisjonen din automatisk spoles tilbake.",
|
||||
"LabelAutoSleepTimerHelp": "Ved avspilling av media mellom angitt start og slutt vil automatisk søvntidsur starte automatisk.",
|
||||
"LabelBooks": "Bøker",
|
||||
"LabelChapterTrack": "Kapittelspor",
|
||||
@@ -121,10 +126,10 @@
|
||||
"LabelCustomTime": "Egendefinert tid",
|
||||
"LabelDescription": "Beskrivelse",
|
||||
"LabelDisableAudioFadeOut": "Skru av automatisk uttoning",
|
||||
"LabelDisableAudioFadeOutHelp": "Lydstyrke vil minkes når det er mindre enn ett minutt igjen på søvntidsuret. Skru av dette for å ikke ha uttoning.",
|
||||
"LabelDisableAudioFadeOutHelp": "Lydstyrke vil minkes når det er mindre enn ett minutt igjen på søvntidsuret. Aktiver denne innstillingen for å ikke ha uttoning.",
|
||||
"LabelDisableAutoRewind": "Skru av automatisk tilbakespoling",
|
||||
"LabelDisableShakeToReset": "Skru av risting for å tilbakestille",
|
||||
"LabelDisableShakeToResetHelp": "Risting av enheten mens tidsuret kjører, eller opptil to minutter etter utløp av tidsuret tilbakestiller søvntidsuret. Skru på denne innstillinger for å skru av risting for å tilbakestille.",
|
||||
"LabelDisableShakeToResetHelp": "Rister du enheten mens tidsuret kjører, eller opptil to minutter etter utløp av tidsuret, tilbakestillers søvntidsuret. Aktiver denne innstillinger for å skru av risting for å tilbakestille.",
|
||||
"LabelDisableVibrateOnReset": "Skru av vibrering ved tilbakestilling",
|
||||
"LabelDisableVibrateOnResetHelp": "Når søvntidsuret blir tilbakestilt vil enheten din vibrere. Skru på denne innstillingen for å skru av vibrering når tidsuret tilbakestilles.",
|
||||
"LabelDiscover": "Oppdagelse",
|
||||
@@ -140,6 +145,8 @@
|
||||
"LabelEnd": "Slutt",
|
||||
"LabelEndOfChapter": "Slutt på kapittel",
|
||||
"LabelEndTime": "Slutt-tid",
|
||||
"LabelEpisode": "Episode",
|
||||
"LabelExplicit": "Eksplisitt",
|
||||
"LabelFeedURL": "Feed Adresse",
|
||||
"LabelFile": "Fil",
|
||||
"LabelFileBirthtime": "Fil Opprettelsesdato",
|
||||
@@ -151,63 +158,94 @@
|
||||
"LabelFontScale": "Font størrelse",
|
||||
"LabelGenre": "Sjanger",
|
||||
"LabelGenres": "Sjangers",
|
||||
"LabelHapticFeedback": "Føllighetsrespons",
|
||||
"LabelHapticFeedback": "Taktil respons",
|
||||
"LabelHasEbook": "Har e-bok",
|
||||
"LabelHasSupplementaryEbook": "Har komplimentær e-bok",
|
||||
"LabelHasSupplementaryEbook": "Har supplerende e-bok",
|
||||
"LabelHeavy": "Tung",
|
||||
"LabelHigh": "Høy",
|
||||
"LabelHost": "Tjener",
|
||||
"LabelInProgress": "I gang",
|
||||
"LabelIncomplete": "Ufullstendig",
|
||||
"LabelInternalAppStorage": "Programlager på enheten",
|
||||
"LabelInternalAppStorage": "Intern lagringsplass",
|
||||
"LabelJumpBackwardsTime": "Tilbakespolingstid",
|
||||
"LabelJumpForwardsTime": "Foroverspolingstid",
|
||||
"LabelKeepScreenAwake": "Hold skjerm våken",
|
||||
"LabelLanguage": "Språk",
|
||||
"LabelLayout": "Utseende",
|
||||
"LabelLayoutAuto": "Automatisk",
|
||||
"LabelLayoutSinglePage": "Enkeltside",
|
||||
"LabelLight": "Lett",
|
||||
"LabelLineSpacing": "Linjemellomrom",
|
||||
"LabelListenAgain": "Lytt igjen",
|
||||
"LabelLocalBooks": "Bøker på enheten",
|
||||
"LabelLocalBooks": "Nedlastede bøker",
|
||||
"LabelLocalPodcasts": "Nedlastede Podcaster",
|
||||
"LabelLockOrientation": "Lås rotasjon",
|
||||
"LabelLockPlayer": "Lås avspiller",
|
||||
"LabelLow": "Lav",
|
||||
"LabelMediaType": "Medie type",
|
||||
"LabelMedium": "Middels",
|
||||
"LabelMissing": "Mangler",
|
||||
"LabelMore": "Mer",
|
||||
"LabelMoreInfo": "Mer info",
|
||||
"LabelName": "Navn",
|
||||
"LabelNarrator": "Forteller",
|
||||
"LabelNarrators": "Fortellere",
|
||||
"LabelNavigateWithVolume": "Naviger med volum-knapper",
|
||||
"LabelNavigateWithVolumeMirrored": "Speilet",
|
||||
"LabelNavigateWithVolumeWhilePlaying": "Navigere med volumknapper under avspilling",
|
||||
"LabelNavigateWithVolumeWhilePlayingDisabled": "Av",
|
||||
"LabelNavigateWithVolumeWhilePlayingEnabled": "På",
|
||||
"LabelNever": "Aldri",
|
||||
"LabelNewestAuthors": "Nyeste forfattere",
|
||||
"LabelNewestEpisodes": "Nyeste episoder",
|
||||
"LabelNo": "Nei",
|
||||
"LabelNotFinished": "Ikke fullført",
|
||||
"LabelNotStarted": "Ikke startet",
|
||||
"LabelNumEpisodes": "{0} episoder",
|
||||
"LabelNumEpisodesIncomplete": "{0} episoder, {1} ufullstendige",
|
||||
"LabelNumberOfEpisodes": "# episoder",
|
||||
"LabelOff": "Av",
|
||||
"LabelOn": "På",
|
||||
"LabelPassword": "Passord",
|
||||
"LabelPath": "Sti",
|
||||
"LabelPlaybackDirect": "Direkte",
|
||||
"LabelPlaybackLocal": "Lokal",
|
||||
"LabelPlaybackSpeed": "Avspillingshastighet",
|
||||
"LabelPlaybackTranscode": "Transkoding",
|
||||
"LabelPlaybackTranscode": "Konvertering",
|
||||
"LabelPodcast": "Podcast",
|
||||
"LabelPodcasts": "Podcaster",
|
||||
"LabelPreventIndexing": "Forhindre at din feed fra å bli indeksert av iTunes og Google podcast kataloger",
|
||||
"LabelProgress": "Framgang",
|
||||
"LabelPubDate": "Publiseringsdato",
|
||||
"LabelPublishYear": "Publikasjonsår",
|
||||
"LabelPublishedDate": "Publisert {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Tilpasset eier e-post",
|
||||
"LabelRSSFeedCustomOwnerName": "Tilpasset eier Navn",
|
||||
"LabelRSSFeedOpen": "RSS-strøm åpen",
|
||||
"LabelRSSFeedPreventIndexing": "Forhindre indeksering",
|
||||
"LabelRSSFeedSlug": "RSS-informasjonskanalunderadresse",
|
||||
"LabelRandomly": "Tilfeldighet",
|
||||
"LabelRead": "Les",
|
||||
"LabelReadAgain": "Les igjen",
|
||||
"LabelRecentSeries": "Nylige serier",
|
||||
"LabelRecentlyAdded": "Nylig tillagt",
|
||||
"LabelRemoveFromPlaylist": "Fjern fra spilleliste",
|
||||
"LabelScaleElapsedTimeBySpeed": "Skaler forløpt tid etter hastighet",
|
||||
"LabelSeason": "Sesong",
|
||||
"LabelSelectADevice": "Velg en enhet",
|
||||
"LabelSequenceAscending": "Stigende rekkefølge",
|
||||
"LabelSequenceDescending": "Synkende rekkefølge",
|
||||
"LabelSeries": "Serier",
|
||||
"LabelServerAddress": "Tjener-adresse",
|
||||
"LabelSetEbookAsPrimary": "Sett som primær",
|
||||
"LabelSetEbookAsSupplementary": "Sett som supplerende",
|
||||
"LabelShakeSensitivity": "Ristingsterskel",
|
||||
"LabelShowAll": "Vis alt",
|
||||
"LabelSize": "Størrelse",
|
||||
"LabelSleepTimer": "Sove-timer",
|
||||
"LabelSleepTimerAlmostDoneChime": "Spill en lyd når nesten ferdig",
|
||||
"LabelSleepTimerAlmostDoneChimeHelp": "Spill av en lyd når søvnklokken har 30 sekunder igjen",
|
||||
"LabelStart": "Start",
|
||||
"LabelStartTime": "Start Tid",
|
||||
"LabelStatsBestDay": "Beste dag",
|
||||
"LabelStatsDailyAverage": "Daglig gjennomsnitt",
|
||||
@@ -219,8 +257,10 @@
|
||||
"LabelStatsMinutesListening": "Minutter lyttet",
|
||||
"LabelStatsWeekListening": "Uker lyttet",
|
||||
"LabelStreamingUsingCellular": "Strømmer via mobildata",
|
||||
"LabelTag": "Merke",
|
||||
"LabelTags": "Tagger",
|
||||
"LabelTheme": "Tema",
|
||||
"LabelThemeBlack": "Svart",
|
||||
"LabelThemeDark": "Mørk",
|
||||
"LabelThemeLight": "Lys",
|
||||
"LabelTimeRemaining": "{0} gjennstående",
|
||||
@@ -228,12 +268,16 @@
|
||||
"LabelTotalSize": "Total størrelse",
|
||||
"LabelTotalTrack": "Totalt antall spor",
|
||||
"LabelTracks": "Spor",
|
||||
"LabelType": "Type",
|
||||
"LabelUnknown": "Ukjent",
|
||||
"LabelUnlockPlayer": "Lås opp avspiller",
|
||||
"LabelUseBookshelfView": "Bruk bokhyllevisning",
|
||||
"LabelUser": "Bruker",
|
||||
"LabelUsername": "Brukernavn",
|
||||
"LabelVeryHigh": "Veldig høy",
|
||||
"LabelVeryLow": "Veldig lav",
|
||||
"LabelYearReviewHide": "Skjul oppsummering av året",
|
||||
"LabelYearReviewShow": "Vis oppsummering av året",
|
||||
"LabelYourBookmarks": "Dine bokmerker",
|
||||
"LabelYourProgress": "Din fremgang",
|
||||
"MessageAndroid10Downloads": "Android 10 og tidligere bruker lagringsplass på enheten for nedlastninger.",
|
||||
@@ -243,13 +287,14 @@
|
||||
"MessageBookshelfEmpty": "Tom bokhylle",
|
||||
"MessageConfirmDeleteLocalEpisode": "Fjen episoden «{0}» fra enheten? Filen på tjeneren forblir som den er.",
|
||||
"MessageConfirmDeleteLocalFiles": "Fjern dette elementets filer fra enheten? Filene på tjeneren og framdriften forblir som den er.",
|
||||
"MessageConfirmDiscardProgress": "Tilbakestill framgang?",
|
||||
"MessageConfirmDisableAutoTimer": "Er du sikker på at du vil slå av den automatiske tidsuret for resten av dagen? Tidsuret vil bli reaktivert på slutten av perioden til dette automatiske søvn-tidsuret, eller hvis du starter appen på nytt.",
|
||||
"MessageConfirmDiscardProgress": "Er du sikker på at du vil tilbakestille fremdriften din?",
|
||||
"MessageConfirmDownloadUsingCellular": "Last ned over mobildata selv om kostnader kan tilkomme?",
|
||||
"MessageConfirmMarkAsFinished": "Merk element som fullført?",
|
||||
"MessageConfirmRemoveBookmark": "Fjern bokmerke?",
|
||||
"MessageConfirmMarkAsFinished": "Er du sikker på at du vil merke element som fullført?",
|
||||
"MessageConfirmRemoveBookmark": "Er du sikker på at du vil fjerne bokmerket?",
|
||||
"MessageConfirmStreamingUsingCellular": "Strøm via mobildata selv om kostnader kan tilkomme?",
|
||||
"MessageDiscardProgress": "Forkast framdrift",
|
||||
"MessageDownloadCompleteProcessing": "Behandler …",
|
||||
"MessageDownloadCompleteProcessing": "Nedlasting fullført. Behandler …",
|
||||
"MessageDownloading": "Laster ned …",
|
||||
"MessageDownloadingEpisode": "Laster ned episode",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episode(r) lagt til i kø for nedlasting",
|
||||
@@ -257,28 +302,44 @@
|
||||
"MessageFetching": "Henter...",
|
||||
"MessageFollowTheProjectOnGithub": "Følg prosjektet på GitHub",
|
||||
"MessageItemDownloadCompleteFailedToCreate": "Element nedlastet, men kunne ikke opprette bibliotekselement",
|
||||
"MessageItemMissing": "Elementet mangler og må bli korrigert på tjeneren. Vanligvis blir et element markert som manglende hvis filens lokasjon ikke er tilgjengelig.",
|
||||
"MessageLoading": "Laster...",
|
||||
"MessageLoadingServerData": "Laster inn tjenerdata …",
|
||||
"MessageLocalFolderDescription": "'Intern lagringsminne for app' er bare tilgjengelig for denne appen. Denne appen støtter bare medier lastet ned direkte gjennom appen. Mapper for delt lagringsminne kan bli brukt for å gi andre apper tilgang til media lastet ned av denne appen.",
|
||||
"MessageMarkAsFinished": "Marker som Fullført",
|
||||
"MessageMediaLinkedToADifferentServer": "Media er lenket til en Audiobookshelf-tjener på en annen adresse ({0}). Framdriften vil bli synkronisert når tilkoblet denne tjeneradressen.",
|
||||
"MessageMediaLinkedToADifferentServer": "Media er lenket til en Audiobookshelf-tjener på en annen adresse ({0}). Framdriften vil bli synkronisert når du blir tilkoblet denne tjeneradressen.",
|
||||
"MessageMediaLinkedToADifferentUser": "Media er lenket til denne tjeneren, men ble lastet ned av en annen bruker. Framdriften vil kun synkroniseres til brukeren som lastet den ned.",
|
||||
"MessageMediaLinkedToServer": "Lenket til tjener {0}",
|
||||
"MessageMediaLinkedToThisServer": "Nedlastet media er lenket til denne tjeneren",
|
||||
"MessageMediaNotLinkedToServer": "Media er ikke lenket til en Audiobookshelf-tjener. Ingen fremdrift vil bli synkronisert.",
|
||||
"MessageNoBookmarks": "Ingen bokmerker",
|
||||
"MessageNoChapters": "Ingen kapitler",
|
||||
"MessageNoCollections": "Ingen samlinger",
|
||||
"MessageNoItems": "Ingen gjenstander",
|
||||
"MessageNoItemsFound": "Ingen gjenstander funnet",
|
||||
"MessageNoListeningSessions": "Ingen Lyttesesjoner",
|
||||
"MessageNoLogs": "Ingen logger",
|
||||
"MessageNoMediaFolders": "Ingen mediamapper",
|
||||
"MessageNoNetworkConnection": "Ingen nettverkstilkobling",
|
||||
"MessageNoPodcastsFound": "Ingen podcaster funnet",
|
||||
"MessageNoSeries": "Ingen serier",
|
||||
"MessageNoUpdatesWereNecessary": "Ingen oppdatering var nødvendig",
|
||||
"MessageNoUserPlaylists": "Du har ingen spillelister",
|
||||
"MessageOldServerAuthReLoginRequired": "Autentisering har blitt forbedret for sikkerhet i tjener-versjon v2.26.0. Alle brukere må logge inn igjen.",
|
||||
"MessageOldServerAuthWarning": "Tjeneren bruker utdatert autentisering",
|
||||
"MessageOldServerAuthWarningHelp": "Denne tjeneren kjører en versjon som er eldre enn v2.26.0. Et mer sikkert autentiseringssystem ble lagt til i v.2.26.0. Det anbefales sterkt å oppdatere tjeneren til siste versjon. Hvis du allerede har oppdatert tjeneren, logg inn igjen for å bruke den nye autentiseringen.",
|
||||
"MessageOldServerConnectionWarning": "Konfigurasjonen til tjener-tilkoblingen bruker en gammel bruker ID. Vennligst slett og legg til igjen denne tjener-tilkoblingen.",
|
||||
"MessageOldServerConnectionWarningHelp": "Du opprettet opprinnelig tilkoblingen til denne tjeneren før database-migreringen i versjon 2.3.0, som ble lansert i juni 2023. En fremtidig oppdatering av tjeneren vil fjerne muligheten for å logge inn med denne gamle tilkoblingen. Vennligst slett den eksisterende tjenertilkoblingen og koble til på nytt (bruk samme tjeneradresse og påloggingsinformasjon). Hvis du har nedlastede medier på denne enheten, må disse lastes ned på nytt for å synkronisere med tjeneren.",
|
||||
"MessagePodcastSearchField": "Skriv inn søkeord eller URL til en RSS-strøm",
|
||||
"MessageProgressSyncFailed": "Det siste forsøket på å rapportere lyttefremdriften til tjeneren mislyktes. Forsøk på å synkronisere fremdriften vil fortsette hvert 15. sekund til hvert minutt så lenge mediet spilles av.",
|
||||
"MessageReportBugsAndContribute": "Rapporter feil, forespør funksjoner og tillegg og bidra på",
|
||||
"MessageSeriesAlreadyDownloaded": "Du har allerede lastet ned alle bøkene i denne serien.",
|
||||
"MessageSeriesDownloadConfirm": "Laste ned manglende {0} bok/bøker med {1} fil(er), totalt {2} til mappe {3}?",
|
||||
"MessageSeriesDownloadConfirmIos": "Laste ned manglende {0} bok/bøker med {1} fil(er), totalt {2}?",
|
||||
"MessageSocketConnectedOverMeteredCellular": "Socket tilkoblet over kvotebasert mobildata",
|
||||
"MessageSocketConnectedOverMeteredWifi": "Socket tilkoblet over kvotebasert Wi-Fi",
|
||||
"MessageSocketConnectedOverUnmeteredWifi": "Socket tilkoblet over ubegrenset Wi-Fi",
|
||||
"MessageSocketConnectedOverUnmeteredCellular": "Tilkobling via ubegrenset mobildata",
|
||||
"MessageSocketConnectedOverUnmeteredWifi": "Tilkoblet over ubegrenset Wi-Fi",
|
||||
"MessageSocketNotConnected": "Socket ikke tilkoblet",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Advarsel! De fleste podcast applikasjoner trenger RSS feed URL som bruker HTTPS",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Advarsel! 1 eller flere av episodene har ikke publikasjonsdato. Noen podcast applikasjoner trenger dette.",
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
"LabelDisableVibrateOnResetHelp": "Gdy wyłącznik czasowy zostanie zresetowany, urządzenie zacznie wibrować. Włącz to ustawienie, aby nie wibrować po zresetowaniu wyłącznika czasowego.",
|
||||
"LabelDiscover": "Odkrywaj",
|
||||
"LabelDownload": "Pobierz",
|
||||
"LabelDownloadUsingCellular": "Pobierz przez Dane Komórkowe",
|
||||
"LabelDownloadUsingCellular": "Pobierz przy użyciu danych komórkowych",
|
||||
"LabelDownloaded": "Pobrane",
|
||||
"LabelDuration": "Czas trwania",
|
||||
"LabelEbook": "Ebook",
|
||||
@@ -168,10 +168,10 @@
|
||||
"LabelInternalAppStorage": "Pamięć wewnętrzna aplikacji",
|
||||
"LabelJumpBackwardsTime": "Przeskok wstecz",
|
||||
"LabelJumpForwardsTime": "Przeskok w przód",
|
||||
"LabelKeepScreenAwake": "Utrzymuj ekran włączony",
|
||||
"LabelKeepScreenAwake": "Utrzymuj włączony ekran",
|
||||
"LabelLanguage": "Język",
|
||||
"LabelLayout": "Układ",
|
||||
"LabelLayoutAuto": "Układ automatyczny",
|
||||
"LabelLayoutAuto": "Automatycznie",
|
||||
"LabelLayoutSinglePage": "Pojedyncza strona",
|
||||
"LabelLight": "Lekko",
|
||||
"LabelLineSpacing": "Odstęp między wierszami",
|
||||
@@ -218,8 +218,8 @@
|
||||
"LabelPubDate": "Data publikacji",
|
||||
"LabelPublishYear": "Rok publikacji",
|
||||
"LabelPublishedDate": "Opublikowano {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Własny email",
|
||||
"LabelRSSFeedCustomOwnerName": "Własna nazwa",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Email właściciela",
|
||||
"LabelRSSFeedCustomOwnerName": "Nazwa właściciela",
|
||||
"LabelRSSFeedOpen": "Otwarty Kanał RSS",
|
||||
"LabelRSSFeedPreventIndexing": "Zapobiegaj indeksowaniu",
|
||||
"LabelRSSFeedSlug": "Numer Kanału RSS",
|
||||
@@ -258,7 +258,7 @@
|
||||
"LabelStreamingUsingCellular": "Transmisja przy użyciu sieci komórkowej",
|
||||
"LabelTag": "Znacznik",
|
||||
"LabelTags": "Znaczniki",
|
||||
"LabelTheme": "Motyw",
|
||||
"LabelTheme": "Kompozycja",
|
||||
"LabelThemeBlack": "Czarny",
|
||||
"LabelThemeDark": "Ciemny",
|
||||
"LabelThemeLight": "Jasny",
|
||||
@@ -297,6 +297,7 @@
|
||||
"MessageDownloading": "Pobieranie...",
|
||||
"MessageDownloadingEpisode": "Pobieranie odcinka",
|
||||
"MessageEpisodesQueuedForDownload": "{0} odcinki w kolejce do pobrania",
|
||||
"MessageFailedToRefreshToken": "Odświeżenie tokenu nie powiodło się. Wymagane jest ponowne zalogowanie.",
|
||||
"MessageFeedURLWillBe": "URL kanału: {0}",
|
||||
"MessageFetching": "Pobieranie...",
|
||||
"MessageFollowTheProjectOnGithub": "Obserwuj projekt na Github'ie",
|
||||
@@ -316,12 +317,17 @@
|
||||
"MessageNoItems": "Brak elementów",
|
||||
"MessageNoItemsFound": "Nie znaleziono żadnych elementów",
|
||||
"MessageNoListeningSessions": "Brak sesji słuchania",
|
||||
"MessageNoLogs": "Brak logów",
|
||||
"MessageNoMediaFolders": "Brak folderów z mediami",
|
||||
"MessageNoNetworkConnection": "Brak połączenia sieciowego",
|
||||
"MessageNoPodcastsFound": "Nie znaleziono podcastów",
|
||||
"MessageNoSeries": "Brak serii",
|
||||
"MessageNoUpdatesWereNecessary": "Brak wymaganych aktualizacji",
|
||||
"MessageNoUserPlaylists": "Nie masz żadnych list odtwarzania",
|
||||
"MessageOldServerAuthReLoginRequired": "Uwierzytelnianie zostało ulepszone pod kątem bezpieczeństwa w serwerze w wersji 2.26.0. Wszyscy użytkownicy muszą się ponownie zalogować.",
|
||||
"MessageOldServerAuthWarning": "Serwer korzysta z przestarzałego uwierzytelniania",
|
||||
"MessageOldServerAuthWarningHelp": "Ten serwer działa w wersji starszej niż 2.26.0. W wersji 2.26.0 dodano bezpieczniejszy system uwierzytelniania. Zdecydowanie zaleca się aktualizację serwera do najnowszej wersji. Jeżeli dokonałeś już aktualizacji serwera, zaloguj się ponownie, aby użyć nowego uwierzytelniania.",
|
||||
"MessageOldServerConnectionWarning": "Konfiguracja połączenia z serwerem używa starego identyfikatora użytkownika. Usuń i dodaj ponownie to połączenie z serwerem.",
|
||||
"MessageOldServerConnectionWarningHelp": "Połączenie z tym serwerem zostało pierwotnie skonfigurowane przed migracją bazy danych w wersji 2.3.0, wydanej w czerwcu 2023 r. Przyszła aktualizacja serwera usunie możliwość logowania się przy użyciu tego starego połączenia. Usuń istniejące połączenie z serwerem i połącz się ponownie (używając tego samego adresu serwera i danych logowania). Jeśli na tym urządzeniu znajdują się pobrane pliki multimedialne, konieczne będzie ich ponowne pobranie w celu synchronizacji z serwerem.",
|
||||
"MessagePodcastSearchField": "Wprowadź wyszukiwane hasło lub adres URL kanału RSS",
|
||||
"MessageProgressSyncFailed": "Ostatnia próba zgłoszenia postępów słuchania do serwera nie powiodła się. Żądania synchronizacji postępu będą wysyłane co 15 sekund do 1 minuty podczas odtwarzania multimediów.",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"ButtonCancel": "Cancelar",
|
||||
"ButtonCancelTimer": "Cancelar Timer",
|
||||
"ButtonClearFilter": "Limpar Filtro",
|
||||
"ButtonClearLogs": "Limpar Logs",
|
||||
"ButtonCloseFeed": "Fechar Feed",
|
||||
"ButtonCollections": "Coleções",
|
||||
"ButtonConnect": "Conectar",
|
||||
@@ -26,7 +27,9 @@
|
||||
"ButtonLatest": "Mais Recentes",
|
||||
"ButtonLibrary": "Biblioteca",
|
||||
"ButtonLocalMedia": "Mídia Local",
|
||||
"ButtonLogs": "Logs",
|
||||
"ButtonManageLocalFiles": "Gerenciar Arquivos Locais",
|
||||
"ButtonMaskServerAddress": "Mascarar endereço do servidor",
|
||||
"ButtonNewFolder": "Nova Pasta",
|
||||
"ButtonNextEpisode": "Próximo Episódio",
|
||||
"ButtonOk": "Ok",
|
||||
@@ -47,9 +50,10 @@
|
||||
"ButtonSendEbookToDevice": "Enviar Ebook para Dispositivo",
|
||||
"ButtonSeries": "Série",
|
||||
"ButtonSetTimer": "Definir Timer",
|
||||
"ButtonStream": "Fazer o Stream",
|
||||
"ButtonStream": "Stream",
|
||||
"ButtonSubmit": "Enviar",
|
||||
"ButtonSwitchServerUser": "Trocar Servidor/Usuário",
|
||||
"ButtonUnmaskServerAddress": "Revelar endereço do servidor",
|
||||
"ButtonUserStats": "Estatísticas do Usuário",
|
||||
"ButtonYes": "Sim",
|
||||
"HeaderAccount": "Conta",
|
||||
@@ -65,7 +69,7 @@
|
||||
"HeaderDownloads": "Downloads",
|
||||
"HeaderEbookFiles": "Arquivos Ebook",
|
||||
"HeaderEpisodes": "Episódios",
|
||||
"HeaderEreaderSettings": "Configurações Ereader",
|
||||
"HeaderEreaderSettings": "Configurações do leitor de ebook",
|
||||
"HeaderLatestEpisodes": "Últimos episódios",
|
||||
"HeaderLibraries": "Bibliotecas",
|
||||
"HeaderLocalFolders": "Pastas Locais",
|
||||
@@ -94,6 +98,9 @@
|
||||
"LabelAll": "Todos",
|
||||
"LabelAllowSeekingOnMediaControls": "Permitir busca de posição nos controles de notificação de mídia",
|
||||
"LabelAlways": "Sempre",
|
||||
"LabelAndroidAutoBrowseLimitForGrouping": "Limite de retirada alfabético",
|
||||
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Não utilize a retirada alfabética quando houver menos do que essa quantidade de itens para mostrar",
|
||||
"LabelAndroidAutoBrowseSeriesSequenceOrder": "Ordem de livros na série",
|
||||
"LabelAskConfirmation": "Pedir confirmação",
|
||||
"LabelAuthor": "Autor",
|
||||
"LabelAuthorFirstLast": "Autor (Nome Sobrenome)",
|
||||
@@ -136,9 +143,10 @@
|
||||
"LabelEnableMp3IndexSeeking": "Ativar busca por índice mp3",
|
||||
"LabelEnableMp3IndexSeekingHelp": "Esta configuração só deve ser ativada se a busca em arquivos mp3 não estiver funcionando corretamente. Buscas incorretas são normalmente causadas por arquivos MP3 com bitrate variável (VBR). Esta configuração forçará a busca por índice, que é uma consulta do tempo-para-byte, elaborado durante a leitura do arquivo. Em alguns casos, com arquivos MP3 maiores, haverá um atraso ao buscar perto do final do arquivo.",
|
||||
"LabelEnd": "Fim",
|
||||
"LabelEndOfChapter": "Fim do Chapítulo",
|
||||
"LabelEndOfChapter": "Fim do Capítulo",
|
||||
"LabelEndTime": "Tempo final",
|
||||
"LabelEpisode": "Episódio",
|
||||
"LabelExplicit": "Explícito",
|
||||
"LabelFeedURL": "URL do Feed",
|
||||
"LabelFile": "Arquivo",
|
||||
"LabelFileBirthtime": "Criação do Arquivo",
|
||||
@@ -155,12 +163,15 @@
|
||||
"LabelHasSupplementaryEbook": "Tem ebook complementar",
|
||||
"LabelHeavy": "Pesado",
|
||||
"LabelHigh": "Alta",
|
||||
"LabelHost": "Host",
|
||||
"LabelInProgress": "Em Andamento",
|
||||
"LabelIncomplete": "Incompleto",
|
||||
"LabelInternalAppStorage": "Armazenamento Interno do App",
|
||||
"LabelJumpBackwardsTime": "Retroceder tempo",
|
||||
"LabelJumpForwardsTime": "Adiantar tempo",
|
||||
"LabelKeepScreenAwake": "Manter a tela acesa",
|
||||
"LabelLanguage": "Idioma",
|
||||
"LabelLayout": "Layout",
|
||||
"LabelLayoutAuto": "Automático",
|
||||
"LabelLayoutSinglePage": "Uma página",
|
||||
"LabelLight": "Leve",
|
||||
@@ -173,31 +184,47 @@
|
||||
"LabelLow": "Baixa",
|
||||
"LabelMediaType": "Tipo de Mídia",
|
||||
"LabelMedium": "Média",
|
||||
"LabelMissing": "Ausente",
|
||||
"LabelMore": "Mais",
|
||||
"LabelMoreInfo": "Mais Informações",
|
||||
"LabelName": "Nome",
|
||||
"LabelNarrator": "Narrador",
|
||||
"LabelNarrators": "Narradores",
|
||||
"LabelNavigateWithVolume": "Navegar com teclas de volume",
|
||||
"LabelNavigateWithVolumeMirrored": "Espelhado",
|
||||
"LabelNavigateWithVolumeWhilePlaying": "Permitir navegação por teclas de volume durante a reprodução",
|
||||
"LabelNavigateWithVolumeWhilePlayingDisabled": "Desligado",
|
||||
"LabelNavigateWithVolumeWhilePlayingEnabled": "Ligado",
|
||||
"LabelNever": "Nunca",
|
||||
"LabelNewestAuthors": "Novos Autores",
|
||||
"LabelNewestEpisodes": "Episódios mais recentes",
|
||||
"LabelNo": "Não",
|
||||
"LabelNotFinished": "Não concluído",
|
||||
"LabelNotStarted": "Não Iniciado",
|
||||
"LabelNumEpisodes": "{0} episódios",
|
||||
"LabelNumEpisodesIncomplete": "{0} episódios, {1} incompletos",
|
||||
"LabelNumberOfEpisodes": "# de Episódios",
|
||||
"LabelOff": "Desligado",
|
||||
"LabelOn": "Ligado",
|
||||
"LabelPassword": "Senha",
|
||||
"LabelPath": "Caminho",
|
||||
"LabelPlaybackDirect": "Direto",
|
||||
"LabelPlaybackLocal": "Local",
|
||||
"LabelPlaybackSpeed": "Velocidade de Reprodução",
|
||||
"LabelPlaybackTranscode": "Transcodificar",
|
||||
"LabelPodcast": "Podcast",
|
||||
"LabelPodcasts": "Podcasts",
|
||||
"LabelPreventIndexing": "Evitar que o seu feed seja indexado pelos diretórios de podcast do iTunes e Google",
|
||||
"LabelProgress": "Progresso",
|
||||
"LabelPubDate": "Data de Publicação",
|
||||
"LabelPublishYear": "Ano de Publicação",
|
||||
"LabelPublishedDate": "Publicado {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "E-mail do dono personalizado",
|
||||
"LabelRSSFeedCustomOwnerName": "Nome do dono personalizado",
|
||||
"LabelRSSFeedOpen": "Feed de RSS Aberto",
|
||||
"LabelRSSFeedPreventIndexing": "Impedir Indexação",
|
||||
"LabelRSSFeedSlug": "Slug do Feed RSS",
|
||||
"LabelRandomly": "Aleatoriamente",
|
||||
"LabelRead": "Lido",
|
||||
"LabelReadAgain": "Ler novamente",
|
||||
"LabelRecentSeries": "Séries Recentes",
|
||||
@@ -206,6 +233,8 @@
|
||||
"LabelScaleElapsedTimeBySpeed": "Proporcionalizar Tempo Decorrido com a Velocidade",
|
||||
"LabelSeason": "Temporada",
|
||||
"LabelSelectADevice": "Selecione um dispositivo",
|
||||
"LabelSequenceAscending": "Sequência Ascendente",
|
||||
"LabelSequenceDescending": "Sequence Descendente",
|
||||
"LabelSeries": "Série",
|
||||
"LabelServerAddress": "Endereço do servidor",
|
||||
"LabelSetEbookAsPrimary": "Definir como principal",
|
||||
@@ -214,6 +243,8 @@
|
||||
"LabelShowAll": "Exibir Todos",
|
||||
"LabelSize": "Tamanho",
|
||||
"LabelSleepTimer": "Timer",
|
||||
"LabelSleepTimerAlmostDoneChime": "Tocar um som quando perto do fim",
|
||||
"LabelSleepTimerAlmostDoneChimeHelp": "Tocar um som quando sobrarem 30 segundos no timer",
|
||||
"LabelStart": "Iniciar",
|
||||
"LabelStartTime": "Iniciado Em",
|
||||
"LabelStatsBestDay": "Melhor Dia",
|
||||
@@ -229,6 +260,7 @@
|
||||
"LabelTag": "Etiqueta",
|
||||
"LabelTags": "Etiquetas",
|
||||
"LabelTheme": "Tema",
|
||||
"LabelThemeBlack": "Preto",
|
||||
"LabelThemeDark": "Escuro",
|
||||
"LabelThemeLight": "Claro",
|
||||
"LabelTimeRemaining": "{0} restantes",
|
||||
@@ -237,12 +269,15 @@
|
||||
"LabelTotalTrack": "Total da Trilha",
|
||||
"LabelTracks": "Trilhas",
|
||||
"LabelType": "Tipo",
|
||||
"LabelUnknown": "Desconhecido",
|
||||
"LabelUnlockPlayer": "Destravar Reprodutor",
|
||||
"LabelUseBookshelfView": "Usar visão estante",
|
||||
"LabelUser": "Usuário",
|
||||
"LabelUsername": "Nome do usuário",
|
||||
"LabelVeryHigh": "Muito Alta",
|
||||
"LabelVeryLow": "Muito Baixa",
|
||||
"LabelYearReviewHide": "Ocultar Retrospectiva",
|
||||
"LabelYearReviewShow": "Exibir Retrospectiva",
|
||||
"LabelYourBookmarks": "Seus Marcadores",
|
||||
"LabelYourProgress": "Seu Progresso",
|
||||
"MessageAndroid10Downloads": "Android 10 e versões anteriores utilizarão o armazenamento interno para downloads.",
|
||||
@@ -252,6 +287,7 @@
|
||||
"MessageBookshelfEmpty": "Estante vazia",
|
||||
"MessageConfirmDeleteLocalEpisode": "Remover episódio local \"{0}\" do seu dispositivo? O arquivo no servidor não será afetado.",
|
||||
"MessageConfirmDeleteLocalFiles": "Remover arquivos locais deste item do seu dispositivo? Os arquivos no servidor e o seu progresso não serão afetados.",
|
||||
"MessageConfirmDisableAutoTimer": "Tem certeza de que deseja desabilitar o timer automático para o resto do dia? O timer será re-ativado ao final do período do timer automático ou se você reiniciar o aplicativo.",
|
||||
"MessageConfirmDiscardProgress": "Tem certeza de que deseja resetar o seu progresso?",
|
||||
"MessageConfirmDownloadUsingCellular": "Você irá fazer o download usando dados do celular. Isso pode incidir em cobrança por sua operadora. Deseja continuar?",
|
||||
"MessageConfirmMarkAsFinished": "Tem certeza de que deseja marcar esse item como concluído?",
|
||||
@@ -262,12 +298,15 @@
|
||||
"MessageDownloading": "Realizando o download...",
|
||||
"MessageDownloadingEpisode": "Realizando o downloading do episódio",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Episódio(s) na fila de download",
|
||||
"MessageFailedToRefreshToken": "Falha ao atualizar o token, faça o login novamente.",
|
||||
"MessageFeedURLWillBe": "URL do Feed será {0}",
|
||||
"MessageFetching": "Buscando...",
|
||||
"MessageFollowTheProjectOnGithub": "Siga este projeto no GitHub",
|
||||
"MessageItemDownloadCompleteFailedToCreate": "Download concluído mas não foi possível criar item da biblioteca",
|
||||
"MessageItemMissing": "O item está ausente e precisa ser corrigido no servidor. Normalmente, um item é marcado como ausente quando não é possível acessar o caminho para o arquivo.",
|
||||
"MessageLoading": "Carregando...",
|
||||
"MessageLoadingServerData": "Carregando dados do servidor...",
|
||||
"MessageLocalFolderDescription": "\"Armazenamento Interno do App\" só pode ser acessado por esse aplicativo. Esse aplicativo só aceita o download de mídias feito diretamente pelo app. Pastas de armazenamento compartilhadas podem ser utilizadas para permitir que outros aplicativos acessem as mídias que foram baixadas por esse app.",
|
||||
"MessageMarkAsFinished": "Marcar como Concluído",
|
||||
"MessageMediaLinkedToADifferentServer": "Mídia está associada a um servidor Audiobookshelf em um endereço diferente ({0}). O progresso será sincronizado quando conectado a esse servidor.",
|
||||
"MessageMediaLinkedToADifferentUser": "Mídia está associada a este servidor mas o download foi feito por outro usuário. O progresso só será sincronizado para o usuário que realizou o download.",
|
||||
@@ -276,15 +315,28 @@
|
||||
"MessageMediaNotLinkedToServer": "Mídia não está associada a um servidor Audiobookshelf. Nenhum progresso será sincronizado.",
|
||||
"MessageNoBookmarks": "Sem Marcadores",
|
||||
"MessageNoChapters": "Sem Capítulos",
|
||||
"MessageNoCollections": "Sem Coleções",
|
||||
"MessageNoItems": "Sem Itens",
|
||||
"MessageNoItemsFound": "Nenhum item encontrado",
|
||||
"MessageNoListeningSessions": "Sem Sessões de Escuta",
|
||||
"MessageNoLogs": "Sem Logs",
|
||||
"MessageNoMediaFolders": "Sem Pastas de Mídia",
|
||||
"MessageNoNetworkConnection": "Nem conexão de rede",
|
||||
"MessageNoPodcastsFound": "Nenhum podcast encontrado",
|
||||
"MessageNoSeries": "Sem séries",
|
||||
"MessageNoUpdatesWereNecessary": "Nenhuma atualização é necessária",
|
||||
"MessageNoUserPlaylists": "Você não tem listas de reprodução",
|
||||
"MessageOldServerAuthReLoginRequired": "Autenticação foi reforçada por motivos de segurança na versão v2.26.0 do servidor. Todos os usuários precisam fazer login novamente.",
|
||||
"MessageOldServerAuthWarning": "O servidor está usando uma autenticação desatualizada",
|
||||
"MessageOldServerAuthWarningHelp": "Este servidor está usando uma versão anterior à v2.26.0. Um sistema de autenticação mais seguro foi incluído na versão v2.26.0. É fortemente recomendada atualização do servidor para a versão mais atual. Se você já atualizou o servidor, faça o login novamente para usar a nova autenticação.",
|
||||
"MessageOldServerConnectionWarning": "A configuração de conexão ao servidor está usando um ID de usuário antigo. Por favor, exclua e acrescente novamente essa conexão ao servidor.",
|
||||
"MessageOldServerConnectionWarningHelp": "A conexão para esse servidor foi configurada antes da migração do banco de dados na versão 2.3.0, lançada em junho de 2023. Uma atualização futura do servidor irá remover a capacidade de fazer o login com essa conexão antiga. Por favor, apague a conexão ao servidor existente e conecte novamente (usando o mesmo endereço do servidor e credenciais). Se você tiver baixado mídias neste dispositivo, a mídia precisará ser baixada novamente para sincronizar com o servidor.",
|
||||
"MessagePodcastSearchField": "Digite um termo para a busca ou a URL de um feed RSS",
|
||||
"MessageProgressSyncFailed": "A tentativa mais recente de informar o seu progresso de escuta para o servidor falhou. Os pedidos de sincronia de progresso continuarão a ser tentados a cada 15 segundo a 1 minuto enquanto a mídia estiver sendo reproduzida.",
|
||||
"MessageReportBugsAndContribute": "Reporte bugs, peça funcionalidades e contribua em",
|
||||
"MessageSeriesAlreadyDownloaded": "Você já baixou todos os livros desta série.",
|
||||
"MessageSeriesDownloadConfirm": "Baixar {0} livro(s) com {1} arquivo(s), totalizando {2} para a pasta {3}?",
|
||||
"MessageSeriesDownloadConfirmIos": "Baixar {0} livro(s) ausente(s) com {1} arquivo(s), totalizando {2}?",
|
||||
"MessageSocketConnectedOverMeteredCellular": "Socket conectado usando rede celular limitada",
|
||||
"MessageSocketConnectedOverMeteredWifi": "Socket conectado usando Wi-Fi limitada",
|
||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket conectado usando rede celular ilimitada",
|
||||
|
||||
@@ -1 +1,359 @@
|
||||
{}
|
||||
{
|
||||
"ButtonAdd": "Adaugă",
|
||||
"ButtonAddNewServer": "Adaugă server nou",
|
||||
"ButtonAuthors": "Autori",
|
||||
"ButtonBack": "Înapoi",
|
||||
"ButtonCancel": "Anulează",
|
||||
"ButtonCancelTimer": "Anulează timerul",
|
||||
"ButtonClearFilter": "Șterge filtrul",
|
||||
"ButtonClearLogs": "Șterge jurnalele",
|
||||
"ButtonCloseFeed": "Închide sursa",
|
||||
"ButtonCollections": "Colecții",
|
||||
"ButtonConnect": "Conectează",
|
||||
"ButtonConnectToServer": "Conectează la server",
|
||||
"ButtonCreate": "Creează",
|
||||
"ButtonCreateBookmark": "Creează semn de carte",
|
||||
"ButtonCreateNewPlaylist": "Creează playlist nou",
|
||||
"ButtonDelete": "Șterge",
|
||||
"ButtonDeleteLocalEpisode": "Șterge episodul local",
|
||||
"ButtonDeleteLocalFile": "Șterge fișierul local",
|
||||
"ButtonDeleteLocalItem": "Șterge elementul local",
|
||||
"ButtonDisableAutoTimer": "Dezactivează timerul automat",
|
||||
"ButtonDisconnect": "Deconectează",
|
||||
"ButtonGoToWebClient": "Deschide în browse",
|
||||
"ButtonHistory": "Istoric",
|
||||
"ButtonHome": "Acasă",
|
||||
"ButtonIssues": "Erori",
|
||||
"ButtonLatest": "Noutăți",
|
||||
"ButtonLibrary": "Bibliotecă",
|
||||
"ButtonLocalMedia": "Media locală",
|
||||
"ButtonLogs": "Jurnale",
|
||||
"ButtonManageLocalFiles": "Gestionare fișiere locale",
|
||||
"ButtonMaskServerAddress": "Nu arăta adresa serverului",
|
||||
"ButtonNewFolder": "Dosar nou",
|
||||
"ButtonNextEpisode": "Episodul următor",
|
||||
"ButtonOk": "OK",
|
||||
"ButtonOpenFeed": "Vezi noutățile",
|
||||
"ButtonOverride": "Înlocuiește",
|
||||
"ButtonPause": "Pauză",
|
||||
"ButtonPlay": "Redă",
|
||||
"ButtonPlayEpisode": "Redă episodul",
|
||||
"ButtonPlaylists": "Liste",
|
||||
"ButtonRead": "Citește",
|
||||
"ButtonReadLess": "Mai puțin",
|
||||
"ButtonReadMore": "Afișează mai mult",
|
||||
"ButtonRemove": "Elimină",
|
||||
"ButtonRemoveFromServer": "Elimină de pe server",
|
||||
"ButtonSave": "Salvează",
|
||||
"ButtonSaveOrder": "Salvează ordinea",
|
||||
"ButtonSearch": "Caută",
|
||||
"ButtonSendEbookToDevice": "Trimite cartea pe dispozitiv",
|
||||
"ButtonSeries": "Serii",
|
||||
"ButtonSetTimer": "Setează limita",
|
||||
"ButtonStream": "Redare online",
|
||||
"ButtonSubmit": "Trimite",
|
||||
"ButtonSwitchServerUser": "Schimbă serverul/utilizatorul",
|
||||
"ButtonUnmaskServerAddress": "Afișează serverul",
|
||||
"ButtonUserStats": "Activitatea mea",
|
||||
"ButtonYes": "Da",
|
||||
"HeaderAccount": "Cont",
|
||||
"HeaderAdvanced": "Avansat",
|
||||
"HeaderAndroidAutoSettings": "Setări Android Auto",
|
||||
"HeaderAudioTracks": "Înregistrări audio",
|
||||
"HeaderChapters": "Capitole",
|
||||
"HeaderCollection": "Colecție",
|
||||
"HeaderCollectionItems": "Conținutul colecției",
|
||||
"HeaderConnectionStatus": "Starea conexiunii",
|
||||
"HeaderDataSettings": "Configurare date",
|
||||
"HeaderDetails": "Detalii",
|
||||
"HeaderDownloads": "Descărcări",
|
||||
"HeaderEbookFiles": "Ebook-uri",
|
||||
"HeaderEpisodes": "Episoade",
|
||||
"HeaderEreaderSettings": "Setări eReader",
|
||||
"HeaderLatestEpisodes": "Episoade recente",
|
||||
"HeaderLibraries": "Biblioteci",
|
||||
"HeaderLocalFolders": "Dosare locale",
|
||||
"HeaderLocalLibraryItems": "Conținut local din bibliotecă",
|
||||
"HeaderNewPlaylist": "Listă nouă",
|
||||
"HeaderOpenRSSFeed": "Deschide flux RSS",
|
||||
"HeaderPlaybackSettings": "Setări redare",
|
||||
"HeaderPlaylist": "Listă de redare",
|
||||
"HeaderPlaylistItems": "Conținut listă",
|
||||
"HeaderProgressSyncFailed": "Progresul nu a fost sincronizat",
|
||||
"HeaderRSSFeed": "Sursă RSS",
|
||||
"HeaderRSSFeedGeneral": "Date RSS",
|
||||
"HeaderRSSFeedIsOpen": "RSS activ",
|
||||
"HeaderSelectDownloadLocation": "Selectează locația descărcării",
|
||||
"HeaderSettings": "Setări",
|
||||
"HeaderSleepTimer": "Timer de somn",
|
||||
"HeaderSleepTimerSettings": "Setări timer somn",
|
||||
"HeaderStatsMinutesListeningChart": "Minute ascultate (ultimele 7 zile)",
|
||||
"HeaderStatsRecentSessions": "Sesiuni recente",
|
||||
"HeaderTableOfContents": "Cuprins",
|
||||
"HeaderUserInterfaceSettings": "Setări interfață",
|
||||
"HeaderYourStats": "Progresul tău",
|
||||
"LabelAddToPlaylist": "Adaugă în listă",
|
||||
"LabelAddedAt": "Adăugat la",
|
||||
"LabelAddedDate": "Adăugat {0}",
|
||||
"LabelAll": "Toate",
|
||||
"LabelAllowSeekingOnMediaControls": "Permite derularea din controalele media",
|
||||
"LabelAlways": "Întotdeauna",
|
||||
"LabelAndroidAutoBrowseLimitForGrouping": "Limită pentru sortare alfabetică",
|
||||
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Nu folosi listarea alfabetică dacă sunt mai puține elemente decât această valoare",
|
||||
"LabelAndroidAutoBrowseSeriesSequenceOrder": "Ordinea cărților din serie",
|
||||
"LabelAskConfirmation": "Cere confirmare",
|
||||
"LabelAuthor": "Autor",
|
||||
"LabelAuthorFirstLast": "Autor (Prenume Nume)",
|
||||
"LabelAuthorLastFirst": "Autor (Nume, Prenume)",
|
||||
"LabelAuthors": "Autori",
|
||||
"LabelAutoDownloadEpisodes": "Descarcă automat episoadele",
|
||||
"LabelAutoRewindTime": "Timp de revenire automată",
|
||||
"LabelAutoSleepTimer": "Timer de somn automat",
|
||||
"LabelAutoSleepTimerAutoRewind": "Somn automat cu revenire",
|
||||
"LabelAutoSleepTimerAutoRewindHelp": "Când timerul de somn se termină, la reluarea redării vei fi mutat automat puțin înapoi.",
|
||||
"LabelAutoSleepTimerHelp": "Când redai media între orele specificate, se va porni automat timerul de somn.",
|
||||
"LabelBooks": "Cărți",
|
||||
"LabelChapterTrack": "Marcaje capitole",
|
||||
"LabelChapters": "Capitole",
|
||||
"LabelClosePlayer": "Închide playerul",
|
||||
"LabelCollapseSeries": "Restrânge seriile",
|
||||
"LabelComplete": "Finalizat",
|
||||
"LabelContinueBooks": "Continuă cărțile",
|
||||
"LabelContinueEpisodes": "Continuă episoadele",
|
||||
"LabelContinueListening": "Ascultă în continuare",
|
||||
"LabelContinueReading": "Continuă lectura",
|
||||
"LabelContinueSeries": "Continuă seria",
|
||||
"LabelCustomTime": "Timp personalizat",
|
||||
"LabelDescription": "Descriere",
|
||||
"LabelDisableAudioFadeOut": "Nu estompa sunetul",
|
||||
"LabelDisableAudioFadeOutHelp": "Volumul audio va scădea treptat când mai rămâne sub 1 minut din timerul de somn. Activează această opțiune pentru a dezactiva estomparea.",
|
||||
"LabelDisableAutoRewind": "Dezactivează revenirea automată",
|
||||
"LabelDisableShakeToReset": "Dezactivează resetarea prin scuturare",
|
||||
"LabelDisableShakeToResetHelp": "Scuturarea dispozitivului în timp ce rulează timerul sau în 2 minute după expirarea lui va reseta timerul de somn. Activează această opțiune pentru a dezactiva resetarea prin scuturare.",
|
||||
"LabelDisableVibrateOnReset": "Dezactivează vibrația la resetare",
|
||||
"LabelDisableVibrateOnResetHelp": "La resetarea timerului de somn, dispozitivul va vibra. Activează această opțiune pentru a dezactiva vibrația.",
|
||||
"LabelDiscover": "Descoperă",
|
||||
"LabelDownload": "Descarcă",
|
||||
"LabelDownloadUsingCellular": "Descarcă prin date mobile",
|
||||
"LabelDownloaded": "Descărcat",
|
||||
"LabelDuration": "Durată",
|
||||
"LabelEbook": "Carte electronică",
|
||||
"LabelEbooks": "Cărți electronice",
|
||||
"LabelEnable": "Activează",
|
||||
"LabelEnableMp3IndexSeeking": "Permite căutarea pe index în MP3",
|
||||
"LabelEnableMp3IndexSeekingHelp": "Activează această opțiune doar dacă ai fișiere MP3 care nu permit derularea corectă. Problema apare de obicei la MP3-uri cu bitrate variabil (VBR). Această setare forțează căutarea pe index, construind o mapare timp–byte pe măsură ce fișierul este citit. La fișiere MP3 mari poate apărea o întârziere când derulezi spre final.",
|
||||
"LabelEnd": "Sfârșit",
|
||||
"LabelEndOfChapter": "Sfârșitul capitolului",
|
||||
"LabelEndTime": "Oră de sfârșit",
|
||||
"LabelEpisode": "Episod",
|
||||
"LabelExplicit": "Explicit",
|
||||
"LabelFeedURL": "URL flux",
|
||||
"LabelFile": "Fișier",
|
||||
"LabelFileBirthtime": "Data creării fișierului",
|
||||
"LabelFileModified": "Fișier modificat",
|
||||
"LabelFilename": "Nume fișier",
|
||||
"LabelFinished": "Finalizat",
|
||||
"LabelFolder": "Dosar",
|
||||
"LabelFontBoldness": "Grosimea fontului",
|
||||
"LabelFontScale": "Mărimea fontului",
|
||||
"LabelGenre": "Gen",
|
||||
"LabelGenres": "Genuri",
|
||||
"LabelHapticFeedback": "Vibrație la atingere",
|
||||
"LabelHasEbook": "Are carte electronică",
|
||||
"LabelHasSupplementaryEbook": "Are carte electronică suplimentară",
|
||||
"LabelHeavy": "Puternic",
|
||||
"LabelHigh": "Ridicat",
|
||||
"LabelHost": "Gazdă",
|
||||
"LabelInProgress": "În desfășurare",
|
||||
"LabelIncomplete": "Incomplet",
|
||||
"LabelInternalAppStorage": "Stocare internă",
|
||||
"LabelJumpBackwardsTime": "Derulare înapoi",
|
||||
"LabelJumpForwardsTime": "Derulare înainte",
|
||||
"LabelKeepScreenAwake": "Ține ecranul aprins",
|
||||
"LabelLanguage": "Limbă",
|
||||
"LabelLayout": "Aspect",
|
||||
"LabelLayoutAuto": "Automat",
|
||||
"LabelLayoutSinglePage": "Pagină unică",
|
||||
"LabelLight": "Deschis",
|
||||
"LabelLineSpacing": "Spațiere între rânduri",
|
||||
"LabelListenAgain": "Ascultă din nou",
|
||||
"LabelLocalBooks": "Cărți locale",
|
||||
"LabelLocalPodcasts": "Podcasturi locale",
|
||||
"LabelLockOrientation": "Blochează orientarea",
|
||||
"LabelLockPlayer": "Blochează playerul",
|
||||
"LabelLow": "Scăzut",
|
||||
"LabelMediaType": "Tip media",
|
||||
"LabelMedium": "Mediu",
|
||||
"LabelMissing": "Lipsă",
|
||||
"LabelMore": "Mai multe",
|
||||
"LabelMoreInfo": "Mai multe informații",
|
||||
"LabelName": "Nume",
|
||||
"LabelNarrator": "Narator",
|
||||
"LabelNarrators": "Naratori",
|
||||
"LabelNavigateWithVolume": "Navighează cu tastele de volum",
|
||||
"LabelNavigateWithVolumeMirrored": "Inversat",
|
||||
"LabelNavigateWithVolumeWhilePlaying": "Permite navigarea cu tastele de volum în timpul redării",
|
||||
"LabelNavigateWithVolumeWhilePlayingDisabled": "Oprit",
|
||||
"LabelNavigateWithVolumeWhilePlayingEnabled": "Pornit",
|
||||
"LabelNever": "Niciodată",
|
||||
"LabelNewestAuthors": "Autori noi",
|
||||
"LabelNewestEpisodes": "Episoade noi",
|
||||
"LabelNo": "Nu",
|
||||
"LabelNotFinished": "Nefinalizat",
|
||||
"LabelNotStarted": "Neînceput",
|
||||
"LabelNumEpisodes": "{0} episoade",
|
||||
"LabelNumEpisodesIncomplete": "{0} episoade, {1} incomplete",
|
||||
"LabelNumberOfEpisodes": "# de Episoade",
|
||||
"LabelOff": "Oprit",
|
||||
"LabelOn": "Pornit",
|
||||
"LabelPassword": "Parolă",
|
||||
"LabelPath": "Cale",
|
||||
"LabelPlaybackDirect": "Direct",
|
||||
"LabelPlaybackLocal": "Local",
|
||||
"LabelPlaybackSpeed": "Viteză redare",
|
||||
"LabelPlaybackTranscode": "Transcodare",
|
||||
"LabelPodcast": "Podcast",
|
||||
"LabelPodcasts": "Podcasturi",
|
||||
"LabelPreventIndexing": "Împiedică indexarea fluxului în directoarele iTunes și Google Podcasts",
|
||||
"LabelProgress": "Progres",
|
||||
"LabelPubDate": "Data publicării",
|
||||
"LabelPublishYear": "Anul publicării",
|
||||
"LabelPublishedDate": "Publicat la {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Email personalizat al proprietarului",
|
||||
"LabelRSSFeedCustomOwnerName": "Nume personalizat al proprietarului",
|
||||
"LabelRSSFeedOpen": "Flux RSS deschis",
|
||||
"LabelRSSFeedPreventIndexing": "Previne indexarea",
|
||||
"LabelRSSFeedSlug": "Identificator flux RSS",
|
||||
"LabelRandomly": "Aleatoriu",
|
||||
"LabelRead": "Citește",
|
||||
"LabelReadAgain": "Citește din nou",
|
||||
"LabelRecentSeries": "Serii recente",
|
||||
"LabelRecentlyAdded": "Adăugate recent",
|
||||
"LabelRemoveFromPlaylist": "Elimină din listă",
|
||||
"LabelScaleElapsedTimeBySpeed": "Ajustează timpul scurs după viteză",
|
||||
"LabelSeason": "Sezon",
|
||||
"LabelSelectADevice": "Alege un dispozitiv",
|
||||
"LabelSequenceAscending": "Ordine crescătoare",
|
||||
"LabelSequenceDescending": "Ordine descrescătoare",
|
||||
"LabelSeries": "Serii",
|
||||
"LabelServerAddress": "Adresă server",
|
||||
"LabelSetEbookAsPrimary": "Setează ca principală",
|
||||
"LabelSetEbookAsSupplementary": "Setează ca suplimentară",
|
||||
"LabelShakeSensitivity": "Sensibilitate la scuturare",
|
||||
"LabelShowAll": "Afișează tot",
|
||||
"LabelSize": "Dimensiune",
|
||||
"LabelSleepTimer": "Timer de somn",
|
||||
"LabelSleepTimerAlmostDoneChime": "Redă un sunet la final",
|
||||
"LabelSleepTimerAlmostDoneChimeHelp": "Redă un sunet când mai rămân 30s din timerul de somn",
|
||||
"LabelStart": "Pornește",
|
||||
"LabelStartTime": "Timp de pornire",
|
||||
"LabelStatsBestDay": "Ziua cea mai bună",
|
||||
"LabelStatsDailyAverage": "Medie zilnică",
|
||||
"LabelStatsDays": "Zile",
|
||||
"LabelStatsDaysListened": "Zile ascultate",
|
||||
"LabelStatsInARow": "la rând",
|
||||
"LabelStatsItemsFinished": "Finalizate",
|
||||
"LabelStatsMinutes": "minute",
|
||||
"LabelStatsMinutesListening": "Minute ascultate",
|
||||
"LabelStatsWeekListening": "Ascultare săptămânală",
|
||||
"LabelStreamingUsingCellular": "Redare online prin date mobile",
|
||||
"LabelTag": "Etichetă",
|
||||
"LabelTags": "Etichete",
|
||||
"LabelTheme": "Temă",
|
||||
"LabelThemeBlack": "Negru",
|
||||
"LabelThemeDark": "Întunecat",
|
||||
"LabelThemeLight": "Deschis",
|
||||
"LabelTimeRemaining": "{0} rămase",
|
||||
"LabelTitle": "Titlu",
|
||||
"LabelTotalSize": "Dimensiune totală",
|
||||
"LabelTotalTrack": "Total audio",
|
||||
"LabelTracks": "Fișiere audio",
|
||||
"LabelType": "Tip",
|
||||
"LabelUnknown": "Necunoscut",
|
||||
"LabelUnlockPlayer": "Deblochează playerul",
|
||||
"LabelUseBookshelfView": "Folosește vizualizare raft",
|
||||
"LabelUser": "Utilizator",
|
||||
"LabelUsername": "Nume utilizator",
|
||||
"LabelVeryHigh": "Foarte ridicat",
|
||||
"LabelVeryLow": "Foarte scăzut",
|
||||
"LabelYearReviewHide": "Ascunde retrospectiva anului",
|
||||
"LabelYearReviewShow": "Vezi retrospectiva anului",
|
||||
"LabelYourBookmarks": "Semnele tale de carte",
|
||||
"LabelYourProgress": "Progresul tău",
|
||||
"MessageAndroid10Downloads": "Pe Android 10 și versiunile anterioare, descărcările vor fi stocate în memoria internă a aplicației.",
|
||||
"MessageAttemptingServerConnection": "Se încearcă conectarea la server...",
|
||||
"MessageAudiobookshelfServerNotConnected": "Serverul Audiobookshelf nu este conectat",
|
||||
"MessageAudiobookshelfServerRequired": "<strong>Important!</strong> Această aplicație este proiectată să funcționeze cu un server Audiobookshelf găzduit de tine sau de cineva cunoscut. Aplicația nu oferă conținut propriu.",
|
||||
"MessageBookshelfEmpty": "Raftul este gol",
|
||||
"MessageConfirmDeleteLocalEpisode": "Ștergi episodul local „{0}” de pe dispozitiv? Fișierul de pe server nu va fi afectat.",
|
||||
"MessageConfirmDeleteLocalFiles": "Ștergi fișierele locale ale acestui element de pe dispozitiv? Fișierele de pe server și progresul nu vor fi afectate.",
|
||||
"MessageConfirmDisableAutoTimer": "Sigur vrei să dezactivezi timerul automat pentru restul zilei? Acesta va fi reactivat la finalul perioadei de somn automat sau când repornești aplicația.",
|
||||
"MessageConfirmDiscardProgress": "Ești sigur că vrei să îți resetezi progresul?",
|
||||
"MessageConfirmDownloadUsingCellular": "Urmează să descarci folosind date mobile. Acest lucru poate genera costuri suplimentare. Vrei să continui?",
|
||||
"MessageConfirmMarkAsFinished": "Sigur vrei să marchezi acest element ca finalizat?",
|
||||
"MessageConfirmRemoveBookmark": "Sigur vrei să ștergi semnul de carte?",
|
||||
"MessageConfirmStreamingUsingCellular": "Urmează să redai online folosind date mobile. Acest lucru poate genera costuri suplimentare. Vrei să continui?",
|
||||
"MessageDiscardProgress": "Resetează progresul",
|
||||
"MessageDownloadCompleteProcessing": "Descărcare finalizată. Se procesează...",
|
||||
"MessageDownloading": "Se descarcă...",
|
||||
"MessageDownloadingEpisode": "Se descarcă episodul",
|
||||
"MessageEpisodesQueuedForDownload": "{0} episod(e) în așteptare pentru descărcare",
|
||||
"MessageFailedToRefreshToken": "Reîmprospătarea tokenului a eșuat, este necesară reconectarea",
|
||||
"MessageFeedURLWillBe": "Adresa fluxului va fi {0}",
|
||||
"MessageFetching": "Se preiau date…",
|
||||
"MessageFollowTheProjectOnGithub": "Urmărește proiectul pe GitHub",
|
||||
"MessageItemDownloadCompleteFailedToCreate": "Descărcarea s-a încheiat, dar nu s-a putut crea elementul în bibliotecă",
|
||||
"MessageItemMissing": "Elementul lipsește și trebuie reparat pe server. De obicei este marcat ca lipsă deoarece căile fișierelor nu sunt accesibile.",
|
||||
"MessageLoading": "Se încarcă…",
|
||||
"MessageLoadingServerData": "Se încarcă datele de pe server…",
|
||||
"MessageLocalFolderDescription": "'Internal App Storage' este accesibilă doar acestei aplicații. Aplicația acceptă doar media descărcată direct prin ea. Poți folosi dosarele de stocare partajată pentru ca alte aplicații să acceseze media descărcată de această aplicație.",
|
||||
"MessageMarkAsFinished": "Marchează ca finalizat",
|
||||
"MessageMediaLinkedToADifferentServer": "Conținutul este asociat unui server Audiobookshelf la o altă adresă ({0}). Progresul va fi sincronizat când te conectezi la acea adresă.",
|
||||
"MessageMediaLinkedToADifferentUser": "Conținutul este asociat acestui server, dar a fost descărcat de un alt utilizator. Progresul se va sincroniza doar pentru utilizatorul care l-a descărcat.",
|
||||
"MessageMediaLinkedToServer": "Asociat serverului {0}",
|
||||
"MessageMediaLinkedToThisServer": "Conținutul descărcat este asociat acestui server",
|
||||
"MessageMediaNotLinkedToServer": "Conținutul nu este asociat unui server Audiobookshelf. Progresul nu va fi sincronizat.",
|
||||
"MessageNoBookmarks": "Fără semne de carte",
|
||||
"MessageNoChapters": "Fără capitole",
|
||||
"MessageNoCollections": "Fără colecții",
|
||||
"MessageNoItems": "Niciun element",
|
||||
"MessageNoItemsFound": "Nu s-au găsit elemente",
|
||||
"MessageNoListeningSessions": "Nicio sesiune de ascultare",
|
||||
"MessageNoLogs": "Fără jurnale",
|
||||
"MessageNoMediaFolders": "Fără dosare media",
|
||||
"MessageNoNetworkConnection": "Fără conexiune la rețea",
|
||||
"MessageNoPodcastsFound": "Nu s-au găsit podcasturi",
|
||||
"MessageNoSeries": "Fără volume",
|
||||
"MessageNoUpdatesWereNecessary": "Nu au fost necesare actualizări",
|
||||
"MessageNoUserPlaylists": "Nu ai nicio listă de redare",
|
||||
"MessageOldServerAuthReLoginRequired": "Autentificarea a fost îmbunătățită pentru siguranță în versiunea de server v2.26.0. Toți utilizatorii trebuie să se reconecteze.",
|
||||
"MessageOldServerAuthWarning": "Serverul folosește un sistem de autentificare învechit",
|
||||
"MessageOldServerAuthWarningHelp": "Acest server rulează o versiune mai veche decât v2.26.0. În v2.26.0 a fost adăugat un sistem de autentificare mai sigur. Este recomandat să actualizezi serverul la cea mai nouă versiune. Dacă l-ai actualizat deja, autentifică-te din nou pentru a folosi noul sistem.",
|
||||
"MessageOldServerConnectionWarning": "Configurația conexiunii la server folosește un ID de utilizator vechi. Șterge și adaugă din nou această conexiune la server.",
|
||||
"MessageOldServerConnectionWarningHelp": "Inițial ai configurat conexiunea la acest server înainte de migrarea bazei de date din 2.3.0 (iunie 2023). O actualizare viitoare a serverului va elimina posibilitatea de autentificare cu această conexiune veche. Te rugăm să ștergi conexiunea existentă și să te reconectezi (folosind aceeași adresă de server și aceleași credențiale). Dacă ai conținut descărcat pe acest dispozitiv, va trebui descărcat din nou pentru a se sincroniza cu serverul.",
|
||||
"MessagePodcastSearchField": "Introdu termenul de căutare sau URL-ul unui flux RSS",
|
||||
"MessageProgressSyncFailed": "Ultima încercare de a raporta progresul de ascultare către server a eșuat. Cererile de sincronizare vor continua la fiecare 15 secunde – 1 minut cât timp rulează redarea.",
|
||||
"MessageReportBugsAndContribute": "Raportează erori, cere funcții noi și contribuie pe",
|
||||
"MessageSeriesAlreadyDownloaded": "Toate volumele au fost deja descărcate.",
|
||||
"MessageSeriesDownloadConfirm": "Descarci cele {0} cărți lipsă, cu {1} fișier(e), total {2}, în dosarul {3}?",
|
||||
"MessageSeriesDownloadConfirmIos": "Descarci cele {0} cărți lipsă, cu {1} fișier(e), total {2}?",
|
||||
"MessageSocketConnectedOverMeteredCellular": "Socket conectat prin date mobile cu trafic contorizat",
|
||||
"MessageSocketConnectedOverMeteredWifi": "Socket conectat prin Wi-Fi cu trafic contorizat",
|
||||
"MessageSocketConnectedOverUnmeteredCellular": "Socket conectat prin date mobile nelimitate",
|
||||
"MessageSocketConnectedOverUnmeteredWifi": "Socket conectat prin Wi-Fi nelimitat",
|
||||
"MessageSocketNotConnected": "Socket neconectat",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Atenționare: Majoritatea aplicațiilor de podcast cer ca URL-ul fluxului RSS să folosească HTTPS",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Atenționare: unul sau mai multe episoade nu au data publicării (Pub Date). Unele aplicații de podcast o cer.",
|
||||
"ToastBookmarkCreateFailed": "Nu s-a putut crea semnul de carte",
|
||||
"ToastBookmarkRemoveFailed": "Nu s-a putut șterge semnul de carte",
|
||||
"ToastBookmarkUpdateFailed": "Nu s-a putut actualiza semnul de carte",
|
||||
"ToastDownloadNotAllowedOnCellular": "Descărcarea nu este permisă pe date mobile",
|
||||
"ToastItemMarkedAsFinishedFailed": "Nu s-a putut marca drept finalizat",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Nu s-a putut marca drept nefinalizat",
|
||||
"ToastPlaylistCreateFailed": "Nu s-a putut crea lista de redare",
|
||||
"ToastPodcastCreateFailed": "Nu s-a putut crea podcastul",
|
||||
"ToastPodcastCreateSuccess": "Podcast creat cu succes",
|
||||
"ToastRSSFeedCloseFailed": "Nu s-a putut închide fluxul RSS",
|
||||
"ToastRSSFeedCloseSuccess": "Flux RSS închis",
|
||||
"ToastStreamingNotAllowedOnCellular": "Redarea online nu este permisă pe date mobile"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"ButtonCancelTimer": "Отмена таймера",
|
||||
"ButtonClearFilter": "Очистить фильтр",
|
||||
"ButtonClearLogs": "Очистить журнал",
|
||||
"ButtonCloseFeed": "Закрыть канал",
|
||||
"ButtonCloseFeed": "Закрыть ленту",
|
||||
"ButtonCollections": "Коллекции",
|
||||
"ButtonConnect": "Подключение",
|
||||
"ButtonConnectToServer": "Подключение к серверу",
|
||||
@@ -15,7 +15,7 @@
|
||||
"ButtonCreateBookmark": "Создать закладку",
|
||||
"ButtonCreateNewPlaylist": "Создать новый плейлист",
|
||||
"ButtonDelete": "Удалить",
|
||||
"ButtonDeleteLocalEpisode": "Удалить локальный эпизод",
|
||||
"ButtonDeleteLocalEpisode": "Удалить выпуск локально",
|
||||
"ButtonDeleteLocalFile": "Удалить локальный файл",
|
||||
"ButtonDeleteLocalItem": "Удалить локальный элемент",
|
||||
"ButtonDisableAutoTimer": "Отключить автоматический таймер",
|
||||
@@ -31,13 +31,13 @@
|
||||
"ButtonManageLocalFiles": "Управление локальными файлами",
|
||||
"ButtonMaskServerAddress": "Скрывать адрес сервера",
|
||||
"ButtonNewFolder": "Новая папка",
|
||||
"ButtonNextEpisode": "Следующий эпизод",
|
||||
"ButtonNextEpisode": "Следующий выпуск",
|
||||
"ButtonOk": "Ок",
|
||||
"ButtonOpenFeed": "Открыть канал",
|
||||
"ButtonOpenFeed": "Открыть ленту",
|
||||
"ButtonOverride": "Переопределить",
|
||||
"ButtonPause": "Пауза",
|
||||
"ButtonPlay": "Слушать",
|
||||
"ButtonPlayEpisode": "Воспроизвести эпизод",
|
||||
"ButtonPlayEpisode": "Воспроизвести выпуск",
|
||||
"ButtonPlaylists": "Плейлисты",
|
||||
"ButtonRead": "Читать",
|
||||
"ButtonReadLess": "Читать меньше",
|
||||
@@ -68,21 +68,21 @@
|
||||
"HeaderDetails": "Подробности",
|
||||
"HeaderDownloads": "Загрузки",
|
||||
"HeaderEbookFiles": "Файлы e-книг",
|
||||
"HeaderEpisodes": "Эпизоды",
|
||||
"HeaderEpisodes": "Выпуски",
|
||||
"HeaderEreaderSettings": "Настройки E-ридера",
|
||||
"HeaderLatestEpisodes": "Последние эпизоды",
|
||||
"HeaderLatestEpisodes": "Последние выпуски",
|
||||
"HeaderLibraries": "Библиотеки",
|
||||
"HeaderLocalFolders": "Локальные папки",
|
||||
"HeaderLocalLibraryItems": "Элементы локальной библиотеки",
|
||||
"HeaderNewPlaylist": "Новый плейлист",
|
||||
"HeaderOpenRSSFeed": "Открыть RSS-канал",
|
||||
"HeaderOpenRSSFeed": "Открыть RSS-ленту",
|
||||
"HeaderPlaybackSettings": "Настройки воспроизведения",
|
||||
"HeaderPlaylist": "Плейлист",
|
||||
"HeaderPlaylistItems": "Элементы списка воспроизведения",
|
||||
"HeaderProgressSyncFailed": "Синхронизация прогресса не удалась",
|
||||
"HeaderRSSFeed": "RSS-канал",
|
||||
"HeaderRSSFeed": "RSS-лента",
|
||||
"HeaderRSSFeedGeneral": "Сведения о RSS",
|
||||
"HeaderRSSFeedIsOpen": "RSS-канал открыт",
|
||||
"HeaderRSSFeedIsOpen": "RSS-лента открыта",
|
||||
"HeaderSelectDownloadLocation": "Выберите место загрузки",
|
||||
"HeaderSettings": "Настройки",
|
||||
"HeaderSleepTimer": "Таймер сна",
|
||||
@@ -106,7 +106,7 @@
|
||||
"LabelAuthorFirstLast": "Автор (Имя Фамилия)",
|
||||
"LabelAuthorLastFirst": "Автор (Фамилия, Имя)",
|
||||
"LabelAuthors": "Авторы",
|
||||
"LabelAutoDownloadEpisodes": "Скачивать эпизоды автоматически",
|
||||
"LabelAutoDownloadEpisodes": "Скачивать выпуски автоматически",
|
||||
"LabelAutoRewindTime": "Время автоматической перемотки",
|
||||
"LabelAutoSleepTimer": "Таймер сна",
|
||||
"LabelAutoSleepTimerAutoRewind": "Перемотка назад таймера сна",
|
||||
@@ -119,7 +119,7 @@
|
||||
"LabelCollapseSeries": "Свернуть серии",
|
||||
"LabelComplete": "Завершить",
|
||||
"LabelContinueBooks": "Продолжить",
|
||||
"LabelContinueEpisodes": "Продолжить эпизоды",
|
||||
"LabelContinueEpisodes": "Продолжить выпуски",
|
||||
"LabelContinueListening": "Продолжить слушать",
|
||||
"LabelContinueReading": "Продолжить чтение",
|
||||
"LabelContinueSeries": "Продолжить серии",
|
||||
@@ -145,9 +145,9 @@
|
||||
"LabelEnd": "Конец",
|
||||
"LabelEndOfChapter": "Конец главы",
|
||||
"LabelEndTime": "Время конца",
|
||||
"LabelEpisode": "Эпизод",
|
||||
"LabelEpisode": "Выпуск",
|
||||
"LabelExplicit": "18+",
|
||||
"LabelFeedURL": "URL канала",
|
||||
"LabelFeedURL": "URL-адрес ленты",
|
||||
"LabelFile": "Файл",
|
||||
"LabelFileBirthtime": "Дата создания",
|
||||
"LabelFileModified": "Дата модификации",
|
||||
@@ -197,13 +197,13 @@
|
||||
"LabelNavigateWithVolumeWhilePlayingEnabled": "Вкл.",
|
||||
"LabelNever": "Никогда",
|
||||
"LabelNewestAuthors": "Новые авторы",
|
||||
"LabelNewestEpisodes": "Новые эпизоды",
|
||||
"LabelNewestEpisodes": "Новые выпуски",
|
||||
"LabelNo": "Нет",
|
||||
"LabelNotFinished": "Не завершено",
|
||||
"LabelNotStarted": "Не запущено",
|
||||
"LabelNumEpisodes": "{0} эпизодов",
|
||||
"LabelNumEpisodesIncomplete": "{0} эпизодов, {1} не завершено",
|
||||
"LabelNumberOfEpisodes": "# из эпизодов",
|
||||
"LabelNumEpisodes": "{0} выпусков",
|
||||
"LabelNumEpisodesIncomplete": "{0} выпусков, {1} не завершено",
|
||||
"LabelNumberOfEpisodes": "# из выпусков",
|
||||
"LabelOff": "Выключить",
|
||||
"LabelOn": "Включён",
|
||||
"LabelPassword": "Пароль",
|
||||
@@ -223,7 +223,7 @@
|
||||
"LabelRSSFeedCustomOwnerName": "Пользовательское Имя владельца",
|
||||
"LabelRSSFeedOpen": "Открыть RSS-ленту",
|
||||
"LabelRSSFeedPreventIndexing": "Запретить индексирование",
|
||||
"LabelRSSFeedSlug": "Встроить RSS-канал",
|
||||
"LabelRSSFeedSlug": "Ключевое слово RSS-ленты",
|
||||
"LabelRandomly": "Случайно",
|
||||
"LabelRead": "Читать",
|
||||
"LabelReadAgain": "Читать снова",
|
||||
@@ -285,7 +285,7 @@
|
||||
"MessageAudiobookshelfServerNotConnected": "Сервер Audiobookshelf не подключен",
|
||||
"MessageAudiobookshelfServerRequired": "<strong>Важно!</strong> Это приложение предназначено для работы с сервером Audiobookshelf, который размещаете вы или кто-то из ваших знакомых. Это приложение не предоставляет никакого контента.",
|
||||
"MessageBookshelfEmpty": "Книжная полка пуста",
|
||||
"MessageConfirmDeleteLocalEpisode": "Удалить локальный эпизод \"{0}\" с Вашего устройства? Файл на сервере не будет затронут.",
|
||||
"MessageConfirmDeleteLocalEpisode": "Удалить локальный выпуск \"{0}\" с вашего устройства? Файл на сервере не будет затронут.",
|
||||
"MessageConfirmDeleteLocalFiles": "Удалить локальные файлы этого элемента с вашего устройства? Это не повлияет на файлы на сервере и ваш прогресс.",
|
||||
"MessageConfirmDisableAutoTimer": "Вы уверены, что хотите отключить автоматический таймер до конца сегодняшнего дня? Таймер будет снова включен по истечении этого периода автоматического отключения или если вы перезапустите приложение.",
|
||||
"MessageConfirmDiscardProgress": "Вы уверены, что хотите сбросить свой прогресс?",
|
||||
@@ -296,9 +296,10 @@
|
||||
"MessageDiscardProgress": "Отбросить прогресс",
|
||||
"MessageDownloadCompleteProcessing": "Загрузка завершена. Обработка...",
|
||||
"MessageDownloading": "Загрузка...",
|
||||
"MessageDownloadingEpisode": "Эпизод скачивается",
|
||||
"MessageEpisodesQueuedForDownload": "{0} Эпизод(ов) запланировано для закачки",
|
||||
"MessageFeedURLWillBe": "URL канала будет {0}",
|
||||
"MessageDownloadingEpisode": "Скачивание выпуска",
|
||||
"MessageEpisodesQueuedForDownload": "{0} выпуск(ов) запланировано для скачивания",
|
||||
"MessageFailedToRefreshToken": "Не удалось обновить токен, требуется повторный вход в систему",
|
||||
"MessageFeedURLWillBe": "URL-адрес ленты будет {0}",
|
||||
"MessageFetching": "Завершается...",
|
||||
"MessageFollowTheProjectOnGithub": "Следите за проектом на GitHub",
|
||||
"MessageItemDownloadCompleteFailedToCreate": "Загрузка элемента завершена, но не удалось создать элемент библиотеки",
|
||||
@@ -325,9 +326,12 @@
|
||||
"MessageNoSeries": "Нет серии",
|
||||
"MessageNoUpdatesWereNecessary": "Обновления не требовались",
|
||||
"MessageNoUserPlaylists": "У вас нет плейлистов",
|
||||
"MessageOldServerAuthReLoginRequired": "В версии server 2.26.0 для обеспечения безопасности была улучшена аутентификация. Всем пользователям необходимо повторно войти в систему.",
|
||||
"MessageOldServerAuthWarning": "Сервер использует устаревшую аутентификацию",
|
||||
"MessageOldServerAuthWarningHelp": "Этот сервер работает под управлением версии старше чем 2.26.0. В версии 2.26.0 была добавлена более безопасная система аутентификации. Настоятельно рекомендуется обновить сервер до последней версии. Если вы уже обновили сервер, войдите в систему еще раз, чтобы использовать новую аутентификацию.",
|
||||
"MessageOldServerConnectionWarning": "Конфигурация подключения к серверу использует старый идентификатор пользователя. Удалите и повторно добавьте это подключение к серверу.",
|
||||
"MessageOldServerConnectionWarningHelp": "Первоначально вы настроили подключение к этому серверу до миграции базы данных в версии 2.3.0, выпущенной в июне 2023 года. Будущее обновление сервера уберет возможность входа с помощью этого старого подключения. Удалите существующее подключение к серверу и подключитесь снова (используя тот же адрес сервера и учетные данные). Если на этом устройстве есть загруженные медиафайлы, их необходимо будет загрузить снова для синхронизации с сервером.",
|
||||
"MessagePodcastSearchField": "Введите поисковый запрос или URL-адрес RSS-канала",
|
||||
"MessagePodcastSearchField": "Введите поисковый запрос или URL-адрес RSS-ленты",
|
||||
"MessageProgressSyncFailed": "Последняя попытка сообщить о вашем прогрессе прослушивания на сервер не удалась. Запросы на синхронизацию прогресса будут продолжаться каждые 15 секунд в течении 1 минуты во время воспроизведения медиа.",
|
||||
"MessageReportBugsAndContribute": "Сообщайте об ошибках, запрашивайте функции и вносите свой вклад на",
|
||||
"MessageSeriesAlreadyDownloaded": "Вы уже скачали все книги из этой серии.",
|
||||
@@ -338,8 +342,8 @@
|
||||
"MessageSocketConnectedOverUnmeteredCellular": "Сокет подключен по безлимитной сотовой связи",
|
||||
"MessageSocketConnectedOverUnmeteredWifi": "Сокет подключен через безлимитный Wi-Fi",
|
||||
"MessageSocketNotConnected": "Сокет не подключен",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Предупреждение: Большинству приложений подкастов потребуется, чтобы URL-адрес RSS-канала использовал HTTPS",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Предупреждение: 1 или более эпизодов не имеют даты публикации. Некоторые приложения для подкастов требуют этого.",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Предупреждение: Большинству приложений подкастов потребуется, чтобы URL-адрес RSS-ленты использовал HTTPS",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Предупреждение: 1 или более выпусков не имеют даты публикации. Некоторые приложения для подкастов требуют этого.",
|
||||
"ToastBookmarkCreateFailed": "Не удалось создать закладку",
|
||||
"ToastBookmarkRemoveFailed": "Не удалось удалить закладку",
|
||||
"ToastBookmarkUpdateFailed": "Не удалось обновить закладку",
|
||||
@@ -349,7 +353,7 @@
|
||||
"ToastPlaylistCreateFailed": "Не удалось создать плейлист",
|
||||
"ToastPodcastCreateFailed": "Не удалось создать подкаст",
|
||||
"ToastPodcastCreateSuccess": "Подкаст успешно создан",
|
||||
"ToastRSSFeedCloseFailed": "Не удалось закрыть RSS-канал",
|
||||
"ToastRSSFeedCloseSuccess": "RSS-канал закрыт",
|
||||
"ToastRSSFeedCloseFailed": "Не удалось закрыть RSS-ленту",
|
||||
"ToastRSSFeedCloseSuccess": "RSS-лента закрыта",
|
||||
"ToastStreamingNotAllowedOnCellular": "Потоковая передача данных по сотовой сети запрещена"
|
||||
}
|
||||
|
||||
@@ -298,6 +298,7 @@
|
||||
"MessageDownloading": "Sťahujem...",
|
||||
"MessageDownloadingEpisode": "Sťahovanie epizódy",
|
||||
"MessageEpisodesQueuedForDownload": "{0} epizód(a) v zozname na sťahovanie",
|
||||
"MessageFailedToRefreshToken": "Token sa nepodarilo obnoviť, vyžaduje sa opakované prihlásenie",
|
||||
"MessageFeedURLWillBe": "URL zdroja bude {0}",
|
||||
"MessageFetching": "Získavam...",
|
||||
"MessageFollowTheProjectOnGithub": "Sledujte projekt na Github-e",
|
||||
@@ -325,6 +326,9 @@
|
||||
"MessageNoSeries": "Žiadne série",
|
||||
"MessageNoUpdatesWereNecessary": "Neboli potrebné žiadne aktualizácie",
|
||||
"MessageNoUserPlaylists": "Nemáte žiadny playlist",
|
||||
"MessageOldServerAuthReLoginRequired": "Overovanie bolo zdôvodu bezpešnosti vylepšené vo verzii servera v2.26.0. U každého používateľa sa vyžaduje opätovné prihlásenie.",
|
||||
"MessageOldServerAuthWarning": "Server používa zastarané overovanie",
|
||||
"MessageOldServerAuthWarningHelp": "Tento server má staršiu verziu ako v2.26.0. Vo verzii v2.26.0 bol zavedený bezpešnejší systém overovania. Dôrazne sa odporúča aktualizovať server na najnovšiu verziu. Ak ste už server aktualizovali, prihláste sa znova pre použitie nového systému overovania.",
|
||||
"MessageOldServerConnectionWarning": "Konfigurácia pripojenia na server používa staré ID používateľa. Prosím, odstráňte a nanovo pridajte dané pripojenie.",
|
||||
"MessageOldServerConnectionWarningHelp": "Pripojenie na tento server bolo nakonfigurované pred migráciou databázy vo verzii 2.3.0, vydanej v júni 2023. V budúcej aktualizácii servera bude možnosť prihlasovania pomocou tohto starého pripojenia odstránená. Prosím, odstráňte aktuálne pripojenie na server a vytvorte nové (pomocou rovnakej adresy a prihlasovacích údajov). V prípade, ak máte na zariadení lokálne stiahnuté akékoľvek médiá, bude potrebné ich stiahnuť nanovo, aby bola možná serverová synchronizácia.",
|
||||
"MessagePodcastSearchField": "Zadajte hľadaný výraz alebo URL RSS zdroja",
|
||||
|
||||
@@ -298,6 +298,7 @@
|
||||
"MessageDownloading": "Prenašanje...",
|
||||
"MessageDownloadingEpisode": "Prenašam epizodo",
|
||||
"MessageEpisodesQueuedForDownload": "{0} epizod v čakalni vrsti za prenos",
|
||||
"MessageFailedToRefreshToken": "Osvežitev žetona ni uspela, potrebna je ponovna prijava",
|
||||
"MessageFeedURLWillBe": "URL vira bo {0}",
|
||||
"MessageFetching": "Pridobivam...",
|
||||
"MessageFollowTheProjectOnGithub": "Sledite projektu na Githubu",
|
||||
@@ -325,6 +326,9 @@
|
||||
"MessageNoSeries": "Ni serij",
|
||||
"MessageNoUpdatesWereNecessary": "Posodobitve niso bile potrebne",
|
||||
"MessageNoUserPlaylists": "Nimate seznamov predvajanja",
|
||||
"MessageOldServerAuthReLoginRequired": "Zaradi varnosti je bila v strežniku različice 2.26.0 izboljšana avtentikacija. Vsi uporabniki se morajo ponovno prijaviti.",
|
||||
"MessageOldServerAuthWarning": "Strežnik uporablja zastarelo preverjanje avtentikacije",
|
||||
"MessageOldServerAuthWarningHelp": "Ta strežnik uporablja različico, starejšo od v2.26.0. V različici v2.26.0 je bil dodan varnejši sistem preverjanja pristnosti. Zelo priporočljivo je, da strežnik posodobite na najnovejšo različico. Če ste strežnik že posodobili, se znova prijavite, da uporabite novo preverjanje pristnosti.",
|
||||
"MessageOldServerConnectionWarning": "Nastavitev povezave s strežnikom uporablja stari ID uporabnika. Izbrišite in znova dodajte to povezavo s strežnikom.",
|
||||
"MessageOldServerConnectionWarningHelp": "Povezavo s tem strežnikom ste prvotno nastavili pred selitvijo baze podatkov v različici 2.3.0, izdani junija 2023. Prihodnja posodobitev strežnika bo odstranila možnost prijave s to staro povezavo. Izbrišite obstoječo povezavo s strežnikom in se znova povežite (z istim naslovom strežnika in poverilnicami). Če imate v tej napravi prenesen medij, ga boste morali znova prenesti za sinhronizacijo s strežnikom.",
|
||||
"MessagePodcastSearchField": "Vnesite iskalni izraz ali URL vira RSS",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"ButtonAdd": "Lägg till",
|
||||
"ButtonAddNewServer": "Lägg Till Ny Server",
|
||||
"ButtonAddNewServer": "Lägg till ny server",
|
||||
"ButtonAuthors": "Författare",
|
||||
"ButtonBack": "Tillbaka",
|
||||
"ButtonCancel": "Avbryt",
|
||||
@@ -51,7 +51,7 @@
|
||||
"ButtonSeries": "Serie",
|
||||
"ButtonSetTimer": "Ställ in timer",
|
||||
"ButtonStream": "Ström",
|
||||
"ButtonSubmit": "Skicka",
|
||||
"ButtonSubmit": "Spara",
|
||||
"ButtonSwitchServerUser": "Byt Server/Användare",
|
||||
"ButtonUnmaskServerAddress": "Visa server adress",
|
||||
"ButtonUserStats": "Användarstatistik",
|
||||
@@ -91,7 +91,7 @@
|
||||
"HeaderStatsRecentSessions": "Senaste tillfällena",
|
||||
"HeaderTableOfContents": "Innehållsförteckning",
|
||||
"HeaderUserInterfaceSettings": "Användargränssnittsinställningar",
|
||||
"HeaderYourStats": "Dina statistik",
|
||||
"HeaderYourStats": "Din statistik",
|
||||
"LabelAddToPlaylist": "Lägg till i en spellista",
|
||||
"LabelAddedAt": "Tillagd vid",
|
||||
"LabelAddedDate": "Adderad {0}",
|
||||
@@ -149,8 +149,8 @@
|
||||
"LabelExplicit": "Bestämd",
|
||||
"LabelFeedURL": "URL-adress för flödet",
|
||||
"LabelFile": "Fil",
|
||||
"LabelFileBirthtime": "Tidpunkt, fil skapad",
|
||||
"LabelFileModified": "Tidpunkt, fil ändrad",
|
||||
"LabelFileBirthtime": "Tidpunkt, adderad",
|
||||
"LabelFileModified": "Tidpunkt, ändrad",
|
||||
"LabelFilename": "Filnamn",
|
||||
"LabelFinished": "Avslutad",
|
||||
"LabelFolder": "Mapp",
|
||||
@@ -221,6 +221,7 @@
|
||||
"LabelPublishedDate": "Publicerad {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Anpassad ägarens e-post",
|
||||
"LabelRSSFeedCustomOwnerName": "Anpassat ägarnamn",
|
||||
"LabelRSSFeedOpen": "RSS-flöde öppet",
|
||||
"LabelRSSFeedPreventIndexing": "Förhindra indexering",
|
||||
"LabelRSSFeedSlug": "RSS-flödesslag",
|
||||
"LabelRandomly": "Slumpartat",
|
||||
@@ -266,7 +267,7 @@
|
||||
"LabelTitle": "Titel",
|
||||
"LabelTotalSize": "Total Storlek",
|
||||
"LabelTotalTrack": "Total Spår",
|
||||
"LabelTracks": "Spår",
|
||||
"LabelTracks": "Ljudspår",
|
||||
"LabelType": "Typ",
|
||||
"LabelUnknown": "Okänd",
|
||||
"LabelUnlockPlayer": "Lås upp spelare",
|
||||
@@ -278,7 +279,7 @@
|
||||
"LabelYearReviewHide": "Dölj årets sammanställning",
|
||||
"LabelYearReviewShow": "Visa årets sammanställning",
|
||||
"LabelYourBookmarks": "Dina bokmärken",
|
||||
"LabelYourProgress": "Din framsteg",
|
||||
"LabelYourProgress": "Framsteg",
|
||||
"MessageAndroid10Downloads": "Android 10 och lägre använder intern applagring för nedladdningar.",
|
||||
"MessageAttemptingServerConnection": "Försöker ansluta till server...",
|
||||
"MessageAudiobookshelfServerNotConnected": "Audiobookshelf-server är ej ansluten",
|
||||
@@ -297,6 +298,7 @@
|
||||
"MessageDownloading": "Laddar ned...",
|
||||
"MessageDownloadingEpisode": "Laddar ner avsnitt",
|
||||
"MessageEpisodesQueuedForDownload": "{0} avsnitt i kö för nedladdning",
|
||||
"MessageFailedToRefreshToken": "Misslyckades med att uppdatera token, nytt inloggningsförsök krävs",
|
||||
"MessageFeedURLWillBe": "Flödes-URL kommer att vara {0}",
|
||||
"MessageFetching": "Hämtar...",
|
||||
"MessageFollowTheProjectOnGithub": "Följ projektet på Github",
|
||||
@@ -324,6 +326,9 @@
|
||||
"MessageNoSeries": "Inga serier",
|
||||
"MessageNoUpdatesWereNecessary": "Inga uppdateringar var nödvändiga",
|
||||
"MessageNoUserPlaylists": "Du har inga spellistor",
|
||||
"MessageOldServerAuthReLoginRequired": "Autentiseringen har förbättrats för ökad säkerhet i server v2.26.0. Alla användare måste logga in igen.",
|
||||
"MessageOldServerAuthWarning": "Servern använder en gammal autentisering",
|
||||
"MessageOldServerAuthWarningHelp": "Den här servern kör en version äldre än v2.26.0. Ett säkrare autentiseringssystem finns i v2.26.0. Det rekommenderas starkt att uppdatera servern till den senaste versionen. Om du redan har uppdaterat servern, logga in igen för att använda den nya autentiseringen.",
|
||||
"MessageOldServerConnectionWarning": "Serveranslutningskonfigurationen använder ett gammalt användar-ID. Vänligen ta bort och lägg till denna serveranslutning igen.",
|
||||
"MessageOldServerConnectionWarningHelp": "Du ställde ursprungligen in anslutningen till denna server före databasflytten i version 2.3.0, som släpptes i juni 2023. En framtida serveruppdatering kommer att ta bort möjligheten att logga in med denna gamla anslutning. Vänligen ta bort den befintliga serveranslutningen och anslut igen (med samma serveradress och inloggningsuppgifter). Om du har nedladdad media på den här enheten måste den laddas ner igen för att synkroniseras med servern.",
|
||||
"MessagePodcastSearchField": "Skriv sökfrågan eller URL-adressen för RSS-flödet",
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
"ButtonAuthors": "Yazarlar",
|
||||
"ButtonBack": "Geri",
|
||||
"ButtonCancel": "İptal",
|
||||
"ButtonCancelTimer": "İptal Zamanlayıcısı",
|
||||
"ButtonCancelTimer": "Zamanlayıcıyı İptal Et",
|
||||
"ButtonClearFilter": "Filtreyi Temizle",
|
||||
"ButtonClearLogs": "Günlükleri Temizle",
|
||||
"ButtonClearLogs": "Kayıtları Temizle",
|
||||
"ButtonCloseFeed": "Akışı Kapat",
|
||||
"ButtonCollections": "Koleksiyonlar",
|
||||
"ButtonConnect": "Bağlan",
|
||||
@@ -18,48 +18,48 @@
|
||||
"ButtonDeleteLocalEpisode": "Yerel bölümü sil",
|
||||
"ButtonDeleteLocalFile": "Yerel dosyayı sil",
|
||||
"ButtonDeleteLocalItem": "Yerel öğeyi sil",
|
||||
"ButtonDisableAutoTimer": "Oto Zamanlayıcıyı Devre Dışı Bırak",
|
||||
"ButtonDisableAutoTimer": "Otomatik Zamanlayıcıyı Devre Dışı Bırak",
|
||||
"ButtonDisconnect": "Bağlantıyı Kes",
|
||||
"ButtonGoToWebClient": "Web İstemcisine Git",
|
||||
"ButtonHistory": "Geçmiş",
|
||||
"ButtonHome": "Ana sayfa",
|
||||
"ButtonHome": "Ana Sayfa",
|
||||
"ButtonIssues": "Sorunlar",
|
||||
"ButtonLatest": "En yeni",
|
||||
"ButtonLatest": "En Yeniler",
|
||||
"ButtonLibrary": "Kütüphane",
|
||||
"ButtonLocalMedia": "Yerel Medya",
|
||||
"ButtonLogs": "Günlükler",
|
||||
"ButtonLogs": "Kayıtlar",
|
||||
"ButtonManageLocalFiles": "Yerel Dosyaları Yönet",
|
||||
"ButtonMaskServerAddress": "Sunucu adresini maskele",
|
||||
"ButtonMaskServerAddress": "Sunucu adresini gizle",
|
||||
"ButtonNewFolder": "Yeni Klasör",
|
||||
"ButtonNextEpisode": "Sonraki Bölüm",
|
||||
"ButtonOk": "Tamam",
|
||||
"ButtonOpenFeed": "Akışı Aç",
|
||||
"ButtonOverride": "Üstüne yaz",
|
||||
"ButtonPause": "Durdur",
|
||||
"ButtonOverride": "Üzerine Yaz",
|
||||
"ButtonPause": "Duraklat",
|
||||
"ButtonPlay": "Oynat",
|
||||
"ButtonPlayEpisode": "Bölümü Oynat",
|
||||
"ButtonPlaylists": "Oynatma listeleri",
|
||||
"ButtonPlaylists": "Oynatma Listeleri",
|
||||
"ButtonRead": "Oku",
|
||||
"ButtonReadLess": "Daha az göster",
|
||||
"ButtonReadMore": "Daha fazla göster",
|
||||
"ButtonReadMore": "Devamını oku",
|
||||
"ButtonRemove": "Kaldır",
|
||||
"ButtonRemoveFromServer": "Sunucudan Kaldır",
|
||||
"ButtonSave": "Kaydet",
|
||||
"ButtonSaveOrder": "Kaydetme Sırası",
|
||||
"ButtonSaveOrder": "Sıralamayı Kaydet",
|
||||
"ButtonSearch": "Ara",
|
||||
"ButtonSendEbookToDevice": "Ekitabı Cihaza Gönder",
|
||||
"ButtonSendEbookToDevice": "E-kitabı Cihaza Gönder",
|
||||
"ButtonSeries": "Seriler",
|
||||
"ButtonSetTimer": "Zamanlayıcı Kur",
|
||||
"ButtonStream": "Yayın",
|
||||
"ButtonStream": "Akış Başlat",
|
||||
"ButtonSubmit": "Gönder",
|
||||
"ButtonSwitchServerUser": "Sunucu/Kullanıcı Değiştir",
|
||||
"ButtonUnmaskServerAddress": "Sunucu adresinin maskesini kaldır",
|
||||
"ButtonUnmaskServerAddress": "Sunucu adresini göster",
|
||||
"ButtonUserStats": "Kullanıcı İstatistikleri",
|
||||
"ButtonYes": "Evet",
|
||||
"HeaderAccount": "Hesap",
|
||||
"HeaderAdvanced": "Gelişmiş",
|
||||
"HeaderAndroidAutoSettings": "Android Auto Ayarları",
|
||||
"HeaderAudioTracks": "Ses Kanalları",
|
||||
"HeaderAudioTracks": "Ses Parçaları",
|
||||
"HeaderChapters": "Bölümler",
|
||||
"HeaderCollection": "Koleksiyon",
|
||||
"HeaderCollectionItems": "Koleksiyon Öğeleri",
|
||||
@@ -67,19 +67,19 @@
|
||||
"HeaderDataSettings": "Veri Ayarları",
|
||||
"HeaderDetails": "Detaylar",
|
||||
"HeaderDownloads": "İndirilenler",
|
||||
"HeaderEbookFiles": "Ebook Dosyaları",
|
||||
"HeaderEbookFiles": "E-kitap Dosyaları",
|
||||
"HeaderEpisodes": "Bölümler",
|
||||
"HeaderEreaderSettings": "Ereader Ayarları",
|
||||
"HeaderLatestEpisodes": "En son bölümler",
|
||||
"HeaderEreaderSettings": "E-kitap Okuyucu Ayarları",
|
||||
"HeaderLatestEpisodes": "En Son Bölümler",
|
||||
"HeaderLibraries": "Kütüphaneler",
|
||||
"HeaderLocalFolders": "Yerel Klasörler",
|
||||
"HeaderLocalLibraryItems": "Yerel Kütüphane Öğeleri",
|
||||
"HeaderNewPlaylist": "Yeni Oynatma Listesi",
|
||||
"HeaderOpenRSSFeed": "RSS Akışını Aç",
|
||||
"HeaderPlaybackSettings": "Çalma Ayarları",
|
||||
"HeaderPlaylist": "Oynatma listesi",
|
||||
"HeaderPlaybackSettings": "Oynatma Ayarları",
|
||||
"HeaderPlaylist": "Oynatma Listesi",
|
||||
"HeaderPlaylistItems": "Oynatma Listesi Öğeleri",
|
||||
"HeaderProgressSyncFailed": "İlerleme Eşzamanlaması Başarısız",
|
||||
"HeaderProgressSyncFailed": "İlerleme Eşitlemesi Başarısız",
|
||||
"HeaderRSSFeed": "RSS Akışı",
|
||||
"HeaderRSSFeedGeneral": "RSS Detayları",
|
||||
"HeaderRSSFeedIsOpen": "RSS Akışı Açık",
|
||||
@@ -87,33 +87,33 @@
|
||||
"HeaderSettings": "Ayarlar",
|
||||
"HeaderSleepTimer": "Uyku Zamanlayıcısı",
|
||||
"HeaderSleepTimerSettings": "Uyku Zamanlayıcısı Ayarları",
|
||||
"HeaderStatsMinutesListeningChart": "Dinlenilen Dakika (son 7 gün)",
|
||||
"HeaderStatsRecentSessions": "Geçmiş Oturumlar",
|
||||
"HeaderStatsMinutesListeningChart": "Dinlenen Dakika (son 7 gün)",
|
||||
"HeaderStatsRecentSessions": "Son Oturumlar",
|
||||
"HeaderTableOfContents": "İçindekiler",
|
||||
"HeaderUserInterfaceSettings": "Kullanıcı Arayüzü Ayarları",
|
||||
"HeaderUserInterfaceSettings": "Arayüz Ayarları",
|
||||
"HeaderYourStats": "İstatistiklerin",
|
||||
"LabelAddToPlaylist": "Oynatma Listesine Ekle",
|
||||
"LabelAddedAt": "Eklenme Zamanı",
|
||||
"LabelAddedDate": "Eklendi {0}",
|
||||
"LabelAll": "Hepsi",
|
||||
"LabelAllowSeekingOnMediaControls": "Medya bildirimi kontrollerinde ileri/geri sarmaya izin ver",
|
||||
"LabelAddedDate": "Eklendi: {0}",
|
||||
"LabelAll": "Tümü",
|
||||
"LabelAllowSeekingOnMediaControls": "Medya bildirim kontrollerinde ileri/geri sarmaya izin ver",
|
||||
"LabelAlways": "Her zaman",
|
||||
"LabelAndroidAutoBrowseLimitForGrouping": "Alfabetik düşüş limiti",
|
||||
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Gösterilecek öğe sayısı bu sayıdan az olduğunda alfabetik düşüş kullanma",
|
||||
"LabelAndroidAutoBrowseSeriesSequenceOrder": "Kitap Serisi Sıralaması",
|
||||
"LabelAskConfirmation": "Doğrulama iste",
|
||||
"LabelAndroidAutoBrowseLimitForGrouping": "Alfabetik gruplama sınırı",
|
||||
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Gösterilecek öğe sayısı bu miktardan az olduğunda alfabetik gruplama kullanma",
|
||||
"LabelAndroidAutoBrowseSeriesSequenceOrder": "Seri kitaplarının sıralaması",
|
||||
"LabelAskConfirmation": "Onay iste",
|
||||
"LabelAuthor": "Yazar",
|
||||
"LabelAuthorFirstLast": "Yazar (İlk Son)",
|
||||
"LabelAuthorLastFirst": "Yazar (Son, İlk)",
|
||||
"LabelAuthorFirstLast": "Yazar (Ad Soyad)",
|
||||
"LabelAuthorLastFirst": "Yazar (Soyad, Ad)",
|
||||
"LabelAuthors": "Yazarlar",
|
||||
"LabelAutoDownloadEpisodes": "Bölümleri Otomatik Olarak İndir",
|
||||
"LabelAutoRewindTime": "Oto geri sarma zamanı",
|
||||
"LabelAutoSleepTimer": "Oto uyku zamanlayıcısı",
|
||||
"LabelAutoSleepTimerAutoRewind": "Oto uyku zamanlayıcı oto geri sarım",
|
||||
"LabelAutoSleepTimerAutoRewindHelp": "Oto uyku zamanlayıcısı bittiğinde, öğeyi tekrar oynatmak otomatik olarak konumunuzu geri saracaktır.",
|
||||
"LabelAutoSleepTimerHelp": "Belirtilen başlangıç ve bitiş zamanları arasında medya oynatıldığında bir uyku zamanlayıcısı otomatik olarak başlatılacaktır.",
|
||||
"LabelAutoDownloadEpisodes": "Bölümleri Otomatik İndir",
|
||||
"LabelAutoRewindTime": "Otomatik geri sarma süresi",
|
||||
"LabelAutoSleepTimer": "Otomatik uyku zamanlayıcısı",
|
||||
"LabelAutoSleepTimerAutoRewind": "Otomatik uyku zamanlayıcısı için geri sarım",
|
||||
"LabelAutoSleepTimerAutoRewindHelp": "Otomatik uyku zamanlayıcısı bittiğinde, öğeyi tekrar oynatmak konumunuzu otomatik olarak geri saracaktır.",
|
||||
"LabelAutoSleepTimerHelp": "Belirtilen başlangıç ve bitiş zamanları arasında medya oynatıldığında bir uyku zamanlayıcısı otomatik olarak başlar.",
|
||||
"LabelBooks": "Kitaplar",
|
||||
"LabelChapterTrack": "Bölüm İlerleme Çubuğu",
|
||||
"LabelChapterTrack": "Bölüm Parçası",
|
||||
"LabelChapters": "Bölümler",
|
||||
"LabelClosePlayer": "Oynatıcıyı kapat",
|
||||
"LabelCollapseSeries": "Seriyi Daralt",
|
||||
@@ -123,84 +123,86 @@
|
||||
"LabelContinueListening": "Dinlemeye Devam Et",
|
||||
"LabelContinueReading": "Okumaya Devam Et",
|
||||
"LabelContinueSeries": "Seriye Devam Et",
|
||||
"LabelCustomTime": "Özelleştirilmiş zaman",
|
||||
"LabelCustomTime": "Özel süre",
|
||||
"LabelDescription": "Açıklama",
|
||||
"LabelDisableAudioFadeOut": "Ses kararmasını(fade) devre dışı bırak",
|
||||
"LabelDisableAudioFadeOutHelp": "Uyku zamanlayıcısında 1 dakikadan az kaldığında ses azalmaya başlayacaktır. Azalmaması için bu ayarı etkinleştirin.",
|
||||
"LabelDisableAudioFadeOut": "Sesin kısılmasını devre dışı bırak",
|
||||
"LabelDisableAudioFadeOutHelp": "Uyku zamanlayıcısında 1 dakikadan az kaldığında ses seviyesi azalmaya başlar. Bu ayarı etkinleştirerek sesin kısılmasını engelleyebilirsiniz.",
|
||||
"LabelDisableAutoRewind": "Otomatik geri sarmayı devre dışı bırak",
|
||||
"LabelDisableShakeToReset": "Sıfırlamak için sallamayı devre dışı bırak",
|
||||
"LabelDisableShakeToResetHelp": "Zamanlayıcı çalışırken YA DA zamanlayıcı bitmeden önce son 2 dakika içinde cihazınızı sallamak uyku zamanlayıcısını sıfırlar. Sıfırlamak için sallamayı devre dışı bırakmak için bu ayarı etkinleştirin.",
|
||||
"LabelDisableVibrateOnReset": "Sıfırlamada titreşmeyi devre dışı bırak",
|
||||
"LabelDisableVibrateOnResetHelp": "Uyku zamanlayıcısı sıfırlandığında cihazınız titreşecektir. Uyku zamanlayıcısı sıfırlandığında titreşmemesi için bu ayarı etkinleştirin.",
|
||||
"LabelDisableShakeToResetHelp": "Zamanlayıcı çalışırken VEYA bitmesine 2 dakikadan az kala cihazınızı sallamak uyku zamanlayıcısını sıfırlar. Bu özelliği devre dışı bırakmak için bu ayarı etkinleştirin.",
|
||||
"LabelDisableVibrateOnReset": "Sıfırlamada titreşimi kapat",
|
||||
"LabelDisableVibrateOnResetHelp": "Uyku zamanlayıcısı sıfırlandığında cihazınız titreşir. Bu ayarı etkinleştirerek titreşimi kapatabilirsiniz.",
|
||||
"LabelDiscover": "Keşfet",
|
||||
"LabelDownload": "İndir",
|
||||
"LabelDownloadUsingCellular": "Hücresel kullanarak indir",
|
||||
"LabelDownloaded": "İndirilmiş",
|
||||
"LabelDownloadUsingCellular": "Hücresel Veri ile İndir",
|
||||
"LabelDownloaded": "İndirildi",
|
||||
"LabelDuration": "Süre",
|
||||
"LabelEbook": "Ekitap",
|
||||
"LabelEbooks": "Ekitaplar",
|
||||
"LabelEbook": "E-kitap",
|
||||
"LabelEbooks": "E-kitaplar",
|
||||
"LabelEnable": "Etkinleştir",
|
||||
"LabelEnableMp3IndexSeeking": "Mp3 dizin aramasını etkinleştir",
|
||||
"LabelEnableMp3IndexSeekingHelp": "Bu ayar sadece eğer mp3 dosyalarınız düzgün bir şekilde yüklenmiyorsa etkinleştirilmelidir",
|
||||
"LabelEnd": "Son",
|
||||
"LabelEnableMp3IndexSeeking": "MP3 dizinlemesini etkinleştir",
|
||||
"LabelEnableMp3IndexSeekingHelp": "Bu ayar yalnızca, ileri-geri sarmada sorun yaşadığınız mp3 dosyalarınız varsa etkinleştirilmelidir. Hatalı sarma genellikle Değişken Bit Hızlı (VBR) MP3'lerden kaynaklanır. Bu ayar, dosya okunurken bir zaman-bayt haritası oluşturan dizin aramayı zorunlu kılar. Büyük MP3 dosyalarında, dosyanın sonlarına doğru sarma yaparken bir gecikme yaşanabilir.",
|
||||
"LabelEnd": "Bitiş",
|
||||
"LabelEndOfChapter": "Bölüm Sonu",
|
||||
"LabelEndTime": "Bitiş zamanı",
|
||||
"LabelEpisode": "Bölüm",
|
||||
"LabelFeedURL": "Akış URLsi",
|
||||
"LabelExplicit": "Açık İçerik",
|
||||
"LabelFeedURL": "Akış URL'si",
|
||||
"LabelFile": "Dosya",
|
||||
"LabelFileBirthtime": "Dosya Oluşum Zamanı",
|
||||
"LabelFileModified": "Dosya Düzenlendi",
|
||||
"LabelFilename": "Dosya İsmi",
|
||||
"LabelFinished": "Tamamlandı",
|
||||
"LabelFileBirthtime": "Dosya Oluşturulma Zamanı",
|
||||
"LabelFileModified": "Dosya Değiştirilme Zamanı",
|
||||
"LabelFilename": "Dosya Adı",
|
||||
"LabelFinished": "Bitirildi",
|
||||
"LabelFolder": "Klasör",
|
||||
"LabelFontBoldness": "Font Kalınlığı",
|
||||
"LabelFontScale": "Font büyüklüğü",
|
||||
"LabelFontBoldness": "Yazı Tipi Kalınlığı",
|
||||
"LabelFontScale": "Yazı Tipi Ölçeği",
|
||||
"LabelGenre": "Tür",
|
||||
"LabelGenres": "Türler",
|
||||
"LabelHapticFeedback": "Haptik geribildirim",
|
||||
"LabelHasEbook": "Ekitabı var",
|
||||
"LabelHasSupplementaryEbook": "İlave ekitabı var",
|
||||
"LabelHeavy": "Ağır",
|
||||
"LabelHapticFeedback": "Dokunsal geribildirim",
|
||||
"LabelHasEbook": "E-kitabı var",
|
||||
"LabelHasSupplementaryEbook": "Ek e-kitabı var",
|
||||
"LabelHeavy": "Çok Kalın",
|
||||
"LabelHigh": "Yüksek",
|
||||
"LabelHost": "Sunucu",
|
||||
"LabelInProgress": "İlerleme Halinde",
|
||||
"LabelInProgress": "Devam Ediyor",
|
||||
"LabelIncomplete": "Tamamlanmamış",
|
||||
"LabelInternalAppStorage": "Dahili Uygulama Deposu",
|
||||
"LabelJumpBackwardsTime": "Geri sarma zamanı",
|
||||
"LabelJumpForwardsTime": "İleri sarma zamanı",
|
||||
"LabelKeepScreenAwake": "Ekranı uyanık tut",
|
||||
"LabelInternalAppStorage": "Dahili Uygulama Depolama Alanı",
|
||||
"LabelJumpBackwardsTime": "Geri sarma süresi",
|
||||
"LabelJumpForwardsTime": "İleri sarma süresi",
|
||||
"LabelKeepScreenAwake": "Ekranı açık tut",
|
||||
"LabelLanguage": "Dil",
|
||||
"LabelLayout": "Düzen",
|
||||
"LabelLayoutAuto": "Oto",
|
||||
"LabelLayoutAuto": "Otomatik",
|
||||
"LabelLayoutSinglePage": "Tek sayfa",
|
||||
"LabelLight": "Açık",
|
||||
"LabelLight": "İnce",
|
||||
"LabelLineSpacing": "Satır aralığı",
|
||||
"LabelListenAgain": "Tekrar Dinle",
|
||||
"LabelLocalBooks": "Yerel Kitaplar",
|
||||
"LabelLocalPodcasts": "Yerel Podcastler",
|
||||
"LabelLocalPodcasts": "Yerel Podcast'ler",
|
||||
"LabelLockOrientation": "Yönü kilitle",
|
||||
"LabelLockPlayer": "Oynatıcıyı Kilitle",
|
||||
"LabelLow": "Düşük",
|
||||
"LabelMediaType": "Medya Türü",
|
||||
"LabelMedium": "Orta",
|
||||
"LabelMissing": "Kayıp",
|
||||
"LabelMore": "Daha fazla",
|
||||
"LabelMoreInfo": "Daha fazla bilgi",
|
||||
"LabelMissing": "Eksik",
|
||||
"LabelMore": "Daha Fazla",
|
||||
"LabelMoreInfo": "Daha Fazla Bilgi",
|
||||
"LabelName": "İsim",
|
||||
"LabelNarrator": "Anlatıcı",
|
||||
"LabelNarrators": "Anlatıcılar",
|
||||
"LabelNavigateWithVolume": "Ses tuşlarını kullanarak hareket et",
|
||||
"LabelNavigateWithVolumeWhilePlaying": "Oynatırken ses tuşlarıyla hareket edilmesine izin ver",
|
||||
"LabelNavigateWithVolume": "Ses tuşlarıyla gezin",
|
||||
"LabelNavigateWithVolumeMirrored": "Aynalanmış",
|
||||
"LabelNavigateWithVolumeWhilePlaying": "Oynatılırken ses tuşlarıyla gezinmeye izin ver",
|
||||
"LabelNavigateWithVolumeWhilePlayingDisabled": "Kapalı",
|
||||
"LabelNavigateWithVolumeWhilePlayingEnabled": "Açık",
|
||||
"LabelNever": "Asla",
|
||||
"LabelNewestAuthors": "En Yeni Yazarlar",
|
||||
"LabelNewestEpisodes": "En Yeni Bölümler",
|
||||
"LabelNo": "Hayır",
|
||||
"LabelNotFinished": "Tamamlanmadı",
|
||||
"LabelNotFinished": "Bitirilmedi",
|
||||
"LabelNotStarted": "Başlanmadı",
|
||||
"LabelNumEpisodes": "{0} bölümler",
|
||||
"LabelNumEpisodesIncomplete": "{0} bölümler, {1} tamamlanmadı",
|
||||
"LabelNumEpisodes": "{0} bölüm",
|
||||
"LabelNumEpisodesIncomplete": "{0} bölüm, {1} eksik",
|
||||
"LabelNumberOfEpisodes": "Bölüm Sayısı",
|
||||
"LabelOff": "Kapalı",
|
||||
"LabelOn": "Açık",
|
||||
@@ -209,49 +211,59 @@
|
||||
"LabelPlaybackDirect": "Doğrudan",
|
||||
"LabelPlaybackLocal": "Yerel",
|
||||
"LabelPlaybackSpeed": "Oynatma Hızı",
|
||||
"LabelPlaybackTranscode": "Transcode",
|
||||
"LabelPlaybackTranscode": "Dönüştürerek Oynat",
|
||||
"LabelPodcast": "Podcast",
|
||||
"LabelPodcasts": "Podcastler",
|
||||
"LabelPreventIndexing": "Akışınızın iTunes ve Google podcast dizinleri tarafından dizinlenmesini önleyin",
|
||||
"LabelPodcasts": "Podcast'ler",
|
||||
"LabelPreventIndexing": "Akışınızın iTunes ve Google tarafından dizine eklenmesini önleyin",
|
||||
"LabelProgress": "İlerleme",
|
||||
"LabelPubDate": "Yay. Tarihi",
|
||||
"LabelPublishYear": "Yayım Yılı",
|
||||
"LabelPublishedDate": "Yayımlandı {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Özelleştirilmiş sahip Emaili",
|
||||
"LabelRSSFeedCustomOwnerName": "Özelleştirilmis sahip İsmi",
|
||||
"LabelRSSFeedPreventIndexing": "Dizinlemeyi Önle",
|
||||
"LabelPubDate": "Yayın Tarihi",
|
||||
"LabelPublishYear": "Yayın Yılı",
|
||||
"LabelPublishedDate": "Yayınlandı: {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Özel Sahip E-postası",
|
||||
"LabelRSSFeedCustomOwnerName": "Özel Sahip Adı",
|
||||
"LabelRSSFeedOpen": "RSS Akışı Açık",
|
||||
"LabelRSSFeedPreventIndexing": "Dizine Eklemeyi Önle",
|
||||
"LabelRSSFeedSlug": "RSS Akışı Kısa Adı",
|
||||
"LabelRandomly": "Rastgele",
|
||||
"LabelRead": "Oku",
|
||||
"LabelReadAgain": "Tekrar Oku",
|
||||
"LabelRecentlyAdded": "Yakınlarda Eklenmiş",
|
||||
"LabelRecentSeries": "Son Seriler",
|
||||
"LabelRecentlyAdded": "Son Eklenenler",
|
||||
"LabelRemoveFromPlaylist": "Oynatma Listesinden Kaldır",
|
||||
"LabelScaleElapsedTimeBySpeed": "Geçen Süreyi Hıza Göre Ölçekle",
|
||||
"LabelSeason": "Sezon",
|
||||
"LabelSelectADevice": "Cihaz seçiniz",
|
||||
"LabelSelectADevice": "Bir cihaz seçin",
|
||||
"LabelSequenceAscending": "Artan Sıralama",
|
||||
"LabelSequenceDescending": "Azalan Sıralama",
|
||||
"LabelSeries": "Seriler",
|
||||
"LabelServerAddress": "Sunucu adresi",
|
||||
"LabelSetEbookAsPrimary": "Birincil olarak ayarla",
|
||||
"LabelSetEbookAsSupplementary": "Yedek olarak ayarla",
|
||||
"LabelSetEbookAsSupplementary": "Ek olarak ayarla",
|
||||
"LabelShakeSensitivity": "Sallama hassasiyeti",
|
||||
"LabelShowAll": "Hepsini Göster",
|
||||
"LabelShowAll": "Tümünü Göster",
|
||||
"LabelSize": "Boyut",
|
||||
"LabelSleepTimer": "Uyku Zamanlayıcısı",
|
||||
"LabelStart": "Başla",
|
||||
"LabelSleepTimerAlmostDoneChime": "Neredeyse bittiğinde bir zil çal",
|
||||
"LabelSleepTimerAlmostDoneChimeHelp": "Uyku zamanlayıcısının bitmesine 30 saniye kala bir zil çalar",
|
||||
"LabelStart": "Başlangıç",
|
||||
"LabelStartTime": "Başlangıç zamanı",
|
||||
"LabelStatsBestDay": "En İyi Gün",
|
||||
"LabelStatsDailyAverage": "Günlük Ortalama",
|
||||
"LabelStatsDays": "Günler",
|
||||
"LabelStatsDaysListened": "Dinlenen Günler",
|
||||
"LabelStatsInARow": "art arda",
|
||||
"LabelStatsItemsFinished": "Bitirilen Öğeler",
|
||||
"LabelStatsDays": "Gün",
|
||||
"LabelStatsDaysListened": "Dinlenen Gün Sayısı",
|
||||
"LabelStatsInARow": "üst üste",
|
||||
"LabelStatsItemsFinished": "Bitirilen Öğe Sayısı",
|
||||
"LabelStatsMinutes": "dakika",
|
||||
"LabelStatsMinutesListening": "Dinlenen Dakika",
|
||||
"LabelStatsWeekListening": "Haftalık Dinleme",
|
||||
"LabelStreamingUsingCellular": "Hücresel Veri ile Akış",
|
||||
"LabelTag": "Etiket",
|
||||
"LabelTags": "Etiketler",
|
||||
"LabelTheme": "Tema",
|
||||
"LabelThemeBlack": "Siyah",
|
||||
"LabelThemeDark": "Koyu",
|
||||
"LabelThemeLight": "Açık",
|
||||
"LabelTimeRemaining": "{0} kalan",
|
||||
"LabelTimeRemaining": "{0} kaldı",
|
||||
"LabelTitle": "Başlık",
|
||||
"LabelTotalSize": "Toplam Boyut",
|
||||
"LabelTotalTrack": "Toplam Parça",
|
||||
@@ -264,13 +276,84 @@
|
||||
"LabelUsername": "Kullanıcı Adı",
|
||||
"LabelVeryHigh": "Çok Yüksek",
|
||||
"LabelVeryLow": "Çok Düşük",
|
||||
"LabelYearReviewHide": "Yıla Bakışı Sakla",
|
||||
"LabelYearReviewShow": "Yıla Bakışı Göster",
|
||||
"LabelYourBookmarks": "Yer İşaretleriniz",
|
||||
"LabelYourProgress": "Gelişiminiz",
|
||||
"MessageAndroid10Downloads": "Android 10 ve aşağısı indirmeler için dahili uygulama deposunu kullanacaktır.",
|
||||
"MessageAttemptingServerConnection": "Sunucusu bağlantısı deneniyor...",
|
||||
"MessageAudiobookshelfServerNotConnected": "Audiobookshelf sunucusu bağlı değil",
|
||||
"MessageAudiobookshelfServerRequired": "<strong>Önemli!</strong> Bu uygulama sizin ya da tanıdığınız birinin hostladığı bir Audiobookshelf sunucusu ile birlikte çalışması için tasarlanmıştır. Bu uygulama herhangi bir içerik sunmaz.",
|
||||
"MessageBookshelfEmpty": "Kitaplık boş"
|
||||
"LabelYearReviewHide": "Yıl Değerlendirmesini Gizle",
|
||||
"LabelYearReviewShow": "Yıl Değerlendirmesini Gör",
|
||||
"LabelYourBookmarks": "Yer İmlerin",
|
||||
"LabelYourProgress": "İlerlemen",
|
||||
"MessageAndroid10Downloads": "Android 10 ve altı sürümler, indirmeler için dahili uygulama depolama alanını kullanacaktır.",
|
||||
"MessageAttemptingServerConnection": "Sunucuya bağlanmaya çalışılıyor...",
|
||||
"MessageAudiobookshelfServerNotConnected": "Audiobookshelf sunucusuna bağlı değil",
|
||||
"MessageAudiobookshelfServerRequired": "<strong>Önemli!</strong> Bu uygulama, sizin veya bir tanıdığınızın barındırdığı bir Audiobookshelf sunucusu ile çalışmak üzere tasarlanmıştır. Bu uygulama herhangi bir içerik sağlamaz.",
|
||||
"MessageBookshelfEmpty": "Kitaplık boş",
|
||||
"MessageConfirmDeleteLocalEpisode": "\"{0}\" adlı yerel bölüm cihazınızdan kaldırılsın mı? Sunucudaki dosya etkilenmeyecektir.",
|
||||
"MessageConfirmDeleteLocalFiles": "Bu öğenin yerel dosyaları cihazınızdan kaldırılsın mı? Sunucudaki dosyalar ve ilerlemeniz etkilenmeyecektir.",
|
||||
"MessageConfirmDisableAutoTimer": "Otomatik zamanlayıcıyı günün geri kalanında devre dışı bırakmak istediğinizden emin misiniz? Zamanlayıcı, bu otomatik uyku periyodu sonunda veya uygulamayı yeniden başlattığınızda tekrar etkinleştirilecektir.",
|
||||
"MessageConfirmDiscardProgress": "İlerlemenizi sıfırlamak istediğinizden emin misiniz?",
|
||||
"MessageConfirmDownloadUsingCellular": "Hücresel veri kullanarak indirmek üzeresiniz. Bu işlem, operatörünüz tarafından veri ücretlendirmesine tabi olabilir. Devam etmek istiyor musunuz?",
|
||||
"MessageConfirmMarkAsFinished": "Bu öğeyi bitirildi olarak işaretlemek istediğinizden emin misiniz?",
|
||||
"MessageConfirmRemoveBookmark": "Yer imini kaldırmak istediğinizden emin misiniz?",
|
||||
"MessageConfirmStreamingUsingCellular": "Hücresel veri kullanarak akış başlatmak üzeresiniz. Bu işlem, operatörünüz tarafından veri ücretlendirmesine tabi olabilir. Devam etmek istiyor musunuz?",
|
||||
"MessageDiscardProgress": "İlerlemeyi Sıfırla",
|
||||
"MessageDownloadCompleteProcessing": "İndirme tamamlandı. İşleniyor...",
|
||||
"MessageDownloading": "İndiriliyor...",
|
||||
"MessageDownloadingEpisode": "Bölüm indiriliyor",
|
||||
"MessageEpisodesQueuedForDownload": "{0} bölüm indirme için sıraya alındı",
|
||||
"MessageFailedToRefreshToken": "Jeton (token) yenilenemedi, yeniden giriş yapılması gerekiyor",
|
||||
"MessageFeedURLWillBe": "Akış URL'si {0} olacak",
|
||||
"MessageFetching": "Alınıyor...",
|
||||
"MessageFollowTheProjectOnGithub": "Projeyi Github'da takip edin",
|
||||
"MessageItemDownloadCompleteFailedToCreate": "Öğe indirme tamamlandı ancak kütüphane öğesi oluşturulamadı",
|
||||
"MessageItemMissing": "Öğe eksik ve sunucuda düzeltilmesi gerekiyor. Genellikle bir öğe, dosya yollarına erişilemediği için eksik olarak işaretlenir.",
|
||||
"MessageLoading": "Yükleniyor...",
|
||||
"MessageLoadingServerData": "Sunucu verileri yükleniyor...",
|
||||
"MessageLocalFolderDescription": "'Dahili Uygulama Depolama Alanı'na yalnızca bu uygulama erişebilir. Bu uygulama, yalnızca kendi içinden indirilen medyayı destekler. Paylaşılan depolama klasörleri, bu uygulama tarafından indirilen medyaya diğer uygulamaların erişmesine izin vermek için kullanılabilir.",
|
||||
"MessageMarkAsFinished": "Bitirildi Olarak İşaretle",
|
||||
"MessageMediaLinkedToADifferentServer": "Medya, farklı bir adresteki ({0}) bir Audiobookshelf sunucusuna bağlı. İlerleme, bu sunucu adresine bağlandığınızda eşitlenecektir.",
|
||||
"MessageMediaLinkedToADifferentUser": "Medya bu sunucuya bağlı ancak farklı bir kullanıcı tarafından indirildi. İlerleme yalnızca indiren kullanıcıyla eşitlenecektir.",
|
||||
"MessageMediaLinkedToServer": "{0} sunucusuna bağlı",
|
||||
"MessageMediaLinkedToThisServer": "İndirilen medya bu sunucuya bağlı",
|
||||
"MessageMediaNotLinkedToServer": "Medya bir Audiobookshelf sunucusuna bağlı değil. İlerleme eşitlenmeyecek.",
|
||||
"MessageNoBookmarks": "Yer İmi Yok",
|
||||
"MessageNoChapters": "Bölüm Yok",
|
||||
"MessageNoCollections": "Koleksiyon Yok",
|
||||
"MessageNoItems": "Öğe Yok",
|
||||
"MessageNoItemsFound": "Öğe bulunamadı",
|
||||
"MessageNoListeningSessions": "Dinleme Oturumu Yok",
|
||||
"MessageNoLogs": "Kayıt yok",
|
||||
"MessageNoMediaFolders": "Medya Klasörü Yok",
|
||||
"MessageNoNetworkConnection": "Ağ bağlantısı yok",
|
||||
"MessageNoPodcastsFound": "Podcast bulunamadı",
|
||||
"MessageNoSeries": "Seri yok",
|
||||
"MessageNoUpdatesWereNecessary": "Güncelleme gerekmedi",
|
||||
"MessageNoUserPlaylists": "Hiç oynatma listeniz yok",
|
||||
"MessageOldServerAuthReLoginRequired": "Sunucu v2.26.0 sürümünde güvenlik için kimlik doğrulama sistemi iyileştirildi. Tüm kullanıcıların yeniden giriş yapması gerekmektedir.",
|
||||
"MessageOldServerAuthWarning": "Sunucu, güncel olmayan bir kimlik doğrulama yöntemi kullanıyor",
|
||||
"MessageOldServerAuthWarningHelp": "Bu sunucu, v2.26.0'dan daha eski bir sürüm çalıştırıyor. v2.26.0'da daha güvenli bir kimlik doğrulama sistemi eklendi. Sunucuyu en son sürüme güncellemeniz şiddetle tavsiye edilir. Sunucuyu zaten güncellediyseniz, yeni kimlik doğrulamasını kullanmak için tekrar giriş yapın.",
|
||||
"MessageOldServerConnectionWarning": "Sunucu bağlantı yapılandırması eski bir kullanıcı kimliği kullanıyor. Lütfen bu sunucu bağlantısını silip yeniden ekleyin.",
|
||||
"MessageOldServerConnectionWarningHelp": "Bu sunucuya bağlantıyı Haziran 2023'te yayınlanan 2.3.0 sürümündeki veritabanı geçişinden önce kurdunuz. Gelecekteki bir sunucu güncellemesi, bu eski bağlantıyla oturum açma yeteneğini kaldıracaktır. Lütfen mevcut sunucu bağlantısını silin ve yeniden bağlanın (aynı sunucu adresini ve kimlik bilgilerini kullanarak). Bu cihazda indirilmiş medyanız varsa, sunucuyla eşitlemek için medyanın yeniden indirilmesi gerekecektir.",
|
||||
"MessagePodcastSearchField": "Arama terimi veya RSS akış URL'si girin",
|
||||
"MessageProgressSyncFailed": "Dinleme ilerlemenizi sunucuya bildirme denemesi başarısız oldu. Medya oynatılırken ilerleme eşitleme istekleri her 15 saniyede bir ile 1 dakika arasında denenmeye devam edecektir.",
|
||||
"MessageReportBugsAndContribute": "Hataları bildirin, özellikler isteyin ve katkıda bulunun",
|
||||
"MessageSeriesAlreadyDownloaded": "Bu serideki tüm kitapları zaten indirdiniz.",
|
||||
"MessageSeriesDownloadConfirm": "{3} klasörüne, toplam {2} boyutunda {1} dosyadan oluşan eksik {0} kitap indirilsin mi?",
|
||||
"MessageSeriesDownloadConfirmIos": "Toplam {2} boyutunda {1} dosyadan oluşan eksik {0} kitap indirilsin mi?",
|
||||
"MessageSocketConnectedOverMeteredCellular": "Soket bağlantısı, kotalı hücresel veri üzerinden kuruldu",
|
||||
"MessageSocketConnectedOverMeteredWifi": "Soket bağlantısı, kotalı kablosuz ağ üzerinden kuruldu",
|
||||
"MessageSocketConnectedOverUnmeteredCellular": "Soket bağlantısı, kotasız hücresel veri üzerinden kuruldu",
|
||||
"MessageSocketConnectedOverUnmeteredWifi": "Soket bağlantısı, kotasız kablosuz ağ üzerinden kuruldu",
|
||||
"MessageSocketNotConnected": "Soket bağlı değil",
|
||||
"NoteRSSFeedPodcastAppsHttps": "Uyarı: Çoğu podcast uygulaması, RSS akış URL'sinin HTTPS kullanmasını gerektirir",
|
||||
"NoteRSSFeedPodcastAppsPubDate": "Uyarı: Bölümlerinizden bir veya daha fazlasının Yayın Tarihi yok. Bazı podcast uygulamaları bunu zorunlu kılar.",
|
||||
"ToastBookmarkCreateFailed": "Yer işareti oluşturulamadı",
|
||||
"ToastBookmarkRemoveFailed": "Yer işareti kaldırılamadı",
|
||||
"ToastBookmarkUpdateFailed": "Yer işareti güncellenemedi",
|
||||
"ToastDownloadNotAllowedOnCellular": "Hücresel veri üzerinden indirmeye izin verilmiyor",
|
||||
"ToastItemMarkedAsFinishedFailed": "Bitirildi olarak işaretlenemedi",
|
||||
"ToastItemMarkedAsNotFinishedFailed": "Bitirilmedi olarak işaretlenemedi",
|
||||
"ToastPlaylistCreateFailed": "Oynatma listesi oluşturulamadı",
|
||||
"ToastPodcastCreateFailed": "Podcast oluşturulamadı",
|
||||
"ToastPodcastCreateSuccess": "Podcast başarıyla oluşturuldu",
|
||||
"ToastRSSFeedCloseFailed": "RSS akışı kapatılamadı",
|
||||
"ToastRSSFeedCloseSuccess": "RSS akışı kapatıldı",
|
||||
"ToastStreamingNotAllowedOnCellular": "Hücresel veri üzerinden akışa izin verilmiyor"
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
"LabelContinueBooks": "Продовжити книги",
|
||||
"LabelContinueEpisodes": "Продовжити епізоди",
|
||||
"LabelContinueListening": "Слухати далі",
|
||||
"LabelContinueReading": "Читати далі",
|
||||
"LabelContinueReading": "Продовжити читання",
|
||||
"LabelContinueSeries": "Продовжити серії",
|
||||
"LabelCustomTime": "Власний час",
|
||||
"LabelDescription": "Опис",
|
||||
@@ -298,6 +298,7 @@
|
||||
"MessageDownloading": "Завантажується...",
|
||||
"MessageDownloadingEpisode": "Скачування епізоду",
|
||||
"MessageEpisodesQueuedForDownload": "{0} епізод(ів) у черзі на завантаження",
|
||||
"MessageFailedToRefreshToken": "Не вдалося оновити токен, потрібен повторний увійти",
|
||||
"MessageFeedURLWillBe": "URL-адреса каналу буде {0}",
|
||||
"MessageFetching": "Отримання...",
|
||||
"MessageFollowTheProjectOnGithub": "Слідкуйте за проєктом на GitHub",
|
||||
@@ -318,7 +319,7 @@
|
||||
"MessageNoItems": "Елементи відсутні",
|
||||
"MessageNoItemsFound": "Елементів не знайдено",
|
||||
"MessageNoListeningSessions": "Сеанси прослуховування відсутні",
|
||||
"MessageNoLogs": "Нема журналу",
|
||||
"MessageNoLogs": "Немає журналу",
|
||||
"MessageNoMediaFolders": "Теки медіа відсутні",
|
||||
"MessageNoNetworkConnection": "Немає підключення до мережі",
|
||||
"MessageNoPodcastsFound": "Подкастів не знайдено",
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
"ButtonAdd": "Thêm",
|
||||
"ButtonAddNewServer": "Thêm Máy Chủ Mới",
|
||||
"ButtonAuthors": "Tác Giả",
|
||||
"ButtonBack": "Quay Lại",
|
||||
"ButtonBack": "Quay lại",
|
||||
"ButtonCancel": "Hủy",
|
||||
"ButtonCancelTimer": "Hủy Bộ Đếm",
|
||||
"ButtonClearFilter": "Xóa Bộ Lọc",
|
||||
"ButtonClearLogs": "Xoá logs",
|
||||
"ButtonCloseFeed": "Đóng Feed",
|
||||
"ButtonCollections": "Bộ Sưu Tập",
|
||||
"ButtonConnect": "Kết Nối",
|
||||
@@ -19,21 +20,28 @@
|
||||
"ButtonDeleteLocalItem": "Xóa mục địa phương",
|
||||
"ButtonDisableAutoTimer": "Tắt Bộ Đếm Tự Động",
|
||||
"ButtonDisconnect": "Ngắt Kết Nối",
|
||||
"ButtonGoToWebClient": "Đi tới trang web",
|
||||
"ButtonHistory": "Lịch Sử",
|
||||
"ButtonHome": "Trang Chủ",
|
||||
"ButtonIssues": "Vấn Đề",
|
||||
"ButtonLatest": "Mới Nhất",
|
||||
"ButtonLibrary": "Thư Viện",
|
||||
"ButtonLocalMedia": "Phương Tiện Địa Phương",
|
||||
"ButtonLogs": "Logs",
|
||||
"ButtonManageLocalFiles": "Quản Lý Tập Tin Địa Phương",
|
||||
"ButtonMaskServerAddress": "Ẩn địa chỉ máy chủ",
|
||||
"ButtonNewFolder": "Thư Mục Mới",
|
||||
"ButtonNextEpisode": "Tập Kế Tiếp",
|
||||
"ButtonOk": "Chấp nhận",
|
||||
"ButtonOpenFeed": "Mở Feed",
|
||||
"ButtonOverride": "Bỏ Qua",
|
||||
"ButtonPause": "Tạm Dừng",
|
||||
"ButtonPlay": "Phát",
|
||||
"ButtonPlayEpisode": "Phát",
|
||||
"ButtonPlaylists": "Danh Sách Phát",
|
||||
"ButtonRead": "Đọc",
|
||||
"ButtonReadLess": "Thu gọn",
|
||||
"ButtonReadMore": "Đọc thêm",
|
||||
"ButtonRemove": "Xóa",
|
||||
"ButtonRemoveFromServer": "Xóa khỏi Máy Chủ",
|
||||
"ButtonSave": "Lưu",
|
||||
@@ -45,15 +53,18 @@
|
||||
"ButtonStream": "Phát Trực Tiếp",
|
||||
"ButtonSubmit": "Gửi",
|
||||
"ButtonSwitchServerUser": "Chuyển Đổi Máy Chủ/Người Dùng",
|
||||
"ButtonUnmaskServerAddress": "Hiện địa chỉ máy chủ",
|
||||
"ButtonUserStats": "Thống Kê Người Dùng",
|
||||
"ButtonYes": "Có",
|
||||
"HeaderAccount": "Tài Khoản",
|
||||
"HeaderAdvanced": "Nâng Cao",
|
||||
"HeaderAndroidAutoSettings": "Cài đặt Android Auto",
|
||||
"HeaderAudioTracks": "Danh Sách Âm Thanh",
|
||||
"HeaderChapters": "Chương",
|
||||
"HeaderCollection": "Bộ Sưu Tập",
|
||||
"HeaderCollectionItems": "Mục Bộ Sưu Tập",
|
||||
"HeaderCollectionItems": "Danh sách Bộ Sưu Tập",
|
||||
"HeaderConnectionStatus": "Trạng Thái Kết Nối",
|
||||
"HeaderDataSettings": "Cài đặt dữ liệu",
|
||||
"HeaderDetails": "Chi Tiết",
|
||||
"HeaderDownloads": "Tải Xuống",
|
||||
"HeaderEbookFiles": "Tập Tin Ebook",
|
||||
@@ -68,6 +79,7 @@
|
||||
"HeaderPlaybackSettings": "Cài Đặt Phát Lại",
|
||||
"HeaderPlaylist": "Danh Sách Phát",
|
||||
"HeaderPlaylistItems": "Các Mục Danh Sách Phát",
|
||||
"HeaderProgressSyncFailed": "Lỗi đồng bộ hoá",
|
||||
"HeaderRSSFeedGeneral": "Thông Tin Chi Tiết về RSS",
|
||||
"HeaderRSSFeedIsOpen": "RSS Feed đã được Mở",
|
||||
"HeaderSelectDownloadLocation": "Chọn Vị Trí Tải Xuống",
|
||||
@@ -78,16 +90,20 @@
|
||||
"HeaderStatsRecentSessions": "Các Phiên Gần Đây",
|
||||
"HeaderTableOfContents": "Mục Lục",
|
||||
"HeaderUserInterfaceSettings": "Cài Đặt Giao Diện Người Dùng",
|
||||
"HeaderYourStats": "Thống Kê của Bạn",
|
||||
"HeaderYourStats": "Thống Kê Của Bạn",
|
||||
"LabelAddToPlaylist": "Thêm vào Danh Sách Phát",
|
||||
"LabelAddedAt": "Đã Thêm Vào",
|
||||
"LabelAddedDate": "Đã thêm {0}",
|
||||
"LabelAll": "Tất Cả",
|
||||
"LabelAllowSeekingOnMediaControls": "Cho phép tìm kiếm vị trí trên các điều khiển phương tiện thông báo",
|
||||
"LabelAlways": "Luôn luôn",
|
||||
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Không dùng danh sách sổ xuống theo thứ tự chữ cái khi số mục hiển thị ít hơn số lượng này",
|
||||
"LabelAskConfirmation": "Yêu cầu xác nhận",
|
||||
"LabelAuthor": "Tác Giả",
|
||||
"LabelAuthorFirstLast": "Tác Giả (Tên Đầu Tiên, Họ)",
|
||||
"LabelAuthorLastFirst": "Tác Giả (Họ, Tên Đầu Tiên)",
|
||||
"LabelAuthors": "Tác Giả",
|
||||
"LabelAutoDownloadEpisodes": "Tự Động Tải Xuống Các Tập Phim",
|
||||
"LabelAutoDownloadEpisodes": "Tự Động Tải Xuống Các Tập",
|
||||
"LabelAutoRewindTime": "Thời Gian Tự Động Lùi",
|
||||
"LabelAutoSleepTimer": "Bộ Đếm Thời Gian Ngủ Tự Động",
|
||||
"LabelAutoSleepTimerAutoRewind": "Lùi Tự Động Bộ Đếm Thời Gian Ngủ",
|
||||
@@ -115,8 +131,10 @@
|
||||
"LabelDisableVibrateOnResetHelp": "Khi bộ đếm thời gian ngủ được đặt lại, thiết bị của bạn sẽ rung. Bật cài đặt này để không rung khi bộ đếm thời gian ngủ được đặt lại.",
|
||||
"LabelDiscover": "Khám Phá",
|
||||
"LabelDownload": "Tải Xuống",
|
||||
"LabelDownloadUsingCellular": "Tải xuống với dữ liệu di động",
|
||||
"LabelDownloaded": "Đã Tải Xuống",
|
||||
"LabelDuration": "Thời Lượng",
|
||||
"LabelEbooks": "",
|
||||
"LabelEnable": "Bật",
|
||||
"LabelEnableMp3IndexSeeking": "Bật Tìm Kiếm Chỉ Mục Mp3",
|
||||
"LabelEnableMp3IndexSeekingHelp": "Cài đặt này chỉ nên được bật nếu bạn có các tập tin mp3 không tìm kiếm đúng cách. Việc tìm kiếm không chính xác chủ yếu là do các tập tin MP3 biến đổi biến (VBR). Cài đặt này sẽ buộc tìm kiếm chỉ mục, trong đó một bản đồ thời gian-đến-byte được xây dựng khi tập tin được đọc. Trong một số trường hợp với các tập tin MP3 lớn, có thể có một sự trễ khi tìm kiếm về phía cuối của tập tin.",
|
||||
@@ -131,6 +149,7 @@
|
||||
"LabelFilename": "Tên Tập Tin",
|
||||
"LabelFinished": "Hoàn Thành",
|
||||
"LabelFolder": "Thư Mục",
|
||||
"LabelFontBoldness": "Chữ đậm",
|
||||
"LabelFontScale": "Tỷ Lệ Phông Chữ",
|
||||
"LabelGenre": "Thể Loại",
|
||||
"LabelGenres": "Thể Loại",
|
||||
@@ -145,6 +164,7 @@
|
||||
"LabelInternalAppStorage": "Bộ Nhớ Ứng Dụng Nội Bộ",
|
||||
"LabelJumpBackwardsTime": "Nhảy Lùi Thời Gian",
|
||||
"LabelJumpForwardsTime": "Nhảy Chuyển Tiếp Thời Gian",
|
||||
"LabelKeepScreenAwake": "Không khoá màn hình",
|
||||
"LabelLanguage": "Ngôn Ngữ",
|
||||
"LabelLayout": "Bố Cục",
|
||||
"LabelLayoutAuto": "Tự Động",
|
||||
@@ -159,27 +179,40 @@
|
||||
"LabelLow": "Thấp",
|
||||
"LabelMediaType": "Loại Phương Tiện",
|
||||
"LabelMedium": "Trung Bình",
|
||||
"LabelMissing": "",
|
||||
"LabelMore": "Thêm",
|
||||
"LabelMoreInfo": "Thông Tin Thêm",
|
||||
"LabelName": "Tên",
|
||||
"LabelNarrator": "Người Đọc",
|
||||
"LabelNarrators": "Người Đọc",
|
||||
"LabelNavigateWithVolume": "Điều hướng bằng nút âm thanh",
|
||||
"LabelNavigateWithVolumeWhilePlaying": "Cho phép sử dụng nút âm lượng để điều hướng",
|
||||
"LabelNavigateWithVolumeWhilePlayingDisabled": "Không cho phép sử dụng nút âm lượng để điều hướng trong lúc phát",
|
||||
"LabelNavigateWithVolumeWhilePlayingEnabled": "Cho phép sử dụng nút âm lượng để điều hướng trong lúc phát",
|
||||
"LabelNever": "Không bao giờ",
|
||||
"LabelNewestAuthors": "Tác Giả Mới Nhất",
|
||||
"LabelNewestEpisodes": "Các Tập Phim Mới Nhất",
|
||||
"LabelNo": "Không",
|
||||
"LabelNotFinished": "Chưa Hoàn Thành",
|
||||
"LabelNotStarted": "Chưa Bắt Đầu",
|
||||
"LabelNumEpisodes": "{0} tập",
|
||||
"LabelNumEpisodesIncomplete": "{0} tập, {1} chưa hoàn thành",
|
||||
"LabelNumberOfEpisodes": "Số lượng tập",
|
||||
"LabelOff": "Tắt",
|
||||
"LabelOn": "Bật",
|
||||
"LabelPassword": "Mật Khẩu",
|
||||
"LabelPath": "Đường Dẫn",
|
||||
"LabelPlaybackDirect": "Trực Tiếp",
|
||||
"LabelPlaybackLocal": "Địa Phương",
|
||||
"LabelPlaybackSpeed": "Tốc Độ Phát Lại",
|
||||
"LabelPlaybackTranscode": "Chuyển Đổi",
|
||||
"LabelPreventIndexing": "Ngăn Chặn Feed của bạn được lập chỉ mục bởi iTunes và Google podcast directories",
|
||||
"LabelPodcast": "Podcast",
|
||||
"LabelPodcasts": "Podcasts",
|
||||
"LabelPreventIndexing": "Ngăn Chặn Feed của bạn bị truy xuất bởi thư mục iTunes và Google podcast",
|
||||
"LabelProgress": "Tiến Độ",
|
||||
"LabelPubDate": "Ngày Xuất Bản",
|
||||
"LabelPublishYear": "Năm Xuất Bản",
|
||||
"LabelPublishedDate": "Xuất bản vào {0}",
|
||||
"LabelRSSFeedCustomOwnerEmail": "Email Chủ Sở Hữu Tùy Chỉnh",
|
||||
"LabelRSSFeedCustomOwnerName": "Tên Chủ Sở Hữu Tùy Chỉnh",
|
||||
"LabelRSSFeedPreventIndexing": "Ngăn Chặn Lập Chỉ Mục",
|
||||
|
||||
@@ -298,6 +298,7 @@
|
||||
"MessageDownloading": "下载中...",
|
||||
"MessageDownloadingEpisode": "正在下载剧集",
|
||||
"MessageEpisodesQueuedForDownload": "{0} 个剧集排队等待下载",
|
||||
"MessageFailedToRefreshToken": "无法刷新令牌,需要重新登录",
|
||||
"MessageFeedURLWillBe": "源 URL 将改为 {0}",
|
||||
"MessageFetching": "正在获取...",
|
||||
"MessageFollowTheProjectOnGithub": "在 GitHub 上关注此项目",
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
const defaultTheme = require('tailwindcss/defaultTheme')
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'components/**/*.vue',
|
||||
'layouts/**/*.vue',
|
||||
'pages/**/*.vue',
|
||||
'mixins/**/*.js',
|
||||
'plugins/**/*.js'
|
||||
],
|
||||
content: ['components/**/*.vue', 'layouts/**/*.vue', 'pages/**/*.vue', 'mixins/**/*.js', 'plugins/**/*.js'],
|
||||
theme: {
|
||||
extend: {
|
||||
screens: {
|
||||
'short': { 'raw': '(max-height: 500px)' }
|
||||
short: { raw: '(max-height: 500px)' }
|
||||
},
|
||||
colors: {
|
||||
bg: 'rgb(var(--color-bg) / <alpha-value>)',
|
||||
@@ -24,7 +18,7 @@ module.exports = {
|
||||
'bg-toggle': 'rgb(var(--color-bg-toggle) / <alpha-value>)',
|
||||
'bg-toggle-selected': 'rgb(var(--color-bg-toggle-selected) / <alpha-value>)',
|
||||
'track-cursor': 'rgb(var(--color-track-cursor) / <alpha-value>)',
|
||||
'track': 'rgb(var(--color-track) / <alpha-value>)',
|
||||
track: 'rgb(var(--color-track) / <alpha-value>)',
|
||||
'track-buffered': 'rgb(var(--color-track-buffered) / <alpha-value>)',
|
||||
accent: '#1ad691',
|
||||
error: '#FF5252',
|
||||
@@ -41,28 +35,29 @@ module.exports = {
|
||||
mono: ['Ubuntu Mono', ...defaultTheme.fontFamily.mono]
|
||||
},
|
||||
fontSize: {
|
||||
'1.5xl': '1.375rem',
|
||||
xxs: '0.625rem'
|
||||
},
|
||||
spacing: {
|
||||
'18': '4.5rem'
|
||||
18: '4.5rem'
|
||||
},
|
||||
height: {
|
||||
'18': '4.5rem'
|
||||
18: '4.5rem'
|
||||
},
|
||||
maxWidth: {
|
||||
'24': '6rem'
|
||||
24: '6rem'
|
||||
},
|
||||
minWidth: {
|
||||
'4': '1rem',
|
||||
'8': '2rem',
|
||||
'10': '2.5rem',
|
||||
'12': '3rem',
|
||||
'16': '4rem'
|
||||
4: '1rem',
|
||||
8: '2rem',
|
||||
10: '2.5rem',
|
||||
12: '3rem',
|
||||
16: '4rem'
|
||||
},
|
||||
minHeight: {
|
||||
'12': '3rem'
|
||||
12: '3rem'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [],
|
||||
plugins: []
|
||||
}
|
||||
|
||||