Merge branch '161-i18n-vue-gettext' into 'develop'

Resolve "Industrialize the i18n workflow"

Closes #161 and #167

See merge request funkwhale/funkwhale!286
This commit is contained in:
Eliot Berriot 2018-06-30 18:36:32 +00:00
commit 262c40bc41
109 changed files with 5478 additions and 1887 deletions

1
.gitignore vendored
View File

@ -91,3 +91,4 @@ data/
po/*.po po/*.po
docs/swagger docs/swagger
_build _build
front/src/translations.json

View File

@ -19,9 +19,12 @@ review_front:
when: manual when: manual
allow_failure: true allow_failure: true
before_script: before_script:
- apt-get update
- apt-get install jq -y
- cd front - cd front
script: script:
- yarn install - yarn install
- yarn run i18n-compile
# this is to ensure we don't have any errors in the output, # this is to ensure we don't have any errors in the output,
# cf https://code.eliotberriot.com/funkwhale/funkwhale/issues/169 # cf https://code.eliotberriot.com/funkwhale/funkwhale/issues/169
- INSTANCE_URL=$REVIEW_INSTANCE_URL yarn run build | tee /dev/stderr | (! grep -i 'ERROR in') - INSTANCE_URL=$REVIEW_INSTANCE_URL yarn run build | tee /dev/stderr | (! grep -i 'ERROR in')
@ -175,11 +178,11 @@ build_front:
stage: build stage: build
image: node:9 image: node:9
before_script: before_script:
- apt-get update
- apt-get install jq -y
- cd front - cd front
script: script:
- yarn install - yarn install
- yarn run i18n-extract
- yarn run i18n-compile - yarn run i18n-compile
# this is to ensure we don't have any errors in the output, # this is to ensure we don't have any errors in the output,
# cf https://code.eliotberriot.com/funkwhale/funkwhale/issues/169 # cf https://code.eliotberriot.com/funkwhale/funkwhale/issues/169

View File

@ -289,8 +289,9 @@ Typical workflow for a contribution
Internationalization Internationalization
-------------------- --------------------
We're using https://github.com/Polyconseil/vue-gettext to manage i18n in the project.
When working on the front-end, any end-user string should be translated When working on the front-end, any end-user string should be translated
using either ``<i18next path="yourstring">`` or the ``$t('yourstring')`` using either ``<translate>yourstring</translate>`` or ``$gettext('yourstring')``
function. function.
Extraction is done by calling ``yarn run i18n-extract``, which Extraction is done by calling ``yarn run i18n-extract``, which

View File

@ -26,4 +26,9 @@ Contribute
---------- ----------
Contribution guidelines as well as development installation instructions Contribution guidelines as well as development installation instructions
are outlined in `CONTRIBUTING <CONTRIBUTING>`_ are outlined in `CONTRIBUTING <CONTRIBUTING>`_.
Translate
^^^^^^^^^
Translators willing to help can refer to `TRANSLATORS <TRANSLATORS>`_ for instructions.

28
TRANSLATORS.rst Normal file
View File

@ -0,0 +1,28 @@
Translating Funkwhale
=====================
Thank you for reading this! If you want to help translate Funkwhale,
you found the proper place :)
Translation is done via our own Weblate instance at https://translate.funkwhale.audio/projects/funkwhale/front/.
You can signup/login using your Gitlab account (from https://code.eliotberriot.com).
Translation workflow
--------------------
Once you're logged-in on the Weblate instance, you can suggest translations. Your suggestions will then be reviewer
by the project maintainer or other translators to ensure consistency.
Guidelines
----------
Respecting those guidelines is mandatory if you want your translation to be included:
- Use gender-neutral language and wording
Requesting a new language
-------------------------
If you'd like to see a new language in Funkwhale, please open an issue here:
https://code.eliotberriot.com/funkwhale/funkwhale/issues

View File

@ -0,0 +1 @@
New translation workflow (#161, #167)

View File

@ -23,6 +23,7 @@ Funkwhale is a self-hosted, modern free and open-source music server, heavily in
api api
third-party third-party
contributing contributing
translators
changelog changelog
Indices and tables Indices and tables

1
docs/translators.rst Normal file
View File

@ -0,0 +1 @@
.. include:: ../TRANSLATORS.rst

View File

@ -14,8 +14,6 @@ var webpackConfig = process.env.NODE_ENV === 'testing'
? require('./webpack.prod.conf') ? require('./webpack.prod.conf')
: require('./webpack.dev.conf') : require('./webpack.dev.conf')
require('./i18n')
// default port where dev server listens for incoming traffic // default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port var port = process.env.PORT || config.dev.port
var host = process.env.HOST || config.dev.host var host = process.env.HOST || config.dev.host

View File

@ -1,49 +0,0 @@
const fs = require('fs');
const path = require('path');
const { gettextToI18next } = require('i18next-conv');
const poDir = path.join(__dirname, '..', '..', 'po')
const outDir = path.join(__dirname, '..', 'static', 'translations')
if (!fs.existsSync(outDir) || !fs.statSync(outDir).isDirectory()) {
fs.mkdirSync(outDir)
}
// Convert .po files to i18next files
fs.readdir(poDir, (err, files) => {
if (err) {
return console.log(err)
}
for (const file of files) {
if (file.endsWith('.po')) {
const lang = file.replace(/\.po$/, '')
const output = path.join(outDir, `${lang}.json`)
fs.readFile(path.join(poDir, file), (err, content) => {
if (err) {
return console.log(err)
}
gettextToI18next(lang, content).then(res => {
fs.writeFile(output, res, err => {
if (err) {
console.log(err)
} else {
console.log(`Wrote translation file: ${output}`)
if (lang === 'en') {
// for english, we need to specify that json values are equal to the keys.
// otherwise we end up with empty strings on the front end for english
var contents = fs.readFileSync(output)
var jsonContent = JSON.parse(contents)
var finalContent = {}
Object.keys(jsonContent).forEach(function(key) {
finalContent[key] = key
})
fs.writeFile(output, JSON.stringify(finalContent))
}
}
})
})
})
}
}
})

1315
front/locales/app.pot Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,11 @@
"author": "Eliot Berriot <contact@eliotberriot.com>", "author": "Eliot Berriot <contact@eliotberriot.com>",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node build/dev-server.js", "dev": "scripts/i18n-compile.sh && node build/dev-server.js",
"start": "node build/dev-server.js", "start": "scripts/i18n-compile.sh && node build/dev-server.js",
"build": "node build/build.js", "build": "node build/build.js",
"i18n-extract": "find src/ -name '*.vue' | xargs vendor/vue-i18n-xgettext/index.js > ../po/en.po", "i18n-extract": "scripts/i18n-extract.sh",
"i18n-compile": "node build/i18n.js", "i18n-compile": "scripts/i18n-compile.sh",
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
"unit-watch": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js", "unit-watch": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js",
"e2e": "node test/e2e/runner.js", "e2e": "node test/e2e/runner.js",
@ -21,9 +21,6 @@
"axios": "^0.17.1", "axios": "^0.17.1",
"dateformat": "^2.0.0", "dateformat": "^2.0.0",
"django-channels": "^1.1.6", "django-channels": "^1.1.6",
"i18next": "^11.1.1",
"i18next-conv": "^6.0.0",
"i18next-fetch-backend": "^0.1.0",
"js-logger": "^1.3.0", "js-logger": "^1.3.0",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
@ -34,6 +31,7 @@
"semantic-ui-css": "^2.2.10", "semantic-ui-css": "^2.2.10",
"showdown": "^1.8.6", "showdown": "^1.8.6",
"vue": "^2.5.16", "vue": "^2.5.16",
"vue-gettext": "^2.0.31",
"vue-lazyload": "^1.1.4", "vue-lazyload": "^1.1.4",
"vue-masonry": "^0.10.16", "vue-masonry": "^0.10.16",
"vue-router": "^2.3.1", "vue-router": "^2.3.1",
@ -61,6 +59,7 @@
"cross-env": "^4.0.0", "cross-env": "^4.0.0",
"cross-spawn": "^5.0.1", "cross-spawn": "^5.0.1",
"css-loader": "^0.28.0", "css-loader": "^0.28.0",
"easygettext": "^2.5.0",
"es6-promise": "^4.2.2", "es6-promise": "^4.2.2",
"eslint": "^3.19.0", "eslint": "^3.19.0",
"eslint-config-standard": "^6.2.1", "eslint-config-standard": "^6.2.1",
@ -104,7 +103,6 @@
"sinon-chai": "^2.8.0", "sinon-chai": "^2.8.0",
"sinon-stub-promise": "^4.0.0", "sinon-stub-promise": "^4.0.0",
"url-loader": "^0.5.8", "url-loader": "^0.5.8",
"vue-i18n-xgettext": "^0.0.4",
"vue-loader": "^12.1.0", "vue-loader": "^12.1.0",
"vue-style-loader": "^3.0.1", "vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.3.3", "vue-template-compiler": "^2.3.3",

3
front/scripts/i18n-compile.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash -eux
locales=$(tail -n +2 src/locales.js | sed -e 's/export default //' | jq '.locales[].code' | xargs echo)
find locales -name '*.po' | xargs $(yarn bin gettext-extract)/gettext-compile --output src/translations.json

28
front/scripts/i18n-extract.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash -eux
locales=$(tail -n +2 src/locales.js | sed -e 's/export default //' | jq '.locales[].code' | xargs echo)
locales_dir="locales"
sources=$(find src -name '*.vue' -o -name '*.html' 2> /dev/null)
js_sources=$(find src -name '*.vue' -o -name '*.js')
touch $locales_dir/app.pot
# Create a main .pot template, then generate .po files for each available language.
# Extract gettext strings from templates files and create a POT dictionary template.
$(yarn bin gettext-extract)/gettext-extract --attribute v-translate --quiet --output $locales_dir/app.pot $sources
xgettext --language=JavaScript --keyword=npgettext:1c,2,3 \
--from-code=utf-8 --join-existing --no-wrap \
--package-name=$(node -e "console.log(require('./package.json').name);") \
--package-version=$(node -e "console.log(require('./package.json').version);") \
--output $locales_dir/app.pot $js_sources
# Fix broken files path/lines in pot
sed -e 's|#: src/|#: front/src/|' -i $locales_dir/app.pot
# Generate .po files for each available language.
echo $locales
for lang in $locales; do \
po_file=$locales_dir/$lang/LC_MESSAGES/app.po; \
echo "msgmerge --update $po_file "; \
mkdir -p $(dirname $po_file); \
[ -f $po_file ] && msgmerge --lang=$lang --update $po_file $locales_dir/app.pot || msginit --no-translator --locale=$lang --input=$locales_dir/app.pot --output-file=$po_file; \
msgattrib --no-wrap --no-obsolete -o $po_file $po_file; \
done;

View File

@ -2,14 +2,14 @@
<div id="app"> <div id="app">
<div class="ui main text container instance-chooser" v-if="!$store.state.instance.instanceUrl"> <div class="ui main text container instance-chooser" v-if="!$store.state.instance.instanceUrl">
<div class="ui padded segment"> <div class="ui padded segment">
<h1 class="ui header">{{ $t('Choose your instance') }}</h1> <h1 class="ui header">{{ $gettext('Choose your instance') }}</h1>
<form class="ui form" @submit.prevent="$store.dispatch('instance/setUrl', instanceUrl)"> <form class="ui form" @submit.prevent="$store.dispatch('instance/setUrl', instanceUrl)">
<p>{{ $t('You need to select an instance in order to continue') }}</p> <p>{{ $gettext('You need to select an instance in order to continue') }}</p>
<div class="ui action input"> <div class="ui action input">
<input type="text" v-model="instanceUrl"> <input type="text" v-model="instanceUrl">
<button type="submit" class="ui button">{{ $t('Submit') }}</button> <button type="submit" class="ui button">{{ $gettext('Submit') }}</button>
</div> </div>
<p>{{ $t('Suggested choices') }}</p> <p>{{ $gettext('Suggested choices') }}</p>
<div class="ui bulleted list"> <div class="ui bulleted list">
<div class="ui item" v-for="url in suggestedInstances"> <div class="ui item" v-for="url in suggestedInstances">
<a @click="instanceUrl = url">{{ url }}</a> <a @click="instanceUrl = url">{{ url }}</a>
@ -27,20 +27,20 @@
<div class="ui container"> <div class="ui container">
<div class="ui stackable equal height stackable grid"> <div class="ui stackable equal height stackable grid">
<div class="three wide column"> <div class="three wide column">
<i18next tag="h4" class="ui header" path="Links"></i18next> <h4 v-translate class="ui header">Links</h4>
<div class="ui link list"> <div class="ui link list">
<router-link class="item" to="/about"> <router-link class="item" to="/about">
<i18next path="About this instance" /> {{ $gettext('About this instance') }}
</router-link> </router-link>
<a href="https://funkwhale.audio" class="item" target="_blank">{{ $t('Official website') }}</a> <a href="https://funkwhale.audio" class="item" target="_blank">{{ $gettext('Official website') }}</a>
<a href="https://docs.funkwhale.audio" class="item" target="_blank">{{ $t('Documentation') }}</a> <a href="https://docs.funkwhale.audio" class="item" target="_blank">{{ $gettext('Documentation') }}</a>
<a href="https://code.eliotberriot.com/funkwhale/funkwhale" class="item" target="_blank"> <a href="https://code.eliotberriot.com/funkwhale/funkwhale" class="item" target="_blank">
<template v-if="version">{{ $t('Source code ({% version %})', {version: version}) }}</template> <translate :translate-params="{version: version}" v-if="version">Source code (%{version})</translate>
<template v-else>{{ $t('Source code') }}</template> <translate v-else>Source code</translate>
</a> </a>
<a href="https://code.eliotberriot.com/funkwhale/funkwhale/issues" class="item" target="_blank">{{ $t('Issue tracker') }}</a> <a href="https://code.eliotberriot.com/funkwhale/funkwhale/issues" class="item" target="_blank">{{ $gettext('Issue tracker') }}</a>
<a @click="switchInstance" class="item" > <a @click="switchInstance" class="item" >
{{ $t('Use another instance') }} {{ $gettext('Use another instance') }}
<template v-if="$store.state.instance.instanceUrl !== '/'"> <template v-if="$store.state.instance.instanceUrl !== '/'">
<br> <br>
({{ $store.state.instance.instanceUrl }}) ({{ $store.state.instance.instanceUrl }})
@ -49,14 +49,26 @@
</div> </div>
</div> </div>
<div class="ten wide column"> <div class="ten wide column">
<i18next tag="h4" class="ui header" path="About funkwhale" /> <h4 v-translate class="ui header">About Funkwhale</h4>
<p> <p>
<i18next path="Funkwhale is a free and open-source project run by volunteers. You can help us improve the platform by reporting bugs, suggesting features and share the project with your friends!"/> <translate>Funkwhale is a free and open-source project run by volunteers. You can help us improve the platform by reporting bugs, suggesting features and share the project with your friends!</translate>
</p> </p>
<p> <p>
<i18next path="The funkwhale logo was kindly designed and provided by Francis Gading."/> <translate>The funkwhale logo was kindly designed and provided by Francis Gading.</translate>
</p> </p>
</div> </div>
<div class="three wide column">
<h4 v-translate class="ui header">Options</h4>
<div class="ui form">
<div class="ui field">
<label>{{ $gettext('Change language') }}</label>
<select class="ui dropdown" v-model="$language.current">
<option v-for="(language, key) in $language.available" :value="key">{{ language }}</option>
</select>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -115,7 +127,7 @@ export default {
}) })
}, },
switchInstance () { switchInstance () {
let confirm = window.confirm(this.$t('This will erase your local data and disconnect you, do you want to continue?')) let confirm = window.confirm(this.$gettext('This will erase your local data and disconnect you, do you want to continue?'))
if (confirm) { if (confirm) {
this.$store.commit('instance/instanceUrl', null) this.$store.commit('instance/instanceUrl', null)
} }
@ -144,6 +156,9 @@ export default {
'$store.state.instance.instanceUrl' () { '$store.state.instance.instanceUrl' () {
this.$store.dispatch('instance/fetchSettings') this.$store.dispatch('instance/fetchSettings')
this.fetchNodeInfo() this.fetchNodeInfo()
},
'$language.current' (newValue) {
this.$store.commit('ui/currentLanguage', newValue)
} }
} }
} }

View File

@ -3,21 +3,23 @@
<div class="ui vertical center aligned stripe segment"> <div class="ui vertical center aligned stripe segment">
<div class="ui text container"> <div class="ui text container">
<h1 class="ui huge header"> <h1 class="ui huge header">
<template v-if="instance.name.value">{{ $t('About {%instance%}', { instance: instance.name.value }) }}</template> <template v-if="instance.name.value" :template-params="{instance: instance.name}">
<template v-else="instance.name.value">{{ $t('About this instance') }}</template> About %{ instance }
</template>
<template v-else="instance.name.value">{{ $gettext('About this instance') }}</template>
</h1> </h1>
<stats></stats> <stats></stats>
</div> </div>
</div> </div>
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<p v-if="!instance.short_description.value && !instance.long_description.value"> <p v-if="!instance.short_description.value && !instance.long_description.value">
{{ $t('Unfortunately, owners of this instance did not yet take the time to complete this page.') }} {{ $gettext('Unfortunately, owners of this instance did not yet take the time to complete this page.') }}
</p> </p>
<router-link <router-link
class="ui button" class="ui button"
v-if="$store.state.auth.availablePermissions['settings']" v-if="$store.state.auth.availablePermissions['settings']"
:to="{path: '/manage/settings', hash: 'instance'}"> :to="{path: '/manage/settings', hash: 'instance'}">
<i class="pencil icon"></i>{{ $t('Edit instance info') }} <i class="pencil icon"></i>{{ $gettext('Edit instance info') }}
</router-link> </router-link>
<div <div
v-if="instance.short_description.value" v-if="instance.short_description.value"

View File

@ -3,15 +3,15 @@
<div class="ui vertical center aligned stripe segment"> <div class="ui vertical center aligned stripe segment">
<div class="ui text container"> <div class="ui text container">
<h1 class="ui huge header"> <h1 class="ui huge header">
{{ $t('Welcome on Funkwhale') }} {{ $gettext('Welcome on Funkwhale') }}
</h1> </h1>
<p>{{ $t('We think listening to music should be simple.') }}</p> <p>{{ $gettext('We think listening to music should be simple.') }}</p>
<router-link class="ui icon button" to="/about"> <router-link class="ui icon button" to="/about">
<i class="info icon"></i> <i class="info icon"></i>
{{ $t('Learn more about this instance') }} {{ $gettext('Learn more about this instance') }}
</router-link> </router-link>
<router-link class="ui icon teal button" to="/library"> <router-link class="ui icon teal button" to="/library">
{{ $t('Get me to the library') }} {{ $gettext('Get me to the library') }}
<i class="right arrow icon"></i> <i class="right arrow icon"></i>
</router-link> </router-link>
</div> </div>
@ -22,9 +22,9 @@
<div class="row"> <div class="row">
<div class="eight wide left floated column"> <div class="eight wide left floated column">
<h2 class="ui header"> <h2 class="ui header">
{{ $t('Why funkwhale?') }} {{ $gettext('Why funkwhale?') }}
</h2> </h2>
<p>{{ $t('That\'s simple: we loved Grooveshark and we want to build something even better.') }}</p> <p>{{ $gettext('That\'s simple: we loved Grooveshark and we want to build something even better.') }}</p>
</div> </div>
<div class="four wide left floated column"> <div class="four wide left floated column">
<img class="ui medium image" src="../assets/logo/logo.png" /> <img class="ui medium image" src="../assets/logo/logo.png" />
@ -35,26 +35,26 @@
<div class="ui middle aligned stackable text container"> <div class="ui middle aligned stackable text container">
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<h2 class="ui header"> <h2 class="ui header">
{{ $t('Unlimited music') }} {{ $gettext('Unlimited music') }}
</h2> </h2>
<p>{{ $t('Funkwhale is designed to make it easy to listen to music you like, or to discover new artists.') }}</p> <p>{{ $gettext('Funkwhale is designed to make it easy to listen to music you like, or to discover new artists.') }}</p>
<div class="ui list"> <div class="ui list">
<div class="item"> <div class="item">
<i class="sound icon"></i> <i class="sound icon"></i>
<div class="content"> <div class="content">
{{ $t('Click once, listen for hours using built-in radios') }} {{ $gettext('Click once, listen for hours using built-in radios') }}
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<i class="heart icon"></i> <i class="heart icon"></i>
<div class="content"> <div class="content">
{{ $t('Keep a track of your favorite songs') }} {{ $gettext('Keep a track of your favorite songs') }}
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<i class="list icon"></i> <i class="list icon"></i>
<div class="content"> <div class="content">
{{ $t('Playlists? We got them') }} {{ $gettext('Playlists? We got them') }}
</div> </div>
</div> </div>
</div> </div>
@ -62,28 +62,31 @@
<div class="ui middle aligned stackable text container"> <div class="ui middle aligned stackable text container">
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<h2 class="ui header"> <h2 class="ui header">
{{ $t('Clean library') }} {{ $gettext('Clean library') }}
</h2> </h2>
<p>{{ $t('Funkwhale takes care of handling your music') }}.</p> <p>{{ $gettext('Funkwhale takes care of handling your music') }}.</p>
<div class="ui list"> <div class="ui list">
<div class="item"> <div class="item">
<i class="download icon"></i> <i class="download icon"></i>
<div class="content"> <div class="content">
{{ $t('Import music from various platforms, such as YouTube or SoundCloud') }} {{ $gettext('Import music from various platforms, such as YouTube or SoundCloud') }}
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<i class="tag icon"></i> <i class="tag icon"></i>
<div class="content"> <div class="content">
<i18next path="Get quality metadata about your music thanks to {%0%}"> <template v-translate>
<a href="https://musicbrainz.org" target="_blank">{{ $t('MusicBrainz') }}</a> Get quality metadata about your music thanks to
</i18next> <a href="https://musicbrainz.org" target="_blank">
MusicBrainz
</a>
</template>
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<i class="plus icon"></i> <i class="plus icon"></i>
<div class="content"> <div class="content">
{{ $t('Covers, lyrics, our goal is to have them all ;)') }} {{ $gettext('Covers, lyrics, our goal is to have them all ;)') }}
</div> </div>
</div> </div>
</div> </div>
@ -91,20 +94,20 @@
<div class="ui middle aligned stackable text container"> <div class="ui middle aligned stackable text container">
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<h2 class="ui header"> <h2 class="ui header">
{{ $t('Easy to use') }} {{ $gettext('Easy to use') }}
</h2> </h2>
<p>{{ $t('Funkwhale is dead simple to use.') }}</p> <p>{{ $gettext('Funkwhale is dead simple to use.') }}</p>
<div class="ui list"> <div class="ui list">
<div class="item"> <div class="item">
<i class="book icon"></i> <i class="book icon"></i>
<div class="content"> <div class="content">
{{ $t('No add-ons, no plugins : you only need a web library') }} {{ $gettext('No add-ons, no plugins : you only need a web library') }}
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<i class="wizard icon"></i> <i class="wizard icon"></i>
<div class="content"> <div class="content">
{{ $t('Access your music from a clean interface that focus on what really matters') }} {{ $gettext('Access your music from a clean interface that focus on what really matters') }}
</div> </div>
</div> </div>
</div> </div>
@ -112,26 +115,26 @@
<div class="ui middle aligned stackable text container"> <div class="ui middle aligned stackable text container">
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<h2 class="ui header"> <h2 class="ui header">
{{ $t('Your music, your way') }} {{ $gettext('Your music, your way') }}
</h2> </h2>
<p>{{ $t('Funkwhale is free and gives you control on your music.') }}</p> <p>{{ $gettext('Funkwhale is free and gives you control on your music.') }}</p>
<div class="ui list"> <div class="ui list">
<div class="item"> <div class="item">
<i class="smile icon"></i> <i class="smile icon"></i>
<div class="content"> <div class="content">
{{ $t('The plaform is free and open-source, you can install it and modify it without worries') }} {{ $gettext('The plaform is free and open-source, you can install it and modify it without worries') }}
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<i class="protect icon"></i> <i class="protect icon"></i>
<div class="content"> <div class="content">
{{ $t('We do not track you or bother you with ads') }} {{ $gettext('We do not track you or bother you with ads') }}
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<i class="users icon"></i> <i class="users icon"></i>
<div class="content"> <div class="content">
{{ $t('You can invite friends and family to your instance so they can enjoy your music') }} {{ $gettext('You can invite friends and family to your instance so they can enjoy your music') }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,13 +5,14 @@
<h1 class="ui huge header"> <h1 class="ui huge header">
<i class="warning icon"></i> <i class="warning icon"></i>
<div class="content"> <div class="content">
<strike>{{ $t('Whale') }}</strike> {{ $t('Page not found!') }} <strike>{{ $gettext('Whale') }}</strike> {{ $gettext('Page not found!') }}
</div> </div>
</h1> </h1>
<p>{{ $t('We\'re sorry, the page you asked for does not exists.') }}</p> <p>{{ $gettext('We\'re sorry, the page you asked for does not exist:') }}</p>
<i18next path="Requested URL: {%0%}"><a :href="path">{{ path }}</a></i18next> <a :href="path">{{ path }}</a>
<div class="ui hidden divider"></div>
<router-link class="ui icon button" to="/"> <router-link class="ui icon button" to="/">
{{ $t('Go to home page') }} {{ $gettext('Go to home page') }}
<i class="right arrow icon"></i> <i class="right arrow icon"></i>
</router-link> </router-link>
</div> </div>

View File

@ -18,13 +18,13 @@
<div class="ui compact fluid two item inverted menu"> <div class="ui compact fluid two item inverted menu">
<a class="active item" @click="selectedTab = 'library'" data-tab="library">Browse</a> <a class="active item" @click="selectedTab = 'library'" data-tab="library">Browse</a>
<a class="item" @click="selectedTab = 'queue'" data-tab="queue"> <a class="item" @click="selectedTab = 'queue'" data-tab="queue">
{{ $t('Queue') }} {{ $gettext('Queue') }}&nbsp;
<template v-if="queue.tracks.length === 0"> <template v-if="queue.tracks.length === 0">
{{ $t('(empty)') }} {{ $gettext('(empty)') }}
</template>
<template v-else>
{{ $t('({%index%} of {%length%})', { index: queue.currentIndex + 1, length: queue.tracks.length }) }}
</template> </template>
<translate v-else :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}">
(%{ index } of %{ length })
</translate>
</a> </a>
</div> </div>
</div> </div>
@ -32,40 +32,45 @@
<div class="ui bottom attached active tab" data-tab="library"> <div class="ui bottom attached active tab" data-tab="library">
<div class="ui inverted vertical large fluid menu"> <div class="ui inverted vertical large fluid menu">
<div class="item"> <div class="item">
<div class="header">{{ $t('My account') }}</div> <div class="header">{{ $gettext('My account') }}</div>
<div class="menu"> <div class="menu">
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'profile', params: {username: $store.state.auth.username}}"><i class="user icon"></i>{{ $t('Logged in as {%name%}', { name: $store.state.auth.username }) }}</router-link> <router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'profile', params: {username: $store.state.auth.username}}">
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon"></i>{{ $t('Logout') }}</router-link> <i class="user icon"></i>
<router-link class="item" v-else :to="{name: 'login'}"><i class="sign in icon"></i>{{ $t('Login') }}</router-link> <translate :translate-params="{username: $store.state.auth.username}">
Logged in as %{ username }
</translate>
</router-link>
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{name: 'logout'}"><i class="sign out icon"></i>{{ $gettext('Logout') }}</router-link>
<router-link class="item" v-else :to="{name: 'login'}"><i class="sign in icon"></i>{{ $gettext('Login') }}</router-link>
</div> </div>
</div> </div>
<div class="item"> <div class="item">
<div class="header">{{ $t('Music') }}</div> <div class="header">{{ $gettext('Music') }}</div>
<div class="menu"> <div class="menu">
<router-link class="item" :to="{path: '/library'}"><i class="sound icon"> </i>{{ $t('Browse library') }}</router-link> <router-link class="item" :to="{path: '/library'}"><i class="sound icon"> </i>{{ $gettext('Browse library') }}</router-link>
<router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/favorites'}"><i class="heart icon"></i>{{ $t('Favorites') }}</router-link> <router-link class="item" v-if="$store.state.auth.authenticated" :to="{path: '/favorites'}"><i class="heart icon"></i>{{ $gettext('Favorites') }}</router-link>
<a <a
@click="$store.commit('playlists/chooseTrack', null)" @click="$store.commit('playlists/chooseTrack', null)"
v-if="$store.state.auth.authenticated" v-if="$store.state.auth.authenticated"
class="item"> class="item">
<i class="list icon"></i>{{ $t('Playlists') }} <i class="list icon"></i>{{ $gettext('Playlists') }}
</a> </a>
<router-link <router-link
v-if="$store.state.auth.authenticated" v-if="$store.state.auth.authenticated"
class="item" :to="{path: '/activity'}"><i class="bell icon"></i>{{ $t('Activity') }}</router-link> class="item" :to="{path: '/activity'}"><i class="bell icon"></i>{{ $gettext('Activity') }}</router-link>
</div> </div>
</div> </div>
<div class="item" v-if="showAdmin"> <div class="item" v-if="showAdmin">
<div class="header">{{ $t('Administration') }}</div> <div class="header">{{ $gettext('Administration') }}</div>
<div class="menu"> <div class="menu">
<router-link <router-link
class="item" class="item"
v-if="$store.state.auth.availablePermissions['library']" v-if="$store.state.auth.availablePermissions['library']"
:to="{name: 'manage.library.files'}"> :to="{name: 'manage.library.files'}">
<i class="book icon"></i>{{ $t('Library') }} <i class="book icon"></i>{{ $gettext('Library') }}
<div <div
:class="['ui', {'teal': $store.state.ui.notifications.importRequests > 0}, 'label']" :class="['ui', {'teal': $store.state.ui.notifications.importRequests > 0}, 'label']"
:title="$t('Pending import requests')"> :title="$gettext('Pending import requests')">
{{ $store.state.ui.notifications.importRequests }}</div> {{ $store.state.ui.notifications.importRequests }}</div>
</router-link> </router-link>
@ -73,29 +78,29 @@
class="item" class="item"
v-else-if="$store.state.auth.availablePermissions['upload']" v-else-if="$store.state.auth.availablePermissions['upload']"
to="/library/import/launch"> to="/library/import/launch">
<i class="download icon"></i>{{ $t('Import music') }} <i class="download icon"></i>{{ $gettext('Import music') }}
</router-link> </router-link>
<router-link <router-link
class="item" class="item"
v-if="$store.state.auth.availablePermissions['federation']" v-if="$store.state.auth.availablePermissions['federation']"
:to="{path: '/manage/federation/libraries'}"> :to="{path: '/manage/federation/libraries'}">
<i class="sitemap icon"></i>{{ $t('Federation') }} <i class="sitemap icon"></i>{{ $gettext('Federation') }}
<div <div
:class="['ui', {'teal': $store.state.ui.notifications.federation > 0}, 'label']" :class="['ui', {'teal': $store.state.ui.notifications.federation > 0}, 'label']"
:title="$t('Pending follow requests')"> :title="$gettext('Pending follow requests')">
{{ $store.state.ui.notifications.federation }}</div> {{ $store.state.ui.notifications.federation }}</div>
</router-link> </router-link>
<router-link <router-link
class="item" class="item"
v-if="$store.state.auth.availablePermissions['settings']" v-if="$store.state.auth.availablePermissions['settings']"
:to="{path: '/manage/settings'}"> :to="{path: '/manage/settings'}">
<i class="settings icon"></i>{{ $t('Settings') }} <i class="settings icon"></i>{{ $gettext('Settings') }}
</router-link> </router-link>
<router-link <router-link
class="item" class="item"
v-if="$store.state.auth.availablePermissions['settings']" v-if="$store.state.auth.availablePermissions['settings']"
:to="{name: 'manage.users.users.list'}"> :to="{name: 'manage.users.users.list'}">
<i class="users icon"></i>{{ $t('Users') }} <i class="users icon"></i>{{ $gettext('Users') }}
</router-link> </router-link>
</div> </div>
</div> </div>
@ -105,12 +110,19 @@
<i class="history icon"></i> <i class="history icon"></i>
<div class="content"> <div class="content">
<div class="header"> <div class="header">
{{ $t('Do you want to restore your previous queue?') }} {{ $gettext('Do you want to restore your previous queue?') }}
</div> </div>
<p>{{ $t('{%count%} tracks', { count: queue.previousQueue.tracks.length }) }}</p> <p>
<translate
translate-plural="%{ count } tracks"
:translate-n="queue.previousQueue.tracks.length"
:translate-params="{count: queue.previousQueue.tracks.length}">
%{ count } track
</translate>
</p>
<div class="ui two buttons"> <div class="ui two buttons">
<div @click="queue.restore()" class="ui basic inverted green button">{{ $t('Yes') }}</div> <div @click="queue.restore()" class="ui basic inverted green button">{{ $gettext('Yes') }}</div>
<div @click="queue.removePrevious()" class="ui basic inverted red button">{{ $t('No') }}</div> <div @click="queue.removePrevious()" class="ui basic inverted red button">{{ $gettext('No') }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -141,10 +153,10 @@
<div v-if="$store.state.radios.running" class="ui black message"> <div v-if="$store.state.radios.running" class="ui black message">
<div class="content"> <div class="content">
<div class="header"> <div class="header">
<i class="feed icon"></i> {{ $t('You have a radio playing') }} <i class="feed icon"></i> {{ $gettext('You have a radio playing') }}
</div> </div>
<p>{{ $t('New tracks will be appended here automatically.') }}</p> <p>{{ $gettext('New tracks will be appended here automatically.') }}</p>
<div @click="$store.dispatch('radios/stop')" class="ui basic inverted red button">{{ $t('Stop radio') }}</div> <div @click="$store.dispatch('radios/stop')" class="ui basic inverted red button">{{ $gettext('Stop radio') }}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,20 +5,12 @@
</div> </div>
<div class="content"> <div class="content">
<div class="summary"> <div class="summary">
<i18next path="{%0%} favorited a track"> <translate :translate-params="{user: event.actor.local_id}">%{ user } favorited a track</translate>
<username class="user" :username="event.actor.local_id" />
</i18next>
<human-date class="date" :date="event.published" /> <human-date class="date" :date="event.published" />
</div> </div>
<div class="extra text"> <div class="extra text">
<router-link :to="{name: 'library.tracks.detail', params: {id: event.object.local_id }}">{{ event.object.name }}</router-link> <router-link :to="{name: 'library.tracks.detail', params: {id: event.object.local_id }}">{{ event.object.name }}</router-link>
<i18next path="from album {%0%}, by {%1%}" v-if="event.object.album"> <translate :translate-params="{album: event.object.album, artist: event.object.artist}">from %{ album } by %{ artist }</translate>
{{ event.object.album }}
<em>{{ event.object.artist }}</em>
</i18next>
<i18next path=", by {%0%}" v-else>
<em>{{ event.object.artist }}</em>
</i18next>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,20 +5,12 @@
</div> </div>
<div class="content"> <div class="content">
<div class="summary"> <div class="summary">
<i18next path="{%0%} listened to a track"> <translate :translate-params="{user: event.actor.local_id}">%{ user } listened to a track</translate>
<username class="user" :username="event.actor.local_id" />
</i18next>
<human-date class="date" :date="event.published" /> <human-date class="date" :date="event.published" />
</div> </div>
<div class="extra text"> <div class="extra text">
<router-link :to="{name: 'library.tracks.detail', params: {id: event.object.local_id }}">{{ event.object.name }}</router-link> <router-link :to="{name: 'library.tracks.detail', params: {id: event.object.local_id }}">{{ event.object.name }}</router-link>
<i18next path="from album {%0%}, by {%1%}" v-if="event.object.album"> <translate :translate-params="{album: event.object.album, artist: event.object.artist}">from %{ album } by %{ artist }</translate>
{{ event.object.album }}<em>{{ event.object.artist }}</em>
</i18next>
<i18next path=", by {%0%}" v-else>
<em>{{ event.object.artist }}</em>
</i18next>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,13 +3,13 @@
<div class="ui divider" /> <div class="ui divider" />
<h3 class="ui header">{{ group.label }}</h3> <h3 class="ui header">{{ group.label }}</h3>
<div v-if="errors.length > 0" class="ui negative message"> <div v-if="errors.length > 0" class="ui negative message">
<div class="header">{{ $t('Error while saving settings') }}</div> <div class="header">{{ $gettext('Error while saving settings') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
</ul> </ul>
</div> </div>
<div v-if="result" class="ui positive message"> <div v-if="result" class="ui positive message">
{{ $t('Settings updated successfully.') }} {{ $gettext('Settings updated successfully.') }}
</div> </div>
<p v-if="group.help">{{ group.help }}</p> <p v-if="group.help">{{ group.help }}</p>
<div v-for="setting in settings" class="ui field"> <div v-for="setting in settings" class="ui field">
@ -61,7 +61,7 @@
<button <button
type="submit" type="submit"
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']"> :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']">
{{ $t('Save') }} {{ $gettext('Save') }}
</button> </button>
</form> </form>
</template> </template>

View File

@ -1,19 +1,19 @@
<template> <template>
<div :title="title" :class="['ui', {'tiny': discrete}, 'buttons']"> <div :title="title" :class="['ui', {'tiny': discrete}, 'buttons']">
<button <button
:title="$t('Add to current queue')" :title="$gettext('Add to current queue')"
@click="addNext(true)" @click="addNext(true)"
:disabled="!playable" :disabled="!playable"
:class="['ui', {loading: isLoading}, {'mini': discrete}, {disabled: !playable}, 'button']"> :class="['ui', {loading: isLoading}, {'mini': discrete}, {disabled: !playable}, 'button']">
<i class="ui play icon"></i> <i class="ui play icon"></i>
<template v-if="!discrete"><slot><i18next path="Play"/></slot></template> <template v-if="!discrete"><slot>{{ $gettext('Play') }}</slot></template>
</button> </button>
<div v-if="!discrete" :class="['ui', {disabled: !playable}, 'floating', 'dropdown', 'icon', 'button']"> <div v-if="!discrete" :class="['ui', {disabled: !playable}, 'floating', 'dropdown', 'icon', 'button']">
<i class="dropdown icon"></i> <i class="dropdown icon"></i>
<div class="menu"> <div class="menu">
<div class="item" :disabled="!playable" @click="add"><i class="plus icon"></i><i18next path="Add to queue"/></div> <div class="item" :disabled="!playable" @click="add"><i class="plus icon"></i>{{ $gettext('Add to queue') }}</div>
<div class="item" :disabled="!playable" @click="addNext()"><i class="step forward icon"></i><i18next path="Play next"/></div> <div class="item" :disabled="!playable" @click="addNext()"><i class="step forward icon"></i>{{ $gettext('Play next') }}</div>
<div class="item" :disabled="!playable" @click="addNext(true)"><i class="arrow down icon"></i><i18next path="Play now"/></div> <div class="item" :disabled="!playable" @click="addNext(true)"><i class="arrow down icon"></i>{{ $gettext('Play now') }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -44,10 +44,10 @@ export default {
computed: { computed: {
title () { title () {
if (this.playable) { if (this.playable) {
return this.$t('Play immediatly') return this.$gettext('Play immediatly')
} else { } else {
if (this.track) { if (this.track) {
return this.$t('This track is not imported and cannot be played') return this.$gettext('This track is not imported and cannot be played')
} }
} }
}, },
@ -142,8 +142,9 @@ export default {
if (tracks.length < 1) { if (tracks.length < 1) {
return return
} }
let msg = this.$ngettext('%{ count } track was added to your queue', '%{ count } tracks were added to your queue', tracks.length)
this.$store.commit('ui/addMessage', { this.$store.commit('ui/addMessage', {
content: this.$t('{% tracks %} tracks were added to your queue.', {tracks: tracks.length}), content: this.$gettextInterpolate(msg, {count: tracks.length}),
date: new Date() date: new Date()
}) })
} }

View File

@ -57,44 +57,44 @@
<div class="two wide column controls ui grid"> <div class="two wide column controls ui grid">
<div <div
:title="$t('Previous track')" :title="$gettext('Previous track')"
class="two wide column control" class="two wide column control"
:disabled="emptyQueue"> :disabled="emptyQueue">
<i @click="previous" :class="['ui', 'backward', {'disabled': emptyQueue}, 'big', 'icon']"></i> <i @click="previous" :class="['ui', 'backward', {'disabled': emptyQueue}, 'big', 'icon']"></i>
</div> </div>
<div <div
v-if="!playing" v-if="!playing"
:title="$t('Play track')" :title="$gettext('Play track')"
class="two wide column control"> class="two wide column control">
<i @click="togglePlay" :class="['ui', 'play', {'disabled': !currentTrack}, 'big', 'icon']"></i> <i @click="togglePlay" :class="['ui', 'play', {'disabled': !currentTrack}, 'big', 'icon']"></i>
</div> </div>
<div <div
v-else v-else
:title="$t('Pause track')" :title="$gettext('Pause track')"
class="two wide column control"> class="two wide column control">
<i @click="togglePlay" :class="['ui', 'pause', {'disabled': !currentTrack}, 'big', 'icon']"></i> <i @click="togglePlay" :class="['ui', 'pause', {'disabled': !currentTrack}, 'big', 'icon']"></i>
</div> </div>
<div <div
:title="$t('Next track')" :title="$gettext('Next track')"
class="two wide column control" class="two wide column control"
:disabled="!hasNext"> :disabled="!hasNext">
<i @click="next" :class="['ui', {'disabled': !hasNext}, 'step', 'forward', 'big', 'icon']" ></i> <i @click="next" :class="['ui', {'disabled': !hasNext}, 'step', 'forward', 'big', 'icon']" ></i>
</div> </div>
<div class="two wide column control volume-control"> <div class="two wide column control volume-control">
<i :title="$t('Unmute')" @click="$store.commit('player/volume', 1)" v-if="volume === 0" class="volume off secondary icon"></i> <i :title="$gettext('Unmute')" @click="$store.commit('player/volume', 1)" v-if="volume === 0" class="volume off secondary icon"></i>
<i :title="$t('Mute')" @click="$store.commit('player/volume', 0)" v-else-if="volume < 0.5" class="volume down secondary icon"></i> <i :title="$gettext('Mute')" @click="$store.commit('player/volume', 0)" v-else-if="volume < 0.5" class="volume down secondary icon"></i>
<i :title="$t('Mute')" @click="$store.commit('player/volume', 0)" v-else class="volume up secondary icon"></i> <i :title="$gettext('Mute')" @click="$store.commit('player/volume', 0)" v-else class="volume up secondary icon"></i>
<input type="range" step="0.05" min="0" max="1" v-model="sliderVolume" /> <input type="range" step="0.05" min="0" max="1" v-model="sliderVolume" />
</div> </div>
<div class="two wide column control looping"> <div class="two wide column control looping">
<i <i
:title="$t('Looping disabled. Click to switch to single-track looping.')" :title="$gettext('Looping disabled. Click to switch to single-track looping.')"
v-if="looping === 0" v-if="looping === 0"
@click="$store.commit('player/looping', 1)" @click="$store.commit('player/looping', 1)"
:disabled="!currentTrack" :disabled="!currentTrack"
:class="['ui', {'disabled': !currentTrack}, 'step', 'repeat', 'secondary', 'icon']"></i> :class="['ui', {'disabled': !currentTrack}, 'step', 'repeat', 'secondary', 'icon']"></i>
<i <i
:title="$t('Looping on a single track. Click to switch to whole queue looping.')" :title="$gettext('Looping on a single track. Click to switch to whole queue looping.')"
v-if="looping === 1" v-if="looping === 1"
@click="$store.commit('player/looping', 2)" @click="$store.commit('player/looping', 2)"
:disabled="!currentTrack" :disabled="!currentTrack"
@ -102,7 +102,7 @@
<span class="ui circular tiny orange label">1</span> <span class="ui circular tiny orange label">1</span>
</i> </i>
<i <i
:title="$t('Looping on whole queue. Click to disable looping.')" :title="$gettext('Looping on whole queue. Click to disable looping.')"
v-if="looping === 2" v-if="looping === 2"
@click="$store.commit('player/looping', 0)" @click="$store.commit('player/looping', 0)"
:disabled="!currentTrack" :disabled="!currentTrack"
@ -111,7 +111,7 @@
</div> </div>
<div <div
:disabled="queue.tracks.length === 0" :disabled="queue.tracks.length === 0"
:title="$t('Shuffle your queue')" :title="$gettext('Shuffle your queue')"
class="two wide column control"> class="two wide column control">
<div v-if="isShuffling" class="ui inline shuffling inverted small active loader"></div> <div v-if="isShuffling" class="ui inline shuffling inverted small active loader"></div>
<i v-else @click="shuffle()" :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> <i v-else @click="shuffle()" :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
@ -119,7 +119,7 @@
<div class="one wide column"></div> <div class="one wide column"></div>
<div <div
:disabled="queue.tracks.length === 0" :disabled="queue.tracks.length === 0"
:title="$t('Clear your queue')" :title="$gettext('Clear your queue')"
class="two wide column control"> class="two wide column control">
<i @click="clean()" :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i> <i @click="clean()" :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
</div> </div>
@ -180,12 +180,13 @@ export default {
return return
} }
let self = this let self = this
let msg = this.$gettext('Queue shuffled!')
this.isShuffling = true this.isShuffling = true
setTimeout(() => { setTimeout(() => {
self.$store.dispatch('queue/shuffle', () => { self.$store.dispatch('queue/shuffle', () => {
self.isShuffling = false self.isShuffling = false
self.$store.commit('ui/addMessage', { self.$store.commit('ui/addMessage', {
content: self.$t('Queue shuffled!'), content: msg,
date: new Date() date: new Date()
}) })
}) })

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<h2><i18next path="Search for some music"/></h2> <h2>{{ $gettext('Search for some music') }}</h2>
<div :class="['ui', {'loading': isLoading }, 'search']"> <div :class="['ui', {'loading': isLoading }, 'search']">
<div class="ui icon big input"> <div class="ui icon big input">
<i class="search icon"></i> <i class="search icon"></i>
@ -8,22 +8,22 @@
</div> </div>
</div> </div>
<template v-if="query.length > 0"> <template v-if="query.length > 0">
<h3 class="ui title"><i18next path="Artists"/></h3> <h3 class="ui title">{{ $gettext('Artists') }}</h3>
<div v-if="results.artists.length > 0" class="ui stackable three column grid"> <div v-if="results.artists.length > 0" class="ui stackable three column grid">
<div class="column" :key="artist.id" v-for="artist in results.artists"> <div class="column" :key="artist.id" v-for="artist in results.artists">
<artist-card class="fluid" :artist="artist" ></artist-card> <artist-card class="fluid" :artist="artist" ></artist-card>
</div> </div>
</div> </div>
<p v-else><i18next path="Sorry, we did not found any artist matching your query"/></p> <p v-else>{{ $gettext('Sorry, we did not found any artist matching your query') }}</p>
</template> </template>
<template v-if="query.length > 0"> <template v-if="query.length > 0">
<h3 class="ui title"><i18next path="Albums"/></h3> <h3 class="ui title">{{ $gettext('Albums') }}</h3>
<div v-if="results.albums.length > 0" class="ui stackable three column grid"> <div v-if="results.albums.length > 0" class="ui stackable three column grid">
<div class="column" :key="album.id" v-for="album in results.albums"> <div class="column" :key="album.id" v-for="album in results.albums">
<album-card class="fluid" :album="album" ></album-card> <album-card class="fluid" :album="album" ></album-card>
</div> </div>
</div> </div>
<p v-else><i18next path="Sorry, we did not found any album matching your query"/></p> <p v-else>{{ $gettext('Sorry, we did not found any album matching your query') }}</p>
</template> </template>
</div> </div>
</template> </template>

View File

@ -10,10 +10,9 @@
</div> </div>
<div class="meta"> <div class="meta">
<span> <span>
<i18next path="By {%0%}">
<router-link tag="span" :to="{name: 'library.artists.detail', params: {id: album.artist.id }}"> <router-link tag="span" :to="{name: 'library.artists.detail', params: {id: album.artist.id }}">
{{ album.artist.name }}</router-link> <translate :translate-params="{artist: album.artist.name}">By %{ artist }</translate>
</i18next> </router-link>
</span><span class="time" v-if="album.release_date"> {{ album.release_date | year }}</span> </span><span class="time" v-if="album.release_date"> {{ album.release_date | year }}</span>
</div> </div>
<div class="description" v-if="mode === 'rich'"> <div class="description" v-if="mode === 'rich'">
@ -39,23 +38,21 @@
</table> </table>
<div class="center aligned segment" v-if="album.tracks.length > initialTracks"> <div class="center aligned segment" v-if="album.tracks.length > initialTracks">
<em v-if="!showAllTracks" @click="showAllTracks = true" class="expand"> <em v-if="!showAllTracks" @click="showAllTracks = true" class="expand">
<i18next path="Show {%0%} more tracks">{{ album.tracks.length - initialTracks }}</i18next> <translate :translate-params="{count: album.tracks.length - initialTracks}" :translate-n="album.tracks.length - initialTracks" translate-plural="Show %{ count } more tracks">Show 1 more track</translate>
</em> </em>
<em v-else @click="showAllTracks = false" class="expand"> <em v-else @click="showAllTracks = false" class="expand">
<i18next path="Collapse" /> {{ $gettext('Collapse') }}
</em> </em>
</div> </div>
</div> </div>
</div> </div>
<div class="extra content"> <div class="extra content">
<play-button class="mini basic orange right floated" :tracks="album.tracks"> <play-button class="mini basic orange right floated" :tracks="album.tracks">
<i18next path="Play all"/> {{ $gettext('Play all') }}
</play-button> </play-button>
<span> <span>
<i class="music icon"></i> <i class="music icon"></i>
<i18next path="{%0%} tracks"> <translate :translate-params="{count: album.tracks.length}" :translate-n="album.tracks.length" translate-plural="%{ count } tracks">1 track</translate>
{{ album.tracks.length }}
</i18next>
</span> </span>
</div> </div>
</div> </div>

View File

@ -28,12 +28,10 @@
</table> </table>
<div class="center aligned segment" v-if="artist.albums.length > initialAlbums"> <div class="center aligned segment" v-if="artist.albums.length > initialAlbums">
<em v-if="!showAllAlbums" @click="showAllAlbums = true" class="expand"> <em v-if="!showAllAlbums" @click="showAllAlbums = true" class="expand">
<i18next path="Show {%0%} more albums"> <translate :translate-params="{count: artist.albums.length - initialAlbums}" :translate-n="artist.albums.length - initialAlbums" translate-plural="Show %{ count } more albums">Show 1 more album</translate>
{{ artist.albums.length - initialAlbums }}
</i18next>
</em> </em>
<em v-else @click="showAllAlbums = false" class="expand"> <em v-else @click="showAllAlbums = false" class="expand">
<i18next path="Collapse"/> {{ $gettext('Collapse') }}
</em> </em>
</div> </div>
</div> </div>
@ -41,12 +39,10 @@
<div class="extra content"> <div class="extra content">
<span> <span>
<i class="sound icon"></i> <i class="sound icon"></i>
<i18next path="{%0%} albums"> <translate :translate-params="{count: artist.albums.length}" :translate-n="artist.albums.length" translate-plural="%{ count } albums">1 album</translate>
{{ artist.albums.length }}
</i18next>
</span> </span>
<play-button class="mini basic orange right floated" :artist="artist.id"> <play-button class="mini basic orange right floated" :artist="artist.id">
<i18next path="Play all"/> {{ $gettext('Play all') }}
</play-button> </play-button>
</div> </div>
</div> </div>

View File

@ -4,9 +4,9 @@
<tr> <tr>
<th></th> <th></th>
<th></th> <th></th>
<i18next tag="th" colspan="6" path="Title"/> <th colspan="6">{{ $gettext('Title') }}</th>
<i18next tag="th" colspan="6" path="Artist"/> <th colspan="6">{{ $gettext('Artist') }}</th>
<i18next tag="th" colspan="6" path="Album"/> <th colspan="6">{{ $gettext('Album') }}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -21,17 +21,17 @@
<tr> <tr>
<th colspan="3"> <th colspan="3">
<button @click="showDownloadModal = !showDownloadModal" class="ui basic button"> <button @click="showDownloadModal = !showDownloadModal" class="ui basic button">
<i18next path="Download..."/> {{ $gettext('Download') }}
</button> </button>
<modal :show.sync="showDownloadModal"> <modal :show.sync="showDownloadModal">
<i18next tag="div" path="Download tracks" class="header" /> <div class="header">{{ $gettext('Download tracks') }}</div>
<div class="content"> <div class="content">
<div class="description"> <div class="description">
<i18next tag="p" path="There is currently no way to download directly multiple tracks from funkwhale as a ZIP archive. However, you can use a command line tools such as {%0%} to easily download a list of tracks."> <p>{{ $gettext('There is currently no way to download directly multiple tracks from funkwhale as a ZIP archive. However, you can use a command line tools such as cURL to easily download a list of tracks.') }}</p>
<a href="https://curl.haxx.se/" target="_blank">cURL</a> {{ $gettext('Simply copy paste the snippet below into a terminal to launch the download.') }}
</i18next> <div class="ui warning message">
<i18next path="Simply copy paste the snippet below into a terminal to launch the download."/> {{ $gettext('Keep your PRIVATE_TOKEN secret as it gives access to your account.') }}
<i18next tag="div" class="ui warning message" path="Keep your PRIVATE_TOKEN secret as it gives access to your account."/> </div>
<pre> <pre>
export PRIVATE_TOKEN="{{ $store.state.auth.token }}" export PRIVATE_TOKEN="{{ $store.state.auth.token }}"
<template v-for="track in tracks"><template v-if="track.files.length > 0"> <template v-for="track in tracks"><template v-if="track.files.length > 0">
@ -40,7 +40,7 @@ curl -G -o "{{ track.files[0].filename }}" <template v-if="$store.state.auth.aut
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<i18next tag="div" class="ui black deny button" path="Cancel" /> <div class="ui black deny button">{{ $gettext('Cancel') }}</div>
</div> </div>
</modal> </modal>
</th> </th>

View File

@ -2,20 +2,20 @@
<div class="main pusher" v-title="'Log In'"> <div class="main pusher" v-title="'Log In'">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<div class="ui small text container"> <div class="ui small text container">
<h2><i18next path="Log in to your Funkwhale account"/></h2> <h2>{{ $gettext('Log in to your Funkwhale account') }}</h2>
<form class="ui form" @submit.prevent="submit()"> <form class="ui form" @submit.prevent="submit()">
<div v-if="error" class="ui negative message"> <div v-if="error" class="ui negative message">
<div class="header"><i18next path="We cannot log you in"/></div> <div class="header">{{ $gettext('We cannot log you in') }}</div>
<ul class="list"> <ul class="list">
<i18next tag="li" v-if="error == 'invalid_credentials'" path="Please double-check your username/password couple is correct"/> <li v-if="error == 'invalid_credentials'">{{ $gettext('Please double-check your username/password couple is correct') }}</li>
<i18next tag="li" v-else path="An unknown error happend, this can mean the server is down or cannot be reached"/> <li v-else>{{ $gettext('An unknown error happend, this can mean the server is down or cannot be reached') }}</li>
</ul> </ul>
</div> </div>
<div class="field"> <div class="field">
<label> <label>
{{ $t('Username or email') }} | {{ $gettext('Username or email') }} |
<router-link :to="{path: '/signup'}"> <router-link :to="{path: '/signup'}">
{{ $t('Create an account') }} {{ $gettext('Create an account') }}
</router-link> </router-link>
</label> </label>
<input <input
@ -30,15 +30,17 @@
</div> </div>
<div class="field"> <div class="field">
<label> <label>
{{ $t('Password') }} | {{ $gettext('Password') }} |
<router-link :to="{name: 'auth.password-reset', query: {email: credentials.username}}"> <router-link :to="{name: 'auth.password-reset', query: {email: credentials.username}}">
{{ $t('Reset your password') }} {{ $gettext('Reset your password') }}
</router-link> </router-link>
</label> </label>
<password-input :index="2" required v-model="credentials.password" /> <password-input :index="2" required v-model="credentials.password" />
</div> </div>
<button tabindex="3" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"><i18next path="Login"/></button> <button tabindex="3" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit">
{{ $gettext('Login') }}
</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -2,10 +2,11 @@
<div class="main pusher" v-title="'Log Out'"> <div class="main pusher" v-title="'Log Out'">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<div class="ui small text container"> <div class="ui small text container">
<h2><i18next path="Are you sure you want to log out?"/></h2> <h2>
<i18next tag="p" path="You are currently logged in as {%0%}">{{ $store.state.auth.username }}</i18next> {{ $gettext('Are you sure you want to log out?') }}
<button class="ui button" @click="$store.dispatch('auth/logout')"><i18next path="Yes, log me out!"/></button> </h2>
</form> <p v-translate="{username: $store.state.auth.username}">You are currently logged in as %{ username }</p>
<button class="ui button" @click="$store.dispatch('auth/logout')">{{ $gettext('Yes, log me out!') }}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,17 +9,19 @@
<i class="circular inverted user green icon"></i> <i class="circular inverted user green icon"></i>
<div class="content"> <div class="content">
{{ $store.state.auth.profile.username }} {{ $store.state.auth.profile.username }}
<i18next class="sub header" path="Registered since {%0%}">{{ signupDate }}</i18next> <div class="sub header" v-translate="{date: signupDate}">Registered since %{ date }</div>
</div> </div>
</h2> </h2>
<div class="ui basic green label"><i18next path="This is you!"/></div> <div class="ui basic green label">
{{ $gettext('This is you!') }}
</div>
<div v-if="$store.state.auth.profile.is_staff" class="ui yellow label"> <div v-if="$store.state.auth.profile.is_staff" class="ui yellow label">
<i class="star icon"></i> <i class="star icon"></i>
<i18next path="Staff member"/> {{ $gettext('Staff member') }}
</div> </div>
<router-link class="ui tiny basic button" :to="{path: '/settings'}"> <router-link class="ui tiny basic button" :to="{path: '/settings'}">
<i class="setting icon"> </i> <i class="setting icon"> </i>
<i18next path="Settings..."/> {{ $gettext('Settings...') }}
</router-link> </router-link>
</div> </div>

View File

@ -2,13 +2,17 @@
<div class="main pusher" v-title="'Account Settings'"> <div class="main pusher" v-title="'Account Settings'">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<div class="ui small text container"> <div class="ui small text container">
<h2 class="ui header"><i18next path="Account settings"/></h2> <h2 class="ui header">
{{ $gettext('Account settings') }}
</h2>
<form class="ui form" @submit.prevent="submitSettings()"> <form class="ui form" @submit.prevent="submitSettings()">
<div v-if="settings.success" class="ui positive message"> <div v-if="settings.success" class="ui positive message">
<div class="header"><i18next path="Settings updated"/></div> <div class="header">
{{ $gettext('Settings updated') }}
</div>
</div> </div>
<div v-if="settings.errors.length > 0" class="ui negative message"> <div v-if="settings.errors.length > 0" class="ui negative message">
<i18next tag="div" class="header" path="We cannot save your settings"/> <div class="header">{{ $gettext('We cannot save your settings') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in settings.errors">{{ error }}</li> <li v-for="error in settings.errors">{{ error }}</li>
</ul> </ul>
@ -20,46 +24,52 @@
<option :value="c.value" v-for="c in f.choices">{{ c.label }}</option> <option :value="c.value" v-for="c in f.choices">{{ c.label }}</option>
</select> </select>
</div> </div>
<button :class="['ui', {'loading': isLoading}, 'button']" type="submit"><i18next path="Update settings"/></button> <button :class="['ui', {'loading': isLoading}, 'button']" type="submit">
{{ $gettext('Update settings') }}
</button>
</form> </form>
</div> </div>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<div class="ui small text container"> <div class="ui small text container">
<h2 class="ui header"><i18next path="Change my password"/></h2> <h2 class="ui header">
{{ $gettext('Change my password') }}
</h2>
<div class="ui message"> <div class="ui message">
{{ $t('Changing your password will also change your Subsonic API password if you have requested one.') }} {{ $gettext('Changing your password will also change your Subsonic API password if you have requested one.') }}
{{ $t('You will have to update your password on your clients that use this password.') }} {{ $gettext('You will have to update your password on your clients that use this password.') }}
</div> </div>
<form class="ui form" @submit.prevent="submitPassword()"> <form class="ui form" @submit.prevent="submitPassword()">
<div v-if="passwordError" class="ui negative message"> <div v-if="passwordError" class="ui negative message">
<div class="header"><i18next path="Cannot change your password"/></div> <div class="header">
{{ $gettext('Cannot change your password') }}
</div>
<ul class="list"> <ul class="list">
<i18next tag="li" v-if="passwordError == 'invalid_credentials'" path="Please double-check your password is correct"/> <li v-if="passwordError == 'invalid_credentials'">{{ $gettext('Please double-check your password is correct') }}</li>
</ul> </ul>
</div> </div>
<div class="field"> <div class="field">
<label><i18next path="Old password"/></label> <label>{{ $gettext('Old password') }}</label>
<password-input required v-model="old_password" /> <password-input required v-model="old_password" />
</div> </div>
<div class="field"> <div class="field">
<label><i18next path="New password"/></label> <label>{{ $gettext('New password') }}</label>
<password-input required v-model="new_password" /> <password-input required v-model="new_password" />
</div> </div>
<dangerous-button <dangerous-button
color="yellow" color="yellow"
:class="['ui', {'loading': isLoading}, 'button']" :class="['ui', {'loading': isLoading}, 'button']"
:action="submitPassword"> :action="submitPassword">
{{ $t('Change password') }} {{ $gettext('Change password') }}
<p slot="modal-header">{{ $t('Change your password?') }}</p> <p slot="modal-header">{{ $gettext('Change your password?') }}</p>
<div slot="modal-content"> <div slot="modal-content">
<p>{{ $t("Changing your password will have the following consequences") }}</p> <p>{{ $gettext("Changing your password will have the following consequences") }}</p>
<ul> <ul>
<li>{{ $t('You will be logged out from this session and have to log out with the new one') }}</li> <li>{{ $gettext('You will be logged out from this session and have to log out with the new one') }}</li>
<li>{{ $t('Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password') }}</li> <li>{{ $gettext('Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password') }}</li>
</ul> </ul>
</div> </div>
<p slot="modal-confirm">{{ $t('Disable access') }}</p> <p slot="modal-confirm">{{ $gettext('Disable access') }}</p>
</dangerous-button> </dangerous-button>
</form> </form>
<div class="ui hidden divider" /> <div class="ui hidden divider" />

View File

@ -2,22 +2,22 @@
<div class="main pusher" v-title="'Sign Up'"> <div class="main pusher" v-title="'Sign Up'">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<div class="ui small text container"> <div class="ui small text container">
<h2>{{ $t("Create a funkwhale account") }}</h2> <h2>{{ $gettext("Create a funkwhale account") }}</h2>
<form <form
:class="['ui', {'loading': isLoadingInstanceSetting}, 'form']" :class="['ui', {'loading': isLoadingInstanceSetting}, 'form']"
@submit.prevent="submit()"> @submit.prevent="submit()">
<p class="ui message" v-if="!$store.state.instance.settings.users.registration_enabled.value"> <p class="ui message" v-if="!$store.state.instance.settings.users.registration_enabled.value">
{{ $t('Registration are closed on this instance, you will need an invitation code to signup.') }} {{ $gettext('Registration are closed on this instance, you will need an invitation code to signup.') }}
</p> </p>
<div v-if="errors.length > 0" class="ui negative message"> <div v-if="errors.length > 0" class="ui negative message">
<div class="header">{{ $t("We cannot create your account") }}</div> <div class="header">{{ $gettext("We cannot create your account") }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
</ul> </ul>
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t("Username") }}</label> <label>{{ $gettext("Username") }}</label>
<input <input
ref="username" ref="username"
required required
@ -27,7 +27,7 @@
v-model="username"> v-model="username">
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t("Email") }}</label> <label>{{ $gettext("Email") }}</label>
<input <input
ref="email" ref="email"
required required
@ -36,20 +36,20 @@
v-model="email"> v-model="email">
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t("Password") }}</label> <label>{{ $gettext("Password") }}</label>
<password-input v-model="password" /> <password-input v-model="password" />
</div> </div>
<div class="field"> <div class="field">
<label v-if="!$store.state.instance.settings.users.registration_enabled.value">{{ $t("Invitation code") }}</label> <label v-if="!$store.state.instance.settings.users.registration_enabled.value">{{ $gettext("Invitation code") }}</label>
<label v-else>{{ $t("Invitation code (optional)") }}</label> <label v-else>{{ $gettext("Invitation code (optional)") }}</label>
<input <input
:required="!$store.state.instance.settings.users.registration_enabled.value" :required="!$store.state.instance.settings.users.registration_enabled.value"
type="text" type="text"
:placeholder="$t('Enter your invitation code (case insensitive)')" :placeholder="$gettext('Enter your invitation code (case insensitive)')"
v-model="invitation"> v-model="invitation">
</div> </div>
<button :class="['ui', 'green', {'loading': isLoading}, 'button']" type="submit"> <button :class="['ui', 'green', {'loading': isLoading}, 'button']" type="submit">
{{ $t("Create my account") }} {{ $gettext("Create my account") }}
</button> </button>
</form> </form>
</div> </div>

View File

@ -1,24 +1,24 @@
<template> <template>
<form class="ui form" @submit.prevent="requestNewToken()"> <form class="ui form" @submit.prevent="requestNewToken()">
<h2>{{ $t('Subsonic API password') }}</h2> <h2>{{ $gettext('Subsonic API password') }}</h2>
<p class="ui message" v-if="!subsonicEnabled"> <p class="ui message" v-if="!subsonicEnabled">
{{ $t('The Subsonic API is not available on this Funkwhale instance.') }} {{ $gettext('The Subsonic API is not available on this Funkwhale instance.') }}
</p> </p>
<p> <p>
{{ $t('Funkwhale is compatible with other music players that support the Subsonic API.') }} {{ $gettext('Funkwhale is compatible with other music players that support the Subsonic API.') }}
{{ $t('You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.') }} {{ $gettext('You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.') }}
</p> </p>
<p> <p>
{{ $t('However, accessing Funkwhale from those clients require a separate password you can set below.') }} {{ $gettext('However, accessing Funkwhale from those clients require a separate password you can set below.') }}
</p> </p>
<p><a href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients" target="_blank"> <p><a href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients" target="_blank">
{{ $t('Discover how to use Funkwhale from other apps') }} {{ $gettext('Discover how to use Funkwhale from other apps') }}
</a></p> </a></p>
<div v-if="success" class="ui positive message"> <div v-if="success" class="ui positive message">
<div class="header">{{ successMessage }}</div> <div class="header">{{ successMessage }}</div>
</div> </div>
<div v-if="subsonicEnabled && errors.length > 0" class="ui negative message"> <div v-if="subsonicEnabled && errors.length > 0" class="ui negative message">
<div class="header">{{ $t('Error') }}</div> <div class="header">{{ $gettext('Error') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
</ul> </ul>
@ -32,25 +32,25 @@
color="grey" color="grey"
:class="['ui', {'loading': isLoading}, 'button']" :class="['ui', {'loading': isLoading}, 'button']"
:action="requestNewToken"> :action="requestNewToken">
{{ $t('Request a new password') }} {{ $gettext('Request a new password') }}
<p slot="modal-header">{{ $t('Request a new Subsonic API password?') }}</p> <p slot="modal-header">{{ $gettext('Request a new Subsonic API password?') }}</p>
<p slot="modal-content">{{ $t('This will log you out from existing devices that use the current password.') }}</p> <p slot="modal-content">{{ $gettext('This will log you out from existing devices that use the current password.') }}</p>
<p slot="modal-confirm">{{ $t('Request a new password') }}</p> <p slot="modal-confirm">{{ $gettext('Request a new password') }}</p>
</dangerous-button> </dangerous-button>
<button <button
v-else v-else
color="grey" color="grey"
:class="['ui', {'loading': isLoading}, 'button']" :class="['ui', {'loading': isLoading}, 'button']"
@click="requestNewToken">{{ $t('Request a password') }}</button> @click="requestNewToken">{{ $gettext('Request a password') }}</button>
<dangerous-button <dangerous-button
v-if="token" v-if="token"
color="yellow" color="yellow"
:class="['ui', {'loading': isLoading}, 'button']" :class="['ui', {'loading': isLoading}, 'button']"
:action="disable"> :action="disable">
{{ $t('Disable Subsonic access') }} {{ $gettext('Disable Subsonic access') }}
<p slot="modal-header">{{ $t('Disable Subsonic API access?') }}</p> <p slot="modal-header">{{ $gettext('Disable Subsonic API access?') }}</p>
<p slot="modal-content">{{ $t('This will completely disable access to the Subsonic API using from account.') }}</p> <p slot="modal-content">{{ $gettext('This will completely disable access to the Subsonic API using from account.') }}</p>
<p slot="modal-confirm">{{ $t('Disable access') }}</p> <p slot="modal-confirm">{{ $gettext('Disable access') }}</p>
</dangerous-button> </dangerous-button>
</template> </template>
</form> </form>
@ -92,7 +92,7 @@ export default {
}) })
}, },
requestNewToken () { requestNewToken () {
this.successMessage = this.$t('Password updated') this.successMessage = this.$gettext('Password updated')
this.success = false this.success = false
this.errors = [] this.errors = []
this.isLoading = true this.isLoading = true
@ -108,7 +108,7 @@ export default {
}) })
}, },
disable () { disable () {
this.successMessage = this.$t('Access disabled') this.successMessage = this.$gettext('Access disabled')
this.success = false this.success = false
this.errors = [] this.errors = []
this.isLoading = true this.isLoading = true

View File

@ -6,7 +6,7 @@
<div class="ui small form"> <div class="ui small form">
<div class="ui inline fields"> <div class="ui inline fields">
<div class="field"> <div class="field">
<label>{{ $t('Actions') }}</label> <label>{{ $gettext('Actions') }}</label>
<select class="ui dropdown" v-model="currentActionName"> <select class="ui dropdown" v-model="currentActionName">
<option v-for="action in actions" :value="action.name"> <option v-for="action in actions" :value="action.name">
{{ action.label }} {{ action.label }}
@ -19,41 +19,75 @@
@click="launchAction" @click="launchAction"
:disabled="checked.length === 0" :disabled="checked.length === 0"
:class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']"> :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']">
{{ $t('Go') }}</div> {{ $gettext('Go') }}</div>
<dangerous-button <dangerous-button
v-else-if="!currentAction.isDangerous" :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']" v-else-if="!currentAction.isDangerous" :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']"
confirm-color="green" confirm-color="green"
color="" color=""
@confirm="launchAction"> @confirm="launchAction">
{{ $t('Go') }} {{ $gettext('Go') }}
<p slot="modal-header">{{ $t('Do you want to launch action "{% action %}" on {% total %} elements?', {action: currentActionName, total: objectsData.count}) }} <p slot="modal-header">
<p slot="modal-content"> <translate
{{ $t('This may affect a lot of elements, please double check this is really what you want.')}} :translate-n="objectsData.count"
:translate-params="{count: objectsData.count, action: currentActionName}"
translate-plural="Do you want to launch %{ action } on %{ count } elements?">
Do you want to launch %{ action } on %{ count } element?
</translate>
</p> </p>
<p slot="modal-confirm">{{ $t('Launch') }}</p> <p slot="modal-content">
{{ $gettext('This may affect a lot of elements, please double check this is really what you want.')}}
</p>
<p slot="modal-confirm">{{ $gettext('Launch') }}</p>
</dangerous-button> </dangerous-button>
</div> </div>
<div class="count field"> <div class="count field">
<span v-if="selectAll">{{ $t('{% count %} on {% total %} selected', {count: objectsData.count, total: objectsData.count}) }}</span> <translate
<span v-else>{{ $t('{% count %} on {% total %} selected', {count: checked.length, total: objectsData.count}) }}</span> tag="span"
v-if="selectAll"
:translate-n="objectsData.count"
:translate-params="{count: objectsData.count, total: objectsData.count}"
translate-plural="%{ count } on %{ total } selected">
%{ count } on %{ total } selected
</translate>
<translate
tag="span"
v-else
:translate-n="checked.length"
:translate-params="{count: checked.length, total: objectsData.count}"
translate-plural="%{ count } on %{ total } selected">
%{ count } on %{ total } selected
</translate>
<template v-if="!currentAction.isDangerous && checkable.length > 0 && checkable.length === checked.length"> <template v-if="!currentAction.isDangerous && checkable.length > 0 && checkable.length === checked.length">
<a @click="selectAll = true" v-if="!selectAll"> <a @click="selectAll = true" v-if="!selectAll">
{{ $t('Select all {% total %} elements', {total: objectsData.count}) }} <translate
:translate-n="objectsData.count"
:translate-params="{total: objectsData.count}"
translate-plural="Select all %{ total } elements">
Select all %{ total } elements
</translate>
</a> </a>
<a @click="selectAll = false" v-else> <a @click="selectAll = false" v-else>
{{ $t('Select only current page') }} {{ $gettext('Select only current page') }}
</a> </a>
</template> </template>
</div> </div>
</div> </div>
<div v-if="actionErrors.length > 0" class="ui negative message"> <div v-if="actionErrors.length > 0" class="ui negative message">
<div class="header">{{ $t('Error while applying action') }}</div> <div class="header">{{ $gettext('Error while applying action') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in actionErrors">{{ error }}</li> <li v-for="error in actionErrors">{{ error }}</li>
</ul> </ul>
</div> </div>
<div v-if="actionResult" class="ui positive message"> <div v-if="actionResult" class="ui positive message">
<p>{{ $t('Action {% action %} was launched successfully on {% count %} objects.', {action: actionResult.action, count: actionResult.updated}) }}</p> <p>
<translate
:translate-n="actionResult.updated"
:translate-params="{count: actionResult.updated, action: actionResult.action}"
translate-plural="Action %{ action } was launched successfully on %{ count } elements">
Action %{ action } was launched successfully on %{ count } element
</translate>
</p>
<slot name="action-success-footer" :result="actionResult"> <slot name="action-success-footer" :result="actionResult">
</slot> </slot>
</div> </div>
@ -81,7 +115,6 @@
:disabled="checkable.indexOf(obj.id) === -1" :disabled="checkable.indexOf(obj.id) === -1"
@click="toggleCheck($event, obj.id, index)" @click="toggleCheck($event, obj.id, index)"
:checked="checked.indexOf(obj.id) > -1"><label>&nbsp;</label> :checked="checked.indexOf(obj.id) > -1"><label>&nbsp;</label>
</div>
</td> </td>
<slot name="row-cells" :obj="obj"></slot> <slot name="row-cells" :obj="obj"></slot>
</tr> </tr>

View File

@ -4,7 +4,9 @@
<modal class="small" :show.sync="showModal"> <modal class="small" :show.sync="showModal">
<div class="header"> <div class="header">
<slot name="modal-header"><i18next path="Do you want to confirm this action?"/></slot> <slot name="modal-header">
{{ $gettext('Do you want to confirm this action?') }}
</slot>
</div> </div>
<div class="scrolling content"> <div class="scrolling content">
<div class="description"> <div class="description">
@ -12,9 +14,13 @@
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<div class="ui cancel button"><i18next path="Cancel"/></div> <div class="ui cancel button">
{{ $gettext('Cancel') }}
</div>
<div :class="['ui', 'confirm', confirmButtonColor, 'button']" @click="confirm"> <div :class="['ui', 'confirm', confirmButtonColor, 'button']" @click="confirm">
<slot name="modal-confirm"><i18next path="Confirm"/></slot> <slot name="modal-confirm">
{{ $gettext('Confirm') }}
</slot>
</div> </div>
</div> </div>
</modal> </modal>

View File

@ -11,13 +11,15 @@
<span <span
@click="collapsed = false" @click="collapsed = false"
v-if="truncated && collapsed" v-if="truncated && collapsed"
class="expand" class="expand">
path="Expand"/> {{ $gettext('Expand') }}
<i18next </span>
<span
@click="collapsed = true" @click="collapsed = true"
v-if="truncated && !collapsed" v-if="truncated && !collapsed"
class="collapse" class="collapse">
path="Collapse"/> {{ $gettext('Collapse') }}
</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,13 +2,18 @@
<div class="main pusher" v-title="'Your Favorites'"> <div class="main pusher" v-title="'Your Favorites'">
<div class="ui vertical center aligned stripe segment"> <div 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"><i18next path="Loading your favorites..."/></div> <div class="ui text loader">
{{ $gettext('Loading your favorites...') }}
</div>
</div> </div>
<h2 v-if="results" class="ui center aligned icon header"> <h2 v-if="results" class="ui center aligned icon header">
<i class="circular inverted heart pink icon"></i> <i class="circular inverted heart pink icon"></i>
<i18next path="{%0%} favorites"> <translate
{{ $store.state.favorites.count }} translate-plural="%{ count } favorites"
</i18next> :translate-n="$store.state.favorites.count"
:translate-params="{count: $store.state.favorites.count}">
1 favorite
</translate>
</h2> </h2>
<radio-button type="favorites"></radio-button> <radio-button type="favorites"></radio-button>
</div> </div>
@ -16,7 +21,7 @@
<div :class="['ui', {'loading': isLoading}, 'form']"> <div :class="['ui', {'loading': isLoading}, 'form']">
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering"/> <label>{{ $gettext('Ordering') }}</label>
<select class="ui dropdown" v-model="ordering"> <select class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option v-for="option in orderingOptions" :value="option[0]">
{{ option[1] }} {{ option[1] }}
@ -24,14 +29,14 @@
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering direction"/> <label>{{ $gettext('Ordering direction') }}</label>
<select class="ui dropdown" v-model="orderingDirection"> <select class="ui dropdown" v-model="orderingDirection">
<option value="+"><i18next path="Ascending"/></option> <option value="+">{{ $gettext('Ascending') }}</option>
<option value="-"><i18next path="Descending"/></option> <option value="-">{{ $gettext('Descending') }}</option>
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Results per page"/> <label>{{ $gettext('Results per page') }}</label>
<select class="ui dropdown" v-model="paginateBy"> <select class="ui dropdown" v-model="paginateBy">
<option :value="parseInt(12)">12</option> <option :value="parseInt(12)">12</option>
<option :value="parseInt(25)">25</option> <option :value="parseInt(25)">25</option>

View File

@ -1,8 +1,8 @@
<template> <template>
<button @click="$store.dispatch('favorites/toggle', track.id)" v-if="button" :class="['ui', 'pink', {'inverted': isFavorite}, {'favorited': isFavorite}, 'button']"> <button @click="$store.dispatch('favorites/toggle', track.id)" v-if="button" :class="['ui', 'pink', {'inverted': isFavorite}, {'favorited': isFavorite}, 'button']">
<i class="heart icon"></i> <i class="heart icon"></i>
<i18next v-if="isFavorite" path="In favorites"/> <translate v-if="isFavorite">In favorites</translate>
<i18next v-else path="Add to favorites"/> <translate v-else>Add to favorites</translate>
</button> </button>
<i v-else @click="$store.dispatch('favorites/toggle', track.id)" :class="['favorite-icon', 'heart', {'pink': isFavorite}, {'favorited': isFavorite}, 'link', 'icon']" :title="title"></i> <i v-else @click="$store.dispatch('favorites/toggle', track.id)" :class="['favorite-icon', 'heart', {'pink': isFavorite}, {'favorited': isFavorite}, 'link', 'icon']" :title="title"></i>
</template> </template>
@ -16,9 +16,9 @@ export default {
computed: { computed: {
title () { title () {
if (this.isFavorite) { if (this.isFavorite) {
return this.$t('Remove from favorites') return this.$gettext('Remove from favorites')
} else { } else {
return this.$t('Add to favorites') return this.$gettext('Add to favorites')
} }
}, },
isFavorite () { isFavorite () {

View File

@ -13,39 +13,42 @@
</div> </div>
<div class="content"> <div class="content">
<span class="right floated" v-if="following"> <span class="right floated" v-if="following">
<i class="check icon"></i><i18next path="Following"/> <i class="check icon"></i>{{ $gettext('Following') }}
</span> </span>
<span class="right floated" v-else-if="manuallyApprovesFollowers"> <span class="right floated" v-else-if="manuallyApprovesFollowers">
<i class="lock icon"></i><i18next path="Followers only"/> <i class="lock icon"></i>{{ $gettext('Followers only') }}
</span> </span>
<span class="right floated" v-else> <span class="right floated" v-else>
<i class="open lock icon"></i><i18next path="Open"/> <i class="open lock icon"></i>{{ $gettext('Open') }}
</span> </span>
<span v-if="totalItems"> <span v-if="totalItems">
<i class="music icon"></i> <i class="music icon"></i>
<i18next path="{%0%} tracks"> <translate
{{ totalItems }} translate-plural="%{ count } tracks"
</i18next> :translate-n="totalItems"
:translate-params="{count: totalItems}">
1 track
</translate>
</span> </span>
</div> </div>
<div class="extra content"> <div class="extra content">
<template v-if="awaitingApproval"> <template v-if="awaitingApproval">
<i class="clock icon"></i> <i class="clock icon"></i>
<i18next path="Follow request pending approval"/> {{ $gettext('Follow request pending approval') }}
</template> </template>
<div <div
v-if="!library" v-if="!library"
@click="follow" @click="follow"
:disabled="isLoading" :disabled="isLoading"
:class="['ui', 'basic', {loading: isLoading}, 'green', 'button']"> :class="['ui', 'basic', {loading: isLoading}, 'green', 'button']">
<i18next v-if="manuallyApprovesFollowers" path="Send a follow request"/> <translate v-if="manuallyApprovesFollowers">Send a follow request</translate>
<i18next v-else path="Follow"/> <translate v-else>Follow</translate>
</div> </div>
<router-link <router-link
v-else v-else
class="ui basic button" class="ui basic button"
:to="{name: 'federation.libraries.detail', params: {id: library.uuid }}"> :to="{name: 'federation.libraries.detail', params: {id: library.uuid }}">
<i18next path="Detail"/> {{ $gettext('Detail') }}
</router-link> </router-link>
</div> </div>
</div> </div>

View File

@ -8,7 +8,9 @@
<div class="ui four wide inline field"> <div class="ui four wide inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<input v-model="pending" type="checkbox"> <input v-model="pending" type="checkbox">
<label><i18next path="Pending approval"/></label> <label>
{{ $gettext('Pending approval') }}
</label>
</div> </div>
</div> </div>
</div> </div>
@ -17,10 +19,10 @@
<table v-if="result" class="ui very basic single line unstackable table"> <table v-if="result" class="ui very basic single line unstackable table">
<thead> <thead>
<tr> <tr>
<i18next tag="th" path="Actor"/> <th>{{ $gettext('Actor') }}</th>
<i18next tag="th" path="Creation date"/> <th>{{ $gettext('Creation date') }}</th>
<i18next tag="th" path="Status"/> <th>{{ $gettext('Status') }}</th>
<i18next tag="th" path="Actions"/> <th>{{ $gettext('Actions') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -33,36 +35,49 @@
</td> </td>
<td> <td>
<template v-if="follow.approved === true"> <template v-if="follow.approved === true">
<i class="check icon"></i><i18next path="Approved"/> <i class="check icon"></i>
{{ $gettext('Approved') }}
</template> </template>
<template v-else-if="follow.approved === false"> <template v-else-if="follow.approved === false">
<i class="x icon"></i><i18next path="Refused"/> <i class="x icon"></i>
{{ $gettext('Refused') }}
</template> </template>
<template v-else> <template v-else>
<i class="clock icon"></i><i18next path="Pending"/> <i class="clock icon"></i>
{{ $gettext('Pending') }}
</template> </template>
</td> </td>
<td> <td>
<dangerous-button v-if="follow.approved !== false" class="tiny basic labeled icon" color='red' @confirm="updateFollow(follow, false)"> <dangerous-button v-if="follow.approved !== false" class="tiny basic labeled icon" color='red' @confirm="updateFollow(follow, false)">
<i class="x icon"></i><i18next path="Deny"/> <i class="x icon"></i>
<p slot="modal-header"><i18next path="Deny access?"/></p> {{ $gettext('Deny') }}
<p slot="modal-content"> <p slot="modal-header">
<i18next path="By confirming, {%0%}@{%1%} will be denied access to your library."> {{ $gettext('Deny access?') }}
{{ follow.actor.preferred_username }} </p>
{{ follow.actor.domain }} <p slot="modal-content">
</i18next> <translate
:translate-params="{username: follow.actor.preferred_username + '@' + follow.actor.domain}">
By confirming, %{ username } will be denied access to your library.
</translate>
</p>
<p slot="modal-confirm">
{{ $gettext('Deny') }}
</p> </p>
<p slot="modal-confirm"><i18next path="Deny"/></p>
</dangerous-button> </dangerous-button>
<dangerous-button v-if="follow.approved !== true" class="tiny basic labeled icon" color='green' @confirm="updateFollow(follow, true)"> <dangerous-button v-if="follow.approved !== true" class="tiny basic labeled icon" color='green' @confirm="updateFollow(follow, true)">
<i class="check icon"></i> <i18next path="Approve"/> <i class="check icon"></i>
<p slot="modal-header"><i18next path="Approve access?"/></p> {{ $gettext('Approve') }}
<p slot="modal-header">
{{ $gettext('Approve access?') }}
</p>
<p slot="modal-content"> <p slot="modal-content">
<i18next path="By confirming, {%0%}@{%1%} will be granted access to your library."> <translate
{{ follow.actor.preferred_username }} :translate-params="{username: follow.actor.preferred_username + '@' + follow.actor.domain}">
{{ follow.actor.domain }} By confirming, %{ username } will be granted access to your library.
</i18next> </translate>
<p slot="modal-confirm"><i18next path="Approve"/></p> <p slot="modal-confirm">
{{ $gettext('Approve') }}
</p>
</dangerous-button> </dangerous-button>
</td> </td>
</tr> </tr>
@ -80,11 +95,10 @@
></pagination> ></pagination>
</th> </th>
<th v-if="result && result.results.length > 0"> <th v-if="result && result.results.length > 0">
<i18next path="Showing results {%0%}-{%1%} on {%2%}"> <translate
{{ ((page-1) * paginateBy) + 1 }} :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
{{ ((page-1) * paginateBy) + result.results.length }} Showing results %{ start }-%{ end } on %{ total }
{{ result.count }} </translate>
</i18next>
</th> </th>
<th></th> <th></th>
<th></th> <th></th>

View File

@ -1,9 +1,15 @@
<template> <template>
<form class="ui form" @submit.prevent="fetchInstanceInfo"> <form class="ui form" @submit.prevent="fetchInstanceInfo">
<h3 class="ui header"><i18next path="Federate with a new instance"/></h3> <h3 class="ui header">
<p><i18next path="Use this form to scan an instance and setup federation."/></p> {{ $gettext('Federate with a new instance') }}
</h3>
<p>
{{ $gettext('Use this form to scan an instance and setup federation.') }}
</p>
<div v-if="errors.length > 0 || scanErrors.length > 0" class="ui negative message"> <div v-if="errors.length > 0 || scanErrors.length > 0" class="ui negative message">
<div class="header"><i18next path="Error while scanning library"/></div> <div class="header">
{{ $gettext('Error while scanning library') }}
</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
<li v-for="error in scanErrors">{{ error }}</li> <li v-for="error in scanErrors">{{ error }}</li>
@ -11,7 +17,9 @@
</div> </div>
<div class="ui two fields"> <div class="ui two fields">
<div class="ui field"> <div class="ui field">
<label><i18next path="Library name"/></label> <label>
{{ $gettext('Library name') }}
</label>
<input v-model="libraryUsername" type="text" placeholder="library@demo.funkwhale.audio" /> <input v-model="libraryUsername" type="text" placeholder="library@demo.funkwhale.audio" />
</div> </div>
<div class="ui field"> <div class="ui field">
@ -21,7 +29,7 @@
:disabled="isLoading" :disabled="isLoading"
:class="['ui', 'icon', {loading: isLoading}, 'button']"> :class="['ui', 'icon', {loading: isLoading}, 'button']">
<i class="search icon"></i> <i class="search icon"></i>
<i18next path="Launch scan"/> {{ $gettext('Launch scan') }}
</button> </button>
</div> </div>
</div> </div>

View File

@ -3,16 +3,16 @@
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label>{{ $t('Search') }}</label> <label>{{ $gettext('Search') }}</label>
<input type="text" v-model="search" placeholder="Search by title, artist, domain..." /> <input type="text" v-model="search" placeholder="Search by title, artist, domain..." />
</div> </div>
<div class="ui field"> <div class="ui field">
<label>{{ $t('Import status') }}</label> <label>{{ $gettext('Import status') }}</label>
<select class="ui dropdown" v-model="importedFilter"> <select class="ui dropdown" v-model="importedFilter">
<option :value="null">{{ $t('Any') }}</option> <option :value="null">{{ $gettext('Any') }}</option>
<option :value="'imported'">{{ $t('Imported') }}</option> <option :value="'imported'">{{ $gettext('Imported') }}</option>
<option :value="'not_imported'">{{ $t('Not imported') }}</option> <option :value="'not_imported'">{{ $gettext('Not imported') }}</option>
<option :value="'import_pending'">{{ $t('Import pending') }}</option> <option :value="'import_pending'">{{ $gettext('Import pending') }}</option>
</select> </select>
</div> </div>
</div> </div>
@ -29,25 +29,28 @@
:action-url="'federation/library-tracks/action/'" :action-url="'federation/library-tracks/action/'"
:filters="actionFilters"> :filters="actionFilters">
<template slot="header-cells"> <template slot="header-cells">
<th>{{ $t('Status') }}</th> <th>{{ $gettext('Status') }}</th>
<th>{{ $t('Title') }}</th> <th>{{ $gettext('Title') }}</th>
<th>{{ $t('Artist') }}</th> <th>{{ $gettext('Artist') }}</th>
<th>{{ $t('Album') }}</th> <th>{{ $gettext('Album') }}</th>
<th>{{ $t('Published date') }}</th> <th>{{ $gettext('Published date') }}</th>
<th v-if="showLibrary">{{ $t('Library') }}</th> <th v-if="showLibrary">{{ $gettext('Library') }}</th>
</template> </template>
<template slot="action-success-footer" slot-scope="scope"> <template slot="action-success-footer" slot-scope="scope">
<router-link <router-link
v-if="scope.result.action === 'import'" v-if="scope.result.action === 'import'"
:to="{name: 'library.import.batches.detail', params: {id: scope.result.result.batch.id }}"> :to="{name: 'library.import.batches.detail', params: {id: scope.result.result.batch.id }}">
{{ $t('Import #{% id %} launched', {id: scope.result.result.batch.id}) }} <translate
:translate-params="{id: scope.result.result.batch.id}">
Import #%{ id } launched
</translate>
</router-link> </router-link>
</template> </template>
<template slot="row-cells" slot-scope="scope"> <template slot="row-cells" slot-scope="scope">
<td> <td>
<span v-if="scope.obj.status === 'imported'" class="ui basic green label">{{ $t('In library') }}</span> <span v-if="scope.obj.status === 'imported'" class="ui basic green label">{{ $gettext('In library') }}</span>
<span v-else-if="scope.obj.status === 'import_pending'" class="ui basic yellow label">{{ $t('Import pending') }}</span> <span v-else-if="scope.obj.status === 'import_pending'" class="ui basic yellow label">{{ $gettext('Import pending') }}</span>
<span v-else class="ui basic label">{{ $t('Not imported') }}</span> <span v-else class="ui basic label">{{ $gettext('Not imported') }}</span>
</td> </td>
<td> <td>
<span :title="scope.obj.title">{{ scope.obj.title|truncate(30) }}</span> <span :title="scope.obj.title">{{ scope.obj.title|truncate(30) }}</span>
@ -78,7 +81,10 @@
></pagination> ></pagination>
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
{{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((page-1) * paginateBy) + 1 , end: ((page-1) * paginateBy) + result.results.length, total: result.count})}} <translate
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
Showing results %{ start }-%{ end } on %{ total }
</translate>
</span> </span>
</div> </div>
</div> </div>
@ -150,10 +156,11 @@ export default {
} }
}, },
actions () { actions () {
let msg = this.$gettext('Import')
return [ return [
{ {
name: 'import', name: 'import',
label: this.$t('Import'), label: msg,
filterCheckable: (obj) => { return obj.status === 'not_imported' } filterCheckable: (obj) => { return obj.status === 'not_imported' }
} }
] ]

View File

@ -6,7 +6,7 @@
:type="passwordInputType" :type="passwordInputType"
@input="$emit('input', $event.target.value)" @input="$emit('input', $event.target.value)"
:value="value"> :value="value">
<span @click="showPassword = !showPassword" :title="$t('Show/hide password')" class="ui icon button"> <span @click="showPassword = !showPassword" :title="$gettext('Show/hide password')" class="ui icon button">
<i class="eye icon"></i> <i class="eye icon"></i>
</span> </span>
</div> </div>

View File

@ -2,26 +2,28 @@
<div> <div>
<div v-if="stats" class="ui stackable two column grid"> <div v-if="stats" class="ui stackable two column grid">
<div class="column"> <div class="column">
<h3 class="ui left aligned header"><i18next path="User activity"/></h3> <h3 class="ui left aligned header">
{{ $gettext('User activity') }}
</h3>
<div v-if="stats" class="ui mini horizontal statistics"> <div v-if="stats" class="ui mini horizontal statistics">
<div class="statistic"> <div class="statistic">
<div class="value"> <div class="value">
<i class="green user icon"></i> <i class="green user icon"></i>
{{ stats.users }} {{ stats.users }}
</div> </div>
<i18next tag="div" class="label" path="users"/> <div class="label">{{ $gettext('users') }}</div>
</div> </div>
<div class="statistic"> <div class="statistic">
<div class="value"> <div class="value">
<i class="orange sound icon"></i> {{ stats.listenings }} <i class="orange sound icon"></i> {{ stats.listenings }}
</div> </div>
<i18next tag="div" class="label" path="tracks listened"/> <div class="label">{{ $gettext('tracks listened') }}</div>
</div> </div>
<div class="statistic"> <div class="statistic">
<div class="value"> <div class="value">
<i class="pink heart icon"></i> {{ stats.trackFavorites }} <i class="pink heart icon"></i> {{ stats.trackFavorites }}
</div> </div>
<i18next tag="div" class="label" path="Tracks favorited"/> <div class="label">{{ $gettext('Tracks favorited') }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -32,25 +34,25 @@
<div class="value"> <div class="value">
{{ parseInt(stats.musicDuration) }} {{ parseInt(stats.musicDuration) }}
</div> </div>
<i18next tag="div" class="label" path="hours of music"/> <div class="label">{{ $gettext('Hours of music') }}</div>
</div> </div>
<div class="statistic"> <div class="statistic">
<div class="value"> <div class="value">
{{ stats.artists }} {{ stats.artists }}
</div> </div>
<i18next tag="div" class="label" path="Artists"/> <div class="label">{{ $gettext('Artists') }}</div>
</div> </div>
<div class="statistic"> <div class="statistic">
<div class="value"> <div class="value">
{{ stats.albums }} {{ stats.albums }}
</div> </div>
<i18next tag="div" class="label" path="Albums"/> <div class="label">{{ $gettext('Albums') }}</div>
</div> </div>
<div class="statistic"> <div class="statistic">
<div class="value"> <div class="value">
{{ stats.tracks }} {{ stats.tracks }}
</div> </div>
<i18next tag="div" class="label" path="tracks"/> <div class="label">{{ $gettext('tracks') }}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -10,30 +10,39 @@
<i class="circular inverted sound yellow icon"></i> <i class="circular inverted sound yellow icon"></i>
<div class="content"> <div class="content">
{{ album.title }} {{ album.title }}
<i18next tag="div" class="sub header" path="Album containing {%0%} tracks, by {%1%}"> <translate
{{ album.tracks.length }} tag="div"
<router-link :to="{name: 'library.artists.detail', params: {id: album.artist.id }}"> translate-plural="Album containing %{ count } tracks, by %{ artist }"
{{ album.artist.name }} :translate-n="album.tracks.length"
:translate-params="{count: album.tracks.length, artist: album.artist.name}">
Album containing %{ count } track, by %{ artist }
</translate>
</div>
<div class="ui basic buttons">
<router-link class="ui button" :to="{name: 'library.artists.detail', params: {id: album.artist.id }}">
{{ $gettext('Artist page') }}
</router-link> </router-link>
</i18next>
</div> </div>
</h2> </h2>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
</button> <play-button class="orange" :tracks="album.tracks">
<play-button class="orange" :tracks="album.tracks"><i18next path="Play all"/></play-button> {{ $gettext('Play all') }}
</play-button>
<a :href="wikipediaUrl" target="_blank" class="ui button"> <a :href="wikipediaUrl" target="_blank" class="ui button">
<i class="wikipedia icon"></i> <i class="wikipedia icon"></i>
<i18next path="Search on Wikipedia"/> {{ $gettext('Search on Wikipedia') }}
</a> </a>
<a :href="musicbrainzUrl" target="_blank" class="ui button"> <a :href="musicbrainzUrl" target="_blank" class="ui button">
<i class="external icon"></i> <i class="external icon"></i>
<i18next path="View on MusicBrainz"/> {{ $gettext('View on MusicBrainz') }}
</a> </a>
</div> </div>
</div> </div>
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2><i18next path="Tracks"/></h2> <h2>
{{ $gettext('Tracks') }}
</h2>
<track-table v-if="album" :display-position="true" :tracks="album.tracks"></track-table> <track-table v-if="album" :display-position="true" :tracks="album.tracks"></track-table>
</div> </div>
</template> </template>

View File

@ -11,22 +11,29 @@
<div class="content"> <div class="content">
{{ artist.name }} {{ artist.name }}
<div class="sub header" v-if="albums"> <div class="sub header" v-if="albums">
{{ $t('{% track_count %} tracks in {% album_count %} albums', {track_count: totalTracks, album_count: albums.length})}} <translate
tag="div"
translate-plural="%{ count } tracks in %{ albumsCount } albums"
:translate-n="totalTracks"
:translate-params="{count: totalTracks, albumsCount: albums.length}">
%{ count } track in %{ albumsCount } albums
</translate>
</div> </div>
</div> </div>
</h2> </h2>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<radio-button type="artist" :object-id="artist.id"></radio-button> <radio-button type="artist" :object-id="artist.id"></radio-button>
</button> <play-button class="orange" :artist="artist.id">
<play-button class="orange" :artist="artist.id"><i18next path="Play all albums"/></play-button> {{ $gettext('Play all albums') }}
</play-button>
<a :href="wikipediaUrl" target="_blank" class="ui button"> <a :href="wikipediaUrl" target="_blank" class="ui button">
<i class="wikipedia icon"></i> <i class="wikipedia icon"></i>
<i18next path="Search on Wikipedia"/> {{ $gettext('Search on Wikipedia') }}
</a> </a>
<a :href="musicbrainzUrl" target="_blank" class="ui button"> <a :href="musicbrainzUrl" target="_blank" class="ui button">
<i class="external icon"></i> <i class="external icon"></i>
<i18next path="View on MusicBrainz"/> {{ $gettext('View on MusicBrainz') }}
</a> </a>
</div> </div>
</div> </div>
@ -34,7 +41,9 @@
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
</div> </div>
<div v-else-if="albums" class="ui vertical stripe segment"> <div v-else-if="albums" class="ui vertical stripe segment">
<h2><i18next path="Albums by this artist"/></h2> <h2>
{{ $gettext('Albums by this artist') }}
</h2>
<div class="ui stackable doubling three column grid"> <div class="ui stackable doubling three column grid">
<div class="column" :key="album.id" v-for="album in albums"> <div class="column" :key="album.id" v-for="album in albums">
<album-card :mode="'rich'" class="fluid" :album="album"></album-card> <album-card :mode="'rich'" class="fluid" :album="album"></album-card>

View File

@ -1,15 +1,19 @@
<template> <template>
<div v-title="'Artists'"> <div v-title="'Artists'">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2 class="ui header"><i18next path="Browsing artists"/></h2> <h2 class="ui header">
{{ $gettext('Browsing artists') }}
</h2>
<div :class="['ui', {'loading': isLoading}, 'form']"> <div :class="['ui', {'loading': isLoading}, 'form']">
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<label><i18next path="Search"/></label> <label>
{{ $gettext('Search') }}
</label>
<input type="text" v-model="query" placeholder="Enter an artist name..."/> <input type="text" v-model="query" placeholder="Enter an artist name..."/>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering"/> <label>{{ $gettext('Ordering') }}</label>
<select class="ui dropdown" v-model="ordering"> <select class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option v-for="option in orderingOptions" :value="option[0]">
{{ option[1] }} {{ option[1] }}
@ -17,14 +21,14 @@
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering direction"/> <label>{{ $gettext('Ordering direction') }}</label>
<select class="ui dropdown" v-model="orderingDirection"> <select class="ui dropdown" v-model="orderingDirection">
<option value="+">Ascending</option> <option value="+">Ascending</option>
<option value="-">Descending</option> <option value="-">Descending</option>
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Results per page"/> <label>{{ $gettext('Results per page') }}</label>
<select class="ui dropdown" v-model="paginateBy"> <select class="ui dropdown" v-model="paginateBy">
<option :value="parseInt(12)">12</option> <option :value="parseInt(12)">12</option>
<option :value="parseInt(25)">25</option> <option :value="parseInt(25)">25</option>

View File

@ -6,20 +6,26 @@
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<div class="ui stackable three column grid"> <div class="ui stackable three column grid">
<div class="column"> <div class="column">
<h2 class="ui header"><i18next path="Latest artists"/></h2> <h2 class="ui header">
{{ $gettext('Latest artists') }}
</h2>
<div :class="['ui', {'active': isLoadingArtists}, 'inline', 'loader']"></div> <div :class="['ui', {'active': isLoadingArtists}, 'inline', 'loader']"></div>
<div v-if="artists.length > 0" v-for="artist in artists.slice(0, 3)" :key="artist.id" class="ui cards"> <div v-if="artists.length > 0" v-for="artist in artists.slice(0, 3)" :key="artist.id" class="ui cards">
<artist-card :artist="artist"></artist-card> <artist-card :artist="artist"></artist-card>
</div> </div>
</div> </div>
<div class="column"> <div class="column">
<h2 class="ui header"><i18next path="Radios"/></h2> <h2 class="ui header">
{{ $gettext('Radios') }}
</h2>
<radio-card :type="'favorites'"></radio-card> <radio-card :type="'favorites'"></radio-card>
<radio-card :type="'random'"></radio-card> <radio-card :type="'random'"></radio-card>
<radio-card :type="'less-listened'"></radio-card> <radio-card :type="'less-listened'"></radio-card>
</div> </div>
<div class="column"> <div class="column">
<h2 class="ui header"><i18next path="Music requests"/></h2> <h2 class="ui header">
{{ $gettext('Music requests') }}
</h2>
<request-form v-if="$store.state.auth.authenticated"></request-form> <request-form v-if="$store.state.auth.authenticated"></request-form>
</div> </div>
</div> </div>

View File

@ -1,16 +1,24 @@
<template> <template>
<div class="main library pusher"> <div class="main library pusher">
<div class="ui secondary pointing menu"> <div class="ui secondary pointing menu">
<router-link class="ui item" to="/library" exact><i18next path="Browse"/></router-link> <router-link class="ui item" to="/library" exact>
<router-link class="ui item" to="/library/artists" exact><i18next path="Artists"/></router-link> {{ $gettext('Browse') }}
<router-link class="ui item" to="/library/radios" exact><i18next path="Radios"/></router-link> </router-link>
<router-link class="ui item" to="/library/playlists" exact><i18next path="Playlists"/></router-link> <router-link class="ui item" to="/library/artists" exact>
{{ $gettext('Artists') }}
</router-link>
<router-link class="ui item" to="/library/radios" exact>
{{ $gettext('Radios') }}
</router-link>
<router-link class="ui item" to="/library/playlists" exact>
{{ $gettext('Playlists') }}
</router-link>
<div class="ui secondary right menu"> <div class="ui secondary right menu">
<router-link v-if="showImports" class="ui item" to="/library/import/launch" exact> <router-link v-if="showImports" class="ui item" to="/library/import/launch" exact>
<i18next path="Import"/> {{ $gettext('Import') }}
</router-link> </router-link>
<router-link v-if="showImports" class="ui item" to="/library/import/batches"> <router-link v-if="showImports" class="ui item" to="/library/import/batches">
<i18next path="Import batches"/> {{ $gettext('Import batches') }}
</router-link> </router-link>
</div> </div>
</div> </div>

View File

@ -1,19 +1,21 @@
<template> <template>
<div v-title="'Radios'"> <div v-title="'Radios'">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2 class="ui header"><i18next path="Browsing radios"/></h2> <h2 class="ui header">
{{ $gettext('Browsing radios') }}
</h2>
<router-link class="ui green basic button" to="/library/radios/build" exact> <router-link class="ui green basic button" to="/library/radios/build" exact>
<i18next path="Create your own radio"/> {{ $gettext('Create your own radio') }}
</router-link> </router-link>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<div :class="['ui', {'loading': isLoading}, 'form']"> <div :class="['ui', {'loading': isLoading}, 'form']">
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<i18next tag="label" path="Search"/> <label>{{ $gettext('Search') }}</label>
<input type="text" v-model="query" placeholder="Enter a radio name..."/> <input type="text" v-model="query" placeholder="Enter a radio name..."/>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering"/> <label>{{ $gettext('Ordering') }}</label>
<select class="ui dropdown" v-model="ordering"> <select class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option v-for="option in orderingOptions" :value="option[0]">
{{ option[1] }} {{ option[1] }}
@ -21,14 +23,18 @@
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering direction"/> <label>{{ $gettext('Ordering direction') }}</label>
<select class="ui dropdown" v-model="orderingDirection"> <select class="ui dropdown" v-model="orderingDirection">
<option value="+"><i18next path="Ascending"/></option> <option value="+">
<option value="-"><i18next path="Descending"/></option> {{ $gettext('Ascending') }}
</option>
<option value="-">
{{ $gettext('Descending') }}
</option>
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Results per page"/> <label>{{ $gettext('Results per page') }}</label>
<select class="ui dropdown" v-model="paginateBy"> <select class="ui dropdown" v-model="paginateBy">
<option :value="parseInt(12)">12</option> <option :value="parseInt(12)">12</option>
<option :value="parseInt(25)">25</option> <option :value="parseInt(25)">25</option>

View File

@ -11,18 +11,25 @@
<div class="content"> <div class="content">
{{ track.title }} {{ track.title }}
<div class="sub header"> <div class="sub header">
<i18next path="From album {%0%} by {%1%}"> <translate :translate-params="{album: track.album.title, artist: track.artist.name}">
<router-link :to="{name: 'library.albums.detail', params: {id: track.album.id }}"> From album %{ album } by %{ artist }
{{ track.album.title }} </translate>
</router-link><router-link :to="{name: 'library.artists.detail', params: {id: track.artist.id }}"> </div>
{{ track.artist.name }} <br>
<div class="ui basic buttons">
<router-link class="ui button" :to="{name: 'library.albums.detail', params: {id: track.album.id }}">
{{ $gettext('Album page') }}
</router-link>
<router-link class="ui button" :to="{name: 'library.artists.detail', params: {id: track.artist.id }}">
{{ $gettext('Artist page') }}
</router-link> </router-link>
</i18next>
</div> </div>
</div> </div>
</h2> </h2>
<play-button class="orange" :track="track"><i18next path="Play"/></play-button> <play-button class="orange" :track="track">
{{ $gettext('Play') }}
</play-button>
<track-favorite-icon :track="track" :button="true"></track-favorite-icon> <track-favorite-icon :track="track" :button="true"></track-favorite-icon>
<track-playlist-icon <track-playlist-icon
:button="true" :button="true"
@ -31,70 +38,72 @@
<a :href="wikipediaUrl" target="_blank" class="ui button"> <a :href="wikipediaUrl" target="_blank" class="ui button">
<i class="wikipedia icon"></i> <i class="wikipedia icon"></i>
<i18next path="Search on Wikipedia"/> {{ $gettext('Search on Wikipedia') }}
</a> </a>
<a :href="musicbrainzUrl" target="_blank" class="ui button"> <a :href="musicbrainzUrl" target="_blank" class="ui button">
<i class="external icon"></i> <i class="external icon"></i>
<i18next path="View on MusicBrainz"/> {{ $gettext('View on MusicBrainz') }}
</a> </a>
<a v-if="downloadUrl" :href="downloadUrl" target="_blank" class="ui button"> <a v-if="downloadUrl" :href="downloadUrl" target="_blank" class="ui button">
<i class="download icon"></i> <i class="download icon"></i>
<i18next path="Download"/> {{ $gettext('Download') }}
</a> </a>
</div> </div>
</div> </div>
<div v-if="file" class="ui vertical stripe center aligned segment"> <div v-if="file" class="ui vertical stripe center aligned segment">
<h2 class="ui header">{{ $t('Track information') }}</h2> <h2 class="ui header">{{ $gettext('Track information') }}</h2>
<table class="ui very basic collapsing celled center aligned table"> <table class="ui very basic collapsing celled center aligned table">
<tbody> <tbody>
<tr> <tr>
<td> <td>
{{ $t('Duration') }} {{ $gettext('Duration') }}
</td> </td>
<td v-if="file.duration"> <td v-if="file.duration">
{{ time.parse(file.duration) }} {{ time.parse(file.duration) }}
</td> </td>
<td v-else> <td v-else>
{{ $t('N/A') }} {{ $gettext('N/A') }}
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
{{ $t('Size') }} {{ $gettext('Size') }}
</td> </td>
<td v-if="file.size"> <td v-if="file.size">
{{ file.size | humanSize }} {{ file.size | humanSize }}
</td> </td>
<td v-else> <td v-else>
{{ $t('N/A') }} {{ $gettext('N/A') }}
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
{{ $t('Bitrate') }} {{ $gettext('Bitrate') }}
</td> </td>
<td v-if="file.bitrate"> <td v-if="file.bitrate">
{{ file.bitrate | humanSize }}/s {{ file.bitrate | humanSize }}/s
</td> </td>
<td v-else> <td v-else>
{{ $t('N/A') }} {{ $gettext('N/A') }}
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="ui vertical stripe center aligned segment"> <div class="ui vertical stripe center aligned segment">
<h2><i18next path="Lyrics"/></h2> <h2>
{{ $gettext('Lyrics') }}
</h2>
<div v-if="isLoadingLyrics" class="ui vertical segment"> <div v-if="isLoadingLyrics" class="ui vertical segment">
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div> <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
</div> </div>
<div v-if="lyrics" v-html="lyrics.content_rendered"> <div v-if="lyrics" v-html="lyrics.content_rendered">
</div> </div>
<template v-if="!isLoadingLyrics & !lyrics"> <template v-if="!isLoadingLyrics & !lyrics">
<i18next tag="p" path="No lyrics available for this track."/> <p>{{ $gettext('No lyrics available for this track.') }}</p>
<a class="ui button" target="_blank" :href="lyricsSearchUrl"> <a class="ui button" target="_blank" :href="lyricsSearchUrl">
<i class="search icon"></i> <i class="search icon"></i>
<i18next path="Search on lyrics.wikia.com"/> {{ $gettext('Search on lyrics.wikia.com') }}
</a> </a>
</template> </template>
</div> </div>

View File

@ -4,7 +4,9 @@
<a :href="getMusicbrainzUrl('artist', metadata.id)" target="_blank" title="View on MusicBrainz">{{ metadata.name }}</a> <a :href="getMusicbrainzUrl('artist', metadata.id)" target="_blank" title="View on MusicBrainz">{{ metadata.name }}</a>
</h3> </h3>
<form class="ui form" @submit.prevent=""> <form class="ui form" @submit.prevent="">
<h6 class="ui header"><i18next path="Filter album types"/></h6> <h6 class="ui header">
{{ $gettext('Filter album types') }}
</h6>
<div class="inline fields"> <div class="inline fields">
<div class="field" v-for="t in availableReleaseTypes"> <div class="field" v-for="t in availableReleaseTypes">
<div class="ui checkbox"> <div class="ui checkbox">
@ -13,7 +15,7 @@
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Query template"/> <label>{{ $gettext('Query template') }}</label>
<input v-model="customQueryTemplate" /> <input v-model="customQueryTemplate" />
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@
<tbody> <tbody>
<tr> <tr>
<td> <td>
<strong>{{ $t('Import batch') }}</strong> <strong>{{ $gettext('Import batch') }}</strong>
</td> </td>
<td> <td>
#{{ batch.id }} #{{ batch.id }}
@ -16,7 +16,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<strong>{{ $t('Launch date') }}</strong> <strong>{{ $gettext('Launch date') }}</strong>
</td> </td>
<td> <td>
<human-date :date="batch.creation_date"></human-date> <human-date :date="batch.creation_date"></human-date>
@ -24,22 +24,22 @@
</tr> </tr>
<tr v-if="batch.user"> <tr v-if="batch.user">
<td> <td>
<strong>{{ $t('Submitted by') }}</strong> <strong>{{ $gettext('Submitted by') }}</strong>
</td> </td>
<td> <td>
<username :username="batch.user.username" /> <username :username="batch.user.username" />
</td> </td>
</tr> </tr>
<tr v-if="stats"> <tr v-if="stats">
<td><strong>{{ $t('Pending') }}</strong></td> <td><strong>{{ $gettext('Pending') }}</strong></td>
<td>{{ stats.pending }}</td> <td>{{ stats.pending }}</td>
</tr> </tr>
<tr v-if="stats"> <tr v-if="stats">
<td><strong>{{ $t('Skipped') }}</strong></td> <td><strong>{{ $gettext('Skipped') }}</strong></td>
<td>{{ stats.skipped }}</td> <td>{{ stats.skipped }}</td>
</tr> </tr>
<tr v-if="stats"> <tr v-if="stats">
<td><strong>{{ $t('Errored') }}</strong></td> <td><strong>{{ $gettext('Errored') }}</strong></td>
<td> <td>
{{ stats.errored }} {{ stats.errored }}
<button <button
@ -47,12 +47,12 @@
v-if="stats.errored > 0" v-if="stats.errored > 0"
class="ui tiny basic icon button"> class="ui tiny basic icon button">
<i class="redo icon" /> <i class="redo icon" />
{{ $t('Rerun errored jobs')}} {{ $gettext('Rerun errored jobs')}}
</button> </button>
</td> </td>
</tr> </tr>
<tr v-if="stats"> <tr v-if="stats">
<td><strong>{{ $t('Finished') }}</strong></td> <td><strong>{{ $gettext('Finished') }}</strong></td>
<td>{{ stats.finished }}/{{ stats.count}}</td> <td>{{ stats.finished }}/{{ stats.count}}</td>
</tr> </tr>
</tbody> </tbody>
@ -60,17 +60,17 @@
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label>{{ $t('Search') }}</label> <label>{{ $gettext('Search') }}</label>
<input type="text" v-model="jobFilters.search" placeholder="Search by source..." /> <input type="text" v-model="jobFilters.search" placeholder="Search by source..." />
</div> </div>
<div class="ui field"> <div class="ui field">
<label>{{ $t('Status') }}</label> <label>{{ $gettext('Status') }}</label>
<select class="ui dropdown" v-model="jobFilters.status"> <select class="ui dropdown" v-model="jobFilters.status">
<option :value="null">{{ $t('Any') }}</option> <option :value="null">{{ $gettext('Any') }}</option>
<option :value="'pending'">{{ $t('Pending') }}</option> <option :value="'pending'">{{ $gettext('Pending') }}</option>
<option :value="'errored'">{{ $t('Errored') }}</option> <option :value="'errored'">{{ $gettext('Errored') }}</option>
<option :value="'finished'">{{ $t('Success') }}</option> <option :value="'finished'">{{ $gettext('Success') }}</option>
<option :value="'skipped'">{{ $t('Skipped') }}</option> <option :value="'skipped'">{{ $gettext('Skipped') }}</option>
</select> </select>
</div> </div>
</div> </div>
@ -78,11 +78,11 @@
<table v-if="jobResult" class="ui unstackable table"> <table v-if="jobResult" class="ui unstackable table">
<thead> <thead>
<tr> <tr>
<th>{{ $t('Job ID') }}</th> <th>{{ $gettext('Job ID') }}</th>
<th>{{ $t('Recording MusicBrainz ID') }}</th> <th>{{ $gettext('Recording MusicBrainz ID') }}</th>
<th>{{ $t('Source') }}</th> <th>{{ $gettext('Source') }}</th>
<th>{{ $t('Status') }}</th> <th>{{ $gettext('Status') }}</th>
<th>{{ $t('Track') }}</th> <th>{{ $gettext('Track') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -103,7 +103,7 @@
<button <button
@click="rerun({batches: [], jobs: [job.id]})" @click="rerun({batches: [], jobs: [job.id]})"
v-if="job.status === 'errored'" v-if="job.status === 'errored'"
:title="$t('Rerun job')" :title="$gettext('Rerun job')"
class="ui tiny basic icon button"> class="ui tiny basic icon button">
<i class="redo icon" /> <i class="redo icon" />
</button> </button>
@ -126,7 +126,10 @@
></pagination> ></pagination>
</th> </th>
<th v-if="jobResult && jobResult.results.length > 0"> <th v-if="jobResult && jobResult.results.length > 0">
{{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((jobFilters.page-1) * jobFilters.paginateBy) + 1 , end: ((jobFilters.page-1) * jobFilters.paginateBy) + jobResult.results.length, total: jobResult.count})}} <translate
:translate-params="{start: ((jobFilters.page-1) * jobFilters.paginateBy) + 1, end: ((jobFilters.page-1) * jobFilters.paginateBy) + jobResult.results.length, total: jobResult.count}">
Showing results %{ start }-%{ end } on %{ total }
</translate>
<th> <th>
<th></th> <th></th>
<th></th> <th></th>

View File

@ -5,25 +5,25 @@
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label>{{ $t('Search') }}</label> <label>{{ $gettext('Search') }}</label>
<input type="text" v-model="filters.search" placeholder="Search by submitter, source..." /> <input type="text" v-model="filters.search" placeholder="Search by submitter, source..." />
</div> </div>
<div class="ui field"> <div class="ui field">
<label>{{ $t('Status') }}</label> <label>{{ $gettext('Status') }}</label>
<select class="ui dropdown" v-model="filters.status"> <select class="ui dropdown" v-model="filters.status">
<option :value="null">{{ $t('Any') }}</option> <option :value="null">{{ $gettext('Any') }}</option>
<option :value="'pending'">{{ $t('Pending') }}</option> <option :value="'pending'">{{ $gettext('Pending') }}</option>
<option :value="'errored'">{{ $t('Errored') }}</option> <option :value="'errored'">{{ $gettext('Errored') }}</option>
<option :value="'finished'">{{ $t('Success') }}</option> <option :value="'finished'">{{ $gettext('Success') }}</option>
</select> </select>
</div> </div>
<div class="ui field"> <div class="ui field">
<label>{{ $t('Import source') }}</label> <label>{{ $gettext('Import source') }}</label>
<select class="ui dropdown" v-model="filters.source"> <select class="ui dropdown" v-model="filters.source">
<option :value="null">{{ $t('Any') }}</option> <option :value="null">{{ $gettext('Any') }}</option>
<option :value="'shell'">{{ $t('CLI') }}</option> <option :value="'shell'">{{ $gettext('CLI') }}</option>
<option :value="'api'">{{ $t('API') }}</option> <option :value="'api'">{{ $gettext('API') }}</option>
<option :value="'federation'">{{ $t('Federation') }}</option> <option :value="'federation'">{{ $gettext('Federation') }}</option>
</select> </select>
</div> </div>
</div> </div>
@ -32,12 +32,12 @@
<table v-if="result && result.results.length > 0" class="ui unstackable table"> <table v-if="result && result.results.length > 0" class="ui unstackable table">
<thead> <thead>
<tr> <tr>
<th>{{ $t('ID') }}</th> <th>{{ $gettext('ID') }}</th>
<th>{{ $t('Launch date') }}</th> <th>{{ $gettext('Launch date') }}</th>
<th>{{ $t('Jobs') }}</th> <th>{{ $gettext('Jobs') }}</th>
<th>{{ $t('Status') }}</th> <th>{{ $gettext('Status') }}</th>
<th>{{ $t('Source') }}</th> <th>{{ $gettext('Source') }}</th>
<th>{{ $t('Submitted by') }}</th> <th>{{ $gettext('Submitted by') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -71,7 +71,10 @@
></pagination> ></pagination>
</th> </th>
<th v-if="result && result.results.length > 0"> <th v-if="result && result.results.length > 0">
{{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((filters.page-1) * filters.paginateBy) + 1 , end: ((filters.page-1) * filters.paginateBy) + result.results.length, total: result.count})}} <translate
:translate-params="{start: ((filters.page-1) * filters.paginateBy) + 1, end: ((filters.page-1) * filters.paginateBy) + result.results.length, total: result.count}">
Showing results %{ start }-%{ end } on %{ total }
</translate>
<th> <th>
<th></th> <th></th>
<th></th> <th></th>

View File

@ -2,8 +2,8 @@
<div> <div>
<div v-if="batch" class="ui container"> <div v-if="batch" class="ui container">
<div class="ui message"> <div class="ui message">
{{ $t('Ensure your music files are properly tagged before uploading them.') }} {{ $gettext('Ensure your music files are properly tagged before uploading them.') }}
<a href="http://picard.musicbrainz.org/" target='_blank'>{{ $t('We recommend using Picard for that purpose.') }}</a> <a href="http://picard.musicbrainz.org/" target='_blank'>{{ $gettext('We recommend using Picard for that purpose.') }}</a>
</div> </div>
<file-upload-widget <file-upload-widget
:class="['ui', 'icon', 'left', 'floated', 'button']" :class="['ui', 'icon', 'left', 'floated', 'button']"
@ -20,31 +20,30 @@
@input-file="inputFile" @input-file="inputFile"
ref="upload"> ref="upload">
<i class="upload icon"></i> <i class="upload icon"></i>
<i18next path="Select files to upload..."/> {{ $gettext('Select files to upload...') }}
</file-upload-widget> </file-upload-widget>
<button <button
:class="['ui', 'right', 'floated', 'icon', {disabled: files.length === 0}, 'button']" :class="['ui', 'right', 'floated', 'icon', {disabled: files.length === 0}, 'button']"
v-if="!$refs.upload || !$refs.upload.active" @click.prevent="startUpload()"> v-if="!$refs.upload || !$refs.upload.active" @click.prevent="startUpload()">
<i class="play icon" aria-hidden="true"></i> <i class="play icon" aria-hidden="true"></i>
<i18next path="Start Upload"/> {{ $gettext('Start Upload') }}
</button> </button>
<button type="button" class="ui right floated icon yellow button" v-else @click.prevent="$refs.upload.active = false"> <button type="button" class="ui right floated icon yellow button" v-else @click.prevent="$refs.upload.active = false">
<i class="pause icon" aria-hidden="true"></i> <i class="pause icon" aria-hidden="true"></i>
<i18next path="Stop Upload"/> {{ $gettext('Stop Upload') }}
</button> </button>
</div> </div>
<div class="ui hidden clearing divider"></div> <div class="ui hidden clearing divider"></div>
<i18next v-if="batch" path="Once all your files are uploaded, simply head over {%0%} to check the import status."> <template v-if="batch">{{ $gettext('Once all your files are uploaded, simply click the following button to check the import status.') }}</template>
<router-link :to="{name: 'library.import.batches.detail', params: {id: batch.id }}"> <router-link class="ui basic button" v-if="batch" :to="{name: 'library.import.batches.detail', params: {id: batch.id }}">
<i18next path="import detail page"/> {{ $gettext('Import detail page') }}
</router-link> </router-link>
</i18next>
<table class="ui single line table"> <table class="ui single line table">
<thead> <thead>
<tr> <tr>
<i18next tag="th" path="File name"/> <th>{{ $gettext('File name') }}</th>
<i18next tag="th" path="Size"/> <th>{{ $gettext('Size') }}</th>
<i18next tag="th" path="Status"/> <th>{{ $gettext('Status') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -55,10 +54,10 @@
<span v-if="file.error" class="ui red label"> <span v-if="file.error" class="ui red label">
{{ file.error }} {{ file.error }}
</span> </span>
<i18next v-else-if="file.success" class="ui green label" path="Success"/> <span v-else-if="file.success" class="ui green label">{{ $gettext('Success') }}</span>
<i18next v-else-if="file.active" class="ui yellow label" path="Uploading..."/> <span v-else-if="file.active" class="ui yellow label">{{ $gettext('Uploading...') }}</span>
<template v-else> <template v-else>
<i18next class="ui label" path="Pending"/> <span class="ui label">{{ $gettext('Pending') }}</span>
<button class="ui tiny basic red icon button" @click.prevent="$refs.upload.remove(file)"><i class="delete icon"></i></button> <button class="ui tiny basic red icon button" @click.prevent="$refs.upload.remove(file)"><i class="delete icon"></i></button>
</template> </template>
</td> </td>

View File

@ -4,34 +4,44 @@
<div class="ui top three attached ordered steps"> <div class="ui top three attached ordered steps">
<a @click="currentStep = 0" :class="['step', {'active': currentStep === 0}, {'completed': currentStep > 0}]"> <a @click="currentStep = 0" :class="['step', {'active': currentStep === 0}, {'completed': currentStep > 0}]">
<div class="content"> <div class="content">
<i18next tag="div" class="title" path="Import source"/> <div class="title">{{ $gettext('Import source') }}</div>
<i18next tag="div" class="description" path="Uploaded files or external source"/> <div class="description">{{ $gettext('Uploaded files or external source') }}</div>
</div> </div>
</a> </a>
<a @click="currentStep = 1" :class="['step', {'active': currentStep === 1}, {'completed': currentStep > 1}]"> <a @click="currentStep = 1" :class="['step', {'active': currentStep === 1}, {'completed': currentStep > 1}]">
<div class="content"> <div class="content">
<i18next tag="div" class="title" path="Metadata"/> <div class="title">{{ $gettext('Metadata') }}</div>
<i18next tag="div" class="description" path="Grab corresponding metadata"/> <div class="description">{{ $gettext('Grab corresponding metadata') }}</div>
</div> </div>
</a> </a>
<a @click="currentStep = 2" :class="['step', {'active': currentStep === 2}, {'completed': currentStep > 2}]"> <a @click="currentStep = 2" :class="['step', {'active': currentStep === 2}, {'completed': currentStep > 2}]">
<div class="content"> <div class="content">
<i18next tag="div" class="title" path="Music"/> <div class="title">{{ $gettext('Music') }}</div>
<i18next tag="div" class="description" path="Select relevant sources or files for import"/> <div class="description">{{ $gettext('Select relevant sources or files for import') }}</div>
</div> </div>
</a> </a>
</div> </div>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<div class="ui centered buttons"> <div class="ui centered buttons">
<button @click="currentStep -= 1" :disabled="currentStep === 0" class="ui icon button"><i class="left arrow icon"></i><i18next path="Previous step"/></button> <button @click="currentStep -= 1" :disabled="currentStep === 0" class="ui icon button"><i class="left arrow icon"></i>
<button @click="nextStep()" v-if="currentStep < 2" class="ui icon button"><i18next path="Next step"/><i class="right arrow icon"></i></button> {{ $gettext('Previous step') }}
</button>
<button @click="nextStep()" v-if="currentStep < 2" class="ui icon button">
{{ $gettext('Next step') }}
<i class="right arrow icon"></i>
</button>
<button <button
@click="$refs.import.launchImport()" @click="$refs.import.launchImport()"
v-if="currentStep === 2 && currentSource != 'upload'" v-if="currentStep === 2 && currentSource != 'upload'"
:class="['ui', 'positive', 'icon', {'loading': isImporting}, 'button']" :class="['ui', 'positive', 'icon', {'loading': isImporting}, 'button']"
:disabled="isImporting || importData.count === 0" :disabled="isImporting || importData.count === 0"
> >
<i18next path="Import {%0%} tracks">{{ importData.count }}</i18next> <translate
:translate-params="{count: importData.count || 0}"
:translate-n="importData.count || 0"
translate-plural="Import %{ count } tracks">
Import %{ count } track
</translate>
<i class="check icon"></i> <i class="check icon"></i>
</button> </button>
<button <button
@ -40,20 +50,20 @@
:class="['ui', 'positive', 'icon', {'disabled': !importBatch}, 'button']" :class="['ui', 'positive', 'icon', {'disabled': !importBatch}, 'button']"
:disabled="!importBatch" :disabled="!importBatch"
> >
{{ $t('Finish import' )}} {{ $gettext('Finish import' )}}
<i class="check icon"></i> <i class="check icon"></i>
</button> </button>
</div> </div>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<div class="ui attached segment"> <div class="ui attached segment">
<template v-if="currentStep === 0"> <template v-if="currentStep === 0">
<i18next tag="p" path="First, choose where you want to import the music from"/> <p>{{ $gettext('First, choose where you want to import the music from') }}</p>
<form class="ui form"> <form class="ui form">
<div class="field"> <div class="field">
<div class="ui radio checkbox"> <div class="ui radio checkbox">
<input type="radio" id="external" value="external" v-model="currentSource"> <input type="radio" id="external" value="external" v-model="currentSource">
<label for="external"> <label for="external">
<i18next path="External source. Supported backends"/> {{ $gettext('External source. Supported backends') }}
<div v-for="backend in backends" class="ui basic label"> <div v-for="backend in backends" class="ui basic label">
<i v-if="backend.icon" :class="[backend.icon, 'icon']"></i> <i v-if="backend.icon" :class="[backend.icon, 'icon']"></i>
{{ backend.label }} {{ backend.label }}
@ -64,7 +74,7 @@
<div class="field"> <div class="field">
<div class="ui radio checkbox"> <div class="ui radio checkbox">
<input type="radio" id="upload" value="upload" v-model="currentSource"> <input type="radio" id="upload" value="upload" v-model="currentSource">
<i18next tag="label" for="upload" path="File upload" /> <label for="upload">{{ $gettext('File upload') }}</label>
</div> </div>
</div> </div>
</form> </form>
@ -73,7 +83,7 @@
<div class="column"> <div class="column">
<form class="ui form" @submit.prevent=""> <form class="ui form" @submit.prevent="">
<div class="field"> <div class="field">
<i18next tag="label" path="Search an entity you want to import:"/> <label>{{ $gettext('Search an entity you want to import:') }}</label>
<metadata-search <metadata-search
:mb-type="mbType" :mb-type="mbType"
:mb-id="mbId" :mb-id="mbId"
@ -81,29 +91,35 @@
@type-changed="updateType"></metadata-search> @type-changed="updateType"></metadata-search>
</div> </div>
</form> </form>
<i18next tag="div" class="ui horizontal divider" path="Or"/> <div class="ui horizontal divider">{{ $gettext('Or') }}</div>
<form class="ui form" @submit.prevent=""> <form class="ui form" @submit.prevent="">
<div class="field"> <div class="field">
<i18next tag="label" path="Input a MusicBrainz ID manually:"/> <label>{{ $gettext('Input a MusicBrainz ID manually:') }}</label>
<input type="text" v-model="currentId" /> <input type="text" v-model="currentId" />
</div> </div>
</form> </form>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<template v-if="currentType && currentId"> <template v-if="currentType && currentId">
<h4 class="ui header"><i18next path="You will import:"/></h4> <h4 class="ui header">
{{ $gettext('You will import:') }}
</h4>
<component <component
:mbId="currentId" :mbId="currentId"
:is="metadataComponent" :is="metadataComponent"
@metadata-changed="this.updateMetadata" @metadata-changed="this.updateMetadata"
></component> ></component>
</template> </template>
<i18next tag="p" path="You can also skip this step and enter metadata manually."/> <p>{{ $gettext('You can also skip this step and enter metadata manually.') }}</p>
</div> </div>
<div class="column"> <div class="column">
<h5 class="ui header">What is metadata?</h5> <h5 class="ui header">What is metadata?</h5>
<i18next tag="p" path="Metadata is the data related to the music you want to import. This includes all the information about the artists, albums and tracks. In order to have a high quality library, it is recommended to grab data from the {%0%} project, which you can think about as the Wikipedia of music."> <template v-translate>
<a href="http://musicbrainz.org/" target="_blank">MusicBrainz</a> Metadata is the data related to the music you want to import. This includes all the information about the artists, albums and tracks. In order to have a high quality library, it is recommended to grab data from the
</i18next> <a href="https://musicbrainz.org" target="_blank">
MusicBrainz
</a>
project, which you can think about as the Wikipedia of music.
</template>
</div> </div>
</div> </div>
<div v-if="currentStep === 2"> <div v-if="currentStep === 2">
@ -128,8 +144,10 @@
</div> </div>
</div> </div>
<div class="ui vertical stripe segment" v-if="currentRequest"> <div class="ui vertical stripe segment" v-if="currentRequest">
<h3 class="ui header"><i18next path="Music request"/></h3> <h3 class="ui header">
<i18next tag="p" path="This import will be associated with the music request below. After the import is finished, the request will be marked as fulfilled."/> {{ $gettext('Music request') }}
</h3>
<p>{{ $gettext('This import will be associated with the music request below. After the import is finished, the request will be marked as fulfilled.') }}</p>
<request-card :request="currentRequest" :import-action="false"></request-card> <request-card :request="currentRequest" :import-action="false"></request-card>
</div> </div>

View File

@ -1,16 +1,18 @@
<template> <template>
<div> <div>
<h3 class="ui dividing block header"> <h3 class="ui dividing block header">
<i18next path="Album {%0%} ({%1%} tracks) by {%2%}"> <translate
<a :href="getMusicbrainzUrl('release', metadata.id)" target="_blank" title="View on MusicBrainz">{{ metadata.title }}</a> tag="div"
({{ tracks.length}} tracks) translate-plural="Album %{ title } (%{ count } tracks) by %{ artist }"
<a :href="getMusicbrainzUrl('artist', metadata['artist-credit'][0]['artist']['id'])" target="_blank" title="View on MusicBrainz">{{ metadata['artist-credit-phrase'] }}</a> :translate-n="tracks.length"
</i18next> :translate-params="{count: tracks.length, title: metadata.title, artist: metadata['artist-credit-phrase']}">
Album %{ title } (%{ count } track) by %{ artist }
</translate>
<div class="ui divider"></div> <div class="ui divider"></div>
<div class="sub header"> <div class="sub header">
<div class="ui toggle checkbox"> <div class="ui toggle checkbox">
<input type="checkbox" v-model="enabled" /> <input type="checkbox" v-model="enabled" />
<i18next tag="label" path="Import this release"/> <label>{{ $gettext('Import this release') }}</label>
</div> </div>
</div> </div>
</h3> </h3>

View File

@ -9,13 +9,13 @@
</h5> </h5>
<div class="ui toggle checkbox"> <div class="ui toggle checkbox">
<input type="checkbox" v-model="enabled" /> <input type="checkbox" v-model="enabled" />
<i18next tag="label" path="Import this track"/> <label>{{ $gettext('Import this track') }}</label>
</div> </div>
</div> </div>
<div class="three wide column" v-if="enabled"> <div class="three wide column" v-if="enabled">
<form class="ui mini form" @submit.prevent=""> <form class="ui mini form" @submit.prevent="">
<div class="field"> <div class="field">
<i18next tag="label" path="Source"/> <label>{{ $gettext('Source') }}</label>
<select v-model="currentBackendId"> <select v-model="currentBackendId">
<option v-for="backend in backends" :value="backend.id"> <option v-for="backend in backends" :value="backend.id">
{{ backend.label }} {{ backend.label }}
@ -28,10 +28,10 @@
<button @click="currentResultIndex -= 1" class="ui basic tiny icon button" :disabled="currentResultIndex === 0"> <button @click="currentResultIndex -= 1" class="ui basic tiny icon button" :disabled="currentResultIndex === 0">
<i class="left arrow icon"></i> <i class="left arrow icon"></i>
</button> </button>
<i18next path="Result {%0%}/{%1%}"> {{ results.total }}
{{ currentResultIndex + 1 }} <translate :translate-params="{current: currentResultIndex + 1, total: results.length}">
{{ results.length }} Result %{ current }/%{ total }
</i18next> </translate>
<button @click="currentResultIndex += 1" class="ui basic tiny icon button" :disabled="currentResultIndex + 1 === results.length"> <button @click="currentResultIndex += 1" class="ui basic tiny icon button" :disabled="currentResultIndex + 1 === results.length">
<i class="right arrow icon"></i> <i class="right arrow icon"></i>
</button> </button>
@ -40,9 +40,9 @@
<div class="four wide column" v-if="enabled"> <div class="four wide column" v-if="enabled">
<form class="ui mini form" @submit.prevent=""> <form class="ui mini form" @submit.prevent="">
<div class="field"> <div class="field">
<i18next tag="label" path="Search query"/> <label>{{ $gettext('Search query') }}</label>
<input type="text" v-model="query" /> <input type="text" v-model="query" />
<i18next tag="label" path="Imported URL"/> <label>{{ $gettext('Imported URL') }}</label>
<input type="text" v-model="importedUrl" /> <input type="text" v-model="importedUrl" />
</div> </div>
</form> </form>

View File

@ -2,30 +2,40 @@
<div class="ui vertical stripe segment" v-title="'Radio Builder'"> <div class="ui vertical stripe segment" v-title="'Radio Builder'">
<div> <div>
<div> <div>
<h2 class="ui header"><i18next path="Builder"/></h2> <h2 class="ui header">
<i18next tag="p" path="You can use this interface to build your own custom radio, which will play tracks according to your criteria"/> {{ $gettext('Builder') }}
</h2>
<p>{{ $gettext('You can use this interface to build your own custom radio, which will play tracks according to your criteria.') }}</p>
<div class="ui form"> <div class="ui form">
<div class="inline fields"> <div class="inline fields">
<div class="field"> <div class="field">
<i18next tag="label" for="name" path="Radio name"/> <label for="name">{{ $gettext('Radio name') }}</label>
<input id="name" type="text" v-model="radioName" placeholder="My awesome radio" /> <input id="name" type="text" v-model="radioName" placeholder="My awesome radio" />
</div> </div>
<div class="field"> <div class="field">
<input id="public" type="checkbox" v-model="isPublic" /> <input id="public" type="checkbox" v-model="isPublic" />
<i18next tag="label" for="public" path="Display publicly"/> <label for="public">{{ $gettext('Display publicly') }}</label>
</div> </div>
<button :disabled="!canSave" @click="save" class="ui green button"><i18next path="Save"/></button> <button :disabled="!canSave" @click="save" class="ui green button">
{{ $gettext('Save') }}
</button>
<radio-button v-if="id" type="custom" :custom-radio-id="id"></radio-button> <radio-button v-if="id" type="custom" :custom-radio-id="id"></radio-button>
</div> </div>
</div> </div>
<div class="ui form"> <div class="ui form">
<p><i18next path="Add filters to customize your radio"/></p> <p>
{{ $gettext('Add filters to customize your radio') }}
</p>
<div class="inline field"> <div class="inline field">
<select class="ui dropdown" v-model="currentFilterType"> <select class="ui dropdown" v-model="currentFilterType">
<option value=""><i18next path="Select a filter"/></option> <option value="">
{{ $gettext('Select a filter') }}
</option>
<option v-for="f in availableFilters" :value="f.type">{{ f.label }}</option> <option v-for="f in availableFilters" :value="f.type">{{ f.label }}</option>
</select> </select>
<button :disabled="!currentFilterType" @click="add" class="ui button"><i18next path="Add filter"/></button> <button :disabled="!currentFilterType" @click="add" class="ui button">
{{ $gettext('Add filter') }}
</button>
</div> </div>
<p v-if="currentFilter"> <p v-if="currentFilter">
{{ currentFilter.help_text }} {{ currentFilter.help_text }}
@ -34,11 +44,11 @@
<table class="ui table"> <table class="ui table">
<thead> <thead>
<tr> <tr>
<i18next tag="th" class="two wide" path="Filter name"/> <th class="two wide">{{ $gettext('Filter name') }}</th>
<i18next tag="th" class="one wide" path="Exclude"/> <th class="one wide">{{ $gettext('Exclude') }}</th>
<i18next tag="th" class="six wide" path="Config"/> <th class="six wide">{{ $gettext('Config') }}</th>
<i18next tag="th" class="five wide" path="Candidates"/> <th class="five wide">{{ $gettext('Candidates') }}</th>
<i18next tag="th" class="two wide" path="Actions"/> <th class="two wide">{{ $gettext('Actions') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -54,9 +64,13 @@
</tbody> </tbody>
</table> </table>
<template v-if="checkResult"> <template v-if="checkResult">
<i18next tag="h3" class="ui header" path="{%0%} tracks matching combined filters"> <h3
{{ checkResult.candidates.count }} class="ui header"
</i18next> v-translate="{count: checkResult.candidates.count}"
:translate-n="checkResult.candidates.count"
translate-plural="%{ count } tracks matching combined filters">
%{ count } track matching combined filters
</h3>
<track-table v-if="checkResult.candidates.sample" :tracks="checkResult.candidates.sample"></track-table> <track-table v-if="checkResult.candidates.sample" :tracks="checkResult.candidates.sample"></track-table>
</template> </template>
</div> </div>

View File

@ -42,7 +42,7 @@
</span> </span>
<modal v-if="checkResult" :show.sync="showCandidadesModal"> <modal v-if="checkResult" :show.sync="showCandidadesModal">
<div class="header"> <div class="header">
<i18next path="Track matching filter"/> {{ $gettext('Track matching filter') }}
</div> </div>
<div class="content"> <div class="content">
<div class="description"> <div class="description">
@ -51,13 +51,13 @@
</div> </div>
<div class="actions"> <div class="actions">
<div class="ui black deny button"> <div class="ui black deny button">
<i18next path="Cancel"/> {{ $gettext('Cancel') }}
</div> </div>
</div> </div>
</modal> </modal>
</td> </td>
<td> <td>
<button @click="$emit('delete', index)" class="ui basic red button"><i18next path="Remove"/></button> <button @click="$emit('delete', index)" class="ui basic red button">{{ $gettext('Remove') }}</button>
</td> </td>
</tr> </tr>
</template> </template>

View File

@ -3,11 +3,11 @@
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label>{{ $t('Search') }}</label> <label>{{ $gettext('Search') }}</label>
<input type="text" v-model="search" placeholder="Search by title, artist, domain..." /> <input type="text" v-model="search" placeholder="Search by title, artist, domain..." />
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering"/> <label>{{ $gettext('Ordering') }}</label>
<select class="ui dropdown" v-model="ordering"> <select class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option v-for="option in orderingOptions" :value="option[0]">
{{ option[1] }} {{ option[1] }}
@ -15,7 +15,7 @@
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering direction"/> <label>{{ $gettext('Ordering direction') }}</label>
<select class="ui dropdown" v-model="orderingDirection"> <select class="ui dropdown" v-model="orderingDirection">
<option value="+">Ascending</option> <option value="+">Ascending</option>
<option value="-">Descending</option> <option value="-">Descending</option>
@ -35,14 +35,14 @@
:action-url="'manage/library/track-files/action/'" :action-url="'manage/library/track-files/action/'"
:filters="actionFilters"> :filters="actionFilters">
<template slot="header-cells"> <template slot="header-cells">
<th>{{ $t('Title') }}</th> <th>{{ $gettext('Title') }}</th>
<th>{{ $t('Artist') }}</th> <th>{{ $gettext('Artist') }}</th>
<th>{{ $t('Album') }}</th> <th>{{ $gettext('Album') }}</th>
<th>{{ $t('Import date') }}</th> <th>{{ $gettext('Import date') }}</th>
<th>{{ $t('Type') }}</th> <th>{{ $gettext('Type') }}</th>
<th>{{ $t('Bitrate') }}</th> <th>{{ $gettext('Bitrate') }}</th>
<th>{{ $t('Duration') }}</th> <th>{{ $gettext('Duration') }}</th>
<th>{{ $t('Size') }}</th> <th>{{ $gettext('Size') }}</th>
</template> </template>
<template slot="row-cells" slot-scope="scope"> <template slot="row-cells" slot-scope="scope">
<td> <td>
@ -61,25 +61,25 @@
{{ scope.obj.audio_mimetype }} {{ scope.obj.audio_mimetype }}
</td> </td>
<td v-else> <td v-else>
{{ $t('N/A') }} {{ $gettext('N/A') }}
</td> </td>
<td v-if="scope.obj.bitrate"> <td v-if="scope.obj.bitrate">
{{ scope.obj.bitrate | humanSize }}/s {{ scope.obj.bitrate | humanSize }}/s
</td> </td>
<td v-else> <td v-else>
{{ $t('N/A') }} {{ $gettext('N/A') }}
</td> </td>
<td v-if="scope.obj.duration"> <td v-if="scope.obj.duration">
{{ time.parse(scope.obj.duration) }} {{ time.parse(scope.obj.duration) }}
</td> </td>
<td v-else> <td v-else>
{{ $t('N/A') }} {{ $gettext('N/A') }}
</td> </td>
<td v-if="scope.obj.size"> <td v-if="scope.obj.size">
{{ scope.obj.size | humanSize }} {{ scope.obj.size | humanSize }}
</td> </td>
<td v-else> <td v-else>
{{ $t('N/A') }} {{ $gettext('N/A') }}
</td> </td>
</template> </template>
</action-table> </action-table>
@ -95,7 +95,10 @@
></pagination> ></pagination>
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
{{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((page-1) * paginateBy) + 1 , end: ((page-1) * paginateBy) + result.results.length, total: result.count})}} <translate
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
Showing results %{ start }-%{ end } on %{ total }
</translate>
</span> </span>
</div> </div>
</div> </div>
@ -178,10 +181,11 @@ export default {
} }
}, },
actions () { actions () {
let msg = this.$gettext('Delete')
return [ return [
{ {
name: 'delete', name: 'delete',
label: this.$t('Delete'), label: msg,
isDangerous: true isDangerous: true
} }
] ]

View File

@ -3,11 +3,11 @@
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label>{{ $t('Search') }}</label> <label>{{ $gettext('Search') }}</label>
<input type="text" v-model="search" placeholder="Search by artist, username, comment..." /> <input type="text" v-model="search" placeholder="Search by artist, username, comment..." />
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering"/> <label>{{ $gettext('Ordering') }}</label>
<select class="ui dropdown" v-model="ordering"> <select class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option v-for="option in orderingOptions" :value="option[0]">
{{ option[1] }} {{ option[1] }}
@ -15,20 +15,20 @@
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering direction"/> <label>{{ $gettext('Ordering direction') }}</label>
<select class="ui dropdown" v-model="orderingDirection"> <select class="ui dropdown" v-model="orderingDirection">
<option value="+">Ascending</option> <option value="+">Ascending</option>
<option value="-">Descending</option> <option value="-">Descending</option>
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t("Status") }}</label> <label>{{ $gettext("Status") }}</label>
<select class="ui dropdown" v-model="status"> <select class="ui dropdown" v-model="status">
<option :value="null">{{ $t('All') }}</option> <option :value="null">{{ $gettext('All') }}</option>
<option :value="'pending'">{{ $t('Pending') }}</option> <option :value="'pending'">{{ $gettext('Pending') }}</option>
<option :value="'accepted'">{{ $t('Accepted') }}</option> <option :value="'accepted'">{{ $gettext('Accepted') }}</option>
<option :value="'imported'">{{ $t('Imported') }}</option> <option :value="'imported'">{{ $gettext('Imported') }}</option>
<option :value="'closed'">{{ $t('Closed') }}</option> <option :value="'closed'">{{ $gettext('Closed') }}</option>
</select> </select>
</div> </div>
</div> </div>
@ -45,48 +45,48 @@
:action-url="'manage/requests/import-requests/action/'" :action-url="'manage/requests/import-requests/action/'"
:filters="actionFilters"> :filters="actionFilters">
<template slot="header-cells"> <template slot="header-cells">
<th>{{ $t('User') }}</th> <th>{{ $gettext('User') }}</th>
<th>{{ $t('Status') }}</th> <th>{{ $gettext('Status') }}</th>
<th>{{ $t('Artist') }}</th> <th>{{ $gettext('Artist') }}</th>
<th>{{ $t('Albums') }}</th> <th>{{ $gettext('Albums') }}</th>
<th>{{ $t('Comment') }}</th> <th>{{ $gettext('Comment') }}</th>
<th>{{ $t('Creation date') }}</th> <th>{{ $gettext('Creation date') }}</th>
<th>{{ $t('Import date') }}</th> <th>{{ $gettext('Import date') }}</th>
<th>{{ $t('Actions') }}</th> <th>{{ $gettext('Actions') }}</th>
</template> </template>
<template slot="row-cells" slot-scope="scope"> <template slot="row-cells" slot-scope="scope">
<td> <td>
{{ scope.obj.user.username }} {{ scope.obj.user.username }}
</td> </td>
<td> <td>
<span class="ui green basic label" v-if="scope.obj.status === 'imported'">{{ $t('Imported') }}</span> <span class="ui green basic label" v-if="scope.obj.status === 'imported'">{{ $gettext('Imported') }}</span>
<span class="ui pink basic label" v-else-if="scope.obj.status === 'accepted'">{{ $t('Accepted') }}</span> <span class="ui pink basic label" v-else-if="scope.obj.status === 'accepted'">{{ $gettext('Accepted') }}</span>
<span class="ui yellow basic label" v-else-if="scope.obj.status === 'pending'">{{ $t('Pending') }}</span> <span class="ui yellow basic label" v-else-if="scope.obj.status === 'pending'">{{ $gettext('Pending') }}</span>
<span class="ui red basic label" v-else-if="scope.obj.status === 'closed'">{{ $t('Closed') }}</span> <span class="ui red basic label" v-else-if="scope.obj.status === 'closed'">{{ $gettext('Closed') }}</span>
</td> </td>
<td> <td>
<span :title="scope.obj.artist_name">{{ scope.obj.artist_name|truncate(30) }}</span> <span :title="scope.obj.artist_name">{{ scope.obj.artist_name|truncate(30) }}</span>
</td> </td>
<td> <td>
<span v-if="scope.obj.albums" :title="scope.obj.albums">{{ scope.obj.albums|truncate(30) }}</span> <span v-if="scope.obj.albums" :title="scope.obj.albums">{{ scope.obj.albums|truncate(30) }}</span>
<template v-else>{{ $t('N/A') }}</template> <template v-else>{{ $gettext('N/A') }}</template>
</td> </td>
<td> <td>
<span v-if="scope.obj.comment" :title="scope.obj.comment">{{ scope.obj.comment|truncate(30) }}</span> <span v-if="scope.obj.comment" :title="scope.obj.comment">{{ scope.obj.comment|truncate(30) }}</span>
<template v-else>{{ $t('N/A') }}</template> <template v-else>{{ $gettext('N/A') }}</template>
</td> </td>
<td> <td>
<human-date :date="scope.obj.creation_date"></human-date> <human-date :date="scope.obj.creation_date"></human-date>
</td> </td>
<td> <td>
<human-date v-if="scope.obj.imported_date" :date="scope.obj.creation_date"></human-date> <human-date v-if="scope.obj.imported_date" :date="scope.obj.creation_date"></human-date>
<template v-else>{{ $t('N/A') }}</template> <template v-else>{{ $gettext('N/A') }}</template>
</td> </td>
<td> <td>
<router-link <router-link
class="ui tiny basic button" class="ui tiny basic button"
:to="{name: 'library.import.launch', query: {request: scope.obj.id}}" :to="{name: 'library.import.launch', query: {request: scope.obj.id}}"
v-if="scope.obj.status === 'pending'">{{ $t('Create import') }}</router-link> v-if="scope.obj.status === 'pending'">{{ $gettext('Create import') }}</router-link>
</td> </td>
</template> </template>
</action-table> </action-table>
@ -102,7 +102,10 @@
></pagination> ></pagination>
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
{{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((page-1) * paginateBy) + 1 , end: ((page-1) * paginateBy) + result.results.length, total: result.count})}} <translate
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
Showing results %{ start }-%{ end } on %{ total }
</translate>
</span> </span>
</div> </div>
</div> </div>
@ -183,21 +186,25 @@ export default {
} }
}, },
actions () { actions () {
// somehow, extraction fails otherwise
let deleteLabel = this.$gettext('Delete')
let markImportedLabel = this.$gettext('Mark as imported')
let markClosedLabel = this.$gettext('Mark as closed')
return [ return [
{ {
name: 'delete', name: 'delete',
label: this.$t('Delete'), label: deleteLabel,
isDangerous: true isDangerous: true
}, },
{ {
name: 'mark_imported', name: 'mark_imported',
label: this.$t('Mark as imported'), label: markImportedLabel,
filterCheckable: (obj) => { return ['pending', 'accepted'].indexOf(obj.status) > -1 }, filterCheckable: (obj) => { return ['pending', 'accepted'].indexOf(obj.status) > -1 },
isDangerous: true isDangerous: true
}, },
{ {
name: 'mark_closed', name: 'mark_closed',
label: this.$t('Mark as closed'), label: markClosedLabel,
filterCheckable: (obj) => { return ['pending', 'accepted'].indexOf(obj.status) > -1 }, filterCheckable: (obj) => { return ['pending', 'accepted'].indexOf(obj.status) > -1 },
isDangerous: true isDangerous: true
} }

View File

@ -2,19 +2,19 @@
<div> <div>
<form class="ui form" @submit.prevent="submit"> <form class="ui form" @submit.prevent="submit">
<div v-if="errors.length > 0" class="ui negative message"> <div v-if="errors.length > 0" class="ui negative message">
<div class="header">{{ $t('Error while creating invitation') }}</div> <div class="header">{{ $gettext('Error while creating invitation') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
</ul> </ul>
</div> </div>
<div class="inline fields"> <div class="inline fields">
<div class="ui field"> <div class="ui field">
<label>{{ $t('Invitation code')}}</label> <label>{{ $gettext('Invitation code')}}</label>
<input type="text" v-model="code" :placeholder="$t('Leave empty for a random code')" /> <input type="text" v-model="code" :placeholder="$gettext('Leave empty for a random code')" />
</div> </div>
<div class="ui field"> <div class="ui field">
<button :class="['ui', {loading: isLoading}, 'button']" :disabled="isLoading" type="submit"> <button :class="['ui', {loading: isLoading}, 'button']" :disabled="isLoading" type="submit">
{{ $t('Get a new invitation') }} {{ $gettext('Get a new invitation') }}
</button> </button>
</div> </div>
</div> </div>
@ -24,8 +24,8 @@
<table class="ui ui basic table"> <table class="ui ui basic table">
<thead> <thead>
<tr> <tr>
<th>{{ $t('Code') }}</th> <th>{{ $gettext('Code') }}</th>
<th>{{ $t('Share link') }}</th> <th>{{ $gettext('Share link') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -35,7 +35,7 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<button class="ui basic button" @click="invitations = []">{{ $t('Clear') }}</button> <button class="ui basic button" @click="invitations = []">{{ $gettext('Clear') }}</button>
</div> </div>
</div> </div>
</template> </template>

View File

@ -3,11 +3,11 @@
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label>{{ $t('Search') }}</label> <label>{{ $gettext('Search') }}</label>
<input type="text" v-model="search" placeholder="Search by username, email, code..." /> <input type="text" v-model="search" placeholder="Search by username, email, code..." />
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t("Ordering") }}</label> <label>{{ $gettext("Ordering") }}</label>
<select class="ui dropdown" v-model="ordering"> <select class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option v-for="option in orderingOptions" :value="option[0]">
{{ option[1] }} {{ option[1] }}
@ -15,11 +15,11 @@
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t("Status") }}</label> <label>{{ $gettext("Status") }}</label>
<select class="ui dropdown" v-model="isOpen"> <select class="ui dropdown" v-model="isOpen">
<option :value="null">{{ $t('All') }}</option> <option :value="null">{{ $gettext('All') }}</option>
<option :value="true">{{ $t('Open') }}</option> <option :value="true">{{ $gettext('Open') }}</option>
<option :value="false">{{ $t('Expired/used') }}</option> <option :value="false">{{ $gettext('Expired/used') }}</option>
</select> </select>
</div> </div>
</div> </div>
@ -36,20 +36,20 @@
:action-url="'manage/users/invitations/action/'" :action-url="'manage/users/invitations/action/'"
:filters="actionFilters"> :filters="actionFilters">
<template slot="header-cells"> <template slot="header-cells">
<th>{{ $t('Owner') }}</th> <th>{{ $gettext('Owner') }}</th>
<th>{{ $t('Status') }}</th> <th>{{ $gettext('Status') }}</th>
<th>{{ $t('Creation date') }}</th> <th>{{ $gettext('Creation date') }}</th>
<th>{{ $t('Expiration date') }}</th> <th>{{ $gettext('Expiration date') }}</th>
<th>{{ $t('Code') }}</th> <th>{{ $gettext('Code') }}</th>
</template> </template>
<template slot="row-cells" slot-scope="scope"> <template slot="row-cells" slot-scope="scope">
<td> <td>
<router-link :to="{name: 'manage.users.users.detail', params: {id: scope.obj.id }}">{{ scope.obj.owner.username }}</router-link> <router-link :to="{name: 'manage.users.users.detail', params: {id: scope.obj.id }}">{{ scope.obj.owner.username }}</router-link>
</td> </td>
<td> <td>
<span v-if="scope.obj.users.length > 0" class="ui green basic label">{{ $t('Used') }}</span> <span v-if="scope.obj.users.length > 0" class="ui green basic label">{{ $gettext('Used') }}</span>
<span v-else-if="moment().isAfter(scope.obj.expiration_date)" class="ui red basic label">{{ $t('Expired') }}</span> <span v-else-if="moment().isAfter(scope.obj.expiration_date)" class="ui red basic label">{{ $gettext('Expired') }}</span>
<span v-else class="ui basic label">{{ $t('Not used') }}</span> <span v-else class="ui basic label">{{ $gettext('Not used') }}</span>
</td> </td>
<td> <td>
<human-date :date="scope.obj.creation_date"></human-date> <human-date :date="scope.obj.creation_date"></human-date>
@ -74,7 +74,10 @@
></pagination> ></pagination>
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
{{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((page-1) * paginateBy) + 1 , end: ((page-1) * paginateBy) + result.results.length, total: result.count})}} <translate
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
Showing results %{ start }-%{ end } on %{ total }
</translate>
</span> </span>
</div> </div>
</div> </div>
@ -155,10 +158,11 @@ export default {
} }
}, },
actions () { actions () {
let deleteLabel = this.$gettext('Delete')
return [ return [
{ {
name: 'delete', name: 'delete',
label: this.$t('Delete'), label: deleteLabel,
filterCheckable: (obj) => { filterCheckable: (obj) => {
return obj.users.length === 0 && moment().isBefore(obj.expiration_date) return obj.users.length === 0 && moment().isBefore(obj.expiration_date)
} }

View File

@ -3,11 +3,11 @@
<div class="ui inline form"> <div class="ui inline form">
<div class="fields"> <div class="fields">
<div class="ui field"> <div class="ui field">
<label>{{ $t('Search') }}</label> <label>{{ $gettext('Search') }}</label>
<input type="text" v-model="search" placeholder="Search by username, email, name..." /> <input type="text" v-model="search" placeholder="Search by username, email, name..." />
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering"/> <label>{{ $gettext('Ordering') }}</label>
<select class="ui dropdown" v-model="ordering"> <select class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option v-for="option in orderingOptions" :value="option[0]">
{{ option[1] }} {{ option[1] }}
@ -15,10 +15,10 @@
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<i18next tag="label" path="Ordering direction"/> <label>{{ $gettext('Ordering direction') }}</label>
<select class="ui dropdown" v-model="orderingDirection"> <select class="ui dropdown" v-model="orderingDirection">
<option value="+">{{ $t('Ascending') }}</option> <option value="+">{{ $gettext('Ascending') }}</option>
<option value="-">{{ $t('Descending') }}</option> <option value="-">{{ $gettext('Descending') }}</option>
</select> </select>
</div> </div>
</div> </div>
@ -35,13 +35,13 @@
:action-url="'manage/library/track-files/action/'" :action-url="'manage/library/track-files/action/'"
:filters="actionFilters"> :filters="actionFilters">
<template slot="header-cells"> <template slot="header-cells">
<th>{{ $t('Username') }}</th> <th>{{ $gettext('Username') }}</th>
<th>{{ $t('Email') }}</th> <th>{{ $gettext('Email') }}</th>
<th>{{ $t('Account status') }}</th> <th>{{ $gettext('Account status') }}</th>
<th>{{ $t('Sign-up') }}</th> <th>{{ $gettext('Sign-up') }}</th>
<th>{{ $t('Last activity') }}</th> <th>{{ $gettext('Last activity') }}</th>
<th>{{ $t('Permissions') }}</th> <th>{{ $gettext('Permissions') }}</th>
<th>{{ $t('Status') }}</th> <th>{{ $gettext('Status') }}</th>
</template> </template>
<template slot="row-cells" slot-scope="scope"> <template slot="row-cells" slot-scope="scope">
<td> <td>
@ -51,15 +51,15 @@
<span>{{ scope.obj.email }}</span> <span>{{ scope.obj.email }}</span>
</td> </td>
<td> <td>
<span v-if="scope.obj.is_active" class="ui basic green label">{{ $t('Active') }}</span> <span v-if="scope.obj.is_active" class="ui basic green label">{{ $gettext('Active') }}</span>
<span v-else class="ui basic grey label">{{ $t('Inactive') }}</span> <span v-else class="ui basic grey label">{{ $gettext('Inactive') }}</span>
</td> </td>
<td> <td>
<human-date :date="scope.obj.date_joined"></human-date> <human-date :date="scope.obj.date_joined"></human-date>
</td> </td>
<td> <td>
<human-date v-if="scope.obj.last_activity" :date="scope.obj.last_activity"></human-date> <human-date v-if="scope.obj.last_activity" :date="scope.obj.last_activity"></human-date>
<template v-else>{{ $t('N/A') }}</template> <template v-else>{{ $gettext('N/A') }}</template>
</td> </td>
<td> <td>
<template v-for="p in permissions"> <template v-for="p in permissions">
@ -67,9 +67,9 @@
</template> </template>
</td> </td>
<td> <td>
<span v-if="scope.obj.is_superuser" class="ui pink label">{{ $t('Admin') }}</span> <span v-if="scope.obj.is_superuser" class="ui pink label">{{ $gettext('Admin') }}</span>
<span v-else-if="scope.obj.is_staff" class="ui purple label">{{ $t('Staff member') }}</span> <span v-else-if="scope.obj.is_staff" class="ui purple label">{{ $gettext('Staff member') }}</span>
<span v-else class="ui basic label">{{ $t('regular user') }}</span> <span v-else class="ui basic label">{{ $gettext('regular user') }}</span>
</td> </td>
</template> </template>
</action-table> </action-table>
@ -85,7 +85,10 @@
></pagination> ></pagination>
<span v-if="result && result.results.length > 0"> <span v-if="result && result.results.length > 0">
{{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((page-1) * paginateBy) + 1 , end: ((page-1) * paginateBy) + result.results.length, total: result.count})}} <translate
:translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
Showing results %{ start }-%{ end } on %{ total }
</translate>
</span> </span>
</div> </div>
</div> </div>
@ -161,19 +164,19 @@ export default {
return [ return [
{ {
'code': 'upload', 'code': 'upload',
'label': this.$t('Upload') 'label': this.$gettext('Upload')
}, },
{ {
'code': 'library', 'code': 'library',
'label': this.$t('Library') 'label': this.$gettext('Library')
}, },
{ {
'code': 'federation', 'code': 'federation',
'label': this.$t('Federation') 'label': this.$gettext('Federation')
}, },
{ {
'code': 'settings', 'code': 'settings',
'label': this.$t('Settings') 'label': this.$gettext('Settings')
} }
] ]
}, },
@ -191,7 +194,7 @@ export default {
return [ return [
// { // {
// name: 'delete', // name: 'delete',
// label: this.$t('Delete'), // label: this.$gettext('Delete'),
// isDangerous: true // isDangerous: true
// } // }
] ]

View File

@ -16,7 +16,7 @@
{{ group['first-release-date'] }} {{ group['first-release-date'] }}
</td> </td>
<td colspan="3"> <td colspan="3">
<a :href="getMusicbrainzUrl('release-group', group.id)" class="discrete link" target="_blank" :title="$t('View on MusicBrainz')"> <a :href="getMusicbrainzUrl('release-group', group.id)" class="discrete link" target="_blank" :title="$gettext('View on MusicBrainz')">
{{ group.title }} {{ group.title }}
</a> </a>
</td> </td>

View File

@ -19,7 +19,7 @@
{{ track.position }} {{ track.position }}
</td> </td>
<td colspan="3"> <td colspan="3">
<a :href="getMusicbrainzUrl('recording', track.id)" class="discrete link" target="_blank" :title="$t('View on MusicBrainz')"> <a :href="getMusicbrainzUrl('recording', track.id)" class="discrete link" target="_blank" :title="$gettext('View on MusicBrainz')">
{{ track.recording.title }} {{ track.recording.title }}
</a> </a>
</td> </td>

View File

@ -12,7 +12,7 @@
</div> </div>
<div class="ui fluid search"> <div class="ui fluid search">
<div class="ui icon input"> <div class="ui icon input">
<input class="prompt" :placeholder="$t('Enter your search query...')" type="text"> <input class="prompt" :placeholder="$gettext('Enter your search query...')" type="text">
<i class="search icon"></i> <i class="search icon"></i>
</div> </div>
<div class="results"></div> <div class="results"></div>
@ -122,15 +122,15 @@ export default {
return [ return [
{ {
value: 'artist', value: 'artist',
label: this.$t('Artist') label: this.$gettext('Artist')
}, },
{ {
value: 'release', value: 'release',
label: this.$t('Album') label: this.$gettext('Album')
}, },
{ {
value: 'recording', value: 'recording',
label: this.$t('Track') label: this.$gettext('Track')
} }
] ]
} }

View File

@ -11,15 +11,18 @@
</div> </div>
<div class="meta"> <div class="meta">
<i class="clock icon"></i> <i class="clock icon"></i>
<i18next path="Updated {%0%}">
<human-date :date="playlist.modification_date" /> <human-date :date="playlist.modification_date" />
</i18next>
</div> </div>
</div> </div>
<div class="extra content"> <div class="extra content">
<span> <span>
<i class="sound icon"></i> <i class="sound icon"></i>
{{ $t('{%count%} tracks', { count: playlist.tracks_count }) }} <translate
translate-plural="%{ count } tracks"
:translate-n="playlist.tracks_count"
:translate-params="{count: playlist.tracks_count}">
%{ count} track
</translate>
</span> </span>
<play-button class="mini basic orange right floated" :playlist="playlist">Play all</play-button> <play-button class="mini basic orange right floated" :playlist="playlist">Play all</play-button>
</div> </div>

View File

@ -2,16 +2,16 @@
<div class="ui text container"> <div class="ui text container">
<playlist-form @updated="$emit('playlist-updated', $event)" :title="false" :playlist="playlist"></playlist-form> <playlist-form @updated="$emit('playlist-updated', $event)" :title="false" :playlist="playlist"></playlist-form>
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{ $t('Playlist editor') }} {{ $gettext('Playlist editor') }}
</h3> </h3>
<div class="ui attached segment"> <div class="ui attached segment">
<template v-if="status === 'loading'"> <template v-if="status === 'loading'">
<div class="ui active tiny inline loader"></div> <div class="ui active tiny inline loader"></div>
{{ $t('Syncing changes to server...') }} {{ $gettext('Syncing changes to server...') }}
</template> </template>
<template v-else-if="status === 'errored'"> <template v-else-if="status === 'errored'">
<i class="red close icon"></i> <i class="red close icon"></i>
{{ $t('An error occured while saving your changes') }} {{ $gettext('An error occured while saving your changes') }}
<div v-if="errors.length > 0" class="ui negative message"> <div v-if="errors.length > 0" class="ui negative message">
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
@ -19,7 +19,7 @@
</div> </div>
</template> </template>
<template v-else-if="status === 'saved'"> <template v-else-if="status === 'saved'">
<i class="green check icon"></i> {{ $t('Changes synced with server') }} <i class="green check icon"></i> {{ $gettext('Changes synced with server') }}
</template> </template>
</div> </div>
<div class="ui bottom attached segment"> <div class="ui bottom attached segment">
@ -29,14 +29,21 @@
:class="['ui', {disabled: queueTracks.length === 0}, 'labeled', 'icon', 'button']" :class="['ui', {disabled: queueTracks.length === 0}, 'labeled', 'icon', 'button']"
title="Copy tracks from current queue to playlist"> title="Copy tracks from current queue to playlist">
<i class="plus icon"></i> <i class="plus icon"></i>
{{ $t('Insert from queue ({%count%} tracks)', { count: queueTracks.length }) }} <translate
translate-plural="Insert from queue (%{ count } tracks)"
:translate-n="queueTracks.length"
:translate-params="{count: queueTracks.length}">
Insert from queue (%{ count } track)
</translate>
</div> </div>
<dangerous-button :disabled="plts.length === 0" class="labeled right floated icon" color='yellow' :action="clearPlaylist"> <dangerous-button :disabled="plts.length === 0" class="labeled right floated icon" color='yellow' :action="clearPlaylist">
<i class="eraser icon"></i> {{ $t('Clear playlist') }} <i class="eraser icon"></i> {{ $gettext('Clear playlist') }}
<p slot="modal-header">{{ $t('Do you want to clear the playlist "{%name%}"?', { name: playlist.name }) }}</p> <p slot="modal-header">
<p slot="modal-content">{{ $t('This will remove all tracks from this playlist and cannot be undone.') }}</p> <translate :translate-params="{playlist: playlist.name}">Do you want to clear the playlist "%{ playlist }"?</translate>
<p slot="modal-confirm">{{ $t('Clear playlist') }}</p> </p>
<p slot="modal-content">{{ $gettext('This will remove all tracks from this playlist and cannot be undone.') }}</p>
<p slot="modal-confirm">{{ $gettext('Clear playlist') }}</p>
</dangerous-button> </dangerous-button>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<template v-if="plts.length > 0"> <template v-if="plts.length > 0">

View File

@ -1,29 +1,29 @@
<template> <template>
<form class="ui form" @submit.prevent="submit()"> <form class="ui form" @submit.prevent="submit()">
<h4 v-if="title" class="ui header">{{ $t('Create a new playlist') }}</h4> <h4 v-if="title" class="ui header">{{ $gettext('Create a new playlist') }}</h4>
<div v-if="success" class="ui positive message"> <div v-if="success" class="ui positive message">
<div class="header"> <div class="header">
<template v-if="playlist"> <template v-if="playlist">
{{ $t('Playlist updated') }} {{ $gettext('Playlist updated') }}
</template> </template>
<template v-else> <template v-else>
{{ $t('Playlist created') }} {{ $gettext('Playlist created') }}
</template> </template>
</div> </div>
</div> </div>
<div v-if="errors.length > 0" class="ui negative message"> <div v-if="errors.length > 0" class="ui negative message">
<div class="header">{{ $t('We cannot create the playlist') }}</div> <div class="header">{{ $gettext('We cannot create the playlist') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
</ul> </ul>
</div> </div>
<div class="three fields"> <div class="three fields">
<div class="field"> <div class="field">
<label>{{ $t('Playlist name') }}</label> <label>{{ $gettext('Playlist name') }}</label>
<input v-model="name" required type="text" placeholder="My awesome playlist" /> <input v-model="name" required type="text" placeholder="My awesome playlist" />
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t('Playlist visibility') }}</label> <label>{{ $gettext('Playlist visibility') }}</label>
<select class="ui dropdown" v-model="privacyLevel"> <select class="ui dropdown" v-model="privacyLevel">
<option :value="c.value" v-for="c in privacyLevelChoices">{{ c.label }}</option> <option :value="c.value" v-for="c in privacyLevelChoices">{{ c.label }}</option>
</select> </select>
@ -31,8 +31,8 @@
<div class="field"> <div class="field">
<label>&nbsp;</label> <label>&nbsp;</label>
<button :class="['ui', 'fluid', {'loading': isLoading}, 'button']" type="submit"> <button :class="['ui', 'fluid', {'loading': isLoading}, 'button']" type="submit">
<template v-if="playlist">{{ $t('Update playlist') }}</template> <template v-if="playlist">{{ $gettext('Update playlist') }}</template>
<template v-else>{{ $t('Create playlist') }}</template> <template v-else>{{ $gettext('Create playlist') }}</template>
</button> </button>
</div> </div>
</div> </div>
@ -73,15 +73,15 @@ export default {
return [ return [
{ {
value: 'me', value: 'me',
label: this.$t('Nobody except me') label: this.$gettext('Nobody except me')
}, },
{ {
value: 'instance', value: 'instance',
label: this.$t('Everyone on this instance') label: this.$gettext('Everyone on this instance')
}, },
{ {
value: 'everyone', value: 'everyone',
label: this.$t('Everyone') label: this.$gettext('Everyone')
} }
] ]
} }

View File

@ -1,33 +1,37 @@
<template> <template>
<modal @update:show="update" :show="$store.state.playlists.showModal"> <modal @update:show="update" :show="$store.state.playlists.showModal">
<div class="header"> <div class="header">
{{ $t('Manage playlists') }} {{ $gettext('Manage playlists') }}
</div> </div>
<div class="scrolling content"> <div class="scrolling content">
<div class="description"> <div class="description">
<template v-if="track"> <template v-if="track">
<h4 class="ui header">{{ $t('Current track') }}</h4> <h4 class="ui header">{{ $gettext('Current track') }}</h4>
<div v-html='trackDisplay'></div> <div
v-translate="{artist: track.artist.name, title: track.title}"
:template-params="{artist: track.artist.name, title: track.title}">
"%{ title }", by %{ artist }
</div>
<div class="ui divider"></div> <div class="ui divider"></div>
</template> </template>
<playlist-form></playlist-form> <playlist-form></playlist-form>
<div class="ui divider"></div> <div class="ui divider"></div>
<div v-if="errors.length > 0" class="ui negative message"> <div v-if="errors.length > 0" class="ui negative message">
<div class="header">{{ $t('We cannot add the track to a playlist') }}</div> <div class="header">{{ $gettext('We cannot add the track to a playlist') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
</ul> </ul>
</div> </div>
</div> </div>
<h4 class="ui header">{{ $t('Available playlists') }}</h4> <h4 class="ui header">{{ $gettext('Available playlists') }}</h4>
<table class="ui unstackable very basic table"> <table class="ui unstackable very basic table">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th>{{ $t('Name') }}</th> <th>{{ $gettext('Name') }}</th>
<th class="sorted descending">{{ $t('Last modification') }}</th> <th class="sorted descending">{{ $gettext('Last modification') }}</th>
<th>{{ $t('Tracks') }}</th> <th>{{ $gettext('Tracks') }}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -46,9 +50,9 @@
<div <div
v-if="track" v-if="track"
class="ui green icon basic small right floated button" class="ui green icon basic small right floated button"
:title="$t('Add to this playlist')" :title="$gettext('Add to this playlist')"
@click="addToPlaylist(playlist.id)"> @click="addToPlaylist(playlist.id)">
<i class="plus icon"></i> {{ $t('Add track') }} <i class="plus icon"></i> {{ $gettext('Add track') }}
</div> </div>
</td> </td>
</tr> </tr>
@ -57,7 +61,7 @@
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<div class="ui cancel button">{{ $t('Cancel') }}</div> <div class="ui cancel button">{{ $gettext('Cancel') }}</div>
</div> </div>
</modal> </modal>
</template> </template>
@ -110,12 +114,6 @@ export default {
let p = _.sortBy(this.playlists, [(e) => { return e.modification_date }]) let p = _.sortBy(this.playlists, [(e) => { return e.modification_date }])
p.reverse() p.reverse()
return p return p
},
trackDisplay () {
return this.$t('"{%title%}" by {%artist%}', {
title: this.track.title,
artist: this.track.artist.name }
)
} }
}, },
watch: { watch: {

View File

@ -4,13 +4,13 @@
v-if="button" v-if="button"
:class="['ui', 'button']"> :class="['ui', 'button']">
<i class="list icon"></i> <i class="list icon"></i>
{{ $t('Add to playlist...') }} {{ $gettext('Add to playlist...') }}
</button> </button>
<i <i
v-else v-else
@click="$store.commit('playlists/chooseTrack', track)" @click="$store.commit('playlists/chooseTrack', track)"
:class="['playlist-icon', 'list', 'link', 'icon']" :class="['playlist-icon', 'list', 'link', 'icon']"
:title="$t('Add to playlist...')"> :title="$gettext('Add to playlist...')">
</i> </i>
</template> </template>

View File

@ -1,8 +1,8 @@
<template> <template>
<button @click="toggleRadio" :class="['ui', 'blue', {'inverted': running}, 'button']"> <button @click="toggleRadio" :class="['ui', 'blue', {'inverted': running}, 'button']">
<i class="ui feed icon"></i> <i class="ui feed icon"></i>
<template v-if="running">{{ $t('Stop') }}</template> <template v-if="running">{{ $gettext('Stop') }}</template>
<template v-else>{{ $t('Start') }}</template> <template v-else>{{ $gettext('Start') }}</template>
radio radio
</button> </button>
</template> </template>

View File

@ -18,7 +18,7 @@
class="ui basic yellow button" class="ui basic yellow button"
v-if="$store.state.auth.authenticated && type === 'custom' && customRadio.user === $store.state.auth.profile.id" v-if="$store.state.auth.authenticated && type === 'custom' && customRadio.user === $store.state.auth.profile.id"
:to="{name: 'library.radios.edit', params: {id: customRadioId }}"> :to="{name: 'library.radios.edit', params: {id: customRadioId }}">
{{ $t('Edit...') }} {{ $gettext('Edit...') }}
</router-link> </router-link>
<radio-button class="right floated button" :type="type" :custom-radio-id="customRadioId"></radio-button> <radio-button class="right floated button" :type="type" :custom-radio-id="customRadioId"></radio-button>
</div> </div>

View File

@ -23,7 +23,7 @@
<button <button
@click="createImport" @click="createImport"
v-if="request.status === 'pending' && importAction && $store.state.auth.availablePermissions['library']" v-if="request.status === 'pending' && importAction && $store.state.auth.availablePermissions['library']"
class="ui mini basic green right floated button">{{ $t('Create import') }}</button> class="ui mini basic green right floated button">{{ $gettext('Create import') }}</button>
</div> </div>
</div> </div>

View File

@ -1,30 +1,30 @@
<template> <template>
<div> <div>
<form v-if="!over" class="ui form" @submit.prevent="submit"> <form v-if="!over" class="ui form" @submit.prevent="submit">
<p>{{ $t('Something\'s missing in the library? Let us know what you would like to listen!') }}</p> <p>{{ $gettext('Something\'s missing in the library? Let us know what you would like to listen!') }}</p>
<div class="required field"> <div class="required field">
<label>{{ $t('Artist name') }}</label> <label>{{ $gettext('Artist name') }}</label>
<input v-model="currentArtistName" placeholder="The Beatles, Mickael Jackson…" required maxlength="200"> <input v-model="currentArtistName" placeholder="The Beatles, Mickael Jackson…" required maxlength="200">
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t('Albums') }}</label> <label>{{ $gettext('Albums') }}</label>
<p>{{ $t('Leave this field empty if you\'re requesting the whole discography.') }}</p> <p>{{ $gettext('Leave this field empty if you\'re requesting the whole discography.') }}</p>
<input v-model="currentAlbums" placeholder="The White Album, Thriller…" maxlength="2000"> <input v-model="currentAlbums" placeholder="The White Album, Thriller…" maxlength="2000">
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t('Comment') }}</label> <label>{{ $gettext('Comment') }}</label>
<textarea v-model="currentComment" rows="3" placeholder="Use this comment box to add details to your request if needed" maxlength="2000"></textarea> <textarea v-model="currentComment" rows="3" placeholder="Use this comment box to add details to your request if needed" maxlength="2000"></textarea>
</div> </div>
<button class="ui submit button" type="submit">{{ $t('Submit') }}</button> <button class="ui submit button" type="submit">{{ $gettext('Submit') }}</button>
</form> </form>
<div v-else class="ui success message"> <div v-else class="ui success message">
<div class="header">Request submitted!</div> <div class="header">Request submitted!</div>
<p>{{ $t('We\'ve received your request, you\'ll get some groove soon ;)') }}</p> <p>{{ $gettext('We\'ve received your request, you\'ll get some groove soon ;)') }}</p>
<button @click="reset" class="ui button">{{ $t('Submit another request') }}</button> <button @click="reset" class="ui button">{{ $gettext('Submit another request') }}</button>
</div> </div>
<div v-if="requests.length > 0"> <div v-if="requests.length > 0">
<div class="ui divider"></div> <div class="ui divider"></div>
<h3 class="ui header">{{ $t('Pending requests') }}</h3> <h3 class="ui header">{{ $gettext('Pending requests') }}</h3>
<div class="ui list"> <div class="ui list">
<div v-for="request in requests" class="item"> <div v-for="request in requests" class="item">
<div class="content"> <div class="content">

13
front/src/locales.js Normal file
View File

@ -0,0 +1,13 @@
/* eslint-disable */
export default {
"locales": [
{
"code": "en_US",
"label": "English (United-States)"
},
{
"code": "fr_FR",
"label": "Français"
}
]
}

View File

@ -11,11 +11,12 @@ import router from './router'
import axios from 'axios' import axios from 'axios'
import {VueMasonryPlugin} from 'vue-masonry' import {VueMasonryPlugin} from 'vue-masonry'
import VueLazyload from 'vue-lazyload' import VueLazyload from 'vue-lazyload'
import i18next from 'i18next'
import i18nextFetch from 'i18next-fetch-backend'
import VueI18Next from '@panter/vue-i18next'
import store from './store' import store from './store'
import GetTextPlugin from 'vue-gettext'
import { sync } from 'vuex-router-sync' import { sync } from 'vuex-router-sync'
import translations from './translations.json'
import locales from '@/locales'
import filters from '@/filters' // eslint-disable-line import filters from '@/filters' // eslint-disable-line
import globals from '@/components/globals' // eslint-disable-line import globals from '@/components/globals' // eslint-disable-line
@ -28,8 +29,31 @@ window.$ = window.jQuery = require('jquery')
// require('./semantic/semantic.css') // require('./semantic/semantic.css')
require('semantic-ui-css/semantic.js') require('semantic-ui-css/semantic.js')
require('masonry-layout') require('masonry-layout')
let availableLanguages = (function () {
let l = {}
locales.locales.forEach(c => {
l[c.code] = c.label
})
return l
})()
let defaultLanguage = 'en_US'
if (availableLanguages[store.state.ui.currentLanguage]) {
defaultLanguage = store.state.ui.currentLanguage
}
Vue.use(GetTextPlugin, {
availableLanguages: availableLanguages,
defaultLanguage: defaultLanguage,
languageVmMixin: {
computed: {
currentKebabCase: function () {
return this.current.toLowerCase().replace('_', '-')
}
}
},
translations: translations,
silent: true
})
Vue.use(VueI18Next)
Vue.use(VueMasonryPlugin) Vue.use(VueMasonryPlugin)
Vue.use(VueLazyload) Vue.use(VueLazyload)
Vue.config.productionTip = false Vue.config.productionTip = false
@ -96,35 +120,17 @@ axios.interceptors.response.use(function (response) {
} }
} }
if (error.backendErrors.length === 0) { if (error.backendErrors.length === 0) {
error.backendErrors.push(i18next.t('An unknown error occured, ensure your are connected to the internet and your funkwhale instance is up and running')) error.backendErrors.push('An unknown error occured, ensure your are connected to the internet and your funkwhale instance is up and running')
} }
// Do something with response error // Do something with response error
return Promise.reject(error) return Promise.reject(error)
}) })
// i18n
i18next
.use(i18nextFetch)
.init({
lng: navigator.language,
fallbackLng: ['en'],
preload: [navigator.language, 'en'],
backend: {
loadPath: '/static/translations/{%lng%}.json'
},
interpolation: {
prefix: '{%',
suffix: '%}'
}
})
const i18n = new VueI18Next(i18next)
/* eslint-disable no-new */ /* eslint-disable no-new */
new Vue({ new Vue({
el: '#app', el: '#app',
router, router,
store, store,
i18n,
template: '<App/>', template: '<App/>',
components: { App } components: { App }
}) })

View File

@ -36,6 +36,10 @@ export default new Vuex.Store({
key: 'instance', key: 'instance',
paths: ['instance.events', 'instance.instanceUrl'] paths: ['instance.events', 'instance.instanceUrl']
}), }),
createPersistedState({
key: 'ui',
paths: ['ui.currentLanguage']
}),
createPersistedState({ createPersistedState({
key: 'radios', key: 'radios',
paths: ['radios'], paths: ['radios'],

View File

@ -3,6 +3,7 @@ import axios from 'axios'
export default { export default {
namespaced: true, namespaced: true,
state: { state: {
currentLanguage: 'en_US',
lastDate: new Date(), lastDate: new Date(),
maxMessages: 100, maxMessages: 100,
messageDisplayDuration: 10000, messageDisplayDuration: 10000,
@ -13,6 +14,9 @@ export default {
} }
}, },
mutations: { mutations: {
currentLanguage: (state, value) => {
state.currentLanguage = value
},
computeLastDate: (state) => { computeLastDate: (state) => {
state.lastDate = new Date() state.lastDate = new Date()
}, },

View File

@ -0,0 +1 @@
{}

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="main pusher" v-title="$t('Instance settings')"> <div class="main pusher" v-title="$gettext('Instance settings')">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<div class="ui text container"> <div class="ui text container">
<div :class="['ui', {'loading': isLoading}, 'form']"></div> <div :class="['ui', {'loading': isLoading}, 'form']"></div>
@ -13,7 +13,7 @@
</div> </div>
<div class="four wide column"> <div class="four wide column">
<div class="ui sticky vertical secondary menu"> <div class="ui sticky vertical secondary menu">
<div class="header item">{{ $t('Sections') }}</div> <div class="header item">{{ $gettext('Sections') }}</div>
<a :class="['menu', {active: group.id === current}, 'item']" <a :class="['menu', {active: group.id === current}, 'item']"
@click.prevent="scrollTo(group.id)" @click.prevent="scrollTo(group.id)"
:href="'#' + group.id" :href="'#' + group.id"
@ -71,9 +71,18 @@ export default {
}, },
computed: { computed: {
groups () { groups () {
// somehow, extraction fails if in the return block directly
let instanceLabel = this.$gettext('Instance information')
let usersLabel = this.$gettext('Users')
let importsLabel = this.$gettext('Imports')
let playlistsLabel = this.$gettext('Playlists')
let federationLabel = this.$gettext('Federation')
let subsonicLabel = this.$gettext('Subsonic')
let statisticsLabel = this.$gettext('Statistics')
let errorLabel = this.$gettext('Error reporting')
return [ return [
{ {
label: this.$t('Instance information'), label: instanceLabel,
id: 'instance', id: 'instance',
settings: [ settings: [
'instance__name', 'instance__name',
@ -82,7 +91,7 @@ export default {
] ]
}, },
{ {
label: this.$t('Users'), label: usersLabel,
id: 'users', id: 'users',
settings: [ settings: [
'users__registration_enabled', 'users__registration_enabled',
@ -91,21 +100,21 @@ export default {
] ]
}, },
{ {
label: this.$t('Imports'), label: importsLabel,
id: 'imports', id: 'imports',
settings: [ settings: [
'providers_youtube__api_key' 'providers_youtube__api_key'
] ]
}, },
{ {
label: this.$t('Playlists'), label: playlistsLabel,
id: 'playlists', id: 'playlists',
settings: [ settings: [
'playlists__max_tracks' 'playlists__max_tracks'
] ]
}, },
{ {
label: this.$t('Federation'), label: federationLabel,
id: 'federation', id: 'federation',
settings: [ settings: [
'federation__enabled', 'federation__enabled',
@ -116,14 +125,14 @@ export default {
] ]
}, },
{ {
label: this.$t('Subsonic'), label: subsonicLabel,
id: 'subsonic', id: 'subsonic',
settings: [ settings: [
'subsonic__enabled' 'subsonic__enabled'
] ]
}, },
{ {
label: this.$t('Statistics'), label: statisticsLabel,
id: 'statistics', id: 'statistics',
settings: [ settings: [
'instance__nodeinfo_enabled', 'instance__nodeinfo_enabled',
@ -132,7 +141,7 @@ export default {
] ]
}, },
{ {
label: this.$t('Error reporting'), label: errorLabel,
id: 'reporting', id: 'reporting',
settings: [ settings: [
'raven__front_enabled', 'raven__front_enabled',

View File

@ -3,14 +3,14 @@
<div class="ui secondary pointing menu"> <div class="ui secondary pointing menu">
<router-link <router-link
class="ui item" class="ui item"
:to="{name: 'manage.library.files'}">{{ $t('Files') }}</router-link> :to="{name: 'manage.library.files'}">{{ $gettext('Files') }}</router-link>
<router-link <router-link
class="ui item" class="ui item"
:to="{name: 'manage.library.requests'}"> :to="{name: 'manage.library.requests'}">
{{ $t('Import requests') }} {{ $gettext('Import requests') }}
<div <div
:class="['ui', {'teal': $store.state.ui.notifications.importRequests > 0}, 'label']" :class="['ui', {'teal': $store.state.ui.notifications.importRequests > 0}, 'label']"
:title="$t('Pending import requests')"> :title="$gettext('Pending import requests')">
{{ $store.state.ui.notifications.importRequests }}</div> {{ $store.state.ui.notifications.importRequests }}</div>
</router-link> </router-link>
</div> </div>

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-title="'Files'"> <div v-title="'Files'">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2 class="ui header">{{ $t('Library files') }}</h2> <h2 class="ui header">{{ $gettext('Library files') }}</h2>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<library-files-table :show-library="true"></library-files-table> <library-files-table :show-library="true"></library-files-table>
</div> </div>

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-title="$t('Import requests')"> <div v-title="$gettext('Import requests')">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2 class="ui header">{{ $t('Import requests') }}</h2> <h2 class="ui header">{{ $gettext('Import requests') }}</h2>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<library-requests-table></library-requests-table> <library-requests-table></library-requests-table>
</div> </div>

View File

@ -1,12 +1,12 @@
<template> <template>
<div class="main pusher" v-title="$t('Manage users')"> <div class="main pusher" v-title="$gettext('Manage users')">
<div class="ui secondary pointing menu"> <div class="ui secondary pointing menu">
<router-link <router-link
class="ui item" class="ui item"
:to="{name: 'manage.users.users.list'}">{{ $t('Users') }}</router-link> :to="{name: 'manage.users.users.list'}">{{ $gettext('Users') }}</router-link>
<router-link <router-link
class="ui item" class="ui item"
:to="{name: 'manage.users.invitations.list'}">{{ $t('Invitations') }}</router-link> :to="{name: 'manage.users.invitations.list'}">{{ $gettext('Invitations') }}</router-link>
</div> </div>
<router-view :key="$route.fullPath"></router-view> <router-view :key="$route.fullPath"></router-view>
</div> </div>

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-title="$t('Invitations')"> <div v-title="$gettext('Invitations')">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2 class="ui header">{{ $t('Invitations') }}</h2> <h2 class="ui header">{{ $gettext('Invitations') }}</h2>
<invitation-form></invitation-form> <invitation-form></invitation-form>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<invitations-table></invitations-table> <invitations-table></invitations-table>

View File

@ -19,7 +19,7 @@
<tbody> <tbody>
<tr> <tr>
<td> <td>
{{ $t('Name') }} {{ $gettext('Name') }}
</td> </td>
<td> <td>
{{ object.name }} {{ object.name }}
@ -27,7 +27,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
{{ $t('Email address') }} {{ $gettext('Email address') }}
</td> </td>
<td> <td>
{{ object.email }} {{ object.email }}
@ -35,7 +35,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
{{ $t('Sign-up') }} {{ $gettext('Sign-up') }}
</td> </td>
<td> <td>
<human-date :date="object.date_joined"></human-date> <human-date :date="object.date_joined"></human-date>
@ -43,17 +43,17 @@
</tr> </tr>
<tr> <tr>
<td> <td>
{{ $t('Last activity') }} {{ $gettext('Last activity') }}
</td> </td>
<td> <td>
<human-date v-if="object.last_activity" :date="object.last_activity"></human-date> <human-date v-if="object.last_activity" :date="object.last_activity"></human-date>
<template v-else>{{ $t('N/A') }}</template> <template v-else>{{ $gettext('N/A') }}</template>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
{{ $t('Account active') }} {{ $gettext('Account active') }}
<span :data-tooltip="$t('Determine if the user account is active or not. Inactive users cannot login or user the service.')"><i class="question circle icon"></i></span> <span :data-tooltip="$gettext('Determine if the user account is active or not. Inactive users cannot login or user the service.')"><i class="question circle icon"></i></span>
</td> </td>
<td> <td>
<div class="ui toggle checkbox"> <div class="ui toggle checkbox">
@ -66,7 +66,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
{{ $t('Permissions') }} {{ $gettext('Permissions') }}
</td> </td>
<td> <td>
<select <select
@ -82,7 +82,7 @@
</table> </table>
</div> </div>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<button @click="fetchData" class="ui basic button">{{ $t('Refresh') }}</button> <button @click="fetchData" class="ui basic button">{{ $gettext('Refresh') }}</button>
</div> </div>
</template> </template>
</div> </div>
@ -145,19 +145,19 @@ export default {
return [ return [
{ {
'code': 'upload', 'code': 'upload',
'label': this.$t('Upload') 'label': this.$gettext('Upload')
}, },
{ {
'code': 'library', 'code': 'library',
'label': this.$t('Library') 'label': this.$gettext('Library')
}, },
{ {
'code': 'federation', 'code': 'federation',
'label': this.$t('Federation') 'label': this.$gettext('Federation')
}, },
{ {
'code': 'settings', 'code': 'settings',
'label': this.$t('Settings') 'label': this.$gettext('Settings')
} }
] ]
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-title="$t('Users')"> <div v-title="$gettext('Users')">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2 class="ui header">{{ $t('Users') }}</h2> <h2 class="ui header">{{ $gettext('Users') }}</h2>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<users-table></users-table> <users-table></users-table>
</div> </div>

View File

@ -1,30 +1,30 @@
<template> <template>
<div class="main pusher" v-title="$t('Confirm your email')"> <div class="main pusher" v-title="$gettext('Confirm your email')">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<div class="ui small text container"> <div class="ui small text container">
<h2>{{ $t('Confirm your email') }}</h2> <h2>{{ $gettext('Confirm your email') }}</h2>
<form v-if="!success" class="ui form" @submit.prevent="submit()"> <form v-if="!success" class="ui form" @submit.prevent="submit()">
<div v-if="errors.length > 0" class="ui negative message"> <div v-if="errors.length > 0" class="ui negative message">
<div class="header">{{ $t('Error while confirming your email') }}</div> <div class="header">{{ $gettext('Error while confirming your email') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
</ul> </ul>
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t('Confirmation code') }}</label> <label>{{ $gettext('Confirmation code') }}</label>
<input type="text" required v-model="key" /> <input type="text" required v-model="key" />
</div> </div>
<router-link :to="{path: '/login'}"> <router-link :to="{path: '/login'}">
{{ $t('Back to login') }} {{ $gettext('Back to login') }}
</router-link> </router-link>
<button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"> <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit">
{{ $t('Confirm your email') }}</button> {{ $gettext('Confirm your email') }}</button>
</form> </form>
<div v-else class="ui positive message"> <div v-else class="ui positive message">
<div class="header">{{ $t('Email confirmed') }}</div> <div class="header">{{ $gettext('Email confirmed') }}</div>
<p>{{ $t('Your email address was confirmed, you can now use the service without limitations.') }}</p> <p>{{ $gettext('Your email address was confirmed, you can now use the service without limitations.') }}</p>
<router-link :to="{name: 'login'}"> <router-link :to="{name: 'login'}">
{{ $t('Proceed to login') }} {{ $gettext('Proceed to login') }}
</router-link> </router-link>
</div> </div>
</div> </div>

View File

@ -1,31 +1,31 @@
<template> <template>
<div class="main pusher" v-title="$t('Reset your password')"> <div class="main pusher" v-title="$gettext('Reset your password')">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<div class="ui small text container"> <div class="ui small text container">
<h2>{{ $t('Reset your password') }}</h2> <h2>{{ $gettext('Reset your password') }}</h2>
<form class="ui form" @submit.prevent="submit()"> <form class="ui form" @submit.prevent="submit()">
<div v-if="errors.length > 0" class="ui negative message"> <div v-if="errors.length > 0" class="ui negative message">
<div class="header">{{ $t('Error while asking for a password reset') }}</div> <div class="header">{{ $gettext('Error while asking for a password reset') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
</ul> </ul>
</div> </div>
<p>{{ $t('Use this form to request a password reset. We will send an email to the given address with instructions to reset your password.') }}</p> <p>{{ $gettext('Use this form to request a password reset. We will send an email to the given address with instructions to reset your password.') }}</p>
<div class="field"> <div class="field">
<label>{{ $t('Account\'s email') }}</label> <label>{{ $gettext('Account\'s email') }}</label>
<input <input
required required
ref="email" ref="email"
type="email" type="email"
autofocus autofocus
:placeholder="$t('Input the email address binded to your account')" :placeholder="$gettext('Input the email address binded to your account')"
v-model="email"> v-model="email">
</div> </div>
<router-link :to="{path: '/login'}"> <router-link :to="{path: '/login'}">
{{ $t('Back to login') }} {{ $gettext('Back to login') }}
</router-link> </router-link>
<button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"> <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit">
{{ $t('Ask for a password reset') }}</button> {{ $gettext('Ask for a password reset') }}</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -1,35 +1,35 @@
<template> <template>
<div class="main pusher" v-title="$t('Change your password')"> <div class="main pusher" v-title="$gettext('Change your password')">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<div class="ui small text container"> <div class="ui small text container">
<h2>{{ $t('Change your password') }}</h2> <h2>{{ $gettext('Change your password') }}</h2>
<form v-if="!success" class="ui form" @submit.prevent="submit()"> <form v-if="!success" class="ui form" @submit.prevent="submit()">
<div v-if="errors.length > 0" class="ui negative message"> <div v-if="errors.length > 0" class="ui negative message">
<div class="header">{{ $t('Error while changing your password') }}</div> <div class="header">{{ $gettext('Error while changing your password') }}</div>
<ul class="list"> <ul class="list">
<li v-for="error in errors">{{ error }}</li> <li v-for="error in errors">{{ error }}</li>
</ul> </ul>
</div> </div>
<template v-if="token && uid"> <template v-if="token && uid">
<div class="field"> <div class="field">
<label>{{ $t('New password') }}</label> <label>{{ $gettext('New password') }}</label>
<password-input v-model="newPassword" /> <password-input v-model="newPassword" />
</div> </div>
<router-link :to="{path: '/login'}"> <router-link :to="{path: '/login'}">
{{ $t('Back to login') }} {{ $gettext('Back to login') }}
</router-link> </router-link>
<button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"> <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit">
{{ $t('Update your password') }}</button> {{ $gettext('Update your password') }}</button>
</template> </template>
<template v-else> <template v-else>
<p>{{ $t('If the email address provided in the previous step is valid and binded to a user account, you should receive an email with reset instructions in the next couple of minutes.') }}</p> <p>{{ $gettext('If the email address provided in the previous step is valid and binded to a user account, you should receive an email with reset instructions in the next couple of minutes.') }}</p>
</template> </template>
</form> </form>
<div v-else class="ui positive message"> <div v-else class="ui positive message">
<div class="header">{{ $t('Password updated successfully') }}</div> <div class="header">{{ $gettext('Password updated successfully') }}</div>
<p>{{ $t('Your password has been updated successfully.') }}</p> <p>{{ $gettext('Your password has been updated successfully.') }}</p>
<router-link :to="{name: 'login'}"> <router-link :to="{name: 'login'}">
{{ $t('Proceed to login') }} {{ $gettext('Proceed to login') }}
</router-link> </router-link>
</div> </div>
</div> </div>

View File

@ -3,16 +3,16 @@
<div class="ui secondary pointing menu"> <div class="ui secondary pointing menu">
<router-link <router-link
class="ui item" class="ui item"
:to="{name: 'federation.libraries.list'}">{{ $t('Libraries') }}</router-link> :to="{name: 'federation.libraries.list'}">{{ $gettext('Libraries') }}</router-link>
<router-link <router-link
class="ui item" class="ui item"
:to="{name: 'federation.tracks.list'}">{{ $t('Tracks') }}</router-link> :to="{name: 'federation.tracks.list'}">{{ $gettext('Tracks') }}</router-link>
<div class="ui secondary right menu"> <div class="ui secondary right menu">
<router-link <router-link
class="ui item" class="ui item"
:to="{name: 'federation.followers.list'}"> :to="{name: 'federation.followers.list'}">
{{ $t('Followers') }} {{ $gettext('Followers') }}
<div class="ui teal label" :title="$t('Pending requests')">{{ requestsCount }}</div> <div class="ui teal label" :title="$gettext('Pending requests')">{{ requestsCount }}</div>
</router-link> </router-link>
</div> </div>
</div> </div>

View File

@ -19,18 +19,18 @@
<tbody> <tbody>
<tr> <tr>
<td > <td >
{{ $t('Follow status') }} {{ $gettext('Follow status') }}
<span :data-tooltip="$t('This indicate if the remote library granted you access')"><i class="question circle icon"></i></span> <span :data-tooltip="$gettext('This indicate if the remote library granted you access')"><i class="question circle icon"></i></span>
</td> </td>
<td> <td>
<template v-if="object.follow.approved === null"> <template v-if="object.follow.approved === null">
<i class="loading icon"></i> {{ $t('Pending approval') }} <i class="loading icon"></i> {{ $gettext('Pending approval') }}
</template> </template>
<template v-else-if="object.follow.approved === true"> <template v-else-if="object.follow.approved === true">
<i class="check icon"></i> {{ $t('Following') }} <i class="check icon"></i> {{ $gettext('Following') }}
</template> </template>
<template v-else-if="object.follow.approved === false"> <template v-else-if="object.follow.approved === false">
<i class="x icon"></i> {{ $t('Not following') }} <i class="x icon"></i> {{ $gettext('Not following') }}
</template> </template>
</td> </td>
<td> <td>
@ -38,8 +38,8 @@
</tr> </tr>
<tr> <tr>
<td> <td>
{{ $t('Federation') }} {{ $gettext('Federation') }}
<span :data-tooltip="$t('Use this flag to enable/disable federation with this library')"><i class="question circle icon"></i></span> <span :data-tooltip="$gettext('Use this flag to enable/disable federation with this library')"><i class="question circle icon"></i></span>
</td> </td>
<td> <td>
<div class="ui toggle checkbox"> <div class="ui toggle checkbox">
@ -54,8 +54,8 @@
</tr> </tr>
<tr> <tr>
<td> <td>
{{ $t('Auto importing') }} {{ $gettext('Auto importing') }}
<span :data-tooltip="$t('When enabled, auto importing will automatically import new tracks published in this library')"><i class="question circle icon"></i></span> <span :data-tooltip="$gettext('When enabled, auto importing will automatically import new tracks published in this library')"><i class="question circle icon"></i></span>
</td> </td>
<td> <td>
<div class="ui toggle checkbox"> <div class="ui toggle checkbox">
@ -82,19 +82,24 @@
</tr> </tr>
--> -->
<tr> <tr>
<td>{{ $t('Library size') }}</td> <td>{{ $gettext('Library size') }}</td>
<td> <td>
<template v-if="object.tracks_count"> <template v-if="object.tracks_count">
{{ $t('{%count%} tracks', { count: object.tracks_count }) }} <translate
translate-plural="%{ count } tracks"
:translate-n="object.tracks_count"
:translate-params="{count: object.tracks_count}">
%{ count } track
</translate>
</template> </template>
<template v-else> <template v-else>
{{ $t('Unkwnown') }} {{ $gettext('Unkwnown') }}
</template> </template>
</td> </td>
<td></td> <td></td>
</tr> </tr>
<tr> <tr>
<td>{{ $t('Last fetched') }}</td> <td>{{ $gettext('Last fetched') }}</td>
<td> <td>
<human-date v-if="object.fetched_date" :date="object.fetched_date"></human-date> <human-date v-if="object.fetched_date" :date="object.fetched_date"></human-date>
<template v-else>Never</template> <template v-else>Never</template>
@ -102,10 +107,10 @@
@click="scan" @click="scan"
v-if="!scanTrigerred" v-if="!scanTrigerred"
:class="['ui', 'basic', {loading: isScanLoading}, 'button']"> :class="['ui', 'basic', {loading: isScanLoading}, 'button']">
<i class="sync icon"></i> {{ $t('Trigger scan') }} <i class="sync icon"></i> {{ $gettext('Trigger scan') }}
</button> </button>
<button v-else class="ui success button"> <button v-else class="ui success button">
<i class="check icon"></i> {{ $t('Scan triggered!') }} <i class="check icon"></i> {{ $gettext('Scan triggered!') }}
</button> </button>
</td> </td>
@ -115,10 +120,10 @@
</table> </table>
</div> </div>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<button @click="fetchData" class="ui basic button">{{ $t('Refresh') }}</button> <button @click="fetchData" class="ui basic button">{{ $gettext('Refresh') }}</button>
</div> </div>
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2>{{ $t('Tracks available in this library') }}</h2> <h2>{{ $gettext('Tracks available in this library') }}</h2>
<library-track-table v-if="!isLoading" :filters="{library: id}"></library-track-table> <library-track-table v-if="!isLoading" :filters="{library: id}"></library-track-table>
</div> </div>
</template> </template>

View File

@ -1,9 +1,9 @@
<template> <template>
<div v-title="'Followers'"> <div v-title="'Followers'">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2 class="ui header">{{ $t('Browsing followers') }}</h2> <h2 class="ui header">{{ $gettext('Browsing followers') }}</h2>
<p> <p>
{{ $t('Be careful when accepting follow requests, as it means the follower will have access to your entire library.') }} {{ $gettext('Be careful when accepting follow requests, as it means the follower will have access to your entire library.') }}
</p> </p>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<library-follow-table></library-follow-table> <library-follow-table></library-follow-table>

View File

@ -1,22 +1,22 @@
<template> <template>
<div v-title="'Libraries'"> <div v-title="'Libraries'">
<div class="ui vertical stripe segment"> <div class="ui vertical stripe segment">
<h2 class="ui header">{{ $t('Browsing libraries') }}</h2> <h2 class="ui header">{{ $gettext('Browsing libraries') }}</h2>
<router-link <router-link
class="ui basic green button" class="ui basic green button"
:to="{name: 'federation.libraries.scan'}"> :to="{name: 'federation.libraries.scan'}">
<i class="plus icon"></i> <i class="plus icon"></i>
{{ $t('Add a new library') }} {{ $gettext('Add a new library') }}
</router-link> </router-link>
<div class="ui hidden divider"></div> <div class="ui hidden divider"></div>
<div :class="['ui', {'loading': isLoading}, 'form']"> <div :class="['ui', {'loading': isLoading}, 'form']">
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<label>{{ $t('Search') }}</label> <label>{{ $gettext('Search') }}</label>
<input class="search" type="text" v-model="query" placeholder="Enter an library domain name..."/> <input class="search" type="text" v-model="query" placeholder="Enter an library domain name..."/>
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t('Ordering') }}</label> <label>{{ $gettext('Ordering') }}</label>
<select class="ui dropdown" v-model="ordering"> <select class="ui dropdown" v-model="ordering">
<option v-for="option in orderingOptions" :value="option[0]"> <option v-for="option in orderingOptions" :value="option[0]">
{{ option[1] }} {{ option[1] }}
@ -24,14 +24,14 @@
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t('Ordering direction') }}</label> <label>{{ $gettext('Ordering direction') }}</label>
<select class="ui dropdown" v-model="orderingDirection"> <select class="ui dropdown" v-model="orderingDirection">
<option value="+">{{ $t('Ascending') }}</option> <option value="+">{{ $gettext('Ascending') }}</option>
<option value="-">{{ $t('Descending') }}</option> <option value="-">{{ $gettext('Descending') }}</option>
</select> </select>
</div> </div>
<div class="field"> <div class="field">
<label>{{ $t('Results per page') }}</label> <label>{{ $gettext('Results per page') }}</label>
<select class="ui dropdown" v-model="paginateBy"> <select class="ui dropdown" v-model="paginateBy">
<option :value="parseInt(12)">12</option> <option :value="parseInt(12)">12</option>
<option :value="parseInt(25)">25</option> <option :value="parseInt(25)">25</option>

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