Replace gettext with i18n
This commit is contained in:
parent
2694417bdf
commit
ebea32faf9
|
@ -76,7 +76,6 @@
|
|||
"@vue/eslint-config-typescript": "11.0.2",
|
||||
"@vue/test-utils": "2.2.4",
|
||||
"@vue/tsconfig": "0.1.3",
|
||||
"easygettext": "2.17.0",
|
||||
"eslint": "8.28.0",
|
||||
"eslint-config-standard": "17.0.0",
|
||||
"eslint-plugin-html": "7.1.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { useStore } from '~/store'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { get } from 'lodash-es'
|
||||
import { humanSize } from '~/utils/filters'
|
||||
import { computed } from 'vue'
|
||||
|
@ -11,9 +11,9 @@ import LogoText from '~/components/LogoText.vue'
|
|||
const store = useStore()
|
||||
const nodeinfo = computed(() => store.state.instance.nodeinfo)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/About/Title', 'About')
|
||||
title: t('About')
|
||||
}))
|
||||
|
||||
const podName = computed(() => get(nodeinfo.value, 'metadata.nodeName') ?? 'Funkwhale')
|
||||
|
@ -67,12 +67,12 @@ const headerStyle = computed(() => {
|
|||
<div class="column" />
|
||||
</div>
|
||||
<h2 class="header">
|
||||
<translate translate-context="Content/About/Heading">
|
||||
<translate >
|
||||
A social platform to enjoy and share music
|
||||
</translate>
|
||||
</h2>
|
||||
<p>
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<translate >
|
||||
Funkwhale is a community-driven project that lets you listen and share music and audio within a decentralized, open network.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -88,19 +88,19 @@ const headerStyle = computed(() => {
|
|||
class="signup-form content"
|
||||
>
|
||||
<h3 class="header">
|
||||
<translate translate-context="*/Signup/Title">
|
||||
<translate >
|
||||
Sign up
|
||||
</translate>
|
||||
</h3>
|
||||
<template v-if="openRegistrations">
|
||||
<p>
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<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-context="Content/About/Paragraph"
|
||||
|
||||
:translate-params="{quota: defaultUploadQuota}"
|
||||
>
|
||||
Users on this pod also get %{ quota } of free storage to upload their own content!
|
||||
|
@ -112,7 +112,7 @@ const headerStyle = computed(() => {
|
|||
/>
|
||||
</template>
|
||||
<div v-else>
|
||||
<p translate-context="Content/About/Paragraph">
|
||||
<p >
|
||||
Registrations are closed on this pod. You can signup on another pod using the link below.
|
||||
</p>
|
||||
|
||||
|
@ -121,7 +121,7 @@ const headerStyle = computed(() => {
|
|||
rel="noopener"
|
||||
href="https://funkwhale.audio/#get-started"
|
||||
>
|
||||
<translate translate-context="Content/About/Link">Find another pod</translate>
|
||||
<translate >Find another pod</translate>
|
||||
<i class="external alternate icon" />
|
||||
</a>
|
||||
</div>
|
||||
|
@ -131,12 +131,12 @@ const headerStyle = computed(() => {
|
|||
class="signup-form content"
|
||||
>
|
||||
<h3 class="header">
|
||||
<translate translate-context="*/Signup/Title">
|
||||
<translate >
|
||||
Sign up
|
||||
</translate>
|
||||
<div class="ui positive message">
|
||||
<div class="header">
|
||||
<translate translate-context="Content/About/Message">
|
||||
<translate >
|
||||
You're already signed in!
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -164,7 +164,7 @@ const headerStyle = computed(() => {
|
|||
id="description"
|
||||
class="ui header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
About this pod
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -175,7 +175,7 @@ const headerStyle = computed(() => {
|
|||
{{ shortDescription }}
|
||||
</div>
|
||||
<p v-else>
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<translate >
|
||||
No description available.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -188,7 +188,7 @@ const headerStyle = computed(() => {
|
|||
<span class="ui big text"><strong>{{ stats.users.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
<translate
|
||||
translate-context="Content/About/*"
|
||||
|
||||
:translate-n="stats.users"
|
||||
translate-plural="active users"
|
||||
>active user</translate>
|
||||
|
@ -213,7 +213,7 @@ const headerStyle = computed(() => {
|
|||
to="/about/pod"
|
||||
class="ui fluid basic secondary button"
|
||||
>
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<translate >
|
||||
Learn More
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -234,12 +234,12 @@ const headerStyle = computed(() => {
|
|||
id="description"
|
||||
class="ui header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
Browse public content
|
||||
</translate>
|
||||
</h3>
|
||||
<p>
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<translate >
|
||||
Listen to public albums and playlists shared on this pod.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -255,11 +255,11 @@ const headerStyle = computed(() => {
|
|||
id="description"
|
||||
class="ui header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">Find another pod</translate>
|
||||
<translate >Find another pod</translate>
|
||||
<i class="external alternate icon" />
|
||||
</h3>
|
||||
<p>
|
||||
<translate translate-context="Content/About/Paragraph">Listen to public albums and playlists shared on this pod.</translate>
|
||||
<translate >Listen to public albums and playlists shared on this pod.</translate>
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
@ -273,11 +273,11 @@ const headerStyle = computed(() => {
|
|||
id="description"
|
||||
class="ui header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">Find an app</translate>
|
||||
<translate >Find an app</translate>
|
||||
<i class="external alternate icon" />
|
||||
</h3>
|
||||
<p>
|
||||
<translate translate-context="Content/About/Paragraph">Use Funkwhale on other devices with our apps.</translate>
|
||||
<translate >Use Funkwhale on other devices with our apps.</translate>
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
@ -287,7 +287,7 @@ const headerStyle = computed(() => {
|
|||
to="/about/pod"
|
||||
class="ui right floated basic secondary button"
|
||||
>
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<translate >
|
||||
About this pod
|
||||
</translate>
|
||||
<i class="icon arrow right" />
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { humanSize } from '~/utils/filters'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useStore } from '~/store'
|
||||
import { get } from 'lodash-es'
|
||||
import { computed } from 'vue'
|
||||
|
@ -9,6 +8,7 @@ import axios from 'axios'
|
|||
|
||||
import useMarkdown from '~/composables/useMarkdown'
|
||||
import type { NodeInfo } from '~/store/instance'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const store = useStore()
|
||||
const nodeinfo = computed(() => store.state.instance.nodeinfo)
|
||||
|
@ -19,9 +19,9 @@ const fetchData = async () => {
|
|||
}
|
||||
fetchData()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/About/Title', 'About')
|
||||
title: t('About')
|
||||
}))
|
||||
|
||||
const podName = computed(() => get(nodeinfo.value, 'metadata.nodeName') || 'Funkwhale')
|
||||
|
@ -99,7 +99,7 @@ const headerStyle = computed(() => {
|
|||
to="/about/pod"
|
||||
class="item"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
About this pod
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -107,7 +107,7 @@ const headerStyle = computed(() => {
|
|||
to="/about/pod#rules"
|
||||
class="item"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
Rules
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -115,7 +115,7 @@ const headerStyle = computed(() => {
|
|||
to="/about/pod#terms"
|
||||
class="item"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
Terms and privacy policy
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -123,7 +123,7 @@ const headerStyle = computed(() => {
|
|||
to="/about/pod#features"
|
||||
class="item"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
Features
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -132,7 +132,7 @@ const headerStyle = computed(() => {
|
|||
to="/about/pod#statistics"
|
||||
class="item"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
Statistics
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -144,7 +144,7 @@ const headerStyle = computed(() => {
|
|||
id="description about-this-pod"
|
||||
class="ui header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
About this pod
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -153,7 +153,7 @@ const headerStyle = computed(() => {
|
|||
:html="longDescription"
|
||||
/>
|
||||
<p v-else>
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<translate >
|
||||
No description available.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -162,7 +162,7 @@ const headerStyle = computed(() => {
|
|||
id="rules"
|
||||
class="ui header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
Rules
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -171,7 +171,7 @@ const headerStyle = computed(() => {
|
|||
:html="rules"
|
||||
/>
|
||||
<p v-else>
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<translate >
|
||||
No rules available.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -180,7 +180,7 @@ const headerStyle = computed(() => {
|
|||
id="terms"
|
||||
class="ui header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
Terms and privacy policy
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -189,7 +189,7 @@ const headerStyle = computed(() => {
|
|||
:html="terms"
|
||||
/>
|
||||
<p v-else>
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<translate >
|
||||
No terms available.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -198,7 +198,7 @@ const headerStyle = computed(() => {
|
|||
id="features"
|
||||
class="header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header/Name">
|
||||
<translate >
|
||||
Features
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -208,7 +208,7 @@ const headerStyle = computed(() => {
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Funkwhale version
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -225,13 +225,13 @@ const headerStyle = computed(() => {
|
|||
class="right aligned"
|
||||
>
|
||||
<span class="features-status ui text">
|
||||
<translate translate-context="*/*/*">N/A</translate>
|
||||
<translate >N/A</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Federation
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -241,7 +241,7 @@ const headerStyle = computed(() => {
|
|||
>
|
||||
<span class="features-status ui text">
|
||||
<i class="check icon" />
|
||||
<translate translate-context="*/*/*/State of feature">Enabled</translate>
|
||||
<translate >Enabled</translate>
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
|
@ -250,13 +250,13 @@ const headerStyle = computed(() => {
|
|||
>
|
||||
<span class="features-status ui text">
|
||||
<i class="x icon" />
|
||||
<translate translate-context="*/*/*/State of feature">Disabled</translate>
|
||||
<translate >Disabled</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Allow-list
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -266,7 +266,7 @@ const headerStyle = computed(() => {
|
|||
>
|
||||
<span class="features-status ui text">
|
||||
<i class="check icon" />
|
||||
<translate translate-context="*/*/*/State of feature">Enabled</translate>
|
||||
<translate >Enabled</translate>
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
|
@ -275,7 +275,7 @@ const headerStyle = computed(() => {
|
|||
>
|
||||
<span class="features-status ui text">
|
||||
<i class="x icon" />
|
||||
<translate translate-context="*/*/*/State of feature">Disabled</translate>
|
||||
<translate >Disabled</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -287,7 +287,7 @@ const headerStyle = computed(() => {
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Anonymous access
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -297,7 +297,7 @@ const headerStyle = computed(() => {
|
|||
>
|
||||
<span class="features-status ui text">
|
||||
<i class="check icon" />
|
||||
<translate translate-context="*/*/*/State of feature">Enabled</translate>
|
||||
<translate >Enabled</translate>
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
|
@ -306,13 +306,13 @@ const headerStyle = computed(() => {
|
|||
>
|
||||
<span class="features-status ui text">
|
||||
<i class="x icon" />
|
||||
<translate translate-context="*/*/*/State of feature">Disabled</translate>
|
||||
<translate >Disabled</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Registrations
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -322,7 +322,7 @@ const headerStyle = computed(() => {
|
|||
>
|
||||
<span class="features-status ui text">
|
||||
<i class="check icon" />
|
||||
<translate translate-context="*/*/*/State of registrations">Open</translate>
|
||||
<translate >Open</translate>
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
|
@ -331,13 +331,13 @@ const headerStyle = computed(() => {
|
|||
>
|
||||
<span class="features-status ui text">
|
||||
<i class="x icon" />
|
||||
<translate translate-context="*/*/*/State of registrations">Closed</translate>
|
||||
<translate >Closed</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Upload quota
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -354,7 +354,7 @@ const headerStyle = computed(() => {
|
|||
class="right aligned"
|
||||
>
|
||||
<span class="features-status ui text">
|
||||
<translate translate-context="*/*/*">N/A</translate>
|
||||
<translate >N/A</translate>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -368,7 +368,7 @@ const headerStyle = computed(() => {
|
|||
id="statistics"
|
||||
class="header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
Statistics
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -395,7 +395,7 @@ const headerStyle = computed(() => {
|
|||
<span class="ui big text"><strong>{{ stats.artists.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
<translate
|
||||
translate-context="Content/About/*"
|
||||
|
||||
:translate-n="stats.artists"
|
||||
translate-plural="artists"
|
||||
>artist</translate>
|
||||
|
@ -409,7 +409,7 @@ const headerStyle = computed(() => {
|
|||
<span class="ui big text"><strong>{{ stats.albums.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
<translate
|
||||
translate-context="Content/About/*"
|
||||
|
||||
:translate-n="stats.albums"
|
||||
translate-plural="albums"
|
||||
>album</translate>
|
||||
|
@ -423,7 +423,7 @@ const headerStyle = computed(() => {
|
|||
<span class="ui big text"><strong>{{ stats.tracks.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
<translate
|
||||
translate-context="Content/About/*"
|
||||
|
||||
:translate-n="stats.tracks"
|
||||
translate-plural="tracks"
|
||||
>track</translate>
|
||||
|
@ -437,7 +437,7 @@ const headerStyle = computed(() => {
|
|||
<span class="ui big text"><strong>{{ stats.users.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
<translate
|
||||
translate-context="Content/About/*"
|
||||
|
||||
:translate-n="stats.users"
|
||||
translate-plural="active users"
|
||||
>active user</translate>
|
||||
|
@ -451,7 +451,7 @@ const headerStyle = computed(() => {
|
|||
<span class="ui big text"><strong>{{ stats.listenings.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
|
||||
<br>
|
||||
<translate
|
||||
translate-context="Content/About/*"
|
||||
|
||||
:translate-n="stats.listenings"
|
||||
translate-plural="listenings"
|
||||
>listening</translate>
|
||||
|
@ -465,7 +465,7 @@ const headerStyle = computed(() => {
|
|||
id="contact"
|
||||
class="ui header"
|
||||
>
|
||||
<translate translate-context="Content/About/Header">
|
||||
<translate >
|
||||
Contact
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -474,7 +474,7 @@ const headerStyle = computed(() => {
|
|||
:href="`mailto:${contactEmail}`"
|
||||
>
|
||||
<translate
|
||||
translate-context="Content/About/Email"
|
||||
|
||||
:translate-params="{ email: contactEmail }"
|
||||
>Send us an email: {{ contactEmail }}</translate>
|
||||
</a>
|
||||
|
@ -487,7 +487,7 @@ const headerStyle = computed(() => {
|
|||
class="ui left floated basic secondary button"
|
||||
>
|
||||
<i class="icon arrow left" />
|
||||
<translate translate-context="Content/About/Paragraph">
|
||||
<translate >
|
||||
Introduction
|
||||
</translate>
|
||||
</router-link>
|
||||
|
|
|
@ -9,12 +9,12 @@ import { humanSize } from '~/utils/filters'
|
|||
import { useStore } from '~/store'
|
||||
import { computed } from 'vue'
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/Home/Title', 'Home')
|
||||
title: t('Home')
|
||||
}))
|
||||
|
||||
const store = useStore()
|
||||
|
@ -70,13 +70,12 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
>
|
||||
<div class="segment-content">
|
||||
<h1 class="ui center aligned large header">
|
||||
<span
|
||||
v-translate="{podName: podName}"
|
||||
translate-context="Content/Home/Header"
|
||||
<translate
|
||||
:translate-params="{podName: podName}"
|
||||
tag="span"
|
||||
>
|
||||
Welcome to %{ podName }!
|
||||
</span>
|
||||
</translate>
|
||||
<div
|
||||
v-if="shortDescription"
|
||||
class="sub header"
|
||||
|
@ -90,7 +89,7 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
<div class="ui stackable grid">
|
||||
<div class="ten wide column">
|
||||
<h2 class="header">
|
||||
<translate translate-context="Content/Home/Header">
|
||||
<translate >
|
||||
About this Funkwhale pod
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -101,7 +100,7 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
<div class="ui stackable grid">
|
||||
<div class="eight wide column">
|
||||
<p v-if="!longDescription">
|
||||
<translate translate-context="Content/Home/Paragraph">
|
||||
<translate >
|
||||
No description available.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -126,7 +125,7 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
class="ui link"
|
||||
:to="{name: 'about'}"
|
||||
>
|
||||
<translate translate-context="Content/Home/Link">
|
||||
<translate >
|
||||
Learn more
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -143,7 +142,7 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
class="ui link"
|
||||
:to="{name: 'about', hash: '#rules'}"
|
||||
>
|
||||
<translate translate-context="Content/Home/Link">
|
||||
<translate >
|
||||
Server rules
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -155,13 +154,13 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
<div class="eight wide column">
|
||||
<template v-if="stats">
|
||||
<h3 class="sub header">
|
||||
<translate translate-context="Content/Home/Header">
|
||||
<translate >
|
||||
Statistics
|
||||
</translate>
|
||||
</h3>
|
||||
<p>
|
||||
<i class="user icon" /><translate
|
||||
translate-context="Content/Home/Stat"
|
||||
|
||||
:translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }"
|
||||
:translate-n="stats.users"
|
||||
translate-plural="%{ count } active users"
|
||||
|
@ -182,7 +181,7 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
</template>
|
||||
<template v-if="contactEmail">
|
||||
<h3 class="sub header">
|
||||
<translate translate-context="Content/Home/Header/Name">
|
||||
<translate >
|
||||
Contact
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -207,34 +206,28 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
<div class="ui stackable grid">
|
||||
<div class="four wide column">
|
||||
<h3 class="header">
|
||||
<translate translate-context="Footer/*/Title/Short">
|
||||
<translate >
|
||||
About Funkwhale
|
||||
</translate>
|
||||
</h3>
|
||||
<p
|
||||
v-translate
|
||||
translate-context="Content/Home/Paragraph"
|
||||
>
|
||||
<translate tag="p">
|
||||
This pod runs Funkwhale, a community-driven project that lets you listen and share music and audio within a decentralized, open network.
|
||||
</p>
|
||||
<p
|
||||
v-translate
|
||||
translate-context="Content/Home/Paragraph"
|
||||
>
|
||||
</translate>
|
||||
<translate tag="p">
|
||||
Funkwhale is free and developed by a friendly community of volunteers.
|
||||
</p>
|
||||
</translate>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
href="https://funkwhale.audio"
|
||||
>
|
||||
<i class="external alternate icon" />
|
||||
<translate translate-context="Content/Home/Link">Visit funkwhale.audio</translate>
|
||||
<translate >Visit funkwhale.audio</translate>
|
||||
</a>
|
||||
</div>
|
||||
<div class="four wide column">
|
||||
<h3 class="header">
|
||||
<translate translate-context="Head/Login/Title">
|
||||
<translate >
|
||||
Log In
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -246,19 +239,19 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
</div>
|
||||
<div class="four wide column">
|
||||
<h3 class="header">
|
||||
<translate translate-context="*/Signup/Title">
|
||||
<translate >
|
||||
Sign up
|
||||
</translate>
|
||||
</h3>
|
||||
<template v-if="openRegistrations">
|
||||
<p>
|
||||
<translate translate-context="Content/Home/Paragraph">
|
||||
<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-context="Content/Home/Paragraph"
|
||||
|
||||
:translate-params="{quota: humanSize(defaultUploadQuota * 1000 * 1000)}"
|
||||
>
|
||||
Users on this pod also get %{ quota } of free storage to upload their own content!
|
||||
|
@ -270,7 +263,7 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
/>
|
||||
</template>
|
||||
<div v-else>
|
||||
<p translate-context="Content/Home/Paragraph">
|
||||
<p >
|
||||
Registrations are closed on this pod. You can signup on another pod using the link below.
|
||||
</p>
|
||||
<a
|
||||
|
@ -279,14 +272,14 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
href="https://funkwhale.audio/#get-started"
|
||||
>
|
||||
<i class="external alternate icon" />
|
||||
<translate translate-context="Content/Home/Link">Find another pod</translate>
|
||||
<translate >Find another pod</translate>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="four wide column">
|
||||
<h3 class="header">
|
||||
<translate translate-context="Content/Home/Header">
|
||||
<translate >
|
||||
Useful links
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -299,12 +292,12 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
class="header"
|
||||
to="/library"
|
||||
>
|
||||
<translate translate-context="Content/Home/Link">
|
||||
<translate >
|
||||
Browse public content
|
||||
</translate>
|
||||
</router-link>
|
||||
<div class="description">
|
||||
<translate translate-context="Content/Home/Link">
|
||||
<translate >
|
||||
Listen to public albums and playlists shared on this pod
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -319,10 +312,10 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<translate translate-context="Content/Home/Link">Mobile apps</translate>
|
||||
<translate >Mobile apps</translate>
|
||||
</a>
|
||||
<div class="description">
|
||||
<translate translate-context="Content/Home/Link">
|
||||
<translate >
|
||||
Use Funkwhale on other devices with our apps
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -337,10 +330,10 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<translate translate-context="Content/Home/Link">User guides</translate>
|
||||
<translate >User guides</translate>
|
||||
</a>
|
||||
<div class="description">
|
||||
<translate translate-context="Content/Home/Link">
|
||||
<translate >
|
||||
Discover everything you need to know about Funkwhale and its features
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -359,12 +352,12 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
:limit="10"
|
||||
>
|
||||
<template #title>
|
||||
<translate translate-context="Content/Home/Title">
|
||||
<translate >
|
||||
Recently added albums
|
||||
</translate>
|
||||
</template>
|
||||
<router-link to="/library">
|
||||
<translate translate-context="Content/Home/Link">
|
||||
<translate >
|
||||
View more…
|
||||
</translate>
|
||||
<div class="ui hidden divider" />
|
||||
|
@ -372,7 +365,7 @@ whenever(() => store.state.auth.authenticated, () => {
|
|||
</album-widget>
|
||||
<div class="ui hidden section divider" />
|
||||
<h3 class="ui header">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
New channels
|
||||
</translate>
|
||||
</h3>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const path = window.location.href
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/*/Title', 'Page Not Found')
|
||||
title: t('Page Not Found')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -20,13 +20,13 @@ const labels = computed(() => ({
|
|||
<h1 class="ui huge header">
|
||||
<i class="warning icon" />
|
||||
<div class="content">
|
||||
<translate translate-context="Content/*/Title">
|
||||
<translate >
|
||||
Page not found!
|
||||
</translate>
|
||||
</div>
|
||||
</h1>
|
||||
<p>
|
||||
<translate translate-context="Content/*/Paragraph">
|
||||
<translate >
|
||||
Sorry, the page you asked for does not exist:
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -36,7 +36,7 @@ const labels = computed(() => ({
|
|||
class="ui icon labeled right button"
|
||||
to="/"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Go to home page
|
||||
</translate>
|
||||
<i class="right arrow icon" />
|
||||
|
|
|
@ -4,7 +4,6 @@ import type { QueueItemSource } from '~/types'
|
|||
import { whenever, watchDebounced, useCurrentElement, useScrollLock, useFullscreen, useIdle, refAutoReset, useStorage } from '@vueuse/core'
|
||||
import { nextTick, ref, computed, watchEffect, onMounted } from 'vue'
|
||||
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
|
@ -18,6 +17,11 @@ 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'
|
||||
|
||||
|
@ -49,24 +53,24 @@ const { currentSound } = useTracks()
|
|||
const queueModal = ref()
|
||||
const { activate, deactivate } = useFocusTrap(queueModal, { allowOutsideClick: true, preventScroll: true })
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const scrollLock = useScrollLock(document.body)
|
||||
const store = useStore()
|
||||
|
||||
const labels = computed(() => ({
|
||||
queue: $pgettext('*/*/*', 'Queue'),
|
||||
populating: $pgettext('*/*/*', 'Fetching radio track'),
|
||||
duration: $pgettext('*/*/*', 'Duration'),
|
||||
addArtistContentFilter: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Hide content from this artist…'),
|
||||
restart: $pgettext('*/*/*', 'Restart track'),
|
||||
previous: $pgettext('*/*/*', 'Previous track'),
|
||||
next: $pgettext('*/*/*', 'Next track'),
|
||||
pause: $pgettext('*/*/*', 'Pause'),
|
||||
play: $pgettext('*/*/*', 'Play'),
|
||||
fullscreen: $pgettext('*/*/*', 'Fullscreen'),
|
||||
exitFullscreen: $pgettext('*/*/*', 'Exit fullscreen'),
|
||||
showCoverArt: $pgettext('*/*/*', 'Show cover art'),
|
||||
showVisualizer: $pgettext('*/*/*', 'Show visualizer')
|
||||
queue: t('Queue'),
|
||||
populating: t('Fetching radio track'),
|
||||
duration: t('Duration'),
|
||||
addArtistContentFilter: t('Hide content from this artist…'),
|
||||
restart: t('Restart track'),
|
||||
previous: t('Previous track'),
|
||||
next: t('Next track'),
|
||||
pause: t('Pause'),
|
||||
play: t('Play'),
|
||||
fullscreen: t('Fullscreen'),
|
||||
exitFullscreen: t('Exit fullscreen'),
|
||||
showCoverArt: t('Show cover art'),
|
||||
showVisualizer: t('Show visualizer')
|
||||
}))
|
||||
|
||||
watchEffect(async () => {
|
||||
|
@ -125,10 +129,11 @@ const queueItems = computed(() => queue.value.map((track, index) => ({
|
|||
...track,
|
||||
key: `${index}-${track.id}`,
|
||||
labels: {
|
||||
remove: $pgettext('*/*/*', 'Remove'),
|
||||
selectTrack: $pgettext('*/*/*', 'Select track'),
|
||||
favorite: $pgettext('*/*/*', 'Favorite track')
|
||||
}
|
||||
remove: t('Remove'),
|
||||
selectTrack: t('Select track'),
|
||||
favorite: t('Favorite track')
|
||||
},
|
||||
duration: time.durationFormatted(track.uploads[0]?.duration ?? 0) ?? ''
|
||||
}) as QueueItemSource))
|
||||
|
||||
const reorderTracks = async (from: number, to: number) => {
|
||||
|
@ -296,18 +301,18 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
|
|||
class="ui small warning message"
|
||||
>
|
||||
<h3 class="header">
|
||||
<translate translate-context="Sidebar/Player/Error message.Title">
|
||||
<translate >
|
||||
The track cannot be loaded
|
||||
</translate>
|
||||
</h3>
|
||||
<p v-if="hasNext && isPlaying">
|
||||
<translate translate-context="Sidebar/Player/Error message.Paragraph">
|
||||
<translate >
|
||||
The next track will play automatically in a few seconds…
|
||||
</translate>
|
||||
<i class="loading spinner icon" />
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-context="Sidebar/Player/Error message.Paragraph">
|
||||
<translate >
|
||||
You may have a connectivity issue.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -408,7 +413,7 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
|
|||
class="ui right floated basic button"
|
||||
@click="$store.commit('ui/queueFocused', null)"
|
||||
>
|
||||
<translate translate-context="*/Queue/*/Verb">
|
||||
<translate >
|
||||
Close
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -416,7 +421,7 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
|
|||
class="ui right floated basic button danger"
|
||||
@click="clear"
|
||||
>
|
||||
<translate translate-context="*/Queue/*/Verb">
|
||||
<translate >
|
||||
Clear
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -424,7 +429,6 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
|
|||
<div class="sub header">
|
||||
<div>
|
||||
<translate
|
||||
translate-context="Sidebar/Queue/Text"
|
||||
:translate-params="{index: currentIndex + 1, length: queue.length}"
|
||||
>
|
||||
Track %{ index } of %{ length }
|
||||
|
@ -473,12 +477,12 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
|
|||
>
|
||||
<div class="content">
|
||||
<h3 class="header">
|
||||
<i class="feed icon" /> <translate translate-context="Sidebar/Player/Title">
|
||||
<i class="feed icon" /> <translate >
|
||||
You have a radio playing
|
||||
</translate>
|
||||
</h3>
|
||||
<p>
|
||||
<translate translate-context="Sidebar/Player/Paragraph">
|
||||
<translate >
|
||||
New tracks will be appended here automatically.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -486,7 +490,7 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
|
|||
class="ui basic primary button"
|
||||
@click="$store.dispatch('radios/stop')"
|
||||
>
|
||||
<translate translate-context="*/Player/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Stop radio
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { BackendError } from '~/types'
|
||||
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
|
@ -37,17 +37,17 @@ const type = ref(props.initialType)
|
|||
const id = ref(props.initialId)
|
||||
const errors = ref([] as string[])
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: type.value === 'rss'
|
||||
? $pgettext('Head/Fetch/Title', 'Subscribe to a podcast RSS feed')
|
||||
: $pgettext('Head/Fetch/Title', 'Subscribe to a podcast hosted on the Fediverse'),
|
||||
? t('Subscribe to a podcast RSS feed')
|
||||
: t('Subscribe to a podcast hosted on the Fediverse'),
|
||||
fieldLabel: type.value === 'rss'
|
||||
? $pgettext('*/*/*', 'RSS feed location')
|
||||
: $pgettext('*/*/*', 'Fediverse object'),
|
||||
? t('RSS feed location')
|
||||
: t('Fediverse object'),
|
||||
fieldPlaceholder: type.value === 'rss'
|
||||
? $pgettext('Head/Fetch/Field.Placeholder', 'https://website.example.com/rss.xml')
|
||||
: $pgettext('Head/Fetch/Field.Placeholder', '@username@example.com')
|
||||
? t('https://website.example.com/rss.xml')
|
||||
: t('@username@example.com')
|
||||
}))
|
||||
|
||||
const obj = ref()
|
||||
|
@ -117,7 +117,7 @@ const createFetch = async () => {
|
|||
obj.value = response.data
|
||||
|
||||
if (response.data.status === 'errored' || response.data.status === 'skipped') {
|
||||
errors.value.push($pgettext('Content/*/Error message.Title', 'This object cannot be retrieved'))
|
||||
errors.value.push(t('This object cannot be retrieved'))
|
||||
}
|
||||
} catch (error) {
|
||||
errors.value = (error as BackendError).backendErrors
|
||||
|
@ -172,7 +172,7 @@ watch(() => props.initialId, () => {
|
|||
@click.prevent="type = 'rss'"
|
||||
>
|
||||
<i class="feed icon" />
|
||||
<translate translate-context="Content/Search/Input.Label/Noun">
|
||||
<translate >
|
||||
RSS
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -182,7 +182,7 @@ watch(() => props.initialId, () => {
|
|||
@click.prevent="type = 'artists'"
|
||||
>
|
||||
<i class="globe icon" />
|
||||
<translate translate-context="Content/Search/Input.Label/Noun">
|
||||
<translate >
|
||||
Fediverse
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -199,7 +199,7 @@ watch(() => props.initialId, () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h3 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Error while fetching object
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -217,12 +217,12 @@ watch(() => props.initialId, () => {
|
|||
{{ labels.fieldLabel }}
|
||||
</label>
|
||||
<p v-if="type === 'rss'">
|
||||
<translate translate-context="Content/Fetch/Paragraph">
|
||||
<translate >
|
||||
Use this form to subscribe to an RSS feed from its URL.
|
||||
</translate>
|
||||
</p>
|
||||
<p v-else-if="type === 'artists'">
|
||||
<translate translate-context="Content/Fetch/Paragraph">
|
||||
<translate >
|
||||
Use this form to subscribe to a channel hosted somewhere else on the Fediverse.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -241,7 +241,7 @@ watch(() => props.initialId, () => {
|
|||
:class="['ui', 'primary', {loading: isLoading}, 'button']"
|
||||
:disabled="isLoading || !id || id.length === 0"
|
||||
>
|
||||
<translate translate-context="Content/Search/Input.Label/Noun">
|
||||
<translate >
|
||||
Search
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -252,7 +252,7 @@ watch(() => props.initialId, () => {
|
|||
class="ui warning message"
|
||||
>
|
||||
<p>
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
This kind of object isn't supported yet
|
||||
</translate>
|
||||
</p>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { useStore } from '~/store'
|
||||
import { uniq } from 'lodash-es'
|
||||
|
@ -38,7 +38,7 @@ const suggestedInstances = computed(() => {
|
|||
|
||||
watch(() => store.state.instance.instanceUrl, () => store.dispatch('instance/fetchSettings'))
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const isError = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const checkAndSwitch = async (url: string) => {
|
||||
|
@ -51,7 +51,7 @@ const checkAndSwitch = async (url: string) => {
|
|||
|
||||
show.value = false
|
||||
store.commit('ui/addMessage', {
|
||||
content: $pgettext('*/Instance/Message', 'You are now using the Funkwhale instance at %{ url }', { url: instanceUrl }),
|
||||
content: t('You are now using the Funkwhale instance at %{ url }', { url: instanceUrl }),
|
||||
date: new Date()
|
||||
})
|
||||
|
||||
|
@ -71,7 +71,7 @@ const checkAndSwitch = async (url: string) => {
|
|||
@update:show="isError = false"
|
||||
>
|
||||
<h3 class="header">
|
||||
<translate translate-context="Popup/Instance/Title">
|
||||
<translate >
|
||||
Choose your instance
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -82,18 +82,18 @@ const checkAndSwitch = async (url: string) => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/Instance/Error message.Title">
|
||||
<translate >
|
||||
It is not possible to connect to the given URL
|
||||
</translate>
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li>
|
||||
<translate translate-context="Popup/Instance/Error message.List item">
|
||||
<translate >
|
||||
The server might be down
|
||||
</translate>
|
||||
</li>
|
||||
<li>
|
||||
<translate translate-context="Popup/Instance/Error message.List item">
|
||||
<translate >
|
||||
The given address is not a Funkwhale server
|
||||
</translate>
|
||||
</li>
|
||||
|
@ -107,7 +107,7 @@ 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"
|
||||
translate-context="Popup/Login/Paragraph"
|
||||
|
||||
>
|
||||
You are currently connected to <a
|
||||
href="%{ url }"
|
||||
|
@ -115,12 +115,12 @@ const checkAndSwitch = async (url: string) => {
|
|||
>%{ hostname } <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 translate-context="Popup/Instance/Paragraph">
|
||||
<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 translate-context="Popup/Instance/Input.Label/Noun">Instance URL</translate></label>
|
||||
<label for="instance-picker"><translate >Instance URL</translate></label>
|
||||
<div class="ui action input">
|
||||
<input
|
||||
id="instance-picker"
|
||||
|
@ -132,7 +132,7 @@ const checkAndSwitch = async (url: string) => {
|
|||
type="submit"
|
||||
:class="['ui', 'icon', {loading: isLoading}, 'button']"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Submit
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -146,7 +146,7 @@ const checkAndSwitch = async (url: string) => {
|
|||
>
|
||||
<div class="field">
|
||||
<h4>
|
||||
<translate translate-context="Popup/Instance/List.Label">
|
||||
<translate >
|
||||
Suggested choices
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -163,7 +163,7 @@ const checkAndSwitch = async (url: string) => {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic cancel button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import SemanticModal from '~/components/semantic/Modal.vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface Events {
|
||||
(e: 'update:show', show: boolean): void
|
||||
|
@ -17,22 +17,22 @@ const props = defineProps<Props>()
|
|||
|
||||
const showRef = useVModel(props, 'show', emit)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const general = computed(() => [
|
||||
{
|
||||
title: $pgettext('Popup/Keyboard shortcuts/Title', 'General shortcuts'),
|
||||
title: t('General shortcuts'),
|
||||
shortcuts: [
|
||||
{
|
||||
key: 'h',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Show available keyboard shortcuts')
|
||||
summary: t('Show available keyboard shortcuts')
|
||||
},
|
||||
{
|
||||
key: 'shift + f',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Focus searchbar')
|
||||
summary: t('Focus searchbar')
|
||||
},
|
||||
{
|
||||
key: 'esc',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Unfocus searchbar')
|
||||
summary: t('Unfocus searchbar')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -40,67 +40,67 @@ const general = computed(() => [
|
|||
|
||||
const player = computed(() => [
|
||||
{
|
||||
title: $pgettext('Popup/Keyboard shortcuts/Title', 'Audio player shortcuts'),
|
||||
title: t('Audio player shortcuts'),
|
||||
shortcuts: [
|
||||
{
|
||||
key: 'p',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Pause/play the current track')
|
||||
summary: t('Pause/play the current track')
|
||||
},
|
||||
{
|
||||
key: 'left',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Seek backwards 5s')
|
||||
summary: t('Seek backwards 5s')
|
||||
},
|
||||
{
|
||||
key: 'right',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Seek forwards 5s')
|
||||
summary: t('Seek forwards 5s')
|
||||
},
|
||||
{
|
||||
key: 'shift + left',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Seek backwards 30s')
|
||||
summary: t('Seek backwards 30s')
|
||||
},
|
||||
{
|
||||
key: 'shift + right',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Seek forwards 30s')
|
||||
summary: t('Seek forwards 30s')
|
||||
},
|
||||
{
|
||||
key: 'ctrl + shift + left',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Play previous track')
|
||||
summary: t('Play previous track')
|
||||
},
|
||||
{
|
||||
key: 'ctrl + shift + right',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Play next track')
|
||||
summary: t('Play next track')
|
||||
},
|
||||
{
|
||||
key: 'shift + up',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Increase volume')
|
||||
summary: t('Increase volume')
|
||||
},
|
||||
{
|
||||
key: 'shift + down',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Decrease volume')
|
||||
summary: t('Decrease volume')
|
||||
},
|
||||
{
|
||||
key: 'm',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Toggle mute')
|
||||
summary: t('Toggle mute')
|
||||
},
|
||||
{
|
||||
key: 'e',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Expand queue/player view')
|
||||
summary: t('Expand queue/player view')
|
||||
},
|
||||
{
|
||||
key: 'l',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Toggle queue looping')
|
||||
summary: t('Toggle queue looping')
|
||||
},
|
||||
{
|
||||
key: 's',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Shuffle queue')
|
||||
summary: t('Shuffle queue')
|
||||
},
|
||||
{
|
||||
key: 'q',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Clear queue')
|
||||
summary: t('Clear queue')
|
||||
},
|
||||
{
|
||||
key: 'f',
|
||||
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Toggle favorite')
|
||||
summary: t('Toggle favorite')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ const player = computed(() => [
|
|||
<template>
|
||||
<semantic-modal v-model:show="showRef">
|
||||
<header class="header">
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Keyboard shortcuts
|
||||
</translate>
|
||||
</header>
|
||||
|
@ -156,7 +156,7 @@ const player = computed(() => [
|
|||
</section>
|
||||
<footer class="actions">
|
||||
<button class="ui basic cancel button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Close
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
<script setup lang="ts">
|
||||
import type { RouteRecordName } from 'vue-router'
|
||||
|
||||
import { computed, ref, watch, watchEffect, onMounted } from 'vue'
|
||||
import { SUPPORTED_LOCALES } from '~/init/locale'
|
||||
import { useCurrentElement } from '@vueuse/core'
|
||||
import { setupDropdown } from '~/utils/fomantic'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useStore } from '~/store'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import SemanticModal from '~/components/semantic/Modal.vue'
|
||||
import UserModal from '~/components/common/UserModal.vue'
|
||||
import Logo from '~/components/Logo.vue'
|
||||
import SearchBar from '~/components/audio/SearchBar.vue'
|
||||
import UserMenu from '~/components/common/UserMenu.vue'
|
||||
import SemanticModal from '~/components/semantic/Modal.vue'
|
||||
import Logo from '~/components/Logo.vue'
|
||||
|
||||
import useThemeList from '~/composables/useThemeList'
|
||||
import useTheme from '~/composables/useTheme'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { computed, ref, watch, watchEffect, onMounted } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useStore } from '~/store'
|
||||
import { setupDropdown } from '~/utils/fomantic'
|
||||
import { useCurrentElement } from '@vueuse/core'
|
||||
|
||||
interface Events {
|
||||
(e: 'show:set-instance-modal'): void
|
||||
|
@ -30,7 +32,7 @@ defineProps<Props>()
|
|||
const store = useStore()
|
||||
const { theme } = useTheme()
|
||||
const themes = useThemeList()
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
|
||||
const route = useRoute()
|
||||
const isCollapsed = ref(true)
|
||||
|
@ -40,15 +42,15 @@ const additionalNotifications = computed(() => store.getters['ui/additionalNotif
|
|||
const logoUrl = computed(() => store.state.auth.authenticated ? 'library.index' : 'index')
|
||||
|
||||
const labels = computed(() => ({
|
||||
mainMenu: $pgettext('Sidebar/*/Hidden text', 'Main menu'),
|
||||
selectTrack: $pgettext('Sidebar/Player/Hidden text', 'Play this track'),
|
||||
pendingFollows: $pgettext('Sidebar/Notifications/Hidden text', 'Pending follow requests'),
|
||||
pendingReviewEdits: $pgettext('Sidebar/Moderation/Hidden text', 'Pending review edits'),
|
||||
pendingReviewReports: $pgettext('Sidebar/Moderation/Hidden text', 'Pending review reports'),
|
||||
language: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Language'),
|
||||
theme: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Theme'),
|
||||
addContent: $pgettext('*/Library/*/Verb', 'Add content'),
|
||||
administration: $pgettext('Sidebar/Admin/Title/Noun', 'Administration')
|
||||
mainMenu: t('Main menu'),
|
||||
selectTrack: t('Play this track'),
|
||||
pendingFollows: t('Pending follow requests'),
|
||||
pendingReviewEdits: t('Pending review edits'),
|
||||
pendingReviewReports: t('Pending review reports'),
|
||||
language: t('Language'),
|
||||
theme: t('Theme'),
|
||||
addContent: t('Add content'),
|
||||
administration: t('Administration')
|
||||
}))
|
||||
|
||||
type SidebarMenuTabs = 'explore' | 'myLibrary'
|
||||
|
@ -104,12 +106,6 @@ const showUserModal = ref(false)
|
|||
const showLanguageModal = ref(false)
|
||||
const showThemeModal = ref(false)
|
||||
|
||||
const gettext = useGettext()
|
||||
const languageSelection = ref(gettext.current)
|
||||
watch(languageSelection, (v) => {
|
||||
store.dispatch('ui/currentLanguage', v)
|
||||
})
|
||||
|
||||
const el = useCurrentElement()
|
||||
watchEffect(() => {
|
||||
if (store.state.auth.authenticated) {
|
||||
|
@ -153,7 +149,7 @@ onMounted(() => {
|
|||
</div>
|
||||
<div class="menu">
|
||||
<h3 class="header">
|
||||
<translate translate-context="Sidebar/Admin/Title/Noun">
|
||||
<translate >
|
||||
Administration
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -170,7 +166,7 @@ onMounted(() => {
|
|||
>
|
||||
{{ $store.state.ui.notifications.pendingReviewEdits }}
|
||||
</div>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Library
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -186,7 +182,7 @@ onMounted(() => {
|
|||
>
|
||||
{{ $store.state.ui.notifications.pendingReviewReports + $store.state.ui.notifications.pendingReviewRequests }}
|
||||
</div>
|
||||
<translate translate-context="*/Moderation/*">
|
||||
<translate >
|
||||
Moderation
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -195,7 +191,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'manage.users.users.list'}"
|
||||
>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Users
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -204,7 +200,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{path: '/manage/settings'}"
|
||||
>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Settings
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -300,12 +296,12 @@ onMounted(() => {
|
|||
</div>
|
||||
<div class="content">
|
||||
<fieldset
|
||||
v-for="(language, key) in $language.available"
|
||||
v-for="(language, key) in SUPPORTED_LOCALES"
|
||||
:key="key"
|
||||
>
|
||||
<input
|
||||
:id="`${key}`"
|
||||
v-model="languageSelection"
|
||||
v-model="$i18n.locale"
|
||||
type="radio"
|
||||
name="language"
|
||||
:value="key"
|
||||
|
@ -366,7 +362,7 @@ onMounted(() => {
|
|||
class="ui fluid tiny primary button"
|
||||
:to="{name: 'login'}"
|
||||
>
|
||||
<translate translate-context="*/Login/*/Verb">
|
||||
<translate >
|
||||
Login
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -375,7 +371,7 @@ onMounted(() => {
|
|||
class="ui fluid tiny button"
|
||||
:to="{path: '/signup'}"
|
||||
>
|
||||
<translate translate-context="*/Signup/Link/Verb">
|
||||
<translate >
|
||||
Create an account
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -389,7 +385,7 @@ onMounted(() => {
|
|||
id="navigation-label"
|
||||
class="visually-hidden"
|
||||
>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Main navigation
|
||||
</translate>
|
||||
</h1>
|
||||
|
@ -411,7 +407,7 @@ onMounted(() => {
|
|||
@click="expanded = 'explore'"
|
||||
@focus="expanded = 'explore'"
|
||||
>
|
||||
<translate translate-context="*/*/*/Verb">
|
||||
<translate >
|
||||
Explore
|
||||
</translate>
|
||||
<i
|
||||
|
@ -424,7 +420,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'search'}"
|
||||
>
|
||||
<i class="search icon" /><translate translate-context="Sidebar/Navigation/List item.Link/Verb">
|
||||
<i class="search icon" /><translate >
|
||||
Search
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -433,7 +429,7 @@ onMounted(() => {
|
|||
:to="{name: 'library.index'}"
|
||||
active-class="_active"
|
||||
>
|
||||
<i class="music icon" /><translate translate-context="Sidebar/Navigation/List item.Link/Verb">
|
||||
<i class="music icon" /><translate >
|
||||
Browse
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -441,7 +437,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.podcasts.browse'}"
|
||||
>
|
||||
<i class="podcast icon" /><translate translate-context="*/*/*">
|
||||
<i class="podcast icon" /><translate >
|
||||
Podcasts
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -449,7 +445,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.albums.browse'}"
|
||||
>
|
||||
<i class="compact disc icon" /><translate translate-context="*/*/*">
|
||||
<i class="compact disc icon" /><translate >
|
||||
Albums
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -457,7 +453,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.artists.browse'}"
|
||||
>
|
||||
<i class="user icon" /><translate translate-context="*/*/*">
|
||||
<i class="user icon" /><translate >
|
||||
Artists
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -465,7 +461,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.playlists.browse'}"
|
||||
>
|
||||
<i class="list icon" /><translate translate-context="*/*/*">
|
||||
<i class="list icon" /><translate >
|
||||
Playlists
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -473,7 +469,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.radios.browse'}"
|
||||
>
|
||||
<i class="feed icon" /><translate translate-context="*/*/*">
|
||||
<i class="feed icon" /><translate >
|
||||
Radios
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -490,7 +486,7 @@ onMounted(() => {
|
|||
@click="expanded = 'myLibrary'"
|
||||
@focus="expanded = 'myLibrary'"
|
||||
>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
My Library
|
||||
</translate>
|
||||
<i
|
||||
|
@ -503,7 +499,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.me'}"
|
||||
>
|
||||
<i class="music icon" /><translate translate-context="Sidebar/Navigation/List item.Link/Verb">
|
||||
<i class="music icon" /><translate >
|
||||
Browse
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -511,7 +507,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.albums.me'}"
|
||||
>
|
||||
<i class="compact disc icon" /><translate translate-context="*/*/*">
|
||||
<i class="compact disc icon" /><translate >
|
||||
Albums
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -519,7 +515,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.artists.me'}"
|
||||
>
|
||||
<i class="user icon" /><translate translate-context="*/*/*">
|
||||
<i class="user icon" /><translate >
|
||||
Artists
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -527,7 +523,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.playlists.me'}"
|
||||
>
|
||||
<i class="list icon" /><translate translate-context="*/*/*">
|
||||
<i class="list icon" /><translate >
|
||||
Playlists
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -535,7 +531,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'library.radios.me'}"
|
||||
>
|
||||
<i class="feed icon" /><translate translate-context="*/*/*">
|
||||
<i class="feed icon" /><translate >
|
||||
Radios
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -543,7 +539,7 @@ onMounted(() => {
|
|||
class="item"
|
||||
:to="{name: 'favorites'}"
|
||||
>
|
||||
<i class="heart icon" /><translate translate-context="Sidebar/Favorites/List item.Link/Noun">
|
||||
<i class="heart icon" /><translate >
|
||||
Favorites
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -554,13 +550,13 @@ onMounted(() => {
|
|||
class="header item"
|
||||
:to="{name: 'subscriptions'}"
|
||||
>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Channels
|
||||
</translate>
|
||||
</router-link>
|
||||
<div class="item">
|
||||
<h3 class="header">
|
||||
<translate translate-context="Footer/About/List item.Link">
|
||||
<translate >
|
||||
More
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -570,7 +566,7 @@ onMounted(() => {
|
|||
to="/about"
|
||||
active-class="router-link-exact-active active"
|
||||
>
|
||||
<i class="info icon" /><translate translate-context="Sidebar/*/List item.Link">
|
||||
<i class="info icon" /><translate >
|
||||
About this pod
|
||||
</translate>
|
||||
</router-link>
|
||||
|
|
|
@ -110,7 +110,7 @@ const save = async () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Error while saving settings
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -127,7 +127,7 @@ const save = async () => {
|
|||
v-if="result"
|
||||
class="ui positive message"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Paragraph">
|
||||
<translate >
|
||||
Settings updated successfully.
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -229,7 +229,7 @@ const save = async () => {
|
|||
<div v-if="values[setting.identifier]">
|
||||
<div class="ui hidden divider" />
|
||||
<h3 class="ui header">
|
||||
<translate translate-context="Content/Settings/Title/Noun">
|
||||
<translate >
|
||||
Current image
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -246,7 +246,7 @@ const save = async () => {
|
|||
type="submit"
|
||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Save
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { Form } from '~/types'
|
|||
import SignupForm from '~/components/auth/SignupForm.vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { arrayMove } from '~/utils'
|
||||
|
||||
interface Events {
|
||||
|
@ -26,11 +26,11 @@ const value = useVModel(props, 'modelValue', emit, { deep: true })
|
|||
const maxFields = ref(10)
|
||||
const isPreviewing = ref(false)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
delete: $pgettext('*/*/*', 'Delete'),
|
||||
up: $pgettext('*/*/*', 'Move up'),
|
||||
down: $pgettext('*/*/*', 'Move down')
|
||||
delete: t('Delete'),
|
||||
up: t('Move up'),
|
||||
down: t('Move down')
|
||||
}))
|
||||
|
||||
if (!value.value?.fields) {
|
||||
|
@ -45,7 +45,7 @@ if (!value.value?.fields) {
|
|||
|
||||
const addField = () => {
|
||||
value.value.fields.push({
|
||||
label: $pgettext('*/*/Form-builder', 'Additional field') + ' ' + (value.value.fields.length + 1),
|
||||
label: t('Additional field') + ' ' + (value.value.fields.length + 1),
|
||||
required: true,
|
||||
input_type: 'short_text'
|
||||
})
|
||||
|
@ -69,7 +69,7 @@ const move = (idx: number, increment: number) => {
|
|||
:class="[{active: !isPreviewing}, 'item']"
|
||||
@click.stop.prevent="isPreviewing = false"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Edit form
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -77,7 +77,7 @@ const move = (idx: number, increment: number) => {
|
|||
:class="[{active: isPreviewing}, 'item']"
|
||||
@click.stop.prevent="isPreviewing = true"
|
||||
>
|
||||
<translate translate-context="*/Form/Menu.item">
|
||||
<translate >
|
||||
Preview form
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -99,10 +99,10 @@ const move = (idx: number, increment: number) => {
|
|||
>
|
||||
<div class="field">
|
||||
<label for="help-text">
|
||||
<translate translate-context="*/*/Label">Help text</translate>
|
||||
<translate >Help text</translate>
|
||||
</label>
|
||||
<p>
|
||||
<translate translate-context="*/*/Help">
|
||||
<translate >
|
||||
An optional text to be displayed at the start of the sign-up form.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -115,10 +115,10 @@ const move = (idx: number, increment: number) => {
|
|||
</div>
|
||||
<div class="field">
|
||||
<label>
|
||||
<translate translate-context="*/*/Label">Additional fields</translate>
|
||||
<translate >Additional fields</translate>
|
||||
</label>
|
||||
<p>
|
||||
<translate translate-context="*/*/Help">
|
||||
<translate >
|
||||
Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -126,21 +126,21 @@ const move = (idx: number, increment: number) => {
|
|||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<translate translate-context="*/*/Form-builder,Help">
|
||||
<translate >
|
||||
Field label
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/Form-builder,Help">
|
||||
<translate >
|
||||
Field type
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/Form-builder,Help">
|
||||
<translate >
|
||||
Required
|
||||
</translate>
|
||||
</th>
|
||||
<th><span class="visually-hidden"><translate translate-context="*/*/Form-builder,Help">Actions</translate></span></th>
|
||||
<th><span class="visually-hidden"><translate >Actions</translate></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -158,12 +158,12 @@ const move = (idx: number, increment: number) => {
|
|||
<td>
|
||||
<select v-model="field.input_type">
|
||||
<option value="short_text">
|
||||
<translate translate-context="*/*/Form-builder">
|
||||
<translate >
|
||||
Short text
|
||||
</translate>
|
||||
</option>
|
||||
<option value="long_text">
|
||||
<translate translate-context="*/*/Form-builder">
|
||||
<translate >
|
||||
Long text
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -172,12 +172,12 @@ const move = (idx: number, increment: number) => {
|
|||
<td>
|
||||
<select v-model="field.required">
|
||||
<option :value="true">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Yes
|
||||
</translate>
|
||||
</option>
|
||||
<option :value="false">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
No
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -214,7 +214,7 @@ const move = (idx: number, increment: number) => {
|
|||
class="ui basic button"
|
||||
@click.stop.prevent="addField"
|
||||
>
|
||||
<translate translate-context="*/*/Form-builder">
|
||||
<translate >
|
||||
Add a new field
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { Channel } from '~/types'
|
||||
|
||||
import { momentFormat } from '~/utils/filters'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
import { computed } from 'vue'
|
||||
|
||||
|
@ -30,10 +30,10 @@ const urlId = computed(() => props.object.actor?.is_local
|
|||
: props.object.uuid
|
||||
)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const updatedTitle = computed(() => {
|
||||
const date = momentFormat(new Date(props.object.artist?.modification_date ?? '1970-01-01'))
|
||||
return $pgettext('*/*/*', 'Updated on %{ date }', { date })
|
||||
return t('Updated on %{ date }', { date })
|
||||
})
|
||||
|
||||
// TODO (wvffle): Use time ago
|
||||
|
@ -67,7 +67,7 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
|
|||
<translate
|
||||
v-if="object.artist?.content_category === 'podcast'"
|
||||
class="meta ellipsis"
|
||||
translate-context="Content/Channel/Paragraph"
|
||||
|
||||
translate-plural="%{ count } episodes"
|
||||
:translate-n="object.artist.tracks_count"
|
||||
:translate-params="{count: object.artist.tracks_count}"
|
||||
|
@ -76,7 +76,7 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
|
|||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
:translate-params="{count: object.artist?.tracks_count}"
|
||||
:translate-n="object.artist?.tracks_count"
|
||||
translate-plural="%{ count } tracks"
|
||||
|
|
|
@ -103,7 +103,7 @@ watch(page, fetchData, { immediate: true })
|
|||
@refresh="fetchData()"
|
||||
>
|
||||
<p>
|
||||
<translate translate-context="Content/Channels/*">
|
||||
<translate >
|
||||
You may need to subscribe to this channel to see its content.
|
||||
</translate>
|
||||
</p>
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { ContentCategory, Channel, BackendError } from '~/types'
|
|||
|
||||
import { slugify } from 'transliteration'
|
||||
import { reactive, computed, ref, watchEffect, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import axios from 'axios'
|
||||
import AttachmentInput from '~/components/common/AttachmentInput.vue'
|
||||
|
@ -29,7 +29,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
step: 1
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
|
||||
const newValues = reactive({
|
||||
name: props.object?.artist?.name ?? '',
|
||||
|
@ -45,13 +45,13 @@ const creating = computed(() => props.object === null)
|
|||
const categoryChoices = computed(() => [
|
||||
{
|
||||
value: 'podcast',
|
||||
label: $pgettext('*/*/*', 'Podcasts'),
|
||||
helpText: $pgettext('Content/Channels/Help', 'Host your episodes and keep your community updated.')
|
||||
label: t('Podcasts'),
|
||||
helpText: t('Host your episodes and keep your community updated.')
|
||||
},
|
||||
{
|
||||
value: 'music',
|
||||
label: $pgettext('*/*/*', 'Artist discography'),
|
||||
helpText: $pgettext('Content/Channels/Help', 'Publish music you make as a nice discography of albums and singles.')
|
||||
label: t('Artist discography'),
|
||||
helpText: t('Publish music you make as a nice discography of albums and singles.')
|
||||
}
|
||||
])
|
||||
|
||||
|
@ -81,8 +81,8 @@ const itunesSubcategories = computed(() => {
|
|||
})
|
||||
|
||||
const labels = computed(() => ({
|
||||
namePlaceholder: $pgettext('Content/Channel/Form.Field.Placeholder', 'Awesome channel name'),
|
||||
usernamePlaceholder: $pgettext('Content/Channel/Form.Field.Placeholder', 'awesomechannelname')
|
||||
namePlaceholder: t('Awesome channel name'),
|
||||
usernamePlaceholder: t('awesomechannelname')
|
||||
}))
|
||||
|
||||
const submittable = computed(() => !!(
|
||||
|
@ -165,7 +165,7 @@ defineExpose({
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Error while saving channel
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -184,7 +184,7 @@ defineExpose({
|
|||
class="ui grouped channel-type required field"
|
||||
>
|
||||
<legend>
|
||||
<translate translate-context="Content/Channel/Paragraph">
|
||||
<translate >
|
||||
What will this channel be used for?
|
||||
</translate>
|
||||
</legend>
|
||||
|
@ -214,7 +214,7 @@ defineExpose({
|
|||
<template v-if="!creating || step === 2">
|
||||
<div class="ui required field">
|
||||
<label for="channel-name">
|
||||
<translate translate-context="Content/Channel/*">Name</translate>
|
||||
<translate >Name</translate>
|
||||
</label>
|
||||
<input
|
||||
v-model="newValues.name"
|
||||
|
@ -225,7 +225,7 @@ defineExpose({
|
|||
</div>
|
||||
<div class="ui required field">
|
||||
<label for="channel-username">
|
||||
<translate translate-context="Content/Channel/*">Fediverse handle</translate>
|
||||
<translate >Fediverse handle</translate>
|
||||
</label>
|
||||
<div class="ui left labeled input">
|
||||
<div class="ui basic label">
|
||||
|
@ -242,7 +242,7 @@ defineExpose({
|
|||
<template v-if="creating">
|
||||
<div class="ui small hidden divider" />
|
||||
<p>
|
||||
<translate translate-context="Content/Channels/Paragraph">
|
||||
<translate >
|
||||
Used in URLs and to follow this channel in the Fediverse. It cannot be changed later.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -254,7 +254,7 @@ defineExpose({
|
|||
:image-class="newValues.content_category === 'podcast' ? '' : 'circular'"
|
||||
@delete="newValues.cover = null"
|
||||
>
|
||||
<translate translate-context="Content/Channel/*">
|
||||
<translate >
|
||||
Channel Picture
|
||||
</translate>
|
||||
</attachment-input>
|
||||
|
@ -264,7 +264,7 @@ defineExpose({
|
|||
<div class="ten wide column">
|
||||
<div class="ui field">
|
||||
<label for="channel-tags">
|
||||
<translate translate-context="*/*/*">Tags</translate>
|
||||
<translate >Tags</translate>
|
||||
</label>
|
||||
<tags-selector
|
||||
id="channel-tags"
|
||||
|
@ -279,7 +279,7 @@ defineExpose({
|
|||
>
|
||||
<div class="ui required field">
|
||||
<label for="channel-language">
|
||||
<translate translate-context="*/*/*">Language</translate>
|
||||
<translate >Language</translate>
|
||||
</label>
|
||||
<select
|
||||
id="channel-language"
|
||||
|
@ -302,7 +302,7 @@ defineExpose({
|
|||
<div class="ui small hidden divider" />
|
||||
<div class="ui field">
|
||||
<label for="channel-name">
|
||||
<translate translate-context="*/*/*">Description</translate>
|
||||
<translate >Description</translate>
|
||||
</label>
|
||||
<content-form v-model="newValues.description" />
|
||||
</div>
|
||||
|
@ -312,7 +312,7 @@ defineExpose({
|
|||
>
|
||||
<div class="ui required field">
|
||||
<label for="channel-itunes-category">
|
||||
<translate translate-context="*/*/*">Category</translate>
|
||||
<translate >Category</translate>
|
||||
</label>
|
||||
<select
|
||||
id="itunes-category"
|
||||
|
@ -332,7 +332,7 @@ defineExpose({
|
|||
</div>
|
||||
<div class="ui field">
|
||||
<label for="channel-itunes-category">
|
||||
<translate translate-context="*/*/*">Subcategory</translate>
|
||||
<translate >Subcategory</translate>
|
||||
</label>
|
||||
<select
|
||||
id="itunes-category"
|
||||
|
@ -357,7 +357,7 @@ defineExpose({
|
|||
>
|
||||
<div class="ui field">
|
||||
<label for="channel-itunes-email">
|
||||
<translate translate-context="*/*/*">Owner e-mail address</translate>
|
||||
<translate >Owner e-mail address</translate>
|
||||
</label>
|
||||
<input
|
||||
id="channel-itunes-email"
|
||||
|
@ -368,7 +368,7 @@ defineExpose({
|
|||
</div>
|
||||
<div class="ui field">
|
||||
<label for="channel-itunes-name">
|
||||
<translate translate-context="*/*/*">Owner name</translate>
|
||||
<translate >Owner name</translate>
|
||||
</label>
|
||||
<input
|
||||
id="channel-itunes-name"
|
||||
|
@ -379,7 +379,7 @@ defineExpose({
|
|||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -390,7 +390,7 @@ defineExpose({
|
|||
class="ui active inverted dimmer"
|
||||
>
|
||||
<div class="ui text loader">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Loading
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -56,7 +56,7 @@ const cover = computed(() => props.serie?.cover ?? null)
|
|||
</strong>
|
||||
<div class="description">
|
||||
<translate
|
||||
translate-context="Content/Channel/Paragraph"
|
||||
|
||||
translate-plural="%{ count } episodes"
|
||||
:translate-n="serie.tracks_count"
|
||||
:translate-params="{count: serie.tracks_count}"
|
||||
|
|
|
@ -84,7 +84,7 @@ fetchData()
|
|||
:class="['ui', 'basic', 'button']"
|
||||
@click="fetchData(nextPage)"
|
||||
>
|
||||
<translate translate-context="*/*/Button,Label">
|
||||
<translate >
|
||||
Show more
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -95,7 +95,7 @@ fetchData()
|
|||
@refresh="fetchData()"
|
||||
>
|
||||
<p>
|
||||
<translate translate-context="Content/Channels/*">
|
||||
<translate >
|
||||
You may need to subscribe to this channel to see its contents.
|
||||
</translate>
|
||||
</p>
|
||||
|
|
|
@ -77,7 +77,7 @@ fetchData()
|
|||
:class="['ui', 'basic', 'button']"
|
||||
@click="fetchData(nextPage)"
|
||||
>
|
||||
<translate translate-context="*/*/Button,Label">
|
||||
<translate >
|
||||
Show more
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -52,11 +52,11 @@ const { copy, copied } = useClipboard({ source: textarea })
|
|||
>
|
||||
<p>
|
||||
<strong>
|
||||
<translate translate-context="Content/Embed/Message">Sharing will not work because this pod doesn't allow anonymous users to access content.</translate>
|
||||
<translate >Sharing will not work because this pod doesn't allow anonymous users to access content.</translate>
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-context="Content/Embed/Message">
|
||||
<translate >
|
||||
Please contact your admins and ask them to update the corresponding setting.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -65,9 +65,9 @@ const { copy, copied } = useClipboard({ source: textarea })
|
|||
<div class="two fields">
|
||||
<div class="field">
|
||||
<div class="field">
|
||||
<label for="embed-width"><translate translate-context="Popup/Embed/Input.Label">Widget width</translate></label>
|
||||
<label for="embed-width"><translate >Widget width</translate></label>
|
||||
<p>
|
||||
<translate translate-context="Popup/Embed/Paragraph">
|
||||
<translate >
|
||||
Leave empty for a responsive widget
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -82,7 +82,7 @@ const { copy, copied } = useClipboard({ source: textarea })
|
|||
<template v-if="type != 'track'">
|
||||
<br>
|
||||
<div class="field">
|
||||
<label for="embed-height"><translate translate-context="Popup/Embed/Input.Label">Widget height</translate></label>
|
||||
<label for="embed-height"><translate >Widget height</translate></label>
|
||||
<input
|
||||
id="embed-height"
|
||||
v-model="height"
|
||||
|
@ -99,13 +99,13 @@ const { copy, copied } = useClipboard({ source: textarea })
|
|||
class="ui right accent labeled icon floated button"
|
||||
@click="copy()"
|
||||
>
|
||||
<i class="copy icon" /><translate translate-context="*/*/Button.Label/Short, Verb">
|
||||
<i class="copy icon" /><translate >
|
||||
Copy
|
||||
</translate>
|
||||
</button>
|
||||
<label for="embed-width"><translate translate-context="Popup/Embed/Input.Label/Noun">Embed code</translate></label>
|
||||
<label for="embed-width"><translate >Embed code</translate></label>
|
||||
<p>
|
||||
<translate translate-context="Popup/Embed/Paragraph">
|
||||
<translate >
|
||||
Copy/paste this code in your website HTML
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -120,7 +120,7 @@ const { copy, copied } = useClipboard({ source: textarea })
|
|||
v-if="copied"
|
||||
class="message"
|
||||
>
|
||||
<translate translate-context="Content/*/Paragraph">
|
||||
<translate >
|
||||
Text copied to clipboard!
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -134,7 +134,7 @@ const { copy, copied } = useClipboard({ source: textarea })
|
|||
:href="iframeSrc"
|
||||
target="_blank"
|
||||
>
|
||||
<translate translate-context="Popup/Embed/Title/Noun">Preview</translate>
|
||||
<translate >Preview</translate>
|
||||
</a>
|
||||
</h3>
|
||||
<iframe
|
||||
|
|
|
@ -40,19 +40,19 @@ const toggle = () => {
|
|||
<i class="heart icon" />
|
||||
<translate
|
||||
v-if="isApproved"
|
||||
translate-context="Content/Library/Card.Button.Label/Verb"
|
||||
|
||||
>
|
||||
Unfollow
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="isPending"
|
||||
translate-context="Content/Library/Card.Button.Label/Verb"
|
||||
|
||||
>
|
||||
Cancel follow request
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Content/Library/Card.Button.Label/Verb"
|
||||
|
||||
>
|
||||
Follow
|
||||
</translate>
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/
|
|||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import usePlayOptions from '~/composables/audio/usePlayOptions'
|
||||
import useReport from '~/composables/moderation/useReport'
|
||||
import { useCurrentElement } from '@vueuse/core'
|
||||
|
@ -63,33 +63,33 @@ const {
|
|||
|
||||
const { report, getReportableObjects } = useReport()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
playNow: $pgettext('*/Queue/Dropdown/Button/Title', 'Play now'),
|
||||
addToQueue: $pgettext('*/Queue/Dropdown/Button/Title', 'Add to current queue'),
|
||||
playNext: $pgettext('*/Queue/Dropdown/Button/Title', 'Play next'),
|
||||
startRadio: $pgettext('*/Queue/Dropdown/Button/Title', 'Play similar songs'),
|
||||
report: $pgettext('*/Moderation/*/Button/Label,Verb', 'Report…'),
|
||||
addToPlaylist: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…'),
|
||||
hideArtist: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Hide content from this artist'),
|
||||
playNow: t('Play now'),
|
||||
addToQueue: t('Add to current queue'),
|
||||
playNext: t('Play next'),
|
||||
startRadio: t('Play similar songs'),
|
||||
report: t('Report…'),
|
||||
addToPlaylist: t('Add to playlist…'),
|
||||
hideArtist: t('Hide content from this artist'),
|
||||
replacePlay: props.track
|
||||
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play track')
|
||||
? t('Play track')
|
||||
: props.album
|
||||
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play album')
|
||||
? t('Play album')
|
||||
: props.artist
|
||||
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play artist')
|
||||
? t('Play artist')
|
||||
: props.playlist
|
||||
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play playlist')
|
||||
: $pgettext('*/Queue/Dropdown/Button/Title', 'Play tracks')
|
||||
? t('Play playlist')
|
||||
: t('Play tracks')
|
||||
}))
|
||||
|
||||
const title = computed(() => {
|
||||
if (playable.value) {
|
||||
return $pgettext('*/*/Button.Label/Noun', 'More…')
|
||||
return t('More…')
|
||||
}
|
||||
|
||||
if (props.track) {
|
||||
return $pgettext('*/Queue/Button/Title', 'This track is not available in any library you have access to')
|
||||
return t('This track is not available in any library you have access to')
|
||||
}
|
||||
|
||||
return ''
|
||||
|
@ -138,7 +138,7 @@ const openMenu = () => {
|
|||
v-else
|
||||
:class="[playIconClass, 'icon']"
|
||||
/>
|
||||
<template v-if="!discrete && !iconOnly"> <slot><translate translate-context="*/Queue/Button.Label/Short, Verb">Play</translate></slot></template>
|
||||
<template v-if="!discrete && !iconOnly"> <slot><translate >Play</translate></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 translate-context="*/Queue/Dropdown/Button/Label/Short">Add to queue</translate>
|
||||
<i class="plus icon" /><translate >Add to queue</translate>
|
||||
</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 translate-context="*/Queue/Button.Label/Short, Verb">Play radio</translate>
|
||||
<i class="feed icon" /><translate >Play radio</translate>
|
||||
</button>
|
||||
<button
|
||||
v-if="track"
|
||||
|
@ -190,7 +190,7 @@ const openMenu = () => {
|
|||
@click.stop="$store.commit('playlists/chooseTrack', track)"
|
||||
>
|
||||
<i class="list icon" />
|
||||
<translate translate-context="Sidebar/Player/Icon.Tooltip/Verb">Add to playlist…</translate>
|
||||
<translate >Add to playlist…</translate>
|
||||
</button>
|
||||
<button
|
||||
v-if="track && $route.name !== 'library.tracks.detail'"
|
||||
|
@ -200,11 +200,11 @@ const openMenu = () => {
|
|||
<i class="info icon" />
|
||||
<translate
|
||||
v-if="track.artist?.content_category === 'podcast'"
|
||||
translate-context="*/Queue/Dropdown/Button/Label/Short"
|
||||
|
||||
>Episode details</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/Queue/Dropdown/Button/Label/Short"
|
||||
|
||||
>Track details</translate>
|
||||
</button>
|
||||
<div class="divider" />
|
||||
|
|
|
@ -3,17 +3,17 @@ import { usePlayer } from '~/composables/audio/player'
|
|||
import { useQueue } from '~/composables/audio/queue'
|
||||
|
||||
import { useMouse, useWindowSize } from '@vueuse/core'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useStore } from '~/store'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
|
||||
import time from '~/utils/time'
|
||||
|
||||
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
||||
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
|
||||
import VolumeControl from './VolumeControl.vue'
|
||||
import PlayerControls from './PlayerControls.vue'
|
||||
import VolumeControl from './VolumeControl.vue'
|
||||
|
||||
const {
|
||||
LoopingMode,
|
||||
|
@ -44,7 +44,7 @@ const {
|
|||
} = useQueue()
|
||||
|
||||
const store = useStore()
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
|
||||
const toggleMobilePlayer = () => {
|
||||
store.commit('ui/queueFocused', ['queue', 'player'].includes(store.state.ui.queueFocused as string) ? null : 'player')
|
||||
|
@ -72,17 +72,17 @@ onKeyboardShortcut(['ctrl', 'shift', 'left'], playPrevious, true)
|
|||
onKeyboardShortcut(['ctrl', 'shift', 'right'], playNext, true)
|
||||
|
||||
const labels = computed(() => ({
|
||||
audioPlayer: $pgettext('Sidebar/Player/Hidden text', 'Media player'),
|
||||
previous: $pgettext('Sidebar/Player/Icon.Tooltip', 'Previous track'),
|
||||
play: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Play'),
|
||||
pause: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Pause'),
|
||||
next: $pgettext('Sidebar/Player/Icon.Tooltip', 'Next track'),
|
||||
unmute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Unmute'),
|
||||
mute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Mute'),
|
||||
expandQueue: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Expand queue'),
|
||||
shuffle: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Shuffle your queue'),
|
||||
clear: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Clear your queue'),
|
||||
addArtistContentFilter: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Hide content from this artist…')
|
||||
audioPlayer: t('Media player'),
|
||||
previous: t('Previous track'),
|
||||
play: t('Play'),
|
||||
pause: t('Pause'),
|
||||
next: t('Next track'),
|
||||
unmute: t('Unmute'),
|
||||
mute: t('Mute'),
|
||||
expandQueue: t('Expand queue'),
|
||||
shuffle: t('Shuffle your queue'),
|
||||
clear: t('Clear your queue'),
|
||||
addArtistContentFilter: t('Hide content from this artist…')
|
||||
}))
|
||||
|
||||
const switchTab = () => {
|
||||
|
@ -103,10 +103,10 @@ initializeFirstTrack()
|
|||
const loopingTitle = computed(() => {
|
||||
const mode = looping.value
|
||||
return mode === LoopingMode.None
|
||||
? $pgettext('Sidebar/Player/Icon.Tooltip', 'Looping disabled. Click to switch to single-track looping.')
|
||||
? t('Looping disabled. Click to switch to single-track looping.')
|
||||
: mode === LoopingMode.LoopTrack
|
||||
? $pgettext('Sidebar/Player/Icon.Tooltip', 'Looping on a single track. Click to switch to whole queue looping.')
|
||||
: $pgettext('Sidebar/Player/Icon.Tooltip', 'Looping on whole queue. Click to disable looping.')
|
||||
? t('Looping on a single track. Click to switch to whole queue looping.')
|
||||
: t('Looping on whole queue. Click to disable looping.')
|
||||
})
|
||||
|
||||
const hideArtist = () => {
|
||||
|
@ -133,7 +133,7 @@ const hideArtist = () => {
|
|||
id="player-label"
|
||||
class="visually-hidden"
|
||||
>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Audio player and controls
|
||||
</translate>
|
||||
</h1>
|
||||
|
@ -302,7 +302,6 @@ const hideArtist = () => {
|
|||
>
|
||||
<i class="stream icon" />
|
||||
<translate
|
||||
translate-context="Sidebar/Queue/Text"
|
||||
:translate-params="{index: currentIndex + 1, length: queue.length}"
|
||||
>
|
||||
%{ index } of %{ length }
|
||||
|
@ -314,7 +313,6 @@ const hideArtist = () => {
|
|||
>
|
||||
<i class="stream icon" />
|
||||
<translate
|
||||
translate-context="Sidebar/Queue/Text"
|
||||
:translate-params="{index: currentIndex + 1, length: queue.length}"
|
||||
>
|
||||
%{ index } of %{ length }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { Artist, Album } from '~/types'
|
||||
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ref, computed, reactive, watch, onMounted } from 'vue'
|
||||
import { refDebounced } from '@vueuse/core'
|
||||
|
||||
|
@ -21,7 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
})
|
||||
|
||||
const logger = useLogger()
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
|
||||
const query = ref('')
|
||||
const queryDebounced = refDebounced(query, 500)
|
||||
|
@ -65,7 +65,7 @@ onMounted(() => {
|
|||
})
|
||||
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('*/Search/Input.Placeholder', 'Artist, album, track…')
|
||||
searchPlaceholder: t('Artist, album, track…')
|
||||
}))
|
||||
|
||||
</script>
|
||||
|
@ -73,7 +73,7 @@ const labels = computed(() => ({
|
|||
<template>
|
||||
<div>
|
||||
<h2>
|
||||
<translate translate-context="Content/Search/Title">
|
||||
<translate >
|
||||
Search for some music
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -91,7 +91,7 @@ const labels = computed(() => ({
|
|||
</div>
|
||||
<template v-if="query.length > 0">
|
||||
<h3 class="ui title">
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Artists
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -105,14 +105,14 @@ const labels = computed(() => ({
|
|||
</div>
|
||||
</div>
|
||||
<p v-else>
|
||||
<translate translate-context="Content/Search/Paragraph">
|
||||
<translate >
|
||||
No artist matched your query
|
||||
</translate>
|
||||
</p>
|
||||
</template>
|
||||
<template v-if="query.length > 0">
|
||||
<h3 class="ui title">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Albums
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -132,7 +132,7 @@ const labels = computed(() => ({
|
|||
</div>
|
||||
</div>
|
||||
<p v-else>
|
||||
<translate translate-context="Content/Search/Paragraph">
|
||||
<translate >
|
||||
No album matched your query
|
||||
</translate>
|
||||
</p>
|
||||
|
|
|
@ -6,7 +6,7 @@ import jQuery from 'jquery'
|
|||
import { trim } from 'lodash-es'
|
||||
import { useFocus, useCurrentElement } from '@vueuse/core'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
|
@ -48,14 +48,14 @@ const { focused } = useFocus(search)
|
|||
onKeyboardShortcut(['shift', 'f'], () => (focused.value = true), true)
|
||||
onKeyboardShortcut(['ctrl', 'k'], () => (focused.value = true), true)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
placeholder: $pgettext('Sidebar/Search/Input.Placeholder', 'Search for artists, albums, tracks…'),
|
||||
searchContent: $pgettext('Sidebar/Search/Input.Label', 'Search for content'),
|
||||
artist: $pgettext('*/*/*/Noun', 'Artist'),
|
||||
album: $pgettext('*/*/*', 'Album'),
|
||||
track: $pgettext('*/*/*/Noun', 'Track'),
|
||||
tag: $pgettext('*/*/*/Noun', 'Tag')
|
||||
placeholder: t('Search for artists, albums, tracks…'),
|
||||
searchContent: t('Search for content'),
|
||||
artist: t('Artist'),
|
||||
album: t('Album'),
|
||||
track: t('Track'),
|
||||
tag: t('Tag')
|
||||
}))
|
||||
|
||||
const router = useRouter()
|
||||
|
@ -77,11 +77,11 @@ const blur = () => {
|
|||
const categories = computed(() => [
|
||||
{
|
||||
code: 'federation',
|
||||
name: $pgettext('*/*/*', 'Federation')
|
||||
name: t('Federation')
|
||||
},
|
||||
{
|
||||
code: 'podcasts',
|
||||
name: $pgettext('*/*/*', 'Podcasts')
|
||||
name: t('Podcasts')
|
||||
},
|
||||
{
|
||||
code: 'artists',
|
||||
|
@ -138,8 +138,8 @@ onMounted(() => {
|
|||
showNoResults: true,
|
||||
error: {
|
||||
// @ts-expect-error Semantic is broken
|
||||
noResultsHeader: $pgettext('Sidebar/Search/Error', 'No matches found'),
|
||||
noResults: $pgettext('Sidebar/Search/Error.Label', 'Sorry, there are no results for this search')
|
||||
noResultsHeader: t('No matches found'),
|
||||
noResults: t('Sorry, there are no results for this search')
|
||||
},
|
||||
|
||||
onSelect (result, response) {
|
||||
|
@ -179,7 +179,7 @@ onMounted(() => {
|
|||
if (category.code === 'federation' && id) {
|
||||
resultsEmpty = false
|
||||
results[category.code]?.results.push({
|
||||
title: $pgettext('Search/*/*', 'Search on the fediverse'),
|
||||
title: t('Search on the fediverse'),
|
||||
routerUrl: {
|
||||
name: 'search',
|
||||
query: { id }
|
||||
|
@ -190,7 +190,7 @@ onMounted(() => {
|
|||
if (category.code === 'podcasts' && id) {
|
||||
resultsEmpty = false
|
||||
results[category.code]?.results.push({
|
||||
title: $pgettext('Search/*/*', 'Subscribe to podcast via RSS'),
|
||||
title: t('Subscribe to podcast via RSS'),
|
||||
routerUrl: {
|
||||
name: 'search',
|
||||
query: { id, type: 'rss' }
|
||||
|
@ -200,7 +200,7 @@ onMounted(() => {
|
|||
|
||||
if (category.code === 'more') {
|
||||
results[category.code]?.results.push({
|
||||
title: $pgettext('Search/*/*', 'More results 🡒'),
|
||||
title: t('More results 🡒'),
|
||||
routerUrl: {
|
||||
name: 'search',
|
||||
query: { type: 'artists', q: query.value }
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { usePlayer } from '~/composables/audio/player'
|
||||
import { useTimeoutFn } from '@vueuse/core'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { volume, mute } = usePlayer()
|
||||
const expanded = ref(false)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
unmute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Unmute'),
|
||||
mute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Mute'),
|
||||
slider: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Adjust volume')
|
||||
unmute: t('Unmute'),
|
||||
mute: t('Mute'),
|
||||
slider: t('Adjust volume')
|
||||
}))
|
||||
|
||||
const { start, stop } = useTimeoutFn(() => (expanded.value = false), 500, { immediate: false })
|
||||
|
|
|
@ -60,7 +60,7 @@ const imageUrl = computed(() => props.album.cover?.urls.original
|
|||
<div class="extra content">
|
||||
<span v-if="album.release_date">{{ momentFormat(new Date(album.release_date), 'Y') }} · </span>
|
||||
<translate
|
||||
translate-context="*/*/*"
|
||||
|
||||
:translate-params="{count: album.tracks_count}"
|
||||
:translate-n="album.tracks_count"
|
||||
translate-plural="%{ count } tracks"
|
||||
|
|
|
@ -112,7 +112,7 @@ watch(
|
|||
:class="['ui', 'basic', 'button']"
|
||||
@click="fetchData(nextPage)"
|
||||
>
|
||||
<translate translate-context="*/*/Button,Label">
|
||||
<translate >
|
||||
Show more
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -64,7 +64,7 @@ const imageUrl = computed(() => cover.value?.urls.original
|
|||
<div class="extra content">
|
||||
<translate
|
||||
v-if="artist.content_category === 'music'"
|
||||
translate-context="*/*/*"
|
||||
|
||||
:translate-params="{count: artist.tracks_count}"
|
||||
:translate-n="artist.tracks_count"
|
||||
translate-plural="%{ count } tracks"
|
||||
|
@ -73,7 +73,7 @@ const imageUrl = computed(() => cover.value?.urls.original
|
|||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
:translate-params="{count: artist.tracks_count}"
|
||||
:translate-n="artist.tracks_count"
|
||||
translate-plural="%{ count } episodes"
|
||||
|
|
|
@ -108,7 +108,7 @@ watch(
|
|||
:class="['ui', 'basic', 'button']"
|
||||
@click="fetchData(nextPage)"
|
||||
>
|
||||
<translate translate-context="*/*/Button,Label">
|
||||
<translate >
|
||||
Show more
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { usePlayer } from '~/composables/audio/player'
|
||||
import { useQueue } from '~/composables/audio/queue'
|
||||
|
@ -52,8 +52,8 @@ const { currentTrack } = useQueue()
|
|||
const { isPlaying } = usePlayer()
|
||||
const { activateTrack } = usePlayOptions(props)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const actionsButtonLabel = computed(() => $pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions'))
|
||||
const { t } = useI18n()
|
||||
const actionsButtonLabel = computed(() => t('Show track actions'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
|||
// import type { Track } from '~/types'
|
||||
|
||||
import { useStore } from '~/store'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import SemanticModal from '~/components/semantic/Modal.vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import usePlayOptions from '~/composables/audio/usePlayOptions'
|
||||
|
@ -58,33 +58,33 @@ const store = useStore()
|
|||
|
||||
const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id))
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const favoriteButton = computed(() => isFavorite.value
|
||||
? $pgettext('Content/Track/Icon.Tooltip/Verb', 'Remove from favorites')
|
||||
: $pgettext('Content/Track/*/Verb', 'Add to favorites')
|
||||
? t('Remove from favorites')
|
||||
: t('Add to favorites')
|
||||
)
|
||||
|
||||
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
|
||||
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Episode details')
|
||||
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Track details')
|
||||
? t('Episode details')
|
||||
: t('Track details')
|
||||
)
|
||||
|
||||
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
|
||||
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View series')
|
||||
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View album')
|
||||
? t('View series')
|
||||
: t('View album')
|
||||
)
|
||||
|
||||
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
|
||||
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View channel')
|
||||
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View artist')
|
||||
? t('View channel')
|
||||
: t('View artist')
|
||||
)
|
||||
|
||||
const labels = computed(() => ({
|
||||
startRadio: $pgettext('*/Queue/Dropdown/Button/Title', 'Play radio'),
|
||||
playNow: $pgettext('*/Queue/Dropdown/Button/Title', 'Play now'),
|
||||
addToQueue: $pgettext('*/Queue/Dropdown/Button/Title', 'Add to queue'),
|
||||
playNext: $pgettext('*/Queue/Dropdown/Button/Title', 'Play next'),
|
||||
addToPlaylist: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…')
|
||||
startRadio: t('Play radio'),
|
||||
playNow: t('Play now'),
|
||||
addToQueue: t('Add to queue'),
|
||||
playNext: t('Play next'),
|
||||
addToPlaylist: t('Add to playlist…')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
|
||||
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
||||
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { usePlayer } from '~/composables/audio/player'
|
||||
import { useQueue } from '~/composables/audio/queue'
|
||||
|
@ -52,8 +52,8 @@ const { currentTrack } = useQueue()
|
|||
const { isPlaying } = usePlayer()
|
||||
const { activateTrack } = usePlayOptions(props)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const actionsButtonLabel = computed(() => $pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions'))
|
||||
const { t } = useI18n()
|
||||
const actionsButtonLabel = computed(() => t('Show track actions'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
|
|||
// import type { Track } from '~/types'
|
||||
|
||||
import { useStore } from '~/store'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import SemanticModal from '~/components/semantic/Modal.vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import usePlayOptions from '~/composables/audio/usePlayOptions'
|
||||
|
@ -58,33 +58,33 @@ const store = useStore()
|
|||
|
||||
const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id))
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const favoriteButton = computed(() => isFavorite.value
|
||||
? $pgettext('Content/Track/Icon.Tooltip/Verb', 'Remove from favorites')
|
||||
: $pgettext('Content/Track/*/Verb', 'Add to favorites')
|
||||
? t('Remove from favorites')
|
||||
: t('Add to favorites')
|
||||
)
|
||||
|
||||
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
|
||||
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Episode details')
|
||||
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Track details')
|
||||
? t('Episode details')
|
||||
: t('Track details')
|
||||
)
|
||||
|
||||
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
|
||||
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View series')
|
||||
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View album')
|
||||
? t('View series')
|
||||
: t('View album')
|
||||
)
|
||||
|
||||
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
|
||||
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View channel')
|
||||
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View artist')
|
||||
? t('View channel')
|
||||
: t('View artist')
|
||||
)
|
||||
|
||||
const labels = computed(() => ({
|
||||
startRadio: $pgettext('*/Queue/Dropdown/Button/Title', 'Play radio'),
|
||||
playNow: $pgettext('*/Queue/Dropdown/Button/Title', 'Play now'),
|
||||
addToQueue: $pgettext('*/Queue/Dropdown/Button/Title', 'Add to queue'),
|
||||
playNext: $pgettext('*/Queue/Dropdown/Button/Title', 'Play next'),
|
||||
addToPlaylist: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…')
|
||||
startRadio: t('Play radio'),
|
||||
playNow: t('Play now'),
|
||||
addToQueue: t('Add to queue'),
|
||||
playNext: t('Play next'),
|
||||
addToPlaylist: t('Add to playlist…')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { Track } from '~/types'
|
||||
|
||||
import { useElementByPoint, useMouse } from '@vueuse/core'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { clone, uniqBy } from 'lodash-es'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
|
@ -93,11 +93,11 @@ const allTracks = computed(() => {
|
|||
: tracks
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('*/*/*/Noun', 'Title'),
|
||||
album: $pgettext('*/*/*/Noun', 'Album'),
|
||||
artist: $pgettext('*/*/*/Noun', 'Artist')
|
||||
title: t('Title'),
|
||||
album: t('Album'),
|
||||
artist: t('Artist')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
|
|
@ -200,7 +200,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
|||
>
|
||||
<div class="ui icon header">
|
||||
<i class="music icon" />
|
||||
<translate translate-context="Content/Home/Placeholder">
|
||||
<translate >
|
||||
Nothing found
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -217,7 +217,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
|||
:class="['ui', 'basic', 'button']"
|
||||
@click="fetchData(nextPage as string)"
|
||||
>
|
||||
<translate translate-context="*/*/Button,Label">
|
||||
<translate >
|
||||
Show more
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import axios from 'axios'
|
||||
|
@ -15,12 +15,12 @@ interface Props {
|
|||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
|
||||
const application = ref()
|
||||
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Content/Applications/Title', 'Edit application')
|
||||
title: t('Edit application')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
@ -72,23 +72,23 @@ store.state.auth.applicationSecret = undefined
|
|||
</div>
|
||||
<template v-else>
|
||||
<router-link :to="{name: 'settings'}">
|
||||
<translate translate-context="Content/Applications/Link">
|
||||
<translate >
|
||||
Back to settings
|
||||
</translate>
|
||||
</router-link>
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Applications/Title">
|
||||
<translate >
|
||||
Application details
|
||||
</translate>
|
||||
</h2>
|
||||
<div class="ui form">
|
||||
<p>
|
||||
<translate translate-context="Content/Application/Paragraph/">
|
||||
<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 translate-context="Content/Applications/Label">Application ID</translate></label>
|
||||
<label for="copy-id"><translate >Application ID</translate></label>
|
||||
<copy-input
|
||||
id="copy-id"
|
||||
:value="application.client_id"
|
||||
|
@ -100,18 +100,18 @@ store.state.auth.applicationSecret = undefined
|
|||
>
|
||||
<div class="ui small warning message">
|
||||
<h3 class="header">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Keep a copy of this token in a safe place
|
||||
</translate>
|
||||
</h3>
|
||||
<p>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
You won't be able to see it again once you leave this screen.
|
||||
</translate>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label for="copy-secret"><translate translate-context="Content/Applications/Label">Application secret</translate></label>
|
||||
<label for="copy-secret"><translate >Application secret</translate></label>
|
||||
<copy-input
|
||||
id="copy-secret"
|
||||
:value="secret"
|
||||
|
@ -121,7 +121,7 @@ store.state.auth.applicationSecret = undefined
|
|||
v-if="application.token != undefined"
|
||||
class="field"
|
||||
>
|
||||
<label for="copy-secret"><translate translate-context="Content/Applications/Label">Access token</translate></label>
|
||||
<label for="copy-secret"><translate >Access token</translate></label>
|
||||
<copy-input
|
||||
id="copy-secret"
|
||||
:value="application.token"
|
||||
|
@ -131,12 +131,12 @@ store.state.auth.applicationSecret = undefined
|
|||
@click.prevent="refreshToken"
|
||||
>
|
||||
<i class="refresh icon" />
|
||||
<translate translate-context="Content/Applications/Label">Regenerate token</translate>
|
||||
<translate >Regenerate token</translate>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Applications/Title">
|
||||
<translate >
|
||||
Edit application
|
||||
</translate>
|
||||
</h2>
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { BackendError, Application } from '~/types'
|
|||
import axios from 'axios'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { computedEager } from '@vueuse/core'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { uniq } from 'lodash-es'
|
||||
|
||||
import useScopes from '~/composables/auth/useScopes'
|
||||
|
@ -25,7 +25,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
defaults: () => ({})
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const scopes = useScopes()
|
||||
.filter(scope => !['reports', 'security'].includes(scope.id))
|
||||
|
||||
|
@ -86,14 +86,14 @@ const toggleAllScopes = (parent: typeof allScopes['value'][number]) => {
|
|||
const scopeParents = computedEager(() => [
|
||||
{
|
||||
id: 'read',
|
||||
label: $pgettext('Content/OAuth Scopes/Label/Verb', 'Read'),
|
||||
description: $pgettext('Content/OAuth Scopes/Help Text', 'Read-only access to user data'),
|
||||
label: t('Read'),
|
||||
description: t('Read-only access to user data'),
|
||||
value: scopeArray.value.includes('read')
|
||||
},
|
||||
{
|
||||
id: 'write',
|
||||
label: $pgettext('Content/OAuth Scopes/Label/Verb', 'Write'),
|
||||
description: $pgettext('Content/OAuth Scopes/Help Text', 'Write-only access to user data'),
|
||||
label: t('Write'),
|
||||
description: t('Write-only access to user data'),
|
||||
value: scopeArray.value.includes('write')
|
||||
}
|
||||
])
|
||||
|
@ -120,7 +120,7 @@ const allScopes = computed(() => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
We cannot save your changes
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -134,7 +134,7 @@ const allScopes = computed(() => {
|
|||
</ul>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<label for="application-name"><translate translate-context="*/*/*/Noun">Name</translate></label>
|
||||
<label for="application-name"><translate >Name</translate></label>
|
||||
<input
|
||||
id="application-name"
|
||||
v-model="fields.name"
|
||||
|
@ -144,7 +144,7 @@ const allScopes = computed(() => {
|
|||
>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<label for="redirect-uris"><translate translate-context="Content/Applications/Input.Label/Noun">Redirect URI</translate></label>
|
||||
<label for="redirect-uris"><translate >Redirect URI</translate></label>
|
||||
<input
|
||||
id="redirect-uris"
|
||||
v-model="fields.redirect_uris"
|
||||
|
@ -152,15 +152,15 @@ const allScopes = computed(() => {
|
|||
type="text"
|
||||
>
|
||||
<p class="help">
|
||||
<translate translate-context="Content/Applications/Help Text">
|
||||
<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 translate-context="Content/*/*/Noun">Scopes</translate></label>
|
||||
<label><translate >Scopes</translate></label>
|
||||
<p>
|
||||
<translate translate-context="Content/Applications/Paragraph/">
|
||||
<translate >
|
||||
Checking the parent "Read" or "Write" scopes implies access to all the corresponding children scopes.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -210,13 +210,13 @@ const allScopes = computed(() => {
|
|||
>
|
||||
<translate
|
||||
v-if="app !== null"
|
||||
translate-context="Content/Applications/Button.Label/Verb"
|
||||
|
||||
>
|
||||
Update application
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Content/Applications/Button.Label/Verb"
|
||||
|
||||
>
|
||||
Create application
|
||||
</translate>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import ApplicationForm from '~/components/auth/ApplicationForm.vue'
|
||||
import { computed, reactive } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { Application } from '~/types'
|
||||
|
||||
import { computed, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useStore } from '~/store'
|
||||
|
||||
import ApplicationForm from '~/components/auth/ApplicationForm.vue'
|
||||
|
||||
interface Props {
|
||||
name?: string
|
||||
scopes?: string
|
||||
|
@ -24,9 +27,9 @@ const defaults = reactive({
|
|||
redirectUris: props.redirectUris
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Content/Settings/Button.Label', 'Create a new application')
|
||||
title: t('Create a new application')
|
||||
}))
|
||||
|
||||
const router = useRouter()
|
||||
|
@ -52,12 +55,12 @@ const created = (application: Application) => {
|
|||
<div class="ui vertical stripe segment">
|
||||
<section class="ui text container">
|
||||
<router-link :to="{name: 'settings'}">
|
||||
<translate translate-context="Content/Applications/Link">
|
||||
<translate >
|
||||
Back to settings
|
||||
</translate>
|
||||
</router-link>
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Settings/Button.Label">
|
||||
<translate >
|
||||
Create a new application
|
||||
</translate>
|
||||
</h2>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { BackendError, Application } from '~/types'
|
||||
|
||||
import axios from 'axios'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
|
@ -21,7 +21,7 @@ interface Props {
|
|||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const sharedLabels = useSharedLabels()
|
||||
const knownScopes = useScopes()
|
||||
|
||||
|
@ -84,7 +84,7 @@ const submit = async () => {
|
|||
}
|
||||
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/Authorize/Title', 'Allow application')
|
||||
title: t('Allow application')
|
||||
}))
|
||||
|
||||
const requestedScopes = computed(() => props.scope.split(' '))
|
||||
|
@ -119,7 +119,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
|
|||
<section class="ui vertical stripe segment">
|
||||
<div class="ui small text container">
|
||||
<h2>
|
||||
<i class="lock open icon" /><translate translate-context="Content/Auth/Title/Verb">
|
||||
<i class="lock open icon" /><translate >
|
||||
Authorize third-party app
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -132,7 +132,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
|
|||
v-if="application"
|
||||
class="header"
|
||||
>
|
||||
<translate translate-context="Popup/Moderation/Error message">
|
||||
<translate >
|
||||
Error while authorizing application
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -140,7 +140,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
|
|||
v-else
|
||||
class="header"
|
||||
>
|
||||
<translate translate-context="Popup/Moderation/Error message">
|
||||
<translate >
|
||||
Error while fetching application data
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -166,7 +166,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
|
|||
>
|
||||
<h3>
|
||||
<translate
|
||||
translate-context="Content/Auth/Title"
|
||||
|
||||
:translate-params="{app: application.name}"
|
||||
>
|
||||
%{ app } wants to access your Funkwhale account
|
||||
|
@ -183,20 +183,20 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
|
|||
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
|
||||
>
|
||||
<i class="pencil icon" />
|
||||
<translate translate-context="Content/Auth/Label/Noun">Write-only</translate>
|
||||
<translate >Write-only</translate>
|
||||
</span>
|
||||
<span
|
||||
v-else-if="!topic.write && topic.read"
|
||||
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
|
||||
>
|
||||
<translate translate-context="Content/Auth/Label/Noun">Read-only</translate>
|
||||
<translate >Read-only</translate>
|
||||
</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 translate-context="Content/Auth/Label/Noun">Full access</translate>
|
||||
<translate >Full access</translate>
|
||||
</span>
|
||||
<i :class="[topic.icon, 'icon']" />
|
||||
<div class="content">
|
||||
|
@ -207,7 +207,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
|
|||
</div>
|
||||
</h4>
|
||||
<div v-if="unknownRequestedScopes.length > 0">
|
||||
<p><strong><translate translate-context="Content/Auth/Paragraph">The application is also requesting the following unknown permissions:</translate></strong></p>
|
||||
<p><strong><translate >The application is also requesting the following unknown permissions:</translate></strong></p>
|
||||
<ul
|
||||
v-for="(unknownscope, key) in unknownRequestedScopes"
|
||||
:key="key"
|
||||
|
@ -221,7 +221,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
|
|||
>
|
||||
<i class="lock open icon" />
|
||||
<translate
|
||||
translate-context="Content/Signup/Button.Label/Verb"
|
||||
|
||||
:translate-params="{app: application.name}"
|
||||
>
|
||||
Authorize %{ app }
|
||||
|
@ -230,21 +230,21 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
|
|||
<p
|
||||
v-if="redirectUri === 'urn:ietf:wg:oauth:2.0:oob'"
|
||||
v-translate
|
||||
translate-context="Content/Auth/Paragraph"
|
||||
|
||||
>
|
||||
You will be shown a code to copy-paste in the application.
|
||||
</p>
|
||||
<p
|
||||
v-else
|
||||
v-translate="{url: redirectUri}"
|
||||
translate-context="Content/Auth/Paragraph"
|
||||
|
||||
:translate-params="{url: redirectUri}"
|
||||
>
|
||||
You will be redirected to <strong>%{ url }</strong>
|
||||
</p>
|
||||
</form>
|
||||
<div v-else-if="code">
|
||||
<p><strong><translate translate-context="Content/Auth/Paragraph">Copy-paste the following code in the application:</translate></strong></p>
|
||||
<p><strong><translate >Copy-paste the following code in the application:</translate></strong></p>
|
||||
<copy-input :value="code" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { BackendError } from '~/types'
|
|||
import type { RouteLocationRaw } from 'vue-router'
|
||||
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
import PasswordInput from '~/components/forms/PasswordInput.vue'
|
||||
|
@ -21,7 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
})
|
||||
|
||||
const domain = location.hostname
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const credentials = reactive({
|
||||
|
@ -30,7 +30,7 @@ const credentials = reactive({
|
|||
})
|
||||
|
||||
const labels = computed(() => ({
|
||||
usernamePlaceholder: $pgettext('Content/Login/Input.Placeholder', 'Enter your username or e-mail address')
|
||||
usernamePlaceholder: t('Enter your username or e-mail address')
|
||||
}))
|
||||
|
||||
const username = ref()
|
||||
|
@ -75,18 +75,18 @@ const submit = async () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Login/Error message.Title">
|
||||
<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 translate-context="Content/Login/Error message.List item/Call to action">
|
||||
<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 translate-context="Content/Login/Error message.List item/Call to action">
|
||||
<translate >
|
||||
Please double-check that your username and password combination is correct and make sure you verified your e-mail address.
|
||||
</translate>
|
||||
</li>
|
||||
|
@ -98,11 +98,11 @@ const submit = async () => {
|
|||
<template v-if="domain === $store.getters['instance/domain']">
|
||||
<div class="field">
|
||||
<label for="username-field">
|
||||
<translate translate-context="Content/Login/Input.Label/Noun">Username or e-mail address</translate>
|
||||
<translate >Username or e-mail address</translate>
|
||||
<template v-if="showSignup">
|
||||
|
|
||||
<router-link :to="{path: '/signup'}">
|
||||
<translate translate-context="*/Signup/Link/Verb">Create an account</translate>
|
||||
<translate >Create an account</translate>
|
||||
</router-link>
|
||||
</template>
|
||||
</label>
|
||||
|
@ -119,12 +119,12 @@ const submit = async () => {
|
|||
</div>
|
||||
<div class="field">
|
||||
<label for="password-field">
|
||||
<translate translate-context="*/*/*">Password</translate> |
|
||||
<translate >Password</translate> |
|
||||
<router-link
|
||||
tabindex="1"
|
||||
:to="{name: 'auth.password-reset', query: {email: credentials.username}}"
|
||||
>
|
||||
<translate translate-context="*/Login/*/Verb">Reset your password</translate>
|
||||
<translate >Reset your password</translate>
|
||||
</router-link>
|
||||
</label>
|
||||
<password-input
|
||||
|
@ -137,7 +137,6 @@ const submit = async () => {
|
|||
<template v-else>
|
||||
<p>
|
||||
<translate
|
||||
translate-context="Content/Auth/Paragraph"
|
||||
:translate-params="{domain: $store.getters['instance/domain']}"
|
||||
>
|
||||
You will be redirected to %{ domain } to authenticate.
|
||||
|
@ -148,7 +147,7 @@ const submit = async () => {
|
|||
:class="['ui', {'loading': isLoading}, 'right', 'floated', buttonClasses, 'button']"
|
||||
type="submit"
|
||||
>
|
||||
<translate translate-context="*/Login/*/Verb">
|
||||
<translate >
|
||||
Login
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/Login/Title', 'Log Out')
|
||||
title: t('Log Out')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -19,13 +19,13 @@ const labels = computed(() => ({
|
|||
class="ui small text container"
|
||||
>
|
||||
<h2>
|
||||
<translate translate-context="Content/Login/Title">
|
||||
<translate >
|
||||
Are you sure you want to log out?
|
||||
</translate>
|
||||
</h2>
|
||||
<p
|
||||
v-translate="{username: $store.state.auth.username}"
|
||||
translate-context="Content/Login/Paragraph"
|
||||
|
||||
>
|
||||
You are currently logged in as %{ username }
|
||||
</p>
|
||||
|
@ -33,7 +33,7 @@ const labels = computed(() => ({
|
|||
class="ui button"
|
||||
@click="$store.dispatch('auth/logout')"
|
||||
>
|
||||
<translate translate-context="Content/Login/Button.Label">
|
||||
<translate >
|
||||
Yes, log me out!
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -43,7 +43,7 @@ const labels = computed(() => ({
|
|||
class="ui small text container"
|
||||
>
|
||||
<h2>
|
||||
<translate translate-context="Content/Login/Title">
|
||||
<translate >
|
||||
You aren't currently logged in
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -51,7 +51,7 @@ const labels = computed(() => ({
|
|||
to="/login"
|
||||
class="ui button"
|
||||
>
|
||||
<translate translate-context="Content/Login/Button.Label">
|
||||
<translate >
|
||||
Log in!
|
||||
</translate>
|
||||
</router-link>
|
||||
|
|
|
@ -69,7 +69,7 @@ const submitAndScan = async () => {
|
|||
target="_blank"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate translate-context="Footer/*/List item.Link/Short, Noun">Documentation</translate>
|
||||
<translate >Documentation</translate>
|
||||
</a>
|
||||
</template>
|
||||
<div class="ui clearing hidden divider" />
|
||||
|
@ -79,7 +79,7 @@ const submitAndScan = async () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Error while saving plugin
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -99,7 +99,7 @@ const submitAndScan = async () => {
|
|||
v-model="enabled"
|
||||
type="checkbox"
|
||||
>
|
||||
<label :for="`${plugin.name}-enabled`"><translate translate-context="*/*/*">Enabled</translate></label>
|
||||
<label :for="`${plugin.name}-enabled`"><translate >Enabled</translate></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui clearing hidden divider" />
|
||||
|
@ -107,7 +107,7 @@ const submitAndScan = async () => {
|
|||
v-if="plugin.source"
|
||||
class="field"
|
||||
>
|
||||
<label for="plugin-library"><translate translate-context="*/*/*/Noun">Library</translate></label>
|
||||
<label for="plugin-library"><translate >Library</translate></label>
|
||||
<select
|
||||
id="plugin-library"
|
||||
v-model="values['library']"
|
||||
|
@ -121,7 +121,7 @@ const submitAndScan = async () => {
|
|||
</option>
|
||||
</select>
|
||||
<div>
|
||||
<translate translate-context="*/*/Paragraph/Noun">
|
||||
<translate >
|
||||
Library where files should be imported.
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -198,7 +198,7 @@ const submitAndScan = async () => {
|
|||
type="submit"
|
||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Save
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -207,7 +207,7 @@ const submitAndScan = async () => {
|
|||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
|
||||
@click.prevent="submitAndScan"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Scan
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -6,7 +6,7 @@ import axios from 'axios'
|
|||
import $ from 'jquery'
|
||||
|
||||
import { computed, reactive, ref, onMounted } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
|
@ -30,7 +30,7 @@ interface Settings {
|
|||
fields: Record<FieldId, Field>
|
||||
}
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const sharedLabels = useSharedLabels()
|
||||
const logger = useLogger()
|
||||
const router = useRouter()
|
||||
|
@ -57,7 +57,7 @@ const settings = reactive({
|
|||
const orderedSettingsFields = SETTINGS_ORDER.map(id => settings.fields[id])
|
||||
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/Settings/Title', 'Account Settings')
|
||||
title: t('Account Settings')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
@ -224,7 +224,7 @@ const deleteAccount = async () => {
|
|||
await axios.delete('users/me/', { data: payload })
|
||||
|
||||
store.commit('ui/addMessage', {
|
||||
content: $pgettext('*/Auth/Message', 'Your deletion request was submitted, your account and content will be deleted shortly'),
|
||||
content: t('Your deletion request was submitted, your account and content will be deleted shortly'),
|
||||
date: new Date()
|
||||
})
|
||||
|
||||
|
@ -276,7 +276,7 @@ fetchOwnedApps()
|
|||
<div class="ui vertical stripe segment">
|
||||
<section class="ui text container">
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Settings/Title">
|
||||
<translate >
|
||||
Account settings
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -289,7 +289,7 @@ fetchOwnedApps()
|
|||
class="ui positive message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Settings/Message">
|
||||
<translate >
|
||||
Settings updated
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -300,7 +300,7 @@ fetchOwnedApps()
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Settings/Error message.Title">
|
||||
<translate >
|
||||
Your settings can't be updated
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -346,7 +346,7 @@ fetchOwnedApps()
|
|||
:class="['ui', { loading: isLoading }, 'button']"
|
||||
type="submit"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Button.Label/Verb">
|
||||
<translate >
|
||||
Update settings
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -355,7 +355,7 @@ fetchOwnedApps()
|
|||
<section class="ui text container">
|
||||
<div class="ui hidden divider" />
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Settings/Title">
|
||||
<translate >
|
||||
Avatar
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -366,7 +366,7 @@ fetchOwnedApps()
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Settings/Error message.Title">
|
||||
<translate >
|
||||
Your avatar cannot be saved
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -385,7 +385,7 @@ fetchOwnedApps()
|
|||
@update:model-value="submitAvatar($event)"
|
||||
@delete="avatar = {uuid: null}"
|
||||
>
|
||||
<translate translate-context="Content/Channel/*">
|
||||
<translate >
|
||||
Avatar
|
||||
</translate>
|
||||
</attachment-input>
|
||||
|
@ -395,14 +395,14 @@ fetchOwnedApps()
|
|||
<section class="ui text container">
|
||||
<div class="ui hidden divider" />
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Settings/Title/Verb">
|
||||
<translate >
|
||||
Change my password
|
||||
</translate>
|
||||
</h2>
|
||||
<div class="ui message">
|
||||
<translate translate-context="Content/Settings/Paragraph'">
|
||||
<translate >
|
||||
Changing your password will also change your Subsonic API password if you have requested one.
|
||||
</translate> <translate translate-context="Content/Settings/Paragraph">
|
||||
</translate> <translate >
|
||||
You will have to update your password on your clients that use this password.
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -416,20 +416,20 @@ fetchOwnedApps()
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Settings/Error message.Title">
|
||||
<translate >
|
||||
Your password cannot be changed
|
||||
</translate>
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li v-if="passwordError == 'invalid_credentials'">
|
||||
<translate translate-context="Content/Settings/Error message.List item/Call to action">
|
||||
<translate >
|
||||
Please double-check your password is correct
|
||||
</translate>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="old-password-field"><translate translate-context="Content/Settings/Input.Label">Current password</translate></label>
|
||||
<label for="old-password-field"><translate >Current password</translate></label>
|
||||
<password-input
|
||||
v-model="credentials.oldPassword"
|
||||
field-id="old-password-field"
|
||||
|
@ -437,7 +437,7 @@ fetchOwnedApps()
|
|||
/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="new-password-field"><translate translate-context="Content/Settings/Input.Label">New password</translate></label>
|
||||
<label for="new-password-field"><translate >New password</translate></label>
|
||||
<password-input
|
||||
v-model="credentials.newPassword"
|
||||
field-id="new-password-field"
|
||||
|
@ -448,12 +448,12 @@ fetchOwnedApps()
|
|||
:class="['ui', {'loading': isLoadingPassword}, {disabled: !credentials.newPassword || !credentials.oldPassword}, 'warning', 'button']"
|
||||
:action="submitPassword"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Button.Label">
|
||||
<translate >
|
||||
Change password
|
||||
</translate>
|
||||
<template #modal-header>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Title">
|
||||
<translate >
|
||||
Change your password?
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -461,18 +461,18 @@ fetchOwnedApps()
|
|||
<template #modal-content>
|
||||
<div>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Paragraph">
|
||||
<translate >
|
||||
Changing your password will have the following consequences:
|
||||
</translate>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<translate translate-context="Popup/Settings/List item">
|
||||
<translate >
|
||||
You will be logged out from this session and have to log in with the new one
|
||||
</translate>
|
||||
</li>
|
||||
<li>
|
||||
<translate translate-context="Popup/Settings/List item">
|
||||
<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>
|
||||
|
@ -481,7 +481,7 @@ fetchOwnedApps()
|
|||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
<translate translate-context="Popup/Settings/Button.Label">
|
||||
<translate >
|
||||
Disable access
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -500,13 +500,13 @@ fetchOwnedApps()
|
|||
<h2 class="ui header">
|
||||
<i class="eye slash outline icon" />
|
||||
<div class="content">
|
||||
<translate translate-context="Content/Settings/Title/Noun">
|
||||
<translate >
|
||||
Content filters
|
||||
</translate>
|
||||
</div>
|
||||
</h2>
|
||||
<p>
|
||||
<translate translate-context="Content/Settings/Paragraph">
|
||||
<translate >
|
||||
Content filters help you hide content you don't want to see on the service.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -516,12 +516,12 @@ fetchOwnedApps()
|
|||
@click="$store.dispatch('moderation/fetchContentFilters')"
|
||||
>
|
||||
<i class="refresh icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Refresh
|
||||
</translate>
|
||||
</button>
|
||||
<h3 class="ui header">
|
||||
<translate translate-context="Content/Settings/Title">
|
||||
<translate >
|
||||
Hidden artists
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -529,12 +529,12 @@ fetchOwnedApps()
|
|||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Name
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Creation date
|
||||
</translate>
|
||||
</th>
|
||||
|
@ -559,7 +559,7 @@ fetchOwnedApps()
|
|||
class="ui basic tiny button"
|
||||
@click="$store.dispatch('moderation/deleteContentFilter', filter.uuid)"
|
||||
>
|
||||
<translate translate-context="*/*/*/Verb">
|
||||
<translate >
|
||||
Delete
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -576,13 +576,13 @@ fetchOwnedApps()
|
|||
<h2 class="ui header">
|
||||
<i class="open lock icon" />
|
||||
<div class="content">
|
||||
<translate translate-context="Content/Settings/Title/Noun">
|
||||
<translate >
|
||||
Authorized apps
|
||||
</translate>
|
||||
</div>
|
||||
</h2>
|
||||
<p>
|
||||
<translate translate-context="Content/Settings/Paragraph">
|
||||
<translate >
|
||||
This is the list of applications that have access to your account data.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -591,7 +591,7 @@ fetchOwnedApps()
|
|||
@click="fetchApps()"
|
||||
>
|
||||
<i class="refresh icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Refresh
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -602,12 +602,12 @@ fetchOwnedApps()
|
|||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Application
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Permissions
|
||||
</translate>
|
||||
</th>
|
||||
|
@ -630,27 +630,27 @@ fetchOwnedApps()
|
|||
:class="['ui', 'tiny', 'danger', { loading: isRevoking.has(app.client_id) }, 'button']"
|
||||
@confirm="revokeApp(app.client_id)"
|
||||
>
|
||||
<translate translate-context="*/*/*/Verb">
|
||||
<translate >
|
||||
Revoke
|
||||
</translate>
|
||||
<template #modal-header>
|
||||
<p
|
||||
v-translate="{application: app.name}"
|
||||
translate-context="Popup/Settings/Title"
|
||||
|
||||
>
|
||||
Revoke access for application "%{ application }"?
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Paragraph">
|
||||
<translate >
|
||||
This will prevent this application from accessing the service on your behalf.
|
||||
</translate>
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
<translate translate-context="*/Settings/Button.Label/Verb">
|
||||
<translate >
|
||||
Revoke access
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -662,11 +662,11 @@ fetchOwnedApps()
|
|||
</table>
|
||||
<empty-state v-else>
|
||||
<template #title>
|
||||
<translate translate-context="Content/Applications/Paragraph">
|
||||
<translate >
|
||||
You don't have any application connected with your account.
|
||||
</translate>
|
||||
</template>
|
||||
<translate translate-context="Content/Applications/Paragraph">
|
||||
<translate >
|
||||
If you authorize third-party applications to access your data, those applications will be listed here.
|
||||
</translate>
|
||||
</empty-state>
|
||||
|
@ -679,13 +679,13 @@ fetchOwnedApps()
|
|||
<h2 class="ui header">
|
||||
<i class="code icon" />
|
||||
<div class="content">
|
||||
<translate translate-context="Content/Settings/Title/Noun">
|
||||
<translate >
|
||||
Your applications
|
||||
</translate>
|
||||
</div>
|
||||
</h2>
|
||||
<p>
|
||||
<translate translate-context="Content/Settings/Paragraph">
|
||||
<translate >
|
||||
This is the list of applications that you have registered.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -693,7 +693,7 @@ fetchOwnedApps()
|
|||
class="ui success button"
|
||||
:to="{name: 'settings.applications.new'}"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Button.Label">
|
||||
<translate >
|
||||
Register a new application
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -704,17 +704,17 @@ fetchOwnedApps()
|
|||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Application
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Scopes
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Creation date
|
||||
</translate>
|
||||
</th>
|
||||
|
@ -742,7 +742,7 @@ fetchOwnedApps()
|
|||
class="ui tiny success button"
|
||||
:to="{name: 'settings.applications.edit', params: {id: app.client_id}}"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Edit
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -750,27 +750,27 @@ fetchOwnedApps()
|
|||
:class="['ui', 'tiny', 'danger', { loading: isDeleting.has(app.client_id) }, 'button']"
|
||||
@confirm="deleteApp(app.client_id)"
|
||||
>
|
||||
<translate translate-context="*/*/*/Verb">
|
||||
<translate >
|
||||
Remove
|
||||
</translate>
|
||||
<template #modal-header>
|
||||
<p
|
||||
v-translate="{application: app.name}"
|
||||
translate-context="Popup/Settings/Title"
|
||||
|
||||
>
|
||||
Remove application "%{ application }"?
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Paragraph">
|
||||
<translate >
|
||||
This will permanently remove the application and all the associated tokens.
|
||||
</translate>
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
<translate translate-context="*/Settings/Button.Label/Verb">
|
||||
<translate >
|
||||
Remove application
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -782,11 +782,11 @@ fetchOwnedApps()
|
|||
</table>
|
||||
<empty-state v-else>
|
||||
<template #title>
|
||||
<translate translate-context="Content/Applications/Paragraph">
|
||||
<translate >
|
||||
You don't have registered any application yet.
|
||||
</translate>
|
||||
</template>
|
||||
<translate translate-context="Content/Applications/Paragraph">
|
||||
<translate >
|
||||
Register one to integrate Funkwhale with third-party applications.
|
||||
</translate>
|
||||
</empty-state>
|
||||
|
@ -800,13 +800,13 @@ fetchOwnedApps()
|
|||
<h2 class="ui header">
|
||||
<i class="code icon" />
|
||||
<div class="content">
|
||||
<translate translate-context="Content/Settings/Title/Noun">
|
||||
<translate >
|
||||
Plugins
|
||||
</translate>
|
||||
</div>
|
||||
</h2>
|
||||
<p>
|
||||
<translate translate-context="Content/Settings/Paragraph">
|
||||
<translate >
|
||||
Use plugins to extend Funkwhale and get additional features.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -814,7 +814,7 @@ fetchOwnedApps()
|
|||
class="ui success button"
|
||||
:to="{name: 'settings.plugins'}"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Button.Label">
|
||||
<translate >
|
||||
Manage plugins
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -824,20 +824,20 @@ fetchOwnedApps()
|
|||
<h2 class="ui header">
|
||||
<i class="comment icon" />
|
||||
<div class="content">
|
||||
<translate translate-context="*/*/Button.Label">
|
||||
<translate >
|
||||
Change my e-mail address
|
||||
</translate>
|
||||
</div>
|
||||
</h2>
|
||||
<p>
|
||||
<translate translate-context="Content/Settings/Paragraph'">
|
||||
<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}"
|
||||
translate-context="Content/Settings/Paragraph'"
|
||||
|
||||
>
|
||||
Your current e-mail address is %{ email }.
|
||||
</translate>
|
||||
|
@ -852,7 +852,7 @@ fetchOwnedApps()
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Settings/Error message.Title">
|
||||
<translate >
|
||||
We cannot change your e-mail address
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -866,7 +866,7 @@ fetchOwnedApps()
|
|||
</ul>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="new-email"><translate translate-context="*/*/*">New e-mail address</translate></label>
|
||||
<label for="new-email"><translate >New e-mail address</translate></label>
|
||||
<input
|
||||
id="new-email"
|
||||
v-model="newEmail"
|
||||
|
@ -875,7 +875,7 @@ fetchOwnedApps()
|
|||
>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="current-password-field-email"><translate translate-context="*/*/*">Password</translate></label>
|
||||
<label for="current-password-field-email"><translate >Password</translate></label>
|
||||
<password-input
|
||||
v-model="emailPassword"
|
||||
field-id="current-password-field-email"
|
||||
|
@ -886,7 +886,7 @@ fetchOwnedApps()
|
|||
type="submit"
|
||||
class="ui button"
|
||||
>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Update
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -897,13 +897,13 @@ fetchOwnedApps()
|
|||
<h2 class="ui header">
|
||||
<i class="trash icon" />
|
||||
<div class="content">
|
||||
<translate translate-context="*/*/Button.Label">
|
||||
<translate >
|
||||
Delete my account
|
||||
</translate>
|
||||
</div>
|
||||
</h2>
|
||||
<p>
|
||||
<translate translate-context="Content/Settings/Paragraph'">
|
||||
<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>
|
||||
|
@ -911,7 +911,7 @@ fetchOwnedApps()
|
|||
role="alert"
|
||||
class="ui warning message"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Paragraph'">
|
||||
<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>
|
||||
|
@ -922,7 +922,7 @@ fetchOwnedApps()
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Settings/Error message.Title">
|
||||
<translate >
|
||||
We cannot delete your account
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -936,7 +936,7 @@ fetchOwnedApps()
|
|||
</ul>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="current-password-field"><translate translate-context="*/*/*">Password</translate></label>
|
||||
<label for="current-password-field"><translate >Password</translate></label>
|
||||
<password-input
|
||||
v-model="deleteAccountPassword"
|
||||
field-id="current-password-field"
|
||||
|
@ -947,12 +947,12 @@ fetchOwnedApps()
|
|||
:class="['ui', {'loading': isDeletingAccount}, {disabled: !deleteAccountPassword}, {danger: deleteAccountPassword}, 'button']"
|
||||
:action="deleteAccount"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label">
|
||||
<translate >
|
||||
Delete my account…
|
||||
</translate>
|
||||
<template #modal-header>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Title">
|
||||
<translate >
|
||||
Do you want to delete your account?
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -960,7 +960,7 @@ fetchOwnedApps()
|
|||
<template #modal-content>
|
||||
<div>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Paragraph">
|
||||
<translate >
|
||||
This is irreversible and will permanently remove your data from our servers. You will we immediately logged out.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -968,7 +968,7 @@ fetchOwnedApps()
|
|||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
<translate translate-context="*/*/Button.Label">
|
||||
<translate >
|
||||
Delete my account
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { RouteLocationRaw } from 'vue-router'
|
|||
import type { BackendError, Form } from '~/types'
|
||||
|
||||
import { computed, reactive, ref, watchEffect } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
import axios from 'axios'
|
||||
|
@ -30,14 +30,14 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
signupApprovalEnabled: undefined
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const logger = useLogger()
|
||||
const store = useStore()
|
||||
|
||||
const labels = computed(() => ({
|
||||
placeholder: $pgettext('Content/Signup/Form/Placeholder', 'Enter your invitation code (case insensitive)'),
|
||||
usernamePlaceholder: $pgettext('Content/Signup/Form/Placeholder', 'Enter your username'),
|
||||
emailPlaceholder: $pgettext('Content/Signup/Form/Placeholder', 'Enter your e-mail address')
|
||||
placeholder: t('Enter your invitation code (case insensitive)'),
|
||||
usernamePlaceholder: t('Enter your username'),
|
||||
emailPlaceholder: t('Enter your e-mail address')
|
||||
}))
|
||||
|
||||
const signupRequiresApproval = computed(() => props.signupApprovalEnabled ?? store.state.instance.settings.moderation.signup_approval_enabled.value)
|
||||
|
@ -88,18 +88,18 @@ fetchInstanceSettings()
|
|||
<div v-if="submitted">
|
||||
<div class="ui success message">
|
||||
<p v-if="signupRequiresApproval">
|
||||
<translate translate-context="Content/Signup/Form/Paragraph">
|
||||
<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 translate-context="Content/Signup/Form/Paragraph">
|
||||
<translate >
|
||||
Your account was successfully created. Please verify your e-mail address before trying to login.
|
||||
</translate>
|
||||
</p>
|
||||
</div>
|
||||
<h2>
|
||||
<translate translate-context="Content/Login/Title/Verb">
|
||||
<translate >
|
||||
Log in to your Funkwhale account
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -117,7 +117,7 @@ fetchInstanceSettings()
|
|||
v-if="!$store.state.instance.settings.users.registration_enabled.value"
|
||||
class="ui message"
|
||||
>
|
||||
<translate translate-context="Content/Signup/Form/Paragraph">
|
||||
<translate >
|
||||
Public registrations are not possible on this instance. You will need an invitation code to sign up.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -125,7 +125,7 @@ fetchInstanceSettings()
|
|||
v-else-if="signupRequiresApproval"
|
||||
class="ui message"
|
||||
>
|
||||
<translate translate-context="Content/Signup/Form/Paragraph">
|
||||
<translate >
|
||||
Registrations on this pod are open, but reviewed by moderators before approval.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -143,7 +143,7 @@ fetchInstanceSettings()
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Signup/Form/Paragraph">
|
||||
<translate >
|
||||
Your account cannot be created.
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -157,7 +157,7 @@ fetchInstanceSettings()
|
|||
</ul>
|
||||
</div>
|
||||
<div class="required field">
|
||||
<label for="username-field"><translate translate-context="Content/*/*">Username</translate></label>
|
||||
<label for="username-field"><translate >Username</translate></label>
|
||||
<input
|
||||
id="username-field"
|
||||
ref="username"
|
||||
|
@ -170,7 +170,7 @@ fetchInstanceSettings()
|
|||
>
|
||||
</div>
|
||||
<div class="required field">
|
||||
<label for="email-field"><translate translate-context="Content/*/*/Noun">E-mail address</translate></label>
|
||||
<label for="email-field"><translate >E-mail address</translate></label>
|
||||
<input
|
||||
id="email-field"
|
||||
ref="email"
|
||||
|
@ -182,7 +182,7 @@ fetchInstanceSettings()
|
|||
>
|
||||
</div>
|
||||
<div class="required field">
|
||||
<label for="password-field"><translate translate-context="*/*/*">Password</translate></label>
|
||||
<label for="password-field"><translate >Password</translate></label>
|
||||
<password-input
|
||||
v-model="payload.password1"
|
||||
field-id="password-field"
|
||||
|
@ -192,7 +192,7 @@ fetchInstanceSettings()
|
|||
v-if="!$store.state.instance.settings.users.registration_enabled.value"
|
||||
class="required field"
|
||||
>
|
||||
<label for="invitation-code"><translate translate-context="Content/*/Input.Label">Invitation code</translate></label>
|
||||
<label for="invitation-code"><translate >Invitation code</translate></label>
|
||||
<input
|
||||
id="invitation-code"
|
||||
v-model="payload.invitation"
|
||||
|
@ -229,7 +229,7 @@ fetchInstanceSettings()
|
|||
:class="['ui', buttonClasses, {'loading': isLoading}, ' right floated button']"
|
||||
type="submit"
|
||||
>
|
||||
<translate translate-context="Content/Signup/Button.Label">
|
||||
<translate >
|
||||
Create my account
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import type { BackendError } from '~/types'
|
||||
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useStore } from '~/store'
|
||||
import axios from 'axios'
|
||||
|
||||
import PasswordInput from '~/components/forms/PasswordInput.vue'
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const subsonicEnabled = computed(() => store.state.instance.settings.subsonic.enabled.value)
|
||||
const labels = computed(() => ({
|
||||
subsonicField: $pgettext('Content/Password/Input.label', 'Your subsonic API password')
|
||||
subsonicField: t('Your subsonic API password')
|
||||
}))
|
||||
|
||||
const errors = ref([] as string[])
|
||||
|
@ -38,7 +38,7 @@ const fetchToken = async () => {
|
|||
const showToken = ref(false)
|
||||
const successMessage = ref('')
|
||||
const requestNewToken = async () => {
|
||||
successMessage.value = $pgettext('Content/Settings/Message', 'Password updated')
|
||||
successMessage.value = t('Password updated')
|
||||
success.value = false
|
||||
errors.value = []
|
||||
isLoading.value = true
|
||||
|
@ -56,7 +56,7 @@ const requestNewToken = async () => {
|
|||
}
|
||||
|
||||
const disable = async () => {
|
||||
successMessage.value = $pgettext('Content/Settings/Message', 'Access disabled')
|
||||
successMessage.value = t('Access disabled')
|
||||
success.value = false
|
||||
errors.value = []
|
||||
isLoading.value = true
|
||||
|
@ -82,7 +82,7 @@ fetchToken()
|
|||
@submit.prevent="requestNewToken()"
|
||||
>
|
||||
<h2>
|
||||
<translate translate-context="Content/Settings/Title">
|
||||
<translate >
|
||||
Subsonic API password
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -90,19 +90,19 @@ fetchToken()
|
|||
v-if="!subsonicEnabled"
|
||||
class="ui message"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Paragraph">
|
||||
<translate >
|
||||
The Subsonic API is not available on this Funkwhale instance.
|
||||
</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-context="Content/Settings/Paragraph'">
|
||||
<translate >
|
||||
Funkwhale is compatible with other music players that support the Subsonic API.
|
||||
</translate> <translate translate-context="Content/Settings/Paragraph">
|
||||
</translate> <translate >
|
||||
You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.
|
||||
</translate>
|
||||
</p>
|
||||
<p>
|
||||
<translate translate-context="Content/Settings/Paragraph">
|
||||
<translate >
|
||||
However, accessing Funkwhale from those clients requires a separate password you can set below.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -111,7 +111,7 @@ fetchToken()
|
|||
href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients"
|
||||
target="_blank"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Link">Discover how to use Funkwhale from other apps</translate>
|
||||
<translate >Discover how to use Funkwhale from other apps</translate>
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
|
@ -128,7 +128,7 @@ fetchToken()
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Error
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -164,26 +164,26 @@ fetchToken()
|
|||
:class="['ui', {'loading': isLoading}, 'button']"
|
||||
:action="requestNewToken"
|
||||
>
|
||||
<translate translate-context="*/Settings/Button.Label/Verb">
|
||||
<translate >
|
||||
Request a new password
|
||||
</translate>
|
||||
<template #modal-header>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Title">
|
||||
<translate >
|
||||
Request a new Subsonic API password?
|
||||
</translate>
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Paragraph">
|
||||
<translate >
|
||||
This will log you out from existing devices that use the current password.
|
||||
</translate>
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
<translate translate-context="*/Settings/Button.Label/Verb">
|
||||
<translate >
|
||||
Request a new password
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -195,7 +195,7 @@ fetchToken()
|
|||
:class="['ui', {'loading': isLoading}, 'button']"
|
||||
@click="requestNewToken"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Button.Label/Verb">
|
||||
<translate >
|
||||
Request a password
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -204,26 +204,26 @@ fetchToken()
|
|||
:class="['ui', {'loading': isLoading}, 'warning', 'button']"
|
||||
:action="disable"
|
||||
>
|
||||
<translate translate-context="Content/Settings/Button.Label/Verb">
|
||||
<translate >
|
||||
Disable Subsonic access
|
||||
</translate>
|
||||
<template #modal-header>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Title">
|
||||
<translate >
|
||||
Disable Subsonic API access?
|
||||
</translate>
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<p>
|
||||
<translate translate-context="Popup/Settings/Paragraph">
|
||||
<translate >
|
||||
This will completely disable access to the Subsonic API using from account.
|
||||
</translate>
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-confirm>
|
||||
<div>
|
||||
<translate translate-context="Popup/Settings/Button.Label">
|
||||
<translate >
|
||||
Disable access
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -59,7 +59,7 @@ defineExpose({
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Error while creating
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -74,7 +74,7 @@ defineExpose({
|
|||
</div>
|
||||
<div class="ui required field">
|
||||
<label for="album-title">
|
||||
<translate translate-context="*/*/*/Noun">Title</translate>
|
||||
<translate >Title</translate>
|
||||
</label>
|
||||
<input
|
||||
v-model="title"
|
||||
|
|
|
@ -35,13 +35,13 @@ const albumForm = ref()
|
|||
<h4 class="header">
|
||||
<translate
|
||||
v-if="channel.content_category === 'podcast'"
|
||||
translate-context="Popup/Channels/Title/Verb"
|
||||
|
||||
>
|
||||
New series
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Popup/Channels/Title"
|
||||
|
||||
>
|
||||
New album
|
||||
</translate>
|
||||
|
@ -57,7 +57,7 @@ const albumForm = ref()
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic cancel button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -66,7 +66,7 @@ const albumForm = ref()
|
|||
:disabled="!submittable"
|
||||
@click.stop.prevent="albumForm.submit()"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label">
|
||||
<translate >
|
||||
Create
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -49,11 +49,11 @@ watch(() => props.channel, fetchData, { immediate: true })
|
|||
<label for="album-dropdown">
|
||||
<translate
|
||||
v-if="channel && channel.artist && channel.artist.content_category === 'podcast'"
|
||||
translate-context="*/*/*"
|
||||
|
||||
>Series</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
>Album</translate>
|
||||
</label>
|
||||
<select
|
||||
|
@ -62,7 +62,7 @@ watch(() => props.channel, fetchData, { immediate: true })
|
|||
class="ui search normal dropdown"
|
||||
>
|
||||
<option value="">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
None
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -72,7 +72,7 @@ watch(() => props.channel, fetchData, { immediate: true })
|
|||
:value="album.id"
|
||||
>
|
||||
{{ album.title }} (<translate
|
||||
translate-context="*/*/*"
|
||||
|
||||
:translate-params="{count: album.tracks_count}"
|
||||
:translate-n="album.tracks_count"
|
||||
translate-plural="%{ count } tracks"
|
||||
|
|
|
@ -55,7 +55,7 @@ fetchLicenses()
|
|||
<template>
|
||||
<div>
|
||||
<label for="license-dropdown">
|
||||
<translate translate-context="Content/*/*/Noun">License</translate>
|
||||
<translate >License</translate>
|
||||
</label>
|
||||
<select
|
||||
id="license-dropdown"
|
||||
|
@ -63,7 +63,7 @@ fetchLicenses()
|
|||
class="ui search normal dropdown"
|
||||
>
|
||||
<option value="">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
None
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -86,7 +86,7 @@ fetchLicenses()
|
|||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<translate translate-context="Content/*/*">About this license</translate>
|
||||
<translate >About this license</translate>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { Channel } from '~/types'
|
||||
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
|
@ -19,17 +19,17 @@ interface Props {
|
|||
const emit = defineEmits<Events>()
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const isSubscribed = computed(() => store.getters['channels/isSubscribed'](props.channel.uuid))
|
||||
const title = computed(() => isSubscribed.value
|
||||
? $pgettext('Content/Channel/Button/Verb', 'Unsubscribe')
|
||||
: $pgettext('Content/Channel/Button/Verb', 'Subscribe')
|
||||
? t('Unsubscribe')
|
||||
: t('Subscribe')
|
||||
)
|
||||
|
||||
const message = computed(() => ({
|
||||
authMessage: $pgettext('Popup/Message/Paragraph', 'You need to be logged in to subscribe to this channel')
|
||||
authMessage: t('You need to be logged in to subscribe to this channel')
|
||||
}))
|
||||
|
||||
const toggle = async () => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { VueUploadItem } from 'vue-upload-component'
|
|||
import { computed, ref, reactive, watchEffect, watch } from 'vue'
|
||||
import { whenever, useCurrentElement } from '@vueuse/core'
|
||||
import { humanSize } from '~/utils/filters'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
import axios from 'axios'
|
||||
|
@ -52,7 +52,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
channel: null
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const errors = ref([] as string[])
|
||||
|
@ -396,7 +396,7 @@ watchEffect(() => {
|
|||
})
|
||||
|
||||
const labels = computed(() => ({
|
||||
editTitle: $pgettext('Content/*/Button.Label/Verb', 'Edit')
|
||||
editTitle: t('Edit')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -411,7 +411,7 @@ const labels = computed(() => ({
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Error while publishing
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -426,7 +426,7 @@ const labels = computed(() => ({
|
|||
</div>
|
||||
<div :class="['ui', 'required', {hidden: step > 1}, 'field']">
|
||||
<label for="channel-dropdown">
|
||||
<translate translate-context="*/*/*">Channel</translate>
|
||||
<translate >Channel</translate>
|
||||
</label>
|
||||
<div
|
||||
id="channel-dropdown"
|
||||
|
@ -449,7 +449,7 @@ const labels = computed(() => ({
|
|||
<div class="content">
|
||||
<p>
|
||||
<i class="copyright icon" />
|
||||
<translate translate-context="Content/Channels/Popup.Paragraph">
|
||||
<translate >
|
||||
Add a license to your upload to ensure some freedoms to your public.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -464,7 +464,7 @@ const labels = computed(() => ({
|
|||
<div class="content">
|
||||
<p>
|
||||
<i class="warning icon" />
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
You don't have any space left to upload your files. Please contact the moderators.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -477,7 +477,7 @@ const labels = computed(() => ({
|
|||
>
|
||||
<p>
|
||||
<i class="redo icon" />
|
||||
<translate translate-context="Popup/Channels/Paragraph">
|
||||
<translate >
|
||||
You have some draft uploads pending publication.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -485,7 +485,7 @@ const labels = computed(() => ({
|
|||
class="ui basic button"
|
||||
@click.stop.prevent="includeDraftUploads = false"
|
||||
>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Ignore
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -493,7 +493,7 @@ const labels = computed(() => ({
|
|||
class="ui basic button"
|
||||
@click.stop.prevent="includeDraftUploads = true"
|
||||
>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Resume
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -547,19 +547,19 @@ const labels = computed(() => ({
|
|||
<template v-else>
|
||||
<translate
|
||||
v-if="file.active"
|
||||
translate-context="Channels/*/*"
|
||||
|
||||
>
|
||||
Uploading
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="file.error"
|
||||
translate-context="Channels/*/*"
|
||||
|
||||
>
|
||||
Errored
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Channels/*/*"
|
||||
|
||||
>
|
||||
Pending
|
||||
</translate>
|
||||
|
@ -567,12 +567,12 @@ const labels = computed(() => ({
|
|||
· {{ parseFloat(file.progress ?? '0') }}%
|
||||
</template>
|
||||
· <a @click.stop.prevent="remove(file)">
|
||||
<translate translate-context="Content/Radio/Button.Label/Verb">Remove</translate>
|
||||
<translate >Remove</translate>
|
||||
</a>
|
||||
<template v-if="file.error">
|
||||
·
|
||||
<a @click.stop.prevent="retry(file)">
|
||||
<translate translate-context="*/*/*">Retry</translate>
|
||||
<translate >Retry</translate>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -592,7 +592,7 @@ const labels = computed(() => ({
|
|||
<p>
|
||||
<i class="info icon" />
|
||||
<translate
|
||||
translate-context="Content/Library/Paragraph"
|
||||
|
||||
:translate-params="{extensions: $store.state.ui.supportedExtensions.join(', ')}"
|
||||
>
|
||||
Supported extensions: %{ extensions }
|
||||
|
@ -615,13 +615,13 @@ const labels = computed(() => ({
|
|||
>
|
||||
<div>
|
||||
<i class="upload icon" />
|
||||
<translate translate-context="Content/Channels/Paragraph">
|
||||
<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 translate-context="*/*/*">
|
||||
<translate >
|
||||
Browse…
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -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 translate-context="*/*/*/Noun">Title</translate>
|
||||
<translate >Title</translate>
|
||||
</label>
|
||||
<input
|
||||
v-model="newValues.title"
|
||||
|
@ -44,7 +44,7 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
|
|||
v-model="newValues.cover"
|
||||
@delete="newValues.cover = ''"
|
||||
>
|
||||
<translate translate-context="Content/Channel/*">
|
||||
<translate >
|
||||
Track Picture
|
||||
</translate>
|
||||
</attachment-input>
|
||||
|
@ -52,7 +52,7 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
|
|||
<div class="ui two fields">
|
||||
<div class="ui field">
|
||||
<label for="upload-tags">
|
||||
<translate translate-context="*/*/*/Noun">Tags</translate>
|
||||
<translate >Tags</translate>
|
||||
</label>
|
||||
<tags-selector
|
||||
id="upload-tags"
|
||||
|
@ -62,7 +62,7 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
|
|||
</div>
|
||||
<div class="ui field">
|
||||
<label for="upload-position">
|
||||
<translate translate-context="*/*/*/Short, Noun">Position</translate>
|
||||
<translate >Position</translate>
|
||||
</label>
|
||||
<input
|
||||
v-model="newValues.position"
|
||||
|
@ -74,7 +74,7 @@ watch(newValues, (values) => emit('update:values', values), { immediate: true })
|
|||
</div>
|
||||
<div class="ui field">
|
||||
<label for="upload-description">
|
||||
<translate translate-context="*/*/*">Description</translate>
|
||||
<translate >Description</translate>
|
||||
</label>
|
||||
<content-form
|
||||
v-model="newValues.description"
|
||||
|
|
|
@ -5,7 +5,7 @@ import { humanSize } from '~/utils/filters'
|
|||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '~/store'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
@ -13,7 +13,7 @@ router.beforeEach(() => store.commit('channels/showUploadModal', { show: false }
|
|||
|
||||
const update = (value: boolean) => store.commit('channels/showUploadModal', { show: value })
|
||||
|
||||
const { $npgettext, $gettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
|
||||
const uploadForm = ref()
|
||||
|
||||
|
@ -29,8 +29,8 @@ const statusInfo = computed(() => {
|
|||
}
|
||||
|
||||
if (statusData.value.totalFiles) {
|
||||
const msg = $npgettext('*/*/*', '%{ count } file', '%{ count } files', statusData.value.totalFiles)
|
||||
info.push($gettext(msg, { count: statusData.value.totalFiles }))
|
||||
const msg = t('no files | %{ count } file | %{ count } files', statusData.value.totalFiles)
|
||||
info.push(t(msg, { count: statusData.value.totalFiles }))
|
||||
}
|
||||
|
||||
if (statusData.value.progress) {
|
||||
|
@ -56,25 +56,25 @@ const isLoading = ref(false)
|
|||
<h4 class="header">
|
||||
<translate
|
||||
v-if="step === 1"
|
||||
translate-context="Popup/Channels/Title/Verb"
|
||||
|
||||
>
|
||||
Publish audio
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="step === 2"
|
||||
translate-context="Popup/Channels/Title"
|
||||
|
||||
>
|
||||
Files to upload
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="step === 3"
|
||||
translate-context="Popup/Channels/Title"
|
||||
|
||||
>
|
||||
Upload details
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="step === 4"
|
||||
translate-context="Popup/Channels/Title"
|
||||
|
||||
>
|
||||
Processing uploads
|
||||
</translate>
|
||||
|
@ -95,7 +95,7 @@ const isLoading = ref(false)
|
|||
</template>
|
||||
<div class="ui very small hidden divider" />
|
||||
<template v-if="statusData && statusData.quotaStatus">
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
Remaining storage space:
|
||||
</translate>
|
||||
{{ humanSize((statusData.quotaStatus.remaining - statusData.uploadedSize) * 1000 * 1000) }}
|
||||
|
@ -106,7 +106,7 @@ const isLoading = ref(false)
|
|||
v-if="step === 1"
|
||||
class="ui basic cancel button"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -115,7 +115,7 @@ const isLoading = ref(false)
|
|||
class="ui basic button"
|
||||
@click.stop.prevent="uploadForm.step -= 1"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Previous step
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -124,7 +124,7 @@ const isLoading = ref(false)
|
|||
class="ui basic button"
|
||||
@click.stop.prevent="uploadForm.step -= 1"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Update
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -133,7 +133,7 @@ const isLoading = ref(false)
|
|||
class="ui primary button"
|
||||
@click.stop.prevent="uploadForm.step += 1"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label">
|
||||
<translate >
|
||||
Next step
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -147,7 +147,7 @@ const isLoading = ref(false)
|
|||
:disabled="!statusData?.canSubmit || undefined"
|
||||
@click.prevent.stop="uploadForm.publish"
|
||||
>
|
||||
<translate translate-context="*/Channels/Button.Label">
|
||||
<translate >
|
||||
Publish
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -164,7 +164,7 @@ const isLoading = ref(false)
|
|||
class="basic item"
|
||||
@click="update(false)"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Finish later
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -176,7 +176,7 @@ const isLoading = ref(false)
|
|||
class="ui basic cancel button"
|
||||
@click="update(false)"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Close
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { BackendError } from '~/types'
|
||||
|
||||
import { ref, computed, reactive, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
|
@ -41,7 +41,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
customObjects: () => []
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
|
||||
const currentActionName = ref(props.actions[0]?.name ?? null)
|
||||
const currentAction = computed(() => props.actions.find(action => action.name === currentActionName.value))
|
||||
|
@ -123,10 +123,10 @@ const toggleCheck = (event: MouseEvent, id: string, index: number) => {
|
|||
}
|
||||
|
||||
const labels = computed(() => ({
|
||||
refresh: $pgettext('Content/*/Button.Tooltip/Verb', 'Refresh table content'),
|
||||
selectAllItems: $pgettext('Content/*/Select/Verb', 'Select all items'),
|
||||
performAction: $pgettext('Content/*/Button.Label', 'Perform actions'),
|
||||
selectItem: $pgettext('Content/*/Select/Verb', 'Select')
|
||||
refresh: t('Refresh table content'),
|
||||
selectAllItems: t('Select all items'),
|
||||
performAction: t('Perform actions'),
|
||||
selectItem: t('Select')
|
||||
}))
|
||||
|
||||
const errors = ref([] as string[])
|
||||
|
@ -167,7 +167,7 @@ const launchAction = async () => {
|
|||
class="right floated"
|
||||
>
|
||||
<span v-if="needsRefresh">
|
||||
<translate translate-context="Content/*/Button.Help text.Paragraph">Content has been updated, click refresh to see up-to-date content</translate>
|
||||
<translate >Content has been updated, click refresh to see up-to-date content</translate>
|
||||
</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 translate-context="Content/*/*/Noun">Actions</translate></label>
|
||||
<label for="actions-select"><translate >Actions</translate></label>
|
||||
<select
|
||||
id="actions-select"
|
||||
v-model="currentActionName"
|
||||
|
@ -208,14 +208,14 @@ const launchAction = async () => {
|
|||
:aria-label="labels.performAction"
|
||||
@confirm="launchAction"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Go
|
||||
</translate>
|
||||
<template #modal-header>
|
||||
<p>
|
||||
<translate
|
||||
key="1"
|
||||
translate-context="Modal/*/Title"
|
||||
|
||||
:translate-n="affectedObjectsCount"
|
||||
:translate-params="{count: affectedObjectsCount, action: currentActionName}"
|
||||
translate-plural="Do you want to launch %{ action } on %{ count } elements?"
|
||||
|
@ -231,7 +231,7 @@ const launchAction = async () => {
|
|||
</template>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Modal/*/Paragraph"
|
||||
|
||||
>
|
||||
This may affect a lot of elements or have irreversible consequences, please double check this is really what you want.
|
||||
</translate>
|
||||
|
@ -239,7 +239,7 @@ const launchAction = async () => {
|
|||
</template>
|
||||
<template #modal-confirm>
|
||||
<div :aria-label="labels.performAction">
|
||||
<translate translate-context="Modal/*/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Launch
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -252,7 +252,7 @@ const launchAction = async () => {
|
|||
:class="['ui', {disabled: checked.length === 0}, {'loading': isLoading}, 'button']"
|
||||
@click="launchAction"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Go
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -260,7 +260,7 @@ const launchAction = async () => {
|
|||
<div class="count field">
|
||||
<translate
|
||||
v-if="selectAll"
|
||||
translate-context="Content/*/Paragraph"
|
||||
|
||||
tag="span"
|
||||
:translate-n="objectsData.count"
|
||||
:translate-params="{count: objectsData.count, total: objectsData.count}"
|
||||
|
@ -270,7 +270,7 @@ const launchAction = async () => {
|
|||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Content/*/Paragraph"
|
||||
|
||||
tag="span"
|
||||
:translate-n="checked.length"
|
||||
:translate-params="{count: checked.length, total: objectsData.count}"
|
||||
|
@ -286,7 +286,7 @@ const launchAction = async () => {
|
|||
>
|
||||
<translate
|
||||
key="3"
|
||||
translate-context="Content/*/Link/Verb"
|
||||
|
||||
:translate-n="objectsData.count"
|
||||
:translate-params="{total: objectsData.count}"
|
||||
translate-plural="Select all %{ total } elements"
|
||||
|
@ -301,7 +301,7 @@ const launchAction = async () => {
|
|||
>
|
||||
<translate
|
||||
key="4"
|
||||
translate-context="Content/*/Link/Verb"
|
||||
|
||||
>Select only current page</translate>
|
||||
</a>
|
||||
</template>
|
||||
|
@ -313,7 +313,7 @@ const launchAction = async () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message/Header">
|
||||
<translate >
|
||||
Error while applying action
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -332,7 +332,7 @@ const launchAction = async () => {
|
|||
>
|
||||
<p>
|
||||
<translate
|
||||
translate-context="Content/*/Paragraph"
|
||||
|
||||
:translate-n="result.updated"
|
||||
:translate-params="{count: result.updated, action: result.action}"
|
||||
translate-plural="Action %{ action } was launched successfully on %{ count } elements"
|
||||
|
|
|
@ -107,7 +107,7 @@ const getAttachmentUrl = (uuid: string) => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Your attachment cannot be saved
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -146,7 +146,7 @@ const getAttachmentUrl = (uuid: string) => {
|
|||
<div class="eleven wide column">
|
||||
<div class="file-input">
|
||||
<label :for="attachmentId">
|
||||
<translate translate-context="*/*/*">Upload New Picture…</translate>
|
||||
<translate >Upload New Picture…</translate>
|
||||
</label>
|
||||
<input
|
||||
:id="attachmentId"
|
||||
|
@ -161,7 +161,7 @@ const getAttachmentUrl = (uuid: string) => {
|
|||
</div>
|
||||
<div class="ui very small hidden divider" />
|
||||
<p>
|
||||
<translate translate-context="Content/*/Paragraph">
|
||||
<translate >
|
||||
PNG or JPG. Dimensions should be between 1400x1400px and 3000x3000px. Maximum file size allowed is 5MB.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -170,7 +170,7 @@ const getAttachmentUrl = (uuid: string) => {
|
|||
class="ui basic tiny button"
|
||||
@click.stop.prevent="remove(value as string)"
|
||||
>
|
||||
<translate translate-context="Content/Radio/Button.Label/Verb">
|
||||
<translate >
|
||||
Remove
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -179,7 +179,7 @@ const getAttachmentUrl = (uuid: string) => {
|
|||
class="ui active inverted dimmer"
|
||||
>
|
||||
<div class="ui indeterminate text loader">
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Uploading file…
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -22,13 +22,13 @@ const value = useVModel(props, 'modelValue', emit)
|
|||
>
|
||||
<translate
|
||||
v-if="value"
|
||||
translate-context="*/*/Button,Label"
|
||||
|
||||
>
|
||||
Expand
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/Button,Label"
|
||||
|
||||
>
|
||||
Collapse
|
||||
</translate>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import axios from 'axios'
|
||||
import { useVModel, watchDebounced, useTextareaAutosize, syncRef } from '@vueuse/core'
|
||||
import { ref, computed, watchEffect, onMounted, nextTick, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface Events {
|
||||
(e: 'update:modelValue', value: string): void
|
||||
|
@ -26,7 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
required: false
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const { textarea, input } = useTextareaAutosize()
|
||||
const value = useVModel(props, 'modelValue', emit)
|
||||
syncRef(value, input)
|
||||
|
@ -36,7 +36,7 @@ const preview = ref()
|
|||
const isLoadingPreview = ref(false)
|
||||
|
||||
const labels = computed(() => ({
|
||||
placeholder: props.placeholder ?? $pgettext('*/Form/Placeholder', 'Write a few words here…')
|
||||
placeholder: props.placeholder ?? t('Write a few words here…')
|
||||
}))
|
||||
|
||||
const remainingChars = computed(() => props.charLimit - props.modelValue.length)
|
||||
|
@ -86,7 +86,7 @@ onMounted(async () => {
|
|||
:class="[{active: !isPreviewing}, 'item']"
|
||||
@click.prevent="isPreviewing = false"
|
||||
>
|
||||
<translate translate-context="*/Form/Menu.item">
|
||||
<translate >
|
||||
Write
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -94,7 +94,7 @@ onMounted(async () => {
|
|||
:class="[{active: isPreviewing}, 'item']"
|
||||
@click.prevent="isPreviewing = true"
|
||||
>
|
||||
<translate translate-context="*/Form/Menu.item">
|
||||
<translate >
|
||||
Preview
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -112,7 +112,7 @@ onMounted(async () => {
|
|||
</div>
|
||||
</div>
|
||||
<p v-else-if="!preview">
|
||||
<translate translate-context="*/Form/Paragraph">
|
||||
<translate >
|
||||
Nothing to preview.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -141,7 +141,7 @@ onMounted(async () => {
|
|||
{{ remainingChars }}
|
||||
</span>
|
||||
<p>
|
||||
<translate translate-context="*/Form/Paragraph">
|
||||
<translate >
|
||||
Markdown syntax is supported.
|
||||
</translate>
|
||||
</p>
|
||||
|
|
|
@ -22,7 +22,7 @@ const { copy, isSupported: canCopy, copied } = useClipboard({ source: value, cop
|
|||
v-if="copied"
|
||||
class="message"
|
||||
>
|
||||
<translate translate-context="Content/*/Paragraph">
|
||||
<translate >
|
||||
Text copied to clipboard!
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -39,7 +39,7 @@ const { copy, isSupported: canCopy, copied } = useClipboard({ source: value, cop
|
|||
@click="copy()"
|
||||
>
|
||||
<i class="copy icon" />
|
||||
<translate translate-context="*/*/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Copy
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -42,7 +42,7 @@ const confirm = () => {
|
|||
>
|
||||
<h4 class="header">
|
||||
<slot name="modal-header">
|
||||
<translate translate-context="Modal/*/Title">
|
||||
<translate >
|
||||
Do you want to confirm this action?
|
||||
</translate>
|
||||
</slot>
|
||||
|
@ -54,7 +54,7 @@ const confirm = () => {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic cancel button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -63,7 +63,7 @@ const confirm = () => {
|
|||
@click="confirm"
|
||||
>
|
||||
<slot name="modal-confirm">
|
||||
<translate translate-context="Modal/*/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Confirm
|
||||
</translate>
|
||||
</slot>
|
||||
|
|
|
@ -20,12 +20,12 @@ const duration = computed(() => {
|
|||
<span>
|
||||
<translate
|
||||
v-if="duration.hours > 0"
|
||||
translate-context="Content/*/Paragraph"
|
||||
|
||||
:translate-params="duration"
|
||||
>%{ hours } h %{ minutes } min</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Content/*/Paragraph"
|
||||
|
||||
:translate-params="duration"
|
||||
>%{ minutes } min</translate>
|
||||
</span>
|
||||
|
|
|
@ -19,7 +19,7 @@ withDefaults(defineProps<Props>(), {
|
|||
<div class="content">
|
||||
<slot name="title">
|
||||
<i class="search icon" />
|
||||
<translate translate-context="Content/*/Paragraph">
|
||||
<translate >
|
||||
No results were found.
|
||||
</translate>
|
||||
</slot>
|
||||
|
@ -32,7 +32,7 @@ withDefaults(defineProps<Props>(), {
|
|||
class="ui button"
|
||||
@click="emit('refresh')"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Refresh
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -28,11 +28,11 @@ const truncated = computed(() => props.content.slice(0, props.length))
|
|||
<br>
|
||||
<translate
|
||||
v-if="expanded"
|
||||
translate-context="*/*/Button,Label"
|
||||
|
||||
>Show less</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/Button,Label"
|
||||
|
||||
>Show more</translate>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface Events {
|
||||
(e: 'update:modelValue', value: string): void
|
||||
|
@ -20,10 +20,10 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
|
||||
const value = useVModel(props, 'modelValue', emit)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'),
|
||||
clear: $pgettext('Content/Library/Button.Label', 'Clear')
|
||||
searchPlaceholder: t('Search…'),
|
||||
clear: t('Clear')
|
||||
}))
|
||||
|
||||
const search = () => {
|
||||
|
@ -42,7 +42,7 @@ const search = () => {
|
|||
for="search-query"
|
||||
class="hidden"
|
||||
>
|
||||
<translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
|
||||
<translate >Search</translate>
|
||||
</label>
|
||||
<input
|
||||
id="search-query"
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { Cover } from '~/types'
|
|||
|
||||
import SemanticModal from '~/components/semantic/Modal.vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface Props {
|
||||
nextRoute: RouteLocationRaw
|
||||
|
@ -16,12 +16,12 @@ defineProps<Props>()
|
|||
|
||||
const show = ref(false)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
header: $pgettext('Popup/Title/Noun', 'Unauthenticated'),
|
||||
login: $pgettext('*/*/Button.Label/Verb', 'Log in'),
|
||||
signup: $pgettext('*/*/Button.Label/Verb', 'Sign up'),
|
||||
description: $pgettext('Popup/*/Paragraph', "You don't have access!")
|
||||
header: t('Unauthenticated'),
|
||||
login: t('Log in'),
|
||||
signup: t('Sign up'),
|
||||
description: t("You don't have access!")
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
|
|
@ -91,19 +91,19 @@ const submit = async () => {
|
|||
href=""
|
||||
@click.stop.prevent="showMore = true"
|
||||
>
|
||||
<translate translate-context="*/*/Button,Label">Show more</translate>
|
||||
<translate >Show more</translate>
|
||||
</a>
|
||||
<a
|
||||
v-else
|
||||
href=""
|
||||
@click.stop.prevent="showMore = false"
|
||||
>
|
||||
<translate translate-context="*/*/Button,Label">Show less</translate>
|
||||
<translate >Show less</translate>
|
||||
</a>
|
||||
</template>
|
||||
</template>
|
||||
<p v-else-if="!isUpdating">
|
||||
<translate translate-context="*/*/Placeholder">
|
||||
<translate >
|
||||
No description available
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -114,7 +114,7 @@ const submit = async () => {
|
|||
@click="isUpdating = true"
|
||||
>
|
||||
<i class="pencil icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
||||
<translate >Edit</translate>
|
||||
</span>
|
||||
</template>
|
||||
<form
|
||||
|
@ -128,7 +128,7 @@ const submit = async () => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Channels/Error message.Title">
|
||||
<translate >
|
||||
Error while updating description
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -149,14 +149,14 @@ const submit = async () => {
|
|||
class="left floated"
|
||||
@click.prevent="isUpdating = false"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
|
||||
<translate >Cancel</translate>
|
||||
</a>
|
||||
<button
|
||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
|
||||
type="submit"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
<translate translate-context="Content/Channels/Button.Label/Verb">
|
||||
<translate >
|
||||
Update description
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { SUPPORTED_LOCALES } from '~/init/locale'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import useThemeList from '~/composables/useThemeList'
|
||||
import useTheme from '~/composables/useTheme'
|
||||
import { computed } from 'vue'
|
||||
|
||||
interface Events {
|
||||
(e: 'show:shortcuts-modal'): void
|
||||
|
@ -11,26 +12,26 @@ interface Events {
|
|||
|
||||
const emit = defineEmits<Events>()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const themes = useThemeList()
|
||||
const { theme } = useTheme()
|
||||
|
||||
const labels = computed(() => ({
|
||||
profile: $pgettext('*/*/*/Noun', 'Profile'),
|
||||
settings: $pgettext('*/*/*/Noun', 'Settings'),
|
||||
logout: $pgettext('Sidebar/Login/List item.Link/Verb', 'Log out'),
|
||||
about: $pgettext('Sidebar/About/List item.Link', 'About'),
|
||||
shortcuts: $pgettext('*/*/*/Noun', 'Keyboard shortcuts'),
|
||||
support: $pgettext('Sidebar/*/Listitem.Link', 'Help'),
|
||||
forum: $pgettext('Sidebar/*/Listitem.Link', 'Forum'),
|
||||
docs: $pgettext('Sidebar/*/Listitem.Link', 'Documentation'),
|
||||
language: $pgettext('Footer/Settings/Dropdown.Label/Short, Verb', 'Change language'),
|
||||
theme: $pgettext('Footer/Settings/Dropdown.Label/Short, Verb', 'Change theme'),
|
||||
chat: $pgettext('Sidebar/*/Listitem.Link', 'Chat room'),
|
||||
git: $pgettext('Footer/*/List item.Link', 'Issue tracker'),
|
||||
login: $pgettext('*/*/Button.Label/Verb', 'Log in'),
|
||||
signup: $pgettext('*/*/Button.Label/Verb', 'Sign up'),
|
||||
notifications: $pgettext('*/Notifications/*', 'Notifications')
|
||||
profile: t('Profile'),
|
||||
settings: t('Settings'),
|
||||
logout: t('Log out'),
|
||||
about: t('About'),
|
||||
shortcuts: t('Keyboard shortcuts'),
|
||||
support: t('Help'),
|
||||
forum: t('Forum'),
|
||||
docs: t('Documentation'),
|
||||
language: t('Change language'),
|
||||
theme: t('Change theme'),
|
||||
chat: t('Chat room'),
|
||||
git: t('Issue tracker'),
|
||||
login: t('Log in'),
|
||||
signup: t('Sign up'),
|
||||
notifications: t('Notifications')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -45,11 +46,11 @@ const labels = computed(() => ({
|
|||
class="menu"
|
||||
>
|
||||
<a
|
||||
v-for="(language, key) in $language.available"
|
||||
v-for="(language, key) in SUPPORTED_LOCALES"
|
||||
:key="key"
|
||||
:class="[{'active': $language.current === key},'item']"
|
||||
:class="[{'active': $i18n.locale === key},'item']"
|
||||
:value="key"
|
||||
@click="$store.dispatch('ui/currentLanguage', key)"
|
||||
@click="$i18n.locale = key"
|
||||
>{{ language }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@ import useThemeList from '~/composables/useThemeList'
|
|||
import useTheme from '~/composables/useTheme'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface Events {
|
||||
(e: 'update:show', value: boolean): void
|
||||
|
@ -24,26 +24,26 @@ const show = useVModel(props, 'show', emit)
|
|||
const { theme } = useTheme()
|
||||
const themes = useThemeList()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
header: $pgettext('Popup/Title/Noun', 'Options'),
|
||||
profile: $pgettext('*/*/*/Noun', 'Profile'),
|
||||
settings: $pgettext('*/*/*/Noun', 'Settings'),
|
||||
logout: $pgettext('Sidebar/Login/List item.Link/Verb', 'Log out'),
|
||||
about: $pgettext('Sidebar/About/List item.Link', 'About'),
|
||||
shortcuts: $pgettext('*/*/*/Noun', 'Keyboard shortcuts'),
|
||||
support: $pgettext('Sidebar/*/Listitem.Link', 'Help'),
|
||||
forum: $pgettext('Sidebar/*/Listitem.Link', 'Forum'),
|
||||
docs: $pgettext('Sidebar/*/Listitem.Link', 'Documentation'),
|
||||
help: $pgettext('Sidebar/*/Listitem.Link', 'Help'),
|
||||
language: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Language'),
|
||||
theme: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Theme'),
|
||||
chat: $pgettext('Sidebar/*/Listitem.Link', 'Chat room'),
|
||||
git: $pgettext('Sidebar/*/List item.Link', 'Issue tracker'),
|
||||
login: $pgettext('*/*/Button.Label/Verb', 'Log in'),
|
||||
signup: $pgettext('*/*/Button.Label/Verb', 'Sign up'),
|
||||
notifications: $pgettext('*/Notifications/*', 'Notifications'),
|
||||
useOtherInstance: $pgettext('Sidebar/*/List item.Link', 'Use another instance')
|
||||
header: t('Options'),
|
||||
profile: t('Profile'),
|
||||
settings: t('Settings'),
|
||||
logout: t('Log out'),
|
||||
about: t('About'),
|
||||
shortcuts: t('Keyboard shortcuts'),
|
||||
support: t('Help'),
|
||||
forum: t('Forum'),
|
||||
docs: t('Documentation'),
|
||||
help: t('Help'),
|
||||
language: t('Language'),
|
||||
theme: t('Theme'),
|
||||
chat: t('Chat room'),
|
||||
git: t('Issue tracker'),
|
||||
login: t('Log in'),
|
||||
signup: t('Sign up'),
|
||||
notifications: t('Notifications'),
|
||||
useOtherInstance: t('Use another instance')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { OrderingField } from '~/store/ui'
|
|||
import type { Track } from '~/types'
|
||||
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { sortedUniq } from 'lodash-es'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
|
@ -96,9 +96,9 @@ onOrderingUpdate(() => {
|
|||
|
||||
onMounted(() => $('.ui.dropdown').dropdown())
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/Favorites/Title', 'Your Favorites')
|
||||
title: t('Your Favorites')
|
||||
}))
|
||||
|
||||
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b)))
|
||||
|
@ -112,7 +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 translate-context="Content/Favorites/Message">
|
||||
<translate >
|
||||
Loading your favorites…
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -126,7 +126,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
translate-plural="%{ count } favorites"
|
||||
:translate-n="$store.state.favorites.count"
|
||||
:translate-params="{ count }"
|
||||
translate-context="Content/Favorites/Title"
|
||||
|
||||
>
|
||||
%{ count } favorite
|
||||
</translate>
|
||||
|
@ -144,7 +144,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
<div class="fields">
|
||||
<div class="field">
|
||||
<label for="favorites-ordering">
|
||||
<translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate>
|
||||
<translate >Ordering</translate>
|
||||
</label>
|
||||
<select
|
||||
id="favorites-ordering"
|
||||
|
@ -162,7 +162,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
</div>
|
||||
<div class="field">
|
||||
<label for="favorites-ordering-direction">
|
||||
<translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate>
|
||||
<translate >Order</translate>
|
||||
</label>
|
||||
<select
|
||||
id="favorites-ordering-direction"
|
||||
|
@ -170,12 +170,12 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Ascending
|
||||
</translate>
|
||||
</option>
|
||||
<option value="-">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Descending
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -183,7 +183,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
</div>
|
||||
<div class="field">
|
||||
<label for="favorites-results">
|
||||
<translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate>
|
||||
<translate >Results per page</translate>
|
||||
</label>
|
||||
<select
|
||||
id="favorites-results"
|
||||
|
@ -223,7 +223,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
<div class="ui icon header">
|
||||
<i class="broken heart icon" />
|
||||
<translate
|
||||
translate-context="Content/Home/Placeholder"
|
||||
|
||||
>
|
||||
No tracks have been added to your favorites yet
|
||||
</translate>
|
||||
|
@ -233,7 +233,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
class="ui success labeled icon button"
|
||||
>
|
||||
<i class="headphones icon" />
|
||||
<translate translate-context="Content/*/Verb">
|
||||
<translate >
|
||||
Browse the library
|
||||
</translate>
|
||||
</router-link>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { QueueTrack } from '~/composables/audio/queue'
|
||||
import type { Track } from '~/types'
|
||||
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
import { computed } from 'vue'
|
||||
|
||||
|
@ -18,13 +18,13 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
border: false
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id))
|
||||
const title = computed(() => isFavorite.value
|
||||
? $pgettext('Content/Track/Icon.Tooltip/Verb', 'Remove from favorites')
|
||||
: $pgettext('Content/Track/*/Verb', 'Add to favorites')
|
||||
? t('Remove from favorites')
|
||||
: t('Add to favorites')
|
||||
)
|
||||
</script>
|
||||
|
||||
|
@ -37,13 +37,13 @@ const title = computed(() => isFavorite.value
|
|||
<i class="heart icon" />
|
||||
<translate
|
||||
v-if="isFavorite"
|
||||
translate-context="Content/Track/Button.Message"
|
||||
|
||||
>
|
||||
In favorites
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Content/Track/*/Verb"
|
||||
|
||||
>
|
||||
Add to favorites
|
||||
</translate>
|
||||
|
|
|
@ -80,7 +80,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
class="small"
|
||||
>
|
||||
<h3 class="header">
|
||||
<translate translate-context="Popup/*/Title">
|
||||
<translate >
|
||||
Refreshing object from remote server…
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -91,12 +91,12 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
class="ui message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/*/Message.Title">
|
||||
<translate >
|
||||
Refresh was skipped
|
||||
</translate>
|
||||
</h4>
|
||||
<p>
|
||||
<translate translate-context="Popup/*/Message.Content">
|
||||
<translate >
|
||||
The remote server answered, but returned data was unsupported by Funkwhale.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -106,12 +106,12 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
class="ui success message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/*/Message.Title">
|
||||
<translate >
|
||||
Refresh successful
|
||||
</translate>
|
||||
</h4>
|
||||
<p>
|
||||
<translate translate-context="Popup/*/Message.Content">
|
||||
<translate >
|
||||
Data was refreshed successfully from remote server.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -121,12 +121,12 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
class="ui error message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/*/Message.Title">
|
||||
<translate >
|
||||
Refresh error
|
||||
</translate>
|
||||
</h4>
|
||||
<p>
|
||||
<translate translate-context="Popup/*/Message.Content">
|
||||
<translate >
|
||||
An error occurred while trying to refresh data:
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -134,7 +134,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Popup/Import/Table.Label/Noun">
|
||||
<translate >
|
||||
Error type
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -144,7 +144,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Popup/Import/Table.Label/Noun">
|
||||
<translate >
|
||||
Error detail
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -152,49 +152,49 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
<translate
|
||||
v-if="data.detail.error_code === 'http' && data.detail.status_code"
|
||||
:translate-params="{status: data.detail.status_code}"
|
||||
translate-context="*/*/Error"
|
||||
|
||||
>
|
||||
The remote server answered with HTTP %{ status }
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="['http', 'request'].indexOf(data.detail.error_code) > -1"
|
||||
translate-context="*/*/Error"
|
||||
|
||||
>
|
||||
An HTTP error occurred while contacting the remote server
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="data.detail.error_code === 'timeout'"
|
||||
translate-context="*/*/Error"
|
||||
|
||||
>
|
||||
The remote server didn't respond quickly enough
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="data.detail.error_code === 'connection'"
|
||||
translate-context="*/*/Error"
|
||||
|
||||
>
|
||||
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"
|
||||
translate-context="*/*/Error"
|
||||
|
||||
>
|
||||
The remote server returned invalid JSON or JSON-LD data
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="data.detail.error_code === 'validation'"
|
||||
translate-context="*/*/Error"
|
||||
|
||||
>
|
||||
Data returned by the remote server had invalid or missing attributes
|
||||
</translate>
|
||||
<translate
|
||||
v-else-if="data.detail.error_code === 'unhandled'"
|
||||
translate-context="*/*/Error"
|
||||
|
||||
>
|
||||
Unknown error
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/Error"
|
||||
|
||||
>
|
||||
Unknown error
|
||||
</translate>
|
||||
|
@ -209,7 +209,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
class="ui active inverted dimmer"
|
||||
>
|
||||
<div class="ui text loader">
|
||||
<translate translate-context="Popup/*/Loading.Title">
|
||||
<translate >
|
||||
Requesting a fetch…
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -219,7 +219,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
class="ui active inverted dimmer"
|
||||
>
|
||||
<div class="ui text loader">
|
||||
<translate translate-context="Popup/*/Loading.Title">
|
||||
<translate >
|
||||
Waiting for result…
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -230,7 +230,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Error while saving settings
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -249,12 +249,12 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
class="ui warning message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/*/Message.Title">
|
||||
<translate >
|
||||
Refresh pending
|
||||
</translate>
|
||||
</h4>
|
||||
<p>
|
||||
<translate translate-context="Popup/*/Message.Content">
|
||||
<translate >
|
||||
The refresh request hasn't been processed in time by our server. It will be processed later.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -262,7 +262,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic cancel button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Close
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -271,7 +271,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
|
|||
class="ui confirm success button"
|
||||
@click.prevent="showModal = false; emit('refresh')"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Close and reload page
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -64,7 +64,7 @@ fetchData()
|
|||
v-if="!isLoading && libraries.length === 0"
|
||||
class="ui subtitle"
|
||||
>
|
||||
<translate translate-context="Content/Federation/Paragraph">
|
||||
<translate >
|
||||
No matching library.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -92,7 +92,7 @@ fetchData()
|
|||
:class="['ui', 'basic', 'button']"
|
||||
@click="fetchData(nextPage)"
|
||||
>
|
||||
<translate translate-context="*/*/Button,Label">
|
||||
<translate >
|
||||
Show more
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useClipboard, useVModel } from '@vueuse/core'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
|
@ -25,10 +25,10 @@ const value = useVModel(props, 'modelValue', emit)
|
|||
|
||||
const showPassword = ref(props.defaultShow)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Content/Settings/Button.Tooltip/Verb', 'Show/hide password'),
|
||||
copy: $pgettext('*/*/Button.Label/Short, Verb', 'Copy')
|
||||
title: t('Show/hide password'),
|
||||
copy: t('Copy')
|
||||
}))
|
||||
|
||||
const passwordInputType = computed(() => showPassword.value ? 'text' : 'password')
|
||||
|
@ -38,7 +38,7 @@ const { isSupported: canCopy, copy } = useClipboard({ source: value })
|
|||
const copyPassword = () => {
|
||||
copy()
|
||||
store.commit('ui/addMessage', {
|
||||
content: $pgettext('Content/*/Paragraph', 'Text copied to clipboard!'),
|
||||
content: t('Text copied to clipboard!'),
|
||||
date: new Date()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import type { Track, Album, Artist, Library } from '~/types'
|
||||
|
||||
import { momentFormat } from '~/utils/filters'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { sum } from 'lodash-es'
|
||||
|
||||
|
@ -39,9 +39,9 @@ const isSerie = computed(() => object.value?.artist.content_category === 'podcas
|
|||
const totalDuration = computed(() => sum((object.value?.tracks ?? []).map(track => track.uploads[0]?.duration ?? 0)))
|
||||
const publicLibraries = computed(() => libraries.value?.filter(library => library.privacy_level === 'everyone') ?? [])
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('*/*/*', 'Album')
|
||||
title: t('Album')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
@ -179,7 +179,7 @@ const remove = async () => {
|
|||
<div class="ui hidden very small divider" />
|
||||
<translate
|
||||
v-if="isSerie"
|
||||
translate-context="Content/Channel/Paragraph"
|
||||
|
||||
translate-plural="%{ count } episodes"
|
||||
:translate-n="totalTracks"
|
||||
:translate-params="{count: totalTracks}"
|
||||
|
@ -188,7 +188,7 @@ const remove = async () => {
|
|||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
:translate-params="{count: totalTracks}"
|
||||
:translate-n="totalTracks"
|
||||
translate-plural="%{ count } tracks"
|
||||
|
@ -267,7 +267,7 @@ const remove = async () => {
|
|||
<template v-if="totalTracks > 0">
|
||||
<translate
|
||||
v-if="isSerie"
|
||||
translate-context="Content/Channel/Paragraph"
|
||||
|
||||
translate-plural="%{ count } episodes"
|
||||
:translate-n="totalTracks"
|
||||
:translate-params="{count: totalTracks}"
|
||||
|
@ -276,7 +276,7 @@ const remove = async () => {
|
|||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
:translate-params="{count: totalTracks}"
|
||||
:translate-n="totalTracks"
|
||||
translate-plural="%{ count } tracks"
|
||||
|
@ -323,7 +323,7 @@ const remove = async () => {
|
|||
:to="{name: 'library.albums.edit', params: {id: object.id }}"
|
||||
>
|
||||
<i class="pencil icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Add a description…
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -341,7 +341,7 @@ const remove = async () => {
|
|||
:to="{name: 'library.albums.edit', params: {id: object.id }}"
|
||||
>
|
||||
<i class="pencil icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Add a description…
|
||||
</translate>
|
||||
</router-link>
|
||||
|
|
|
@ -62,13 +62,13 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
|
|||
<h2 class="ui header">
|
||||
<translate
|
||||
v-if="isSerie"
|
||||
translate-context="Content/Channels/*"
|
||||
|
||||
>
|
||||
Episodes
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
>
|
||||
Tracks
|
||||
</translate>
|
||||
|
@ -96,7 +96,6 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
|
|||
<translate
|
||||
tag="h3"
|
||||
:translate-params="{number: tracks[0]?.disc_number ?? index + 1}"
|
||||
translate-context="Content/Album/"
|
||||
>
|
||||
Volume %{ number }
|
||||
</translate>
|
||||
|
@ -138,7 +137,7 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
|
|||
|
||||
<template v-if="!artist.channel && !isSerie">
|
||||
<h2>
|
||||
<translate translate-context="Content/*/Title/Noun">
|
||||
<translate >
|
||||
User libraries
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -146,7 +145,7 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
|
|||
:url="'albums/' + object.id + '/libraries/'"
|
||||
@loaded="emit('libraries-loaded', $event)"
|
||||
>
|
||||
<translate translate-context="Content/Album/Paragraph">
|
||||
<translate >
|
||||
This album is present in the following libraries:
|
||||
</translate>
|
||||
</library-widget>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { Album, Artist, Library } from '~/types'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { getDomain } from '~/utils'
|
||||
|
||||
|
@ -33,9 +33,9 @@ const showEmbedModal = ref(false)
|
|||
|
||||
const domain = computed(() => getDomain(props.object.fid))
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
more: $pgettext('*/*/Button.Label/Noun', 'More…')
|
||||
more: t('More…')
|
||||
}))
|
||||
|
||||
const isEmbedable = computed(() => (props.isChannel && props.artist?.channel?.actor) || props.publicLibraries.length)
|
||||
|
@ -53,7 +53,7 @@ const remove = () => emit('remove')
|
|||
v-model:show="showEmbedModal"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/Album/Title/Verb">Embed this album on your website</translate>
|
||||
<translate >Embed this album on your website</translate>
|
||||
</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 translate-context="*/*/Button.Label/Verb">Cancel</translate>
|
||||
<translate >Cancel</translate>
|
||||
</button>
|
||||
</div>
|
||||
</semantic-modal>
|
||||
|
@ -86,7 +86,7 @@ const remove = () => emit('remove')
|
|||
<i class="external icon" />
|
||||
<translate
|
||||
:translate-params="{domain: domain}"
|
||||
translate-context="Content/*/Button.Label/Verb"
|
||||
|
||||
>View on %{ domain }</translate>
|
||||
</a>
|
||||
|
||||
|
@ -97,7 +97,7 @@ const remove = () => emit('remove')
|
|||
@click="showEmbedModal = !showEmbedModal"
|
||||
>
|
||||
<i class="code icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">Embed</translate>
|
||||
<translate >Embed</translate>
|
||||
</div>
|
||||
<a
|
||||
v-if="isAlbum && musicbrainzUrl"
|
||||
|
@ -107,7 +107,7 @@ const remove = () => emit('remove')
|
|||
class="basic item"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
|
||||
<translate >View on MusicBrainz</translate>
|
||||
</a>
|
||||
<a
|
||||
v-if="!isChannel && isAlbum"
|
||||
|
@ -117,7 +117,7 @@ const remove = () => emit('remove')
|
|||
class="basic item"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate>
|
||||
<translate >Search on Discogs</translate>
|
||||
</a>
|
||||
<router-link
|
||||
v-if="object.is_local"
|
||||
|
@ -125,7 +125,7 @@ const remove = () => emit('remove')
|
|||
class="basic item"
|
||||
>
|
||||
<i class="edit icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
||||
<translate >Edit</translate>
|
||||
</router-link>
|
||||
<dangerous-button
|
||||
v-if="artist && $store.state.auth.authenticated && artist.channel && artist.attributed_to.full_username === $store.state.auth.fullUsername"
|
||||
|
@ -133,16 +133,16 @@ const remove = () => emit('remove')
|
|||
@confirm="remove()"
|
||||
>
|
||||
<i class="ui trash icon" />
|
||||
<translate translate-context="*/*/*/Verb">Delete…</translate>
|
||||
<translate >Delete…</translate>
|
||||
<template #modal-header>
|
||||
<p>
|
||||
<translate translate-context="Popup/Channel/Title">Delete this album?</translate>
|
||||
<translate >Delete this album?</translate>
|
||||
</p>
|
||||
</template>
|
||||
<template #modal-content>
|
||||
<div>
|
||||
<p>
|
||||
<translate translate-context="Content/Moderation/Paragraph">
|
||||
<translate >
|
||||
The album will be deleted, as well as any related files and data. This action is irreversible.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -150,7 +150,7 @@ const remove = () => emit('remove')
|
|||
</template>
|
||||
<template #modal-confirm>
|
||||
<p>
|
||||
<translate translate-context="*/*/*/Verb">Delete</translate>
|
||||
<translate >Delete</translate>
|
||||
</p>
|
||||
</template>
|
||||
</dangerous-button>
|
||||
|
@ -171,7 +171,7 @@ const remove = () => emit('remove')
|
|||
:to="{name: 'manage.library.albums.detail', params: {id: object.id}}"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
|
||||
<translate >Open in moderation interface</translate>
|
||||
</router-link>
|
||||
<a
|
||||
v-if="$store.state.auth.profile && $store.state.auth.profile?.is_superuser"
|
||||
|
@ -181,7 +181,7 @@ const remove = () => emit('remove')
|
|||
rel="noopener noreferrer"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>
|
||||
<translate >View in Django's admin</translate>
|
||||
</a>
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
@ -24,13 +24,13 @@ const canEdit = store.state.auth.availablePermissions.library
|
|||
<h2>
|
||||
<translate
|
||||
v-if="canEdit"
|
||||
translate-context="Content/*/Title"
|
||||
|
||||
>
|
||||
Edit this album
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Content/*/Title"
|
||||
|
||||
>
|
||||
Suggest an edit on this album
|
||||
</translate>
|
||||
|
@ -39,7 +39,7 @@ const canEdit = store.state.auth.availablePermissions.library
|
|||
v-if="!object.is_local"
|
||||
class="ui message"
|
||||
>
|
||||
<translate translate-context="Content/*/Message">
|
||||
<translate >
|
||||
This object is managed by another server, you cannot edit it.
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@ import type { OrderingField } from '~/store/ui'
|
|||
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRouteQuery } from '@vueuse/router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { syncRef } from '@vueuse/core'
|
||||
import { sortedUniq } from 'lodash-es'
|
||||
import { useStore } from '~/store'
|
||||
|
@ -108,10 +108,10 @@ onOrderingUpdate(() => {
|
|||
|
||||
onMounted(() => $('.ui.dropdown').dropdown())
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Enter album title…'),
|
||||
title: $pgettext('*/*/*', 'Albums')
|
||||
searchPlaceholder: t('Enter album title…'),
|
||||
title: t('Albums')
|
||||
}))
|
||||
|
||||
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b)))
|
||||
|
@ -121,7 +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 translate-context="Content/Album/Title">
|
||||
<translate >
|
||||
Browsing albums
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -132,7 +132,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
<div class="fields">
|
||||
<div class="field">
|
||||
<label for="albums-search">
|
||||
<translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
|
||||
<translate >Search</translate>
|
||||
</label>
|
||||
<div class="ui action input">
|
||||
<input
|
||||
|
@ -145,18 +145,18 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
<button
|
||||
class="ui icon button"
|
||||
type="submit"
|
||||
:aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')"
|
||||
:aria-label="t('Search')"
|
||||
>
|
||||
<i class="search icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="tags-search"><translate translate-context="*/*/*/Noun">Tags</translate></label>
|
||||
<label for="tags-search"><translate >Tags</translate></label>
|
||||
<tags-selector v-model="tags" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="album-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||
<label for="album-ordering"><translate >Ordering</translate></label>
|
||||
<select
|
||||
id="album-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -172,26 +172,26 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="album-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
|
||||
<label for="album-ordering-direction"><translate >Ordering direction</translate></label>
|
||||
<select
|
||||
id="album-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Ascending
|
||||
</translate>
|
||||
</option>
|
||||
<option value="-">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Descending
|
||||
</translate>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="album-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
|
||||
<label for="album-results"><translate >Results per page</translate></label>
|
||||
<select
|
||||
id="album-results"
|
||||
v-model="paginateBy"
|
||||
|
@ -234,7 +234,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
>
|
||||
<div class="ui icon header">
|
||||
<i class="compact disc icon" />
|
||||
<translate translate-context="Content/Albums/Placeholder">
|
||||
<translate >
|
||||
No results matching your query
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -244,7 +244,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
class="ui success button labeled icon"
|
||||
>
|
||||
<i class="upload icon" />
|
||||
<translate translate-context="Content/*/Verb">
|
||||
<translate >
|
||||
Add some music
|
||||
</translate>
|
||||
</router-link>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { Track, Album, Artist, Library } from '~/types'
|
||||
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getDomain } from '~/utils'
|
||||
import { useStore } from '~/store'
|
||||
|
@ -57,9 +57,9 @@ const headerStyle = computed(() => cover.value?.urls.original
|
|||
: ''
|
||||
)
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('*/*/*', 'Artist')
|
||||
title: t('Artist')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
@ -118,7 +118,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
class="sub header"
|
||||
>
|
||||
<translate
|
||||
translate-context="Content/Artist/Paragraph"
|
||||
|
||||
tag="div"
|
||||
translate-plural="%{ count } tracks in %{ albumsCount } albums"
|
||||
:translate-n="totalTracks"
|
||||
|
@ -147,7 +147,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
class="vibrant"
|
||||
:artist="object"
|
||||
>
|
||||
<translate translate-context="Content/Artist/Button.Label/Verb">
|
||||
<translate >
|
||||
Play all albums
|
||||
</translate>
|
||||
</play-button>
|
||||
|
@ -158,7 +158,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
v-model:show="showEmbedModal"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/Artist/Title/Verb">
|
||||
<translate >
|
||||
Embed this artist work on your website
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -172,7 +172,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui deny button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -183,7 +183,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
class="ui button"
|
||||
@click="dropdown.click()"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Noun">
|
||||
<translate >
|
||||
More…
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -203,7 +203,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
<i class="external icon" />
|
||||
<translate
|
||||
:translate-params="{domain: domain}"
|
||||
translate-context="Content/*/Button.Label/Verb"
|
||||
|
||||
>View on %{ domain }</translate>
|
||||
</a>
|
||||
|
||||
|
@ -214,7 +214,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
@click.prevent="showEmbedModal = !showEmbedModal"
|
||||
>
|
||||
<i class="code icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Embed
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -225,7 +225,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
class="basic item"
|
||||
>
|
||||
<i class="wikipedia w icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">Search on Wikipedia</translate>
|
||||
<translate >Search on Wikipedia</translate>
|
||||
</a>
|
||||
<a
|
||||
v-if="musicbrainzUrl"
|
||||
|
@ -235,7 +235,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
class="basic item"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
|
||||
<translate >View on MusicBrainz</translate>
|
||||
</a>
|
||||
<a
|
||||
:href="discogsUrl"
|
||||
|
@ -244,7 +244,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
class="basic item"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate>
|
||||
<translate >Search on Discogs</translate>
|
||||
</a>
|
||||
<router-link
|
||||
v-if="object.is_local"
|
||||
|
@ -252,7 +252,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
class="basic item"
|
||||
>
|
||||
<i class="edit icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Edit
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -274,7 +274,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
:to="{name: 'manage.library.artists.detail', params: {id: object.id}}"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
<translate translate-context="Content/Moderation/Link">
|
||||
<translate >
|
||||
Open in moderation interface
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -286,7 +286,7 @@ watch(() => props.id, fetchData, { immediate: true })
|
|||
rel="noopener noreferrer"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>
|
||||
<translate >View in Django's admin</translate>
|
||||
</a>
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
@ -65,7 +65,7 @@ const loadMoreAlbums = async () => {
|
|||
<div class="ui hidden divider" />
|
||||
<div class="ui message">
|
||||
<p>
|
||||
<translate translate-context="Content/Artist/Paragraph">
|
||||
<translate >
|
||||
You are currently hiding content related to this artist.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -73,7 +73,7 @@ const loadMoreAlbums = async () => {
|
|||
class="right floated"
|
||||
:to="{name: 'settings'}"
|
||||
>
|
||||
<translate translate-context="Content/Moderation/Link">
|
||||
<translate >
|
||||
Review my filters
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -81,7 +81,7 @@ const loadMoreAlbums = async () => {
|
|||
class="ui basic tiny button"
|
||||
@click="$store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)"
|
||||
>
|
||||
<translate translate-context="Content/Moderation/Button.Label">
|
||||
<translate >
|
||||
Remove filter
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -98,7 +98,7 @@ const loadMoreAlbums = async () => {
|
|||
class="ui vertical stripe segment"
|
||||
>
|
||||
<h2>
|
||||
<translate translate-context="Content/Artist/Title">
|
||||
<translate >
|
||||
Albums by this artist
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -115,7 +115,7 @@ const loadMoreAlbums = async () => {
|
|||
:class="['ui', {loading: isLoadingMoreAlbums}, 'button']"
|
||||
@click="loadMoreAlbums()"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label">
|
||||
<translate >
|
||||
Load more…
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -132,7 +132,7 @@ const loadMoreAlbums = async () => {
|
|||
>
|
||||
<template #header>
|
||||
<h2>
|
||||
<translate translate-context="Content/Artist/Title">
|
||||
<translate >
|
||||
New tracks by this artist
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -142,7 +142,7 @@ const loadMoreAlbums = async () => {
|
|||
</section>
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2>
|
||||
<translate translate-context="Content/*/Title/Noun">
|
||||
<translate >
|
||||
User libraries
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -150,7 +150,7 @@ const loadMoreAlbums = async () => {
|
|||
:url="'artists/' + object.id + '/libraries/'"
|
||||
@loaded="emit('libraries-loaded', $event)"
|
||||
>
|
||||
<translate translate-context="Content/Artist/Paragraph">
|
||||
<translate >
|
||||
This artist is present in the following libraries:
|
||||
</translate>
|
||||
</library-widget>
|
||||
|
|
|
@ -24,13 +24,13 @@ const canEdit = store.state.auth.availablePermissions.library
|
|||
<h2>
|
||||
<translate
|
||||
v-if="canEdit"
|
||||
translate-context="Content/*/Title"
|
||||
|
||||
>
|
||||
Edit this artist
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Content/*/Title"
|
||||
|
||||
>
|
||||
Suggest an edit on this artist
|
||||
</translate>
|
||||
|
@ -39,7 +39,7 @@ const canEdit = store.state.auth.availablePermissions.library
|
|||
v-if="!object.is_local"
|
||||
class="ui message"
|
||||
>
|
||||
<translate translate-context="Content/*/Message">
|
||||
<translate >
|
||||
This object is managed by another server, you cannot edit it.
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@ import type { OrderingField } from '~/store/ui'
|
|||
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import { useRouteQuery } from '@vueuse/router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { syncRef } from '@vueuse/core'
|
||||
import { sortedUniq } from 'lodash-es'
|
||||
import { useStore } from '~/store'
|
||||
|
@ -109,10 +109,10 @@ onOrderingUpdate(() => {
|
|||
|
||||
onMounted(() => $('.ui.dropdown').dropdown())
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'),
|
||||
title: $pgettext('*/*/*/Noun', 'Artists')
|
||||
searchPlaceholder: t('Search…'),
|
||||
title: t('Artists')
|
||||
}))
|
||||
|
||||
const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b)))
|
||||
|
@ -122,7 +122,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
<main v-title="labels.title">
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Artist/Title">
|
||||
<translate >
|
||||
Browsing artists
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -133,7 +133,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
<div class="fields">
|
||||
<div class="field">
|
||||
<label for="artist-search">
|
||||
<translate translate-context="Content/Search/Input.Label/Noun">Artist name</translate>
|
||||
<translate >Artist name</translate>
|
||||
</label>
|
||||
<div class="ui action input">
|
||||
<input
|
||||
|
@ -146,18 +146,18 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
<button
|
||||
class="ui icon button"
|
||||
type="submit"
|
||||
:aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')"
|
||||
:aria-label="t('Search')"
|
||||
>
|
||||
<i class="search icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="tags-search"><translate translate-context="*/*/*/Noun">Tags</translate></label>
|
||||
<label for="tags-search"><translate >Tags</translate></label>
|
||||
<tags-selector v-model="tags" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="artist-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||
<label for="artist-ordering"><translate >Ordering</translate></label>
|
||||
<select
|
||||
id="artist-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -173,26 +173,26 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="artist-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
|
||||
<label for="artist-ordering-direction"><translate >Ordering direction</translate></label>
|
||||
<select
|
||||
id="artist-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Ascending
|
||||
</translate>
|
||||
</option>
|
||||
<option value="-">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Descending
|
||||
</translate>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="artist-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
|
||||
<label for="artist-results"><translate >Results per page</translate></label>
|
||||
<select
|
||||
id="artist-results"
|
||||
v-model="paginateBy"
|
||||
|
@ -223,7 +223,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
<label
|
||||
for="exclude-compilation"
|
||||
class="visually-hidden"
|
||||
><translate translate-context="Content/Search/Checkbox/Noun">Exclude Compilation Artists</translate></label>
|
||||
><translate >Exclude Compilation Artists</translate></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -252,7 +252,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
>
|
||||
<div class="ui icon header">
|
||||
<i class="compact disc icon" />
|
||||
<translate translate-context="Content/Artists/Placeholder">
|
||||
<translate >
|
||||
No results matching your query
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -262,7 +262,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
class="ui success button labeled icon"
|
||||
>
|
||||
<i class="upload icon" />
|
||||
<translate translate-context="Content/*/Verb">
|
||||
<translate >
|
||||
Add some music
|
||||
</translate>
|
||||
</router-link>
|
||||
|
|
|
@ -157,7 +157,7 @@ const approve = async (approved: boolean) => {
|
|||
<h4 class="header">
|
||||
<router-link :to="detailUrl">
|
||||
<translate
|
||||
translate-context="Content/Library/Card/Short"
|
||||
|
||||
:translate-params="{id: obj.uuid.substring(0, 8)}"
|
||||
>
|
||||
Modification %{ id }
|
||||
|
@ -171,7 +171,7 @@ const approve = async (approved: boolean) => {
|
|||
>
|
||||
<i class="music icon" />
|
||||
<translate
|
||||
translate-context="Content/Library/Card/Short"
|
||||
|
||||
:translate-params="{id: obj.target.id, name: obj.target.repr}"
|
||||
>
|
||||
Track #%{ id } - %{ name }
|
||||
|
@ -186,19 +186,19 @@ const approve = async (approved: boolean) => {
|
|||
<span class="right floated">
|
||||
<span v-if="obj.is_approved && obj.is_applied">
|
||||
<i class="success check icon" />
|
||||
<translate translate-context="Content/Library/Card/Short">Approved and applied</translate>
|
||||
<translate >Approved and applied</translate>
|
||||
</span>
|
||||
<span v-else-if="obj.is_approved">
|
||||
<i class="success check icon" />
|
||||
<translate translate-context="Content/*/*/Short">Approved</translate>
|
||||
<translate >Approved</translate>
|
||||
</span>
|
||||
<span v-else-if="obj.is_approved === null">
|
||||
<i class="warning hourglass icon" />
|
||||
<translate translate-context="Content/Admin/*/Noun">Pending review</translate>
|
||||
<translate >Pending review</translate>
|
||||
</span>
|
||||
<span v-else-if="obj.is_approved === false">
|
||||
<i class="danger x icon" />
|
||||
<translate translate-context="Content/Library/*/Short">Rejected</translate>
|
||||
<translate >Rejected</translate>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -217,17 +217,17 @@ const approve = async (approved: boolean) => {
|
|||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<translate translate-context="Content/Library/Card.Table.Header/Short">
|
||||
<translate >
|
||||
Field
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/Library/Card.Table.Header/Short">
|
||||
<translate >
|
||||
Old value
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/Library/Card.Table.Header/Short">
|
||||
<translate >
|
||||
New value
|
||||
</translate>
|
||||
</th>
|
||||
|
@ -259,7 +259,7 @@ const approve = async (approved: boolean) => {
|
|||
</template>
|
||||
</td>
|
||||
<td v-else>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
N/A
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -319,7 +319,7 @@ const approve = async (approved: boolean) => {
|
|||
:class="['ui', {loading: isLoading}, 'success', 'basic', 'button']"
|
||||
@click="approve(true)"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Approve
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -328,7 +328,7 @@ const approve = async (approved: boolean) => {
|
|||
:class="['ui', {loading: isLoading}, 'warning', 'basic', 'button']"
|
||||
@click="approve(false)"
|
||||
>
|
||||
<translate translate-context="Content/Library/Button.Label">
|
||||
<translate >
|
||||
Reject
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -337,12 +337,12 @@ const approve = async (approved: boolean) => {
|
|||
:class="['ui', {loading: isLoading}, 'basic danger button']"
|
||||
:action="remove"
|
||||
>
|
||||
<translate translate-context="*/*/*/Verb">
|
||||
<translate >
|
||||
Delete
|
||||
</translate>
|
||||
<template #modal-header>
|
||||
<p>
|
||||
<translate translate-context="Popup/Library/Title">
|
||||
<translate >
|
||||
Delete this suggestion?
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -350,7 +350,7 @@ const approve = async (approved: boolean) => {
|
|||
<template #modal-content>
|
||||
<div>
|
||||
<p>
|
||||
<translate translate-context="Popup/Library/Paragraph">
|
||||
<translate >
|
||||
The suggestion will be completely removed, this action is irreversible.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -358,7 +358,7 @@ const approve = async (approved: boolean) => {
|
|||
</template>
|
||||
<template #modal-confirm>
|
||||
<p>
|
||||
<translate translate-context="*/*/*/Verb">
|
||||
<translate >
|
||||
Delete
|
||||
</translate>
|
||||
</p>
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { BackendError, License, ReviewState } from '~/types'
|
|||
|
||||
import { computed, onMounted, reactive, ref, watchEffect } from 'vue'
|
||||
import { isEqual, clone } from 'lodash-es'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
import axios from 'axios'
|
||||
|
@ -26,7 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
licenses: () => []
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const configs = useEditConfigs()
|
||||
const store = useStore()
|
||||
|
||||
|
@ -46,7 +46,7 @@ const canEdit = computed(() => {
|
|||
})
|
||||
|
||||
const labels = computed(() => ({
|
||||
summaryPlaceholder: $pgettext('*/*/Placeholder', 'A short summary describing your changes.')
|
||||
summaryPlaceholder: t('A short summary describing your changes.')
|
||||
}))
|
||||
|
||||
const mutationsUrl = computed(() => props.objectType === 'track'
|
||||
|
@ -148,7 +148,7 @@ const resetField = (fieldId: string) => {
|
|||
<div v-if="submittedMutation">
|
||||
<div class="ui positive message">
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
Your edit was successfully submitted.
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -161,7 +161,7 @@ const resetField = (fieldId: string) => {
|
|||
class="ui button"
|
||||
@click.prevent="submittedMutation = null"
|
||||
>
|
||||
<translate translate-context="Content/Library/Button.Label">
|
||||
<translate >
|
||||
Submit another edit
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -175,27 +175,27 @@ const resetField = (fieldId: string) => {
|
|||
>
|
||||
<div>
|
||||
<template v-if="showPendingReview">
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
Recent edits awaiting review
|
||||
</translate>
|
||||
<button
|
||||
class="ui tiny basic right floated button"
|
||||
@click.prevent="showPendingReview = false"
|
||||
>
|
||||
<translate translate-context="Content/Library/Button.Label">
|
||||
<translate >
|
||||
Show all edits
|
||||
</translate>
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
Recent edits
|
||||
</translate>
|
||||
<button
|
||||
class="ui tiny basic right floated button"
|
||||
@click.prevent="showPendingReview = true"
|
||||
>
|
||||
<translate translate-context="Content/Library/Button.Label">
|
||||
<translate >
|
||||
Restrict to unreviewed edits
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -203,7 +203,7 @@ const resetField = (fieldId: string) => {
|
|||
</div>
|
||||
<template #empty-state>
|
||||
<empty-state>
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
Suggest a change using the form below.
|
||||
</translate>
|
||||
</empty-state>
|
||||
|
@ -220,7 +220,7 @@ const resetField = (fieldId: string) => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Content/Library/Error message.Title">
|
||||
<translate >
|
||||
Error while submitting edit
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -237,7 +237,7 @@ const resetField = (fieldId: string) => {
|
|||
v-if="!canEdit"
|
||||
class="ui message"
|
||||
>
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval.
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -268,7 +268,7 @@ const resetField = (fieldId: string) => {
|
|||
class="ui fluid search dropdown"
|
||||
>
|
||||
<option :value="null">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
N/A
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -286,7 +286,7 @@ const resetField = (fieldId: string) => {
|
|||
@click.prevent="values[fieldConfig.id] = null"
|
||||
>
|
||||
<i class="x icon" />
|
||||
<translate translate-context="Content/Library/Button.Label">
|
||||
<translate >
|
||||
Clear
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -325,7 +325,7 @@ const resetField = (fieldId: string) => {
|
|||
@click.prevent="values[fieldConfig.id] = []"
|
||||
>
|
||||
<i class="x icon" />
|
||||
<translate translate-context="Content/Library/Button.Label">
|
||||
<translate >
|
||||
Clear
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -337,7 +337,7 @@ const resetField = (fieldId: string) => {
|
|||
@click.prevent="resetField(fieldConfig.id)"
|
||||
>
|
||||
<i class="undo icon" />
|
||||
<translate translate-context="Content/Library/Button.Label">
|
||||
<translate >
|
||||
Reset to initial value
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -345,7 +345,7 @@ const resetField = (fieldId: string) => {
|
|||
</div>
|
||||
</template>
|
||||
<div class="field">
|
||||
<label for="summary"><translate translate-context="*/*/*">Summary (optional)</translate></label>
|
||||
<label for="summary"><translate >Summary (optional)</translate></label>
|
||||
<textarea
|
||||
id="change-summary"
|
||||
v-model="summary"
|
||||
|
@ -359,7 +359,7 @@ const resetField = (fieldId: string) => {
|
|||
class="ui left floated button"
|
||||
:to="{name: 'library.tracks.detail', params: {id: object.id }}"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -370,13 +370,13 @@ const resetField = (fieldId: string) => {
|
|||
>
|
||||
<translate
|
||||
v-if="canEdit"
|
||||
translate-context="Content/Library/Button.Label/Verb"
|
||||
|
||||
>
|
||||
Submit and apply edit
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Content/Library/Button.Label/Verb"
|
||||
|
||||
>
|
||||
Submit suggestion
|
||||
</translate>
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { VueUploadItem } from 'vue-upload-component'
|
|||
import { computed, ref, reactive, watch, nextTick } from 'vue'
|
||||
import { useEventListener, useIntervalFn } from '@vueuse/core'
|
||||
import { humanSize, truncate } from '~/utils/filters'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { sortBy } from 'lodash-es'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
|
@ -34,7 +34,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
defaultImportReference: ''
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const upload = ref()
|
||||
|
@ -43,13 +43,12 @@ const supportedExtensions = computed(() => store.state.ui.supportedExtensions)
|
|||
|
||||
const labels = computed(() => ({
|
||||
tooltips: {
|
||||
denied: $pgettext('Content/Library/Help text', 'Upload denied, ensure the file is not too big and that you have not reached your quota'),
|
||||
server: $pgettext('Content/Library/Help text', 'Cannot upload this file, ensure it is not too big'),
|
||||
network: $pgettext('Content/Library/Help text', 'A network error occurred while uploading this file'),
|
||||
timeout: $pgettext('Content/Library/Help text', 'Upload timeout, please try again'),
|
||||
retry: $pgettext('*/*/*/Verb', 'Retry'),
|
||||
extension: $pgettext(
|
||||
'Content/Library/Help text',
|
||||
denied: t('Upload denied, ensure the file is not too big and that you have not reached your quota'),
|
||||
server: t('Cannot upload this file, ensure it is not too big'),
|
||||
network: t('A network error occurred while uploading this file'),
|
||||
timeout: t('Upload timeout, please try again'),
|
||||
retry: t('Retry'),
|
||||
extension: t(
|
||||
'Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }',
|
||||
{ extensions: supportedExtensions.value.join(', ') }
|
||||
)
|
||||
|
@ -283,7 +282,7 @@ const retry = (files: Omit<VueUploadItem, 'xhr'>[]) => {
|
|||
useEventListener(window, 'beforeunload', (event) => {
|
||||
if (!hasActiveUploads.value) return null
|
||||
event.preventDefault()
|
||||
return (event.returnValue = $pgettext('*/*/*', 'This page is asking you to confirm that you want to leave - data you have entered may not be saved.'))
|
||||
return (event.returnValue = t('This page is asking you to confirm that you want to leave - data you have entered may not be saved.'))
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -295,7 +294,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
:class="['item', {active: currentTab === 'uploads'}]"
|
||||
@click.prevent="currentTab = 'uploads'"
|
||||
>
|
||||
<translate translate-context="Content/Library/Tab.Title/Short">Uploading</translate>
|
||||
<translate >Uploading</translate>
|
||||
<div
|
||||
v-if="files.length === 0"
|
||||
class="ui label"
|
||||
|
@ -320,7 +319,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
:class="['item', {active: currentTab === 'processing'}]"
|
||||
@click.prevent="currentTab = 'processing'"
|
||||
>
|
||||
<translate translate-context="Content/Library/Tab.Title/Short">Processing</translate>
|
||||
<translate >Processing</translate>
|
||||
<div
|
||||
v-if="processableFiles === 0"
|
||||
class="ui label"
|
||||
|
@ -345,7 +344,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
<div :class="['ui', {loading: isLoadingQuota}, 'container']">
|
||||
<div :class="['ui', {red: remainingSpace === 0}, {warning: remainingSpace > 0 && remainingSpace <= 50}, 'small', 'statistic']">
|
||||
<div class="label">
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
Remaining storage space
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -355,33 +354,33 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
</div>
|
||||
<div class="ui divider" />
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Library/Title/Verb">
|
||||
<translate >
|
||||
Upload music from '~/your local storage
|
||||
</translate>
|
||||
</h2>
|
||||
<div class="ui message">
|
||||
<p>
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
You are about to upload music to your library. Before proceeding, please ensure that:
|
||||
</translate>
|
||||
</p>
|
||||
<ul>
|
||||
<li v-if="library.privacy_level != 'me'">
|
||||
<translate translate-context="Content/Library/List item">
|
||||
<translate >
|
||||
You are not uploading copyrighted content in a public library, otherwise you may be infringing the law
|
||||
</translate>
|
||||
</li>
|
||||
<li>
|
||||
<translate translate-context="Content/Library/List item">
|
||||
<translate >
|
||||
The music files you are uploading are tagged properly.
|
||||
</translate>
|
||||
<a
|
||||
href="http://picard.musicbrainz.org/"
|
||||
target="_blank"
|
||||
><translate translate-context="Content/Library/Link">We recommend using Picard for that purpose.</translate></a>
|
||||
><translate >We recommend using Picard for that purpose.</translate></a>
|
||||
</li>
|
||||
<li>
|
||||
<translate translate-context="Content/Library/List item">
|
||||
<translate >
|
||||
The music files you are uploading are in OGG, Flac, MP3 or AIFF format
|
||||
</translate>
|
||||
</li>
|
||||
|
@ -401,14 +400,14 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
@input-file="inputFile"
|
||||
>
|
||||
<i class="upload icon" />
|
||||
<translate translate-context="Content/Library/Paragraph/Call to action">
|
||||
<translate >
|
||||
Click to select files to upload or drag and drop files or directories
|
||||
</translate>
|
||||
<br>
|
||||
<br>
|
||||
<i>
|
||||
<translate
|
||||
translate-context="Content/Library/Paragraph"
|
||||
|
||||
:translate-params="{extensions: supportedExtensions.join(', ')}"
|
||||
>
|
||||
Supported extensions: %{ extensions }
|
||||
|
@ -425,22 +424,22 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="ten wide">
|
||||
<translate translate-context="Content/Library/Table.Label">
|
||||
<translate >
|
||||
Filename
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Size
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Status
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Actions
|
||||
</translate>
|
||||
</th>
|
||||
|
@ -454,7 +453,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
class="ui right floated small basic button"
|
||||
@click.prevent="retry(retryableFiles)"
|
||||
>
|
||||
<translate translate-context="Content/Library/Table">
|
||||
<translate >
|
||||
Retry failed uploads
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -486,7 +485,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
>
|
||||
<translate
|
||||
key="1"
|
||||
translate-context="Content/Library/Table"
|
||||
|
||||
>Uploaded</translate>
|
||||
</span>
|
||||
<span
|
||||
|
@ -495,7 +494,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
>
|
||||
<translate
|
||||
key="2"
|
||||
translate-context="Content/Library/Table"
|
||||
|
||||
>
|
||||
Uploading…
|
||||
</translate>
|
||||
|
@ -507,7 +506,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
>
|
||||
<translate
|
||||
key="3"
|
||||
translate-context="Content/Library/*/Short"
|
||||
|
||||
>
|
||||
Pending
|
||||
</translate>
|
||||
|
@ -539,7 +538,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
</div>
|
||||
<div class="ui divider" />
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Library/Title/Verb">
|
||||
<translate >
|
||||
Import music from your server
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -549,7 +548,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
class="ui negative message"
|
||||
>
|
||||
<h3 class="header">
|
||||
<translate translate-context="Content/*/Error message.Title">
|
||||
<translate >
|
||||
Error while launching import
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -570,17 +569,17 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
/>
|
||||
<template v-if="fsStatus && fsStatus.import">
|
||||
<h3 class="ui header">
|
||||
<translate translate-context="Content/Library/Title/Verb">
|
||||
<translate >
|
||||
Import status
|
||||
</translate>
|
||||
</h3>
|
||||
<p v-if="fsStatus.import.reference !== importReference">
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
Results of your previous import:
|
||||
</translate>
|
||||
</p>
|
||||
<p v-else>
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
Results of your import:
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -590,7 +589,7 @@ useEventListener(window, 'beforeunload', (event) => {
|
|||
class="ui button"
|
||||
@click="cancelFsScan"
|
||||
>
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -42,7 +42,7 @@ const handleClick = (entry: FSEntry) => {
|
|||
class="ui button"
|
||||
@click.prevent="emit('import')"
|
||||
>
|
||||
<translate translate-context="Content/Library/Button/Verb">
|
||||
<translate >
|
||||
Import
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -15,7 +15,7 @@ defineProps<Props>()
|
|||
class="ui active dimmer"
|
||||
>
|
||||
<div class="ui text loader">
|
||||
<translate translate-context="Content/Library/Paragraph">
|
||||
<translate >
|
||||
Import hasn't started yet
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
import axios from 'axios'
|
||||
|
@ -24,9 +24,9 @@ const artists = ref([])
|
|||
|
||||
const logger = useLogger()
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/Home/Title', 'Library')
|
||||
title: t('Library')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
@ -67,7 +67,7 @@ fetchData()
|
|||
:websocket-handlers="['Listen']"
|
||||
>
|
||||
<template #title>
|
||||
<translate translate-context="Content/Home/Title">
|
||||
<translate >
|
||||
Recently listened
|
||||
</translate>
|
||||
</template>
|
||||
|
@ -79,7 +79,7 @@ fetchData()
|
|||
:filters="{scope: scope, ordering: '-creation_date'}"
|
||||
>
|
||||
<template #title>
|
||||
<translate translate-context="Content/Home/Title">
|
||||
<translate >
|
||||
Recently favorited
|
||||
</translate>
|
||||
</template>
|
||||
|
@ -91,7 +91,7 @@ fetchData()
|
|||
:filters="{scope: scope, playable: true, ordering: '-modification_date'}"
|
||||
>
|
||||
<template #title>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Playlists
|
||||
</translate>
|
||||
</template>
|
||||
|
@ -103,7 +103,7 @@ fetchData()
|
|||
<div class="column">
|
||||
<album-widget :filters="{scope: scope, playable: true, ordering: '-creation_date'}">
|
||||
<template #title>
|
||||
<translate translate-context="Content/Home/Title">
|
||||
<translate >
|
||||
Recently added
|
||||
</translate>
|
||||
</template>
|
||||
|
@ -112,7 +112,7 @@ fetchData()
|
|||
</div>
|
||||
<template v-if="scope === 'all'">
|
||||
<h3 class="ui header">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
New channels
|
||||
</translate>
|
||||
</h3>
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Upload } from '~/types'
|
|||
|
||||
import SemanticModal from '~/components/semantic/Modal.vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface ErrorEntry {
|
||||
key: string
|
||||
|
@ -44,7 +44,7 @@ const getErrors = (details: object): ErrorEntry[] => {
|
|||
return errors
|
||||
}
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
|
||||
const getErrorData = (upload: Upload) => {
|
||||
const payload = upload.import_details ?? { error_code: '', detail: {} }
|
||||
|
@ -58,11 +58,11 @@ const getErrorData = (upload: Upload) => {
|
|||
supportUrl: 'https://forum.funkwhale.audio/t/support',
|
||||
documentationUrl: `https://docs.funkwhale.audio/users/upload.html#${errorCode}`,
|
||||
label: errorCode === 'invalid_metadata'
|
||||
? $pgettext('Popup/Import/Error.Label', 'Invalid metadata')
|
||||
: $pgettext('*/*/Error', 'Unknown error'),
|
||||
? t('Invalid metadata')
|
||||
: t('Unknown error'),
|
||||
detail: errorCode === 'invalid_metadata'
|
||||
? $pgettext('Popup/Import/Error.Label', 'The metadata included in the file is invalid or some mandatory fields are missing.')
|
||||
: $pgettext('Popup/Import/Error.Label', 'An unknown error occurred'),
|
||||
? t('The metadata included in the file is invalid or some mandatory fields are missing.')
|
||||
: t('An unknown error occurred'),
|
||||
errorRows: errorCode === 'invalid_metadata'
|
||||
? getErrors(payload.detail ?? {})
|
||||
: [],
|
||||
|
@ -77,7 +77,7 @@ const getErrorData = (upload: Upload) => {
|
|||
<template>
|
||||
<semantic-modal v-model:show="show">
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/Import/Title">
|
||||
<translate >
|
||||
Import detail
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -90,7 +90,7 @@ const getErrorData = (upload: Upload) => {
|
|||
v-if="upload.import_status === 'pending'"
|
||||
class="ui message"
|
||||
>
|
||||
<translate translate-context="Popup/Import/Message">
|
||||
<translate >
|
||||
Upload is still pending and will soon be processed by the server.
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -98,7 +98,7 @@ const getErrorData = (upload: Upload) => {
|
|||
v-if="upload.import_status === 'finished'"
|
||||
class="ui success message"
|
||||
>
|
||||
<translate translate-context="Popup/Import/Message">
|
||||
<translate >
|
||||
Upload was successfully processed by the server.
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -107,7 +107,7 @@ const getErrorData = (upload: Upload) => {
|
|||
role="alert"
|
||||
class="ui warning message"
|
||||
>
|
||||
<translate translate-context="Popup/Import/Message">
|
||||
<translate >
|
||||
Upload was skipped because a similar one is already available in one of your libraries.
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -115,7 +115,7 @@ const getErrorData = (upload: Upload) => {
|
|||
v-if="upload.import_status === 'errored'"
|
||||
class="ui error message"
|
||||
>
|
||||
<translate translate-context="Popup/Import/Message">
|
||||
<translate >
|
||||
An error occurred during upload processing. You will find more information below.
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -124,7 +124,7 @@ const getErrorData = (upload: Upload) => {
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Popup/Import/Table.Label/Noun">
|
||||
<translate >
|
||||
Error type
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -134,7 +134,7 @@ const getErrorData = (upload: Upload) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Popup/Import/Table.Label/Noun">
|
||||
<translate >
|
||||
Error detail
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -152,7 +152,7 @@ const getErrorData = (upload: Upload) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Footer/*/Link">
|
||||
<translate >
|
||||
Getting help
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -163,7 +163,7 @@ const getErrorData = (upload: Upload) => {
|
|||
:href="getErrorData(upload).documentationUrl"
|
||||
target="_blank"
|
||||
>
|
||||
<translate translate-context="Popup/Import/Table.Label/Value">Read our documentation for this error</translate>
|
||||
<translate >Read our documentation for this error</translate>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -171,7 +171,7 @@ const getErrorData = (upload: Upload) => {
|
|||
:href="getErrorData(upload).supportUrl"
|
||||
target="_blank"
|
||||
>
|
||||
<translate translate-context="Popup/Import/Table.Label/Value">Open a support thread (include the debug information below in your message)</translate>
|
||||
<translate >Open a support thread (include the debug information below in your message)</translate>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -179,7 +179,7 @@ const getErrorData = (upload: Upload) => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Popup/Import/Table.Label/Noun">
|
||||
<translate >
|
||||
Debug information
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -200,7 +200,7 @@ const getErrorData = (upload: Upload) => {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui deny button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Close
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -6,7 +6,7 @@ import type { OrderingField } from '~/store/ui'
|
|||
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import { useRouteQuery } from '@vueuse/router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { syncRef } from '@vueuse/core'
|
||||
import { sortedUniq } from 'lodash-es'
|
||||
import { useStore } from '~/store'
|
||||
|
@ -110,10 +110,10 @@ onOrderingUpdate(() => {
|
|||
|
||||
onMounted(() => $('.ui.dropdown').dropdown())
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'),
|
||||
title: $pgettext('*/*/*/Noun', 'Podcasts')
|
||||
searchPlaceholder: t('Search…'),
|
||||
title: t('Podcasts')
|
||||
}))
|
||||
|
||||
const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b)))
|
||||
|
@ -123,7 +123,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
<main v-title="labels.title">
|
||||
<section class="ui vertical stripe segment">
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Podcasts/Title">
|
||||
<translate >
|
||||
Browsing podcasts
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -134,7 +134,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
<div class="fields">
|
||||
<div class="field">
|
||||
<label for="artist-search">
|
||||
<translate translate-context="Content/Search/Input.Label/Noun">Podcast title</translate>
|
||||
<translate >Podcast title</translate>
|
||||
</label>
|
||||
<div class="ui action input">
|
||||
<input
|
||||
|
@ -147,18 +147,18 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
<button
|
||||
class="ui icon button"
|
||||
type="submit"
|
||||
:aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')"
|
||||
:aria-label="t('Search')"
|
||||
>
|
||||
<i class="search icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="tags-search"><translate translate-context="*/*/*/Noun">Tags</translate></label>
|
||||
<label for="tags-search"><translate >Tags</translate></label>
|
||||
<tags-selector v-model="tags" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="artist-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||
<label for="artist-ordering"><translate >Ordering</translate></label>
|
||||
<select
|
||||
id="artist-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -174,26 +174,26 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="artist-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
|
||||
<label for="artist-ordering-direction"><translate >Ordering direction</translate></label>
|
||||
<select
|
||||
id="artist-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Ascending
|
||||
</translate>
|
||||
</option>
|
||||
<option value="-">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Descending
|
||||
</translate>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="artist-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
|
||||
<label for="artist-results"><translate >Results per page</translate></label>
|
||||
<select
|
||||
id="artist-results"
|
||||
v-model="paginateBy"
|
||||
|
@ -234,7 +234,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
>
|
||||
<div class="ui icon header">
|
||||
<i class="podcast icon" />
|
||||
<translate translate-context="Content/Artists/Placeholder">
|
||||
<translate >
|
||||
No results matching your query
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -244,7 +244,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
class="ui success button labeled icon"
|
||||
>
|
||||
<i class="upload icon" />
|
||||
<translate translate-context="Content/*/Verb">
|
||||
<translate >
|
||||
Create a Channel
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -255,7 +255,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
<div class="actions">
|
||||
<a @click.stop.prevent="showSubscribeModal = true">
|
||||
<i class="plus icon" />
|
||||
<translate translate-context="Content/Profile/Button">Subscribe to feed</translate>
|
||||
<translate >Subscribe to feed</translate>
|
||||
</a>
|
||||
</div>
|
||||
</h1>
|
||||
|
@ -275,7 +275,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
:fullscreen="false"
|
||||
>
|
||||
<h2 class="header">
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Subscription
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -293,7 +293,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic deny button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -303,7 +303,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
class="ui primary button"
|
||||
>
|
||||
<i class="bookmark icon" />
|
||||
<translate translate-context="*/*/*/Verb">
|
||||
<translate >
|
||||
Subscribe
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -6,7 +6,7 @@ import type { OrderingField } from '~/store/ui'
|
|||
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRouteQuery } from '@vueuse/router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { syncRef } from '@vueuse/core'
|
||||
import { sortedUniq } from 'lodash-es'
|
||||
import { useStore } from '~/store'
|
||||
|
@ -99,10 +99,10 @@ onOrderingUpdate(() => {
|
|||
|
||||
onMounted(() => $('.ui.dropdown').dropdown())
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Enter a radio name…'),
|
||||
title: $pgettext('*/*/*', 'Radios')
|
||||
searchPlaceholder: t('Enter a radio name…'),
|
||||
title: t('Radios')
|
||||
}))
|
||||
|
||||
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b)))
|
||||
|
@ -112,14 +112,14 @@ 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 translate-context="Content/Radio/Title">
|
||||
<translate >
|
||||
Browsing radios
|
||||
</translate>
|
||||
</h2>
|
||||
<div class="ui hidden divider" />
|
||||
<div class="ui row">
|
||||
<h3 class="ui header">
|
||||
<translate translate-context="Content/Radio/Title">
|
||||
<translate >
|
||||
Instance radios
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -144,7 +144,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
|
||||
<div class="ui hidden divider" />
|
||||
<h3 class="ui header">
|
||||
<translate translate-context="Content/Radio/Title">
|
||||
<translate >
|
||||
User radios
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -153,7 +153,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
class="ui success button"
|
||||
to="/library/radios/build"
|
||||
>
|
||||
<translate translate-context="Content/Radio/Button.Label/Verb">
|
||||
<translate >
|
||||
Create your own radio
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -164,7 +164,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label for="radios-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<label for="radios-search"><translate >Search</translate></label>
|
||||
<div class="ui action input">
|
||||
<input
|
||||
id="radios-search"
|
||||
|
@ -176,14 +176,14 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
<button
|
||||
class="ui icon button"
|
||||
type="submit"
|
||||
:aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')"
|
||||
:aria-label="t('Search')"
|
||||
>
|
||||
<i class="search icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="radios-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||
<label for="radios-ordering"><translate >Ordering</translate></label>
|
||||
<select
|
||||
id="radios-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -199,26 +199,26 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="radios-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label>
|
||||
<label for="radios-ordering-direction"><translate >Order</translate></label>
|
||||
<select
|
||||
id="radios-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Ascending
|
||||
</translate>
|
||||
</option>
|
||||
<option value="-">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Descending
|
||||
</translate>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="radios-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
|
||||
<label for="radios-results"><translate >Results per page</translate></label>
|
||||
<select
|
||||
id="radios-results"
|
||||
v-model="paginateBy"
|
||||
|
@ -242,7 +242,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
>
|
||||
<div class="ui icon header">
|
||||
<i class="feed icon" />
|
||||
<translate translate-context="Content/Radios/Placeholder">
|
||||
<translate >
|
||||
No results matching your query
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -252,7 +252,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
|
|||
class="ui success button labeled icon"
|
||||
>
|
||||
<i class="rss icon" />
|
||||
<translate translate-context="Content/*/Verb">
|
||||
<translate >
|
||||
Create a radio
|
||||
</translate>
|
||||
</router-link>
|
||||
|
|
|
@ -36,7 +36,7 @@ const labels = computed(() => ({
|
|||
:to="{name: 'manage.library.tags.detail', params: {id: id}}"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
<translate translate-context="Content/Moderation/Link">
|
||||
<translate >
|
||||
Open in moderation interface
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -50,7 +50,7 @@ const labels = computed(() => ({
|
|||
>
|
||||
<template #title>
|
||||
<router-link :to="{name: 'library.artists.browse', query: {tag: id}}">
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Artists
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -59,7 +59,7 @@ const labels = computed(() => ({
|
|||
<div class="ui hidden divider" />
|
||||
<div class="ui hidden divider" />
|
||||
<h3 class="ui header">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Channels
|
||||
</translate>
|
||||
</h3>
|
||||
|
@ -79,7 +79,7 @@ const labels = computed(() => ({
|
|||
>
|
||||
<template #title>
|
||||
<router-link :to="{name: 'library.albums.browse', query: {tag: id}}">
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Albums
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -97,7 +97,7 @@ const labels = computed(() => ({
|
|||
:filters="{playable: true, ordering: '-creation_date', tag: id}"
|
||||
>
|
||||
<template #title>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Tracks
|
||||
</translate>
|
||||
</template>
|
||||
|
|
|
@ -99,7 +99,7 @@ onMounted(async () => {
|
|||
class="search"
|
||||
>
|
||||
<div class="default text">
|
||||
<translate translate-context="*/Dropdown/Placeholder/Verb">
|
||||
<translate >
|
||||
Search…
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { Track, Artist, Library } from '~/types'
|
|||
|
||||
import { momentFormat } from '~/utils/filters'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getDomain } from '~/utils'
|
||||
import { useStore } from '~/store'
|
||||
|
@ -67,8 +67,7 @@ const attributedToUrl = computed(() => router.resolve({
|
|||
const escapeHtml = (unsafe: string) => document.createTextNode(unsafe).textContent ?? ''
|
||||
const subtitle = computed(() => {
|
||||
if (track.value?.attributed_to) {
|
||||
return $pgettext(
|
||||
'Content/Track/Paragraph',
|
||||
return t(
|
||||
'Uploaded by <a class="internal" href="%{ uploaderUrl }">%{ uploader }</a> on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>',
|
||||
{
|
||||
uploaderUrl: attributedToUrl.value,
|
||||
|
@ -79,8 +78,7 @@ const subtitle = computed(() => {
|
|||
)
|
||||
}
|
||||
|
||||
return $pgettext(
|
||||
'Content/Track/Paragraph',
|
||||
return t(
|
||||
'Uploaded on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>',
|
||||
{
|
||||
date: escapeHtml(track.value?.creation_date ?? ''),
|
||||
|
@ -89,11 +87,11 @@ const subtitle = computed(() => {
|
|||
)
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('*/*/*/Noun', 'Track'),
|
||||
download: $pgettext('Content/Track/Link/Verb', 'Download'),
|
||||
more: $pgettext('*/*/Button.Label/Noun', 'More…')
|
||||
title: t('Track'),
|
||||
download: t('Download'),
|
||||
more: t('More…')
|
||||
}))
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
@ -157,7 +155,7 @@ const remove = async () => {
|
|||
class="vibrant"
|
||||
:track="track"
|
||||
>
|
||||
<translate translate-context="*/Queue/Button.Label/Short, Verb">
|
||||
<translate >
|
||||
Play
|
||||
</translate>
|
||||
</play-button>
|
||||
|
@ -189,7 +187,7 @@ const remove = async () => {
|
|||
v-model:show="showEmbedModal"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/Track/Title">
|
||||
<translate >
|
||||
Embed this track on your website
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -203,7 +201,7 @@ const remove = async () => {
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui basic deny button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -228,7 +226,7 @@ const remove = async () => {
|
|||
<i class="external icon" />
|
||||
<translate
|
||||
:translate-params="{domain: domain}"
|
||||
translate-context="Content/*/Button.Label/Verb"
|
||||
|
||||
>View on %{ domain }</translate>
|
||||
</a>
|
||||
<div
|
||||
|
@ -238,7 +236,7 @@ const remove = async () => {
|
|||
@click="showEmbedModal = !showEmbedModal"
|
||||
>
|
||||
<i class="code icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Embed
|
||||
</translate>
|
||||
</div>
|
||||
|
@ -249,7 +247,7 @@ const remove = async () => {
|
|||
class="basic item"
|
||||
>
|
||||
<i class="wikipedia w icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">Search on Wikipedia</translate>
|
||||
<translate >Search on Wikipedia</translate>
|
||||
</a>
|
||||
<a
|
||||
v-if="discogsUrl"
|
||||
|
@ -259,7 +257,7 @@ const remove = async () => {
|
|||
class="basic item"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate>
|
||||
<translate >Search on Discogs</translate>
|
||||
</a>
|
||||
<router-link
|
||||
v-if="track.is_local"
|
||||
|
@ -267,7 +265,7 @@ const remove = async () => {
|
|||
class="basic item"
|
||||
>
|
||||
<i class="edit icon" />
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Edit
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -277,12 +275,12 @@ const remove = async () => {
|
|||
@confirm="remove()"
|
||||
>
|
||||
<i class="ui trash icon" />
|
||||
<translate translate-context="*/*/*/Verb">
|
||||
<translate >
|
||||
Delete…
|
||||
</translate>
|
||||
<template #modal-header>
|
||||
<p>
|
||||
<translate translate-context="Popup/Channel/Title">
|
||||
<translate >
|
||||
Delete this track?
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -290,7 +288,7 @@ const remove = async () => {
|
|||
<template #modal-content>
|
||||
<div>
|
||||
<p>
|
||||
<translate translate-context="Content/Moderation/Paragraph">
|
||||
<translate >
|
||||
The track will be deleted, as well as any related files and data. This action is irreversible.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -298,7 +296,7 @@ const remove = async () => {
|
|||
</template>
|
||||
<template #modal-confirm>
|
||||
<p>
|
||||
<translate translate-context="*/*/*/Verb">
|
||||
<translate >
|
||||
Delete
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -321,7 +319,7 @@ const remove = async () => {
|
|||
:to="{name: 'manage.library.tracks.detail', params: {id: track.id}}"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
<translate translate-context="Content/Moderation/Link">
|
||||
<translate >
|
||||
Open in moderation interface
|
||||
</translate>
|
||||
</router-link>
|
||||
|
@ -333,7 +331,7 @@ const remove = async () => {
|
|||
rel="noopener noreferrer"
|
||||
>
|
||||
<i class="wrench icon" />
|
||||
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>
|
||||
<translate >View in Django's admin</translate>
|
||||
</a>
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
@ -78,13 +78,13 @@ watchEffect(() => {
|
|||
<h3 class="ui header">
|
||||
<translate
|
||||
v-if="track.artist?.content_category === 'music'"
|
||||
translate-context="Content/*/*"
|
||||
|
||||
>
|
||||
Track Details
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="Content/*/*"
|
||||
|
||||
>
|
||||
Episode Details
|
||||
</translate>
|
||||
|
@ -93,7 +93,7 @@ watchEffect(() => {
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Content/*/*">
|
||||
<translate >
|
||||
Duration
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -103,7 +103,7 @@ watchEffect(() => {
|
|||
</template>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
>
|
||||
N/A
|
||||
</translate>
|
||||
|
@ -111,7 +111,7 @@ watchEffect(() => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Size
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -121,7 +121,7 @@ watchEffect(() => {
|
|||
</template>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
>
|
||||
N/A
|
||||
</translate>
|
||||
|
@ -129,7 +129,7 @@ watchEffect(() => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Codec
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -139,7 +139,7 @@ watchEffect(() => {
|
|||
</template>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
>
|
||||
N/A
|
||||
</translate>
|
||||
|
@ -147,7 +147,7 @@ watchEffect(() => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Content/Track/*/Noun">
|
||||
<translate >
|
||||
Bitrate
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -157,7 +157,7 @@ watchEffect(() => {
|
|||
</template>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
>
|
||||
N/A
|
||||
</translate>
|
||||
|
@ -165,7 +165,7 @@ watchEffect(() => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Content/*/*">
|
||||
<translate >
|
||||
Downloads
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -188,7 +188,7 @@ watchEffect(() => {
|
|||
:can-update="false"
|
||||
/>
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/*/*">
|
||||
<translate >
|
||||
Release Details
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -196,7 +196,7 @@ watchEffect(() => {
|
|||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Artist
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -210,13 +210,13 @@ watchEffect(() => {
|
|||
<td>
|
||||
<translate
|
||||
v-if="track.album.artist.content_category === 'music'"
|
||||
translate-context="*/*/*/Noun"
|
||||
|
||||
>
|
||||
Album
|
||||
</translate>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
>
|
||||
Serie
|
||||
</translate>
|
||||
|
@ -229,7 +229,7 @@ watchEffect(() => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Year
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -238,7 +238,7 @@ watchEffect(() => {
|
|||
{{ momentFormat(new Date(track.album.release_date), 'Y') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
N/A
|
||||
</translate>
|
||||
</template>
|
||||
|
@ -246,7 +246,7 @@ watchEffect(() => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Content/Track/*/Noun">
|
||||
<translate >
|
||||
Copyright
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -256,7 +256,7 @@ watchEffect(() => {
|
|||
:title="track.copyright"
|
||||
>{{ truncate(track.copyright, 50) }}</span>
|
||||
<template v-else>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
N/A
|
||||
</translate>
|
||||
</template>
|
||||
|
@ -264,7 +264,7 @@ watchEffect(() => {
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
License
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -277,7 +277,7 @@ watchEffect(() => {
|
|||
>{{ license.name }}</a>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
>
|
||||
N/A
|
||||
</translate>
|
||||
|
@ -285,7 +285,7 @@ watchEffect(() => {
|
|||
</tr>
|
||||
<tr v-if="!track.is_local">
|
||||
<td>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
URL
|
||||
</translate>
|
||||
</td>
|
||||
|
@ -308,10 +308,10 @@ watchEffect(() => {
|
|||
rel="noreferrer noopener"
|
||||
>
|
||||
<i class="external icon" />
|
||||
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
|
||||
<translate >View on MusicBrainz</translate>
|
||||
</a>
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/*/Title/Noun">
|
||||
<translate >
|
||||
Related Playlists
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -321,7 +321,7 @@ watchEffect(() => {
|
|||
/>
|
||||
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/*/Title/Noun">
|
||||
<translate >
|
||||
Related Libraries
|
||||
</translate>
|
||||
</h2>
|
||||
|
@ -329,7 +329,7 @@ watchEffect(() => {
|
|||
:url="`tracks/${track.id}/libraries/`"
|
||||
@loaded="emit('libraries-loaded', $event)"
|
||||
>
|
||||
<translate translate-context="Content/Track/Paragraph">
|
||||
<translate >
|
||||
This track is present in the following libraries:
|
||||
</translate>
|
||||
</library-widget>
|
||||
|
|
|
@ -46,13 +46,13 @@ fetchLicenses()
|
|||
<h2>
|
||||
<translate
|
||||
v-if="canEdit"
|
||||
translate-context="Content/*/Title"
|
||||
|
||||
>
|
||||
Edit this track
|
||||
</translate>
|
||||
<translate
|
||||
key="2"
|
||||
translate-context="Content/*/Title"
|
||||
|
||||
>
|
||||
Suggest an edit on this track
|
||||
</translate>
|
||||
|
@ -61,7 +61,7 @@ fetchLicenses()
|
|||
v-if="!object.is_local"
|
||||
class="ui message"
|
||||
>
|
||||
<translate translate-context="Content/*/Message">
|
||||
<translate >
|
||||
This object is managed by another server, you cannot edit it.
|
||||
</translate>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, reactive, watch, watchEffect, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import axios from 'axios'
|
||||
import $ from 'jquery'
|
||||
|
@ -51,14 +51,14 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
id: 0
|
||||
})
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
|
||||
const labels = computed(() => ({
|
||||
title: $pgettext('Head/Radio/Title', 'Radio Builder'),
|
||||
title: t('Radio Builder'),
|
||||
placeholder: {
|
||||
description: $pgettext('Content/Radio/Input.Placeholder', 'My awesome description'),
|
||||
name: $pgettext('Content/Radio/Input.Placeholder', 'My awesome radio')
|
||||
description: t('My awesome description'),
|
||||
name: t('My awesome radio')
|
||||
}
|
||||
}))
|
||||
|
||||
|
@ -203,12 +203,12 @@ onMounted(() => {
|
|||
<div>
|
||||
<section>
|
||||
<h2 class="ui header">
|
||||
<translate translate-context="Content/Radio/Title">
|
||||
<translate >
|
||||
Builder
|
||||
</translate>
|
||||
</h2>
|
||||
<p>
|
||||
<translate translate-context="Content/Radio/Paragraph">
|
||||
<translate >
|
||||
You can use this interface to build your own custom radio, which will play tracks according to your criteria.
|
||||
</translate>
|
||||
</p>
|
||||
|
@ -219,12 +219,12 @@ onMounted(() => {
|
|||
>
|
||||
<h4 class="header">
|
||||
<template v-if="radioName">
|
||||
<translate translate-context="Content/Radio/Message">
|
||||
<translate >
|
||||
Radio updated
|
||||
</translate>
|
||||
</template>
|
||||
<template v-else>
|
||||
<translate translate-context="Content/Radio/Message">
|
||||
<translate >
|
||||
Radio created
|
||||
</translate>
|
||||
</template>
|
||||
|
@ -232,7 +232,7 @@ onMounted(() => {
|
|||
</div>
|
||||
<div class="">
|
||||
<div class="field">
|
||||
<label for="name"><translate translate-context="Content/Radio/Input.Label/Noun">Radio name</translate></label>
|
||||
<label for="name"><translate >Radio name</translate></label>
|
||||
<input
|
||||
id="name"
|
||||
v-model="radioName"
|
||||
|
@ -242,7 +242,7 @@ onMounted(() => {
|
|||
>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="description"><translate translate-context="*/*/*/Noun">Description</translate></label>
|
||||
<label for="description"><translate >Description</translate></label>
|
||||
<textarea
|
||||
id="description"
|
||||
v-model="radioDesc"
|
||||
|
@ -257,7 +257,7 @@ onMounted(() => {
|
|||
v-model="isPublic"
|
||||
type="checkbox"
|
||||
>
|
||||
<label for="public"><translate translate-context="Content/Radio/Checkbox.Label/Verb">Display publicly</translate></label>
|
||||
<label for="public"><translate >Display publicly</translate></label>
|
||||
</div>
|
||||
<div class="ui hidden divider" />
|
||||
<button
|
||||
|
@ -265,7 +265,7 @@ onMounted(() => {
|
|||
:class="['ui', 'success', {loading: isLoading}, 'button']"
|
||||
@click="save"
|
||||
>
|
||||
<translate translate-context="Content/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Save
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -281,14 +281,14 @@ onMounted(() => {
|
|||
<label
|
||||
id="radioFilterLabel"
|
||||
for="radio-filters"
|
||||
><translate translate-context="Content/Radio/Paragraph">Add filters to customize your radio</translate></label>
|
||||
><translate >Add filters to customize your radio</translate></label>
|
||||
<select
|
||||
id="radio-filters"
|
||||
v-model="currentFilterType"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="">
|
||||
<translate translate-context="Content/Radio/Dropdown.Placeholder/Verb">
|
||||
<translate >
|
||||
Select a filter
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -306,7 +306,7 @@ onMounted(() => {
|
|||
class="ui button"
|
||||
@click="add"
|
||||
>
|
||||
<translate translate-context="Content/Radio/Button.Label/Verb">
|
||||
<translate >
|
||||
Add filter
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -319,27 +319,27 @@ onMounted(() => {
|
|||
<thead>
|
||||
<tr>
|
||||
<th class="two wide">
|
||||
<translate translate-context="Content/Radio/Table.Label/Noun">
|
||||
<translate >
|
||||
Filter name
|
||||
</translate>
|
||||
</th>
|
||||
<th class="one wide">
|
||||
<translate translate-context="Content/Radio/Table.Label/Verb">
|
||||
<translate >
|
||||
Exclude
|
||||
</translate>
|
||||
</th>
|
||||
<th class="six wide">
|
||||
<translate translate-context="Content/Radio/Table.Label/Verb (Value is a List of Parameters)">
|
||||
<translate >
|
||||
Config
|
||||
</translate>
|
||||
</th>
|
||||
<th class="five wide">
|
||||
<translate translate-context="Content/Radio/Table.Label/Noun (Value is a number of Tracks)">
|
||||
<translate >
|
||||
Candidates
|
||||
</translate>
|
||||
</th>
|
||||
<th class="two wide">
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Actions
|
||||
</translate>
|
||||
</th>
|
||||
|
@ -363,7 +363,7 @@ onMounted(() => {
|
|||
class="ui header"
|
||||
:translate-n="checkResult.candidates.count"
|
||||
translate-plural="%{ count } tracks matching combined filters"
|
||||
translate-context="Content/Radio/Table.Paragraph/Short"
|
||||
|
||||
>
|
||||
%{ count } track matching combined filters
|
||||
</h3>
|
||||
|
|
|
@ -124,7 +124,7 @@ watch(exclude, fetchCandidates)
|
|||
for="exclude-filter"
|
||||
class="visually-hidden"
|
||||
>
|
||||
<translate translate-context="Popup/Radio/Title/Noun">Exclude</translate>
|
||||
<translate >Exclude</translate>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -180,7 +180,7 @@ watch(exclude, fetchCandidates)
|
|||
v-model:show="showCandidadesModal"
|
||||
>
|
||||
<h4 class="header">
|
||||
<translate translate-context="Popup/Radio/Title/Noun">
|
||||
<translate >
|
||||
Tracks matching filter
|
||||
</translate>
|
||||
</h4>
|
||||
|
@ -194,7 +194,7 @@ watch(exclude, fetchCandidates)
|
|||
</div>
|
||||
<div class="actions">
|
||||
<button class="ui deny button">
|
||||
<translate translate-context="*/*/Button.Label/Verb">
|
||||
<translate >
|
||||
Cancel
|
||||
</translate>
|
||||
</button>
|
||||
|
@ -206,7 +206,7 @@ watch(exclude, fetchCandidates)
|
|||
class="ui danger button"
|
||||
@click="$emit('delete', index)"
|
||||
>
|
||||
<translate translate-context="Content/Radio/Button.Label/Verb">
|
||||
<translate >
|
||||
Remove
|
||||
</translate>
|
||||
</button>
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { RouteRecordName } from 'vue-router'
|
|||
import type { OrderingField } from '~/store/ui'
|
||||
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
|
@ -80,10 +80,10 @@ onOrderingUpdate(fetchData)
|
|||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by domain, name, account…'),
|
||||
openModeration: $pgettext('Content/Moderation/Verb', 'Open in moderation interface')
|
||||
searchPlaceholder: t('Search by domain, name, account…'),
|
||||
openModeration: t('Open in moderation interface')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -92,7 +92,7 @@ const labels = computed(() => ({
|
|||
<div class="ui inline form">
|
||||
<div class="fields">
|
||||
<div class="ui six wide field">
|
||||
<label for="channel-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<label for="channel-search"><translate >Search</translate></label>
|
||||
<form @submit.prevent="query = search.value">
|
||||
<input
|
||||
id="channel-search"
|
||||
|
@ -105,7 +105,7 @@ const labels = computed(() => ({
|
|||
</form>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="channel-category"><translate translate-context="*/*/*">Category</translate></label>
|
||||
<label for="channel-category"><translate >Category</translate></label>
|
||||
<select
|
||||
id="channel-category"
|
||||
class="ui dropdown"
|
||||
|
@ -113,7 +113,7 @@ const labels = computed(() => ({
|
|||
@change="addSearchToken('category', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">
|
||||
<translate translate-context="Content/*/Dropdown">
|
||||
<translate >
|
||||
All
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -129,7 +129,7 @@ const labels = computed(() => ({
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="channel-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||
<label for="channel-ordering"><translate >Ordering</translate></label>
|
||||
<select
|
||||
id="channel-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -145,19 +145,19 @@ const labels = computed(() => ({
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="channel-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
|
||||
<label for="channel-ordering-direction"><translate >Ordering direction</translate></label>
|
||||
<select
|
||||
id="channel-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Ascending
|
||||
</translate>
|
||||
</option>
|
||||
<option value="-">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Descending
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -182,32 +182,32 @@ const labels = computed(() => ({
|
|||
>
|
||||
<template #header-cells>
|
||||
<th>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Name
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Account
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/Moderation/*/Noun">
|
||||
<translate >
|
||||
Domain
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Albums
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Tracks
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Creation date
|
||||
</translate>
|
||||
</th>
|
||||
|
@ -250,7 +250,7 @@ const labels = computed(() => ({
|
|||
@click.prevent="addSearchToken('domain', scope.obj.attributed_to.domain)"
|
||||
>
|
||||
<i class="home icon" />
|
||||
<translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
|
||||
<translate >Local</translate>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -276,7 +276,7 @@ const labels = computed(() => ({
|
|||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
<translate
|
||||
translate-context="Content/*/Paragraph"
|
||||
|
||||
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
|
||||
>
|
||||
Showing results %{ start }-%{ end } on %{ total }
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { RouteRecordName } from 'vue-router'
|
|||
import type { OrderingField } from '~/store/ui'
|
||||
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
|
@ -49,13 +49,13 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
|
|||
['name', 'name']
|
||||
]
|
||||
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
||||
const actions = computed(() => [
|
||||
{
|
||||
name: 'delete',
|
||||
label: $pgettext('*/*/*/Verb', 'Delete'),
|
||||
confirmationMessage: $pgettext('Popup/*/Paragraph', 'The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible.'),
|
||||
label: t('Delete'),
|
||||
confirmationMessage: t('The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible.'),
|
||||
isDangerous: true,
|
||||
allowAll: false,
|
||||
confirmColor: 'danger'
|
||||
|
@ -94,8 +94,8 @@ fetchData()
|
|||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by domain, title, artist, MusicBrainz ID…'),
|
||||
openModeration: $pgettext('Content/Moderation/Verb', 'Open in moderation interface')
|
||||
searchPlaceholder: t('Search by domain, title, artist, MusicBrainz ID…'),
|
||||
openModeration: t('Open in moderation interface')
|
||||
}))
|
||||
</script>
|
||||
|
||||
|
@ -104,7 +104,7 @@ const labels = computed(() => ({
|
|||
<div class="ui inline form">
|
||||
<div class="fields">
|
||||
<div class="ui six wide field">
|
||||
<label for="albums-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<label for="albums-search"><translate >Search</translate></label>
|
||||
<form @submit.prevent="query = search.value">
|
||||
<input
|
||||
id="albums-search"
|
||||
|
@ -117,7 +117,7 @@ const labels = computed(() => ({
|
|||
</form>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="albums-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||
<label for="albums-ordering"><translate >Ordering</translate></label>
|
||||
<select
|
||||
id="albums-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -133,18 +133,18 @@ const labels = computed(() => ({
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="albums-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
|
||||
<label for="albums-ordering-direction"><translate >Ordering direction</translate></label>
|
||||
<select
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Ascending
|
||||
</translate>
|
||||
</option>
|
||||
<option value="-">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Descending
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -169,32 +169,32 @@ const labels = computed(() => ({
|
|||
>
|
||||
<template #header-cells>
|
||||
<th>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Title
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Artist
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/Moderation/*/Noun">
|
||||
<translate >
|
||||
Domain
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Tracks
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Release date
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Creation date
|
||||
</translate>
|
||||
</th>
|
||||
|
@ -238,7 +238,7 @@ const labels = computed(() => ({
|
|||
@click.prevent="addSearchToken('domain', scope.obj.domain)"
|
||||
>
|
||||
<i class="home icon" />
|
||||
<translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
|
||||
<translate >Local</translate>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -251,7 +251,7 @@ const labels = computed(() => ({
|
|||
/>
|
||||
<translate
|
||||
v-else
|
||||
translate-context="*/*/*"
|
||||
|
||||
>
|
||||
N/A
|
||||
</translate>
|
||||
|
@ -273,7 +273,7 @@ const labels = computed(() => ({
|
|||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
<translate
|
||||
translate-context="Content/*/Paragraph"
|
||||
|
||||
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
|
||||
>
|
||||
Showing results %{ start }-%{ end } on %{ total }
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { RouteRecordName } from 'vue-router'
|
|||
import type { OrderingField } from '~/store/ui'
|
||||
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
|
@ -52,8 +52,8 @@ const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
|
|||
const actions = computed(() => [
|
||||
{
|
||||
name: 'delete',
|
||||
label: $pgettext('*/*/*/Verb', 'Delete'),
|
||||
confirmationMessage: $pgettext('Popup/*/Paragraph', 'The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible.'),
|
||||
label: t('Delete'),
|
||||
confirmationMessage: t('The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible.'),
|
||||
isDangerous: true,
|
||||
allowAll: false,
|
||||
confirmColor: 'danger'
|
||||
|
@ -91,9 +91,9 @@ onOrderingUpdate(fetchData)
|
|||
fetchData()
|
||||
|
||||
const sharedLabels = useSharedLabels()
|
||||
const { $pgettext } = useGettext()
|
||||
const { t } = useI18n()
|
||||
const labels = computed(() => ({
|
||||
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by domain, name, MusicBrainz ID…')
|
||||
searchPlaceholder: t('Search by domain, name, MusicBrainz ID…')
|
||||
}))
|
||||
|
||||
const getUrl = (artist: { channel?: number; id: number }) => {
|
||||
|
@ -108,7 +108,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
|
|||
<div class="ui inline form">
|
||||
<div class="fields">
|
||||
<div class="ui six wide field">
|
||||
<label for="artists-serarch"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
|
||||
<label for="artists-serarch"><translate >Search</translate></label>
|
||||
<form @submit.prevent="query = search.value">
|
||||
<input
|
||||
id="artists-search"
|
||||
|
@ -121,7 +121,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
|
|||
</form>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="artists-category"><translate translate-context="*/*/*">Category</translate></label>
|
||||
<label for="artists-category"><translate >Category</translate></label>
|
||||
<select
|
||||
id="artists-category"
|
||||
class="ui dropdown"
|
||||
|
@ -129,7 +129,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
|
|||
@change="addSearchToken('category', ($event.target as HTMLSelectElement).value)"
|
||||
>
|
||||
<option value="">
|
||||
<translate translate-context="Content/*/Dropdown">
|
||||
<translate >
|
||||
All
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -145,7 +145,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="artists-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
|
||||
<label for="artists-ordering"><translate >Ordering</translate></label>
|
||||
<select
|
||||
id="artists-ordering"
|
||||
v-model="ordering"
|
||||
|
@ -161,19 +161,19 @@ const getUrl = (artist: { channel?: number; id: number }) => {
|
|||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="artists-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
|
||||
<label for="artists-ordering-direction"><translate >Ordering direction</translate></label>
|
||||
<select
|
||||
id="artists-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="ui dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Ascending
|
||||
</translate>
|
||||
</option>
|
||||
<option value="-">
|
||||
<translate translate-context="Content/Search/Dropdown">
|
||||
<translate >
|
||||
Descending
|
||||
</translate>
|
||||
</option>
|
||||
|
@ -198,27 +198,27 @@ const getUrl = (artist: { channel?: number; id: number }) => {
|
|||
>
|
||||
<template #header-cells>
|
||||
<th>
|
||||
<translate translate-context="*/*/*/Noun">
|
||||
<translate >
|
||||
Name
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/Moderation/*/Noun">
|
||||
<translate >
|
||||
Domain
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Albums
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="*/*/*">
|
||||
<translate >
|
||||
Tracks
|
||||
</translate>
|
||||
</th>
|
||||
<th>
|
||||
<translate translate-context="Content/*/*/Noun">
|
||||
<translate >
|
||||
Creation date
|
||||
</translate>
|
||||
</th>
|
||||
|
@ -250,7 +250,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
|
|||
@click.prevent="addSearchToken('domain', scope.obj.domain)"
|
||||
>
|
||||
<i class="home icon" />
|
||||
<translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
|
||||
<translate >Local</translate>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -276,7 +276,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
|
|||
|
||||
<span v-if="result && result.results.length > 0">
|
||||
<translate
|
||||
translate-context="Content/*/Paragraph"
|
||||
|
||||
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
|
||||
>
|
||||
Showing results %{ start }-%{ end } on %{ total }
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue