Merge pull request #1476 from hacketiwack/i18n

Translation of Web UI
This commit is contained in:
Christian Meffert 2022-06-03 07:03:51 +02:00 committed by GitHub
commit ccdcc0cbf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 6624 additions and 1971 deletions

File diff suppressed because one or more lines are too long

3979
web-src/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,13 @@
"name": "owntone-web",
"version": "2.0.0",
"scripts": {
"dev": "vite",
"serve": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
"format": "prettier . --write"
"dev": "vite",
"format": "prettier . --write",
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"",
"preview": "vite preview"
},
"dependencies": {
"@aacassandra/vue3-progressbar": "^1.0.3",
@ -23,6 +24,7 @@
"spotify-web-api-js": "^1.5.2",
"string-to-color": "^2.2.2",
"vue": "^3.2.33",
"vue-i18n": "^9.1.0",
"vue-router": "^4.0.14",
"vue-scrollto": "^2.20.0",
"vue3-click-away": "^1.2.4",
@ -31,12 +33,13 @@
"vuex": "^4.0.2"
},
"devDependencies": {
"@intlify/vite-plugin-vue-i18n": "^3.4.0",
"@vitejs/plugin-vue": "^2.3.1",
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-vue": "^8.6.0",
"prettier": "2.6.2",
"sass": "^1.50.0",
"vite": "^2.9.5"
"vite": "^2.9.8"
}
}

View File

@ -116,15 +116,6 @@ export default {
methods: {
connect: function () {
/*
this.$store.dispatch('add_notification', {
text: 'Connecting to OwnTone server',
type: 'info',
topic: 'connection',
timeout: 2000
})
*/
webapi
.config()
.then(({ data }) => {
@ -137,7 +128,7 @@ export default {
})
.catch(() => {
this.$store.dispatch('add_notification', {
text: 'Failed to connect to OwnTone server',
text: this.$t('server.connection.failed'),
type: 'danger',
topic: 'connection'
})
@ -147,7 +138,7 @@ export default {
open_ws: function () {
if (this.$store.state.config.websocket_port <= 0) {
this.$store.dispatch('add_notification', {
text: 'Missing websocket port',
text: this.$t('server.missing-port'),
type: 'danger'
})
return
@ -183,14 +174,6 @@ export default {
})
socket.onopen = function () {
/*
vm.$store.dispatch('add_notification', {
text: 'Connection to server established',
type: 'primary',
topic: 'connection',
timeout: 2000
})
*/
vm.reconnect_attempts = 0
socket.send(
JSON.stringify({
@ -221,17 +204,6 @@ export default {
socket.onclose = function () {
// vm.$store.dispatch('add_notification', { text: 'Connection closed', type: 'danger', timeout: 2000 })
}
/*
socket.onerror = function () {
vm.reconnect_attempts++
vm.$store.dispatch('add_notification', {
text:
'Connection lost. Reconnecting ... (' + vm.reconnect_attempts + ')',
type: 'danger',
topic: 'connection'
})
}
*/
// When the app becomes active, force an update of all information, because we
// may have missed notifications while the app was inactive.

View File

@ -11,23 +11,20 @@
aria-controls="dropdown-menu"
@click="is_active = !is_active"
>
<span>{{ modelValue }}</span>
<span class="icon">
<mdicon name="chevron-down" size="16" />
</span>
<span v-text="option.name" />
<mdicon class="icon" name="chevron-down" size="16" />
</button>
</div>
<div id="dropdown-menu" class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a
v-for="option in options"
:key="option"
v-for="o in options"
:key="o.id"
class="dropdown-item"
:class="{ 'is-active': modelValue === option }"
@click="select(option)"
>
{{ option }}
</a>
:class="{ 'is-active': modelValue === o.id }"
@click="select(o)"
v-text="o.name"
/>
</div>
</div>
</div>
@ -36,8 +33,6 @@
<script>
export default {
name: 'DropdownMenu',
// eslint-disable-next-line vue/prop-name-casing
props: ['modelValue', 'options'],
emits: ['update:modelValue'],
@ -47,6 +42,14 @@ export default {
}
},
computed: {
option: {
get() {
return this.options.find((option) => option.id === this.modelValue)
}
}
},
methods: {
onClickOutside(event) {
this.is_active = false
@ -54,7 +57,7 @@ export default {
select(option) {
this.is_active = false
this.$emit('update:modelValue', option)
this.$emit('update:modelValue', option.id)
}
}
}

View File

@ -6,8 +6,8 @@
:key="char"
class="button is-small"
@click="nav(char)"
>{{ char }}</a
>
v-text="char"
/>
</nav>
</section>
</template>
@ -15,9 +15,7 @@
<script>
export default {
name: 'IndexButtonList',
props: ['index'],
computed: {
filtered_index() {
if (!this.index) {
@ -27,7 +25,6 @@ export default {
return this.index.filter((c) => !specialChars.includes(c))
}
},
methods: {
nav: function (id) {
this.$router.push({ hash: '#index_' + id })

View File

@ -4,8 +4,8 @@
<span
:id="'index_' + album.groupKey"
class="tag is-info is-light is-small has-text-weight-bold"
>{{ album.groupKey }}</span
>
v-text="album.groupKey"
/>
</div>
<div v-else-if="album.isItem" class="media" @click="open_album(album.item)">
<div v-if="is_visible_artwork" class="media-left fd-has-action">
@ -24,25 +24,20 @@
</div>
<div class="media-content fd-has-action is-clipped">
<div style="margin-top: 0.7rem">
<h1 class="title is-6">
{{ album.item.name }}
</h1>
<h1 class="title is-6" v-text="album.item.name" />
<h2 class="subtitle is-7 has-text-grey">
<b>{{ album.item.artist }}</b>
<b v-text="album.item.artist" />
</h2>
<h2
v-if="album.item.date_released && album.item.media_kind === 'music'"
class="subtitle is-7 has-text-grey has-text-weight-normal"
>
{{ $filters.date(album.item.date_released) }}
</h2>
v-text="$filters.date(album.item.date_released)"
/>
</div>
</div>
<div class="media-right" style="padding-top: 0.7rem">
<a @click.prevent.stop="open_dialog(album.item)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>
@ -64,11 +59,10 @@
@delete="remove_podcast"
>
<template #modal-content>
<p>Permanently remove this podcast from your library?</p>
<p v-text="$t('list.albums.info-1')" />
<p class="is-size-7">
(This will also remove the RSS playlist
<b>{{ rss_playlist_to_remove.name }}</b
>.)
(<span v-text="$t('list.albums.info-2')" />
<b v-text="rss_playlist_to_remove.name" />)
</p>
</template>
</modal-dialog>
@ -152,7 +146,7 @@ export default {
const rssPlaylists = data.items.filter((pl) => pl.type === 'rss')
if (rssPlaylists.length !== 1) {
this.$store.dispatch('add_notification', {
text: 'Podcast cannot be removed. Probably it was not added as an RSS playlist.',
text: this.$t('list.albums.notification'),
type: 'danger'
})
return

View File

@ -5,8 +5,8 @@
<span
:id="'index_' + artist.groupKey"
class="tag is-info is-light is-small has-text-weight-bold"
>{{ artist.groupKey }}</span
>
v-text="artist.groupKey"
/>
</div>
</div>
<div
@ -15,15 +15,11 @@
@click="open_artist(artist.item)"
>
<div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">
{{ artist.item.name }}
</h1>
<h1 class="title is-6" v-text="artist.item.name" />
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(artist.item)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>

View File

@ -5,8 +5,8 @@
<span
:id="'index_' + composer.groupKey"
class="tag is-info is-light is-small has-text-weight-bold"
>{{ composer.groupKey }}</span
>
v-text="composer.groupKey"
/>
</div>
</div>
<div
@ -15,15 +15,11 @@
@click="open_composer(composer.item)"
>
<div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">
{{ composer.item.name }}
</h1>
<h1 class="title is-6" v-text="composer.item.name" />
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(composer.item)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>
@ -44,7 +40,6 @@ import ModalDialogComposer from '@/components/ModalDialogComposer.vue'
export default {
name: 'ListComposers',
components: { ModalDialogComposer },
props: ['composers', 'media_kind', 'hide_group_title'],
data() {

View File

@ -5,9 +5,7 @@
@click="open_parent_directory()"
>
<figure class="media-left fd-has-action">
<span class="icon">
<mdicon name="subdirectory-arrow-left" size="16" />
</span>
<mdicon class="icon" name="subdirectory-arrow-left" size="16" />
</figure>
<div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">..</h1>
@ -19,23 +17,18 @@
<template v-for="directory in directories" :key="directory.path">
<div class="media" @click="open_directory(directory)">
<figure class="media-left fd-has-action">
<span class="icon">
<mdicon name="folder" size="16" />
</span>
<mdicon class="icon" name="folder" size="16" />
</figure>
<div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">
{{ directory.path.substring(directory.path.lastIndexOf('/') + 1) }}
</h1>
<h2 class="subtitle is-7 has-text-grey-light">
{{ directory.path }}
</h2>
<h1
class="title is-6"
v-text="directory.path.substring(directory.path.lastIndexOf('/') + 1)"
/>
<h2 class="subtitle is-7 has-text-grey-light" v-text="directory.path" />
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(directory)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>

View File

@ -5,21 +5,17 @@
<span
:id="'index_' + genre.groupKey"
class="tag is-info is-light is-small has-text-weight-bold"
>{{ genre.groupKey }}</span
>
v-text="genre.groupKey"
/>
</div>
</div>
<div v-else-if="genre.isItem" class="media" @click="open_genre(genre.item)">
<div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">
{{ genre.item.name }}
</h1>
<h1 class="title is-6" v-text="genre.item.name" />
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(genre.item)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>

View File

@ -1,11 +1,12 @@
<template>
<div v-if="is_next || !show_only_next_items" class="media">
<div v-if="edit_mode" class="media-left">
<span class="icon has-text-grey fd-is-movable handle"
><mdicon name="drag-horizontal" size="16"
/></span>
<mdicon
class="icon has-text-grey fd-is-movable handle"
name="drag-horizontal"
size="16"
/>
</div>
<div class="media-content fd-has-action is-clipped" @click="play">
<h1
class="title is-6"
@ -13,9 +14,8 @@
'has-text-primary': item.id === state.item_id,
'has-text-grey-light': !is_next
}"
>
{{ item.title }}
</h1>
v-text="item.title"
/>
<h2
class="subtitle is-7"
:class="{
@ -24,7 +24,7 @@
'has-text-grey': is_next && item.id !== state.item_id
}"
>
<b>{{ item.artist }}</b>
<b v-text="item.artist" />
</h2>
<h2
class="subtitle is-7"
@ -33,9 +33,8 @@
'has-text-grey-light': !is_next,
'has-text-grey': is_next && item.id !== state.item_id
}"
>
{{ item.album }}
</h2>
v-text="item.album"
/>
</div>
<div class="media-right">
<slot name="actions" />

View File

@ -7,20 +7,14 @@
@click="open_playlist(playlist.item)"
>
<figure class="media-left fd-has-action">
<span class="icon">
<mdicon :name="icon_name(playlist.item)" size="16" />
</span>
<mdicon class="icon" :name="icon_name(playlist.item)" size="16" />
</figure>
<div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">
{{ playlist.item.name }}
</h1>
<h1 class="title is-6" v-text="playlist.item.name" />
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(playlist.item)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>

View File

@ -8,9 +8,7 @@
@click="play_track(index, track)"
>
<figure v-if="show_icon" class="media-left fd-has-action">
<span class="icon">
<mdicon name="file-outline" size="16" />
</span>
<mdicon class="icon" name="file-outline" size="16" />
</figure>
<div class="media-content fd-has-action is-clipped">
<h1
@ -19,15 +17,10 @@
'has-text-grey':
track.media_kind === 'podcast' && track.play_count > 0
}"
>
{{ track.title }}
</h1>
<h2 class="subtitle is-7 has-text-grey">
<b>{{ track.artist }}</b>
</h2>
<h2 class="subtitle is-7 has-text-grey">
{{ track.album }}
</h2>
v-text="track.title"
/>
<h2 class="subtitle is-7 has-text-grey" v-text="track.artist" />
<h2 class="subtitle is-7 has-text-grey" v-text="track.album" />
<progress-bar
v-if="show_progress"
:max="track.length_ms"
@ -36,13 +29,10 @@
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(track)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>
<teleport to="#app">
<modal-dialog-track
:show="show_details_modal"

View File

@ -6,33 +6,32 @@
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p v-if="title" class="title is-4">
{{ title }}
</p>
<p v-if="title" class="title is-4" v-text="title" />
<slot name="modal-content" />
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="$emit('close')">
<span class="icon"><mdicon name="cancel" size="16" /></span>
<span class="is-size-7">{{
close_action ? close_action : 'Cancel'
}}</span>
<mdicon class="icon" name="cancel" size="16" />
<span
class="is-size-7"
v-text="close_action ? close_action : t('dialog.cancel')"
/>
</a>
<a
v-if="delete_action"
class="card-footer-item has-background-danger has-text-white has-text-weight-bold"
@click="$emit('delete')"
>
<span class="icon"><mdicon name="delete" size="16" /></span>
<span class="is-size-7">{{ delete_action }}</span>
<mdicon class="icon" name="delete" size="16" />
<span class="is-size-7" v-text="delete_action" />
</a>
<a
v-if="ok_action"
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="$emit('ok')"
>
<span class="icon"><mdicon name="check" size="16" /></span>
<span class="is-size-7">{{ ok_action }}</span>
<mdicon class="icon" name="check" size="16" />
<span class="is-size-7" v-text="ok_action" />
</a>
</footer>
</div>

View File

@ -6,7 +6,7 @@
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">Add Podcast RSS feed URL</p>
<p class="title is-4" v-text="$t('dialog.add.rss.title')" />
<form @submit.prevent="add_stream">
<div class="field">
<p class="control is-expanded has-icons-left">
@ -15,24 +15,22 @@
v-model="url"
class="input is-shadowless"
type="text"
placeholder="http://url-to-rss"
:placeholder="$t('dialog.add.rss.placeholder')"
:disabled="loading"
/>
<span class="icon is-left">
<mdicon name="rss" size="16" />
</span>
</p>
<p class="help">
Adding a podcast includes creating an RSS playlist, that
will allow OwnTone to manage the podcast subscription.
<mdicon class="icon is-left" name="rss" size="16" />
</p>
<p class="help" v-text="$t('dialog.add.rss.help')" />
</div>
</form>
</div>
<footer v-if="loading" class="card-footer">
<a class="card-footer-item button is-loading">
<span class="icon"><mdicon name="web" size="16" /></span>
<span class="is-size-7">Processing ...</span>
<mdicon class="icon" name="web" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.add.rss.processing')"
/>
</a>
</footer>
<footer v-else class="card-footer">
@ -40,17 +38,15 @@
class="card-footer-item has-text-danger"
@click="$emit('close')"
>
<span class="icon"><mdicon name="cancel" size="16" /></span>
<span class="is-size-7">Cancel</span>
<mdicon class="icon" name="cancel" size="16" />
<span class="is-size-7" v-text="$t('dialog.add.rss.cancel')" />
</a>
<a
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="add_stream"
>
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span class="is-size-7" v-text="$t('dialog.add.rss.add')" />
</a>
</footer>
</div>

View File

@ -6,7 +6,7 @@
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">Add stream URL</p>
<p class="title is-4" v-text="$t('dialog.add.stream.title')" />
<form class="fd-has-margin-bottom" @submit.prevent="play">
<div class="field">
<p class="control is-expanded has-icons-left">
@ -15,20 +15,21 @@
v-model="url"
class="input is-shadowless"
type="text"
placeholder="http://url-to-stream"
:placeholder="$t('dialog.add.stream.placeholder')"
:disabled="loading"
/>
<span class="icon is-left">
<mdicon name="web" size="16" />
</span>
<mdicon class="icon is-left" name="web" size="16" />
</p>
</div>
</form>
</div>
<footer v-if="loading" class="card-footer">
<a class="card-footer-item has-text-dark">
<span class="icon"><mdicon name="web" size="16" /></span>
<span class="is-size-7">Loading ...</span>
<mdicon class="icon" name="web" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.add.stream.loading')"
/>
</a>
</footer>
<footer v-else class="card-footer">
@ -36,21 +37,22 @@
class="card-footer-item has-text-danger"
@click="$emit('close')"
>
<span class="icon"><mdicon name="cancel" size="16" /></span>
<span class="is-size-7">Cancel</span>
<mdicon class="icon" name="cancel" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.add.stream.cancel')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="add_stream">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span class="is-size-7" v-text="$t('dialog.add.stream.add')" />
</a>
<a
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="play"
>
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span class="is-size-7" v-text="$t('dialog.add.stream.play')" />
</a>
</footer>
</div>

View File

@ -13,75 +13,91 @@
class="image is-square fd-has-margin-bottom fd-has-shadow"
/>
<p class="title is-4">
<a class="has-text-link" @click="open_album">{{
album.name
}}</a>
<a
class="has-text-link"
@click="open_album"
v-text="album.name"
/>
</p>
<div v-if="media_kind_resolved === 'podcast'" class="buttons">
<a class="button is-small" @click="mark_played"
>Mark as played</a
>
<a class="button is-small" @click="$emit('remove-podcast')"
>Remove podcast</a
>
<a
class="button is-small"
@click="mark_played"
v-text="$t('dialog.album.mark-as-played')"
/>
<a
class="button is-small"
@click="$emit('remove-podcast')"
v-text="$t('dialog.album.remove-podcast')"
/>
</div>
<div class="content is-small">
<p v-if="album.artist">
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{
album.artist
}}</a>
<span class="heading" v-text="$t('dialog.album.artist')" />
<a
class="title is-6 has-text-link"
@click="open_artist"
v-text="album.artist"
/>
</p>
<p v-if="album.date_released">
<span class="heading">Release date</span>
<span class="title is-6">{{
$filters.date(album.date_released)
}}</span>
<span
class="heading"
v-text="$t('dialog.album.release-date')"
/>
<span
class="title is-6"
v-text="$filters.date(album.date_released)"
/>
</p>
<p v-else-if="album.year > 0">
<span class="heading">Year</span>
<span class="title is-6">{{ album.year }}</span>
<span class="heading" v-text="$t('dialog.album.year')" />
<span class="title is-6" v-text="album.year" />
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ album.track_count }}</span>
<span class="heading" v-text="$t('dialog.album.tracks')" />
<span class="title is-6" v-text="album.track_count" />
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{
$filters.durationInHours(album.length_ms)
}}</span>
<span class="heading" v-text="$t('dialog.album.duration')" />
<span
class="title is-6"
v-text="$filters.durationInHours(album.length_ms)"
/>
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6"
>{{ album.media_kind }} - {{ album.data_kind }}</span
>
<span class="heading" v-text="$t('dialog.album.type')" />
<span
class="title is-6"
v-text="
[
t('media.kind.' + album.media_kind),
t('data.kind.' + album.data_kind)
].join(' - ')
"
/>
</p>
<p>
<span class="heading">Added at</span>
<span class="title is-6">{{
$filters.datetime(album.time_added)
}}</span>
<span class="heading" v-text="$t('dialog.album.added-on')" />
<span
class="title is-6"
v-text="$filters.datetime(album.time_added)"
/>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span class="is-size-7" v-text="$t('dialog.album.add')" />
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span class="is-size-7" v-text="$t('dialog.album.add-next')" />
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span class="is-size-7" v-text="$t('dialog.album.play')" />
</a>
</footer>
</div>

View File

@ -7,47 +7,49 @@
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_artist">{{
artist.name
}}</a>
<a
class="has-text-link"
@click="open_artist"
v-text="artist.name"
/>
</p>
<div class="content is-small">
<p>
<span class="heading">Albums</span>
<span class="title is-6">{{ artist.album_count }}</span>
<span class="heading" v-text="$t('dialog.artist.albums')" />
<span class="title is-6" v-text="artist.album_count" />
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ artist.track_count }}</span>
<span class="heading" v-text="$t('dialog.artist.tracks')" />
<span class="title is-6" v-text="artist.track_count" />
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ artist.data_kind }}</span>
<span class="heading" v-text="$t('dialog.artist.type')" />
<span
class="title is-6"
v-text="$t('data.kind.' + artist.data_kind)"
/>
</p>
<p>
<span class="heading">Added at</span>
<span class="title is-6">{{
$filters.datetime(artist.time_added)
}}</span>
<span class="heading" v-text="$t('dialog.artist.added-on')" />
<span
class="title is-6"
v-text="$filters.datetime(artist.time_added)"
/>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span class="is-size-7" v-text="$t('dialog.artist.add')" />
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span class="is-size-7" v-text="$t('dialog.artist.add-next')" />
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span class="is-size-7" v-text="$t('dialog.artist.play')" />
</a>
</footer>
</div>

View File

@ -7,45 +7,51 @@
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_albums">{{
composer.name
}}</a>
<a
class="has-text-link"
@click="open_albums"
v-text="composer.name"
/>
</p>
<p>
<span class="heading">Albums</span>
<a class="has-text-link is-6" @click="open_albums">{{
composer.album_count
}}</a>
<span class="heading" v-text="$t('dialog.composer.albums')" />
<a
class="has-text-link is-6"
@click="open_albums"
v-text="composer.album_count"
/>
</p>
<p>
<span class="heading">Tracks</span>
<a class="has-text-link is-6" @click="open_tracks">{{
composer.track_count
}}</a>
<span class="heading" v-text="$t('dialog.composer.tracks')" />
<a
class="has-text-link is-6"
@click="open_tracks"
v-text="composer.track_count"
/>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{
$filters.durationInHours(composer.length_ms)
}}</span>
<span class="heading" v-text="$t('dialog.composer.duration')" />
<span
class="title is-6"
v-text="$filters.durationInHours(composer.length_ms)"
/>
</p>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span class="is-size-7" v-text="$t('dialog.composer.add')" />
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.composer.add-next')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span class="is-size-7" v-text="$t('dialog.composer.play')" />
</a>
</footer>
</div>

View File

@ -6,26 +6,23 @@
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ directory.path }}
</p>
<p class="title is-4" v-text="directory.path" />
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span class="is-size-7" v-text="$t('dialog.directory.add')" />
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.directory.add-next')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span class="is-size-7" v-text="$t('dialog.directory.play')" />
</a>
</footer>
</div>

View File

@ -7,43 +7,42 @@
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_genre">{{
genre.name
}}</a>
<a
class="has-text-link"
@click="open_genre"
v-text="genre.name"
/>
</p>
<div class="content is-small">
<p>
<span class="heading">Albums</span>
<span class="title is-6">{{ genre.album_count }}</span>
<span class="heading" v-text="$t('dialog.genre.albums')" />
<span class="title is-6" v-text="genre.album_count" />
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ genre.track_count }}</span>
<span class="heading" v-text="$t('dialog.genre.tracks')" />
<span class="title is-6" v-text="genre.track_count" />
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{
$filters.durationInHours(genre.length_ms)
}}</span>
<span class="heading" v-text="$t('dialog.genre.duration')" />
<span
class="title is-6"
v-text="$filters.durationInHours(genre.length_ms)"
/>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span class="is-size-7" v-text="$t('dialog.genre.add')" />
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span class="is-size-7" v-text="$t('dialog.genre.add-next')" />
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span class="is-size-7" v-text="$t('dialog.genre.play')" />
</a>
</footer>
</div>

View File

@ -7,41 +7,45 @@
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{
playlist.name
}}</a>
<a
class="has-text-link"
@click="open_playlist"
v-text="playlist.name"
/>
</p>
<div class="content is-small">
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ playlist.path }}</span>
<span class="title is-6" v-text="playlist.path" />
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ playlist.type }}</span>
<span class="heading" v-text="$t('dialog.playlist.type')" />
<span class="title is-6" v-text="playlist.type" />
</p>
<p v-if="!playlist.folder">
<span class="heading">Track count</span>
<span class="title is-6">{{ playlist.item_count }}</span>
<span
class="heading"
v-text="$t('dialog.playlist.track-count')"
/>
<span class="title is-6" v-text="playlist.item_count" />
</p>
</div>
</div>
<footer v-if="!playlist.folder" class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span class="is-size-7" v-text="$t('dialog.playlist.add')" />
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.playlist.add-next')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span class="is-size-7" v-text="$t('dialog.playlist.play')" />
</a>
</footer>
</div>

View File

@ -6,7 +6,7 @@
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">Save queue to playlist</p>
<p class="title is-4" v-text="$t('dialog.playlist.save.title')" />
<form class="fd-has-margin-bottom" @submit.prevent="save">
<div class="field">
<p class="control is-expanded has-icons-left">
@ -18,17 +18,18 @@
placeholder="Playlist name"
:disabled="loading"
/>
<span class="icon is-left">
<mdicon name="file-music" size="16" />
</span>
<mdicon class="icon is-left" name="file-music" size="16" />
</p>
</div>
</form>
</div>
<footer v-if="loading" class="card-footer">
<a class="card-footer-item has-text-dark">
<span class="icon"><mdicon name="web" size="16" /></span>
<span class="is-size-7">Saving ...</span>
<mdicon class="icon" name="web" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.playlist.save.saving')"
/>
</a>
</footer>
<footer v-else class="card-footer">
@ -36,17 +37,21 @@
class="card-footer-item has-text-danger"
@click="$emit('close')"
>
<span class="icon"><mdicon name="cancel" size="16" /></span>
<span class="is-size-7">Cancel</span>
<mdicon class="icon" name="cancel" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.playlist.save.cancel')"
/>
</a>
<a
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="save"
>
<span class="icon"
><mdicon name="content-save" size="16"
/></span>
<span class="is-size-7">Save</span>
<mdicon class="icon" name="content-save" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.playlist.save.save')"
/>
</a>
</footer>
</div>

View File

@ -6,98 +6,146 @@
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ item.title }}
</p>
<p class="subtitle">
{{ item.artist }}
</p>
<p class="title is-4" v-text="item.title" />
<p class="subtitle" v-text="item.artist" />
<div class="content is-small">
<p>
<span class="heading">Album</span>
<span
class="heading"
v-text="$t('dialog.queue-item.album')"
/>
<a
v-if="item.album_id"
class="title is-6 has-text-link"
@click="open_album"
>{{ item.album }}</a
>
<span v-else class="title is-6">{{ item.album }}</span>
v-text="item.album"
/>
<span v-else class="title is-6" v-text="item.album" />
</p>
<p v-if="item.album_artist">
<span class="heading">Album artist</span>
<span
class="heading"
v-text="$t('dialog.queue-item.album-artist')"
/>
<a
v-if="item.album_artist_id"
class="title is-6 has-text-link"
@click="open_album_artist"
>{{ item.album_artist }}</a
>
<span v-else class="title is-6">{{ item.album_artist }}</span>
v-text="item.album_artist"
/>
<span v-else class="title is-6" v-text="item.album_artist" />
</p>
<p v-if="item.composer">
<span class="heading">Composer</span>
<span class="title is-6">{{ item.composer }}</span>
<span
class="heading"
v-text="$t('dialog.queue-item.composer')"
/>
<span class="title is-6" v-text="item.composer" />
</p>
<p v-if="item.year > 0">
<span class="heading">Year</span>
<span class="title is-6">{{ item.year }}</span>
<span class="heading" v-text="$t('dialog.queue-item.year')" />
<span class="title is-6" v-text="item.year" />
</p>
<p v-if="item.genre">
<span class="heading">Genre</span>
<a class="title is-6 has-text-link" @click="open_genre">{{
item.genre
}}</a>
<span
class="heading"
v-text="$t('dialog.queue-item.genre')"
/>
<a
class="title is-6 has-text-link"
@click="open_genre"
v-text="item.genre"
/>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6"
>{{ item.track_number }} / {{ item.disc_number }}</span
>
<span
class="heading"
v-text="$t('dialog.queue-item.position')"
/>
<span
class="title is-6"
v-text="[item.disc_number, item.track_number].join(' / ')"
/>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{
$filters.durationInHours(item.length_ms)
}}</span>
<span
class="heading"
v-text="$t('dialog.queue-item.duration')"
/>
<span
class="title is-6"
v-text="$filters.durationInHours(item.length_ms)"
/>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ item.path }}</span>
<span class="heading" v-text="$t('dialog.queue-item.path')" />
<span class="title is-6" v-text="item.path" />
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6"
>{{ item.media_kind }} - {{ item.data_kind }}
<span class="heading" v-text="$t('dialog.queue-item.type')" />
<span class="title is-6">
<span
v-text="[item.media_kind, item.data_kind].join(' - ')"
/>
<span
v-if="item.data_kind === 'spotify'"
class="has-text-weight-normal"
>(<a @click="open_spotify_artist">artist</a>,
<a @click="open_spotify_album">album</a>)</span
></span
>
>
(<a
@click="open_spotify_artist"
v-text="$t('dialog.queue-item.spotify-artist')"
/>,
<a
@click="open_spotify_album"
v-text="$t('dialog.queue-item.spotify-album')"
/>)
</span>
</span>
</p>
<p>
<span class="heading">Quality</span>
<span
class="heading"
v-text="$t('dialog.queue-item.quality')"
/>
<span class="title is-6">
{{ item.type }}
<span v-if="item.samplerate">
| {{ item.samplerate }} Hz</span
>
<span v-if="item.channels">
| {{ $filters.channels(item.channels) }}</span
>
<span v-if="item.bitrate"> | {{ item.bitrate }} Kb/s</span>
<span v-text="item.type" />
<span
v-if="item.samplerate"
v-text="
$t('dialog.queue-item.samplerate', {
rate: item.samplerate
})
"
/>
<span
v-if="item.channels"
v-text="
$t('dialog.queue-item.channels', {
channels: $filters.channels(item.channels)
})
"
/>
<span
v-if="item.bitrate"
v-text="
$t('dialog.queue-item.bitrate', { rate: item.bitrate })
"
/>
</span>
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="remove">
<span class="icon"><mdicon name="delete" size="16" /></span>
<span class="is-size-7">Remove</span>
<mdicon class="icon" name="delete" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.queue-item.remove')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span class="is-size-7" v-text="$t('dialog.queue-item.play')" />
</a>
</footer>
</div>

View File

@ -6,11 +6,12 @@
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">Remote pairing request</p>
<p
class="title is-4"
v-text="$t('dialog.remote-pairing.title')"
/>
<form @submit.prevent="kickoff_pairing">
<label class="label">
{{ pairing.remote }}
</label>
<label class="label" v-text="pairing.remote" />
<div class="field">
<div class="control">
<input
@ -29,15 +30,21 @@
class="card-footer-item has-text-danger"
@click="$emit('close')"
>
<span class="icon"><mdicon name="cancel" size="16" /></span>
<span class="is-size-7">Cancel</span>
<mdicon class="icon" name="cancel" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.remote-pairing.cancel')"
/>
</a>
<a
class="card-footer-item has-background-info has-text-white has-text-weight-bold"
@click="kickoff_pairing"
>
<span class="icon"><mdicon name="cellphone" size="16" /></span>
<span class="is-size-7">Pair Remote</span>
<mdicon class="icon" name="cellphone" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.remote-pairing.pair')"
/>
</a>
</footer>
</div>

View File

@ -6,138 +6,173 @@
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ track.title }}
</p>
<p class="subtitle">
{{ track.artist }}
</p>
<p class="title is-4" v-text="track.title" />
<p class="subtitle" v-text="track.artist" />
<div v-if="track.media_kind === 'podcast'" class="buttons">
<a
v-if="track.play_count > 0"
class="button is-small"
@click="mark_new"
>Mark as new</a
>
v-text="$t('dialog.track.mark-as-new')"
/>
<a
v-if="track.play_count === 0"
class="button is-small"
@click="mark_played"
>Mark as played</a
>
v-text="$t('dialog.track.mark-as-played')"
/>
</div>
<div class="content is-small">
<p>
<span class="heading">Album</span>
<a class="title is-6 has-text-link" @click="open_album">{{
track.album
}}</a>
<span class="heading" v-text="$t('dialog.track.album')" />
<a
class="title is-6 has-text-link"
@click="open_album"
v-text="track.album"
/>
</p>
<p
v-if="track.album_artist && track.media_kind !== 'audiobook'"
>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{
track.album_artist
}}</a>
<span
class="heading"
v-text="$t('dialog.track.album-artist')"
/>
<a
class="title is-6 has-text-link"
@click="open_artist"
v-text="track.album_artist"
/>
</p>
<p v-if="track.composer">
<span class="heading">Composer</span>
<span class="title is-6">{{ track.composer }}</span>
<span class="heading" v-text="$t('dialog.track.composer')" />
<span class="title is-6" v-text="track.composer" />
</p>
<p v-if="track.date_released">
<span class="heading">Release date</span>
<span class="title is-6">{{
$filters.date(track.date_released)
}}</span>
<span
class="heading"
v-text="$t('dialog.track.release-date')"
/>
<span
class="title is-6"
v-text="$filters.date(track.date_released)"
/>
</p>
<p v-else-if="track.year > 0">
<span class="heading">Year</span>
<span class="title is-6">{{ track.year }}</span>
<span class="heading" v-text="$t('dialog.track.year')" />
<span class="title is-6" v-text="track.year" />
</p>
<p v-if="track.genre">
<span class="heading">Genre</span>
<a class="title is-6 has-text-link" @click="open_genre">{{
track.genre
}}</a>
<span class="heading" v-text="$t('dialog.track.genre')" />
<a
class="title is-6 has-text-link"
@click="open_genre"
v-text="track.genre"
/>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6"
>{{ track.track_number }} / {{ track.disc_number }}</span
>
<span class="heading" v-text="$t('dialog.track.position')" />
<span
class="title is-6"
v-text="[track.disc_number, track.track_number].join(' / ')"
/>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{
$filters.durationInHours(track.length_ms)
}}</span>
<span class="heading" v-text="$t('dialog.track.duration')" />
<span
class="title is-6"
v-text="$filters.durationInHours(track.length_ms)"
/>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ track.path }}</span>
<span class="heading" v-text="$t('dialog.track.path')" />
<span class="title is-6" v-text="track.path" />
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6"
>{{ track.media_kind }} - {{ track.data_kind }}
<span class="heading" v-text="$t('dialog.track.type')" />
<span class="title is-6">
<span
v-text="[track.media_kind, track.data_kind].join(' - ')"
/>
<span
v-if="track.data_kind === 'spotify'"
class="has-text-weight-normal"
>(<a @click="open_spotify_artist">artist</a>,
<a @click="open_spotify_album">album</a>)</span
></span
>
</p>
<p>
<span class="heading">Quality</span>
<span class="title is-6">
{{ track.type }}
<span v-if="track.samplerate">
| {{ track.samplerate }} Hz</span
>
<span v-if="track.channels">
| {{ $filters.channels(track.channels) }}</span
>
<span v-if="track.bitrate">
| {{ track.bitrate }} Kb/s</span
>
(<a
@click="open_spotify_artist"
v-text="$t('dialog.track.spotify-artist')"
/>,
<a
@click="open_spotify_album"
v-text="$t('dialog.track.spotify-album')"
/>)
</span>
</span>
</p>
<p>
<span class="heading">Added at</span>
<span class="title is-6">{{
$filters.datetime(track.time_added)
}}</span>
<span class="heading" v-text="$t('dialog.track.quality')" />
<span class="title is-6">
<span v-text="track.type" />
<span
v-if="track.samplerate"
v-text="
$t('dialog.track.samplerate', {
rate: track.samplerate
})
"
/>
<span
v-if="track.channels"
v-text="
$t('dialog.track.channels', {
channels: $filters.channels(track.channels)
})
"
/>
<span
v-if="track.bitrate"
v-text="
$t('dialog.track.bitrate', { rate: track.bitrate })
"
/>
</span>
</p>
<p>
<span class="heading">Rating</span>
<span class="title is-6"
>{{ Math.floor(track.rating / 10) }} / 10</span
>
<span class="heading" v-text="$t('dialog.track.added-on')" />
<span
class="title is-6"
v-text="$filters.datetime(track.time_added)"
/>
</p>
<p>
<span class="heading" v-text="$t('dialog.track.rating')" />
<span
class="title is-6"
v-text="
$t('dialog.track.rating-value', {
rating: Math.floor(track.rating / 10)
})
"
/>
</p>
<p v-if="track.comment">
<span class="heading">Comment</span>
<span class="title is-6">{{ track.comment }}</span>
<span class="heading" v-text="$t('dialog.track.comment')" />
<span class="title is-6" v-text="track.comment" />
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span class="is-size-7" v-text="$t('dialog.track.add')" />
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span class="is-size-7" v-text="$t('dialog.track.add-next')" />
</a>
<a class="card-footer-item has-text-dark" @click="play_track">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span class="is-size-7" v-text="$t('dialog.track.play')" />
</a>
</footer>
</div>

View File

@ -1,40 +1,48 @@
<template>
<modal-dialog
:show="show"
title="Update library"
:ok_action="library.updating ? '' : 'Rescan'"
close_action="Close"
:title="$t('dialog.update.title')"
:ok_action="library.updating ? '' : $t('dialog.update.rescan')"
:close_action="$t('dialog.update.cancel')"
@ok="update_library"
@close="close()"
>
<template #modal-content>
<div v-if="!library.updating">
<p class="mb-3">Scan for new, deleted and modified files</p>
<p class="mb-3" v-text="$t('dialog.update.info')" />
<div v-if="spotify_enabled || rss.tracks > 0" class="field">
<div class="control">
<div class="select is-small">
<select v-model="update_dialog_scan_kind">
<option value="">Update everything</option>
<option value="files">Only update local library</option>
<option v-if="spotify_enabled" value="spotify">
Only update Spotify
</option>
<option v-if="rss.tracks > 0" value="rss">
Only update RSS feeds
</option>
<option value="" v-text="$t('dialog.update.all')" />
<option value="files" v-text="$t('dialog.update.local')" />
<option
v-if="spotify_enabled"
value="spotify"
v-text="$t('dialog.update.spotify')"
/>
<option
v-if="rss.tracks > 0"
value="rss"
v-text="$t('dialog.update.feeds')"
/>
</select>
</div>
</div>
</div>
<div class="field">
<label class="checkbox is-size-7 is-small">
<input v-model="rescan_metadata" type="checkbox" />
Rescan metadata for unmodified files
<input
v-model="rescan_metadata"
type="checkbox"
style="margin-right: 5px"
/>
<span v-text="$t('dialog.update.rescan-metadata')" />
</label>
</div>
</div>
<div v-else>
<p class="mb-3">Library update in progress ...</p>
<p class="mb-3" v-text="$t('dialog.update.progress')" />
</div>
</template>
</modal-dialog>

View File

@ -12,9 +12,8 @@
<div class="navbar-brand fd-expanded">
<!-- Link to queue -->
<navbar-item-link to="/" exact>
<span class="icon"><mdicon name="playlist-play" size="24" /></span>
<mdicon class="icon" name="playlist-play" size="24" />
</navbar-item-link>
<!-- Now playing artist/title (not visible on "now playing" page) -->
<router-link
v-if="!is_now_playing_page"
@ -25,16 +24,18 @@
>
<div class="is-clipped">
<p class="is-size-7 fd-is-text-clipped">
<strong>{{ now_playing.title }}</strong
><br />
{{ now_playing.artist
}}<span v-if="now_playing.data_kind === 'url'">
- {{ now_playing.album }}</span
>
<strong v-text="now_playing.title" />
<br />
<span v-text="now_playing.artist" />
<span
v-if="now_playing.data_kind === 'url'"
v-text="
$t('navigation.now-playing', { album: now_playing.album })
"
/>
</p>
</div>
</router-link>
<!-- Skip previous (not visible on "now playing" page) -->
<player-button-previous
v-if="is_now_playing_page"
@ -65,19 +66,17 @@
class="navbar-item"
:icon_size="24"
/>
<!-- Player menu button (only visible on mobile and tablet) -->
<a
class="navbar-item fd-margin-left-auto is-hidden-desktop"
@click="show_player_menu = !show_player_menu"
>
<span class="icon"
><mdicon
:name="show_player_menu ? 'chevron-down' : 'chevron-up'"
size="18"
/></span>
<mdicon
class="icon"
:name="show_player_menu ? 'chevron-down' : 'chevron-up'"
size="18"
/>
</a>
<!-- Player menu dropup menu (only visible on desktop) -->
<div
class="navbar-item has-dropdown has-dropdown-up fd-margin-left-auto is-hidden-touch"
@ -87,13 +86,12 @@
class="navbar-link is-arrowless"
@click="show_player_menu = !show_player_menu"
>
<span class="icon"
><mdicon
:name="show_player_menu ? 'chevron-down' : 'chevron-up'"
size="18"
/></span>
<mdicon
class="icon"
:name="show_player_menu ? 'chevron-down' : 'chevron-up'"
size="18"
/>
</a>
<div
class="navbar-dropdown is-right is-boxed"
style="margin-right: 6px; margin-bottom: 6px; border-radius: 6px"
@ -107,16 +105,16 @@
class="button is-white is-small"
@click="toggle_mute_volume"
>
<span class="icon"
><mdicon
:name="player.volume > 0 ? 'volume-high' : 'volume-off'"
size="18"
/></span>
<mdicon
class="icon"
:name="player.volume > 0 ? 'volume-high' : 'volume-off'"
size="18"
/>
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
<p class="heading">Volume</p>
<p class="heading" v-text="$t('navigation.volume')" />
<Slider
v-model="player.volume"
:min="0"
@ -126,20 +124,11 @@
:classes="{ target: 'slider' }"
@change="set_volume"
/>
<!--range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:value="player.volume"
@change="set_volume">
</range-slider-->
</div>
</div>
</div>
</div>
</div>
<!-- Outputs: master volume -->
<hr class="fd-navbar-divider" />
<navbar-item-output
@ -147,7 +136,6 @@
:key="output.id"
:output="output"
/>
<!-- Outputs: stream volume -->
<hr class="fd-navbar-divider" />
<div class="navbar-item">
@ -157,15 +145,18 @@
<a
class="button is-white is-small"
:class="{ 'is-loading': loading }"
><span
>
<span
class="icon fd-has-action"
:class="{
'has-text-grey-light': !playing && !loading,
'is-loading': loading
}"
@click="togglePlay"
><mdicon name="radio-tower" size="18" /></span
></a>
>
<mdicon name="broadcast" size="18" />
</span>
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
@ -173,10 +164,19 @@
class="heading"
:class="{ 'has-text-grey-light': !playing }"
>
HTTP stream
<a href="stream.mp3"
><span class="is-lowercase">(stream.mp3)</span></a
<span v-text="$t('navigation.stream')" />
<a
href="stream.mp3"
style="margin-left: 5px"
target="_blank"
>
<mdicon
class="icon"
name="open-in-new"
size="16"
style="vertical-align: middle"
/>
</a>
</p>
<Slider
v-model="stream_volume"
@ -188,21 +188,11 @@
:classes="{ target: 'slider' }"
@change="set_stream_volume"
/>
<!--range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:disabled="!playing"
:value="stream_volume"
@change="set_stream_volume">
</range-slider-->
</div>
</div>
</div>
</div>
</div>
<!-- Playback controls -->
<hr class="fd-navbar-divider" />
<div class="navbar-item">
@ -219,7 +209,6 @@
</div>
</div>
</div>
<!-- Player menu (only visible on mobile and tablet) -->
<div
class="navbar-menu is-hidden-desktop"
@ -235,25 +224,23 @@
<player-button-consume class="button" :icon_size="18" />
</div>
</div>
<hr class="fd-navbar-divider" />
<!-- Outputs: master volume -->
<div class="navbar-item">
<div class="level is-mobile">
<div class="level-left fd-expanded">
<div class="level-item" style="flex-grow: 0">
<a class="button is-white is-small" @click="toggle_mute_volume">
<span class="icon"
><mdicon
:name="player.volume > 0 ? 'volume-high' : 'volume-off'"
size="18"
/></span>
<mdicon
class="icon"
:name="player.volume > 0 ? 'volume-high' : 'volume-off'"
size="18"
/>
</a>
</div>
<div class="level-item fd-expanded">
<div class="fd-expanded">
<p class="heading">Volume</p>
<p class="heading" v-text="$t('navigation.volume')" />
<Slider
v-model="player.volume"
:min="0"
@ -263,27 +250,17 @@
:classes="{ target: 'slider' }"
@change="set_volume"
/>
<!--range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:value="player.volume"
@change="set_volume">
</range-slider-->
</div>
</div>
</div>
</div>
</div>
<!-- Outputs: speaker volumes -->
<navbar-item-output
v-for="output in outputs"
:key="output.id"
:output="output"
/>
<!-- Outputs: stream volume -->
<hr class="fd-navbar-divider" />
<div class="navbar-item fd-has-margin-bottom">
@ -301,7 +278,8 @@
'is-loading': loading
}"
@click="togglePlay"
><mdicon name="radio-tower" size="16" />
>
<mdicon name="broadcast" size="16" />
</span>
</a>
</div>
@ -311,10 +289,19 @@
class="heading"
:class="{ 'has-text-grey-light': !playing }"
>
HTTP stream
<a href="stream.mp3"
><span class="is-lowercase">(stream.mp3)</span></a
<span v-text="$t('navigation.stream')" />
<a
href="stream.mp3"
style="margin-left: 5px"
target="_blank"
>
<mdicon
class="icon"
name="open-in-new"
size="16"
style="vertical-align: middle"
/>
</a>
</p>
<Slider
v-model="stream_volume"
@ -326,15 +313,6 @@
:classes="{ target: 'slider' }"
@change="set_stream_volume"
/>
<!-- range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:disabled="!playing"
:value="stream_volume"
@change="set_stream_volume">
</range-slider-->
</div>
</div>
</div>
@ -358,7 +336,6 @@ import PlayerButtonConsume from '@/components/PlayerButtonConsume.vue'
import PlayerButtonRepeat from '@/components/PlayerButtonRepeat.vue'
import PlayerButtonSeekBack from '@/components/PlayerButtonSeekBack.vue'
import PlayerButtonSeekForward from '@/components/PlayerButtonSeekForward.vue'
//import RangeSlider from 'vue-range-slider'
import Slider from '@vueform/slider'
import * as types from '@/store/mutation_types'
@ -367,7 +344,6 @@ export default {
components: {
NavbarItemLink,
NavbarItemOutput,
//RangeSlider,
Slider,
PlayerButtonPlayPause,
PlayerButtonNext,
@ -382,11 +358,9 @@ export default {
data() {
return {
old_volume: 0,
playing: false,
loading: false,
stream_volume: 10,
show_outputs_menu: false,
show_desktop_outputs_menu: false
}
@ -488,7 +462,7 @@ export default {
a.addEventListener('error', (e) => {
this.closeAudio()
this.$store.dispatch('add_notification', {
text: 'HTTP stream error: failed to load stream or stopped loading due to network problem',
text: this.$t('navigation.stream-error'),
type: 'danger'
})
this.playing = false

View File

@ -18,9 +18,8 @@
<p
class="heading"
:class="{ 'has-text-grey-light': !output.selected }"
>
{{ output.name }}
</p>
v-text="output.name"
/>
<Slider
v-model="volume"
:min="0"
@ -31,15 +30,6 @@
:classes="{ target: 'slider' }"
@change="set_volume"
/>
<!--range-slider
class="slider fd-has-action"
min="0"
max="100"
step="1"
:disabled="!output.selected"
:value="volume"
@change="set_volume" >
</range-slider-->
</div>
</div>
</div>
@ -48,14 +38,12 @@
</template>
<script>
//import RangeSlider from 'vue-range-slider'
import Slider from '@vueform/slider'
import webapi from '@/webapi'
export default {
name: 'NavbarItemOutput',
components: {
// RangeSlider
Slider
},

View File

@ -7,27 +7,26 @@
>
<div class="navbar-brand">
<navbar-item-link v-if="is_visible_playlists" to="/playlists">
<span class="icon"><mdicon name="music-box-multiple" size="16" /></span>
<mdicon class="icon" name="music-box-multiple" size="16" />
</navbar-item-link>
<navbar-item-link v-if="is_visible_music" to="/music">
<span class="icon"><mdicon name="music" size="16" /></span>
<mdicon class="icon" name="music" size="16" />
</navbar-item-link>
<navbar-item-link v-if="is_visible_podcasts" to="/podcasts">
<span class="icon"><mdicon name="microphone" size="16" /></span>
<mdicon class="icon" name="podcast" size="16" />
</navbar-item-link>
<navbar-item-link v-if="is_visible_audiobooks" to="/audiobooks">
<span class="icon"><mdicon name="book-open-variant" size="16" /></span>
<mdicon class="icon" name="book-open-variant" size="16" />
</navbar-item-link>
<navbar-item-link v-if="is_visible_radio" to="/radio">
<span class="icon"><mdicon name="radio" size="16" /></span>
<mdicon class="icon" name="radio-tower" size="16" />
</navbar-item-link>
<navbar-item-link v-if="is_visible_files" to="/files">
<span class="icon"><mdicon name="folder-open" size="16" /></span>
<mdicon class="icon" name="folder-open" size="16" />
</navbar-item-link>
<navbar-item-link v-if="is_visible_search" to="/search">
<span class="icon"><mdicon name="magnify" size="16" /></span>
<mdicon class="icon" name="magnify" size="16" />
</navbar-item-link>
<div
class="navbar-burger"
:class="{ 'is-active': show_burger_menu }"
@ -38,10 +37,8 @@
<span />
</div>
</div>
<div class="navbar-menu" :class="{ 'is-active': show_burger_menu }">
<div class="navbar-start" />
<div class="navbar-end">
<!-- Burger menu entries -->
<div
@ -50,67 +47,76 @@
@click="on_click_outside_settings"
>
<a class="navbar-link is-arrowless">
<span class="icon is-hidden-touch"
><mdicon name="menu" size="24"
/></span>
<span class="is-hidden-desktop has-text-weight-bold">OwnTone</span>
<mdicon class="icon is-hidden-touch" name="menu" size="24" />
<span
class="is-hidden-desktop has-text-weight-bold"
v-text="$t('navigation.title')"
/>
</a>
<div class="navbar-dropdown is-right">
<navbar-item-link to="/playlists">
<span class="icon"
><mdicon name="music-box-multiple" size="16"
/></span>
<b>Playlists</b>
<mdicon class="icon" name="music-box-multiple" size="16" />
<b v-text="$t('navigation.playlists')" />
</navbar-item-link>
<navbar-item-link to="/music" exact>
<span class="icon"><mdicon name="music" size="16" /></span>
<b>Music</b>
<mdicon class="icon" name="music" size="16" />
<b v-text="$t('navigation.music')" />
</navbar-item-link>
<navbar-item-link to="/music/artists">
<span class="fd-navbar-item-level2">Artists</span>
<span
class="fd-navbar-item-level2"
v-text="$t('navigation.artists')"
/>
</navbar-item-link>
<navbar-item-link to="/music/albums">
<span class="fd-navbar-item-level2">Albums</span>
<span
class="fd-navbar-item-level2"
v-text="$t('navigation.albums')"
/>
</navbar-item-link>
<navbar-item-link to="/music/genres">
<span class="fd-navbar-item-level2">Genres</span>
<span
class="fd-navbar-item-level2"
v-text="$t('navigation.genres')"
/>
</navbar-item-link>
<navbar-item-link v-if="spotify_enabled" to="/music/spotify">
<span class="fd-navbar-item-level2">Spotify</span>
<span
class="fd-navbar-item-level2"
v-text="$t('navigation.spotify')"
/>
</navbar-item-link>
<navbar-item-link to="/podcasts">
<span class="icon"><mdicon name="microphone" size="16" /></span>
<b>Podcasts</b>
<mdicon class="icon" name="podcast" size="16" />
<b v-text="$t('navigation.podcasts')" />
</navbar-item-link>
<navbar-item-link to="/audiobooks">
<span class="icon"
><mdicon name="book-open-variant" size="16"
/></span>
<b>Audiobooks</b>
<mdicon class="icon" name="book-open-variant" size="16" />
<b v-text="$t('navigation.audiobooks')" />
</navbar-item-link>
<navbar-item-link to="/radio">
<span class="icon"><mdicon name="radio" size="16" /></span>
<b>Radio</b>
<mdicon class="icon" name="radio-tower" size="16" />
<b v-text="$t('navigation.radio')" />
</navbar-item-link>
<navbar-item-link to="/files">
<span class="icon"><mdicon name="folder-open" size="16" /></span>
<b>Files</b>
<mdicon class="icon" name="folder-open" size="16" />
<b v-text="$t('navigation.files')" />
</navbar-item-link>
<navbar-item-link to="/search">
<span class="icon"><mdicon name="magnify" size="16" /></span>
<b>Search</b>
<mdicon class="icon" name="magnify" size="16" />
<b v-text="$t('navigation.search')" />
</navbar-item-link>
<hr class="fd-navbar-divider" />
<navbar-item-link to="/settings/webinterface">
Settings
</navbar-item-link>
<a class="navbar-item" @click.stop.prevent="open_update_dialog()">
Update Library
</a>
<navbar-item-link to="/about"> About </navbar-item-link>
<navbar-item-link
to="/settings/webinterface"
v-text="$t('navigation.settings')"
/>
<a
class="navbar-item"
@click.stop.prevent="open_update_dialog()"
v-text="$t('navigation.update-library')"
/>
<navbar-item-link to="/about" v-text="$t('navigation.about')" />
<div
class="navbar-item is-hidden-desktop"
style="margin-bottom: 2.5rem"
@ -119,7 +125,6 @@
</div>
</div>
</div>
<div
v-show="show_settings_menu"
class="is-overlay"

View File

@ -12,7 +12,7 @@
]"
>
<button class="delete" @click="remove(notification)" />
{{ notification.text }}
<span v-text="notification.text" />
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
<template>
<a :class="{ 'is-warning': is_consume }" @click="toggle_consume_mode">
<span class="icon"><mdicon name="fire" :size="icon_size" /></span>
<mdicon class="icon" name="fire" :size="icon_size" />
</a>
</template>

View File

@ -1,6 +1,6 @@
<template>
<a :disabled="disabled" @click="play_next">
<span class="icon"><mdicon name="skip-forward" :size="icon_size" /></span>
<mdicon class="icon" name="skip-forward" :size="icon_size" />
</a>
</template>

View File

@ -1,6 +1,6 @@
<template>
<a :disabled="disabled" @click="toggle_play_pause">
<span class="icon"><mdicon :name="icon_name" :size="icon_size" /></span>
<mdicon class="icon" :name="icon_name" :size="icon_size" />
</a>
</template>
@ -49,7 +49,7 @@ export default {
if (this.disabled) {
if (this.show_disabled_message) {
this.$store.dispatch('add_notification', {
text: 'Queue is empty',
text: this.$t('server.empty-queue'),
type: 'info',
topic: 'connection',
timeout: 2000

View File

@ -1,6 +1,6 @@
<template>
<a :disabled="disabled" @click="play_previous">
<span class="icon"><mdicon name="skip-backward" :size="icon_size" /></span>
<mdicon class="icon" name="skip-backward" :size="icon_size" />
</a>
</template>

View File

@ -1,6 +1,6 @@
<template>
<a :class="{ 'is-warning': !is_repeat_off }" @click="toggle_repeat_mode">
<span class="icon"><mdicon :name="icon_name" :size="icon_size" /></span>
<mdicon class="icon" :name="icon_name" :size="icon_size" />
</a>
</template>

View File

@ -1,6 +1,6 @@
<template>
<a v-if="visible" :disabled="disabled" @click="seek">
<span class="icon"><mdicon name="rewind" :size="icon_size" /></span>
<mdicon class="icon" name="rewind" :size="icon_size" />
</a>
</template>

View File

@ -1,6 +1,6 @@
<template>
<a v-if="visible" :disabled="disabled" @click="seek">
<span class="icon"><mdicon name="fast-forward" :size="icon_size" /></span>
<mdicon class="icon" name="fast-forward" :size="icon_size" />
</a>
</template>

View File

@ -1,6 +1,6 @@
<template>
<a :class="{ 'is-warning': is_shuffle }" @click="toggle_shuffle_mode">
<span class="icon"><mdicon :name="icon_name" :size="icon_size" /></span>
<mdicon class="icon" :name="icon_name" :size="icon_size" />
</a>
</template>

View File

@ -1,22 +1,19 @@
<template>
<div class="field">
<label class="checkbox">
<label class="switch">
<input
ref="settings_checkbox"
ref="setting"
type="checkbox"
:checked="value"
style="margin-right: 5px"
@change="set_update_timer"
/>
<slot name="label" />
<i
class="is-size-7"
:class="{
'has-text-info': statusUpdate === 'success',
'has-text-danger': statusUpdate === 'error'
}"
>
{{ info }}</i
>
:class="{ 'has-text-info': is_success, 'has-text-danger': is_error }"
v-text="info"
/>
</label>
<p v-if="$slots['info']" class="help">
<slot name="info" />
@ -30,15 +27,12 @@ import * as types from '@/store/mutation_types'
export default {
name: 'SettingsCheckbox',
props: ['category_name', 'option_name'],
data() {
return {
timerDelay: 2000,
timerId: -1,
// <empty>: default/no changes, 'success': update succesful, 'error': update failed
statusUpdate: ''
}
},
@ -65,11 +59,19 @@ export default {
info() {
if (this.statusUpdate === 'success') {
return '(setting saved)'
return this.$t('setting.saved')
} else if (this.statusUpdate === 'error') {
return '(error saving setting)'
return this.$t('setting.not-saved')
}
return ''
},
is_success() {
return this.statusUpdate === 'success'
},
is_error() {
return this.statusUpdate === 'error'
}
},
@ -81,7 +83,7 @@ export default {
}
this.statusUpdate = ''
const newValue = this.$refs.settings_checkbox.checked
const newValue = this.$refs.setting.checked
if (newValue !== this.value) {
this.timerId = window.setTimeout(this.update_setting, this.timerDelay)
}
@ -90,8 +92,8 @@ export default {
update_setting() {
this.timerId = -1
const newValue = this.$refs.settings_checkbox.checked
console.log(this.$refs.settings_checkbox)
const newValue = this.$refs.setting.checked
console.log(this.$refs.setting)
if (newValue === this.value) {
this.statusUpdate = ''
return
@ -110,7 +112,7 @@ export default {
})
.catch(() => {
this.statusUpdate = 'error'
this.$refs.settings_checkbox.checked = this.value
this.$refs.setting.checked = this.value
})
.finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)

View File

@ -5,17 +5,13 @@
<slot name="label" />
<i
class="is-size-7"
:class="{
'has-text-info': statusUpdate === 'success',
'has-text-danger': statusUpdate === 'error'
}"
>
{{ info }}</i
>
:class="{ 'has-text-info': is_success, 'has-text-danger': is_error }"
v-text="info"
/>
</label>
<div class="control">
<input
ref="settings_number"
ref="setting"
class="input"
type="number"
min="0"
@ -38,14 +34,12 @@ import * as types from '@/store/mutation_types'
export default {
name: 'SettingsIntfield',
props: ['category_name', 'option_name', 'placeholder', 'disabled'],
data() {
return {
timerDelay: 2000,
timerId: -1,
statusUpdate: ''
}
},
@ -72,11 +66,19 @@ export default {
info() {
if (this.statusUpdate === 'success') {
return '(setting saved)'
return this.$t('setting.saved')
} else if (this.statusUpdate === 'error') {
return '(error saving setting)'
return this.$t('setting.not-saved')
}
return ''
},
is_success() {
return this.statusUpdate === 'success'
},
is_error() {
return this.statusUpdate === 'error'
}
},
@ -88,7 +90,7 @@ export default {
}
this.statusUpdate = ''
const newValue = this.$refs.settings_number.value
const newValue = this.$refs.setting.value
if (newValue !== this.value) {
this.timerId = window.setTimeout(this.update_setting, this.timerDelay)
}
@ -97,7 +99,7 @@ export default {
update_setting() {
this.timerId = -1
const newValue = this.$refs.settings_number.value
const newValue = this.$refs.setting.value
if (newValue === this.value) {
this.statusUpdate = ''
return
@ -116,7 +118,7 @@ export default {
})
.catch(() => {
this.statusUpdate = 'error'
this.$refs.settings_number.value = this.value
this.$refs.setting.value = this.value
})
.finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)

View File

@ -5,17 +5,13 @@
<slot name="label" />
<i
class="is-size-7"
:class="{
'has-text-info': statusUpdate === 'success',
'has-text-danger': statusUpdate === 'error'
}"
>
{{ info }}</i
>
:class="{ 'has-text-info': is_success, 'has-text-danger': is_error }"
v-text="info"
/>
</label>
<div class="control">
<input
ref="settings_text"
ref="setting"
class="input"
type="text"
:placeholder="placeholder"
@ -43,8 +39,6 @@ export default {
return {
timerDelay: 2000,
timerId: -1,
// <empty>: default/no changes, 'success': update succesful, 'error': update failed
statusUpdate: ''
}
},
@ -71,11 +65,19 @@ export default {
info() {
if (this.statusUpdate === 'success') {
return '(setting saved)'
return this.$t('setting.saved')
} else if (this.statusUpdate === 'error') {
return '(error saving setting)'
return this.$t('setting.not-saved')
}
return ''
},
is_success() {
return this.statusUpdate === 'success'
},
is_error() {
return this.statusUpdate === 'error'
}
},
@ -87,7 +89,7 @@ export default {
}
this.statusUpdate = ''
const newValue = this.$refs.settings_text.value
const newValue = this.$refs.setting.value
if (newValue !== this.value) {
this.timerId = window.setTimeout(this.update_setting, this.timerDelay)
}
@ -96,7 +98,7 @@ export default {
update_setting() {
this.timerId = -1
const newValue = this.$refs.settings_text.value
const newValue = this.$refs.setting.value
if (newValue === this.value) {
this.statusUpdate = ''
return
@ -115,7 +117,7 @@ export default {
})
.catch(() => {
this.statusUpdate = 'error'
this.$refs.settings_text.value = this.value
this.$refs.setting.value = this.value
})
.finally(() => {
this.timerId = window.setTimeout(this.clear_status, this.timerDelay)

View File

@ -4,15 +4,16 @@
<slot name="artwork" />
</div>
<div class="media-content fd-has-action is-clipped">
<h1 class="title is-6">
{{ album.name }}
</h1>
<h1 class="title is-6" v-text="album.name" />
<h2 class="subtitle is-7 has-text-grey">
<b>{{ album.artists[0].name }}</b>
</h2>
<h2 class="subtitle is-7 has-text-grey has-text-weight-normal">
({{ album.album_type }}, {{ $filters.date(album.release_date) }})
<b v-text="album.artists[0].name" />
</h2>
<h2
class="subtitle is-7 has-text-grey has-text-weight-normal"
v-text="
[album.album_type, $filters.date(album.release_date)].join(', ')
"
/>
</div>
<div class="media-right">
<slot name="actions" />

View File

@ -1,9 +1,7 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped" @click="open_artist">
<h1 class="title is-6">
{{ artist.name }}
</h1>
<h1 class="title is-6" v-text="artist.name" />
</div>
<div class="media-right">
<slot name="actions" />

View File

@ -1,12 +1,8 @@
<template>
<div class="media">
<div class="media-content fd-has-action is-clipped" @click="open_playlist">
<h1 class="title is-6">
{{ playlist.name }}
</h1>
<h2 class="subtitle is-7">
{{ playlist.owner.display_name }}
</h2>
<h1 class="title is-6" v-text="playlist.name" />
<h2 class="subtitle is-7" v-text="playlist.owner.display_name" />
</div>
<div class="media-right">
<slot name="actions" />

View File

@ -4,9 +4,8 @@
<h1
class="title is-6"
:class="{ 'has-text-grey-light': track.is_playable === false }"
>
{{ track.name }}
</h1>
v-text="track.name"
/>
<h2
class="subtitle is-7"
:class="{
@ -14,13 +13,18 @@
'has-text-grey-light': track.is_playable === false
}"
>
<b>{{ track.artists[0].name }}</b>
<b v-text="track.artists[0].name" />
</h2>
<h2 v-if="track.is_playable === false" class="subtitle is-7">
(Track is not playable<span
(<span v-text="$t('list.spotify.not-playable-track')" />
<span
v-if="track.restrictions && track.restrictions.reason"
>, restriction reason: {{ track.restrictions.reason }}</span
>)
v-text="
$t('list.spotify.restriction-reason', {
reason: track.restrictions.reason
})
"
/>)
</h2>
</div>
<div class="media-right">
@ -34,9 +38,7 @@ import webapi from '@/webapi'
export default {
name: 'SpotifyListItemTrack',
props: ['track', 'position', 'album', 'context_uri'],
methods: {
play: function () {
webapi.player_play_uri(this.context_uri, false, this.position)

View File

@ -18,45 +18,64 @@
/>
</figure>
<p class="title is-4">
<a class="has-text-link" @click="open_album">{{
album.name
}}</a>
<a
class="has-text-link"
@click="open_album"
v-text="album.name"
/>
</p>
<div class="content is-small">
<p>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{
album.artists[0].name
}}</a>
<span
class="heading"
v-text="$t('dialog.spotify.album.album-artist')"
/>
<a
class="title is-6 has-text-link"
@click="open_artist"
v-text="album.artists[0].name"
/>
</p>
<p>
<span class="heading">Release date</span>
<span class="title is-6">{{
$filters.date(album.release_date)
}}</span>
<span
class="heading"
v-text="$t('dialog.spotify.album.release-date')"
/>
<span
class="title is-6"
v-text="$filters.date(album.release_date)"
/>
</p>
<p>
<span class="heading">Type</span>
<span class="title is-6">{{ album.album_type }}</span>
<span
class="heading"
v-text="$t('dialog.spotify.album.type')"
/>
<span class="title is-6" v-text="album.album_type" />
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.album.add')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.album.add-next')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.album.play')"
/>
</a>
</footer>
</div>

View File

@ -7,40 +7,55 @@
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_artist">{{
artist.name
}}</a>
<a
class="has-text-link"
@click="open_artist"
v-text="artist.name"
/>
</p>
<div class="content is-small">
<p>
<span class="heading">Popularity / Followers</span>
<span class="title is-6"
>{{ artist.popularity }} /
{{ artist.followers.total }}</span
>
<span
class="heading"
v-text="$t('dialog.spotify.artist.popularity')"
/>
<span
class="title is-6"
v-text="
[artist.popularity, artist.followers.total].join(' / ')
"
/>
</p>
<p>
<span class="heading">Genres</span>
<span class="title is-6">{{ artist.genres.join(', ') }}</span>
<span
class="heading"
v-text="$t('dialog.spotify.artist.genres')"
/>
<span class="title is-6" v-text="artist.genres.join(', ')" />
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.artist.add')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.artist.add-next')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.artist.play')"
/>
</a>
</footer>
</div>

View File

@ -7,43 +7,60 @@
<div class="card">
<div class="card-content">
<p class="title is-4">
<a class="has-text-link" @click="open_playlist">{{
playlist.name
}}</a>
<a
class="has-text-link"
@click="open_playlist"
v-text="playlist.name"
/>
</p>
<div class="content is-small">
<p>
<span class="heading">Owner</span>
<span class="title is-6">{{
playlist.owner.display_name
}}</span>
<span
class="heading"
v-text="$t('dialog.spotify.playlist.owner')"
/>
<span
class="title is-6"
v-text="playlist.owner.display_name"
/>
</p>
<p>
<span class="heading">Tracks</span>
<span class="title is-6">{{ playlist.tracks.total }}</span>
<span
class="heading"
v-text="$t('dialog.spotify.playlist.tracks')"
/>
<span class="title is-6" v-text="playlist.tracks.total" />
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ playlist.uri }}</span>
<span
class="heading"
v-text="$t('dialog.spotify.playlist.path')"
/>
<span class="title is-6" v-text="playlist.uri" />
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.playlist.add')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.playlist.add-next')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.playlist.play')"
/>
</a>
</footer>
</div>

View File

@ -6,65 +6,91 @@
<div class="modal-content fd-modal-card">
<div class="card">
<div class="card-content">
<p class="title is-4">
{{ track.name }}
</p>
<p class="subtitle">
{{ track.artists[0].name }}
</p>
<p class="title is-4" v-text="track.name" />
<p class="subtitle" v-text="track.artists[0].name" />
<div class="content is-small">
<p>
<span class="heading">Album</span>
<a class="title is-6 has-text-link" @click="open_album">{{
album.name
}}</a>
<span
class="heading"
v-text="$t('dialog.spotify.track.album')"
/>
<a
class="title is-6 has-text-link"
@click="open_album"
v-text="album.name"
/>
</p>
<p>
<span class="heading">Album artist</span>
<a class="title is-6 has-text-link" @click="open_artist">{{
album.artists[0].name
}}</a>
<span
class="heading"
v-text="$t('dialog.spotify.track.album-artist')"
/>
<a
class="title is-6 has-text-link"
@click="open_artist"
v-text="album.artists[0].name"
/>
</p>
<p>
<span class="heading">Release date</span>
<span class="title is-6">{{
$filters.date(album.release_date)
}}</span>
<span
class="heading"
v-text="$t('dialog.spotify.track.release-date')"
/>
<span
class="title is-6"
v-text="$filters.date(album.release_date)"
/>
</p>
<p>
<span class="heading">Track / Disc</span>
<span class="title is-6"
>{{ track.track_number }} / {{ track.disc_number }}</span
>
<span
class="heading"
v-text="$t('dialog.spotify.track.position')"
/>
<span
class="title is-6"
v-text="[track.disc_number, track.track_number].join(' / ')"
/>
</p>
<p>
<span class="heading">Length</span>
<span class="title is-6">{{
$filters.durationInHours(track.duration_ms)
}}</span>
<span
class="heading"
v-text="$t('dialog.spotify.track.duration')"
/>
<span
class="title is-6"
v-text="$filters.durationInHours(track.duration_ms)"
/>
</p>
<p>
<span class="heading">Path</span>
<span class="title is-6">{{ track.uri }}</span>
<span
class="heading"
v-text="$t('dialog.spotify.track.path')"
/>
<span class="title is-6" v-text="track.uri" />
</p>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item has-text-dark" @click="queue_add">
<span class="icon"
><mdicon name="playlist-plus" size="16"
/></span>
<span class="is-size-7">Add</span>
<mdicon class="icon" name="playlist-plus" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.track.add')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="queue_add_next">
<span class="icon"
><mdicon name="playlist-play" size="16"
/></span>
<span class="is-size-7">Add Next</span>
<mdicon class="icon" name="playlist-play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.track.add-next')"
/>
</a>
<a class="card-footer-item has-text-dark" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span class="is-size-7">Play</span>
<mdicon class="icon" name="play" size="16" />
<span
class="is-size-7"
v-text="$t('dialog.spotify.track.play')"
/>
</a>
</footer>
</div>

View File

@ -12,10 +12,12 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"
><mdicon name="account-music" size="16"
/></span>
<span class="">Authors</span>
<mdicon
class="icon is-small"
name="account-music"
size="16"
/>
<span v-text="$t('tabs.audiobooks.authors')" />
</a>
</li>
</router-link>
@ -26,10 +28,8 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"
><mdicon name="album" size="16"
/></span>
<span class="">Audiobooks</span>
<mdicon class="icon is-small" name="album" size="16" />
<span v-text="$t('tabs.audiobooks.audiobooks')" />
</a>
</li>
</router-link>

View File

@ -12,10 +12,8 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"
><mdicon name="web" size="16"
/></span>
<span class="">Browse</span>
<mdicon class="icon is-small" name="web" size="16" />
<span v-text="$t('page.settings.tabs.music.browse')" />
</a>
</li>
</router-link>
@ -26,10 +24,12 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"
><mdicon name="account-music" size="16"
/></span>
<span class="">Artists</span>
<mdicon
class="icon is-small"
name="account-music"
size="16"
/>
<span v-text="$t('page.settings.tabs.music.artists')" />
</a>
</li>
</router-link>
@ -40,10 +40,8 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"
><mdicon name="album" size="16"
/></span>
<span class="">Albums</span>
<mdicon class="icon is-small" name="album" size="16" />
<span v-text="$t('page.settings.tabs.music.albums')" />
</a>
</li>
</router-link>
@ -54,10 +52,8 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"
><mdicon name="speaker" size="16"
/></span>
<span class="">Genres</span>
<mdicon class="icon is-small" name="speaker" size="16" />
<span v-text="$t('page.settings.tabs.music.genres')" />
</a>
</li>
</router-link>
@ -68,10 +64,12 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"
><mdicon name="book-open-page-variant" size="16"
/></span>
<span class="">Composers</span>
<mdicon
class="icon is-small"
name="book-open-page-variant"
size="16"
/>
<span v-text="$t('page.settings.tabs.music.composers')" />
</a>
</li>
</router-link>
@ -83,10 +81,8 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="icon is-small"
><mdicon name="spotify" size="16"
/></span>
<span class="">Spotify</span>
<mdicon class="icon is-small" name="spotify" size="16" />
<span v-text="$t('page.settings.tabs.music.spotify')" />
</a>
</li>
</router-link>

View File

@ -11,10 +11,8 @@
}"
>
<a @click="search_library">
<span class="icon is-small"
><mdicon name="bookshelf" size="16"
/></span>
<span class="">Library</span>
<mdicon class="icon is-small" name="bookshelf" size="16" />
<span v-text="$t('tabs.search.library')" />
</a>
</li>
<li
@ -23,10 +21,8 @@
}"
>
<a @click="search_spotify">
<span class="icon is-small"
><mdicon name="spotify" size="16"
/></span>
<span class="">Spotify</span>
<mdicon class="icon is-small" name="spotify" size="16" />
<span v-text="$t('tabs.search.spotify')" />
</a>
</li>
</ul>

View File

@ -12,7 +12,7 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="">Webinterface</span>
<span v-text="$t('page.settings.tabs.settings.general')" />
</a>
</li>
</router-link>
@ -23,7 +23,11 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="">Remotes &amp; Outputs</span>
<span
v-text="
$t('page.settings.tabs.settings.remotes-and-outputs')
"
/>
</a>
</li>
</router-link>
@ -34,7 +38,7 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="">Artwork</span>
<span v-text="$t('page.settings.tabs.settings.artwork')" />
</a>
</li>
</router-link>
@ -45,7 +49,9 @@
>
<li :class="{ 'is-active': isActive }">
<a @click="navigate" @keypress.enter="navigate">
<span class="">Online Services</span>
<span
v-text="$t('page.settings.tabs.settings.online-services')"
/>
</a>
</li>
</router-link>
@ -59,9 +65,7 @@
<script>
export default {
name: 'TabsSettings',
computed: {}
name: 'TabsSettings'
}
</script>

15
web-src/src/i18n.js Normal file
View File

@ -0,0 +1,15 @@
import { createI18n } from 'vue-i18n'
/*
* All i18n resources specified in the plugin `include` option can be loaded
* at once using the import syntax.
*/
import messages from '@intlify/vite-plugin-vue-i18n/messages'
export default createI18n({
legacy: false,
globalInjection: true,
locale: 'en',
fallbackLocale: 'en',
messages
})

View File

@ -2,9 +2,10 @@ import {
mdiAccountMusic,
mdiAlbum,
mdiArrowCollapseDown,
mdiBookOpenVariant,
mdiBookOpenPageVariant,
mdiBookOpenVariant,
mdiBookshelf,
mdiBroadcast,
mdiCancel,
mdiCast,
mdiCastVariant,
@ -26,16 +27,16 @@ import {
mdiFolderOpen,
mdiMagnify,
mdiMenu,
mdiMicrophone,
mdiMusic,
mdiMusicBoxMultiple,
mdiOpenInNew,
mdiPause,
mdiPencil,
mdiPipe,
mdiPlay,
mdiPlaylistPlay,
mdiPlaylistPlus,
mdiRadio,
mdiPodcast,
mdiRadioTower,
mdiRefresh,
mdiRepeat,
@ -61,9 +62,10 @@ export const icons = {
mdiAccountMusic,
mdiAlbum,
mdiArrowCollapseDown,
mdiBookOpenVariant,
mdiBookOpenPageVariant,
mdiBookOpenVariant,
mdiBookshelf,
mdiBroadcast,
mdiCancel,
mdiCast,
mdiCastVariant,
@ -85,16 +87,16 @@ export const icons = {
mdiFolderOpen,
mdiMagnify,
mdiMenu,
mdiMicrophone,
mdiMusic,
mdiMusicBoxMultiple,
mdiOpenInNew,
mdiPause,
mdiPencil,
mdiPipe,
mdiPlay,
mdiPlaylistPlay,
mdiPlaylistPlus,
mdiRadio,
mdiPodcast,
mdiRadioTower,
mdiRefresh,
mdiRepeat,

554
web-src/src/locales/en.json Normal file
View File

@ -0,0 +1,554 @@
{
"data": {
"kind": {
"file": "File",
"url": "URL",
"spotify": "Spotify",
"pipe": "Stream"
}
},
"dialog": {
"cancel": "Cancel",
"add": {
"rss": {
"add": "Add",
"cancel": "Cancel",
"help": "Adding a podcast includes creating an RSS playlist, that will allow OwnTone to manage the podcast subscription.",
"placeholder": "https://url-to-rss",
"processing": "Processing…",
"title": "Add Podcast RSS feed URL"
},
"stream": {
"add": "Add",
"cancel": "Cancel",
"loading": "Loading…",
"placeholder": "https://url-to-stream",
"play": "Play",
"title": "Add Stream URL"
}
},
"album": {
"add-next": "Add Next",
"add": "Add",
"added-on": "Added on",
"artist": "Album artist",
"duration": "Duration",
"mark-as-played": "Mark as played",
"play": "Play",
"release-date": "Release date",
"remove-podcast": "Remove podcast",
"tracks": "Track count",
"type": "Type",
"year": "Year"
},
"artist": {
"add-next": "Add Next",
"add": "Add",
"added-on": "Added On",
"albums": "Albums",
"play": "Play",
"tracks": "Tracks",
"type": "Type"
},
"composer": {
"add-next": "Add Next",
"add": "Add",
"albums": "Albums",
"duration": "Duration",
"play": "Play",
"tracks": "Tracks"
},
"directory": {
"add-next": "Add Next",
"add": "Add",
"play": "Play"
},
"genre": {
"add-next": "Add Next",
"add": "Add",
"albums": "Albums",
"duration": "Duration",
"play": "Play",
"tracks": "Tracks"
},
"playlist": {
"add-next": "Add Next",
"add": "Add",
"play": "Play",
"track-count": "Track Count",
"type": "Type",
"save": {
"cancel": "Cancel",
"save": "Save",
"saving": "Saving…",
"title": "Save queue to playlist"
}
},
"queue-item": {
"album-artist": "Album Artist",
"album": "Album",
"bitrate": "{'|'} {rate} kbit/s",
"channels": "{'|'} {channels}",
"composer": "Composer",
"duration": "Duration",
"genre": "Genre",
"path": "Path",
"play": "Play",
"position": "Disc / Track",
"quality": "Quality",
"remove": "Remove",
"samplerate": "{'|'} {rate} Hz",
"spotify-album": "album",
"spotify-artist": "artist",
"type": "Type",
"year": "Year"
},
"remote-pairing": {
"cancel": "Cancel",
"pair": "Pair Remote",
"title": "Remote pairing request"
},
"spotify": {
"album": {
"add-next": "Add Next",
"add": "Add",
"album-artist": "Album Artist",
"play": "Play",
"release-date": "Release Date",
"type": "Type"
},
"artist": {
"add-next": "Add Next",
"add": "Add",
"genres": "Genres",
"play": "Play",
"popularity": "Popularity / Followers"
},
"playlist": {
"add-next": "Add Next",
"add": "Add",
"owner": "Owner",
"path": "Path",
"play": "Play",
"tracks": "Tracks"
},
"track": {
"add-next": "Add Next",
"add": "Add",
"album-artist": "Album Artist",
"album": "Album",
"duration": "Duration",
"path": "Path",
"play": "Play",
"position": "Disc / Track",
"release-date": "Release Date"
}
},
"track": {
"add-next": "Add Next",
"add": "Add",
"added-on": "Added On",
"album-artist": "Album Artist",
"album": "Album",
"bitrate": " {'|'} {rate} Kb/s",
"channels": " {'|'} {channels}",
"comment": "Comment",
"composer": "Composer",
"duration": "Duration",
"genre": "Genre",
"mark-as-new": "Mark as new",
"mark-as-played": "Mark as played",
"path": "Path",
"play": "Play",
"position": "Disc / Track",
"quality": "Quality",
"rating-value": "{rating} / 10",
"rating": "Rating",
"release-date": "Release Date",
"samplerate": " {'|'} {rate} Hz",
"spotify-album": "album",
"spotify-artist": "artist",
"type": "Type",
"year": "Year"
},
"update": {
"all": "Update everything",
"cancel": "Cancel",
"feeds": "Only update RSS feeds",
"info": "Scan for new, deleted and modified files",
"local": "Only update local library",
"progress": "Library update in progress…",
"rescan-metadata": "Rescan metadata of unmodified files",
"rescan": "Rescan",
"spotify": "Only update Spotify",
"title": "Library update"
}
},
"language": {
"en": "English",
"fr": "French (Français)"
},
"list": {
"albums": {
"info-1": "Permanently remove this podcast from your library?",
"info-2": "This will also remove the RSS playlist ",
"notification": "Podcast cannot be removed. Probably it was not added as an RSS playlist."
},
"spotify": {
"not-playable-track": "Track is not playable",
"restriction-reason": ", restriction reason: {reason}"
}
},
"media": {
"kind": {
"album": "Album",
"audiobook": "Audiobook",
"music": "Music",
"podcast": "Podcast"
}
},
"navigation": {
"about": "About",
"albums": "Albums",
"artists": "Artists",
"audiobooks": "Audiobooks",
"now-playing": " - {album}",
"stream-error": "HTTP stream error: failed to load stream or stopped loading due to network problem",
"stream": "HTTP stream",
"volume": "Volume",
"files": "Files",
"genres": "Genres",
"music": "Music",
"playlists": "Playlists",
"podcasts": "Podcasts",
"radio": "Radio",
"search": "Search",
"settings": "Settings",
"spotify": "Spotify",
"title": "OwnTone",
"update-library": "Update Library"
},
"page": {
"about": {
"albums": "Albums",
"artists": "Artists",
"built-with": "Web interface built with <a href=\"https://bulma.io\">Bulma</a>, <a href=\"https://materialdesignicons.com/\">Material Design Icons</a>, <a href=\"https://vuejs.org/\">Vue.js</a>, <a href=\"https://github.com/mzabriskie/axios\">axios</a> and <a href=\"https://github.com/owntone/owntone-server/network/dependencies\">more</a>.",
"compiled-with": "Compiled with support for {options}.",
"library": "Library",
"total-playtime": "Total playtime",
"tracks": "Tracks",
"update": "Update",
"updated-on": "{time} ago",
"updated": "Updated",
"uptime": "Uptime",
"version": "Version {version}"
},
"album": {
"shuffle": "Shuffle",
"track-count": "{count} tracks"
},
"albums": {
"count": "{count} albums",
"filter": "Filter",
"hide-singles-help": "If active, hides singles and albums with tracks that only appear in playlists.",
"hide-singles": "Hide singles",
"hide-spotify-help": "If active, hides albums that only appear in your Spotify library.",
"hide-spotify": "Hide albums from Spotify",
"title": "Albums",
"sort-by": {
"title": "Sort by",
"name": "Name",
"recently-added": "Recently added",
"recently-released": "Recently released"
}
},
"artist": {
"album-count": "{count} albums {'|'} ",
"shuffle": "Shuffle",
"track-count": "{count} tracks",
"sort-by": {
"title": "Sort by",
"name": "Name",
"release-date": "Release date"
}
},
"artists": {
"count": "{count} artists",
"filter": "Filter",
"sort-by": {
"title": "Sort by",
"name": "Name",
"recently-added": "Rencently added"
},
"hide-singles-help": "If active, hides artists that only appear on singles or playlists.",
"hide-singles": "Hide singles",
"hide-spotify-help": "If active, hides artists that only appear in your Spotify library.",
"hide-spotify": "Hide artists from Spotify",
"title": "Artists"
},
"audiobooks": {
"album": {
"play": "Play",
"track-count": "{count} tracks"
},
"albums": {
"count": "{count} audiobooks",
"title": "Audiobooks"
},
"artist": {
"albums-count": "{count} albums",
"shuffle": "Shuffle"
},
"artists": {
"count": "{count} authors",
"title": "Authors"
}
},
"browse": {
"albums": "albums",
"show-more": "Show more",
"tracks": "tracks",
"recently-added": {
"albums": "albums",
"title": "Recently added"
},
"recently-played": {
"title": "Recently played",
"tracks": "tracks"
}
},
"composer": {
"album-count": "{count} albums {'|'}",
"shuffle": "Shuffle",
"track-count": "{count} tracks",
"tracks": {
"album-count": "{count} albums",
"track-count": " {'|'} {count} tracks",
"shuffle": "Shuffle"
}
},
"composers": {
"count": "{count} composers",
"title": "Composers"
},
"files": {
"play": "Play",
"title": "Files"
},
"genre": {
"album-count": "{count} albums {'|'} ",
"shuffle": "Shuffle",
"tracks": {
"album-count": "{count} albums {'|'} ",
"count": " {count} tracks",
"shuffle": "Shuffle"
}
},
"genres": {
"count": "{count} genres",
"title": "Genres"
},
"now-playing": {
"info": "Add some tracks by browsing your library",
"title": "Your play queue is empty"
},
"playlist": {
"length": "{length} tracks",
"shuffle": "Shuffle"
},
"playlists": {
"count": "{count} playlists"
},
"podcast": {
"play": "Play",
"remove-error": "Podcast cannot be removed. Probably it was not added as an RSS playlist.",
"remove-info-1": "Permanently remove this podcast from your library?",
"remove-info-2": "This will also remove the RSS playlist ",
"track-count": "{count} tracks"
},
"podcasts": {
"add": "Add",
"count": "{count} podcasts",
"mark-all-played": "Mark All Played",
"new-episodes": "New Episodes",
"title": "Podcasts",
"update": "Update"
},
"queue": {
"add-stream": "Add stream",
"clear": "Clear",
"count": "{count} tracks",
"edit": "Edit",
"hide-previous": "Hide previous",
"title": "Queue"
},
"radio": {
"count": "{count} stations",
"title": "Radio"
},
"search": {
"albums": "Albums",
"artists": "Artists",
"audiobooks": "Audiobooks",
"composers": "Composers",
"help": "Tip: you can search by a smart playlist query language <a href=\"https://github.com/owntone/owntone-server/blob/master/README_SMARTPL.md\" target=\"_blank\">expression</a> if you prefix it with <code>query:</code>.",
"no-albums": "No albums found",
"no-artists": "No artists found",
"no-audiobooks": "No audiobooks found",
"no-composers": "No composers found",
"no-playlists": "No playlists found",
"no-podcasts": "No podcasts found",
"no-tracks": "No tracks found",
"playlists": "Playlists",
"podcasts": "Podcasts",
"show-albums": "Show all {count} albums",
"show-artists": "Show all {count} artists",
"show-audiobooks": "Show all {count} audiobooks",
"show-composers": "Show all {count} composers",
"show-playlists": "Show all {count} playlists",
"show-podcasts": "Show all {count} podcasts",
"show-tracks": "Show all {count} tracks",
"tracks": "Tracks"
},
"settings": {
"artwork": {
"artwork": "Artwork",
"coverartarchive": "Cover Art Archive",
"discogs": "Discogs",
"explanation-1": "OwnTone supports PNG and JPEG artwork which is either placed as separate image files in the library, embedded in the media files or made available online by radio stations.",
"explanation-2": "In addition to that, you can enable fetching artwork from the following artwork providers:",
"spotify": "Spotify"
},
"devices": {
"no-active-pairing": "No active pairing request.",
"pairing-request": "Remote pairing request from ",
"pairing": "Remote Pairing",
"send": "Send",
"speaker-pairing-info": "If your speaker requires pairing then activate it below and enter the PIN that it displays.",
"speaker-pairing": "Speaker pairing and device verification",
"verification-code": "Enter verification code",
"verify": "Verify"
},
"general": {
"album-lists": "Album Lists",
"audiobooks": "Audiobooks",
"files": "Audio Files",
"language": "Language",
"music": "Music",
"navigation-item-selection-info": "If you select more items than can be shown on your screen then the burger menu will disappear.",
"navigation-item-selection": "Select the top navigation bar menu items",
"navigation-items": "Navigation Bar",
"now-playing-page": "Now playing page",
"playlists": "Playlists",
"podcasts": "Podcasts",
"radio": "Radio",
"recently-added-page-info": "Limit the number of albums shown on the \"Recently Added\" page",
"recently-added-page": "Recently added page",
"search": "Search",
"show-composer-genres-info-1": "Comma separated list of genres the composer should be displayed on the \"now playing page\"",
"show-composer-genres-info-2": "Leave empty to always show the composer.",
"show-composer-genres-info-3": "The genre tag of the current track is matched by checking, if one of the defined genres are included. For example setting to \"classical, soundtrack\" will show the composer for tracks with a genre tag of \"Contemporary Classical\"",
"show-composer-genres": "Show composer only for listed genres",
"show-composer-info": "If enabled the composer of the current playing track is shown on the \"now playing page\"",
"show-composer": "Show composer",
"show-coverart": "Show cover artwork in album list"
},
"services": {
"lastfm": {
"grant-access": "<b>Last.fm</b> - Login with your Last.fm username and password to enable scrobbling",
"info": "OwnTone will not store your Last.fm username/password, only the session key. The session key does not expire.",
"title": "Last.fm",
"no-support": "OwnTone was built without support for Last.fm.",
"stop-scrobbling": "Stop scrobbling"
},
"spotify": {
"no-support": "OwnTone was either built without support for Spotify or libspotify is not installed.",
"help": "If you normally log into Spotify with your Facebook account you must first go to Spotify's web site where you can get the Spotify username and password that matches your account.",
"logged-as": "Logged in as ",
"requirements": "You must have a Spotify premium account.",
"scopes": "Access to the Spotify Web API enables scanning of your Spotify library. Required scopes are: ",
"user": "Access granted for ",
"authorize": "Authorize Web API access",
"credentials": " - Login with your Spotify username and password",
"grant-access": "<b>Spotify Web API</b> - Grant access to the Spotify Web API",
"help-1": "libspotify enables OwnTone to play Spotify tracks.",
"help-2": "OwnTone will not store your password, but will still be able to log you in automatically afterwards, because libspotify saves a login token.",
"reauthorize": "Please reauthorize Web API access to grant OwnTone the following additional access rights: ",
"title": "Spotify"
},
"login": "Login",
"logout": "Logout"
},
"tabs": {
"music": {
"albums": "Albums",
"artists": "Artists",
"browse": "Browse",
"composers": "Composers",
"genres": "Genres",
"spotify": "Spotify"
},
"search": {
"library": "Library",
"spotify": "Spotify"
},
"settings": {
"artwork": "Artwork",
"general": "General",
"online-services": "Online Services",
"remotes-and-outputs": "Remotes and Outputs"
}
}
},
"spotify": {
"album": {
"shuffle": "Shuffle",
"track-count": "{count} tracks"
},
"artist": {
"album-count": "{count} albums",
"shuffle": "Shuffle"
},
"browse": {
"featured-playlists": "Featured Playlists",
"new-releases": "New Releases",
"show-more": "Show More"
},
"playlist": {
"count": "{count} tracks",
"shuffle": "Shuffle"
},
"search": {
"albums": "Albums",
"artists": "Artists",
"no-albums": "No albums found",
"no-artists": "No artists found",
"no-playlists": "No playlists found",
"no-tracks": "No tracks found",
"playlists": "Playlists",
"show-all-albums": "Show all {count} albums",
"show-all-artists": "Show all {count} artists",
"show-all-playlists": "Show all {count} playlists",
"show-all-tracks": "Show all {count} tracks",
"tracks": "Tracks"
}
}
},
"setting": {
"not-saved": " (error saving setting)",
"saved": " (setting saved)"
},
"server": {
"connection-failed": "Failed to connect to OwnTone server",
"missing-port": "Missing websocket port",
"request-failed": "Request failed (status: {status} {cause} {url})",
"queue-saved": "Queue saved to playlist {name}",
"appended-tracks": "{count} tracks appended to the queue",
"empty-queue": "Queue is empty"
},
"group-by-list": {
"today": "Today",
"last-week": "Last week",
"last-month": "Last month"
}
}

554
web-src/src/locales/fr.json Normal file
View File

@ -0,0 +1,554 @@
{
"data": {
"kind": {
"file": "Fichier",
"url": "URL",
"spotify": "Spotify",
"pipe": "Flux"
}
},
"dialog": {
"cancel": "Annuler",
"add": {
"rss": {
"add": "Ajouter",
"cancel": "Annuler",
"help": "Lajout dun podcast inclut la création dune liste de lecture RSS, qui permettra à OwnTone de gérer labonnement au podcast.",
"placeholder": "https://url-du-flux-rss",
"processing": "Traitement en cours…",
"title": "Ajouter lURL du flux RSS du podcast"
},
"stream": {
"add": "Ajouter",
"cancel": "Annuler",
"loading": "Chargement…",
"placeholder": "https://url-du-flux",
"play": "Lire",
"title": "Ajouter lURL du flux"
}
},
"album": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"added-on": "Ajouté le",
"artiste": "Album artist",
"duration": "Durée",
"mark-as-played": "Marquer comme lu",
"play": "Lire",
"release-date": "Date de sortie",
"remove-podcast": "Supprimer le podcast",
"pistes": "Pistes",
"type": "Type",
"year": "Année"
},
"artiste": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"added-on": "Ajouté le",
"albums": "Albums",
"play": "Lire",
"pistes": "Pistes",
"type": "Type"
},
"composer": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"albums": "Albums",
"duration": "Durée",
"play": "Lire",
"pistes": "Pistes"
},
"directory": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"play": "Lire"
},
"genre": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"albums": "Albums",
"duration": "Durée",
"play": "Lire",
"pistes": "Pistes"
},
"playlist": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"play": "Lire",
"track-count": "Pistes",
"type": "Type",
"save": {
"cancel": "Annuler",
"save": "Enregistrer",
"saving": "Enregistrement en cours…",
"title": "Enregister la file dattente dans une liste de lecture"
}
},
"queue-item": {
"album-artist": "Artiste de lalbum",
"album": "Album",
"bitrate": "{'|'} {rate} kbit/s",
"channels": "{'|'} {channels}",
"composer": "Compositeur",
"duration": "Durée",
"genre": "Genre",
"path": "Emplacement",
"play": "Lire",
"position": "Disque / Piste",
"quality": "Qualité",
"remove": "Supprimer",
"samplerate": "{'|'} {rate} Hz",
"spotify-album": "album",
"spotify-artist": "artiste",
"type": "Type",
"year": "Année"
},
"remote-pairing": {
"cancel": "Annuler",
"pair": "Jumeler la télécommande",
"title": "Demande de jumelage de télécommande"
},
"spotify": {
"album": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"album-artist": "Artiste de lalbum",
"play": "Lire",
"release-date": "Date de sortie",
"type": "Type"
},
"artiste": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"genres": "Genres",
"play": "Lire",
"popularity": "Popularités / Followers"
},
"playlist": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"owner": "Propriéataire",
"path": "Emplacement",
"play": "Lire",
"pistes": "Pistes"
},
"track": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"album-artist": "Artiste de lalbum",
"album": "Album",
"duration": "Durée",
"path": "Emplacement",
"play": "Lire",
"position": "Disque / Piste",
"release-date": "Date de sortie"
}
},
"track": {
"add-next": "Ajouter ensuite",
"add": "Ajouter",
"added-on": "Ajouté le",
"album-artist": "Artiste de lalbum",
"album": "Album",
"bitrate": " {'|'} {rate} Kb/s",
"channels": " {'|'} {channels}",
"comment": "Commentaire",
"composer": "Compositeur",
"duration": "Durée",
"genre": "Genre",
"mark-as-new": "Marquer comme nouveau",
"mark-as-played": "Marquer comme lu",
"path": "Emplacement",
"play": "Lire",
"position": "Disque / Piste",
"quality": "Qualité",
"rating-value": "{rating} / 10",
"rating": "Classement",
"release-date": "Date de sortie",
"samplerate": " {'|'} {rate} Hz",
"spotify-album": "album",
"spotify-artist": "artiste",
"type": "Type",
"year": "Année"
},
"update": {
"all": "Tout actualiser",
"cancel": "Annuler",
"feeds": "Actualiser uniquement les flux RSS",
"info": "Recherche les fichiers ajoutés, supprimés et modifiés",
"local": "Actualiser uniquement la bibliothèque locale",
"progress": "Actualisation de la bibliothèque en cours…",
"rescan-metadata": "Analyser les métadonnées des fichiers non modifiés",
"rescan": "Analyser",
"spotify": "Actualiser uniquement Spotify",
"title": "Actualisation de la bibliothèque"
}
},
"language": {
"en": "Anglais (English)",
"fr": "Français"
},
"list": {
"albums": {
"info-1": "Supprimer définitivement ce podcast de votre bibliothèque ?",
"info-2": "Cela supprimera également la liste de lecture RSS ",
"notification": "Le podcast ne peut être supprimé. Il navait probablement pas été ajouté comme une liste de lecture RSS."
},
"spotify": {
"not-playable-track": "La piste ne peut pas être lue",
"restriction-reason": ", raison de la restriction : {reason}"
}
},
"media": {
"kind": {
"album": "Album",
"audiobook": "Livre audio",
"music": "Musique",
"podcast": "Podcast"
}
},
"navigation": {
"about": "À propos",
"albums": "Albums",
"artists": "Artistes",
"audiobooks": "Livres audio",
"now-playing": " - {album}",
"stream-error": "Erreur du flux HTTP : échec du chargement du flux ou arrêt du chargement en raison dun problème réseau",
"stream": "Flux HTTP",
"volume": "Volume",
"files": "Fichiers",
"genres": "Genres",
"music": "Musique",
"playlists": "Listes de lecture",
"podcasts": "Podcasts",
"radio": "Radio",
"search": "Recherche",
"settings": "Réglages",
"spotify": "Spotify",
"title": "OwnTone",
"update-library": "Actualiser la bibliothèque"
},
"page": {
"about": {
"albums": "Albums",
"artists": "Artistes",
"built-with": "Interface utilisateur construite avec <a href=\"https://bulma.io\">Bulma</a>, <a href=\"https://materialdesignicons.com/\">Material Design Icons</a>, <a href=\"https://vuejs.org/\">Vue.js</a>, <a href=\"https://github.com/mzabriskie/axios\">axios</a> et <a href=\"https://github.com/owntone/owntone-server/network/dependencies\">plus</a>.",
"compiled-with": "Compilé avec les options {options}.",
"library": "Bibliothèque",
"total-playtime": "Durée totale de lecture",
"pistes": "Pistes",
"update": "Actualiser",
"updated-on": "il y a {time}",
"updated": "Mis à jour",
"uptime": "Temps de fonctionnement",
"name": "Version {version}"
},
"album": {
"shuffle": "Lecture aléatoire",
"track-count": "{count} pistes"
},
"albums": {
"count": "{count} albums",
"filter": "Filtre",
"hide-singles-help": "Si actif, masque les singles et les albums dont les pistes napparaissent que dans les listes de lecture.",
"hide-singles": "Masquer les singles",
"hide-spotify-help": "Si actif, masque les albums qui napparaissent que dans votre bibliothèque Spotify.",
"hide-spotify": "Masquer les albums de Spotify",
"title": "Albums",
"sort-by": {
"title": "Trier par",
"name": "Nom",
"recently-added": "Ajouts récents",
"recently-released": "Sorties récentes"
}
},
"artist": {
"album-count": "{count} albums {'|'} ",
"shuffle": "Lecture aléatoire",
"track-count": "{count} pistes",
"sort-by": {
"title": "Trier par",
"name": "Nom",
"release-date": "Date de sortie"
}
},
"artists": {
"count": "{count} artists",
"filter": "Filtre",
"sort-by": {
"title": "Trier par",
"name": "Nom",
"recently-added": "Ajouts récents"
},
"hide-singles-help": "Si actif, masque les artistes qui napparaissent que dans des singles ou des listes de lecture.",
"hide-singles": "Masquer les singles",
"hide-spotify-help": "Si actif, masque les artistes qui napparaissent que dans votre bibliothèque Spotify.",
"hide-spotify": "Masquer les artistes de Spotify",
"title": "Artistes"
},
"audiobooks": {
"album": {
"play": "Lire",
"track-count": "{count} pistes"
},
"albums": {
"count": "{count} livres audio",
"title": "Livres audio"
},
"artiste": {
"albums-count": "{count} albums",
"shuffle": "Lecture aléatoire"
},
"artists": {
"count": "{count} auteurs",
"title": "Auteurs"
}
},
"browse": {
"albums": "albums",
"show-more": "Afficher plus",
"pistes": "pistes",
"recently-added": {
"albums": "albums",
"title": "Ajouts récents"
},
"recently-played": {
"title": "Lectures récentes",
"pistes": "pistes"
}
},
"composer": {
"album-count": "{count} albums {'|'}",
"shuffle": "Lecture aléatoire",
"track-count": "{count} pistes",
"pistes": {
"album-count": "{count} albums",
"track-count": " {'|'} {count} pistes",
"shuffle": "Lecture aléatoire"
}
},
"composers": {
"count": "{count} compositeurs",
"title": "Compositeurs"
},
"files": {
"play": "Lire",
"title": "Fichiers"
},
"genre": {
"album-count": "{count} albums {'|'} ",
"shuffle": "Lecture aléatoire",
"pistes": {
"album-count": "{count} albums {'|'} ",
"count": " {count} pistes",
"shuffle": "Lecture aléatoire"
}
},
"genres": {
"count": "{count} genres",
"title": "Genres"
},
"now-playing": {
"info": "Ajoutez des pistes en parcourant votre bibliothèque",
"title": "La file dattente est vide"
},
"playlist": {
"length": "{length} pistes",
"shuffle": "Lecture aléatoire"
},
"playlists": {
"count": "{count} listes de lecture"
},
"podcast": {
"play": "Lire",
"remove-error": "Le podcast ne peut pas être supprimé. Il na probablement pas été ajouté en tant que liste de lecture RSS.",
"remove-info-1": "Supprimer ce podcast de manière permanente de la bibliothèque ?",
"remove-info-2": "Cela supprimera également la liste de lecture RSS ",
"track-count": "{count} pistes"
},
"podcasts": {
"add": "Ajouter",
"count": "{count} podcasts",
"mark-all-played": "Marquer comme lus",
"new-episodes": "Nouveaux épisodes",
"title": "Podcasts",
"update": "Actualiser"
},
"queue": {
"add-stream": "Ajouter un flux",
"clear": "Effacer",
"count": "{count} pistes",
"edit": "Éditer",
"hide-previous": "Masquer lhistorique",
"queue": "File dattente"
},
"radio": {
"count": "{count} stations",
"title": "Radio"
},
"search": {
"albums": "Albums",
"artists": "Artistes",
"audiobooks": "Livres audio",
"composers": "Compositeurs",
"help": "Astuce : en préfixant votre requête avec <code>query:</code>, vous pouvez effectuer une recherche avec une <a href=\"https://github.com/owntone/owntone-server/blob/master/README_SMARTPL.md\" target=\"_blank\">expression</a> du langage de requête de liste de lecture intelligente.",
"no-albums": "Aucun album trouvé",
"no-artists": "Aucun artiste trouvé",
"no-audiobooks": "Aucun livre audio trouvé",
"no-composers": "Aucun compositeur trouvé",
"no-playlists": "Aucune liste de lecture trouvée",
"no-podcasts": "Aucune podcast trouvé",
"no-tracks": "Aucune piste trouvée",
"playlists": "Listes de lecture",
"podcasts": "Podcasts",
"show-albums": "Afficher les {count} albums",
"show-artists": "Afficher les {count} artistes",
"show-audiobooks": "Afficher les {count} livres audio",
"show-composers": "Afficher les {count} compositeurs",
"show-playlists": "Afficher les {count} listes de lecture",
"show-podcasts": "Afficher les {count} podcasts",
"show-tracks": "Afficher les {count} pistes",
"pistes": "Pistes"
},
"settings": {
"artwork": {
"artwork": "Illustrations",
"coverartarchive": "Cover Art Archive",
"discogs": "Discogs",
"explanation-1": "Prend en charge les illustrations au format PNG et JPEG qui sont soit placées dans la bibliothèque en tant que fichiers image séparés, soit intégrées dans les fichiers média, soit mises à disposition en ligne par les stations de radio.",
"explanation-2": "En outre, vous pouvez activer la récupération des illustrations à partir des fournisseurs dillustrations suivants :",
"spotify": "Spotify"
},
"devices": {
"no-active-pairing": "Aucune demande de jumelage active.",
"pairing-request": "Demande de jumelage de télécommande ",
"pairing": "Jumelage de télécommande",
"send": "Envoyer",
"speaker-pairing-info": "Si votre enceinte nécessite un jumelage, activez-la ci-dessous et entrez le code PIN quelle affiche.",
"speaker-pairing": "Jumelage denceinte et vérification dappareil",
"verification-code": "Code de vérification",
"verify": "Vérifier"
},
"general": {
"album-lists": "Listes dalbum",
"audiobooks": "Livres audio",
"files": "Fichiers audio",
"language": "Langue",
"music": "Musique",
"navigation-item-selection-info": "Si vous sélectionnez plus déléments que ce qui peut être affiché sur votre écran, le menu disparaîtra.",
"navigation-item-selection": "Sélectionnez les éléments de la barre de navigation supérieure",
"navigation-items": "Barre de navigation",
"now-playing-page": "Now playing page",
"playlists": "Listes de lecture",
"podcasts": "Podcasts",
"radio": "Radio",
"recently-added-page-info": "Limiter le nombre dalbum affichés dans la section \"Ajouts récents\"",
"recently-added-page": "Recently added page",
"search": "Search",
"show-composer-genres-info-1": "Liste des genres, séparés par des virgules, que le compositeur doit afficher sur la page \"Lecture en cours\".",
"show-composer-genres-info-2": "Laissez vide pour toujours afficher le compositeur.",
"show-composer-genres-info-3": "Létiquette de genre de la piste actuelle est comparée en vérifiant si lun des genres définis est inclus. Par exemple, en choisissant \"classique, bande sonore\", le compositeur pour les pistes dont létiquette de genre est \"classique contemporain\" sera affiché.",
"show-composer-genres": "Afficher le compositeur uniquement pour les genres listés",
"show-composer-info": "Si actif, le compositeur de la piste en cours de lecture est affiché sur la page \"lecture en cours\"",
"show-composer": "Afficher le compositeur",
"show-coverart": "Afficher les illustration dans la liste dalbums"
},
"services": {
"lastfm": {
"grant-access": "<b>Last.fm</b> - Connectez-vous avec votre nom dutilisateur et votre mot de passe Last.fm pour activer le scrobbling.",
"info": "Le nom dutilisateur et le mot de passe Last.fm ne sont pas enregistrés, uniquement la clé de session. La clé de session nexpire pas.",
"title": "Last.fm",
"no-support": "Loption Last.fm nest pas présente.",
"stop-scrobbling": "Arrêter le scrobbling"
},
"spotify": {
"no-support": "Loption Spotify nest pas présente.",
"help": "Si vous vous connectez habituellement à Spotify avec votre compte Facebook, vous devez dabord vous rendre sur le site de Spotify pour obtenir le nom dutilisateur et le mot de passe correspondant à votre compte.",
"logged-as": "Connecté en tant que ",
"requirements": "Vous devez posséder un compte Spotify Premium.",
"scopes": "Laccès à lAPI de Spotify permet lanalyse de votre bibliothèque Spotify. Les champs dapplication requis sont les suivants :",
"user": "Accès autorisé pour ",
"authorize": "Autoriser laccès à lAPI",
"credentials": " - Connectez-vous avec votre nom dutilisateur et mot de passe Spotify",
"grant-access": "<b>Spotify</b> - Accordez laccès à lAPI de Spotify",
"help-1": "La bibliothèque libspotify permet de lire les pistes de Spotify.",
"help-2": "Votre nom dutilisateur et votre mot de passe Spotify ne sont pas enregistrés, uniquement le jeton de connexion.",
"reauthorize": "Veuillez autoriser à nouveau laccès à lAPI pour accorder à OwnTone les droits daccès supplémentaires suivants :",
"title": "Spotify"
},
"login": "Se connecter",
"logout": "Se déconnecter"
},
"tabs": {
"music": {
"albums": "Albums",
"artists": "Artistes",
"browse": "Parcourir",
"composers": "Compositeurs",
"genres": "Genres",
"spotify": "Spotify"
},
"search": {
"library": "Library",
"spotify": "Spotify"
},
"settings": {
"artwork": "Illustrations",
"general": "Général",
"online-services": "Services en ligne",
"remotes-and-outputs": "Télécommandes et sorties"
}
}
},
"spotify": {
"album": {
"shuffle": "Lecture aléatoire",
"track-count": "{count} pistes"
},
"artiste": {
"album-count": "{count} albums",
"shuffle": "Lecture aléatoire"
},
"browse": {
"featured-playlists": "Listes de lecture en vedette",
"new-releases": "Nouvelle sorties",
"show-more": "Afficher plus"
},
"playlist": {
"count": "{count} pistes",
"shuffle": "Lecture aléatoire"
},
"search": {
"albums": "Albums",
"artists": "Artistes",
"no-albums": "Aucun album trouvé",
"no-artists": "Aucun artiste trouvé",
"no-playlists": "Aucune liste de lecture trouvée",
"no-tracks": "Aucune piste trouvée",
"playlists": "Listes de lecture",
"show-all-albums": "Afficher les {count} albums",
"show-all-artists": "Afficher les {count} artistes",
"show-all-playlists": "Afficher les {count} listes de lecture",
"show-all-tracks": "Afficher les {count} pistes",
"pistes": "Pistes"
}
}
},
"setting": {
"not-saved": " (erreur à lenregistrement du réglage)",
"saved": " (réglage enregistré)"
},
"server": {
"connection-failed": "Échec de connexion au serveur",
"missing-port": "Port websocket manquant",
"request-failed": "La requête a échoué (status: {status} {cause} {url})",
"queue-saved": "La file dattente enregistrée dans la liste de lecture {name}",
"appended-tracks": "{count} pistes ajoutées à la file dattente",
"empty-queue": "La file dattente est vide"
},
"group-by-list": {
"today": "Aujourdhui",
"last-week": "La semaine dernière",
"last-month": "Le mois dernier"
}
}

View File

@ -1,4 +1,5 @@
import { createApp } from 'vue'
import i18n from './i18n'
import store from './store'
import { router } from './router'
import VueProgressBar from '@aacassandra/vue3-progressbar'
@ -30,6 +31,7 @@ const app = createApp(App)
.use(mdiVue, {
icons: icons
})
.use(i18n)
app.config.globalProperties.$filters = filters
app.mount('#app')

View File

@ -4,10 +4,7 @@
<div class="container">
<div class="columns is-centered">
<div class="column is-four-fifths has-text-centered-mobile">
<p class="heading"><b>OwnTone</b> - version {{ config.version }}</p>
<h1 class="title is-4">
{{ config.library_name }}
</h1>
<h1 class="title is-4" v-text="config.library_name" />
</div>
</div>
</div>
@ -21,65 +18,84 @@
<!-- Left side -->
<div class="level-left">
<div class="level-item">
<h2 class="title is-5">Library</h2>
<h2 class="title is-5" v-text="$t('page.about.library')" />
</div>
</div>
<!-- Right side -->
<div class="level-right">
<div v-if="library.updating">
<a class="button is-small is-loading">Update</a>
<a
class="button is-small is-loading"
v-text="$t('page.about.update')"
/>
</div>
<div v-else>
<a class="button is-small" @click="showUpdateDialog()"
>Update</a
>
<a
class="button is-small"
@click="showUpdateDialog()"
v-text="$t('page.about.update')"
/>
</div>
</div>
</nav>
<table class="table">
<tbody>
<tr>
<th>Artists</th>
<th class="has-text-left" v-text="$t('page.about.artists')" />
<td
class="has-text-right"
v-text="$filters.number(library.artists)"
/>
</tr>
<tr>
<th class="has-text-left" v-text="$t('page.about.albums')" />
<td
class="has-text-right"
v-text="$filters.number(library.albums)"
/>
</tr>
<tr>
<th class="has-text-left" v-text="$t('page.about.tracks')" />
<td
class="has-text-right"
v-text="$filters.number(library.songs)"
/>
</tr>
<tr>
<th class="has-text-left" v-text="$t('page.about.total-playtime')" />
<td
class="has-text-right"
v-text="
$filters.durationInDays(library.db_playtime * 1000)
"
/>
</tr>
<tr>
<th class="has-text-left" v-text="$t('page.about.updated')" />
<td class="has-text-right">
{{ $filters.number(library.artists) }}
<span
v-text="
$t('page.about.updated-on', {
time: $filters.timeFromNow(library.updated_at)
})
"
/>
(<span
class="has-text-grey"
v-text="$filters.datetime(library.updated_at)"
/>)
</td>
</tr>
<tr>
<th>Albums</th>
<th class="has-text-left" v-text="$t('page.about.uptime')" />
<td class="has-text-right">
{{ $filters.number(library.albums) }}
</td>
</tr>
<tr>
<th>Tracks</th>
<td class="has-text-right">
{{ $filters.number(library.songs) }}
</td>
</tr>
<tr>
<th>Total playtime</th>
<td class="has-text-right">
{{ $filters.durationInDays(library.db_playtime * 1000) }}
</td>
</tr>
<tr>
<th>Library updated</th>
<td class="has-text-right">
{{ $filters.timeFromNow(library.updated_at) }} ago
<span class="has-text-grey"
>({{ $filters.datetime(library.updated_at) }})</span
>
</td>
</tr>
<tr>
<th>Uptime</th>
<td class="has-text-right">
{{ $filters.timeFromNow(library.started_at, true) }}
<span class="has-text-grey"
>({{ $filters.datetime(library.started_at) }})</span
>
<span
v-text="$filters.timeFromNow(library.started_at, true)"
/>
(<span
class="has-text-grey"
v-text="$filters.datetime(library.started_at)"
/>)
</td>
</tr>
</tbody>
@ -94,20 +110,16 @@
<div class="columns is-centered">
<div class="column is-four-fifths">
<div class="content has-text-centered-mobile">
<p class="is-size-7">
Compiled with support for {{ config.buildoptions.join(', ') }}.
</p>
<p class="is-size-7">
Web interface built with <a href="http://bulma.io">Bulma</a>,
<a href="https://materialdesignicons.com/"
>Material Design Icons</a
>, <a href="https://vuejs.org/">Vue.js</a>,
<a href="https://github.com/mzabriskie/axios">axios</a> and
<a
href="https://github.com/owntone/owntone-server/network/dependencies"
>more</a
>.
</p>
<p class="is-size-7" v-text="$t('page.about.version', { version: config.version })" />
<p
class="is-size-7"
v-text="
$t('page.about.compiled-with', {
options: config.buildoptions.join(', ')
})
"
/>
<p class="is-size-7" v-html="$t('page.about.built-with')" />
</div>
</div>
</div>

View File

@ -1,23 +1,20 @@
<template>
<content-with-hero>
<template #heading-left>
<h1 class="title is-5">
{{ album.name }}
</h1>
<h1 class="title is-5" v-text="album.name" />
<h2 class="subtitle is-6 has-text-link has-text-weight-normal">
<a class="has-text-link" @click="open_artist">{{ album.artist }}</a>
<a class="has-text-link" @click="open_artist" v-text="album.artist" />
</h2>
<div class="buttons fd-is-centered-mobile fd-has-margin-top">
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.album.shuffle')" />
</a>
<a
class="button is-small is-light is-rounded"
@click="show_album_details_modal = true"
>
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
</div>
</template>
@ -32,9 +29,10 @@
</p>
</template>
<template #content>
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top">
{{ album.track_count }} tracks
</p>
<p
class="heading is-7 has-text-centered-mobile fd-has-margin-top"
v-text="$t('page.album.track-count', { count: album.track_count })"
/>
<list-tracks :tracks="tracks" :uris="album.uri" />
<modal-dialog-album
:show="show_album_details_modal"

View File

@ -1,14 +1,16 @@
<template>
<div class="fd-page-with-tabs">
<tabs-music />
<content-with-heading>
<template #options>
<index-button-list :index="albums.indexList" />
<div class="columns">
<div class="column">
<p class="heading" style="margin-bottom: 24px">Filter</p>
<p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.albums.filter')"
/>
<div class="field">
<div class="control">
<input
@ -18,12 +20,12 @@
name="switchHideSingles"
class="switch"
/>
<label for="switchHideSingles">Hide singles</label>
<label
for="switchHideSingles"
v-text="$t('page.albums.hide-singles')"
/>
</div>
<p class="help">
If active, hides singles and albums with tracks that only appear
in playlists.
</p>
<p class="help" v-text="$t('page.albums.hide-singles-help')" />
</div>
<div v-if="spotify_enabled" class="field">
<div class="control">
@ -34,26 +36,33 @@
name="switchHideSpotify"
class="switch"
/>
<label for="switchHideSpotify">Hide albums from Spotify</label>
<label
for="switchHideSpotify"
v-text="$t('page.albums.hide-spotify')"
/>
</div>
<p class="help">
If active, hides albums that only appear in your Spotify
library.
</p>
<p class="help" v-text="$t('page.albums.hide-spotify-help')" />
</div>
</div>
<div class="column">
<p class="heading" style="margin-bottom: 24px">Sort by</p>
<p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.albums.sort-by.title')"
/>
<dropdown-menu
v-model="selected_groupby_option_name"
:options="groupby_option_names"
v-model="selected_groupby_option_id"
:options="groupby_options"
/>
</div>
</div>
</template>
<template #heading-left>
<p class="title is-4">Albums</p>
<p class="heading">{{ albums.count }} Albums</p>
<p class="title is-4" v-text="$t('page.albums.title')" />
<p
class="heading"
v-text="$t('page.albums.count', { count: albums.count })"
/>
</template>
<template #heading-right />
<template #content>
@ -117,16 +126,22 @@ export default {
// List of group by/sort options for itemsGroupByList
groupby_options: [
{ name: 'Name', options: bySortName('name_sort') },
{
name: 'Recently added',
id: 1,
name: this.$t('page.albums.sort-by.name'),
options: bySortName('name_sort')
},
{
id: 2,
name: this.$t('page.albums.sort-by.recently-added'),
options: byYear('time_added', {
direction: 'desc',
defaultValue: '0000'
})
},
{
name: 'Recently released',
id: 3,
name: this.$t('page.albums.sort-by.recently-released'),
options: byYear('date_released', {
direction: 'desc',
defaultValue: '0000'
@ -139,7 +154,7 @@ export default {
computed: {
albums() {
const groupBy = this.groupby_options.find(
(o) => o.name === this.selected_groupby_option_name
(o) => o.id === this.selected_groupby_option_id
)
this.albums_list.group(groupBy.options, [
(album) => !this.hide_singles || album.track_count > 2,
@ -149,11 +164,7 @@ export default {
return this.albums_list
},
groupby_option_names() {
return [...this.groupby_options].map((o) => o.name)
},
selected_groupby_option_name: {
selected_groupby_option_id: {
get() {
return this.$store.state.albums_sort
},

View File

@ -3,18 +3,20 @@
<template #options>
<div class="columns">
<div class="column">
<p class="heading" style="margin-bottom: 24px">Sort by</p>
<p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.artist.sort-by.title')"
/>
<dropdown-menu
v-model="selected_groupby_option_name"
:options="groupby_option_names"
v-model="selected_groupby_option_id"
:options="groupby_options"
/>
</div>
</div>
</template>
<template #heading-left>
<p class="title is-4">
{{ artist.name }}
</p>
<p class="title is-4" v-text="artist.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -22,20 +24,24 @@
class="button is-small is-light is-rounded"
@click="show_artist_details_modal = true"
>
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.artist.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">
{{ artist.album_count }} albums |
<a class="has-text-link" @click="open_tracks"
>{{ artist.track_count }} tracks</a
>
<span
v-text="$t('page.artist.album-count', { count: artist.album_count })"
/>
<a
class="has-text-link"
@click="open_tracks"
v-text="$t('page.artist.track-count', { count: artist.track_count })"
/>
</p>
<list-albums :albums="albums" :hide_group_title="true" />
<modal-dialog-artist
@ -99,9 +105,14 @@ export default {
// List of group by/sort options for itemsGroupByList
groupby_options: [
{ name: 'Name', options: bySortName('name_sort') },
{
name: 'Release date',
id: 1,
name: this.$t('page.artist.sort-by.name'),
options: bySortName('name_sort')
},
{
id: 2,
name: this.$t('page.artist.sort-by.release-date'),
options: byYear('date_released', {
direction: 'asc',
defaultValue: '0000'
@ -116,18 +127,14 @@ export default {
computed: {
albums() {
const groupBy = this.groupby_options.find(
(o) => o.name === this.selected_groupby_option_name
(o) => o.name === this.selected_groupby_option_id
)
this.albums_list.group(groupBy.options)
return this.albums_list
},
groupby_option_names() {
return [...this.groupby_options].map((o) => o.name)
},
selected_groupby_option_name: {
selected_groupby_option_id: {
get() {
return this.$store.state.artist_albums_sort
},

View File

@ -5,9 +5,7 @@
<index-button-list :index="index_list" />
</template>
<template #heading-left>
<p class="title is-4">
{{ artist.name }}
</p>
<p class="title is-4" v-text="artist.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -15,22 +13,26 @@
class="button is-small is-light is-rounded"
@click="show_artist_details_modal = true"
>
<span class="icon"
><mdicon name="dots-horizontal" size="16"
/></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.artist.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">
<a class="has-text-link" @click="open_artist"
>{{ artist.album_count }} albums</a
>
| {{ artist.track_count }} tracks
<a
class="has-text-link"
@click="open_artist"
v-text="
$t('page.artist.track-count', {
albums: artist.album_count,
tracks: artist.track_count
})
"
/>
</p>
<list-tracks :tracks="tracks.items" :uris="track_uris" />
<modal-dialog-artist

View File

@ -1,14 +1,16 @@
<template>
<div class="fd-page-with-tabs">
<tabs-music />
<content-with-heading>
<template #options>
<index-button-list :index="artists.indexList" />
<div class="columns">
<div class="column">
<p class="heading" style="margin-bottom: 24px">Filter</p>
<p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.artists.filter')"
/>
<div class="field">
<div class="control">
<input
@ -18,12 +20,12 @@
name="switchHideSingles"
class="switch"
/>
<label for="switchHideSingles">Hide singles</label>
<label
for="switchHideSingles"
v-text="$t('page.artists.hide-singles')"
/>
</div>
<p class="help">
If active, hides artists that only appear on singles or
playlists.
</p>
<p class="help" v-text="$t('page.artists.hide-singles-help')" />
</div>
<div v-if="spotify_enabled" class="field">
<div class="control">
@ -34,26 +36,33 @@
name="switchHideSpotify"
class="switch"
/>
<label for="switchHideSpotify">Hide artists from Spotify</label>
<label
for="switchHideSpotify"
v-text="$t('page.artists.hide-spotify')"
/>
</div>
<p class="help">
If active, hides artists that only appear in your Spotify
library.
</p>
<p class="help" v-text="$t('page.artists.hide-spotify-help')" />
</div>
</div>
<div class="column">
<p class="heading" style="margin-bottom: 24px">Sort by</p>
<p
class="heading"
style="margin-bottom: 24px"
v-text="$t('page.artists.sort-by.title')"
/>
<dropdown-menu
v-model="selected_groupby_option_name"
:options="groupby_option_names"
v-model="selected_groupby_option_id"
:options="groupby_options"
/>
</div>
</div>
</template>
<template #heading-left>
<p class="title is-4">Artists</p>
<p class="heading">{{ artists.count }} Artists</p>
<p class="title is-4" v-text="$t('page.artists.title')" />
<p
class="heading"
v-text="$t('page.artists.count', { count: artists.count })"
/>
</template>
<template #heading-right />
<template #content>
@ -118,9 +127,14 @@ export default {
// List of group by/sort options for itemsGroupByList
groupby_options: [
{ name: 'Name', options: bySortName('name_sort') },
{
name: 'Recently added',
id: 1,
name: this.$t('page.artists.sort-by.name'),
options: bySortName('name_sort')
},
{
id: 2,
name: this.$t('page.artists.sort-by.recently-added'),
options: byYear('time_added', {
direction: 'desc',
defaultValue: '0000'
@ -138,7 +152,7 @@ export default {
}
const groupBy = this.groupby_options.find(
(o) => o.name === this.selected_groupby_option_name
(o) => o.id === this.selected_groupby_option_id
)
this.artists_list.group(groupBy.options, [
(artist) =>
@ -149,12 +163,7 @@ export default {
return this.artists_list
},
// List for the drop down menu
groupby_option_names() {
return [...this.groupby_options].map((o) => o.name)
},
selected_groupby_option_name: {
selected_groupby_option_id: {
get() {
return this.$store.state.artists_sort
},

View File

@ -1,23 +1,20 @@
<template>
<content-with-hero>
<template #heading-left>
<h1 class="title is-5">
{{ album.name }}
</h1>
<h1 class="title is-5" v-text="album.name" />
<h2 class="subtitle is-6 has-text-link has-text-weight-normal">
<a class="has-text-link" @click="open_artist">{{ album.artist }}</a>
<a class="has-text-link" @click="open_artist" v-text="album.artist" />
</h2>
<div class="buttons fd-is-centered-mobile fd-has-margin-top">
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span>Play</span>
<mdicon class="icon" name="play" size="16" />
<span v-text="$t('page.audiobooks.album.play')" />
</a>
<a
class="button is-small is-light is-rounded"
@click="show_album_details_modal = true"
>
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
</div>
</template>
@ -32,9 +29,12 @@
</p>
</template>
<template #content>
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top">
{{ album.track_count }} tracks
</p>
<p
class="heading is-7 has-text-centered-mobile fd-has-margin-top"
v-text="
$t('page.audiobooks.album.track-count', { count: album.track_count })
"
/>
<list-tracks :tracks="tracks" :uris="album.uri" />
<modal-dialog-album
:show="show_album_details_modal"

View File

@ -1,14 +1,16 @@
<template>
<div class="fd-page-with-tabs">
<tabs-audiobooks />
<content-with-heading>
<template #options>
<index-button-list :index="albums.indexList" />
</template>
<template #heading-left>
<p class="title is-4">Audiobooks</p>
<p class="heading">{{ albums.count }} Audiobooks</p>
<p class="title is-4" v-text="$t('page.audiobooks.albums.title')" />
<p
class="heading"
v-text="$t('page.audiobooks.albums.count', { count: albums.count })"
/>
</template>
<template #content>
<list-albums :albums="albums" />

View File

@ -1,9 +1,7 @@
<template>
<content-with-heading>
<template #heading-left>
<p class="title is-4">
{{ artist.name }}
</p>
<p class="title is-4" v-text="artist.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -11,18 +9,23 @@
class="button is-small is-light is-rounded"
@click="show_artist_details_modal = true"
>
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="play" size="16" />
<span v-text="$t('page.audiobooks.artist.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">
{{ artist.album_count }} albums
</p>
<p
class="heading has-text-centered-mobile"
v-text="
$t('page.audiobooks.artist.album-count', {
count: artist.album_count
})
"
/>
<list-albums :albums="albums" />
<modal-dialog-artist
:show="show_artist_details_modal"

View File

@ -1,14 +1,16 @@
<template>
<div class="fd-page-with-tabs">
<tabs-audiobooks />
<content-with-heading>
<template #options>
<index-button-list :index="artists.indexList" />
</template>
<template #heading-left>
<p class="title is-4">Authors</p>
<p class="heading">{{ artists.count }} Authors</p>
<p class="title is-4" v-text="$t('page.audiobooks.artists.title')" />
<p
class="heading"
v-text="$t('page.audiobooks.artists.count', { count: tists.count })"
/>
</template>
<template #heading-right />
<template #content>

View File

@ -1,12 +1,11 @@
<template>
<div class="fd-page-with-tabs">
<tabs-music />
<!-- Recently added -->
<content-with-heading>
<template #heading-left>
<p class="title is-4">Recently added</p>
<p class="heading">albums</p>
<p class="title is-4" v-text="$t('page.browse.recently-added.title')" />
<p class="heading" v-text="$t('page.browse.albums')" />
</template>
<template #content>
<list-albums :albums="recently_added" />
@ -17,18 +16,20 @@
<a
class="button is-light is-small is-rounded"
@click="open_browse('recently_added')"
>Show more</a
>
v-text="$t('page.browse.show-more')"
/>
</p>
</nav>
</template>
</content-with-heading>
<!-- Recently played -->
<content-with-heading>
<template #heading-left>
<p class="title is-4">Recently played</p>
<p class="heading">tracks</p>
<p
class="title is-4"
v-text="$t('page.browse.recently-played.title')"
/>
<p class="heading" v-text="$t('page.browse.tracks')" />
</template>
<template #content>
<list-tracks :tracks="recently_played.items" />
@ -39,8 +40,8 @@
<a
class="button is-light is-small is-rounded"
@click="open_browse('recently_played')"
>Show more</a
>
v-text="$t('page.browse.show-more')"
/>
</p>
</nav>
</template>

View File

@ -1,11 +1,10 @@
<template>
<div class="fd-page-with-tabs">
<tabs-music />
<content-with-heading>
<template #heading-left>
<p class="title is-4">Recently added</p>
<p class="heading">albums</p>
<p class="title is-4" v-text="$t('page.browse.recently-added.title')" />
<p class="heading" v-text="$t('page.browse.recently-added.albums')" />
</template>
<template #content>
<list-albums :albums="recently_added" />

View File

@ -1,11 +1,13 @@
<template>
<div class="fd-page-with-tabs">
<tabs-music />
<content-with-heading>
<template #heading-left>
<p class="title is-4">Recently played</p>
<p class="heading">tracks</p>
<p
class="title is-4"
v-text="$t('page.browse.recently.played.title')"
/>
<p class="heading" v-text="$t('page.browse.recently.played.tracks')" />
</template>
<template #content>
<list-tracks :tracks="recently_played.items" />

View File

@ -2,9 +2,7 @@
<div>
<content-with-heading>
<template #heading-left>
<p class="title is-4">
{{ composer.name }}
</p>
<p class="title is-4" v-text="composer.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -12,25 +10,30 @@
class="button is-small is-light is-rounded"
@click="show_composer_details_modal = true"
>
<span class="icon"
><mdicon name="dots-horizontal" size="16"
/></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.composer.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">
{{ composer.album_count }} albums |
<a class="has-text-link" @click="open_tracks"
>{{ composer.track_count }} tracks</a
>
<span
v-text="
$t('page.composer.album-count', { count: composer.album_count })
"
/>
<a
class="has-text-link"
@click="open_tracks"
v-text="
$t('page.composer.track-count', { count: composer.track_count })
"
/>
</p>
<list-albums :albums="albums_list" :hide_group_title="true" />
<modal-dialog-composer
:show="show_composer_details_modal"
:composer="composer"

View File

@ -2,9 +2,7 @@
<div>
<content-with-heading>
<template #heading-left>
<p class="title is-4">
{{ composer.name }}
</p>
<p class="title is-4" v-text="composer.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -12,22 +10,30 @@
class="button is-small is-light is-rounded"
@click="show_composer_details_modal = true"
>
<span class="icon"
><mdicon name="dots-horizontal" size="16"
/></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.composer.tracks.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">
<a class="has-text-link" @click="open_albums"
>{{ composer.album_count }} albums</a
>
| {{ composer.track_count }} tracks
<a
class="has-text-link"
@click="open_albums"
v-text="
$t('page.composer.tracks.album-count', {
count: composer.album_count
})
"
/>
<span
v-text="
$t('page.composer.track-count', { count: composer.track_count })
"
/>
</p>
<list-tracks :tracks="tracks.items" :expression="play_expression" />
<modal-dialog-composer

View File

@ -1,14 +1,16 @@
<template>
<div>
<tabs-music />
<content-with-heading>
<template #options>
<index-button-list :index="composers.indexList" />
</template>
<template #heading-left>
<p class="title is-4">Composers</p>
<p class="heading">{{ composers.total }} composers</p>
<p class="title is-4" v-text="$t('page.composers.title')" />
<p
class="heading"
v-text="$t('page.composers.count', { count: composers.total })"
/>
</template>
<template #content>
<list-composers :composers="composers" />

View File

@ -2,10 +2,8 @@
<div>
<content-with-heading>
<template #heading-left>
<p class="title is-4">Files</p>
<p class="title is-7 has-text-grey">
{{ current_directory }}
</p>
<p class="title is-4" v-text="$t('page.files.title')" />
<p class="title is-7 has-text-grey" v-text="current_directory" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -13,27 +11,22 @@
class="button is-small is-light is-rounded"
@click="open_directory_dialog({ path: current_directory })"
>
<span class="icon"
><mdicon name="dots-horizontal" size="16"
/></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="play" size="16" /></span>
<span>Play</span>
<mdicon class="icon" name="play" size="16" />
<span v-text="$t('page.files.play')" />
</a>
</div>
</template>
<template #content>
<list-directories :directories="files.directories" />
<list-playlists :playlists="files.playlists.items" />
<list-tracks
:tracks="files.tracks.items"
:expression="play_expression"
:show_icon="true"
/>
<modal-dialog-directory
:show="show_directory_details_modal"
:directory="selected_directory"

View File

@ -5,9 +5,7 @@
<index-button-list :index="albums_list.indexList" />
</template>
<template #heading-left>
<p class="title is-4">
{{ genre.name }}
</p>
<p class="title is-4" v-text="genre.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -15,22 +13,24 @@
class="button is-small is-light is-rounded"
@click="show_genre_details_modal = true"
>
<span class="icon"
><mdicon name="dots-horizontal" size="16"
/></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.genre.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">
{{ genre.album_count }} albums |
<a class="has-text-link" @click="open_tracks"
>{{ genre.track_count }} tracks</a
>
<span
v-text="$t('page.genre.album-count', { count: genre.album_count })"
/>
<a
class="has-text-link"
@click="open_tracks"
v-text="$t('page.genre.track-count', { count: genre.track_count })"
/>
</p>
<list-albums :albums="albums_list" />
<modal-dialog-genre

View File

@ -5,9 +5,7 @@
<index-button-list :index="index_list" />
</template>
<template #heading-left>
<p class="title is-4">
{{ genre.name }}
</p>
<p class="title is-4" v-text="genre.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -15,22 +13,26 @@
class="button is-small is-light is-rounded"
@click="show_genre_details_modal = true"
>
<span class="icon"
><mdicon name="dots-horizontal" size="16"
/></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.genre.tracks.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">
<a class="has-text-link" @click="open_genre"
>{{ genre.album_count }} albums</a
>
| {{ genre.track_count }} tracks
<a
class="has-text-link"
@click="open_genre"
v-text="
$t('page.genre.tracks.album-count', { count: genre.album_count })
"
/>
<span
v-text="$t('page.genre.tracks.count', { count: genre.track_count })"
/>
</p>
<list-tracks :tracks="tracks.items" :expression="expression" />
<modal-dialog-genre

View File

@ -1,14 +1,16 @@
<template>
<div class="fd-page-with-tabs">
<tabs-music />
<content-with-heading>
<template #options>
<index-button-list :index="genres.indexList" />
</template>
<template #heading-left>
<p class="title is-4">Genres</p>
<p class="heading">{{ genres.total }} genres</p>
<p class="title is-4" v-text="$t('page.genres.title')" />
<p
class="heading"
v-text="$t('page.genres.count', { count: genres.total })"
/>
</template>
<template #content>
<list-genres :genres="genres" />

View File

@ -26,41 +26,29 @@
@start="start_dragging"
@end="end_dragging"
/>
<!--range-slider
class="seek-slider fd-has-action"
min="0"
:max="state.item_length_ms"
:value="item_progress_ms"
:disabled="state.state === 'stop'"
step="1000"
@change="seek" >
</range-slider-->
</p>
<p class="content">
<span
>{{ $filters.durationInHours(item_progress_ms) }} /
{{ $filters.durationInHours(now_playing.length_ms) }}</span
>
v-text="
[
$filters.durationInHours(item_progress_ms),
$filters.durationInHours(now_playing.length_ms)
].join(' / ')
"
/>
</p>
</div>
</div>
<div class="fd-has-padding-left-right">
<div class="container has-text-centered fd-has-margin-top">
<h1 class="title is-5">
{{ now_playing.title }}
</h1>
<h2 class="title is-6">
{{ now_playing.artist }}
</h2>
<h1 class="title is-5" v-text="now_playing.title" />
<h2 class="title is-6" v-text="now_playing.artist" />
<h2
v-if="composer"
class="subtitle is-6 has-text-grey has-text-weight-bold"
>
{{ composer }}
</h2>
<h3 class="subtitle is-6">
{{ now_playing.album }}
</h3>
v-text="composer"
/>
<h3 class="subtitle is-6" v-text="now_playing.album" />
</div>
</div>
</div>
@ -70,8 +58,8 @@
style="flex-direction: column"
>
<div class="content has-text-centered">
<h1 class="title is-5">Your play queue is empty</h1>
<p>Add some tracks by browsing your library</p>
<h1 class="title is-5" v-text="$('page.now-playing.title')" />
<p v-text="$('page.now-playing.info')" />
</div>
</div>
</div>
@ -85,7 +73,6 @@
<script>
import ModalDialogQueueItem from '@/components/ModalDialogQueueItem.vue'
//import RangeSlider from 'vue-range-slider'
import Slider from '@vueform/slider'
import CoverArtwork from '@/components/CoverArtwork.vue'
import webapi from '@/webapi'
@ -95,7 +82,6 @@ export default {
name: 'PageNowPlaying',
components: {
ModalDialogQueueItem,
// RangeSlider,
Slider,
CoverArtwork
},

View File

@ -1,9 +1,7 @@
<template>
<content-with-heading>
<template #heading-left>
<div class="title is-4">
{{ playlist.name }}
</div>
<div class="title is-4" v-text="playlist.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -11,16 +9,19 @@
class="button is-small is-light is-rounded"
@click="show_playlist_details_modal = true"
>
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.playlist.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">{{ tracks.length }} tracks</p>
<p
class="heading has-text-centered-mobile"
v-text="$t('page.playlist.length', { length: tracks.length })"
/>
<list-tracks :tracks="tracks" :uris="uris" />
<modal-dialog-playlist
:show="show_playlist_details_modal"

View File

@ -1,10 +1,11 @@
<template>
<content-with-heading>
<template #heading-left>
<p class="title is-4">
{{ playlist.name }}
</p>
<p class="heading">{{ playlists.count }} playlists</p>
<p class="title is-4" v-text="playlist.name" />
<p
class="heading"
v-text="$t('page.playlists.count', { count: playlists.count })"
/>
</template>
<template #content>
<list-playlists :playlists="playlists" />

View File

@ -1,9 +1,7 @@
<template>
<content-with-heading>
<template #heading-left>
<div class="title is-4">
{{ album.name }}
</div>
<div class="title is-4" v-text="album.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -11,20 +9,19 @@
class="button is-small is-light is-rounded"
@click="show_album_details_modal = true"
>
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon">
<mdicon name="play" size="16" />
</span>
<span>Play</span>
<mdicon class="icon" name="play" size="16" />
<span v-text="$t('page.podcast.play')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">
{{ album.track_count }} tracks
</p>
<p
class="heading has-text-centered-mobile"
v-text="$t('page.podcast.track-count', { count: album.track_count })"
/>
<list-tracks
:tracks="tracks"
:show_progress="true"
@ -47,11 +44,10 @@
@delete="remove_podcast"
>
<template #modal-content>
<p>Permanently remove this podcast from your library?</p>
<p v-text="$t('page.podcast.remove-info-1')" />
<p class="is-size-7">
(This will also remove the RSS playlist
<b>{{ rss_playlist_to_remove.name }}</b
>.)
(<span v-text="$t('page.podcast.remove-info-2')" />
<b v-text="rss_playlist_to_remove.name" />)
</p>
</template>
</modal-dialog>
@ -131,7 +127,7 @@ export default {
const rssPlaylists = data.items.filter((pl) => pl.type === 'rss')
if (rssPlaylists.length !== 1) {
this.$store.dispatch('add_notification', {
text: 'Podcast cannot be removed. Probably it was not added as an RSS playlist.',
text: this.$t('page.podcast.remove-error'),
type: 'danger'
})
return

View File

@ -2,15 +2,13 @@
<div>
<content-with-heading v-if="new_episodes.items.length > 0">
<template #heading-left>
<p class="title is-4">New episodes</p>
<p class="title is-4" v-text="$t('page.podcasts.new-episodes')" />
</template>
<template #heading-right>
<div class="buttons is-centered">
<a class="button is-small" @click="mark_all_played">
<span class="icon">
<mdicon name="pencil" size="16" />
</span>
<span>Mark All Played</span>
<mdicon class="icon" name="pencil" size="16" />
<span v-text="$t('page.podcasts.mark-all-played')" />
</a>
</div>
</template>
@ -22,25 +20,23 @@
/>
</template>
</content-with-heading>
<content-with-heading>
<template #heading-left>
<p class="title is-4">Podcasts</p>
<p class="heading">{{ albums.total }} podcasts</p>
<p class="title is-4" v-text="$t('page.podcasts.title')" />
<p
class="heading"
v-text="$t('page.podcasts.count', { count: albums.total })"
/>
</template>
<template #heading-right>
<div class="buttons is-centered">
<a v-if="rss.tracks > 0" class="button is-small" @click="update_rss">
<span class="icon">
<mdicon name="refresh" size="16" />
</span>
<span>Update</span>
<mdicon class="icon" name="refresh" size="16" />
<span v-text="$t('page.podcasts.update')" />
</a>
<a class="button is-small" @click="open_add_podcast_dialog">
<span class="icon">
<mdicon name="rss" size="16" />
</span>
<span>Add Podcast</span>
<mdicon class="icon" name="rss" size="16" />
<span v-text="$t('page.podcasts.add')" />
</a>
</div>
</template>

View File

@ -1,8 +1,11 @@
<template>
<content-with-heading>
<template #heading-left>
<p class="heading">{{ queue.count }} tracks</p>
<p class="title is-4">Queue</p>
<p class="title is-4" v-text="$t('page.queue.title')" />
<p
class="heading"
v-text="$t('page.queue.count', { count: queue.count })"
/>
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -11,32 +14,24 @@
:class="{ 'is-info': show_only_next_items }"
@click="update_show_next_items"
>
<span class="icon">
<mdicon name="arrow-collapse-down" size="16" />
</span>
<span>Hide previous</span>
<mdicon class="icon" name="arrow-collapse-down" size="16" />
<span v-text="$t('page.queue.hide-previous')" />
</a>
<a class="button is-small" @click="open_add_stream_dialog">
<span class="icon">
<mdicon name="web" size="16" />
</span>
<span>Add Stream</span>
<mdicon class="icon" name="web" size="16" />
<span v-text="$t('page.queue.add-stream')" />
</a>
<a
class="button is-small"
:class="{ 'is-info': edit_mode }"
@click="edit_mode = !edit_mode"
>
<span class="icon">
<mdicon name="pencil" size="16" />
</span>
<span>Edit</span>
<mdicon class="icon" name="pencil" size="16" />
<span v-text="$t('page.queue.edit')" />
</a>
<a class="button is-small" @click="queue_clear">
<span class="icon">
<mdicon name="delete-empty" size="16" />
</span>
<span>Clear</span>
<mdicon class="icon" name="delete-empty" size="16" />
<span v-text="$t('page.queue.clear')" />
</a>
<a
v-if="is_queue_save_allowed"
@ -44,10 +39,8 @@
:disabled="queue_items.length === 0"
@click="save_dialog"
>
<span class="icon">
<mdicon name="content-save" size="16" />
</span>
<span>Save</span>
<mdicon class="icon" name="content-save" size="16" />
<span v-text="$t('page.queue.save')" />
</a>
</div>
</template>
@ -68,17 +61,17 @@
>
<template #actions>
<a v-if="!edit_mode" @click.prevent.stop="open_dialog(element)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon
class="icon has-text-dark"
name="dots-vertical"
size="16"
/>
</a>
<a
v-if="element.id !== state.item_id && edit_mode"
@click.prevent.stop="remove(element)"
>
<span class="icon has-text-grey"
><mdicon name="delete" size="18"
/></span>
<mdicon class="icon has-text-grey" name="delete" size="18" />
</a>
</template>
</list-item-queue-item>

View File

@ -2,12 +2,13 @@
<div>
<content-with-heading>
<template #heading-left>
<p class="title is-4">Radio</p>
<p class="title is-4" v-text="$t('page.radio.title')" />
</template>
<template #content>
<p class="heading has-text-centered-mobile">
{{ tracks.total }} tracks
</p>
<p
class="heading has-text-centered-mobile"
v-text="$t('page.radio.count', { count: tracks.total })"
/>
<list-tracks :tracks="tracks.items" />
</template>
</content-with-heading>

View File

@ -16,18 +16,10 @@
placeholder="Search"
autocomplete="off"
/>
<span class="icon is-left">
<mdicon name="magnify" size="16" />
</span>
<mdicon class="icon is-left" name="magnify" size="16" />
</p>
<p class="help has-text-centered">
Tip: you can search by a smart playlist query language
<a
href="https://github.com/owntone/owntone-server/blob/master/README_SMARTPL.md"
target="_blank"
>expression</a
>
if you prefix it with <code>query:</code>.
<span v-html="$t('page.search.help')" />
</p>
</div>
</form>
@ -37,20 +29,18 @@
:key="recent_search"
class="tag"
@click="open_recent_search(recent_search)"
>{{ recent_search }}</a
>
v-text="recent_search"
/>
</div>
</div>
</div>
</div>
</section>
<tabs-search :query="search_query" />
<!-- Tracks -->
<content-with-heading v-if="show_tracks && tracks.total">
<template #heading-left>
<p class="title is-4">Tracks</p>
<p class="title is-4" v-text="$t('page.search.tracks')" />
</template>
<template #content>
<list-tracks :tracks="tracks.items" />
@ -61,22 +51,25 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_tracks"
>Show all {{ tracks.total.toLocaleString() }} tracks</a
>
v-text="
$t('page.search.show.tracks', {
count: tracks.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_tracks && !tracks.total" class="mt-6">
<template #content>
<p><i>No tracks found</i></p>
<p><i v-text="$t('page.search.no-tracks')" /></p>
</template>
</content-text>
<!-- Artists -->
<content-with-heading v-if="show_artists && artists.total">
<template #heading-left>
<p class="title is-4">Artists</p>
<p class="title is-4" v-text="$t('page.search.artists')" />
</template>
<template #content>
<list-artists :artists="artists" :hide_group_title="true" />
@ -87,22 +80,25 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_artists"
>Show all {{ artists.total.toLocaleString() }} artists</a
>
v-text="
$t('page.search.show.artists', {
count: artists.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_artists && !artists.total">
<template #content>
<p><i>No artists found</i></p>
<p><i v-text="$t('page.search.no-artists')" /></p>
</template>
</content-text>
<!-- Albums -->
<content-with-heading v-if="show_albums && albums.total">
<template #heading-left>
<p class="title is-4">Albums</p>
<p class="title is-4" v-text="$t('page.search.albums')" />
</template>
<template #content>
<list-albums :albums="albums" :hide_group_title="true" />
@ -113,22 +109,25 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_albums"
>Show all {{ albums.total.toLocaleString() }} albums</a
>
v-text="
$t('page.search.show-albums', {
count: albums.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_albums && !albums.total">
<template #content>
<p><i>No albums found</i></p>
<p><i v-text="$t('page.search.no-albums')" /></p>
</template>
</content-text>
<!-- Composers -->
<content-with-heading v-if="show_composers && composers.total">
<template #heading-left>
<p class="title is-4">Composers</p>
<p class="title is-4" v-text="$t('page.search.composers')" />
</template>
<template #content>
<list-composers :composers="composers" />
@ -139,22 +138,25 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_composers"
>Show all {{ composers.total.toLocaleString() }} composers</a
>
v-text="
$t('page.search.show.composers', {
count: composers.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_composers && !composers.total">
<template #content>
<p><i>No composers found</i></p>
<p><i v-text="$t('page.search.no-composers')" /></p>
</template>
</content-text>
<!-- Playlists -->
<content-with-heading v-if="show_playlists && playlists.total">
<template #heading-left>
<p class="title is-4">Playlists</p>
<p class="title is-4" v-text="$t('page.search.playlists')" />
</template>
<template #content>
<list-playlists :playlists="playlists" />
@ -165,22 +167,25 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_playlists"
>Show all {{ playlists.total.toLocaleString() }} playlists</a
>
v-text="
$t('page.search.show.playlists', {
count: playlists.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_playlists && !playlists.total">
<template #content>
<p><i>No playlists found</i></p>
<p><i v-text="$t('page.search.no-playlists')" /></p>
</template>
</content-text>
<!-- Podcasts -->
<content-with-heading v-if="show_podcasts && podcasts.total">
<template #heading-left>
<p class="title is-4">Podcasts</p>
<p class="title is-4" v-text="$t('page.search.podcasts')" />
</template>
<template #content>
<list-albums :albums="podcasts" />
@ -191,22 +196,26 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_podcasts"
>Show all {{ podcasts.total.toLocaleString() }} podcasts</a
>
v-text="
$t('page.search.show.podcasts', {
count: podcasts.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_podcasts && !podcasts.total">
<template #content>
<p><i>No podcasts found</i></p>
<p><i v-text="$t('page.search.no-podcasts')" /></p>
</template>
</content-text>
<!-- Audiobooks -->
<content-with-heading v-if="show_audiobooks && audiobooks.total">
<template #heading-left>
<p class="title is-4">Audiobooks</p>
<p class="title is-4" v-text="$t('page.search.audiobooks')" />
</template>
<template #content>
<list-albums :albums="audiobooks" />
@ -217,15 +226,19 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_audiobooks"
>Show all {{ audiobooks.total.toLocaleString() }} audiobooks</a
>
v-text="
$t('page.search.show.audiobooks', {
count: audiobooks.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_audiobooks && !audiobooks.total">
<template #content>
<p><i>No audiobooks found</i></p>
<p><i v-text="$t('page.search.no-audiobooks')" /></p>
</template>
</content-text>
</div>
@ -260,7 +273,6 @@ export default {
data() {
return {
search_query: '',
tracks: { items: [], total: 0 },
artists: new GroupByList(),
albums: new GroupByList(),

View File

@ -1,39 +1,40 @@
<template>
<div class="fd-page-with-tabs">
<tabs-settings />
<content-with-heading>
<template #heading-left>
<div class="title is-4">Artwork</div>
<div class="title is-4" v-text="$t('page.settings.artwork.artwork')" />
</template>
<template #content>
<div class="content">
<p>
OwnTone supports PNG and JPEG artwork which is either placed as
separate image files in the library, embedded in the media files or
made available online by radio stations.
</p>
<p>
In addition to that, you can enable fetching artwork from the
following artwork providers:
</p>
</div>
<div
class="content"
v-text="$t('page.settings.artwork.explanation-1')"
/>
<div
class="content"
v-text="$t('page.settings.artwork.explanation-2')"
/>
<settings-checkbox
v-if="spotify.libspotify_logged_in"
category_name="artwork"
option_name="use_artwork_source_spotify"
>
<template #label> Spotify </template>
<template #label>
<span v-text="$t('page.settings.artwork.spotify')" />
<a href="https://www.spotify.com/" target="_blank">
<mdicon class="icon" name="open-in-new" size="16" />
</a>
</template>
</settings-checkbox>
<settings-checkbox
category_name="artwork"
option_name="use_artwork_source_discogs"
>
<template #label>
Discogs (<a href="https://www.discogs.com/"
>https://www.discogs.com/</a
>)
<span v-text="$t('page.settings.artwork.discogs')" />
<a href="https://www.discogs.com/" target="_blank">
<mdicon class="icon" name="open-in-new" size="16" />
</a>
</template>
</settings-checkbox>
<settings-checkbox
@ -41,9 +42,10 @@
option_name="use_artwork_source_coverartarchive"
>
<template #label>
Cover Art Archive (<a href="https://coverartarchive.org/"
>https://coverartarchive.org/</a
>)
<span v-text="$t('page.settings.artwork.coverartarchive')" />
<a href="https://coverartarchive.org/" target="_blank">
<mdicon class="icon" name="open-in-new" size="16" />
</a>
</template>
</settings-checkbox>
</template>

View File

@ -1,38 +1,33 @@
<template>
<div class="fd-page-with-tabs">
<tabs-settings />
<content-with-heading>
<template #heading-left>
<div class="title is-4">Spotify</div>
<div
class="title is-4"
v-text="$t('page.settings.services.spotify.title')"
/>
</template>
<template #content>
<div v-if="!spotify.spotify_installed" class="notification is-size-7">
<p>
OwnTone was either built without support for Spotify or libspotify
is not installed.
</p>
<p v-text="$t('page.settings.services.spotify.no-support')" />
</div>
<div v-if="spotify.spotify_installed">
<div class="notification is-size-7">
<b>You must have a Spotify premium account</b>.
<span v-if="use_libspotity"
>If you normally log into Spotify with your Facebook account you
must first go to Spotify's web site where you can get the Spotify
username and password that matches your account.</span
>
<b v-text="$t('page.settings.services.spotify.requirements')" />
<span
v-if="use_libspotity"
v-text="$t('page.settings.services.spotify.help')"
/>
</div>
<div v-if="use_libspotity">
<p class="content">
<b>libspotify</b> - Login with your Spotify username and password
<b v-text="$t('page.settings.services.spotify')" />
<span v-text="$t('page.settings.services.spotify.credentials')" />
</p>
<p v-if="spotify.libspotify_logged_in" class="fd-has-margin-bottom">
Logged in as
<b
><code>{{ spotify.libspotify_user }}</code></b
>
<span v-text="$t('page.settings.services.spotify.logged-as')" />
<b><code v-text="spotify.libspotify_user" /></b>
</p>
<form
v-if="spotify.spotify_installed && !spotify.libspotify_logged_in"
@ -46,9 +41,7 @@
type="text"
placeholder="Username"
/>
<p class="help is-danger">
{{ libspotify.errors.user }}
</p>
<p class="help is-danger" v-text="libspotify.errors.user" />
</div>
<div class="control is-expanded">
<input
@ -57,44 +50,41 @@
type="password"
placeholder="Password"
/>
<p class="help is-danger">
{{ libspotify.errors.password }}
</p>
<p
class="help is-danger"
v-text="libspotify.errors.password"
/>
</div>
<div class="control">
<button class="button is-info">Login</button>
<button
class="button is-info"
v-text="$t('page.settings.services.login')"
/>
</div>
</div>
</form>
<p class="help is-danger">
{{ libspotify.errors.error }}
</p>
<p class="help">
libspotify enables OwnTone to play Spotify tracks.
</p>
<p class="help">
OwnTone will not store your password, but will still be able to
log you in automatically afterwards, because libspotify saves a
login token.
</p>
<p class="help is-danger" v-text="libspotify.errors.error" />
<p
class="help"
v-text="$t('page.settings.services.spotify.help-1')"
/>
<p
class="help"
v-text="$t('page.settings.services.spotify.help-2')"
/>
</div>
<div class="fd-has-margin-top">
<p class="content">
<b>Spotify Web API</b> - Grant access to the Spotify Web API
</p>
<p
class="content"
v-html="$t('page.settings.services.spotify.grant-access')"
/>
<p v-if="spotify.webapi_token_valid">
Access granted for
<b
><code>{{ spotify.webapi_user }}</code></b
>
<span v-text="$t('page.settings.services.spotify.user')" />
<code v-text="spotify.webapi_user" />
</p>
<p v-if="spotify_missing_scope.length > 0" class="help is-danger">
Please reauthorize Web API access to grant OwnTone the following
additional access rights:
<b
><code>{{ spotify_missing_scope.join() }}</code></b
>
<span v-text="$t('page.settings.services.spotify.reauthorize')" />
<code v-text="spotify_missing_scope.join()" />
</p>
<div class="field fd-has-margin-top">
<div class="control">
@ -106,45 +96,52 @@
spotify_missing_scope.length > 0
}"
:href="spotify.oauth_uri"
>Authorize Web API access</a
>
v-text="$t('page.settings.services.spotify.authorize')"
/>
</div>
</div>
<p class="help">
Access to the Spotify Web API enables scanning of your Spotify
library. Required scopes are
<code>{{ spotify_required_scope.join() }}</code
>.
<span v-text="$t('page.settings.services.spotify.scopes')" />
<code v-text="spotify_required_scope.join()" />
</p>
<div
v-if="spotify.webapi_token_valid"
class="field fd-has-margin-top"
>
<div class="control">
<a class="button is-danger" @click="logout_spotify">Logout</a>
<a
class="button is-danger"
@click="logout_spotify"
v-text="$t('page.settings.services.logout')"
/>
</div>
</div>
</div>
</div>
</template>
</content-with-heading>
<content-with-heading>
<template #heading-left>
<div class="title is-4">Last.fm</div>
<div
class="title is-4"
v-text="$t('page.settings.services.lastfm.title')"
/>
</template>
<template #content>
<div v-if="!lastfm.enabled" class="notification is-size-7">
<p>OwnTone was built without support for Last.fm.</p>
<p v-text="$t('page.settings.services.lastfm.no-support')" />
</div>
<div v-if="lastfm.enabled">
<p class="content">
<b>Last.fm</b> - Login with your Last.fm username and password to
enable scrobbling
</p>
<p
class="content"
v-html="$t('page.settings.services.lastfm.grant-access')"
/>
<div v-if="lastfm.scrobbling_enabled">
<a class="button" @click="logoutLastfm">Stop scrobbling</a>
<a
class="button"
@click="logoutLastfm"
v-text="$t('page.settings.services.lastfm.stop-scrobbling')"
/>
</div>
<div v-if="!lastfm.scrobbling_enabled">
<form @submit.prevent="login_lastfm">
@ -156,9 +153,7 @@
type="text"
placeholder="Username"
/>
<p class="help is-danger">
{{ lastfm_login.errors.user }}
</p>
<p class="help is-danger" v-text="lastfm_login.errors.user" />
</div>
<div class="control is-expanded">
<input
@ -167,21 +162,24 @@
type="password"
placeholder="Password"
/>
<p class="help is-danger">
{{ lastfm_login.errors.password }}
</p>
<p
class="help is-danger"
v-text="lastfm_login.errors.password"
/>
</div>
<div class="control">
<button class="button is-info" type="submit">Login</button>
<button
class="button is-info"
type="submit"
v-text="$t('page.settings.services.login')"
/>
</div>
</div>
<p class="help is-danger">
{{ lastfm_login.errors.error }}
</p>
<p class="help">
OwnTone will not store your Last.fm username/password, only the
session key. The session key does not expire.
</p>
<p class="help is-danger" v-text="lastfm_login.errors.error" />
<p
class="help"
v-text="$t('page.settings.services.lastfm.info')"
/>
</form>
</div>
</div>

View File

@ -1,18 +1,17 @@
<template>
<div class="fd-page-with-tabs">
<tabs-settings />
<content-with-heading>
<template #heading-left>
<div class="title is-4">Remote Pairing</div>
<div class="title is-4" v-text="$t('page.settings.devices.pairing')" />
</template>
<template #content>
<!-- Paring request active -->
<div v-if="pairing.active" class="notification">
<form @submit.prevent="kickoff_pairing">
<label class="label has-text-weight-normal">
Remote pairing request from <b>{{ pairing.remote }}</b>
<span v-text="$t('page.settings.devices.pairing-request')" />
<b v-text="pairing.remote" />
</label>
<div class="field is-grouped">
<div class="control">
@ -24,29 +23,33 @@
/>
</div>
<div class="control">
<button class="button is-info" type="submit">Send</button>
<button
class="button is-info"
type="submit"
v-text="$t('page.settings.devices.send')"
/>
</div>
</div>
</form>
</div>
<!-- No pairing requests -->
<div v-if="!pairing.active" class="content">
<p>No active pairing request.</p>
<p v-text="$t('page.settings.devices.no-active-pairing')" />
</div>
</template>
</content-with-heading>
<content-with-heading>
<template #heading-left>
<div class="title is-4">Speaker pairing and device verification</div>
<div
class="title is-4"
v-text="$t('page.settings.devices.speaker-pairing')"
/>
</template>
<template #content>
<p class="content">
If your speaker requires pairing then activate it below and enter the
PIN that it displays.
</p>
<p
class="content"
v-text="$t('page.settings.devices.speaker-pairing-info')"
/>
<div v-for="output in outputs" :key="output.id">
<div class="field">
<div class="control">
@ -54,9 +57,10 @@
<input
v-model="output.selected"
type="checkbox"
style="margin-right: 5px"
@change="output_toggle(output.id)"
/>
{{ output.name }}
<span v-text="output.name" />
</label>
</div>
</div>
@ -71,11 +75,15 @@
v-model="verification_req.pin"
class="input"
type="text"
placeholder="Enter verification code"
:placeholder="$t('page.settings.devices.verification-code')"
/>
</div>
<div class="control">
<button class="button is-info" type="submit">Verify</button>
<button
class="button is-info"
type="submit"
v-text="$t('page.settings.devices.verify')"
/>
</div>
</div>
</form>

View File

@ -1,92 +1,123 @@
<template>
<div class="fd-page-with-tabs">
<tabs-settings />
<content-with-heading>
<template #heading-left>
<div class="title is-4">Navbar items</div>
<div class="title is-4" v-text="$t('page.settings.general.language')" />
</template>
<template #content>
<p class="content">Select the top navigation bar menu items</p>
<div class="notification is-size-7">
If you select more items than can be shown on your screen then the
burger menu will disappear.
</div>
<dropdown-menu v-model="locale" :options="locales" />
</template>
</content-with-heading>
<content-with-heading>
<template #heading-left>
<div
class="title is-4"
v-text="$t('page.settings.general.navigation-items')"
/>
</template>
<template #content>
<p
class="content"
v-text="$t('page.settings.general.navigation-item-selection')"
/>
<div
class="notification is-size-7"
v-text="$t('page.settings.general.navigation-item-selection-info')"
/>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_playlists"
>
<template #label> Playlists </template>
<template #label>
<span v-text="$t('page.settings.general.playlists')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_music"
>
<template #label> Music </template>
<template #label>
<span v-text="$t('page.settings.general.music')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_podcasts"
>
<template #label> Podcasts </template>
<template #label>
<span v-text="$t('page.settings.general.podcasts')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_audiobooks"
>
<template #label> Audiobooks </template>
<template #label>
<span v-text="$t('page.settings.general.audiobooks')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_radio"
>
<template #label> Radio </template>
<template #label>
<span v-text="$t('page.settings.general.radio')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_files"
>
<template #label> Files </template>
<template #label>
<span v-text="$t('page.settings.general.files')" />
</template>
</settings-checkbox>
<settings-checkbox
category_name="webinterface"
option_name="show_menu_item_search"
>
<template #label> Search </template>
<template #label>
<span v-text="$t('page.settings.general.search')" />
</template>
</settings-checkbox>
</template>
</content-with-heading>
<content-with-heading>
<template #heading-left>
<div class="title is-4">Album lists</div>
<div
class="title is-4"
v-text="$t('page.settings.general.album-lists')"
/>
</template>
<template #content>
<settings-checkbox
category_name="webinterface"
option_name="show_cover_artwork_in_album_lists"
>
<template #label> Show cover artwork in album list </template>
<template #label>
<span v-text="$t('page.settings.general.show-coverart')" />
</template>
</settings-checkbox>
</template>
</content-with-heading>
<content-with-heading>
<template #heading-left>
<div class="title is-4">Now playing page</div>
<div
class="title is-4"
v-text="$t('page.settings.general.now-playing-page')"
/>
</template>
<template #content>
<settings-checkbox
category_name="webinterface"
option_name="show_composer_now_playing"
>
<template #label> Show composer </template>
<template #label>
<span v-text="$t('page.settings.general.show-composer')" />
</template>
<template #info>
If enabled the composer of the current playing track is shown on the
&quot;now playing page&quot;
<span v-text="$t('page.settings.general.show-composer-info')" />
</template>
</settings-checkbox>
<settings-textfield
@ -95,36 +126,42 @@
:disabled="!settings_option_show_composer_now_playing"
placeholder="Genres"
>
<template #label> Show composer only for listed genres </template>
<template #label>
<span v-text="$t('page.settings.general.show-composer-genres')" />
</template>
<template #info>
<p class="help">
Comma separated list of genres the composer should be displayed on
the &quot;now playing page&quot;.
</p>
<p class="help">Leave empty to always show the composer.</p>
<p class="help">
The genre tag of the current track is matched by checking, if one
of the defined genres are included. For example setting to
<code>classical, soundtrack</code> will show the composer for
tracks with a genre tag of &quot;Contemporary Classical&quot;.<br />
</p>
<p
class="help"
v-text="$t('page.settings.general.show-composer-genres-info-1')"
/>
<p
class="help"
v-text="$t('page.settings.general.show-composer-genres-info-2')"
/>
<p
class="help"
v-text="$t('page.settings.general.show-composer-genres-info-3')"
/>
</template>
</settings-textfield>
</template>
</content-with-heading>
<content-with-heading>
<template #heading-left>
<div class="title is-4">Recently added page</div>
<div
class="title is-4"
v-text="$t('page.settings.general.recently-added-page')"
/>
</template>
<template #content>
<settings-intfield
category_name="webinterface"
option_name="recently_added_limit"
>
<template #label>
Limit the number of albums shown on the "Recently Added" page
<span
v-text="$t('page.settings.general.recently-added-page-info')"
/>
</template>
</settings-intfield>
</template>
@ -138,6 +175,7 @@ import TabsSettings from '@/components/TabsSettings.vue'
import SettingsCheckbox from '@/components/SettingsCheckbox.vue'
import SettingsTextfield from '@/components/SettingsTextfield.vue'
import SettingsIntfield from '@/components/SettingsIntfield.vue'
import DropdownMenu from '@/components/DropdownMenu.vue'
export default {
name: 'SettingsPageWebinterface',
@ -146,12 +184,28 @@ export default {
TabsSettings,
SettingsCheckbox,
SettingsTextfield,
SettingsIntfield
SettingsIntfield,
DropdownMenu
},
computed: {
settings_option_show_composer_now_playing() {
return this.$store.getters.settings_option_show_composer_now_playing
},
locale: {
get() {
return this.$i18n.locale
},
set(locale) {
this.$i18n.locale = locale
}
},
locales: {
get() {
return this.$i18n.availableLocales.map((item) => {
return { id: item, name: this.$t('language.' + item) }
})
}
}
}
}

View File

@ -1,29 +1,27 @@
<template>
<content-with-hero>
<template #heading-left>
<h1 class="title is-5">
{{ album.name }}
</h1>
<h1 class="title is-5" v-text="album.name" />
<h2 class="subtitle is-6 has-text-link has-text-weight-normal">
<a class="has-text-link" @click="open_artist">{{
album.artists[0].name
}}</a>
<a
class="has-text-link"
@click="open_artist"
v-text="album.artists[0].name"
/>
</h2>
<div class="buttons fd-is-centered-mobile fd-has-margin-top">
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.spotify.album.shuffle')" />
</a>
<a
class="button is-small is-light is-rounded"
@click="show_album_details_modal = true"
>
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
</div>
</template>
<template #heading-right>
<p class="image is-square fd-has-shadow fd-has-action">
<cover-artwork
@ -34,11 +32,13 @@
/>
</p>
</template>
<template #content>
<p class="heading is-7 has-text-centered-mobile fd-has-margin-top">
{{ album.tracks.total }} tracks
</p>
<p
class="heading is-7 has-text-centered-mobile fd-has-margin-top"
v-text="
$t('page.spotify.album.track-count', { count: album.tracks.total })
"
/>
<spotify-list-item-track
v-for="(track, index) in album.tracks.items"
:key="track.id"
@ -49,9 +49,7 @@
>
<template #actions>
<a @click.prevent.stop="open_track_dialog(track)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</template>
</spotify-list-item-track>

View File

@ -1,9 +1,7 @@
<template>
<content-with-heading>
<template #heading-left>
<p class="title is-4">
{{ artist.name }}
</p>
<p class="title is-4" v-text="artist.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -11,16 +9,19 @@
class="button is-small is-light is-rounded"
@click="show_artist_details_modal = true"
>
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.spotify.artist.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">{{ total }} albums</p>
<p
class="heading has-text-centered-mobile"
v-text="$t('page.spotify.artist.album-count', { count: total })"
/>
<spotify-list-item-album
v-for="album in albums"
:key="album.id"
@ -40,9 +41,7 @@
</template>
<template #actions>
<a @click.prevent.stop="open_dialog(album)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</template>
</spotify-list-item-album>

View File

@ -1,11 +1,10 @@
<template>
<div class="fd-page-with-tabs">
<tabs-music />
<!-- New Releases -->
<content-with-heading>
<template #heading-left>
<p class="title is-4">New Releases</p>
<p class="title is-4" v-text="$t('page.spotify.browse.new-releases')" />
</template>
<template #content>
<spotify-list-item-album
@ -27,9 +26,11 @@
</template>
<template #actions>
<a @click.prevent.stop="open_album_dialog(album)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon
class="icon has-text-dark"
name="dots-vertical"
size="16"
/>
</a>
</template>
</spotify-list-item-album>
@ -45,18 +46,19 @@
<router-link
to="/music/spotify/new-releases"
class="button is-light is-small is-rounded"
>
Show more
</router-link>
v-text="$t('page.spotify.browse.show-more')"
/>
</p>
</nav>
</template>
</content-with-heading>
<!-- Featured Playlists -->
<content-with-heading>
<template #heading-left>
<p class="title is-4">Featured Playlists</p>
<p
class="title is-4"
v-text="$t('page.spotify.browse.featured-playlists')"
/>
</template>
<template #content>
<spotify-list-item-playlist
@ -66,9 +68,11 @@
>
<template #actions>
<a @click.prevent.stop="open_playlist_dialog(playlist)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon
class="icon has-text-dark"
name="dots-vertical"
size="16"
/>
</a>
</template>
</spotify-list-item-playlist>
@ -84,9 +88,8 @@
<router-link
to="/music/spotify/featured-playlists"
class="button is-light is-small is-rounded"
>
Show more
</router-link>
v-text="$t('page.spotify.browse.show-more')"
/>
</p>
</nav>
</template>

View File

@ -1,7 +1,6 @@
<template>
<div class="fd-page-with-tabs">
<tabs-music />
<content-with-heading>
<template #heading-left>
<p class="title is-4">Featured Playlists</p>
@ -14,9 +13,11 @@
>
<template #actions>
<a @click.prevent.stop="open_playlist_dialog(playlist)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon
class="icon has-text-dark"
name="dots-vertical"
size="16"
/>
</a>
</template>
</spotify-list-item-playlist>

View File

@ -1,7 +1,6 @@
<template>
<div class="fd-page-with-tabs">
<tabs-music />
<content-with-heading>
<template #heading-left>
<p class="title is-4">New Releases</p>
@ -26,9 +25,11 @@
</template>
<template #actions>
<a @click.prevent.stop="open_album_dialog(album)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon
class="icon has-text-dark"
name="dots-vertical"
size="16"
/>
</a>
</template>
</spotify-list-item-album>

View File

@ -1,9 +1,7 @@
<template>
<content-with-heading>
<template #heading-left>
<div class="title is-4">
{{ playlist.name }}
</div>
<div class="title is-4" v-text="playlist.name" />
</template>
<template #heading-right>
<div class="buttons is-centered">
@ -11,18 +9,21 @@
class="button is-small is-light is-rounded"
@click="show_playlist_details_modal = true"
>
<span class="icon"><mdicon name="dots-horizontal" size="16" /></span>
<mdicon class="icon" name="dots-horizontal" size="16" />
</a>
<a class="button is-small is-dark is-rounded" @click="play">
<span class="icon"><mdicon name="shuffle" size="16" /></span>
<span>Shuffle</span>
<mdicon class="icon" name="shuffle" size="16" />
<span v-text="$t('page.spotify.playlist.shuffle')" />
</a>
</div>
</template>
<template #content>
<p class="heading has-text-centered-mobile">
{{ playlist.tracks.total }} tracks
</p>
<p
class="heading has-text-centered-mobile"
v-text="
$t('page.spotify.playlist.count', { count: playlist.tracks.total })
"
/>
<spotify-list-item-track
v-for="(item, index) in tracks"
:key="item.track.id"
@ -33,9 +34,7 @@
>
<template #actions>
<a @click.prevent.stop="open_track_dialog(item.track)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</template>
</spotify-list-item-track>

View File

@ -16,9 +16,7 @@
placeholder="Search"
autocomplete="off"
/>
<span class="icon is-left">
<mdicon name="magnify" size="16" />
</span>
<mdicon class="icon is-left" name="magnify" size="16" />
</p>
</div>
</form>
@ -28,20 +26,18 @@
:key="recent_search"
class="tag"
@click="open_recent_search(recent_search)"
>{{ recent_search }}</a
>
v-text="recent_search"
/>
</div>
</div>
</div>
</div>
</section>
<tabs-search :query="search_query" />
<!-- Tracks -->
<content-with-heading v-if="show_tracks && tracks.total">
<template #heading-left>
<p class="title is-4">Tracks</p>
<p class="title is-4" v-text="$t('page.spotify.search.tracks')" />
</template>
<template #content>
<spotify-list-item-track
@ -54,9 +50,11 @@
>
<template #actions>
<a @click.prevent.stop="open_track_dialog(track)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon
class="icon has-text-dark"
name="dots-vertical"
size="16"
/>
</a>
</template>
</spotify-list-item-track>
@ -79,22 +77,25 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_tracks"
>Show all {{ tracks.total.toLocaleString() }} tracks</a
>
v-text="
$t('page.spotify.search.show-all-tracks', {
count: tracks.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_tracks && !tracks.total" class="mt-6">
<template #content>
<p><i>No tracks found</i></p>
<p><i v-text="$t('page.spotify.search.no-tracks')" /></p>
</template>
</content-text>
<!-- Artists -->
<content-with-heading v-if="show_artists && artists.total">
<template #heading-left>
<p class="title is-4">Artists</p>
<p class="title is-4" v-text="$t('page.spotify.search.artists')" />
</template>
<template #content>
<spotify-list-item-artist
@ -104,9 +105,11 @@
>
<template #actions>
<a @click.prevent.stop="open_artist_dialog(artist)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon
class="icon has-text-dark"
name="dots-vertical"
size="16"
/>
</a>
</template>
</spotify-list-item-artist>
@ -128,22 +131,25 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_artists"
>Show all {{ artists.total.toLocaleString() }} artists</a
>
v-text="
$t('page.spotify.search.show-all-artists', {
count: artists.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_artists && !artists.total">
<template #content>
<p><i>No artists found</i></p>
<p><i v-text="$t('page.spotify.search.no-artists')" /></p>
</template>
</content-text>
<!-- Albums -->
<content-with-heading v-if="show_albums && albums.total">
<template #heading-left>
<p class="title is-4">Albums</p>
<p class="title is-4" v-text="$t('page.spotify.search.albums')" />
</template>
<template #content>
<spotify-list-item-album
@ -165,9 +171,11 @@
</template>
<template #actions>
<a @click.prevent.stop="open_album_dialog(album)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon
class="icon has-text-dark"
name="dots-vertical"
size="16"
/>
</a>
</template>
</spotify-list-item-album>
@ -189,22 +197,25 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_albums"
>Show all {{ albums.total.toLocaleString() }} albums</a
>
v-text="
$t('page.spotify.search.show-all-albums', {
count: albums.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_albums && !albums.total">
<template #content>
<p><i>No albums found</i></p>
<p><i v-text="$t('page.spotify.search.no-albums')" /></p>
</template>
</content-text>
<!-- Playlists -->
<content-with-heading v-if="show_playlists && playlists.total">
<template #heading-left>
<p class="title is-4">Playlists</p>
<p class="title is-4" v-text="$t('page.spotify.search.playlists')" />
</template>
<template #content>
<spotify-list-item-playlist
@ -214,9 +225,11 @@
>
<template #actions>
<a @click.prevent.stop="open_playlist_dialog(playlist)">
<span class="icon has-text-dark"
><mdicon name="dots-vertical" size="16"
/></span>
<mdicon
class="icon has-text-dark"
name="dots-vertical"
size="16"
/>
</a>
</template>
</spotify-list-item-playlist>
@ -238,15 +251,19 @@
<a
class="button is-light is-small is-rounded"
@click="open_search_playlists"
>Show all {{ playlists.total.toLocaleString() }} playlists</a
>
v-text="
$t('page.spotify.search.show-all-playlists', {
count: playlists.total.toLocaleString()
})
"
/>
</p>
</nav>
</template>
</content-with-heading>
<content-text v-if="show_playlists && !playlists.total">
<template #content>
<p><i>No playlists found</i></p>
<p><i v-text="$t('page.spotify.search.no-playlists')" /></p>
</template>
</content-text>
</div>

View File

@ -55,9 +55,9 @@ export default createStore({
hide_singles: false,
hide_spotify: false,
artists_sort: 'Name',
artist_albums_sort: 'Name',
albums_sort: 'Name',
artists_sort: 1,
artist_albums_sort: 1,
albums_sort: 1,
show_only_next_items: false,
show_burger_menu: false,
show_player_menu: false,

Some files were not shown because too many files have changed in this diff Show More