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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,9 +9,9 @@ const expanded = ref(false)
const { t } = useI18n() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
unmute: t('Unmute'), unmute: t('components.audio.VolumeControl.unmuteLabel'),
mute: t('Mute'), mute: t('components.audio.VolumeControl.muteLabel'),
slider: t('Adjust volume') slider: t('components.audio.VolumeControl.sliderLabel')
})) }))
const { start, stop } = useTimeoutFn(() => (expanded.value = false), 500, { immediate: false }) 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>
<div class="extra content"> <div class="extra content">
<span v-if="album.release_date">{{ momentFormat(new Date(album.release_date), 'Y') }} · </span> <span v-if="album.release_date">{{ momentFormat(new Date(album.release_date), 'Y') }} · </span>
<translate <span>
{{ $t('components.audio.album.Card.trackCount', album.tracks_count) }}
:translate-params="{count: album.tracks_count}" </span>
:translate-n="album.tracks_count"
translate-plural="%{ count } tracks"
>
%{ count } track
</translate>
<play-button <play-button
class="right floated basic icon" class="right floated basic icon"
:dropdown-only="true" :dropdown-only="true"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,12 +24,12 @@ const store = useStore()
const isSubscribed = computed(() => store.getters['channels/isSubscribed'](props.channel.uuid)) const isSubscribed = computed(() => store.getters['channels/isSubscribed'](props.channel.uuid))
const title = computed(() => isSubscribed.value const title = computed(() => isSubscribed.value
? t('Unsubscribe') ? t('components.channels.SubscribeButton.unsubscribeLabel')
: t('Subscribe') : t('components.channels.SubscribeButton.subscribeLabel')
) )
const message = computed(() => ({ 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 () => { const toggle = async () => {

View File

@ -396,7 +396,7 @@ watchEffect(() => {
}) })
const labels = computed(() => ({ const labels = computed(() => ({
editTitle: t('Edit') editTitle: t('components.channels.UploadForm.editTitle')
})) }))
</script> </script>
@ -411,7 +411,7 @@ const labels = computed(() => ({
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
Error while publishing {{ $t('components.channels.UploadForm.failureHeader') }}
</h4> </h4>
<ul class="list"> <ul class="list">
<li <li
@ -424,7 +424,7 @@ const labels = computed(() => ({
</div> </div>
<div :class="['ui', 'required', {hidden: step > 1}, 'field']"> <div :class="['ui', 'required', {hidden: step > 1}, 'field']">
<label for="channel-dropdown"> <label for="channel-dropdown">
Channel {{ $t('components.channels.UploadForm.channelLabel') }}
</label> </label>
<div <div
id="channel-dropdown" id="channel-dropdown"
@ -447,7 +447,7 @@ const labels = computed(() => ({
<div class="content"> <div class="content">
<p> <p>
<i class="copyright icon" /> <i class="copyright icon" />
Add a license to your upload to ensure some freedoms to your public. {{ $t('components.channels.UploadForm.licenseTip') }}
</p> </p>
</div> </div>
</div> </div>
@ -460,7 +460,7 @@ const labels = computed(() => ({
<div class="content"> <div class="content">
<p> <p>
<i class="warning icon" /> <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> </p>
</div> </div>
</div> </div>
@ -471,19 +471,19 @@ const labels = computed(() => ({
> >
<p> <p>
<i class="redo icon" /> <i class="redo icon" />
You have some draft uploads pending publication. {{ $t('components.channels.UploadForm.pendingDraftsMessage') }}
</p> </p>
<button <button
class="ui basic button" class="ui basic button"
@click.stop.prevent="includeDraftUploads = false" @click.stop.prevent="includeDraftUploads = false"
> >
Ignore {{ $t('components.channels.UploadForm.ignoreButton') }}
</button> </button>
<button <button
class="ui basic button" class="ui basic button"
@click.stop.prevent="includeDraftUploads = true" @click.stop.prevent="includeDraftUploads = true"
> >
Resume {{ $t('components.channels.UploadForm.resumeButton') }}
</button> </button>
</div> </div>
<div <div
@ -533,31 +533,31 @@ const labels = computed(() => ({
</template> </template>
</template> </template>
<template v-else> <template v-else>
<translate <span
v-if="file.active" v-if="file.active"
> >
Uploading {{ $t('components.channels.UploadForm.uploadingStatus') }}
</translate> </span>
<translate <span
v-else-if="file.error" v-else-if="file.error"
> >
Errored {{ $t('components.channels.UploadForm.erroredStatus') }}
</translate> </span>
<translate <span
v-else v-else
> >
Pending {{ $t('components.channels.UploadForm.pendingStatus') }}
</translate> </span>
· {{ humanSize(file.size ?? 0) }} · {{ humanSize(file.size ?? 0) }}
· {{ parseFloat(file.progress ?? '0') }}% · {{ parseFloat(file.progress ?? '0') }}%
</template> </template>
· <a @click.stop.prevent="remove(file)"> · <a @click.stop.prevent="remove(file)">
Remove {{ $t('components.channels.UploadForm.removeUpload') }}
</a> </a>
<template v-if="file.error"> <template v-if="file.error">
· ·
<a @click.stop.prevent="retry(file)"> <a @click.stop.prevent="retry(file)">
Retry {{ $t('components.channels.UploadForm.retryUpload') }}
</a> </a>
</template> </template>
</div> </div>
@ -576,12 +576,7 @@ const labels = computed(() => ({
<div class="content"> <div class="content">
<p> <p>
<i class="info icon" /> <i class="info icon" />
<translate {{ $t('components.channels.UploadForm.supportedExtensions', {extensions: $store.state.ui.supportedExtensions.join(', ')}) }}
:translate-params="{extensions: $store.state.ui.supportedExtensions.join(', ')}"
>
Supported extensions: %{ extensions }
</translate>
</p> </p>
</div> </div>
</div> </div>
@ -600,11 +595,11 @@ const labels = computed(() => ({
> >
<div> <div>
<i class="upload icon" />&nbsp; <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>
<div class="ui very small divider" /> <div class="ui very small divider" />
<div> <div>
Browse {{ $t('components.channels.UploadForm.openFileBrowser') }}
</div> </div>
</file-upload-widget> </file-upload-widget>
<div class="ui hidden divider" /> <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', {loading: isLoading}, 'form']">
<div class="ui required field"> <div class="ui required field">
<label for="upload-title"> <label for="upload-title">
Title {{ $t('components.channels.UploadMetadataForm.titleLabel') }}
</label> </label>
<input <input
v-model="newValues.title" v-model="newValues.title"
@ -44,13 +44,13 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
v-model="newValues.cover" v-model="newValues.cover"
@delete="newValues.cover = ''" @delete="newValues.cover = ''"
> >
Track Picture {{ $t('components.channels.UploadMetadataForm.trackImage') }}
</attachment-input> </attachment-input>
<div class="ui small hidden divider" /> <div class="ui small hidden divider" />
<div class="ui two fields"> <div class="ui two fields">
<div class="ui field"> <div class="ui field">
<label for="upload-tags"> <label for="upload-tags">
Tags {{ $t('components.channels.UploadMetadataForm.tagsLabel') }}
</label> </label>
<tags-selector <tags-selector
id="upload-tags" id="upload-tags"
@ -60,7 +60,7 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
</div> </div>
<div class="ui field"> <div class="ui field">
<label for="upload-position"> <label for="upload-position">
Position {{ $t('components.channels.UploadMetadataForm.uploadPosition') }}
</label> </label>
<input <input
v-model="newValues.position" v-model="newValues.position"
@ -72,7 +72,7 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
</div> </div>
<div class="ui field"> <div class="ui field">
<label for="upload-description"> <label for="upload-description">
Description {{ $t('components.channels.UploadMetadataForm.descriptionLabel') }}
</label> </label>
<content-form <content-form
v-model="newValues.description" v-model="newValues.description"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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