Start using vue-18n in templates

This commit is contained in:
Ciarán Ainsworth 2022-09-15 23:01:21 +00:00 committed by Kasper Seweryn
parent edfbf94313
commit 592e53486f
58 changed files with 1119 additions and 622 deletions

View File

@ -110,7 +110,7 @@ const save = async () => {
class="ui negative message"
>
<h4 class="header">
Error while saving settings
{{ $t('components.admin.SettingsGroup.errorMessage') }}
</h4>
<ul class="list">
<li
@ -125,7 +125,7 @@ const save = async () => {
v-if="result"
class="ui positive message"
>
Settings updated successfully.
{{ $t('components.admin.SettingsGroup.successMessage') }}
</div>
<div
v-for="(setting, key) in settings"
@ -225,7 +225,7 @@ const save = async () => {
<div v-if="values[setting.identifier]">
<div class="ui hidden divider" />
<h3 class="ui header">
Current image
{{ $t('components.admin.SettingsGroup.currentImage') }}
</h3>
<img
v-if="values[setting.identifier]"
@ -240,7 +240,7 @@ const save = async () => {
type="submit"
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
>
Save
{{ $t('components.admin.SettingsGroup.saveButton') }}
</button>
</form>
</template>

View File

@ -28,9 +28,9 @@ const isPreviewing = ref(false)
const { t } = useI18n()
const labels = computed(() => ({
delete: t('Delete'),
up: t('Move up'),
down: t('Move down')
delete: t('components.admin.SignupFormBuilder.deleteLabel'),
up: t('components.admin.SignupFormBuilder.moveUpLabel'),
down: t('components.admin.SignupFormBuilder.moveDownLabel')
}))
if (!value.value?.fields) {
@ -45,7 +45,7 @@ if (!value.value?.fields) {
const addField = () => {
value.value.fields.push({
label: t('Additional field') + ' ' + (value.value.fields.length + 1),
label: t('components.admin.SignupFormBuilder.additionalFieldInput') + ' ' + (value.value.fields.length + 1),
required: true,
input_type: 'short_text'
})
@ -69,13 +69,13 @@ const move = (idx: number, increment: number) => {
:class="[{active: !isPreviewing}, 'item']"
@click.stop.prevent="isPreviewing = false"
>
Edit form
{{ $t('components.admin.SignupFormBuilder.editForm') }}
</button>
<button
:class="[{active: isPreviewing}, 'item']"
@click.stop.prevent="isPreviewing = true"
>
Preview form
{{ $t('components.admin.SignupFormBuilder.previewForm') }}
</button>
</div>
<div
@ -95,10 +95,10 @@ const move = (idx: number, increment: number) => {
>
<div class="field">
<label for="help-text">
Help text
{{ $t('components.admin.SignupFormBuilder.helpTextLabel') }}
</label>
<p>
An optional text to be displayed at the start of the sign-up form.
{{ $t('components.admin.SignupFormBuilder.helpTextMessage') }}
</p>
<content-form
v-if="value.help_text"
@ -109,22 +109,22 @@ const move = (idx: number, increment: number) => {
</div>
<div class="field">
<label>
Additional fields
{{ $t('components.admin.SignupFormBuilder.additionalFieldsLabel') }}
</label>
<p>
Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled.
{{ $t('components.admin.SignupFormBuilder.additionalFieldsMessage') }}
</p>
<table v-if="value.fields?.length > 0">
<thead>
<tr>
<th>
Field label
{{ $t('components.admin.SignupFormBuilder.fieldLabelTableHeader') }}
</th>
<th>
Field type
{{ $t('components.admin.SignupFormBuilder.fieldTypeTableHeader') }}
</th>
<th>
Required
{{ $t('components.admin.SignupFormBuilder.requiredTableHeader') }}
</th>
<th><span class="visually-hidden">Actions</span></th>
</tr>
@ -144,20 +144,20 @@ const move = (idx: number, increment: number) => {
<td>
<select v-model="field.input_type">
<option value="short_text">
Short text
{{ $t('components.admin.SignupFormBuilder.shortTextInput') }}
</option>
<option value="long_text">
Long text
{{ $t('components.admin.SignupFormBuilder.longTextInput') }}
</option>
</select>
</td>
<td>
<select v-model="field.required">
<option :value="true">
Yes
{{ $t('components.admin.SignupFormBuilder.requiredTrue') }}
</option>
<option :value="false">
No
{{ $t('components.admin.SignupFormBuilder.requiredFalse') }}
</option>
</select>
</td>
@ -192,7 +192,7 @@ const move = (idx: number, increment: number) => {
class="ui basic button"
@click.stop.prevent="addField"
>
Add a new field
{{ $t('components.admin.SignupFormBuilder.addFieldButton') }}
</button>
</div>
</div>

View File

@ -33,7 +33,7 @@ const urlId = computed(() => props.object.actor?.is_local
const { t } = useI18n()
const updatedTitle = computed(() => {
const date = momentFormat(new Date(props.object.artist?.modification_date ?? '1970-01-01'))
return t('Updated on %{ date }', { date })
return t('components.audio.ChannelCard.updatedOn', { date })
})
// TODO (wvffle): Use time ago
@ -64,25 +64,17 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
</router-link>
</strong>
<div class="description">
<translate
<span
v-if="object.artist?.content_category === 'podcast'"
class="meta ellipsis"
translate-plural="%{ count } episodes"
:translate-n="object.artist.tracks_count"
:translate-params="{count: object.artist.tracks_count}"
>
%{ count } episode
</translate>
<translate
{{ $t('components.audio.ChannelCard.episodeCount', object.artist.tracks_count) }}
</span>
<span
v-else
:translate-params="{count: object.artist?.tracks_count}"
:translate-n="object.artist?.tracks_count"
translate-plural="%{ count } tracks"
>
%{ count } track
</translate>
{{ $t('components.audio.ChannelCard.trackCount', object.artist?.tracks_count) }}
</span>
<tags-list
label-classes="tiny"
:truncate-size="20"
@ -100,7 +92,7 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
:datetime="object.artist?.modification_date"
:title="updatedTitle"
>
%{ updatedAgo }
{{ $t('{ updatedAgo }') }}
</time>
<play-button
class="right floated basic icon"

View File

@ -103,7 +103,7 @@ watch(page, fetchData, { immediate: true })
@refresh="fetchData()"
>
<p>
You may need to subscribe to this channel to see its content.
{{ $t('components.audio.ChannelEntries.emptyMessage') }}
</p>
</empty-state>
</template>

View File

@ -45,13 +45,13 @@ const creating = computed(() => props.object === null)
const categoryChoices = computed(() => [
{
value: 'podcast',
label: t('Podcasts'),
helpText: t('Host your episodes and keep your community updated.')
label: t('components.audio.ChannelForm.podcastsLabel'),
helpText: t('components.audio.ChannelForm.podcastsHelpText')
},
{
value: 'music',
label: t('Artist discography'),
helpText: t('Publish music you make as a nice discography of albums and singles.')
label: t('components.audio.ChannelForm.discographyLabel'),
helpText: t('components.audio.ChannelForm.discographyHelpText')
}
])
@ -81,8 +81,8 @@ const itunesSubcategories = computed(() => {
})
const labels = computed(() => ({
namePlaceholder: t('Awesome channel name'),
usernamePlaceholder: t('awesomechannelname')
namePlaceholder: t('components.audio.ChannelForm.namePlaceholder'),
usernamePlaceholder: t('components.audio.ChannelForm.usernamePlaceholder')
}))
const submittable = computed(() => !!(
@ -165,7 +165,7 @@ defineExpose({
class="ui negative message"
>
<h4 class="header">
Error while saving channel
{{ $t('components.audio.ChannelForm.errorHeader') }}
</h4>
<ul class="list">
<li
@ -182,7 +182,7 @@ defineExpose({
class="ui grouped channel-type required field"
>
<legend>
What will this channel be used for?
{{ $t('components.audio.ChannelForm.channelPurposeLegend') }}
</legend>
<div class="ui hidden divider" />
<div class="field">
@ -210,7 +210,7 @@ defineExpose({
<template v-if="!creating || step === 2">
<div class="ui required field">
<label for="channel-name">
Name
{{ $t('components.audio.ChannelForm.channelNameLabel') }}
</label>
<input
v-model="newValues.name"
@ -221,7 +221,7 @@ defineExpose({
</div>
<div class="ui required field">
<label for="channel-username">
Fediverse handle
{{ $t('components.audio.ChannelForm.channelUsernameLabel') }}
</label>
<div class="ui left labeled input">
<div class="ui basic label">
@ -238,7 +238,7 @@ defineExpose({
<template v-if="creating">
<div class="ui small hidden divider" />
<p>
Used in URLs and to follow this channel in the Fediverse. It cannot be changed later.
{{ $t('components.audio.ChannelForm.channelUsernameDescription') }}
</p>
</template>
</div>
@ -248,7 +248,7 @@ defineExpose({
:image-class="newValues.content_category === 'podcast' ? '' : 'circular'"
@delete="newValues.cover = null"
>
Channel Picture
{{ $t('components.audio.ChannelForm.channelImageLabel') }}
</attachment-input>
</div>
<div class="ui small hidden divider" />
@ -256,7 +256,7 @@ defineExpose({
<div class="ten wide column">
<div class="ui field">
<label for="channel-tags">
Tags
{{ $t('components.audio.ChannelForm.channelTagsLabel') }}
</label>
<tags-selector
id="channel-tags"
@ -271,7 +271,7 @@ defineExpose({
>
<div class="ui required field">
<label for="channel-language">
Language
{{ $t('components.audio.ChannelForm.channelLanguageLabel') }}
</label>
<select
id="channel-language"
@ -294,7 +294,7 @@ defineExpose({
<div class="ui small hidden divider" />
<div class="ui field">
<label for="channel-name">
Description
{{ $t('components.audio.ChannelForm.channelDescriptionLabel') }}
</label>
<content-form v-model="newValues.description" />
</div>
@ -304,7 +304,7 @@ defineExpose({
>
<div class="ui required field">
<label for="channel-itunes-category">
Category
{{ $t('components.audio.ChannelForm.channelCategoryLabel') }}
</label>
<select
id="itunes-category"
@ -324,7 +324,7 @@ defineExpose({
</div>
<div class="ui field">
<label for="channel-itunes-category">
Subcategory
{{ $t('components.audio.ChannelForm.channelSubcategoryLabel') }}
</label>
<select
id="itunes-category"
@ -349,7 +349,7 @@ defineExpose({
>
<div class="ui field">
<label for="channel-itunes-email">
Owner e-mail address
{{ $t('components.audio.ChannelForm.channelEmailLabel') }}
</label>
<input
id="channel-itunes-email"
@ -360,7 +360,7 @@ defineExpose({
</div>
<div class="ui field">
<label for="channel-itunes-name">
Owner name
{{ $t('components.audio.ChannelForm.channelOwnerLabel') }}
</label>
<input
id="channel-itunes-name"
@ -371,7 +371,7 @@ defineExpose({
</div>
</div>
<p>
Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes.
{{ $t('components.audio.ChannelForm.channelPodcastFieldsHelp') }}
</p>
</template>
</template>
@ -380,7 +380,7 @@ defineExpose({
class="ui active inverted dimmer"
>
<div class="ui text loader">
Loading
{{ $t('components.audio.ChannelForm.loadingMessage') }}
</div>
</div>
</form>

View File

@ -55,14 +55,9 @@ const cover = computed(() => props.serie?.cover ?? null)
</router-link>
</strong>
<div class="description">
<translate
translate-plural="%{ count } episodes"
:translate-n="serie.tracks_count"
:translate-params="{count: serie.tracks_count}"
>
%{ count } episode
</translate>
<span>
{{ $t('components.audio.ChannelSerieCard.episodeCount', serie.tracks_count) }}
</span>
</div>
</div>
<div class="controls">

View File

@ -84,7 +84,7 @@ fetchData()
:class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)"
>
Show more
{{ $t('components.audio.ChannelSeries.showMore') }}
</button>
</template>
<template v-if="!isLoading && albums.length === 0">
@ -93,7 +93,7 @@ fetchData()
@refresh="fetchData()"
>
<p>
You may need to subscribe to this channel to see its contents.
{{ $t('components.audio.ChannelSeries.emptyMessage') }}
</p>
</empty-state>
</template>

View File

@ -77,7 +77,7 @@ fetchData()
:class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)"
>
Show more
{{ $t('components.audio.ChannelsWidget.showMore') }}
</button>
</template>
<template v-if="!isLoading && channels.length === 0">

View File

@ -52,20 +52,20 @@ const { copy, copied } = useClipboard({ source: textarea })
>
<p>
<strong>
Sharing will not work because this pod doesn't allow anonymous users to access content.
{{ $t('components.audio.EmbedWizard.anonymousAccessWarning') }}
</strong>
</p>
<p>
Please contact your admins and ask them to update the corresponding setting.
{{ $t('components.audio.EmbedWizard.anonymousAccessHelp') }}
</p>
</div>
<div class="ui form">
<div class="two fields">
<div class="field">
<div class="field">
<label for="embed-width">Widget width</label>
<label for="embed-width">{{ $t('components.audio.EmbedWizard.widgetWidthLabel') }}</label>
<p>
Leave empty for a responsive widget
{{ $t('components.audio.EmbedWizard.widgetWidthHelp') }}
</p>
<input
id="embed-width"
@ -78,7 +78,7 @@ const { copy, copied } = useClipboard({ source: textarea })
<template v-if="type != 'track'">
<br>
<div class="field">
<label for="embed-height">Widget height</label>
<label for="embed-height">{{ $t('components.audio.EmbedWizard.widgetHeightLabel') }}</label>
<input
id="embed-height"
v-model="height"
@ -95,11 +95,12 @@ const { copy, copied } = useClipboard({ source: textarea })
class="ui right accent labeled icon floated button"
@click="copy()"
>
<i class="copy icon" /> Copy
<i class="copy icon" />
{{ $t('components.audio.EmbedWizard.copyButton') }}
</button>
<label for="embed-width">Embed code</label>
<label for="embed-width">{{ $t('components.audio.EmbedWizard.embedCodeLabel') }}</label>
<p>
Copy/paste this code in your website HTML
{{ $t('components.audio.EmbedWizard.embedCodeHelp') }}
</p>
<textarea
ref="textarea"
@ -112,7 +113,7 @@ const { copy, copied } = useClipboard({ source: textarea })
v-if="copied"
class="message"
>
Text copied to clipboard!
{{ $t('components.audio.EmbedWizard.copyButtonSuccessMessage') }}
</p>
</div>
</div>
@ -124,7 +125,7 @@ const { copy, copied } = useClipboard({ source: textarea })
:href="iframeSrc"
target="_blank"
>
Preview
{{ $t('components.audio.EmbedWizard.previewHeader') }}
</a>
</h3>
<iframe

View File

@ -38,20 +38,20 @@ const toggle = () => {
@click.stop="toggle"
>
<i class="heart icon" />
<translate
<span
v-if="isApproved"
>
Unfollow
</translate>
<translate
{{ $t('components.audio.LibraryFollowButton.unfollowLabel') }}
</span>
<span
v-else-if="isPending"
>
Cancel follow request
</translate>
<translate
{{ $t('components.audio.LibraryFollowButton.cancelLabel') }}
</span>
<span
v-else
>
Follow
</translate>
{{ $t('components.audio.LibraryFollowButton.followLabel') }}
</span>
</button>
</template>

View File

@ -65,31 +65,31 @@ const { report, getReportableObjects } = useReport()
const { t } = useI18n()
const labels = computed(() => ({
playNow: t('Play now'),
addToQueue: t('Add to current queue'),
playNext: t('Play next'),
startRadio: t('Play similar songs'),
report: t('Report…'),
addToPlaylist: t('Add to playlist…'),
hideArtist: t('Hide content from this artist'),
playNow: t('components.audio.PlayButton.playNowLabel'),
addToQueue: t('components.audio.PlayButton.addToQueueLabel'),
playNext: t('components.audio.PlayButton.playNextLabel'),
startRadio: t('components.audio.PlayButton.startRadioLabel'),
report: t('components.audio.PlayButton.reportLabel'),
addToPlaylist: t('components.audio.PlayButton.addToPlaylistLabel'),
hideArtist: t('components.audio.PlayButton.hideArtistLabel'),
replacePlay: props.track
? t('Play track')
? t('components.audio.PlayButton.playTrackLabel')
: props.album
? t('Play album')
? t('components.audio.PlayButton.playAlbumLabel')
: props.artist
? t('Play artist')
? t('components.audio.PlayButton.playArtistLabel')
: props.playlist
? t('Play playlist')
: t('Play tracks')
? t('components.audio.PlayButton.playPlaylistLabel')
: t('components.audio.PlayButton.playTracksLabel')
}))
const title = computed(() => {
if (playable.value) {
return t('More…')
return t('components.audio.PlayButton.moreTitle')
}
if (props.track) {
return t('This track is not available in any library you have access to')
return t('components.audio.PlayButton.notAvailableTitle')
}
return ''
@ -138,7 +138,7 @@ const openMenu = () => {
v-else
:class="[playIconClass, 'icon']"
/>
<template v-if="!discrete && !iconOnly">&nbsp;<slot>Play</slot></template>
<template v-if="!discrete && !iconOnly">&nbsp;<slot>{{ $t('components.audio.PlayButton.discretePlayButton') }}</slot></template>
</button>
<button
v-if="!discrete && !iconOnly"
@ -156,7 +156,7 @@ const openMenu = () => {
:title="labels.addToQueue"
@click.stop.prevent="enqueue"
>
<i class="plus icon" />Add to queue
<i class="plus icon" />{{ labels.addToQueue }}
</button>
<button
class="item basic"
@ -181,7 +181,7 @@ const openMenu = () => {
:title="labels.startRadio"
@click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track?.id})"
>
<i class="feed icon" />Play radio
<i class="feed icon" />{{ labels.startRadio }}
</button>
<button
v-if="track"
@ -190,7 +190,7 @@ const openMenu = () => {
@click.stop="$store.commit('playlists/chooseTrack', track)"
>
<i class="list icon" />
Add to playlist
{{ labels.addToPlaylist }}
</button>
<button
v-if="track && $route.name !== 'library.tracks.detail'"
@ -198,12 +198,12 @@ const openMenu = () => {
@click.stop.prevent="$router.push(`/library/tracks/${track?.id}/`)"
>
<i class="info icon" />
<translate
<span
v-if="track.artist?.content_category === 'podcast'"
>Episode details</translate>
<translate
>{{ $t('components.audio.PlayButton.episodeDetailsButton') }}</span>
<span
v-else
>Track details</translate>
>{{ $t('components.audio.PlayButton.trackDetailsButton') }}</span>
</button>
<div class="divider" />
<button

View File

@ -72,17 +72,17 @@ onKeyboardShortcut(['ctrl', 'shift', 'left'], playPrevious, true)
onKeyboardShortcut(['ctrl', 'shift', 'right'], playNext, true)
const labels = computed(() => ({
audioPlayer: t('Media player'),
previous: t('Previous track'),
play: t('Play'),
pause: t('Pause'),
next: t('Next track'),
unmute: t('Unmute'),
mute: t('Mute'),
expandQueue: t('Expand queue'),
shuffle: t('Shuffle your queue'),
clear: t('Clear your queue'),
addArtistContentFilter: t('Hide content from this artist…')
audioPlayer: t('components.audio.Player.audioPlayerLabel'),
previous: t('components.audio.Player.previousTrackLabel'),
play: t('components.audio.Player.playLabel'),
pause: t('components.audio.Player.pauseLabel'),
next: t('components.audio.Player.nextTrackLabel'),
unmute: t('components.audio.Player.unmuteLabel'),
mute: t('components.audio.Player.muteLabel'),
expandQueue: t('components.audio.Player.expandQueueLabel'),
shuffle: t('components.audio.Player.shuffleQueueLabel'),
clear: t('components.audio.Player.clearQueueLabel'),
addArtistContentFilter: t('components.audio.Player.addArtistContentFilterLabel')
}))
const switchTab = () => {
@ -103,10 +103,10 @@ initializeFirstTrack()
const loopingTitle = computed(() => {
const mode = looping.value
return mode === LoopingMode.None
? t('Looping disabled. Click to switch to single-track looping.')
? t('components.audio.Player.loopingDisabledLabel')
: mode === LoopingMode.LoopTrack
? t('Looping on a single track. Click to switch to whole queue looping.')
: t('Looping on whole queue. Click to disable looping.')
? t('components.audio.Player.loopingSingleLabel')
: t('components.audio.Player.loopingWholeQueueLabel')
})
const hideArtist = () => {
@ -133,7 +133,7 @@ const hideArtist = () => {
id="player-label"
class="visually-hidden"
>
Audio player and controls
{{ $t('components.audio.Player.playerHeader') }}
</h1>
<div
class="ui inverted segment fixed-controls"
@ -299,22 +299,16 @@ const hideArtist = () => {
@click.stop="toggleMobilePlayer"
>
<i class="stream icon" />
<translate
:translate-params="{index: currentIndex + 1, length: queue.length}"
>
%{ index } of %{ length }
</translate>
<span>
{{ $t('components.audio.Player.queuePosition', { index: currentIndex + 1 }, { length: queue.length }) }}
</span>
</button>
<button
class="position circular control button desktop-and-below"
@click.stop="switchTab"
>
<i class="stream icon" />
<translate
:translate-params="{index: currentIndex + 1, length: queue.length}"
>
%{ index } of %{ length }
</translate>
{{ $t('components.audio.Player.queuePosition', { index: currentIndex + 1 }, { length: queue.length }) }}
</button>
<button

View File

@ -65,7 +65,7 @@ onMounted(() => {
})
const labels = computed(() => ({
searchPlaceholder: t('Artist, album, track…')
searchPlaceholder: t('components.audio.Search.searchPlaceHolderLabel')
}))
</script>
@ -73,7 +73,7 @@ const labels = computed(() => ({
<template>
<div>
<h2>
Search for some music
{{ $t('components.audio.Search.searchHeader') }}
</h2>
<div :class="['ui', {'loading': isLoading }, 'search']">
<div class="ui icon big input">
@ -89,7 +89,7 @@ const labels = computed(() => ({
</div>
<template v-if="query.length > 0">
<h3 class="ui title">
Artists
{{ $t('components.audio.Search.artistsHeader') }}
</h3>
<div v-if="results.artists.length > 0">
<div class="ui cards">
@ -101,12 +101,12 @@ const labels = computed(() => ({
</div>
</div>
<p v-else>
No artist matched your query
{{ $t('components.audio.Search.noArtistsMessage') }}
</p>
</template>
<template v-if="query.length > 0">
<h3 class="ui title">
Albums
{{ $t('components.audio.Search.albumsHeader') }}
</h3>
<div
v-if="results.albums.length > 0"
@ -124,7 +124,7 @@ const labels = computed(() => ({
</div>
</div>
<p v-else>
No album matched your query
{{ $t('components.audio.Search.noAlbumsMessage') }}
</p>
</template>
</div>

View File

@ -50,12 +50,12 @@ onKeyboardShortcut(['ctrl', 'k'], () => (focused.value = true), true)
const { t } = useI18n()
const labels = computed(() => ({
placeholder: t('Search for artists, albums, tracks…'),
searchContent: t('Search for content'),
artist: t('Artist'),
album: t('Album'),
track: t('Track'),
tag: t('Tag')
placeholder: t('components.audio.SearchBar.placeHolderLabel'),
searchContent: t('components.audio.SearchBar.searchContentLabel'),
artist: t('components.audio.SearchBar.artistLabel'),
album: t('components.audio.SearchBar.albumLabel'),
track: t('components.audio.SearchBar.trackLabel'),
tag: t('components.audio.SearchBar.tagLabel')
}))
const router = useRouter()
@ -77,11 +77,11 @@ const blur = () => {
const categories = computed(() => [
{
code: 'federation',
name: t('Federation')
name: t('components.audio.SearchBar.federationCategory')
},
{
code: 'podcasts',
name: t('Podcasts')
name: t('components.audio.SearchBar.podcastsCategory')
},
{
code: 'artists',
@ -138,8 +138,8 @@ onMounted(() => {
showNoResults: true,
error: {
// @ts-expect-error Semantic is broken
noResultsHeader: t('No matches found'),
noResults: t('Sorry, there are no results for this search')
noResultsHeader: t('components.audio.SearchBar.noResultsHeader'),
noResults: t('components.audio.SearchBar.noResultsMessage')
},
onSelect (result, response) {
@ -179,7 +179,7 @@ onMounted(() => {
if (category.code === 'federation' && id) {
resultsEmpty = false
results[category.code]?.results.push({
title: t('Search on the fediverse'),
title: t('components.audio.SearchBar.fediverseSearchLabel'),
routerUrl: {
name: 'search',
query: { id }
@ -190,7 +190,7 @@ onMounted(() => {
if (category.code === 'podcasts' && id) {
resultsEmpty = false
results[category.code]?.results.push({
title: t('Subscribe to podcast via RSS'),
title: t('components.audio.SearchBar.rssSearchLabel'),
routerUrl: {
name: 'search',
query: { id, type: 'rss' }
@ -200,7 +200,7 @@ onMounted(() => {
if (category.code === 'more') {
results[category.code]?.results.push({
title: t('More results 🡒'),
title: t('components.audio.SearchBar.moreResultsLabel'),
routerUrl: {
name: 'search',
query: { type: 'artists', q: query.value }

View File

@ -9,9 +9,9 @@ const expanded = ref(false)
const { t } = useI18n()
const labels = computed(() => ({
unmute: t('Unmute'),
mute: t('Mute'),
slider: t('Adjust volume')
unmute: t('components.audio.VolumeControl.unmuteLabel'),
mute: t('components.audio.VolumeControl.muteLabel'),
slider: t('components.audio.VolumeControl.sliderLabel')
}))
const { start, stop } = useTimeoutFn(() => (expanded.value = false), 500, { immediate: false })

View File

@ -59,14 +59,9 @@ const imageUrl = computed(() => props.album.cover?.urls.original
</div>
<div class="extra content">
<span v-if="album.release_date">{{ momentFormat(new Date(album.release_date), 'Y') }} · </span>
<translate
:translate-params="{count: album.tracks_count}"
:translate-n="album.tracks_count"
translate-plural="%{ count } tracks"
>
%{ count } track
</translate>
<span>
{{ $t('components.audio.album.Card.trackCount', album.tracks_count) }}
</span>
<play-button
class="right floated basic icon"
:dropdown-only="true"

View File

@ -112,7 +112,7 @@ watch(
:class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)"
>
Show more
{{ $t('components.audio.album.Widget.showMore') }}
</button>
</template>
</div>

View File

@ -62,24 +62,16 @@ const imageUrl = computed(() => cover.value?.urls.original
/>
</div>
<div class="extra content">
<translate
<span
v-if="artist.content_category === 'music'"
:translate-params="{count: artist.tracks_count}"
:translate-n="artist.tracks_count"
translate-plural="%{ count } tracks"
>
%{ count } track
</translate>
<translate
{{ $t('components.audio.artist.Card.trackCount', artist.tracks_count) }}
</span>
<span
v-else
:translate-params="{count: artist.tracks_count}"
:translate-n="artist.tracks_count"
translate-plural="%{ count } episodes"
>
%{ count } episode
</translate>
{{ $t('components.audio.artist.Card.episodeCount', artist.tracks_count) }}
</span>
<play-button
class="right floated basic icon"
:dropdown-only="true"

View File

@ -108,7 +108,7 @@ watch(
:class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)"
>
Show more
{{ $t('components.audio.artist.Widget.showMore') }}
</button>
</template>
</div>

View File

@ -53,7 +53,7 @@ const { isPlaying } = usePlayer()
const { activateTrack } = usePlayOptions(props)
const { t } = useI18n()
const actionsButtonLabel = computed(() => t('Show track actions'))
const actionsButtonLabel = computed(() => t('components.audio.podcast.MobileRow.actionsButtonlabel'))
</script>
<template>

View File

@ -60,31 +60,31 @@ const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.tr
const { t } = useI18n()
const favoriteButton = computed(() => isFavorite.value
? t('Remove from favorites')
: t('Add to favorites')
? t('components.audio.podcast.Modal.removeFromFavorites')
: t('components.audio.podcast.Modal.addToFavorites')
)
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? t('Episode details')
: t('Track details')
? t('components.audio.podcast.Modal.episodeDetails')
: t('components.audio.podcast.Modal.trackDetails')
)
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? t('View series')
: t('View album')
? t('components.audio.podcast.Modal.seriesDetails')
: t('components.audio.podcast.Modal.albumDetails')
)
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? t('View channel')
: t('View artist')
? t('components.audio.podcast.Modal.channelDetails')
: t('components.audio.podcast.Modal.artistDetails')
)
const labels = computed(() => ({
startRadio: t('Play radio'),
playNow: t('Play now'),
addToQueue: t('Add to queue'),
playNext: t('Play next'),
addToPlaylist: t('Add to playlist…')
startRadio: t('components.audio.podcast.Modal.startRadio'),
playNow: t('components.audio.podcast.Modal.playNow'),
addToQueue: t('components.audio.podcast.Modal.addToQueue'),
playNext: t('components.audio.podcast.Modal.playNext'),
addToPlaylist: t('components.audio.podcast.Modal.addToPlaylist')
}))
</script>

View File

@ -53,7 +53,7 @@ const { isPlaying } = usePlayer()
const { activateTrack } = usePlayOptions(props)
const { t } = useI18n()
const actionsButtonLabel = computed(() => t('Show track actions'))
const actionsButtonLabel = computed(() => t('components.audio.track.MobileRow.actionsButtonlabel'))
</script>
<template>

View File

@ -60,31 +60,31 @@ const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.tr
const { t } = useI18n()
const favoriteButton = computed(() => isFavorite.value
? t('Remove from favorites')
: t('Add to favorites')
? t('components.audio.track.Modal.removeFromFavorites')
: t('components.audio.track.Modal.addToFavorites')
)
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? t('Episode details')
: t('Track details')
? t('components.audio.track.Modal.episodeDetails')
: t('components.audio.track.Modal.trackDetails')
)
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? t('View series')
: t('View album')
? t('components.audio.track.Modal.seriesDetails')
: t('components.audio.track.Modal.albumDetails')
)
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? t('View channel')
: t('View artist')
? t('components.audio.track.Modal.channelDetails')
: t('components.audio.track.Modal.artistDetails')
)
const labels = computed(() => ({
startRadio: t('Play radio'),
playNow: t('Play now'),
addToQueue: t('Add to queue'),
playNext: t('Play next'),
addToPlaylist: t('Add to playlist…')
startRadio: t('components.audio.track.Modal.startRadio'),
playNow: t('components.audio.track.Modal.playNow'),
addToQueue: t('components.audio.track.Modal.addToQueue'),
playNext: t('components.audio.track.Modal.playNext'),
addToPlaylist: t('components.audio.track.Modal.addToPlaylist')
}))
</script>

View File

@ -95,9 +95,9 @@ const allTracks = computed(() => {
const { t } = useI18n()
const labels = computed(() => ({
title: t('Title'),
album: t('Album'),
artist: t('Artist')
title: t('components.audio.track.Table.titleLabel'),
album: t('components.audio.track.Table.albumLabel'),
artist: t('components.audio.track.Table.albumLabel')
}))
const isLoading = ref(false)

View File

@ -200,7 +200,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
>
<div class="ui icon header">
<i class="music icon" />
Nothing found
{{ $t('components.audio.track.Widget.noResultsMessage') }}
</div>
<div
v-if="isLoading"
@ -215,7 +215,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
:class="['ui', 'basic', 'button']"
@click="fetchData(nextPage as string)"
>
Show more
{{ $t('components.audio.track.Widget.showMore') }}
</button>
</template>
</div>

View File

@ -20,7 +20,7 @@ const { t } = useI18n()
const application = ref()
const labels = computed(() => ({
title: t('Edit application')
title: t('components.auth.ApplicationEdit.editApplicationLabel')
}))
const isLoading = ref(false)
@ -72,17 +72,17 @@ store.state.auth.applicationSecret = undefined
</div>
<template v-else>
<router-link :to="{name: 'settings'}">
Back to settings
{{ $t('components.auth.ApplicationEdit.backToSettingsLink') }}
</router-link>
<h2 class="ui header">
Application details
{{ $t('components.auth.ApplicationEdit.appDetailsHeader') }}
</h2>
<div class="ui form">
<p>
Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else.
{{ $t('components.auth.ApplicationEdit.appDetailsDescription') }}
</p>
<div class="field">
<label for="copy-id">Application ID</label>
<label for="copy-id">{{ $t('components.auth.ApplicationEdit.appIdLabel') }}</label>
<copy-input
id="copy-id"
:value="application.client_id"
@ -94,14 +94,14 @@ store.state.auth.applicationSecret = undefined
>
<div class="ui small warning message">
<h3 class="header">
Keep a copy of this token in a safe place
{{ $t('components.auth.ApplicationEdit.appSecretWarningTitle') }}
</h3>
<p>
You won't be able to see it again once you leave this screen.
{{ $t('components.auth.ApplicationEdit.appSecretWarningMessage') }}
</p>
</div>
<label for="copy-secret">Application secret</label>
<label for="copy-secret">{{ $t('components.auth.ApplicationEdit.appSecretLabel') }}</label>
<copy-input
id="copy-secret"
:value="secret"
@ -111,7 +111,7 @@ store.state.auth.applicationSecret = undefined
v-if="application.token != undefined"
class="field"
>
<label for="copy-secret">Access token</label>
<label for="copy-secret">{{ $t('components.auth.ApplicationEdit.accessTokenLabel') }}</label>
<copy-input
id="copy-secret"
:value="application.token"
@ -121,12 +121,12 @@ store.state.auth.applicationSecret = undefined
@click.prevent="refreshToken"
>
<i class="refresh icon" />
Regenerate token
{{ $t('components.auth.ApplicationEdit.regenerateTokenLink') }}
</a>
</div>
</div>
<h2 class="ui header">
Edit application
{{ $t('components.auth.ApplicationEdit.editAppHeader') }}
</h2>
<application-form
:app="application"

View File

@ -86,14 +86,14 @@ const toggleAllScopes = (parent: typeof allScopes['value'][number]) => {
const scopeParents = computedEager(() => [
{
id: 'read',
label: t('Read'),
description: t('Read-only access to user data'),
label: t('components.auth.ApplicationForm.readScopeLabel'),
description: t('components.auth.ApplicationForm.readScopeDescription'),
value: scopeArray.value.includes('read')
},
{
id: 'write',
label: t('Write'),
description: t('Write-only access to user data'),
label: t('components.auth.ApplicationForm.writeScopeLabel'),
description: t('components.auth.ApplicationForm.writeScopeDescription'),
value: scopeArray.value.includes('write')
}
])
@ -120,7 +120,7 @@ const allScopes = computed(() => {
class="ui negative message"
>
<h4 class="header">
We cannot save your changes
{{ $t('components.auth.ApplicationForm.saveFailureMessage') }}
</h4>
<ul class="list">
<li
@ -132,7 +132,7 @@ const allScopes = computed(() => {
</ul>
</div>
<div class="ui field">
<label for="application-name">Name</label>
<label for="application-name">{{ $t('components.auth.ApplicationForm.applicationNameLabel') }}</label>
<input
id="application-name"
v-model="fields.name"
@ -142,7 +142,7 @@ const allScopes = computed(() => {
>
</div>
<div class="ui field">
<label for="redirect-uris">Redirect URI</label>
<label for="redirect-uris">{{ $t('components.auth.ApplicationForm.redirectUrisLabel') }}</label>
<input
id="redirect-uris"
v-model="fields.redirect_uris"
@ -150,13 +150,13 @@ const allScopes = computed(() => {
type="text"
>
<p class="help">
Use "urn:ietf:wg:oauth:2.0:oob" as a redirect URI if your application is not served on the web.
{{ $t('components.auth.ApplicationForm.redirectUrisHelp') }}
</p>
</div>
<div class="ui field">
<label>Scopes</label>
<label>{{ $t('components.auth.ApplicationForm.scopesLabel') }}</label>
<p>
Checking the parent "Read" or "Write" scopes implies access to all the corresponding children scopes.
{{ $t('components.auth.ApplicationForm.scopesDescription') }}
</p>
<div class="ui stackable two column grid">
<div
@ -205,12 +205,12 @@ const allScopes = computed(() => {
<translate
v-if="app !== null"
>
Update application
{{ $t('components.auth.ApplicationForm.updateButtonLabel') }}
</translate>
<translate
v-else
>
Create application
{{ $t('components.auth.ApplicationForm.createButtonLabel') }}
</translate>
</button>
</form>

View File

@ -29,7 +29,7 @@ const defaults = reactive({
const { t } = useI18n()
const labels = computed(() => ({
title: t('Create a new application')
title: t('components.auth.ApplicationNew.title')
}))
const router = useRouter()
@ -55,10 +55,10 @@ const created = (application: Application) => {
<div class="ui vertical stripe segment">
<section class="ui text container">
<router-link :to="{name: 'settings'}">
Back to settings
{{ $t('components.auth.ApplicationNew.backToSettingsLink') }}
</router-link>
<h2 class="ui header">
Create a new application
{{ title }}
</h2>
<application-form
:defaults="defaults"

View File

@ -84,7 +84,7 @@ const submit = async () => {
}
const labels = computed(() => ({
title: t('Allow application')
title: t('components.auth.Authorize.title')
}))
const requestedScopes = computed(() => props.scope.split(' '))
@ -119,7 +119,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
<section class="ui vertical stripe segment">
<div class="ui small text container">
<h2>
<i class="lock open icon" /> Authorize third-party app
<i class="lock open icon" />{{ $t('components.auth.Authorize.authorizeThirdPartyAppHeader') }}
</h2>
<div
v-if="errors.length > 0"
@ -130,13 +130,13 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
v-if="application"
class="header"
>
Error while authorizing application
{{ $t('components.auth.Authorize.authorizeFailureMessage') }}
</h4>
<h4
v-else
class="header"
>
Error while fetching application data
{{ $t('components.auth.Authorize.fetchDataFailureMessage') }}
</h4>
<ul class="list">
<li
@ -159,12 +159,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
@submit.prevent="submit"
>
<h3>
<translate
:translate-params="{app: application.name}"
>
%{ app } wants to access your Funkwhale account
</translate>
{{ $t('components.auth.Authorize.appAccessHeader', app: application.name) }}
</h3>
<h4
@ -177,20 +172,20 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
>
<i class="pencil icon" />
Write-only
{{ $t('components.auth.Authorize.writeOnlyScopeHeader') }}
</span>
<span
v-else-if="!topic.write && topic.read"
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
>
Read-only
{{ $t('components.auth.Authorize.writeOnlyScopeHeader') }}
</span>
<span
v-else-if="topic.write && topic.read"
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
>
<i class="pencil icon" />
Full access
{{ $t('components.auth.Authorize.allScopesHeader') }}
</span>
<i :class="[topic.icon, 'icon']" />
<div class="content">
@ -201,7 +196,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
</div>
</h4>
<div v-if="unknownRequestedScopes.length > 0">
<p><strong>The application is also requesting the following unknown permissions:</strong></p>
<p><strong>{{ $t('components.auth.Authorize.unknownPermissionsMessage') }}</strong></p>
<ul
v-for="(unknownscope, key) in unknownRequestedScopes"
:key="key"
@ -214,30 +209,21 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
type="submit"
>
<i class="lock open icon" />
<translate
:translate-params="{app: application.name}"
>
Authorize %{ app }
</translate>
{{ $t('components.auth.Authorize.authorizeAppButton', { app: application.name }) }}
</button>
<p
v-if="redirectUri === 'urn:ietf:wg:oauth:2.0:oob'"
v-translate
>
You will be shown a code to copy-paste in the application.
{{ $t('components.auth.Authorize.copyCodeHelp') }}
</p>
<p
v-else
v-translate="{url: redirectUri}"
:translate-params="{url: redirectUri}"
>
You will be redirected to <strong>%{ url }</strong>
{{ $t('components.auth.Authorize.redirectHelp', url: redirectUri) }}
</p>
</form>
<div v-else-if="code">
<p><strong>Copy-paste the following code in the application:</strong></p>
<p><strong>{{ $t('components.auth.Authorize.copyCodeDescription') }}</strong></p>
<copy-input :value="code" />
</div>
</div>

View File

@ -30,7 +30,7 @@ const credentials = reactive({
})
const labels = computed(() => ({
usernamePlaceholder: t('Enter your username or e-mail address')
usernamePlaceholder: t('components.auth.LoginForm.usernamePlaceholder')
}))
const username = ref()
@ -75,14 +75,14 @@ const submit = async () => {
class="ui negative message"
>
<h4 class="header">
We cannot log you in
{{ $t('components.auth.LoginForm.loginFailureHeader') }}
</h4>
<ul class="list">
<li v-if="errors[0] == 'invalid_credentials' && $store.state.instance.settings.moderation.signup_approval_enabled.value">
If you signed-up recently, you may need to wait before our moderation team review your account, or verify your e-mail address.
{{ $t('components.auth.LoginForm.approvalRequiredHelp') }}
</li>
<li v-else-if="errors[0] == 'invalid_credentials'">
Please double-check that your username and password combination is correct and make sure you verified your e-mail address.
{{ $t('components.auth.LoginForm.invalidCredentialsHelp') }}
</li>
<li v-else>
{{ errors[0] }}
@ -92,11 +92,11 @@ const submit = async () => {
<template v-if="domain === $store.getters['instance/domain']">
<div class="field">
<label for="username-field">
Username or e-mail address
{{ $t('components.auth.LoginForm.usernameFieldLabel') }}
<template v-if="showSignup">
|
<router-link :to="{path: '/signup'}">
Create an account
{{ $t('components.auth.LoginForm.createAccountLink') }}
</router-link>
</template>
</label>
@ -113,12 +113,12 @@ const submit = async () => {
</div>
<div class="field">
<label for="password-field">
Password |
{{ $t('components.auth.LoginForm.passwordFieldLabel') }}
<router-link
tabindex="1"
:to="{name: 'auth.password-reset', query: {email: credentials.username}}"
>
Reset your password
{{ $t('components.auth.LoginForm.resetPasswordLink') }}
</router-link>
</label>
<password-input
@ -130,18 +130,14 @@ const submit = async () => {
</template>
<template v-else>
<p>
<translate
:translate-params="{domain: $store.getters['instance/domain']}"
>
You will be redirected to %{ domain } to authenticate.
</translate>
{{ $t('components.auth.LoginForm.redirectMessage', { domain: $store.getters['instance/domain'] }) }}
</p>
</template>
<button
:class="['ui', {'loading': isLoading}, 'right', 'floated', buttonClasses, 'button']"
type="submit"
>
Login
{{ $t('components.auth.LoginForm.loginButton') }}
</button>
</form>
</template>

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const labels = computed(() => ({
title: t('Log Out')
title: t('components.auth.Logout.title')
}))
</script>
@ -19,18 +19,16 @@ const labels = computed(() => ({
class="ui small text container"
>
<h2>
Are you sure you want to log out?
{{ $t('components.auth.Logout.confirmHeader') }}
</h2>
<p
v-translate="{username: $store.state.auth.username}"
>
You are currently logged in as %{ username }
<p>
{{ $t('components.auth.Logout.loggedInUsername', { username: $store.state.auth.username }) }}
</p>
<button
class="ui button"
@click="$store.dispatch('auth/logout')"
>
Yes, log me out!
{{ $t('components.auth.Logout.confirmLogoutButton') }}
</button>
</div>
<div
@ -38,13 +36,13 @@ const labels = computed(() => ({
class="ui small text container"
>
<h2>
You aren't currently logged in
{{ $t('components.auth.Logout.loggedOutHeader') }}
</h2>
<router-link
to="/login"
class="ui button"
>
Log in!
{{ $t('components.auth.Logout.logInLink') }}
</router-link>
</div>
</section>

View File

@ -69,7 +69,7 @@ const submitAndScan = async () => {
target="_blank"
>
<i class="external icon" />
Documentation
{{ $t('components.auth.Plugin.documentationLink') }}
</a>
</template>
<div class="ui clearing hidden divider" />
@ -79,7 +79,7 @@ const submitAndScan = async () => {
class="ui negative message"
>
<h4 class="header">
Error while saving plugin
{{ $t('components.auth.Plugin.saveFailureHeader') }}
</h4>
<ul class="list">
<li
@ -97,7 +97,7 @@ const submitAndScan = async () => {
v-model="enabled"
type="checkbox"
>
<label :for="`${plugin.name}-enabled`">Enabled</label>
<label :for="`${plugin.name}-enabled`">{{ $t('components.auth.Plugin.pluginEnabledLabel') }}</label>
</div>
</div>
<div class="ui clearing hidden divider" />
@ -105,7 +105,7 @@ const submitAndScan = async () => {
v-if="plugin.source"
class="field"
>
<label for="plugin-library">Library</label>
<label for="plugin-library">{{ $t('components.auth.Plugin.libraryLabel') }}</label>
<select
id="plugin-library"
v-model="values['library']"
@ -119,7 +119,7 @@ const submitAndScan = async () => {
</option>
</select>
<div>
Library where files should be imported.
{{ $t('components.auth.Plugin.libraryDescription') }}
</div>
</div>
<template v-if="(plugin.conf?.length ?? 0) > 0">
@ -194,14 +194,14 @@ const submitAndScan = async () => {
type="submit"
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
>
Save
{{ $t('components.auth.Plugin.saveButton') }}
</button>
<button
v-if="plugin.source"
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
@click.prevent="submitAndScan"
>
Scan
{{ $t('components.auth.Plugin.scanButton') }}
</button>
<div class="ui clearing hidden divider" />
</form>

View File

@ -57,7 +57,7 @@ const settings = reactive({
const orderedSettingsFields = SETTINGS_ORDER.map(id => settings.fields[id])
const labels = computed(() => ({
title: t('Account Settings')
title: t('components.auth.Settings.title')
}))
const isLoading = ref(false)
@ -224,7 +224,7 @@ const deleteAccount = async () => {
await axios.delete('users/me/', { data: payload })
store.commit('ui/addMessage', {
content: t('Your deletion request was submitted, your account and content will be deleted shortly'),
content: t('components.auth.Settings.deletionRequest'),
date: new Date()
})
@ -276,7 +276,7 @@ fetchOwnedApps()
<div class="ui vertical stripe segment">
<section class="ui text container">
<h2 class="ui header">
Account settings
{{ $t('components.auth.Settings.accountSettingsHeader') }}
</h2>
<form
class="ui form"
@ -287,7 +287,7 @@ fetchOwnedApps()
class="ui positive message"
>
<h4 class="header">
Settings updated
{{ $t('components.auth.Settings.settingsUpdatedHeader') }}
</h4>
</div>
<div
@ -296,7 +296,7 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
Your settings can't be updated
{{ $t('components.auth.Settings.settingsUpdateFailureHeader') }}
</h4>
<ul class="list">
<li
@ -340,14 +340,14 @@ fetchOwnedApps()
:class="['ui', { loading: isLoading }, 'button']"
type="submit"
>
Update settings
{{ $t('components.auth.Settings.updateSettingsButton') }}
</button>
</form>
</section>
<section class="ui text container">
<div class="ui hidden divider" />
<h2 class="ui header">
Avatar
{{ $t('components.auth.Settings.avatarHeader') }}
</h2>
<div class="ui form">
<div
@ -356,7 +356,7 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
Your avatar cannot be saved
{{ $t('components.auth.Settings.avatarSaveFailureHeader') }}
</h4>
<ul class="list">
<li
@ -373,7 +373,7 @@ fetchOwnedApps()
@update:model-value="submitAvatar($event)"
@delete="avatar = {uuid: null}"
>
Avatar
{{ $t('components.auth.Settings.avatarInputLabel') }}
</attachment-input>
</div>
</section>
@ -381,10 +381,10 @@ fetchOwnedApps()
<section class="ui text container">
<div class="ui hidden divider" />
<h2 class="ui header">
Change my password
{{ $t('components.auth.Settings.changePasswordHeader') }}
</h2>
<div class="ui message">
Changing your password will also change your Subsonic API password if you have requested one.&nbsp; You will have to update your password on your clients that use this password.
{{ $t('components.auth.Settings.changePasswordMessage') }}
</div>
<form
class="ui form"
@ -396,16 +396,16 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
Your password cannot be changed
{{ $t('components.auth.Settings.changePasswordFailureMessage') }}
</h4>
<ul class="list">
<li v-if="passwordError == 'invalid_credentials'">
Please double-check your password is correct
{{ $t('components.auth.Settings.changePasswordHelp') }}
</li>
</ul>
</div>
<div class="field">
<label for="old-password-field">Current password</label>
<label for="old-password-field">{{ $t('components.auth.Settings.currentPasswordLabel') }}</label>
<password-input
v-model="credentials.oldPassword"
field-id="old-password-field"
@ -413,7 +413,7 @@ fetchOwnedApps()
/>
</div>
<div class="field">
<label for="new-password-field">New password</label>
<label for="new-password-field">{{ $t('components.auth.Settings.newPasswordLabel') }}</label>
<password-input
v-model="credentials.newPassword"
field-id="new-password-field"
@ -424,30 +424,30 @@ fetchOwnedApps()
:class="['ui', {'loading': isLoadingPassword}, {disabled: !credentials.newPassword || !credentials.oldPassword}, 'warning', 'button']"
:action="submitPassword"
>
Change password
{{ $t('components.auth.Settings.changePasswordButton') }}
<template #modal-header>
<p>
Change your password?
{{ $t('components.auth.Settings.changePasswordModalHeader') }}
</p>
</template>
<template #modal-content>
<div>
<p>
Changing your password will have the following consequences:
{{ $t('components.auth.Settings.changePasswordWarning') }}
</p>
<ul>
<li>
You will be logged out from this session and have to log in with the new one
{{ $t('components.auth.Settings.changePasswordLogout') }}
</li>
<li>
Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password
{{ $t('components.auth.Settings.changePasswordSubsonic') }}
</li>
</ul>
</div>
</template>
<template #modal-confirm>
<div>
Disable access
{{ $t('components.auth.Settings.disableSubsonicMessage') }}
</div>
</template>
</dangerous-button>
@ -464,11 +464,11 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="eye slash outline icon" />
<div class="content">
Content filters
{{ $t('components.auth.Settings.contentFiltersHeader') }}
</div>
</h2>
<p>
Content filters help you hide content you don't want to see on the service.
{{ $t('components.auth.Settings.contentFiltersDescription') }}
</p>
<button
@ -476,19 +476,19 @@ fetchOwnedApps()
@click="$store.dispatch('moderation/fetchContentFilters')"
>
<i class="refresh icon" />&nbsp;
Refresh
{{ $t('components.auth.Settings.refreshButton') }}
</button>
<h3 class="ui header">
Hidden artists
{{ $t('components.auth.Settings.hiddenArtistsHeader') }}
</h3>
<table class="ui compact very basic unstackable table">
<thead>
<tr>
<th>
Name
{{ $t('components.auth.Settings.artistNameTableHeader') }}
</th>
<th>
Creation date
{{ $t('components.auth.Settings.filterCreationDateTableHeader') }}
</th>
<th />
</tr>
@ -511,7 +511,7 @@ fetchOwnedApps()
class="ui basic tiny button"
@click="$store.dispatch('moderation/deleteContentFilter', filter.uuid)"
>
Delete
{{ $t('components.auth.Settings.filterDeleteButton') }}
</button>
</td>
</tr>
@ -526,18 +526,18 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="open lock icon" />
<div class="content">
Authorized apps
{{ $t('components.auth.Settings.authorizedAppsHeader') }}
</div>
</h2>
<p>
This is the list of applications that have access to your account data.
{{ $t('components.auth.Settings.authorizedAppsDescription') }}
</p>
<button
:class="['ui', 'icon', { loading: isLoadingApps }, 'button']"
@click="fetchApps()"
>
<i class="refresh icon" />&nbsp;
Refresh
{{ $t('components.auth.Settings.refreshButton') }}
</button>
<table
v-if="apps.length > 0"
@ -546,10 +546,10 @@ fetchOwnedApps()
<thead>
<tr>
<th>
Application
{{ $t('components.auth.Settings.appNameTableHeader') }}
</th>
<th>
Permissions
{{ $t('components.auth.Settings.appPermissionsTableHeader') }}
</th>
<th />
</tr>
@ -570,22 +570,20 @@ fetchOwnedApps()
:class="['ui', 'tiny', 'danger', { loading: isRevoking.has(app.client_id) }, 'button']"
@confirm="revokeApp(app.client_id)"
>
Revoke
{{ $t('components.auth.Settings.permissionDeleteButton') }}
<template #modal-header>
<p
v-translate="{application: app.name}"
>
Revoke access for application "%{ application }"?
<p>
{{ $t('components.auth.Settings.revokePermissionModalMessage', app: app.name) }}
</p>
</template>
<template #modal-content>
<p>
This will prevent this application from accessing the service on your behalf.
{{ $t('components.auth.Settings.revokePermissionModalWarning') }}
</p>
</template>
<template #modal-confirm>
<div>
Revoke access
{{ $t('components.auth.Settings.revokeAccessButton') }}
</div>
</template>
</dangerous-button>
@ -595,9 +593,9 @@ fetchOwnedApps()
</table>
<empty-state v-else>
<template #title>
You don't have any application connected with your account.
{{ $t('components.auth.Settings.emptyAppMessage') }}
</template>
If you authorize third-party applications to access your data, those applications will be listed here.
{{ $t('components.auth.Settings.emptyAppHelp') }}
</empty-state>
</section>
<section
@ -608,17 +606,17 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="code icon" />
<div class="content">
Your applications
{{ $t('components.auth.Settings.personalAppsHeader') }}
</div>
</h2>
<p>
This is the list of applications that you have registered.
{{ $t('components.auth.Settings.personalAppsDescription') }}
</p>
<router-link
class="ui success button"
:to="{name: 'settings.applications.new'}"
>
Register a new application
{{ $t('components.auth.Settings.newAppLink') }}
</router-link>
<table
v-if="ownedApps.length > 0"
@ -627,13 +625,13 @@ fetchOwnedApps()
<thead>
<tr>
<th>
Application
{{ $t('components.auth.Settings.personalAppNameTableHeader') }}
</th>
<th>
Scopes
{{ $t('components.auth.Settings.personalAppScopesTableHeader') }}
</th>
<th>
Creation date
{{ $t('components.auth.Settings.personalAppCreationDateTableHeader') }}
</th>
<th />
</tr>
@ -659,28 +657,26 @@ fetchOwnedApps()
class="ui tiny success button"
:to="{name: 'settings.applications.edit', params: {id: app.client_id}}"
>
Edit
{{ $t('components.auth.Settings.personalAppEditLink') }}
</router-link>
<dangerous-button
:class="['ui', 'tiny', 'danger', { loading: isDeleting.has(app.client_id) }, 'button']"
@confirm="deleteApp(app.client_id)"
>
Remove
{{ $t('components.auth.Settings.personalAppDeleteLink') }}
<template #modal-header>
<p
v-translate="{application: app.name}"
>
Remove application "%{ application }"?
<p>
{{ $t('components.auth.Settings.deletePersonalAppModalMessage', app: app.name) }}
</p>
</template>
<template #modal-content>
<p>
This will permanently remove the application and all the associated tokens.
{{ $t('components.auth.Settings.deletePersonalAppModalWarning') }}
</p>
</template>
<template #modal-confirm>
<div>
Remove application
{{ $t('components.auth.Settings.deletePersonalAppButton') }}
</div>
</template>
</dangerous-button>
@ -690,9 +686,9 @@ fetchOwnedApps()
</table>
<empty-state v-else>
<template #title>
You don't have registered any application yet.
{{ $t('components.auth.Settings.emptyPersonalAppMessage') }}
</template>
Register one to integrate Funkwhale with third-party applications.
{{ $t('components.auth.Settings.emptyPersonalAppHelp') }}
</empty-state>
</section>
@ -704,17 +700,17 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="code icon" />
<div class="content">
Plugins
{{ $t('components.auth.Settings.pluginsHeader') }}
</div>
</h2>
<p>
Use plugins to extend Funkwhale and get additional features.
{{ $t('components.auth.Settings.pluginsDescription') }}
</p>
<router-link
class="ui success button"
:to="{name: 'settings.plugins'}"
>
Manage plugins
{{ $t('components.auth.Settings.managePluginsLink') }}
</router-link>
</section>
<section class="ui text container">
@ -722,18 +718,14 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="comment icon" />
<div class="content">
Change my e-mail address
{{ $t('components.auth.Settings.changeEmailHeader') }}
</div>
</h2>
<p>
Change the e-mail address associated with your account. We will send a confirmation to the new address.
{{ $t('components.auth.Settings.changeEmailDescription') }}
</p>
<p>
<translate
:translate-params="{email: $store.state.auth.profile?.email}"
>
Your current e-mail address is %{ email }.
</translate>
{{ $t('components.auth.Settings.currentEmailLabel', { email: email }) }}
</p>
<form
class="ui form"
@ -745,7 +737,7 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
We cannot change your e-mail address
{{ $t('components.auth.Settings.changeEmailFailureMessage') }}
</h4>
<ul class="list">
<li
@ -757,7 +749,7 @@ fetchOwnedApps()
</ul>
</div>
<div class="field">
<label for="new-email">New e-mail address</label>
<label for="new-email">{{ $t('components.auth.Settings.newEmailLabel') }}</label>
<input
id="new-email"
v-model="newEmail"
@ -766,7 +758,7 @@ fetchOwnedApps()
>
</div>
<div class="field">
<label for="current-password-field-email">Password</label>
<label for="current-password-field-email">{{ $t('components.auth.Settings.currentPasswordLabel')} }}</label>
<password-input
v-model="emailPassword"
field-id="current-password-field-email"
@ -777,7 +769,7 @@ fetchOwnedApps()
type="submit"
class="ui button"
>
Update
{{ $t('components.auth.Settings.updateEmailButton') }}
</button>
</form>
</section>
@ -786,17 +778,17 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="trash icon" />
<div class="content">
Delete my account
{{ $t('components.auth.Settings.deleteAccountHeader') }}
</div>
</h2>
<p>
You can permanently and irreversibly delete your account and all the associated data using the form below. You will be asked for confirmation.
{{ $t('components.auth.Settings.deleteAccountDescription') }}
</p>
<div
role="alert"
class="ui warning message"
>
Your account will be deleted from our servers within a few minutes. We will also notify other servers who may have a copy of some of your data so they can proceed to deletion. Please note that some of these servers may be offline or unwilling to comply though.
{{ $t('components.auth.Settings.deleteAccountWarning') }}
</div>
<div class="ui form">
<div
@ -805,7 +797,7 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
We cannot delete your account
{{ $t('components.auth.Settings.deleteAccountFailureMessage') }}
</h4>
<ul class="list">
<li
@ -817,7 +809,7 @@ fetchOwnedApps()
</ul>
</div>
<div class="field">
<label for="current-password-field">Password</label>
<label for="current-password-field">{{ $t('components.auth.Settings.currentPasswordLabel') }}</label>
<password-input
v-model="deleteAccountPassword"
field-id="current-password-field"
@ -828,22 +820,22 @@ fetchOwnedApps()
:class="['ui', {'loading': isDeletingAccount}, {disabled: !deleteAccountPassword}, {danger: deleteAccountPassword}, 'button']"
:action="deleteAccount"
>
Delete my account
{{ $t('components.auth.Settings.deleteAccountButton') }}
<template #modal-header>
<p>
Do you want to delete your account?
{{ $t('components.auth.Settings.deleteAccountConfirmationMessage') }}
</p>
</template>
<template #modal-content>
<div>
<p>
This is irreversible and will permanently remove your data from our servers. You will we immediately logged out.
{{ $t('components.auth.Settings.deleteAccountConfirmationWarning') }}
</p>
</div>
</template>
<template #modal-confirm>
<div>
Delete my account
{{ $t('components.auth.Settings.deleteAccountConfirmButton') }}
</div>
</template>
</dangerous-button>

View File

@ -35,9 +35,9 @@ const logger = useLogger()
const store = useStore()
const labels = computed(() => ({
placeholder: t('Enter your invitation code (case insensitive)'),
usernamePlaceholder: t('Enter your username'),
emailPlaceholder: t('Enter your e-mail address')
placeholder: t('components.auth.SignupForm.invitationCodePlaceholder'),
usernamePlaceholder: t('components.auth.SignupForm.usernamePlaceholder'),
emailPlaceholder: t('components.auth.SignupForm.emailPlaceholder')
}))
const signupRequiresApproval = computed(() => props.signupApprovalEnabled ?? store.state.instance.settings.moderation.signup_approval_enabled.value)
@ -88,14 +88,14 @@ fetchInstanceSettings()
<div v-if="submitted">
<div class="ui success message">
<p v-if="signupRequiresApproval">
Your account request was successfully submitted. You will be notified by e-mail when our moderation team has reviewed your request.
{{ $t('components.auth.SignupForm.awaitingReviewMessage') }}
</p>
<p v-else>
Your account was successfully created. Please verify your e-mail address before trying to login.
{{ $t('components.auth.SignupForm.accountCreationSuccessMessage') }}
</p>
</div>
<h2>
Log in to your Funkwhale account
{{ $t('components.auth.SignupForm.loginHeader') }}
</h2>
<login-form
button-classes="basic success"
@ -111,13 +111,13 @@ fetchInstanceSettings()
v-if="!$store.state.instance.settings.users.registration_enabled.value"
class="ui message"
>
Public registrations are not possible on this instance. You will need an invitation code to sign up.
{{ $t('components.auth.SignupForm.registrationClosedMessage') }}
</p>
<p
v-else-if="signupRequiresApproval"
class="ui message"
>
Registrations on this pod are open, but reviewed by moderators before approval.
{{ $t('components.auth.SignupForm.requiresReviewMessage') }}
</p>
<template v-if="formCustomization?.help_text">
<rendered-description
@ -133,7 +133,7 @@ fetchInstanceSettings()
class="ui negative message"
>
<h4 class="header">
Your account cannot be created.
{{ $t('components.auth.SignupForm.signupFailureMessage') }}
</h4>
<ul class="list">
<li
@ -145,7 +145,7 @@ fetchInstanceSettings()
</ul>
</div>
<div class="required field">
<label for="username-field">Username</label>
<label for="username-field">{{ $t('components.auth.SignupForm.usernameFieldLabel') }}</label>
<input
id="username-field"
ref="username"
@ -158,7 +158,7 @@ fetchInstanceSettings()
>
</div>
<div class="required field">
<label for="email-field">E-mail address</label>
<label for="email-field">{{ $t('components.auth.SignupForm.emailFieldLabel') }}</label>
<input
id="email-field"
ref="email"
@ -170,7 +170,7 @@ fetchInstanceSettings()
>
</div>
<div class="required field">
<label for="password-field">Password</label>
<label for="password-field">{{ $t('components.auth.SignupForm.passwordFieldLabel') }}</label>
<password-input
v-model="payload.password1"
field-id="password-field"
@ -180,7 +180,7 @@ fetchInstanceSettings()
v-if="!$store.state.instance.settings.users.registration_enabled.value"
class="required field"
>
<label for="invitation-code">Invitation code</label>
<label for="invitation-code">{{ $t('components.auth.SignupForm.invitationCodeFieldLabel') }}</label>
<input
id="invitation-code"
v-model="payload.invitation"
@ -217,7 +217,7 @@ fetchInstanceSettings()
:class="['ui', buttonClasses, {'loading': isLoading}, ' right floated button']"
type="submit"
>
Create my account
{{ $t('components.auth.SignupForm.createAccountButton') }}
</button>
</form>
</template>

View File

@ -13,7 +13,7 @@ const store = useStore()
const subsonicEnabled = computed(() => store.state.instance.settings.subsonic.enabled.value)
const labels = computed(() => ({
subsonicField: t('Your subsonic API password')
subsonicField: t('components.auth.SubsonicTokenForm.subsonicFieldLabel')
}))
const errors = ref([] as string[])
@ -38,7 +38,7 @@ const fetchToken = async () => {
const showToken = ref(false)
const successMessage = ref('')
const requestNewToken = async () => {
successMessage.value = t('Password updated')
successMessage.value = t('components.auth.SubsonicTokenForm.successMessage')
success.value = false
errors.value = []
isLoading.value = true
@ -56,7 +56,7 @@ const requestNewToken = async () => {
}
const disable = async () => {
successMessage.value = t('Access disabled')
successMessage.value = t('components.auth.SubsonicTokenForm.disabledMessage')
success.value = false
errors.value = []
isLoading.value = true
@ -82,26 +82,26 @@ fetchToken()
@submit.prevent="requestNewToken()"
>
<h2>
Subsonic API password
{{ $t('components.auth.SubsonicTokenForm.subsonicHeader') }}
</h2>
<p
v-if="!subsonicEnabled"
class="ui message"
>
The Subsonic API is not available on this Funkwhale instance.
{{ $t('components.auth.SubsonicTokenForm.unavailableMessage') }}
</p>
<p>
Funkwhale is compatible with other music players that support the Subsonic API.&nbsp; You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.
{{ $t('components.auth.SubsonicTokenForm.subsonicApiDescription') }}
</p>
<p>
However, accessing Funkwhale from those clients requires a separate password you can set below.
{{ $t('components.auth.SubsonicTokenForm.subsonicPasswordInfo') }}
</p>
<p>
<a
href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients"
target="_blank"
>
Discover how to use Funkwhale from other apps
{{ $t('components.auth.SubsonicTokenForm.appsLink') }}
</a>
</p>
<div
@ -118,7 +118,7 @@ fetchToken()
class="ui negative message"
>
<h4 class="header">
Error
{{ $t('components.auth.SubsonicTokenForm.errorHeader') }}
</h4>
<ul class="list">
<li
@ -152,20 +152,20 @@ fetchToken()
:class="['ui', {'loading': isLoading}, 'button']"
:action="requestNewToken"
>
Request a new password
{{ $t('components.auth.SubsonicTokenForm.requestNewTokenButton') }}
<template #modal-header>
<p>
Request a new Subsonic API password?
{{ $t('components.auth.SubsonicTokenForm.requestNewTokenModalHeader') }}
</p>
</template>
<template #modal-content>
<p>
This will log you out from existing devices that use the current password.
{{ $t('components.auth.SubsonicTokenForm.requestNewTokenWarning') }}
</p>
</template>
<template #modal-confirm>
<div>
Request a new password
{{ $t('components.auth.SubsonicTokenForm.requestNewTokenButton') }}
</div>
</template>
</dangerous-button>
@ -175,27 +175,27 @@ fetchToken()
:class="['ui', {'loading': isLoading}, 'button']"
@click="requestNewToken"
>
Request a password
{{ $t('components.auth.SubsonicTokenForm.requestTokenButton') }}
</button>
<dangerous-button
v-if="token"
:class="['ui', {'loading': isLoading}, 'warning', 'button']"
:action="disable"
>
Disable Subsonic access
{{ $t('components.auth.SubsonicTokenForm.disableSubsonicAccessButton') }}
<template #modal-header>
<p>
Disable Subsonic API access?
{{ $t('components.auth.SubsonicTokenForm.disableSubsonicAccessModalHeader') }}
</p>
</template>
<template #modal-content>
<p>
This will completely disable access to the Subsonic API using from account.
{{ $t('components.auth.SubsonicTokenForm.disableSubsonicAccessWarning') }}
</p>
</template>
<template #modal-confirm>
<div>
Disable access
{{ $t('components.auth.SubsonicTokenForm.disableSubsonicAccessConfirm')}}
</div>
</template>
</dangerous-button>

View File

@ -59,7 +59,7 @@ defineExpose({
class="ui negative message"
>
<h4 class="header">
Error while creating
{{ $t('components.channels.AlbumForm.errorHeader') }}
</h4>
<ul class="list">
<li
@ -72,7 +72,7 @@ defineExpose({
</div>
<div class="ui required field">
<label for="album-title">
Title
{{ $t('components.channels.AlbumForm.titleLabel') }}
</label>
<input
v-model="title"

View File

@ -36,12 +36,12 @@ const albumForm = ref()
<translate
v-if="channel.content_category === 'podcast'"
>
New series
{{ $t('components.channels.AlbumModal.newSeriesHeader') }}
</translate>
<translate
v-else
>
New album
{{ $t('components.channels.AlbumModal.newAlbumHeader') }}
</translate>
</h4>
<div class="scrolling content">
@ -55,14 +55,14 @@ const albumForm = ref()
</div>
<div class="actions">
<button class="ui basic cancel button">
Cancel
{{ $t('components.channels.AlbumModal.cancelButton') }}
</button>
<button
:class="['ui', 'primary', {loading: isLoading}, 'button']"
:disabled="!submittable"
@click.stop.prevent="albumForm.submit()"
>
Create
{{ $t('components.channels.AlbumModal.createButton') }}
</button>
</div>
</semantic-modal>

View File

@ -47,12 +47,12 @@ watch(() => props.channel, fetchData, { immediate: true })
<template>
<div>
<label for="album-dropdown">
<translate
<span
v-if="channel && channel.artist && channel.artist.content_category === 'podcast'"
>Series</translate>
<translate
>{{ $t('components.channels.AlbumSelect.seriesLabel') }}</span>
<span
v-else
>Album</translate>
>{{ $t('components.channels.AlbumSelect.albumLabel') }}</span>
</label>
<select
id="album-dropdown"
@ -67,14 +67,10 @@ watch(() => props.channel, fetchData, { immediate: true })
:key="album.id"
:value="album.id"
>
{{ album.title }} (<translate
:translate-params="{count: album.tracks_count}"
:translate-n="album.tracks_count"
translate-plural="%{ count } tracks"
>
%{ count } track
</translate>)
{{ album.title }}
<span>
{{ $t('components.channels.AlbumSelect.trackCount', { tracks_count: album.tracks_count }) }}
</span>
</option>
</select>
</div>

View File

@ -55,7 +55,7 @@ fetchLicenses()
<template>
<div>
<label for="license-dropdown">
License
{{ $t('components.channels.LicenseSelect.licenseLabel') }}
</label>
<select
id="license-dropdown"
@ -63,7 +63,7 @@ fetchLicenses()
class="ui search normal dropdown"
>
<option value="">
None
{{ $t('components.channels.LicenseSelect.noneLabel') }}
</option>
<option
v-for="l in featuredLicenses"
@ -84,7 +84,7 @@ fetchLicenses()
target="_blank"
rel="noreferrer noopener"
>
About this license
{{ $t('components.channels.LicenseSelect.licenseInfo') }}
</a>
</p>
</div>

View File

@ -24,12 +24,12 @@ const store = useStore()
const isSubscribed = computed(() => store.getters['channels/isSubscribed'](props.channel.uuid))
const title = computed(() => isSubscribed.value
? t('Unsubscribe')
: t('Subscribe')
? t('components.channels.SubscribeButton.unsubscribeLabel')
: t('components.channels.SubscribeButton.subscribeLabel')
)
const message = computed(() => ({
authMessage: t('You need to be logged in to subscribe to this channel')
authMessage: t('components.channels.SubscribeButton.authMessage')
}))
const toggle = async () => {

View File

@ -396,7 +396,7 @@ watchEffect(() => {
})
const labels = computed(() => ({
editTitle: t('Edit')
editTitle: t('components.channels.UploadForm.editTitle')
}))
</script>
@ -411,7 +411,7 @@ const labels = computed(() => ({
class="ui negative message"
>
<h4 class="header">
Error while publishing
{{ $t('components.channels.UploadForm.failureHeader') }}
</h4>
<ul class="list">
<li
@ -424,7 +424,7 @@ const labels = computed(() => ({
</div>
<div :class="['ui', 'required', {hidden: step > 1}, 'field']">
<label for="channel-dropdown">
Channel
{{ $t('components.channels.UploadForm.channelLabel') }}
</label>
<div
id="channel-dropdown"
@ -447,7 +447,7 @@ const labels = computed(() => ({
<div class="content">
<p>
<i class="copyright icon" />
Add a license to your upload to ensure some freedoms to your public.
{{ $t('components.channels.UploadForm.licenseTip') }}
</p>
</div>
</div>
@ -460,7 +460,7 @@ const labels = computed(() => ({
<div class="content">
<p>
<i class="warning icon" />
You don't have any space left to upload your files. Please contact the moderators.
{{ $t('components.channels.UploadForm.noSpaceWarning') }}
</p>
</div>
</div>
@ -471,19 +471,19 @@ const labels = computed(() => ({
>
<p>
<i class="redo icon" />
You have some draft uploads pending publication.
{{ $t('components.channels.UploadForm.pendingDraftsMessage') }}
</p>
<button
class="ui basic button"
@click.stop.prevent="includeDraftUploads = false"
>
Ignore
{{ $t('components.channels.UploadForm.ignoreButton') }}
</button>
<button
class="ui basic button"
@click.stop.prevent="includeDraftUploads = true"
>
Resume
{{ $t('components.channels.UploadForm.resumeButton') }}
</button>
</div>
<div
@ -533,31 +533,31 @@ const labels = computed(() => ({
</template>
</template>
<template v-else>
<translate
<span
v-if="file.active"
>
Uploading
</translate>
<translate
{{ $t('components.channels.UploadForm.uploadingStatus') }}
</span>
<span
v-else-if="file.error"
>
Errored
</translate>
<translate
{{ $t('components.channels.UploadForm.erroredStatus') }}
</span>
<span
v-else
>
Pending
</translate>
{{ $t('components.channels.UploadForm.pendingStatus') }}
</span>
· {{ humanSize(file.size ?? 0) }}
· {{ parseFloat(file.progress ?? '0') }}%
</template>
· <a @click.stop.prevent="remove(file)">
Remove
{{ $t('components.channels.UploadForm.removeUpload') }}
</a>
<template v-if="file.error">
·
<a @click.stop.prevent="retry(file)">
Retry
{{ $t('components.channels.UploadForm.retryUpload') }}
</a>
</template>
</div>
@ -576,12 +576,7 @@ const labels = computed(() => ({
<div class="content">
<p>
<i class="info icon" />
<translate
:translate-params="{extensions: $store.state.ui.supportedExtensions.join(', ')}"
>
Supported extensions: %{ extensions }
</translate>
{{ $t('components.channels.UploadForm.supportedExtensions', {extensions: $store.state.ui.supportedExtensions.join(', ')}) }}
</p>
</div>
</div>
@ -600,11 +595,11 @@ const labels = computed(() => ({
>
<div>
<i class="upload icon" />&nbsp;
Drag and drop your files here or open the browser to upload your files
{{ $t('components.channels.UploadForm.dragAndDrop') }}
</div>
<div class="ui very small divider" />
<div>
Browse
{{ $t('components.channels.UploadForm.openFileBrowser') }}
</div>
</file-upload-widget>
<div class="ui hidden divider" />

View File

@ -33,7 +33,7 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
<div :class="['ui', {loading: isLoading}, 'form']">
<div class="ui required field">
<label for="upload-title">
Title
{{ $t('components.channels.UploadMetadataForm.titleLabel') }}
</label>
<input
v-model="newValues.title"
@ -44,13 +44,13 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
v-model="newValues.cover"
@delete="newValues.cover = ''"
>
Track Picture
{{ $t('components.channels.UploadMetadataForm.trackImage') }}
</attachment-input>
<div class="ui small hidden divider" />
<div class="ui two fields">
<div class="ui field">
<label for="upload-tags">
Tags
{{ $t('components.channels.UploadMetadataForm.tagsLabel') }}
</label>
<tags-selector
id="upload-tags"
@ -60,7 +60,7 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
</div>
<div class="ui field">
<label for="upload-position">
Position
{{ $t('components.channels.UploadMetadataForm.uploadPosition') }}
</label>
<input
v-model="newValues.position"
@ -72,7 +72,7 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
</div>
<div class="ui field">
<label for="upload-description">
Description
{{ $t('components.channels.UploadMetadataForm.descriptionLabel') }}
</label>
<content-form
v-model="newValues.description"

View File

@ -29,7 +29,7 @@ const statusInfo = computed(() => {
}
if (statusData.value.totalFiles) {
const msg = t('no files | %{ count } file | %{ count } files', statusData.value.totalFiles)
const msg = t('components.channels.UploadModal.fileCount', statusData.value.totalFiles)
info.push(t(msg, { count: statusData.value.totalFiles }))
}
@ -54,26 +54,26 @@ const isLoading = ref(false)
class="small"
>
<h4 class="header">
<translate
<span
v-if="step === 1"
>
Publish audio
</translate>
<translate
{{ $t('components.channels.UploadModal.publishStep') }}
</span>
<span
v-else-if="step === 2"
>
Files to upload
</translate>
<translate
{{ $t('components.channels.UploadModal.uploadStep') }}
</span>
<span
v-else-if="step === 3"
>
Upload details
</translate>
<translate
{{ $t('components.channels.UploadModal.uploadDetails') }}
</span>
<span
v-else-if="step === 4"
>
Processing uploads
</translate>
{{ $t('components.channels.UploadModal.processingUploads') }}
</span>
</h4>
<div class="scrolling content">
<channel-upload-form
@ -91,8 +91,7 @@ const isLoading = ref(false)
</template>
<div class="ui very small hidden divider" />
<template v-if="statusData && statusData.quotaStatus">
Remaining storage space:
{{ humanSize((statusData.quotaStatus.remaining - statusData.uploadedSize) * 1000 * 1000) }}
{{ $t('components.channels.UploadModal.remainingSpace', humanSize((statusData.quotaStatus.remaining - statusData.uploadedSize) * 1000 * 1000)) }}
</template>
</div>
<div class="ui hidden clearing divider mobile-only" />
@ -100,28 +99,28 @@ const isLoading = ref(false)
v-if="step === 1"
class="ui basic cancel button"
>
Cancel
{{ $t('components.channels.UploadModal.cancelButton') }}
</button>
<button
v-else-if="step < 3"
class="ui basic button"
@click.stop.prevent="uploadForm.step -= 1"
>
Previous step
{{ $t('components.channels.UploadModal.previousButton') }}
</button>
<button
v-else-if="step === 3"
class="ui basic button"
@click.stop.prevent="uploadForm.step -= 1"
>
Update
{{ $t('components.channels.UploadModal.updateButton') }}
</button>
<button
v-if="step === 1"
class="ui primary button"
@click.stop.prevent="uploadForm.step += 1"
>
Next step
{{ $t('components.channels.UploadModal.nextButton') }}
</button>
<div
v-if="step === 2"
@ -133,7 +132,7 @@ const isLoading = ref(false)
:disabled="!statusData?.canSubmit || undefined"
@click.prevent.stop="uploadForm.publish"
>
Publish
{{ $t('components.channels.UploadModal.publishButton') }}
</button>
<button
ref="dropdown"
@ -148,7 +147,7 @@ const isLoading = ref(false)
class="basic item"
@click="update(false)"
>
Finish later
{{ $t('components.channels.UploadModal.finishLaterButton') }}
</div>
</div>
</button>
@ -158,7 +157,7 @@ const isLoading = ref(false)
class="ui basic cancel button"
@click="update(false)"
>
Close
{{ $t('components.channels.UploadModal.closeButton') }}
</button>
</div>
</semantic-modal>

View File

@ -123,10 +123,10 @@ const toggleCheck = (event: MouseEvent, id: string, index: number) => {
}
const labels = computed(() => ({
refresh: t('Refresh table content'),
selectAllItems: t('Select all items'),
performAction: t('Perform actions'),
selectItem: t('Select')
refresh: t('components.common.ActionTable.refreshLabel'),
selectAllItems: t('components.common.ActionTable.selectAllLabel'),
performAction: t('components.common.ActionTable.performActionLabel'),
selectItem: t('components.common.ActionTable.selectItemLabel')
}))
const errors = ref([] as string[])
@ -167,7 +167,7 @@ const launchAction = async () => {
class="right floated"
>
<span v-if="needsRefresh">
Content has been updated, click refresh to see up-to-date content
{{ $t('components.common.ActionTable.contentUpdatedMessage') }}
</span>
<button
class="ui basic icon button"
@ -208,18 +208,14 @@ const launchAction = async () => {
:aria-label="labels.performAction"
@confirm="launchAction"
>
Go
{{ $t('components.common.ActionTable.performActionButton') }}
<template #modal-header>
<p>
<translate
<span
key="1"
:translate-n="affectedObjectsCount"
:translate-params="{count: affectedObjectsCount, action: currentActionName}"
translate-plural="Do you want to launch %{ action } on %{ count } elements?"
>
Do you want to launch %{ action } on %{ count } element?
</translate>
{{ $t('components.common.ActionTable.performActionConfirmation', {action: currentActionName}, {count: affectedObjectsCount} ) }}
</span>
</p>
</template>
<template #modal-content>
@ -227,16 +223,16 @@ const launchAction = async () => {
<template v-if="currentAction?.confirmationMessage">
{{ currentAction?.confirmationMessage }}
</template>
<translate
<span
v-else
>
This may affect a lot of elements or have irreversible consequences, please double check this is really what you want.
</translate>
{{ $t('components.common.ActionTable.performActionWarning') }}
</span>
</p>
</template>
<template #modal-confirm>
<div :aria-label="labels.performAction">
Launch
{{ $t('components.common.ActionTable.launchActionButton') }}
</div>
</template>
</dangerous-button>
@ -247,54 +243,40 @@ const launchAction = async () => {
:class="['ui', {disabled: checked.length === 0}, {'loading': isLoading}, 'button']"
@click="launchAction"
>
Go
{{ $t('components.common.ActionTable.performActionButton') }}
</button>
</div>
<div class="count field">
<translate
<span
v-if="selectAll"
tag="span"
:translate-n="objectsData.count"
:translate-params="{count: objectsData.count, total: objectsData.count}"
translate-plural="All %{ count } elements selected"
>
All %{ count } element selected
</translate>
<translate
{{ $t('components.common.ActionTable.allElementsSelectedMessage', {count: objectData.count}) }}
</span>
<span
v-else
tag="span"
:translate-n="checked.length"
:translate-params="{count: checked.length, total: objectsData.count}"
translate-plural="%{ count } on %{ total } selected"
>
%{ count } on %{ total } selected
</translate>
{{ $t('components.common.ActionTable.elementsSelectedMessage', {count: checked.length }, {total: objectsData.count}) }}
</span>
<template v-if="currentAction?.allowAll && checkable.length > 0 && checkable.length === checked.length">
<a
v-if="!selectAll"
href=""
@click.prevent="selectAll = true"
>
<translate
<span
key="3"
:translate-n="objectsData.count"
:translate-params="{total: objectsData.count}"
translate-plural="Select all %{ total } elements"
>
Select one element
</translate>
{{ $t('components.common.ActionTable.selectElementsMessage', {total: objectsData.count}) }}
</span>
</a>
<a
v-else
href=""
@click.prevent="selectAll = false"
>
<translate
<span
key="4"
>Select only current page</translate>
>{{ $t('components.common.ActionTable.selectCurrentPage') }}</span>
</a>
</template>
</div>
@ -305,7 +287,7 @@ const launchAction = async () => {
class="ui negative message"
>
<h4 class="header">
Error while applying action
{{ $t('components.common.ActionTable.actionErrorMessage') }}
</h4>
<ul class="list">
<li
@ -321,14 +303,10 @@ const launchAction = async () => {
class="ui positive message"
>
<p>
<translate
:translate-n="result.updated"
:translate-params="{count: result.updated, action: result.action}"
translate-plural="Action %{ action } was launched successfully on %{ count } elements"
<span
>
Action %{ action } was launched successfully on %{ count } element
</translate>
{{ $t('components.common.ActionTable.actionSuccessMessage', {action: result.action}, {count: result.updated}) }}
</span>
</p>
<slot

View File

@ -107,7 +107,7 @@ const getAttachmentUrl = (uuid: string) => {
class="ui negative message"
>
<h4 class="header">
Your attachment cannot be saved
{{ $t('components.common.AttachmentInput.saveFailureMessage') }}
</h4>
<ul class="list">
<li
@ -144,7 +144,7 @@ const getAttachmentUrl = (uuid: string) => {
<div class="eleven wide column">
<div class="file-input">
<label :for="attachmentId">
Upload New Picture
{{ $t('components.common.AttachmentInput.uploadLabel') }}
</label>
<input
:id="attachmentId"
@ -159,21 +159,21 @@ const getAttachmentUrl = (uuid: string) => {
</div>
<div class="ui very small hidden divider" />
<p>
PNG or JPG. Dimensions should be between 1400x1400px and 3000x3000px. Maximum file size allowed is 5MB.
{{ $t('components.common.AttachmentInput.uploadHelp') }}
</p>
<button
v-if="value"
class="ui basic tiny button"
@click.stop.prevent="remove(value as string)"
>
Remove
{{ $t('components.common.AttachmentInput.removeButton') }}
</button>
<div
v-if="isLoading"
class="ui active inverted dimmer"
>
<div class="ui indeterminate text loader">
Uploading file
{{ $t('components.common.AttachmentInput.uploadingMessage') }}
</div>
</div>
</div>

View File

@ -20,16 +20,16 @@ const value = useVModel(props, 'modelValue', emit)
class="collapse link"
@click.prevent="value = !value"
>
<translate
<span
v-if="value"
>
Expand
</translate>
<translate
{{ $t('components.common.CollapseLink.expandLabel') }}
</span>
<span
v-else
>
Collapse
</translate>
{{ $t('components.common.CollapseLink.collapseLabel') }}
</span>
<i :class="[{ down: !value, right: value }, 'angle', 'icon']" />
</a>
</template>

View File

@ -36,7 +36,7 @@ const preview = ref()
const isLoadingPreview = ref(false)
const labels = computed(() => ({
placeholder: props.placeholder ?? t('Write a few words here…')
placeholder: props.placeholder ?? t('components.common.ContentForm.placeHolderLabel')
}))
const remainingChars = computed(() => props.charLimit - props.modelValue.length)
@ -86,13 +86,13 @@ onMounted(async () => {
:class="[{active: !isPreviewing}, 'item']"
@click.prevent="isPreviewing = false"
>
Write
{{ $t('components.common.ContentForm.writeButton') }}
</button>
<button
:class="[{active: isPreviewing}, 'item']"
@click.prevent="isPreviewing = true"
>
Preview
{{ $t('components.common.ContentForm.previewButton') }}
</button>
</div>
<template v-if="isPreviewing">
@ -108,7 +108,7 @@ onMounted(async () => {
</div>
</div>
<p v-else-if="!preview">
Nothing to preview.
{{ $t('components.common.ContentForm.noContentMessage') }}
</p>
<sanitized-html
v-else
@ -135,7 +135,7 @@ onMounted(async () => {
{{ remainingChars }}
</span>
<p>
Markdown syntax is supported.
{{ $t('components.common.ContentForm.markdownMessage') }}
</p>
</div>
</div>

View File

@ -22,7 +22,7 @@ const { copy, isSupported: canCopy, copied } = useClipboard({ source: value, cop
v-if="copied"
class="message"
>
Text copied to clipboard!
{{ $t('components.common.CopyInput.copySuccessMessage') }}
</p>
<input
:id="id"
@ -37,7 +37,7 @@ const { copy, isSupported: canCopy, copied } = useClipboard({ source: value, cop
@click="copy()"
>
<i class="copy icon" />
Copy
{{ $t('components.common.CopyInput.copyButton') }}
</button>
</div>
</template>

View File

@ -42,7 +42,7 @@ const confirm = () => {
>
<h4 class="header">
<slot name="modal-header">
Do you want to confirm this action?
{{ $t('components.common.DangerousButton.confirmAction') }}
</slot>
</h4>
<div class="scrolling content">
@ -52,14 +52,14 @@ const confirm = () => {
</div>
<div class="actions">
<button class="ui basic cancel button">
Cancel
{{ $t('components.common.DangerousButton.cancelButton') }}
</button>
<button
:class="['ui', 'confirm', confirmColor, 'button']"
@click="confirm"
>
<slot name="modal-confirm">
Confirm
{{ $t('components.common.DangerousButton.confirmButton') }}
</slot>
</button>
</div>

View File

@ -18,15 +18,11 @@ const duration = computed(() => {
<template>
<span>
<translate
<span
v-if="duration.hours > 0"
:translate-params="duration"
>%{ hours } h %{ minutes } min</translate>
<translate
>{{ $t('components.common.Duration.hoursFormat', {hours: duration.hours}, {minutes: duration.minutes}) }}</span>
<span
v-else
:translate-params="duration"
>%{ minutes } min</translate>
>{{ $t('components.common.Duration.minutesFormat', {minutes: duration.minutes}) }}</span>
</span>
</template>

View File

@ -19,7 +19,7 @@ withDefaults(defineProps<Props>(), {
<div class="content">
<slot name="title">
<i class="search icon" />
No results were found.
{{ $t('components.common.EmptyState.emptyState') }}
</slot>
</div>
</h4>
@ -30,7 +30,7 @@ withDefaults(defineProps<Props>(), {
class="ui button"
@click="emit('refresh')"
>
Refresh
{{ $t('components.common.EmptyState.refreshButton') }}
</button>
</div>
</div>

View File

@ -26,12 +26,12 @@ const truncated = computed(() => props.content.slice(0, props.length))
@click.prevent="toggleExpanded()"
>
<br>
<translate
<span
v-if="expanded"
>Show less</translate>
<translate
>{{ $t('components.common.ExpandableDiv.showLess') }}</span>
<span
v-else
>Show more</translate>
>{{ $t('components.common.ExpandableDiv.showMore') }}</span>
</a>
</div>
</template>

View File

@ -22,8 +22,8 @@ const value = useVModel(props, 'modelValue', emit)
const { t } = useI18n()
const labels = computed(() => ({
searchPlaceholder: t('Search…'),
clear: t('Clear')
searchPlaceholder: t('components.common.InlineSearchBar.searchPlaceholder'),
clear: t('components.common.InlineSearchBar.clearLabel')
}))
const search = () => {
@ -42,7 +42,7 @@ const search = () => {
for="search-query"
class="hidden"
>
Search
{{ $t('components.common.InlineSearchBar.searchLabel') }}
</label>
<input
id="search-query"

View File

@ -18,10 +18,10 @@ const show = ref(false)
const { t } = useI18n()
const labels = computed(() => ({
header: t('Unauthenticated'),
login: t('Log in'),
signup: t('Sign up'),
description: t("You don't have access!")
header: t('components.common.LoginModal.header'),
login: t('components.common.LoginModal.loginLabel'),
signup: t('components.common.LoginModal.signupLabel'),
description: t('components.common.LoginModal.noAccessDescription')
}))
</script>

View File

@ -111,8 +111,8 @@ onMounted(() => $('.ui.dropdown').dropdown())
const { t } = useI18n()
const labels = computed(() => ({
searchPlaceholder: t('Search…'),
title: t('Artists')
searchPlaceholder: t('components.library.Artists.searchPlaceholder'),
title: t('components.library.Artists.title')
}))
const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b)))

View File

@ -1,2 +1,594 @@
{
"components": {
"admin": {
"SettingsGroup": {
"errorMessage": "Error while saving settings.",
"successMessage": "Settings updated successfully.",
"currentImage": "Current image",
"saveButton": "Save"
},
"SignupFormBuilder": {
"deleteLabel": "Delete",
"moveUpLabel": "Move up",
"moveDownLabel": "Move down",
"additionalFieldInput": "Additional field",
"editForm": "Edit form",
"previewForm": "Preview Form",
"helpTextLabel": "Help text",
"helpTextMessage": "An optional text to be displayed at the start of the sign-up form.",
"additionalFieldsLabel": "Additional fields",
"additionalFieldsMessage": "Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled.",
"fieldLabelTableHeader": "Field label",
"fieldTypeTableHeader": "Field type",
"requiredTableHeader": "Required",
"shortTextInput": "Short text",
"longTextInput": "Long text",
"requiredTrue": "Yes",
"requiredFalse": "No",
"addFieldButton": "Add a new field"
}
},
"audio": {
"ChannelCard": {
"updatedOn": "Updated on {date}",
"episodeCount": "No episodes | {episode_count} episode | {episode_count} episodes",
"trackCount": "No tracks | {tracks_count} track | {tracks_count} tracks"
},
"ChannelEntries": {
"emptyMessage": "You may need to subscribe to this channel to see its content."
},
"ChannelForm": {
"podcastsLabel": "Podcasts",
"podcastsHelpText": "Host your episodes and keep your community updated.",
"discographyLabel": "Artist Discography",
"discographyHelpText": "Publish music you make as a nice discography of albums and singles.",
"namePlaceholder": "Awesome channel name",
"usernamePlaceholder": "awesomechannelname",
"errorHeader": "Error while saving channel",
"channelPurposeLegend": "What will this channel be used for?",
"channelNameLabel": "Name",
"channelUsernameLabel": "Fediverse handle",
"channelUsernameDescription": "Used in URLs and to follow this channel in the Fediverse. It cannot be changed later.",
"channelImageLabel": "Channel Picture",
"channelTagsLabel": "Tags",
"channelLanguageLabel": "Language",
"channelDescriptionLabel": "Description",
"channelCategoryLabel": "Category",
"channelSubcategoryLabel": "Subcategory",
"channelEmailLabel": "Owner e-mail address",
"channelOwnerLabel": "Owner name",
"channelPodcastFieldsHelp": "Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes.",
"loadingMessage": "Loading"
},
"ChannelSerieCard": {
"episodeCount": "No episodes | {episode_count} episode | {episode_count} episodes"
},
"ChannelSeries": {
"showMore": "Show more",
"emptyMessage": "You may need to subscribe to this channel to see its contents."
},
"ChannelsWidget": {
"showMore": "Show more"
},
"EmbedWizard": {
"anonymousAccessWarning": "Sharing will not work because this pod doesn't allow anonymous users to access content.",
"anonymousAccessHelp": "Please contact your admins and ask them to update the corresponding setting.",
"widgetWidthLabel": "Widget width",
"widgetWidthHelp": "Leave empty for a responsive widget",
"widgetHeightLabel": "Widget height",
"copyButton": "Copy",
"embedCodeLabel": "Embed code",
"embedCodeHelp": "Copy/paste this code in your website HTML",
"copyButtonSuccessMessage": "Text copied to clipboard!",
"previewHeader": "Preview"
},
"LibraryFollowButton": {
"unfollowLabel": "Unfollow",
"cancelLabel": "Cancel follow request",
"followLabel": "Follow"
},
"PlayButton": {
"playNowLabel": "Play now",
"addToQueueLabel": "Add to current queue",
"playNextLabel": "Play next",
"startRadioLabel": "Play similar songs",
"reportLabel": "Report…",
"addToPlaylistLabel": "Add to playlist",
"hideArtistLabel": "Hide content from this artist",
"playTrackLabel": "Play track",
"playAlbumLabel": "Play album",
"playArtistLabel": "Play artist",
"playPlaylistLabel": "Play playlist",
"playTracksLabel": "Play tracks",
"moreTitle": "More…",
"notAvailableTitle": "This track is not available in any library you have access to",
"episodeDetailsButton": "Episode details",
"trackDetailsButton": "Track details",
"discretePlayButton": "Play"
},
"Player": {
"audioPlayerLabel": "Media player",
"previousTrackLabel": "Previous track",
"playLabel": "Play",
"pauseLabel": "Pause",
"nextTrackLabel": "Next track",
"unmuteLabel": "Unmute",
"muteLabel": "Mute",
"expandQueueLabel": "Expand queue",
"loopingDisabledLabel": "Looping disabled. Click to switch to single-track looping.",
"loopingSingleLabel": "Looping on a single track. Click to switch to whole queue looping.",
"loopingWholeQueueLabel": "Looping on whole queue. Click to disable looping.",
"shuffleQueueLabel": "Shuffle your queue",
"clearQueueLabel": "Clear your queue",
"addArtistContentFilterLabel": "Hide content from this artist…",
"playerHeader": "Audio player and controls",
"queuePosition": "{index} of {length}"
},
"Search": {
"searchPlaceHolderLabel": "Artist, album, track…",
"searchHeader": "Search for some music",
"artistsHeader": "Artists",
"albumsHeader": "Albums",
"noArtistsMessage": "No artist matched your query",
"noAlbumsMessage": "No album matched your query"
},
"SearchBar": {
"placeHolderLabel": "Search for artists, albums, tracks…",
"searchContentLabel": "Search for content",
"artistLabel": "Artist",
"albumLabel": "Album",
"trackLabel": "Track",
"tagLabel": "Tag",
"federationCategory": "Federation",
"podcastsCategory": "Podcasts",
"noResultsHeader": "No matches found",
"noResultsMessage": "Sorry, there are no results for this search",
"fediverseSearchLabel": "Search on the fediverse",
"rssSearchLabel": "Subscribe to podcast via RSS",
"moreResultsLabel": "More results 🡒"
},
"VolumeControl": {
"unmuteLabel": "Unmute",
"muteLabel": "Mute",
"sliderLabel": "Adjust volume"
},
"album": {
"Card": {
"trackCount": "No tracks | {tracks_count} track | {tracks_count} tracks"
},
"Widget": {
"showMore": "Show more"
}
},
"artist": {
"Card": {
"trackCount": "No tracks | {tracks_count} track | {tracks_count} tracks",
"episodeCount": "No episodes | {episode_count} episode | {episode_count} episodes"
},
"Widget": {
"showMore":"Show more"
}
},
"podcast": {
"MobileRow": {
"actionsButtonlabel": "Show track actions"
},
"Modal": {
"addToFavorites": "Add to favorites",
"removeFromFavorites": "Remove from favorites",
"episodeDetails": "Episode details",
"trackDetails": "Track details",
"seriesDetails": "View series",
"albumDetails": "View album",
"channelDetails": "View channel",
"artistDetails": "View artist",
"startRadio": "Play radio",
"playNow": "Play now",
"addToQueue": "Add to queue",
"playNext": "Play next",
"addToPlaylist": "Add to playlist"
}
},
"track": {
"MobileRow": {
"actionsButtonlabel": "Show track actions"
},
"Modal": {
"addToFavorites": "Add to favorites",
"removeFromFavorites": "Remove from favorites",
"episodeDetails": "Episode details",
"trackDetails": "Track details",
"seriesDetails": "View series",
"albumDetails": "View album",
"channelDetails": "View channel",
"artistDetails": "View artist",
"startRadio": "Play radio",
"playNow": "Play now",
"addToQueue": "Add to queue",
"playNext": "Play next",
"addToPlaylist": "Add to playlist"
},
"Table": {
"titleLabel": "Title",
"albumLabel": "Album",
"artistLabel": "Artist"
},
"Widget": {
"noResultsMessage": "Nothing found",
"showMore":"Show more"
}
}
},
"auth": {
"ApplicationEdit": {
"editApplicationLabel": "Edit application",
"backToSettingsLink": "Back to settings",
"appDetailsHeader": "Application Details",
"appDetailsDescription": "Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else.",
"appIdLabel": "Application ID",
"appSecretLabel": "Application secret",
"accessTokenLabel": "Access token",
"regenerateTokenLink": "Regenerate token",
"editAppHeader": "Edit application"
},
"ApplicationForm": {
"readScopeLabel": "Read",
"readScopeDescription": "Read-only access to user data",
"writeScopeLabel": "Write",
"writeScopeDescription": "Write-only access to user data",
"saveFailureMessage": "We cannot save your changes",
"applicationNameLabel": "Name",
"redirectUrisLabel": "Redirect URI",
"redirectUrisHelp": "Use \"urn:ietf:wg:oauth:2.0:oob\" as a redirect URI if your application is not served on the web.",
"scopesLabel": "Scopes",
"scopesDescription": "Checking the parent \"Read\" or \"Write\" scopes implies access to all the corresponding children scopes.",
"updateButtonLabel": "Update application",
"createButtonLabel": "Create application"
},
"ApplicationNew": {
"title": "Create a new application",
"backToSettingsLink": "Back to settings"
},
"Authorize": {
"title": "Allow application",
"authorizeThirdPartyAppHeader": "Authorize third-party app",
"authorizeFailureMessage": "Error while authorizing application",
"fetchDataFailureMessage": "Error while fetching application data",
"appAccessHeader": "{app_name} wants to access your Funkwhale acount",
"writeOnlyScopeHeader": "Write-only",
"readOnlyScopeHeader": "Read-only",
"allScopesHeader": "Full access",
"unknownPermissionsMessage": "The application is also requesting the following unknown permissions:",
"authorizeAppButton": "Authorize {app}",
"copyCodeHelp": "You will be shown a code to copy-past in the application",
"redirectHelp": "You will be redirected to <strong>{url}</strong>",
"copyCodeDescription": "Copy-paste the following code in the application:"
},
"LoginForm": {
"usernamePlaceholder": "Enter your username or e-mail address",
"loginFailureHeader": "We cannot log you in",
"approvalRequiredHelp": "If you signed-up recently, you may need to wait before our moderation team review your account, or verify your e-mail address.",
"invalidCredentialsHelp": "Please double-check that your username and password combination is correct and make sure you verified your e-mail address.",
"usernameFieldLabel": "Username or e-mail address",
"createAccountLink": "Create an account",
"passwordFieldLabel": "Password |",
"resetPasswordLink": "Reset your password",
"redirectMessage": "You will be redirected to {domain} to authenticate",
"loginButton": "Login"
},
"Logout": {
"title": "Log out",
"confirmHeader": "Are you sure you want to log out?",
"loggedInUsername": "You are currently logged in as {username}",
"confirmLogoutButton": "Yes, log me out!",
"loggedOutHeader": "You aren't currently logged in",
"logInLink": "Log in!"
},
"Plugin": {
"documentationLink": "Documentation",
"saveFailureHeader": "Error while saving plugin",
"pluginEnabledLabel": "Enabled",
"libraryLabel": "Library",
"libraryDescription": "Library where files should be imported.",
"saveButton": "Save",
"scanButton": "Scan"
},
"Settings": {
"title": "Account Settings",
"deletionRequest": "Your deletion request was submitted, your account and content will be deleted shortly",
"accountSettingsHeader": "Account settings",
"settingsUpdatedHeader": "Settings updated",
"settingsUpdateFailureHeader": "Your settings can't be updated",
"updateSettingsButton": "Update settings",
"avatarHeader": "Avatar",
"avatarSaveFailureHeader": "Your avatar cannot be saved",
"avatarInputLabel": "Avatar",
"changePasswordHeader": "Change my password",
"changePasswordMessage": "Changing your password will also change your Subsonic API password if you have requested one \n You will have to update your password on your clients that use this password.",
"changePasswordFailureMessage": "Your password cannot be changed",
"changePasswordHelp": "Please double-check your password is correct",
"currentPasswordLabel": "Current password",
"newPasswordLabel": "New password",
"changePasswordButton": "Change password",
"changePasswordModalHeader": "Change your password?",
"changePasswordWarning": "Changing your password will have the following consequences",
"changePasswordLogout": "You will be logged out from this session and have to log in with the new one",
"changePasswordSubsonic": "Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password",
"disableSubsonicMessage": "Disable access",
"contentFiltersHeader": "Content filters",
"contentFiltersDescription": "Content filters help you hide content you don't want to see on the service.",
"refreshButton": "Refresh",
"hiddenArtistsHeader": "Hidden artists",
"artistNameTableHeader": "Name",
"filterCreationDateTableHeader": "Creation date",
"filterDeleteButton": "Delete",
"authorizedAppsHeader": "Authorized apps",
"authorizedAppsDescription": "This is the list of applications that have access to your account data.",
"appNameTableHeader": "Application",
"appPermissionsTableHeader": "Permissions",
"permissionDeleteButton": "Revoke",
"revokePermissionModalMessage": "Revoke access for application {app}?",
"revokePermissionModalWarning": "This will prevent this application from accessing the service on your behalf.",
"revokeAccessButton": "Revoke access",
"emptyAppMessage": "You don't have any application connected with your account.",
"emptyAppHelp": "If you authorize third-party applications to access your data, those applications will be listed here.",
"personalAppsHeader": "Your applications",
"personalAppsDescription": "This is the list of applications that you have registered.",
"newAppLink": "Register a new application",
"personalAppNameTableHeader": "Application",
"personalAppScopesTableHeader": "Scopes",
"personalAppCreationDateTableHeader": "Creation date",
"personalAppEditLink": "Edit",
"personalAppDeleteLink": "Remove",
"deletePersonalAppModalMessage": "Remove application {app}?",
"deletePersonalAppModalWarning": "This will permanently remove the application and all the associated tokens.",
"deletePersonalAppButton": "Remove application",
"emptyPersonalAppMessage": "You don't have registered any application yet.",
"emptyPersonalAppHelp": "Register one to integrate Funkwhale with third-party applications.",
"pluginsHeader": "Plugins",
"pluginsDescription": "Use plugins to extend Funkwhale and get additional features.",
"managePluginsLink": "Manage plugins",
"changeEmailHeader": "Change my e-mail address",
"changeEmailDescription": "Change the e-mail address associated with your account. We will send a confirmation to the new address.",
"currentEmailLabel": "Your current e-mail address is {email}",
"changeEmailFailureMessage": "We cannot change your e-mail address",
"newEmailLabel": "New e-mail address",
"updateEmailButton": "Update",
"deleteAccountHeader": "Delete my account",
"deleteAccountDescription": "You can permanently and irreversibly delete your account and all the associated data using the form below. You will be asked for confirmation.",
"deleteAccountWarning": "Your account will be deleted from our servers within a few minutes. We will also notify other servers who may have a copy of some of your data so they can proceed to deletion. Please note that some of these servers may be offline or unwilling to comply though.",
"deleteAccountFailureMessage": "We cannot delete your account",
"deleteAccountButton": "Delete my account…",
"deleteAccountConfirmationMessage": "Do you want to delete your acount?",
"deleteAccountConfirmationWarning": "This is irreversible and will permanently remove your data from our servers. You will we immediatly logged out.",
"deleteAccountConfirmButton": "Delete my account"
},
"SignupForm": {
"invitationCodePlaceholder": "Enter your invitation code (case insensitive)",
"usernamePlaceholder": "Enter your username",
"emailPlaceholder": "Enter your e-mail address",
"awaitingReviewMessage": "Your account request was successfully submitted. You will be notified by e-mail when our moderation team has reviewed your request.",
"accountCreationSuccessMessage": "Your account was successfully created. Please verify your e-mail address before trying to login.",
"loginHeader": "Log in to your Funkwhale account",
"registrationClosedMessage": "Public registrations are not possible on this instance. You will need an invitation code to sign up.",
"requiresReviewMessage": "Registrations on this pod are open, but reviewed by moderators before approval.",
"signupFailureMessage": "Your account cannot be created.",
"usernameFieldLabel": "Username",
"emailFieldLabel": "E-mail address",
"passwordFieldLabel": "Password",
"invitationCodeFieldLabel": "Invitation code",
"createAccountButton": "Create my account"
},
"SubsonicTokenForm": {
"subsonicFieldLabel": "Your subsonic API password",
"successMessage": "Password updated",
"disabledMessage": "Access disabled",
"subsonicHeader": "Subsonic API password",
"unavailableMessage": "The Subsonic API is not available on this Funkwhale instance.",
"subsonicApiDescription": "Funkwhale is compatible with other music players that support the Subsonic API.\n You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.",
"subsonicPasswordInfo": "However, accessing Funkwhale from those clients requires a separate password you can set below.",
"appsLink": "Discover how to use Funkwhale from other apps",
"errorHeader": "Error",
"requestNewTokenButton": "Request a new password",
"requestNewTokenModalHeader": "Request a new Subsonic API password?",
"requestNewTokenWarning": "This will log you out from existing devices that use the current password.",
"requestTokenButton": "Request a password",
"disableSubsonicAccessButton": "Disable Subsonic access",
"disableSubsonicAccessModalHeader": "Disable Subsonic API access?",
"disableSubsonicAccessWarning": "This will completely disable access to the Subsonic API using from account.",
"disableSubsonicAccessConfirm": "Disable access"
}
},
"channels": {
"AlbumForm": {
"errorHeader": "Error while creating",
"titleLabel": "Title"
},
"AlbumModal": {
"newSeriesHeader": "New series",
"newAlbumHeader": "New album",
"cancelButton": "Cancel",
"createButton": "Create"
},
"AlbumSelect": {
"seriesLabel": "Series",
"albumLabel": "Album",
"noneLabel": "None",
"trackCount":"(No tracks | {tracks_count} track | {tracks_count} tracks)"
},
"LicenseSelect": {
"licenseLabel": "License",
"noneLabel": "None",
"licenseInfo": "About this license"
},
"SubscribeButton": {
"unsubscribeLabel": "Unsubscribe",
"subscribeLabel": "Subscribe",
"authMessage": "You need to be logged in to subscribe to this channel"
},
"UploadForm": {
"editTitle": "Edit",
"failureHeader": "Error while publishing",
"channelLabel": "Channel",
"licenseTip": "Add a license to your upload to ensure some freedoms to your public.",
"noSpaceWarning": "You don't have any space left to upload your files. Please contact the moderators.",
"pendingDraftsMessage": "You have some draft uploads pending publication.",
"ignoreButton": "Ignore",
"resumeButton": "Resume",
"uploadingStatus": "Uploading",
"erroredStatus": "Errored",
"pendingStatus": "Pending",
"removeUpload": "Remove",
"retryUpload": "Retry",
"supportedExtensions": "Supported extensions {extensions}",
"dragAndDrop": "Drag and drop your files here or open the browser to upload your files",
"openFileBrowser": "Browse…"
},
"UploadMetadataForm": {
"titleLabel": "Title",
"trackImage": "Track Picture",
"tagsLabel": "Tags",
"uploadPosition": "Position",
"descriptionLabel": "Description"
},
"UploadModal": {
"fileCount": "no files | {count} file | {count} files",
"publishStep": "Publish audio",
"uploadStep": "Files to upload",
"uploadDetails": "Upload details",
"processingUploads": "Processing uploads",
"remainingSpace": "Remaining storage space: {space}",
"cancelButton": "Cancel",
"previousButton": "Previous step",
"updateButton": "Update",
"nextButton": "Next",
"publishButton": "Publish",
"finishLaterButton": "Finish later",
"closeButton": "Close"
}
},
"common": {
"ActionTable": {
"refreshLabel": "Refresh table content",
"selectAllLabel": "Select all items",
"performActionLabel": "Perform actions",
"selectItemLabel": "Select",
"contentUpdatedMessage": "Content has been updated, click refresh to see up-to-date content",
"performActionButton": "Go",
"performActionConfirmation": "Do you want to launch {action} on {count} element? | Do you want to launch {action} on {count} elements?",
"performActionWarning": "This may affect a lot of elements or have irreversible consequences, please double check this is really what you want.",
"launchActionButton": "Launch",
"allElementsSelectedMessage": "No elements selected | {count} element selected | All {count} elements selected",
"elementsSelectedMessage": "{count} on {total} selected",
"selectElementsMessage": "Select one element | Select all {total} elements",
"selectCurrentPage": "Select only current page",
"actionErrorMessage": "Error while applying action",
"actionSuccessMessage": "Action {action} was launched successfully on {count} element | Action {action} was launched successfully on {count} elements"
},
"AttachmentInput": {
"saveFailureMessage": "Your attachment cannot be saved",
"uploadLabel": "Upload New Picture…",
"uploadHelp": "PNG or JPG. Dimensions should be between 1400x1400px and 3000x3000px. Maximum file size allowed is 5MB.",
"removeButton": "Remove",
"uploadingMessage": "Uploading file…"
},
"CollapseLink": {
"expandLabel": "Expand",
"collapseLabel": "Collapse"
},
"ContentForm": {
"placeHolderLabel": "Write a few words here…",
"writeButton": "Write",
"previewButton": "Preview",
"noContentMessage": "Nothing to preview",
"markdownMessage": "Markdown syntax is supported."
},
"CopyInput": {
"copySuccessMessage": "Text copied to clipboard!",
"copyButton": "Copy"
},
"DangerousButton": {
"confirmAction": "Do you want to confirm this action?",
"cancelButton": "Cancel",
"confirmButton": "Confirm"
},
"Duration": {
"hoursFormat": "{hours} h {minutes} min",
"minutesFormat": "{minutes} min"
},
"EmptyState": {
"emptyState": "No results were found.",
"refreshButton": "Refresh"
},
"ExpandableDiv": {
"showLess": "Show less",
"showMore": "Show more"
},
"InlineSearchBar": {
"searchPlaceholder": "Search…",
"clearLabel": "Clear",
"searchLabel": "Search"
},
"LoginModal": {
"header": "Unauthenticated",
"loginLabel": "Log in",
"signupLabel": "Sign up",
"noAccessDescription": "You don't have access"
}
},
"library": {
"Artists": {
"title": "Artists",
"searchPlaceholder": "Search…"
}
}
},
"views": {
"Notifications": {
"title": "Notifications",
"messagesHeader": "Your messages",
"instanceSupportHeader": "Support this Funkwhale pod",
"instanceReminderDelay": "Remind me in:",
"instanceReminder30": "30 days",
"instanceReminder60": "60 days",
"instanceReminder90": "90 days",
"instanceReminderNever": "Never",
"instanceReminderSubmitButton": "Got it!",
"funkwhaleSupportHeader": "Do you like Funkwhale?",
"funkwhaleSupportMessage": "We noticed you've been here for a while. If Funkwhale is useful to you, we could use your help to make it even better!",
"funkwhaleSupportDonateLink": "Donate",
"funkwhaleSupportHelpLink": "Discover other ways to help",
"funkwhaleReminderDelay": "Remind me in:",
"funkwhaleReminder30": "30 days",
"funkwhaleReminder60": "60 days",
"funkwhaleReminder90": "90 days",
"funkwhaleReminderNever": "Never",
"funkwhaleReminderSubmitButton": "Got it!",
"notificationsHeader": "Your notifications",
"showReadNotificationsCheckbox": "Show read notifications",
"markAllReadButton": "Mark all as read",
"loadingNotifications": "Loading notifications…",
"noNotifications": "No notification to show"
},
"Search": {
"artistsLabel": "Artists",
"albumsLabel": "Albums",
"tracksLabel": "Tracks",
"playlistsLabel": "Playlists",
"radiosLabel": "Radios",
"tagsLabel": "Tags",
"podcastsLabel": "Podcasts",
"seriesLabel": "Series",
"rssTitle": "Subscribe to a podcast RSS feed",
"remoteTitle": "Search a remote object",
"searchTitle": "Search",
"submitSearchLabel": "Submit Search Query",
"searchHeader": "Search"
},
"library": {
"LibraryBase": {
}
}
}
}

View File

@ -23,7 +23,7 @@ const showInstanceSupportMessage = computed(() => store.getters['ui/showInstance
const showFunkwhaleSupportMessage = computed(() => store.getters['ui/showFunkwhaleSupportMessage'])
const labels = computed(() => ({
title: t('Notifications')
title: t('views.Notifications.title')
}))
const filters = reactive({
@ -101,7 +101,7 @@ const markAllAsRead = async () => {
class="ui container"
>
<h1 class="ui header">
Your messages
{{ $t('views.Notifications.messagesHeader') }}
</h1>
<div class="ui two column stackable grid">
<div
@ -110,7 +110,7 @@ const markAllAsRead = async () => {
>
<div class="ui attached info message">
<h4 class="header">
Support this Funkwhale pod
{{ $t('views.Notifications.instanceSupportHeader') }}
</h4>
<sanitized-html :html="supportMessage" />
</div>
@ -121,30 +121,30 @@ const markAllAsRead = async () => {
>
<div class="inline field">
<label for="instance-reminder-delay">
Remind me in:
{{ $t('views.Notifications.instanceReminderDelay') }}
</label>
<select
id="instance-reminder-delay"
v-model="instanceSupportMessageDelay"
>
<option :value="30">
30 days
{{ $t('views.Notifications.instanceReminder30') }}
</option>
<option :value="60">
60 days
{{ $t('views.Notifications.instanceReminder60') }}
</option>
<option :value="90">
90 days
{{ $t('views.Notifications.instanceReminder90') }}
</option>
<option :value="null">
Never
{{ $t('views.Notifications.instanceReminderNever') }}
</option>
</select>
<button
type="submit"
class="ui right floated basic button"
>
Got it!
{{ $t('views.Notifications.instanceReminderSubmitButton') }}
</button>
</div>
</form>
@ -156,10 +156,10 @@ const markAllAsRead = async () => {
>
<div class="ui info attached message">
<h4 class="header">
Do you like Funkwhale?
{{ $t('views.Notifications.funkwhaleSupportHeader') }}
</h4>
<p>
We noticed you've been here for a while. If Funkwhale is useful to you, we could use your help to make it even better!
{{ $t('views.Notifications.funkwhaleSupportMessage') }}
</p>
<a
href="https://funkwhale.audio/support-us"
@ -167,7 +167,7 @@ const markAllAsRead = async () => {
rel="noopener"
class="ui primary inverted button"
>
Donate
{{ $t('views.Notifications.funkwhaleSupportDonateLink') }}
</a>
<a
href="https://contribute.funkwhale.audio"
@ -175,7 +175,7 @@ const markAllAsRead = async () => {
rel="noopener"
class="ui secondary inverted button"
>
Discover other ways to help
{{ $t('views.Notifications.funkwhaleSupportHelpLink') }}
</a>
</div>
<div class="ui bottom attached segment">
@ -185,30 +185,30 @@ const markAllAsRead = async () => {
>
<div class="inline field">
<label for="funkwhale-reminder-delay">
Remind me in:
{{ $t('views.Notifications.funkwhaleReminderDelay') }}
</label>
<select
id="funkwhale-reminder-delay"
v-model="funkwhaleSupportMessageDelay"
>
<option :value="30">
30 days
{{ $t('views.Notifications.funkwhaleReminder30') }}
</option>
<option :value="60">
60 days
{{ $t('views.Notifications.funkwhaleReminder60') }}
</option>
<option :value="90">
90 days
{{ $t('views.Notifications.funkwhaleReminder90') }}
</option>
<option :value="null">
Never
{{ $t('views.Notifications.funkwhaleReminderNever') }}
</option>
</select>
<button
type="submit"
class="ui right floated basic button"
>
Got it!
{{ $t('views.Notifications.funkwhaleReminderSubmitButton') }}
</button>
</div>
</form>
@ -217,7 +217,7 @@ const markAllAsRead = async () => {
</div>
</div>
<h1 class="ui header">
Your notifications
{{ $t('views.Notifications.notificationsHeader') }}
</h1>
<div class="ui toggle checkbox">
<input
@ -225,7 +225,7 @@ const markAllAsRead = async () => {
v-model="filters.is_read"
type="checkbox"
>
<label for="show-read-notifications">Show read notifications</label>
<label for="show-read-notifications">{{ $t('views.Notifications.showReadNotificationsCheckbox') }}</label>
</div>
<button
v-if="filters.is_read === false && notifications.count > 0"
@ -233,7 +233,7 @@ const markAllAsRead = async () => {
@click.prevent="markAllAsRead"
>
<i class="ui check icon" />
Mark all as read
{{ $t('views.Notifications.markAllReadButton') }}
</button>
<div class="ui hidden divider" />
@ -242,7 +242,7 @@ const markAllAsRead = async () => {
:class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"
>
<div class="ui text loader">
Loading notifications
{{ $t('views.Notifications.loadingNotifications') }}
</div>
</div>
@ -259,7 +259,7 @@ const markAllAsRead = async () => {
</tbody>
</table>
<p v-else-if="additionalNotifications === 0">
No notification to show.
{{ $t('views.Notifications.noNotifications') }}
</p>
</div>
</section>

View File

@ -65,43 +65,43 @@ interface SearchType {
const types = computed(() => [
{
id: 'artists',
label: t('Artists'),
label: t('views.Search.artistsLabel'),
includeChannels: true,
contentCategory: 'music'
},
{
id: 'albums',
label: t('Albums'),
label: t('views.Search.albumsLabel'),
includeChannels: true,
contentCategory: 'music'
},
{
id: 'tracks',
label: t('Tracks')
label: t('views.Search.tracksLabel')
},
{
id: 'playlists',
label: t('Playlists')
label: t('views.Search.playlistsLabel')
},
{
id: 'radios',
label: t('Radios'),
label: t('views.Search.radiosLabel'),
endpoint: 'radios/radios'
},
{
id: 'tags',
label: t('Tags')
label: t('views.Search.tagsLabel')
},
{
id: 'podcasts',
label: t('Podcasts'),
label: t('views.Search.podcastsLabel'),
endpoint: '/artists',
contentCategory: 'podcast',
includeChannels: true
},
{
id: 'series',
label: t('Series'),
label: t('views.Search.seriesLabel'),
endpoint: '/albums',
includeChannels: true,
contentCategory: 'podcast'
@ -181,11 +181,11 @@ const labels = computed(() => ({
title: id.value
? (
type.value === 'rss'
? t('Subscribe to a podcast RSS feed')
: t('Search a remote object')
? t('views.Search.rssTitle')
: t('views.Search.remoteTitle')
)
: t('Search'),
submitSearch: t('Submit Search Query')
: t('views.Search.searchTitle'),
submitSearch: t('views.Search.submitSearchLabel')
}))
const radioConfig = computed(() => {
@ -236,7 +236,7 @@ const radioConfig = computed(() => {
>
<h2>
<label for="query">
Search
{{ $t('views.Search.searchHeader') }}
</label>
</h2>
<div class="ui two column doubling stackable grid container">