Add all missing strings
This commit is contained in:
parent
5e1e260606
commit
db9986e7b9
|
@ -109,7 +109,7 @@ store.dispatch('auth/fetchUser')
|
|||
<component :is="Component" />
|
||||
<template #fallback>
|
||||
<!-- TODO (wvffle): Add loader -->
|
||||
Loading...
|
||||
{{ $t('App.loading') }}
|
||||
</template>
|
||||
</Suspense>
|
||||
</keep-alive>
|
||||
|
|
|
@ -202,16 +202,16 @@ const allScopes = computed(() => {
|
|||
:class="['ui', {'loading': isLoading}, 'success', 'button']"
|
||||
type="submit"
|
||||
>
|
||||
<translate
|
||||
<span
|
||||
v-if="app !== null"
|
||||
>
|
||||
{{ $t('components.auth.ApplicationForm.updateButtonLabel') }}
|
||||
</translate>
|
||||
<translate
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('components.auth.ApplicationForm.createButtonLabel') }}
|
||||
</translate>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
@ -384,7 +384,7 @@ fetchOwnedApps()
|
|||
{{ $t('components.auth.Settings.changePasswordHeader') }}
|
||||
</h2>
|
||||
<div class="ui message">
|
||||
{{ $t('components.auth.Settings.changePasswordMessage') }}
|
||||
{{ $t('components.auth.Settings.changePasswordMessage') }} {{ $t('components.auth.Settings.changePasswordMessageContinued') }}
|
||||
</div>
|
||||
<form
|
||||
class="ui form"
|
||||
|
|
|
@ -91,7 +91,7 @@ fetchToken()
|
|||
{{ $t('components.auth.SubsonicTokenForm.unavailableMessage') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('components.auth.SubsonicTokenForm.subsonicApiDescription') }}
|
||||
{{ $t('components.auth.SubsonicTokenForm.subsonicApiDescription') }} {{ $t('components.auth.SubsonicTokenForm.subsonicApiDescriptionContinued') }}
|
||||
</p>
|
||||
<p>
|
||||
{{ $t('components.auth.SubsonicTokenForm.subsonicPasswordInfo') }}
|
||||
|
|
|
@ -33,16 +33,16 @@ const albumForm = ref()
|
|||
class="small"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate
|
||||
<span
|
||||
v-if="channel.content_category === 'podcast'"
|
||||
>
|
||||
{{ $t('components.channels.AlbumModal.newSeriesHeader') }}
|
||||
</translate>
|
||||
<translate
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('components.channels.AlbumModal.newAlbumHeader') }}
|
||||
</translate>
|
||||
</span>
|
||||
</h4>
|
||||
<div class="scrolling content">
|
||||
<channel-album-form
|
||||
|
|
|
@ -29,8 +29,8 @@ const statusInfo = computed(() => {
|
|||
}
|
||||
|
||||
if (statusData.value.totalFiles) {
|
||||
const msg = t('components.channels.UploadModal.fileCount', statusData.value.totalFiles)
|
||||
info.push(t(msg, { count: statusData.value.totalFiles }))
|
||||
const msg = t('components.channels.UploadModal.fileCount', { count: statusData.value.totalFiles })
|
||||
info.push(msg)
|
||||
}
|
||||
|
||||
if (statusData.value.progress) {
|
||||
|
|
|
@ -363,7 +363,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
{{ $t('components.library.FileUpload.localUploadCopyright') }}
|
||||
</li>
|
||||
<li>
|
||||
{{ $t('components.library.FileUpload.localUploadTag') }}
|
||||
{{ $t('components.library.FileUpload.localUploadTag') }}
|
||||
<a
|
||||
href="http://picard.musicbrainz.org/"
|
||||
target="_blank"
|
||||
|
|
|
@ -44,16 +44,16 @@ fetchLicenses()
|
|||
<section class="ui vertical stripe segment">
|
||||
<div class="ui text container">
|
||||
<h2>
|
||||
<translate
|
||||
<span
|
||||
v-if="canEdit"
|
||||
>
|
||||
{{ $t('components.library.TrackEdit.editTrackHeader') }}
|
||||
</translate>
|
||||
<translate
|
||||
</span>
|
||||
<span
|
||||
key="2"
|
||||
>
|
||||
{{ $t('components.library.TrackEdit.suggestEditHeader') }}
|
||||
</translate>
|
||||
</span>
|
||||
</h2>
|
||||
<div
|
||||
v-if="!object.is_local"
|
||||
|
|
|
@ -344,11 +344,11 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => {
|
|||
</td>
|
||||
<td>
|
||||
<span v-if="scope.obj.size">{{ humanSize(scope.obj.size) }}</span>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('components.manage.library.UploadsTable.notApplicable') }}
|
||||
</translate>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<human-date :date="scope.obj.creation_date" />
|
||||
|
@ -358,11 +358,11 @@ const getPrivacyLevelChoice = (privacyLevel: PrivacyLevel) => {
|
|||
v-if="scope.obj.accessed_date"
|
||||
:date="scope.obj.accessed_date"
|
||||
/>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('components.manage.library.UploadsTable.notApplicable') }}
|
||||
</translate>
|
||||
</span>
|
||||
</td>
|
||||
</template>
|
||||
</action-table>
|
||||
|
|
|
@ -93,11 +93,11 @@ store.dispatch('playlists/fetchOwn')
|
|||
</div>
|
||||
</h2>
|
||||
</template>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('components.playlists.PlaylistModal.managePlaylistsHeader') }}
|
||||
</translate>
|
||||
</span>
|
||||
</h4>
|
||||
<div class="scrolling content">
|
||||
<playlist-form
|
||||
|
|
|
@ -56,7 +56,7 @@ export default (props: PlayOptionsProps) => {
|
|||
}
|
||||
|
||||
store.commit('ui/addMessage', {
|
||||
content: t('%{ count } tracks were added to your queue | %{ count } track was added to your queue | %{ count } tracks were added to your queue', tracks.length),
|
||||
content: t('composables.audio.usePlayOptions.addToQueueMessage', { count: tracks.length }),
|
||||
date: new Date()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,143 +8,143 @@ const { t } = i18n.global
|
|||
export default () => ({
|
||||
fields: {
|
||||
privacy_level: {
|
||||
label: t('Activity visibility'),
|
||||
help: t('Determine the visibility level of your activity'),
|
||||
label: t('composables.locale.useSharedLabels.fields.privacyLevel.label'),
|
||||
help: t('composables.locale.useSharedLabels.fields.privacyLevel.help'),
|
||||
choices: {
|
||||
me: t('Nobody except me'),
|
||||
instance: t('Everyone on this instance'),
|
||||
everyone: t('Everyone, across all instances')
|
||||
me: t('composables.locale.useSharedLabels.fields.privacyLevel.choices.private'),
|
||||
instance: t('composables.locale.useSharedLabels.fields.privacyLevel.choices.instance'),
|
||||
everyone: t('composables.locale.useSharedLabels.fields.privacyLevel.choices.public')
|
||||
} as Record<PrivacyLevel, string>,
|
||||
shortChoices: {
|
||||
me: t('Private'),
|
||||
instance: t('Instance'),
|
||||
everyone: t('Everyone')
|
||||
me: t('composables.locale.useSharedLabels.fields.privacyLevel.shortChoices.private'),
|
||||
instance: t('composables.locale.useSharedLabels.fields.privacyLevel.shortChoices.instance'),
|
||||
everyone: t('composables.locale.useSharedLabels.fields.privacyLevel.shortChoices.public')
|
||||
} as Record<PrivacyLevel, string>
|
||||
},
|
||||
import_status: {
|
||||
label: t('Click to display more information about the import process for this upload'),
|
||||
label: t('composables.locale.useSharedLabels.fields.importStatus.label'),
|
||||
choices: {
|
||||
skipped: {
|
||||
label: t('Skipped'),
|
||||
help: t('This track is already present in one of your libraries')
|
||||
label: t('composables.locale.useSharedLabels.fields.importStatus.choices.skipped.label'),
|
||||
help: t('composables.locale.useSharedLabels.fields.importStatus.choices.skipped.help')
|
||||
},
|
||||
draft: {
|
||||
label: t('Draft'),
|
||||
help: t('This track has been uploaded, but hasn\'t been scheduled for processing yet')
|
||||
label: t('composables.locale.useSharedLabels.fields.importStatus.choices.draft.label'),
|
||||
help: t('composables.locale.useSharedLabels.fields.importStatus.choices.draft.help')
|
||||
},
|
||||
pending: {
|
||||
label: t('Pending'),
|
||||
help: t('This track has been uploaded, but hasn\'t been processed by the server yet')
|
||||
label: t('composables.locale.useSharedLabels.fields.importStatus.choices.pending.label'),
|
||||
help: t('composables.locale.useSharedLabels.fields.importStatus.choices.pending.help')
|
||||
},
|
||||
errored: {
|
||||
label: t('Errored'),
|
||||
help: t('This track could not be processed, please make sure it is tagged correctly')
|
||||
label: t('composables.locale.useSharedLabels.fields.importStatus.choices.errored.label'),
|
||||
help: t('composables.locale.useSharedLabels.fields.importStatus.choices.errored.help')
|
||||
},
|
||||
finished: {
|
||||
label: t('Finished'),
|
||||
help: t('Imported')
|
||||
label: t('composables.locale.useSharedLabels.fields.importStatus.choices.finished.label'),
|
||||
help: t('composables.locale.useSharedLabels.fields.importStatus.choices.finished.help')
|
||||
}
|
||||
} as Record<ImportStatus, { label: string, help: string }>
|
||||
},
|
||||
report_type: {
|
||||
label: t('Category'),
|
||||
label: t('composables.locale.useSharedLabels.fields.reportType.label'),
|
||||
choices: {
|
||||
takedown_request: t('Takedown request'),
|
||||
invalid_metadata: t('Invalid metadata'),
|
||||
illegal_content: t('Illegal content'),
|
||||
offensive_content: t('Offensive content'),
|
||||
other: t('Other')
|
||||
takedown_request: t('composables.locale.useSharedLabels.fields.reportType.choices.takedownRequest'),
|
||||
invalid_metadata: t('composables.locale.useSharedLabels.fields.reportType.choices.invalidMetadata'),
|
||||
illegal_content: t('composables.locale.useSharedLabels.fields.reportType.choices.illegalContent'),
|
||||
offensive_content: t('composables.locale.useSharedLabels.fields.reportType.choices.offensiveContent'),
|
||||
other: t('composables.locale.useSharedLabels.fields.reportType.choices.other')
|
||||
}
|
||||
},
|
||||
summary: {
|
||||
label: t('Bio'),
|
||||
label: t('composables.locale.useSharedLabels.fields.summary.label'),
|
||||
help: undefined
|
||||
},
|
||||
content_category: {
|
||||
label: t('Content category'),
|
||||
label: t('composables.locale.useSharedLabels.fields.contentCategory.label'),
|
||||
choices: {
|
||||
podcast: t('Podcast'),
|
||||
music: t('Music'),
|
||||
other: t('Other')
|
||||
podcast: t('composables.locale.useSharedLabels.fields.contentCategory.choices.podcast'),
|
||||
music: t('composables.locale.useSharedLabels.fields.contentCategory.choices.music'),
|
||||
other: t('composables.locale.useSharedLabels.fields.contentCategory.choices.other')
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
creation_date: t('Creation date'),
|
||||
release_date: t('Release date'),
|
||||
accessed_date: t('Accessed date'),
|
||||
applied_date: t('Applied date'),
|
||||
handled_date: t('Handled date'),
|
||||
first_seen: t('First seen date'),
|
||||
last_seen: t('Last seen date'),
|
||||
modification_date: t('Modification date'),
|
||||
expiration_date: t('Expiration date'),
|
||||
track_title: t('Track name'),
|
||||
album_title: t('Album name'),
|
||||
artist_name: t('Artist name'),
|
||||
name: t('Name'),
|
||||
length: t('Duration'),
|
||||
items_count: t('Items'),
|
||||
size: t('Size'),
|
||||
bitrate: t('Bitrate'),
|
||||
duration: t('Duration'),
|
||||
date_joined: t('Sign-up date'),
|
||||
last_activity: t('Last activity'),
|
||||
username: t('Username'),
|
||||
domain: t('Domain'),
|
||||
users: t('Users'),
|
||||
received_messages: t('Received messages'),
|
||||
uploads: t('Uploads'),
|
||||
followers: t('Followers')
|
||||
creation_date: t('composables.locale.useSharedLabels.filters.creationDate'),
|
||||
release_date: t('composables.locale.useSharedLabels.filters.releaseDate'),
|
||||
accessed_date: t('composables.locale.useSharedLabels.filters.accessedDate'),
|
||||
applied_date: t('composables.locale.useSharedLabels.filters.appliedDate'),
|
||||
handled_date: t('composables.locale.useSharedLabels.filters.handledDate'),
|
||||
first_seen: t('composables.locale.useSharedLabels.filters.firstSeen'),
|
||||
last_seen: t('composables.locale.useSharedLabels.filters.lastSeen'),
|
||||
modification_date: t('composables.locale.useSharedLabels.filters.modificationDate'),
|
||||
expiration_date: t('composables.locale.useSharedLabels.filters.expirationDate'),
|
||||
track_title: t('composables.locale.useSharedLabels.filters.trackTitle'),
|
||||
album_title: t('composables.locale.useSharedLabels.filters.albumTitle'),
|
||||
artist_name: t('composables.locale.useSharedLabels.filters.artistName'),
|
||||
name: t('composables.locale.useSharedLabels.filters.name'),
|
||||
length: t('composables.locale.useSharedLabels.filters.duration'),
|
||||
items_count: t('composables.locale.useSharedLabels.filters.itemsCount'),
|
||||
size: t('composables.locale.useSharedLabels.filters.size'),
|
||||
bitrate: t('composables.locale.useSharedLabels.filters.bitrate'),
|
||||
duration: t('composables.locale.useSharedLabels.filters.duration'),
|
||||
date_joined: t('composables.locale.useSharedLabels.filters.dateJoined'),
|
||||
last_activity: t('composables.locale.useSharedLabels.filters.lastActivity'),
|
||||
username: t('composables.locale.useSharedLabels.filters.username'),
|
||||
domain: t('composables.locale.useSharedLabels.filters.domain'),
|
||||
users: t('composables.locale.useSharedLabels.filters.users'),
|
||||
received_messages: t('composables.locale.useSharedLabels.filters.receivedMessages'),
|
||||
uploads: t('composables.locale.useSharedLabels.filters.uploads'),
|
||||
followers: t('composables.locale.useSharedLabels.filters.followers')
|
||||
},
|
||||
scopes: {
|
||||
profile: {
|
||||
label: t('Profile'),
|
||||
description: t('Access to e-mail, username, and profile information')
|
||||
label: t('composables.locale.useSharedLabels.scopes.profile.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.profile.description')
|
||||
},
|
||||
libraries: {
|
||||
label: t('Libraries and uploads'),
|
||||
description: t('Access to audio files, libraries, artists, albums and tracks')
|
||||
label: t('composables.locale.useSharedLabels.scopes.libraries.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.libraries.description')
|
||||
},
|
||||
favorites: {
|
||||
label: t('Favorites'),
|
||||
description: t('Access to favorites')
|
||||
label: t('composables.locale.useSharedLabels.scopes.favorites.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.favorites.description')
|
||||
},
|
||||
listenings: {
|
||||
label: t('Listenings'),
|
||||
description: t('Access to listening history')
|
||||
label: t('composables.locale.useSharedLabels.scopes.listenings.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.listenings.description')
|
||||
},
|
||||
follows: {
|
||||
label: t('Follows'),
|
||||
description: t('Access to follows')
|
||||
label: t('composables.locale.useSharedLabels.scopes.follows.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.follows.description')
|
||||
},
|
||||
playlists: {
|
||||
label: t('Playlists'),
|
||||
description: t('Access to playlists')
|
||||
label: t('composables.locale.useSharedLabels.scopes.playlists.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.playlists.description')
|
||||
},
|
||||
radios: {
|
||||
label: t('Radios'),
|
||||
description: t('Access to radios')
|
||||
label: t('composables.locale.useSharedLabels.scopes.radios.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.radios.description')
|
||||
},
|
||||
filters: {
|
||||
label: t('Content filters'),
|
||||
description: t('Access to content filters')
|
||||
label: t('composables.locale.useSharedLabels.scopes.filters.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.filters.description')
|
||||
},
|
||||
notifications: {
|
||||
label: t('Notifications'),
|
||||
description: t('Access to notifications')
|
||||
label: t('composables.locale.useSharedLabels.scopes.notifications.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.notifications.description')
|
||||
},
|
||||
edits: {
|
||||
label: t('Edits'),
|
||||
description: t('Access to edits')
|
||||
label: t('composables.locale.useSharedLabels.scopes.edits.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.edits.description')
|
||||
},
|
||||
security: {
|
||||
label: t('Security'),
|
||||
description: t('Access to security settings such as password and authorization')
|
||||
label: t('composables.locale.useSharedLabels.scopes.security.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.security.description')
|
||||
},
|
||||
reports: {
|
||||
label: t('Reports'),
|
||||
description: t('Access to moderation reports')
|
||||
label: t('composables.locale.useSharedLabels.scopes.reports.label'),
|
||||
description: t('composables.locale.useSharedLabels.scopes.reports.description')
|
||||
}
|
||||
} as Record<ScopeId, { label: string, description: string }>
|
||||
})
|
||||
|
|
|
@ -27,7 +27,7 @@ const description: ConfigField = {
|
|||
id: 'description',
|
||||
type: 'content',
|
||||
required: true,
|
||||
label: t('Description'),
|
||||
label: t('composables.moderation.useEditConfigs.description.label'),
|
||||
getValue: (obj) => obj.description ?? { text: '', content_type: 'text/markdown' },
|
||||
getValueRepr: getContentValueRepr
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ const cover: ConfigField = {
|
|||
id: 'cover',
|
||||
type: 'attachment',
|
||||
required: false,
|
||||
label: t('Cover'),
|
||||
label: t('composables.moderation.useEditConfigs.cover.label'),
|
||||
getValue: (obj) => obj.cover?.uuid ?? null
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ const tags: ConfigField = {
|
|||
id: 'tags',
|
||||
type: 'tags',
|
||||
required: true,
|
||||
label: t('Tags'),
|
||||
label: t('composables.moderation.useEditConfigs.tags.label'),
|
||||
getValue: (obj) => { return obj.tags },
|
||||
getValueRepr: (tags: string[]) => tags.slice().sort().join('\n')
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export default (): Configs => ({
|
|||
id: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: t('Name'),
|
||||
label: t('composables.moderation.useEditConfigs.artist.name'),
|
||||
getValue: (artist) => (artist as Artist).name
|
||||
},
|
||||
description,
|
||||
|
@ -71,7 +71,7 @@ export default (): Configs => ({
|
|||
id: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: t('Title'),
|
||||
label: t('composables.moderation.useEditConfigs.album.title'),
|
||||
getValue: (album) => (album as Album).title
|
||||
},
|
||||
description,
|
||||
|
@ -79,7 +79,7 @@ export default (): Configs => ({
|
|||
id: 'release_date',
|
||||
type: 'text',
|
||||
required: false,
|
||||
label: t('Release date'),
|
||||
label: t('composables.moderation.useEditConfigs.album.releaseDate'),
|
||||
getValue: (album) => (album as Album).release_date
|
||||
},
|
||||
cover,
|
||||
|
@ -92,7 +92,7 @@ export default (): Configs => ({
|
|||
id: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: t('Title'),
|
||||
label: t('composables.moderation.useEditConfigs.track.title'),
|
||||
getValue: (track) => (track as Track).title
|
||||
},
|
||||
description,
|
||||
|
@ -102,21 +102,21 @@ export default (): Configs => ({
|
|||
type: 'text',
|
||||
inputType: 'number',
|
||||
required: false,
|
||||
label: t('Position'),
|
||||
label: t('composables.moderation.useEditConfigs.track.position'),
|
||||
getValue: (track) => (track as Track).position
|
||||
},
|
||||
{
|
||||
id: 'copyright',
|
||||
type: 'text',
|
||||
required: false,
|
||||
label: t('Copyright'),
|
||||
label: t('composables.moderation.useEditConfigs.track.copyright'),
|
||||
getValue: (track) => (track as Track).copyright
|
||||
},
|
||||
{
|
||||
id: 'license',
|
||||
type: 'license',
|
||||
required: false,
|
||||
label: t('License'),
|
||||
label: t('composables.moderation.useEditConfigs.track.license'),
|
||||
getValue: (track) => (track as Track).license
|
||||
},
|
||||
tags
|
||||
|
|
|
@ -35,26 +35,26 @@ const getReportableObjects = ({ track, album, artist, playlist, account, library
|
|||
|
||||
if (account) {
|
||||
reportableObjs.push({
|
||||
label: t('Report @%{ username }…', { username: account.preferred_username }),
|
||||
label: t('composables.moderation.useReport.account.label', { username: account.preferred_username }),
|
||||
target: {
|
||||
type: 'account',
|
||||
_obj: account,
|
||||
full_username: account.full_username,
|
||||
label: account.full_username,
|
||||
typeLabel: t('Account')
|
||||
typeLabel: t('composables.moderation.useReport.account.typeLabel')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (track) {
|
||||
reportableObjs.push({
|
||||
label: t('Report this track…'),
|
||||
label: t('composables.moderation.useReport.track.label'),
|
||||
target: {
|
||||
type: 'track',
|
||||
id: track.id,
|
||||
_obj: track,
|
||||
label: track.title,
|
||||
typeLabel: t('Track')
|
||||
typeLabel: t('composables.moderation.useReport.track.typeLabel')
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -64,13 +64,13 @@ const getReportableObjects = ({ track, album, artist, playlist, account, library
|
|||
|
||||
if (album) {
|
||||
reportableObjs.push({
|
||||
label: t('Report this album…'),
|
||||
label: t('composables.moderation.useReport.album.label'),
|
||||
target: {
|
||||
type: 'album',
|
||||
id: album.id,
|
||||
label: album.title,
|
||||
_obj: album,
|
||||
typeLabel: t('Album')
|
||||
typeLabel: t('composables.moderation.useReport.album.typeLabel')
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -81,50 +81,50 @@ const getReportableObjects = ({ track, album, artist, playlist, account, library
|
|||
|
||||
if (channel) {
|
||||
reportableObjs.push({
|
||||
label: t('Report this channel…'),
|
||||
label: t('composables.moderation.useReport.channel.label'),
|
||||
target: {
|
||||
type: 'channel',
|
||||
uuid: channel.uuid,
|
||||
label: channel.artist?.name ?? t('Unknown artist'),
|
||||
label: channel.artist?.name ?? t('composables.moderation.useReport.artist.unknownLabel'),
|
||||
_obj: channel,
|
||||
typeLabel: t('Channel')
|
||||
typeLabel: t('composables.moderation.useReport.channel.typeLabel')
|
||||
}
|
||||
})
|
||||
} else if (artist) {
|
||||
reportableObjs.push({
|
||||
label: t('Report this artist…'),
|
||||
label: t('composables.moderation.useReport.artist.label'),
|
||||
target: {
|
||||
type: 'artist',
|
||||
id: artist.id,
|
||||
label: artist.name,
|
||||
_obj: artist,
|
||||
typeLabel: t('Artist')
|
||||
typeLabel: t('composables.moderation.useReport.artist.typeLabel')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (playlist) {
|
||||
reportableObjs.push({
|
||||
label: t('Report this playlist…'),
|
||||
label: t('composables.moderation.useReport.playlist.label'),
|
||||
target: {
|
||||
type: 'playlist',
|
||||
id: playlist.id,
|
||||
label: playlist.name,
|
||||
_obj: playlist,
|
||||
typeLabel: t('Playlist')
|
||||
typeLabel: t('composables.moderation.useReport.playlist.typeLabel')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (library) {
|
||||
reportableObjs.push({
|
||||
label: t('Report this library…'),
|
||||
label: t('composables.moderation.useReport.library.label'),
|
||||
target: {
|
||||
type: 'library',
|
||||
uuid: library.uuid,
|
||||
label: library.name,
|
||||
_obj: library,
|
||||
typeLabel: t('Library')
|
||||
typeLabel: t('composables.moderation.useReport.library.typeLabel')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -26,33 +26,33 @@ const { t } = useI18n()
|
|||
|
||||
const tags: ModeratedField = {
|
||||
id: 'tags',
|
||||
label: t('Tags'),
|
||||
label: t('composables.moderation.useReportConfigs.tags.label'),
|
||||
getValueRepr: (tags: string[]) => tags.slice().sort().join('\n')
|
||||
}
|
||||
|
||||
const name: ModeratedField = {
|
||||
id: 'name',
|
||||
label: t('Name')
|
||||
label: t('composables.moderation.useReportConfigs.name.label')
|
||||
}
|
||||
|
||||
const creationDate: ModeratedField = {
|
||||
id: 'creation_date',
|
||||
label: t('Creation date')
|
||||
label: t('composables.moderation.useReportConfigs.creationDate.label')
|
||||
}
|
||||
|
||||
const musicBrainzId: ModeratedField = {
|
||||
id: 'mbid',
|
||||
label: t('MusicBrainz ID')
|
||||
label: t('composables.moderation.useReportConfigs.musicbrainzId.label')
|
||||
}
|
||||
|
||||
const visibility: ModeratedField = {
|
||||
id: 'privacy_level',
|
||||
label: t('Visibility')
|
||||
label: t('composables.moderation.useReportConfigs.visibility.label')
|
||||
}
|
||||
|
||||
export default (): Configs => ({
|
||||
artist: {
|
||||
label: t('Artist'),
|
||||
label: t('composables.moderation.useReportConfigs.artist.label'),
|
||||
icon: 'users',
|
||||
getDeleteUrl: (obj) => {
|
||||
return `manage/library/artists/${obj.id}/`
|
||||
|
@ -69,7 +69,7 @@ export default (): Configs => ({
|
|||
]
|
||||
},
|
||||
album: {
|
||||
label: t('Album'),
|
||||
label: t('composables.moderation.useReportConfigs.album.label'),
|
||||
icon: 'play',
|
||||
getDeleteUrl: (obj) => {
|
||||
return `manage/library/albums/${obj.id}/`
|
||||
|
@ -81,19 +81,19 @@ export default (): Configs => ({
|
|||
moderatedFields: [
|
||||
{
|
||||
id: 'title',
|
||||
label: t('Title')
|
||||
label: t('composables.moderation.useReportConfigs.album.title')
|
||||
},
|
||||
creationDate,
|
||||
{
|
||||
id: 'release_date',
|
||||
label: t('Release date')
|
||||
label: t('composables.moderation.useReportConfigs.album.releaseDate')
|
||||
},
|
||||
tags,
|
||||
musicBrainzId
|
||||
]
|
||||
},
|
||||
track: {
|
||||
label: t('Track'),
|
||||
label: t('composables.moderation.useReportConfigs.track.label'),
|
||||
icon: 'music',
|
||||
getDeleteUrl: (obj) => {
|
||||
return `manage/library/tracks/${obj.id}/`
|
||||
|
@ -105,26 +105,26 @@ export default (): Configs => ({
|
|||
moderatedFields: [
|
||||
{
|
||||
id: 'title',
|
||||
label: t('Title')
|
||||
label: t('composables.moderation.useReportConfigs.track.title')
|
||||
},
|
||||
{
|
||||
id: 'position',
|
||||
label: t('Position')
|
||||
label: t('composables.moderation.useReportConfigs.track.position')
|
||||
},
|
||||
{
|
||||
id: 'copyright',
|
||||
label: t('Copyright')
|
||||
label: t('composables.moderation.useReportConfigs.track.copyright')
|
||||
},
|
||||
{
|
||||
id: 'license',
|
||||
label: t('License')
|
||||
label: t('composables.moderation.useReportConfigs.track.license')
|
||||
},
|
||||
tags,
|
||||
musicBrainzId
|
||||
]
|
||||
},
|
||||
library: {
|
||||
label: t('Library'),
|
||||
label: t('composables.moderation.useReportConfigs.library.label'),
|
||||
icon: 'book',
|
||||
getDeleteUrl: (obj) => {
|
||||
return `manage/library/libraries/${obj.uuid}/`
|
||||
|
@ -136,13 +136,13 @@ export default (): Configs => ({
|
|||
name,
|
||||
{
|
||||
id: 'description',
|
||||
label: t('Description')
|
||||
label: t('composables.moderation.useReportConfigs.library.description')
|
||||
},
|
||||
visibility
|
||||
]
|
||||
},
|
||||
playlist: {
|
||||
label: t('Playlist'),
|
||||
label: t('composables.moderation.useReportConfigs.playlist.label'),
|
||||
icon: 'list',
|
||||
urls: {
|
||||
getDetail: (obj) => ({ name: 'library.playlists.detail', params: { id: obj.id } })
|
||||
|
@ -154,7 +154,7 @@ export default (): Configs => ({
|
|||
]
|
||||
},
|
||||
account: {
|
||||
label: t('Account'),
|
||||
label: t('composables.moderation.useReportConfigs.account.label'),
|
||||
icon: 'user',
|
||||
urls: {
|
||||
getDetail: (obj) => ({ name: 'profile.full.overview', params: { username: obj.preferred_username, domain: obj.domain } }),
|
||||
|
@ -164,12 +164,12 @@ export default (): Configs => ({
|
|||
name,
|
||||
{
|
||||
id: 'summary',
|
||||
label: t('Bio')
|
||||
label: t('composables.moderation.useReportConfigs.account.summary')
|
||||
}
|
||||
]
|
||||
},
|
||||
channel: {
|
||||
label: t('Channel'),
|
||||
label: t('composables.moderation.useReportConfigs.channel.label'),
|
||||
icon: 'stream',
|
||||
urls: {
|
||||
getDetail: (obj) => ({ name: 'channels.detail', params: { id: obj.uuid } }),
|
||||
|
|
|
@ -16,7 +16,7 @@ async function useErrorHandler (error: Error | BackendError, eventId?: string):
|
|||
? 'Unexpected API error'
|
||||
: 'Unexpected error'
|
||||
|
||||
let content = t('An unexpected error occured.')
|
||||
let content = t('composables.useErrorHandler.unexpectedError')
|
||||
|
||||
if ('backendErrors' in error) {
|
||||
logger.error(title, error, error.backendErrors)
|
||||
|
@ -35,7 +35,7 @@ async function useErrorHandler (error: Error | BackendError, eventId?: string):
|
|||
|
||||
const { get } = useCookies()
|
||||
if (get(COOKIE) === 'yes') {
|
||||
content = t('An unexpected error occurred. <br><sub>To help us understand why it happened, please attach a detailed description of what you did that has triggered the error.</sub>')
|
||||
content = t('composables.useErrorHandler.errorReport')
|
||||
const user = store.state.auth.authenticated
|
||||
? {
|
||||
name: store.state.auth.username,
|
||||
|
@ -44,7 +44,7 @@ async function useErrorHandler (error: Error | BackendError, eventId?: string):
|
|||
: undefined
|
||||
|
||||
actions.push({
|
||||
text: t('Leave feedback'),
|
||||
text: t('composables.useErrorHandler.leaveFeedback'),
|
||||
class: 'basic red',
|
||||
click: () => Sentry.showReportDialog({
|
||||
eventId: eventId ?? Sentry.captureException(error),
|
||||
|
|
|
@ -7,17 +7,17 @@ const { t } = i18n.global
|
|||
const themeList: ThemeEntry[] = [
|
||||
{
|
||||
icon: 'palette icon',
|
||||
name: t('Browser default'),
|
||||
name: t('composables.useThemeList.browserDefault'),
|
||||
key: 'auto'
|
||||
},
|
||||
{
|
||||
icon: 'sun icon',
|
||||
name: t('Light'),
|
||||
name: t('composables.useThemeList.lightTheme'),
|
||||
key: 'light'
|
||||
},
|
||||
{
|
||||
icon: 'moon icon',
|
||||
name: t('Dark'),
|
||||
name: t('composables.useThemeList.darkTheme'),
|
||||
key: 'dark'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -75,9 +75,9 @@ export const install: InitModule = ({ store, router }) => {
|
|||
|
||||
if (rateLimitStatus.availableSeconds) {
|
||||
const tryAgain = moment().add(rateLimitStatus.availableSeconds, 's').toNow(true)
|
||||
message = t('You sent too many requests and have been rate limited, please try again in %{ delay }', { delay: tryAgain })
|
||||
message = t('init.axios.rateLimitDelay', { delay: tryAgain })
|
||||
} else {
|
||||
message = t('You sent too many requests and have been rate limited, please try again later')
|
||||
message = t('init.axios.rateLimitLater')
|
||||
}
|
||||
|
||||
error.backendErrors.push(message)
|
||||
|
|
|
@ -90,11 +90,11 @@ export const install: InitModule = async ({ app, router, store }) => {
|
|||
return store.commit('ui/addMessage', {
|
||||
content: hostname === 'am.funkwhale.audio'
|
||||
? t(
|
||||
'To enhance the quality of our services, we would like to collect information about crashes during your session.<br><sub>The stack traces will be shared to <a href="%{origin}">Funkwhale\'s official Glitchtip instance</a> in order to help us understand how and when the errors occur.</sub>',
|
||||
{ hostname, origin }
|
||||
'init.sentry.funkwhaleGlitchtipMessage',
|
||||
{ origin }
|
||||
)
|
||||
: t(
|
||||
'To enhance the quality of our services, we would like to collect information about crashes during your session.<br><sub>The stack traces will be shared to <a href="%{origin}">%{hostname}</a> in order to help us understand how and when the errors occur.</sub>',
|
||||
'init.sentry.ownGlitchtipMessage',
|
||||
{ hostname, origin }
|
||||
),
|
||||
date: new Date(),
|
||||
|
@ -103,7 +103,7 @@ export const install: InitModule = async ({ app, router, store }) => {
|
|||
classActions: 'bottom attached opaque',
|
||||
actions: [
|
||||
{
|
||||
text: t('Allow'),
|
||||
text: t('init.sentry.allow'),
|
||||
class: 'primary',
|
||||
click: () => {
|
||||
set(COOKIE, 'yes')
|
||||
|
@ -111,7 +111,7 @@ export const install: InitModule = async ({ app, router, store }) => {
|
|||
}
|
||||
},
|
||||
{
|
||||
text: t('Deny'),
|
||||
text: t('init.sentry.deny'),
|
||||
class: 'basic',
|
||||
click: () => set(COOKIE, 'no')
|
||||
}
|
||||
|
|
|
@ -21,19 +21,19 @@ export const install: InitModule = ({ store }) => {
|
|||
},
|
||||
onNeedRefresh () {
|
||||
store.commit('ui/addMessage', {
|
||||
content: t('A new version of the app is available.'),
|
||||
content: t('init.serviceWorker.newAppVersion'),
|
||||
date: new Date(),
|
||||
key: 'refreshApp',
|
||||
displayTime: 0,
|
||||
classActions: 'bottom attached opaque',
|
||||
actions: [
|
||||
{
|
||||
text: t('Update'),
|
||||
text: t('init.serviceWorker.actions.update'),
|
||||
class: 'primary',
|
||||
click: () => updateSW()
|
||||
},
|
||||
{
|
||||
text: t('Later'),
|
||||
text: t('init.serviceWorker.actions.later'),
|
||||
class: 'basic'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"App": {
|
||||
"loading": "Loading..."
|
||||
},
|
||||
"components": {
|
||||
"About": {
|
||||
"title": "About",
|
||||
|
@ -503,7 +506,8 @@
|
|||
"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.",
|
||||
"changePasswordMessage": "Changing your password will also change your Subsonic API password if you have requested one.",
|
||||
"changePasswordMessageContinued": "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",
|
||||
|
@ -584,7 +588,8 @@
|
|||
"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.",
|
||||
"subsonicApiDescription": "Funkwhale is compatible with other music players that support the Subsonic API.",
|
||||
"subsonicApiDescriptionContinued": "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",
|
||||
|
@ -982,7 +987,7 @@
|
|||
"localUploadHeader": "Upload music from '~/your local storage",
|
||||
"localUploadMessage": "You are about to upload music to your library. Before proceeding, please ensure that:",
|
||||
"localUploadCopyright": "You are not uploading copyrighted content in a public library, otherwise you may be infringing the law",
|
||||
"localUploadTag": "The music files you are uploading are tagged properly. ",
|
||||
"localUploadTag": "The music files you are uploading are tagged properly.",
|
||||
"localUploadPicardLink": "We recommend using Picard for that purpose.",
|
||||
"localUploadSupportedFormats": "The music files you are uploading are in OGG, Flac, MP3 or AIFF format",
|
||||
"uploadWidgetLabel": "Click to select files to upload or drag and drop files or directories",
|
||||
|
@ -1659,6 +1664,273 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"composables": {
|
||||
"useErrorHandler": {
|
||||
"unexpectedError": "An unexpected error occurred.",
|
||||
"errorReport": "An unexpected error occured. <br><sub>To help us understand why it happened, please attach a detailed description of what you did that has triggered the error.</sub>",
|
||||
"leaveFeedback": "Leave feedback"
|
||||
},
|
||||
"useThemeList": {
|
||||
"browserDefault": "Browser default",
|
||||
"lightTheme": "Light",
|
||||
"darkTheme": "Dark"
|
||||
},
|
||||
"audio": {
|
||||
"usePlayOptions": {
|
||||
"addToQueueMessage": "{count} tracks were added to your queue | {count} track was added to your queue | {count} tracks were added to your queue"
|
||||
},
|
||||
"useQueue": {
|
||||
"queueShuffled": "Queue shuffled!"
|
||||
}
|
||||
},
|
||||
"locale": {
|
||||
"useSharedLabels": {
|
||||
"fields": {
|
||||
"privacyLevel": {
|
||||
"label": "Activity visibility",
|
||||
"help": "Determine the visiblity level of your activity",
|
||||
"choices": {
|
||||
"private": "Nobody except me",
|
||||
"instance": "Everyone on this instance",
|
||||
"public": "Everyone, across all instances"
|
||||
},
|
||||
"shortChoices": {
|
||||
"private": "Private",
|
||||
"instance": "Instance",
|
||||
"public": "Everyone"
|
||||
}
|
||||
},
|
||||
"importStatus": {
|
||||
"label": "Click to display more information about the import process for this upload",
|
||||
"choices": {
|
||||
"skipped": {
|
||||
"label": "Skipped",
|
||||
"help": "This track is already present in one of your libraries"
|
||||
},
|
||||
"draft": {
|
||||
"label": "Draft",
|
||||
"help": "This track has been uploaded, but hasn't been scheduled for processing yet"
|
||||
},
|
||||
"pending": {
|
||||
"label": "Pending",
|
||||
"help": "This track has been uploaded, but hasn't been processed by the server yet"
|
||||
},
|
||||
"errored": {
|
||||
"label": "Errored",
|
||||
"help": "This track could not be processed, please make sure it is tagged correctly"
|
||||
},
|
||||
"finished": {
|
||||
"label": "Finished",
|
||||
"help": "Imported"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reportType": {
|
||||
"label": "Category",
|
||||
"choices": {
|
||||
"takedownRequest": "Takedown request",
|
||||
"invalidMetadata": "Invalid metadata",
|
||||
"illegalContent": "Illegal content",
|
||||
"offensiveContent": "Offensive content",
|
||||
"other": "Other"
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"label": "Bio"
|
||||
},
|
||||
"contentCategory": {
|
||||
"label": "Content category",
|
||||
"choices": {
|
||||
"podcast": "Podcast",
|
||||
"music": "Music",
|
||||
"other": "Other"
|
||||
}
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"creationDate": "Creation date",
|
||||
"releaseDate": "Release date",
|
||||
"accessedDate": "'Accessed date",
|
||||
"appliedDate": "'Applied date",
|
||||
"handledDate": "'Handled date",
|
||||
"firstSeen": "'First seen date",
|
||||
"lastSeen": "'Last seen date",
|
||||
"modificationDate": "'Modification date",
|
||||
"expirationDate": "'Expiration date",
|
||||
"trackTitle": "'Track name",
|
||||
"albumTitle": "'Album name",
|
||||
"artistName": "'Artist name",
|
||||
"name": "'Name",
|
||||
"length": "'Duration",
|
||||
"itemsCount": "'Items",
|
||||
"size": "'Size",
|
||||
"bitrate": "'Bitrate",
|
||||
"duration": "'Duration",
|
||||
"dateJoined": "'Sign-up date",
|
||||
"lastActivity": "'Last activity",
|
||||
"username": "'Username",
|
||||
"domain": "'Domaimn",
|
||||
"users": "'Users",
|
||||
"receivedMessages": "'Received messages",
|
||||
"uploads": "'Uploads",
|
||||
"followers": "'Followers"
|
||||
},
|
||||
"scopes": {
|
||||
"profile": {
|
||||
"label": "Profile",
|
||||
"description": "Access to e-mail, username, and profile information"
|
||||
},
|
||||
"libraries": {
|
||||
"label": "Libraries and uploads",
|
||||
"description": "Access to audio files, libraries, artists, albums and tracks"
|
||||
},
|
||||
"favorites": {
|
||||
"label": "Favorites",
|
||||
"description": "Access to favorites"
|
||||
},
|
||||
"listenings": {
|
||||
"label": "Listenings",
|
||||
"description": "Access to listening history"
|
||||
},
|
||||
"follows": {
|
||||
"label": "Follows",
|
||||
"description": "Access to follows"
|
||||
},
|
||||
"playlists": {
|
||||
"label": "Playlists",
|
||||
"description": "Access to playlists"
|
||||
},
|
||||
"radios": {
|
||||
"label": "Radios",
|
||||
"description": "Access to radios"
|
||||
},
|
||||
"filters": {
|
||||
"label": "Content filters",
|
||||
"description": "Access to content filters"
|
||||
},
|
||||
"notifications": {
|
||||
"label": "Notifications",
|
||||
"description": "Access to notifications"
|
||||
},
|
||||
"edits": {
|
||||
"label": "Edits",
|
||||
"description": "Access to edits"
|
||||
},
|
||||
"security": {
|
||||
"label": "Security",
|
||||
"description": "Access to security settings such as password and authorization"
|
||||
},
|
||||
"reports": {
|
||||
"label": "Reports",
|
||||
"description": "Access to moderation reports"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"moderation": {
|
||||
"useEditConfigs": {
|
||||
"description": {
|
||||
"label": "Description"
|
||||
},
|
||||
"cover": {
|
||||
"label": "Cover"
|
||||
},
|
||||
"tags": {
|
||||
"label": "Tags"
|
||||
},
|
||||
"artist": {
|
||||
"name": "Name"
|
||||
},
|
||||
"album": {
|
||||
"title": "Title",
|
||||
"releaseDate": "Release date"
|
||||
},
|
||||
"track": {
|
||||
"title": "Title",
|
||||
"position": "Position",
|
||||
"copyright": "Copyright",
|
||||
"license": "Licence"
|
||||
}
|
||||
},
|
||||
"useReport": {
|
||||
"account": {
|
||||
"label": "Report {'@'}{username}",
|
||||
"typeLabel": "Account"
|
||||
},
|
||||
"track": {
|
||||
"label": "Report this track…",
|
||||
"typeLabel": "Track"
|
||||
},
|
||||
"album": {
|
||||
"label": "Report this album…",
|
||||
"typeLabel": "Album"
|
||||
},
|
||||
"channel": {
|
||||
"label": "Report this channel…",
|
||||
"typeLabel": "Channel"
|
||||
},
|
||||
"artist": {
|
||||
"label": "Report this artist…",
|
||||
"typeLabel": "Artist",
|
||||
"unknownLabel": "Unknown artist"
|
||||
},
|
||||
"playlist": {
|
||||
"label": "Report this playlist…",
|
||||
"typeLabel": "Playlist"
|
||||
},
|
||||
"library": {
|
||||
"label": "Report this library…",
|
||||
"typeLabel": "Library"
|
||||
}
|
||||
},
|
||||
"useReportConfigs": {
|
||||
"tags": {
|
||||
"label": "Tags"
|
||||
},
|
||||
"name": {
|
||||
"label": "Name"
|
||||
},
|
||||
"creationDate": {
|
||||
"label": "Creation date"
|
||||
},
|
||||
"musicbrainzId": {
|
||||
"label": "MusicBrainz ID"
|
||||
},
|
||||
"visibility": {
|
||||
"label": "Visibility"
|
||||
},
|
||||
"artist": {
|
||||
"label": "Artist"
|
||||
},
|
||||
"album": {
|
||||
"label": "Album",
|
||||
"title": "Title",
|
||||
"releaseDate": "Release date"
|
||||
},
|
||||
"track": {
|
||||
"label": "Track",
|
||||
"title": "Title",
|
||||
"position": "Position",
|
||||
"copyright": "Copyright",
|
||||
"license": "Licence"
|
||||
},
|
||||
"library": {
|
||||
"label": "Library",
|
||||
"description": "Description"
|
||||
},
|
||||
"playlist": {
|
||||
"label": "Playlist"
|
||||
},
|
||||
"account": {
|
||||
"label": "Account",
|
||||
"summary": "Bio"
|
||||
},
|
||||
"channel": {
|
||||
"label": "Channel"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"embed": {
|
||||
"EmbedFrame": {
|
||||
"badResource": "idget improperly configured (bad resource type {type}).",
|
||||
|
@ -1670,6 +1942,25 @@
|
|||
"unknownTrackError": "An unknown error occurred while loading track data."
|
||||
}
|
||||
},
|
||||
"init": {
|
||||
"axios": {
|
||||
"rateLimitDelay": "You sent too many requests and have been rate limited, please try again in {delay}",
|
||||
"rateLimitLater": "You sent too many requests and have been rate limited, please try again later"
|
||||
},
|
||||
"sentry": {
|
||||
"funkwhaleGlitchtipMessage": "To enhance the quality of our services, we would like to collect information about crashes during your session.<br><sub>The stack traces will be shared to <a href=\"{origin}\">Funkwhale's official Glitchtip instance</a> in order to help us understand how and when the errors occur.</sub>",
|
||||
"ownGlitchtipMessage": "To enhance the quality of our services, we would like to collect information about crashes during your session.<br><sub>The stack traces will be shared to <a href=\"{origin}\">{hostname}</a> in order to help us understand how and when the errors occur.</sub>",
|
||||
"allow": "Allow",
|
||||
"deny": "Deny"
|
||||
},
|
||||
"serviceWorker": {
|
||||
"newAppVersion": "A new version of the app is available.",
|
||||
"actions": {
|
||||
"update": "Update",
|
||||
"later": "Later"
|
||||
}
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"Notifications": {
|
||||
"title": "Notifications",
|
||||
|
@ -1731,14 +2022,14 @@
|
|||
"descriptionLabel": "Description",
|
||||
"urlLabel": "URL",
|
||||
"rssLabel": "RSS feed",
|
||||
"activityHeader": "Activity ",
|
||||
"activityHeader": "Activity",
|
||||
"firstSeenLabel": "First seen",
|
||||
"listeningsLabel": "Listenings",
|
||||
"favoritedLabel": "Favorited tracks",
|
||||
"playlistsLabel": "Playlists",
|
||||
"linkedReportsLabel": "Linked reports",
|
||||
"editsLabel": "Edits",
|
||||
"audioContentHeader": "Audio content ",
|
||||
"audioContentHeader": "Audio content;",
|
||||
"cachedSizeLabel": "Cached size",
|
||||
"totalSizeLabel": "Total size",
|
||||
"uploadsLabel": "Uploads",
|
||||
|
@ -1790,14 +2081,14 @@
|
|||
"artistLabel": "Artist",
|
||||
"domainLabel": "Domain",
|
||||
"descriptionLabel": "Description",
|
||||
"activityHeader": "Activity ",
|
||||
"activityHeader": "Activity;",
|
||||
"firstSeenLabel": "First seen",
|
||||
"listeningsLabel": "Listenings",
|
||||
"favoritedLabel": "Favorited tracks",
|
||||
"playlistsLabel": "Playlists",
|
||||
"linkedReportsLabel": "Linked reports",
|
||||
"editsLabel": "Edits",
|
||||
"audioContentHeader": "Audio content ",
|
||||
"audioContentHeader": "Audio content",
|
||||
"cachedSizeLabel": "Cached size",
|
||||
"totalSizeLabel": "Total size",
|
||||
"librariesLabel": "Libraries",
|
||||
|
@ -1821,14 +2112,14 @@
|
|||
"categoryLabel": "Category",
|
||||
"domainLabel": "Domain",
|
||||
"descriptionLabel": "Description",
|
||||
"activityHeader": "Activity ",
|
||||
"activityHeader": "Activity",
|
||||
"firstSeenLabel": "First seen",
|
||||
"listeningsLabel": "Listenings",
|
||||
"favoritedLabel": "Favorited tracks",
|
||||
"playlistsLabel": "Playlists",
|
||||
"linkedReportsLabel": "Linked reports",
|
||||
"editsLabel": "Edits",
|
||||
"audioContentHeader": "Audio content ",
|
||||
"audioContentHeader": "Audio content",
|
||||
"cachedSizeLabel": "Cached size",
|
||||
"totalSizeLabel": "Total size",
|
||||
"librariesLabel": "Libraries",
|
||||
|
@ -1866,11 +2157,11 @@
|
|||
"accountLabel": "Account",
|
||||
"domainLabel": "Domain",
|
||||
"descriptionLabel": "Description",
|
||||
"activityHeader": "Activity ",
|
||||
"activityHeader": "Activity",
|
||||
"firstSeenLabel": "First seen",
|
||||
"followersLabel": "Followers",
|
||||
"linkedReportsLabel": "Linked reports",
|
||||
"audioContentHeader": "Audio content ",
|
||||
"audioContentHeader": "Audio content",
|
||||
"cachedSizeLabel": "Cached size",
|
||||
"totalSizeLabel": "Total size",
|
||||
"artistsLabel": "Artists",
|
||||
|
@ -1886,9 +2177,9 @@
|
|||
"deleteModalMessage": "The tag will be removed and unlinked from any existing entity. This action is irreversible.",
|
||||
"tagDataHeader": "Tag data",
|
||||
"nameLabel": "Name",
|
||||
"activityHeader": "Activity ",
|
||||
"activityHeader": "Activity",
|
||||
"firstSeenLabel": "First seen",
|
||||
"audioContentHeader": "Audio content ",
|
||||
"audioContentHeader": "Audio content",
|
||||
"artistsLabel": "Artists",
|
||||
"albumsLabel": "Albums",
|
||||
"tracksLabel": "Tracks"
|
||||
|
@ -1916,14 +2207,13 @@
|
|||
"licenseLabel": "License",
|
||||
"domainLabel": "Domain",
|
||||
"descriptionLabel": "Description",
|
||||
"activityHeader": "Activity ",
|
||||
"activityHeader": "Activity",
|
||||
"firstSeenLabel": "First seen",
|
||||
"listeningsLabel": "Listenings",
|
||||
"favoritedLabel": "Favorited tracks",
|
||||
"playlistsLabel": "Playlists",
|
||||
"linkedReportsLabel": "Linked reports",
|
||||
"editsLabel": "Edits",
|
||||
"audioContentHeader": "Audio content ",
|
||||
"cachedSizeLabel": "Cached size",
|
||||
"totalSizeLabel": "Total size",
|
||||
"librariesLabel": "Libraries",
|
||||
|
@ -1944,11 +2234,11 @@
|
|||
"domainLabel": "Domain",
|
||||
"importStatusLabel": "Import status",
|
||||
"libraryLabel": "Library",
|
||||
"activityHeader": "Activity ",
|
||||
"activityHeader": "Activity",
|
||||
"firstSeenLabel": "First seen",
|
||||
"accessedDateLabel": "Accessed date",
|
||||
"notApplicable": "N/A",
|
||||
"audioContentHeader": "Audio content ",
|
||||
"audioContentHeader": "Audio content",
|
||||
"trackLabel": "Track",
|
||||
"cachedSizeLabel": "Cached size",
|
||||
"sizeLabel": "Size",
|
||||
|
@ -1957,6 +2247,504 @@
|
|||
"durationLabel": "Duration",
|
||||
"typeLabel": "Type"
|
||||
}
|
||||
},
|
||||
"moderation": {
|
||||
"AccountsDetail": {
|
||||
"statsWarning": "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object",
|
||||
"uploadQuota": "Determine how much content the user can upload. Leave empty to use the default value of the instance.",
|
||||
"libraryPermission": "Library",
|
||||
"moderationPermission": "Moderation",
|
||||
"settingsPermission": "Settings",
|
||||
"localAccountLabel": "Local account",
|
||||
"openProfileLink": "Open profile",
|
||||
"djangoLink": "View in Django's admin",
|
||||
"remoteProfileLink": "Open remote profile",
|
||||
"noPolicyHeader": "You don't have any rule in place for this account.",
|
||||
"policyDescription": "Moderation policies help you control how your instance interact with a given domain or account",
|
||||
"addPolicyButton": "Add a moderation policy",
|
||||
"activePolicyHeader": "This domain is subject to specific moderation rules",
|
||||
"accountDataHeader": "Account data",
|
||||
"usernameLabel": "Username",
|
||||
"domainLabel": "Domain",
|
||||
"displayNameLabel": "Display name",
|
||||
"emailLabel": "Email address",
|
||||
"loginStatusLabel": "Login status",
|
||||
"enabledStatus": "Enabled",
|
||||
"disabledStatus": "Disabled",
|
||||
"permissionsLabel": "Permissions",
|
||||
"userTypeLabel": "Type",
|
||||
"lastCheckedLabel": "Last checked",
|
||||
"notApplicable": "N/A",
|
||||
"signupDateLabel": "Sign-up date",
|
||||
"lastActivityLabel": "Last activity",
|
||||
"activityHeader": "Activty",
|
||||
"firstSeenLabel": "First seen",
|
||||
"emittedMessagesLabel": "Emitted messages",
|
||||
"receivedFollowsLabel": "Received library follows",
|
||||
"emittedFollowsLabel": "Emitted library follows",
|
||||
"linkedReportsLabel": "Linked reports",
|
||||
"requestsLabel": "Requests",
|
||||
"audioContentHeader": "Audio content",
|
||||
"cachedSizeLabel": "Cached size",
|
||||
"uploadQuotaLabel": "Upload quota",
|
||||
"megabyteLabel": "MB",
|
||||
"totalSizeLabel": "Total size",
|
||||
"channelsLabel": "Channels",
|
||||
"librariesLabel": "Libraries",
|
||||
"uploadsLabel": "Uploads",
|
||||
"artistsLabel": "Artists",
|
||||
"albumsLabel": "Albums",
|
||||
"tracksLabel": "Tracks"
|
||||
},
|
||||
"Base": {
|
||||
"moderation": "Moderation",
|
||||
"secondaryMenu": "Secondary menu",
|
||||
"reportsLink": "Reports",
|
||||
"userRequestsLink": "User Requests",
|
||||
"domainsLink": "Domains",
|
||||
"accountsLink": "Accounts"
|
||||
},
|
||||
"DomainsDetail": {
|
||||
"statsWarning": "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object",
|
||||
"websiteLink": "Open website",
|
||||
"djangoLink": "View in Django's admin",
|
||||
"removeFromAllowList": "Remove from allow-list",
|
||||
"addToAllowList": "Add to allow-list",
|
||||
"noPolicyHeader": "You don't have any rule in place for this domain.",
|
||||
"policyDescription": "Moderation policies help you control how your instance interact with a given domain or account",
|
||||
"addPolicyButton": "Add a moderation policy",
|
||||
"activePolicyHeader": "This domain is subject to specific moderation rules",
|
||||
"instanceDataHeader": "Instance data",
|
||||
"inAllowListLabel": "Is present on allow-list",
|
||||
"inAllowListTrue": "Yes",
|
||||
"inAllowListFalse": "No",
|
||||
"lastCheckedLabel": "Last checked",
|
||||
"notApplicable": "N/A",
|
||||
"softwareLabel": "Software",
|
||||
"softwareValue": "{name} ({version})",
|
||||
"domainNameLabel": "Name",
|
||||
"totalUsersLabel": "Total users",
|
||||
"nodeInfoStatusLabel": "Status",
|
||||
"nodeInfoFailureMessage": "Error while fetching node info",
|
||||
"refreshNodeInfoButton": "Refresh node info",
|
||||
"activityHeader": "Activty",
|
||||
"firstSeenLabel": "First seen",
|
||||
"knownAccountsLink": "Known accounts",
|
||||
"emittedMessagesLabel": "Emitted messages",
|
||||
"receivedFollowsLabel": "Received library follows",
|
||||
"emittedFollowsLabel": "Emitted library follows",
|
||||
"audioContentHeader": "Audio content",
|
||||
"cachedSizeLabel": "Cached size",
|
||||
"totalSizeLabel": "Total size",
|
||||
"channelsLabel": "Channels",
|
||||
"librariesLabel": "Libraries",
|
||||
"uploadsLabel": "Uploads",
|
||||
"artistsLabel": "Artists",
|
||||
"albumsLabel": "Albums",
|
||||
"tracksLabel": "Tracks"
|
||||
},
|
||||
"DomainsList": {
|
||||
"title": "Domains",
|
||||
"failureHeader": "Error while creating domain",
|
||||
"addDomainLabel": "Add a domain",
|
||||
"addToAllowListLabel": "Add to allow-list",
|
||||
"addButton": "Add"
|
||||
},
|
||||
"ReportsList": {
|
||||
"title": "Reports",
|
||||
"searchPlaceholder": "Search by account, summary, domain…",
|
||||
"searchLabel": "Search",
|
||||
"statusLabel": "Status",
|
||||
"allOption": "All",
|
||||
"resolvedStatus": "Resolved",
|
||||
"unresolvedStatus": "Unresolved",
|
||||
"orderingLabel": "Ordering",
|
||||
"orderingDirectionLabel": "Order",
|
||||
"ascendingOrdering": "Ascending",
|
||||
"descendingOrdering": "Descending"
|
||||
},
|
||||
"RequestsList": {
|
||||
"title": "User Requests",
|
||||
"searchPlaceholder": "Search by username",
|
||||
"searchLabel": "Search",
|
||||
"statusLabel": "Status",
|
||||
"allOption": "All",
|
||||
"pendingStatus": "Pending",
|
||||
"approvedStatus": "Approved",
|
||||
"refusedStatus": "Refused",
|
||||
"orderingLabel": "Ordering",
|
||||
"orderingDirectionLabel": "Order",
|
||||
"ascendingOrdering": "Ascending",
|
||||
"descendingOrdering": "Descending"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"Base": {
|
||||
"title": "Manage users",
|
||||
"secondaryMenu": "Secondary menu",
|
||||
"usersLink": "Users",
|
||||
"invitationsLink": "Invitations"
|
||||
}
|
||||
}
|
||||
},
|
||||
"auth": {
|
||||
"Callback": {
|
||||
"loggingInHeader": "Logging in…"
|
||||
},
|
||||
"EmailConfirm": {
|
||||
"confirm": "Confirm your e-mail address",
|
||||
"confirmFailureHeader": "Could not confirm your e-mail address",
|
||||
"confirmationCodeLabel": "Confirmation code",
|
||||
"backToLoginLink": "Return to login",
|
||||
"confirmSuccessHeader": "E-mail address confirmed",
|
||||
"confirmSuccessMessage": "You can now use the service without limitations",
|
||||
"goToLoginLink": "Proceed to login"
|
||||
},
|
||||
"Login": {
|
||||
"title": "Log in",
|
||||
"loginHeader": "Log in to your Funkwhale account"
|
||||
},
|
||||
"PasswordReset": {
|
||||
"placeholder": "Enter the e-mail address linked to your account",
|
||||
"title": "Reset your password",
|
||||
"resetFailureHeader": "Error while asking for a password reset",
|
||||
"resetFormDescription": "Use this form to request a password reset. We will send an e-mail to the given address with instructions to reset your password.",
|
||||
"emailLabel": "Account's e-mail address",
|
||||
"backToLoginLink": "Back to login",
|
||||
"requestResetButton": "Ask for a password reset"
|
||||
},
|
||||
"PasswordResetConfirm": {
|
||||
"title": "Change your password",
|
||||
"changeFailureHeader": "Error while changing your password",
|
||||
"newPasswordLabel": "New password",
|
||||
"backToLoginLink": "Back to login",
|
||||
"updatePasswordButton": "Update your password",
|
||||
"requestSentMessage": "If the e-mail address provided in the previous step is valid and linked to a user account, you should receive an e-mail with reset instructions in the next couple of minutes.",
|
||||
"changeSuccessHeader": "Password updated successfully",
|
||||
"changeSuccessMessage": "Your password has been updated successfully.",
|
||||
"goToLoginLink": "Proceed to login"
|
||||
},
|
||||
"Plugins": {
|
||||
"title": "Manage plugins"
|
||||
},
|
||||
"ProfileActivity": {
|
||||
"recentlyListened": "Recently listened",
|
||||
"recentlyFavorited": "Recently favorited",
|
||||
"playlistsHeader": "Playlists"
|
||||
},
|
||||
"ProfileBase": {
|
||||
"title": "{username}'s profile",
|
||||
"domainViewLink": "View on {domain}",
|
||||
"moderationLink": "Open in moderation interface",
|
||||
"ownUserLabel": "This is you!",
|
||||
"overviewLink": "Overview",
|
||||
"activityLink": "Activity"
|
||||
},
|
||||
"ProfileOverview": {
|
||||
"channelsHeader": "Channels",
|
||||
"addNewLink": "Add New",
|
||||
"librariesHeader": "User Libraries",
|
||||
"sharedLibraries": "This user shared the following libraries",
|
||||
"createChannelModalHeader": "Create channel",
|
||||
"podcastChannelHeader": "Podcast channel",
|
||||
"artistChannelHeader": "Artist channel",
|
||||
"cancelButton": "Cancel",
|
||||
"previousButton": "Previous step",
|
||||
"nextButton": "Next step",
|
||||
"createChannelButton": "Create channel"
|
||||
},
|
||||
"Signup": {
|
||||
"title": "Sign up",
|
||||
"createAccountHeader": "Create a Funkwhale account"
|
||||
}
|
||||
},
|
||||
"channels": {
|
||||
"DetailBase": {
|
||||
"title": "Channel",
|
||||
"episodeCount": "No episodes | {count} episode | {count} episodes",
|
||||
"trackCount": "No tracks | {count} track | {count} tracks",
|
||||
"subscriberCount": "No subscribers | {count} subscriber | {count} subscribers",
|
||||
"listeningsCount": "No listenings | {count} listening | {count} listenings",
|
||||
"subscribeModalHeader": "Subscribe to this channel",
|
||||
"subscribeOnFunkwhale": "Subscribe on Funkwhale",
|
||||
"subscribeRss": "Subscribe via RSS",
|
||||
"copyUrl": "Copy paste the following URL in your favorite podcatcher:",
|
||||
"subscribeOnFediverse": "Subscribe on the Fediverse",
|
||||
"subscribeOnFediverseDescription": "If you're using Mastodon or other fediverse applications, you can subscribe to this account:",
|
||||
"cancelButton": "Cancel",
|
||||
"embedButton": "Embed",
|
||||
"domainViewLink": "View on {domain}",
|
||||
"editButton": "Edit…",
|
||||
"deleteButton": "Delete…",
|
||||
"deleteModalHeader": "Delete this Channel?",
|
||||
"deleteModalMessage": "The channel will be deleted, as well as any related files and data. This action is irreversible.",
|
||||
"deleteModalConfirm": "Delete",
|
||||
"moderationLink": "Open in moderation interface",
|
||||
"mirroredLink": "Mirrored from {domain}",
|
||||
"uploadButton": "Upload",
|
||||
"playButton": "Play",
|
||||
"embedModalHeader": "Embed this artist work on your website",
|
||||
"podcastChannelHeader": "Podcast channel",
|
||||
"artistChannelHeader": "Artist channel",
|
||||
"updateChannelButton": "Update channel",
|
||||
"channelOverview": "Overview",
|
||||
"channelEpisodes": "All episodes",
|
||||
"channelTracks": "Tracks"
|
||||
},
|
||||
"DetailOverview": {
|
||||
"uploadsSuccessHeader": "Uploads published successfully",
|
||||
"uploadsProgress": "Processed uploads: {finished}/{total}",
|
||||
"uploadsFailureHeader": "Some uploads couldn't be published",
|
||||
"skippedUploadsLink": "View skipped uploads",
|
||||
"erroredUploadsLink": "View errored uploads",
|
||||
"uploadsProcessingHeader": "Uploads are being processed",
|
||||
"uploadsProcessingMessage": "Your uploads are being processed by Funkwhale and will be live very soon.",
|
||||
"latestEpisodes": "Latest episodes",
|
||||
"latestTracks": "Latest tracks",
|
||||
"seriesHeader": "Series",
|
||||
"albumsHeader": "Albums",
|
||||
"addAlbumLink": "Add new"
|
||||
},
|
||||
"SubscriptionsList": {
|
||||
"title": "Subscribed Channels",
|
||||
"searchPlaceholder": "Filter by name…",
|
||||
"addNewLink": "Add new",
|
||||
"subscriptionModalHeader": "Subscription",
|
||||
"cancelButton": "Cancel",
|
||||
"subscribeButton": "Subscribe"
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"Base": {
|
||||
"title": "Add content",
|
||||
"secondaryMenu": "Secondary menu",
|
||||
"librariesLink": "Libraries",
|
||||
"tracksLink": "Tracks"
|
||||
},
|
||||
"Home": {
|
||||
"title": "Add and manage content",
|
||||
"uploadQuota": "This instance offers up to {quota} of storage space for every user.",
|
||||
"channelHeader": "Publish your work in a channel",
|
||||
"channelDescription": "If you are a musician or a podcaster, channels are designed for you!",
|
||||
"channelDescriptionContinued": "Share your work publicly and get subscribers on Funkwhale, the Fediverse or any podcasting application.",
|
||||
"getStartedButton": "Get started",
|
||||
"libraryUploadHeader": "Upload third-party content in a library",
|
||||
"libraryUploadDescription": "Upload your personal music library to Funkwhale to enjoy it from anywhere and share it with friends and family.",
|
||||
"followLibrariesHeader": "Follow remote libraries",
|
||||
"followLibrariesDescription": "Follow libraries from other users to get access to new music. Public libraries can be followed immediately, while following a private library requires approval from its owner."
|
||||
},
|
||||
"libraries": {
|
||||
"Card": {
|
||||
"sizeLabel": "Total size of the files in this library",
|
||||
"trackCount":"No tracks | {count} track | {count} tracks",
|
||||
"uploadButton": "Upload",
|
||||
"detailsLink": "Library Details"
|
||||
},
|
||||
"FilesTable": {
|
||||
"deleteLabel": "Delete",
|
||||
"restartImportLabel": "Restart import",
|
||||
"searchPlaceholder": "Search by domain, title, artist, album…",
|
||||
"showStatus": "Show information about the upload status for this track",
|
||||
"searchLabel": "Search",
|
||||
"importStatusLabel": "Import status",
|
||||
"allOption": "All",
|
||||
"draftStatus": "Draft",
|
||||
"pendingStatus": "Pending",
|
||||
"skippedStatus": "Skipped",
|
||||
"failedStatus": "Failed",
|
||||
"finishedStatus": "Finished",
|
||||
"orderingLabel": "Ordering",
|
||||
"orderingDirectionLabel": "Ordering direction",
|
||||
"ascendingOrdering": "Ascending",
|
||||
"descendingOrdering": "Descending",
|
||||
"emptyState": "No tracks have been added to this libray yet",
|
||||
"titleTableHeader": "Title",
|
||||
"artistTableHeader": "Artist",
|
||||
"albumTableHeader": "Album",
|
||||
"uploadDateTableHeader": "Upload date",
|
||||
"importStatusTableHeader": "Import status",
|
||||
"durationTableHeader": "Duration",
|
||||
"sizeTableHeader": "Size",
|
||||
"notApplicable": "N/A",
|
||||
"resultsDisplay":"Showing results {start}-{end} on {total}"
|
||||
},
|
||||
"Form": {
|
||||
"descriptionPlaceholder": "This library contains my personal music, I hope you like it.",
|
||||
"namePlaceholder": "My awesome library",
|
||||
"libraryUpdateMessage": "Library updated",
|
||||
"libraryCreateMessage": "Library created",
|
||||
"libraryDeleteMessage": "LIbrary deleted",
|
||||
"libraryHelp": "Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family.",
|
||||
"failureHeader": "Error",
|
||||
"nameLabel": "Name",
|
||||
"descriptionLabel" :"Description",
|
||||
"visibilityLabel": "Visibility",
|
||||
"visibilityDescription": "You are able to share your library with other people, regardless of its visibilty.",
|
||||
"updateButton": "Update library",
|
||||
"createButton": "Create library",
|
||||
"deleteButton": "Delete",
|
||||
"deleteModalHeader": "Delete this library?",
|
||||
"deleteModalMessage": "The library and all its tracks will be deleted. This can not be undone.",
|
||||
"deleteModalConfirm": "Delete library"
|
||||
},
|
||||
"Home": {
|
||||
"loadingLibraries": "Loading libraries…",
|
||||
"ownLibrariesHeader": "My libraries",
|
||||
"emptyState": "Looks like you don't have a library, it's time to create one.",
|
||||
"createLibraryLink": "Create a new library"
|
||||
},
|
||||
"Quota": {
|
||||
"currentUsageHeader": "Current usage",
|
||||
"loadingMessage": "Loading usage data…",
|
||||
"percentUsed": "{progress}%",
|
||||
"currentUsage": "{amount} used on {max} allowed",
|
||||
"pendingLabel": "Pending files",
|
||||
"viewFilesLink": "View files",
|
||||
"purgeButton": "Purge",
|
||||
"purgePendingModalHeader": "Purge pending files?",
|
||||
"purgePendingModalMessage": "Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota.",
|
||||
"skippedLabel": "Skipped files",
|
||||
"purgeSkippedModalHeader": "Purge skipped files?",
|
||||
"purgeSkippedModalMessage": "Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota.",
|
||||
"erroredLabel": "Errored files",
|
||||
"purgeErroredModalHeader": "Purge errored files?",
|
||||
"purgeErroredModalMessage": "Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota."
|
||||
|
||||
}
|
||||
},
|
||||
"remote": {
|
||||
"Card": {
|
||||
"privateTooltip": "This library is private and your approval from its owner is needed to access its content",
|
||||
"publicTooltip": "This library is public and you can access its content freely",
|
||||
"scanSkipped": "Scan skipped (previous scan is too recent)",
|
||||
"scanLaunched": "Scan launched",
|
||||
"followError": "Cannot follow remote library: {error}",
|
||||
"unfollowError": "Cannot unfollow remote library: {error}",
|
||||
"trackCount": "No tracks | {count} track | {count} tracks",
|
||||
"scanPending": "Scan pending",
|
||||
"scanProgress": "Scanning ({progress})",
|
||||
"scanFailure": "Problem during scanning",
|
||||
"scanSuccess": "Scanned",
|
||||
"scanPartialSuccess": "Scanned with errors",
|
||||
"scanDetails": "Details",
|
||||
"lastUpdate": "Last update: ",
|
||||
"failedTracks": "Failed tracks: {tracks}",
|
||||
"scanNowButton": "Scan now ",
|
||||
"sharingLinkLabel": "Sharing link",
|
||||
"followButton": "Follow",
|
||||
"pendingApprovalButton": "Follow request pending approval",
|
||||
"cancelFollowButton": "Cancel follow request",
|
||||
"unfollowButton": "Unfollow",
|
||||
"unfollowModalHeader": "Unfollow this libary?",
|
||||
"unfollowModalMessage": "By unfollowing this library, you loose access to its content."
|
||||
},
|
||||
"Home": {
|
||||
"loadingMessage": "Loading remote libraries…",
|
||||
"remoteLibrariesHeader": "Remote libraries",
|
||||
"remoteLibrariesDescription": "Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access.",
|
||||
"knownLibrariesHeader": "Known libraries",
|
||||
"refreshButton": "Refresh"
|
||||
},
|
||||
"ScanForm": {
|
||||
"placeholder": "Enter a library URL",
|
||||
"submitLibrarySearch": "Submit search",
|
||||
"failureHeader": "Could not fetch remote library",
|
||||
"searchLabel": "Search a remote library"
|
||||
}
|
||||
}
|
||||
},
|
||||
"library": {
|
||||
"DetailAlbums": {
|
||||
"ownerEmptyState": "This library is empty, you should upload something in it!",
|
||||
"viewerEmptyState": "You may need to follow this library to see its content."
|
||||
},
|
||||
"DetailOverview": {
|
||||
"ownerEmptyState": "This library is empty, you should upload something in it!",
|
||||
"viewerEmptyState": "You may need to follow this library to see its content."
|
||||
},
|
||||
"DetailTracks": {
|
||||
"ownerEmptyState": "This library is empty, you should upload something in it!",
|
||||
"viewerEmptyState": "You may need to follow this library to see its content."
|
||||
},
|
||||
"Edit": {
|
||||
"libraryContentsHeader": "Library contents",
|
||||
"followersHeader": "Followers",
|
||||
"loadingFollowers": "Loading followers…",
|
||||
"userTableHeader": "User",
|
||||
"dateTableHeader": "Date",
|
||||
"statusTableHeader": "Status",
|
||||
"actionTableHeader": "Action",
|
||||
"pendingStatus": "Pending approval",
|
||||
"acceptedStatus": "Accepted",
|
||||
"rejectedStatus": "Rejected",
|
||||
"acceptButton": "Accept",
|
||||
"rejectButton": "Reject",
|
||||
"noFollowers": "Nobody is following this library"
|
||||
},
|
||||
"LibraryBase": {
|
||||
"title": "Library",
|
||||
"privateVisibility": "Private",
|
||||
"instanceVisibility": "Restricted",
|
||||
"publicVisibility": "Public",
|
||||
"privateTooltip": "This library is private and your approval from its owner is needed to access its content",
|
||||
"instanceTooltip": "This library is restricted to users on this pod only",
|
||||
"publicTooltip": "This library is public and you can access its content freely",
|
||||
"domainViewLink": "View on {domain}",
|
||||
"moderationLink": "Open in moderation interface",
|
||||
"ownerLink": "Owned by {username}",
|
||||
"trackCount": "No tracks | {count} track | {count} tracks",
|
||||
"sharingLinkLabel": "Sharing link",
|
||||
"sharingLinkDescription": "Share this link with other users so they can request access to this library by copy-pasting it in their pod search bar.",
|
||||
"artistsLink": "Artists",
|
||||
"albumsLink": "Albums",
|
||||
"tracksLink": "Tracks",
|
||||
"uploadButton": "Upload",
|
||||
"editButton": "Edit"
|
||||
}
|
||||
},
|
||||
"playlists": {
|
||||
"Detail": {
|
||||
"title": "Playlist",
|
||||
"trackCount": "Playlist containing {count} track, by {username} | Playlist containing {count} tracks, by {username}",
|
||||
"playAllButton": "Play all",
|
||||
"stopEditButton": "Stop Editing",
|
||||
"editButton": "Edit",
|
||||
"embedButton": "Embed",
|
||||
"deleteButton": "Delete",
|
||||
"deleteModalHeader": "Do you want to delete the playlist {playlist}?",
|
||||
"deleteModalMessage": "This will completely delete this playlist and cannot be undone.",
|
||||
"deleteModalConfirm": "Delete playlist",
|
||||
"embedModalHeader": "Embed this playlist on your website",
|
||||
"cancelButton": "Cancel",
|
||||
"tracksHeader": "Tracks",
|
||||
"emptyState": "There are no tracks in this playlist yet"
|
||||
},
|
||||
"List": {
|
||||
"playlistsHeader": "Playlists",
|
||||
"searchPlaceholder": "Enter playlist name…",
|
||||
"browsePlaylistsHeader": "Browsing playlists",
|
||||
"manageButton": "Manage your playlists",
|
||||
"searchLabel": "Search",
|
||||
"orderingLabel": "Ordering",
|
||||
"orderingDirectionLabel": "Order",
|
||||
"ascendingOrdering": "Ascending",
|
||||
"descendingOrdering": "Descending",
|
||||
"resultsPerPage": "Results per page",
|
||||
"emptyState": "No results matching your query",
|
||||
"createPlaylistButton": "Create a playlist"
|
||||
}
|
||||
},
|
||||
"radios": {
|
||||
"Detail": {
|
||||
"title": "Radio",
|
||||
"radioSubheader": "Radio containing {tracks} tracks, by ",
|
||||
"editButton": "Edit…",
|
||||
"deleteButton": "Delete",
|
||||
"deleteModalHeader": "Do you want to delete the radio {radio}?",
|
||||
"deleteModalMessage": "This will completely delete this radio and cannot be undone.",
|
||||
"deleteModalConfirm": "Delete radio",
|
||||
"tracksHeader": "Tracks",
|
||||
"emptyState": "No tracks have been hadded to this radio yet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -290,7 +290,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="feed icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.ChannelDetail.activityHeader') }}
|
||||
{{ $t('views.admin.ChannelDetail.activityHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
@ -369,7 +369,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="music icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.ChannelDetail.audioContentHeader') }}
|
||||
{{ $t('views.admin.ChannelDetail.audioContentHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
|
|
@ -277,7 +277,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="feed icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.AlbumDetail.activityHeader') }}
|
||||
{{ $t('views.admin.library.AlbumDetail.activityHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
@ -356,7 +356,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="music icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.AlbumDetail.audioContentHeader') }}
|
||||
{{ $t('views.admin.library.AlbumDetail.audioContentHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
|
|
@ -276,7 +276,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="feed icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.ArtistDetail.activityHeader') }}
|
||||
{{ $t('views.admin.library.ArtistDetail.activityHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
@ -355,7 +355,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="music icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.ArtistDetail.audioContentHeader') }}
|
||||
{{ $t('views.admin.library.ArtistDetail.audioContentHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
|
|
@ -278,7 +278,7 @@ const updateObj = async (attr: string) => {
|
|||
<h3 class="ui header">
|
||||
<i class="feed icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.LibraryDetail.activityHeader') }}
|
||||
{{ $t('views.admin.library.LibraryDetail.activityHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
@ -331,7 +331,7 @@ const updateObj = async (attr: string) => {
|
|||
<h3 class="ui header">
|
||||
<i class="music icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.LibraryDetail.audioContentHeader') }}
|
||||
{{ $t('views.admin.library.LibraryDetail.audioContentHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
|
|
@ -157,7 +157,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="feed icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.TagDetail.activityHeader') }}
|
||||
{{ $t('views.admin.library.TagDetail.activityHeader') }}
|
||||
</div>
|
||||
</h3>
|
||||
<table class="ui very basic table">
|
||||
|
@ -179,7 +179,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="music icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.TagDetail.audioContentHeader') }}
|
||||
{{ $t('views.admin.library.TagDetail.audioContentHeader') }}
|
||||
</div>
|
||||
</h3>
|
||||
<table class="ui very basic table">
|
||||
|
|
|
@ -329,7 +329,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="feed icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.TrackDetail.activityHeader') }}
|
||||
{{ $t('views.admin.library.TrackDetail.activityHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
@ -408,7 +408,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
<h3 class="ui header">
|
||||
<i class="music icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.TrackDetail.trackDataHeader') }}
|
||||
{{ $t('views.admin.library.TrackDetail.trackDataHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
|
|
@ -264,7 +264,7 @@ const showUploadDetailModal = ref(false)
|
|||
<h3 class="ui header">
|
||||
<i class="feed icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.UploadDetail.activityHeader') }}
|
||||
{{ $t('views.admin.library.UploadDetail.activityHeader') }}
|
||||
</div>
|
||||
</h3>
|
||||
<table class="ui very basic table">
|
||||
|
@ -286,11 +286,11 @@ const showUploadDetailModal = ref(false)
|
|||
v-if="object.accessed_date"
|
||||
:date="object.accessed_date"
|
||||
/>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('views.admin.library.UploadDetail.notApplicable') }}
|
||||
</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -302,7 +302,7 @@ const showUploadDetailModal = ref(false)
|
|||
<h3 class="ui header">
|
||||
<i class="music icon" />
|
||||
<div class="content">
|
||||
{{ $t('views.admin.library.UploadDetail.audioContentHeader') }}
|
||||
{{ $t('views.admin.library.UploadDetail.audioContentHeader') }}
|
||||
</div>
|
||||
</h3>
|
||||
<table class="ui very basic table">
|
||||
|
@ -325,11 +325,11 @@ const showUploadDetailModal = ref(false)
|
|||
<template v-if="object.audio_file">
|
||||
{{ humanSize(object.size) }}
|
||||
</template>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('views.admin.library.UploadDetail.notApplicable') }}
|
||||
</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -348,11 +348,11 @@ const showUploadDetailModal = ref(false)
|
|||
<template v-if="object.bitrate">
|
||||
{{ $t('views.admin.library.UploadDetail.bitrateValue', {bitrate: humanSize(object.bitrate)}) }}
|
||||
</template>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('views.admin.library.UploadDetail.notApplicable') }}
|
||||
</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -363,11 +363,11 @@ const showUploadDetailModal = ref(false)
|
|||
<template v-if="object.duration">
|
||||
{{ time.parse(object.duration) }}
|
||||
</template>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('views.admin.library.UploadDetail.notApplicable') }}
|
||||
</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -380,11 +380,11 @@ const showUploadDetailModal = ref(false)
|
|||
<template v-if="object.mimetype">
|
||||
{{ object.mimetype }}
|
||||
</template>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
{{ $t('views.admin.library.UploadDetail.notApplicable') }}
|
||||
</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -26,14 +26,14 @@ const { t } = useI18n()
|
|||
const logger = useLogger()
|
||||
|
||||
const labels = computed(() => ({
|
||||
statsWarning: t('Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'),
|
||||
uploadQuota: t('Determine how much content the user can upload. Leave empty to use the default value of the instance.')
|
||||
statsWarning: t('views.admin.moderation.AccountsDetail.statsWarning'),
|
||||
uploadQuota: t('views.admin.moderation.AccountsDetail.uploadQuota')
|
||||
}))
|
||||
|
||||
const allPermissions = computed(() => [
|
||||
{ code: 'library', label: t('Library') },
|
||||
{ code: 'moderation', label: t('Moderation') },
|
||||
{ code: 'settings', label: t('Settings') }
|
||||
{ code: 'library', label: t('views.admin.moderation.AccountsDetail.libraryPermission') },
|
||||
{ code: 'moderation', label: t('views.admin.moderation.AccountsDetail.moderationPermission') },
|
||||
{ code: 'settings', label: t('views.admin.moderation.AccountsDetail.settingsPermission') }
|
||||
])
|
||||
|
||||
const isLoadingPolicy = ref(false)
|
||||
|
@ -167,7 +167,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<template v-if="object.user">
|
||||
<span class="ui tiny accent label">
|
||||
<i class="home icon" />
|
||||
Local account
|
||||
{{ $t('views.admin.moderation.AccountsDetail.localAccountLabel') }}
|
||||
</span>
|
||||
|
||||
</template>
|
||||
|
@ -176,7 +176,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Open profile
|
||||
{{ $t('views.admin.moderation.AccountsDetail.openProfileLink') }}
|
||||
<i class="external icon" />
|
||||
</a>
|
||||
</div>
|
||||
|
@ -192,7 +192,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
rel="noopener noreferrer"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
View in Django's admin
|
||||
{{ $t('views.admin.moderation.AccountsDetail.djangoLink') }}
|
||||
</a>
|
||||
<a
|
||||
v-else-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
|
||||
|
@ -202,7 +202,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
rel="noopener noreferrer"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
View in Django's admin
|
||||
{{ $t('views.admin.moderation.AccountsDetail.djangoLink') }}
|
||||
</a>
|
||||
<button
|
||||
v-dropdown
|
||||
|
@ -217,7 +217,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
rel="noopener noreferrer"
|
||||
>
|
||||
<i class="external icon" />
|
||||
Open remote profile
|
||||
{{ $t('views.admin.moderation.AccountsDetail.remoteProfileLink') }}
|
||||
</a>
|
||||
</div>
|
||||
</button>
|
||||
|
@ -243,17 +243,17 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<header class="ui header">
|
||||
<h3>
|
||||
<i class="shield icon" />
|
||||
You don't have any rule in place for this account.
|
||||
{{ $t('views.admin.moderation.AccountsDetail.noPolicyHeader') }}
|
||||
</h3>
|
||||
</header>
|
||||
<p>
|
||||
Moderation policies help you control how your instance interact with a given domain or account.
|
||||
{{ $t('views.admin.moderation.AccountsDetail.policyDescription') }}
|
||||
</p>
|
||||
<button
|
||||
class="ui primary button"
|
||||
@click="showPolicyForm = true"
|
||||
>
|
||||
Add a moderation policy
|
||||
{{ $t('views.admin.moderation.AccountsDetail.addPolicyButton') }}
|
||||
</button>
|
||||
</template>
|
||||
<instance-policy-card
|
||||
|
@ -263,7 +263,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
>
|
||||
<header class="ui header">
|
||||
<h3>
|
||||
This domain is subject to specific moderation rules
|
||||
{{ $t('views.admin.moderation.AccountsDetail.activePolicyHeader') }}
|
||||
</h3>
|
||||
</header>
|
||||
</instance-policy-card>
|
||||
|
@ -287,14 +287,14 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<h3 class="ui header">
|
||||
<i class="info icon" />
|
||||
<div class="content">
|
||||
Account data
|
||||
{{ $t('views.admin.moderation.AccountsDetail.accountDataHeader') }}
|
||||
</div>
|
||||
</h3>
|
||||
<table class="ui very basic table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Username
|
||||
{{ $t('views.admin.moderation.AccountsDetail.usernameLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ object.preferred_username }}
|
||||
|
@ -303,7 +303,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<tr v-if="!object.user">
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}">
|
||||
Domain
|
||||
{{ $t('views.admin.moderation.AccountsDetail.domainLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -312,7 +312,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Display name
|
||||
{{ $t('views.admin.moderation.AccountsDetail.displayNameLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ object.name }}
|
||||
|
@ -320,7 +320,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr v-if="object.user">
|
||||
<td>
|
||||
Email address
|
||||
{{ $t('views.admin.moderation.AccountsDetail.emailLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ object.user.email }}
|
||||
|
@ -328,7 +328,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr v-if="object.user">
|
||||
<td>
|
||||
Login status
|
||||
{{ $t('views.admin.moderation.AccountsDetail.loginStatusLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
|
@ -342,29 +342,29 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
@change="updateUser('is_active')"
|
||||
>
|
||||
<label for="is-active">
|
||||
<translate
|
||||
<span
|
||||
v-if="object.user.is_active"
|
||||
>Enabled</translate>
|
||||
<translate
|
||||
>{{ $t('views.admin.moderation.AccountsDetail.enabledStatus') }}</span>
|
||||
<span
|
||||
v-else
|
||||
>Disabled</translate>
|
||||
>{{ $t('views.admin.moderation.AccountsDetail.disabledStatus') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<translate
|
||||
<span
|
||||
v-else-if="object.user.is_active"
|
||||
>
|
||||
Enabled
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.admin.moderation.AccountsDetail.enabledStatus') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
Disabled
|
||||
</translate>
|
||||
{{ $t('views.admin.moderation.AccountsDetail.disabledStatus') }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="object.user">
|
||||
<td>
|
||||
Permissions
|
||||
{{ $t('views.admin.moderation.AccountsDetail.permissionsLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
|
@ -386,7 +386,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Type
|
||||
{{ $t('views.admin.moderation.AccountsDetail.userTypeLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ object.type }}
|
||||
|
@ -394,23 +394,23 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr v-if="!object.user">
|
||||
<td>
|
||||
Last checked
|
||||
{{ $t('views.admin.moderation.AccountsDetail.lastCheckedLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
<human-date
|
||||
v-if="object.last_fetch_date"
|
||||
:date="object.last_fetch_date"
|
||||
/>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
N/A
|
||||
</translate>
|
||||
{{ $t('views.admin.moderation.AccountsDetail.notApplicable') }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="object.user">
|
||||
<td>
|
||||
Sign-up date
|
||||
{{ $t('views.admin.moderation.AccountsDetail.signupDateLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
<human-date :date="object.user.date_joined" />
|
||||
|
@ -418,7 +418,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr v-if="object.user">
|
||||
<td>
|
||||
Last activity
|
||||
{{ $t('views.admin.moderation.AccountsDetail.lastActivityLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
<human-date :date="object.user.last_activity" />
|
||||
|
@ -433,7 +433,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<h3 class="ui header">
|
||||
<i class="feed icon" />
|
||||
<div class="content">
|
||||
Activity
|
||||
{{ $t('views.admin.moderation.AccountsDetail.activityHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
@ -453,7 +453,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<tbody>
|
||||
<tr v-if="!object.user">
|
||||
<td>
|
||||
First seen
|
||||
{{ $t('views.admin.moderation.AccountsDetail.firstSeenLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
<human-date :date="object.creation_date" />
|
||||
|
@ -461,7 +461,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Emitted messages
|
||||
{{ $t('views.admin.moderation.AccountsDetail.emittedMessagesLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ stats.outbox_activities }}
|
||||
|
@ -469,7 +469,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Received library follows
|
||||
{{ $t('views.admin.moderation.AccountsDetail.receivedFollowsLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ stats.received_library_follows }}
|
||||
|
@ -477,7 +477,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Emitted library follows
|
||||
{{ $t('views.admin.moderation.AccountsDetail.emittedFollowsLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ stats.emitted_library_follows }}
|
||||
|
@ -486,7 +486,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `account:${object.full_username}`) }}">
|
||||
Linked reports
|
||||
{{ $t('views.admin.moderation.AccountsDetail.linkedReportsLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -496,7 +496,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.moderation.requests.list', query: {q: getQuery('submitter', `${object.full_username}`) }}">
|
||||
Requests
|
||||
{{ $t('views.admin.moderation.AccountsDetail.requestsLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -512,7 +512,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<h3 class="ui header">
|
||||
<i class="music icon" />
|
||||
<div class="content">
|
||||
Audio content
|
||||
{{ $t('views.admin.moderation.AccountsDetail.audioContentHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
@ -532,7 +532,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<tbody>
|
||||
<tr v-if="!object.user">
|
||||
<td>
|
||||
Cached size
|
||||
{{ $t('views.admin.moderation.AccountsDetail.cachedSizeLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ humanSize(stats.media_downloaded_size) }}
|
||||
|
@ -540,7 +540,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr v-if="object.user">
|
||||
<td>
|
||||
Upload quota
|
||||
{{ $t('views.admin.moderation.AccountsDetail.uploadQuotaLabel') }}
|
||||
<span :data-tooltip="labels.uploadQuota"><i class="question circle icon" /></span>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -553,7 +553,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
@change="updateUser('upload_quota', true)"
|
||||
>
|
||||
<div class="ui basic label">
|
||||
MB 
|
||||
{{ $t('views.admin.moderation.AccountsDetail.megabyteLabel') }}
|
||||
</div>
|
||||
<action-feedback
|
||||
class="ui basic label"
|
||||
|
@ -565,7 +565,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Total size
|
||||
{{ $t('views.admin.moderation.AccountsDetail.totalSizeLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ humanSize(stats.media_total_size) }}
|
||||
|
@ -574,7 +574,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.channels', query: {q: getQuery('account', object.full_username) }}">
|
||||
Channels
|
||||
{{ $t('views.admin.moderation.AccountsDetail.channelsLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -584,7 +584,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('account', object.full_username) }}">
|
||||
Libraries
|
||||
{{ $t('views.admin.moderation.AccountsDetail.librariesLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -594,7 +594,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('account', object.full_username) }}">
|
||||
Uploads
|
||||
{{ $t('views.admin.moderation.AccountsDetail.uploadsLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -603,7 +603,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Artists
|
||||
{{ $t('views.admin.moderation.AccountsDetail.artistsLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ stats.artists }}
|
||||
|
@ -611,7 +611,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Albums
|
||||
{{ $t('views.admin.moderation.AccountsDetail.albumsLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ stats.albums }}
|
||||
|
@ -619,7 +619,7 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Tracks
|
||||
{{ $t('views.admin.moderation.AccountsDetail.tracksLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ stats.tracks }}
|
||||
|
|
|
@ -9,8 +9,8 @@ const { t } = useI18n()
|
|||
|
||||
const allowListEnabled = ref(false)
|
||||
const labels = computed(() => ({
|
||||
moderation: t('Moderation'),
|
||||
secondaryMenu: t('Secondary menu')
|
||||
moderation: t('views.admin.moderation.Base.moderation'),
|
||||
secondaryMenu: t('views.admin.moderation.Base.secondaryMenu')
|
||||
}))
|
||||
|
||||
const fetchNodeInfo = async () => {
|
||||
|
@ -35,7 +35,7 @@ fetchNodeInfo()
|
|||
class="ui item"
|
||||
:to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}"
|
||||
>
|
||||
Reports
|
||||
{{ $t('views.admin.moderation.Base.reportsLink') }}
|
||||
<div
|
||||
v-if="$store.state.ui.notifications.pendingReviewReports > 0"
|
||||
:class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
|
||||
|
@ -47,7 +47,7 @@ fetchNodeInfo()
|
|||
class="ui item"
|
||||
:to="{name: 'manage.moderation.requests.list', query: {q: 'status:pending'}}"
|
||||
>
|
||||
User Requests
|
||||
{{ $t('views.admin.moderation.Base.userRequestsLink') }}
|
||||
<div
|
||||
v-if="$store.state.ui.notifications.pendingReviewRequests > 0"
|
||||
:class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
|
||||
|
@ -59,13 +59,13 @@ fetchNodeInfo()
|
|||
class="ui item"
|
||||
:to="{name: 'manage.moderation.domains.list'}"
|
||||
>
|
||||
Domains
|
||||
{{ $t('views.admin.moderation.Base.domainsLink') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="ui item"
|
||||
:to="{name: 'manage.moderation.accounts.list'}"
|
||||
>
|
||||
Accounts
|
||||
{{ $t('views.admin.moderation.Base.accountsLink') }}
|
||||
</router-link>
|
||||
</nav>
|
||||
<router-view
|
||||
|
|
|
@ -23,7 +23,7 @@ const props = defineProps<Props>()
|
|||
const { t } = useI18n()
|
||||
|
||||
const labels = computed(() => ({
|
||||
statsWarning: t('Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
|
||||
statsWarning: t('views.admin.moderation.DomainsDetail.statsWarning')
|
||||
}))
|
||||
|
||||
const isLoadingPolicy = ref(false)
|
||||
|
@ -133,7 +133,7 @@ const setAllowList = async (value: boolean) => {
|
|||
rel="noopener noreferrer"
|
||||
class="logo-wrapper"
|
||||
>
|
||||
Open website
|
||||
{{ $t('views.admin.moderation.DomainsDetail.websiteLink') }}
|
||||
<i class="external icon" />
|
||||
</a>
|
||||
</div>
|
||||
|
@ -149,7 +149,7 @@ const setAllowList = async (value: boolean) => {
|
|||
rel="noopener noreferrer"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
View in Django's admin
|
||||
{{ $t('views.admin.moderation.DomainsDetail.djangoLink') }}
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
|
@ -162,7 +162,7 @@ const setAllowList = async (value: boolean) => {
|
|||
@click.prevent="setAllowList(false)"
|
||||
>
|
||||
<i class="x icon" />
|
||||
Remove from allow-list
|
||||
{{ $t('views.admin.moderation.DomainsDetail.removeFromAllowList') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
|
@ -170,7 +170,7 @@ const setAllowList = async (value: boolean) => {
|
|||
@click.prevent="setAllowList(true)"
|
||||
>
|
||||
<i class="check icon" />
|
||||
Add to allow-list
|
||||
{{ $t('views.admin.moderation.DomainsDetail.addToAllowList') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -191,17 +191,17 @@ const setAllowList = async (value: boolean) => {
|
|||
<header class="ui header">
|
||||
<h3>
|
||||
<i class="shield icon" />
|
||||
You don't have any rule in place for this domain.
|
||||
{{ $t('views.admin.moderation.DomainsDetail.noPolicyHeader') }}
|
||||
</h3>
|
||||
</header>
|
||||
<p>
|
||||
Moderation policies help you control how your instance interact with a given domain or account.
|
||||
{{ $t('views.admin.moderation.DomainsDetail.policyDescription') }}
|
||||
</p>
|
||||
<button
|
||||
class="ui primary button"
|
||||
@click="showPolicyForm = true"
|
||||
>
|
||||
Add a moderation policy
|
||||
{{ $t('views.admin.moderation.DomainsDetail.addPolicyButton') }}
|
||||
</button>
|
||||
</template>
|
||||
<instance-policy-card
|
||||
|
@ -211,7 +211,7 @@ const setAllowList = async (value: boolean) => {
|
|||
>
|
||||
<header class="ui header">
|
||||
<h3>
|
||||
This domain is subject to specific moderation rules
|
||||
{{ $t('views.admin.moderation.DomainsDetail.activePolicyHeader') }}
|
||||
</h3>
|
||||
</header>
|
||||
</instance-policy-card>
|
||||
|
@ -235,78 +235,78 @@ const setAllowList = async (value: boolean) => {
|
|||
<h3 class="ui header">
|
||||
<i class="info icon" />
|
||||
<div class="content">
|
||||
Instance data
|
||||
{{ $t('views.admin.moderation.DomainsDetail.instanceDataHeader') }}
|
||||
</div>
|
||||
</h3>
|
||||
<table class="ui very basic table">
|
||||
<tbody>
|
||||
<tr v-if="allowListEnabled">
|
||||
<td>
|
||||
Is present on allow-list
|
||||
{{ $t('views.admin.moderation.DomainsDetail.inAllowListLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
<translate
|
||||
<span
|
||||
v-if="object.allowed"
|
||||
>
|
||||
Yes
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.admin.moderation.DomainsDetail.inAllowListTrue') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
No
|
||||
</translate>
|
||||
{{ $t('views.admin.moderation.DomainsDetail.inAllowListFalse') }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Last checked
|
||||
{{ $t('views.admin.moderation.DomainsDetail.lastCheckedLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
<human-date
|
||||
v-if="object.nodeinfo_fetch_date"
|
||||
:date="object.nodeinfo_fetch_date"
|
||||
/>
|
||||
<translate
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
N/A
|
||||
</translate>
|
||||
{{ $t('views.admin.moderation.DomainsDetail.notApplicable') }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<template v-if="object.nodeinfo && object.nodeinfo.status === 'ok'">
|
||||
<tr>
|
||||
<td>
|
||||
Software
|
||||
{{ $t('views.admin.moderation.DomainsDetail.softwareLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ get(object, 'nodeinfo.payload.software.name', t('N/A')) }} ({{ get(object, 'nodeinfo.payload.software.version', t('N/A')) }})
|
||||
{{ $t('views.admin.moderation.DomainsDetail.softwareValue', {name: get(object, 'nodeinfo.payload.software.name', t('views.admin.moderation.DomainsDetail.notApplicable')), version: get(object, 'nodeinfo.payload.software.version', t('views.admin.moderation.DomainsDetail.notApplicable'))}) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Name
|
||||
{{ $t('views.admin.moderation.DomainsDetail.domainNameLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ get(object, 'nodeinfo.payload.metadata.nodeName', t('N/A')) }}
|
||||
{{ get(object, 'nodeinfo.payload.metadata.nodeName', t('views.admin.moderation.DomainsDetail.notApplicable')) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Total users
|
||||
{{ $t('views.admin.moderation.DomainsDetail.totalUsersLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ get(object, 'nodeinfo.payload.usage.users.total', t('N/A')) }}
|
||||
{{ get(object, 'nodeinfo.payload.usage.users.total', t('views.admin.moderation.DomainsDetail.notApplicable')) }}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-if="object.nodeinfo && object.nodeinfo.status === 'error'">
|
||||
<tr>
|
||||
<td>
|
||||
Status
|
||||
{{ $t('views.admin.moderation.DomainsDetail.nodeInfoStatusLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
Error while fetching node info
|
||||
{{ $t('views.admin.moderation.DomainsDetail.nodeInfoFailureMessage') }}
|
||||
|
||||
<span :data-tooltip="object.nodeinfo.error"><i class="question circle icon" /></span>
|
||||
</td>
|
||||
|
@ -319,7 +319,7 @@ const setAllowList = async (value: boolean) => {
|
|||
:url="'manage/federation/domains/' + object.name + '/nodeinfo/'"
|
||||
@action-done="refreshNodeInfo"
|
||||
>
|
||||
Refresh node info
|
||||
{{ $t('views.admin.moderation.DomainsDetail.refreshNodeInfoButton') }}
|
||||
</ajax-button>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -328,7 +328,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<h3 class="ui header">
|
||||
<i class="feed icon" />
|
||||
<div class="content">
|
||||
Activity
|
||||
{{ $t('views.admin.moderation.DomainsDetail.activityHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
@ -348,7 +348,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
First seen
|
||||
{{ $t('views.admin.moderation.DomainsDetail.firstSeenLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
<human-date :date="object.creation_date" />
|
||||
|
@ -359,7 +359,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<router-link
|
||||
:to="{name: 'manage.moderation.accounts.list', query: {q: 'domain:' + object.name }}"
|
||||
>
|
||||
Known accounts
|
||||
{{ $t('views.admin.moderation.DomainsDetail.knownAccountsLink') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -368,7 +368,7 @@ const setAllowList = async (value: boolean) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Emitted messages
|
||||
{{ $t('views.admin.moderation.DomainsDetail.emittedMessagesLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ stats.outbox_activities }}
|
||||
|
@ -376,7 +376,7 @@ const setAllowList = async (value: boolean) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Received library follows
|
||||
{{ $t('views.admin.moderation.DomainsDetail.receivedFollowsLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ stats.received_library_follows }}
|
||||
|
@ -384,7 +384,7 @@ const setAllowList = async (value: boolean) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Emitted library follows
|
||||
{{ $t('views.admin.moderation.DomainsDetail.emittedFollowsLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ stats.emitted_library_follows }}
|
||||
|
@ -399,7 +399,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<h3 class="ui header">
|
||||
<i class="music icon" />
|
||||
<div class="content">
|
||||
Audio content
|
||||
{{ $t('views.admin.moderation.DomainsDetail.audioContentHeader') }}
|
||||
<span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
|
||||
</div>
|
||||
</h3>
|
||||
|
@ -419,7 +419,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
Cached size
|
||||
{{ $t('views.admin.moderation.DomainsDetail.cachedSizeLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ humanSize(stats.media_downloaded_size) }}
|
||||
|
@ -427,7 +427,7 @@ const setAllowList = async (value: boolean) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Total size
|
||||
{{ $t('views.admin.moderation.DomainsDetail.totalSizeLabel') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ humanSize(stats.media_total_size) }}
|
||||
|
@ -436,7 +436,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.channels', query: {q: getQuery('domain', object.name) }}">
|
||||
Channels
|
||||
{{ $t('views.admin.moderation.DomainsDetail.channelsLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -446,7 +446,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('domain', object.name) }}">
|
||||
Libraries
|
||||
{{ $t('views.admin.moderation.DomainsDetail.librariesLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -456,7 +456,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('domain', object.name) }}">
|
||||
Uploads
|
||||
{{ $t('views.admin.moderation.DomainsDetail.uploadsLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -466,7 +466,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.library.artists', query: {q: getQuery('domain', object.name) }}">
|
||||
Artists
|
||||
{{ $t('views.admin.moderation.DomainsDetail.artistsLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -476,7 +476,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.library.albums', query: {q: getQuery('domain', object.name) }}">
|
||||
Albums
|
||||
{{ $t('views.admin.moderation.DomainsDetail.albumsLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -486,7 +486,7 @@ const setAllowList = async (value: boolean) => {
|
|||
<tr>
|
||||
<td>
|
||||
<router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('domain', object.name) }}">
|
||||
Tracks
|
||||
{{ $t('views.admin.moderation.DomainsDetail.tracksLabel') }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
|
|
|
@ -20,7 +20,7 @@ const { t } = useI18n()
|
|||
const router = useRouter()
|
||||
|
||||
const labels = computed(() => ({
|
||||
domains: t('Domains')
|
||||
domains: t('views.admin.moderation.DomainsList.title')
|
||||
}))
|
||||
|
||||
const domainName = ref('')
|
||||
|
@ -50,7 +50,7 @@ const createDomain = async () => {
|
|||
<main v-title="labels.domains">
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2 class="ui left floated header">
|
||||
Domains
|
||||
{{ $t('views.admin.moderation.DomainsList.title') }}
|
||||
</h2>
|
||||
<form
|
||||
class="ui right floated form"
|
||||
|
@ -62,7 +62,7 @@ const createDomain = async () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
Error while creating domain
|
||||
{{ $t('views.admin.moderation.DomainsList.failureHeader') }}
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li
|
||||
|
@ -75,7 +75,7 @@ const createDomain = async () => {
|
|||
</div>
|
||||
<div class="inline fields">
|
||||
<div class="field">
|
||||
<label for="add-domain">Add a domain</label>
|
||||
<label for="add-domain">{{ $t('views.admin.moderation.DomainsList.addDomainLabel') }}</label>
|
||||
<input
|
||||
id="add-domain"
|
||||
v-model="domainName"
|
||||
|
@ -93,7 +93,7 @@ const createDomain = async () => {
|
|||
type="checkbox"
|
||||
name="allowed"
|
||||
>
|
||||
<label for="allowed">Add to allow-list</label>
|
||||
<label for="allowed">{{ $t('views.admin.moderation.DomainsList.addToAllowListLabel') }}</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button
|
||||
|
@ -101,7 +101,7 @@ const createDomain = async () => {
|
|||
type="submit"
|
||||
:disabled="isCreating"
|
||||
>
|
||||
Add
|
||||
{{ $t('views.admin.moderation.DomainsList.addButton') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -90,8 +90,8 @@ fetchData()
|
|||
const { t } = useI18n()
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: t('Search by account, summary, domain…'),
|
||||
reports: t('Reports')
|
||||
searchPlaceholder: t('views.admin.moderation.ReportsList.searchPlaceholder'),
|
||||
reports: t('views.admin.moderation.ReportsList.title')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -99,13 +99,13 @@ const labels = computed(() => ({
|
|||
<main v-title="labels.reports">
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2 class="ui header">
|
||||
Reports
|
||||
{{ $t('views.admin.moderation.ReportsList.title') }}
|
||||
</h2>
|
||||
<div class="ui hidden divider" />
|
||||
<div class="ui inline form">
|
||||
<div class="fields">
|
||||
<div class="ui field">
|
||||
<label for="reports-search">Search</label>
|
||||
<label for="reports-search">{{ $t('views.admin.moderation.ReportsList.searchLabel') }}</label>
|
||||
<form @submit.prevent="query = search.value">
|
||||
<input
|
||||
id="reports-search"
|
||||
|
@ -118,7 +118,7 @@ const labels = computed(() => ({
|
|||
</form>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="reports-status">Status</label>
|
||||
<label for="reports-status">{{ $t('views.admin.moderation.ReportsList.statusLabel') }}</label>
|
||||
<select
|
||||
id="reports-status"
|
||||
class="ui dropdown"
|
||||
|
@ -126,13 +126,13 @@ const labels = computed(() => ({
|
|||
@change="addSearchToken('resolved', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">
|
||||
All
|
||||
{{ $t('views.admin.moderation.ReportsList.allOption') }}
|
||||
</option>
|
||||
<option value="yes">
|
||||
Resolved
|
||||
{{ $t('views.admin.moderation.ReportsList.resolvedStatus') }}
|
||||
</option>
|
||||
<option value="no">
|
||||
Unresolved
|
||||
{{ $t('views.admin.moderation.ReportsList.unresolvedStatus') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -144,7 +144,7 @@ const labels = computed(() => ({
|
|||
@update:model-value="addSearchToken('category', $event)"
|
||||
/>
|
||||
<div class="field">
|
||||
<label for="reports-ordering">Ordering</label>
|
||||
<label for="reports-ordering">{{ $t('views.admin.moderation.ReportsList.orderingLabel') }}</label>
|
||||
<select
|
||||
id="reports-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -160,17 +160,17 @@ const labels = computed(() => ({
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="reports-ordering-direction">Order</label>
|
||||
<label for="reports-ordering-direction">{{ $t('views.admin.moderation.ReportsList.orderingDirectionLabel') }}</label>
|
||||
<select
|
||||
id="reports-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
Ascending
|
||||
{{ $t('views.admin.moderation.ReportsList.ascendingOrdering') }}
|
||||
</option>
|
||||
<option value="-">
|
||||
Descending
|
||||
{{ $t('views.admin.moderation.ReportsList.descendingOrdering') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
@ -86,8 +86,8 @@ fetchData()
|
|||
const { t } = useI18n()
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: t('Search by username…'),
|
||||
reports: t('User Requests')
|
||||
searchPlaceholder: t('views.admin.moderation.RequestsList.searchPlaceholder'),
|
||||
reports: t('views.admin.moderation.RequestsList.title')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -95,13 +95,13 @@ const labels = computed(() => ({
|
|||
<main v-title="labels.reports">
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2 class="ui header">
|
||||
User Requests
|
||||
{{ $t('views.admin.moderation.RequestsList.title') }}
|
||||
</h2>
|
||||
<div class="ui hidden divider" />
|
||||
<div class="ui inline form">
|
||||
<div class="fields">
|
||||
<div class="ui field">
|
||||
<label for="requests-search">Search</label>
|
||||
<label for="requests-search">{{ $t('views.admin.moderation.RequestsList.searchLabel') }}</label>
|
||||
<form @submit.prevent="query = search.value">
|
||||
<input
|
||||
id="requests-search"
|
||||
|
@ -114,7 +114,7 @@ const labels = computed(() => ({
|
|||
</form>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="requests-status">Status</label>
|
||||
<label for="requests-status">{{ $t('views.admin.moderation.RequestsList.statusLabel') }}</label>
|
||||
<select
|
||||
id="requests-status"
|
||||
class="ui dropdown"
|
||||
|
@ -122,21 +122,21 @@ const labels = computed(() => ({
|
|||
@change="addSearchToken('status', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">
|
||||
All
|
||||
{{ $t('views.admin.moderation.RequestsList.allOption') }}
|
||||
</option>
|
||||
<option value="pending">
|
||||
Pending
|
||||
{{ $t('views.admin.moderation.RequestsList.pendingStatus') }}
|
||||
</option>
|
||||
<option value="approved">
|
||||
Approved
|
||||
{{ $t('views.admin.moderation.RequestsList.approvedStatus') }}
|
||||
</option>
|
||||
<option value="refused">
|
||||
Refused
|
||||
{{ $t('views.admin.moderation.RequestsList.refusedStatus') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="requests-ordering">Ordering</label>
|
||||
<label for="requests-ordering">{{ $t('views.admin.moderation.RequestsList.orderingLabel') }}</label>
|
||||
<select
|
||||
id="requests-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -152,17 +152,17 @@ const labels = computed(() => ({
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="requests-ordering-direction">Order</label>
|
||||
<label for="requests-ordering-direction">{{ $t('views.admin.moderation.RequestsList.orderingDirectionLabel') }}</label>
|
||||
<select
|
||||
id="requests-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
Ascending
|
||||
{{ $t('views.admin.moderation.RequestsList.ascendingOrdering') }}
|
||||
</option>
|
||||
<option value="-">
|
||||
Descending
|
||||
{{ $t('views.admin.moderation.RequestsList.descendingOrdering') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
@ -5,8 +5,8 @@ import { computed } from 'vue'
|
|||
const { t } = useI18n()
|
||||
|
||||
const labels = computed(() => ({
|
||||
manageUsers: t('Manage users'),
|
||||
secondaryMenu: t('Secondary menu')
|
||||
manageUsers: t('views.admin.users.Base.title'),
|
||||
secondaryMenu: t('views.admin.users.Base.secondaryMenu')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -24,13 +24,13 @@ const labels = computed(() => ({
|
|||
class="ui item"
|
||||
:to="{name: 'manage.users.users.list'}"
|
||||
>
|
||||
Users
|
||||
{{ $t('views.admin.users.Base.usersLink') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="ui item"
|
||||
:to="{name: 'manage.users.invitations.list'}"
|
||||
>
|
||||
Invitations
|
||||
{{ $t('views.admin.users.Base.invitationsLink') }}
|
||||
</router-link>
|
||||
</nav>
|
||||
<router-view :key="$route.fullPath" />
|
||||
|
|
|
@ -27,7 +27,7 @@ onMounted(async () => {
|
|||
<div class="ui active inverted dimmer">
|
||||
<div class="ui text loader">
|
||||
<h2>
|
||||
Logging in…
|
||||
{{ $t('views.auth.Callback.loggingInHeader') }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,7 @@ const props = defineProps<Props>()
|
|||
const { t } = useI18n()
|
||||
|
||||
const labels = computed(() => ({
|
||||
confirm: t('Confirm your e-mail address')
|
||||
confirm: t('views.auth.EmailConfirm.confirm')
|
||||
}))
|
||||
|
||||
const errors = ref([] as string[])
|
||||
|
@ -60,7 +60,7 @@ onMounted(() => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
Could not confirm your e-mail address
|
||||
{{ $t('views.auth.EmailConfirm.confirmFailureHeader') }}
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li
|
||||
|
@ -72,7 +72,7 @@ onMounted(() => {
|
|||
</ul>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="confirmation-code">Confirmation code</label>
|
||||
<label for="confirmation-code">{{ $t('views.auth.EmailConfirm.confirmationCodeLabel') }}</label>
|
||||
<input
|
||||
id="confirmation-code"
|
||||
v-model="key"
|
||||
|
@ -82,7 +82,7 @@ onMounted(() => {
|
|||
>
|
||||
</div>
|
||||
<router-link :to="{path: '/login'}">
|
||||
Return to login
|
||||
{{ $t('views.auth.EmailConfirm.backToLoginLink') }}
|
||||
</router-link>
|
||||
<button
|
||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
|
||||
|
@ -96,13 +96,13 @@ onMounted(() => {
|
|||
class="ui positive message"
|
||||
>
|
||||
<h4 class="header">
|
||||
E-mail address confirmed
|
||||
{{ $t('views.auth.EmailConfirm.confirmSuccessHeader') }}
|
||||
</h4>
|
||||
<p>
|
||||
You can now use the service without limitations.
|
||||
{{ $t('views.auth.EmailConfirm.confirmSuccessMessage') }}
|
||||
</p>
|
||||
<router-link :to="{name: 'login'}">
|
||||
Proceed to login
|
||||
{{ $t('views.auth.EmailConfirm.goToLoginLink') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: t('Log In')
|
||||
title: t('views.auth.Login.title')
|
||||
}))
|
||||
|
||||
const store = useStore()
|
||||
|
@ -37,7 +37,7 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
<section class="ui vertical stripe segment">
|
||||
<div class="ui small text container">
|
||||
<h2>
|
||||
Log in to your Funkwhale account
|
||||
{{ $t('views.auth.Login.loginHeader') }}
|
||||
</h2>
|
||||
<login-form :next="next" />
|
||||
</div>
|
||||
|
|
|
@ -18,8 +18,8 @@ const { t } = useI18n()
|
|||
const router = useRouter()
|
||||
|
||||
const labels = computed(() => ({
|
||||
placeholder: t('Enter the e-mail address linked to your account'),
|
||||
reset: t('Reset your password')
|
||||
placeholder: t('views.auth.PasswordReset.placeholder'),
|
||||
reset: t('views.auth.PasswordReset.title')
|
||||
}))
|
||||
|
||||
const email = ref(props.defaultEmail)
|
||||
|
@ -51,7 +51,7 @@ onMounted(() => emailInput.value.focus())
|
|||
<section class="ui vertical stripe segment">
|
||||
<div class="ui small text container">
|
||||
<h2>
|
||||
Reset your password
|
||||
{{ $t('views.auth.PasswordReset.title') }}
|
||||
</h2>
|
||||
<form
|
||||
class="ui form"
|
||||
|
@ -63,7 +63,7 @@ onMounted(() => emailInput.value.focus())
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
Error while asking for a password reset
|
||||
{{ $t('views.auth.PasswordReset.resetFailureHeader') }}
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li
|
||||
|
@ -75,10 +75,10 @@ onMounted(() => emailInput.value.focus())
|
|||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
Use this form to request a password reset. We will send an e-mail to the given address with instructions to reset your password.
|
||||
{{ $t('views.auth.PasswordReset.resetFormDescription') }}
|
||||
</p>
|
||||
<div class="field">
|
||||
<label for="account-email">Account's e-mail address</label>
|
||||
<label for="account-email">{{ $t('views.auth.PasswordReset.emailLabel') }}</label>
|
||||
<input
|
||||
id="account-email"
|
||||
ref="emailInput"
|
||||
|
@ -91,13 +91,13 @@ onMounted(() => emailInput.value.focus())
|
|||
>
|
||||
</div>
|
||||
<router-link :to="{path: '/login'}">
|
||||
Back to login
|
||||
{{ $t('views.auth.PasswordReset.backToLoginLink') }}
|
||||
</router-link>
|
||||
<button
|
||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
|
||||
type="submit"
|
||||
>
|
||||
Ask for a password reset
|
||||
{{ $t('views.auth.PasswordReset.requestResetButton') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@ const props = defineProps<Props>()
|
|||
const { t } = useI18n()
|
||||
|
||||
const labels = computed(() => ({
|
||||
changePassword: t('Change your password')
|
||||
changePassword: t('views.auth.PasswordResetConfirm.title')
|
||||
}))
|
||||
|
||||
const newPassword = ref('')
|
||||
|
@ -68,7 +68,7 @@ const submit = async () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
Error while changing your password
|
||||
{{ $t('views.auth.PasswordResetConfirm.changeFailureHeader') }}
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li
|
||||
|
@ -81,25 +81,25 @@ const submit = async () => {
|
|||
</div>
|
||||
<template v-if="token && uid">
|
||||
<div class="field">
|
||||
<label for="password-field">New password</label>
|
||||
<label for="password-field">{{ $t('views.auth.PasswordResetConfirm.newPasswordLabel') }}</label>
|
||||
<password-input
|
||||
v-model="newPassword"
|
||||
field-id="password-field"
|
||||
/>
|
||||
</div>
|
||||
<router-link :to="{path: '/login'}">
|
||||
Back to login
|
||||
{{ $t('views.auth.PasswordResetConfirm.backToLoginLink') }}
|
||||
</router-link>
|
||||
<button
|
||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
|
||||
type="submit"
|
||||
>
|
||||
Update your password
|
||||
{{ $t('views.auth.PasswordResetConfirm.updatePasswordButton') }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>
|
||||
If the e-mail address provided in the previous step is valid and linked to a user account, you should receive an e-mail with reset instructions in the next couple of minutes.
|
||||
{{ $t('views.auth.PasswordResetConfirm.requestSentMessage') }}
|
||||
</p>
|
||||
</template>
|
||||
</form>
|
||||
|
@ -108,13 +108,13 @@ const submit = async () => {
|
|||
class="ui positive message"
|
||||
>
|
||||
<h4 class="header">
|
||||
Password updated successfully
|
||||
{{ $t('views.auth.PasswordResetConfirm.changeSuccessHeader') }}
|
||||
</h4>
|
||||
<p>
|
||||
Your password has been updated successfully.
|
||||
{{ $t('views.auth.PasswordResetConfirm.changeSuccessMessage') }}
|
||||
</p>
|
||||
<router-link :to="{name: 'login'}">
|
||||
Proceed to login
|
||||
{{ $t('views.auth.PasswordResetConfirm.goToLoginLink') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,7 @@ import useErrorHandler from '~/composables/useErrorHandler'
|
|||
const { t } = useI18n()
|
||||
|
||||
const labels = computed(() => ({
|
||||
title: t('Manage plugins')
|
||||
title: t('views.auth.Plugins.title')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
|
|
@ -27,7 +27,7 @@ const recentActivity = ref(0)
|
|||
:client-only="true"
|
||||
/>
|
||||
<h2 class="ui header">
|
||||
Recently listened
|
||||
{{ $t('views.auth.ProfileActivity.recentlyListened') }}
|
||||
</h2>
|
||||
<div class="ui divider" />
|
||||
<track-widget
|
||||
|
@ -39,7 +39,7 @@ const recentActivity = ref(0)
|
|||
<div class="ui hidden divider" />
|
||||
<div>
|
||||
<h2 class="ui header">
|
||||
Recently favorited
|
||||
{{ $t('views.auth.ProfileActivity.recentlyFavorited') }}
|
||||
</h2>
|
||||
<div class="ui divider" />
|
||||
<track-widget
|
||||
|
@ -50,7 +50,7 @@ const recentActivity = ref(0)
|
|||
<div class="ui hidden divider" />
|
||||
<div>
|
||||
<h2 class="ui header">
|
||||
Playlists
|
||||
{{ $t('views.auth.ProfileActivity.playlistsHeader') }}
|
||||
</h2>
|
||||
<div class="ui divider" />
|
||||
<playlist-widget
|
||||
|
|
|
@ -43,7 +43,7 @@ const routerParams = computed(() => props.domain
|
|||
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
usernameProfile: t("%{ username }'s profile", { username: props.username })
|
||||
usernameProfile: t('views.auth.ProfileBase.title', { username: props.username })
|
||||
}))
|
||||
|
||||
onBeforeRouteUpdate((to) => {
|
||||
|
@ -100,9 +100,7 @@ watch(props, fetchData, { immediate: true })
|
|||
class="basic item"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate
|
||||
:translate-params="{domain: object.domain}"
|
||||
>View on %{ domain }</translate>
|
||||
{{ $t('views.auth.ProfileBase.domainViewLink', {domain: object.domain}) }}
|
||||
</a>
|
||||
<div
|
||||
v-for="obj in getReportableObjects({account: object})"
|
||||
|
@ -121,7 +119,7 @@ watch(props, fetchData, { immediate: true })
|
|||
:to="{name: 'manage.moderation.accounts.detail', params: {id: object.full_username}}"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
Open in moderation interface
|
||||
{{ $t('views.auth.ProfileBase.moderationLink') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</button>
|
||||
|
@ -150,7 +148,7 @@ watch(props, fetchData, { immediate: true })
|
|||
<template v-if="object.full_username === $store.state.auth.fullUsername">
|
||||
<div class="ui very small hidden divider" />
|
||||
<div class="ui basic success label">
|
||||
This is you!
|
||||
{{ $t('views.auth.ProfileBase.ownUserLabel') }}
|
||||
</div>
|
||||
</template>
|
||||
</h1>
|
||||
|
@ -173,13 +171,13 @@ watch(props, fetchData, { immediate: true })
|
|||
class="item"
|
||||
:to="{name: 'profile.overview', params: routerParams}"
|
||||
>
|
||||
Overview
|
||||
{{ $t('views.auth.ProfileBase.overviewLink') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="item"
|
||||
:to="{name: 'profile.activity', params: routerParams}"
|
||||
>
|
||||
Activity
|
||||
{{ $t('views.auth.ProfileBase.activityLink') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="ui hidden divider" />
|
||||
|
|
|
@ -42,7 +42,7 @@ const createForm = ref()
|
|||
</div>
|
||||
<div>
|
||||
<h2 class="ui with-actions header">
|
||||
Channels
|
||||
{{ $t('views.auth.ProfileOverview.channelsHeader') }}
|
||||
<div
|
||||
v-if="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"
|
||||
class="actions"
|
||||
|
@ -52,47 +52,47 @@ const createForm = ref()
|
|||
@click.stop.prevent="showCreateModal = true"
|
||||
>
|
||||
<i class="plus icon" />
|
||||
Add new
|
||||
{{ $t('views.auth.ProfileOverview.addNewLink') }}
|
||||
</a>
|
||||
</div>
|
||||
</h2>
|
||||
<channels-widget :filters="{scope: `actor:${object.full_username}`}" />
|
||||
<h2 class="ui with-actions header">
|
||||
User Libraries
|
||||
{{ $t('views.auth.ProfileOverview.librariesHeader') }}
|
||||
<div
|
||||
v-if="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"
|
||||
class="actions"
|
||||
>
|
||||
<router-link :to="{name: 'content.libraries.index'}">
|
||||
<i class="plus icon" />
|
||||
Add new
|
||||
{{ $t('views.auth.ProfileOverview.addNewLink') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</h2>
|
||||
<library-widget :url="`federation/actors/${object.full_username}/libraries/`">
|
||||
<template #title>
|
||||
This user shared the following libraries
|
||||
{{ $t('views.auth.ProfileOverview.sharedLibraries') }}
|
||||
</template>
|
||||
</library-widget>
|
||||
</div>
|
||||
|
||||
<semantic-modal v-model:show="showCreateModal">
|
||||
<h4 class="header">
|
||||
<translate
|
||||
<span
|
||||
v-if="step === 1"
|
||||
>
|
||||
Create channel
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.auth.ProfileOverview.createChannelModalHeader') }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="category === 'podcast'"
|
||||
>
|
||||
Podcast channel
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.auth.ProfileOverview.podcastChannelHeader') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
Artist channel
|
||||
</translate>
|
||||
{{ $t('views.auth.ProfileOverview.artistChannelHeader') }}
|
||||
</span>
|
||||
</h4>
|
||||
<div
|
||||
ref="modalContent"
|
||||
|
@ -115,21 +115,21 @@ const createForm = ref()
|
|||
v-if="step === 1"
|
||||
class="ui basic deny button"
|
||||
>
|
||||
Cancel
|
||||
{{ $t('views.auth.ProfileOverview.cancelButton') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="step > 1"
|
||||
class="ui basic button"
|
||||
@click.stop.prevent="step -= 1"
|
||||
>
|
||||
Previous step
|
||||
{{ $t('views.auth.ProfileOverview.previousButton') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="step === 1"
|
||||
class="ui primary button"
|
||||
@click.stop.prevent="step += 1"
|
||||
>
|
||||
Next step
|
||||
{{ $t('views.auth.ProfileOverview.nextButton') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="step === 2"
|
||||
|
@ -138,7 +138,7 @@ const createForm = ref()
|
|||
:disabled="!submittable && !loading"
|
||||
@click.prevent.stop="createForm.submit"
|
||||
>
|
||||
Create channel
|
||||
{{ $t('views.auth.ProfileOverview.createChannelButton') }}
|
||||
</button>
|
||||
</div>
|
||||
</semantic-modal>
|
||||
|
|
|
@ -19,7 +19,7 @@ withDefaults(defineProps<Props>(), {
|
|||
const { t } = useI18n()
|
||||
|
||||
const labels = computed(() => ({
|
||||
title: t('Sign Up')
|
||||
title: t('views.auth.Signup.title')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -31,7 +31,7 @@ const labels = computed(() => ({
|
|||
<section class="ui vertical stripe segment">
|
||||
<div class="ui small text container">
|
||||
<h2>
|
||||
Create a Funkwhale account
|
||||
{{ $t('views.auth.Signup.createAccountHeader') }}
|
||||
</h2>
|
||||
<signup-form
|
||||
:default-invitation="defaultInvitation"
|
||||
|
|
|
@ -55,7 +55,7 @@ const externalDomain = computed(() => {
|
|||
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: t('Channel')
|
||||
title: t('views.channels.DetailBase.title')
|
||||
}))
|
||||
|
||||
onBeforeRouteUpdate((to) => {
|
||||
|
@ -167,42 +167,22 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
/>
|
||||
<template v-if="totalTracks > 0">
|
||||
<div class="ui hidden very small divider" />
|
||||
<translate
|
||||
<span
|
||||
v-if="object.artist?.content_category === 'podcast'"
|
||||
|
||||
translate-plural="%{ count } episodes"
|
||||
:translate-n="totalTracks"
|
||||
:translate-params="{count: totalTracks}"
|
||||
>
|
||||
%{ count } episode
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.channels.DetailBase.episodeCount', {count: totalTracks}) }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
|
||||
:translate-params="{count: totalTracks}"
|
||||
:translate-n="totalTracks"
|
||||
translate-plural="%{ count } tracks"
|
||||
>
|
||||
%{ count } track
|
||||
</translate>
|
||||
{{ $t('views.channels.DetailBase.trackCount', {count: totalTracks}) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="object.attributed_to.full_username === $store.state.auth.fullUsername || $store.getters['channels/isSubscribed'](object.uuid)">
|
||||
<br><translate
|
||||
|
||||
translate-plural="%{ count } subscribers"
|
||||
:translate-n="object?.subscriptions_count"
|
||||
:translate-params="{count: object?.subscriptions_count}"
|
||||
>
|
||||
%{ count } subscriber
|
||||
</translate>
|
||||
<br><translate
|
||||
|
||||
translate-plural="%{ count } listenings"
|
||||
:translate-n="object?.downloads_count"
|
||||
:translate-params="{count: object?.downloads_count}"
|
||||
>
|
||||
%{ count } listening
|
||||
</translate>
|
||||
<br>
|
||||
{{ $t('views.channels.DetailBase.subscriberCount', {count: object?.subscriptions_count}) }}
|
||||
<br>
|
||||
{{ $t('views.channels.DetailBase.listeningsCount', {count: object?.downloads_count}) }}
|
||||
</template>
|
||||
<div class="ui hidden small divider" />
|
||||
<a
|
||||
|
@ -216,14 +196,14 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
class="tiny"
|
||||
>
|
||||
<h4 class="header">
|
||||
Subscribe to this channel
|
||||
{{ $t('views.channels.DetailBase.subscribeModalHeader') }}
|
||||
</h4>
|
||||
<div class="scrollable content">
|
||||
<div class="description">
|
||||
<template v-if="$store.state.auth.authenticated">
|
||||
<h3>
|
||||
<i class="user icon" />
|
||||
Subscribe on Funkwhale
|
||||
{{ $t('views.channels.DetailBase.subscribeOnFunkwhale') }}
|
||||
</h3>
|
||||
<subscribe-button
|
||||
:channel="object"
|
||||
|
@ -234,20 +214,20 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
<template v-if="object.rss_url">
|
||||
<h3>
|
||||
<i class="feed icon" />
|
||||
Subscribe via RSS
|
||||
{{ $t('views.channels.DetailBase.subscribeRss') }}
|
||||
</h3>
|
||||
<p>
|
||||
Copy-paste the following URL in your favorite podcatcher:
|
||||
{{ $t('views.channels.DetailBase.copyUrl') }}
|
||||
</p>
|
||||
<copy-input :value="object.rss_url" />
|
||||
</template>
|
||||
<template v-if="object.actor">
|
||||
<h3>
|
||||
<i class="bell icon" />
|
||||
Subscribe on the Fediverse
|
||||
{{ $t('views.channels.DetailBase.subscribeOnFediverse') }}
|
||||
</h3>
|
||||
<p>
|
||||
If you're using Mastodon or other fediverse applications, you can subscribe to this account:
|
||||
{{ $t('views.channels.DetailBase.subscribeOnFediverseDescription') }}
|
||||
</p>
|
||||
<copy-input
|
||||
id="copy-tag"
|
||||
|
@ -258,7 +238,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic deny button">
|
||||
Cancel
|
||||
{{ $t('views.channels.DetailBase.cancelButton') }}
|
||||
</button>
|
||||
</div>
|
||||
</semantic-modal>
|
||||
|
@ -276,7 +256,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
@click.prevent="showEmbedModal = !showEmbedModal"
|
||||
>
|
||||
<i class="code icon" />
|
||||
Embed
|
||||
{{ $t('views.channels.DetailBase.embedButton') }}
|
||||
</a>
|
||||
<a
|
||||
v-if="object.actor && object.actor.domain != $store.getters['instance/domain']"
|
||||
|
@ -285,9 +265,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
class="basic item"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate
|
||||
:translate-params="{domain: object.actor.domain}"
|
||||
>View on %{ domain }</translate>
|
||||
{{ $t('views.channels.DetailBase.domainViewLink', {domain: object.actor.domain}) }}
|
||||
</a>
|
||||
<div class="divider" />
|
||||
<a
|
||||
|
@ -308,7 +286,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
@click.stop.prevent="showEditModal = true"
|
||||
>
|
||||
<i class="edit icon" />
|
||||
Edit…
|
||||
{{ $t('views.channels.DetailBase.editButton') }}
|
||||
</a>
|
||||
<dangerous-button
|
||||
v-if="object"
|
||||
|
@ -316,22 +294,22 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
@confirm="remove()"
|
||||
>
|
||||
<i class="ui trash icon" />
|
||||
Delete…
|
||||
{{ $t('views.channels.DetailBase.deleteButton') }}
|
||||
<template #modal-header>
|
||||
<p>
|
||||
Delete this Channel?
|
||||
{{ $t('views.channels.DetailBase.deleteModalHeader') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<div>
|
||||
<p>
|
||||
The channel will be deleted, as well as any related files and data. This action is irreversible.
|
||||
{{ $t('views.channels.DetailBase.deleteModalMessage') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<p>
|
||||
Delete
|
||||
{{ $t('views.channels.DetailBase.deleteModalConfirm') }}
|
||||
</p>
|
||||
</template>
|
||||
</dangerous-button>
|
||||
|
@ -343,7 +321,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
:to="{name: 'manage.channels.detail', params: {id: object.uuid}}"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
Open in moderation interface
|
||||
{{ $t('views.channels.DetailBase.moderationLink') }}
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -374,9 +352,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
target="_blank"
|
||||
>
|
||||
<i class="external link icon" />
|
||||
<translate
|
||||
:translate-params="{domain: externalDomain}"
|
||||
>Mirrored from %{ domain }</translate>
|
||||
{{ $t('views.channels.DetailBase.mirroredLink', {domain: externalDomain}) }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -391,7 +367,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
@click.prevent.stop="$store.commit('channels/showUploadModal', {show: true, config: {channel: object}})"
|
||||
>
|
||||
<i class="upload icon" />
|
||||
Upload
|
||||
{{ $t('views.channels.DetailBase.uploadButton') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ui buttons">
|
||||
|
@ -400,7 +376,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
class="vibrant"
|
||||
:artist="object.artist"
|
||||
>
|
||||
Play
|
||||
{{ $t('views.channels.DetailBase.playButton') }}
|
||||
</play-button>
|
||||
</div>
|
||||
<div class="ui buttons">
|
||||
|
@ -416,7 +392,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
v-model:show="showEmbedModal"
|
||||
>
|
||||
<h4 class="header">
|
||||
Embed this artist work on your website
|
||||
{{ $t('views.channels.DetailBase.embedModalHeader') }}
|
||||
</h4>
|
||||
<div class="scrolling content">
|
||||
<div class="description">
|
||||
|
@ -428,7 +404,7 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic deny button">
|
||||
Cancel
|
||||
{{ $t('views.channels.DetailBase.cancelButton') }}
|
||||
</button>
|
||||
</div>
|
||||
</semantic-modal>
|
||||
|
@ -437,16 +413,16 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
v-model:show="showEditModal"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate
|
||||
<span
|
||||
v-if="object.artist?.content_category === 'podcast'"
|
||||
>
|
||||
Podcast channel
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.channels.DetailBase.podcastChannelHeader') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
Artist channel
|
||||
</translate>
|
||||
{{ $t('views.channels.DetailBase.artistChannelHeader') }}
|
||||
</span>
|
||||
</h4>
|
||||
<div class="scrolling content">
|
||||
<channel-form
|
||||
|
@ -460,14 +436,14 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui left floated basic deny button">
|
||||
Cancel
|
||||
{{ $t('views.channels.DetailBase.cancelButton') }}
|
||||
</button>
|
||||
<button
|
||||
:class="['ui', 'primary', 'confirm', {loading: edit.loading}, 'button']"
|
||||
:disabled="!edit.submittable"
|
||||
@click.stop="editForm?.submit"
|
||||
>
|
||||
Update channel
|
||||
{{ $t('views.channels.DetailBase.updateChannelButton') }}
|
||||
</button>
|
||||
</div>
|
||||
</semantic-modal>
|
||||
|
@ -488,23 +464,23 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
|
||||
:to="{name: 'channels.detail', params: {id: id}}"
|
||||
>
|
||||
Overview
|
||||
{{ $t('views.channels.DetailBase.channelOverview') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="item"
|
||||
|
||||
:to="{name: 'channels.detail.episodes', params: {id: id}}"
|
||||
>
|
||||
<translate
|
||||
<span
|
||||
v-if="isPodcast"
|
||||
>
|
||||
All Episodes
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.channels.DetailBase.channelEpisodes') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
Tracks
|
||||
</translate>
|
||||
{{ $t('views.channels.DetailBase.channelTracks') }}
|
||||
</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="ui hidden divider" />
|
||||
|
|
|
@ -101,15 +101,15 @@ const albumModal = ref()
|
|||
@click="pendingUploads.length = 0"
|
||||
/>
|
||||
<h3 class="ui header">
|
||||
Uploads published successfully
|
||||
{{ $t('views.channels.DetailOverview.uploadsSuccessHeader') }}
|
||||
</h3>
|
||||
<p>
|
||||
Processed uploads: {{ processedUploads.length }}/{{ pendingUploads.length }}
|
||||
{{ $t('views.channels.DetailOverview.uploadsProgress', {finished: processedUploads.length, total: pendingUploads.length}) }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-else-if="isOver">
|
||||
<h3 class="ui header">
|
||||
Some uploads couldn't be published
|
||||
{{ $t('views.channels.DetailOverview.uploadsFailureHeader') }}
|
||||
</h3>
|
||||
<div class="ui hidden divider" />
|
||||
<router-link
|
||||
|
@ -117,26 +117,26 @@ const albumModal = ref()
|
|||
class="ui basic button"
|
||||
:to="{name: 'content.libraries.files', query: {q: 'status:skipped'}}"
|
||||
>
|
||||
View skipped uploads
|
||||
{{ $t('views.channels.DetailOverview.skippedUploadsLink') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="erroredUploads.length > 0"
|
||||
class="ui basic button"
|
||||
:to="{name: 'content.libraries.files', query: {q: 'status:errored'}}"
|
||||
>
|
||||
View errored uploads
|
||||
{{ $t('views.channels.DetailOverview.erroredUploadsLink') }}
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="ui inline right floated active loader" />
|
||||
<h3 class="ui header">
|
||||
Uploads are being processed
|
||||
{{ $t('views.channels.DetailOverview.uploadsProcessingHeader') }}
|
||||
</h3>
|
||||
<p>
|
||||
Your uploads are being processed by Funkwhale and will be live very soon.
|
||||
{{ $t('views.channels.DetailOverview.uploadsProcessingMessage') }}
|
||||
</p>
|
||||
<p>
|
||||
Processed uploads: {{ processedUploads.length }}/{{ pendingUploads.length }}
|
||||
{{ $t('views.channels.DetailOverview.uploadsProgress', {finished: processedUploads.length, total: pendingUploads.length}) }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -156,16 +156,16 @@ const albumModal = ref()
|
|||
:filters="{channel: object.uuid, ordering: '-creation_date', page_size: '25'}"
|
||||
>
|
||||
<h2 class="ui header">
|
||||
<translate
|
||||
<span
|
||||
v-if="isPodcast"
|
||||
>
|
||||
Latest episodes
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.channels.DetailOverview.latestEpisodes') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
Latest tracks
|
||||
</translate>
|
||||
{{ $t('views.channels.DetailOverview.latestTracks') }}
|
||||
</span>
|
||||
</h2>
|
||||
</channel-entries>
|
||||
<div class="ui hidden divider" />
|
||||
|
@ -175,23 +175,23 @@ const albumModal = ref()
|
|||
:is-podcast="isPodcast"
|
||||
>
|
||||
<h2 class="ui with-actions header">
|
||||
<translate
|
||||
<span
|
||||
v-if="isPodcast"
|
||||
>
|
||||
Series
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.channels.DetailOverview.seriesHeader') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
Albums
|
||||
</translate>
|
||||
{{ $t('views.channels.DetailOverview.albumsHeader') }}
|
||||
</span>
|
||||
<div
|
||||
v-if="isOwner"
|
||||
class="actions"
|
||||
>
|
||||
<a @click.stop.prevent="albumModal.show = true">
|
||||
<i class="plus icon" />
|
||||
Add new
|
||||
{{ $t('views.channels.DetailOverview.addAlbumLink') }}
|
||||
</a>
|
||||
</div>
|
||||
</h2>
|
||||
|
|
|
@ -25,8 +25,8 @@ const widgetKey = ref(new Date().toLocaleString())
|
|||
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: t('Subscribed Channels'),
|
||||
searchPlaceholder: t('Filter by name…')
|
||||
title: t('views.channels.SubscriptionsList.title'),
|
||||
searchPlaceholder: t('views.channels.SubscriptionsList.searchPlaceholder')
|
||||
}))
|
||||
|
||||
const previousPage = ref()
|
||||
|
@ -66,7 +66,7 @@ const showSubscribeModal = ref(false)
|
|||
<div class="actions">
|
||||
<a @click.stop.prevent="showSubscribeModal = true">
|
||||
<i class="plus icon" />
|
||||
Add new
|
||||
{{ $t('views.channels.SubscriptionsList.addNewLink') }}
|
||||
</a>
|
||||
</div>
|
||||
</h1>
|
||||
|
@ -76,7 +76,7 @@ const showSubscribeModal = ref(false)
|
|||
:fullscreen="false"
|
||||
>
|
||||
<h2 class="header">
|
||||
Subscription
|
||||
{{ $t('views.channels.SubscriptionsList.subscriptionModalHeader') }}
|
||||
</h2>
|
||||
<div
|
||||
ref="modalContent"
|
||||
|
@ -92,7 +92,7 @@ const showSubscribeModal = ref(false)
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic deny button">
|
||||
Cancel
|
||||
{{ $t('views.channels.SubscriptionsList.cancelButton') }}
|
||||
</button>
|
||||
<button
|
||||
form="remote-search"
|
||||
|
@ -100,7 +100,7 @@ const showSubscribeModal = ref(false)
|
|||
class="ui primary button"
|
||||
>
|
||||
<i class="bookmark icon" />
|
||||
Subscribe
|
||||
{{ $t('views.channels.SubscriptionsList.subscribeButton') }}
|
||||
</button>
|
||||
</div>
|
||||
</semantic-modal>
|
||||
|
|
|
@ -5,8 +5,8 @@ import { computed } from 'vue'
|
|||
const { t } = useI18n()
|
||||
|
||||
const labels = computed(() => ({
|
||||
secondaryMenu: t('Secondary menu'),
|
||||
title: t('Add content')
|
||||
secondaryMenu: t('views.content.Base.secondaryMenu'),
|
||||
title: t('views.content.Base.title')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -24,13 +24,13 @@ const labels = computed(() => ({
|
|||
class="ui item"
|
||||
:to="{name: 'content.libraries.index'}"
|
||||
>
|
||||
Libraries
|
||||
{{ $t('views.content.Base.librariesLink') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="ui item"
|
||||
:to="{name: 'content.libraries.files'}"
|
||||
>
|
||||
Tracks
|
||||
{{ $t('views.content.Base.tracksLink') }}
|
||||
</router-link>
|
||||
</nav>
|
||||
<router-view :key="$route.fullPath" />
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useStore } from '~/store'
|
|||
const { t } = useI18n()
|
||||
|
||||
const labels = computed(() => ({
|
||||
title: t('Add and manage content')
|
||||
title: t('views.content.Home.title')
|
||||
}))
|
||||
|
||||
const store = useStore()
|
||||
|
@ -23,51 +23,51 @@ const defaultQuota = computed(() => humanSize(quota.value * 1e6))
|
|||
<div class="ui text container">
|
||||
<h1>{{ labels.title }}</h1>
|
||||
<p>
|
||||
<strong>{{ $t('This instance offers up to %{quota} of storage space for every user.', { quota: defaultQuota }) }}</strong>
|
||||
<strong>{{ $t('views.content.Home.uploadQuota', { quota: defaultQuota }) }}</strong>
|
||||
</p>
|
||||
<div class="ui segment">
|
||||
<h2>
|
||||
<i class="feed icon" />
|
||||
Publish your work in a channel
|
||||
{{ $t('views.content.Home.channelHeader') }}
|
||||
</h2>
|
||||
<p>
|
||||
If you are a musician or a podcaster, channels are designed for you!  If you are a musician or a podcaster, channels are designed for you! work publicly and get subscribers on Funkwhale, the Fediverse or any podcasting application.
|
||||
{{ $t('views.content.Home.channelDescription') }} {{ $t('views.content.Home.channelDescriptionContinued') }}
|
||||
</p>
|
||||
<router-link
|
||||
:to="{name: 'profile.overview', params: {username: store.state.auth.username}, hash: '#channels'}"
|
||||
class="ui primary button"
|
||||
>
|
||||
Get started
|
||||
{{ $t('views.content.Home.getStartedButton') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="ui segment">
|
||||
<h2>
|
||||
<i class="cloud icon" />
|
||||
Upload third-party content in a library
|
||||
{{ $t('views.content.Home.libraryUploadHeader') }}
|
||||
</h2>
|
||||
<p>
|
||||
Upload your personal music library to Funkwhale to enjoy it from anywhere and share it with friends and family.
|
||||
{{ $t('views.content.Home.libraryUploadDescription') }}
|
||||
</p>
|
||||
<router-link
|
||||
:to="{name: 'content.libraries.index'}"
|
||||
class="ui primary button"
|
||||
>
|
||||
Get started
|
||||
{{ $t('views.content.Home.getStartedButton') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="ui segment">
|
||||
<h2>
|
||||
<i class="download icon" />
|
||||
Follow remote libraries
|
||||
{{ $t('views.content.Home.followLibrariesHeader') }}
|
||||
</h2>
|
||||
<p>
|
||||
Follow libraries from other users to get access to new music. Public libraries can be followed immediately, while following a private library requires approval from its owner.
|
||||
{{ $t('views.content.Home.followLibrariesDescription') }}
|
||||
</p>
|
||||
<router-link
|
||||
:to="{name: 'content.remote.index'}"
|
||||
class="ui primary button"
|
||||
>
|
||||
Get started
|
||||
{{ $t('views.content.Home.getStartedButton') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@ const { t } = useI18n()
|
|||
|
||||
const sharedLabels = useSharedLabels()
|
||||
|
||||
const sizeLabel = computed(() => t('Total size of the files in this library'))
|
||||
const sizeLabel = computed(() => t('views.content.libraries.Card.sizeLabel'))
|
||||
|
||||
const privacyTooltips = (level: PrivacyLevel) => `Visibility: ${sharedLabels.fields.privacy_level.choices[level].toLowerCase()}`
|
||||
</script>
|
||||
|
@ -69,14 +69,7 @@ const privacyTooltips = (level: PrivacyLevel) => `Visibility: ${sharedLabels.fie
|
|||
{{ humanSize(library.size) }}
|
||||
</span>
|
||||
<i class="music icon" />
|
||||
<translate
|
||||
|
||||
:translate-params="{count: library.uploads_count}"
|
||||
:translate-n="library.uploads_count"
|
||||
translate-plural="%{ count } tracks"
|
||||
>
|
||||
%{ count } track
|
||||
</translate>
|
||||
{{ $t('views.content.libraries.Card.trackCount') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui bottom basic attached buttons">
|
||||
|
@ -84,13 +77,13 @@ const privacyTooltips = (level: PrivacyLevel) => `Visibility: ${sharedLabels.fie
|
|||
:to="{name: 'library.detail.upload', params: {id: library.uuid}}"
|
||||
class="ui button"
|
||||
>
|
||||
Upload
|
||||
{{ $t('views.content.libraries.Card.uploadButton') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{name: 'library.detail', params: {id: library.uuid}}"
|
||||
class="ui button"
|
||||
>
|
||||
Library Details
|
||||
{{ $t('views.content.libraries.Card.detailsLink') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -70,14 +70,14 @@ const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
|||
const actions = computed(() => [
|
||||
{
|
||||
name: 'delete',
|
||||
label: t('Delete'),
|
||||
label: t('views.content.libraries.FilesTable.deleteLabel'),
|
||||
isDangerous: true,
|
||||
allowAll: true,
|
||||
confirmColor: 'danger'
|
||||
},
|
||||
{
|
||||
name: 'relaunch_import',
|
||||
label: t('Restart import'),
|
||||
label: t('views.content.libraries.FilesTable.restartImportLabel'),
|
||||
isDangerous: true,
|
||||
allowAll: true,
|
||||
filterCheckable: (filter: { import_status: ImportStatus }) => {
|
||||
|
@ -120,8 +120,8 @@ fetchData()
|
|||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: t('Search by title, artist, album…'),
|
||||
showStatus: t('Show information about the upload status for this track')
|
||||
searchPlaceholder: t('views.content.libraries.FilesTable.searchPlaceholder'),
|
||||
showStatus: t('views.content.libraries.FilesTable.showStatus')
|
||||
}))
|
||||
|
||||
const detailedUpload = ref()
|
||||
|
@ -138,7 +138,7 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
|||
<div class="fields">
|
||||
<div class="ui six wide field">
|
||||
<label for="files-search">
|
||||
Search
|
||||
{{ $t('views.content.libraries.FilesTable.searchLabel') }}
|
||||
</label>
|
||||
<form @submit.prevent="query = search.value">
|
||||
<input
|
||||
|
@ -153,7 +153,7 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
|||
</div>
|
||||
<div class="field">
|
||||
<label for="import-status">
|
||||
Import status
|
||||
{{ $t('views.content.libraries.FilesTable.importStatusLabel') }}
|
||||
</label>
|
||||
<select
|
||||
id="import-status"
|
||||
|
@ -162,28 +162,28 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
|||
@change="addSearchToken('status', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value>
|
||||
All
|
||||
{{ $t('views.content.libraries.FilesTable.allOption') }}
|
||||
</option>
|
||||
<option value="draft">
|
||||
Draft
|
||||
{{ $t('views.content.libraries.FilesTable.draftStatus') }}
|
||||
</option>
|
||||
<option value="pending">
|
||||
Pending
|
||||
{{ $t('views.content.libraries.FilesTable.pendingStatus') }}
|
||||
</option>
|
||||
<option value="skipped">
|
||||
Skipped
|
||||
{{ $t('views.content.libraries.FilesTable.skippedStatus') }}
|
||||
</option>
|
||||
<option value="errored">
|
||||
Failed
|
||||
{{ $t('views.content.libraries.FilesTable.failedStatus') }}
|
||||
</option>
|
||||
<option value="finished">
|
||||
Finished
|
||||
{{ $t('views.content.libraries.FilesTable.finishedStatus') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="ordering-select">
|
||||
Ordering
|
||||
{{ $t('views.content.libraries.FilesTable.orderingLabel') }}
|
||||
</label>
|
||||
<select
|
||||
id="ordering-select"
|
||||
|
@ -201,7 +201,7 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
|||
</div>
|
||||
<div class="field">
|
||||
<label for="ordering-direction">
|
||||
Ordering direction
|
||||
{{ $t('views.content.libraries.FilesTable.orderingDirectionLabel') }}
|
||||
</label>
|
||||
<select
|
||||
id="ordering-direction"
|
||||
|
@ -209,10 +209,10 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
|||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
Ascending
|
||||
{{ $t('views.content.libraries.FilesTable.ascendingOrdering') }}
|
||||
</option>
|
||||
<option value="-">
|
||||
Descending
|
||||
{{ $t('views.content.libraries.FilesTable.descendingOrdering') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -236,7 +236,7 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
|||
>
|
||||
<div class="ui icon header">
|
||||
<i class="upload icon" />
|
||||
No tracks have been added to this library yet
|
||||
{{ $t('views.content.libraries.FilesTable.emptyState') }}
|
||||
</div>
|
||||
</div>
|
||||
<action-table
|
||||
|
@ -254,25 +254,25 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
|||
>
|
||||
<template #header-cells>
|
||||
<th>
|
||||
Title
|
||||
{{ $t('views.content.libraries.FilesTable.titleTableHeader') }}
|
||||
</th>
|
||||
<th>
|
||||
Artist
|
||||
{{ $t('views.content.libraries.FilesTable.artistTableHeader') }}
|
||||
</th>
|
||||
<th>
|
||||
Album
|
||||
{{ $t('views.content.libraries.FilesTable.albumTableHeader') }}
|
||||
</th>
|
||||
<th>
|
||||
Upload date
|
||||
{{ $t('views.content.libraries.FilesTable.uploadDateTableHeader') }}
|
||||
</th>
|
||||
<th>
|
||||
Import status
|
||||
{{ $t('views.content.libraries.FilesTable.importStatusTableHeader') }}
|
||||
</th>
|
||||
<th>
|
||||
Duration
|
||||
{{ $t('views.content.libraries.FilesTable.durationTableHeader') }}
|
||||
</th>
|
||||
<th>
|
||||
Size
|
||||
{{ $t('views.content.libraries.FilesTable.sizeTableHeader') }}
|
||||
</th>
|
||||
</template>
|
||||
<template
|
||||
|
@ -330,13 +330,13 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
|||
{{ time.parse(scope.obj.duration) }}
|
||||
</td>
|
||||
<td v-else>
|
||||
N/A
|
||||
{{ $t('views.content.libraries.FilesTable.notApplicable') }}
|
||||
</td>
|
||||
<td v-if="scope.obj.size">
|
||||
{{ humanSize(scope.obj.size) }}
|
||||
</td>
|
||||
<td v-else>
|
||||
N/A
|
||||
{{ $t('views.content.libraries.FilesTable.notApplicable') }}
|
||||
</td>
|
||||
</template>
|
||||
</action-table>
|
||||
|
@ -351,10 +351,7 @@ const getImportStatusChoice = (importStatus: ImportStatus) => {
|
|||
/>
|
||||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
<translate
|
||||
|
||||
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
|
||||
>Showing results %{ start }-%{ end } on %{ total }</translate>
|
||||
{{ $t('views.content.libraries.FilesTable.resultsDisplay', {start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -30,8 +30,8 @@ const sharedLabels = useSharedLabels()
|
|||
const store = useStore()
|
||||
|
||||
const labels = computed(() => ({
|
||||
descriptionPlaceholder: t('This library contains my personal music, I hope you like it.'),
|
||||
namePlaceholder: t('My awesome library')
|
||||
descriptionPlaceholder: t('views.content.libraries.Form.descriptionPlaceholder'),
|
||||
namePlaceholder: t('views.content.libraries.Form.namePlaceholder')
|
||||
}))
|
||||
|
||||
const currentVisibilityLevel = ref(props.library?.privacy_level ?? 'me')
|
||||
|
@ -59,8 +59,8 @@ const submit = async () => {
|
|||
|
||||
store.commit('ui/addMessage', {
|
||||
content: props.library
|
||||
? t('Library updated')
|
||||
: t('Library created'),
|
||||
? t('views.content.libraries.Form.libraryUpdateMessage')
|
||||
: t('views.content.libraries.Form.libraryCreateMessage'),
|
||||
date: new Date()
|
||||
})
|
||||
} catch (error) {
|
||||
|
@ -77,7 +77,7 @@ const remove = async () => {
|
|||
await axios.delete(`libraries/${props.library?.uuid}/`)
|
||||
emit('deleted')
|
||||
store.commit('ui/addMessage', {
|
||||
content: t('Library deleted'),
|
||||
content: t('views.content.libraries.Form.libraryDeleteMessage'),
|
||||
date: new Date()
|
||||
})
|
||||
} catch (error) {
|
||||
|
@ -94,7 +94,7 @@ const remove = async () => {
|
|||
@submit.prevent="submit"
|
||||
>
|
||||
<p v-if="!library">
|
||||
Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family.
|
||||
{{ $t('views.content.libraries.Form.libraryHelp') }}
|
||||
</p>
|
||||
<div
|
||||
v-if="errors.length > 0"
|
||||
|
@ -102,7 +102,7 @@ const remove = async () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
Error
|
||||
{{ $t('views.content.libraries.Form.failureHeader') }}
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li
|
||||
|
@ -114,7 +114,7 @@ const remove = async () => {
|
|||
</ul>
|
||||
</div>
|
||||
<div class="required field">
|
||||
<label for="current-name">Name</label>
|
||||
<label for="current-name">{{ $t('views.content.libraries.Form.nameLabel') }}</label>
|
||||
<input
|
||||
id="current-name"
|
||||
v-model="currentName"
|
||||
|
@ -125,7 +125,7 @@ const remove = async () => {
|
|||
>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="current-description">Description</label>
|
||||
<label for="current-description">{{ $t('views.content.libraries.Form.descriptionLabel') }}</label>
|
||||
<textarea
|
||||
id="current-description"
|
||||
v-model="currentDescription"
|
||||
|
@ -134,9 +134,9 @@ const remove = async () => {
|
|||
/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="visibility-level">Visibility</label>
|
||||
<label for="visibility-level">{{ $t('views.content.libraries.Form.visibilityLabel') }}</label>
|
||||
<p>
|
||||
You are able to share your library with other people, regardless of its visibility.
|
||||
{{ $t('views.content.libraries.Form.visibilityDescription') }}
|
||||
</p>
|
||||
<select
|
||||
id="visibility-level"
|
||||
|
@ -156,16 +156,16 @@ const remove = async () => {
|
|||
class="ui submit button"
|
||||
type="submit"
|
||||
>
|
||||
<translate
|
||||
<span
|
||||
v-if="library"
|
||||
>
|
||||
Update library
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.content.libraries.Form.updateButton') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
Create library
|
||||
</translate>
|
||||
{{ $t('views.content.libraries.Form.createButton') }}
|
||||
</span>
|
||||
</button>
|
||||
<dangerous-button
|
||||
v-if="library"
|
||||
|
@ -173,20 +173,20 @@ const remove = async () => {
|
|||
class="ui right floated basic danger button"
|
||||
@confirm="remove"
|
||||
>
|
||||
Delete
|
||||
{{ $t('views.content.libraries.Form.deleteButton') }}
|
||||
<template #modal-header>
|
||||
<p>
|
||||
Delete this library?
|
||||
{{ $t('views.content.libraries.Form.deleteModalHeader') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
The library and all its tracks will be deleted. This can not be undone.
|
||||
{{ $t('views.content.libraries.Form.deleteModalMessage') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
Delete library
|
||||
{{ $t('views.content.libraries.Form.deleteModalConfirm') }}
|
||||
</div>
|
||||
</template>
|
||||
</dangerous-button>
|
||||
|
|
|
@ -46,7 +46,7 @@ const libraryCreated = (library: Library) => {
|
|||
:class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"
|
||||
>
|
||||
<div class="ui text loader">
|
||||
Loading Libraries…
|
||||
{{ $t('views.content.libraries.Home.loadingLibraries') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -54,11 +54,11 @@ const libraryCreated = (library: Library) => {
|
|||
class="ui text container"
|
||||
>
|
||||
<h1 class="ui header">
|
||||
My libraries
|
||||
{{ $t('views.content.libraries.Home.ownLibrariesHeader') }}
|
||||
</h1>
|
||||
|
||||
<p v-if="libraries.length == 0">
|
||||
Looks like you don't have a library, it's time to create one.
|
||||
{{ $t('views.content.libraries.Home.emptyState') }}
|
||||
</p>
|
||||
<a
|
||||
:aria-expanded="!hiddenForm"
|
||||
|
@ -73,7 +73,7 @@ const libraryCreated = (library: Library) => {
|
|||
v-else
|
||||
class="minus icon"
|
||||
/>
|
||||
Create a new library
|
||||
{{ $t('views.content.libraries.Home.createLibraryLink') }}
|
||||
</a>
|
||||
<library-form
|
||||
v-if="!hiddenForm"
|
||||
|
|
|
@ -53,14 +53,14 @@ const purgeErroredFiles = () => purge('errored')
|
|||
<template>
|
||||
<div class="ui segment">
|
||||
<h3 class="ui header">
|
||||
Current usage
|
||||
{{ $t('views.content.libraries.Quota.currentUsageHeader') }}
|
||||
</h3>
|
||||
<div
|
||||
v-if="isLoading"
|
||||
:class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"
|
||||
>
|
||||
<div class="ui text loader">
|
||||
Loading usage data…
|
||||
{{ $t('views.content.libraries.Quota.loadingMessage') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -72,19 +72,14 @@ const purgeErroredFiles = () => purge('errored')
|
|||
:style="{width: `${progress}%`}"
|
||||
>
|
||||
<div class="progress">
|
||||
{{ progress }}%
|
||||
{{ $t('views.content.libraries.Quota.percentUsed', {progress: progress}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="quotaStatus"
|
||||
class="label"
|
||||
>
|
||||
<translate
|
||||
|
||||
:translate-params="{max: humanSize(quotaStatus.max * 1000 * 1000), current: humanSize(quotaStatus.current * 1000 * 1000)}"
|
||||
>
|
||||
%{ current } used on %{ max } allowed
|
||||
</translate>
|
||||
{{ $t('views.content.libraries.Quota.currentUsage', {max: humanSize(quotaStatus.max * 1000 * 1000), current: humanSize(quotaStatus.current * 1000 * 1000)}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui hidden divider" />
|
||||
|
@ -101,7 +96,7 @@ const purgeErroredFiles = () => purge('errored')
|
|||
{{ humanSize(quotaStatus.pending * 1000 * 1000) }}
|
||||
</div>
|
||||
<div class="label">
|
||||
Pending files
|
||||
{{ $t('views.content.libraries.Quota.pendingLabel') }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -109,27 +104,27 @@ const purgeErroredFiles = () => purge('errored')
|
|||
class="ui basic primary tiny button"
|
||||
:to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'pending'}])}}"
|
||||
>
|
||||
View files
|
||||
{{ $t('views.content.libraries.Quota.viewFilesLink') }}
|
||||
</router-link>
|
||||
|
||||
<dangerous-button
|
||||
class="ui basic tiny button"
|
||||
:action="purgePendingFiles"
|
||||
>
|
||||
Purge
|
||||
{{ $t('views.content.libraries.Quota.purgeButton') }}
|
||||
<template #modal-header>
|
||||
<p>
|
||||
Purge pending files?
|
||||
{{ $t('views.content.libraries.Quota.purgePendingModalHeader') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota.
|
||||
{{ $t('views.content.libraries.Quota.purgePendingModalMessage') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
Purge
|
||||
{{ $t('views.content.libraries.Quota.purgeButton') }}
|
||||
</div>
|
||||
</template>
|
||||
</dangerous-button>
|
||||
|
@ -144,7 +139,7 @@ const purgeErroredFiles = () => purge('errored')
|
|||
{{ humanSize(quotaStatus.skipped * 1000 * 1000) }}
|
||||
</div>
|
||||
<div class="label">
|
||||
Skipped files
|
||||
{{ $t('views.content.libraries.Quota.skippedLabel') }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -152,26 +147,26 @@ const purgeErroredFiles = () => purge('errored')
|
|||
class="ui basic primary tiny button"
|
||||
:to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'skipped'}])}}"
|
||||
>
|
||||
View files
|
||||
{{ $t('views.content.libraries.Quota.viewFilesLink') }}
|
||||
</router-link>
|
||||
<dangerous-button
|
||||
class="ui basic tiny button"
|
||||
:action="purgeSkippedFiles"
|
||||
>
|
||||
Purge
|
||||
{{ $t('views.content.libraries.Quota.purgeButton') }}
|
||||
<template #modal-header>
|
||||
<p>
|
||||
Purge skipped files?
|
||||
{{ $t('views.content.libraries.Quota.purgeSkippedModalHeader') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota.
|
||||
{{ $t('views.content.libraries.Quota.purgeSkippedModalMessage') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
Purge
|
||||
{{ $t('views.content.libraries.Quota.purgeButton') }}
|
||||
</div>
|
||||
</template>
|
||||
</dangerous-button>
|
||||
|
@ -186,7 +181,7 @@ const purgeErroredFiles = () => purge('errored')
|
|||
{{ humanSize(quotaStatus.errored * 1000 * 1000) }}
|
||||
</div>
|
||||
<div class="label">
|
||||
Errored files
|
||||
{{ $t('views.content.libraries.Quota.erroredLabel') }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -194,26 +189,26 @@ const purgeErroredFiles = () => purge('errored')
|
|||
class="ui basic primary tiny button"
|
||||
:to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'errored'}])}}"
|
||||
>
|
||||
View files
|
||||
{{ $t('views.content.libraries.Quota.viewFilesLink') }}
|
||||
</router-link>
|
||||
<dangerous-button
|
||||
class="ui basic tiny button"
|
||||
:action="purgeErroredFiles"
|
||||
>
|
||||
Purge
|
||||
{{ $t('views.content.libraries.Quota.purgeButton') }}
|
||||
<template #modal-header>
|
||||
<p>
|
||||
Purge errored files?
|
||||
{{ $t('views.content.libraries.Quota.purgeErroredModalHeader') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota.
|
||||
{{ $t('views.content.libraries.Quota.purgeErroredModalMessage') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
Purge
|
||||
{{ $t('views.content.libraries.Quota.purgeButton') }}
|
||||
</div>
|
||||
</template>
|
||||
</dangerous-button>
|
||||
|
|
|
@ -50,8 +50,8 @@ const radioPlayable = computed(() => (
|
|||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
tooltips: {
|
||||
me: t('This library is private and your approval from its owner is needed to access its content'),
|
||||
everyone: t('This library is public and you can access its content freely')
|
||||
me: t('views.content.remote.Card.privateTooltip'),
|
||||
everyone: t('views.content.remote.Card.publicTooltip')
|
||||
}
|
||||
}))
|
||||
|
||||
|
@ -65,8 +65,8 @@ const launchScan = async () => {
|
|||
store.commit('ui/addMessage', {
|
||||
date: new Date(),
|
||||
content: response.data.status === 'skipped'
|
||||
? t('Scan skipped (previous scan is too recent)')
|
||||
: t('Scan launched')
|
||||
? t('views.content.remote.Card.scanSkipped')
|
||||
: t('views.content.remote.Card.scanLaunched')
|
||||
})
|
||||
} catch (error) {
|
||||
useErrorHandler(error as Error)
|
||||
|
@ -82,8 +82,7 @@ const follow = async () => {
|
|||
} catch (error) {
|
||||
console.error(error)
|
||||
store.commit('ui/addMessage', {
|
||||
// TODO (wvffle): Translate
|
||||
content: 'Cannot follow remote library: ' + error,
|
||||
content: t('views.content.remote.Card.followError', { error }),
|
||||
date: new Date()
|
||||
})
|
||||
}
|
||||
|
@ -100,8 +99,7 @@ const unfollow = async () => {
|
|||
}
|
||||
} catch (error) {
|
||||
store.commit('ui/addMessage', {
|
||||
// TODO (wvffle): Translate
|
||||
content: 'Cannot unfollow remote library: ' + error,
|
||||
content: t('views.content.remote.Card.unfollowError', { error }),
|
||||
date: new Date()
|
||||
})
|
||||
}
|
||||
|
@ -193,14 +191,7 @@ watch(showScan, (shouldShow) => {
|
|||
</div>
|
||||
<div class="meta">
|
||||
<i class="music icon" />
|
||||
<translate
|
||||
|
||||
:translate-params="{count: library.uploads_count}"
|
||||
:translate-n="library.uploads_count"
|
||||
translate-plural="%{ count } tracks"
|
||||
>
|
||||
%{ count } track
|
||||
</translate>
|
||||
{{ $t('views.content.remote.Card.trackCount', {count: library.uploads_count}) }}
|
||||
</div>
|
||||
<div
|
||||
v-if="displayScan && latestScan"
|
||||
|
@ -208,35 +199,30 @@ watch(showScan, (shouldShow) => {
|
|||
>
|
||||
<template v-if="latestScan.status === 'pending'">
|
||||
<i class="hourglass icon" />
|
||||
Scan pending
|
||||
{{ $t('views.content.remote.Card.scanPending') }}
|
||||
</template>
|
||||
<template v-if="latestScan.status === 'scanning'">
|
||||
<i class="loading spinner icon" />
|
||||
<translate
|
||||
|
||||
:translate-params="{progress: scanProgress}"
|
||||
>
|
||||
Scanning… (%{ progress }%)
|
||||
</translate>
|
||||
{{ $t('views.content.remote.Card.scanProgress', {progress: scanProgress}) }}
|
||||
</template>
|
||||
<template v-else-if="latestScan.status === 'errored'">
|
||||
<i class="dangerdownload icon" />
|
||||
Problem during scanning
|
||||
{{ $t('views.content.remote.Card.scanFailure') }}
|
||||
</template>
|
||||
<template v-else-if="latestScan.status === 'finished' && latestScan.errored_files === 0">
|
||||
<i class="success download icon" />
|
||||
Scanned
|
||||
{{ $t('views.content.remote.Card.scanSuccess') }}
|
||||
</template>
|
||||
<template v-else-if="latestScan.status === 'finished' && latestScan.errored_files > 0">
|
||||
<i class="warning download icon" />
|
||||
Scanned with errors
|
||||
{{ $t('views.content.remote.Card.scanPartialSuccess') }}
|
||||
</template>
|
||||
<a
|
||||
href=""
|
||||
class="link right floated"
|
||||
@click.prevent="showScan = !showScan"
|
||||
>
|
||||
Details
|
||||
{{ $t('views.content.remote.Card.scanDetails') }}
|
||||
<i
|
||||
v-if="showScan"
|
||||
class="angle down icon"
|
||||
|
@ -248,9 +234,9 @@ watch(showScan, (shouldShow) => {
|
|||
</a>
|
||||
<div v-if="showScan">
|
||||
<template v-if="latestScan.modification_date">
|
||||
Last update:<human-date :date="latestScan.modification_date" /><br>
|
||||
{{ $t('views.content.remote.Card.lastUpdate') }}<human-date :date="latestScan.modification_date" /><br>
|
||||
</template>
|
||||
Failed tracks: {{ latestScan.errored_files }}
|
||||
{{ $t('views.content.remote.Card.failedTracks', {tracks: latestScan.errored_files}) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -262,7 +248,7 @@ watch(showScan, (shouldShow) => {
|
|||
class="right floated link"
|
||||
@click.prevent="launchScan"
|
||||
>
|
||||
Scan now <i class="paper plane icon" />
|
||||
{{ $t('views.content.remote.Card.scanNowButton') }}<i class="paper plane icon" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -278,7 +264,7 @@ watch(showScan, (shouldShow) => {
|
|||
>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label :for="library.fid">Sharing link</label>
|
||||
<label :for="library.fid">{{ $t('views.content.remote.Card.sharingLinkLabel') }}</label>
|
||||
<copy-input
|
||||
:id="library.fid"
|
||||
:button-classes="'basic'"
|
||||
|
@ -302,20 +288,20 @@ watch(showScan, (shouldShow) => {
|
|||
:class="['ui', 'success', {'loading': isLoadingFollow}, 'button']"
|
||||
@click="follow()"
|
||||
>
|
||||
Follow
|
||||
{{ $t('views.content.remote.Card.followButton') }}
|
||||
</button>
|
||||
<template v-else-if="!library.follow.approved">
|
||||
<button
|
||||
class="ui disabled button"
|
||||
>
|
||||
<i class="hourglass icon" />
|
||||
Follow request pending approval
|
||||
{{ $t('views.content.remote.Card.pendingApprovalButton') }}
|
||||
</button>
|
||||
<button
|
||||
class="ui button"
|
||||
@click="unfollow"
|
||||
>
|
||||
Cancel follow request
|
||||
{{ $t('views.content.remote.Card.cancelFollowButton') }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else-if="library.follow.approved">
|
||||
|
@ -323,22 +309,22 @@ watch(showScan, (shouldShow) => {
|
|||
:class="['ui', 'button']"
|
||||
:action="unfollow"
|
||||
>
|
||||
Unfollow
|
||||
{{ $t('views.content.remote.Card.unfollowButton') }}
|
||||
<template #modal-header>
|
||||
<p>
|
||||
Unfollow this library?
|
||||
{{ $t('views.content.remote.Card.unfollowModalHeader') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<div>
|
||||
<p>
|
||||
By unfollowing this library, you loose access to its content.
|
||||
{{ $t('views.content.remote.Card.unfollowModalMessage') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
Unfollow
|
||||
{{ $t('views.content.remote.Card.unfollowButton') }}
|
||||
</div>
|
||||
</template>
|
||||
</dangerous-button>
|
||||
|
|
|
@ -47,7 +47,7 @@ const scanResult = ref()
|
|||
:class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"
|
||||
>
|
||||
<div class="ui text loader">
|
||||
Loading remote libraries…
|
||||
{{ $t('views.content.remote.Home.loadingMessage') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
@ -55,10 +55,10 @@ const scanResult = ref()
|
|||
class="ui text container"
|
||||
>
|
||||
<h1 class="ui header">
|
||||
Remote libraries
|
||||
{{ $t('views.content.remote.Home.remoteLibrariesHeader') }}
|
||||
</h1>
|
||||
<p>
|
||||
Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access.
|
||||
{{ $t('views.content.remote.Home.remoteLibrariesDescription') }}
|
||||
</p>
|
||||
<scan-form @scanned="scanResult = $event" />
|
||||
<div class="ui hidden divider" />
|
||||
|
@ -74,14 +74,15 @@ const scanResult = ref()
|
|||
</div>
|
||||
<template v-if="existingFollows && existingFollows.count > 0">
|
||||
<h2>
|
||||
Known libraries
|
||||
{{ $t('views.content.remote.Home.knownLibrariesHeader') }}
|
||||
</h2>
|
||||
<a
|
||||
href=""
|
||||
class="discrete link"
|
||||
@click.prevent="fetchData"
|
||||
>
|
||||
<i :class="['ui', 'circular', 'refresh', 'icon']" /> Refresh
|
||||
<i :class="['ui', 'circular', 'refresh', 'icon']" />
|
||||
{{ $t('views.content.remote.Home.refreshButton') }}
|
||||
</a>
|
||||
<div class="ui hidden divider" />
|
||||
<div class="ui two cards">
|
||||
|
|
|
@ -15,8 +15,8 @@ const emit = defineEmits<Events>()
|
|||
const { t } = useI18n()
|
||||
|
||||
const labels = computed(() => ({
|
||||
placeholder: t('Enter a library URL'),
|
||||
submitLibrarySearch: t('Submit search')
|
||||
placeholder: t('views.content.remote.ScanForm.placeholder'),
|
||||
submitLibrarySearch: t('views.content.remote.ScanForm.submitLibrarySearch')
|
||||
}))
|
||||
|
||||
const errors = ref([] as string[])
|
||||
|
@ -49,7 +49,7 @@ const scan = async () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
Could not fetch remote library
|
||||
{{ $t('views.content.remote.ScanForm.failureHeader') }}
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li
|
||||
|
@ -61,7 +61,7 @@ const scan = async () => {
|
|||
</ul>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<label for="library-search">Search a remote library</label>
|
||||
<label for="library-search">{{ $t('views.content.remote.ScanForm.searchLabel') }}</label>
|
||||
<div :class="['ui', 'action', {loading: isLoading}, 'input']">
|
||||
<input
|
||||
id="library-search"
|
||||
|
|
|
@ -23,16 +23,16 @@ defineProps<Props>()
|
|||
<template #empty-state>
|
||||
<empty-state>
|
||||
<p>
|
||||
<translate
|
||||
<span
|
||||
v-if="isOwner"
|
||||
>
|
||||
This library is empty, you should upload something in it!
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.library.DetailAlbums.ownerEmptyState') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
You may need to follow this library to see its content.
|
||||
</translate>
|
||||
{{ $t('views.library.DetailAlbums.viewerEmptyState') }}
|
||||
</span>
|
||||
</p>
|
||||
</empty-state>
|
||||
</template>
|
||||
|
|
|
@ -32,16 +32,16 @@ defineProps<Props>()
|
|||
<template #empty-state>
|
||||
<empty-state>
|
||||
<p>
|
||||
<translate
|
||||
<span
|
||||
v-if="isOwner"
|
||||
>
|
||||
This library is empty, you should upload something in it!
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.library.DetailOverview.ownerEmptyState') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
You may need to follow this library to see its content.
|
||||
</translate>
|
||||
{{ $t('views.library.DetailOverview.viewerEmptyState') }}
|
||||
</span>
|
||||
</p>
|
||||
</empty-state>
|
||||
</template>
|
||||
|
|
|
@ -22,16 +22,16 @@ defineProps<Props>()
|
|||
<template #empty-state>
|
||||
<empty-state>
|
||||
<p>
|
||||
<translate
|
||||
<span
|
||||
v-if="isOwner"
|
||||
>
|
||||
This library is empty, you should upload something in it!
|
||||
</translate>
|
||||
<translate
|
||||
{{ $t('views.library.DetailTracks.ownerEmptyState') }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
You may need to follow this library to see its content.
|
||||
</translate>
|
||||
{{ $t('views.library.DetailTracks.viewerEmptyState') }}
|
||||
</span>
|
||||
</p>
|
||||
</empty-state>
|
||||
</template>
|
||||
|
|
|
@ -59,20 +59,20 @@ const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
|
|||
/>
|
||||
<div class="ui hidden divider" />
|
||||
<h2 class="ui header">
|
||||
Library contents
|
||||
{{ $t('views.library.Edit.libraryContentsHeader') }}
|
||||
</h2>
|
||||
<library-files-table :filters="{ library: object.uuid }" />
|
||||
|
||||
<div class="ui hidden divider" />
|
||||
<h2 class="ui header">
|
||||
Followers
|
||||
{{ $t('views.library.Edit.followersHeader') }}
|
||||
</h2>
|
||||
<div
|
||||
v-if="isLoading"
|
||||
:class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"
|
||||
>
|
||||
<div class="ui text loader">
|
||||
Loading followers…
|
||||
{{ $t('views.library.Edit.loadingFollowers') }}
|
||||
</div>
|
||||
</div>
|
||||
<table
|
||||
|
@ -82,16 +82,16 @@ const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
|
|||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
User
|
||||
{{ $t('views.library.Edit.userTableHeader') }}
|
||||
</th>
|
||||
<th>
|
||||
Date
|
||||
{{ $t('views.library.Edit.dateTableHeader') }}
|
||||
</th>
|
||||
<th>
|
||||
Status
|
||||
{{ $t('views.library.Edit.statusTableHeader') }}
|
||||
</th>
|
||||
<th>
|
||||
Action
|
||||
{{ $t('views.library.Edit.actionTableHeader') }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -106,19 +106,19 @@ const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
|
|||
v-if="follow.approved === null"
|
||||
:class="['ui', 'warning', 'basic', 'label']"
|
||||
>
|
||||
Pending approval
|
||||
{{ $t('views.library.Edit.pendingStatus') }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="follow.approved === true"
|
||||
:class="['ui', 'success', 'basic', 'label']"
|
||||
>
|
||||
Accepted
|
||||
{{ $t('views.library.Edit.acceptedStatus') }}
|
||||
</span>
|
||||
<span
|
||||
v-else-if="follow.approved === false"
|
||||
:class="['ui', 'danger', 'basic', 'label']"
|
||||
>
|
||||
Rejected
|
||||
{{ $t('views.library.Edit.rejectedStatus') }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -127,20 +127,22 @@ const updateApproved = async (follow: LibraryFollow, approved: boolean) => {
|
|||
:class="['ui', 'mini', 'icon', 'labeled', 'success', 'button']"
|
||||
@click="updateApproved(follow, true)"
|
||||
>
|
||||
<i class="ui check icon" /> Accept
|
||||
<i class="ui check icon" />
|
||||
{{ $t('views.library.Edit.acceptButton') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="follow.approved === null || follow.approved === true"
|
||||
:class="['ui', 'mini', 'icon', 'labeled', 'danger', 'button']"
|
||||
@click="updateApproved(follow, false)"
|
||||
>
|
||||
<i class="ui x icon" /> Reject
|
||||
<i class="ui x icon" />
|
||||
{{ $t('views.library.Edit.rejectButton') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p v-else>
|
||||
Nobody is following this library
|
||||
{{ $t('views.library.Edit.noFollowers') }}
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
@ -36,16 +36,16 @@ const isPlayable = computed(() => (object.value?.uploads_count ?? 0) > 0 && (
|
|||
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: t('Library'),
|
||||
title: t('views.library.LibraryBase.title'),
|
||||
visibility: {
|
||||
me: t('Private'),
|
||||
instance: t('Restricted'),
|
||||
everyone: t('Public')
|
||||
me: t('views.library.LibraryBase.privateVisibility'),
|
||||
instance: t('views.library.LibraryBase.instanceVisibility'),
|
||||
everyone: t('views.library.LibraryBase.publicVisibility')
|
||||
},
|
||||
tooltips: {
|
||||
me: t('This library is private and your approval from its owner is needed to access its content'),
|
||||
instance: t('This library is restricted to users on this pod only'),
|
||||
everyone: t('This library is public and you can access its content freely')
|
||||
me: t('views.library.LibraryBase.privateTooltip'),
|
||||
instance: t('views.library.LibraryBase.instanceTooltip'),
|
||||
everyone: t('views.library.LibraryBase.publicTooltip')
|
||||
}
|
||||
}))
|
||||
|
||||
|
@ -110,9 +110,7 @@ const updateUploads = (count: number) => {
|
|||
class="basic item"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate
|
||||
:translate-params="{domain: object.actor.domain}"
|
||||
>View on %{ domain }</translate>
|
||||
{{ $t('views.library.LibraryBase.domainViewLink', {domain: object.actor.domain}) }}
|
||||
</a>
|
||||
<div
|
||||
v-for="obj in getReportableObjects({library: object})"
|
||||
|
@ -131,7 +129,7 @@ const updateUploads = (count: number) => {
|
|||
:to="{name: 'manage.library.libraries.detail', params: {id: object.uuid}}"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
Open in moderation interface
|
||||
{{ $t('views.library.LibraryBase.moderationLink') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</button>
|
||||
|
@ -150,12 +148,7 @@ const updateUploads = (count: number) => {
|
|||
:actor="object.actor"
|
||||
:truncate-length="0"
|
||||
>
|
||||
<translate
|
||||
|
||||
:translate-params="{username: object.actor.full_username}"
|
||||
>
|
||||
Owned by %{ username }
|
||||
</translate>
|
||||
{{ $t('views.library.LibraryBase.ownerLink', {username: object.actor.full_username}) }}
|
||||
</actor-link>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -183,14 +176,7 @@ const updateUploads = (count: number) => {
|
|||
{{ labels.visibility.everyone }}
|
||||
</span> ·
|
||||
<i class="music icon" />
|
||||
<translate
|
||||
|
||||
:translate-params="{count: object.uploads_count}"
|
||||
:translate-n="object.uploads_count"
|
||||
translate-plural="%{ count } tracks"
|
||||
>
|
||||
%{ count } track
|
||||
</translate>
|
||||
{{ $t('views.library.LibraryBase.trackCount', {count: object.uploads_count}) }}
|
||||
<span v-if="object.size">
|
||||
· <i class="database icon" />
|
||||
{{ humanSize(object.size) }}
|
||||
|
@ -227,10 +213,10 @@ const updateUploads = (count: number) => {
|
|||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label for="copy-input">
|
||||
Sharing link
|
||||
{{ $t('views.library.LibraryBase.sharingLinkLabel') }}
|
||||
</label>
|
||||
<p>
|
||||
Share this link with other users so they can request access to this library by copy-pasting it in their pod search bar.
|
||||
{{ $t('views.library.LibraryBase.sharingLinkDescription') }}
|
||||
</p>
|
||||
<copy-input :value="object.fid" />
|
||||
</div>
|
||||
|
@ -245,21 +231,21 @@ const updateUploads = (count: number) => {
|
|||
|
||||
:to="{name: 'library.detail'}"
|
||||
>
|
||||
Artists
|
||||
{{ $t('views.library.LibraryBase.artistsLink') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="item"
|
||||
|
||||
:to="{name: 'library.detail.albums'}"
|
||||
>
|
||||
Albums
|
||||
{{ $t('views.library.LibraryBase.albumsLink') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
class="item"
|
||||
|
||||
:to="{name: 'library.detail.tracks'}"
|
||||
>
|
||||
Tracks
|
||||
{{ $t('views.library.LibraryBase.tracksLink') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="isOwner"
|
||||
|
@ -268,7 +254,7 @@ const updateUploads = (count: number) => {
|
|||
:to="{name: 'library.detail.upload'}"
|
||||
>
|
||||
<i class="upload icon" />
|
||||
Upload
|
||||
{{ $t('views.library.LibraryBase.uploadButton') }}
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="isOwner"
|
||||
|
@ -277,7 +263,7 @@ const updateUploads = (count: number) => {
|
|||
:to="{name: 'library.detail.edit'}"
|
||||
>
|
||||
<i class="pencil icon" />
|
||||
Edit
|
||||
{{ $t('views.library.LibraryBase.editButton') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="ui hidden divider" />
|
||||
|
|
|
@ -38,7 +38,7 @@ const tracks = computed(() => playlistTracks.value.map(({ track }, index) => ({
|
|||
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
playlist: t('Playlist')
|
||||
playlist: t('views.playlists.Detail.title')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
@ -93,13 +93,8 @@ const deletePlaylist = async () => {
|
|||
<div class="content">
|
||||
{{ playlist.name }}
|
||||
<div class="sub header">
|
||||
<translate
|
||||
translate-plural="Playlist containing %{ count } tracks, by %{ username }"
|
||||
:translate-n="playlist.tracks_count"
|
||||
:translate-params="{count: playlist.tracks_count, username: playlist.user.username}"
|
||||
>
|
||||
Playlist containing %{ count } track, by %{ username }
|
||||
</translate><br>
|
||||
{{ $t('views.playlists.Detail.trackCount', {count: tracks_count, username: playlist.user.username}) }}
|
||||
<br>
|
||||
<duration :seconds="playlist.duration" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -112,7 +107,7 @@ const deletePlaylist = async () => {
|
|||
:is-playable="playlist.is_playable"
|
||||
:tracks="tracks"
|
||||
>
|
||||
Play all
|
||||
{{ $t('views.playlists.Detail.playAllButton') }}
|
||||
</play-button>
|
||||
</div>
|
||||
<div class="ui buttons">
|
||||
|
@ -123,10 +118,10 @@ const deletePlaylist = async () => {
|
|||
>
|
||||
<i class="pencil icon" />
|
||||
<template v-if="edit">
|
||||
Stop Editing
|
||||
{{ $t('views.playlists.Detail.stopEditButton') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
Edit
|
||||
{{ $t('views.playlists.Detail.editButton') }}
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -137,31 +132,28 @@ const deletePlaylist = async () => {
|
|||
@click="showEmbedModal = !showEmbedModal"
|
||||
>
|
||||
<i class="code icon" />
|
||||
Embed
|
||||
{{ $t('views.playlists.Detail.embedButton') }}
|
||||
</button>
|
||||
<dangerous-button
|
||||
v-if="$store.state.auth.profile && playlist.user.id === $store.state.auth.profile.id"
|
||||
class="ui labeled danger icon button"
|
||||
:action="deletePlaylist"
|
||||
>
|
||||
<i class="trash icon" /> Delete
|
||||
<i class="trash icon" />
|
||||
{{ $t('views.playlists.Detail.deleteButton') }}
|
||||
<template #modal-header>
|
||||
<p
|
||||
v-translate="{playlist: playlist.name}"
|
||||
|
||||
:translate-params="{playlist: playlist.name}"
|
||||
>
|
||||
Do you want to delete the playlist "%{ playlist }"?
|
||||
<p>
|
||||
{{ $t('views.playlists.Detail.deleteModalHeader', {playlist: playlist.name}) }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
This will completely delete this playlist and cannot be undone.
|
||||
{{ $t('views.playlists.Detail.deleteModalMessage') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
Delete playlist
|
||||
{{ $t('views.playlists.Detail.deleteModalConfirm') }}
|
||||
</div>
|
||||
</template>
|
||||
</dangerous-button>
|
||||
|
@ -172,7 +164,7 @@ const deletePlaylist = async () => {
|
|||
v-model:show="showEmbedModal"
|
||||
>
|
||||
<h4 class="header">
|
||||
Embed this playlist on your website
|
||||
{{ $t('views.playlists.Detail.embedModalHeader') }}
|
||||
</h4>
|
||||
<div class="scrolling content">
|
||||
<div class="description">
|
||||
|
@ -184,7 +176,7 @@ const deletePlaylist = async () => {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic deny button">
|
||||
Cancel
|
||||
{{ $t('views.playlists.Detail.cancelButton') }}
|
||||
</button>
|
||||
</div>
|
||||
</semantic-modal>
|
||||
|
@ -199,7 +191,7 @@ const deletePlaylist = async () => {
|
|||
</template>
|
||||
<template v-else-if="tracks.length > 0">
|
||||
<h2>
|
||||
Tracks
|
||||
{{ $t('views.playlists.Detail.tracksHeader') }}
|
||||
</h2>
|
||||
<track-table
|
||||
:display-position="true"
|
||||
|
@ -213,14 +205,14 @@ const deletePlaylist = async () => {
|
|||
>
|
||||
<div class="ui icon header">
|
||||
<i class="list icon" />
|
||||
There are no tracks in this playlist yet
|
||||
{{ $t('views.playlists.Detail.emptyState') }}
|
||||
</div>
|
||||
<button
|
||||
class="ui success icon labeled button"
|
||||
@click="edit = !edit"
|
||||
>
|
||||
<i class="pencil icon" />
|
||||
Edit
|
||||
{{ $t('views.playlists.Detail.editButton') }}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -97,8 +97,8 @@ onMounted(() => $('.ui.dropdown').dropdown())
|
|||
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
playlists: t('Playlists'),
|
||||
searchPlaceholder: t('Enter playlist name…')
|
||||
playlists: t('views.playlists.List.playlistsHeader'),
|
||||
searchPlaceholder: t('views.playlists.List.searchPlaceholder')
|
||||
}))
|
||||
|
||||
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b)))
|
||||
|
@ -108,14 +108,14 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
<main v-title="labels.playlists">
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2 class="ui header">
|
||||
Browsing playlists
|
||||
{{ $t('views.playlists.List.browsePlaylistsHeader') }}
|
||||
</h2>
|
||||
<template v-if="$store.state.auth.authenticated">
|
||||
<button
|
||||
class="ui success button"
|
||||
@click="$store.commit('playlists/showModal', true)"
|
||||
>
|
||||
Manage your playlists
|
||||
{{ $t('views.playlists.List.manageButton') }}
|
||||
</button>
|
||||
<div class="ui hidden divider" />
|
||||
</template>
|
||||
|
@ -125,7 +125,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label for="playlists-search">Search</label>
|
||||
<label for="playlists-search">{{ $t('views.playlists.List.searchLabel') }}</label>
|
||||
<div class="ui action input">
|
||||
<input
|
||||
id="playlists-search"
|
||||
|
@ -137,14 +137,14 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
<button
|
||||
class="ui icon button"
|
||||
type="submit"
|
||||
:aria-label="t('Search')"
|
||||
:aria-label="t('views.playlists.List.searchLabel')"
|
||||
>
|
||||
<i class="search icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="playlists-ordering">Ordering</label>
|
||||
<label for="playlists-ordering">{{ $t('views.playlists.List.orderingLabel') }}</label>
|
||||
<select
|
||||
id="playlists-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -160,22 +160,22 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="playlists-ordering-direction">Order</label>
|
||||
<label for="playlists-ordering-direction">{{ $t('views.playlists.List.orderingDirectionLabel') }}</label>
|
||||
<select
|
||||
id="playlists-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
Ascending
|
||||
{{ $t('views.playlists.List.ascendingOrdering') }}
|
||||
</option>
|
||||
<option value="-">
|
||||
Descending
|
||||
{{ $t('views.playlists.List.descendingOrdering') }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="playlists-results">Results per page</label>
|
||||
<label for="playlists-results">{{ $t('views.playlists.List.resultsPerPage') }}</label>
|
||||
<select
|
||||
id="playlists-results"
|
||||
v-model="paginateBy"
|
||||
|
@ -204,7 +204,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
>
|
||||
<div class="ui icon header">
|
||||
<i class="list icon" />
|
||||
No results matching your query
|
||||
{{ $t('views.playlists.List.emptyState') }}
|
||||
</div>
|
||||
<button
|
||||
v-if="$store.state.auth.authenticated"
|
||||
|
@ -212,7 +212,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
@click="$store.commit('playlists/chooseTrack', null)"
|
||||
>
|
||||
<i class="list icon" />
|
||||
Create a playlist
|
||||
{{ $t('views.playlists.List.createPlaylistButton') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ui center aligned basic segment">
|
||||
|
|
|
@ -26,7 +26,7 @@ const page = ref(1)
|
|||
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: t('Radio')
|
||||
title: t('views.radios.Detail.title')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
@ -82,8 +82,7 @@ const deleteRadio = async () => {
|
|||
<div class="content">
|
||||
{{ radio.name }}
|
||||
<div class="sub header">
|
||||
Radio containing {{ totalTracks }} tracks,
|
||||
by <username :username="radio.user.username" />
|
||||
{{ $t('views.radios.Detail.radioSubheader', {tracks: totalTracks}) }}<username :username="radio.user.username" />
|
||||
</div>
|
||||
</div>
|
||||
</h2>
|
||||
|
@ -98,30 +97,26 @@ const deleteRadio = async () => {
|
|||
:to="{name: 'library.radios.edit', params: {id: radio.id}}"
|
||||
>
|
||||
<i class="pencil icon" />
|
||||
Edit…
|
||||
{{ $t('views.radios.Detail.editButton') }}
|
||||
</router-link>
|
||||
<dangerous-button
|
||||
class="ui labeled danger icon button"
|
||||
:action="deleteRadio"
|
||||
>
|
||||
<i class="trash icon" /> Delete
|
||||
<i class="trash icon" /> {{ $t('views.radios.Detail.deleteButton') }}
|
||||
<template #modal-header>
|
||||
<p
|
||||
v-translate="{radio: radio.name}"
|
||||
|
||||
:translate-params="{radio: radio.name}"
|
||||
>
|
||||
Do you want to delete the radio "%{ radio }"?
|
||||
<p>
|
||||
{{ $t('views.radios.Detail.deleteModalHeader', {radio: radio.name}) }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
This will completely delete this radio and cannot be undone.
|
||||
{{ $t('views.radios.Detail.deleteModalMessage') }}
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<p>
|
||||
Delete radio
|
||||
{{ $t('views.radios.Detail.deleteModalConfirm') }}
|
||||
</p>
|
||||
</template>
|
||||
</dangerous-button>
|
||||
|
@ -133,7 +128,7 @@ const deleteRadio = async () => {
|
|||
class="ui vertical stripe segment"
|
||||
>
|
||||
<h2>
|
||||
Tracks
|
||||
{{ $t('views.radios.Detail.tracksHeader') }}
|
||||
</h2>
|
||||
<track-table :tracks="tracks" />
|
||||
<div class="ui center aligned basic segment">
|
||||
|
@ -151,7 +146,7 @@ const deleteRadio = async () => {
|
|||
>
|
||||
<div class="ui icon header">
|
||||
<i class="rss icon" />
|
||||
No tracks have been added to this radio yet
|
||||
{{ $t('views.radios.Detail.emptyState') }}
|
||||
</div>
|
||||
<router-link
|
||||
v-if="$store.state.auth.username === radio?.user.username"
|
||||
|
@ -159,7 +154,7 @@ const deleteRadio = async () => {
|
|||
:to="{name: 'library.radios.edit', params: { id: radio?.id }}"
|
||||
>
|
||||
<i class="pencil icon" />
|
||||
Edit…
|
||||
{{ $t('views.radios.Detail.editButton') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</main>
|
||||
|
|
Loading…
Reference in New Issue