Add eslint rules for i18n

This commit is contained in:
wvffle 2022-09-10 16:31:48 +00:00 committed by Kasper Seweryn
parent ebea32faf9
commit edfbf94313
200 changed files with 1738 additions and 295018 deletions

15
.vscode/settings.json vendored
View File

@ -2,9 +2,20 @@
"python.defaultInterpreterPath": "/workspace/funkwhale/api/.venv/bin/python",
"python.testing.cwd": "/workspace/funkwhale/api",
"python.envFile": "/workspace/funkwhale/.gitpod/.env",
"python.testing.pytestArgs": ["--cov=funkwhale_api", "tests/"],
"python.testing.pytestArgs": [
"--cov=funkwhale_api",
"tests/"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"vitest.enable": true,
"vitest.commandLine": "yarn vitest"
"vitest.commandLine": "yarn vitest",
"i18n-ally.localesPaths": [
"front/src/locales"
],
"i18n-ally.pathMatcher": "*.json",
"i18n-ally.enabledFrameworks": [
"vue"
],
"i18n-ally.keystyle": "nested"
}

View File

@ -5,6 +5,7 @@ module.exports = {
},
extends: [
'plugin:vue/vue3-recommended',
'plugin:@intlify/vue-i18n/recommended',
'@vue/typescript/recommended',
'@vue/standard'
],
@ -32,6 +33,11 @@ module.exports = {
'no-redeclare': 'off',
'no-undef': 'off',
// NOTE: i18n
'@intlify/vue-i18n/valid-message-syntax': 'error',
'@intlify/vue-i18n/no-missing-keys': 'error',
'@intlify/vue-i18n/no-dynamic-keys': 'error',
// TODO (wvffle): Remove after VUI and #1618
'vue/multi-word-component-names': 'off',
'import/extensions': 'off',
@ -51,5 +57,11 @@ module.exports = {
'vue/comment-directive': 'off'
}
}
]
],
settings: {
'vue-i18n': {
localeDir: 'src/locales/*.{json,json5,yaml,yml}',
messageSyntaxVersion: '^9.0.0'
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -58,6 +58,8 @@
"vuex-router-sync": "5.0.0"
},
"devDependencies": {
"@intlify/eslint-plugin-vue-i18n": "^2.0.0",
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@types/diff": "5.0.2",
"@types/dompurify": "2.4.0",
"@types/howler": "2.2.7",

View File

@ -67,14 +67,10 @@ const headerStyle = computed(() => {
<div class="column" />
</div>
<h2 class="header">
<translate >
A social platform to enjoy and share music
</translate>
</h2>
<p>
<translate >
Funkwhale is a community-driven project that lets you listen and share music and audio within a decentralized, open network.
</translate>
</p>
</div>
</div>
@ -88,23 +84,14 @@ const headerStyle = computed(() => {
class="signup-form content"
>
<h3 class="header">
<translate >
Sign up
</translate>
</h3>
<template v-if="openRegistrations">
<p>
<translate >
Sign up now to keep a track of your favorites, create playlists, discover new content and much more!
</translate>
</p>
<p v-if="defaultUploadQuota">
<translate
:translate-params="{quota: defaultUploadQuota}"
>
Users on this pod also get %{ quota } of free storage to upload their own content!
</translate>
</p>
<signup-form
button-classes="success"
@ -121,7 +108,7 @@ const headerStyle = computed(() => {
rel="noopener"
href="https://funkwhale.audio/#get-started"
>
<translate >Find another pod</translate>
Find another pod
&nbsp;<i class="external alternate icon" />
</a>
</div>
@ -131,19 +118,13 @@ const headerStyle = computed(() => {
class="signup-form content"
>
<h3 class="header">
<translate >
Sign up
</translate>
<div class="ui positive message">
<div class="header">
<translate >
You're already signed in!
</translate>
</div>
<p>
<translate translate-contect="Content/About/Hello">
Hello
</translate> {{ $store.state.auth.username }}
Hello {{ $store.state.auth.username }}
</p>
</div>
</h3>
@ -164,9 +145,7 @@ const headerStyle = computed(() => {
id="description"
class="ui header"
>
<translate >
About this pod
</translate>
</h3>
<div
v-if="shortDescription"
@ -175,9 +154,7 @@ const headerStyle = computed(() => {
{{ shortDescription }}
</div>
<p v-else>
<translate >
No description available.
</translate>
</p>
<template v-if="stats">
@ -187,22 +164,14 @@ const headerStyle = computed(() => {
<span class="statistics-figure ui text">
<span class="ui big text"><strong>{{ stats.users.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br>
<translate
:translate-n="stats.users"
translate-plural="active users"
>active user</translate>
{{ $t('active user | active users', stats.users) }}
</span>
</div>
<div class="column">
<span class="statistics-figure ui text">
<span class="ui big text"><strong>{{ stats.hours.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br>
<translate
translate-context="Content/About/*"
:translate-n="stats.hours"
translate-plural="hours of music"
>hour of music</translate>
{{ $t('hour of music | hours of music', stats.hours) }}
</span>
</div>
</div>
@ -213,9 +182,7 @@ const headerStyle = computed(() => {
to="/about/pod"
class="ui fluid basic secondary button"
>
<translate >
Learn More
</translate>
</router-link>
</div>
</div>
@ -234,14 +201,10 @@ const headerStyle = computed(() => {
id="description"
class="ui header"
>
<translate >
Browse public content
</translate>
</h3>
<p>
<translate >
Listen to public albums and playlists shared on this pod.
</translate>
</p>
</div>
</router-link>
@ -255,11 +218,11 @@ const headerStyle = computed(() => {
id="description"
class="ui header"
>
<translate >Find another pod</translate>
Find another pod
&nbsp;<i class="external alternate icon" />
</h3>
<p>
<translate >Listen to public albums and playlists shared on this pod.</translate>
Listen to public albums and playlists shared on this pod.
</p>
</div>
</a>
@ -273,11 +236,11 @@ const headerStyle = computed(() => {
id="description"
class="ui header"
>
<translate >Find an app</translate>
Find an app
&nbsp;<i class="external alternate icon" />
</h3>
<p>
<translate >Use Funkwhale on other devices with our apps.</translate>
Use Funkwhale on other devices with our apps.
</p>
</div>
</a>
@ -287,9 +250,7 @@ const headerStyle = computed(() => {
to="/about/pod"
class="ui right floated basic secondary button"
>
<translate >
About this pod
</translate>
<i class="icon arrow right" />
</router-link>
</div>

View File

@ -99,42 +99,32 @@ const headerStyle = computed(() => {
to="/about/pod"
class="item"
>
<translate >
About this pod
</translate>
</router-link>
<router-link
to="/about/pod#rules"
class="item"
>
<translate >
Rules
</translate>
</router-link>
<router-link
to="/about/pod#terms"
class="item"
>
<translate >
Terms and privacy policy
</translate>
</router-link>
<router-link
to="/about/pod#features"
class="item"
>
<translate >
Features
</translate>
</router-link>
<router-link
v-if="stats"
to="/about/pod#statistics"
class="item"
>
<translate >
Statistics
</translate>
</router-link>
</div>
</div>
@ -144,63 +134,49 @@ const headerStyle = computed(() => {
id="description about-this-pod"
class="ui header"
>
<translate >
About this pod
</translate>
</h2>
<sanitized-html
v-if="longDescription"
:html="longDescription"
/>
<p v-else>
<translate >
No description available.
</translate>
</p>
<h3
id="rules"
class="ui header"
>
<translate >
Rules
</translate>
</h3>
<sanitized-html
v-if="rules"
:html="rules"
/>
<p v-else>
<translate >
No rules available.
</translate>
</p>
<h3
id="terms"
class="ui header"
>
<translate >
Terms and privacy policy
</translate>
</h3>
<sanitized-html
v-if="terms"
:html="terms"
/>
<p v-else>
<translate >
No terms available.
</translate>
</p>
<h3
id="features"
class="header"
>
<translate >
Features
</translate>
</h3>
<div class="features-container ui two column stackable grid">
<div class="column">
@ -208,9 +184,7 @@ const headerStyle = computed(() => {
<tbody>
<tr>
<td>
<translate >
Funkwhale version
</translate>
</td>
<td
v-if="version"
@ -225,15 +199,13 @@ const headerStyle = computed(() => {
class="right aligned"
>
<span class="features-status ui text">
<translate >N/A</translate>
N/A
</span>
</td>
</tr>
<tr>
<td>
<translate >
Federation
</translate>
</td>
<td
v-if="federationEnabled"
@ -241,7 +213,7 @@ const headerStyle = computed(() => {
>
<span class="features-status ui text">
<i class="check icon" />
<translate >Enabled</translate>
Enabled
</span>
</td>
<td
@ -250,15 +222,13 @@ const headerStyle = computed(() => {
>
<span class="features-status ui text">
<i class="x icon" />
<translate >Disabled</translate>
Disabled
</span>
</td>
</tr>
<tr>
<td>
<translate >
Allow-list
</translate>
</td>
<td
v-if="allowListEnabled"
@ -266,7 +236,7 @@ const headerStyle = computed(() => {
>
<span class="features-status ui text">
<i class="check icon" />
<translate >Enabled</translate>
Enabled
</span>
</td>
<td
@ -275,7 +245,7 @@ const headerStyle = computed(() => {
>
<span class="features-status ui text">
<i class="x icon" />
<translate >Disabled</translate>
Disabled
</span>
</td>
</tr>
@ -287,9 +257,7 @@ const headerStyle = computed(() => {
<tbody>
<tr>
<td>
<translate >
Anonymous access
</translate>
</td>
<td
v-if="anonymousCanListen"
@ -297,7 +265,7 @@ const headerStyle = computed(() => {
>
<span class="features-status ui text">
<i class="check icon" />
<translate >Enabled</translate>
Enabled
</span>
</td>
<td
@ -306,15 +274,13 @@ const headerStyle = computed(() => {
>
<span class="features-status ui text">
<i class="x icon" />
<translate >Disabled</translate>
Disabled
</span>
</td>
</tr>
<tr>
<td>
<translate >
Registrations
</translate>
</td>
<td
v-if="openRegistrations"
@ -322,7 +288,7 @@ const headerStyle = computed(() => {
>
<span class="features-status ui text">
<i class="check icon" />
<translate >Open</translate>
Open
</span>
</td>
<td
@ -331,15 +297,13 @@ const headerStyle = computed(() => {
>
<span class="features-status ui text">
<i class="x icon" />
<translate >Closed</translate>
Closed
</span>
</td>
</tr>
<tr>
<td>
<translate >
Upload quota
</translate>
</td>
<td
v-if="defaultUploadQuota"
@ -354,7 +318,7 @@ const headerStyle = computed(() => {
class="right aligned"
>
<span class="features-status ui text">
<translate >N/A</translate>
N/A
</span>
</td>
</tr>
@ -368,9 +332,7 @@ const headerStyle = computed(() => {
id="statistics"
class="header"
>
<translate >
Statistics
</translate>
</h3>
<div class="statistics-container">
<div
@ -380,11 +342,7 @@ const headerStyle = computed(() => {
<span class="statistics-figure ui text">
<span class="ui big text"><strong>{{ stats.hours.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br>
<translate
translate-context="Content/About/*"
:translate-n="stats.hours"
translate-plural="hours of music"
>hour of music</translate>
{{ $t('hour of music | hours of music', stats.hours) }}
</span>
</div>
<div
@ -394,11 +352,7 @@ const headerStyle = computed(() => {
<span class="statistics-figure ui text">
<span class="ui big text"><strong>{{ stats.artists.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br>
<translate
:translate-n="stats.artists"
translate-plural="artists"
>artist</translate>
{{ $t('artist | artists', stats.artists) }}
</span>
</div>
<div
@ -408,11 +362,7 @@ const headerStyle = computed(() => {
<span class="statistics-figure ui text">
<span class="ui big text"><strong>{{ stats.albums.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br>
<translate
:translate-n="stats.albums"
translate-plural="albums"
>album</translate>
{{ $t('album | albums', stats.albums) }}
</span>
</div>
<div
@ -422,11 +372,7 @@ const headerStyle = computed(() => {
<span class="statistics-figure ui text">
<span class="ui big text"><strong>{{ stats.tracks.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br>
<translate
:translate-n="stats.tracks"
translate-plural="tracks"
>track</translate>
{{ $t('track | tracks', stats.tracks) }}
</span>
</div>
<div
@ -436,11 +382,7 @@ const headerStyle = computed(() => {
<span class="statistics-figure ui text">
<span class="ui big text"><strong>{{ stats.users.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br>
<translate
:translate-n="stats.users"
translate-plural="active users"
>active user</translate>
{{ $t('active user | active users', stats.users) }}
</span>
</div>
<div
@ -450,11 +392,7 @@ const headerStyle = computed(() => {
<span class="statistics-figure ui text">
<span class="ui big text"><strong>{{ stats.listenings.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br>
<translate
:translate-n="stats.listenings"
translate-plural="listenings"
>listening</translate>
{{ $t('listening | listenings', stats.listenings) }}
</span>
</div>
</div>
@ -465,18 +403,13 @@ const headerStyle = computed(() => {
id="contact"
class="ui header"
>
<translate >
Contact
</translate>
</h3>
<a
v-if="contactEmail"
:href="`mailto:${contactEmail}`"
>
<translate
:translate-params="{ email: contactEmail }"
>Send us an email: {{ contactEmail }}</translate>
{{ $t('Send us an email: %{ contactEmail }', { contactEmail }) }}
</a>
</template>
@ -487,9 +420,7 @@ const headerStyle = computed(() => {
class="ui left floated basic secondary button"
>
<i class="icon arrow left" />
<translate >
Introduction
</translate>
</router-link>
</div>
</div>

View File

@ -70,12 +70,9 @@ whenever(() => store.state.auth.authenticated, () => {
>
<div class="segment-content">
<h1 class="ui center aligned large header">
<translate
:translate-params="{podName: podName}"
tag="span"
>
Welcome to %{ podName }!
</translate>
<span>
{{ $t('Welcome to %{ podName }!', { podName }) }}
</span>
<div
v-if="shortDescription"
class="sub header"
@ -89,9 +86,7 @@ whenever(() => store.state.auth.authenticated, () => {
<div class="ui stackable grid">
<div class="ten wide column">
<h2 class="header">
<translate >
About this Funkwhale pod
</translate>
</h2>
<div
id="pod"
@ -100,9 +95,7 @@ whenever(() => store.state.auth.authenticated, () => {
<div class="ui stackable grid">
<div class="eight wide column">
<p v-if="!longDescription">
<translate >
No description available.
</translate>
</p>
<template v-if="longDescription || rules">
<sanitized-html
@ -125,9 +118,7 @@ whenever(() => store.state.auth.authenticated, () => {
class="ui link"
:to="{name: 'about'}"
>
<translate >
Learn more
</translate>
</router-link>
</div>
</div>
@ -142,9 +133,7 @@ whenever(() => store.state.auth.authenticated, () => {
class="ui link"
:to="{name: 'about', hash: '#rules'}"
>
<translate >
Server rules
</translate>
</router-link>
</div>
</div>
@ -154,36 +143,20 @@ whenever(() => store.state.auth.authenticated, () => {
<div class="eight wide column">
<template v-if="stats">
<h3 class="sub header">
<translate >
Statistics
</translate>
</h3>
<p>
<i class="user icon" /><translate
:translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }"
:translate-n="stats.users"
translate-plural="%{ count } active users"
>
%{ count } active user
</translate>
<i class="user icon" />
{{ $t('%{ users } active user | %{ users } active users', stats, stats.users) }}
</p>
<p>
<i class="music icon" /><translate
translate-context="Content/Home/Stat"
:translate-params="{count: stats.hours.toLocaleString($store.state.ui.momentLocale)}"
:translate-n="stats.hours"
translate-plural="%{ count } hours of music"
>
%{ count } hour of music
</translate>
<i class="music icon" />
{{ $t('%{ hours } hour of music | %{ hours } hours of music', stats, stats.hours) }}
</p>
</template>
<template v-if="contactEmail">
<h3 class="sub header">
<translate >
Contact
</translate>
</h3>
<i class="at icon" />
<a :href="`mailto:${contactEmail}`">{{ contactEmail }}</a>
@ -206,30 +179,26 @@ whenever(() => store.state.auth.authenticated, () => {
<div class="ui stackable grid">
<div class="four wide column">
<h3 class="header">
<translate >
About Funkwhale
</translate>
</h3>
<translate tag="p">
<p>
This pod runs Funkwhale, a community-driven project that lets you listen and share music and audio within a decentralized, open network.
</translate>
<translate tag="p">
</p>
<p>
Funkwhale is free and developed by a friendly community of volunteers.
</translate>
</p>
<a
target="_blank"
rel="noopener"
href="https://funkwhale.audio"
>
<i class="external alternate icon" />
<translate >Visit funkwhale.audio</translate>
Visit funkwhale.audio
</a>
</div>
<div class="four wide column">
<h3 class="header">
<translate >
Log In
</translate>
</h3>
<login-form
button-classes="success"
@ -239,23 +208,14 @@ whenever(() => store.state.auth.authenticated, () => {
</div>
<div class="four wide column">
<h3 class="header">
<translate >
Sign up
</translate>
</h3>
<template v-if="openRegistrations">
<p>
<translate >
Sign up now to keep track of your favorites, create playlists, discover new content and much more!
</translate>
</p>
<p v-if="defaultUploadQuota">
<translate
:translate-params="{quota: humanSize(defaultUploadQuota * 1000 * 1000)}"
>
Users on this pod also get %{ quota } of free storage to upload their own content!
</translate>
{{ $t('Users on this pod also get %{ quota } of free storage to upload their own content!', { quota: humanSize(defaultUploadQuota * 1000 * 1000) }) }}
</p>
<signup-form
button-classes="success"
@ -272,16 +232,14 @@ whenever(() => store.state.auth.authenticated, () => {
href="https://funkwhale.audio/#get-started"
>
<i class="external alternate icon" />
<translate >Find another pod</translate>
Find another pod
</a>
</div>
</div>
<div class="four wide column">
<h3 class="header">
<translate >
Useful links
</translate>
</h3>
<div class="ui relaxed list">
<div class="item">
@ -292,14 +250,10 @@ whenever(() => store.state.auth.authenticated, () => {
class="header"
to="/library"
>
<translate >
Browse public content
</translate>
</router-link>
<div class="description">
<translate >
Listen to public albums and playlists shared on this pod
</translate>
</div>
</div>
</div>
@ -312,12 +266,10 @@ whenever(() => store.state.auth.authenticated, () => {
target="_blank"
rel="noopener"
>
<translate >Mobile apps</translate>
Mobile apps
</a>
<div class="description">
<translate >
Use Funkwhale on other devices with our apps
</translate>
</div>
</div>
</div>
@ -330,12 +282,10 @@ whenever(() => store.state.auth.authenticated, () => {
target="_blank"
rel="noopener"
>
<translate >User guides</translate>
User guides
</a>
<div class="description">
<translate >
Discover everything you need to know about Funkwhale and its features
</translate>
</div>
</div>
</div>
@ -352,22 +302,16 @@ whenever(() => store.state.auth.authenticated, () => {
:limit="10"
>
<template #title>
<translate >
Recently added albums
</translate>
</template>
<router-link to="/library">
<translate >
View more
</translate>
<div class="ui hidden divider" />
</router-link>
</album-widget>
<div class="ui hidden section divider" />
<h3 class="ui header">
<translate >
New channels
</translate>
</h3>
<channels-widget
:show-modification-date="true"

View File

@ -20,15 +20,11 @@ const labels = computed(() => ({
<h1 class="ui huge header">
<i class="warning icon" />
<div class="content">
<translate >
Page not found!
</translate>
</div>
</h1>
<p>
<translate >
Sorry, the page you asked for does not exist:
</translate>
</p>
<a :href="path">{{ path }}</a>
<div class="ui hidden divider" />
@ -36,9 +32,7 @@ const labels = computed(() => ({
class="ui icon labeled right button"
to="/"
>
<translate >
Go to home page
</translate>
<i class="right arrow icon" />
</router-link>
</div>

View File

@ -5,6 +5,7 @@ import { whenever, watchDebounced, useCurrentElement, useScrollLock, useFullscre
import { nextTick, ref, computed, watchEffect, onMounted } from 'vue'
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useStore } from '~/store'
import { usePlayer } from '~/composables/audio/player'
@ -17,10 +18,6 @@ import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
import PlayerControls from '~/components/audio/PlayerControls.vue'
import MilkDrop from '~/components/audio/visualizer/MilkDrop.vue'
import { whenever, watchDebounced, useCurrentElement, useScrollLock } from '@vueuse/core'
import { useI18n } from 'vue-i18n'
import useQueue from '~/composables/audio/useQueue'
import usePlayer from '~/composables/audio/usePlayer'
import VirtualList from '~/components/vui/list/VirtualList.vue'
import QueueItem from '~/components/QueueItem.vue'
@ -301,20 +298,14 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
class="ui small warning message"
>
<h3 class="header">
<translate >
The track cannot be loaded
</translate>
</h3>
<p v-if="hasNext && isPlaying">
<translate >
The next track will play automatically in a few seconds
</translate>
<i class="loading spinner icon" />
</p>
<p>
<translate >
You may have a connectivity issue.
</translate>
</p>
</div>
<div
@ -413,17 +404,13 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
class="ui right floated basic button"
@click="$store.commit('ui/queueFocused', null)"
>
<translate >
Close
</translate>
</button>
<button
class="ui right floated basic button danger"
@click="clear"
>
<translate >
Clear
</translate>
</button>
{{ labels.queue }}
<div class="sub header">
@ -477,22 +464,17 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
>
<div class="content">
<h3 class="header">
<i class="feed icon" /> <translate >
<i class="feed icon" />
You have a radio playing
</translate>
</h3>
<p>
<translate >
New tracks will be appended here automatically.
</translate>
</p>
<button
class="ui basic primary button"
@click="$store.dispatch('radios/stop')"
>
<translate >
Stop radio
</translate>
</button>
</div>
</div>

View File

@ -172,9 +172,7 @@ watch(() => props.initialId, () => {
@click.prevent="type = 'rss'"
>
<i class="feed icon" />
<translate >
RSS
</translate>
</button>
<div class="or" />
<button
@ -182,9 +180,7 @@ watch(() => props.initialId, () => {
@click.prevent="type = 'artists'"
>
<i class="globe icon" />
<translate >
Fediverse
</translate>
</button>
</div>
<div v-else>
@ -199,9 +195,7 @@ watch(() => props.initialId, () => {
class="ui negative message"
>
<h3 class="header">
<translate >
Error while fetching object
</translate>
</h3>
<ul class="list">
<li
@ -217,14 +211,10 @@ watch(() => props.initialId, () => {
{{ labels.fieldLabel }}
</label>
<p v-if="type === 'rss'">
<translate >
Use this form to subscribe to an RSS feed from its URL.
</translate>
</p>
<p v-else-if="type === 'artists'">
<translate >
Use this form to subscribe to a channel hosted somewhere else on the Fediverse.
</translate>
</p>
<input
id="object-id"
@ -241,9 +231,7 @@ watch(() => props.initialId, () => {
:class="['ui', 'primary', {loading: isLoading}, 'button']"
:disabled="isLoading || !id || id.length === 0"
>
<translate >
Search
</translate>
</button>
</form>
<div
@ -252,9 +240,7 @@ watch(() => props.initialId, () => {
class="ui warning message"
>
<p>
<translate >
This kind of object isn't supported yet
</translate>
</p>
</div>
</div>

View File

@ -71,9 +71,7 @@ const checkAndSwitch = async (url: string) => {
@update:show="isError = false"
>
<h3 class="header">
<translate >
Choose your instance
</translate>
</h3>
<div class="scrolling content">
<div
@ -82,20 +80,14 @@ const checkAndSwitch = async (url: string) => {
class="ui negative message"
>
<h4 class="header">
<translate >
It is not possible to connect to the given URL
</translate>
</h4>
<ul class="list">
<li>
<translate >
The server might be down
</translate>
</li>
<li>
<translate >
The given address is not a Funkwhale server
</translate>
</li>
</ul>
</div>
@ -107,7 +99,6 @@ const checkAndSwitch = async (url: string) => {
v-if="$store.state.instance.instanceUrl"
v-translate="{url: $store.state.instance.instanceUrl, hostname: $store.getters['instance/domain'] }"
class="description"
>
You are currently connected to <a
href="%{ url }"
@ -115,12 +106,10 @@ const checkAndSwitch = async (url: string) => {
>%{ hostname }&nbsp;<i class="external icon" /></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted.
</p>
<p v-else>
<translate >
To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices.
</translate>
</p>
<div class="field">
<label for="instance-picker"><translate >Instance URL</translate></label>
<label for="instance-picker">Instance URL</label>
<div class="ui action input">
<input
id="instance-picker"
@ -132,9 +121,7 @@ const checkAndSwitch = async (url: string) => {
type="submit"
:class="['ui', 'icon', {loading: isLoading}, 'button']"
>
<translate >
Submit
</translate>
</button>
</div>
</div>
@ -146,9 +133,7 @@ const checkAndSwitch = async (url: string) => {
>
<div class="field">
<h4>
<translate >
Suggested choices
</translate>
</h4>
<button
v-for="(url, key) in suggestedInstances"
@ -163,9 +148,7 @@ const checkAndSwitch = async (url: string) => {
</div>
<div class="actions">
<button class="ui basic cancel button">
<translate >
Cancel
</translate>
</button>
</div>
</semantic-modal>

View File

@ -110,9 +110,7 @@ const player = computed(() => [
<template>
<semantic-modal v-model:show="showRef">
<header class="header">
<translate >
Keyboard shortcuts
</translate>
</header>
<section class="scrolling content">
<div class="ui stackable two column grid">
@ -156,9 +154,7 @@ const player = computed(() => [
</section>
<footer class="actions">
<button class="ui basic cancel button">
<translate >
Close
</translate>
</button>
</footer>
</semantic-modal>

View File

@ -149,9 +149,7 @@ onMounted(() => {
</div>
<div class="menu">
<h3 class="header">
<translate >
Administration
</translate>
</h3>
<div class="divider" />
<router-link
@ -166,9 +164,7 @@ onMounted(() => {
>
{{ $store.state.ui.notifications.pendingReviewEdits }}
</div>
<translate >
Library
</translate>
</router-link>
<router-link
v-if="$store.state.auth.availablePermissions['moderation']"
@ -182,27 +178,21 @@ onMounted(() => {
>
{{ $store.state.ui.notifications.pendingReviewReports + $store.state.ui.notifications.pendingReviewRequests }}
</div>
<translate >
Moderation
</translate>
</router-link>
<router-link
v-if="$store.state.auth.availablePermissions['settings']"
class="item"
:to="{name: 'manage.users.users.list'}"
>
<translate >
Users
</translate>
</router-link>
<router-link
v-if="$store.state.auth.availablePermissions['settings']"
class="item"
:to="{path: '/manage/settings'}"
>
<translate >
Settings
</translate>
</router-link>
</div>
</div>
@ -327,17 +317,17 @@ onMounted(() => {
</div>
<div class="content">
<fieldset
v-for="t in themes"
:key="t.key"
v-for="th in themes"
:key="th.key"
>
<input
:id="t.key"
:id="th.key"
v-model="theme"
type="radio"
name="theme"
:value="t.key"
:value="th.key"
>
<label :for="t.key">{{ t.name }}</label>
<label :for="th.key">{{ th.name }}</label>
</fieldset>
</div>
</semantic-modal>
@ -362,18 +352,14 @@ onMounted(() => {
class="ui fluid tiny primary button"
:to="{name: 'login'}"
>
<translate >
Login
</translate>
</router-link>
<div class="ui small hidden divider" />
<router-link
class="ui fluid tiny button"
:to="{path: '/signup'}"
>
<translate >
Create an account
</translate>
</router-link>
</div>
<nav
@ -385,9 +371,7 @@ onMounted(() => {
id="navigation-label"
class="visually-hidden"
>
<translate >
Main navigation
</translate>
</h1>
<div class="ui small hidden divider" />
<section
@ -407,9 +391,7 @@ onMounted(() => {
@click="expanded = 'explore'"
@focus="expanded = 'explore'"
>
<translate >
Explore
</translate>
<i
v-if="expanded !== 'explore'"
class="angle right icon"
@ -420,58 +402,44 @@ onMounted(() => {
class="item"
:to="{name: 'search'}"
>
<i class="search icon" /><translate >
Search
</translate>
<i class="search icon" /> Search
</router-link>
<router-link
class="item"
:to="{name: 'library.index'}"
active-class="_active"
>
<i class="music icon" /><translate >
Browse
</translate>
<i class="music icon" /> Browse
</router-link>
<router-link
class="item"
:to="{name: 'library.podcasts.browse'}"
>
<i class="podcast icon" /><translate >
Podcasts
</translate>
<i class="podcast icon" /> Podcasts
</router-link>
<router-link
class="item"
:to="{name: 'library.albums.browse'}"
>
<i class="compact disc icon" /><translate >
Albums
</translate>
<i class="compact disc icon" /> Albums
</router-link>
<router-link
class="item"
:to="{name: 'library.artists.browse'}"
>
<i class="user icon" /><translate >
Artists
</translate>
<i class="user icon" /> Artists
</router-link>
<router-link
class="item"
:to="{name: 'library.playlists.browse'}"
>
<i class="list icon" /><translate >
Playlists
</translate>
<i class="list icon" /> Playlists
</router-link>
<router-link
class="item"
:to="{name: 'library.radios.browse'}"
>
<i class="feed icon" /><translate >
Radios
</translate>
<i class="feed icon" /> Radios
</router-link>
</div>
</div>
@ -486,9 +454,7 @@ onMounted(() => {
@click="expanded = 'myLibrary'"
@focus="expanded = 'myLibrary'"
>
<translate >
My Library
</translate>
<i
v-if="expanded !== 'myLibrary'"
class="angle right icon"
@ -499,49 +465,37 @@ onMounted(() => {
class="item"
:to="{name: 'library.me'}"
>
<i class="music icon" /><translate >
Browse
</translate>
<i class="music icon" /> Browse
</router-link>
<router-link
class="item"
:to="{name: 'library.albums.me'}"
>
<i class="compact disc icon" /><translate >
Albums
</translate>
<i class="compact disc icon" /> Albums
</router-link>
<router-link
class="item"
:to="{name: 'library.artists.me'}"
>
<i class="user icon" /><translate >
Artists
</translate>
<i class="user icon" /> Artists
</router-link>
<router-link
class="item"
:to="{name: 'library.playlists.me'}"
>
<i class="list icon" /><translate >
Playlists
</translate>
<i class="list icon" /> Playlists
</router-link>
<router-link
class="item"
:to="{name: 'library.radios.me'}"
>
<i class="feed icon" /><translate >
Radios
</translate>
<i class="feed icon" /> Radios
</router-link>
<router-link
class="item"
:to="{name: 'favorites'}"
>
<i class="heart icon" /><translate >
Favorites
</translate>
<i class="heart icon" /> Favorites
</router-link>
</div>
</div>
@ -550,15 +504,11 @@ onMounted(() => {
class="header item"
:to="{name: 'subscriptions'}"
>
<translate >
Channels
</translate>
</router-link>
<div class="item">
<h3 class="header">
<translate >
More
</translate>
</h3>
<div class="menu">
<router-link
@ -566,9 +516,7 @@ onMounted(() => {
to="/about"
active-class="router-link-exact-active active"
>
<i class="info icon" /><translate >
About this pod
</translate>
<i class="info icon" /> About this pod
</router-link>
</div>
</div>

View File

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

View File

@ -69,17 +69,13 @@ const move = (idx: number, increment: number) => {
:class="[{active: !isPreviewing}, 'item']"
@click.stop.prevent="isPreviewing = false"
>
<translate >
Edit form
</translate>
</button>
<button
:class="[{active: isPreviewing}, 'item']"
@click.stop.prevent="isPreviewing = true"
>
<translate >
Preview form
</translate>
</button>
</div>
<div
@ -99,12 +95,10 @@ const move = (idx: number, increment: number) => {
>
<div class="field">
<label for="help-text">
<translate >Help text</translate>
Help text
</label>
<p>
<translate >
An optional text to be displayed at the start of the sign-up form.
</translate>
</p>
<content-form
v-if="value.help_text"
@ -115,32 +109,24 @@ const move = (idx: number, increment: number) => {
</div>
<div class="field">
<label>
<translate >Additional fields</translate>
Additional fields
</label>
<p>
<translate >
Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled.
</translate>
</p>
<table v-if="value.fields?.length > 0">
<thead>
<tr>
<th>
<translate >
Field label
</translate>
</th>
<th>
<translate >
Field type
</translate>
</th>
<th>
<translate >
Required
</translate>
</th>
<th><span class="visually-hidden"><translate >Actions</translate></span></th>
<th><span class="visually-hidden">Actions</span></th>
</tr>
</thead>
<tbody>
@ -158,28 +144,20 @@ const move = (idx: number, increment: number) => {
<td>
<select v-model="field.input_type">
<option value="short_text">
<translate >
Short text
</translate>
</option>
<option value="long_text">
<translate >
Long text
</translate>
</option>
</select>
</td>
<td>
<select v-model="field.required">
<option :value="true">
<translate >
Yes
</translate>
</option>
<option :value="false">
<translate >
No
</translate>
</option>
</select>
</td>
@ -214,9 +192,7 @@ const move = (idx: number, increment: number) => {
class="ui basic button"
@click.stop.prevent="addField"
>
<translate >
Add a new field
</translate>
</button>
</div>
</div>

View File

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

View File

@ -165,9 +165,7 @@ defineExpose({
class="ui negative message"
>
<h4 class="header">
<translate >
Error while saving channel
</translate>
</h4>
<ul class="list">
<li
@ -184,9 +182,7 @@ defineExpose({
class="ui grouped channel-type required field"
>
<legend>
<translate >
What will this channel be used for?
</translate>
</legend>
<div class="ui hidden divider" />
<div class="field">
@ -214,7 +210,7 @@ defineExpose({
<template v-if="!creating || step === 2">
<div class="ui required field">
<label for="channel-name">
<translate >Name</translate>
Name
</label>
<input
v-model="newValues.name"
@ -225,7 +221,7 @@ defineExpose({
</div>
<div class="ui required field">
<label for="channel-username">
<translate >Fediverse handle</translate>
Fediverse handle
</label>
<div class="ui left labeled input">
<div class="ui basic label">
@ -242,9 +238,7 @@ defineExpose({
<template v-if="creating">
<div class="ui small hidden divider" />
<p>
<translate >
Used in URLs and to follow this channel in the Fediverse. It cannot be changed later.
</translate>
</p>
</template>
</div>
@ -254,9 +248,7 @@ defineExpose({
:image-class="newValues.content_category === 'podcast' ? '' : 'circular'"
@delete="newValues.cover = null"
>
<translate >
Channel Picture
</translate>
</attachment-input>
</div>
<div class="ui small hidden divider" />
@ -264,7 +256,7 @@ defineExpose({
<div class="ten wide column">
<div class="ui field">
<label for="channel-tags">
<translate >Tags</translate>
Tags
</label>
<tags-selector
id="channel-tags"
@ -279,7 +271,7 @@ defineExpose({
>
<div class="ui required field">
<label for="channel-language">
<translate >Language</translate>
Language
</label>
<select
id="channel-language"
@ -302,7 +294,7 @@ defineExpose({
<div class="ui small hidden divider" />
<div class="ui field">
<label for="channel-name">
<translate >Description</translate>
Description
</label>
<content-form v-model="newValues.description" />
</div>
@ -312,7 +304,7 @@ defineExpose({
>
<div class="ui required field">
<label for="channel-itunes-category">
<translate >Category</translate>
Category
</label>
<select
id="itunes-category"
@ -332,7 +324,7 @@ defineExpose({
</div>
<div class="ui field">
<label for="channel-itunes-category">
<translate >Subcategory</translate>
Subcategory
</label>
<select
id="itunes-category"
@ -357,7 +349,7 @@ defineExpose({
>
<div class="ui field">
<label for="channel-itunes-email">
<translate >Owner e-mail address</translate>
Owner e-mail address
</label>
<input
id="channel-itunes-email"
@ -368,7 +360,7 @@ defineExpose({
</div>
<div class="ui field">
<label for="channel-itunes-name">
<translate >Owner name</translate>
Owner name
</label>
<input
id="channel-itunes-name"
@ -379,9 +371,7 @@ defineExpose({
</div>
</div>
<p>
<translate >
Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes.
</translate>
</p>
</template>
</template>
@ -390,9 +380,7 @@ defineExpose({
class="ui active inverted dimmer"
>
<div class="ui text loader">
<translate >
Loading
</translate>
</div>
</div>
</form>

View File

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

View File

@ -77,9 +77,7 @@ fetchData()
:class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)"
>
<translate >
Show more
</translate>
</button>
</template>
<template v-if="!isLoading && channels.length === 0">

View File

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

View File

@ -40,19 +40,16 @@ const toggle = () => {
<i class="heart icon" />
<translate
v-if="isApproved"
>
Unfollow
</translate>
<translate
v-else-if="isPending"
>
Cancel follow request
</translate>
<translate
v-else
>
Follow
</translate>

View File

@ -138,7 +138,7 @@ const openMenu = () => {
v-else
:class="[playIconClass, 'icon']"
/>
<template v-if="!discrete && !iconOnly">&nbsp;<slot><translate >Play</translate></slot></template>
<template v-if="!discrete && !iconOnly">&nbsp;<slot>Play</slot></template>
</button>
<button
v-if="!discrete && !iconOnly"
@ -156,7 +156,7 @@ const openMenu = () => {
:title="labels.addToQueue"
@click.stop.prevent="enqueue"
>
<i class="plus icon" /><translate >Add to queue</translate>
<i class="plus icon" />Add to queue
</button>
<button
class="item basic"
@ -181,7 +181,7 @@ const openMenu = () => {
:title="labels.startRadio"
@click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track?.id})"
>
<i class="feed icon" /><translate >Play radio</translate>
<i class="feed icon" />Play radio
</button>
<button
v-if="track"
@ -190,7 +190,7 @@ const openMenu = () => {
@click.stop="$store.commit('playlists/chooseTrack', track)"
>
<i class="list icon" />
<translate >Add to playlist</translate>
Add to playlist
</button>
<button
v-if="track && $route.name !== 'library.tracks.detail'"
@ -200,11 +200,9 @@ const openMenu = () => {
<i class="info icon" />
<translate
v-if="track.artist?.content_category === 'podcast'"
>Episode details</translate>
<translate
v-else
>Track details</translate>
</button>
<div class="divider" />

View File

@ -133,9 +133,7 @@ const hideArtist = () => {
id="player-label"
class="visually-hidden"
>
<translate >
Audio player and controls
</translate>
</h1>
<div
class="ui inverted segment fixed-controls"

View File

@ -73,9 +73,7 @@ const labels = computed(() => ({
<template>
<div>
<h2>
<translate >
Search for some music
</translate>
</h2>
<div :class="['ui', {'loading': isLoading }, 'search']">
<div class="ui icon big input">
@ -91,9 +89,7 @@ const labels = computed(() => ({
</div>
<template v-if="query.length > 0">
<h3 class="ui title">
<translate >
Artists
</translate>
</h3>
<div v-if="results.artists.length > 0">
<div class="ui cards">
@ -105,16 +101,12 @@ const labels = computed(() => ({
</div>
</div>
<p v-else>
<translate >
No artist matched your query
</translate>
</p>
</template>
<template v-if="query.length > 0">
<h3 class="ui title">
<translate >
Albums
</translate>
</h3>
<div
v-if="results.albums.length > 0"
@ -132,9 +124,7 @@ const labels = computed(() => ({
</div>
</div>
<p v-else>
<translate >
No album matched your query
</translate>
</p>
</template>
</div>

View File

@ -112,9 +112,7 @@ watch(
:class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)"
>
<translate >
Show more
</translate>
</button>
</template>
</div>

View File

@ -108,9 +108,7 @@ watch(
:class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)"
>
<translate >
Show more
</translate>
</button>
</template>
</div>

View File

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

View File

@ -72,23 +72,17 @@ store.state.auth.applicationSecret = undefined
</div>
<template v-else>
<router-link :to="{name: 'settings'}">
<translate >
Back to settings
</translate>
</router-link>
<h2 class="ui header">
<translate >
Application details
</translate>
</h2>
<div class="ui form">
<p>
<translate >
Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else.
</translate>
</p>
<div class="field">
<label for="copy-id"><translate >Application ID</translate></label>
<label for="copy-id">Application ID</label>
<copy-input
id="copy-id"
:value="application.client_id"
@ -100,18 +94,14 @@ store.state.auth.applicationSecret = undefined
>
<div class="ui small warning message">
<h3 class="header">
<translate >
Keep a copy of this token in a safe place
</translate>
</h3>
<p>
<translate >
You won't be able to see it again once you leave this screen.
</translate>
</p>
</div>
<label for="copy-secret"><translate >Application secret</translate></label>
<label for="copy-secret">Application secret</label>
<copy-input
id="copy-secret"
:value="secret"
@ -121,7 +111,7 @@ store.state.auth.applicationSecret = undefined
v-if="application.token != undefined"
class="field"
>
<label for="copy-secret"><translate >Access token</translate></label>
<label for="copy-secret">Access token</label>
<copy-input
id="copy-secret"
:value="application.token"
@ -131,14 +121,12 @@ store.state.auth.applicationSecret = undefined
@click.prevent="refreshToken"
>
<i class="refresh icon" />
<translate >Regenerate token</translate>
Regenerate token
</a>
</div>
</div>
<h2 class="ui header">
<translate >
Edit application
</translate>
</h2>
<application-form
:app="application"

View File

@ -120,9 +120,7 @@ const allScopes = computed(() => {
class="ui negative message"
>
<h4 class="header">
<translate >
We cannot save your changes
</translate>
</h4>
<ul class="list">
<li
@ -134,7 +132,7 @@ const allScopes = computed(() => {
</ul>
</div>
<div class="ui field">
<label for="application-name"><translate >Name</translate></label>
<label for="application-name">Name</label>
<input
id="application-name"
v-model="fields.name"
@ -144,7 +142,7 @@ const allScopes = computed(() => {
>
</div>
<div class="ui field">
<label for="redirect-uris"><translate >Redirect URI</translate></label>
<label for="redirect-uris">Redirect URI</label>
<input
id="redirect-uris"
v-model="fields.redirect_uris"
@ -152,17 +150,13 @@ const allScopes = computed(() => {
type="text"
>
<p class="help">
<translate >
Use "urn:ietf:wg:oauth:2.0:oob" as a redirect URI if your application is not served on the web.
</translate>
</p>
</div>
<div class="ui field">
<label><translate >Scopes</translate></label>
<label>Scopes</label>
<p>
<translate >
Checking the parent "Read" or "Write" scopes implies access to all the corresponding children scopes.
</translate>
</p>
<div class="ui stackable two column grid">
<div
@ -210,13 +204,11 @@ const allScopes = computed(() => {
>
<translate
v-if="app !== null"
>
Update application
</translate>
<translate
v-else
>
Create application
</translate>

View File

@ -55,14 +55,10 @@ const created = (application: Application) => {
<div class="ui vertical stripe segment">
<section class="ui text container">
<router-link :to="{name: 'settings'}">
<translate >
Back to settings
</translate>
</router-link>
<h2 class="ui header">
<translate >
Create a new application
</translate>
</h2>
<application-form
:defaults="defaults"

View File

@ -119,9 +119,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
<section class="ui vertical stripe segment">
<div class="ui small text container">
<h2>
<i class="lock open icon" /><translate >
Authorize third-party app
</translate>
<i class="lock open icon" /> Authorize third-party app
</h2>
<div
v-if="errors.length > 0"
@ -132,17 +130,13 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
v-if="application"
class="header"
>
<translate >
Error while authorizing application
</translate>
</h4>
<h4
v-else
class="header"
>
<translate >
Error while fetching application data
</translate>
</h4>
<ul class="list">
<li
@ -183,20 +177,20 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
>
<i class="pencil icon" />
<translate >Write-only</translate>
Write-only
</span>
<span
v-else-if="!topic.write && topic.read"
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
>
<translate >Read-only</translate>
Read-only
</span>
<span
v-else-if="topic.write && topic.read"
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
>
<i class="pencil icon" />
<translate >Full access</translate>
Full access
</span>
<i :class="[topic.icon, 'icon']" />
<div class="content">
@ -207,7 +201,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
</div>
</h4>
<div v-if="unknownRequestedScopes.length > 0">
<p><strong><translate >The application is also requesting the following unknown permissions:</translate></strong></p>
<p><strong>The application is also requesting the following unknown permissions:</strong></p>
<ul
v-for="(unknownscope, key) in unknownRequestedScopes"
:key="key"
@ -230,7 +224,6 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
<p
v-if="redirectUri === 'urn:ietf:wg:oauth:2.0:oob'"
v-translate
>
You will be shown a code to copy-paste in the application.
</p>
@ -244,7 +237,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
</p>
</form>
<div v-else-if="code">
<p><strong><translate >Copy-paste the following code in the application:</translate></strong></p>
<p><strong>Copy-paste the following code in the application:</strong></p>
<copy-input :value="code" />
</div>
</div>

View File

@ -75,20 +75,14 @@ const submit = async () => {
class="ui negative message"
>
<h4 class="header">
<translate >
We cannot log you in
</translate>
</h4>
<ul class="list">
<li v-if="errors[0] == 'invalid_credentials' && $store.state.instance.settings.moderation.signup_approval_enabled.value">
<translate >
If you signed-up recently, you may need to wait before our moderation team review your account, or verify your e-mail address.
</translate>
</li>
<li v-else-if="errors[0] == 'invalid_credentials'">
<translate >
Please double-check that your username and password combination is correct and make sure you verified your e-mail address.
</translate>
</li>
<li v-else>
{{ errors[0] }}
@ -98,11 +92,11 @@ const submit = async () => {
<template v-if="domain === $store.getters['instance/domain']">
<div class="field">
<label for="username-field">
<translate >Username or e-mail address</translate>
Username or e-mail address
<template v-if="showSignup">
|
<router-link :to="{path: '/signup'}">
<translate >Create an account</translate>
Create an account
</router-link>
</template>
</label>
@ -119,12 +113,12 @@ const submit = async () => {
</div>
<div class="field">
<label for="password-field">
<translate >Password</translate> |
Password |
<router-link
tabindex="1"
:to="{name: 'auth.password-reset', query: {email: credentials.username}}"
>
<translate >Reset your password</translate>
Reset your password
</router-link>
</label>
<password-input
@ -147,9 +141,7 @@ const submit = async () => {
:class="['ui', {'loading': isLoading}, 'right', 'floated', buttonClasses, 'button']"
type="submit"
>
<translate >
Login
</translate>
</button>
</form>
</template>

View File

@ -19,13 +19,10 @@ const labels = computed(() => ({
class="ui small text container"
>
<h2>
<translate >
Are you sure you want to log out?
</translate>
</h2>
<p
v-translate="{username: $store.state.auth.username}"
>
You are currently logged in as %{ username }
</p>
@ -33,9 +30,7 @@ const labels = computed(() => ({
class="ui button"
@click="$store.dispatch('auth/logout')"
>
<translate >
Yes, log me out!
</translate>
</button>
</div>
<div
@ -43,17 +38,13 @@ const labels = computed(() => ({
class="ui small text container"
>
<h2>
<translate >
You aren't currently logged in
</translate>
</h2>
<router-link
to="/login"
class="ui button"
>
<translate >
Log in!
</translate>
</router-link>
</div>
</section>

View File

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

View File

@ -276,9 +276,7 @@ fetchOwnedApps()
<div class="ui vertical stripe segment">
<section class="ui text container">
<h2 class="ui header">
<translate >
Account settings
</translate>
</h2>
<form
class="ui form"
@ -289,9 +287,7 @@ fetchOwnedApps()
class="ui positive message"
>
<h4 class="header">
<translate >
Settings updated
</translate>
</h4>
</div>
<div
@ -300,9 +296,7 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
<translate >
Your settings can't be updated
</translate>
</h4>
<ul class="list">
<li
@ -346,18 +340,14 @@ fetchOwnedApps()
:class="['ui', { loading: isLoading }, 'button']"
type="submit"
>
<translate >
Update settings
</translate>
</button>
</form>
</section>
<section class="ui text container">
<div class="ui hidden divider" />
<h2 class="ui header">
<translate >
Avatar
</translate>
</h2>
<div class="ui form">
<div
@ -366,9 +356,7 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
<translate >
Your avatar cannot be saved
</translate>
</h4>
<ul class="list">
<li
@ -385,9 +373,7 @@ fetchOwnedApps()
@update:model-value="submitAvatar($event)"
@delete="avatar = {uuid: null}"
>
<translate >
Avatar
</translate>
</attachment-input>
</div>
</section>
@ -395,16 +381,10 @@ fetchOwnedApps()
<section class="ui text container">
<div class="ui hidden divider" />
<h2 class="ui header">
<translate >
Change my password
</translate>
</h2>
<div class="ui message">
<translate >
Changing your password will also change your Subsonic API password if you have requested one.
</translate>&nbsp;<translate >
You will have to update your password on your clients that use this password.
</translate>
Changing your password will also change your Subsonic API password if you have requested one.&nbsp; You will have to update your password on your clients that use this password.
</div>
<form
class="ui form"
@ -416,20 +396,16 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
<translate >
Your password cannot be changed
</translate>
</h4>
<ul class="list">
<li v-if="passwordError == 'invalid_credentials'">
<translate >
Please double-check your password is correct
</translate>
</li>
</ul>
</div>
<div class="field">
<label for="old-password-field"><translate >Current password</translate></label>
<label for="old-password-field">Current password</label>
<password-input
v-model="credentials.oldPassword"
field-id="old-password-field"
@ -437,7 +413,7 @@ fetchOwnedApps()
/>
</div>
<div class="field">
<label for="new-password-field"><translate >New password</translate></label>
<label for="new-password-field">New password</label>
<password-input
v-model="credentials.newPassword"
field-id="new-password-field"
@ -448,42 +424,30 @@ fetchOwnedApps()
:class="['ui', {'loading': isLoadingPassword}, {disabled: !credentials.newPassword || !credentials.oldPassword}, 'warning', 'button']"
:action="submitPassword"
>
<translate >
Change password
</translate>
<template #modal-header>
<p>
<translate >
Change your password?
</translate>
</p>
</template>
<template #modal-content>
<div>
<p>
<translate >
Changing your password will have the following consequences:
</translate>
</p>
<ul>
<li>
<translate >
You will be logged out from this session and have to log in with the new one
</translate>
</li>
<li>
<translate >
Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password
</translate>
</li>
</ul>
</div>
</template>
<template #modal-confirm>
<div>
<translate >
Disable access
</translate>
</div>
</template>
</dangerous-button>
@ -500,15 +464,11 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="eye slash outline icon" />
<div class="content">
<translate >
Content filters
</translate>
</div>
</h2>
<p>
<translate >
Content filters help you hide content you don't want to see on the service.
</translate>
</p>
<button
@ -516,27 +476,19 @@ fetchOwnedApps()
@click="$store.dispatch('moderation/fetchContentFilters')"
>
<i class="refresh icon" />&nbsp;
<translate >
Refresh
</translate>
</button>
<h3 class="ui header">
<translate >
Hidden artists
</translate>
</h3>
<table class="ui compact very basic unstackable table">
<thead>
<tr>
<th>
<translate >
Name
</translate>
</th>
<th>
<translate >
Creation date
</translate>
</th>
<th />
</tr>
@ -559,9 +511,7 @@ fetchOwnedApps()
class="ui basic tiny button"
@click="$store.dispatch('moderation/deleteContentFilter', filter.uuid)"
>
<translate >
Delete
</translate>
</button>
</td>
</tr>
@ -576,24 +526,18 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="open lock icon" />
<div class="content">
<translate >
Authorized apps
</translate>
</div>
</h2>
<p>
<translate >
This is the list of applications that have access to your account data.
</translate>
</p>
<button
:class="['ui', 'icon', { loading: isLoadingApps }, 'button']"
@click="fetchApps()"
>
<i class="refresh icon" />&nbsp;
<translate >
Refresh
</translate>
</button>
<table
v-if="apps.length > 0"
@ -602,14 +546,10 @@ fetchOwnedApps()
<thead>
<tr>
<th>
<translate >
Application
</translate>
</th>
<th>
<translate >
Permissions
</translate>
</th>
<th />
</tr>
@ -630,29 +570,22 @@ fetchOwnedApps()
:class="['ui', 'tiny', 'danger', { loading: isRevoking.has(app.client_id) }, 'button']"
@confirm="revokeApp(app.client_id)"
>
<translate >
Revoke
</translate>
<template #modal-header>
<p
v-translate="{application: app.name}"
>
Revoke access for application "%{ application }"?
</p>
</template>
<template #modal-content>
<p>
<translate >
This will prevent this application from accessing the service on your behalf.
</translate>
</p>
</template>
<template #modal-confirm>
<div>
<translate >
Revoke access
</translate>
</div>
</template>
</dangerous-button>
@ -662,13 +595,9 @@ fetchOwnedApps()
</table>
<empty-state v-else>
<template #title>
<translate >
You don't have any application connected with your account.
</translate>
</template>
<translate >
If you authorize third-party applications to access your data, those applications will be listed here.
</translate>
</empty-state>
</section>
<section
@ -679,23 +608,17 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="code icon" />
<div class="content">
<translate >
Your applications
</translate>
</div>
</h2>
<p>
<translate >
This is the list of applications that you have registered.
</translate>
</p>
<router-link
class="ui success button"
:to="{name: 'settings.applications.new'}"
>
<translate >
Register a new application
</translate>
</router-link>
<table
v-if="ownedApps.length > 0"
@ -704,19 +627,13 @@ fetchOwnedApps()
<thead>
<tr>
<th>
<translate >
Application
</translate>
</th>
<th>
<translate >
Scopes
</translate>
</th>
<th>
<translate >
Creation date
</translate>
</th>
<th />
</tr>
@ -742,37 +659,28 @@ fetchOwnedApps()
class="ui tiny success button"
:to="{name: 'settings.applications.edit', params: {id: app.client_id}}"
>
<translate >
Edit
</translate>
</router-link>
<dangerous-button
:class="['ui', 'tiny', 'danger', { loading: isDeleting.has(app.client_id) }, 'button']"
@confirm="deleteApp(app.client_id)"
>
<translate >
Remove
</translate>
<template #modal-header>
<p
v-translate="{application: app.name}"
>
Remove application "%{ application }"?
</p>
</template>
<template #modal-content>
<p>
<translate >
This will permanently remove the application and all the associated tokens.
</translate>
</p>
</template>
<template #modal-confirm>
<div>
<translate >
Remove application
</translate>
</div>
</template>
</dangerous-button>
@ -782,13 +690,9 @@ fetchOwnedApps()
</table>
<empty-state v-else>
<template #title>
<translate >
You don't have registered any application yet.
</translate>
</template>
<translate >
Register one to integrate Funkwhale with third-party applications.
</translate>
</empty-state>
</section>
@ -800,23 +704,17 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="code icon" />
<div class="content">
<translate >
Plugins
</translate>
</div>
</h2>
<p>
<translate >
Use plugins to extend Funkwhale and get additional features.
</translate>
</p>
<router-link
class="ui success button"
:to="{name: 'settings.plugins'}"
>
<translate >
Manage plugins
</translate>
</router-link>
</section>
<section class="ui text container">
@ -824,20 +722,15 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="comment icon" />
<div class="content">
<translate >
Change my e-mail address
</translate>
</div>
</h2>
<p>
<translate >
Change the e-mail address associated with your account. We will send a confirmation to the new address.
</translate>
</p>
<p>
<translate
:translate-params="{email: $store.state.auth.profile?.email}"
>
Your current e-mail address is %{ email }.
</translate>
@ -852,9 +745,7 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
<translate >
We cannot change your e-mail address
</translate>
</h4>
<ul class="list">
<li
@ -866,7 +757,7 @@ fetchOwnedApps()
</ul>
</div>
<div class="field">
<label for="new-email"><translate >New e-mail address</translate></label>
<label for="new-email">New e-mail address</label>
<input
id="new-email"
v-model="newEmail"
@ -875,7 +766,7 @@ fetchOwnedApps()
>
</div>
<div class="field">
<label for="current-password-field-email"><translate >Password</translate></label>
<label for="current-password-field-email">Password</label>
<password-input
v-model="emailPassword"
field-id="current-password-field-email"
@ -886,9 +777,7 @@ fetchOwnedApps()
type="submit"
class="ui button"
>
<translate >
Update
</translate>
</button>
</form>
</section>
@ -897,23 +786,17 @@ fetchOwnedApps()
<h2 class="ui header">
<i class="trash icon" />
<div class="content">
<translate >
Delete my account
</translate>
</div>
</h2>
<p>
<translate >
You can permanently and irreversibly delete your account and all the associated data using the form below. You will be asked for confirmation.
</translate>
</p>
<div
role="alert"
class="ui warning message"
>
<translate >
Your account will be deleted from our servers within a few minutes. We will also notify other servers who may have a copy of some of your data so they can proceed to deletion. Please note that some of these servers may be offline or unwilling to comply though.
</translate>
</div>
<div class="ui form">
<div
@ -922,9 +805,7 @@ fetchOwnedApps()
class="ui negative message"
>
<h4 class="header">
<translate >
We cannot delete your account
</translate>
</h4>
<ul class="list">
<li
@ -936,7 +817,7 @@ fetchOwnedApps()
</ul>
</div>
<div class="field">
<label for="current-password-field"><translate >Password</translate></label>
<label for="current-password-field">Password</label>
<password-input
v-model="deleteAccountPassword"
field-id="current-password-field"
@ -947,30 +828,22 @@ fetchOwnedApps()
:class="['ui', {'loading': isDeletingAccount}, {disabled: !deleteAccountPassword}, {danger: deleteAccountPassword}, 'button']"
:action="deleteAccount"
>
<translate >
Delete my account
</translate>
<template #modal-header>
<p>
<translate >
Do you want to delete your account?
</translate>
</p>
</template>
<template #modal-content>
<div>
<p>
<translate >
This is irreversible and will permanently remove your data from our servers. You will we immediately logged out.
</translate>
</p>
</div>
</template>
<template #modal-confirm>
<div>
<translate >
Delete my account
</translate>
</div>
</template>
</dangerous-button>

View File

@ -88,20 +88,14 @@ fetchInstanceSettings()
<div v-if="submitted">
<div class="ui success message">
<p v-if="signupRequiresApproval">
<translate >
Your account request was successfully submitted. You will be notified by e-mail when our moderation team has reviewed your request.
</translate>
</p>
<p v-else>
<translate >
Your account was successfully created. Please verify your e-mail address before trying to login.
</translate>
</p>
</div>
<h2>
<translate >
Log in to your Funkwhale account
</translate>
</h2>
<login-form
button-classes="basic success"
@ -117,17 +111,13 @@ fetchInstanceSettings()
v-if="!$store.state.instance.settings.users.registration_enabled.value"
class="ui message"
>
<translate >
Public registrations are not possible on this instance. You will need an invitation code to sign up.
</translate>
</p>
<p
v-else-if="signupRequiresApproval"
class="ui message"
>
<translate >
Registrations on this pod are open, but reviewed by moderators before approval.
</translate>
</p>
<template v-if="formCustomization?.help_text">
<rendered-description
@ -143,9 +133,7 @@ fetchInstanceSettings()
class="ui negative message"
>
<h4 class="header">
<translate >
Your account cannot be created.
</translate>
</h4>
<ul class="list">
<li
@ -157,7 +145,7 @@ fetchInstanceSettings()
</ul>
</div>
<div class="required field">
<label for="username-field"><translate >Username</translate></label>
<label for="username-field">Username</label>
<input
id="username-field"
ref="username"
@ -170,7 +158,7 @@ fetchInstanceSettings()
>
</div>
<div class="required field">
<label for="email-field"><translate >E-mail address</translate></label>
<label for="email-field">E-mail address</label>
<input
id="email-field"
ref="email"
@ -182,7 +170,7 @@ fetchInstanceSettings()
>
</div>
<div class="required field">
<label for="password-field"><translate >Password</translate></label>
<label for="password-field">Password</label>
<password-input
v-model="payload.password1"
field-id="password-field"
@ -192,7 +180,7 @@ fetchInstanceSettings()
v-if="!$store.state.instance.settings.users.registration_enabled.value"
class="required field"
>
<label for="invitation-code"><translate >Invitation code</translate></label>
<label for="invitation-code">Invitation code</label>
<input
id="invitation-code"
v-model="payload.invitation"
@ -229,9 +217,7 @@ fetchInstanceSettings()
:class="['ui', buttonClasses, {'loading': isLoading}, ' right floated button']"
type="submit"
>
<translate >
Create my account
</translate>
</button>
</form>
</template>

View File

@ -82,36 +82,26 @@ fetchToken()
@submit.prevent="requestNewToken()"
>
<h2>
<translate >
Subsonic API password
</translate>
</h2>
<p
v-if="!subsonicEnabled"
class="ui message"
>
<translate >
The Subsonic API is not available on this Funkwhale instance.
</translate>
</p>
<p>
<translate >
Funkwhale is compatible with other music players that support the Subsonic API.
</translate>&nbsp;<translate >
You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.
</translate>
Funkwhale is compatible with other music players that support the Subsonic API.&nbsp; You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.
</p>
<p>
<translate >
However, accessing Funkwhale from those clients requires a separate password you can set below.
</translate>
</p>
<p>
<a
href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients"
target="_blank"
>
<translate >Discover how to use Funkwhale from other apps</translate>
Discover how to use Funkwhale from other apps
</a>
</p>
<div
@ -128,9 +118,7 @@ fetchToken()
class="ui negative message"
>
<h4 class="header">
<translate >
Error
</translate>
</h4>
<ul class="list">
<li
@ -164,28 +152,20 @@ fetchToken()
:class="['ui', {'loading': isLoading}, 'button']"
:action="requestNewToken"
>
<translate >
Request a new password
</translate>
<template #modal-header>
<p>
<translate >
Request a new Subsonic API password?
</translate>
</p>
</template>
<template #modal-content>
<p>
<translate >
This will log you out from existing devices that use the current password.
</translate>
</p>
</template>
<template #modal-confirm>
<div>
<translate >
Request a new password
</translate>
</div>
</template>
</dangerous-button>
@ -195,37 +175,27 @@ fetchToken()
:class="['ui', {'loading': isLoading}, 'button']"
@click="requestNewToken"
>
<translate >
Request a password
</translate>
</button>
<dangerous-button
v-if="token"
:class="['ui', {'loading': isLoading}, 'warning', 'button']"
:action="disable"
>
<translate >
Disable Subsonic access
</translate>
<template #modal-header>
<p>
<translate >
Disable Subsonic API access?
</translate>
</p>
</template>
<template #modal-content>
<p>
<translate >
This will completely disable access to the Subsonic API using from account.
</translate>
</p>
</template>
<template #modal-confirm>
<div>
<translate >
Disable access
</translate>
</div>
</template>
</dangerous-button>

View File

@ -59,9 +59,7 @@ defineExpose({
class="ui negative message"
>
<h4 class="header">
<translate >
Error while creating
</translate>
</h4>
<ul class="list">
<li
@ -74,7 +72,7 @@ defineExpose({
</div>
<div class="ui required field">
<label for="album-title">
<translate >Title</translate>
Title
</label>
<input
v-model="title"

View File

@ -35,13 +35,11 @@ const albumForm = ref()
<h4 class="header">
<translate
v-if="channel.content_category === 'podcast'"
>
New series
</translate>
<translate
v-else
>
New album
</translate>
@ -57,18 +55,14 @@ const albumForm = ref()
</div>
<div class="actions">
<button class="ui basic cancel button">
<translate >
Cancel
</translate>
</button>
<button
:class="['ui', 'primary', {loading: isLoading}, 'button']"
:disabled="!submittable"
@click.stop.prevent="albumForm.submit()"
>
<translate >
Create
</translate>
</button>
</div>
</semantic-modal>

View File

@ -49,11 +49,9 @@ watch(() => props.channel, fetchData, { immediate: true })
<label for="album-dropdown">
<translate
v-if="channel && channel.artist && channel.artist.content_category === 'podcast'"
>Series</translate>
<translate
v-else
>Album</translate>
</label>
<select
@ -62,9 +60,7 @@ watch(() => props.channel, fetchData, { immediate: true })
class="ui search normal dropdown"
>
<option value="">
<translate >
None
</translate>
</option>
<option
v-for="album in albums"

View File

@ -55,7 +55,7 @@ fetchLicenses()
<template>
<div>
<label for="license-dropdown">
<translate >License</translate>
License
</label>
<select
id="license-dropdown"
@ -63,9 +63,7 @@ fetchLicenses()
class="ui search normal dropdown"
>
<option value="">
<translate >
None
</translate>
</option>
<option
v-for="l in featuredLicenses"
@ -86,7 +84,7 @@ fetchLicenses()
target="_blank"
rel="noreferrer noopener"
>
<translate >About this license</translate>
About this license
</a>
</p>
</div>

View File

@ -411,9 +411,7 @@ const labels = computed(() => ({
class="ui negative message"
>
<h4 class="header">
<translate >
Error while publishing
</translate>
</h4>
<ul class="list">
<li
@ -426,7 +424,7 @@ const labels = computed(() => ({
</div>
<div :class="['ui', 'required', {hidden: step > 1}, 'field']">
<label for="channel-dropdown">
<translate >Channel</translate>
Channel
</label>
<div
id="channel-dropdown"
@ -449,9 +447,7 @@ const labels = computed(() => ({
<div class="content">
<p>
<i class="copyright icon" />
<translate >
Add a license to your upload to ensure some freedoms to your public.
</translate>
</p>
</div>
</div>
@ -464,9 +460,7 @@ const labels = computed(() => ({
<div class="content">
<p>
<i class="warning icon" />
<translate >
You don't have any space left to upload your files. Please contact the moderators.
</translate>
</p>
</div>
</div>
@ -477,25 +471,19 @@ const labels = computed(() => ({
>
<p>
<i class="redo icon" />
<translate >
You have some draft uploads pending publication.
</translate>
</p>
<button
class="ui basic button"
@click.stop.prevent="includeDraftUploads = false"
>
<translate >
Ignore
</translate>
</button>
<button
class="ui basic button"
@click.stop.prevent="includeDraftUploads = true"
>
<translate >
Resume
</translate>
</button>
</div>
<div
@ -547,19 +535,16 @@ const labels = computed(() => ({
<template v-else>
<translate
v-if="file.active"
>
Uploading
</translate>
<translate
v-else-if="file.error"
>
Errored
</translate>
<translate
v-else
>
Pending
</translate>
@ -567,12 +552,12 @@ const labels = computed(() => ({
· {{ parseFloat(file.progress ?? '0') }}%
</template>
· <a @click.stop.prevent="remove(file)">
<translate >Remove</translate>
Remove
</a>
<template v-if="file.error">
·
<a @click.stop.prevent="retry(file)">
<translate >Retry</translate>
Retry
</a>
</template>
</div>
@ -615,15 +600,11 @@ const labels = computed(() => ({
>
<div>
<i class="upload icon" />&nbsp;
<translate >
Drag and drop your files here or open the browser to upload your files
</translate>
</div>
<div class="ui very small divider" />
<div>
<translate >
Browse
</translate>
</div>
</file-upload-widget>
<div class="ui hidden divider" />

View File

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

View File

@ -56,25 +56,21 @@ const isLoading = ref(false)
<h4 class="header">
<translate
v-if="step === 1"
>
Publish audio
</translate>
<translate
v-else-if="step === 2"
>
Files to upload
</translate>
<translate
v-else-if="step === 3"
>
Upload details
</translate>
<translate
v-else-if="step === 4"
>
Processing uploads
</translate>
@ -95,9 +91,7 @@ const isLoading = ref(false)
</template>
<div class="ui very small hidden divider" />
<template v-if="statusData && statusData.quotaStatus">
<translate >
Remaining storage space:
</translate>
{{ humanSize((statusData.quotaStatus.remaining - statusData.uploadedSize) * 1000 * 1000) }}
</template>
</div>
@ -106,36 +100,28 @@ const isLoading = ref(false)
v-if="step === 1"
class="ui basic cancel button"
>
<translate >
Cancel
</translate>
</button>
<button
v-else-if="step < 3"
class="ui basic button"
@click.stop.prevent="uploadForm.step -= 1"
>
<translate >
Previous step
</translate>
</button>
<button
v-else-if="step === 3"
class="ui basic button"
@click.stop.prevent="uploadForm.step -= 1"
>
<translate >
Update
</translate>
</button>
<button
v-if="step === 1"
class="ui primary button"
@click.stop.prevent="uploadForm.step += 1"
>
<translate >
Next step
</translate>
</button>
<div
v-if="step === 2"
@ -147,9 +133,7 @@ const isLoading = ref(false)
:disabled="!statusData?.canSubmit || undefined"
@click.prevent.stop="uploadForm.publish"
>
<translate >
Publish
</translate>
</button>
<button
ref="dropdown"
@ -164,9 +148,7 @@ const isLoading = ref(false)
class="basic item"
@click="update(false)"
>
<translate >
Finish later
</translate>
</div>
</div>
</button>
@ -176,9 +158,7 @@ const isLoading = ref(false)
class="ui basic cancel button"
@click="update(false)"
>
<translate >
Close
</translate>
</button>
</div>
</semantic-modal>

View File

@ -167,7 +167,7 @@ const launchAction = async () => {
class="right floated"
>
<span v-if="needsRefresh">
<translate >Content has been updated, click refresh to see up-to-date content</translate>
Content has been updated, click refresh to see up-to-date content
</span>
<button
class="ui basic icon button"
@ -185,7 +185,7 @@ const launchAction = async () => {
>
<div class="ui inline fields">
<div class="field">
<label for="actions-select"><translate >Actions</translate></label>
<label for="actions-select">Actions</label>
<select
id="actions-select"
v-model="currentActionName"
@ -208,9 +208,7 @@ const launchAction = async () => {
:aria-label="labels.performAction"
@confirm="launchAction"
>
<translate >
Go
</translate>
<template #modal-header>
<p>
<translate
@ -231,7 +229,6 @@ const launchAction = async () => {
</template>
<translate
v-else
>
This may affect a lot of elements or have irreversible consequences, please double check this is really what you want.
</translate>
@ -239,9 +236,7 @@ const launchAction = async () => {
</template>
<template #modal-confirm>
<div :aria-label="labels.performAction">
<translate >
Launch
</translate>
</div>
</template>
</dangerous-button>
@ -252,9 +247,7 @@ const launchAction = async () => {
:class="['ui', {disabled: checked.length === 0}, {'loading': isLoading}, 'button']"
@click="launchAction"
>
<translate >
Go
</translate>
</button>
</div>
<div class="count field">
@ -301,7 +294,6 @@ const launchAction = async () => {
>
<translate
key="4"
>Select only current page</translate>
</a>
</template>
@ -313,9 +305,7 @@ const launchAction = async () => {
class="ui negative message"
>
<h4 class="header">
<translate >
Error while applying action
</translate>
</h4>
<ul class="list">
<li

View File

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

View File

@ -22,13 +22,11 @@ const value = useVModel(props, 'modelValue', emit)
>
<translate
v-if="value"
>
Expand
</translate>
<translate
v-else
>
Collapse
</translate>

View File

@ -86,17 +86,13 @@ onMounted(async () => {
:class="[{active: !isPreviewing}, 'item']"
@click.prevent="isPreviewing = false"
>
<translate >
Write
</translate>
</button>
<button
:class="[{active: isPreviewing}, 'item']"
@click.prevent="isPreviewing = true"
>
<translate >
Preview
</translate>
</button>
</div>
<template v-if="isPreviewing">
@ -112,9 +108,7 @@ onMounted(async () => {
</div>
</div>
<p v-else-if="!preview">
<translate >
Nothing to preview.
</translate>
</p>
<sanitized-html
v-else
@ -141,9 +135,7 @@ onMounted(async () => {
{{ remainingChars }}
</span>
<p>
<translate >
Markdown syntax is supported.
</translate>
</p>
</div>
</div>

View File

@ -22,9 +22,7 @@ const { copy, isSupported: canCopy, copied } = useClipboard({ source: value, cop
v-if="copied"
class="message"
>
<translate >
Text copied to clipboard!
</translate>
</p>
<input
:id="id"
@ -39,9 +37,7 @@ const { copy, isSupported: canCopy, copied } = useClipboard({ source: value, cop
@click="copy()"
>
<i class="copy icon" />
<translate >
Copy
</translate>
</button>
</div>
</template>

View File

@ -42,9 +42,7 @@ const confirm = () => {
>
<h4 class="header">
<slot name="modal-header">
<translate >
Do you want to confirm this action?
</translate>
</slot>
</h4>
<div class="scrolling content">
@ -54,18 +52,14 @@ const confirm = () => {
</div>
<div class="actions">
<button class="ui basic cancel button">
<translate >
Cancel
</translate>
</button>
<button
:class="['ui', 'confirm', confirmColor, 'button']"
@click="confirm"
>
<slot name="modal-confirm">
<translate >
Confirm
</translate>
</slot>
</button>
</div>

View File

@ -19,9 +19,7 @@ withDefaults(defineProps<Props>(), {
<div class="content">
<slot name="title">
<i class="search icon" />
<translate >
No results were found.
</translate>
</slot>
</div>
</h4>
@ -32,9 +30,7 @@ withDefaults(defineProps<Props>(), {
class="ui button"
@click="emit('refresh')"
>
<translate >
Refresh
</translate>
</button>
</div>
</div>

View File

@ -28,11 +28,9 @@ const truncated = computed(() => props.content.slice(0, props.length))
<br>
<translate
v-if="expanded"
>Show less</translate>
<translate
v-else
>Show more</translate>
</a>
</div>

View File

@ -42,7 +42,7 @@ const search = () => {
for="search-query"
class="hidden"
>
<translate >Search</translate>
Search
</label>
<input
id="search-query"

View File

@ -91,21 +91,19 @@ const submit = async () => {
href=""
@click.stop.prevent="showMore = true"
>
<translate >Show more</translate>
Show more
</a>
<a
v-else
href=""
@click.stop.prevent="showMore = false"
>
<translate >Show less</translate>
Show less
</a>
</template>
</template>
<p v-else-if="!isUpdating">
<translate >
No description available
</translate>
</p>
<template v-if="!isUpdating && canUpdate && updateUrl">
<div class="ui hidden divider" />
@ -114,7 +112,7 @@ const submit = async () => {
@click="isUpdating = true"
>
<i class="pencil icon" />
<translate >Edit</translate>
Edit
</span>
</template>
<form
@ -128,9 +126,7 @@ const submit = async () => {
class="ui negative message"
>
<h4 class="header">
<translate >
Error while updating description
</translate>
</h4>
<ul class="list">
<li
@ -149,16 +145,14 @@ const submit = async () => {
class="left floated"
@click.prevent="isUpdating = false"
>
<translate >Cancel</translate>
Cancel
</a>
<button
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
type="submit"
:disabled="isLoading"
>
<translate >
Update description
</translate>
</button>
<div class="ui clearing hidden divider" />
</form>

View File

@ -63,13 +63,13 @@ const labels = computed(() => ({
class="menu"
>
<a
v-for="t in themes"
:key="t.key"
:class="[{'active': theme === t.key}, 'item']"
:value="t.key"
@click="theme = t.key"
v-for="th in themes"
:key="th.key"
:class="[{'active': theme === th.key}, 'item']"
:value="th.key"
@click="theme = th.key"
>
<i :class="t.icon" />
<i :class="th.icon" />
{{ t.name }}
</a>
</div>

View File

@ -112,9 +112,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<section class="ui vertical center aligned stripe segment">
<div :class="['ui', { 'active': isLoading }, 'inverted', 'dimmer']">
<div class="ui text loader">
<translate >
Loading your favorites
</translate>
</div>
</div>
<h2
@ -126,7 +124,6 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
translate-plural="%{ count } favorites"
:translate-n="$store.state.favorites.count"
:translate-params="{ count }"
>
%{ count } favorite
</translate>
@ -144,7 +141,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<div class="fields">
<div class="field">
<label for="favorites-ordering">
<translate >Ordering</translate>
Ordering
</label>
<select
id="favorites-ordering"
@ -162,7 +159,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
</div>
<div class="field">
<label for="favorites-ordering-direction">
<translate >Order</translate>
Order
</label>
<select
id="favorites-ordering-direction"
@ -170,20 +167,16 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
class="ui dropdown"
>
<option value="+">
<translate >
Ascending
</translate>
</option>
<option value="-">
<translate >
Descending
</translate>
</option>
</select>
</div>
<div class="field">
<label for="favorites-results">
<translate >Results per page</translate>
Results per page
</label>
<select
id="favorites-results"
@ -222,20 +215,14 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
>
<div class="ui icon header">
<i class="broken heart icon" />
<translate
>
No tracks have been added to your favorites yet
</translate>
</div>
<router-link
:to="'/library'"
class="ui success labeled icon button"
>
<i class="headphones icon" />
<translate >
Browse the library
</translate>
</router-link>
</div>
</main>

View File

@ -37,13 +37,11 @@ const title = computed(() => isFavorite.value
<i class="heart icon" />
<translate
v-if="isFavorite"
>
In favorites
</translate>
<translate
v-else
>
Add to favorites
</translate>

View File

@ -80,9 +80,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="small"
>
<h3 class="header">
<translate >
Refreshing object from remote server
</translate>
</h3>
<div class="scrolling content">
<template v-if="data && data.status != 'pending'">
@ -91,14 +89,10 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui message"
>
<h4 class="header">
<translate >
Refresh was skipped
</translate>
</h4>
<p>
<translate >
The remote server answered, but returned data was unsupported by Funkwhale.
</translate>
</p>
</div>
<div
@ -106,14 +100,10 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui success message"
>
<h4 class="header">
<translate >
Refresh successful
</translate>
</h4>
<p>
<translate >
Data was refreshed successfully from remote server.
</translate>
</p>
</div>
<div
@ -121,22 +111,16 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui error message"
>
<h4 class="header">
<translate >
Refresh error
</translate>
</h4>
<p>
<translate >
An error occurred while trying to refresh data:
</translate>
</p>
<table class="ui very basic collapsing celled table">
<tbody>
<tr>
<td>
<translate >
Error type
</translate>
</td>
<td>
{{ data.detail.error_code }}
@ -144,57 +128,47 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
</tr>
<tr>
<td>
<translate >
Error detail
</translate>
</td>
<td>
<translate
v-if="data.detail.error_code === 'http' && data.detail.status_code"
:translate-params="{status: data.detail.status_code}"
>
The remote server answered with HTTP %{ status }
</translate>
<translate
v-else-if="['http', 'request'].indexOf(data.detail.error_code) > -1"
>
An HTTP error occurred while contacting the remote server
</translate>
<translate
v-else-if="data.detail.error_code === 'timeout'"
>
The remote server didn't respond quickly enough
</translate>
<translate
v-else-if="data.detail.error_code === 'connection'"
>
Impossible to connect to the remote server
</translate>
<translate
v-else-if="['invalid_json', 'invalid_jsonld', 'missing_jsonld_type'].indexOf(data.detail.error_code) > -1"
>
The remote server returned invalid JSON or JSON-LD data
</translate>
<translate
v-else-if="data.detail.error_code === 'validation'"
>
Data returned by the remote server had invalid or missing attributes
</translate>
<translate
v-else-if="data.detail.error_code === 'unhandled'"
>
Unknown error
</translate>
<translate
v-else
>
Unknown error
</translate>
@ -209,9 +183,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui active inverted dimmer"
>
<div class="ui text loader">
<translate >
Requesting a fetch
</translate>
</div>
</div>
<div
@ -219,9 +191,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui active inverted dimmer"
>
<div class="ui text loader">
<translate >
Waiting for result
</translate>
</div>
</div>
<div
@ -230,9 +200,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui negative message"
>
<h4 class="header">
<translate >
Error while saving settings
</translate>
</h4>
<ul class="list">
<li
@ -249,31 +217,23 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui warning message"
>
<h4 class="header">
<translate >
Refresh pending
</translate>
</h4>
<p>
<translate >
The refresh request hasn't been processed in time by our server. It will be processed later.
</translate>
</p>
</div>
</div>
<div class="actions">
<button class="ui basic cancel button">
<translate >
Close
</translate>
</button>
<button
v-if="data && data.status === 'finished'"
class="ui confirm success button"
@click.prevent="showModal = false; emit('refresh')"
>
<translate >
Close and reload page
</translate>
</button>
</div>
</semantic-modal>

View File

@ -64,9 +64,7 @@ fetchData()
v-if="!isLoading && libraries.length === 0"
class="ui subtitle"
>
<translate >
No matching library.
</translate>
</p>
<div class="ui hidden divider" />
<div class="ui cards">
@ -92,9 +90,7 @@ fetchData()
:class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)"
>
<translate >
Show more
</translate>
</button>
</template>
</div>

View File

@ -323,9 +323,7 @@ const remove = async () => {
:to="{name: 'library.albums.edit', params: {id: object.id }}"
>
<i class="pencil icon" />
<translate >
Add a description
</translate>
</router-link>
</div>
</div>
@ -341,9 +339,7 @@ const remove = async () => {
:to="{name: 'library.albums.edit', params: {id: object.id }}"
>
<i class="pencil icon" />
<translate >
Add a description
</translate>
</router-link>
</template>
</div>

View File

@ -62,13 +62,11 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
<h2 class="ui header">
<translate
v-if="isSerie"
>
Episodes
</translate>
<translate
v-else
>
Tracks
</translate>
@ -137,17 +135,13 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
<template v-if="!artist.channel && !isSerie">
<h2>
<translate >
User libraries
</translate>
</h2>
<library-widget
:url="'albums/' + object.id + '/libraries/'"
@loaded="emit('libraries-loaded', $event)"
>
<translate >
This album is present in the following libraries:
</translate>
</library-widget>
</template>
</div>

View File

@ -53,7 +53,7 @@ const remove = () => emit('remove')
v-model:show="showEmbedModal"
>
<h4 class="header">
<translate >Embed this album on your website</translate>
Embed this album on your website
</h4>
<div class="scrolling content">
<div class="description">
@ -66,7 +66,7 @@ const remove = () => emit('remove')
</div>
<div class="actions">
<button class="ui basic deny button">
<translate >Cancel</translate>
Cancel
</button>
</div>
</semantic-modal>
@ -86,7 +86,6 @@ const remove = () => emit('remove')
<i class="external icon" />
<translate
:translate-params="{domain: domain}"
>View on %{ domain }</translate>
</a>
@ -97,7 +96,7 @@ const remove = () => emit('remove')
@click="showEmbedModal = !showEmbedModal"
>
<i class="code icon" />
<translate >Embed</translate>
Embed
</div>
<a
v-if="isAlbum && musicbrainzUrl"
@ -107,7 +106,7 @@ const remove = () => emit('remove')
class="basic item"
>
<i class="external icon" />
<translate >View on MusicBrainz</translate>
View on MusicBrainz
</a>
<a
v-if="!isChannel && isAlbum"
@ -117,7 +116,7 @@ const remove = () => emit('remove')
class="basic item"
>
<i class="external icon" />
<translate >Search on Discogs</translate>
Search on Discogs
</a>
<router-link
v-if="object.is_local"
@ -125,7 +124,7 @@ const remove = () => emit('remove')
class="basic item"
>
<i class="edit icon" />
<translate >Edit</translate>
Edit
</router-link>
<dangerous-button
v-if="artist && $store.state.auth.authenticated && artist.channel && artist.attributed_to.full_username === $store.state.auth.fullUsername"
@ -133,24 +132,22 @@ const remove = () => emit('remove')
@confirm="remove()"
>
<i class="ui trash icon" />
<translate >Delete</translate>
Delete
<template #modal-header>
<p>
<translate >Delete this album?</translate>
Delete this album?
</p>
</template>
<template #modal-content>
<div>
<p>
<translate >
The album will be deleted, as well as any related files and data. This action is irreversible.
</translate>
</p>
</div>
</template>
<template #modal-confirm>
<p>
<translate >Delete</translate>
Delete
</p>
</template>
</dangerous-button>
@ -171,7 +168,7 @@ const remove = () => emit('remove')
:to="{name: 'manage.library.albums.detail', params: {id: object.id}}"
>
<i class="wrench icon" />
<translate >Open in moderation interface</translate>
Open in moderation interface
</router-link>
<a
v-if="$store.state.auth.profile && $store.state.auth.profile?.is_superuser"
@ -181,7 +178,7 @@ const remove = () => emit('remove')
rel="noopener noreferrer"
>
<i class="wrench icon" />
<translate >View in Django's admin</translate>&nbsp;
View in Django's admin&nbsp;
</a>
</div>
</button>

View File

@ -24,13 +24,11 @@ const canEdit = store.state.auth.availablePermissions.library
<h2>
<translate
v-if="canEdit"
>
Edit this album
</translate>
<translate
v-else
>
Suggest an edit on this album
</translate>
@ -39,9 +37,7 @@ const canEdit = store.state.auth.availablePermissions.library
v-if="!object.is_local"
class="ui message"
>
<translate >
This object is managed by another server, you cannot edit it.
</translate>
</div>
<edit-form
v-else

View File

@ -121,9 +121,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<main v-title="labels.title">
<section class="ui vertical stripe segment">
<h2 class="ui header">
<translate >
Browsing albums
</translate>
</h2>
<form
:class="['ui', {'loading': isLoading}, 'form']"
@ -132,7 +130,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<div class="fields">
<div class="field">
<label for="albums-search">
<translate >Search</translate>
Search
</label>
<div class="ui action input">
<input
@ -152,11 +150,11 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
</div>
</div>
<div class="field">
<label for="tags-search"><translate >Tags</translate></label>
<label for="tags-search">Tags</label>
<tags-selector v-model="tags" />
</div>
<div class="field">
<label for="album-ordering"><translate >Ordering</translate></label>
<label for="album-ordering">Ordering</label>
<select
id="album-ordering"
v-model="ordering"
@ -172,26 +170,22 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
</select>
</div>
<div class="field">
<label for="album-ordering-direction"><translate >Ordering direction</translate></label>
<label for="album-ordering-direction">Ordering direction</label>
<select
id="album-ordering-direction"
v-model="orderingDirection"
class="ui dropdown"
>
<option value="+">
<translate >
Ascending
</translate>
</option>
<option value="-">
<translate >
Descending
</translate>
</option>
</select>
</div>
<div class="field">
<label for="album-results"><translate >Results per page</translate></label>
<label for="album-results">Results per page</label>
<select
id="album-results"
v-model="paginateBy"
@ -234,9 +228,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
>
<div class="ui icon header">
<i class="compact disc icon" />
<translate >
No results matching your query
</translate>
</div>
<router-link
v-if="$store.state.auth.authenticated"
@ -244,9 +236,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
class="ui success button labeled icon"
>
<i class="upload icon" />
<translate >
Add some music
</translate>
</router-link>
</div>
</div>

View File

@ -147,9 +147,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="vibrant"
:artist="object"
>
<translate >
Play all albums
</translate>
</play-button>
</div>
@ -158,9 +156,7 @@ watch(() => props.id, fetchData, { immediate: true })
v-model:show="showEmbedModal"
>
<h4 class="header">
<translate >
Embed this artist work on your website
</translate>
</h4>
<div class="scrolling content">
<div class="description">
@ -172,9 +168,7 @@ watch(() => props.id, fetchData, { immediate: true })
</div>
<div class="actions">
<button class="ui deny button">
<translate >
Cancel
</translate>
</button>
</div>
</semantic-modal>
@ -183,9 +177,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="ui button"
@click="dropdown.click()"
>
<translate >
More
</translate>
</button>
<button
ref="dropdown"
@ -203,7 +195,6 @@ watch(() => props.id, fetchData, { immediate: true })
<i class="external icon" />
<translate
:translate-params="{domain: domain}"
>View on %{ domain }</translate>
</a>
@ -214,9 +205,7 @@ watch(() => props.id, fetchData, { immediate: true })
@click.prevent="showEmbedModal = !showEmbedModal"
>
<i class="code icon" />
<translate >
Embed
</translate>
</button>
<a
:href="wikipediaUrl"
@ -225,7 +214,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="basic item"
>
<i class="wikipedia w icon" />
<translate >Search on Wikipedia</translate>
Search on Wikipedia
</a>
<a
v-if="musicbrainzUrl"
@ -235,7 +224,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="basic item"
>
<i class="external icon" />
<translate >View on MusicBrainz</translate>
View on MusicBrainz
</a>
<a
:href="discogsUrl"
@ -244,7 +233,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="basic item"
>
<i class="external icon" />
<translate >Search on Discogs</translate>
Search on Discogs
</a>
<router-link
v-if="object.is_local"
@ -252,9 +241,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="basic item"
>
<i class="edit icon" />
<translate >
Edit
</translate>
</router-link>
<div class="divider" />
<div
@ -274,9 +261,7 @@ watch(() => props.id, fetchData, { immediate: true })
:to="{name: 'manage.library.artists.detail', params: {id: object.id}}"
>
<i class="wrench icon" />
<translate >
Open in moderation interface
</translate>
</router-link>
<a
v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
@ -286,7 +271,7 @@ watch(() => props.id, fetchData, { immediate: true })
rel="noopener noreferrer"
>
<i class="wrench icon" />
<translate >View in Django's admin</translate>&nbsp;
View in Django's admin&nbsp;
</a>
</div>
</button>

View File

@ -65,25 +65,19 @@ const loadMoreAlbums = async () => {
<div class="ui hidden divider" />
<div class="ui message">
<p>
<translate >
You are currently hiding content related to this artist.
</translate>
</p>
<router-link
class="right floated"
:to="{name: 'settings'}"
>
<translate >
Review my filters
</translate>
</router-link>
<button
class="ui basic tiny button"
@click="$store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)"
>
<translate >
Remove filter
</translate>
</button>
</div>
</div>
@ -98,9 +92,7 @@ const loadMoreAlbums = async () => {
class="ui vertical stripe segment"
>
<h2>
<translate >
Albums by this artist
</translate>
</h2>
<div class="ui cards app-cards">
<album-card
@ -115,9 +107,7 @@ const loadMoreAlbums = async () => {
:class="['ui', {loading: isLoadingMoreAlbums}, 'button']"
@click="loadMoreAlbums()"
>
<translate >
Load more
</translate>
</button>
</section>
<section
@ -132,9 +122,7 @@ const loadMoreAlbums = async () => {
>
<template #header>
<h2>
<translate >
New tracks by this artist
</translate>
</h2>
<div class="ui hidden divider" />
</template>
@ -142,17 +130,13 @@ const loadMoreAlbums = async () => {
</section>
<section class="ui vertical stripe segment">
<h2>
<translate >
User libraries
</translate>
</h2>
<library-widget
:url="'artists/' + object.id + '/libraries/'"
@loaded="emit('libraries-loaded', $event)"
>
<translate >
This artist is present in the following libraries:
</translate>
</library-widget>
</section>
</div>

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