Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
jovuit
funkwhale_OLD
Commits
70441f45
Commit
70441f45
authored
Feb 07, 2019
by
Jo Vuit
Browse files
Merge branch 'patch-1' of dev.funkwhale.audio:jovuit/funkwhale into develop
parents
bf67f379
3ca17d3f
Pipeline
#3175
passed with stages
in 2 minutes and 23 seconds
Changes
3
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CONTRIBUTING.rst
View file @
70441f45
...
...
@@ -353,12 +353,160 @@ 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
using either ``<translate>yourstring</translate>`` or ``$gettext('yourstring')``
function.
When working on the front-end, any end-user string should be marked as a translatable string,
with the proper context, as described below.
Translations in HTML
^^^^^^^^^^^^^^^^^^^^
Translations in HTML use the ``<translate>`` tag::
<template>
<div>
<h1><translate :translate-context="'Content/Profile/Header'">User profile</translate></h1>
<p>
<translate
:translate-context="'Content/Profile/Paragraph'"
:translate-params="{username: 'alice'}">
You are logged in as %{ username }
</translate>
</p>
<p>
<translate
:translate-context="'Content/Profile/Paragraph'"
translate-plural="You have %{ count } new messages, that's a lot!"
:translate-n="unreadMessagesCount"
:translate-params="{count: unreadMessagesCount}">
You have 1 new message
</translate>
</p>
</div>
</template>
Anything between the `<translate>` and `</translate>` delimiters will be considered as a translatable string.
You can use variables in the translated string via the ``:translate-params="{var: 'value'}"`` directive, and reference them like this:
``val value is %{ value }``.
For pluralization, you need to use ``translate-params`` in conjunction with ``translate-plural`` and ``translate-n``:
- ``translate-params`` should contain the variable you're using for pluralization (which is usually shown to the user)
- ``translate-n`` should match the same variable
- The ``<translate>`` delimiters contain the non-pluralized version of your string
- The ``translate-plural`` directive contains the pluralized version of your string
Translations in javascript
^^^^^^^^^^^^^^^^^^^^^^^^^^
Translations in javascript work by calling the ``this.$*gettext`` functions::
export default {
computed: {
strings () {
let tracksCount = 42
let playButton = this.$pgettext('Sidebar/Player/Button/Verb, Short', 'Play')
let loginMessage = this.$pgettext('*/Login/Message', 'Welcome back %{ username }')
let addedMessage = this.$npgettext('*/Player/Message', 'One track was queued', '%{ count } tracks were queued', tracksCount)
console.log(this.$gettextInterpolate(addedMessage, {count: tracksCount}))
console.log(this.$gettextInterpolate(loginMessage, {username: 'alice'}))
}
}
}
The first argument of the ``$pgettext`` and ``$npgettext`` functions is the string context.
Contextualization
^^^^^^^^^^^^^^^^^
Translation contexts provided via the ``translate-context`` directive and the ``$pgettext`` and ``$npgettext`` are never shown to end users
but visible by Funkwhale translators. They help translators where and how the strings are used,
especially with short or ambiguous strings, like ``May``, which can refer a month or a verb.
While we could in theory use free form context, like ``This string is inside a button, in the main page, and is a call to action``,
Funkwhale use a hierarchical structure to write contexts and keep them short and consistents accross the app. The previous context,
rewritten correctly would be: ``Content/Home/Button/Call to action``.
This hierarchical structure is made of several parts:
- The location part, which is required and refers to the big blocks found in Funkwhale UI where the translated string is displayed:
- ``Content``
- ``Footer``
- ``Menu``
- ``Modal``
- ``Sidebar``
- ``*`` for strings that are not tied to a specific location
- The feature part, which is required, and refers to the feature associated with the translated string:
- ``About``
- ``Admin``
- ``Album``
- ``Artist``
- ``Home``
- ``Login``
- ``Moderation``
- ``Player``
- ``Playlist``
- ``Notifications``
- ``Radio``
- ``Settings``
- ``Signup``
- ``Track``
- ``Queue``
- ``*`` for strings that are not tied to a specific feature
- The component part, which is required and refers to the type of element that contain the string:
- ``Button``
- ``Card``
- ``Dropdown``
- ``Form``
- ``Header``
- ``Help text``
- ``Icon``
- ``Input``
- ``Image``
- ``Label``
- ``Link``
- ``List item``
- ``Message``
- ``Paragraph``
- ``Placeholder``
- ``Tab``
- ``Table``
- ``Title``
- ``Tooltip``
- ``*`` for strings that are not tied to a specific component
The detail part, which is optional and refers to the contents of the string itself, such as:
- ``Call to action``
- ``Verb``
- ``Short``
Here are a few examples of valid context hierarchies:
- ``Sidebar/Player/Button/Title``
- ``Content/Home/Button/Call to action``
- ``Footer/*/Help text``
- ``*/*/*/Verb, Short``
- ``Modal/Playlist/Button``
It's possible to nest multiple component parts to reach a higher level of detail. The component parts are then separated by a dot:
- ``Sidebar/Queue/Tab.Title``
- ``Content/*/Button.Title``
- ``Content/*/Table.Header``
- ``Footer/*/List item.Link``
- ``Content/*/Form.Help text``
Collecting translatable strings
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you want to ensure your translatable strings are correctly marked for translation,
you can try to extract them.
Extraction is done by calling ``yarn run i18n-extract``, which
will pull all the strings from source files and put them in a PO file.
will pull all the strings from source files and put them in a PO files.
You can then inspect the PO files to ensure everything is fine (but don't commit them, it's not needed).
Contributing to the API
-----------------------
...
...
front/src/components/audio/PlayButton.vue
View file @
70441f45
...
...
@@ -7,15 +7,23 @@
:disabled=
"!playable"
:class=
"buttonClasses.concat(['ui',
{loading: isLoading}, {'mini': discrete}, {disabled: !playable}])">
<i
:class=
"[playIconClass, 'icon']"
></i>
<template
v-if=
"!discrete && !iconOnly"
><slot><translate>
Play
</translate></slot></
template
>
<template
v-if=
"!discrete && !iconOnly"
><slot><translate
:v-context=
"'*/Queue/Button/Label/Short, Verb'"
>
Play
</translate></slot></
template
>
</button>
<div
v-if=
"!discrete && !iconOnly"
:class=
"['ui', {disabled: !playable}, 'floating', 'dropdown', {'icon': !dropdownOnly}, {'button': !dropdownOnly}]"
>
<i
:class=
"dropdownIconClasses.concat(['icon'])"
></i>
<div
class=
"menu"
>
<button
class=
"item basic"
ref=
"add"
data-ref=
"add"
:disabled=
"!playable"
@
click.stop.prevent=
"add"
:title=
"labels.addToQueue"
><i
class=
"plus icon"
></i><translate>
Add to queue
</translate></button>
<button
class=
"item basic"
ref=
"addNext"
data-ref=
"addNext"
:disabled=
"!playable"
@
click.stop.prevent=
"addNext()"
:title=
"labels.playNext"
><i
class=
"step forward icon"
></i><translate>
Play next
</translate></button>
<button
class=
"item basic"
ref=
"playNow"
data-ref=
"playNow"
:disabled=
"!playable"
@
click.stop.prevent=
"addNext(true)"
:title=
"labels.playNow"
><i
class=
"play icon"
></i><translate>
Play now
</translate></button>
<button
v-if=
"track"
class=
"item basic"
:disabled=
"!playable"
@
click.stop.prevent=
"$store.dispatch('radios/start', {type: 'similar', objectId: track.id})"
:title=
"labels.startRadio"
><i
class=
"feed icon"
></i><translate>
Start radio
</translate></button>
<button
class=
"item basic"
ref=
"add"
data-ref=
"add"
:disabled=
"!playable"
@
click.stop.prevent=
"add"
:title=
"labels.addToQueue"
>
<i
class=
"plus icon"
></i><translate
:v-context=
"'*/Queue/Dropdown/Button/Label/Short'"
>
Add to queue
</translate>
</button>
<button
class=
"item basic"
ref=
"addNext"
data-ref=
"addNext"
:disabled=
"!playable"
@
click.stop.prevent=
"addNext()"
:title=
"labels.playNext"
>
<i
class=
"step forward icon"
></i><translate
:v-context=
"'*/Queue/Dropdown/Button/Label/Short'"
>
Play next
</translate>
</button>
<button
class=
"item basic"
ref=
"playNow"
data-ref=
"playNow"
:disabled=
"!playable"
@
click.stop.prevent=
"addNext(true)"
:title=
"labels.playNow"
>
<i
class=
"play icon"
></i><translate
:v-context=
"'*/Queue/Dropdown/Button/Label/Short'"
>
Play now
</translate>
</button>
<button
v-if=
"track"
class=
"item basic"
:disabled=
"!playable"
@
click.stop.prevent=
"$store.dispatch('radios/start', {type: 'similar', objectId: track.id})"
:title=
"labels.startRadio"
>
<i
class=
"feed icon"
></i><translate
:v-context=
"'*/Queue/Dropdown/Button/Label/Short'"
>
Start radio
</translate>
</button>
</div>
</div>
</span>
...
...
@@ -61,18 +69,18 @@ export default {
computed
:
{
labels
()
{
return
{
playNow
:
this
.
$gettext
(
'
Play now
'
),
addToQueue
:
this
.
$gettext
(
'
Add to current queue
'
),
playNext
:
this
.
$gettext
(
'
Play next
'
),
startRadio
:
this
.
$gettext
(
'
Play similar songs
'
)
playNow
:
this
.
$
p
gettext
(
'
*/Queue/Dropdown/Button/Title
'
,
'
Play now
'
),
addToQueue
:
this
.
$
p
gettext
(
'
*/Queue/Dropdown/Button/Title
'
,
'
Add to current queue
'
),
playNext
:
this
.
$
p
gettext
(
'
*/Queue/Dropdown/Button/Title
'
,
'
Play next
'
),
startRadio
:
this
.
$
p
gettext
(
'
*/Queue/Dropdown/Button/Title
'
,
'
Play similar songs
'
)
}
},
title
()
{
if
(
this
.
playable
)
{
return
this
.
$gettext
(
'
Play...
'
)
return
this
.
$
p
gettext
(
'
*/Queue/Button/Title
'
,
'
Play...
'
)
}
else
{
if
(
this
.
track
)
{
return
this
.
$gettext
(
'
This track is not available in any library you have access to
'
)
return
this
.
$
p
gettext
(
'
*/Queue/Button/Title
'
,
'
This track is not available in any library you have access to
'
)
}
}
},
...
...
@@ -179,7 +187,7 @@ export default {
if
(
tracks
.
length
<
1
)
{
return
}
let
msg
=
this
.
$ngettext
(
'
%{ count } track was added to your queue
'
,
'
%{ count } tracks were added to your queue
'
,
tracks
.
length
)
let
msg
=
this
.
$n
p
gettext
(
'
*/Queue/Message
'
,
'
%{ count } track was added to your queue
'
,
'
%{ count } tracks were added to your queue
'
,
tracks
.
length
)
this
.
$store
.
commit
(
'
ui/addMessage
'
,
{
content
:
this
.
$gettextInterpolate
(
msg
,
{
count
:
tracks
.
length
}),
date
:
new
Date
()
...
...
front/src/components/auth/Signup.vue
View file @
70441f45
...
...
@@ -2,22 +2,22 @@
<main
class=
"main pusher"
v-title=
"labels.title"
>
<section
class=
"ui vertical stripe segment"
>
<div
class=
"ui small text container"
>
<h2><translate>
Create a funkwhale account
</translate></h2>
<h2><translate
:v-context=
"'Content/Signup/Header'"
>
Create a funkwhale account
</translate></h2>
<form
:class=
"['ui',
{'loading': isLoadingInstanceSetting}, 'form']"
@submit.prevent="submit()">
<p
class=
"ui message"
v-if=
"!$store.state.instance.settings.users.registration_enabled.value"
>
<translate>
Registration are closed on this instance, you will need an invitation code to signup.
</translate>
<translate
:v-context=
"'Content/Signup/Form/Message'"
>
Registration are closed on this instance, you will need an invitation code to signup.
</translate>
</p>
<div
v-if=
"errors.length > 0"
class=
"ui negative message"
>
<div
class=
"header"
><translate>
We cannot create your account
</translate></div>
<div
class=
"header"
><translate
:v-context=
"'Content/Signup/Form/Message'"
>
We cannot create your account
</translate></div>
<ul
class=
"list"
>
<li
v-for=
"error in errors"
>
{{
error
}}
</li>
</ul>
</div>
<div
class=
"field"
>
<label><translate>
Username
</translate></label>
<label><translate
:v-context=
"'Content/Signup/Form/Label'"
>
Username
</translate></label>
<input
ref=
"username"
name=
"username"
...
...
@@ -28,7 +28,7 @@
v-model=
"username"
>
</div>
<div
class=
"field"
>
<label><translate>
Email
</translate></label>
<label><translate
:v-context=
"'Content/Signup/Form/Label'"
>
Email
</translate></label>
<input
ref=
"email"
name=
"email"
...
...
@@ -38,11 +38,11 @@
v-model=
"email"
>
</div>
<div
class=
"field"
>
<label><translate>
Password
</translate></label>
<label><translate
:v-context=
"'Content/Signup/Form/Label'"
>
Password
</translate></label>
<password-input
v-model=
"password"
/>
</div>
<div
class=
"field"
v-if=
"!$store.state.instance.settings.users.registration_enabled.value"
>
<label><translate>
Invitation code
</translate></label>
<label><translate
:v-context=
"'Content/Signup/Form/Label'"
>
Invitation code
</translate></label>
<input
required
type=
"text"
...
...
@@ -51,7 +51,7 @@
v-model=
"invitation"
>
</div>
<button
:class=
"['ui', 'green',
{'loading': isLoading}, 'button']" type="submit">
<translate>
Create my account
</translate>
<translate
:v-context=
"'Content/Signup/Form/Button'"
>
Create my account
</translate>
</button>
</form>
</div>
...
...
@@ -94,12 +94,13 @@ export default {
},
computed
:
{
labels
()
{
let
title
=
this
.
$gettext
(
"
Sign Up
"
)
let
placeholder
=
this
.
$gettext
(
let
title
=
this
.
$pgettext
(
"
*/Signup/Title
"
,
"
Sign Up
"
)
let
placeholder
=
this
.
$pgettext
(
"
Content/Signup/Form/Placeholder
"
,
"
Enter your invitation code (case insensitive)
"
)
let
usernamePlaceholder
=
this
.
$gettext
(
"
Enter your username
"
)
let
emailPlaceholder
=
this
.
$gettext
(
"
Enter your email
"
)
let
usernamePlaceholder
=
this
.
$
p
gettext
(
"
Content/Signup/Form/Placeholder
"
,
"
Enter your username
"
)
let
emailPlaceholder
=
this
.
$
p
gettext
(
"
Content/Signup/Form/Placeholder
"
,
"
Enter your email
"
)
return
{
title
,
usernamePlaceholder
,
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment