Add:Language code setting and translations #448

This commit is contained in:
advplyr
2023-12-03 17:37:01 -06:00
parent 88fbebe5c4
commit 6621f8b2ee
63 changed files with 12972 additions and 279 deletions
+6 -6
View File
@@ -1,14 +1,14 @@
<template>
<div class="w-full h-full p-4">
<ui-text-input-with-label :value="serverAddress" label="Host" disabled class="my-2" />
<ui-text-input-with-label :value="serverAddress" :label="$strings.LabelHost" disabled class="my-2" />
<ui-text-input-with-label :value="username" label="Username" disabled class="my-2" />
<ui-text-input-with-label :value="username" :label="$strings.LabelUsername" disabled class="my-2" />
<ui-btn color="primary flex items-center justify-between gap-2 ml-auto text-base mt-8" @click="logout">Switch Server/User<span class="material-icons" style="font-size: 1.1rem">logout</span></ui-btn>
<ui-btn color="primary flex items-center justify-between gap-2 ml-auto text-base mt-8" @click="logout">{{ $strings.ButtonSwitchServerUser }}<span class="material-icons" style="font-size: 1.1rem">logout</span></ui-btn>
<div class="flex justify-end items-center m-4 gap-3 right-0 bottom-0 absolute">
<p class="text-smtext-yellow-400 text-right">Report bugs, request features, provide feedback, and contribute on <a class="underline" href="https://github.com/advplyr/audiobookshelf-app" target="_blank">GitHub</a></p>
<a href="https://github.com/advplyr/audiobookshelf-app" target="_blank" class="text-white hover:text-gray-200 hover:scale-150 hover:rotate-6 transform duration-500">
<div class="flex justify-center items-center my-4 left-0 right-0 bottom-0 absolute">
<p class="text-sm text-white">{{ $strings.MessageReportBugsAndContribute }} <a class="underline" href="https://github.com/advplyr/audiobookshelf-app" target="_blank">GitHub</a></p>
<a href="https://github.com/advplyr/audiobookshelf-app" target="_blank" class="text-white hover:text-gray-200 hover:scale-150 hover:rotate-6 transform duration-500 ml-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="24" height="24" viewBox="0 0 24 24">
<path
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"
+15 -15
View File
@@ -2,41 +2,37 @@
<div class="w-full h-full min-h-full relative">
<div v-if="attemptingConnection" class="w-full pt-4 flex items-center justify-center">
<widgets-loading-spinner />
<p class="pl-4">Attempting server connection...</p>
<p class="pl-4">{{ $strings.MessageAttemptingServerConnection }}</p>
</div>
<div v-if="shelves.length && isLoading" class="w-full pt-4 flex items-center justify-center">
<widgets-loading-spinner />
<p class="pl-4">Loading server data...</p>
<p class="pl-4">{{ $strings.MessageLoadingServerData }}</p>
</div>
<div class="w-full" :class="{ 'py-6': altViewEnabled }">
<template v-for="(shelf, index) in shelves">
<bookshelf-shelf :key="shelf.id" :label="shelf.label" :entities="shelf.entities" :type="shelf.type" :style="{ zIndex: shelves.length - index }" />
<bookshelf-shelf :key="shelf.id" :label="getShelfLabel(shelf)" :entities="shelf.entities" :type="shelf.type" :style="{ zIndex: shelves.length - index }" />
</template>
</div>
<div v-if="!shelves.length && !isLoading" class="absolute top-0 left-0 w-full h-full flex items-center justify-center">
<div>
<p class="mb-4 text-center text-xl">
Bookshelf empty
<span v-show="user">
for library
<strong>{{ currentLibraryName }}</strong>
</span>
{{ $strings.MessageBookshelfEmpty }}
</p>
<div class="w-full" v-if="!user">
<div class="flex justify-center items-center mb-3">
<span class="material-icons text-error text-lg">cloud_off</span>
<p class="pl-2 text-error text-sm">Audiobookshelf server not connected.</p>
<p class="pl-2 text-error text-sm">{{ $strings.MessageAudiobookshelfServerNotConnected }}</p>
</div>
</div>
<div class="flex justify-center">
<ui-btn v-if="!user" small @click="$router.push('/connect')" class="w-32">Connect</ui-btn>
<ui-btn v-if="!user" small @click="$router.push('/connect')" class="w-32">{{ $strings.ButtonConnect }}</ui-btn>
</div>
</div>
</div>
<div v-else-if="!shelves.length && isLoading && !attemptingConnection" class="absolute top-0 left-0 z-50 w-full h-full flex items-center justify-center">
<ui-loading-indicator text="Loading..." />
<ui-loading-indicator :text="$strings.MessageLoading" />
</div>
</div>
</template>
@@ -113,6 +109,10 @@ export default {
}
},
methods: {
getShelfLabel(shelf) {
if (shelf.labelStringKey && this.$strings[shelf.labelStringKey]) return this.$strings[shelf.labelStringKey]
return shelf.label
},
getLocalMediaItemCategories() {
const localMedia = this.localLibraryItems
if (!localMedia?.length) return []
@@ -147,7 +147,7 @@ export default {
if (booksContinueListening.length) {
categories.push({
id: 'local-books-continue',
label: 'Continue Books',
label: this.$strings.LabelContinueBooks,
type: 'book',
localOnly: true,
entities: booksContinueListening.sort((a, b) => {
@@ -161,7 +161,7 @@ export default {
if (podcastEpisodesContinueListening.length) {
categories.push({
id: 'local-episodes-continue',
label: 'Continue Episodes',
label: this.$strings.LabelContinueEpisodes,
type: 'episode',
localOnly: true,
entities: podcastEpisodesContinueListening.sort((a, b) => {
@@ -177,7 +177,7 @@ export default {
if (books.length) {
categories.push({
id: 'local-books',
label: 'Local Books',
label: this.$strings.LabelLocalBooks,
type: 'book',
entities: books.sort((a, b) => {
if (a.progress && a.progress.isFinished) return 1
@@ -192,7 +192,7 @@ export default {
if (podcasts.length) {
categories.push({
id: 'local-podcasts',
label: 'Local Podcasts',
label: this.$strings.LabelLocalPodcasts,
type: 'podcast',
entities: podcasts
})
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<div class="w-full p-4">
<h1 class="text-xl mb-2 font-semibold">Latest Episodes</h1>
<h1 class="text-xl mb-2 font-semibold">{{ $strings.HeaderLatestEpisodes }}</h1>
<template v-for="episode in recentEpisodes">
<tables-podcast-latest-episode-row :episode="episode" :local-episode="localEpisodeMap[episode.id]" :library-item-id="episode.libraryItemId" :local-library-item-id="null" :is-local="isLocal" :key="episode.id" @addToPlaylist="addEpisodeToPlaylist" />
+4 -4
View File
@@ -8,10 +8,10 @@
</div>
<div v-if="!networkConnected" class="w-full text-center py-6">
<p class="text-lg text-error">No network connection</p>
<p class="text-lg text-error">{{ $strings.MessageNoNetworkConnection }}</p>
</div>
<div v-else class="w-full mx-auto pb-2 overflow-y-auto overflow-x-hidden h-[calc(100%-85px)]">
<p v-if="termSearched && !results.length && !processing" class="text-center text-xl">No Podcasts Found</p>
<p v-if="termSearched && !results.length && !processing" class="text-center text-xl">{{ $strings.MessageNoPodcastsFound }}</p>
<template v-for="podcast in results">
<div :key="podcast.id" class="p-2 border-b border-white border-opacity-10" @click="selectPodcast(podcast)">
<div class="flex">
@@ -22,7 +22,7 @@
</div>
<div class="flex-grow pl-2">
<p class="text-xs text-gray-100 whitespace-nowrap truncate">{{ podcast.artistName }}</p>
<p class="text-xxs text-gray-300 leading-5">{{ podcast.trackCount }} Episodes</p>
<p class="text-xxs text-gray-300 leading-5">{{ podcast.trackCount }} {{ $strings.HeaderEpisodes }}</p>
</div>
</div>
@@ -36,7 +36,7 @@
<div class="flex items-center px-2 h-16">
<div class="flex items-center" @click="clearSelected">
<span class="material-icons text-2xl text-gray-300">arrow_back</span>
<p class="pl-2 uppercase text-sm font-semibold text-gray-300 leading-4 pb-px">Back</p>
<p class="pl-2 uppercase text-sm font-semibold text-gray-300 leading-4 pb-px">{{ $strings.ButtonBack }}</p>
</div>
</div>
+11 -11
View File
@@ -22,16 +22,16 @@
<!-- user progress card -->
<div v-if="progressPercent > 0" class="px-4 py-2 bg-primary text-sm font-semibold rounded-md text-gray-200 mt-4 relative" :class="resettingProgress ? 'opacity-25' : ''">
<p class="leading-6">Your Progress: {{ Math.round(progressPercent * 100) }}%</p>
<p v-if="progressPercent < 1" class="text-gray-400 text-xs">{{ $elapsedPretty(userTimeRemaining) }} remaining</p>
<p v-else class="text-gray-400 text-xs">Finished {{ $formatDate(userProgressFinishedAt) }}</p>
<p class="leading-6">{{ $strings.LabelYourProgress }}: {{ Math.round(progressPercent * 100) }}%</p>
<p v-if="progressPercent < 1" class="text-gray-400 text-xs">{{ $getString('LabelTimeRemaining', [$elapsedPretty(userTimeRemaining)]) }}</p>
<p v-else class="text-gray-400 text-xs">{{ $strings.LabelFinished }} {{ $formatDate(userProgressFinishedAt) }}</p>
</div>
<!-- action buttons -->
<div class="flex mt-4 -mx-1">
<ui-btn color="success" class="flex items-center justify-center flex-grow mx-1" :padding-x="4" @click="playClick">
<span class="material-icons">{{ playerIsPlaying ? 'pause' : 'play_arrow' }}</span>
<span class="px-1 text-sm">{{ playerIsPlaying ? 'Pause' : localEpisodeId ? 'Play' : 'Stream' }}</span>
<span class="px-1 text-sm">{{ playerIsPlaying ? $strings.ButtonPause : localEpisodeId ? $strings.ButtonPlay : $strings.ButtonStream }}</span>
</ui-btn>
<ui-btn v-if="showDownload" :color="downloadItem ? 'warning' : 'primary'" class="flex items-center justify-center mx-1" :padding-x="2" @click="downloadClick">
<span class="material-icons" :class="downloadItem ? 'animate-pulse' : ''">{{ downloadItem ? 'downloading' : 'download' }}</span>
@@ -257,7 +257,7 @@ export default {
if (!this.userIsFinished) {
items.push({
text: 'Mark as Finished',
text: this.$strings.MessageMarkAsFinished,
value: 'markFinished',
icon: 'beenhere'
})
@@ -265,7 +265,7 @@ export default {
if (this.progressPercent > 0) {
items.push({
text: 'Discard Progress',
text: this.$strings.MessageDiscardProgress,
value: 'discardProgress',
icon: 'backspace'
})
@@ -273,7 +273,7 @@ export default {
if (!this.isLocal) {
items.push({
text: 'Add to Playlist',
text: this.$strings.LabelAddToPlaylist,
value: 'playlist',
icon: 'playlist_add'
})
@@ -281,7 +281,7 @@ export default {
if (this.localEpisodeId) {
items.push({
text: 'Delete Local Episode',
text: this.$strings.ButtonDeleteLocalEpisode,
value: 'deleteLocal',
icon: 'delete'
})
@@ -289,7 +289,7 @@ export default {
if (this.isAdminOrUp && this.serverEpisodeId) {
items.push({
text: 'Remove from Server',
text: this.$strings.ButtonRemoveFromServer,
value: 'remove_from_server',
icon: 'delete_forever'
})
@@ -453,7 +453,7 @@ export default {
const { value } = await Dialog.confirm({
title: 'Confirm',
message: 'Are you sure you want to reset your progress?'
message: this.$strings.MessageConfirmDiscardProgress
})
if (value) {
this.resettingProgress = true
@@ -505,7 +505,7 @@ export default {
}
this.$nativeHttp.patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload).catch((error) => {
console.error('Failed', error)
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
this.$toast.error(updatePayload.isFinished ? this.$strings.ToastItemMarkedAsFinishedFailed : this.$strings.ToastItemMarkedAsNotFinishedFailed)
})
}
},
+14 -14
View File
@@ -53,11 +53,11 @@
<div v-if="showPlay || showRead" class="flex mt-4 -mx-1">
<ui-btn v-if="showPlay" color="success" class="flex items-center justify-center flex-grow mx-1" :padding-x="4" @click="playClick">
<span class="material-icons">{{ playerIsPlaying ? 'pause' : 'play_arrow' }}</span>
<span class="px-1 text-sm">{{ playerIsPlaying ? 'Pause' : isPodcast ? 'Next Episode' : hasLocal ? 'Play' : 'Stream' }}</span>
<span class="px-1 text-sm">{{ playerIsPlaying ? $strings.ButtonPause : isPodcast ? $strings.ButtonNextEpisode : hasLocal ? $strings.ButtonPlay : $strings.ButtonStream }}</span>
</ui-btn>
<ui-btn v-if="showRead" color="info" class="flex items-center justify-center mx-1" :class="showPlay ? '' : 'flex-grow'" :padding-x="2" @click="readBook">
<span class="material-icons">auto_stories</span>
<span v-if="!showPlay" class="px-2 text-base">Read {{ ebookFormat }}</span>
<span v-if="!showPlay" class="px-2 text-base">{{ $strings.ButtonRead }} {{ ebookFormat }}</span>
</ui-btn>
<ui-btn v-if="showDownload" :color="downloadItem ? 'warning' : 'primary'" class="flex items-center justify-center mx-1" :padding-x="2" @click="downloadClick">
<span class="material-icons" :class="downloadItem ? 'animate-pulse' : ''">{{ downloadItem ? 'downloading' : 'download' }}</span>
@@ -68,20 +68,20 @@
</div>
<div v-if="!isPodcast && progressPercent > 0" class="px-4 py-2 bg-primary text-sm font-semibold rounded-md text-gray-200 mt-4 text-center">
<p>Your Progress: {{ Math.round(progressPercent * 100) }}%</p>
<p v-if="!useEBookProgress && !userIsFinished" class="text-gray-400 text-xs">{{ $elapsedPretty(userTimeRemaining) }} remaining</p>
<p v-else-if="userIsFinished" class="text-gray-400 text-xs">Finished {{ $formatDate(userProgressFinishedAt) }}</p>
<p>{{ $strings.LabelYourProgress }}: {{ Math.round(progressPercent * 100) }}%</p>
<p v-if="!useEBookProgress && !userIsFinished" class="text-gray-400 text-xs">{{ $getString('LabelTimeRemaining', [$elapsedPretty(userTimeRemaining)]) }}</p>
<p v-else-if="userIsFinished" class="text-gray-400 text-xs">{{ $strings.LabelFinished }} {{ $formatDate(userProgressFinishedAt) }}</p>
</div>
</div>
<div v-if="downloadItem" class="py-3">
<p v-if="downloadItem.itemProgress == 1" class="text-center text-lg">Download complete. Processing...</p>
<p v-else class="text-center text-lg">Downloading! ({{ Math.round(downloadItem.itemProgress * 100) }}%)</p>
<p v-if="downloadItem.itemProgress == 1" class="text-center text-lg">{{ $strings.MessageDownloadCompleteProcessing }}</p>
<p v-else class="text-center text-lg">{{ $strings.MessageDownloading }} ({{ Math.round(downloadItem.itemProgress * 100) }}%)</p>
</div>
<!-- metadata -->
<div id="metadata" class="grid gap-2 my-2" style>
<div v-if="podcastAuthor || (bookAuthors && bookAuthors.length)" class="text-white text-opacity-60 uppercase text-sm">Author</div>
<div v-if="podcastAuthor || (bookAuthors && bookAuthors.length)" class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelAuthor }}</div>
<div v-if="podcastAuthor" class="text-sm">{{ podcastAuthor }}</div>
<div v-else-if="bookAuthors && bookAuthors.length" class="text-sm">
<template v-for="(author, index) in bookAuthors">
@@ -90,10 +90,10 @@
</template>
</div>
<div v-if="podcastType" class="text-white text-opacity-60 uppercase text-sm">Type</div>
<div v-if="podcastType" class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelType }}</div>
<div v-if="podcastType" class="text-sm capitalize">{{ podcastType }}</div>
<div v-if="series && series.length" class="text-white text-opacity-60 uppercase text-sm">Series</div>
<div v-if="series && series.length" class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelSeries }}</div>
<div v-if="series && series.length" class="truncate text-sm">
<template v-for="(series, index) in seriesList">
<nuxt-link :key="series.id" :to="`/bookshelf/series/${series.id}`" class="underline">{{ series.text }}</nuxt-link
@@ -101,10 +101,10 @@
</template>
</div>
<div v-if="numTracks" class="text-white text-opacity-60 uppercase text-sm">Duration</div>
<div v-if="numTracks" class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelDuration }}</div>
<div v-if="numTracks" class="text-sm">{{ $elapsedPretty(duration) }}</div>
<div v-if="narrators && narrators.length" class="text-white text-opacity-60 uppercase text-sm">{{ narrators.length === 1 ? 'Narrator' : 'Narrators' }}</div>
<div v-if="narrators && narrators.length" class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelNarrators }}</div>
<div v-if="narrators && narrators.length" class="truncate text-sm">
<template v-for="(narrator, index) in narrators">
<nuxt-link :key="narrator" :to="`/bookshelf/library?filter=narrators.${$encode(narrator)}`" class="underline">{{ narrator }}</nuxt-link
@@ -112,7 +112,7 @@
</template>
</div>
<div v-if="genres.length" class="text-white text-opacity-60 uppercase text-sm">{{ genres.length === 1 ? 'Genre' : 'Genres' }}</div>
<div v-if="genres.length" class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelGenres }}</div>
<div v-if="genres.length" class="truncate text-sm">
<template v-for="(genre, index) in genres">
<nuxt-link :key="genre" :to="`/bookshelf/library?filter=genres.${$encode(genre)}`" class="underline">{{ genre }}</nuxt-link
@@ -120,7 +120,7 @@
</template>
</div>
<div v-if="publishedYear" class="text-white text-opacity-60 uppercase text-sm">Published</div>
<div v-if="publishedYear" class="text-white text-opacity-60 uppercase text-sm">{{ $strings.LabelPublishYear }}</div>
<div v-if="publishedYear" class="text-sm">{{ publishedYear }}</div>
</div>
+7 -7
View File
@@ -1,15 +1,15 @@
<template>
<div class="w-full h-full py-6 px-4">
<div class="flex items-center mb-2">
<p class="text-base font-semibold">Folder: {{ folderName }}</p>
<p class="text-base font-semibold">{{ $strings.LabelFolder }}: {{ folderName }}</p>
<div class="flex-grow" />
<span v-if="dialogItems.length" class="material-icons" @click="showDialog = true">more_vert</span>
</div>
<p class="text-sm mb-4 text-white text-opacity-60">Media Type: {{ mediaType }}</p>
<p class="text-sm mb-4 text-white text-opacity-60">{{ $strings.LabelMediaType }}: {{ mediaType }}</p>
<p class="mb-2 text-base text-white">Local Library Items ({{ localLibraryItems.length }})</p>
<p class="mb-2 text-base text-white">{{ $strings.HeaderLocalLibraryItems }} ({{ localLibraryItems.length }})</p>
<div class="w-full media-item-container overflow-y-auto">
<template v-for="localLibraryItem in localLibraryItems">
@@ -65,7 +65,7 @@ export default {
if (this.isInternalStorage) return []
const items = []
items.push({
text: 'Remove',
text: this.$strings.ButtonRemove,
value: 'remove'
})
return items
@@ -77,14 +77,14 @@ export default {
if (localLibraryItem.mediaType == 'book') {
const txts = []
if (localLibraryItem.media.ebookFile) {
txts.push(`${localLibraryItem.media.ebookFile.ebookFormat} EBook`)
txts.push(`${localLibraryItem.media.ebookFile.ebookFormat} ${this.$strings.LabelEbook}`)
}
if (localLibraryItem.media.tracks?.length) {
txts.push(`${localLibraryItem.media.tracks.length} Tracks`)
txts.push(`${localLibraryItem.media.tracks.length} ${this.$strings.LabelTracks}`)
}
return txts.join(' • ')
} else {
return `${localLibraryItem.media.episodes?.length || 0} Episodes`
return `${localLibraryItem.media.episodes?.length || 0} ${this.$strings.HeaderEpisodes}`
}
},
dialogAction(action) {
+7 -7
View File
@@ -1,6 +1,6 @@
<template>
<div class="w-full h-full py-6">
<h1 class="text-base font-semibold px-2 mb-2">Local Folders</h1>
<h1 class="text-base font-semibold px-2 mb-2">{{ $strings.HeaderLocalFolders }}</h1>
<div v-if="!isIos" class="w-full max-w-full px-2 py-2">
<template v-for="folder in localFolders">
@@ -13,19 +13,19 @@
</nuxt-link>
</template>
<div v-if="!localFolders.length" class="flex justify-center">
<p class="text-center">No Media Folders</p>
<p class="text-center">{{ $strings.MessageNoMediaFolders }}</p>
</div>
<div v-if="!isAndroid10OrBelow || overrideFolderRestriction" class="flex border-t border-white border-opacity-10 my-4 py-4">
<div class="flex-grow pr-1">
<ui-dropdown v-model="newFolderMediaType" placeholder="Select media type" :items="mediaTypeItems" />
</div>
<ui-btn small class="w-28" color="success" @click="selectFolder">New Folder</ui-btn>
<ui-btn small class="w-28" color="success" @click="selectFolder">{{ $strings.ButtonNewFolder }}</ui-btn>
</div>
<div v-else class="flex border-t border-white border-opacity-10 my-4 py-4">
<div class="flex-grow pr-1">
<p class="text-sm">Android 10 and below will use internal app storage for downloads.</p>
<p class="text-sm">{{ $strings.MessageAndroid10Downloads }}</p>
</div>
<ui-btn small class="w-28" color="primary" @click="overrideFolderRestriction = true">Override</ui-btn>
<ui-btn small class="w-28" color="primary" @click="overrideFolderRestriction = true">{{ $strings.ButtonOverride }}</ui-btn>
</div>
</div>
</div>
@@ -43,11 +43,11 @@ export default {
mediaTypeItems: [
{
value: 'book',
text: 'Books'
text: this.$strings.LabelBooks
},
{
value: 'podcast',
text: 'Podcasts'
text: this.$strings.LabelPodcasts
}
],
syncing: false,
+5 -5
View File
@@ -11,7 +11,7 @@
<span class="material-icons" @click="showItemDialog">more_vert</span>
</div>
<p v-if="!isIos" class="px-2 text-sm mb-0.5 text-white text-opacity-75">Folder: {{ folderName }}</p>
<p v-if="!isIos" class="px-2 text-sm mb-0.5 text-white text-opacity-75">{{ $strings.LabelFolder }}: {{ folderName }}</p>
<p class="px-2 mb-4 text-xs text-gray-400">{{ libraryItemId ? 'Linked to item on server ' + liServerAddress : 'Not linked to server item' }}</p>
@@ -107,7 +107,7 @@
<div v-if="orderChanged" class="fixed left-0 w-full py-4 px-4 bg-bg box-shadow-book flex items-center" :style="{ bottom: isPlayerOpen ? '120px' : '0px' }">
<div class="flex-grow" />
<ui-btn small color="success" @click="saveTrackOrder">Save Order</ui-btn>
<ui-btn small color="success" @click="saveTrackOrder">{{ $strings.ButtonSaveOrder }}</ui-btn>
</div>
<modals-dialog v-model="showDialog" :items="dialogItems" @action="dialogAction" />
@@ -220,14 +220,14 @@ export default {
if (this.selectedAudioTrack || this.selectedEpisode) {
return [
{
text: 'Delete local file',
text: this.$strings.ButtonDeleteLocalFile,
value: 'track-delete'
}
]
} else {
return [
{
text: 'Delete local item',
text: this.$strings.ButtonDeleteLocalItem,
value: 'delete'
}
]
@@ -259,7 +259,7 @@ export default {
this.localLibraryItem = response
this.audioTracksCopy = this.audioTracks.map((at) => ({ ...at }))
} else {
this.$toast.info(`No updates necessary`)
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
}
this.orderChanged = false
},
+9 -9
View File
@@ -1,16 +1,16 @@
<template>
<div class="w-full h-full">
<div class="px-4 py-6">
<ui-text-input ref="input" v-model="search" @input="updateSearch" borderless placeholder="Search" bg="white bg-opacity-10" rounded="md" prepend-icon="search" text-size="base" clearable class="w-full text-lg" />
<ui-text-input ref="input" v-model="search" @input="updateSearch" borderless :placeholder="$strings.ButtonSearch" bg="white bg-opacity-10" rounded="md" prepend-icon="search" text-size="base" clearable class="w-full text-lg" />
</div>
<div class="w-full overflow-x-hidden overflow-y-auto search-content px-4" @click.stop>
<div v-show="isFetching" class="w-full py-8 flex justify-center">
<p class="text-lg text-gray-400">Fetching...</p>
<p class="text-lg text-gray-400">{{ $strings.MessageFetching }}</p>
</div>
<div v-if="!isFetching && lastSearch && !totalResults" class="w-full py-8 flex justify-center">
<p class="text-lg text-gray-400">Nothing found</p>
<p class="text-lg text-gray-400">{{ $strings.MessageNoItemsFound }}</p>
</div>
<p v-if="bookResults.length" class="font-semibold text-sm mb-1">Books</p>
<p v-if="bookResults.length" class="font-semibold text-sm mb-1">{{ $strings.LabelBooks }}</p>
<template v-for="item in bookResults">
<div :key="item.libraryItem.id" class="w-full h-16 py-1">
<nuxt-link :to="`/item/${item.libraryItem.id}`">
@@ -19,7 +19,7 @@
</div>
</template>
<p v-if="podcastResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">Podcasts</p>
<p v-if="podcastResults.length" class="uppercase text-xs text-gray-400 my-1 px-1 font-semibold">{{ $strings.LabelPodcasts }}</p>
<template v-for="item in podcastResults">
<div :key="item.libraryItem.id" class="text-gray-50 select-none relative cursor-pointer hover:bg-black-400 py-1">
<nuxt-link :to="`/item/${item.libraryItem.id}`">
@@ -28,7 +28,7 @@
</div>
</template>
<p v-if="seriesResults.length" class="font-semibold text-sm mb-1 mt-2">Series</p>
<p v-if="seriesResults.length" class="font-semibold text-sm mb-1 mt-2">{{ $strings.LabelSeries }}</p>
<template v-for="seriesResult in seriesResults">
<div :key="seriesResult.series.id" class="w-full h-16 py-1">
<nuxt-link :to="`/bookshelf/series/${seriesResult.series.id}`">
@@ -37,7 +37,7 @@
</div>
</template>
<p v-if="authorResults.length" class="font-semibold text-sm mb-1 mt-2">Authors</p>
<p v-if="authorResults.length" class="font-semibold text-sm mb-1 mt-2">{{ $strings.LabelAuthors }}</p>
<template v-for="authorResult in authorResults">
<div :key="authorResult.id" class="w-full h-14 py-1">
<nuxt-link :to="`/bookshelf/library?filter=authors.${$encode(authorResult.id)}`">
@@ -46,7 +46,7 @@
</div>
</template>
<p v-if="narratorResults.length" class="font-semibold text-sm mb-1 mt-2">Narrators</p>
<p v-if="narratorResults.length" class="font-semibold text-sm mb-1 mt-2">{{ $strings.LabelNarrators }}</p>
<template v-for="narrator in narratorResults">
<div :key="narrator.name" class="w-full h-14 py-1">
<nuxt-link :to="`/bookshelf/library?filter=narrators.${$encode(narrator.name)}`">
@@ -55,7 +55,7 @@
</div>
</template>
<p v-if="tagResults.length" class="font-semibold text-sm mb-1 mt-2">Tags</p>
<p v-if="tagResults.length" class="font-semibold text-sm mb-1 mt-2">{{ $strings.LabelTags }}</p>
<template v-for="tag in tagResults">
<div :key="tag.name" class="w-full h-14 py-1">
<nuxt-link :to="`/bookshelf/library?filter=tags.${$encode(tag.name)}`">
+73 -46
View File
@@ -1,67 +1,73 @@
<template>
<div class="w-full h-full px-8 py-8 overflow-y-auto">
<div class="w-full h-full px-4 py-8 overflow-y-auto">
<!-- Display settings -->
<p class="uppercase text-xs font-semibold text-gray-300 mb-2">User Interface Settings</p>
<p class="uppercase text-xs font-semibold text-gray-300 mb-2">{{ $strings.HeaderUserInterfaceSettings }}</p>
<div class="flex items-center py-3" @click="toggleEnableAltView">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="enableBookshelfView" @input="saveSettings" />
</div>
<p class="pl-4">Use bookshelf view</p>
<p class="pl-4">{{ $strings.LabelUseBookshelfView }}</p>
</div>
<!-- screen.orientation.lock not supported on iOS webview -->
<div v-if="!isiOS" class="flex items-center py-3" @click.stop="toggleLockOrientation">
<div class="w-10 flex justify-center pointer-events-none">
<ui-toggle-switch v-model="lockCurrentOrientation" />
</div>
<p class="pl-4">Lock orientation</p>
<p class="pl-4">{{ $strings.LabelLockOrientation }}</p>
</div>
<div class="py-3 flex items-center">
<p class="pr-4 w-36">Haptic feedback</p>
<p class="pr-4 w-36">{{ $strings.LabelHapticFeedback }}</p>
<div @click.stop="showHapticFeedbackOptions">
<ui-text-input :value="hapticFeedbackOption" readonly append-icon="expand_more" style="max-width: 145px" />
</div>
</div>
<div class="py-3 flex items-center">
<p class="pr-4 w-36">{{ $strings.LabelLanguage }}</p>
<div @click.stop="showLanguageOptions">
<ui-text-input :value="languageOption" readonly append-icon="expand_more" style="max-width: 225px" />
</div>
</div>
<!-- Playback settings -->
<p class="uppercase text-xs font-semibold text-gray-300 mb-2 mt-10">Playback Settings</p>
<p class="uppercase text-xs font-semibold text-gray-300 mb-2 mt-10">{{ $strings.HeaderPlaybackSettings }}</p>
<div v-if="!isiOS" class="flex items-center py-3" @click="toggleDisableAutoRewind">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.disableAutoRewind" @input="saveSettings" />
</div>
<p class="pl-4">Disable auto rewind</p>
<p class="pl-4">{{ $strings.LabelDisableAutoRewind }}</p>
</div>
<div class="flex items-center py-3" @click="toggleJumpBackwards">
<div class="w-10 flex justify-center">
<span class="material-icons text-4xl">{{ currentJumpBackwardsTimeIcon }}</span>
</div>
<p class="pl-4">Jump backwards time</p>
<p class="pl-4">{{ $strings.LabelJumpBackwardsTime }}</p>
</div>
<div class="flex items-center py-3" @click="toggleJumpForward">
<div class="w-10 flex justify-center">
<span class="material-icons text-4xl">{{ currentJumpForwardTimeIcon }}</span>
</div>
<p class="pl-4">Jump forwards time</p>
<p class="pl-4">{{ $strings.LabelJumpForwardsTime }}</p>
</div>
<div v-if="!isiOS" class="flex items-center py-3" @click="toggleEnableMp3IndexSeeking">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.enableMp3IndexSeeking" @input="saveSettings" />
</div>
<p class="pl-4">Enable mp3 index seeking</p>
<p class="pl-4">{{ $strings.LabelEnableMp3IndexSeeking }}</p>
<span class="material-icons-outlined ml-2" @click.stop="showConfirmMp3IndexSeeking">info</span>
</div>
<!-- Sleep timer settings -->
<template v-if="!isiOS">
<p class="uppercase text-xs font-semibold text-gray-300 mb-2 mt-10">Sleep Timer Settings</p>
<p class="uppercase text-xs font-semibold text-gray-300 mb-2 mt-10">{{ $strings.HeaderSleepTimerSettings }}</p>
<div class="flex items-center py-3" @click="toggleDisableShakeToResetSleepTimer">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.disableShakeToResetSleepTimer" @input="saveSettings" />
</div>
<p class="pl-4">Disable shake to reset</p>
<p class="pl-4">{{ $strings.LabelDisableShakeToReset }}</p>
<span class="material-icons-outlined ml-2" @click.stop="showInfo('disableShakeToResetSleepTimer')">info</span>
</div>
<div v-if="!settings.disableShakeToResetSleepTimer" class="py-3 flex items-center">
<p class="pr-4 w-36">Shake Sensitivity</p>
<p class="pr-4 w-36">{{ $strings.LabelShakeSensitivity }}</p>
<div @click.stop="showShakeSensitivityOptions">
<ui-text-input :value="shakeSensitivityOption" readonly append-icon="expand_more" style="width: 145px; max-width: 145px" />
</div>
@@ -70,35 +76,35 @@
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.disableSleepTimerFadeOut" @input="saveSettings" />
</div>
<p class="pl-4">Disable audio fade out</p>
<p class="pl-4">{{ $strings.LabelDisableAudioFadeOut }}</p>
<span class="material-icons-outlined ml-2" @click.stop="showInfo('disableSleepTimerFadeOut')">info</span>
</div>
<div class="flex items-center py-3" @click="toggleDisableSleepTimerResetFeedback">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.disableSleepTimerResetFeedback" @input="saveSettings" />
</div>
<p class="pl-4">Disable vibrate on reset</p>
<p class="pl-4">{{ $strings.LabelDisableVibrateOnReset }}</p>
<span class="material-icons-outlined ml-2" @click.stop="showInfo('disableSleepTimerResetFeedback')">info</span>
</div>
<div class="flex items-center py-3" @click="toggleAutoSleepTimer">
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.autoSleepTimer" @input="saveSettings" />
</div>
<p class="pl-4">Auto Sleep Timer</p>
<p class="pl-4">{{ $strings.LabelAutoSleepTimer }}</p>
<span class="material-icons-outlined ml-2" @click.stop="showInfo('autoSleepTimer')">info</span>
</div>
</template>
<!-- Auto Sleep timer settings -->
<div v-if="settings.autoSleepTimer" class="py-3 flex items-center">
<p class="pr-4 w-36">Start Time</p>
<p class="pr-4 w-36">{{ $strings.LabelStartTime }}</p>
<ui-text-input type="time" v-model="settings.autoSleepTimerStartTime" style="width: 145px; max-width: 145px" @input="autoSleepTimerTimeUpdated" />
</div>
<div v-if="settings.autoSleepTimer" class="py-3 flex items-center">
<p class="pr-4 w-36">End Time</p>
<p class="pr-4 w-36">{{ $strings.LabelEndTime }}</p>
<ui-text-input type="time" v-model="settings.autoSleepTimerEndTime" style="width: 145px; max-width: 145px" @input="autoSleepTimerTimeUpdated" />
</div>
<div v-if="settings.autoSleepTimer" class="py-3 flex items-center">
<p class="pr-4 w-36">Sleep Timer</p>
<p class="pr-4 w-36">{{ $strings.LabelSleepTimer }}</p>
<div @click.stop="showSleepTimerOptions">
<ui-text-input :value="sleepTimerLengthOption" readonly append-icon="expand_more" style="width: 145px; max-width: 145px" />
</div>
@@ -107,11 +113,11 @@
<div class="w-10 flex justify-center">
<ui-toggle-switch v-model="settings.autoSleepTimerAutoRewind" @input="saveSettings" />
</div>
<p class="pl-4">Auto Sleep Timer Auto Rewind</p>
<p class="pl-4">{{ $strings.LabelAutoSleepTimerAutoRewind }}</p>
<span class="material-icons-outlined ml-2" @click.stop="showInfo('autoSleepTimerAutoRewind')">info</span>
</div>
<div v-if="settings.autoSleepTimerAutoRewind" class="py-3 flex items-center">
<p class="pr-4 w-36">Auto Rewind Time</p>
<p class="pr-4 w-36">{{ $strings.LabelAutoRewindTime }}</p>
<div @click.stop="showAutoSleepTimerRewindOptions">
<ui-text-input :value="autoSleepTimerRewindLengthOption" readonly append-icon="expand_more" style="width: 145px; max-width: 145px" />
</div>
@@ -151,72 +157,73 @@ export default {
disableSleepTimerFadeOut: false,
disableSleepTimerResetFeedback: false,
autoSleepTimerAutoRewind: false,
autoSleepTimerAutoRewindTime: 300000 // 5 minutes
autoSleepTimerAutoRewindTime: 300000, // 5 minutes
languageCode: 'en-us'
},
lockCurrentOrientation: false,
settingInfo: {
disableShakeToResetSleepTimer: {
name: 'Disable shake to reset sleep timer',
message: 'Shaking your device while the timer is running OR within 2 minutes of the timer expiring will reset the sleep timer. Enable this setting to disable shake to reset.'
name: this.$strings.LabelDisableShakeToReset,
message: this.$strings.LabelDisableShakeToResetHelp
},
autoSleepTimer: {
name: 'Auto Sleep Timer',
message: 'When playing media between the specified start and end times a sleep timer will automatically start.'
name: this.$strings.LabelAutoSleepTimer,
message: this.$strings.LabelAutoSleepTimerHelp
},
disableSleepTimerFadeOut: {
name: 'Disable audio fade out',
message: 'Audio volume will start decreasing when there is less than 1 minute remaining on the sleep timer. Enable this setting to not fade out.'
name: this.$strings.LabelDisableAudioFadeOut,
message: this.$strings.LabelDisableAudioFadeOutHelp
},
disableSleepTimerResetFeedback: {
name: 'Disable vibrate on reset',
message: 'When the sleep timer gets reset your device will vibrate. Enable this setting to not vibrate when the sleep timer resets.'
name: this.$strings.LabelDisableVibrateOnReset,
message: this.$strings.LabelDisableVibrateOnResetHelp
},
autoSleepTimerAutoRewind: {
name: 'Enable sleep timer auto rewind',
message: 'When the auto sleep timer finishes, playing the item again will automatically rewind your position.'
name: this.$strings.LabelAutoSleepTimerAutoRewind,
message: this.$strings.LabelAutoSleepTimerAutoRewindHelp
},
enableMp3IndexSeeking: {
name: 'Enable mp3 index seeking',
message: 'This setting should only be enabled if you have mp3 files that are not seeking correctly. Inaccurate seeking is most likely due to Variable birate (VBR) MP3 files. This setting will force index seeking, in which a time-to-byte mapping is built as the file is read. In some cases with large MP3 files there will be a delay when seeking towards the end of the file.'
name: this.$strings.LabelEnableMp3IndexSeeking,
message: this.$strings.LabelEnableMp3IndexSeekingHelp
}
},
hapticFeedbackItems: [
{
text: 'Off',
text: this.$strings.LabelOff,
value: 'OFF'
},
{
text: 'Light',
text: this.$strings.LabelLight,
value: 'LIGHT'
},
{
text: 'Medium',
text: this.$strings.LabelMedium,
value: 'MEDIUM'
},
{
text: 'Heavy',
text: this.$strings.LabelHeavy,
value: 'HEAVY'
}
],
shakeSensitivityItems: [
{
text: 'Very Low',
text: this.$strings.LabelVeryLow,
value: 'VERY_LOW'
},
{
text: 'Low',
text: this.$strings.LabelLow,
value: 'LOW'
},
{
text: 'Medium',
text: this.$strings.LabelMedium,
value: 'MEDIUM'
},
{
text: 'High',
text: this.$strings.LabelHigh,
value: 'HIGH'
},
{
text: 'Very High',
text: this.$strings.LabelVeryHigh,
value: 'VERY_HIGH'
}
]
@@ -241,6 +248,9 @@ export default {
jumpBackwardsItems() {
return this.$store.state.globals.jumpBackwardsItems || []
},
languageOptionItems() {
return this.$languageCodeOptions || []
},
currentJumpForwardTimeIcon() {
return this.jumpForwardItems[this.currentJumpForwardTimeIndex].icon
},
@@ -257,14 +267,17 @@ export default {
},
shakeSensitivityOption() {
const item = this.shakeSensitivityItems.find((i) => i.value === this.settings.shakeSensitivity)
return item ? item.text : 'Error'
return item?.text || 'Error'
},
hapticFeedbackOption() {
const item = this.hapticFeedbackItems.find((i) => i.value === this.settings.hapticFeedback)
return item ? item.text : 'Error'
return item?.text || 'Error'
},
languageOption() {
return this.languageOptionItems.find((i) => i.value === this.settings.languageCode)?.text || 'English'
},
sleepTimerLengthOption() {
if (!this.settings.sleepTimerLength) return 'End of Chapter'
if (!this.settings.sleepTimerLength) return this.$strings.LabelEndOfChapter
const minutes = Number(this.settings.sleepTimerLength) / 1000 / 60
return `${minutes} min`
},
@@ -275,6 +288,7 @@ export default {
moreMenuItems() {
if (this.moreMenuSetting === 'shakeSensitivity') return this.shakeSensitivityItems
else if (this.moreMenuSetting === 'hapticFeedback') return this.hapticFeedbackItems
else if (this.moreMenuSetting === 'language') return this.languageOptionItems
return []
}
},
@@ -301,6 +315,10 @@ export default {
this.moreMenuSetting = 'shakeSensitivity'
this.showMoreMenuDialog = true
},
showLanguageOptions() {
this.moreMenuSetting = 'language'
this.showMoreMenuDialog = true
},
clickMenuAction(action) {
this.showMoreMenuDialog = false
if (this.moreMenuSetting === 'shakeSensitivity') {
@@ -309,6 +327,9 @@ export default {
} else if (this.moreMenuSetting === 'hapticFeedback') {
this.settings.hapticFeedback = action
this.hapticFeedbackUpdated(action)
} else if (this.moreMenuSetting === 'language') {
this.settings.languageCode = action
this.languageOptionUpdated(action)
}
},
autoSleepTimerTimeUpdated(val) {
@@ -320,6 +341,10 @@ export default {
this.$store.commit('globals/setHapticFeedback', val)
this.saveSettings()
},
languageOptionUpdated(val) {
this.$setLanguageCode(val)
this.saveSettings()
},
showInfo(setting) {
if (this.settingInfo[setting]) {
Dialog.alert({
@@ -432,6 +457,8 @@ export default {
this.settings.autoSleepTimerAutoRewind = !!deviceSettings.autoSleepTimerAutoRewind
this.settings.autoSleepTimerAutoRewindTime = !isNaN(deviceSettings.autoSleepTimerAutoRewindTime) ? deviceSettings.autoSleepTimerAutoRewindTime : 300000 // 5 minutes
this.settings.languageCode = deviceSettings.languageCode || 'en-us'
}
},
mounted() {