diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 19f034b9f..35a0ebddf 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -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 ``yourstring`` 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 ```` tag:: + + + +Anything between the `` and `` 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 ```` 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: + +- ``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 ----------------------- diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue index 07cb1f585..425d0a1d7 100644 --- a/front/src/components/audio/PlayButton.vue +++ b/front/src/components/audio/PlayButton.vue @@ -7,15 +7,23 @@ :disabled="!playable" :class="buttonClasses.concat(['ui', {loading: isLoading}, {'mini': discrete}, {disabled: !playable}])"> - +
@@ -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.$pgettext('*/Queue/Dropdown/Button/Title', 'Play now'), + addToQueue: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Add to current queue'), + playNext: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play next'), + startRadio: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play similar songs') } }, title () { if (this.playable) { - return this.$gettext('Play...') + return this.$pgettext('*/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.$pgettext('*/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.$npgettext('*/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() diff --git a/front/src/components/auth/Signup.vue b/front/src/components/auth/Signup.vue index 815f0253a..685be288d 100644 --- a/front/src/components/auth/Signup.vue +++ b/front/src/components/auth/Signup.vue @@ -2,22 +2,22 @@
-

Create a funkwhale account

+

Create a funkwhale account

- Registration are closed on this instance, you will need an invitation code to signup. + Registration are closed on this instance, you will need an invitation code to signup.

-
We cannot create your account
+
We cannot create your account
  • {{ error }}
- +
- +
- +
- +
@@ -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.$pgettext("Content/Signup/Form/Placeholder", "Enter your username") + let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your email") return { title, usernamePlaceholder,