Replace gettext with i18n

This commit is contained in:
wvffle 2022-09-08 14:32:45 +00:00 committed by Kasper Seweryn
parent 2694417bdf
commit ebea32faf9
197 changed files with 2623 additions and 2932 deletions

View File

@ -76,7 +76,6 @@
"@vue/eslint-config-typescript": "11.0.2", "@vue/eslint-config-typescript": "11.0.2",
"@vue/test-utils": "2.2.4", "@vue/test-utils": "2.2.4",
"@vue/tsconfig": "0.1.3", "@vue/tsconfig": "0.1.3",
"easygettext": "2.17.0",
"eslint": "8.28.0", "eslint": "8.28.0",
"eslint-config-standard": "17.0.0", "eslint-config-standard": "17.0.0",
"eslint-plugin-html": "7.1.0", "eslint-plugin-html": "7.1.0",

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useStore } from '~/store' import { useStore } from '~/store'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { get } from 'lodash-es' import { get } from 'lodash-es'
import { humanSize } from '~/utils/filters' import { humanSize } from '~/utils/filters'
import { computed } from 'vue' import { computed } from 'vue'
@ -11,9 +11,9 @@ import LogoText from '~/components/LogoText.vue'
const store = useStore() const store = useStore()
const nodeinfo = computed(() => store.state.instance.nodeinfo) const nodeinfo = computed(() => store.state.instance.nodeinfo)
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/About/Title', 'About') title: t('About')
})) }))
const podName = computed(() => get(nodeinfo.value, 'metadata.nodeName') ?? 'Funkwhale') const podName = computed(() => get(nodeinfo.value, 'metadata.nodeName') ?? 'Funkwhale')
@ -67,12 +67,12 @@ const headerStyle = computed(() => {
<div class="column" /> <div class="column" />
</div> </div>
<h2 class="header"> <h2 class="header">
<translate translate-context="Content/About/Heading"> <translate >
A social platform to enjoy and share music A social platform to enjoy and share music
</translate> </translate>
</h2> </h2>
<p> <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. Funkwhale is a community-driven project that lets you listen and share music and audio within a decentralized, open network.
</translate> </translate>
</p> </p>
@ -88,19 +88,19 @@ const headerStyle = computed(() => {
class="signup-form content" class="signup-form content"
> >
<h3 class="header"> <h3 class="header">
<translate translate-context="*/Signup/Title"> <translate >
Sign up Sign up
</translate> </translate>
</h3> </h3>
<template v-if="openRegistrations"> <template v-if="openRegistrations">
<p> <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! Sign up now to keep a track of your favorites, create playlists, discover new content and much more!
</translate> </translate>
</p> </p>
<p v-if="defaultUploadQuota"> <p v-if="defaultUploadQuota">
<translate <translate
translate-context="Content/About/Paragraph"
:translate-params="{quota: defaultUploadQuota}" :translate-params="{quota: defaultUploadQuota}"
> >
Users on this pod also get %{ quota } of free storage to upload their own content! Users on this pod also get %{ quota } of free storage to upload their own content!
@ -112,7 +112,7 @@ const headerStyle = computed(() => {
/> />
</template> </template>
<div v-else> <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. Registrations are closed on this pod. You can signup on another pod using the link below.
</p> </p>
@ -121,7 +121,7 @@ const headerStyle = computed(() => {
rel="noopener" rel="noopener"
href="https://funkwhale.audio/#get-started" href="https://funkwhale.audio/#get-started"
> >
<translate translate-context="Content/About/Link">Find another pod</translate> <translate >Find another pod</translate>
&nbsp;<i class="external alternate icon" /> &nbsp;<i class="external alternate icon" />
</a> </a>
</div> </div>
@ -131,12 +131,12 @@ const headerStyle = computed(() => {
class="signup-form content" class="signup-form content"
> >
<h3 class="header"> <h3 class="header">
<translate translate-context="*/Signup/Title"> <translate >
Sign up Sign up
</translate> </translate>
<div class="ui positive message"> <div class="ui positive message">
<div class="header"> <div class="header">
<translate translate-context="Content/About/Message"> <translate >
You're already signed in! You're already signed in!
</translate> </translate>
</div> </div>
@ -164,7 +164,7 @@ const headerStyle = computed(() => {
id="description" id="description"
class="ui header" class="ui header"
> >
<translate translate-context="Content/About/Header"> <translate >
About this pod About this pod
</translate> </translate>
</h3> </h3>
@ -175,7 +175,7 @@ const headerStyle = computed(() => {
{{ shortDescription }} {{ shortDescription }}
</div> </div>
<p v-else> <p v-else>
<translate translate-context="Content/About/Paragraph"> <translate >
No description available. No description available.
</translate> </translate>
</p> </p>
@ -188,7 +188,7 @@ const headerStyle = computed(() => {
<span class="ui big text"><strong>{{ stats.users.toLocaleString($store.state.ui.momentLocale) }}</strong></span> <span class="ui big text"><strong>{{ stats.users.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br> <br>
<translate <translate
translate-context="Content/About/*"
:translate-n="stats.users" :translate-n="stats.users"
translate-plural="active users" translate-plural="active users"
>active user</translate> >active user</translate>
@ -213,7 +213,7 @@ const headerStyle = computed(() => {
to="/about/pod" to="/about/pod"
class="ui fluid basic secondary button" class="ui fluid basic secondary button"
> >
<translate translate-context="Content/About/Paragraph"> <translate >
Learn More Learn More
</translate> </translate>
</router-link> </router-link>
@ -234,12 +234,12 @@ const headerStyle = computed(() => {
id="description" id="description"
class="ui header" class="ui header"
> >
<translate translate-context="Content/About/Header"> <translate >
Browse public content Browse public content
</translate> </translate>
</h3> </h3>
<p> <p>
<translate translate-context="Content/About/Paragraph"> <translate >
Listen to public albums and playlists shared on this pod. Listen to public albums and playlists shared on this pod.
</translate> </translate>
</p> </p>
@ -255,11 +255,11 @@ const headerStyle = computed(() => {
id="description" id="description"
class="ui header" class="ui header"
> >
<translate translate-context="Content/About/Header">Find another pod</translate> <translate >Find another pod</translate>
&nbsp;<i class="external alternate icon" /> &nbsp;<i class="external alternate icon" />
</h3> </h3>
<p> <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> </p>
</div> </div>
</a> </a>
@ -273,11 +273,11 @@ const headerStyle = computed(() => {
id="description" id="description"
class="ui header" class="ui header"
> >
<translate translate-context="Content/About/Header">Find an app</translate> <translate >Find an app</translate>
&nbsp;<i class="external alternate icon" /> &nbsp;<i class="external alternate icon" />
</h3> </h3>
<p> <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> </p>
</div> </div>
</a> </a>
@ -287,7 +287,7 @@ const headerStyle = computed(() => {
to="/about/pod" to="/about/pod"
class="ui right floated basic secondary button" class="ui right floated basic secondary button"
> >
<translate translate-context="Content/About/Paragraph"> <translate >
About this pod About this pod
</translate> </translate>
<i class="icon arrow right" /> <i class="icon arrow right" />

View File

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { humanSize } from '~/utils/filters' import { humanSize } from '~/utils/filters'
import { useGettext } from 'vue3-gettext'
import { useStore } from '~/store' import { useStore } from '~/store'
import { get } from 'lodash-es' import { get } from 'lodash-es'
import { computed } from 'vue' import { computed } from 'vue'
@ -9,6 +8,7 @@ import axios from 'axios'
import useMarkdown from '~/composables/useMarkdown' import useMarkdown from '~/composables/useMarkdown'
import type { NodeInfo } from '~/store/instance' import type { NodeInfo } from '~/store/instance'
import { useI18n } from 'vue-i18n'
const store = useStore() const store = useStore()
const nodeinfo = computed(() => store.state.instance.nodeinfo) const nodeinfo = computed(() => store.state.instance.nodeinfo)
@ -19,9 +19,9 @@ const fetchData = async () => {
} }
fetchData() fetchData()
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/About/Title', 'About') title: t('About')
})) }))
const podName = computed(() => get(nodeinfo.value, 'metadata.nodeName') || 'Funkwhale') const podName = computed(() => get(nodeinfo.value, 'metadata.nodeName') || 'Funkwhale')
@ -99,7 +99,7 @@ const headerStyle = computed(() => {
to="/about/pod" to="/about/pod"
class="item" class="item"
> >
<translate translate-context="Content/About/Header"> <translate >
About this pod About this pod
</translate> </translate>
</router-link> </router-link>
@ -107,7 +107,7 @@ const headerStyle = computed(() => {
to="/about/pod#rules" to="/about/pod#rules"
class="item" class="item"
> >
<translate translate-context="Content/About/Header"> <translate >
Rules Rules
</translate> </translate>
</router-link> </router-link>
@ -115,7 +115,7 @@ const headerStyle = computed(() => {
to="/about/pod#terms" to="/about/pod#terms"
class="item" class="item"
> >
<translate translate-context="Content/About/Header"> <translate >
Terms and privacy policy Terms and privacy policy
</translate> </translate>
</router-link> </router-link>
@ -123,7 +123,7 @@ const headerStyle = computed(() => {
to="/about/pod#features" to="/about/pod#features"
class="item" class="item"
> >
<translate translate-context="Content/About/Header"> <translate >
Features Features
</translate> </translate>
</router-link> </router-link>
@ -132,7 +132,7 @@ const headerStyle = computed(() => {
to="/about/pod#statistics" to="/about/pod#statistics"
class="item" class="item"
> >
<translate translate-context="Content/About/Header"> <translate >
Statistics Statistics
</translate> </translate>
</router-link> </router-link>
@ -144,7 +144,7 @@ const headerStyle = computed(() => {
id="description about-this-pod" id="description about-this-pod"
class="ui header" class="ui header"
> >
<translate translate-context="Content/About/Header"> <translate >
About this pod About this pod
</translate> </translate>
</h2> </h2>
@ -153,7 +153,7 @@ const headerStyle = computed(() => {
:html="longDescription" :html="longDescription"
/> />
<p v-else> <p v-else>
<translate translate-context="Content/About/Paragraph"> <translate >
No description available. No description available.
</translate> </translate>
</p> </p>
@ -162,7 +162,7 @@ const headerStyle = computed(() => {
id="rules" id="rules"
class="ui header" class="ui header"
> >
<translate translate-context="Content/About/Header"> <translate >
Rules Rules
</translate> </translate>
</h3> </h3>
@ -171,7 +171,7 @@ const headerStyle = computed(() => {
:html="rules" :html="rules"
/> />
<p v-else> <p v-else>
<translate translate-context="Content/About/Paragraph"> <translate >
No rules available. No rules available.
</translate> </translate>
</p> </p>
@ -180,7 +180,7 @@ const headerStyle = computed(() => {
id="terms" id="terms"
class="ui header" class="ui header"
> >
<translate translate-context="Content/About/Header"> <translate >
Terms and privacy policy Terms and privacy policy
</translate> </translate>
</h3> </h3>
@ -189,7 +189,7 @@ const headerStyle = computed(() => {
:html="terms" :html="terms"
/> />
<p v-else> <p v-else>
<translate translate-context="Content/About/Paragraph"> <translate >
No terms available. No terms available.
</translate> </translate>
</p> </p>
@ -198,7 +198,7 @@ const headerStyle = computed(() => {
id="features" id="features"
class="header" class="header"
> >
<translate translate-context="Content/About/Header/Name"> <translate >
Features Features
</translate> </translate>
</h3> </h3>
@ -208,7 +208,7 @@ const headerStyle = computed(() => {
<tbody> <tbody>
<tr> <tr>
<td> <td>
<translate translate-context="*/*/*"> <translate >
Funkwhale version Funkwhale version
</translate> </translate>
</td> </td>
@ -225,13 +225,13 @@ const headerStyle = computed(() => {
class="right aligned" class="right aligned"
> >
<span class="features-status ui text"> <span class="features-status ui text">
<translate translate-context="*/*/*">N/A</translate> <translate >N/A</translate>
</span> </span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="*/*/*"> <translate >
Federation Federation
</translate> </translate>
</td> </td>
@ -241,7 +241,7 @@ const headerStyle = computed(() => {
> >
<span class="features-status ui text"> <span class="features-status ui text">
<i class="check icon" /> <i class="check icon" />
<translate translate-context="*/*/*/State of feature">Enabled</translate> <translate >Enabled</translate>
</span> </span>
</td> </td>
<td <td
@ -250,13 +250,13 @@ const headerStyle = computed(() => {
> >
<span class="features-status ui text"> <span class="features-status ui text">
<i class="x icon" /> <i class="x icon" />
<translate translate-context="*/*/*/State of feature">Disabled</translate> <translate >Disabled</translate>
</span> </span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="*/*/*"> <translate >
Allow-list Allow-list
</translate> </translate>
</td> </td>
@ -266,7 +266,7 @@ const headerStyle = computed(() => {
> >
<span class="features-status ui text"> <span class="features-status ui text">
<i class="check icon" /> <i class="check icon" />
<translate translate-context="*/*/*/State of feature">Enabled</translate> <translate >Enabled</translate>
</span> </span>
</td> </td>
<td <td
@ -275,7 +275,7 @@ const headerStyle = computed(() => {
> >
<span class="features-status ui text"> <span class="features-status ui text">
<i class="x icon" /> <i class="x icon" />
<translate translate-context="*/*/*/State of feature">Disabled</translate> <translate >Disabled</translate>
</span> </span>
</td> </td>
</tr> </tr>
@ -287,7 +287,7 @@ const headerStyle = computed(() => {
<tbody> <tbody>
<tr> <tr>
<td> <td>
<translate translate-context="*/*/*"> <translate >
Anonymous access Anonymous access
</translate> </translate>
</td> </td>
@ -297,7 +297,7 @@ const headerStyle = computed(() => {
> >
<span class="features-status ui text"> <span class="features-status ui text">
<i class="check icon" /> <i class="check icon" />
<translate translate-context="*/*/*/State of feature">Enabled</translate> <translate >Enabled</translate>
</span> </span>
</td> </td>
<td <td
@ -306,13 +306,13 @@ const headerStyle = computed(() => {
> >
<span class="features-status ui text"> <span class="features-status ui text">
<i class="x icon" /> <i class="x icon" />
<translate translate-context="*/*/*/State of feature">Disabled</translate> <translate >Disabled</translate>
</span> </span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="*/*/*"> <translate >
Registrations Registrations
</translate> </translate>
</td> </td>
@ -322,7 +322,7 @@ const headerStyle = computed(() => {
> >
<span class="features-status ui text"> <span class="features-status ui text">
<i class="check icon" /> <i class="check icon" />
<translate translate-context="*/*/*/State of registrations">Open</translate> <translate >Open</translate>
</span> </span>
</td> </td>
<td <td
@ -331,13 +331,13 @@ const headerStyle = computed(() => {
> >
<span class="features-status ui text"> <span class="features-status ui text">
<i class="x icon" /> <i class="x icon" />
<translate translate-context="*/*/*/State of registrations">Closed</translate> <translate >Closed</translate>
</span> </span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="*/*/*"> <translate >
Upload quota Upload quota
</translate> </translate>
</td> </td>
@ -354,7 +354,7 @@ const headerStyle = computed(() => {
class="right aligned" class="right aligned"
> >
<span class="features-status ui text"> <span class="features-status ui text">
<translate translate-context="*/*/*">N/A</translate> <translate >N/A</translate>
</span> </span>
</td> </td>
</tr> </tr>
@ -368,7 +368,7 @@ const headerStyle = computed(() => {
id="statistics" id="statistics"
class="header" class="header"
> >
<translate translate-context="Content/About/Header"> <translate >
Statistics Statistics
</translate> </translate>
</h3> </h3>
@ -395,7 +395,7 @@ const headerStyle = computed(() => {
<span class="ui big text"><strong>{{ stats.artists.toLocaleString($store.state.ui.momentLocale) }}</strong></span> <span class="ui big text"><strong>{{ stats.artists.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br> <br>
<translate <translate
translate-context="Content/About/*"
:translate-n="stats.artists" :translate-n="stats.artists"
translate-plural="artists" translate-plural="artists"
>artist</translate> >artist</translate>
@ -409,7 +409,7 @@ const headerStyle = computed(() => {
<span class="ui big text"><strong>{{ stats.albums.toLocaleString($store.state.ui.momentLocale) }}</strong></span> <span class="ui big text"><strong>{{ stats.albums.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br> <br>
<translate <translate
translate-context="Content/About/*"
:translate-n="stats.albums" :translate-n="stats.albums"
translate-plural="albums" translate-plural="albums"
>album</translate> >album</translate>
@ -423,7 +423,7 @@ const headerStyle = computed(() => {
<span class="ui big text"><strong>{{ stats.tracks.toLocaleString($store.state.ui.momentLocale) }}</strong></span> <span class="ui big text"><strong>{{ stats.tracks.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br> <br>
<translate <translate
translate-context="Content/About/*"
:translate-n="stats.tracks" :translate-n="stats.tracks"
translate-plural="tracks" translate-plural="tracks"
>track</translate> >track</translate>
@ -437,7 +437,7 @@ const headerStyle = computed(() => {
<span class="ui big text"><strong>{{ stats.users.toLocaleString($store.state.ui.momentLocale) }}</strong></span> <span class="ui big text"><strong>{{ stats.users.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br> <br>
<translate <translate
translate-context="Content/About/*"
:translate-n="stats.users" :translate-n="stats.users"
translate-plural="active users" translate-plural="active users"
>active user</translate> >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> <span class="ui big text"><strong>{{ stats.listenings.toLocaleString($store.state.ui.momentLocale) }}</strong></span>
<br> <br>
<translate <translate
translate-context="Content/About/*"
:translate-n="stats.listenings" :translate-n="stats.listenings"
translate-plural="listenings" translate-plural="listenings"
>listening</translate> >listening</translate>
@ -465,7 +465,7 @@ const headerStyle = computed(() => {
id="contact" id="contact"
class="ui header" class="ui header"
> >
<translate translate-context="Content/About/Header"> <translate >
Contact Contact
</translate> </translate>
</h3> </h3>
@ -474,7 +474,7 @@ const headerStyle = computed(() => {
:href="`mailto:${contactEmail}`" :href="`mailto:${contactEmail}`"
> >
<translate <translate
translate-context="Content/About/Email"
:translate-params="{ email: contactEmail }" :translate-params="{ email: contactEmail }"
>Send us an email: {{ contactEmail }}</translate> >Send us an email: {{ contactEmail }}</translate>
</a> </a>
@ -487,7 +487,7 @@ const headerStyle = computed(() => {
class="ui left floated basic secondary button" class="ui left floated basic secondary button"
> >
<i class="icon arrow left" /> <i class="icon arrow left" />
<translate translate-context="Content/About/Paragraph"> <translate >
Introduction Introduction
</translate> </translate>
</router-link> </router-link>

View File

@ -9,12 +9,12 @@ import { humanSize } from '~/utils/filters'
import { useStore } from '~/store' import { useStore } from '~/store'
import { computed } from 'vue' import { computed } from 'vue'
import { whenever } from '@vueuse/core' import { whenever } from '@vueuse/core'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/Home/Title', 'Home') title: t('Home')
})) }))
const store = useStore() const store = useStore()
@ -70,13 +70,12 @@ whenever(() => store.state.auth.authenticated, () => {
> >
<div class="segment-content"> <div class="segment-content">
<h1 class="ui center aligned large header"> <h1 class="ui center aligned large header">
<span <translate
v-translate="{podName: podName}"
translate-context="Content/Home/Header"
:translate-params="{podName: podName}" :translate-params="{podName: podName}"
tag="span"
> >
Welcome to %{ podName }! Welcome to %{ podName }!
</span> </translate>
<div <div
v-if="shortDescription" v-if="shortDescription"
class="sub header" class="sub header"
@ -90,7 +89,7 @@ whenever(() => store.state.auth.authenticated, () => {
<div class="ui stackable grid"> <div class="ui stackable grid">
<div class="ten wide column"> <div class="ten wide column">
<h2 class="header"> <h2 class="header">
<translate translate-context="Content/Home/Header"> <translate >
About this Funkwhale pod About this Funkwhale pod
</translate> </translate>
</h2> </h2>
@ -101,7 +100,7 @@ whenever(() => store.state.auth.authenticated, () => {
<div class="ui stackable grid"> <div class="ui stackable grid">
<div class="eight wide column"> <div class="eight wide column">
<p v-if="!longDescription"> <p v-if="!longDescription">
<translate translate-context="Content/Home/Paragraph"> <translate >
No description available. No description available.
</translate> </translate>
</p> </p>
@ -126,7 +125,7 @@ whenever(() => store.state.auth.authenticated, () => {
class="ui link" class="ui link"
:to="{name: 'about'}" :to="{name: 'about'}"
> >
<translate translate-context="Content/Home/Link"> <translate >
Learn more Learn more
</translate> </translate>
</router-link> </router-link>
@ -143,7 +142,7 @@ whenever(() => store.state.auth.authenticated, () => {
class="ui link" class="ui link"
:to="{name: 'about', hash: '#rules'}" :to="{name: 'about', hash: '#rules'}"
> >
<translate translate-context="Content/Home/Link"> <translate >
Server rules Server rules
</translate> </translate>
</router-link> </router-link>
@ -155,13 +154,13 @@ whenever(() => store.state.auth.authenticated, () => {
<div class="eight wide column"> <div class="eight wide column">
<template v-if="stats"> <template v-if="stats">
<h3 class="sub header"> <h3 class="sub header">
<translate translate-context="Content/Home/Header"> <translate >
Statistics Statistics
</translate> </translate>
</h3> </h3>
<p> <p>
<i class="user icon" /><translate <i class="user icon" /><translate
translate-context="Content/Home/Stat"
:translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }" :translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }"
:translate-n="stats.users" :translate-n="stats.users"
translate-plural="%{ count } active users" translate-plural="%{ count } active users"
@ -182,7 +181,7 @@ whenever(() => store.state.auth.authenticated, () => {
</template> </template>
<template v-if="contactEmail"> <template v-if="contactEmail">
<h3 class="sub header"> <h3 class="sub header">
<translate translate-context="Content/Home/Header/Name"> <translate >
Contact Contact
</translate> </translate>
</h3> </h3>
@ -207,34 +206,28 @@ whenever(() => store.state.auth.authenticated, () => {
<div class="ui stackable grid"> <div class="ui stackable grid">
<div class="four wide column"> <div class="four wide column">
<h3 class="header"> <h3 class="header">
<translate translate-context="Footer/*/Title/Short"> <translate >
About Funkwhale About Funkwhale
</translate> </translate>
</h3> </h3>
<p <translate tag="p">
v-translate
translate-context="Content/Home/Paragraph"
>
This pod runs Funkwhale, a community-driven project that lets you listen and share music and audio within a decentralized, open network. This pod runs Funkwhale, a community-driven project that lets you listen and share music and audio within a decentralized, open network.
</p> </translate>
<p <translate tag="p">
v-translate
translate-context="Content/Home/Paragraph"
>
Funkwhale is free and developed by a friendly community of volunteers. Funkwhale is free and developed by a friendly community of volunteers.
</p> </translate>
<a <a
target="_blank" target="_blank"
rel="noopener" rel="noopener"
href="https://funkwhale.audio" href="https://funkwhale.audio"
> >
<i class="external alternate icon" /> <i class="external alternate icon" />
<translate translate-context="Content/Home/Link">Visit funkwhale.audio</translate> <translate >Visit funkwhale.audio</translate>
</a> </a>
</div> </div>
<div class="four wide column"> <div class="four wide column">
<h3 class="header"> <h3 class="header">
<translate translate-context="Head/Login/Title"> <translate >
Log In Log In
</translate> </translate>
</h3> </h3>
@ -246,19 +239,19 @@ whenever(() => store.state.auth.authenticated, () => {
</div> </div>
<div class="four wide column"> <div class="four wide column">
<h3 class="header"> <h3 class="header">
<translate translate-context="*/Signup/Title"> <translate >
Sign up Sign up
</translate> </translate>
</h3> </h3>
<template v-if="openRegistrations"> <template v-if="openRegistrations">
<p> <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! Sign up now to keep track of your favorites, create playlists, discover new content and much more!
</translate> </translate>
</p> </p>
<p v-if="defaultUploadQuota"> <p v-if="defaultUploadQuota">
<translate <translate
translate-context="Content/Home/Paragraph"
:translate-params="{quota: humanSize(defaultUploadQuota * 1000 * 1000)}" :translate-params="{quota: humanSize(defaultUploadQuota * 1000 * 1000)}"
> >
Users on this pod also get %{ quota } of free storage to upload their own content! 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> </template>
<div v-else> <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. Registrations are closed on this pod. You can signup on another pod using the link below.
</p> </p>
<a <a
@ -279,14 +272,14 @@ whenever(() => store.state.auth.authenticated, () => {
href="https://funkwhale.audio/#get-started" href="https://funkwhale.audio/#get-started"
> >
<i class="external alternate icon" /> <i class="external alternate icon" />
<translate translate-context="Content/Home/Link">Find another pod</translate> <translate >Find another pod</translate>
</a> </a>
</div> </div>
</div> </div>
<div class="four wide column"> <div class="four wide column">
<h3 class="header"> <h3 class="header">
<translate translate-context="Content/Home/Header"> <translate >
Useful links Useful links
</translate> </translate>
</h3> </h3>
@ -299,12 +292,12 @@ whenever(() => store.state.auth.authenticated, () => {
class="header" class="header"
to="/library" to="/library"
> >
<translate translate-context="Content/Home/Link"> <translate >
Browse public content Browse public content
</translate> </translate>
</router-link> </router-link>
<div class="description"> <div class="description">
<translate translate-context="Content/Home/Link"> <translate >
Listen to public albums and playlists shared on this pod Listen to public albums and playlists shared on this pod
</translate> </translate>
</div> </div>
@ -319,10 +312,10 @@ whenever(() => store.state.auth.authenticated, () => {
target="_blank" target="_blank"
rel="noopener" rel="noopener"
> >
<translate translate-context="Content/Home/Link">Mobile apps</translate> <translate >Mobile apps</translate>
</a> </a>
<div class="description"> <div class="description">
<translate translate-context="Content/Home/Link"> <translate >
Use Funkwhale on other devices with our apps Use Funkwhale on other devices with our apps
</translate> </translate>
</div> </div>
@ -337,10 +330,10 @@ whenever(() => store.state.auth.authenticated, () => {
target="_blank" target="_blank"
rel="noopener" rel="noopener"
> >
<translate translate-context="Content/Home/Link">User guides</translate> <translate >User guides</translate>
</a> </a>
<div class="description"> <div class="description">
<translate translate-context="Content/Home/Link"> <translate >
Discover everything you need to know about Funkwhale and its features Discover everything you need to know about Funkwhale and its features
</translate> </translate>
</div> </div>
@ -359,12 +352,12 @@ whenever(() => store.state.auth.authenticated, () => {
:limit="10" :limit="10"
> >
<template #title> <template #title>
<translate translate-context="Content/Home/Title"> <translate >
Recently added albums Recently added albums
</translate> </translate>
</template> </template>
<router-link to="/library"> <router-link to="/library">
<translate translate-context="Content/Home/Link"> <translate >
View more View more
</translate> </translate>
<div class="ui hidden divider" /> <div class="ui hidden divider" />
@ -372,7 +365,7 @@ whenever(() => store.state.auth.authenticated, () => {
</album-widget> </album-widget>
<div class="ui hidden section divider" /> <div class="ui hidden section divider" />
<h3 class="ui header"> <h3 class="ui header">
<translate translate-context="*/*/*"> <translate >
New channels New channels
</translate> </translate>
</h3> </h3>

View File

@ -1,12 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
const path = window.location.href const path = window.location.href
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/*/Title', 'Page Not Found') title: t('Page Not Found')
})) }))
</script> </script>
@ -20,13 +20,13 @@ const labels = computed(() => ({
<h1 class="ui huge header"> <h1 class="ui huge header">
<i class="warning icon" /> <i class="warning icon" />
<div class="content"> <div class="content">
<translate translate-context="Content/*/Title"> <translate >
Page not found! Page not found!
</translate> </translate>
</div> </div>
</h1> </h1>
<p> <p>
<translate translate-context="Content/*/Paragraph"> <translate >
Sorry, the page you asked for does not exist: Sorry, the page you asked for does not exist:
</translate> </translate>
</p> </p>
@ -36,7 +36,7 @@ const labels = computed(() => ({
class="ui icon labeled right button" class="ui icon labeled right button"
to="/" to="/"
> >
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Go to home page Go to home page
</translate> </translate>
<i class="right arrow icon" /> <i class="right arrow icon" />

View File

@ -4,7 +4,6 @@ import type { QueueItemSource } from '~/types'
import { whenever, watchDebounced, useCurrentElement, useScrollLock, useFullscreen, useIdle, refAutoReset, useStorage } from '@vueuse/core' import { whenever, watchDebounced, useCurrentElement, useScrollLock, useFullscreen, useIdle, refAutoReset, useStorage } from '@vueuse/core'
import { nextTick, ref, computed, watchEffect, onMounted } from 'vue' import { nextTick, ref, computed, watchEffect, onMounted } from 'vue'
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap' import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
import { useGettext } from 'vue3-gettext'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -18,6 +17,11 @@ import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue' import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
import PlayerControls from '~/components/audio/PlayerControls.vue' import PlayerControls from '~/components/audio/PlayerControls.vue'
import MilkDrop from '~/components/audio/visualizer/MilkDrop.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 VirtualList from '~/components/vui/list/VirtualList.vue'
import QueueItem from '~/components/QueueItem.vue' import QueueItem from '~/components/QueueItem.vue'
@ -49,24 +53,24 @@ const { currentSound } = useTracks()
const queueModal = ref() const queueModal = ref()
const { activate, deactivate } = useFocusTrap(queueModal, { allowOutsideClick: true, preventScroll: true }) const { activate, deactivate } = useFocusTrap(queueModal, { allowOutsideClick: true, preventScroll: true })
const { $pgettext } = useGettext() const { t } = useI18n()
const scrollLock = useScrollLock(document.body) const scrollLock = useScrollLock(document.body)
const store = useStore() const store = useStore()
const labels = computed(() => ({ const labels = computed(() => ({
queue: $pgettext('*/*/*', 'Queue'), queue: t('Queue'),
populating: $pgettext('*/*/*', 'Fetching radio track'), populating: t('Fetching radio track'),
duration: $pgettext('*/*/*', 'Duration'), duration: t('Duration'),
addArtistContentFilter: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Hide content from this artist…'), addArtistContentFilter: t('Hide content from this artist…'),
restart: $pgettext('*/*/*', 'Restart track'), restart: t('Restart track'),
previous: $pgettext('*/*/*', 'Previous track'), previous: t('Previous track'),
next: $pgettext('*/*/*', 'Next track'), next: t('Next track'),
pause: $pgettext('*/*/*', 'Pause'), pause: t('Pause'),
play: $pgettext('*/*/*', 'Play'), play: t('Play'),
fullscreen: $pgettext('*/*/*', 'Fullscreen'), fullscreen: t('Fullscreen'),
exitFullscreen: $pgettext('*/*/*', 'Exit fullscreen'), exitFullscreen: t('Exit fullscreen'),
showCoverArt: $pgettext('*/*/*', 'Show cover art'), showCoverArt: t('Show cover art'),
showVisualizer: $pgettext('*/*/*', 'Show visualizer') showVisualizer: t('Show visualizer')
})) }))
watchEffect(async () => { watchEffect(async () => {
@ -125,10 +129,11 @@ const queueItems = computed(() => queue.value.map((track, index) => ({
...track, ...track,
key: `${index}-${track.id}`, key: `${index}-${track.id}`,
labels: { labels: {
remove: $pgettext('*/*/*', 'Remove'), remove: t('Remove'),
selectTrack: $pgettext('*/*/*', 'Select track'), selectTrack: t('Select track'),
favorite: $pgettext('*/*/*', 'Favorite track') favorite: t('Favorite track')
} },
duration: time.durationFormatted(track.uploads[0]?.duration ?? 0) ?? ''
}) as QueueItemSource)) }) as QueueItemSource))
const reorderTracks = async (from: number, to: number) => { 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" class="ui small warning message"
> >
<h3 class="header"> <h3 class="header">
<translate translate-context="Sidebar/Player/Error message.Title"> <translate >
The track cannot be loaded The track cannot be loaded
</translate> </translate>
</h3> </h3>
<p v-if="hasNext && isPlaying"> <p v-if="hasNext && isPlaying">
<translate translate-context="Sidebar/Player/Error message.Paragraph"> <translate >
The next track will play automatically in a few seconds The next track will play automatically in a few seconds
</translate> </translate>
<i class="loading spinner icon" /> <i class="loading spinner icon" />
</p> </p>
<p> <p>
<translate translate-context="Sidebar/Player/Error message.Paragraph"> <translate >
You may have a connectivity issue. You may have a connectivity issue.
</translate> </translate>
</p> </p>
@ -408,7 +413,7 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
class="ui right floated basic button" class="ui right floated basic button"
@click="$store.commit('ui/queueFocused', null)" @click="$store.commit('ui/queueFocused', null)"
> >
<translate translate-context="*/Queue/*/Verb"> <translate >
Close Close
</translate> </translate>
</button> </button>
@ -416,7 +421,7 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
class="ui right floated basic button danger" class="ui right floated basic button danger"
@click="clear" @click="clear"
> >
<translate translate-context="*/Queue/*/Verb"> <translate >
Clear Clear
</translate> </translate>
</button> </button>
@ -424,7 +429,6 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
<div class="sub header"> <div class="sub header">
<div> <div>
<translate <translate
translate-context="Sidebar/Queue/Text"
:translate-params="{index: currentIndex + 1, length: queue.length}" :translate-params="{index: currentIndex + 1, length: queue.length}"
> >
Track %{ index } of %{ length } Track %{ index } of %{ length }
@ -473,12 +477,12 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
> >
<div class="content"> <div class="content">
<h3 class="header"> <h3 class="header">
<i class="feed icon" /> <translate translate-context="Sidebar/Player/Title"> <i class="feed icon" /> <translate >
You have a radio playing You have a radio playing
</translate> </translate>
</h3> </h3>
<p> <p>
<translate translate-context="Sidebar/Player/Paragraph"> <translate >
New tracks will be appended here automatically. New tracks will be appended here automatically.
</translate> </translate>
</p> </p>
@ -486,7 +490,7 @@ const coverType = useStorage('queue:cover-type', CoverType.COVER_ART)
class="ui basic primary button" class="ui basic primary button"
@click="$store.dispatch('radios/stop')" @click="$store.dispatch('radios/stop')"
> >
<translate translate-context="*/Player/Button.Label/Short, Verb"> <translate >
Stop radio Stop radio
</translate> </translate>
</button> </button>

View File

@ -2,7 +2,7 @@
import type { BackendError } from '~/types' import type { BackendError } from '~/types'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -37,17 +37,17 @@ const type = ref(props.initialType)
const id = ref(props.initialId) const id = ref(props.initialId)
const errors = ref([] as string[]) const errors = ref([] as string[])
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: type.value === 'rss' title: type.value === 'rss'
? $pgettext('Head/Fetch/Title', 'Subscribe to a podcast RSS feed') ? t('Subscribe to a podcast RSS feed')
: $pgettext('Head/Fetch/Title', 'Subscribe to a podcast hosted on the Fediverse'), : t('Subscribe to a podcast hosted on the Fediverse'),
fieldLabel: type.value === 'rss' fieldLabel: type.value === 'rss'
? $pgettext('*/*/*', 'RSS feed location') ? t('RSS feed location')
: $pgettext('*/*/*', 'Fediverse object'), : t('Fediverse object'),
fieldPlaceholder: type.value === 'rss' fieldPlaceholder: type.value === 'rss'
? $pgettext('Head/Fetch/Field.Placeholder', 'https://website.example.com/rss.xml') ? t('https://website.example.com/rss.xml')
: $pgettext('Head/Fetch/Field.Placeholder', '@username@example.com') : t('@username@example.com')
})) }))
const obj = ref() const obj = ref()
@ -117,7 +117,7 @@ const createFetch = async () => {
obj.value = response.data obj.value = response.data
if (response.data.status === 'errored' || response.data.status === 'skipped') { 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) { } catch (error) {
errors.value = (error as BackendError).backendErrors errors.value = (error as BackendError).backendErrors
@ -172,7 +172,7 @@ watch(() => props.initialId, () => {
@click.prevent="type = 'rss'" @click.prevent="type = 'rss'"
> >
<i class="feed icon" /> <i class="feed icon" />
<translate translate-context="Content/Search/Input.Label/Noun"> <translate >
RSS RSS
</translate> </translate>
</button> </button>
@ -182,7 +182,7 @@ watch(() => props.initialId, () => {
@click.prevent="type = 'artists'" @click.prevent="type = 'artists'"
> >
<i class="globe icon" /> <i class="globe icon" />
<translate translate-context="Content/Search/Input.Label/Noun"> <translate >
Fediverse Fediverse
</translate> </translate>
</button> </button>
@ -199,7 +199,7 @@ watch(() => props.initialId, () => {
class="ui negative message" class="ui negative message"
> >
<h3 class="header"> <h3 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Error while fetching object Error while fetching object
</translate> </translate>
</h3> </h3>
@ -217,12 +217,12 @@ watch(() => props.initialId, () => {
{{ labels.fieldLabel }} {{ labels.fieldLabel }}
</label> </label>
<p v-if="type === 'rss'"> <p v-if="type === 'rss'">
<translate translate-context="Content/Fetch/Paragraph"> <translate >
Use this form to subscribe to an RSS feed from its URL. Use this form to subscribe to an RSS feed from its URL.
</translate> </translate>
</p> </p>
<p v-else-if="type === 'artists'"> <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. Use this form to subscribe to a channel hosted somewhere else on the Fediverse.
</translate> </translate>
</p> </p>
@ -241,7 +241,7 @@ watch(() => props.initialId, () => {
:class="['ui', 'primary', {loading: isLoading}, 'button']" :class="['ui', 'primary', {loading: isLoading}, 'button']"
:disabled="isLoading || !id || id.length === 0" :disabled="isLoading || !id || id.length === 0"
> >
<translate translate-context="Content/Search/Input.Label/Noun"> <translate >
Search Search
</translate> </translate>
</button> </button>
@ -252,7 +252,7 @@ watch(() => props.initialId, () => {
class="ui warning message" class="ui warning message"
> >
<p> <p>
<translate translate-context="Content/*/Error message.Title"> <translate >
This kind of object isn't supported yet This kind of object isn't supported yet
</translate> </translate>
</p> </p>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue' import { ref, computed, watch, nextTick } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { useStore } from '~/store' import { useStore } from '~/store'
import { uniq } from 'lodash-es' import { uniq } from 'lodash-es'
@ -38,7 +38,7 @@ const suggestedInstances = computed(() => {
watch(() => store.state.instance.instanceUrl, () => store.dispatch('instance/fetchSettings')) watch(() => store.state.instance.instanceUrl, () => store.dispatch('instance/fetchSettings'))
const { $pgettext } = useGettext() const { t } = useI18n()
const isError = ref(false) const isError = ref(false)
const isLoading = ref(false) const isLoading = ref(false)
const checkAndSwitch = async (url: string) => { const checkAndSwitch = async (url: string) => {
@ -51,7 +51,7 @@ const checkAndSwitch = async (url: string) => {
show.value = false show.value = false
store.commit('ui/addMessage', { 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() date: new Date()
}) })
@ -71,7 +71,7 @@ const checkAndSwitch = async (url: string) => {
@update:show="isError = false" @update:show="isError = false"
> >
<h3 class="header"> <h3 class="header">
<translate translate-context="Popup/Instance/Title"> <translate >
Choose your instance Choose your instance
</translate> </translate>
</h3> </h3>
@ -82,18 +82,18 @@ const checkAndSwitch = async (url: string) => {
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Popup/Instance/Error message.Title"> <translate >
It is not possible to connect to the given URL It is not possible to connect to the given URL
</translate> </translate>
</h4> </h4>
<ul class="list"> <ul class="list">
<li> <li>
<translate translate-context="Popup/Instance/Error message.List item"> <translate >
The server might be down The server might be down
</translate> </translate>
</li> </li>
<li> <li>
<translate translate-context="Popup/Instance/Error message.List item"> <translate >
The given address is not a Funkwhale server The given address is not a Funkwhale server
</translate> </translate>
</li> </li>
@ -107,7 +107,7 @@ const checkAndSwitch = async (url: string) => {
v-if="$store.state.instance.instanceUrl" v-if="$store.state.instance.instanceUrl"
v-translate="{url: $store.state.instance.instanceUrl, hostname: $store.getters['instance/domain'] }" v-translate="{url: $store.state.instance.instanceUrl, hostname: $store.getters['instance/domain'] }"
class="description" class="description"
translate-context="Popup/Login/Paragraph"
> >
You are currently connected to <a You are currently connected to <a
href="%{ url }" href="%{ url }"
@ -115,12 +115,12 @@ const checkAndSwitch = async (url: string) => {
>%{ hostname }&nbsp;<i class="external icon" /></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted. >%{ hostname }&nbsp;<i class="external icon" /></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted.
</p> </p>
<p v-else> <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. To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices.
</translate> </translate>
</p> </p>
<div class="field"> <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"> <div class="ui action input">
<input <input
id="instance-picker" id="instance-picker"
@ -132,7 +132,7 @@ const checkAndSwitch = async (url: string) => {
type="submit" type="submit"
:class="['ui', 'icon', {loading: isLoading}, 'button']" :class="['ui', 'icon', {loading: isLoading}, 'button']"
> >
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Submit Submit
</translate> </translate>
</button> </button>
@ -146,7 +146,7 @@ const checkAndSwitch = async (url: string) => {
> >
<div class="field"> <div class="field">
<h4> <h4>
<translate translate-context="Popup/Instance/List.Label"> <translate >
Suggested choices Suggested choices
</translate> </translate>
</h4> </h4>
@ -163,7 +163,7 @@ const checkAndSwitch = async (url: string) => {
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic cancel button"> <button class="ui basic cancel button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</button> </button>

View File

@ -2,7 +2,7 @@
import SemanticModal from '~/components/semantic/Modal.vue' import SemanticModal from '~/components/semantic/Modal.vue'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { computed } from 'vue' import { computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
interface Events { interface Events {
(e: 'update:show', show: boolean): void (e: 'update:show', show: boolean): void
@ -17,22 +17,22 @@ const props = defineProps<Props>()
const showRef = useVModel(props, 'show', emit) const showRef = useVModel(props, 'show', emit)
const { $pgettext } = useGettext() const { t } = useI18n()
const general = computed(() => [ const general = computed(() => [
{ {
title: $pgettext('Popup/Keyboard shortcuts/Title', 'General shortcuts'), title: t('General shortcuts'),
shortcuts: [ shortcuts: [
{ {
key: 'h', key: 'h',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Show available keyboard shortcuts') summary: t('Show available keyboard shortcuts')
}, },
{ {
key: 'shift + f', key: 'shift + f',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Focus searchbar') summary: t('Focus searchbar')
}, },
{ {
key: 'esc', 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(() => [ const player = computed(() => [
{ {
title: $pgettext('Popup/Keyboard shortcuts/Title', 'Audio player shortcuts'), title: t('Audio player shortcuts'),
shortcuts: [ shortcuts: [
{ {
key: 'p', key: 'p',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Pause/play the current track') summary: t('Pause/play the current track')
}, },
{ {
key: 'left', key: 'left',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Seek backwards 5s') summary: t('Seek backwards 5s')
}, },
{ {
key: 'right', key: 'right',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Seek forwards 5s') summary: t('Seek forwards 5s')
}, },
{ {
key: 'shift + left', key: 'shift + left',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Seek backwards 30s') summary: t('Seek backwards 30s')
}, },
{ {
key: 'shift + right', key: 'shift + right',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Seek forwards 30s') summary: t('Seek forwards 30s')
}, },
{ {
key: 'ctrl + shift + left', key: 'ctrl + shift + left',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Play previous track') summary: t('Play previous track')
}, },
{ {
key: 'ctrl + shift + right', key: 'ctrl + shift + right',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Play next track') summary: t('Play next track')
}, },
{ {
key: 'shift + up', key: 'shift + up',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Increase volume') summary: t('Increase volume')
}, },
{ {
key: 'shift + down', key: 'shift + down',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Decrease volume') summary: t('Decrease volume')
}, },
{ {
key: 'm', key: 'm',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Toggle mute') summary: t('Toggle mute')
}, },
{ {
key: 'e', key: 'e',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Expand queue/player view') summary: t('Expand queue/player view')
}, },
{ {
key: 'l', key: 'l',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Toggle queue looping') summary: t('Toggle queue looping')
}, },
{ {
key: 's', key: 's',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Shuffle queue') summary: t('Shuffle queue')
}, },
{ {
key: 'q', key: 'q',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Clear queue') summary: t('Clear queue')
}, },
{ {
key: 'f', key: 'f',
summary: $pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Toggle favorite') summary: t('Toggle favorite')
} }
] ]
} }
@ -110,7 +110,7 @@ const player = computed(() => [
<template> <template>
<semantic-modal v-model:show="showRef"> <semantic-modal v-model:show="showRef">
<header class="header"> <header class="header">
<translate translate-context="*/*/*/Noun"> <translate >
Keyboard shortcuts Keyboard shortcuts
</translate> </translate>
</header> </header>
@ -156,7 +156,7 @@ const player = computed(() => [
</section> </section>
<footer class="actions"> <footer class="actions">
<button class="ui basic cancel button"> <button class="ui basic cancel button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Close Close
</translate> </translate>
</button> </button>

View File

@ -1,20 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RouteRecordName } from 'vue-router' 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 UserModal from '~/components/common/UserModal.vue'
import Logo from '~/components/Logo.vue'
import SearchBar from '~/components/audio/SearchBar.vue' import SearchBar from '~/components/audio/SearchBar.vue'
import UserMenu from '~/components/common/UserMenu.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 useThemeList from '~/composables/useThemeList'
import useTheme from '~/composables/useTheme' 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 { interface Events {
(e: 'show:set-instance-modal'): void (e: 'show:set-instance-modal'): void
@ -30,7 +32,7 @@ defineProps<Props>()
const store = useStore() const store = useStore()
const { theme } = useTheme() const { theme } = useTheme()
const themes = useThemeList() const themes = useThemeList()
const { $pgettext } = useGettext() const { t } = useI18n()
const route = useRoute() const route = useRoute()
const isCollapsed = ref(true) 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 logoUrl = computed(() => store.state.auth.authenticated ? 'library.index' : 'index')
const labels = computed(() => ({ const labels = computed(() => ({
mainMenu: $pgettext('Sidebar/*/Hidden text', 'Main menu'), mainMenu: t('Main menu'),
selectTrack: $pgettext('Sidebar/Player/Hidden text', 'Play this track'), selectTrack: t('Play this track'),
pendingFollows: $pgettext('Sidebar/Notifications/Hidden text', 'Pending follow requests'), pendingFollows: t('Pending follow requests'),
pendingReviewEdits: $pgettext('Sidebar/Moderation/Hidden text', 'Pending review edits'), pendingReviewEdits: t('Pending review edits'),
pendingReviewReports: $pgettext('Sidebar/Moderation/Hidden text', 'Pending review reports'), pendingReviewReports: t('Pending review reports'),
language: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Language'), language: t('Language'),
theme: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Theme'), theme: t('Theme'),
addContent: $pgettext('*/Library/*/Verb', 'Add content'), addContent: t('Add content'),
administration: $pgettext('Sidebar/Admin/Title/Noun', 'Administration') administration: t('Administration')
})) }))
type SidebarMenuTabs = 'explore' | 'myLibrary' type SidebarMenuTabs = 'explore' | 'myLibrary'
@ -104,12 +106,6 @@ const showUserModal = ref(false)
const showLanguageModal = ref(false) const showLanguageModal = ref(false)
const showThemeModal = 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() const el = useCurrentElement()
watchEffect(() => { watchEffect(() => {
if (store.state.auth.authenticated) { if (store.state.auth.authenticated) {
@ -153,7 +149,7 @@ onMounted(() => {
</div> </div>
<div class="menu"> <div class="menu">
<h3 class="header"> <h3 class="header">
<translate translate-context="Sidebar/Admin/Title/Noun"> <translate >
Administration Administration
</translate> </translate>
</h3> </h3>
@ -170,7 +166,7 @@ onMounted(() => {
> >
{{ $store.state.ui.notifications.pendingReviewEdits }} {{ $store.state.ui.notifications.pendingReviewEdits }}
</div> </div>
<translate translate-context="*/*/*/Noun"> <translate >
Library Library
</translate> </translate>
</router-link> </router-link>
@ -186,7 +182,7 @@ onMounted(() => {
> >
{{ $store.state.ui.notifications.pendingReviewReports + $store.state.ui.notifications.pendingReviewRequests }} {{ $store.state.ui.notifications.pendingReviewReports + $store.state.ui.notifications.pendingReviewRequests }}
</div> </div>
<translate translate-context="*/Moderation/*"> <translate >
Moderation Moderation
</translate> </translate>
</router-link> </router-link>
@ -195,7 +191,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'manage.users.users.list'}" :to="{name: 'manage.users.users.list'}"
> >
<translate translate-context="*/*/*/Noun"> <translate >
Users Users
</translate> </translate>
</router-link> </router-link>
@ -204,7 +200,7 @@ onMounted(() => {
class="item" class="item"
:to="{path: '/manage/settings'}" :to="{path: '/manage/settings'}"
> >
<translate translate-context="*/*/*/Noun"> <translate >
Settings Settings
</translate> </translate>
</router-link> </router-link>
@ -300,12 +296,12 @@ onMounted(() => {
</div> </div>
<div class="content"> <div class="content">
<fieldset <fieldset
v-for="(language, key) in $language.available" v-for="(language, key) in SUPPORTED_LOCALES"
:key="key" :key="key"
> >
<input <input
:id="`${key}`" :id="`${key}`"
v-model="languageSelection" v-model="$i18n.locale"
type="radio" type="radio"
name="language" name="language"
:value="key" :value="key"
@ -366,7 +362,7 @@ onMounted(() => {
class="ui fluid tiny primary button" class="ui fluid tiny primary button"
:to="{name: 'login'}" :to="{name: 'login'}"
> >
<translate translate-context="*/Login/*/Verb"> <translate >
Login Login
</translate> </translate>
</router-link> </router-link>
@ -375,7 +371,7 @@ onMounted(() => {
class="ui fluid tiny button" class="ui fluid tiny button"
:to="{path: '/signup'}" :to="{path: '/signup'}"
> >
<translate translate-context="*/Signup/Link/Verb"> <translate >
Create an account Create an account
</translate> </translate>
</router-link> </router-link>
@ -389,7 +385,7 @@ onMounted(() => {
id="navigation-label" id="navigation-label"
class="visually-hidden" class="visually-hidden"
> >
<translate translate-context="*/*/*"> <translate >
Main navigation Main navigation
</translate> </translate>
</h1> </h1>
@ -411,7 +407,7 @@ onMounted(() => {
@click="expanded = 'explore'" @click="expanded = 'explore'"
@focus="expanded = 'explore'" @focus="expanded = 'explore'"
> >
<translate translate-context="*/*/*/Verb"> <translate >
Explore Explore
</translate> </translate>
<i <i
@ -424,7 +420,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'search'}" :to="{name: 'search'}"
> >
<i class="search icon" /><translate translate-context="Sidebar/Navigation/List item.Link/Verb"> <i class="search icon" /><translate >
Search Search
</translate> </translate>
</router-link> </router-link>
@ -433,7 +429,7 @@ onMounted(() => {
:to="{name: 'library.index'}" :to="{name: 'library.index'}"
active-class="_active" active-class="_active"
> >
<i class="music icon" /><translate translate-context="Sidebar/Navigation/List item.Link/Verb"> <i class="music icon" /><translate >
Browse Browse
</translate> </translate>
</router-link> </router-link>
@ -441,7 +437,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.podcasts.browse'}" :to="{name: 'library.podcasts.browse'}"
> >
<i class="podcast icon" /><translate translate-context="*/*/*"> <i class="podcast icon" /><translate >
Podcasts Podcasts
</translate> </translate>
</router-link> </router-link>
@ -449,7 +445,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.albums.browse'}" :to="{name: 'library.albums.browse'}"
> >
<i class="compact disc icon" /><translate translate-context="*/*/*"> <i class="compact disc icon" /><translate >
Albums Albums
</translate> </translate>
</router-link> </router-link>
@ -457,7 +453,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.artists.browse'}" :to="{name: 'library.artists.browse'}"
> >
<i class="user icon" /><translate translate-context="*/*/*"> <i class="user icon" /><translate >
Artists Artists
</translate> </translate>
</router-link> </router-link>
@ -465,7 +461,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.playlists.browse'}" :to="{name: 'library.playlists.browse'}"
> >
<i class="list icon" /><translate translate-context="*/*/*"> <i class="list icon" /><translate >
Playlists Playlists
</translate> </translate>
</router-link> </router-link>
@ -473,7 +469,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.radios.browse'}" :to="{name: 'library.radios.browse'}"
> >
<i class="feed icon" /><translate translate-context="*/*/*"> <i class="feed icon" /><translate >
Radios Radios
</translate> </translate>
</router-link> </router-link>
@ -490,7 +486,7 @@ onMounted(() => {
@click="expanded = 'myLibrary'" @click="expanded = 'myLibrary'"
@focus="expanded = 'myLibrary'" @focus="expanded = 'myLibrary'"
> >
<translate translate-context="*/*/*/Noun"> <translate >
My Library My Library
</translate> </translate>
<i <i
@ -503,7 +499,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.me'}" :to="{name: 'library.me'}"
> >
<i class="music icon" /><translate translate-context="Sidebar/Navigation/List item.Link/Verb"> <i class="music icon" /><translate >
Browse Browse
</translate> </translate>
</router-link> </router-link>
@ -511,7 +507,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.albums.me'}" :to="{name: 'library.albums.me'}"
> >
<i class="compact disc icon" /><translate translate-context="*/*/*"> <i class="compact disc icon" /><translate >
Albums Albums
</translate> </translate>
</router-link> </router-link>
@ -519,7 +515,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.artists.me'}" :to="{name: 'library.artists.me'}"
> >
<i class="user icon" /><translate translate-context="*/*/*"> <i class="user icon" /><translate >
Artists Artists
</translate> </translate>
</router-link> </router-link>
@ -527,7 +523,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.playlists.me'}" :to="{name: 'library.playlists.me'}"
> >
<i class="list icon" /><translate translate-context="*/*/*"> <i class="list icon" /><translate >
Playlists Playlists
</translate> </translate>
</router-link> </router-link>
@ -535,7 +531,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'library.radios.me'}" :to="{name: 'library.radios.me'}"
> >
<i class="feed icon" /><translate translate-context="*/*/*"> <i class="feed icon" /><translate >
Radios Radios
</translate> </translate>
</router-link> </router-link>
@ -543,7 +539,7 @@ onMounted(() => {
class="item" class="item"
:to="{name: 'favorites'}" :to="{name: 'favorites'}"
> >
<i class="heart icon" /><translate translate-context="Sidebar/Favorites/List item.Link/Noun"> <i class="heart icon" /><translate >
Favorites Favorites
</translate> </translate>
</router-link> </router-link>
@ -554,13 +550,13 @@ onMounted(() => {
class="header item" class="header item"
:to="{name: 'subscriptions'}" :to="{name: 'subscriptions'}"
> >
<translate translate-context="*/*/*"> <translate >
Channels Channels
</translate> </translate>
</router-link> </router-link>
<div class="item"> <div class="item">
<h3 class="header"> <h3 class="header">
<translate translate-context="Footer/About/List item.Link"> <translate >
More More
</translate> </translate>
</h3> </h3>
@ -570,7 +566,7 @@ onMounted(() => {
to="/about" to="/about"
active-class="router-link-exact-active active" 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 About this pod
</translate> </translate>
</router-link> </router-link>

View File

@ -110,7 +110,7 @@ const save = async () => {
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Error while saving settings Error while saving settings
</translate> </translate>
</h4> </h4>
@ -127,7 +127,7 @@ const save = async () => {
v-if="result" v-if="result"
class="ui positive message" class="ui positive message"
> >
<translate translate-context="Content/Settings/Paragraph"> <translate >
Settings updated successfully. Settings updated successfully.
</translate> </translate>
</div> </div>
@ -229,7 +229,7 @@ const save = async () => {
<div v-if="values[setting.identifier]"> <div v-if="values[setting.identifier]">
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<h3 class="ui header"> <h3 class="ui header">
<translate translate-context="Content/Settings/Title/Noun"> <translate >
Current image Current image
</translate> </translate>
</h3> </h3>
@ -246,7 +246,7 @@ const save = async () => {
type="submit" type="submit"
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
> >
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Save Save
</translate> </translate>
</button> </button>

View File

@ -4,7 +4,7 @@ import type { Form } from '~/types'
import SignupForm from '~/components/auth/SignupForm.vue' import SignupForm from '~/components/auth/SignupForm.vue'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { arrayMove } from '~/utils' import { arrayMove } from '~/utils'
interface Events { interface Events {
@ -26,11 +26,11 @@ const value = useVModel(props, 'modelValue', emit, { deep: true })
const maxFields = ref(10) const maxFields = ref(10)
const isPreviewing = ref(false) const isPreviewing = ref(false)
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
delete: $pgettext('*/*/*', 'Delete'), delete: t('Delete'),
up: $pgettext('*/*/*', 'Move up'), up: t('Move up'),
down: $pgettext('*/*/*', 'Move down') down: t('Move down')
})) }))
if (!value.value?.fields) { if (!value.value?.fields) {
@ -45,7 +45,7 @@ if (!value.value?.fields) {
const addField = () => { const addField = () => {
value.value.fields.push({ value.value.fields.push({
label: $pgettext('*/*/Form-builder', 'Additional field') + ' ' + (value.value.fields.length + 1), label: t('Additional field') + ' ' + (value.value.fields.length + 1),
required: true, required: true,
input_type: 'short_text' input_type: 'short_text'
}) })
@ -69,7 +69,7 @@ const move = (idx: number, increment: number) => {
:class="[{active: !isPreviewing}, 'item']" :class="[{active: !isPreviewing}, 'item']"
@click.stop.prevent="isPreviewing = false" @click.stop.prevent="isPreviewing = false"
> >
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Edit form Edit form
</translate> </translate>
</button> </button>
@ -77,7 +77,7 @@ const move = (idx: number, increment: number) => {
:class="[{active: isPreviewing}, 'item']" :class="[{active: isPreviewing}, 'item']"
@click.stop.prevent="isPreviewing = true" @click.stop.prevent="isPreviewing = true"
> >
<translate translate-context="*/Form/Menu.item"> <translate >
Preview form Preview form
</translate> </translate>
</button> </button>
@ -99,10 +99,10 @@ const move = (idx: number, increment: number) => {
> >
<div class="field"> <div class="field">
<label for="help-text"> <label for="help-text">
<translate translate-context="*/*/Label">Help text</translate> <translate >Help text</translate>
</label> </label>
<p> <p>
<translate translate-context="*/*/Help"> <translate >
An optional text to be displayed at the start of the sign-up form. An optional text to be displayed at the start of the sign-up form.
</translate> </translate>
</p> </p>
@ -115,10 +115,10 @@ const move = (idx: number, increment: number) => {
</div> </div>
<div class="field"> <div class="field">
<label> <label>
<translate translate-context="*/*/Label">Additional fields</translate> <translate >Additional fields</translate>
</label> </label>
<p> <p>
<translate translate-context="*/*/Help"> <translate >
Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled. Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled.
</translate> </translate>
</p> </p>
@ -126,21 +126,21 @@ const move = (idx: number, increment: number) => {
<thead> <thead>
<tr> <tr>
<th> <th>
<translate translate-context="*/*/Form-builder,Help"> <translate >
Field label Field label
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/Form-builder,Help"> <translate >
Field type Field type
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/Form-builder,Help"> <translate >
Required Required
</translate> </translate>
</th> </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> </tr>
</thead> </thead>
<tbody> <tbody>
@ -158,12 +158,12 @@ const move = (idx: number, increment: number) => {
<td> <td>
<select v-model="field.input_type"> <select v-model="field.input_type">
<option value="short_text"> <option value="short_text">
<translate translate-context="*/*/Form-builder"> <translate >
Short text Short text
</translate> </translate>
</option> </option>
<option value="long_text"> <option value="long_text">
<translate translate-context="*/*/Form-builder"> <translate >
Long text Long text
</translate> </translate>
</option> </option>
@ -172,12 +172,12 @@ const move = (idx: number, increment: number) => {
<td> <td>
<select v-model="field.required"> <select v-model="field.required">
<option :value="true"> <option :value="true">
<translate translate-context="*/*/*"> <translate >
Yes Yes
</translate> </translate>
</option> </option>
<option :value="false"> <option :value="false">
<translate translate-context="*/*/*"> <translate >
No No
</translate> </translate>
</option> </option>
@ -214,7 +214,7 @@ const move = (idx: number, increment: number) => {
class="ui basic button" class="ui basic button"
@click.stop.prevent="addField" @click.stop.prevent="addField"
> >
<translate translate-context="*/*/Form-builder"> <translate >
Add a new field Add a new field
</translate> </translate>
</button> </button>

View File

@ -2,7 +2,7 @@
import type { Channel } from '~/types' import type { Channel } from '~/types'
import { momentFormat } from '~/utils/filters' import { momentFormat } from '~/utils/filters'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import { computed } from 'vue' import { computed } from 'vue'
@ -30,10 +30,10 @@ const urlId = computed(() => props.object.actor?.is_local
: props.object.uuid : props.object.uuid
) )
const { $pgettext } = useGettext() const { t } = useI18n()
const updatedTitle = computed(() => { const updatedTitle = computed(() => {
const date = momentFormat(new Date(props.object.artist?.modification_date ?? '1970-01-01')) const date = momentFormat(new Date(props.object.artist?.modification_date ?? '1970-01-01'))
return $pgettext('*/*/*', 'Updated on %{ date }', { date }) return t('Updated on %{ date }', { date })
}) })
// TODO (wvffle): Use time ago // TODO (wvffle): Use time ago
@ -67,7 +67,7 @@ const updatedAgo = computed(() => moment(props.object.artist?.modification_date)
<translate <translate
v-if="object.artist?.content_category === 'podcast'" v-if="object.artist?.content_category === 'podcast'"
class="meta ellipsis" class="meta ellipsis"
translate-context="Content/Channel/Paragraph"
translate-plural="%{ count } episodes" translate-plural="%{ count } episodes"
:translate-n="object.artist.tracks_count" :translate-n="object.artist.tracks_count"
:translate-params="{count: 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>
<translate <translate
v-else v-else
translate-context="*/*/*"
:translate-params="{count: object.artist?.tracks_count}" :translate-params="{count: object.artist?.tracks_count}"
:translate-n="object.artist?.tracks_count" :translate-n="object.artist?.tracks_count"
translate-plural="%{ count } tracks" translate-plural="%{ count } tracks"

View File

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

View File

@ -3,7 +3,7 @@ import type { ContentCategory, Channel, BackendError } from '~/types'
import { slugify } from 'transliteration' import { slugify } from 'transliteration'
import { reactive, computed, ref, watchEffect, watch } from 'vue' import { reactive, computed, ref, watchEffect, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import axios from 'axios' import axios from 'axios'
import AttachmentInput from '~/components/common/AttachmentInput.vue' import AttachmentInput from '~/components/common/AttachmentInput.vue'
@ -29,7 +29,7 @@ const props = withDefaults(defineProps<Props>(), {
step: 1 step: 1
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const newValues = reactive({ const newValues = reactive({
name: props.object?.artist?.name ?? '', name: props.object?.artist?.name ?? '',
@ -45,13 +45,13 @@ const creating = computed(() => props.object === null)
const categoryChoices = computed(() => [ const categoryChoices = computed(() => [
{ {
value: 'podcast', value: 'podcast',
label: $pgettext('*/*/*', 'Podcasts'), label: t('Podcasts'),
helpText: $pgettext('Content/Channels/Help', 'Host your episodes and keep your community updated.') helpText: t('Host your episodes and keep your community updated.')
}, },
{ {
value: 'music', value: 'music',
label: $pgettext('*/*/*', 'Artist discography'), label: t('Artist discography'),
helpText: $pgettext('Content/Channels/Help', 'Publish music you make as a nice discography of albums and singles.') helpText: t('Publish music you make as a nice discography of albums and singles.')
} }
]) ])
@ -81,8 +81,8 @@ const itunesSubcategories = computed(() => {
}) })
const labels = computed(() => ({ const labels = computed(() => ({
namePlaceholder: $pgettext('Content/Channel/Form.Field.Placeholder', 'Awesome channel name'), namePlaceholder: t('Awesome channel name'),
usernamePlaceholder: $pgettext('Content/Channel/Form.Field.Placeholder', 'awesomechannelname') usernamePlaceholder: t('awesomechannelname')
})) }))
const submittable = computed(() => !!( const submittable = computed(() => !!(
@ -165,7 +165,7 @@ defineExpose({
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Error while saving channel Error while saving channel
</translate> </translate>
</h4> </h4>
@ -184,7 +184,7 @@ defineExpose({
class="ui grouped channel-type required field" class="ui grouped channel-type required field"
> >
<legend> <legend>
<translate translate-context="Content/Channel/Paragraph"> <translate >
What will this channel be used for? What will this channel be used for?
</translate> </translate>
</legend> </legend>
@ -214,7 +214,7 @@ defineExpose({
<template v-if="!creating || step === 2"> <template v-if="!creating || step === 2">
<div class="ui required field"> <div class="ui required field">
<label for="channel-name"> <label for="channel-name">
<translate translate-context="Content/Channel/*">Name</translate> <translate >Name</translate>
</label> </label>
<input <input
v-model="newValues.name" v-model="newValues.name"
@ -225,7 +225,7 @@ defineExpose({
</div> </div>
<div class="ui required field"> <div class="ui required field">
<label for="channel-username"> <label for="channel-username">
<translate translate-context="Content/Channel/*">Fediverse handle</translate> <translate >Fediverse handle</translate>
</label> </label>
<div class="ui left labeled input"> <div class="ui left labeled input">
<div class="ui basic label"> <div class="ui basic label">
@ -242,7 +242,7 @@ defineExpose({
<template v-if="creating"> <template v-if="creating">
<div class="ui small hidden divider" /> <div class="ui small hidden divider" />
<p> <p>
<translate translate-context="Content/Channels/Paragraph"> <translate >
Used in URLs and to follow this channel in the Fediverse. It cannot be changed later. Used in URLs and to follow this channel in the Fediverse. It cannot be changed later.
</translate> </translate>
</p> </p>
@ -254,7 +254,7 @@ defineExpose({
:image-class="newValues.content_category === 'podcast' ? '' : 'circular'" :image-class="newValues.content_category === 'podcast' ? '' : 'circular'"
@delete="newValues.cover = null" @delete="newValues.cover = null"
> >
<translate translate-context="Content/Channel/*"> <translate >
Channel Picture Channel Picture
</translate> </translate>
</attachment-input> </attachment-input>
@ -264,7 +264,7 @@ defineExpose({
<div class="ten wide column"> <div class="ten wide column">
<div class="ui field"> <div class="ui field">
<label for="channel-tags"> <label for="channel-tags">
<translate translate-context="*/*/*">Tags</translate> <translate >Tags</translate>
</label> </label>
<tags-selector <tags-selector
id="channel-tags" id="channel-tags"
@ -279,7 +279,7 @@ defineExpose({
> >
<div class="ui required field"> <div class="ui required field">
<label for="channel-language"> <label for="channel-language">
<translate translate-context="*/*/*">Language</translate> <translate >Language</translate>
</label> </label>
<select <select
id="channel-language" id="channel-language"
@ -302,7 +302,7 @@ defineExpose({
<div class="ui small hidden divider" /> <div class="ui small hidden divider" />
<div class="ui field"> <div class="ui field">
<label for="channel-name"> <label for="channel-name">
<translate translate-context="*/*/*">Description</translate> <translate >Description</translate>
</label> </label>
<content-form v-model="newValues.description" /> <content-form v-model="newValues.description" />
</div> </div>
@ -312,7 +312,7 @@ defineExpose({
> >
<div class="ui required field"> <div class="ui required field">
<label for="channel-itunes-category"> <label for="channel-itunes-category">
<translate translate-context="*/*/*">Category</translate> <translate >Category</translate>
</label> </label>
<select <select
id="itunes-category" id="itunes-category"
@ -332,7 +332,7 @@ defineExpose({
</div> </div>
<div class="ui field"> <div class="ui field">
<label for="channel-itunes-category"> <label for="channel-itunes-category">
<translate translate-context="*/*/*">Subcategory</translate> <translate >Subcategory</translate>
</label> </label>
<select <select
id="itunes-category" id="itunes-category"
@ -357,7 +357,7 @@ defineExpose({
> >
<div class="ui field"> <div class="ui field">
<label for="channel-itunes-email"> <label for="channel-itunes-email">
<translate translate-context="*/*/*">Owner e-mail address</translate> <translate >Owner e-mail address</translate>
</label> </label>
<input <input
id="channel-itunes-email" id="channel-itunes-email"
@ -368,7 +368,7 @@ defineExpose({
</div> </div>
<div class="ui field"> <div class="ui field">
<label for="channel-itunes-name"> <label for="channel-itunes-name">
<translate translate-context="*/*/*">Owner name</translate> <translate >Owner name</translate>
</label> </label>
<input <input
id="channel-itunes-name" id="channel-itunes-name"
@ -379,7 +379,7 @@ defineExpose({
</div> </div>
</div> </div>
<p> <p>
<translate translate-context="*/*/*"> <translate >
Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes. Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes.
</translate> </translate>
</p> </p>
@ -390,7 +390,7 @@ defineExpose({
class="ui active inverted dimmer" class="ui active inverted dimmer"
> >
<div class="ui text loader"> <div class="ui text loader">
<translate translate-context="*/*/*"> <translate >
Loading Loading
</translate> </translate>
</div> </div>

View File

@ -56,7 +56,7 @@ const cover = computed(() => props.serie?.cover ?? null)
</strong> </strong>
<div class="description"> <div class="description">
<translate <translate
translate-context="Content/Channel/Paragraph"
translate-plural="%{ count } episodes" translate-plural="%{ count } episodes"
:translate-n="serie.tracks_count" :translate-n="serie.tracks_count"
:translate-params="{count: serie.tracks_count}" :translate-params="{count: serie.tracks_count}"

View File

@ -84,7 +84,7 @@ fetchData()
:class="['ui', 'basic', 'button']" :class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)" @click="fetchData(nextPage)"
> >
<translate translate-context="*/*/Button,Label"> <translate >
Show more Show more
</translate> </translate>
</button> </button>
@ -95,7 +95,7 @@ fetchData()
@refresh="fetchData()" @refresh="fetchData()"
> >
<p> <p>
<translate translate-context="Content/Channels/*"> <translate >
You may need to subscribe to this channel to see its contents. You may need to subscribe to this channel to see its contents.
</translate> </translate>
</p> </p>

View File

@ -77,7 +77,7 @@ fetchData()
:class="['ui', 'basic', 'button']" :class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)" @click="fetchData(nextPage)"
> >
<translate translate-context="*/*/Button,Label"> <translate >
Show more Show more
</translate> </translate>
</button> </button>

View File

@ -52,11 +52,11 @@ const { copy, copied } = useClipboard({ source: textarea })
> >
<p> <p>
<strong> <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> </strong>
</p> </p>
<p> <p>
<translate translate-context="Content/Embed/Message"> <translate >
Please contact your admins and ask them to update the corresponding setting. Please contact your admins and ask them to update the corresponding setting.
</translate> </translate>
</p> </p>
@ -65,9 +65,9 @@ const { copy, copied } = useClipboard({ source: textarea })
<div class="two fields"> <div class="two fields">
<div class="field"> <div class="field">
<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> <p>
<translate translate-context="Popup/Embed/Paragraph"> <translate >
Leave empty for a responsive widget Leave empty for a responsive widget
</translate> </translate>
</p> </p>
@ -82,7 +82,7 @@ const { copy, copied } = useClipboard({ source: textarea })
<template v-if="type != 'track'"> <template v-if="type != 'track'">
<br> <br>
<div class="field"> <div class="field">
<label for="embed-height"><translate translate-context="Popup/Embed/Input.Label">Widget height</translate></label> <label for="embed-height"><translate >Widget height</translate></label>
<input <input
id="embed-height" id="embed-height"
v-model="height" v-model="height"
@ -99,13 +99,13 @@ const { copy, copied } = useClipboard({ source: textarea })
class="ui right accent labeled icon floated button" class="ui right accent labeled icon floated button"
@click="copy()" @click="copy()"
> >
<i class="copy icon" /><translate translate-context="*/*/Button.Label/Short, Verb"> <i class="copy icon" /><translate >
Copy Copy
</translate> </translate>
</button> </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> <p>
<translate translate-context="Popup/Embed/Paragraph"> <translate >
Copy/paste this code in your website HTML Copy/paste this code in your website HTML
</translate> </translate>
</p> </p>
@ -120,7 +120,7 @@ const { copy, copied } = useClipboard({ source: textarea })
v-if="copied" v-if="copied"
class="message" class="message"
> >
<translate translate-context="Content/*/Paragraph"> <translate >
Text copied to clipboard! Text copied to clipboard!
</translate> </translate>
</p> </p>
@ -134,7 +134,7 @@ const { copy, copied } = useClipboard({ source: textarea })
:href="iframeSrc" :href="iframeSrc"
target="_blank" target="_blank"
> >
<translate translate-context="Popup/Embed/Title/Noun">Preview</translate> <translate >Preview</translate>
</a> </a>
</h3> </h3>
<iframe <iframe

View File

@ -40,19 +40,19 @@ const toggle = () => {
<i class="heart icon" /> <i class="heart icon" />
<translate <translate
v-if="isApproved" v-if="isApproved"
translate-context="Content/Library/Card.Button.Label/Verb"
> >
Unfollow Unfollow
</translate> </translate>
<translate <translate
v-else-if="isPending" v-else-if="isPending"
translate-context="Content/Library/Card.Button.Label/Verb"
> >
Cancel follow request Cancel follow request
</translate> </translate>
<translate <translate
v-else v-else
translate-context="Content/Library/Card.Button.Label/Verb"
> >
Follow Follow
</translate> </translate>

View File

@ -3,7 +3,7 @@ import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions' import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import usePlayOptions from '~/composables/audio/usePlayOptions' import usePlayOptions from '~/composables/audio/usePlayOptions'
import useReport from '~/composables/moderation/useReport' import useReport from '~/composables/moderation/useReport'
import { useCurrentElement } from '@vueuse/core' import { useCurrentElement } from '@vueuse/core'
@ -63,33 +63,33 @@ const {
const { report, getReportableObjects } = useReport() const { report, getReportableObjects } = useReport()
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
playNow: $pgettext('*/Queue/Dropdown/Button/Title', 'Play now'), playNow: t('Play now'),
addToQueue: $pgettext('*/Queue/Dropdown/Button/Title', 'Add to current queue'), addToQueue: t('Add to current queue'),
playNext: $pgettext('*/Queue/Dropdown/Button/Title', 'Play next'), playNext: t('Play next'),
startRadio: $pgettext('*/Queue/Dropdown/Button/Title', 'Play similar songs'), startRadio: t('Play similar songs'),
report: $pgettext('*/Moderation/*/Button/Label,Verb', 'Report…'), report: t('Report…'),
addToPlaylist: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…'), addToPlaylist: t('Add to playlist…'),
hideArtist: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Hide content from this artist'), hideArtist: t('Hide content from this artist'),
replacePlay: props.track replacePlay: props.track
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play track') ? t('Play track')
: props.album : props.album
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play album') ? t('Play album')
: props.artist : props.artist
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play artist') ? t('Play artist')
: props.playlist : props.playlist
? $pgettext('*/Queue/Dropdown/Button/Title', 'Play playlist') ? t('Play playlist')
: $pgettext('*/Queue/Dropdown/Button/Title', 'Play tracks') : t('Play tracks')
})) }))
const title = computed(() => { const title = computed(() => {
if (playable.value) { if (playable.value) {
return $pgettext('*/*/Button.Label/Noun', 'More…') return t('More…')
} }
if (props.track) { 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 '' return ''
@ -138,7 +138,7 @@ const openMenu = () => {
v-else v-else
:class="[playIconClass, 'icon']" :class="[playIconClass, 'icon']"
/> />
<template v-if="!discrete && !iconOnly">&nbsp;<slot><translate translate-context="*/Queue/Button.Label/Short, Verb">Play</translate></slot></template> <template v-if="!discrete && !iconOnly">&nbsp;<slot><translate >Play</translate></slot></template>
</button> </button>
<button <button
v-if="!discrete && !iconOnly" v-if="!discrete && !iconOnly"
@ -156,7 +156,7 @@ const openMenu = () => {
:title="labels.addToQueue" :title="labels.addToQueue"
@click.stop.prevent="enqueue" @click.stop.prevent="enqueue"
> >
<i class="plus icon" /><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Add to queue</translate> <i class="plus icon" /><translate >Add to queue</translate>
</button> </button>
<button <button
class="item basic" class="item basic"
@ -181,7 +181,7 @@ const openMenu = () => {
:title="labels.startRadio" :title="labels.startRadio"
@click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track?.id})" @click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track?.id})"
> >
<i class="feed icon" /><translate translate-context="*/Queue/Button.Label/Short, Verb">Play radio</translate> <i class="feed icon" /><translate >Play radio</translate>
</button> </button>
<button <button
v-if="track" v-if="track"
@ -190,7 +190,7 @@ const openMenu = () => {
@click.stop="$store.commit('playlists/chooseTrack', track)" @click.stop="$store.commit('playlists/chooseTrack', track)"
> >
<i class="list icon" /> <i class="list icon" />
<translate translate-context="Sidebar/Player/Icon.Tooltip/Verb">Add to playlist</translate> <translate >Add to playlist</translate>
</button> </button>
<button <button
v-if="track && $route.name !== 'library.tracks.detail'" v-if="track && $route.name !== 'library.tracks.detail'"
@ -200,11 +200,11 @@ const openMenu = () => {
<i class="info icon" /> <i class="info icon" />
<translate <translate
v-if="track.artist?.content_category === 'podcast'" v-if="track.artist?.content_category === 'podcast'"
translate-context="*/Queue/Dropdown/Button/Label/Short"
>Episode details</translate> >Episode details</translate>
<translate <translate
v-else v-else
translate-context="*/Queue/Dropdown/Button/Label/Short"
>Track details</translate> >Track details</translate>
</button> </button>
<div class="divider" /> <div class="divider" />

View File

@ -3,17 +3,17 @@ import { usePlayer } from '~/composables/audio/player'
import { useQueue } from '~/composables/audio/queue' import { useQueue } from '~/composables/audio/queue'
import { useMouse, useWindowSize } from '@vueuse/core' import { useMouse, useWindowSize } from '@vueuse/core'
import { useGettext } from 'vue3-gettext'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useI18n } from 'vue-i18n'
import onKeyboardShortcut from '~/composables/onKeyboardShortcut' import onKeyboardShortcut from '~/composables/onKeyboardShortcut'
import time from '~/utils/time' import time from '~/utils/time'
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue' import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue' import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
import VolumeControl from './VolumeControl.vue'
import PlayerControls from './PlayerControls.vue' import PlayerControls from './PlayerControls.vue'
import VolumeControl from './VolumeControl.vue'
const { const {
LoopingMode, LoopingMode,
@ -44,7 +44,7 @@ const {
} = useQueue() } = useQueue()
const store = useStore() const store = useStore()
const { $pgettext } = useGettext() const { t } = useI18n()
const toggleMobilePlayer = () => { const toggleMobilePlayer = () => {
store.commit('ui/queueFocused', ['queue', 'player'].includes(store.state.ui.queueFocused as string) ? null : 'player') 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) onKeyboardShortcut(['ctrl', 'shift', 'right'], playNext, true)
const labels = computed(() => ({ const labels = computed(() => ({
audioPlayer: $pgettext('Sidebar/Player/Hidden text', 'Media player'), audioPlayer: t('Media player'),
previous: $pgettext('Sidebar/Player/Icon.Tooltip', 'Previous track'), previous: t('Previous track'),
play: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Play'), play: t('Play'),
pause: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Pause'), pause: t('Pause'),
next: $pgettext('Sidebar/Player/Icon.Tooltip', 'Next track'), next: t('Next track'),
unmute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Unmute'), unmute: t('Unmute'),
mute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Mute'), mute: t('Mute'),
expandQueue: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Expand queue'), expandQueue: t('Expand queue'),
shuffle: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Shuffle your queue'), shuffle: t('Shuffle your queue'),
clear: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Clear your queue'), clear: t('Clear your queue'),
addArtistContentFilter: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Hide content from this artist…') addArtistContentFilter: t('Hide content from this artist…')
})) }))
const switchTab = () => { const switchTab = () => {
@ -103,10 +103,10 @@ initializeFirstTrack()
const loopingTitle = computed(() => { const loopingTitle = computed(() => {
const mode = looping.value const mode = looping.value
return mode === LoopingMode.None return mode === LoopingMode.None
? $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 : mode === LoopingMode.LoopTrack
? $pgettext('Sidebar/Player/Icon.Tooltip', 'Looping on a single track. Click to switch to whole queue looping.') ? t('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 whole queue. Click to disable looping.')
}) })
const hideArtist = () => { const hideArtist = () => {
@ -133,7 +133,7 @@ const hideArtist = () => {
id="player-label" id="player-label"
class="visually-hidden" class="visually-hidden"
> >
<translate translate-context="*/*/*"> <translate >
Audio player and controls Audio player and controls
</translate> </translate>
</h1> </h1>
@ -302,7 +302,6 @@ const hideArtist = () => {
> >
<i class="stream icon" /> <i class="stream icon" />
<translate <translate
translate-context="Sidebar/Queue/Text"
:translate-params="{index: currentIndex + 1, length: queue.length}" :translate-params="{index: currentIndex + 1, length: queue.length}"
> >
%{ index } of %{ length } %{ index } of %{ length }
@ -314,7 +313,6 @@ const hideArtist = () => {
> >
<i class="stream icon" /> <i class="stream icon" />
<translate <translate
translate-context="Sidebar/Queue/Text"
:translate-params="{index: currentIndex + 1, length: queue.length}" :translate-params="{index: currentIndex + 1, length: queue.length}"
> >
%{ index } of %{ length } %{ index } of %{ length }

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Artist, Album } from '~/types' 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 { ref, computed, reactive, watch, onMounted } from 'vue'
import { refDebounced } from '@vueuse/core' import { refDebounced } from '@vueuse/core'
@ -21,7 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
}) })
const logger = useLogger() const logger = useLogger()
const { $pgettext } = useGettext() const { t } = useI18n()
const query = ref('') const query = ref('')
const queryDebounced = refDebounced(query, 500) const queryDebounced = refDebounced(query, 500)
@ -65,7 +65,7 @@ onMounted(() => {
}) })
const labels = computed(() => ({ const labels = computed(() => ({
searchPlaceholder: $pgettext('*/Search/Input.Placeholder', 'Artist, album, track…') searchPlaceholder: t('Artist, album, track…')
})) }))
</script> </script>
@ -73,7 +73,7 @@ const labels = computed(() => ({
<template> <template>
<div> <div>
<h2> <h2>
<translate translate-context="Content/Search/Title"> <translate >
Search for some music Search for some music
</translate> </translate>
</h2> </h2>
@ -91,7 +91,7 @@ const labels = computed(() => ({
</div> </div>
<template v-if="query.length > 0"> <template v-if="query.length > 0">
<h3 class="ui title"> <h3 class="ui title">
<translate translate-context="*/*/*/Noun"> <translate >
Artists Artists
</translate> </translate>
</h3> </h3>
@ -105,14 +105,14 @@ const labels = computed(() => ({
</div> </div>
</div> </div>
<p v-else> <p v-else>
<translate translate-context="Content/Search/Paragraph"> <translate >
No artist matched your query No artist matched your query
</translate> </translate>
</p> </p>
</template> </template>
<template v-if="query.length > 0"> <template v-if="query.length > 0">
<h3 class="ui title"> <h3 class="ui title">
<translate translate-context="*/*/*"> <translate >
Albums Albums
</translate> </translate>
</h3> </h3>
@ -132,7 +132,7 @@ const labels = computed(() => ({
</div> </div>
</div> </div>
<p v-else> <p v-else>
<translate translate-context="Content/Search/Paragraph"> <translate >
No album matched your query No album matched your query
</translate> </translate>
</p> </p>

View File

@ -6,7 +6,7 @@ import jQuery from 'jquery'
import { trim } from 'lodash-es' import { trim } from 'lodash-es'
import { useFocus, useCurrentElement } from '@vueuse/core' import { useFocus, useCurrentElement } from '@vueuse/core'
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -48,14 +48,14 @@ const { focused } = useFocus(search)
onKeyboardShortcut(['shift', 'f'], () => (focused.value = true), true) onKeyboardShortcut(['shift', 'f'], () => (focused.value = true), true)
onKeyboardShortcut(['ctrl', 'k'], () => (focused.value = true), true) onKeyboardShortcut(['ctrl', 'k'], () => (focused.value = true), true)
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
placeholder: $pgettext('Sidebar/Search/Input.Placeholder', 'Search for artists, albums, tracks…'), placeholder: t('Search for artists, albums, tracks…'),
searchContent: $pgettext('Sidebar/Search/Input.Label', 'Search for content'), searchContent: t('Search for content'),
artist: $pgettext('*/*/*/Noun', 'Artist'), artist: t('Artist'),
album: $pgettext('*/*/*', 'Album'), album: t('Album'),
track: $pgettext('*/*/*/Noun', 'Track'), track: t('Track'),
tag: $pgettext('*/*/*/Noun', 'Tag') tag: t('Tag')
})) }))
const router = useRouter() const router = useRouter()
@ -77,11 +77,11 @@ const blur = () => {
const categories = computed(() => [ const categories = computed(() => [
{ {
code: 'federation', code: 'federation',
name: $pgettext('*/*/*', 'Federation') name: t('Federation')
}, },
{ {
code: 'podcasts', code: 'podcasts',
name: $pgettext('*/*/*', 'Podcasts') name: t('Podcasts')
}, },
{ {
code: 'artists', code: 'artists',
@ -138,8 +138,8 @@ onMounted(() => {
showNoResults: true, showNoResults: true,
error: { error: {
// @ts-expect-error Semantic is broken // @ts-expect-error Semantic is broken
noResultsHeader: $pgettext('Sidebar/Search/Error', 'No matches found'), noResultsHeader: t('No matches found'),
noResults: $pgettext('Sidebar/Search/Error.Label', 'Sorry, there are no results for this search') noResults: t('Sorry, there are no results for this search')
}, },
onSelect (result, response) { onSelect (result, response) {
@ -179,7 +179,7 @@ onMounted(() => {
if (category.code === 'federation' && id) { if (category.code === 'federation' && id) {
resultsEmpty = false resultsEmpty = false
results[category.code]?.results.push({ results[category.code]?.results.push({
title: $pgettext('Search/*/*', 'Search on the fediverse'), title: t('Search on the fediverse'),
routerUrl: { routerUrl: {
name: 'search', name: 'search',
query: { id } query: { id }
@ -190,7 +190,7 @@ onMounted(() => {
if (category.code === 'podcasts' && id) { if (category.code === 'podcasts' && id) {
resultsEmpty = false resultsEmpty = false
results[category.code]?.results.push({ results[category.code]?.results.push({
title: $pgettext('Search/*/*', 'Subscribe to podcast via RSS'), title: t('Subscribe to podcast via RSS'),
routerUrl: { routerUrl: {
name: 'search', name: 'search',
query: { id, type: 'rss' } query: { id, type: 'rss' }
@ -200,7 +200,7 @@ onMounted(() => {
if (category.code === 'more') { if (category.code === 'more') {
results[category.code]?.results.push({ results[category.code]?.results.push({
title: $pgettext('Search/*/*', 'More results 🡒'), title: t('More results 🡒'),
routerUrl: { routerUrl: {
name: 'search', name: 'search',
query: { type: 'artists', q: query.value } query: { type: 'artists', q: query.value }

View File

@ -1,17 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { usePlayer } from '~/composables/audio/player' import { usePlayer } from '~/composables/audio/player'
import { useTimeoutFn } from '@vueuse/core' import { useTimeoutFn } from '@vueuse/core'
import { useGettext } from 'vue3-gettext'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
const { volume, mute } = usePlayer() const { volume, mute } = usePlayer()
const expanded = ref(false) const expanded = ref(false)
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
unmute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Unmute'), unmute: t('Unmute'),
mute: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Mute'), mute: t('Mute'),
slider: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Adjust volume') slider: t('Adjust volume')
})) }))
const { start, stop } = useTimeoutFn(() => (expanded.value = false), 500, { immediate: false }) const { start, stop } = useTimeoutFn(() => (expanded.value = false), 500, { immediate: false })

View File

@ -60,7 +60,7 @@ const imageUrl = computed(() => props.album.cover?.urls.original
<div class="extra content"> <div class="extra content">
<span v-if="album.release_date">{{ momentFormat(new Date(album.release_date), 'Y') }} · </span> <span v-if="album.release_date">{{ momentFormat(new Date(album.release_date), 'Y') }} · </span>
<translate <translate
translate-context="*/*/*"
:translate-params="{count: album.tracks_count}" :translate-params="{count: album.tracks_count}"
:translate-n="album.tracks_count" :translate-n="album.tracks_count"
translate-plural="%{ count } tracks" translate-plural="%{ count } tracks"

View File

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

View File

@ -64,7 +64,7 @@ const imageUrl = computed(() => cover.value?.urls.original
<div class="extra content"> <div class="extra content">
<translate <translate
v-if="artist.content_category === 'music'" v-if="artist.content_category === 'music'"
translate-context="*/*/*"
:translate-params="{count: artist.tracks_count}" :translate-params="{count: artist.tracks_count}"
:translate-n="artist.tracks_count" :translate-n="artist.tracks_count"
translate-plural="%{ count } tracks" translate-plural="%{ count } tracks"
@ -73,7 +73,7 @@ const imageUrl = computed(() => cover.value?.urls.original
</translate> </translate>
<translate <translate
v-else v-else
translate-context="*/*/*"
:translate-params="{count: artist.tracks_count}" :translate-params="{count: artist.tracks_count}"
:translate-n="artist.tracks_count" :translate-n="artist.tracks_count"
translate-plural="%{ count } episodes" translate-plural="%{ count } episodes"

View File

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

View File

@ -2,8 +2,8 @@
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types' import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions' import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
import { useGettext } from 'vue3-gettext'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { usePlayer } from '~/composables/audio/player' import { usePlayer } from '~/composables/audio/player'
import { useQueue } from '~/composables/audio/queue' import { useQueue } from '~/composables/audio/queue'
@ -52,8 +52,8 @@ const { currentTrack } = useQueue()
const { isPlaying } = usePlayer() const { isPlaying } = usePlayer()
const { activateTrack } = usePlayOptions(props) const { activateTrack } = usePlayOptions(props)
const { $pgettext } = useGettext() const { t } = useI18n()
const actionsButtonLabel = computed(() => $pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions')) const actionsButtonLabel = computed(() => t('Show track actions'))
</script> </script>
<template> <template>

View File

@ -4,7 +4,7 @@ import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
// import type { Track } from '~/types' // import type { Track } from '~/types'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import SemanticModal from '~/components/semantic/Modal.vue' import SemanticModal from '~/components/semantic/Modal.vue'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import usePlayOptions from '~/composables/audio/usePlayOptions' import usePlayOptions from '~/composables/audio/usePlayOptions'
@ -58,33 +58,33 @@ const store = useStore()
const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id)) const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id))
const { $pgettext } = useGettext() const { t } = useI18n()
const favoriteButton = computed(() => isFavorite.value const favoriteButton = computed(() => isFavorite.value
? $pgettext('Content/Track/Icon.Tooltip/Verb', 'Remove from favorites') ? t('Remove from favorites')
: $pgettext('Content/Track/*/Verb', 'Add to favorites') : t('Add to favorites')
) )
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Episode details') ? t('Episode details')
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Track details') : t('Track details')
) )
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View series') ? t('View series')
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View album') : t('View album')
) )
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View channel') ? t('View channel')
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View artist') : t('View artist')
) )
const labels = computed(() => ({ const labels = computed(() => ({
startRadio: $pgettext('*/Queue/Dropdown/Button/Title', 'Play radio'), startRadio: t('Play radio'),
playNow: $pgettext('*/Queue/Dropdown/Button/Title', 'Play now'), playNow: t('Play now'),
addToQueue: $pgettext('*/Queue/Dropdown/Button/Title', 'Add to queue'), addToQueue: t('Add to queue'),
playNext: $pgettext('*/Queue/Dropdown/Button/Title', 'Play next'), playNext: t('Play next'),
addToPlaylist: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…') addToPlaylist: t('Add to playlist…')
})) }))
</script> </script>

View File

@ -2,8 +2,8 @@
import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types' import type { Track, Artist, Album, Playlist, Library, Channel, Actor } from '~/types'
import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions' import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
import { useGettext } from 'vue3-gettext'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { usePlayer } from '~/composables/audio/player' import { usePlayer } from '~/composables/audio/player'
import { useQueue } from '~/composables/audio/queue' import { useQueue } from '~/composables/audio/queue'
@ -52,8 +52,8 @@ const { currentTrack } = useQueue()
const { isPlaying } = usePlayer() const { isPlaying } = usePlayer()
const { activateTrack } = usePlayOptions(props) const { activateTrack } = usePlayOptions(props)
const { $pgettext } = useGettext() const { t } = useI18n()
const actionsButtonLabel = computed(() => $pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions')) const actionsButtonLabel = computed(() => t('Show track actions'))
</script> </script>
<template> <template>

View File

@ -4,7 +4,7 @@ import type { PlayOptionsProps } from '~/composables/audio/usePlayOptions'
// import type { Track } from '~/types' // import type { Track } from '~/types'
import { useStore } from '~/store' import { useStore } from '~/store'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import SemanticModal from '~/components/semantic/Modal.vue' import SemanticModal from '~/components/semantic/Modal.vue'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import usePlayOptions from '~/composables/audio/usePlayOptions' import usePlayOptions from '~/composables/audio/usePlayOptions'
@ -58,33 +58,33 @@ const store = useStore()
const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id)) const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id))
const { $pgettext } = useGettext() const { t } = useI18n()
const favoriteButton = computed(() => isFavorite.value const favoriteButton = computed(() => isFavorite.value
? $pgettext('Content/Track/Icon.Tooltip/Verb', 'Remove from favorites') ? t('Remove from favorites')
: $pgettext('Content/Track/*/Verb', 'Add to favorites') : t('Add to favorites')
) )
const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' const trackDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Episode details') ? t('Episode details')
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'Track details') : t('Track details')
) )
const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' const albumDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View series') ? t('View series')
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View album') : t('View album')
) )
const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast' const artistDetailsButton = computed(() => props.track.artist?.content_category === 'podcast'
? $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View channel') ? t('View channel')
: $pgettext('*/Queue/Dropdown/Button/Label/Short', 'View artist') : t('View artist')
) )
const labels = computed(() => ({ const labels = computed(() => ({
startRadio: $pgettext('*/Queue/Dropdown/Button/Title', 'Play radio'), startRadio: t('Play radio'),
playNow: $pgettext('*/Queue/Dropdown/Button/Title', 'Play now'), playNow: t('Play now'),
addToQueue: $pgettext('*/Queue/Dropdown/Button/Title', 'Add to queue'), addToQueue: t('Add to queue'),
playNext: $pgettext('*/Queue/Dropdown/Button/Title', 'Play next'), playNext: t('Play next'),
addToPlaylist: $pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…') addToPlaylist: t('Add to playlist…')
})) }))
</script> </script>

View File

@ -2,7 +2,7 @@
import type { Track } from '~/types' import type { Track } from '~/types'
import { useElementByPoint, useMouse } from '@vueuse/core' import { useElementByPoint, useMouse } from '@vueuse/core'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { clone, uniqBy } from 'lodash-es' import { clone, uniqBy } from 'lodash-es'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
@ -93,11 +93,11 @@ const allTracks = computed(() => {
: tracks : tracks
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('*/*/*/Noun', 'Title'), title: t('Title'),
album: $pgettext('*/*/*/Noun', 'Album'), album: t('Album'),
artist: $pgettext('*/*/*/Noun', 'Artist') artist: t('Artist')
})) }))
const isLoading = ref(false) const isLoading = ref(false)

View File

@ -200,7 +200,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
> >
<div class="ui icon header"> <div class="ui icon header">
<i class="music icon" /> <i class="music icon" />
<translate translate-context="Content/Home/Placeholder"> <translate >
Nothing found Nothing found
</translate> </translate>
</div> </div>
@ -217,7 +217,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
:class="['ui', 'basic', 'button']" :class="['ui', 'basic', 'button']"
@click="fetchData(nextPage as string)" @click="fetchData(nextPage as string)"
> >
<translate translate-context="*/*/Button,Label"> <translate >
Show more Show more
</translate> </translate>
</button> </button>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import axios from 'axios' import axios from 'axios'
@ -15,12 +15,12 @@ interface Props {
const props = defineProps<Props>() const props = defineProps<Props>()
const { $pgettext } = useGettext() const { t } = useI18n()
const application = ref() const application = ref()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Content/Applications/Title', 'Edit application') title: t('Edit application')
})) }))
const isLoading = ref(false) const isLoading = ref(false)
@ -72,23 +72,23 @@ store.state.auth.applicationSecret = undefined
</div> </div>
<template v-else> <template v-else>
<router-link :to="{name: 'settings'}"> <router-link :to="{name: 'settings'}">
<translate translate-context="Content/Applications/Link"> <translate >
Back to settings Back to settings
</translate> </translate>
</router-link> </router-link>
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Applications/Title"> <translate >
Application details Application details
</translate> </translate>
</h2> </h2>
<div class="ui form"> <div class="ui form">
<p> <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. Application ID and secret are really sensitive values and must be treated like passwords. Do not share those with anyone else.
</translate> </translate>
</p> </p>
<div class="field"> <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 <copy-input
id="copy-id" id="copy-id"
:value="application.client_id" :value="application.client_id"
@ -100,18 +100,18 @@ store.state.auth.applicationSecret = undefined
> >
<div class="ui small warning message"> <div class="ui small warning message">
<h3 class="header"> <h3 class="header">
<translate translate-context="*/*/*"> <translate >
Keep a copy of this token in a safe place Keep a copy of this token in a safe place
</translate> </translate>
</h3> </h3>
<p> <p>
<translate translate-context="*/*/*"> <translate >
You won't be able to see it again once you leave this screen. You won't be able to see it again once you leave this screen.
</translate> </translate>
</p> </p>
</div> </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 <copy-input
id="copy-secret" id="copy-secret"
:value="secret" :value="secret"
@ -121,7 +121,7 @@ store.state.auth.applicationSecret = undefined
v-if="application.token != undefined" v-if="application.token != undefined"
class="field" 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 <copy-input
id="copy-secret" id="copy-secret"
:value="application.token" :value="application.token"
@ -131,12 +131,12 @@ store.state.auth.applicationSecret = undefined
@click.prevent="refreshToken" @click.prevent="refreshToken"
> >
<i class="refresh icon" /> <i class="refresh icon" />
<translate translate-context="Content/Applications/Label">Regenerate token</translate> <translate >Regenerate token</translate>
</a> </a>
</div> </div>
</div> </div>
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Applications/Title"> <translate >
Edit application Edit application
</translate> </translate>
</h2> </h2>

View File

@ -4,7 +4,7 @@ import type { BackendError, Application } from '~/types'
import axios from 'axios' import axios from 'axios'
import { ref, reactive, computed } from 'vue' import { ref, reactive, computed } from 'vue'
import { computedEager } from '@vueuse/core' import { computedEager } from '@vueuse/core'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { uniq } from 'lodash-es' import { uniq } from 'lodash-es'
import useScopes from '~/composables/auth/useScopes' import useScopes from '~/composables/auth/useScopes'
@ -25,7 +25,7 @@ const props = withDefaults(defineProps<Props>(), {
defaults: () => ({}) defaults: () => ({})
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const scopes = useScopes() const scopes = useScopes()
.filter(scope => !['reports', 'security'].includes(scope.id)) .filter(scope => !['reports', 'security'].includes(scope.id))
@ -86,14 +86,14 @@ const toggleAllScopes = (parent: typeof allScopes['value'][number]) => {
const scopeParents = computedEager(() => [ const scopeParents = computedEager(() => [
{ {
id: 'read', id: 'read',
label: $pgettext('Content/OAuth Scopes/Label/Verb', 'Read'), label: t('Read'),
description: $pgettext('Content/OAuth Scopes/Help Text', 'Read-only access to user data'), description: t('Read-only access to user data'),
value: scopeArray.value.includes('read') value: scopeArray.value.includes('read')
}, },
{ {
id: 'write', id: 'write',
label: $pgettext('Content/OAuth Scopes/Label/Verb', 'Write'), label: t('Write'),
description: $pgettext('Content/OAuth Scopes/Help Text', 'Write-only access to user data'), description: t('Write-only access to user data'),
value: scopeArray.value.includes('write') value: scopeArray.value.includes('write')
} }
]) ])
@ -120,7 +120,7 @@ const allScopes = computed(() => {
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
We cannot save your changes We cannot save your changes
</translate> </translate>
</h4> </h4>
@ -134,7 +134,7 @@ const allScopes = computed(() => {
</ul> </ul>
</div> </div>
<div class="ui field"> <div class="ui field">
<label for="application-name"><translate translate-context="*/*/*/Noun">Name</translate></label> <label for="application-name"><translate >Name</translate></label>
<input <input
id="application-name" id="application-name"
v-model="fields.name" v-model="fields.name"
@ -144,7 +144,7 @@ const allScopes = computed(() => {
> >
</div> </div>
<div class="ui field"> <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 <input
id="redirect-uris" id="redirect-uris"
v-model="fields.redirect_uris" v-model="fields.redirect_uris"
@ -152,15 +152,15 @@ const allScopes = computed(() => {
type="text" type="text"
> >
<p class="help"> <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. Use "urn:ietf:wg:oauth:2.0:oob" as a redirect URI if your application is not served on the web.
</translate> </translate>
</p> </p>
</div> </div>
<div class="ui field"> <div class="ui field">
<label><translate translate-context="Content/*/*/Noun">Scopes</translate></label> <label><translate >Scopes</translate></label>
<p> <p>
<translate translate-context="Content/Applications/Paragraph/"> <translate >
Checking the parent "Read" or "Write" scopes implies access to all the corresponding children scopes. Checking the parent "Read" or "Write" scopes implies access to all the corresponding children scopes.
</translate> </translate>
</p> </p>
@ -210,13 +210,13 @@ const allScopes = computed(() => {
> >
<translate <translate
v-if="app !== null" v-if="app !== null"
translate-context="Content/Applications/Button.Label/Verb"
> >
Update application Update application
</translate> </translate>
<translate <translate
v-else v-else
translate-context="Content/Applications/Button.Label/Verb"
> >
Create application Create application
</translate> </translate>

View File

@ -1,11 +1,14 @@
<script setup lang="ts"> <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 type { Application } from '~/types'
import { computed, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import ApplicationForm from '~/components/auth/ApplicationForm.vue'
interface Props { interface Props {
name?: string name?: string
scopes?: string scopes?: string
@ -24,9 +27,9 @@ const defaults = reactive({
redirectUris: props.redirectUris redirectUris: props.redirectUris
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Content/Settings/Button.Label', 'Create a new application') title: t('Create a new application')
})) }))
const router = useRouter() const router = useRouter()
@ -52,12 +55,12 @@ const created = (application: Application) => {
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<section class="ui text container"> <section class="ui text container">
<router-link :to="{name: 'settings'}"> <router-link :to="{name: 'settings'}">
<translate translate-context="Content/Applications/Link"> <translate >
Back to settings Back to settings
</translate> </translate>
</router-link> </router-link>
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Settings/Button.Label"> <translate >
Create a new application Create a new application
</translate> </translate>
</h2> </h2>

View File

@ -2,7 +2,7 @@
import type { BackendError, Application } from '~/types' import type { BackendError, Application } from '~/types'
import axios from 'axios' import axios from 'axios'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { whenever } from '@vueuse/core' import { whenever } from '@vueuse/core'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
@ -21,7 +21,7 @@ interface Props {
const props = defineProps<Props>() const props = defineProps<Props>()
const { $pgettext } = useGettext() const { t } = useI18n()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const knownScopes = useScopes() const knownScopes = useScopes()
@ -84,7 +84,7 @@ const submit = async () => {
} }
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/Authorize/Title', 'Allow application') title: t('Allow application')
})) }))
const requestedScopes = computed(() => props.scope.split(' ')) const requestedScopes = computed(() => props.scope.split(' '))
@ -119,7 +119,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
<section class="ui vertical stripe segment"> <section class="ui vertical stripe segment">
<div class="ui small text container"> <div class="ui small text container">
<h2> <h2>
<i class="lock open icon" /><translate translate-context="Content/Auth/Title/Verb"> <i class="lock open icon" /><translate >
Authorize third-party app Authorize third-party app
</translate> </translate>
</h2> </h2>
@ -132,7 +132,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
v-if="application" v-if="application"
class="header" class="header"
> >
<translate translate-context="Popup/Moderation/Error message"> <translate >
Error while authorizing application Error while authorizing application
</translate> </translate>
</h4> </h4>
@ -140,7 +140,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
v-else v-else
class="header" class="header"
> >
<translate translate-context="Popup/Moderation/Error message"> <translate >
Error while fetching application data Error while fetching application data
</translate> </translate>
</h4> </h4>
@ -166,7 +166,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
> >
<h3> <h3>
<translate <translate
translate-context="Content/Auth/Title"
:translate-params="{app: application.name}" :translate-params="{app: application.name}"
> >
%{ app } wants to access your Funkwhale account %{ 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']" :class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
> >
<i class="pencil icon" /> <i class="pencil icon" />
<translate translate-context="Content/Auth/Label/Noun">Write-only</translate> <translate >Write-only</translate>
</span> </span>
<span <span
v-else-if="!topic.write && topic.read" v-else-if="!topic.write && topic.read"
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']" :class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
> >
<translate translate-context="Content/Auth/Label/Noun">Read-only</translate> <translate >Read-only</translate>
</span> </span>
<span <span
v-else-if="topic.write && topic.read" v-else-if="topic.write && topic.read"
:class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']" :class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
> >
<i class="pencil icon" /> <i class="pencil icon" />
<translate translate-context="Content/Auth/Label/Noun">Full access</translate> <translate >Full access</translate>
</span> </span>
<i :class="[topic.icon, 'icon']" /> <i :class="[topic.icon, 'icon']" />
<div class="content"> <div class="content">
@ -207,7 +207,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
</div> </div>
</h4> </h4>
<div v-if="unknownRequestedScopes.length > 0"> <div v-if="unknownRequestedScopes.length > 0">
<p><strong><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 <ul
v-for="(unknownscope, key) in unknownRequestedScopes" v-for="(unknownscope, key) in unknownRequestedScopes"
:key="key" :key="key"
@ -221,7 +221,7 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
> >
<i class="lock open icon" /> <i class="lock open icon" />
<translate <translate
translate-context="Content/Signup/Button.Label/Verb"
:translate-params="{app: application.name}" :translate-params="{app: application.name}"
> >
Authorize %{ app } Authorize %{ app }
@ -230,21 +230,21 @@ whenever(() => props.clientId, fetchApplication, { immediate: true })
<p <p
v-if="redirectUri === 'urn:ietf:wg:oauth:2.0:oob'" v-if="redirectUri === 'urn:ietf:wg:oauth:2.0:oob'"
v-translate v-translate
translate-context="Content/Auth/Paragraph"
> >
You will be shown a code to copy-paste in the application. You will be shown a code to copy-paste in the application.
</p> </p>
<p <p
v-else v-else
v-translate="{url: redirectUri}" v-translate="{url: redirectUri}"
translate-context="Content/Auth/Paragraph"
:translate-params="{url: redirectUri}" :translate-params="{url: redirectUri}"
> >
You will be redirected to <strong>%{ url }</strong> You will be redirected to <strong>%{ url }</strong>
</p> </p>
</form> </form>
<div v-else-if="code"> <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" /> <copy-input :value="code" />
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@ import type { BackendError } from '~/types'
import type { RouteLocationRaw } from 'vue-router' import type { RouteLocationRaw } from 'vue-router'
import { ref, reactive, computed, onMounted, nextTick } from 'vue' import { ref, reactive, computed, onMounted, nextTick } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import PasswordInput from '~/components/forms/PasswordInput.vue' import PasswordInput from '~/components/forms/PasswordInput.vue'
@ -21,7 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
}) })
const domain = location.hostname const domain = location.hostname
const { $pgettext } = useGettext() const { t } = useI18n()
const store = useStore() const store = useStore()
const credentials = reactive({ const credentials = reactive({
@ -30,7 +30,7 @@ const credentials = reactive({
}) })
const labels = computed(() => ({ 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() const username = ref()
@ -75,18 +75,18 @@ const submit = async () => {
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Login/Error message.Title"> <translate >
We cannot log you in We cannot log you in
</translate> </translate>
</h4> </h4>
<ul class="list"> <ul class="list">
<li v-if="errors[0] == 'invalid_credentials' && $store.state.instance.settings.moderation.signup_approval_enabled.value"> <li v-if="errors[0] == 'invalid_credentials' && $store.state.instance.settings.moderation.signup_approval_enabled.value">
<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. If you signed-up recently, you may need to wait before our moderation team review your account, or verify your e-mail address.
</translate> </translate>
</li> </li>
<li v-else-if="errors[0] == 'invalid_credentials'"> <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. Please double-check that your username and password combination is correct and make sure you verified your e-mail address.
</translate> </translate>
</li> </li>
@ -98,11 +98,11 @@ const submit = async () => {
<template v-if="domain === $store.getters['instance/domain']"> <template v-if="domain === $store.getters['instance/domain']">
<div class="field"> <div class="field">
<label for="username-field"> <label for="username-field">
<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"> <template v-if="showSignup">
| |
<router-link :to="{path: '/signup'}"> <router-link :to="{path: '/signup'}">
<translate translate-context="*/Signup/Link/Verb">Create an account</translate> <translate >Create an account</translate>
</router-link> </router-link>
</template> </template>
</label> </label>
@ -119,12 +119,12 @@ const submit = async () => {
</div> </div>
<div class="field"> <div class="field">
<label for="password-field"> <label for="password-field">
<translate translate-context="*/*/*">Password</translate> | <translate >Password</translate> |
<router-link <router-link
tabindex="1" tabindex="1"
:to="{name: 'auth.password-reset', query: {email: credentials.username}}" :to="{name: 'auth.password-reset', query: {email: credentials.username}}"
> >
<translate translate-context="*/Login/*/Verb">Reset your password</translate> <translate >Reset your password</translate>
</router-link> </router-link>
</label> </label>
<password-input <password-input
@ -137,7 +137,6 @@ const submit = async () => {
<template v-else> <template v-else>
<p> <p>
<translate <translate
translate-context="Content/Auth/Paragraph"
:translate-params="{domain: $store.getters['instance/domain']}" :translate-params="{domain: $store.getters['instance/domain']}"
> >
You will be redirected to %{ domain } to authenticate. You will be redirected to %{ domain } to authenticate.
@ -148,7 +147,7 @@ const submit = async () => {
:class="['ui', {'loading': isLoading}, 'right', 'floated', buttonClasses, 'button']" :class="['ui', {'loading': isLoading}, 'right', 'floated', buttonClasses, 'button']"
type="submit" type="submit"
> >
<translate translate-context="*/Login/*/Verb"> <translate >
Login Login
</translate> </translate>
</button> </button>

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/Login/Title', 'Log Out') title: t('Log Out')
})) }))
</script> </script>
@ -19,13 +19,13 @@ const labels = computed(() => ({
class="ui small text container" class="ui small text container"
> >
<h2> <h2>
<translate translate-context="Content/Login/Title"> <translate >
Are you sure you want to log out? Are you sure you want to log out?
</translate> </translate>
</h2> </h2>
<p <p
v-translate="{username: $store.state.auth.username}" v-translate="{username: $store.state.auth.username}"
translate-context="Content/Login/Paragraph"
> >
You are currently logged in as %{ username } You are currently logged in as %{ username }
</p> </p>
@ -33,7 +33,7 @@ const labels = computed(() => ({
class="ui button" class="ui button"
@click="$store.dispatch('auth/logout')" @click="$store.dispatch('auth/logout')"
> >
<translate translate-context="Content/Login/Button.Label"> <translate >
Yes, log me out! Yes, log me out!
</translate> </translate>
</button> </button>
@ -43,7 +43,7 @@ const labels = computed(() => ({
class="ui small text container" class="ui small text container"
> >
<h2> <h2>
<translate translate-context="Content/Login/Title"> <translate >
You aren't currently logged in You aren't currently logged in
</translate> </translate>
</h2> </h2>
@ -51,7 +51,7 @@ const labels = computed(() => ({
to="/login" to="/login"
class="ui button" class="ui button"
> >
<translate translate-context="Content/Login/Button.Label"> <translate >
Log in! Log in!
</translate> </translate>
</router-link> </router-link>

View File

@ -69,7 +69,7 @@ const submitAndScan = async () => {
target="_blank" target="_blank"
> >
<i class="external icon" /> <i class="external icon" />
<translate translate-context="Footer/*/List item.Link/Short, Noun">Documentation</translate> <translate >Documentation</translate>
</a> </a>
</template> </template>
<div class="ui clearing hidden divider" /> <div class="ui clearing hidden divider" />
@ -79,7 +79,7 @@ const submitAndScan = async () => {
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Error while saving plugin Error while saving plugin
</translate> </translate>
</h4> </h4>
@ -99,7 +99,7 @@ const submitAndScan = async () => {
v-model="enabled" v-model="enabled"
type="checkbox" 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> </div>
<div class="ui clearing hidden divider" /> <div class="ui clearing hidden divider" />
@ -107,7 +107,7 @@ const submitAndScan = async () => {
v-if="plugin.source" v-if="plugin.source"
class="field" class="field"
> >
<label for="plugin-library"><translate translate-context="*/*/*/Noun">Library</translate></label> <label for="plugin-library"><translate >Library</translate></label>
<select <select
id="plugin-library" id="plugin-library"
v-model="values['library']" v-model="values['library']"
@ -121,7 +121,7 @@ const submitAndScan = async () => {
</option> </option>
</select> </select>
<div> <div>
<translate translate-context="*/*/Paragraph/Noun"> <translate >
Library where files should be imported. Library where files should be imported.
</translate> </translate>
</div> </div>
@ -198,7 +198,7 @@ const submitAndScan = async () => {
type="submit" type="submit"
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
> >
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Save Save
</translate> </translate>
</button> </button>
@ -207,7 +207,7 @@ const submitAndScan = async () => {
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
@click.prevent="submitAndScan" @click.prevent="submitAndScan"
> >
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Scan Scan
</translate> </translate>
</button> </button>

View File

@ -6,7 +6,7 @@ import axios from 'axios'
import $ from 'jquery' import $ from 'jquery'
import { computed, reactive, ref, onMounted } from 'vue' import { computed, reactive, ref, onMounted } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -30,7 +30,7 @@ interface Settings {
fields: Record<FieldId, Field> fields: Record<FieldId, Field>
} }
const { $pgettext } = useGettext() const { t } = useI18n()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const logger = useLogger() const logger = useLogger()
const router = useRouter() const router = useRouter()
@ -57,7 +57,7 @@ const settings = reactive({
const orderedSettingsFields = SETTINGS_ORDER.map(id => settings.fields[id]) const orderedSettingsFields = SETTINGS_ORDER.map(id => settings.fields[id])
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/Settings/Title', 'Account Settings') title: t('Account Settings')
})) }))
const isLoading = ref(false) const isLoading = ref(false)
@ -224,7 +224,7 @@ const deleteAccount = async () => {
await axios.delete('users/me/', { data: payload }) await axios.delete('users/me/', { data: payload })
store.commit('ui/addMessage', { store.commit('ui/addMessage', {
content: $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() date: new Date()
}) })
@ -276,7 +276,7 @@ fetchOwnedApps()
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<section class="ui text container"> <section class="ui text container">
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Settings/Title"> <translate >
Account settings Account settings
</translate> </translate>
</h2> </h2>
@ -289,7 +289,7 @@ fetchOwnedApps()
class="ui positive message" class="ui positive message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Settings/Message"> <translate >
Settings updated Settings updated
</translate> </translate>
</h4> </h4>
@ -300,7 +300,7 @@ fetchOwnedApps()
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Settings/Error message.Title"> <translate >
Your settings can't be updated Your settings can't be updated
</translate> </translate>
</h4> </h4>
@ -346,7 +346,7 @@ fetchOwnedApps()
:class="['ui', { loading: isLoading }, 'button']" :class="['ui', { loading: isLoading }, 'button']"
type="submit" type="submit"
> >
<translate translate-context="Content/Settings/Button.Label/Verb"> <translate >
Update settings Update settings
</translate> </translate>
</button> </button>
@ -355,7 +355,7 @@ fetchOwnedApps()
<section class="ui text container"> <section class="ui text container">
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Settings/Title"> <translate >
Avatar Avatar
</translate> </translate>
</h2> </h2>
@ -366,7 +366,7 @@ fetchOwnedApps()
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Settings/Error message.Title"> <translate >
Your avatar cannot be saved Your avatar cannot be saved
</translate> </translate>
</h4> </h4>
@ -385,7 +385,7 @@ fetchOwnedApps()
@update:model-value="submitAvatar($event)" @update:model-value="submitAvatar($event)"
@delete="avatar = {uuid: null}" @delete="avatar = {uuid: null}"
> >
<translate translate-context="Content/Channel/*"> <translate >
Avatar Avatar
</translate> </translate>
</attachment-input> </attachment-input>
@ -395,14 +395,14 @@ fetchOwnedApps()
<section class="ui text container"> <section class="ui text container">
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Settings/Title/Verb"> <translate >
Change my password Change my password
</translate> </translate>
</h2> </h2>
<div class="ui message"> <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. Changing your password will also change your Subsonic API password if you have requested one.
</translate>&nbsp;<translate translate-context="Content/Settings/Paragraph"> </translate>&nbsp;<translate >
You will have to update your password on your clients that use this password. You will have to update your password on your clients that use this password.
</translate> </translate>
</div> </div>
@ -416,20 +416,20 @@ fetchOwnedApps()
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Settings/Error message.Title"> <translate >
Your password cannot be changed Your password cannot be changed
</translate> </translate>
</h4> </h4>
<ul class="list"> <ul class="list">
<li v-if="passwordError == 'invalid_credentials'"> <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 Please double-check your password is correct
</translate> </translate>
</li> </li>
</ul> </ul>
</div> </div>
<div class="field"> <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 <password-input
v-model="credentials.oldPassword" v-model="credentials.oldPassword"
field-id="old-password-field" field-id="old-password-field"
@ -437,7 +437,7 @@ fetchOwnedApps()
/> />
</div> </div>
<div class="field"> <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 <password-input
v-model="credentials.newPassword" v-model="credentials.newPassword"
field-id="new-password-field" field-id="new-password-field"
@ -448,12 +448,12 @@ fetchOwnedApps()
:class="['ui', {'loading': isLoadingPassword}, {disabled: !credentials.newPassword || !credentials.oldPassword}, 'warning', 'button']" :class="['ui', {'loading': isLoadingPassword}, {disabled: !credentials.newPassword || !credentials.oldPassword}, 'warning', 'button']"
:action="submitPassword" :action="submitPassword"
> >
<translate translate-context="Content/Settings/Button.Label"> <translate >
Change password Change password
</translate> </translate>
<template #modal-header> <template #modal-header>
<p> <p>
<translate translate-context="Popup/Settings/Title"> <translate >
Change your password? Change your password?
</translate> </translate>
</p> </p>
@ -461,18 +461,18 @@ fetchOwnedApps()
<template #modal-content> <template #modal-content>
<div> <div>
<p> <p>
<translate translate-context="Popup/Settings/Paragraph"> <translate >
Changing your password will have the following consequences: Changing your password will have the following consequences:
</translate> </translate>
</p> </p>
<ul> <ul>
<li> <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 You will be logged out from this session and have to log in with the new one
</translate> </translate>
</li> </li>
<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 Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password
</translate> </translate>
</li> </li>
@ -481,7 +481,7 @@ fetchOwnedApps()
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div> <div>
<translate translate-context="Popup/Settings/Button.Label"> <translate >
Disable access Disable access
</translate> </translate>
</div> </div>
@ -500,13 +500,13 @@ fetchOwnedApps()
<h2 class="ui header"> <h2 class="ui header">
<i class="eye slash outline icon" /> <i class="eye slash outline icon" />
<div class="content"> <div class="content">
<translate translate-context="Content/Settings/Title/Noun"> <translate >
Content filters Content filters
</translate> </translate>
</div> </div>
</h2> </h2>
<p> <p>
<translate translate-context="Content/Settings/Paragraph"> <translate >
Content filters help you hide content you don't want to see on the service. Content filters help you hide content you don't want to see on the service.
</translate> </translate>
</p> </p>
@ -516,12 +516,12 @@ fetchOwnedApps()
@click="$store.dispatch('moderation/fetchContentFilters')" @click="$store.dispatch('moderation/fetchContentFilters')"
> >
<i class="refresh icon" />&nbsp; <i class="refresh icon" />&nbsp;
<translate translate-context="Content/*/Button.Label/Short, Verb"> <translate >
Refresh Refresh
</translate> </translate>
</button> </button>
<h3 class="ui header"> <h3 class="ui header">
<translate translate-context="Content/Settings/Title"> <translate >
Hidden artists Hidden artists
</translate> </translate>
</h3> </h3>
@ -529,12 +529,12 @@ fetchOwnedApps()
<thead> <thead>
<tr> <tr>
<th> <th>
<translate translate-context="*/*/*/Noun"> <translate >
Name Name
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/*/*/Noun"> <translate >
Creation date Creation date
</translate> </translate>
</th> </th>
@ -559,7 +559,7 @@ fetchOwnedApps()
class="ui basic tiny button" class="ui basic tiny button"
@click="$store.dispatch('moderation/deleteContentFilter', filter.uuid)" @click="$store.dispatch('moderation/deleteContentFilter', filter.uuid)"
> >
<translate translate-context="*/*/*/Verb"> <translate >
Delete Delete
</translate> </translate>
</button> </button>
@ -576,13 +576,13 @@ fetchOwnedApps()
<h2 class="ui header"> <h2 class="ui header">
<i class="open lock icon" /> <i class="open lock icon" />
<div class="content"> <div class="content">
<translate translate-context="Content/Settings/Title/Noun"> <translate >
Authorized apps Authorized apps
</translate> </translate>
</div> </div>
</h2> </h2>
<p> <p>
<translate translate-context="Content/Settings/Paragraph"> <translate >
This is the list of applications that have access to your account data. This is the list of applications that have access to your account data.
</translate> </translate>
</p> </p>
@ -591,7 +591,7 @@ fetchOwnedApps()
@click="fetchApps()" @click="fetchApps()"
> >
<i class="refresh icon" />&nbsp; <i class="refresh icon" />&nbsp;
<translate translate-context="Content/*/Button.Label/Short, Verb"> <translate >
Refresh Refresh
</translate> </translate>
</button> </button>
@ -602,12 +602,12 @@ fetchOwnedApps()
<thead> <thead>
<tr> <tr>
<th> <th>
<translate translate-context="*/*/*/Noun"> <translate >
Application Application
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/*/*/Noun"> <translate >
Permissions Permissions
</translate> </translate>
</th> </th>
@ -630,27 +630,27 @@ fetchOwnedApps()
:class="['ui', 'tiny', 'danger', { loading: isRevoking.has(app.client_id) }, 'button']" :class="['ui', 'tiny', 'danger', { loading: isRevoking.has(app.client_id) }, 'button']"
@confirm="revokeApp(app.client_id)" @confirm="revokeApp(app.client_id)"
> >
<translate translate-context="*/*/*/Verb"> <translate >
Revoke Revoke
</translate> </translate>
<template #modal-header> <template #modal-header>
<p <p
v-translate="{application: app.name}" v-translate="{application: app.name}"
translate-context="Popup/Settings/Title"
> >
Revoke access for application "%{ application }"? Revoke access for application "%{ application }"?
</p> </p>
</template> </template>
<template #modal-content> <template #modal-content>
<p> <p>
<translate translate-context="Popup/Settings/Paragraph"> <translate >
This will prevent this application from accessing the service on your behalf. This will prevent this application from accessing the service on your behalf.
</translate> </translate>
</p> </p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div> <div>
<translate translate-context="*/Settings/Button.Label/Verb"> <translate >
Revoke access Revoke access
</translate> </translate>
</div> </div>
@ -662,11 +662,11 @@ fetchOwnedApps()
</table> </table>
<empty-state v-else> <empty-state v-else>
<template #title> <template #title>
<translate translate-context="Content/Applications/Paragraph"> <translate >
You don't have any application connected with your account. You don't have any application connected with your account.
</translate> </translate>
</template> </template>
<translate translate-context="Content/Applications/Paragraph"> <translate >
If you authorize third-party applications to access your data, those applications will be listed here. If you authorize third-party applications to access your data, those applications will be listed here.
</translate> </translate>
</empty-state> </empty-state>
@ -679,13 +679,13 @@ fetchOwnedApps()
<h2 class="ui header"> <h2 class="ui header">
<i class="code icon" /> <i class="code icon" />
<div class="content"> <div class="content">
<translate translate-context="Content/Settings/Title/Noun"> <translate >
Your applications Your applications
</translate> </translate>
</div> </div>
</h2> </h2>
<p> <p>
<translate translate-context="Content/Settings/Paragraph"> <translate >
This is the list of applications that you have registered. This is the list of applications that you have registered.
</translate> </translate>
</p> </p>
@ -693,7 +693,7 @@ fetchOwnedApps()
class="ui success button" class="ui success button"
:to="{name: 'settings.applications.new'}" :to="{name: 'settings.applications.new'}"
> >
<translate translate-context="Content/Settings/Button.Label"> <translate >
Register a new application Register a new application
</translate> </translate>
</router-link> </router-link>
@ -704,17 +704,17 @@ fetchOwnedApps()
<thead> <thead>
<tr> <tr>
<th> <th>
<translate translate-context="*/*/*/Noun"> <translate >
Application Application
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/*/*/Noun"> <translate >
Scopes Scopes
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/*/*/Noun"> <translate >
Creation date Creation date
</translate> </translate>
</th> </th>
@ -742,7 +742,7 @@ fetchOwnedApps()
class="ui tiny success button" class="ui tiny success button"
:to="{name: 'settings.applications.edit', params: {id: app.client_id}}" :to="{name: 'settings.applications.edit', params: {id: app.client_id}}"
> >
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Edit Edit
</translate> </translate>
</router-link> </router-link>
@ -750,27 +750,27 @@ fetchOwnedApps()
:class="['ui', 'tiny', 'danger', { loading: isDeleting.has(app.client_id) }, 'button']" :class="['ui', 'tiny', 'danger', { loading: isDeleting.has(app.client_id) }, 'button']"
@confirm="deleteApp(app.client_id)" @confirm="deleteApp(app.client_id)"
> >
<translate translate-context="*/*/*/Verb"> <translate >
Remove Remove
</translate> </translate>
<template #modal-header> <template #modal-header>
<p <p
v-translate="{application: app.name}" v-translate="{application: app.name}"
translate-context="Popup/Settings/Title"
> >
Remove application "%{ application }"? Remove application "%{ application }"?
</p> </p>
</template> </template>
<template #modal-content> <template #modal-content>
<p> <p>
<translate translate-context="Popup/Settings/Paragraph"> <translate >
This will permanently remove the application and all the associated tokens. This will permanently remove the application and all the associated tokens.
</translate> </translate>
</p> </p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div> <div>
<translate translate-context="*/Settings/Button.Label/Verb"> <translate >
Remove application Remove application
</translate> </translate>
</div> </div>
@ -782,11 +782,11 @@ fetchOwnedApps()
</table> </table>
<empty-state v-else> <empty-state v-else>
<template #title> <template #title>
<translate translate-context="Content/Applications/Paragraph"> <translate >
You don't have registered any application yet. You don't have registered any application yet.
</translate> </translate>
</template> </template>
<translate translate-context="Content/Applications/Paragraph"> <translate >
Register one to integrate Funkwhale with third-party applications. Register one to integrate Funkwhale with third-party applications.
</translate> </translate>
</empty-state> </empty-state>
@ -800,13 +800,13 @@ fetchOwnedApps()
<h2 class="ui header"> <h2 class="ui header">
<i class="code icon" /> <i class="code icon" />
<div class="content"> <div class="content">
<translate translate-context="Content/Settings/Title/Noun"> <translate >
Plugins Plugins
</translate> </translate>
</div> </div>
</h2> </h2>
<p> <p>
<translate translate-context="Content/Settings/Paragraph"> <translate >
Use plugins to extend Funkwhale and get additional features. Use plugins to extend Funkwhale and get additional features.
</translate> </translate>
</p> </p>
@ -814,7 +814,7 @@ fetchOwnedApps()
class="ui success button" class="ui success button"
:to="{name: 'settings.plugins'}" :to="{name: 'settings.plugins'}"
> >
<translate translate-context="Content/Settings/Button.Label"> <translate >
Manage plugins Manage plugins
</translate> </translate>
</router-link> </router-link>
@ -824,20 +824,20 @@ fetchOwnedApps()
<h2 class="ui header"> <h2 class="ui header">
<i class="comment icon" /> <i class="comment icon" />
<div class="content"> <div class="content">
<translate translate-context="*/*/Button.Label"> <translate >
Change my e-mail address Change my e-mail address
</translate> </translate>
</div> </div>
</h2> </h2>
<p> <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. Change the e-mail address associated with your account. We will send a confirmation to the new address.
</translate> </translate>
</p> </p>
<p> <p>
<translate <translate
:translate-params="{email: $store.state.auth.profile?.email}" :translate-params="{email: $store.state.auth.profile?.email}"
translate-context="Content/Settings/Paragraph'"
> >
Your current e-mail address is %{ email }. Your current e-mail address is %{ email }.
</translate> </translate>
@ -852,7 +852,7 @@ fetchOwnedApps()
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Settings/Error message.Title"> <translate >
We cannot change your e-mail address We cannot change your e-mail address
</translate> </translate>
</h4> </h4>
@ -866,7 +866,7 @@ fetchOwnedApps()
</ul> </ul>
</div> </div>
<div class="field"> <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 <input
id="new-email" id="new-email"
v-model="newEmail" v-model="newEmail"
@ -875,7 +875,7 @@ fetchOwnedApps()
> >
</div> </div>
<div class="field"> <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 <password-input
v-model="emailPassword" v-model="emailPassword"
field-id="current-password-field-email" field-id="current-password-field-email"
@ -886,7 +886,7 @@ fetchOwnedApps()
type="submit" type="submit"
class="ui button" class="ui button"
> >
<translate translate-context="*/*/*"> <translate >
Update Update
</translate> </translate>
</button> </button>
@ -897,13 +897,13 @@ fetchOwnedApps()
<h2 class="ui header"> <h2 class="ui header">
<i class="trash icon" /> <i class="trash icon" />
<div class="content"> <div class="content">
<translate translate-context="*/*/Button.Label"> <translate >
Delete my account Delete my account
</translate> </translate>
</div> </div>
</h2> </h2>
<p> <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. You can permanently and irreversibly delete your account and all the associated data using the form below. You will be asked for confirmation.
</translate> </translate>
</p> </p>
@ -911,7 +911,7 @@ fetchOwnedApps()
role="alert" role="alert"
class="ui warning message" 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. 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> </translate>
</div> </div>
@ -922,7 +922,7 @@ fetchOwnedApps()
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Settings/Error message.Title"> <translate >
We cannot delete your account We cannot delete your account
</translate> </translate>
</h4> </h4>
@ -936,7 +936,7 @@ fetchOwnedApps()
</ul> </ul>
</div> </div>
<div class="field"> <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 <password-input
v-model="deleteAccountPassword" v-model="deleteAccountPassword"
field-id="current-password-field" field-id="current-password-field"
@ -947,12 +947,12 @@ fetchOwnedApps()
:class="['ui', {'loading': isDeletingAccount}, {disabled: !deleteAccountPassword}, {danger: deleteAccountPassword}, 'button']" :class="['ui', {'loading': isDeletingAccount}, {disabled: !deleteAccountPassword}, {danger: deleteAccountPassword}, 'button']"
:action="deleteAccount" :action="deleteAccount"
> >
<translate translate-context="*/*/Button.Label"> <translate >
Delete my account Delete my account
</translate> </translate>
<template #modal-header> <template #modal-header>
<p> <p>
<translate translate-context="Popup/Settings/Title"> <translate >
Do you want to delete your account? Do you want to delete your account?
</translate> </translate>
</p> </p>
@ -960,7 +960,7 @@ fetchOwnedApps()
<template #modal-content> <template #modal-content>
<div> <div>
<p> <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. This is irreversible and will permanently remove your data from our servers. You will we immediately logged out.
</translate> </translate>
</p> </p>
@ -968,7 +968,7 @@ fetchOwnedApps()
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div> <div>
<translate translate-context="*/*/Button.Label"> <translate >
Delete my account Delete my account
</translate> </translate>
</div> </div>

View File

@ -3,7 +3,7 @@ import type { RouteLocationRaw } from 'vue-router'
import type { BackendError, Form } from '~/types' import type { BackendError, Form } from '~/types'
import { computed, reactive, ref, watchEffect } from 'vue' import { computed, reactive, ref, watchEffect } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
@ -30,14 +30,14 @@ const props = withDefaults(defineProps<Props>(), {
signupApprovalEnabled: undefined signupApprovalEnabled: undefined
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const logger = useLogger() const logger = useLogger()
const store = useStore() const store = useStore()
const labels = computed(() => ({ const labels = computed(() => ({
placeholder: $pgettext('Content/Signup/Form/Placeholder', 'Enter your invitation code (case insensitive)'), placeholder: t('Enter your invitation code (case insensitive)'),
usernamePlaceholder: $pgettext('Content/Signup/Form/Placeholder', 'Enter your username'), usernamePlaceholder: t('Enter your username'),
emailPlaceholder: $pgettext('Content/Signup/Form/Placeholder', 'Enter your e-mail address') emailPlaceholder: t('Enter your e-mail address')
})) }))
const signupRequiresApproval = computed(() => props.signupApprovalEnabled ?? store.state.instance.settings.moderation.signup_approval_enabled.value) const signupRequiresApproval = computed(() => props.signupApprovalEnabled ?? store.state.instance.settings.moderation.signup_approval_enabled.value)
@ -88,18 +88,18 @@ fetchInstanceSettings()
<div v-if="submitted"> <div v-if="submitted">
<div class="ui success message"> <div class="ui success message">
<p v-if="signupRequiresApproval"> <p v-if="signupRequiresApproval">
<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. Your account request was successfully submitted. You will be notified by e-mail when our moderation team has reviewed your request.
</translate> </translate>
</p> </p>
<p v-else> <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. Your account was successfully created. Please verify your e-mail address before trying to login.
</translate> </translate>
</p> </p>
</div> </div>
<h2> <h2>
<translate translate-context="Content/Login/Title/Verb"> <translate >
Log in to your Funkwhale account Log in to your Funkwhale account
</translate> </translate>
</h2> </h2>
@ -117,7 +117,7 @@ fetchInstanceSettings()
v-if="!$store.state.instance.settings.users.registration_enabled.value" v-if="!$store.state.instance.settings.users.registration_enabled.value"
class="ui message" class="ui message"
> >
<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. Public registrations are not possible on this instance. You will need an invitation code to sign up.
</translate> </translate>
</p> </p>
@ -125,7 +125,7 @@ fetchInstanceSettings()
v-else-if="signupRequiresApproval" v-else-if="signupRequiresApproval"
class="ui message" class="ui message"
> >
<translate translate-context="Content/Signup/Form/Paragraph"> <translate >
Registrations on this pod are open, but reviewed by moderators before approval. Registrations on this pod are open, but reviewed by moderators before approval.
</translate> </translate>
</p> </p>
@ -143,7 +143,7 @@ fetchInstanceSettings()
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Signup/Form/Paragraph"> <translate >
Your account cannot be created. Your account cannot be created.
</translate> </translate>
</h4> </h4>
@ -157,7 +157,7 @@ fetchInstanceSettings()
</ul> </ul>
</div> </div>
<div class="required field"> <div class="required field">
<label for="username-field"><translate translate-context="Content/*/*">Username</translate></label> <label for="username-field"><translate >Username</translate></label>
<input <input
id="username-field" id="username-field"
ref="username" ref="username"
@ -170,7 +170,7 @@ fetchInstanceSettings()
> >
</div> </div>
<div class="required field"> <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 <input
id="email-field" id="email-field"
ref="email" ref="email"
@ -182,7 +182,7 @@ fetchInstanceSettings()
> >
</div> </div>
<div class="required field"> <div class="required field">
<label for="password-field"><translate translate-context="*/*/*">Password</translate></label> <label for="password-field"><translate >Password</translate></label>
<password-input <password-input
v-model="payload.password1" v-model="payload.password1"
field-id="password-field" field-id="password-field"
@ -192,7 +192,7 @@ fetchInstanceSettings()
v-if="!$store.state.instance.settings.users.registration_enabled.value" v-if="!$store.state.instance.settings.users.registration_enabled.value"
class="required field" class="required field"
> >
<label for="invitation-code"><translate translate-context="Content/*/Input.Label">Invitation code</translate></label> <label for="invitation-code"><translate >Invitation code</translate></label>
<input <input
id="invitation-code" id="invitation-code"
v-model="payload.invitation" v-model="payload.invitation"
@ -229,7 +229,7 @@ fetchInstanceSettings()
:class="['ui', buttonClasses, {'loading': isLoading}, ' right floated button']" :class="['ui', buttonClasses, {'loading': isLoading}, ' right floated button']"
type="submit" type="submit"
> >
<translate translate-context="Content/Signup/Button.Label"> <translate >
Create my account Create my account
</translate> </translate>
</button> </button>

View File

@ -1,19 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import type { BackendError } from '~/types' import type { BackendError } from '~/types'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
import PasswordInput from '~/components/forms/PasswordInput.vue' import PasswordInput from '~/components/forms/PasswordInput.vue'
const { $pgettext } = useGettext() const { t } = useI18n()
const store = useStore() const store = useStore()
const subsonicEnabled = computed(() => store.state.instance.settings.subsonic.enabled.value) const subsonicEnabled = computed(() => store.state.instance.settings.subsonic.enabled.value)
const labels = computed(() => ({ const labels = computed(() => ({
subsonicField: $pgettext('Content/Password/Input.label', 'Your subsonic API password') subsonicField: t('Your subsonic API password')
})) }))
const errors = ref([] as string[]) const errors = ref([] as string[])
@ -38,7 +38,7 @@ const fetchToken = async () => {
const showToken = ref(false) const showToken = ref(false)
const successMessage = ref('') const successMessage = ref('')
const requestNewToken = async () => { const requestNewToken = async () => {
successMessage.value = $pgettext('Content/Settings/Message', 'Password updated') successMessage.value = t('Password updated')
success.value = false success.value = false
errors.value = [] errors.value = []
isLoading.value = true isLoading.value = true
@ -56,7 +56,7 @@ const requestNewToken = async () => {
} }
const disable = async () => { const disable = async () => {
successMessage.value = $pgettext('Content/Settings/Message', 'Access disabled') successMessage.value = t('Access disabled')
success.value = false success.value = false
errors.value = [] errors.value = []
isLoading.value = true isLoading.value = true
@ -82,7 +82,7 @@ fetchToken()
@submit.prevent="requestNewToken()" @submit.prevent="requestNewToken()"
> >
<h2> <h2>
<translate translate-context="Content/Settings/Title"> <translate >
Subsonic API password Subsonic API password
</translate> </translate>
</h2> </h2>
@ -90,19 +90,19 @@ fetchToken()
v-if="!subsonicEnabled" v-if="!subsonicEnabled"
class="ui message" class="ui message"
> >
<translate translate-context="Content/Settings/Paragraph"> <translate >
The Subsonic API is not available on this Funkwhale instance. The Subsonic API is not available on this Funkwhale instance.
</translate> </translate>
</p> </p>
<p> <p>
<translate translate-context="Content/Settings/Paragraph'"> <translate >
Funkwhale is compatible with other music players that support the Subsonic API. Funkwhale is compatible with other music players that support the Subsonic API.
</translate>&nbsp;<translate translate-context="Content/Settings/Paragraph"> </translate>&nbsp;<translate >
You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance. You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.
</translate> </translate>
</p> </p>
<p> <p>
<translate translate-context="Content/Settings/Paragraph"> <translate >
However, accessing Funkwhale from those clients requires a separate password you can set below. However, accessing Funkwhale from those clients requires a separate password you can set below.
</translate> </translate>
</p> </p>
@ -111,7 +111,7 @@ fetchToken()
href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients" href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients"
target="_blank" target="_blank"
> >
<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> </a>
</p> </p>
<div <div
@ -128,7 +128,7 @@ fetchToken()
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Error Error
</translate> </translate>
</h4> </h4>
@ -164,26 +164,26 @@ fetchToken()
:class="['ui', {'loading': isLoading}, 'button']" :class="['ui', {'loading': isLoading}, 'button']"
:action="requestNewToken" :action="requestNewToken"
> >
<translate translate-context="*/Settings/Button.Label/Verb"> <translate >
Request a new password Request a new password
</translate> </translate>
<template #modal-header> <template #modal-header>
<p> <p>
<translate translate-context="Popup/Settings/Title"> <translate >
Request a new Subsonic API password? Request a new Subsonic API password?
</translate> </translate>
</p> </p>
</template> </template>
<template #modal-content> <template #modal-content>
<p> <p>
<translate translate-context="Popup/Settings/Paragraph"> <translate >
This will log you out from existing devices that use the current password. This will log you out from existing devices that use the current password.
</translate> </translate>
</p> </p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div> <div>
<translate translate-context="*/Settings/Button.Label/Verb"> <translate >
Request a new password Request a new password
</translate> </translate>
</div> </div>
@ -195,7 +195,7 @@ fetchToken()
:class="['ui', {'loading': isLoading}, 'button']" :class="['ui', {'loading': isLoading}, 'button']"
@click="requestNewToken" @click="requestNewToken"
> >
<translate translate-context="Content/Settings/Button.Label/Verb"> <translate >
Request a password Request a password
</translate> </translate>
</button> </button>
@ -204,26 +204,26 @@ fetchToken()
:class="['ui', {'loading': isLoading}, 'warning', 'button']" :class="['ui', {'loading': isLoading}, 'warning', 'button']"
:action="disable" :action="disable"
> >
<translate translate-context="Content/Settings/Button.Label/Verb"> <translate >
Disable Subsonic access Disable Subsonic access
</translate> </translate>
<template #modal-header> <template #modal-header>
<p> <p>
<translate translate-context="Popup/Settings/Title"> <translate >
Disable Subsonic API access? Disable Subsonic API access?
</translate> </translate>
</p> </p>
</template> </template>
<template #modal-content> <template #modal-content>
<p> <p>
<translate translate-context="Popup/Settings/Paragraph"> <translate >
This will completely disable access to the Subsonic API using from account. This will completely disable access to the Subsonic API using from account.
</translate> </translate>
</p> </p>
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div> <div>
<translate translate-context="Popup/Settings/Button.Label"> <translate >
Disable access Disable access
</translate> </translate>
</div> </div>

View File

@ -59,7 +59,7 @@ defineExpose({
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Error while creating Error while creating
</translate> </translate>
</h4> </h4>
@ -74,7 +74,7 @@ defineExpose({
</div> </div>
<div class="ui required field"> <div class="ui required field">
<label for="album-title"> <label for="album-title">
<translate translate-context="*/*/*/Noun">Title</translate> <translate >Title</translate>
</label> </label>
<input <input
v-model="title" v-model="title"

View File

@ -35,13 +35,13 @@ const albumForm = ref()
<h4 class="header"> <h4 class="header">
<translate <translate
v-if="channel.content_category === 'podcast'" v-if="channel.content_category === 'podcast'"
translate-context="Popup/Channels/Title/Verb"
> >
New series New series
</translate> </translate>
<translate <translate
v-else v-else
translate-context="Popup/Channels/Title"
> >
New album New album
</translate> </translate>
@ -57,7 +57,7 @@ const albumForm = ref()
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic cancel button"> <button class="ui basic cancel button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</button> </button>
@ -66,7 +66,7 @@ const albumForm = ref()
:disabled="!submittable" :disabled="!submittable"
@click.stop.prevent="albumForm.submit()" @click.stop.prevent="albumForm.submit()"
> >
<translate translate-context="*/*/Button.Label"> <translate >
Create Create
</translate> </translate>
</button> </button>

View File

@ -49,11 +49,11 @@ watch(() => props.channel, fetchData, { immediate: true })
<label for="album-dropdown"> <label for="album-dropdown">
<translate <translate
v-if="channel && channel.artist && channel.artist.content_category === 'podcast'" v-if="channel && channel.artist && channel.artist.content_category === 'podcast'"
translate-context="*/*/*"
>Series</translate> >Series</translate>
<translate <translate
v-else v-else
translate-context="*/*/*"
>Album</translate> >Album</translate>
</label> </label>
<select <select
@ -62,7 +62,7 @@ watch(() => props.channel, fetchData, { immediate: true })
class="ui search normal dropdown" class="ui search normal dropdown"
> >
<option value=""> <option value="">
<translate translate-context="*/*/*"> <translate >
None None
</translate> </translate>
</option> </option>
@ -72,7 +72,7 @@ watch(() => props.channel, fetchData, { immediate: true })
:value="album.id" :value="album.id"
> >
{{ album.title }} (<translate {{ album.title }} (<translate
translate-context="*/*/*"
:translate-params="{count: album.tracks_count}" :translate-params="{count: album.tracks_count}"
:translate-n="album.tracks_count" :translate-n="album.tracks_count"
translate-plural="%{ count } tracks" translate-plural="%{ count } tracks"

View File

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

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Channel } from '~/types' import type { Channel } from '~/types'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -19,17 +19,17 @@ interface Props {
const emit = defineEmits<Events>() const emit = defineEmits<Events>()
const props = defineProps<Props>() const props = defineProps<Props>()
const { $pgettext } = useGettext() const { t } = useI18n()
const store = useStore() const store = useStore()
const isSubscribed = computed(() => store.getters['channels/isSubscribed'](props.channel.uuid)) const isSubscribed = computed(() => store.getters['channels/isSubscribed'](props.channel.uuid))
const title = computed(() => isSubscribed.value const title = computed(() => isSubscribed.value
? $pgettext('Content/Channel/Button/Verb', 'Unsubscribe') ? t('Unsubscribe')
: $pgettext('Content/Channel/Button/Verb', 'Subscribe') : t('Subscribe')
) )
const message = computed(() => ({ 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 () => { const toggle = async () => {

View File

@ -5,7 +5,7 @@ import type { VueUploadItem } from 'vue-upload-component'
import { computed, ref, reactive, watchEffect, watch } from 'vue' import { computed, ref, reactive, watchEffect, watch } from 'vue'
import { whenever, useCurrentElement } from '@vueuse/core' import { whenever, useCurrentElement } from '@vueuse/core'
import { humanSize } from '~/utils/filters' import { humanSize } from '~/utils/filters'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
@ -52,7 +52,7 @@ const props = withDefaults(defineProps<Props>(), {
channel: null channel: null
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const store = useStore() const store = useStore()
const errors = ref([] as string[]) const errors = ref([] as string[])
@ -396,7 +396,7 @@ watchEffect(() => {
}) })
const labels = computed(() => ({ const labels = computed(() => ({
editTitle: $pgettext('Content/*/Button.Label/Verb', 'Edit') editTitle: t('Edit')
})) }))
</script> </script>
@ -411,7 +411,7 @@ const labels = computed(() => ({
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Error while publishing Error while publishing
</translate> </translate>
</h4> </h4>
@ -426,7 +426,7 @@ const labels = computed(() => ({
</div> </div>
<div :class="['ui', 'required', {hidden: step > 1}, 'field']"> <div :class="['ui', 'required', {hidden: step > 1}, 'field']">
<label for="channel-dropdown"> <label for="channel-dropdown">
<translate translate-context="*/*/*">Channel</translate> <translate >Channel</translate>
</label> </label>
<div <div
id="channel-dropdown" id="channel-dropdown"
@ -449,7 +449,7 @@ const labels = computed(() => ({
<div class="content"> <div class="content">
<p> <p>
<i class="copyright icon" /> <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. Add a license to your upload to ensure some freedoms to your public.
</translate> </translate>
</p> </p>
@ -464,7 +464,7 @@ const labels = computed(() => ({
<div class="content"> <div class="content">
<p> <p>
<i class="warning icon" /> <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. You don't have any space left to upload your files. Please contact the moderators.
</translate> </translate>
</p> </p>
@ -477,7 +477,7 @@ const labels = computed(() => ({
> >
<p> <p>
<i class="redo icon" /> <i class="redo icon" />
<translate translate-context="Popup/Channels/Paragraph"> <translate >
You have some draft uploads pending publication. You have some draft uploads pending publication.
</translate> </translate>
</p> </p>
@ -485,7 +485,7 @@ const labels = computed(() => ({
class="ui basic button" class="ui basic button"
@click.stop.prevent="includeDraftUploads = false" @click.stop.prevent="includeDraftUploads = false"
> >
<translate translate-context="*/*/*"> <translate >
Ignore Ignore
</translate> </translate>
</button> </button>
@ -493,7 +493,7 @@ const labels = computed(() => ({
class="ui basic button" class="ui basic button"
@click.stop.prevent="includeDraftUploads = true" @click.stop.prevent="includeDraftUploads = true"
> >
<translate translate-context="*/*/*"> <translate >
Resume Resume
</translate> </translate>
</button> </button>
@ -547,19 +547,19 @@ const labels = computed(() => ({
<template v-else> <template v-else>
<translate <translate
v-if="file.active" v-if="file.active"
translate-context="Channels/*/*"
> >
Uploading Uploading
</translate> </translate>
<translate <translate
v-else-if="file.error" v-else-if="file.error"
translate-context="Channels/*/*"
> >
Errored Errored
</translate> </translate>
<translate <translate
v-else v-else
translate-context="Channels/*/*"
> >
Pending Pending
</translate> </translate>
@ -567,12 +567,12 @@ const labels = computed(() => ({
· {{ parseFloat(file.progress ?? '0') }}% · {{ parseFloat(file.progress ?? '0') }}%
</template> </template>
· <a @click.stop.prevent="remove(file)"> · <a @click.stop.prevent="remove(file)">
<translate translate-context="Content/Radio/Button.Label/Verb">Remove</translate> <translate >Remove</translate>
</a> </a>
<template v-if="file.error"> <template v-if="file.error">
· ·
<a @click.stop.prevent="retry(file)"> <a @click.stop.prevent="retry(file)">
<translate translate-context="*/*/*">Retry</translate> <translate >Retry</translate>
</a> </a>
</template> </template>
</div> </div>
@ -592,7 +592,7 @@ const labels = computed(() => ({
<p> <p>
<i class="info icon" /> <i class="info icon" />
<translate <translate
translate-context="Content/Library/Paragraph"
:translate-params="{extensions: $store.state.ui.supportedExtensions.join(', ')}" :translate-params="{extensions: $store.state.ui.supportedExtensions.join(', ')}"
> >
Supported extensions: %{ extensions } Supported extensions: %{ extensions }
@ -615,13 +615,13 @@ const labels = computed(() => ({
> >
<div> <div>
<i class="upload icon" />&nbsp; <i class="upload icon" />&nbsp;
<translate translate-context="Content/Channels/Paragraph"> <translate >
Drag and drop your files here or open the browser to upload your files Drag and drop your files here or open the browser to upload your files
</translate> </translate>
</div> </div>
<div class="ui very small divider" /> <div class="ui very small divider" />
<div> <div>
<translate translate-context="*/*/*"> <translate >
Browse Browse
</translate> </translate>
</div> </div>

View File

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

View File

@ -5,7 +5,7 @@ import { humanSize } from '~/utils/filters'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useStore } from '~/store' import { useStore } from '~/store'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
const store = useStore() const store = useStore()
const router = useRouter() 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 update = (value: boolean) => store.commit('channels/showUploadModal', { show: value })
const { $npgettext, $gettext } = useGettext() const { t } = useI18n()
const uploadForm = ref() const uploadForm = ref()
@ -29,8 +29,8 @@ const statusInfo = computed(() => {
} }
if (statusData.value.totalFiles) { if (statusData.value.totalFiles) {
const msg = $npgettext('*/*/*', '%{ count } file', '%{ count } files', statusData.value.totalFiles) const msg = t('no files | %{ count } file | %{ count } files', statusData.value.totalFiles)
info.push($gettext(msg, { count: statusData.value.totalFiles })) info.push(t(msg, { count: statusData.value.totalFiles }))
} }
if (statusData.value.progress) { if (statusData.value.progress) {
@ -56,25 +56,25 @@ const isLoading = ref(false)
<h4 class="header"> <h4 class="header">
<translate <translate
v-if="step === 1" v-if="step === 1"
translate-context="Popup/Channels/Title/Verb"
> >
Publish audio Publish audio
</translate> </translate>
<translate <translate
v-else-if="step === 2" v-else-if="step === 2"
translate-context="Popup/Channels/Title"
> >
Files to upload Files to upload
</translate> </translate>
<translate <translate
v-else-if="step === 3" v-else-if="step === 3"
translate-context="Popup/Channels/Title"
> >
Upload details Upload details
</translate> </translate>
<translate <translate
v-else-if="step === 4" v-else-if="step === 4"
translate-context="Popup/Channels/Title"
> >
Processing uploads Processing uploads
</translate> </translate>
@ -95,7 +95,7 @@ const isLoading = ref(false)
</template> </template>
<div class="ui very small hidden divider" /> <div class="ui very small hidden divider" />
<template v-if="statusData && statusData.quotaStatus"> <template v-if="statusData && statusData.quotaStatus">
<translate translate-context="Content/Library/Paragraph"> <translate >
Remaining storage space: Remaining storage space:
</translate> </translate>
{{ humanSize((statusData.quotaStatus.remaining - statusData.uploadedSize) * 1000 * 1000) }} {{ humanSize((statusData.quotaStatus.remaining - statusData.uploadedSize) * 1000 * 1000) }}
@ -106,7 +106,7 @@ const isLoading = ref(false)
v-if="step === 1" v-if="step === 1"
class="ui basic cancel button" class="ui basic cancel button"
> >
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</button> </button>
@ -115,7 +115,7 @@ const isLoading = ref(false)
class="ui basic button" class="ui basic button"
@click.stop.prevent="uploadForm.step -= 1" @click.stop.prevent="uploadForm.step -= 1"
> >
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Previous step Previous step
</translate> </translate>
</button> </button>
@ -124,7 +124,7 @@ const isLoading = ref(false)
class="ui basic button" class="ui basic button"
@click.stop.prevent="uploadForm.step -= 1" @click.stop.prevent="uploadForm.step -= 1"
> >
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Update Update
</translate> </translate>
</button> </button>
@ -133,7 +133,7 @@ const isLoading = ref(false)
class="ui primary button" class="ui primary button"
@click.stop.prevent="uploadForm.step += 1" @click.stop.prevent="uploadForm.step += 1"
> >
<translate translate-context="*/*/Button.Label"> <translate >
Next step Next step
</translate> </translate>
</button> </button>
@ -147,7 +147,7 @@ const isLoading = ref(false)
:disabled="!statusData?.canSubmit || undefined" :disabled="!statusData?.canSubmit || undefined"
@click.prevent.stop="uploadForm.publish" @click.prevent.stop="uploadForm.publish"
> >
<translate translate-context="*/Channels/Button.Label"> <translate >
Publish Publish
</translate> </translate>
</button> </button>
@ -164,7 +164,7 @@ const isLoading = ref(false)
class="basic item" class="basic item"
@click="update(false)" @click="update(false)"
> >
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Finish later Finish later
</translate> </translate>
</div> </div>
@ -176,7 +176,7 @@ const isLoading = ref(false)
class="ui basic cancel button" class="ui basic cancel button"
@click="update(false)" @click="update(false)"
> >
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Close Close
</translate> </translate>
</button> </button>

View File

@ -2,7 +2,7 @@
import type { BackendError } from '~/types' import type { BackendError } from '~/types'
import { ref, computed, reactive, watch } from 'vue' import { ref, computed, reactive, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import axios from 'axios' import axios from 'axios'
@ -41,7 +41,7 @@ const props = withDefaults(defineProps<Props>(), {
customObjects: () => [] customObjects: () => []
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const currentActionName = ref(props.actions[0]?.name ?? null) const currentActionName = ref(props.actions[0]?.name ?? null)
const currentAction = computed(() => props.actions.find(action => action.name === currentActionName.value)) 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(() => ({ const labels = computed(() => ({
refresh: $pgettext('Content/*/Button.Tooltip/Verb', 'Refresh table content'), refresh: t('Refresh table content'),
selectAllItems: $pgettext('Content/*/Select/Verb', 'Select all items'), selectAllItems: t('Select all items'),
performAction: $pgettext('Content/*/Button.Label', 'Perform actions'), performAction: t('Perform actions'),
selectItem: $pgettext('Content/*/Select/Verb', 'Select') selectItem: t('Select')
})) }))
const errors = ref([] as string[]) const errors = ref([] as string[])
@ -167,7 +167,7 @@ const launchAction = async () => {
class="right floated" class="right floated"
> >
<span v-if="needsRefresh"> <span v-if="needsRefresh">
<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> </span>
<button <button
class="ui basic icon button" class="ui basic icon button"
@ -185,7 +185,7 @@ const launchAction = async () => {
> >
<div class="ui inline fields"> <div class="ui inline fields">
<div class="field"> <div class="field">
<label for="actions-select"><translate translate-context="Content/*/*/Noun">Actions</translate></label> <label for="actions-select"><translate >Actions</translate></label>
<select <select
id="actions-select" id="actions-select"
v-model="currentActionName" v-model="currentActionName"
@ -208,14 +208,14 @@ const launchAction = async () => {
:aria-label="labels.performAction" :aria-label="labels.performAction"
@confirm="launchAction" @confirm="launchAction"
> >
<translate translate-context="Content/*/Button.Label/Short, Verb"> <translate >
Go Go
</translate> </translate>
<template #modal-header> <template #modal-header>
<p> <p>
<translate <translate
key="1" key="1"
translate-context="Modal/*/Title"
:translate-n="affectedObjectsCount" :translate-n="affectedObjectsCount"
:translate-params="{count: affectedObjectsCount, action: currentActionName}" :translate-params="{count: affectedObjectsCount, action: currentActionName}"
translate-plural="Do you want to launch %{ action } on %{ count } elements?" translate-plural="Do you want to launch %{ action } on %{ count } elements?"
@ -231,7 +231,7 @@ const launchAction = async () => {
</template> </template>
<translate <translate
v-else 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. This may affect a lot of elements or have irreversible consequences, please double check this is really what you want.
</translate> </translate>
@ -239,7 +239,7 @@ const launchAction = async () => {
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<div :aria-label="labels.performAction"> <div :aria-label="labels.performAction">
<translate translate-context="Modal/*/Button.Label/Short, Verb"> <translate >
Launch Launch
</translate> </translate>
</div> </div>
@ -252,7 +252,7 @@ const launchAction = async () => {
:class="['ui', {disabled: checked.length === 0}, {'loading': isLoading}, 'button']" :class="['ui', {disabled: checked.length === 0}, {'loading': isLoading}, 'button']"
@click="launchAction" @click="launchAction"
> >
<translate translate-context="Content/*/Button.Label/Short, Verb"> <translate >
Go Go
</translate> </translate>
</button> </button>
@ -260,7 +260,7 @@ const launchAction = async () => {
<div class="count field"> <div class="count field">
<translate <translate
v-if="selectAll" v-if="selectAll"
translate-context="Content/*/Paragraph"
tag="span" tag="span"
:translate-n="objectsData.count" :translate-n="objectsData.count"
:translate-params="{count: objectsData.count, total: objectsData.count}" :translate-params="{count: objectsData.count, total: objectsData.count}"
@ -270,7 +270,7 @@ const launchAction = async () => {
</translate> </translate>
<translate <translate
v-else v-else
translate-context="Content/*/Paragraph"
tag="span" tag="span"
:translate-n="checked.length" :translate-n="checked.length"
:translate-params="{count: checked.length, total: objectsData.count}" :translate-params="{count: checked.length, total: objectsData.count}"
@ -286,7 +286,7 @@ const launchAction = async () => {
> >
<translate <translate
key="3" key="3"
translate-context="Content/*/Link/Verb"
:translate-n="objectsData.count" :translate-n="objectsData.count"
:translate-params="{total: objectsData.count}" :translate-params="{total: objectsData.count}"
translate-plural="Select all %{ total } elements" translate-plural="Select all %{ total } elements"
@ -301,7 +301,7 @@ const launchAction = async () => {
> >
<translate <translate
key="4" key="4"
translate-context="Content/*/Link/Verb"
>Select only current page</translate> >Select only current page</translate>
</a> </a>
</template> </template>
@ -313,7 +313,7 @@ const launchAction = async () => {
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message/Header"> <translate >
Error while applying action Error while applying action
</translate> </translate>
</h4> </h4>
@ -332,7 +332,7 @@ const launchAction = async () => {
> >
<p> <p>
<translate <translate
translate-context="Content/*/Paragraph"
:translate-n="result.updated" :translate-n="result.updated"
:translate-params="{count: result.updated, action: result.action}" :translate-params="{count: result.updated, action: result.action}"
translate-plural="Action %{ action } was launched successfully on %{ count } elements" translate-plural="Action %{ action } was launched successfully on %{ count } elements"

View File

@ -107,7 +107,7 @@ const getAttachmentUrl = (uuid: string) => {
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Your attachment cannot be saved Your attachment cannot be saved
</translate> </translate>
</h4> </h4>
@ -146,7 +146,7 @@ const getAttachmentUrl = (uuid: string) => {
<div class="eleven wide column"> <div class="eleven wide column">
<div class="file-input"> <div class="file-input">
<label :for="attachmentId"> <label :for="attachmentId">
<translate translate-context="*/*/*">Upload New Picture</translate> <translate >Upload New Picture</translate>
</label> </label>
<input <input
:id="attachmentId" :id="attachmentId"
@ -161,7 +161,7 @@ const getAttachmentUrl = (uuid: string) => {
</div> </div>
<div class="ui very small hidden divider" /> <div class="ui very small hidden divider" />
<p> <p>
<translate translate-context="Content/*/Paragraph"> <translate >
PNG or JPG. Dimensions should be between 1400x1400px and 3000x3000px. Maximum file size allowed is 5MB. PNG or JPG. Dimensions should be between 1400x1400px and 3000x3000px. Maximum file size allowed is 5MB.
</translate> </translate>
</p> </p>
@ -170,7 +170,7 @@ const getAttachmentUrl = (uuid: string) => {
class="ui basic tiny button" class="ui basic tiny button"
@click.stop.prevent="remove(value as string)" @click.stop.prevent="remove(value as string)"
> >
<translate translate-context="Content/Radio/Button.Label/Verb"> <translate >
Remove Remove
</translate> </translate>
</button> </button>
@ -179,7 +179,7 @@ const getAttachmentUrl = (uuid: string) => {
class="ui active inverted dimmer" class="ui active inverted dimmer"
> >
<div class="ui indeterminate text loader"> <div class="ui indeterminate text loader">
<translate translate-context="Content/*/*/Noun"> <translate >
Uploading file Uploading file
</translate> </translate>
</div> </div>

View File

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

View File

@ -2,7 +2,7 @@
import axios from 'axios' import axios from 'axios'
import { useVModel, watchDebounced, useTextareaAutosize, syncRef } from '@vueuse/core' import { useVModel, watchDebounced, useTextareaAutosize, syncRef } from '@vueuse/core'
import { ref, computed, watchEffect, onMounted, nextTick, watch } from 'vue' import { ref, computed, watchEffect, onMounted, nextTick, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
interface Events { interface Events {
(e: 'update:modelValue', value: string): void (e: 'update:modelValue', value: string): void
@ -26,7 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
required: false required: false
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const { textarea, input } = useTextareaAutosize() const { textarea, input } = useTextareaAutosize()
const value = useVModel(props, 'modelValue', emit) const value = useVModel(props, 'modelValue', emit)
syncRef(value, input) syncRef(value, input)
@ -36,7 +36,7 @@ const preview = ref()
const isLoadingPreview = ref(false) const isLoadingPreview = ref(false)
const labels = computed(() => ({ 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) const remainingChars = computed(() => props.charLimit - props.modelValue.length)
@ -86,7 +86,7 @@ onMounted(async () => {
:class="[{active: !isPreviewing}, 'item']" :class="[{active: !isPreviewing}, 'item']"
@click.prevent="isPreviewing = false" @click.prevent="isPreviewing = false"
> >
<translate translate-context="*/Form/Menu.item"> <translate >
Write Write
</translate> </translate>
</button> </button>
@ -94,7 +94,7 @@ onMounted(async () => {
:class="[{active: isPreviewing}, 'item']" :class="[{active: isPreviewing}, 'item']"
@click.prevent="isPreviewing = true" @click.prevent="isPreviewing = true"
> >
<translate translate-context="*/Form/Menu.item"> <translate >
Preview Preview
</translate> </translate>
</button> </button>
@ -112,7 +112,7 @@ onMounted(async () => {
</div> </div>
</div> </div>
<p v-else-if="!preview"> <p v-else-if="!preview">
<translate translate-context="*/Form/Paragraph"> <translate >
Nothing to preview. Nothing to preview.
</translate> </translate>
</p> </p>
@ -141,7 +141,7 @@ onMounted(async () => {
{{ remainingChars }} {{ remainingChars }}
</span> </span>
<p> <p>
<translate translate-context="*/Form/Paragraph"> <translate >
Markdown syntax is supported. Markdown syntax is supported.
</translate> </translate>
</p> </p>

View File

@ -22,7 +22,7 @@ const { copy, isSupported: canCopy, copied } = useClipboard({ source: value, cop
v-if="copied" v-if="copied"
class="message" class="message"
> >
<translate translate-context="Content/*/Paragraph"> <translate >
Text copied to clipboard! Text copied to clipboard!
</translate> </translate>
</p> </p>
@ -39,7 +39,7 @@ const { copy, isSupported: canCopy, copied } = useClipboard({ source: value, cop
@click="copy()" @click="copy()"
> >
<i class="copy icon" /> <i class="copy icon" />
<translate translate-context="*/*/Button.Label/Short, Verb"> <translate >
Copy Copy
</translate> </translate>
</button> </button>

View File

@ -42,7 +42,7 @@ const confirm = () => {
> >
<h4 class="header"> <h4 class="header">
<slot name="modal-header"> <slot name="modal-header">
<translate translate-context="Modal/*/Title"> <translate >
Do you want to confirm this action? Do you want to confirm this action?
</translate> </translate>
</slot> </slot>
@ -54,7 +54,7 @@ const confirm = () => {
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic cancel button"> <button class="ui basic cancel button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</button> </button>
@ -63,7 +63,7 @@ const confirm = () => {
@click="confirm" @click="confirm"
> >
<slot name="modal-confirm"> <slot name="modal-confirm">
<translate translate-context="Modal/*/Button.Label/Short, Verb"> <translate >
Confirm Confirm
</translate> </translate>
</slot> </slot>

View File

@ -20,12 +20,12 @@ const duration = computed(() => {
<span> <span>
<translate <translate
v-if="duration.hours > 0" v-if="duration.hours > 0"
translate-context="Content/*/Paragraph"
:translate-params="duration" :translate-params="duration"
>%{ hours } h %{ minutes } min</translate> >%{ hours } h %{ minutes } min</translate>
<translate <translate
v-else v-else
translate-context="Content/*/Paragraph"
:translate-params="duration" :translate-params="duration"
>%{ minutes } min</translate> >%{ minutes } min</translate>
</span> </span>

View File

@ -19,7 +19,7 @@ withDefaults(defineProps<Props>(), {
<div class="content"> <div class="content">
<slot name="title"> <slot name="title">
<i class="search icon" /> <i class="search icon" />
<translate translate-context="Content/*/Paragraph"> <translate >
No results were found. No results were found.
</translate> </translate>
</slot> </slot>
@ -32,7 +32,7 @@ withDefaults(defineProps<Props>(), {
class="ui button" class="ui button"
@click="emit('refresh')" @click="emit('refresh')"
> >
<translate translate-context="Content/*/Button.Label/Short, Verb"> <translate >
Refresh Refresh
</translate> </translate>
</button> </button>

View File

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

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { computed } from 'vue' import { computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
interface Events { interface Events {
(e: 'update:modelValue', value: string): void (e: 'update:modelValue', value: string): void
@ -20,10 +20,10 @@ const props = withDefaults(defineProps<Props>(), {
const value = useVModel(props, 'modelValue', emit) const value = useVModel(props, 'modelValue', emit)
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'), searchPlaceholder: t('Search…'),
clear: $pgettext('Content/Library/Button.Label', 'Clear') clear: t('Clear')
})) }))
const search = () => { const search = () => {
@ -42,7 +42,7 @@ const search = () => {
for="search-query" for="search-query"
class="hidden" class="hidden"
> >
<translate translate-context="Content/Search/Input.Label/Noun">Search</translate> <translate >Search</translate>
</label> </label>
<input <input
id="search-query" id="search-query"

View File

@ -4,7 +4,7 @@ import type { Cover } from '~/types'
import SemanticModal from '~/components/semantic/Modal.vue' import SemanticModal from '~/components/semantic/Modal.vue'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
interface Props { interface Props {
nextRoute: RouteLocationRaw nextRoute: RouteLocationRaw
@ -16,12 +16,12 @@ defineProps<Props>()
const show = ref(false) const show = ref(false)
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
header: $pgettext('Popup/Title/Noun', 'Unauthenticated'), header: t('Unauthenticated'),
login: $pgettext('*/*/Button.Label/Verb', 'Log in'), login: t('Log in'),
signup: $pgettext('*/*/Button.Label/Verb', 'Sign up'), signup: t('Sign up'),
description: $pgettext('Popup/*/Paragraph', "You don't have access!") description: t("You don't have access!")
})) }))
</script> </script>

View File

@ -91,19 +91,19 @@ const submit = async () => {
href="" href=""
@click.stop.prevent="showMore = true" @click.stop.prevent="showMore = true"
> >
<translate translate-context="*/*/Button,Label">Show more</translate> <translate >Show more</translate>
</a> </a>
<a <a
v-else v-else
href="" href=""
@click.stop.prevent="showMore = false" @click.stop.prevent="showMore = false"
> >
<translate translate-context="*/*/Button,Label">Show less</translate> <translate >Show less</translate>
</a> </a>
</template> </template>
</template> </template>
<p v-else-if="!isUpdating"> <p v-else-if="!isUpdating">
<translate translate-context="*/*/Placeholder"> <translate >
No description available No description available
</translate> </translate>
</p> </p>
@ -114,7 +114,7 @@ const submit = async () => {
@click="isUpdating = true" @click="isUpdating = true"
> >
<i class="pencil icon" /> <i class="pencil icon" />
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate> <translate >Edit</translate>
</span> </span>
</template> </template>
<form <form
@ -128,7 +128,7 @@ const submit = async () => {
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Channels/Error message.Title"> <translate >
Error while updating description Error while updating description
</translate> </translate>
</h4> </h4>
@ -149,14 +149,14 @@ const submit = async () => {
class="left floated" class="left floated"
@click.prevent="isUpdating = false" @click.prevent="isUpdating = false"
> >
<translate translate-context="*/*/Button.Label/Verb">Cancel</translate> <translate >Cancel</translate>
</a> </a>
<button <button
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
type="submit" type="submit"
:disabled="isLoading" :disabled="isLoading"
> >
<translate translate-context="Content/Channels/Button.Label/Verb"> <translate >
Update description Update description
</translate> </translate>
</button> </button>

View File

@ -1,9 +1,10 @@
<script setup lang="ts"> <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 useThemeList from '~/composables/useThemeList'
import useTheme from '~/composables/useTheme' import useTheme from '~/composables/useTheme'
import { computed } from 'vue'
interface Events { interface Events {
(e: 'show:shortcuts-modal'): void (e: 'show:shortcuts-modal'): void
@ -11,26 +12,26 @@ interface Events {
const emit = defineEmits<Events>() const emit = defineEmits<Events>()
const { $pgettext } = useGettext() const { t } = useI18n()
const themes = useThemeList() const themes = useThemeList()
const { theme } = useTheme() const { theme } = useTheme()
const labels = computed(() => ({ const labels = computed(() => ({
profile: $pgettext('*/*/*/Noun', 'Profile'), profile: t('Profile'),
settings: $pgettext('*/*/*/Noun', 'Settings'), settings: t('Settings'),
logout: $pgettext('Sidebar/Login/List item.Link/Verb', 'Log out'), logout: t('Log out'),
about: $pgettext('Sidebar/About/List item.Link', 'About'), about: t('About'),
shortcuts: $pgettext('*/*/*/Noun', 'Keyboard shortcuts'), shortcuts: t('Keyboard shortcuts'),
support: $pgettext('Sidebar/*/Listitem.Link', 'Help'), support: t('Help'),
forum: $pgettext('Sidebar/*/Listitem.Link', 'Forum'), forum: t('Forum'),
docs: $pgettext('Sidebar/*/Listitem.Link', 'Documentation'), docs: t('Documentation'),
language: $pgettext('Footer/Settings/Dropdown.Label/Short, Verb', 'Change language'), language: t('Change language'),
theme: $pgettext('Footer/Settings/Dropdown.Label/Short, Verb', 'Change theme'), theme: t('Change theme'),
chat: $pgettext('Sidebar/*/Listitem.Link', 'Chat room'), chat: t('Chat room'),
git: $pgettext('Footer/*/List item.Link', 'Issue tracker'), git: t('Issue tracker'),
login: $pgettext('*/*/Button.Label/Verb', 'Log in'), login: t('Log in'),
signup: $pgettext('*/*/Button.Label/Verb', 'Sign up'), signup: t('Sign up'),
notifications: $pgettext('*/Notifications/*', 'Notifications') notifications: t('Notifications')
})) }))
</script> </script>
@ -45,11 +46,11 @@ const labels = computed(() => ({
class="menu" class="menu"
> >
<a <a
v-for="(language, key) in $language.available" v-for="(language, key) in SUPPORTED_LOCALES"
:key="key" :key="key"
:class="[{'active': $language.current === key},'item']" :class="[{'active': $i18n.locale === key},'item']"
:value="key" :value="key"
@click="$store.dispatch('ui/currentLanguage', key)" @click="$i18n.locale = key"
>{{ language }}</a> >{{ language }}</a>
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@ import useThemeList from '~/composables/useThemeList'
import useTheme from '~/composables/useTheme' import useTheme from '~/composables/useTheme'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { computed } from 'vue' import { computed } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
interface Events { interface Events {
(e: 'update:show', value: boolean): void (e: 'update:show', value: boolean): void
@ -24,26 +24,26 @@ const show = useVModel(props, 'show', emit)
const { theme } = useTheme() const { theme } = useTheme()
const themes = useThemeList() const themes = useThemeList()
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
header: $pgettext('Popup/Title/Noun', 'Options'), header: t('Options'),
profile: $pgettext('*/*/*/Noun', 'Profile'), profile: t('Profile'),
settings: $pgettext('*/*/*/Noun', 'Settings'), settings: t('Settings'),
logout: $pgettext('Sidebar/Login/List item.Link/Verb', 'Log out'), logout: t('Log out'),
about: $pgettext('Sidebar/About/List item.Link', 'About'), about: t('About'),
shortcuts: $pgettext('*/*/*/Noun', 'Keyboard shortcuts'), shortcuts: t('Keyboard shortcuts'),
support: $pgettext('Sidebar/*/Listitem.Link', 'Help'), support: t('Help'),
forum: $pgettext('Sidebar/*/Listitem.Link', 'Forum'), forum: t('Forum'),
docs: $pgettext('Sidebar/*/Listitem.Link', 'Documentation'), docs: t('Documentation'),
help: $pgettext('Sidebar/*/Listitem.Link', 'Help'), help: t('Help'),
language: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Language'), language: t('Language'),
theme: $pgettext('Sidebar/Settings/Dropdown.Label/Short, Verb', 'Theme'), theme: t('Theme'),
chat: $pgettext('Sidebar/*/Listitem.Link', 'Chat room'), chat: t('Chat room'),
git: $pgettext('Sidebar/*/List item.Link', 'Issue tracker'), git: t('Issue tracker'),
login: $pgettext('*/*/Button.Label/Verb', 'Log in'), login: t('Log in'),
signup: $pgettext('*/*/Button.Label/Verb', 'Sign up'), signup: t('Sign up'),
notifications: $pgettext('*/Notifications/*', 'Notifications'), notifications: t('Notifications'),
useOtherInstance: $pgettext('Sidebar/*/List item.Link', 'Use another instance') useOtherInstance: t('Use another instance')
})) }))
</script> </script>

View File

@ -5,7 +5,7 @@ import type { OrderingField } from '~/store/ui'
import type { Track } from '~/types' import type { Track } from '~/types'
import { computed, onMounted, reactive, ref, watch } from 'vue' import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { sortedUniq } from 'lodash-es' import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -96,9 +96,9 @@ onOrderingUpdate(() => {
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ 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))) 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"> <section class="ui vertical center aligned stripe segment">
<div :class="['ui', { 'active': isLoading }, 'inverted', 'dimmer']"> <div :class="['ui', { 'active': isLoading }, 'inverted', 'dimmer']">
<div class="ui text loader"> <div class="ui text loader">
<translate translate-context="Content/Favorites/Message"> <translate >
Loading your favorites Loading your favorites
</translate> </translate>
</div> </div>
@ -126,7 +126,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
translate-plural="%{ count } favorites" translate-plural="%{ count } favorites"
:translate-n="$store.state.favorites.count" :translate-n="$store.state.favorites.count"
:translate-params="{ count }" :translate-params="{ count }"
translate-context="Content/Favorites/Title"
> >
%{ count } favorite %{ count } favorite
</translate> </translate>
@ -144,7 +144,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<label for="favorites-ordering"> <label for="favorites-ordering">
<translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate> <translate >Ordering</translate>
</label> </label>
<select <select
id="favorites-ordering" id="favorites-ordering"
@ -162,7 +162,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
</div> </div>
<div class="field"> <div class="field">
<label for="favorites-ordering-direction"> <label for="favorites-ordering-direction">
<translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate> <translate >Order</translate>
</label> </label>
<select <select
id="favorites-ordering-direction" id="favorites-ordering-direction"
@ -170,12 +170,12 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
class="ui dropdown" class="ui dropdown"
> >
<option value="+"> <option value="+">
<translate translate-context="Content/Search/Dropdown"> <translate >
Ascending Ascending
</translate> </translate>
</option> </option>
<option value="-"> <option value="-">
<translate translate-context="Content/Search/Dropdown"> <translate >
Descending Descending
</translate> </translate>
</option> </option>
@ -183,7 +183,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
</div> </div>
<div class="field"> <div class="field">
<label for="favorites-results"> <label for="favorites-results">
<translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate> <translate >Results per page</translate>
</label> </label>
<select <select
id="favorites-results" id="favorites-results"
@ -223,7 +223,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<div class="ui icon header"> <div class="ui icon header">
<i class="broken heart icon" /> <i class="broken heart icon" />
<translate <translate
translate-context="Content/Home/Placeholder"
> >
No tracks have been added to your favorites yet No tracks have been added to your favorites yet
</translate> </translate>
@ -233,7 +233,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
class="ui success labeled icon button" class="ui success labeled icon button"
> >
<i class="headphones icon" /> <i class="headphones icon" />
<translate translate-context="Content/*/Verb"> <translate >
Browse the library Browse the library
</translate> </translate>
</router-link> </router-link>

View File

@ -2,7 +2,7 @@
import type { QueueTrack } from '~/composables/audio/queue' import type { QueueTrack } from '~/composables/audio/queue'
import type { Track } from '~/types' import type { Track } from '~/types'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import { computed } from 'vue' import { computed } from 'vue'
@ -18,13 +18,13 @@ const props = withDefaults(defineProps<Props>(), {
border: false border: false
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const store = useStore() const store = useStore()
const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id)) const isFavorite = computed(() => store.getters['favorites/isFavorite'](props.track.id))
const title = computed(() => isFavorite.value const title = computed(() => isFavorite.value
? $pgettext('Content/Track/Icon.Tooltip/Verb', 'Remove from favorites') ? t('Remove from favorites')
: $pgettext('Content/Track/*/Verb', 'Add to favorites') : t('Add to favorites')
) )
</script> </script>
@ -37,13 +37,13 @@ const title = computed(() => isFavorite.value
<i class="heart icon" /> <i class="heart icon" />
<translate <translate
v-if="isFavorite" v-if="isFavorite"
translate-context="Content/Track/Button.Message"
> >
In favorites In favorites
</translate> </translate>
<translate <translate
v-else v-else
translate-context="Content/Track/*/Verb"
> >
Add to favorites Add to favorites
</translate> </translate>

View File

@ -80,7 +80,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="small" class="small"
> >
<h3 class="header"> <h3 class="header">
<translate translate-context="Popup/*/Title"> <translate >
Refreshing object from remote server Refreshing object from remote server
</translate> </translate>
</h3> </h3>
@ -91,12 +91,12 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui message" class="ui message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Popup/*/Message.Title"> <translate >
Refresh was skipped Refresh was skipped
</translate> </translate>
</h4> </h4>
<p> <p>
<translate translate-context="Popup/*/Message.Content"> <translate >
The remote server answered, but returned data was unsupported by Funkwhale. The remote server answered, but returned data was unsupported by Funkwhale.
</translate> </translate>
</p> </p>
@ -106,12 +106,12 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui success message" class="ui success message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Popup/*/Message.Title"> <translate >
Refresh successful Refresh successful
</translate> </translate>
</h4> </h4>
<p> <p>
<translate translate-context="Popup/*/Message.Content"> <translate >
Data was refreshed successfully from remote server. Data was refreshed successfully from remote server.
</translate> </translate>
</p> </p>
@ -121,12 +121,12 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui error message" class="ui error message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Popup/*/Message.Title"> <translate >
Refresh error Refresh error
</translate> </translate>
</h4> </h4>
<p> <p>
<translate translate-context="Popup/*/Message.Content"> <translate >
An error occurred while trying to refresh data: An error occurred while trying to refresh data:
</translate> </translate>
</p> </p>
@ -134,7 +134,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
<tbody> <tbody>
<tr> <tr>
<td> <td>
<translate translate-context="Popup/Import/Table.Label/Noun"> <translate >
Error type Error type
</translate> </translate>
</td> </td>
@ -144,7 +144,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Popup/Import/Table.Label/Noun"> <translate >
Error detail Error detail
</translate> </translate>
</td> </td>
@ -152,49 +152,49 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
<translate <translate
v-if="data.detail.error_code === 'http' && data.detail.status_code" v-if="data.detail.error_code === 'http' && data.detail.status_code"
:translate-params="{status: data.detail.status_code}" :translate-params="{status: data.detail.status_code}"
translate-context="*/*/Error"
> >
The remote server answered with HTTP %{ status } The remote server answered with HTTP %{ status }
</translate> </translate>
<translate <translate
v-else-if="['http', 'request'].indexOf(data.detail.error_code) > -1" v-else-if="['http', 'request'].indexOf(data.detail.error_code) > -1"
translate-context="*/*/Error"
> >
An HTTP error occurred while contacting the remote server An HTTP error occurred while contacting the remote server
</translate> </translate>
<translate <translate
v-else-if="data.detail.error_code === 'timeout'" v-else-if="data.detail.error_code === 'timeout'"
translate-context="*/*/Error"
> >
The remote server didn't respond quickly enough The remote server didn't respond quickly enough
</translate> </translate>
<translate <translate
v-else-if="data.detail.error_code === 'connection'" v-else-if="data.detail.error_code === 'connection'"
translate-context="*/*/Error"
> >
Impossible to connect to the remote server Impossible to connect to the remote server
</translate> </translate>
<translate <translate
v-else-if="['invalid_json', 'invalid_jsonld', 'missing_jsonld_type'].indexOf(data.detail.error_code) > -1" 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 The remote server returned invalid JSON or JSON-LD data
</translate> </translate>
<translate <translate
v-else-if="data.detail.error_code === 'validation'" v-else-if="data.detail.error_code === 'validation'"
translate-context="*/*/Error"
> >
Data returned by the remote server had invalid or missing attributes Data returned by the remote server had invalid or missing attributes
</translate> </translate>
<translate <translate
v-else-if="data.detail.error_code === 'unhandled'" v-else-if="data.detail.error_code === 'unhandled'"
translate-context="*/*/Error"
> >
Unknown error Unknown error
</translate> </translate>
<translate <translate
v-else v-else
translate-context="*/*/Error"
> >
Unknown error Unknown error
</translate> </translate>
@ -209,7 +209,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui active inverted dimmer" class="ui active inverted dimmer"
> >
<div class="ui text loader"> <div class="ui text loader">
<translate translate-context="Popup/*/Loading.Title"> <translate >
Requesting a fetch Requesting a fetch
</translate> </translate>
</div> </div>
@ -219,7 +219,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui active inverted dimmer" class="ui active inverted dimmer"
> >
<div class="ui text loader"> <div class="ui text loader">
<translate translate-context="Popup/*/Loading.Title"> <translate >
Waiting for result Waiting for result
</translate> </translate>
</div> </div>
@ -230,7 +230,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Error while saving settings Error while saving settings
</translate> </translate>
</h4> </h4>
@ -249,12 +249,12 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui warning message" class="ui warning message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Popup/*/Message.Title"> <translate >
Refresh pending Refresh pending
</translate> </translate>
</h4> </h4>
<p> <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. The refresh request hasn't been processed in time by our server. It will be processed later.
</translate> </translate>
</p> </p>
@ -262,7 +262,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic cancel button"> <button class="ui basic cancel button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Close Close
</translate> </translate>
</button> </button>
@ -271,7 +271,7 @@ const { start: startPolling } = useTimeoutFn(poll, 1000, { immediate: false })
class="ui confirm success button" class="ui confirm success button"
@click.prevent="showModal = false; emit('refresh')" @click.prevent="showModal = false; emit('refresh')"
> >
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Close and reload page Close and reload page
</translate> </translate>
</button> </button>

View File

@ -64,7 +64,7 @@ fetchData()
v-if="!isLoading && libraries.length === 0" v-if="!isLoading && libraries.length === 0"
class="ui subtitle" class="ui subtitle"
> >
<translate translate-context="Content/Federation/Paragraph"> <translate >
No matching library. No matching library.
</translate> </translate>
</p> </p>
@ -92,7 +92,7 @@ fetchData()
:class="['ui', 'basic', 'button']" :class="['ui', 'basic', 'button']"
@click="fetchData(nextPage)" @click="fetchData(nextPage)"
> >
<translate translate-context="*/*/Button,Label"> <translate >
Show more Show more
</translate> </translate>
</button> </button>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useClipboard, useVModel } from '@vueuse/core' import { useClipboard, useVModel } from '@vueuse/core'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -25,10 +25,10 @@ const value = useVModel(props, 'modelValue', emit)
const showPassword = ref(props.defaultShow) const showPassword = ref(props.defaultShow)
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Content/Settings/Button.Tooltip/Verb', 'Show/hide password'), title: t('Show/hide password'),
copy: $pgettext('*/*/Button.Label/Short, Verb', 'Copy') copy: t('Copy')
})) }))
const passwordInputType = computed(() => showPassword.value ? 'text' : 'password') const passwordInputType = computed(() => showPassword.value ? 'text' : 'password')
@ -38,7 +38,7 @@ const { isSupported: canCopy, copy } = useClipboard({ source: value })
const copyPassword = () => { const copyPassword = () => {
copy() copy()
store.commit('ui/addMessage', { store.commit('ui/addMessage', {
content: $pgettext('Content/*/Paragraph', 'Text copied to clipboard!'), content: t('Text copied to clipboard!'),
date: new Date() date: new Date()
}) })
} }

View File

@ -2,8 +2,8 @@
import type { Track, Album, Artist, Library } from '~/types' import type { Track, Album, Artist, Library } from '~/types'
import { momentFormat } from '~/utils/filters' import { momentFormat } from '~/utils/filters'
import { useGettext } from 'vue3-gettext'
import { computed, reactive, ref, watch } from 'vue' import { computed, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { sum } from 'lodash-es' 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 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 publicLibraries = computed(() => libraries.value?.filter(library => library.privacy_level === 'everyone') ?? [])
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('*/*/*', 'Album') title: t('Album')
})) }))
const isLoading = ref(false) const isLoading = ref(false)
@ -179,7 +179,7 @@ const remove = async () => {
<div class="ui hidden very small divider" /> <div class="ui hidden very small divider" />
<translate <translate
v-if="isSerie" v-if="isSerie"
translate-context="Content/Channel/Paragraph"
translate-plural="%{ count } episodes" translate-plural="%{ count } episodes"
:translate-n="totalTracks" :translate-n="totalTracks"
:translate-params="{count: totalTracks}" :translate-params="{count: totalTracks}"
@ -188,7 +188,7 @@ const remove = async () => {
</translate> </translate>
<translate <translate
v-else v-else
translate-context="*/*/*"
:translate-params="{count: totalTracks}" :translate-params="{count: totalTracks}"
:translate-n="totalTracks" :translate-n="totalTracks"
translate-plural="%{ count } tracks" translate-plural="%{ count } tracks"
@ -267,7 +267,7 @@ const remove = async () => {
<template v-if="totalTracks > 0"> <template v-if="totalTracks > 0">
<translate <translate
v-if="isSerie" v-if="isSerie"
translate-context="Content/Channel/Paragraph"
translate-plural="%{ count } episodes" translate-plural="%{ count } episodes"
:translate-n="totalTracks" :translate-n="totalTracks"
:translate-params="{count: totalTracks}" :translate-params="{count: totalTracks}"
@ -276,7 +276,7 @@ const remove = async () => {
</translate> </translate>
<translate <translate
v-else v-else
translate-context="*/*/*"
:translate-params="{count: totalTracks}" :translate-params="{count: totalTracks}"
:translate-n="totalTracks" :translate-n="totalTracks"
translate-plural="%{ count } tracks" translate-plural="%{ count } tracks"
@ -323,7 +323,7 @@ const remove = async () => {
:to="{name: 'library.albums.edit', params: {id: object.id }}" :to="{name: 'library.albums.edit', params: {id: object.id }}"
> >
<i class="pencil icon" /> <i class="pencil icon" />
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Add a description Add a description
</translate> </translate>
</router-link> </router-link>
@ -341,7 +341,7 @@ const remove = async () => {
:to="{name: 'library.albums.edit', params: {id: object.id }}" :to="{name: 'library.albums.edit', params: {id: object.id }}"
> >
<i class="pencil icon" /> <i class="pencil icon" />
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Add a description Add a description
</translate> </translate>
</router-link> </router-link>

View File

@ -62,13 +62,13 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
<h2 class="ui header"> <h2 class="ui header">
<translate <translate
v-if="isSerie" v-if="isSerie"
translate-context="Content/Channels/*"
> >
Episodes Episodes
</translate> </translate>
<translate <translate
v-else v-else
translate-context="*/*/*"
> >
Tracks Tracks
</translate> </translate>
@ -96,7 +96,6 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
<translate <translate
tag="h3" tag="h3"
:translate-params="{number: tracks[0]?.disc_number ?? index + 1}" :translate-params="{number: tracks[0]?.disc_number ?? index + 1}"
translate-context="Content/Album/"
> >
Volume %{ number } Volume %{ number }
</translate> </translate>
@ -138,7 +137,7 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
<template v-if="!artist.channel && !isSerie"> <template v-if="!artist.channel && !isSerie">
<h2> <h2>
<translate translate-context="Content/*/Title/Noun"> <translate >
User libraries User libraries
</translate> </translate>
</h2> </h2>
@ -146,7 +145,7 @@ const paginatedDiscs = computed(() => props.object.tracks.slice(props.paginateBy
:url="'albums/' + object.id + '/libraries/'" :url="'albums/' + object.id + '/libraries/'"
@loaded="emit('libraries-loaded', $event)" @loaded="emit('libraries-loaded', $event)"
> >
<translate translate-context="Content/Album/Paragraph"> <translate >
This album is present in the following libraries: This album is present in the following libraries:
</translate> </translate>
</library-widget> </library-widget>

View File

@ -2,7 +2,7 @@
import type { Album, Artist, Library } from '~/types' import type { Album, Artist, Library } from '~/types'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { getDomain } from '~/utils' import { getDomain } from '~/utils'
@ -33,9 +33,9 @@ const showEmbedModal = ref(false)
const domain = computed(() => getDomain(props.object.fid)) const domain = computed(() => getDomain(props.object.fid))
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
more: $pgettext('*/*/Button.Label/Noun', 'More…') more: t('More…')
})) }))
const isEmbedable = computed(() => (props.isChannel && props.artist?.channel?.actor) || props.publicLibraries.length) const isEmbedable = computed(() => (props.isChannel && props.artist?.channel?.actor) || props.publicLibraries.length)
@ -53,7 +53,7 @@ const remove = () => emit('remove')
v-model:show="showEmbedModal" v-model:show="showEmbedModal"
> >
<h4 class="header"> <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> </h4>
<div class="scrolling content"> <div class="scrolling content">
<div class="description"> <div class="description">
@ -66,7 +66,7 @@ const remove = () => emit('remove')
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic deny button"> <button class="ui basic deny button">
<translate translate-context="*/*/Button.Label/Verb">Cancel</translate> <translate >Cancel</translate>
</button> </button>
</div> </div>
</semantic-modal> </semantic-modal>
@ -86,7 +86,7 @@ const remove = () => emit('remove')
<i class="external icon" /> <i class="external icon" />
<translate <translate
:translate-params="{domain: domain}" :translate-params="{domain: domain}"
translate-context="Content/*/Button.Label/Verb"
>View on %{ domain }</translate> >View on %{ domain }</translate>
</a> </a>
@ -97,7 +97,7 @@ const remove = () => emit('remove')
@click="showEmbedModal = !showEmbedModal" @click="showEmbedModal = !showEmbedModal"
> >
<i class="code icon" /> <i class="code icon" />
<translate translate-context="Content/*/Button.Label/Verb">Embed</translate> <translate >Embed</translate>
</div> </div>
<a <a
v-if="isAlbum && musicbrainzUrl" v-if="isAlbum && musicbrainzUrl"
@ -107,7 +107,7 @@ const remove = () => emit('remove')
class="basic item" class="basic item"
> >
<i class="external icon" /> <i class="external icon" />
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate> <translate >View on MusicBrainz</translate>
</a> </a>
<a <a
v-if="!isChannel && isAlbum" v-if="!isChannel && isAlbum"
@ -117,7 +117,7 @@ const remove = () => emit('remove')
class="basic item" class="basic item"
> >
<i class="external icon" /> <i class="external icon" />
<translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate> <translate >Search on Discogs</translate>
</a> </a>
<router-link <router-link
v-if="object.is_local" v-if="object.is_local"
@ -125,7 +125,7 @@ const remove = () => emit('remove')
class="basic item" class="basic item"
> >
<i class="edit icon" /> <i class="edit icon" />
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate> <translate >Edit</translate>
</router-link> </router-link>
<dangerous-button <dangerous-button
v-if="artist && $store.state.auth.authenticated && artist.channel && artist.attributed_to.full_username === $store.state.auth.fullUsername" 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()" @confirm="remove()"
> >
<i class="ui trash icon" /> <i class="ui trash icon" />
<translate translate-context="*/*/*/Verb">Delete</translate> <translate >Delete</translate>
<template #modal-header> <template #modal-header>
<p> <p>
<translate translate-context="Popup/Channel/Title">Delete this album?</translate> <translate >Delete this album?</translate>
</p> </p>
</template> </template>
<template #modal-content> <template #modal-content>
<div> <div>
<p> <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. The album will be deleted, as well as any related files and data. This action is irreversible.
</translate> </translate>
</p> </p>
@ -150,7 +150,7 @@ const remove = () => emit('remove')
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p> <p>
<translate translate-context="*/*/*/Verb">Delete</translate> <translate >Delete</translate>
</p> </p>
</template> </template>
</dangerous-button> </dangerous-button>
@ -171,7 +171,7 @@ const remove = () => emit('remove')
:to="{name: 'manage.library.albums.detail', params: {id: object.id}}" :to="{name: 'manage.library.albums.detail', params: {id: object.id}}"
> >
<i class="wrench icon" /> <i class="wrench icon" />
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate> <translate >Open in moderation interface</translate>
</router-link> </router-link>
<a <a
v-if="$store.state.auth.profile && $store.state.auth.profile?.is_superuser" v-if="$store.state.auth.profile && $store.state.auth.profile?.is_superuser"
@ -181,7 +181,7 @@ const remove = () => emit('remove')
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i class="wrench icon" /> <i class="wrench icon" />
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp; <translate >View in Django's admin</translate>&nbsp;
</a> </a>
</div> </div>
</button> </button>

View File

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

View File

@ -6,7 +6,7 @@ import type { OrderingField } from '~/store/ui'
import { computed, onMounted, ref, watch } from 'vue' import { computed, onMounted, ref, watch } from 'vue'
import { useRouteQuery } from '@vueuse/router' import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { syncRef } from '@vueuse/core' import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es' import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -108,10 +108,10 @@ onOrderingUpdate(() => {
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Enter album title…'), searchPlaceholder: t('Enter album title…'),
title: $pgettext('*/*/*', 'Albums') title: t('Albums')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b))) 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"> <main v-title="labels.title">
<section class="ui vertical stripe segment"> <section class="ui vertical stripe segment">
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Album/Title"> <translate >
Browsing albums Browsing albums
</translate> </translate>
</h2> </h2>
@ -132,7 +132,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<label for="albums-search"> <label for="albums-search">
<translate translate-context="Content/Search/Input.Label/Noun">Search</translate> <translate >Search</translate>
</label> </label>
<div class="ui action input"> <div class="ui action input">
<input <input
@ -145,18 +145,18 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<button <button
class="ui icon button" class="ui icon button"
type="submit" type="submit"
:aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')" :aria-label="t('Search')"
> >
<i class="search icon" /> <i class="search icon" />
</button> </button>
</div> </div>
</div> </div>
<div class="field"> <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" /> <tags-selector v-model="tags" />
</div> </div>
<div class="field"> <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 <select
id="album-ordering" id="album-ordering"
v-model="ordering" v-model="ordering"
@ -172,26 +172,26 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="album-ordering-direction" id="album-ordering-direction"
v-model="orderingDirection" v-model="orderingDirection"
class="ui dropdown" class="ui dropdown"
> >
<option value="+"> <option value="+">
<translate translate-context="Content/Search/Dropdown"> <translate >
Ascending Ascending
</translate> </translate>
</option> </option>
<option value="-"> <option value="-">
<translate translate-context="Content/Search/Dropdown"> <translate >
Descending Descending
</translate> </translate>
</option> </option>
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="album-results" id="album-results"
v-model="paginateBy" v-model="paginateBy"
@ -234,7 +234,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
> >
<div class="ui icon header"> <div class="ui icon header">
<i class="compact disc icon" /> <i class="compact disc icon" />
<translate translate-context="Content/Albums/Placeholder"> <translate >
No results matching your query No results matching your query
</translate> </translate>
</div> </div>
@ -244,7 +244,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
class="ui success button labeled icon" class="ui success button labeled icon"
> >
<i class="upload icon" /> <i class="upload icon" />
<translate translate-context="Content/*/Verb"> <translate >
Add some music Add some music
</translate> </translate>
</router-link> </router-link>

View File

@ -2,7 +2,7 @@
import type { Track, Album, Artist, Library } from '~/types' import type { Track, Album, Artist, Library } from '~/types'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { getDomain } from '~/utils' import { getDomain } from '~/utils'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -57,9 +57,9 @@ const headerStyle = computed(() => cover.value?.urls.original
: '' : ''
) )
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('*/*/*', 'Artist') title: t('Artist')
})) }))
const isLoading = ref(false) const isLoading = ref(false)
@ -118,7 +118,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="sub header" class="sub header"
> >
<translate <translate
translate-context="Content/Artist/Paragraph"
tag="div" tag="div"
translate-plural="%{ count } tracks in %{ albumsCount } albums" translate-plural="%{ count } tracks in %{ albumsCount } albums"
:translate-n="totalTracks" :translate-n="totalTracks"
@ -147,7 +147,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="vibrant" class="vibrant"
:artist="object" :artist="object"
> >
<translate translate-context="Content/Artist/Button.Label/Verb"> <translate >
Play all albums Play all albums
</translate> </translate>
</play-button> </play-button>
@ -158,7 +158,7 @@ watch(() => props.id, fetchData, { immediate: true })
v-model:show="showEmbedModal" v-model:show="showEmbedModal"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Popup/Artist/Title/Verb"> <translate >
Embed this artist work on your website Embed this artist work on your website
</translate> </translate>
</h4> </h4>
@ -172,7 +172,7 @@ watch(() => props.id, fetchData, { immediate: true })
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui deny button"> <button class="ui deny button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</button> </button>
@ -183,7 +183,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="ui button" class="ui button"
@click="dropdown.click()" @click="dropdown.click()"
> >
<translate translate-context="*/*/Button.Label/Noun"> <translate >
More More
</translate> </translate>
</button> </button>
@ -203,7 +203,7 @@ watch(() => props.id, fetchData, { immediate: true })
<i class="external icon" /> <i class="external icon" />
<translate <translate
:translate-params="{domain: domain}" :translate-params="{domain: domain}"
translate-context="Content/*/Button.Label/Verb"
>View on %{ domain }</translate> >View on %{ domain }</translate>
</a> </a>
@ -214,7 +214,7 @@ watch(() => props.id, fetchData, { immediate: true })
@click.prevent="showEmbedModal = !showEmbedModal" @click.prevent="showEmbedModal = !showEmbedModal"
> >
<i class="code icon" /> <i class="code icon" />
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Embed Embed
</translate> </translate>
</button> </button>
@ -225,7 +225,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="basic item" class="basic item"
> >
<i class="wikipedia w icon" /> <i class="wikipedia w icon" />
<translate translate-context="Content/*/Button.Label/Verb">Search on Wikipedia</translate> <translate >Search on Wikipedia</translate>
</a> </a>
<a <a
v-if="musicbrainzUrl" v-if="musicbrainzUrl"
@ -235,7 +235,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="basic item" class="basic item"
> >
<i class="external icon" /> <i class="external icon" />
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate> <translate >View on MusicBrainz</translate>
</a> </a>
<a <a
:href="discogsUrl" :href="discogsUrl"
@ -244,7 +244,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="basic item" class="basic item"
> >
<i class="external icon" /> <i class="external icon" />
<translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate> <translate >Search on Discogs</translate>
</a> </a>
<router-link <router-link
v-if="object.is_local" v-if="object.is_local"
@ -252,7 +252,7 @@ watch(() => props.id, fetchData, { immediate: true })
class="basic item" class="basic item"
> >
<i class="edit icon" /> <i class="edit icon" />
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Edit Edit
</translate> </translate>
</router-link> </router-link>
@ -274,7 +274,7 @@ watch(() => props.id, fetchData, { immediate: true })
:to="{name: 'manage.library.artists.detail', params: {id: object.id}}" :to="{name: 'manage.library.artists.detail', params: {id: object.id}}"
> >
<i class="wrench icon" /> <i class="wrench icon" />
<translate translate-context="Content/Moderation/Link"> <translate >
Open in moderation interface Open in moderation interface
</translate> </translate>
</router-link> </router-link>
@ -286,7 +286,7 @@ watch(() => props.id, fetchData, { immediate: true })
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i class="wrench icon" /> <i class="wrench icon" />
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp; <translate >View in Django's admin</translate>&nbsp;
</a> </a>
</div> </div>
</button> </button>

View File

@ -65,7 +65,7 @@ const loadMoreAlbums = async () => {
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<div class="ui message"> <div class="ui message">
<p> <p>
<translate translate-context="Content/Artist/Paragraph"> <translate >
You are currently hiding content related to this artist. You are currently hiding content related to this artist.
</translate> </translate>
</p> </p>
@ -73,7 +73,7 @@ const loadMoreAlbums = async () => {
class="right floated" class="right floated"
:to="{name: 'settings'}" :to="{name: 'settings'}"
> >
<translate translate-context="Content/Moderation/Link"> <translate >
Review my filters Review my filters
</translate> </translate>
</router-link> </router-link>
@ -81,7 +81,7 @@ const loadMoreAlbums = async () => {
class="ui basic tiny button" class="ui basic tiny button"
@click="$store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)" @click="$store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)"
> >
<translate translate-context="Content/Moderation/Button.Label"> <translate >
Remove filter Remove filter
</translate> </translate>
</button> </button>
@ -98,7 +98,7 @@ const loadMoreAlbums = async () => {
class="ui vertical stripe segment" class="ui vertical stripe segment"
> >
<h2> <h2>
<translate translate-context="Content/Artist/Title"> <translate >
Albums by this artist Albums by this artist
</translate> </translate>
</h2> </h2>
@ -115,7 +115,7 @@ const loadMoreAlbums = async () => {
:class="['ui', {loading: isLoadingMoreAlbums}, 'button']" :class="['ui', {loading: isLoadingMoreAlbums}, 'button']"
@click="loadMoreAlbums()" @click="loadMoreAlbums()"
> >
<translate translate-context="Content/*/Button.Label"> <translate >
Load more Load more
</translate> </translate>
</button> </button>
@ -132,7 +132,7 @@ const loadMoreAlbums = async () => {
> >
<template #header> <template #header>
<h2> <h2>
<translate translate-context="Content/Artist/Title"> <translate >
New tracks by this artist New tracks by this artist
</translate> </translate>
</h2> </h2>
@ -142,7 +142,7 @@ const loadMoreAlbums = async () => {
</section> </section>
<section class="ui vertical stripe segment"> <section class="ui vertical stripe segment">
<h2> <h2>
<translate translate-context="Content/*/Title/Noun"> <translate >
User libraries User libraries
</translate> </translate>
</h2> </h2>
@ -150,7 +150,7 @@ const loadMoreAlbums = async () => {
:url="'artists/' + object.id + '/libraries/'" :url="'artists/' + object.id + '/libraries/'"
@loaded="emit('libraries-loaded', $event)" @loaded="emit('libraries-loaded', $event)"
> >
<translate translate-context="Content/Artist/Paragraph"> <translate >
This artist is present in the following libraries: This artist is present in the following libraries:
</translate> </translate>
</library-widget> </library-widget>

View File

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

View File

@ -6,7 +6,7 @@ import type { OrderingField } from '~/store/ui'
import { computed, ref, watch, onMounted } from 'vue' import { computed, ref, watch, onMounted } from 'vue'
import { useRouteQuery } from '@vueuse/router' import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { syncRef } from '@vueuse/core' import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es' import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -109,10 +109,10 @@ onOrderingUpdate(() => {
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'), searchPlaceholder: t('Search…'),
title: $pgettext('*/*/*/Noun', 'Artists') title: t('Artists')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b))) const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b)))
@ -122,7 +122,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
<main v-title="labels.title"> <main v-title="labels.title">
<section class="ui vertical stripe segment"> <section class="ui vertical stripe segment">
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Artist/Title"> <translate >
Browsing artists Browsing artists
</translate> </translate>
</h2> </h2>
@ -133,7 +133,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<label for="artist-search"> <label for="artist-search">
<translate translate-context="Content/Search/Input.Label/Noun">Artist name</translate> <translate >Artist name</translate>
</label> </label>
<div class="ui action input"> <div class="ui action input">
<input <input
@ -146,18 +146,18 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
<button <button
class="ui icon button" class="ui icon button"
type="submit" type="submit"
:aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')" :aria-label="t('Search')"
> >
<i class="search icon" /> <i class="search icon" />
</button> </button>
</div> </div>
</div> </div>
<div class="field"> <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" /> <tags-selector v-model="tags" />
</div> </div>
<div class="field"> <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 <select
id="artist-ordering" id="artist-ordering"
v-model="ordering" v-model="ordering"
@ -173,26 +173,26 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="artist-ordering-direction" id="artist-ordering-direction"
v-model="orderingDirection" v-model="orderingDirection"
class="ui dropdown" class="ui dropdown"
> >
<option value="+"> <option value="+">
<translate translate-context="Content/Search/Dropdown"> <translate >
Ascending Ascending
</translate> </translate>
</option> </option>
<option value="-"> <option value="-">
<translate translate-context="Content/Search/Dropdown"> <translate >
Descending Descending
</translate> </translate>
</option> </option>
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="artist-results" id="artist-results"
v-model="paginateBy" v-model="paginateBy"
@ -223,7 +223,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
<label <label
for="exclude-compilation" for="exclude-compilation"
class="visually-hidden" class="visually-hidden"
><translate translate-context="Content/Search/Checkbox/Noun">Exclude Compilation Artists</translate></label> ><translate >Exclude Compilation Artists</translate></label>
</div> </div>
</div> </div>
</div> </div>
@ -252,7 +252,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
> >
<div class="ui icon header"> <div class="ui icon header">
<i class="compact disc icon" /> <i class="compact disc icon" />
<translate translate-context="Content/Artists/Placeholder"> <translate >
No results matching your query No results matching your query
</translate> </translate>
</div> </div>
@ -262,7 +262,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
class="ui success button labeled icon" class="ui success button labeled icon"
> >
<i class="upload icon" /> <i class="upload icon" />
<translate translate-context="Content/*/Verb"> <translate >
Add some music Add some music
</translate> </translate>
</router-link> </router-link>

View File

@ -157,7 +157,7 @@ const approve = async (approved: boolean) => {
<h4 class="header"> <h4 class="header">
<router-link :to="detailUrl"> <router-link :to="detailUrl">
<translate <translate
translate-context="Content/Library/Card/Short"
:translate-params="{id: obj.uuid.substring(0, 8)}" :translate-params="{id: obj.uuid.substring(0, 8)}"
> >
Modification %{ id } Modification %{ id }
@ -171,7 +171,7 @@ const approve = async (approved: boolean) => {
> >
<i class="music icon" /> <i class="music icon" />
<translate <translate
translate-context="Content/Library/Card/Short"
:translate-params="{id: obj.target.id, name: obj.target.repr}" :translate-params="{id: obj.target.id, name: obj.target.repr}"
> >
Track #%{ id } - %{ name } Track #%{ id } - %{ name }
@ -186,19 +186,19 @@ const approve = async (approved: boolean) => {
<span class="right floated"> <span class="right floated">
<span v-if="obj.is_approved && obj.is_applied"> <span v-if="obj.is_approved && obj.is_applied">
<i class="success check icon" /> <i class="success check icon" />
<translate translate-context="Content/Library/Card/Short">Approved and applied</translate> <translate >Approved and applied</translate>
</span> </span>
<span v-else-if="obj.is_approved"> <span v-else-if="obj.is_approved">
<i class="success check icon" /> <i class="success check icon" />
<translate translate-context="Content/*/*/Short">Approved</translate> <translate >Approved</translate>
</span> </span>
<span v-else-if="obj.is_approved === null"> <span v-else-if="obj.is_approved === null">
<i class="warning hourglass icon" /> <i class="warning hourglass icon" />
<translate translate-context="Content/Admin/*/Noun">Pending review</translate> <translate >Pending review</translate>
</span> </span>
<span v-else-if="obj.is_approved === false"> <span v-else-if="obj.is_approved === false">
<i class="danger x icon" /> <i class="danger x icon" />
<translate translate-context="Content/Library/*/Short">Rejected</translate> <translate >Rejected</translate>
</span> </span>
</span> </span>
</div> </div>
@ -217,17 +217,17 @@ const approve = async (approved: boolean) => {
<thead> <thead>
<tr> <tr>
<th> <th>
<translate translate-context="Content/Library/Card.Table.Header/Short"> <translate >
Field Field
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/Library/Card.Table.Header/Short"> <translate >
Old value Old value
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/Library/Card.Table.Header/Short"> <translate >
New value New value
</translate> </translate>
</th> </th>
@ -259,7 +259,7 @@ const approve = async (approved: boolean) => {
</template> </template>
</td> </td>
<td v-else> <td v-else>
<translate translate-context="*/*/*"> <translate >
N/A N/A
</translate> </translate>
</td> </td>
@ -319,7 +319,7 @@ const approve = async (approved: boolean) => {
:class="['ui', {loading: isLoading}, 'success', 'basic', 'button']" :class="['ui', {loading: isLoading}, 'success', 'basic', 'button']"
@click="approve(true)" @click="approve(true)"
> >
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Approve Approve
</translate> </translate>
</button> </button>
@ -328,7 +328,7 @@ const approve = async (approved: boolean) => {
:class="['ui', {loading: isLoading}, 'warning', 'basic', 'button']" :class="['ui', {loading: isLoading}, 'warning', 'basic', 'button']"
@click="approve(false)" @click="approve(false)"
> >
<translate translate-context="Content/Library/Button.Label"> <translate >
Reject Reject
</translate> </translate>
</button> </button>
@ -337,12 +337,12 @@ const approve = async (approved: boolean) => {
:class="['ui', {loading: isLoading}, 'basic danger button']" :class="['ui', {loading: isLoading}, 'basic danger button']"
:action="remove" :action="remove"
> >
<translate translate-context="*/*/*/Verb"> <translate >
Delete Delete
</translate> </translate>
<template #modal-header> <template #modal-header>
<p> <p>
<translate translate-context="Popup/Library/Title"> <translate >
Delete this suggestion? Delete this suggestion?
</translate> </translate>
</p> </p>
@ -350,7 +350,7 @@ const approve = async (approved: boolean) => {
<template #modal-content> <template #modal-content>
<div> <div>
<p> <p>
<translate translate-context="Popup/Library/Paragraph"> <translate >
The suggestion will be completely removed, this action is irreversible. The suggestion will be completely removed, this action is irreversible.
</translate> </translate>
</p> </p>
@ -358,7 +358,7 @@ const approve = async (approved: boolean) => {
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p> <p>
<translate translate-context="*/*/*/Verb"> <translate >
Delete Delete
</translate> </translate>
</p> </p>

View File

@ -4,7 +4,7 @@ import type { BackendError, License, ReviewState } from '~/types'
import { computed, onMounted, reactive, ref, watchEffect } from 'vue' import { computed, onMounted, reactive, ref, watchEffect } from 'vue'
import { isEqual, clone } from 'lodash-es' import { isEqual, clone } from 'lodash-es'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useStore } from '~/store' import { useStore } from '~/store'
import axios from 'axios' import axios from 'axios'
@ -26,7 +26,7 @@ const props = withDefaults(defineProps<Props>(), {
licenses: () => [] licenses: () => []
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const configs = useEditConfigs() const configs = useEditConfigs()
const store = useStore() const store = useStore()
@ -46,7 +46,7 @@ const canEdit = computed(() => {
}) })
const labels = 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' const mutationsUrl = computed(() => props.objectType === 'track'
@ -148,7 +148,7 @@ const resetField = (fieldId: string) => {
<div v-if="submittedMutation"> <div v-if="submittedMutation">
<div class="ui positive message"> <div class="ui positive message">
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Library/Paragraph"> <translate >
Your edit was successfully submitted. Your edit was successfully submitted.
</translate> </translate>
</h4> </h4>
@ -161,7 +161,7 @@ const resetField = (fieldId: string) => {
class="ui button" class="ui button"
@click.prevent="submittedMutation = null" @click.prevent="submittedMutation = null"
> >
<translate translate-context="Content/Library/Button.Label"> <translate >
Submit another edit Submit another edit
</translate> </translate>
</button> </button>
@ -175,27 +175,27 @@ const resetField = (fieldId: string) => {
> >
<div> <div>
<template v-if="showPendingReview"> <template v-if="showPendingReview">
<translate translate-context="Content/Library/Paragraph"> <translate >
Recent edits awaiting review Recent edits awaiting review
</translate> </translate>
<button <button
class="ui tiny basic right floated button" class="ui tiny basic right floated button"
@click.prevent="showPendingReview = false" @click.prevent="showPendingReview = false"
> >
<translate translate-context="Content/Library/Button.Label"> <translate >
Show all edits Show all edits
</translate> </translate>
</button> </button>
</template> </template>
<template v-else> <template v-else>
<translate translate-context="Content/Library/Paragraph"> <translate >
Recent edits Recent edits
</translate> </translate>
<button <button
class="ui tiny basic right floated button" class="ui tiny basic right floated button"
@click.prevent="showPendingReview = true" @click.prevent="showPendingReview = true"
> >
<translate translate-context="Content/Library/Button.Label"> <translate >
Restrict to unreviewed edits Restrict to unreviewed edits
</translate> </translate>
</button> </button>
@ -203,7 +203,7 @@ const resetField = (fieldId: string) => {
</div> </div>
<template #empty-state> <template #empty-state>
<empty-state> <empty-state>
<translate translate-context="Content/Library/Paragraph"> <translate >
Suggest a change using the form below. Suggest a change using the form below.
</translate> </translate>
</empty-state> </empty-state>
@ -220,7 +220,7 @@ const resetField = (fieldId: string) => {
class="ui negative message" class="ui negative message"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Content/Library/Error message.Title"> <translate >
Error while submitting edit Error while submitting edit
</translate> </translate>
</h4> </h4>
@ -237,7 +237,7 @@ const resetField = (fieldId: string) => {
v-if="!canEdit" v-if="!canEdit"
class="ui message" 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. You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval.
</translate> </translate>
</div> </div>
@ -268,7 +268,7 @@ const resetField = (fieldId: string) => {
class="ui fluid search dropdown" class="ui fluid search dropdown"
> >
<option :value="null"> <option :value="null">
<translate translate-context="*/*/*"> <translate >
N/A N/A
</translate> </translate>
</option> </option>
@ -286,7 +286,7 @@ const resetField = (fieldId: string) => {
@click.prevent="values[fieldConfig.id] = null" @click.prevent="values[fieldConfig.id] = null"
> >
<i class="x icon" /> <i class="x icon" />
<translate translate-context="Content/Library/Button.Label"> <translate >
Clear Clear
</translate> </translate>
</button> </button>
@ -325,7 +325,7 @@ const resetField = (fieldId: string) => {
@click.prevent="values[fieldConfig.id] = []" @click.prevent="values[fieldConfig.id] = []"
> >
<i class="x icon" /> <i class="x icon" />
<translate translate-context="Content/Library/Button.Label"> <translate >
Clear Clear
</translate> </translate>
</button> </button>
@ -337,7 +337,7 @@ const resetField = (fieldId: string) => {
@click.prevent="resetField(fieldConfig.id)" @click.prevent="resetField(fieldConfig.id)"
> >
<i class="undo icon" /> <i class="undo icon" />
<translate translate-context="Content/Library/Button.Label"> <translate >
Reset to initial value Reset to initial value
</translate> </translate>
</button> </button>
@ -345,7 +345,7 @@ const resetField = (fieldId: string) => {
</div> </div>
</template> </template>
<div class="field"> <div class="field">
<label for="summary"><translate translate-context="*/*/*">Summary (optional)</translate></label> <label for="summary"><translate >Summary (optional)</translate></label>
<textarea <textarea
id="change-summary" id="change-summary"
v-model="summary" v-model="summary"
@ -359,7 +359,7 @@ const resetField = (fieldId: string) => {
class="ui left floated button" class="ui left floated button"
:to="{name: 'library.tracks.detail', params: {id: object.id }}" :to="{name: 'library.tracks.detail', params: {id: object.id }}"
> >
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</router-link> </router-link>
@ -370,13 +370,13 @@ const resetField = (fieldId: string) => {
> >
<translate <translate
v-if="canEdit" v-if="canEdit"
translate-context="Content/Library/Button.Label/Verb"
> >
Submit and apply edit Submit and apply edit
</translate> </translate>
<translate <translate
v-else v-else
translate-context="Content/Library/Button.Label/Verb"
> >
Submit suggestion Submit suggestion
</translate> </translate>

View File

@ -5,7 +5,7 @@ import type { VueUploadItem } from 'vue-upload-component'
import { computed, ref, reactive, watch, nextTick } from 'vue' import { computed, ref, reactive, watch, nextTick } from 'vue'
import { useEventListener, useIntervalFn } from '@vueuse/core' import { useEventListener, useIntervalFn } from '@vueuse/core'
import { humanSize, truncate } from '~/utils/filters' import { humanSize, truncate } from '~/utils/filters'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { sortBy } from 'lodash-es' import { sortBy } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -34,7 +34,7 @@ const props = withDefaults(defineProps<Props>(), {
defaultImportReference: '' defaultImportReference: ''
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const store = useStore() const store = useStore()
const upload = ref() const upload = ref()
@ -43,13 +43,12 @@ const supportedExtensions = computed(() => store.state.ui.supportedExtensions)
const labels = computed(() => ({ const labels = computed(() => ({
tooltips: { tooltips: {
denied: $pgettext('Content/Library/Help text', 'Upload denied, ensure the file is not too big and that you have not reached your quota'), denied: t('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'), server: t('Cannot upload this file, ensure it is not too big'),
network: $pgettext('Content/Library/Help text', 'A network error occurred while uploading this file'), network: t('A network error occurred while uploading this file'),
timeout: $pgettext('Content/Library/Help text', 'Upload timeout, please try again'), timeout: t('Upload timeout, please try again'),
retry: $pgettext('*/*/*/Verb', 'Retry'), retry: t('Retry'),
extension: $pgettext( extension: t(
'Content/Library/Help text',
'Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }', 'Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }',
{ extensions: supportedExtensions.value.join(', ') } { extensions: supportedExtensions.value.join(', ') }
) )
@ -283,7 +282,7 @@ const retry = (files: Omit<VueUploadItem, 'xhr'>[]) => {
useEventListener(window, 'beforeunload', (event) => { useEventListener(window, 'beforeunload', (event) => {
if (!hasActiveUploads.value) return null if (!hasActiveUploads.value) return null
event.preventDefault() 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> </script>
@ -295,7 +294,7 @@ useEventListener(window, 'beforeunload', (event) => {
:class="['item', {active: currentTab === 'uploads'}]" :class="['item', {active: currentTab === 'uploads'}]"
@click.prevent="currentTab = 'uploads'" @click.prevent="currentTab = 'uploads'"
> >
<translate translate-context="Content/Library/Tab.Title/Short">Uploading</translate> <translate >Uploading</translate>
<div <div
v-if="files.length === 0" v-if="files.length === 0"
class="ui label" class="ui label"
@ -320,7 +319,7 @@ useEventListener(window, 'beforeunload', (event) => {
:class="['item', {active: currentTab === 'processing'}]" :class="['item', {active: currentTab === 'processing'}]"
@click.prevent="currentTab = 'processing'" @click.prevent="currentTab = 'processing'"
> >
<translate translate-context="Content/Library/Tab.Title/Short">Processing</translate> <translate >Processing</translate>
<div <div
v-if="processableFiles === 0" v-if="processableFiles === 0"
class="ui label" class="ui label"
@ -345,7 +344,7 @@ useEventListener(window, 'beforeunload', (event) => {
<div :class="['ui', {loading: isLoadingQuota}, 'container']"> <div :class="['ui', {loading: isLoadingQuota}, 'container']">
<div :class="['ui', {red: remainingSpace === 0}, {warning: remainingSpace > 0 && remainingSpace <= 50}, 'small', 'statistic']"> <div :class="['ui', {red: remainingSpace === 0}, {warning: remainingSpace > 0 && remainingSpace <= 50}, 'small', 'statistic']">
<div class="label"> <div class="label">
<translate translate-context="Content/Library/Paragraph"> <translate >
Remaining storage space Remaining storage space
</translate> </translate>
</div> </div>
@ -355,33 +354,33 @@ useEventListener(window, 'beforeunload', (event) => {
</div> </div>
<div class="ui divider" /> <div class="ui divider" />
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Library/Title/Verb"> <translate >
Upload music from '~/your local storage Upload music from '~/your local storage
</translate> </translate>
</h2> </h2>
<div class="ui message"> <div class="ui message">
<p> <p>
<translate translate-context="Content/Library/Paragraph"> <translate >
You are about to upload music to your library. Before proceeding, please ensure that: You are about to upload music to your library. Before proceeding, please ensure that:
</translate> </translate>
</p> </p>
<ul> <ul>
<li v-if="library.privacy_level != 'me'"> <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 You are not uploading copyrighted content in a public library, otherwise you may be infringing the law
</translate> </translate>
</li> </li>
<li> <li>
<translate translate-context="Content/Library/List item"> <translate >
The music files you are uploading are tagged properly. The music files you are uploading are tagged properly.
</translate>&nbsp; </translate>&nbsp;
<a <a
href="http://picard.musicbrainz.org/" href="http://picard.musicbrainz.org/"
target="_blank" 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>
<li> <li>
<translate translate-context="Content/Library/List item"> <translate >
The music files you are uploading are in OGG, Flac, MP3 or AIFF format The music files you are uploading are in OGG, Flac, MP3 or AIFF format
</translate> </translate>
</li> </li>
@ -401,14 +400,14 @@ useEventListener(window, 'beforeunload', (event) => {
@input-file="inputFile" @input-file="inputFile"
> >
<i class="upload icon" />&nbsp; <i class="upload icon" />&nbsp;
<translate translate-context="Content/Library/Paragraph/Call to action"> <translate >
Click to select files to upload or drag and drop files or directories Click to select files to upload or drag and drop files or directories
</translate> </translate>
<br> <br>
<br> <br>
<i> <i>
<translate <translate
translate-context="Content/Library/Paragraph"
:translate-params="{extensions: supportedExtensions.join(', ')}" :translate-params="{extensions: supportedExtensions.join(', ')}"
> >
Supported extensions: %{ extensions } Supported extensions: %{ extensions }
@ -425,22 +424,22 @@ useEventListener(window, 'beforeunload', (event) => {
<thead> <thead>
<tr> <tr>
<th class="ten wide"> <th class="ten wide">
<translate translate-context="Content/Library/Table.Label"> <translate >
Filename Filename
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/*/*/Noun"> <translate >
Size Size
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/*"> <translate >
Status Status
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/*"> <translate >
Actions Actions
</translate> </translate>
</th> </th>
@ -454,7 +453,7 @@ useEventListener(window, 'beforeunload', (event) => {
class="ui right floated small basic button" class="ui right floated small basic button"
@click.prevent="retry(retryableFiles)" @click.prevent="retry(retryableFiles)"
> >
<translate translate-context="Content/Library/Table"> <translate >
Retry failed uploads Retry failed uploads
</translate> </translate>
</button> </button>
@ -486,7 +485,7 @@ useEventListener(window, 'beforeunload', (event) => {
> >
<translate <translate
key="1" key="1"
translate-context="Content/Library/Table"
>Uploaded</translate> >Uploaded</translate>
</span> </span>
<span <span
@ -495,7 +494,7 @@ useEventListener(window, 'beforeunload', (event) => {
> >
<translate <translate
key="2" key="2"
translate-context="Content/Library/Table"
> >
Uploading Uploading
</translate> </translate>
@ -507,7 +506,7 @@ useEventListener(window, 'beforeunload', (event) => {
> >
<translate <translate
key="3" key="3"
translate-context="Content/Library/*/Short"
> >
Pending Pending
</translate> </translate>
@ -539,7 +538,7 @@ useEventListener(window, 'beforeunload', (event) => {
</div> </div>
<div class="ui divider" /> <div class="ui divider" />
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Library/Title/Verb"> <translate >
Import music from your server Import music from your server
</translate> </translate>
</h2> </h2>
@ -549,7 +548,7 @@ useEventListener(window, 'beforeunload', (event) => {
class="ui negative message" class="ui negative message"
> >
<h3 class="header"> <h3 class="header">
<translate translate-context="Content/*/Error message.Title"> <translate >
Error while launching import Error while launching import
</translate> </translate>
</h3> </h3>
@ -570,17 +569,17 @@ useEventListener(window, 'beforeunload', (event) => {
/> />
<template v-if="fsStatus && fsStatus.import"> <template v-if="fsStatus && fsStatus.import">
<h3 class="ui header"> <h3 class="ui header">
<translate translate-context="Content/Library/Title/Verb"> <translate >
Import status Import status
</translate> </translate>
</h3> </h3>
<p v-if="fsStatus.import.reference !== importReference"> <p v-if="fsStatus.import.reference !== importReference">
<translate translate-context="Content/Library/Paragraph"> <translate >
Results of your previous import: Results of your previous import:
</translate> </translate>
</p> </p>
<p v-else> <p v-else>
<translate translate-context="Content/Library/Paragraph"> <translate >
Results of your import: Results of your import:
</translate> </translate>
</p> </p>
@ -590,7 +589,7 @@ useEventListener(window, 'beforeunload', (event) => {
class="ui button" class="ui button"
@click="cancelFsScan" @click="cancelFsScan"
> >
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</button> </button>

View File

@ -42,7 +42,7 @@ const handleClick = (entry: FSEntry) => {
class="ui button" class="ui button"
@click.prevent="emit('import')" @click.prevent="emit('import')"
> >
<translate translate-context="Content/Library/Button/Verb"> <translate >
Import Import
</translate> </translate>
</button> </button>

View File

@ -15,7 +15,7 @@ defineProps<Props>()
class="ui active dimmer" class="ui active dimmer"
> >
<div class="ui text loader"> <div class="ui text loader">
<translate translate-context="Content/Library/Paragraph"> <translate >
Import hasn't started yet Import hasn't started yet
</translate> </translate>
</div> </div>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import axios from 'axios' import axios from 'axios'
@ -24,9 +24,9 @@ const artists = ref([])
const logger = useLogger() const logger = useLogger()
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/Home/Title', 'Library') title: t('Library')
})) }))
const isLoading = ref(false) const isLoading = ref(false)
@ -67,7 +67,7 @@ fetchData()
:websocket-handlers="['Listen']" :websocket-handlers="['Listen']"
> >
<template #title> <template #title>
<translate translate-context="Content/Home/Title"> <translate >
Recently listened Recently listened
</translate> </translate>
</template> </template>
@ -79,7 +79,7 @@ fetchData()
:filters="{scope: scope, ordering: '-creation_date'}" :filters="{scope: scope, ordering: '-creation_date'}"
> >
<template #title> <template #title>
<translate translate-context="Content/Home/Title"> <translate >
Recently favorited Recently favorited
</translate> </translate>
</template> </template>
@ -91,7 +91,7 @@ fetchData()
:filters="{scope: scope, playable: true, ordering: '-modification_date'}" :filters="{scope: scope, playable: true, ordering: '-modification_date'}"
> >
<template #title> <template #title>
<translate translate-context="*/*/*"> <translate >
Playlists Playlists
</translate> </translate>
</template> </template>
@ -103,7 +103,7 @@ fetchData()
<div class="column"> <div class="column">
<album-widget :filters="{scope: scope, playable: true, ordering: '-creation_date'}"> <album-widget :filters="{scope: scope, playable: true, ordering: '-creation_date'}">
<template #title> <template #title>
<translate translate-context="Content/Home/Title"> <translate >
Recently added Recently added
</translate> </translate>
</template> </template>
@ -112,7 +112,7 @@ fetchData()
</div> </div>
<template v-if="scope === 'all'"> <template v-if="scope === 'all'">
<h3 class="ui header"> <h3 class="ui header">
<translate translate-context="*/*/*"> <translate >
New channels New channels
</translate> </translate>
</h3> </h3>

View File

@ -3,7 +3,7 @@ import type { Upload } from '~/types'
import SemanticModal from '~/components/semantic/Modal.vue' import SemanticModal from '~/components/semantic/Modal.vue'
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
interface ErrorEntry { interface ErrorEntry {
key: string key: string
@ -44,7 +44,7 @@ const getErrors = (details: object): ErrorEntry[] => {
return errors return errors
} }
const { $pgettext } = useGettext() const { t } = useI18n()
const getErrorData = (upload: Upload) => { const getErrorData = (upload: Upload) => {
const payload = upload.import_details ?? { error_code: '', detail: {} } const payload = upload.import_details ?? { error_code: '', detail: {} }
@ -58,11 +58,11 @@ const getErrorData = (upload: Upload) => {
supportUrl: 'https://forum.funkwhale.audio/t/support', supportUrl: 'https://forum.funkwhale.audio/t/support',
documentationUrl: `https://docs.funkwhale.audio/users/upload.html#${errorCode}`, documentationUrl: `https://docs.funkwhale.audio/users/upload.html#${errorCode}`,
label: errorCode === 'invalid_metadata' label: errorCode === 'invalid_metadata'
? $pgettext('Popup/Import/Error.Label', 'Invalid metadata') ? t('Invalid metadata')
: $pgettext('*/*/Error', 'Unknown error'), : t('Unknown error'),
detail: errorCode === 'invalid_metadata' detail: errorCode === 'invalid_metadata'
? $pgettext('Popup/Import/Error.Label', 'The metadata included in the file is invalid or some mandatory fields are missing.') ? t('The metadata included in the file is invalid or some mandatory fields are missing.')
: $pgettext('Popup/Import/Error.Label', 'An unknown error occurred'), : t('An unknown error occurred'),
errorRows: errorCode === 'invalid_metadata' errorRows: errorCode === 'invalid_metadata'
? getErrors(payload.detail ?? {}) ? getErrors(payload.detail ?? {})
: [], : [],
@ -77,7 +77,7 @@ const getErrorData = (upload: Upload) => {
<template> <template>
<semantic-modal v-model:show="show"> <semantic-modal v-model:show="show">
<h4 class="header"> <h4 class="header">
<translate translate-context="Popup/Import/Title"> <translate >
Import detail Import detail
</translate> </translate>
</h4> </h4>
@ -90,7 +90,7 @@ const getErrorData = (upload: Upload) => {
v-if="upload.import_status === 'pending'" v-if="upload.import_status === 'pending'"
class="ui message" class="ui message"
> >
<translate translate-context="Popup/Import/Message"> <translate >
Upload is still pending and will soon be processed by the server. Upload is still pending and will soon be processed by the server.
</translate> </translate>
</div> </div>
@ -98,7 +98,7 @@ const getErrorData = (upload: Upload) => {
v-if="upload.import_status === 'finished'" v-if="upload.import_status === 'finished'"
class="ui success message" class="ui success message"
> >
<translate translate-context="Popup/Import/Message"> <translate >
Upload was successfully processed by the server. Upload was successfully processed by the server.
</translate> </translate>
</div> </div>
@ -107,7 +107,7 @@ const getErrorData = (upload: Upload) => {
role="alert" role="alert"
class="ui warning message" 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. Upload was skipped because a similar one is already available in one of your libraries.
</translate> </translate>
</div> </div>
@ -115,7 +115,7 @@ const getErrorData = (upload: Upload) => {
v-if="upload.import_status === 'errored'" v-if="upload.import_status === 'errored'"
class="ui error message" class="ui error message"
> >
<translate translate-context="Popup/Import/Message"> <translate >
An error occurred during upload processing. You will find more information below. An error occurred during upload processing. You will find more information below.
</translate> </translate>
</div> </div>
@ -124,7 +124,7 @@ const getErrorData = (upload: Upload) => {
<tbody> <tbody>
<tr> <tr>
<td> <td>
<translate translate-context="Popup/Import/Table.Label/Noun"> <translate >
Error type Error type
</translate> </translate>
</td> </td>
@ -134,7 +134,7 @@ const getErrorData = (upload: Upload) => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Popup/Import/Table.Label/Noun"> <translate >
Error detail Error detail
</translate> </translate>
</td> </td>
@ -152,7 +152,7 @@ const getErrorData = (upload: Upload) => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Footer/*/Link"> <translate >
Getting help Getting help
</translate> </translate>
</td> </td>
@ -163,7 +163,7 @@ const getErrorData = (upload: Upload) => {
:href="getErrorData(upload).documentationUrl" :href="getErrorData(upload).documentationUrl"
target="_blank" 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> </a>
</li> </li>
<li> <li>
@ -171,7 +171,7 @@ const getErrorData = (upload: Upload) => {
:href="getErrorData(upload).supportUrl" :href="getErrorData(upload).supportUrl"
target="_blank" 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> </a>
</li> </li>
</ul> </ul>
@ -179,7 +179,7 @@ const getErrorData = (upload: Upload) => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Popup/Import/Table.Label/Noun"> <translate >
Debug information Debug information
</translate> </translate>
</td> </td>
@ -200,7 +200,7 @@ const getErrorData = (upload: Upload) => {
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui deny button"> <button class="ui deny button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Close Close
</translate> </translate>
</button> </button>

View File

@ -6,7 +6,7 @@ import type { OrderingField } from '~/store/ui'
import { computed, ref, watch, onMounted } from 'vue' import { computed, ref, watch, onMounted } from 'vue'
import { useRouteQuery } from '@vueuse/router' import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { syncRef } from '@vueuse/core' import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es' import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -110,10 +110,10 @@ onOrderingUpdate(() => {
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search…'), searchPlaceholder: t('Search…'),
title: $pgettext('*/*/*/Noun', 'Podcasts') title: t('Podcasts')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b))) const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value].sort((a, b) => a - b)))
@ -123,7 +123,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
<main v-title="labels.title"> <main v-title="labels.title">
<section class="ui vertical stripe segment"> <section class="ui vertical stripe segment">
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Podcasts/Title"> <translate >
Browsing podcasts Browsing podcasts
</translate> </translate>
</h2> </h2>
@ -134,7 +134,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<label for="artist-search"> <label for="artist-search">
<translate translate-context="Content/Search/Input.Label/Noun">Podcast title</translate> <translate >Podcast title</translate>
</label> </label>
<div class="ui action input"> <div class="ui action input">
<input <input
@ -147,18 +147,18 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
<button <button
class="ui icon button" class="ui icon button"
type="submit" type="submit"
:aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')" :aria-label="t('Search')"
> >
<i class="search icon" /> <i class="search icon" />
</button> </button>
</div> </div>
</div> </div>
<div class="field"> <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" /> <tags-selector v-model="tags" />
</div> </div>
<div class="field"> <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 <select
id="artist-ordering" id="artist-ordering"
v-model="ordering" v-model="ordering"
@ -174,26 +174,26 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="artist-ordering-direction" id="artist-ordering-direction"
v-model="orderingDirection" v-model="orderingDirection"
class="ui dropdown" class="ui dropdown"
> >
<option value="+"> <option value="+">
<translate translate-context="Content/Search/Dropdown"> <translate >
Ascending Ascending
</translate> </translate>
</option> </option>
<option value="-"> <option value="-">
<translate translate-context="Content/Search/Dropdown"> <translate >
Descending Descending
</translate> </translate>
</option> </option>
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="artist-results" id="artist-results"
v-model="paginateBy" v-model="paginateBy"
@ -234,7 +234,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
> >
<div class="ui icon header"> <div class="ui icon header">
<i class="podcast icon" /> <i class="podcast icon" />
<translate translate-context="Content/Artists/Placeholder"> <translate >
No results matching your query No results matching your query
</translate> </translate>
</div> </div>
@ -244,7 +244,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
class="ui success button labeled icon" class="ui success button labeled icon"
> >
<i class="upload icon" /> <i class="upload icon" />
<translate translate-context="Content/*/Verb"> <translate >
Create a Channel Create a Channel
</translate> </translate>
</router-link> </router-link>
@ -255,7 +255,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
<div class="actions"> <div class="actions">
<a @click.stop.prevent="showSubscribeModal = true"> <a @click.stop.prevent="showSubscribeModal = true">
<i class="plus icon" /> <i class="plus icon" />
<translate translate-context="Content/Profile/Button">Subscribe to feed</translate> <translate >Subscribe to feed</translate>
</a> </a>
</div> </div>
</h1> </h1>
@ -275,7 +275,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
:fullscreen="false" :fullscreen="false"
> >
<h2 class="header"> <h2 class="header">
<translate translate-context="*/*/*/Noun"> <translate >
Subscription Subscription
</translate> </translate>
</h2> </h2>
@ -293,7 +293,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic deny button"> <button class="ui basic deny button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</button> </button>
@ -303,7 +303,7 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
class="ui primary button" class="ui primary button"
> >
<i class="bookmark icon" /> <i class="bookmark icon" />
<translate translate-context="*/*/*/Verb"> <translate >
Subscribe Subscribe
</translate> </translate>
</button> </button>

View File

@ -6,7 +6,7 @@ import type { OrderingField } from '~/store/ui'
import { computed, onMounted, ref, watch } from 'vue' import { computed, onMounted, ref, watch } from 'vue'
import { useRouteQuery } from '@vueuse/router' import { useRouteQuery } from '@vueuse/router'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { syncRef } from '@vueuse/core' import { syncRef } from '@vueuse/core'
import { sortedUniq } from 'lodash-es' import { sortedUniq } from 'lodash-es'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -99,10 +99,10 @@ onOrderingUpdate(() => {
onMounted(() => $('.ui.dropdown').dropdown()) onMounted(() => $('.ui.dropdown').dropdown())
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Enter a radio name…'), searchPlaceholder: t('Enter a radio name…'),
title: $pgettext('*/*/*', 'Radios') title: t('Radios')
})) }))
const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value].sort((a, b) => a - b))) 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"> <main v-title="labels.title">
<section class="ui vertical stripe segment"> <section class="ui vertical stripe segment">
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Radio/Title"> <translate >
Browsing radios Browsing radios
</translate> </translate>
</h2> </h2>
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<div class="ui row"> <div class="ui row">
<h3 class="ui header"> <h3 class="ui header">
<translate translate-context="Content/Radio/Title"> <translate >
Instance radios Instance radios
</translate> </translate>
</h3> </h3>
@ -144,7 +144,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<h3 class="ui header"> <h3 class="ui header">
<translate translate-context="Content/Radio/Title"> <translate >
User radios User radios
</translate> </translate>
</h3> </h3>
@ -153,7 +153,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
class="ui success button" class="ui success button"
to="/library/radios/build" to="/library/radios/build"
> >
<translate translate-context="Content/Radio/Button.Label/Verb"> <translate >
Create your own radio Create your own radio
</translate> </translate>
</router-link> </router-link>
@ -164,7 +164,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
> >
<div class="fields"> <div class="fields">
<div class="field"> <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"> <div class="ui action input">
<input <input
id="radios-search" id="radios-search"
@ -176,14 +176,14 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
<button <button
class="ui icon button" class="ui icon button"
type="submit" type="submit"
:aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')" :aria-label="t('Search')"
> >
<i class="search icon" /> <i class="search icon" />
</button> </button>
</div> </div>
</div> </div>
<div class="field"> <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 <select
id="radios-ordering" id="radios-ordering"
v-model="ordering" v-model="ordering"
@ -199,26 +199,26 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="radios-ordering-direction" id="radios-ordering-direction"
v-model="orderingDirection" v-model="orderingDirection"
class="ui dropdown" class="ui dropdown"
> >
<option value="+"> <option value="+">
<translate translate-context="Content/Search/Dropdown"> <translate >
Ascending Ascending
</translate> </translate>
</option> </option>
<option value="-"> <option value="-">
<translate translate-context="Content/Search/Dropdown"> <translate >
Descending Descending
</translate> </translate>
</option> </option>
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="radios-results" id="radios-results"
v-model="paginateBy" v-model="paginateBy"
@ -242,7 +242,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
> >
<div class="ui icon header"> <div class="ui icon header">
<i class="feed icon" /> <i class="feed icon" />
<translate translate-context="Content/Radios/Placeholder"> <translate >
No results matching your query No results matching your query
</translate> </translate>
</div> </div>
@ -252,7 +252,7 @@ const paginateOptions = computed(() => sortedUniq([12, 25, 50, paginateBy.value]
class="ui success button labeled icon" class="ui success button labeled icon"
> >
<i class="rss icon" /> <i class="rss icon" />
<translate translate-context="Content/*/Verb"> <translate >
Create a radio Create a radio
</translate> </translate>
</router-link> </router-link>

View File

@ -36,7 +36,7 @@ const labels = computed(() => ({
:to="{name: 'manage.library.tags.detail', params: {id: id}}" :to="{name: 'manage.library.tags.detail', params: {id: id}}"
> >
<i class="wrench icon" /> <i class="wrench icon" />
<translate translate-context="Content/Moderation/Link"> <translate >
Open in moderation interface Open in moderation interface
</translate> </translate>
</router-link> </router-link>
@ -50,7 +50,7 @@ const labels = computed(() => ({
> >
<template #title> <template #title>
<router-link :to="{name: 'library.artists.browse', query: {tag: id}}"> <router-link :to="{name: 'library.artists.browse', query: {tag: id}}">
<translate translate-context="*/*/*/Noun"> <translate >
Artists Artists
</translate> </translate>
</router-link> </router-link>
@ -59,7 +59,7 @@ const labels = computed(() => ({
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<h3 class="ui header"> <h3 class="ui header">
<translate translate-context="*/*/*"> <translate >
Channels Channels
</translate> </translate>
</h3> </h3>
@ -79,7 +79,7 @@ const labels = computed(() => ({
> >
<template #title> <template #title>
<router-link :to="{name: 'library.albums.browse', query: {tag: id}}"> <router-link :to="{name: 'library.albums.browse', query: {tag: id}}">
<translate translate-context="*/*/*"> <translate >
Albums Albums
</translate> </translate>
</router-link> </router-link>
@ -97,7 +97,7 @@ const labels = computed(() => ({
:filters="{playable: true, ordering: '-creation_date', tag: id}" :filters="{playable: true, ordering: '-creation_date', tag: id}"
> >
<template #title> <template #title>
<translate translate-context="*/*/*"> <translate >
Tracks Tracks
</translate> </translate>
</template> </template>

View File

@ -99,7 +99,7 @@ onMounted(async () => {
class="search" class="search"
> >
<div class="default text"> <div class="default text">
<translate translate-context="*/Dropdown/Placeholder/Verb"> <translate >
Search Search
</translate> </translate>
</div> </div>

View File

@ -3,7 +3,7 @@ import type { Track, Artist, Library } from '~/types'
import { momentFormat } from '~/utils/filters' import { momentFormat } from '~/utils/filters'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { getDomain } from '~/utils' import { getDomain } from '~/utils'
import { useStore } from '~/store' import { useStore } from '~/store'
@ -67,8 +67,7 @@ const attributedToUrl = computed(() => router.resolve({
const escapeHtml = (unsafe: string) => document.createTextNode(unsafe).textContent ?? '' const escapeHtml = (unsafe: string) => document.createTextNode(unsafe).textContent ?? ''
const subtitle = computed(() => { const subtitle = computed(() => {
if (track.value?.attributed_to) { if (track.value?.attributed_to) {
return $pgettext( return t(
'Content/Track/Paragraph',
'Uploaded by <a class="internal" href="%{ uploaderUrl }">%{ uploader }</a> on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>', 'Uploaded by <a class="internal" href="%{ uploaderUrl }">%{ uploader }</a> on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>',
{ {
uploaderUrl: attributedToUrl.value, uploaderUrl: attributedToUrl.value,
@ -79,8 +78,7 @@ const subtitle = computed(() => {
) )
} }
return $pgettext( return t(
'Content/Track/Paragraph',
'Uploaded on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>', 'Uploaded on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>',
{ {
date: escapeHtml(track.value?.creation_date ?? ''), date: escapeHtml(track.value?.creation_date ?? ''),
@ -89,11 +87,11 @@ const subtitle = computed(() => {
) )
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('*/*/*/Noun', 'Track'), title: t('Track'),
download: $pgettext('Content/Track/Link/Verb', 'Download'), download: t('Download'),
more: $pgettext('*/*/Button.Label/Noun', 'More…') more: t('More…')
})) }))
const isLoading = ref(false) const isLoading = ref(false)
@ -157,7 +155,7 @@ const remove = async () => {
class="vibrant" class="vibrant"
:track="track" :track="track"
> >
<translate translate-context="*/Queue/Button.Label/Short, Verb"> <translate >
Play Play
</translate> </translate>
</play-button> </play-button>
@ -189,7 +187,7 @@ const remove = async () => {
v-model:show="showEmbedModal" v-model:show="showEmbedModal"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Popup/Track/Title"> <translate >
Embed this track on your website Embed this track on your website
</translate> </translate>
</h4> </h4>
@ -203,7 +201,7 @@ const remove = async () => {
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui basic deny button"> <button class="ui basic deny button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</button> </button>
@ -228,7 +226,7 @@ const remove = async () => {
<i class="external icon" /> <i class="external icon" />
<translate <translate
:translate-params="{domain: domain}" :translate-params="{domain: domain}"
translate-context="Content/*/Button.Label/Verb"
>View on %{ domain }</translate> >View on %{ domain }</translate>
</a> </a>
<div <div
@ -238,7 +236,7 @@ const remove = async () => {
@click="showEmbedModal = !showEmbedModal" @click="showEmbedModal = !showEmbedModal"
> >
<i class="code icon" /> <i class="code icon" />
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Embed Embed
</translate> </translate>
</div> </div>
@ -249,7 +247,7 @@ const remove = async () => {
class="basic item" class="basic item"
> >
<i class="wikipedia w icon" /> <i class="wikipedia w icon" />
<translate translate-context="Content/*/Button.Label/Verb">Search on Wikipedia</translate> <translate >Search on Wikipedia</translate>
</a> </a>
<a <a
v-if="discogsUrl" v-if="discogsUrl"
@ -259,7 +257,7 @@ const remove = async () => {
class="basic item" class="basic item"
> >
<i class="external icon" /> <i class="external icon" />
<translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate> <translate >Search on Discogs</translate>
</a> </a>
<router-link <router-link
v-if="track.is_local" v-if="track.is_local"
@ -267,7 +265,7 @@ const remove = async () => {
class="basic item" class="basic item"
> >
<i class="edit icon" /> <i class="edit icon" />
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Edit Edit
</translate> </translate>
</router-link> </router-link>
@ -277,12 +275,12 @@ const remove = async () => {
@confirm="remove()" @confirm="remove()"
> >
<i class="ui trash icon" /> <i class="ui trash icon" />
<translate translate-context="*/*/*/Verb"> <translate >
Delete Delete
</translate> </translate>
<template #modal-header> <template #modal-header>
<p> <p>
<translate translate-context="Popup/Channel/Title"> <translate >
Delete this track? Delete this track?
</translate> </translate>
</p> </p>
@ -290,7 +288,7 @@ const remove = async () => {
<template #modal-content> <template #modal-content>
<div> <div>
<p> <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. The track will be deleted, as well as any related files and data. This action is irreversible.
</translate> </translate>
</p> </p>
@ -298,7 +296,7 @@ const remove = async () => {
</template> </template>
<template #modal-confirm> <template #modal-confirm>
<p> <p>
<translate translate-context="*/*/*/Verb"> <translate >
Delete Delete
</translate> </translate>
</p> </p>
@ -321,7 +319,7 @@ const remove = async () => {
:to="{name: 'manage.library.tracks.detail', params: {id: track.id}}" :to="{name: 'manage.library.tracks.detail', params: {id: track.id}}"
> >
<i class="wrench icon" /> <i class="wrench icon" />
<translate translate-context="Content/Moderation/Link"> <translate >
Open in moderation interface Open in moderation interface
</translate> </translate>
</router-link> </router-link>
@ -333,7 +331,7 @@ const remove = async () => {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<i class="wrench icon" /> <i class="wrench icon" />
<translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp; <translate >View in Django's admin</translate>&nbsp;
</a> </a>
</div> </div>
</button> </button>

View File

@ -78,13 +78,13 @@ watchEffect(() => {
<h3 class="ui header"> <h3 class="ui header">
<translate <translate
v-if="track.artist?.content_category === 'music'" v-if="track.artist?.content_category === 'music'"
translate-context="Content/*/*"
> >
Track Details Track Details
</translate> </translate>
<translate <translate
v-else v-else
translate-context="Content/*/*"
> >
Episode Details Episode Details
</translate> </translate>
@ -93,7 +93,7 @@ watchEffect(() => {
<tbody> <tbody>
<tr> <tr>
<td> <td>
<translate translate-context="Content/*/*"> <translate >
Duration Duration
</translate> </translate>
</td> </td>
@ -103,7 +103,7 @@ watchEffect(() => {
</template> </template>
<translate <translate
v-else v-else
translate-context="*/*/*"
> >
N/A N/A
</translate> </translate>
@ -111,7 +111,7 @@ watchEffect(() => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Content/*/*/Noun"> <translate >
Size Size
</translate> </translate>
</td> </td>
@ -121,7 +121,7 @@ watchEffect(() => {
</template> </template>
<translate <translate
v-else v-else
translate-context="*/*/*"
> >
N/A N/A
</translate> </translate>
@ -129,7 +129,7 @@ watchEffect(() => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Content/*/*/Noun"> <translate >
Codec Codec
</translate> </translate>
</td> </td>
@ -139,7 +139,7 @@ watchEffect(() => {
</template> </template>
<translate <translate
v-else v-else
translate-context="*/*/*"
> >
N/A N/A
</translate> </translate>
@ -147,7 +147,7 @@ watchEffect(() => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Content/Track/*/Noun"> <translate >
Bitrate Bitrate
</translate> </translate>
</td> </td>
@ -157,7 +157,7 @@ watchEffect(() => {
</template> </template>
<translate <translate
v-else v-else
translate-context="*/*/*"
> >
N/A N/A
</translate> </translate>
@ -165,7 +165,7 @@ watchEffect(() => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Content/*/*"> <translate >
Downloads Downloads
</translate> </translate>
</td> </td>
@ -188,7 +188,7 @@ watchEffect(() => {
:can-update="false" :can-update="false"
/> />
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/*/*"> <translate >
Release Details Release Details
</translate> </translate>
</h2> </h2>
@ -196,7 +196,7 @@ watchEffect(() => {
<tbody> <tbody>
<tr> <tr>
<td> <td>
<translate translate-context="*/*/*/Noun"> <translate >
Artist Artist
</translate> </translate>
</td> </td>
@ -210,13 +210,13 @@ watchEffect(() => {
<td> <td>
<translate <translate
v-if="track.album.artist.content_category === 'music'" v-if="track.album.artist.content_category === 'music'"
translate-context="*/*/*/Noun"
> >
Album Album
</translate> </translate>
<translate <translate
v-else v-else
translate-context="*/*/*"
> >
Serie Serie
</translate> </translate>
@ -229,7 +229,7 @@ watchEffect(() => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="*/*/*"> <translate >
Year Year
</translate> </translate>
</td> </td>
@ -238,7 +238,7 @@ watchEffect(() => {
{{ momentFormat(new Date(track.album.release_date), 'Y') }} {{ momentFormat(new Date(track.album.release_date), 'Y') }}
</template> </template>
<template v-else> <template v-else>
<translate translate-context="*/*/*"> <translate >
N/A N/A
</translate> </translate>
</template> </template>
@ -246,7 +246,7 @@ watchEffect(() => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Content/Track/*/Noun"> <translate >
Copyright Copyright
</translate> </translate>
</td> </td>
@ -256,7 +256,7 @@ watchEffect(() => {
:title="track.copyright" :title="track.copyright"
>{{ truncate(track.copyright, 50) }}</span> >{{ truncate(track.copyright, 50) }}</span>
<template v-else> <template v-else>
<translate translate-context="*/*/*"> <translate >
N/A N/A
</translate> </translate>
</template> </template>
@ -264,7 +264,7 @@ watchEffect(() => {
</tr> </tr>
<tr> <tr>
<td> <td>
<translate translate-context="Content/*/*/Noun"> <translate >
License License
</translate> </translate>
</td> </td>
@ -277,7 +277,7 @@ watchEffect(() => {
>{{ license.name }}</a> >{{ license.name }}</a>
<translate <translate
v-else v-else
translate-context="*/*/*"
> >
N/A N/A
</translate> </translate>
@ -285,7 +285,7 @@ watchEffect(() => {
</tr> </tr>
<tr v-if="!track.is_local"> <tr v-if="!track.is_local">
<td> <td>
<translate translate-context="Content/*/*/Noun"> <translate >
URL URL
</translate> </translate>
</td> </td>
@ -308,10 +308,10 @@ watchEffect(() => {
rel="noreferrer noopener" rel="noreferrer noopener"
> >
<i class="external icon" /> <i class="external icon" />
<translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate> <translate >View on MusicBrainz</translate>
</a> </a>
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/*/Title/Noun"> <translate >
Related Playlists Related Playlists
</translate> </translate>
</h2> </h2>
@ -321,7 +321,7 @@ watchEffect(() => {
/> />
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/*/Title/Noun"> <translate >
Related Libraries Related Libraries
</translate> </translate>
</h2> </h2>
@ -329,7 +329,7 @@ watchEffect(() => {
:url="`tracks/${track.id}/libraries/`" :url="`tracks/${track.id}/libraries/`"
@loaded="emit('libraries-loaded', $event)" @loaded="emit('libraries-loaded', $event)"
> >
<translate translate-context="Content/Track/Paragraph"> <translate >
This track is present in the following libraries: This track is present in the following libraries:
</translate> </translate>
</library-widget> </library-widget>

View File

@ -46,13 +46,13 @@ fetchLicenses()
<h2> <h2>
<translate <translate
v-if="canEdit" v-if="canEdit"
translate-context="Content/*/Title"
> >
Edit this track Edit this track
</translate> </translate>
<translate <translate
key="2" key="2"
translate-context="Content/*/Title"
> >
Suggest an edit on this track Suggest an edit on this track
</translate> </translate>
@ -61,7 +61,7 @@ fetchLicenses()
v-if="!object.is_local" v-if="!object.is_local"
class="ui message" class="ui message"
> >
<translate translate-context="Content/*/Message"> <translate >
This object is managed by another server, you cannot edit it. This object is managed by another server, you cannot edit it.
</translate> </translate>
</div> </div>

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, reactive, watch, watchEffect, onMounted } from 'vue' import { computed, ref, reactive, watch, watchEffect, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import axios from 'axios' import axios from 'axios'
import $ from 'jquery' import $ from 'jquery'
@ -51,14 +51,14 @@ const props = withDefaults(defineProps<Props>(), {
id: 0 id: 0
}) })
const { $pgettext } = useGettext() const { t } = useI18n()
const router = useRouter() const router = useRouter()
const labels = computed(() => ({ const labels = computed(() => ({
title: $pgettext('Head/Radio/Title', 'Radio Builder'), title: t('Radio Builder'),
placeholder: { placeholder: {
description: $pgettext('Content/Radio/Input.Placeholder', 'My awesome description'), description: t('My awesome description'),
name: $pgettext('Content/Radio/Input.Placeholder', 'My awesome radio') name: t('My awesome radio')
} }
})) }))
@ -203,12 +203,12 @@ onMounted(() => {
<div> <div>
<section> <section>
<h2 class="ui header"> <h2 class="ui header">
<translate translate-context="Content/Radio/Title"> <translate >
Builder Builder
</translate> </translate>
</h2> </h2>
<p> <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. You can use this interface to build your own custom radio, which will play tracks according to your criteria.
</translate> </translate>
</p> </p>
@ -219,12 +219,12 @@ onMounted(() => {
> >
<h4 class="header"> <h4 class="header">
<template v-if="radioName"> <template v-if="radioName">
<translate translate-context="Content/Radio/Message"> <translate >
Radio updated Radio updated
</translate> </translate>
</template> </template>
<template v-else> <template v-else>
<translate translate-context="Content/Radio/Message"> <translate >
Radio created Radio created
</translate> </translate>
</template> </template>
@ -232,7 +232,7 @@ onMounted(() => {
</div> </div>
<div class=""> <div class="">
<div class="field"> <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 <input
id="name" id="name"
v-model="radioName" v-model="radioName"
@ -242,7 +242,7 @@ onMounted(() => {
> >
</div> </div>
<div class="field"> <div class="field">
<label for="description"><translate translate-context="*/*/*/Noun">Description</translate></label> <label for="description"><translate >Description</translate></label>
<textarea <textarea
id="description" id="description"
v-model="radioDesc" v-model="radioDesc"
@ -257,7 +257,7 @@ onMounted(() => {
v-model="isPublic" v-model="isPublic"
type="checkbox" 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>
<div class="ui hidden divider" /> <div class="ui hidden divider" />
<button <button
@ -265,7 +265,7 @@ onMounted(() => {
:class="['ui', 'success', {loading: isLoading}, 'button']" :class="['ui', 'success', {loading: isLoading}, 'button']"
@click="save" @click="save"
> >
<translate translate-context="Content/*/Button.Label/Verb"> <translate >
Save Save
</translate> </translate>
</button> </button>
@ -281,14 +281,14 @@ onMounted(() => {
<label <label
id="radioFilterLabel" id="radioFilterLabel"
for="radio-filters" 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 <select
id="radio-filters" id="radio-filters"
v-model="currentFilterType" v-model="currentFilterType"
class="ui dropdown" class="ui dropdown"
> >
<option value=""> <option value="">
<translate translate-context="Content/Radio/Dropdown.Placeholder/Verb"> <translate >
Select a filter Select a filter
</translate> </translate>
</option> </option>
@ -306,7 +306,7 @@ onMounted(() => {
class="ui button" class="ui button"
@click="add" @click="add"
> >
<translate translate-context="Content/Radio/Button.Label/Verb"> <translate >
Add filter Add filter
</translate> </translate>
</button> </button>
@ -319,27 +319,27 @@ onMounted(() => {
<thead> <thead>
<tr> <tr>
<th class="two wide"> <th class="two wide">
<translate translate-context="Content/Radio/Table.Label/Noun"> <translate >
Filter name Filter name
</translate> </translate>
</th> </th>
<th class="one wide"> <th class="one wide">
<translate translate-context="Content/Radio/Table.Label/Verb"> <translate >
Exclude Exclude
</translate> </translate>
</th> </th>
<th class="six wide"> <th class="six wide">
<translate translate-context="Content/Radio/Table.Label/Verb (Value is a List of Parameters)"> <translate >
Config Config
</translate> </translate>
</th> </th>
<th class="five wide"> <th class="five wide">
<translate translate-context="Content/Radio/Table.Label/Noun (Value is a number of Tracks)"> <translate >
Candidates Candidates
</translate> </translate>
</th> </th>
<th class="two wide"> <th class="two wide">
<translate translate-context="Content/*/*/Noun"> <translate >
Actions Actions
</translate> </translate>
</th> </th>
@ -363,7 +363,7 @@ onMounted(() => {
class="ui header" class="ui header"
:translate-n="checkResult.candidates.count" :translate-n="checkResult.candidates.count"
translate-plural="%{ count } tracks matching combined filters" translate-plural="%{ count } tracks matching combined filters"
translate-context="Content/Radio/Table.Paragraph/Short"
> >
%{ count } track matching combined filters %{ count } track matching combined filters
</h3> </h3>

View File

@ -124,7 +124,7 @@ watch(exclude, fetchCandidates)
for="exclude-filter" for="exclude-filter"
class="visually-hidden" class="visually-hidden"
> >
<translate translate-context="Popup/Radio/Title/Noun">Exclude</translate> <translate >Exclude</translate>
</label> </label>
</div> </div>
</td> </td>
@ -180,7 +180,7 @@ watch(exclude, fetchCandidates)
v-model:show="showCandidadesModal" v-model:show="showCandidadesModal"
> >
<h4 class="header"> <h4 class="header">
<translate translate-context="Popup/Radio/Title/Noun"> <translate >
Tracks matching filter Tracks matching filter
</translate> </translate>
</h4> </h4>
@ -194,7 +194,7 @@ watch(exclude, fetchCandidates)
</div> </div>
<div class="actions"> <div class="actions">
<button class="ui deny button"> <button class="ui deny button">
<translate translate-context="*/*/Button.Label/Verb"> <translate >
Cancel Cancel
</translate> </translate>
</button> </button>
@ -206,7 +206,7 @@ watch(exclude, fetchCandidates)
class="ui danger button" class="ui danger button"
@click="$emit('delete', index)" @click="$emit('delete', index)"
> >
<translate translate-context="Content/Radio/Button.Label/Verb"> <translate >
Remove Remove
</translate> </translate>
</button> </button>

View File

@ -5,7 +5,7 @@ import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui' import type { OrderingField } from '~/store/ui'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import axios from 'axios' import axios from 'axios'
@ -80,10 +80,10 @@ onOrderingUpdate(fetchData)
fetchData() fetchData()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by domain, name, account…'), searchPlaceholder: t('Search by domain, name, account…'),
openModeration: $pgettext('Content/Moderation/Verb', 'Open in moderation interface') openModeration: t('Open in moderation interface')
})) }))
</script> </script>
@ -92,7 +92,7 @@ const labels = computed(() => ({
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui six wide field"> <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"> <form @submit.prevent="query = search.value">
<input <input
id="channel-search" id="channel-search"
@ -105,7 +105,7 @@ const labels = computed(() => ({
</form> </form>
</div> </div>
<div class="field"> <div class="field">
<label for="channel-category"><translate translate-context="*/*/*">Category</translate></label> <label for="channel-category"><translate >Category</translate></label>
<select <select
id="channel-category" id="channel-category"
class="ui dropdown" class="ui dropdown"
@ -113,7 +113,7 @@ const labels = computed(() => ({
@change="addSearchToken('category', ($event.target as HTMLSelectElement).value)" @change="addSearchToken('category', ($event.target as HTMLSelectElement).value)"
> >
<option value=""> <option value="">
<translate translate-context="Content/*/Dropdown"> <translate >
All All
</translate> </translate>
</option> </option>
@ -129,7 +129,7 @@ const labels = computed(() => ({
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="channel-ordering" id="channel-ordering"
v-model="ordering" v-model="ordering"
@ -145,19 +145,19 @@ const labels = computed(() => ({
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="channel-ordering-direction" id="channel-ordering-direction"
v-model="orderingDirection" v-model="orderingDirection"
class="ui dropdown" class="ui dropdown"
> >
<option value="+"> <option value="+">
<translate translate-context="Content/Search/Dropdown"> <translate >
Ascending Ascending
</translate> </translate>
</option> </option>
<option value="-"> <option value="-">
<translate translate-context="Content/Search/Dropdown"> <translate >
Descending Descending
</translate> </translate>
</option> </option>
@ -182,32 +182,32 @@ const labels = computed(() => ({
> >
<template #header-cells> <template #header-cells>
<th> <th>
<translate translate-context="*/*/*/Noun"> <translate >
Name Name
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/*/Noun"> <translate >
Account Account
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/Moderation/*/Noun"> <translate >
Domain Domain
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/*"> <translate >
Albums Albums
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/*"> <translate >
Tracks Tracks
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/*/*/Noun"> <translate >
Creation date Creation date
</translate> </translate>
</th> </th>
@ -250,7 +250,7 @@ const labels = computed(() => ({
@click.prevent="addSearchToken('domain', scope.obj.attributed_to.domain)" @click.prevent="addSearchToken('domain', scope.obj.attributed_to.domain)"
> >
<i class="home icon" /> <i class="home icon" />
<translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> <translate >Local</translate>
</a> </a>
</td> </td>
<td> <td>
@ -276,7 +276,7 @@ const labels = computed(() => ({
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
<translate <translate
translate-context="Content/*/Paragraph"
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}" :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
> >
Showing results %{ start }-%{ end } on %{ total } Showing results %{ start }-%{ end } on %{ total }

View File

@ -5,7 +5,7 @@ import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui' import type { OrderingField } from '~/store/ui'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import axios from 'axios' import axios from 'axios'
@ -49,13 +49,13 @@ const orderingOptions: [OrderingField, keyof typeof sharedLabels.filters][] = [
['name', 'name'] ['name', 'name']
] ]
const { $pgettext } = useGettext() const { t } = useI18n()
const actionFilters = computed(() => ({ q: query.value, ...props.filters })) const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
const actions = computed(() => [ const actions = computed(() => [
{ {
name: 'delete', name: 'delete',
label: $pgettext('*/*/*/Verb', 'Delete'), label: t('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.'), confirmationMessage: t('The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible.'),
isDangerous: true, isDangerous: true,
allowAll: false, allowAll: false,
confirmColor: 'danger' confirmColor: 'danger'
@ -94,8 +94,8 @@ fetchData()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const labels = computed(() => ({ const labels = computed(() => ({
searchPlaceholder: $pgettext('Content/Search/Input.Placeholder', 'Search by domain, title, artist, MusicBrainz ID…'), searchPlaceholder: t('Search by domain, title, artist, MusicBrainz ID…'),
openModeration: $pgettext('Content/Moderation/Verb', 'Open in moderation interface') openModeration: t('Open in moderation interface')
})) }))
</script> </script>
@ -104,7 +104,7 @@ const labels = computed(() => ({
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui six wide field"> <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"> <form @submit.prevent="query = search.value">
<input <input
id="albums-search" id="albums-search"
@ -117,7 +117,7 @@ const labels = computed(() => ({
</form> </form>
</div> </div>
<div class="field"> <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 <select
id="albums-ordering" id="albums-ordering"
v-model="ordering" v-model="ordering"
@ -133,18 +133,18 @@ const labels = computed(() => ({
</select> </select>
</div> </div>
<div class="field"> <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 <select
v-model="orderingDirection" v-model="orderingDirection"
class="ui dropdown" class="ui dropdown"
> >
<option value="+"> <option value="+">
<translate translate-context="Content/Search/Dropdown"> <translate >
Ascending Ascending
</translate> </translate>
</option> </option>
<option value="-"> <option value="-">
<translate translate-context="Content/Search/Dropdown"> <translate >
Descending Descending
</translate> </translate>
</option> </option>
@ -169,32 +169,32 @@ const labels = computed(() => ({
> >
<template #header-cells> <template #header-cells>
<th> <th>
<translate translate-context="*/*/*/Noun"> <translate >
Title Title
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/*/Noun"> <translate >
Artist Artist
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/Moderation/*/Noun"> <translate >
Domain Domain
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/*"> <translate >
Tracks Tracks
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/*/*/Noun"> <translate >
Release date Release date
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/*/*/Noun"> <translate >
Creation date Creation date
</translate> </translate>
</th> </th>
@ -238,7 +238,7 @@ const labels = computed(() => ({
@click.prevent="addSearchToken('domain', scope.obj.domain)" @click.prevent="addSearchToken('domain', scope.obj.domain)"
> >
<i class="home icon" /> <i class="home icon" />
<translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> <translate >Local</translate>
</a> </a>
</td> </td>
<td> <td>
@ -251,7 +251,7 @@ const labels = computed(() => ({
/> />
<translate <translate
v-else v-else
translate-context="*/*/*"
> >
N/A N/A
</translate> </translate>
@ -273,7 +273,7 @@ const labels = computed(() => ({
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
<translate <translate
translate-context="Content/*/Paragraph"
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}" :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
> >
Showing results %{ start }-%{ end } on %{ total } Showing results %{ start }-%{ end } on %{ total }

View File

@ -5,7 +5,7 @@ import type { RouteRecordName } from 'vue-router'
import type { OrderingField } from '~/store/ui' import type { OrderingField } from '~/store/ui'
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useI18n } from 'vue-i18n'
import axios from 'axios' import axios from 'axios'
@ -52,8 +52,8 @@ const actionFilters = computed(() => ({ q: query.value, ...props.filters }))
const actions = computed(() => [ const actions = computed(() => [
{ {
name: 'delete', name: 'delete',
label: $pgettext('*/*/*/Verb', 'Delete'), label: t('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.'), 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, isDangerous: true,
allowAll: false, allowAll: false,
confirmColor: 'danger' confirmColor: 'danger'
@ -91,9 +91,9 @@ onOrderingUpdate(fetchData)
fetchData() fetchData()
const sharedLabels = useSharedLabels() const sharedLabels = useSharedLabels()
const { $pgettext } = useGettext() const { t } = useI18n()
const labels = computed(() => ({ 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 }) => { 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="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui six wide field"> <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"> <form @submit.prevent="query = search.value">
<input <input
id="artists-search" id="artists-search"
@ -121,7 +121,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
</form> </form>
</div> </div>
<div class="field"> <div class="field">
<label for="artists-category"><translate translate-context="*/*/*">Category</translate></label> <label for="artists-category"><translate >Category</translate></label>
<select <select
id="artists-category" id="artists-category"
class="ui dropdown" class="ui dropdown"
@ -129,7 +129,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
@change="addSearchToken('category', ($event.target as HTMLSelectElement).value)" @change="addSearchToken('category', ($event.target as HTMLSelectElement).value)"
> >
<option value=""> <option value="">
<translate translate-context="Content/*/Dropdown"> <translate >
All All
</translate> </translate>
</option> </option>
@ -145,7 +145,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="artists-ordering" id="artists-ordering"
v-model="ordering" v-model="ordering"
@ -161,19 +161,19 @@ const getUrl = (artist: { channel?: number; id: number }) => {
</select> </select>
</div> </div>
<div class="field"> <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 <select
id="artists-ordering-direction" id="artists-ordering-direction"
v-model="orderingDirection" v-model="orderingDirection"
class="ui dropdown" class="ui dropdown"
> >
<option value="+"> <option value="+">
<translate translate-context="Content/Search/Dropdown"> <translate >
Ascending Ascending
</translate> </translate>
</option> </option>
<option value="-"> <option value="-">
<translate translate-context="Content/Search/Dropdown"> <translate >
Descending Descending
</translate> </translate>
</option> </option>
@ -198,27 +198,27 @@ const getUrl = (artist: { channel?: number; id: number }) => {
> >
<template #header-cells> <template #header-cells>
<th> <th>
<translate translate-context="*/*/*/Noun"> <translate >
Name Name
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/Moderation/*/Noun"> <translate >
Domain Domain
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/*"> <translate >
Albums Albums
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="*/*/*"> <translate >
Tracks Tracks
</translate> </translate>
</th> </th>
<th> <th>
<translate translate-context="Content/*/*/Noun"> <translate >
Creation date Creation date
</translate> </translate>
</th> </th>
@ -250,7 +250,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
@click.prevent="addSearchToken('domain', scope.obj.domain)" @click.prevent="addSearchToken('domain', scope.obj.domain)"
> >
<i class="home icon" /> <i class="home icon" />
<translate translate-context="Content/Moderation/*/Short, Noun">Local</translate> <translate >Local</translate>
</a> </a>
</td> </td>
<td> <td>
@ -276,7 +276,7 @@ const getUrl = (artist: { channel?: number; id: number }) => {
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
<translate <translate
translate-context="Content/*/Paragraph"
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}" :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
> >
Showing results %{ start }-%{ end } on %{ total } Showing results %{ start }-%{ end } on %{ total }

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